Merge branch 'develop' into feat/scylladb
This commit is contained in:
commit
43fb3375be
317 changed files with 6070 additions and 4826 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -44,14 +44,15 @@ api-docs.json
|
||||||
*.log
|
*.log
|
||||||
*.code-workspace
|
*.code-workspace
|
||||||
.DS_Store
|
.DS_Store
|
||||||
files
|
files/
|
||||||
ormconfig.json
|
ormconfig.json
|
||||||
packages/backend/assets/instance.css
|
packages/backend/assets/instance.css
|
||||||
packages/backend/assets/sounds/None.mp3
|
packages/backend/assets/sounds/None.mp3
|
||||||
packages/backend/assets/LICENSE
|
packages/backend/assets/LICENSE
|
||||||
|
|
||||||
!/packages/backend/queue/processors/db
|
!/packages/backend/queue/processors/db
|
||||||
!packages/backend/src/db
|
!/packages/backend/src/db
|
||||||
|
!/packages/backend/src/server/api/endpoints/drive/files
|
||||||
|
|
||||||
packages/megalodon/lib
|
packages/megalodon/lib
|
||||||
packages/megalodon/.idea
|
packages/megalodon/.idea
|
||||||
|
|
|
@ -16,7 +16,7 @@ stages:
|
||||||
|
|
||||||
testCommit:
|
testCommit:
|
||||||
stage: build
|
stage: build
|
||||||
image: node:alpine
|
image: node:latest
|
||||||
# Pick zero or more services to be used on all builds.
|
# Pick zero or more services to be used on all builds.
|
||||||
# Only needed when using a docker container to run your tests in.
|
# Only needed when using a docker container to run your tests in.
|
||||||
# Check out: https://docs.gitlab.com/ee/ci/services/index.html
|
# Check out: https://docs.gitlab.com/ee/ci/services/index.html
|
||||||
|
@ -30,7 +30,9 @@ testCommit:
|
||||||
# POSTGRES_PASSWORD: $POSTGRES_PASSWORD
|
# POSTGRES_PASSWORD: $POSTGRES_PASSWORD
|
||||||
# POSTGRES_HOST_AUTH_METHOD: trust
|
# POSTGRES_HOST_AUTH_METHOD: trust
|
||||||
script:
|
script:
|
||||||
- apk add --no-cache cargo python3 make g++
|
- apt-get update && apt-get install -y git wget curl build-essential python3
|
||||||
|
- curl https://sh.rustup.rs -sSf | sh -s -- -y
|
||||||
|
- source "$HOME/.cargo/env"
|
||||||
- cp .config/ci.yml .config/default.yml
|
- cp .config/ci.yml .config/default.yml
|
||||||
- corepack enable
|
- corepack enable
|
||||||
- corepack prepare pnpm@latest --activate
|
- corepack prepare pnpm@latest --activate
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
pipeline:
|
|
||||||
testCommit:
|
|
||||||
image: node:alpine
|
|
||||||
commands:
|
|
||||||
- apk add --no-cache cargo python3 make g++
|
|
||||||
- cp .config/ci.yml .config/default.yml
|
|
||||||
- corepack enable
|
|
||||||
- corepack prepare pnpm@latest --activate
|
|
||||||
- pnpm i --frozen-lockfile
|
|
||||||
- pnpm run build
|
|
||||||
- pnpm run migrate
|
|
||||||
|
|
||||||
services:
|
|
||||||
database:
|
|
||||||
image: postgres:15
|
|
||||||
environment:
|
|
||||||
- POSTGRES_PASSWORD=test
|
|
||||||
redis:
|
|
||||||
image: redis
|
|
||||||
|
|
||||||
branches:
|
|
||||||
include: [ main, beta, develop, feature/* ]
|
|
|
@ -1,15 +0,0 @@
|
||||||
pipeline:
|
|
||||||
publish-docker-latest:
|
|
||||||
image: plugins/kaniko
|
|
||||||
settings:
|
|
||||||
repo: thatonecalculator/firefish
|
|
||||||
tags: latest
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
username:
|
|
||||||
# Secret 'docker_username' needs to be set in the CI settings
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
# Secret 'docker_password' needs to be set in the CI settings
|
|
||||||
from_secret: docker_password
|
|
||||||
|
|
||||||
branches: main
|
|
|
@ -1,14 +0,0 @@
|
||||||
pipeline:
|
|
||||||
publish-docker-latest:
|
|
||||||
image: plugins/kaniko
|
|
||||||
settings:
|
|
||||||
repo: thatonecalculator/firefish
|
|
||||||
tags: rc
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
username:
|
|
||||||
# Secret 'docker_username' needs to be set in the CI settings
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
# Secret 'docker_password' needs to be set in the CI settings
|
|
||||||
from_secret: docker_password
|
|
||||||
branches: beta
|
|
|
@ -1,18 +0,0 @@
|
||||||
pipeline:
|
|
||||||
publish-docker-tag:
|
|
||||||
image: plugins/kaniko
|
|
||||||
settings:
|
|
||||||
repo: thatonecalculator/firefish
|
|
||||||
# Uses the tag from git for the container tag
|
|
||||||
tags: ${CI_COMMIT_TAG}
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
username:
|
|
||||||
# Secret 'docker_username' needs to be set in the CI settings
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
# Secret 'docker_password' needs to be set in the CI settings
|
|
||||||
from_secret: docker_password
|
|
||||||
when:
|
|
||||||
# Push new version when version tag is created
|
|
||||||
event: tag
|
|
||||||
tag: v*
|
|
|
@ -1,11 +0,0 @@
|
||||||
pipeline:
|
|
||||||
docker-build:
|
|
||||||
image: plugins/kaniko
|
|
||||||
settings:
|
|
||||||
repo: thatonecalculator/firefish
|
|
||||||
tags: test
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
no_push: true
|
|
||||||
|
|
||||||
branches:
|
|
||||||
include: [ main, develop, beta ]
|
|
15
Dockerfile
15
Dockerfile
|
@ -1,9 +1,14 @@
|
||||||
## Install dev and compilation dependencies, build files
|
## Install dev and compilation dependencies, build files
|
||||||
FROM alpine:3.18 as build
|
FROM node:latest as build
|
||||||
WORKDIR /firefish
|
WORKDIR /firefish
|
||||||
|
|
||||||
# Install compilation dependencies
|
# Install compilation dependencies
|
||||||
RUN apk add --no-cache --no-progress git alpine-sdk python3 nodejs-current npm rust cargo vips
|
RUN apt-get update && apt-get install -y libvips42 python3 git wget curl build-essential
|
||||||
|
RUN mkdir -m777 /opt/rust /opt/cargo
|
||||||
|
ENV RUSTUP_HOME=/opt/rust CARGO_HOME=/opt/cargo PATH=/opt/cargo/bin:$PATH
|
||||||
|
RUN wget --https-only --secure-protocol=TLSv1_2 -O- https://sh.rustup.rs | sh /dev/stdin -y
|
||||||
|
RUN printf '#!/bin/sh\nexport CARGO_HOME=/opt/cargo\nexec /bin/sh "$@"\n' >/usr/local/bin/sh
|
||||||
|
RUN chmod +x /usr/local/bin/sh
|
||||||
|
|
||||||
# Copy only the cargo dependency-related files first, to cache efficiently
|
# Copy only the cargo dependency-related files first, to cache efficiently
|
||||||
COPY packages/backend/native-utils/Cargo.toml packages/backend/native-utils/Cargo.toml
|
COPY packages/backend/native-utils/Cargo.toml packages/backend/native-utils/Cargo.toml
|
||||||
|
@ -28,7 +33,7 @@ COPY packages/backend/native-utils/package.json packages/backend/native-utils/pa
|
||||||
COPY packages/backend/native-utils/npm/linux-x64-musl/package.json packages/backend/native-utils/npm/linux-x64-musl/package.json
|
COPY packages/backend/native-utils/npm/linux-x64-musl/package.json packages/backend/native-utils/npm/linux-x64-musl/package.json
|
||||||
COPY packages/backend/native-utils/npm/linux-arm64-musl/package.json packages/backend/native-utils/npm/linux-arm64-musl/package.json
|
COPY packages/backend/native-utils/npm/linux-arm64-musl/package.json packages/backend/native-utils/npm/linux-arm64-musl/package.json
|
||||||
|
|
||||||
# Configure corepack and pnpm, and install dev mode dependencies for compilation
|
# Configure pnpm, and install dev mode dependencies for compilation
|
||||||
RUN corepack enable && corepack prepare pnpm@latest --activate && pnpm i --frozen-lockfile
|
RUN corepack enable && corepack prepare pnpm@latest --activate && pnpm i --frozen-lockfile
|
||||||
|
|
||||||
# Copy in the rest of the native-utils rust files
|
# Copy in the rest of the native-utils rust files
|
||||||
|
@ -45,11 +50,11 @@ RUN env NODE_ENV=production sh -c "pnpm run --filter '!native-utils' build && pn
|
||||||
RUN pnpm i --prod --frozen-lockfile
|
RUN pnpm i --prod --frozen-lockfile
|
||||||
|
|
||||||
## Runtime container
|
## Runtime container
|
||||||
FROM alpine:3.18
|
FROM node:latest
|
||||||
WORKDIR /firefish
|
WORKDIR /firefish
|
||||||
|
|
||||||
# Install runtime dependencies
|
# Install runtime dependencies
|
||||||
RUN apk add --no-cache --no-progress tini ffmpeg vips-dev zip unzip nodejs-current
|
RUN apt-get update && apt-get install -y libvips-dev zip unzip tini ffmpeg
|
||||||
|
|
||||||
COPY . ./
|
COPY . ./
|
||||||
|
|
||||||
|
|
|
@ -103,4 +103,4 @@ NODE_ENV=production pnpm run migrate
|
||||||
|
|
||||||
## Reverse
|
## Reverse
|
||||||
|
|
||||||
You ***cannot*** migrate back to Misskey from Firefish due to re-hashing passwords on signin with argon2. You can migrate from Calckey to FoundKey, although this is not recommended due to FoundKey being end-of-life, and may have some problems with alt-text.
|
You ***cannot*** migrate back to Misskey from Firefish due to re-hashing passwords on signin with argon2. You can migrate from Firefish to FoundKey, although this is not recommended due to FoundKey being end-of-life, and may have some problems with alt-text.
|
||||||
|
|
|
@ -97,7 +97,7 @@ enterListName: "Gib einen Namen für die Liste ein"
|
||||||
privacy: "Privatsphäre"
|
privacy: "Privatsphäre"
|
||||||
makeFollowManuallyApprove: "Folgeanfragen bedürfen der Genehmigung"
|
makeFollowManuallyApprove: "Folgeanfragen bedürfen der Genehmigung"
|
||||||
defaultNoteVisibility: "Standard-Sichtbarkeit"
|
defaultNoteVisibility: "Standard-Sichtbarkeit"
|
||||||
follow: "Folge ich"
|
follow: "Folgen"
|
||||||
followRequest: "Follow anfragen"
|
followRequest: "Follow anfragen"
|
||||||
followRequests: "Follow-Anfragen"
|
followRequests: "Follow-Anfragen"
|
||||||
unfollow: "Nicht mehr folgen"
|
unfollow: "Nicht mehr folgen"
|
||||||
|
@ -1106,6 +1106,7 @@ _aboutFirefish:
|
||||||
um bei der Deckung der Betriebskosten zu helfen.
|
um bei der Deckung der Betriebskosten zu helfen.
|
||||||
sponsors: Firefish-Sponsoren
|
sponsors: Firefish-Sponsoren
|
||||||
donateHost: Spende an {host}
|
donateHost: Spende an {host}
|
||||||
|
misskeyContributors: Misskey-Mitwirkende
|
||||||
_nsfw:
|
_nsfw:
|
||||||
respect: "Mit NSFW gekennzeichnete Mediendateien verbergen"
|
respect: "Mit NSFW gekennzeichnete Mediendateien verbergen"
|
||||||
ignore: "Mit NSFW gekennzeichnete Mediendateien nicht verbergen"
|
ignore: "Mit NSFW gekennzeichnete Mediendateien nicht verbergen"
|
||||||
|
@ -2201,3 +2202,5 @@ delete2faConfirm: Passkeys werden unwiderruflich von diesem Account gelöscht. F
|
||||||
deletePasskeysConfirm: Alle Passkeys und Security-Keys werden unwiderruflich von diesem
|
deletePasskeysConfirm: Alle Passkeys und Security-Keys werden unwiderruflich von diesem
|
||||||
Account gelöscht. Fortfahren?
|
Account gelöscht. Fortfahren?
|
||||||
inputNotMatch: Eingabe stimmt nicht überein
|
inputNotMatch: Eingabe stimmt nicht überein
|
||||||
|
addRe: Ein "re:" am Anfang des Kommentars hinzufügen, um einem Beitrag mit einer Inhaltswarnung
|
||||||
|
zu antworten
|
||||||
|
|
|
@ -337,7 +337,7 @@ emptyDrive: "Your Drive is empty"
|
||||||
emptyFolder: "This folder is empty"
|
emptyFolder: "This folder is empty"
|
||||||
unableToDelete: "Unable to delete"
|
unableToDelete: "Unable to delete"
|
||||||
inputNewFileName: "Enter a new filename"
|
inputNewFileName: "Enter a new filename"
|
||||||
inputNewDescription: "Enter new caption"
|
inputNewDescription: "Enter new description"
|
||||||
inputNewFolderName: "Enter a new folder name"
|
inputNewFolderName: "Enter a new folder name"
|
||||||
circularReferenceFolder: "The destination folder is a subfolder of the folder you
|
circularReferenceFolder: "The destination folder is a subfolder of the folder you
|
||||||
wish to move."
|
wish to move."
|
||||||
|
@ -634,8 +634,8 @@ disablePlayer: "Close video player"
|
||||||
expandTweet: "Expand tweet"
|
expandTweet: "Expand tweet"
|
||||||
themeEditor: "Theme editor"
|
themeEditor: "Theme editor"
|
||||||
description: "Description"
|
description: "Description"
|
||||||
describeFile: "Add caption"
|
describeFile: "Add description"
|
||||||
enterFileDescription: "Enter caption"
|
enterFileDescription: "Enter description"
|
||||||
author: "Author"
|
author: "Author"
|
||||||
leaveConfirm: "There are unsaved changes. Do you want to discard them?"
|
leaveConfirm: "There are unsaved changes. Do you want to discard them?"
|
||||||
manage: "Management"
|
manage: "Management"
|
||||||
|
@ -1054,7 +1054,7 @@ showUpdates: "Show a popup when Firefish updates"
|
||||||
recommendedInstances: "Recommended servers"
|
recommendedInstances: "Recommended servers"
|
||||||
recommendedInstancesDescription: "Recommended servers separated by line breaks to
|
recommendedInstancesDescription: "Recommended servers separated by line breaks to
|
||||||
appear in the recommended timeline."
|
appear in the recommended timeline."
|
||||||
caption: "Auto Caption"
|
caption: "Auto description"
|
||||||
splash: "Splash Screen"
|
splash: "Splash Screen"
|
||||||
updateAvailable: "There might be an update available!"
|
updateAvailable: "There might be an update available!"
|
||||||
swipeOnMobile: "Allow swiping between pages"
|
swipeOnMobile: "Allow swiping between pages"
|
||||||
|
@ -1135,6 +1135,10 @@ delete2faConfirm: "This will irreversibly delete 2FA on this account. Proceed?"
|
||||||
deletePasskeysConfirm: "This will irreversibly delete all passkeys and security keys on this account. Proceed?"
|
deletePasskeysConfirm: "This will irreversibly delete all passkeys and security keys on this account. Proceed?"
|
||||||
inputNotMatch: "Input does not match"
|
inputNotMatch: "Input does not match"
|
||||||
addRe: "Add \"re:\" at the beginning of comment in reply to a post with a content warning"
|
addRe: "Add \"re:\" at the beginning of comment in reply to a post with a content warning"
|
||||||
|
confirm: "Confirm"
|
||||||
|
importZip: "Import ZIP"
|
||||||
|
exportZip: "Export ZIP"
|
||||||
|
emojiPackCreator: "Emoji pack creator"
|
||||||
|
|
||||||
_sensitiveMediaDetection:
|
_sensitiveMediaDetection:
|
||||||
description: "Reduces the effort of server moderation through automatically recognizing
|
description: "Reduces the effort of server moderation through automatically recognizing
|
||||||
|
|
|
@ -572,8 +572,8 @@ updateRemoteUser: "Actualizar información de usuario remoto"
|
||||||
deleteAllFiles: "Borrar todos los archivos"
|
deleteAllFiles: "Borrar todos los archivos"
|
||||||
deleteAllFilesConfirm: "¿Desea borrar todos los archivos?"
|
deleteAllFilesConfirm: "¿Desea borrar todos los archivos?"
|
||||||
removeAllFollowing: "Retener todos los siguientes"
|
removeAllFollowing: "Retener todos los siguientes"
|
||||||
removeAllFollowingDescription: "Cancelar todos los siguientes del servidor {host}.
|
removeAllFollowingDescription: "Al ejecutar esto todas las cuentas de {host} dejarán
|
||||||
Ejecutar en caso de que esta instancia haya dejado de existir."
|
de seguirse. Por favor, ejecuta esto si el servidor ya no existe."
|
||||||
userSuspended: "Este usuario ha sido suspendido."
|
userSuspended: "Este usuario ha sido suspendido."
|
||||||
userSilenced: "Este usuario ha sido silenciado."
|
userSilenced: "Este usuario ha sido silenciado."
|
||||||
yourAccountSuspendedTitle: "Esta cuenta ha sido suspendida"
|
yourAccountSuspendedTitle: "Esta cuenta ha sido suspendida"
|
||||||
|
@ -838,7 +838,7 @@ gallery: "Galería"
|
||||||
recentPosts: "Posts recientes"
|
recentPosts: "Posts recientes"
|
||||||
popularPosts: "Más vistos"
|
popularPosts: "Más vistos"
|
||||||
shareWithNote: "Compartir con una publicación"
|
shareWithNote: "Compartir con una publicación"
|
||||||
ads: "Banners"
|
ads: "Banners de la comunidad"
|
||||||
expiration: "Termina el"
|
expiration: "Termina el"
|
||||||
memo: "Notas"
|
memo: "Notas"
|
||||||
priority: "Prioridad"
|
priority: "Prioridad"
|
||||||
|
@ -892,7 +892,7 @@ unmuteThread: "Mostrar hilo"
|
||||||
ffVisibility: "Visibilidad de seguidores y seguidos"
|
ffVisibility: "Visibilidad de seguidores y seguidos"
|
||||||
ffVisibilityDescription: "Puedes configurar quien puede ver a quienes sigues y quienes
|
ffVisibilityDescription: "Puedes configurar quien puede ver a quienes sigues y quienes
|
||||||
te siguen"
|
te siguen"
|
||||||
continueThread: "Ver la continuación del hilo"
|
continueThread: "Continuar hilo"
|
||||||
deleteAccountConfirm: "La cuenta será borrada irreversiblemente. ¿Está seguro?"
|
deleteAccountConfirm: "La cuenta será borrada irreversiblemente. ¿Está seguro?"
|
||||||
incorrectPassword: "La contraseña es incorrecta"
|
incorrectPassword: "La contraseña es incorrecta"
|
||||||
voteConfirm: "¿Confirma su voto a {choice}?"
|
voteConfirm: "¿Confirma su voto a {choice}?"
|
||||||
|
@ -906,12 +906,12 @@ overridedDeviceKind: "Tipo de dispositivo"
|
||||||
smartphone: "Teléfono smartphone"
|
smartphone: "Teléfono smartphone"
|
||||||
tablet: "Tablet"
|
tablet: "Tablet"
|
||||||
auto: "Automático"
|
auto: "Automático"
|
||||||
themeColor: "Color del tema"
|
themeColor: "Color de la marquesina del servidor"
|
||||||
size: "Tamaño"
|
size: "Tamaño"
|
||||||
numberOfColumn: "Cantidad de columnas"
|
numberOfColumn: "Cantidad de columnas"
|
||||||
searchByGoogle: "Buscar"
|
searchByGoogle: "Buscar"
|
||||||
instanceDefaultLightTheme: "Tema claro por defecto de la instancia"
|
instanceDefaultLightTheme: "Tema claro por defecto del servidor"
|
||||||
instanceDefaultDarkTheme: "Tema oscuro por defecto de la instancia"
|
instanceDefaultDarkTheme: "Tema oscuro por defecto del servidor"
|
||||||
instanceDefaultThemeDescription: "Ingrese el código del tema en formato objeto"
|
instanceDefaultThemeDescription: "Ingrese el código del tema en formato objeto"
|
||||||
mutePeriod: "Período de silenciamiento"
|
mutePeriod: "Período de silenciamiento"
|
||||||
indefinitely: "Sin límite de tiempo"
|
indefinitely: "Sin límite de tiempo"
|
||||||
|
@ -968,7 +968,7 @@ beta: "Beta"
|
||||||
enableAutoSensitive: "Marcar automáticamente contenido NSFW"
|
enableAutoSensitive: "Marcar automáticamente contenido NSFW"
|
||||||
enableAutoSensitiveDescription: "Permite la detección y marcado automático de contenido
|
enableAutoSensitiveDescription: "Permite la detección y marcado automático de contenido
|
||||||
NSFW usando 'Machine Learning' cuando sea posible. Incluso si esta opción está desactivada,
|
NSFW usando 'Machine Learning' cuando sea posible. Incluso si esta opción está desactivada,
|
||||||
puede ser activado para toda la instancia."
|
puede ser activado para todo el servidor."
|
||||||
activeEmailValidationDescription: "Habilita la validación estricta de direcciones
|
activeEmailValidationDescription: "Habilita la validación estricta de direcciones
|
||||||
de correo electrónico, lo cual incluye la revisión de direcciones desechables y
|
de correo electrónico, lo cual incluye la revisión de direcciones desechables y
|
||||||
si se puede comunicar con éstas. Cuando está deshabilitado, sólo el formato de la
|
si se puede comunicar con éstas. Cuando está deshabilitado, sólo el formato de la
|
||||||
|
@ -1085,6 +1085,7 @@ _aboutFirefish:
|
||||||
pleaseDonateToHost: También considera donar a tu propio servidor , {host}, para
|
pleaseDonateToHost: También considera donar a tu propio servidor , {host}, para
|
||||||
ayudar con los costos de operación.
|
ayudar con los costos de operación.
|
||||||
sponsors: Patrocinadores de Firefish
|
sponsors: Patrocinadores de Firefish
|
||||||
|
misskeyContributors: Contribuidores de Misskey
|
||||||
_nsfw:
|
_nsfw:
|
||||||
respect: "Ocultar medios NSFW"
|
respect: "Ocultar medios NSFW"
|
||||||
ignore: "No esconder medios NSFW "
|
ignore: "No esconder medios NSFW "
|
||||||
|
@ -1177,8 +1178,10 @@ _mfm:
|
||||||
fade: Fundido
|
fade: Fundido
|
||||||
advanced: MFM avanzado
|
advanced: MFM avanzado
|
||||||
play: Reproducir MFM
|
play: Reproducir MFM
|
||||||
foregroundDescription: Cambiar el color en primer plano del texto.
|
foregroundDescription: Cambiar el color del texto en primer plano.
|
||||||
background: Color de fondo
|
background: Color de fondo
|
||||||
|
positionDescription: Mueve el contenido en una cantidad especificada.
|
||||||
|
fadeDescription: Funde el contenido dentro y fuera.
|
||||||
_instanceTicker:
|
_instanceTicker:
|
||||||
none: "No mostrar"
|
none: "No mostrar"
|
||||||
remote: "Mostrar a usuarios remotos"
|
remote: "Mostrar a usuarios remotos"
|
||||||
|
@ -1197,7 +1200,7 @@ _channel:
|
||||||
owned: "Dueño"
|
owned: "Dueño"
|
||||||
following: "Siguiendo"
|
following: "Siguiendo"
|
||||||
usersCount: "{n} participantes"
|
usersCount: "{n} participantes"
|
||||||
notesCount: "{n} publicaciones"
|
notesCount: "{n} Publicaciones"
|
||||||
nameOnly: Nombre solamente
|
nameOnly: Nombre solamente
|
||||||
nameAndDescription: Nombre y descripción
|
nameAndDescription: Nombre y descripción
|
||||||
_menuDisplay:
|
_menuDisplay:
|
||||||
|
@ -1301,7 +1304,7 @@ _theme:
|
||||||
fgHighlighted: "Texto resaltado"
|
fgHighlighted: "Texto resaltado"
|
||||||
_sfx:
|
_sfx:
|
||||||
note: "Nueva publicación"
|
note: "Nueva publicación"
|
||||||
noteMy: "Nota (a mí mismo)"
|
noteMy: "Publicación propia"
|
||||||
notification: "Notificaciones"
|
notification: "Notificaciones"
|
||||||
chat: "Chat"
|
chat: "Chat"
|
||||||
chatBg: "Chat (Fondo)"
|
chatBg: "Chat (Fondo)"
|
||||||
|
@ -1310,11 +1313,11 @@ _sfx:
|
||||||
_ago:
|
_ago:
|
||||||
future: "Futuro"
|
future: "Futuro"
|
||||||
justNow: "Recién ahora"
|
justNow: "Recién ahora"
|
||||||
secondsAgo: "Hace {n} segundo(s)"
|
secondsAgo: "Hace {n} s"
|
||||||
minutesAgo: "Hace {n} minuto(s)"
|
minutesAgo: "Hace {n} m"
|
||||||
hoursAgo: "Hace {n} hora(s)"
|
hoursAgo: "Hace {n} hora(s)"
|
||||||
daysAgo: "Hace {n} día(s)"
|
daysAgo: "Hace {n} d"
|
||||||
weeksAgo: "Hace {n} semana(s)"
|
weeksAgo: "Hace {n} sem"
|
||||||
monthsAgo: "Hace {n} mes(es)"
|
monthsAgo: "Hace {n} mes(es)"
|
||||||
yearsAgo: "Hace {n} año(s)"
|
yearsAgo: "Hace {n} año(s)"
|
||||||
_time:
|
_time:
|
||||||
|
@ -1339,15 +1342,15 @@ _tutorial:
|
||||||
step5_1: "¡Líneas de tiempo, líneas de tiempo por todas partes!"
|
step5_1: "¡Líneas de tiempo, líneas de tiempo por todas partes!"
|
||||||
step5_2: "Tu servidor tiene {timelines} diferentes líneas de tiempo habilitadas."
|
step5_2: "Tu servidor tiene {timelines} diferentes líneas de tiempo habilitadas."
|
||||||
step5_3: "La línea de tiempo Inicio {icon} es donde puedes ver las publicaciones
|
step5_3: "La línea de tiempo Inicio {icon} es donde puedes ver las publicaciones
|
||||||
de tus seguidores."
|
de personas que sigues."
|
||||||
step5_4: "La línea de tiempo Local {icon} es donde puedes ver las publicaciones
|
step5_4: "La línea de tiempo Local {icon} es donde puedes ver las publicaciones
|
||||||
de todos los demás en esta instancia."
|
de todos los demás en este servidor."
|
||||||
step5_5: "La línea de tiempo {icon} recomendada es donde puedes ver las publicaciones
|
step5_5: "La línea de tiempo {icon} social es una combinación de las líneas de tiempo
|
||||||
de las instancias que los administradores recomiendan."
|
Inicio y Local."
|
||||||
step5_6: "La línea de tiempo Social {icon} es donde puedes ver las publicaciones
|
step5_6: "La línea de tiempo {icon} recomendada es donde puedes ver las publicaciones
|
||||||
de los amigos de tus seguidores."
|
de los servidores que los administradores recomiendan."
|
||||||
step5_7: "La línea de tiempo Global {icon} es donde puedes ver las publicaciones
|
step5_7: "La línea de tiempo {icon} global es donde puedes ver las publicaciones
|
||||||
de todas las demás instancias conectadas."
|
de todos los demás servidores a los cuales este servidor conecta."
|
||||||
step6_1: "Entonces, ¿qué es este lugar?"
|
step6_1: "Entonces, ¿qué es este lugar?"
|
||||||
step6_2: "Bueno, no sólo te has unido a Firefish. Te has unido a un portal del Fediverso,
|
step6_2: "Bueno, no sólo te has unido a Firefish. Te has unido a un portal del Fediverso,
|
||||||
una red interconectada de miles de servidores, llamada \"instancias\""
|
una red interconectada de miles de servidores, llamada \"instancias\""
|
||||||
|
@ -1368,6 +1371,26 @@ _2fa:
|
||||||
securityKeyInfo: "Se puede configurar el inicio de sesión usando una clave de seguridad
|
securityKeyInfo: "Se puede configurar el inicio de sesión usando una clave de seguridad
|
||||||
de hardware que soporte FIDO2 o con un certificado de huella digital o con un
|
de hardware que soporte FIDO2 o con un certificado de huella digital o con un
|
||||||
PIN"
|
PIN"
|
||||||
|
chromePasskeyNotSupported: Contraseñas de Chrome no están soportadas.
|
||||||
|
removeKeyConfirm: ¿Realmente deseas borrar la clave {name}?
|
||||||
|
step3Title: Ingresa un código de autorización
|
||||||
|
renewTOTP: Reconfigurar la aplicación autorizadora
|
||||||
|
whyTOTPOnlyRenew: La aplicación autorizadora no puede ser quitada mientras la clave
|
||||||
|
de seguridad siga registrada.
|
||||||
|
renewTOTPConfirm: Esto causará que los códigos de verificación de la aplicación
|
||||||
|
anterior dejen de funcionar
|
||||||
|
renewTOTPOk: Reconfigurar
|
||||||
|
securityKeyNotSupported: Tu navegador no soporta claves de seguridad.
|
||||||
|
step2Click: Presionar este código QR te permitirá registrar la autorización 2FA
|
||||||
|
a tu clave de seguridad o aplicación autorizadora.
|
||||||
|
registerTOTPBeforeKey: Por favor configura una aplicación autorizadora para registrar
|
||||||
|
una clave de seguridad o de paso.
|
||||||
|
securityKeyName: Ingresa el nombre de la clave
|
||||||
|
tapSecurityKey: Por favor, espera al navegador para registrar la clave de seguridad
|
||||||
|
o de paso
|
||||||
|
renewTOTPCancel: Cancelar
|
||||||
|
token: Token 2FA
|
||||||
|
removeKey: Quitar clave de seguridad
|
||||||
_permissions:
|
_permissions:
|
||||||
"read:account": "Ver información de la cuenta"
|
"read:account": "Ver información de la cuenta"
|
||||||
"write:account": "Editar información de la cuenta"
|
"write:account": "Editar información de la cuenta"
|
||||||
|
@ -1383,7 +1406,7 @@ _permissions:
|
||||||
"write:messaging": "Administrar chat"
|
"write:messaging": "Administrar chat"
|
||||||
"read:mutes": "Ver usuarios silenciados"
|
"read:mutes": "Ver usuarios silenciados"
|
||||||
"write:mutes": "Administrar usuarios silenciados"
|
"write:mutes": "Administrar usuarios silenciados"
|
||||||
"write:notes": "Crear/borrar notas"
|
"write:notes": "Crear o borrar publicaciones"
|
||||||
"read:notifications": "Ver notificaciones"
|
"read:notifications": "Ver notificaciones"
|
||||||
"write:notifications": "Administrar notificaciones"
|
"write:notifications": "Administrar notificaciones"
|
||||||
"read:reactions": "Ver reacciones"
|
"read:reactions": "Ver reacciones"
|
||||||
|
@ -1405,16 +1428,19 @@ _auth:
|
||||||
shareAccess: "¿Desea permitir el acceso a la cuenta \"{name}\"?"
|
shareAccess: "¿Desea permitir el acceso a la cuenta \"{name}\"?"
|
||||||
shareAccessAsk: "¿Está seguro de que desea autorizar esta aplicación para acceder
|
shareAccessAsk: "¿Está seguro de que desea autorizar esta aplicación para acceder
|
||||||
a su cuenta?"
|
a su cuenta?"
|
||||||
permissionAsk: "Esta aplicación requiere los siguientes permisos"
|
permissionAsk: "Esta aplicación requiere los siguientes permisos:"
|
||||||
pleaseGoBack: "Por favor, vuelve a la aplicación"
|
pleaseGoBack: "Por favor, vuelve a la aplicación"
|
||||||
callback: "Volviendo a la aplicación"
|
callback: "Volviendo a la aplicación"
|
||||||
denied: "Acceso denegado"
|
denied: "Acceso denegado"
|
||||||
|
copyAsk: 'Por favor, pega el siguiente código de autorización en la aplicación:'
|
||||||
|
allPermissions: Acceso completo
|
||||||
_antennaSources:
|
_antennaSources:
|
||||||
all: "Todas las notas"
|
all: "Todas las publicaciones"
|
||||||
homeTimeline: "Notas de los usuarios que sigues"
|
homeTimeline: "Publicaciones de los usuarios que sigues"
|
||||||
users: "Notas de un usuario o varios"
|
users: "Publicaciones de usuarios específicos"
|
||||||
userList: "Notas de los usuarios de una lista"
|
userList: "Publicaciones de una lista de usuarios específica"
|
||||||
userGroup: "Notas de los usuarios de una grupo"
|
userGroup: "Publicaciones de usuarios de un grupo"
|
||||||
|
instances: Publicaciones de todos los usuarios en un servidor
|
||||||
_weekday:
|
_weekday:
|
||||||
sunday: "Domingo"
|
sunday: "Domingo"
|
||||||
monday: "Lunes"
|
monday: "Lunes"
|
||||||
|
@ -1431,24 +1457,28 @@ _widgets:
|
||||||
trends: "Tendencias"
|
trends: "Tendencias"
|
||||||
clock: "Reloj"
|
clock: "Reloj"
|
||||||
rss: "Lector RSS"
|
rss: "Lector RSS"
|
||||||
rssTicker: "Ticker-RSS"
|
rssTicker: "Marquesina RSS"
|
||||||
activity: "Actividad"
|
activity: "Actividad"
|
||||||
photos: "Fotos"
|
photos: "Fotos"
|
||||||
digitalClock: "Reloj digital"
|
digitalClock: "Reloj digital"
|
||||||
unixClock: "Reloj UNIX"
|
unixClock: "Reloj UNIX"
|
||||||
federation: "Federación"
|
federation: "Federación"
|
||||||
instanceCloud: "Nube de palabras de la instancia"
|
instanceCloud: "Nube de servidores"
|
||||||
postForm: "Formulario"
|
postForm: "Formulario"
|
||||||
slideshow: "Diapositivas"
|
slideshow: "Diapositivas"
|
||||||
button: "Botón"
|
button: "Botón"
|
||||||
onlineUsers: "Usuarios en linea"
|
onlineUsers: "Usuarios en línea"
|
||||||
jobQueue: "Cola de trabajos"
|
jobQueue: "Cola de trabajos"
|
||||||
serverMetric: "Estadísticas del servidor"
|
serverMetric: "Estadísticas del servidor"
|
||||||
aiscript: "Consola de AiScript"
|
aiscript: "Consola de AiScript"
|
||||||
aichan: "indigo"
|
aichan: "indigo"
|
||||||
userList: Lista Usuarios
|
userList: Lista de usuarios
|
||||||
_userList:
|
_userList:
|
||||||
chooseList: Seleccione una lista
|
chooseList: Seleccione una lista
|
||||||
|
serverInfo: Información del servidor
|
||||||
|
meiliStatus: Estado del servidor
|
||||||
|
meiliSize: Tamaño del índice
|
||||||
|
meiliIndexCount: Publicaciones indizadas
|
||||||
_cw:
|
_cw:
|
||||||
hide: "Ocultar"
|
hide: "Ocultar"
|
||||||
show: "Ver más"
|
show: "Ver más"
|
||||||
|
@ -1478,18 +1508,18 @@ _poll:
|
||||||
remainingSeconds: "Quedan {s} segundos para que finalice"
|
remainingSeconds: "Quedan {s} segundos para que finalice"
|
||||||
_visibility:
|
_visibility:
|
||||||
public: "Público"
|
public: "Público"
|
||||||
publicDescription: "Visible para todos los usuarios"
|
publicDescription: "Tu publicación será visible en todas las líneas de tiempo"
|
||||||
home: "Inicio"
|
home: "Sin listar (Inicio)"
|
||||||
homeDescription: "Visible sólo en la linea de tiempo de inicio"
|
homeDescription: "Visible sólo en la linea de tiempo de inicio"
|
||||||
followers: "Seguidores"
|
followers: "Seguidores"
|
||||||
followersDescription: "Visible sólo para tus seguidores"
|
followersDescription: "Hacer sólo visible sólo para tus seguidores y usuarios mencionados"
|
||||||
specified: "Mensaje directo"
|
specified: "Mensaje directo"
|
||||||
specifiedDescription: "Visible sólo para los usuarios elegidos"
|
specifiedDescription: "Visible sólo para los usuarios elegidos"
|
||||||
localOnly: "Solo local"
|
localOnly: "Solo local"
|
||||||
localOnlyDescription: "Oculto para usuarios remotos"
|
localOnlyDescription: "Oculto para usuarios remotos"
|
||||||
_postForm:
|
_postForm:
|
||||||
replyPlaceholder: "Responder a esta nota"
|
replyPlaceholder: "Responder a esta publicación..."
|
||||||
quotePlaceholder: "Citar esta nota"
|
quotePlaceholder: "Citar esta publicación..."
|
||||||
channelPlaceholder: "Postear en el canal"
|
channelPlaceholder: "Postear en el canal"
|
||||||
_placeholders:
|
_placeholders:
|
||||||
a: "¿Qué haces?"
|
a: "¿Qué haces?"
|
||||||
|
@ -1514,7 +1544,7 @@ _profile:
|
||||||
locationDescription: Si ingresas tu ciudad primero, el tiempo local tuyo será visible
|
locationDescription: Si ingresas tu ciudad primero, el tiempo local tuyo será visible
|
||||||
para otros usuarios.
|
para otros usuarios.
|
||||||
_exportOrImport:
|
_exportOrImport:
|
||||||
allNotes: "Todas las notas"
|
allNotes: "Todas las publicaciones"
|
||||||
followingList: "Siguiendo"
|
followingList: "Siguiendo"
|
||||||
muteList: "Silenciados"
|
muteList: "Silenciados"
|
||||||
blockingList: "Bloqueados"
|
blockingList: "Bloqueados"
|
||||||
|
@ -1527,10 +1557,10 @@ _charts:
|
||||||
usersIncDec: "Variación de usuarios"
|
usersIncDec: "Variación de usuarios"
|
||||||
usersTotal: "Total de usuarios"
|
usersTotal: "Total de usuarios"
|
||||||
activeUsers: "Cantidad de usuarios activos"
|
activeUsers: "Cantidad de usuarios activos"
|
||||||
notesIncDec: "Variación de la cantidad de notas"
|
notesIncDec: "Diferencia en la cantidad de publicaciones"
|
||||||
localNotesIncDec: "Variación de la cantidad de notas locales"
|
localNotesIncDec: "Diferencia en la cantidad de publicaciones locales"
|
||||||
remoteNotesIncDec: "Variación de la cantidad de notas remotas"
|
remoteNotesIncDec: "Diferencia en el número de publicaciones remotas"
|
||||||
notesTotal: "Total de notas"
|
notesTotal: "Total de publicaciones"
|
||||||
filesIncDec: "Variación de cantidad de archivos"
|
filesIncDec: "Variación de cantidad de archivos"
|
||||||
filesTotal: "Total de archivos"
|
filesTotal: "Total de archivos"
|
||||||
storageUsageIncDec: "Variación de uso del almacenamiento"
|
storageUsageIncDec: "Variación de uso del almacenamiento"
|
||||||
|
@ -1539,8 +1569,8 @@ _instanceCharts:
|
||||||
requests: "Pedidos"
|
requests: "Pedidos"
|
||||||
users: "Variación de usuarios"
|
users: "Variación de usuarios"
|
||||||
usersTotal: "Total acumulado de usuarios"
|
usersTotal: "Total acumulado de usuarios"
|
||||||
notes: "Variación de la cantidad de notas"
|
notes: "Diferencia en el número de publicaciones"
|
||||||
notesTotal: "Total acumulado de la cantidad de notas"
|
notesTotal: "Total acumulado de publicaciones"
|
||||||
ff: "Variación de cantidad de seguidos/seguidores"
|
ff: "Variación de cantidad de seguidos/seguidores"
|
||||||
ffTotal: "Total acumulado de cantidad de seguidos/seguidores"
|
ffTotal: "Total acumulado de cantidad de seguidos/seguidores"
|
||||||
cacheSize: "Variación del tamaño de la caché"
|
cacheSize: "Variación del tamaño de la caché"
|
||||||
|
@ -1627,10 +1657,10 @@ _pages:
|
||||||
id: "Lienzo ID"
|
id: "Lienzo ID"
|
||||||
width: "Ancho"
|
width: "Ancho"
|
||||||
height: "Altura"
|
height: "Altura"
|
||||||
note: "Nota embebida"
|
note: "Publicación incrustada"
|
||||||
_note:
|
_note:
|
||||||
id: "Id de la nota"
|
id: "ID de la publicación"
|
||||||
idDescription: "Pega la URL de la nota para configurarla"
|
idDescription: "Puedes también pegar la URL de la publicación aquí."
|
||||||
detailed: "Ver Detalles"
|
detailed: "Ver Detalles"
|
||||||
switch: "Interruptor"
|
switch: "Interruptor"
|
||||||
_switch:
|
_switch:
|
||||||
|
@ -1853,7 +1883,7 @@ _notification:
|
||||||
youGotMention: "Mención de {name}"
|
youGotMention: "Mención de {name}"
|
||||||
youGotReply: "Respuesta de {name}"
|
youGotReply: "Respuesta de {name}"
|
||||||
youGotQuote: "Citado por {name}"
|
youGotQuote: "Citado por {name}"
|
||||||
youRenoted: "Renotado por {name}"
|
youRenoted: "Impulsado por {name}"
|
||||||
youGotPoll: "Encuestado por {name}"
|
youGotPoll: "Encuestado por {name}"
|
||||||
youGotMessagingMessageFromUser: "{name} comenzó un chat contigo"
|
youGotMessagingMessageFromUser: "{name} comenzó un chat contigo"
|
||||||
youGotMessagingMessageFromGroup: "Tienes un chat de {name}"
|
youGotMessagingMessageFromGroup: "Tienes un chat de {name}"
|
||||||
|
@ -1868,7 +1898,7 @@ _notification:
|
||||||
follow: "Siguiendo"
|
follow: "Siguiendo"
|
||||||
mention: "Menciones"
|
mention: "Menciones"
|
||||||
reply: "Respuestas"
|
reply: "Respuestas"
|
||||||
renote: "Renotar"
|
renote: "Impulsos"
|
||||||
quote: "Citar"
|
quote: "Citar"
|
||||||
reaction: "Reacción"
|
reaction: "Reacción"
|
||||||
pollVote: "Votado en la encuesta"
|
pollVote: "Votado en la encuesta"
|
||||||
|
@ -1880,7 +1910,10 @@ _notification:
|
||||||
_actions:
|
_actions:
|
||||||
followBack: "Te sigue de vuelta"
|
followBack: "Te sigue de vuelta"
|
||||||
reply: "Responder"
|
reply: "Responder"
|
||||||
renote: "Renotar"
|
renote: "Impulsos"
|
||||||
|
renoted: impulsó tu publicación
|
||||||
|
reacted: reaccionó a tu publicación
|
||||||
|
voted: votó en tu encuesta
|
||||||
_deck:
|
_deck:
|
||||||
alwaysShowMainColumn: "Siempre mostrar la columna principal"
|
alwaysShowMainColumn: "Siempre mostrar la columna principal"
|
||||||
columnAlign: "Alinear columnas"
|
columnAlign: "Alinear columnas"
|
||||||
|
@ -1892,9 +1925,9 @@ _deck:
|
||||||
swapDown: "Mover abajo"
|
swapDown: "Mover abajo"
|
||||||
stackLeft: "Apilar a la izquierda"
|
stackLeft: "Apilar a la izquierda"
|
||||||
popRight: "Sacar a la derecha"
|
popRight: "Sacar a la derecha"
|
||||||
profile: "Perfil"
|
profile: "Espacio de trabajo"
|
||||||
newProfile: "Nuevo perfil"
|
newProfile: "Nuevo espacio de trabajo"
|
||||||
deleteProfile: "Eliminar perfil"
|
deleteProfile: "Eliminar espacio de trabajo"
|
||||||
introduction: "¡Crea la interfaz perfecta para tí organizando las columnas libremente!"
|
introduction: "¡Crea la interfaz perfecta para tí organizando las columnas libremente!"
|
||||||
introduction2: "Presiona en la + de la derecha de la pantalla para añadir nuevas
|
introduction2: "Presiona en la + de la derecha de la pantalla para añadir nuevas
|
||||||
columnas donde quieras."
|
columnas donde quieras."
|
||||||
|
@ -1905,10 +1938,13 @@ _deck:
|
||||||
widgets: "Widgets"
|
widgets: "Widgets"
|
||||||
notifications: "Notificaciones"
|
notifications: "Notificaciones"
|
||||||
tl: "Linea de tiempo"
|
tl: "Linea de tiempo"
|
||||||
antenna: "Antenas"
|
antenna: "Antena"
|
||||||
list: "Listas"
|
list: "Listas"
|
||||||
mentions: "Menciones"
|
mentions: "Menciones"
|
||||||
direct: "Mensaje directo"
|
direct: "Mensajes directos"
|
||||||
|
channel: Canal
|
||||||
|
renameProfile: Renombrar espacio de trabajo
|
||||||
|
nameAlreadyExists: Este nombre de espacio de trabajo ya existe.
|
||||||
manageGroups: Administrar grupos
|
manageGroups: Administrar grupos
|
||||||
replayTutorial: Repetir Tutorial
|
replayTutorial: Repetir Tutorial
|
||||||
privateMode: Modo privado
|
privateMode: Modo privado
|
||||||
|
@ -1923,11 +1959,13 @@ breakFollowConfirm: ¿Estás seguro de que quieres eliminar el seguidor?
|
||||||
subscribePushNotification: Habilitar notificaciones
|
subscribePushNotification: Habilitar notificaciones
|
||||||
unsubscribePushNotification: Desactivar notificaciones
|
unsubscribePushNotification: Desactivar notificaciones
|
||||||
pushNotificationAlreadySubscribed: Las notificaciones ya están activados
|
pushNotificationAlreadySubscribed: Las notificaciones ya están activados
|
||||||
pushNotificationNotSupported: Su navegador o instancia no admite notificaciones
|
pushNotificationNotSupported: Su navegador o servidor no admite notificaciones
|
||||||
moveAccount: ¡Mover cuenta!
|
moveAccount: ¡Mover cuenta!
|
||||||
moveFrom: Mueve a esta cuenta de una cuenta antigua
|
moveFrom: Mueve a esta cuenta de una cuenta antigua
|
||||||
moveFromLabel: 'La cuenta que estás moviendo de:'
|
moveFromLabel: 'La cuenta que estás moviendo de:'
|
||||||
moveAccountDescription: ''
|
moveAccountDescription: 'Este proceso es irreversible. Asegúrate de configurar un
|
||||||
|
alias para ésta cuenta en tu cuenta nueva antes de comenzar. Por favor, ingresa
|
||||||
|
la etiqueta de la cuenta en el formato siguiente: @persona@servidor.tld'
|
||||||
license: Licencia
|
license: Licencia
|
||||||
noThankYou: No gracias
|
noThankYou: No gracias
|
||||||
userSaysSomethingReason: '{name} dijo {reason}'
|
userSaysSomethingReason: '{name} dijo {reason}'
|
||||||
|
@ -1938,7 +1976,7 @@ caption: Auto Subtítulos
|
||||||
showAds: Mostrar banners
|
showAds: Mostrar banners
|
||||||
enterSendsMessage: Presione "RETORNO" en los mensajes para enviar el mensaje (para
|
enterSendsMessage: Presione "RETORNO" en los mensajes para enviar el mensaje (para
|
||||||
apagarlo es Ctrl + RETORNO)
|
apagarlo es Ctrl + RETORNO)
|
||||||
recommendedInstances: Instancias Recomendadas
|
recommendedInstances: Servidores recomendados
|
||||||
instanceSecurity: Seguridad del servidor
|
instanceSecurity: Seguridad del servidor
|
||||||
seperateRenoteQuote: Separar botones de Impulsar y Citar
|
seperateRenoteQuote: Separar botones de Impulsar y Citar
|
||||||
_messaging:
|
_messaging:
|
||||||
|
@ -1995,6 +2033,10 @@ _filters:
|
||||||
fromUser: Del usuario
|
fromUser: Del usuario
|
||||||
fromDomain: Desde el dominio
|
fromDomain: Desde el dominio
|
||||||
notesAfter: Publicaciones posteriores
|
notesAfter: Publicaciones posteriores
|
||||||
|
followingOnly: Sólo seguidos
|
||||||
|
withFile: Con archivo
|
||||||
|
followersOnly: Sólo seguidores
|
||||||
|
notesBefore: Publicaciones anteriores
|
||||||
userSaysSomethingReasonReply: '{name} respondió a una publicación que contiene {reason}'
|
userSaysSomethingReasonReply: '{name} respondió a una publicación que contiene {reason}'
|
||||||
userSaysSomethingReasonQuote: '{name} citó una publicación que contiene {reason}'
|
userSaysSomethingReasonQuote: '{name} citó una publicación que contiene {reason}'
|
||||||
privateModeInfo: Al activar, solo servidores autorizados podrán federar con tu servidor.
|
privateModeInfo: Al activar, solo servidores autorizados podrán federar con tu servidor.
|
||||||
|
@ -2022,3 +2064,103 @@ remindMeLater: Recordar nuevamente
|
||||||
removeQuote: Eliminar cita
|
removeQuote: Eliminar cita
|
||||||
removeRecipient: Eliminar destinatario
|
removeRecipient: Eliminar destinatario
|
||||||
removeMember: Eliminar miembro
|
removeMember: Eliminar miembro
|
||||||
|
_skinTones:
|
||||||
|
light: Claro
|
||||||
|
dark: Obscuro
|
||||||
|
yellow: Amarillo
|
||||||
|
medium: Medio
|
||||||
|
mediumLight: Claro medio
|
||||||
|
mediumDark: Obscuro medio
|
||||||
|
secureModeInfo: Al pedir a otros servidores, no mandar si no hay prueba de confianza.
|
||||||
|
enableIdenticonGeneration: Activar la generación de Identicon
|
||||||
|
sendModMail: Enviar aviso de moderación
|
||||||
|
reactionPickerSkinTone: Tono de piel preferido en emojis
|
||||||
|
_dialog:
|
||||||
|
charactersExceeded: '¡Límite de caracteres excedido! Actual: {current}/Límite: {max}'
|
||||||
|
charactersBelow: '¡Caracteres insuficientes! Actual: {current}/Límite: {min}'
|
||||||
|
expandOnNoteClick: Abrir publicación al hacer click
|
||||||
|
_experiments:
|
||||||
|
enablePostImports: Habilitar importación de publicaciones
|
||||||
|
title: Experimentos
|
||||||
|
postImportsCaption: Permite a los usuarios importar sus publicaciones desde Firefish,
|
||||||
|
Misskey, Mastodon, Akkoma y Pleroma. Puede causar una bajada en el rendimiento
|
||||||
|
del servidor si la cola de trabajos está atascada.
|
||||||
|
showUpdates: Mostrar una notificación emergente cuando Firefish se actualice
|
||||||
|
recommendedInstancesDescription: Servidores recomendados separador por saltos de línea
|
||||||
|
para que aparezcan el la línea de tiempo recomendados.
|
||||||
|
swipeOnMobile: Permitir el pase entre páginas
|
||||||
|
addRe: Añadir "re:" al comienzo del comentario en una respuesta a una publicación
|
||||||
|
sin aviso de contenido
|
||||||
|
showAdminUpdates: Avisar si hay una nueva versión de Firefish disponible (sólo adminsitrador)
|
||||||
|
_feeds:
|
||||||
|
rss: RSS
|
||||||
|
copyFeed: Copiar feed
|
||||||
|
atom: Atom
|
||||||
|
jsonFeed: Feed JSON
|
||||||
|
secureMode: Modo seguro (Recuperación Autorizada)
|
||||||
|
splash: Pantalla de bienvenida
|
||||||
|
moveToLabel: 'Cuenta a la cual estás migrando:'
|
||||||
|
alt: ALT
|
||||||
|
video: Video
|
||||||
|
audio: Audio
|
||||||
|
swipeOnDesktop: Permitir el pase de páginas del estilo móvil en el escritorio
|
||||||
|
enableCustomKaTeXMacro: Habilitar macros KaTeX personalizadas
|
||||||
|
noteId: ID de publicación
|
||||||
|
preventAiLearning: Prevenir el uso por parte de bots de IA
|
||||||
|
isLocked: Esta cuenta requiere aprobación de seguidores
|
||||||
|
origin: Origen
|
||||||
|
newer: reciente
|
||||||
|
older: antiguo
|
||||||
|
objectStorageS3ForcePathStyle: Usar URL de punto final basada en rutas
|
||||||
|
objectStorageS3ForcePathStyleDesc: Activa esto para construir puntos finales URL en
|
||||||
|
el formato 's3.amazonaws.com/<bucket>/' en lugar de '<bucket>.s3.amazonaws.com'.
|
||||||
|
customSplashIconsDescription: URL para los iconos de la pantalla de bienvenida separadas
|
||||||
|
por saltos de línea para ser mostrados al azar cada vez que el usuario carga/recarga
|
||||||
|
la página. Por favor, asegúrate que las imágenes sean URL estáticas, preferentemente
|
||||||
|
a 192x192.
|
||||||
|
updateAvailable: ¡Quizá hay una actualización disponible!
|
||||||
|
moveTo: Mover la cuenta actual a una cuenta nueva
|
||||||
|
moveFromDescription: 'Esto pondrá un alias en tu cuenta antigua para así poder migrar
|
||||||
|
desde esa cuenta a la nueva. Haz esto ANTES de migrar tu cuenta antigua. Por favor,
|
||||||
|
ingresa la etiqueta de la cuenta con el formato siguiente: @persona@servidor.tld'
|
||||||
|
defaultReaction: Emoji por defecto para reaccionar a las publicaciones entrantes y
|
||||||
|
salientes
|
||||||
|
indexFromDescription: Deja en blanco para indizar todas las publicaciones
|
||||||
|
deletePasskeys: Borrar claves de paso
|
||||||
|
deletePasskeysConfirm: Esto borrará irreversiblemente todas las claves de paso y de
|
||||||
|
seguridad en esta cuenta, ¿Proceder?
|
||||||
|
inputNotMatch: Las entradas no coinciden
|
||||||
|
indexFrom: Indizar desde la ID de la publicación en adelante
|
||||||
|
indexPosts: Indizar publicaciones
|
||||||
|
isModerator: Moderador
|
||||||
|
isAdmin: Administrador
|
||||||
|
isPatron: Mecenas de Firefish
|
||||||
|
logoImageUrl: URL de la imagen del logotipo
|
||||||
|
xl: XL
|
||||||
|
migrationConfirm: "¿Estás absolutamente seguro de que quieres migrar a tu cuenta a
|
||||||
|
{account}? Una vez hecho esto, no podrás revertir el cambio, ni tampoco usar tu
|
||||||
|
cuenta normalmente.\nTambién, asegúrate de que has configurado ésta cuenta como
|
||||||
|
la cuenta desde la cual estás migrando."
|
||||||
|
indexNotice: Indizando ahora. Esto puede llevar bastante tiempo, por favor, no reinicies
|
||||||
|
el servidor por lo menos hasta dentro de una hora.
|
||||||
|
customKaTeXMacro: Macros KaTeX personalizadas
|
||||||
|
customKaTeXMacroDescription: '¡Configura macros para escribir expresiones matemáticas
|
||||||
|
fácilmente! La notación es conforme la las definiciones de comandos LaTeX y puede
|
||||||
|
ser escrita como \nuevocomando{\ nombre}{contenido} o \nuevocomando{\nombre}[número
|
||||||
|
de argumentos]{contenido}. Por ejemplo, \nuevocomando{\añadir}[2]{#1 + #2} expanderá
|
||||||
|
\añadir{3}{foo} a 3 + foo. Las llaves que contienen al nombre de la macro serán
|
||||||
|
cambiadas a paréntesis o corchetes. Esto afecta a los corchetes usados para argumentos.
|
||||||
|
Una (y sólo una) macro puede ser definida por línea, y no podrás saltar la línea
|
||||||
|
en medio de la definición. Líneas erróneas son ignoradas. Sólo funciones de sustitución
|
||||||
|
simple son soportadas; sintaxis avanzada, como ramificación condicional no puede
|
||||||
|
ser usada aquí.'
|
||||||
|
signupsDisabled: Los registros en esta instancia están desactivados, pero, ¡siempre
|
||||||
|
podrás registrarte en otro servidor! Si tienes un código de invitación para este
|
||||||
|
servidor, por favor, rellena el campo siguiente.
|
||||||
|
preventAiLearningDescription: Pedir a los modelos de IA no analizar el contenido de
|
||||||
|
publicas, como publicaciones e imágenes.
|
||||||
|
noGraze: Por favor desactiva la extensión de navegador "Graze for Mastodon" ya que
|
||||||
|
interfiere con Firefish.
|
||||||
|
silencedWarning: Esta página se muestra debido a que estos usuarios son de servidores
|
||||||
|
que tu administrador ha silenciado, ya que son presumiblemente fuente de spam.
|
||||||
|
isBot: Esta cuenta es un bot
|
||||||
|
|
|
@ -53,7 +53,7 @@ sendMessage: "Envoyer un message"
|
||||||
copyUsername: "Copier le nom d’utilisateur·rice"
|
copyUsername: "Copier le nom d’utilisateur·rice"
|
||||||
searchUser: "Chercher un·e utilisateur·rice"
|
searchUser: "Chercher un·e utilisateur·rice"
|
||||||
reply: "Répondre"
|
reply: "Répondre"
|
||||||
loadMore: "Afficher plus"
|
loadMore: "Charger plus"
|
||||||
showMore: "Afficher plus"
|
showMore: "Afficher plus"
|
||||||
showLess: "Fermer"
|
showLess: "Fermer"
|
||||||
youGotNewFollower: "Vous suit"
|
youGotNewFollower: "Vous suit"
|
||||||
|
@ -245,7 +245,7 @@ currentPassword: "Mot de passe actuel"
|
||||||
newPassword: "Nouveau mot de passe"
|
newPassword: "Nouveau mot de passe"
|
||||||
newPasswordRetype: "Répéter le nouveau mot de passe"
|
newPasswordRetype: "Répéter le nouveau mot de passe"
|
||||||
attachFile: "Joindre un fichier"
|
attachFile: "Joindre un fichier"
|
||||||
more: "Plus"
|
more: "Plus !"
|
||||||
featured: "Tendances"
|
featured: "Tendances"
|
||||||
usernameOrUserId: "Nom d’utilisateur·rice ou ID utilisateur"
|
usernameOrUserId: "Nom d’utilisateur·rice ou ID utilisateur"
|
||||||
noSuchUser: "Utilisateur·rice non trouvé·e"
|
noSuchUser: "Utilisateur·rice non trouvé·e"
|
||||||
|
@ -516,11 +516,11 @@ showFeaturedNotesInTimeline: "Afficher les publications des Tendances dans le fi
|
||||||
d'actualité"
|
d'actualité"
|
||||||
objectStorage: "Stockage d'objets"
|
objectStorage: "Stockage d'objets"
|
||||||
useObjectStorage: "Utiliser le stockage d'objets"
|
useObjectStorage: "Utiliser le stockage d'objets"
|
||||||
objectStorageBaseUrl: "Base URL"
|
objectStorageBaseUrl: "URL racine"
|
||||||
objectStorageBaseUrlDesc: "Préfixe d’URL utilisé pour construire l’URL vers le référencement
|
objectStorageBaseUrlDesc: "Préfixe d’URL utilisé pour construire l’URL vers le référencement
|
||||||
d’objet (média). Spécifiez son URL si vous utilisez un CDN ou un proxy, sinon spécifiez
|
d’objet (média). Spécifiez son URL si vous utilisez un CDN ou un proxy, sinon spécifiez
|
||||||
l’adresse accessible au public selon le guide de service que vous allez utiliser.\n
|
l’adresse accessible au public selon le guide de service que vous allez utiliser.\n
|
||||||
Ex: 'https://<bucket>.s3.amazonaws.com' pour AWS S3 et 'https://storage.googleapis.com/<bucket>'
|
Ex : 'https://<bucket>.s3.amazonaws.com' pour AWS S3 et 'https://storage.googleapis.com/<bucket>'
|
||||||
pour GCS."
|
pour GCS."
|
||||||
objectStorageBucket: "Bucket"
|
objectStorageBucket: "Bucket"
|
||||||
objectStorageBucketDesc: "Veuillez spécifier le nom du compartiment utilisé sur le
|
objectStorageBucketDesc: "Veuillez spécifier le nom du compartiment utilisé sur le
|
||||||
|
@ -591,7 +591,7 @@ divider: "Séparateur"
|
||||||
addItem: "Ajouter un élément"
|
addItem: "Ajouter un élément"
|
||||||
relays: "Relais"
|
relays: "Relais"
|
||||||
addRelay: "Ajouter un relais"
|
addRelay: "Ajouter un relais"
|
||||||
inboxUrl: "Inbox URL"
|
inboxUrl: "URL de boîte de récéption"
|
||||||
addedRelays: "Relais ajoutés"
|
addedRelays: "Relais ajoutés"
|
||||||
serviceworkerInfo: "Devrait être activé pour les notifications push."
|
serviceworkerInfo: "Devrait être activé pour les notifications push."
|
||||||
deletedNote: "Publication supprimée"
|
deletedNote: "Publication supprimée"
|
||||||
|
@ -839,7 +839,7 @@ gallery: "Galerie"
|
||||||
recentPosts: "Publications récentes"
|
recentPosts: "Publications récentes"
|
||||||
popularPosts: "Publications populaires"
|
popularPosts: "Publications populaires"
|
||||||
shareWithNote: "Partager dans une publication"
|
shareWithNote: "Partager dans une publication"
|
||||||
ads: "Bannière communautaire"
|
ads: "Bannières communautaires"
|
||||||
expiration: "Échéance"
|
expiration: "Échéance"
|
||||||
memo: "Pense-bête"
|
memo: "Pense-bête"
|
||||||
priority: "Priorité"
|
priority: "Priorité"
|
||||||
|
@ -987,7 +987,7 @@ _plugin:
|
||||||
_registry:
|
_registry:
|
||||||
scope: "Portée"
|
scope: "Portée"
|
||||||
key: "Clé"
|
key: "Clé"
|
||||||
keys: "Clé "
|
keys: "Clés"
|
||||||
domain: "Domaine"
|
domain: "Domaine"
|
||||||
createKey: "Créer une clé"
|
createKey: "Créer une clé"
|
||||||
_aboutFirefish:
|
_aboutFirefish:
|
||||||
|
@ -1009,6 +1009,7 @@ _aboutFirefish:
|
||||||
donateHost: Faire un don à {host}
|
donateHost: Faire un don à {host}
|
||||||
patronsList: Listé chronologiquement, pas par taille de donation. Faite un don avec
|
patronsList: Listé chronologiquement, pas par taille de donation. Faite un don avec
|
||||||
le lien ci-dessus pour avoir votre nom affiché ici !
|
le lien ci-dessus pour avoir votre nom affiché ici !
|
||||||
|
misskeyContributors: Contributeurs Misskey
|
||||||
_nsfw:
|
_nsfw:
|
||||||
respect: "Cacher les médias marqués comme contenu sensible"
|
respect: "Cacher les médias marqués comme contenu sensible"
|
||||||
ignore: "Afficher les médias sensibles"
|
ignore: "Afficher les médias sensibles"
|
||||||
|
@ -1020,7 +1021,7 @@ _mfm:
|
||||||
dummy: "La Fédiverse s'agrandit avec Firefish"
|
dummy: "La Fédiverse s'agrandit avec Firefish"
|
||||||
mention: "Mentionner"
|
mention: "Mentionner"
|
||||||
mentionDescription: "Vous pouvez afficher un utilisateur spécifique en indiquant
|
mentionDescription: "Vous pouvez afficher un utilisateur spécifique en indiquant
|
||||||
une arobase suivie d'un nom d'utilisateur"
|
le symbole d'arobase (@) suivie d'un nom d'utilisateur."
|
||||||
hashtag: "Hashtags"
|
hashtag: "Hashtags"
|
||||||
hashtagDescription: "Vous pouvez afficher les hashtags en utilisant un croisillon
|
hashtagDescription: "Vous pouvez afficher les hashtags en utilisant un croisillon
|
||||||
et du texte."
|
et du texte."
|
||||||
|
@ -1033,7 +1034,7 @@ _mfm:
|
||||||
small: "Diminuer l'emphase"
|
small: "Diminuer l'emphase"
|
||||||
smallDescription: "Le contenu peut être affiché en petit et fin."
|
smallDescription: "Le contenu peut être affiché en petit et fin."
|
||||||
center: "Centrer"
|
center: "Centrer"
|
||||||
centerDescription: "Le contenu peut être centré"
|
centerDescription: "Centre le contenu sur la page."
|
||||||
inlineCode: "Code (inline)"
|
inlineCode: "Code (inline)"
|
||||||
inlineCodeDescription: "Affiche la coloration syntaxique des lignes de code."
|
inlineCodeDescription: "Affiche la coloration syntaxique des lignes de code."
|
||||||
blockCode: "Bloc de code"
|
blockCode: "Bloc de code"
|
||||||
|
@ -1175,11 +1176,11 @@ _theme:
|
||||||
argument: "Argument"
|
argument: "Argument"
|
||||||
basedProp: "Nom de la propriété référencée"
|
basedProp: "Nom de la propriété référencée"
|
||||||
alpha: "Transparence"
|
alpha: "Transparence"
|
||||||
darken: "Sombre"
|
darken: "Assombrir"
|
||||||
lighten: "Clair"
|
lighten: "Clair"
|
||||||
inputConstantName: "Insérez un nom de constante"
|
inputConstantName: "Insérez un nom de constante"
|
||||||
importInfo: "Vous pouvez importer un thème vers l’éditeur de thèmes en saisissant
|
importInfo: "Vous pouvez importer un thème vers l’éditeur de thèmes en saisissant
|
||||||
son code ici."
|
son code ici"
|
||||||
deleteConstantConfirm: "Êtes-vous sûr·e de vouloir supprimer la constante {const}
|
deleteConstantConfirm: "Êtes-vous sûr·e de vouloir supprimer la constante {const}
|
||||||
?"
|
?"
|
||||||
keys:
|
keys:
|
||||||
|
@ -1251,9 +1252,9 @@ _time:
|
||||||
day: "j"
|
day: "j"
|
||||||
_tutorial:
|
_tutorial:
|
||||||
title: "Comment utiliser Firefish"
|
title: "Comment utiliser Firefish"
|
||||||
step1_1: "Bienvenue!"
|
step1_1: "Bienvenue !"
|
||||||
step1_2: "On va vous installer. Vous serez opérationnel en un rien de temps"
|
step1_2: "On va vous installer. Vous serez opérationnel en un rien de temps !"
|
||||||
step2_1: "Tout d'abord, remplissez votre profil"
|
step2_1: "Tout d'abord, remplissez votre profil."
|
||||||
step2_2: "En fournissant quelques informations sur qui vous êtes, il sera plus facile
|
step2_2: "En fournissant quelques informations sur qui vous êtes, il sera plus facile
|
||||||
pour les autres de savoir s'ils veulent voir vos publcations ou vous suivre."
|
pour les autres de savoir s'ils veulent voir vos publcations ou vous suivre."
|
||||||
step3_1: "Maintenant il est temps de suivre des gens !"
|
step3_1: "Maintenant il est temps de suivre des gens !"
|
||||||
|
@ -1264,7 +1265,7 @@ _tutorial:
|
||||||
step4_2: "Pour votre première publication, certaines personnes aiment faire une
|
step4_2: "Pour votre première publication, certaines personnes aiment faire une
|
||||||
{introduction} ou un simple 'Bonjour tout le monde !'"
|
{introduction} ou un simple 'Bonjour tout le monde !'"
|
||||||
step5_1: "Des fils, des fils d’actualité partout !"
|
step5_1: "Des fils, des fils d’actualité partout !"
|
||||||
step5_2: "Votre serveur a {timelines} fils différents disponibles !"
|
step5_2: "Votre serveur a {timelines} fils différents activés."
|
||||||
step5_3: "Le fil {icon} Principal est l'endroit où vous pouvez voir les publications
|
step5_3: "Le fil {icon} Principal est l'endroit où vous pouvez voir les publications
|
||||||
de vos abonnements."
|
de vos abonnements."
|
||||||
step5_4: "La fil {icon} Local est l'endroit où vous pouvez voir les publications
|
step5_4: "La fil {icon} Local est l'endroit où vous pouvez voir les publications
|
||||||
|
@ -1411,7 +1412,7 @@ _widgets:
|
||||||
rssTicker: Bandeau RSS
|
rssTicker: Bandeau RSS
|
||||||
_cw:
|
_cw:
|
||||||
hide: "Masquer"
|
hide: "Masquer"
|
||||||
show: "Afficher plus …"
|
show: "Afficher contenu"
|
||||||
chars: "{count} caractères"
|
chars: "{count} caractères"
|
||||||
files: "{count} fichiers"
|
files: "{count} fichiers"
|
||||||
_poll:
|
_poll:
|
||||||
|
@ -1421,8 +1422,8 @@ _poll:
|
||||||
canMultipleVote: "Autoriser le multi-choix"
|
canMultipleVote: "Autoriser le multi-choix"
|
||||||
expiration: "Fin du sondage"
|
expiration: "Fin du sondage"
|
||||||
infinite: "Illimité"
|
infinite: "Illimité"
|
||||||
at: "Choisir une date"
|
at: "Expire le..."
|
||||||
after: "Choisir la durée"
|
after: "Expire après..."
|
||||||
deadlineDate: "Date de fin"
|
deadlineDate: "Date de fin"
|
||||||
deadlineTime: "Heure de fin"
|
deadlineTime: "Heure de fin"
|
||||||
duration: "Durée"
|
duration: "Durée"
|
||||||
|
@ -1502,7 +1503,7 @@ _instanceCharts:
|
||||||
usersTotal: "Total cumulé du nombre d'utilisateur·rice·s"
|
usersTotal: "Total cumulé du nombre d'utilisateur·rice·s"
|
||||||
notes: "Variation du nombre de publications"
|
notes: "Variation du nombre de publications"
|
||||||
notesTotal: "Nombre total cumulé des publications"
|
notesTotal: "Nombre total cumulé des publications"
|
||||||
ff: "Variation des abonné·e·s / abonnements"
|
ff: "Variation des abonnements / abonné·e·s "
|
||||||
ffTotal: "Total cumulé du nombre d'abonné·e·s / abonnements"
|
ffTotal: "Total cumulé du nombre d'abonné·e·s / abonnements"
|
||||||
cacheSize: "Variation de la taille du cache"
|
cacheSize: "Variation de la taille du cache"
|
||||||
cacheSizeTotal: "Total cumulé de la taille du cache"
|
cacheSizeTotal: "Total cumulé de la taille du cache"
|
||||||
|
@ -1811,7 +1812,7 @@ _relayStatus:
|
||||||
accepted: "Accepté"
|
accepted: "Accepté"
|
||||||
rejected: "Refusée"
|
rejected: "Refusée"
|
||||||
_notification:
|
_notification:
|
||||||
fileUploaded: "Le fichier a été téléversé !"
|
fileUploaded: "Le fichier a été téléversé"
|
||||||
youGotMention: "{name} vous a mentionné"
|
youGotMention: "{name} vous a mentionné"
|
||||||
youGotReply: "Réponse de {name}"
|
youGotReply: "Réponse de {name}"
|
||||||
youGotQuote: "Cité·e par {name}"
|
youGotQuote: "Cité·e par {name}"
|
||||||
|
@ -1992,7 +1993,7 @@ type: Type
|
||||||
speed: Vitesse
|
speed: Vitesse
|
||||||
slow: Lent
|
slow: Lent
|
||||||
move: Déplacer
|
move: Déplacer
|
||||||
showAds: Afficher les bannières communautaire/publicités
|
showAds: Afficher les bannières communautaire (publicités)
|
||||||
enterSendsMessage: Appuyer sur Entrée pendant la rédaction pour envoyer le message
|
enterSendsMessage: Appuyer sur Entrée pendant la rédaction pour envoyer le message
|
||||||
(sinon Ctrl+Entrée)
|
(sinon Ctrl+Entrée)
|
||||||
allowedInstancesDescription: Noms des serveurs autorisés pour la fédération, chacun
|
allowedInstancesDescription: Noms des serveurs autorisés pour la fédération, chacun
|
||||||
|
@ -2070,14 +2071,14 @@ customKaTeXMacro: Macros KaTeX personnalisées
|
||||||
enableCustomKaTeXMacro: Activer les macros KaTeX personnalisées
|
enableCustomKaTeXMacro: Activer les macros KaTeX personnalisées
|
||||||
noteId: ID des publications
|
noteId: ID des publications
|
||||||
customKaTeXMacroDescription: "Définissez des macros pour écrire des expressions mathématiques
|
customKaTeXMacroDescription: "Définissez des macros pour écrire des expressions mathématiques
|
||||||
simplement ! La notation se conforme aux définitions de commandes LaTeX et s'écrit
|
simplement ! La notation se conforme aux définitions de commandes LaTeX et s'écrit
|
||||||
\\newcommand{\\·name}{content} ou \\newcommand{\\name}[number of arguments]{content}.
|
\\newcommand{\\·name}{content} ou \\newcommand{\\name}[number of arguments]{content}.
|
||||||
Par exemple, \\newcommand{\\add}[2]{#1 + #2} étendra \\add{3}{foo} en 3 + foo. Les
|
Par exemple, \\newcommand{\\add}[2]{#1 + #2} étendra \\add{3}{foo} en 3 + foo. Les
|
||||||
accolades entourant le nom de la macro peuvent être changés pour des parenthèses
|
accolades entourant le nom de la macro peuvent être changés pour des parenthèses
|
||||||
ou des crochets. Cela affectera les types de parenthèses utilisées pour les arguments.
|
ou des crochets. Cela affectera les types de parenthèses utilisées pour les arguments.
|
||||||
Une (et une seule) macro peut être définie par ligne, et vous ne pouvez pas couper
|
Une (et une seule) macro peut être définie par ligne, et vous ne pouvez pas couper
|
||||||
la ligne au milieu d'une définition. Les lignes invalides sont simplement ignorées.
|
la ligne au milieu d'une définition. Les lignes invalides sont simplement ignorées.
|
||||||
Seulement de simples fonctions de substitution de chaines sont supportées; la syntaxe
|
Seulement de simples fonctions de substitution de chaines sont supportées ; la syntaxe
|
||||||
avancée, telle que la ramification conditionnelle, ne peut pas être utilisée ici."
|
avancée, telle que la ramification conditionnelle, ne peut pas être utilisée ici."
|
||||||
enableRecommendedTimeline: Activer le fil recommandé
|
enableRecommendedTimeline: Activer le fil recommandé
|
||||||
silenceThisInstance: Masquer ce serveur
|
silenceThisInstance: Masquer ce serveur
|
||||||
|
@ -2196,10 +2197,12 @@ _skinTones:
|
||||||
objectStorageS3ForcePathStyle: Utiliser des URL d'endpoints basées sur le chemin
|
objectStorageS3ForcePathStyle: Utiliser des URL d'endpoints basées sur le chemin
|
||||||
objectStorageS3ForcePathStyleDesc: Activez cette option pour construire les URL d'endpoints
|
objectStorageS3ForcePathStyleDesc: Activez cette option pour construire les URL d'endpoints
|
||||||
au format 's3.amazonaws.com/<bucket>/' au lieu de '<bucket>.s3.amazonaws.com'.
|
au format 's3.amazonaws.com/<bucket>/' au lieu de '<bucket>.s3.amazonaws.com'.
|
||||||
delete2fa: Supprimer 2FA
|
delete2fa: Désativer A2F
|
||||||
deletePasskeys: Supprimer les clés d'accès
|
deletePasskeys: Supprimer les clés d'accès
|
||||||
delete2faConfirm: Cela supprimera de manière irréversible la double authentification
|
delete2faConfirm: Cela supprimera de manière irréversible la double authentification
|
||||||
sur ce compte. Souhaitez-vous continuer ?
|
sur ce compte. Souhaitez-vous continuer ?
|
||||||
inputNotMatch: L'entrée ne correspond pas
|
inputNotMatch: L'entrée ne correspond pas
|
||||||
deletePasskeysConfirm: Cela supprimera de manière irréversible toutes les clés d'accès
|
deletePasskeysConfirm: Cela supprimera de manière irréversible toutes les clés d'accès
|
||||||
et les clés de sécurité sur ce compte. Souhaitez-vous continuer ?
|
et les clés de sécurité sur ce compte. Souhaitez-vous continuer ?
|
||||||
|
addRe: Ajouter "re:" au début d’un avertissement de contenu (CW) en réponse à une
|
||||||
|
publication avec un avertissement de contenu
|
||||||
|
|
|
@ -114,3 +114,607 @@ mention: Omtale
|
||||||
mentions: Omtaler
|
mentions: Omtaler
|
||||||
edited: Redigert {date} {time}
|
edited: Redigert {date} {time}
|
||||||
cw: Innholdsadvarsel
|
cw: Innholdsadvarsel
|
||||||
|
suspendConfirm: Er du sikker på at du vil suspendere denne kontoen?
|
||||||
|
recipient: Mottaker(e)
|
||||||
|
annotation: Kommentarer
|
||||||
|
registeredAt: Registrert
|
||||||
|
federation: Føderering
|
||||||
|
blockThisInstance: Blokker denne tjeneren
|
||||||
|
cpuAndMemory: CPU og minne
|
||||||
|
silencedInstances: Stumme tjenere
|
||||||
|
network: Nettverk
|
||||||
|
disk: Lagring
|
||||||
|
instanceInfo: Tjenerinformasjon
|
||||||
|
blockedInstancesDescription: Liste over maskinnavn på tjenere du vil blokkere. Blokkerte
|
||||||
|
tjenere vi ikke lenger kunne kommunisere med denne tjeneren.
|
||||||
|
blockedUsers: Blokkerte brukere
|
||||||
|
mutedUsers: Stumme brukere
|
||||||
|
editProfile: Rediger profil
|
||||||
|
done: Ferdig
|
||||||
|
exploreUsersCount: Det er {count} brukere
|
||||||
|
moderation: Moderering
|
||||||
|
securityKey: Sikkerhetsnøkkel
|
||||||
|
registerSecurityKey: Registerer en sikkerhetsnøkkel
|
||||||
|
recentlyDiscoveredUsers: Nylig oppdagede brukere
|
||||||
|
nUsersMentioned: Nevnt av {n} brukere
|
||||||
|
cacheClear: Slett mellomlager
|
||||||
|
unblock: Avblokker
|
||||||
|
suspend: Suspender
|
||||||
|
instanceFollowing: Følger på tjener
|
||||||
|
instanceFollowers: Følgere fra tjener
|
||||||
|
imageUrl: Bilde-URL
|
||||||
|
remove: Slett
|
||||||
|
keepOriginalUploading: Behold opprinnelig bilde
|
||||||
|
images: Bilder
|
||||||
|
birthday: Fødselsdag
|
||||||
|
yearsOld: '{age} år gammel'
|
||||||
|
renameFolder: Gi katalogen nytt navn
|
||||||
|
remoteUserCaution: Informasjon fra eksterne brukere kan være ufullstendig.
|
||||||
|
activity: Aktivitet
|
||||||
|
drive: Disk
|
||||||
|
renameFile: Omdøp fil
|
||||||
|
folderName: Katalognavn
|
||||||
|
createFolder: Opprett katalog
|
||||||
|
inputNewDescription: Oppgi ny bildetekst
|
||||||
|
inputNewFolderName: Oppgi nytt katalognavn
|
||||||
|
copyUrl: Kopier URL
|
||||||
|
hcaptchaSiteKey: hCaptcha-nøkkel for nettstedet
|
||||||
|
hcaptchaSecretKey: Hemmelig hCaptcha-nøkkel
|
||||||
|
recaptchaSiteKey: Nettstednøkkel til reCAPTCHA
|
||||||
|
recaptchaSecretKey: Hemmelig nøkkel til reCAPTCHA
|
||||||
|
unsilenceConfirm: Er du sikker på at du vil omgjøre stummingen av denne brukeren?
|
||||||
|
popularUsers: Populære brukere
|
||||||
|
moderator: Moderator
|
||||||
|
groupName: Gruppenavn
|
||||||
|
transfer: Overfør
|
||||||
|
preferencesBackups: Foretrukne sikkerhetskopier
|
||||||
|
edit: Rediger
|
||||||
|
emailServer: Eposttjener
|
||||||
|
testEmail: Test epost-utsending
|
||||||
|
notificationSettingDesc: Velg typen av varlinger som skal vises.
|
||||||
|
useGlobalSetting: Bruk globale innstillinger
|
||||||
|
useGlobalSettingDesc: Hvis dette er slått på vil varslingsinnstillingene til kontoen
|
||||||
|
bli brukt. Om den er slått av kan du bruke individuell konfigurasjon.
|
||||||
|
attachCancel: Slett vedegg
|
||||||
|
markAsSensitive: Merk som sensitivt innhold
|
||||||
|
renoteMute: Stum fremhevinger
|
||||||
|
renoteUnmute: Vis boosts fra bruker
|
||||||
|
addEmoji: Legg til
|
||||||
|
settingGuide: Foreslåtte innstillinger
|
||||||
|
cacheRemoteFilesDescription: Når denne innstillingen er avslått vil eksterne filer
|
||||||
|
lastes direkte fra andre tjerene. Å slå dette av vil føre til mindre bruk av lagringsplass,
|
||||||
|
men vil øke nettverkstrafikken fordi miniatyrbilder ikke vil bli generert.
|
||||||
|
unsuspend: Fjern suspendering
|
||||||
|
selectInstance: Velg en tjener
|
||||||
|
flagShowTimelineReplies: Vis svar i tidslinje
|
||||||
|
latestRequestSentAt: Siste forespørsel sendt
|
||||||
|
latestRequestReceivedAt: Siste forespørsel mottatt
|
||||||
|
latestStatus: Siste status
|
||||||
|
storageUsage: Lagringsplass brukt
|
||||||
|
charts: Grafer
|
||||||
|
perHour: Per time
|
||||||
|
perDay: Per dag
|
||||||
|
stopActivityDelivery: Stopp sending av aktiviteter
|
||||||
|
jobQueue: Jobbkø
|
||||||
|
clearQueue: Tøm kø
|
||||||
|
muteAndBlock: Stumming og blokkering
|
||||||
|
noInstances: Ingen tjenere
|
||||||
|
noteDeleteConfirm: Er du sikker på at du vil slette denne posten?
|
||||||
|
pinLimitExceeded: Du kan ikke feste flere poster
|
||||||
|
subscribing: Abonnerer
|
||||||
|
publishing: Publisering
|
||||||
|
usernameOrUserId: Brukernavn eller brukeridentifikator
|
||||||
|
removed: Slettet
|
||||||
|
removeAreYouSure: Er du sikker på at du ønsker å slette "{x}"?
|
||||||
|
deleteAreYouSure: Er du sikker på at du vil slette "{x}"?
|
||||||
|
resetAreYouSure: Sikker på at du ønsker å nullstille?
|
||||||
|
messaging: Chat
|
||||||
|
uploadFromUrlMayTakeTime: Det kan ta litt tid før opplastingen er ferdig.
|
||||||
|
manageGroups: Administrer grupper
|
||||||
|
tos: Vilkår for bruk
|
||||||
|
registeredDate: Ble med
|
||||||
|
location: Lokasjon
|
||||||
|
darkThemes: Mørke tema
|
||||||
|
whenServerDisconnected: Ved bortfall av nettverksforbindelse mot tjeneren
|
||||||
|
unwatch: Slutt å følge med på
|
||||||
|
accept: Godkjenn
|
||||||
|
reject: Avslå
|
||||||
|
normal: Normal
|
||||||
|
instanceName: Tjenernavn
|
||||||
|
instanceDescription: Tjenerbeskrivelse
|
||||||
|
maintainerName: Administrator
|
||||||
|
maintainerEmail: Administrator-epost
|
||||||
|
monthX: '{month}'
|
||||||
|
connectService: Koble til
|
||||||
|
disconnectService: Koble fra
|
||||||
|
enableLocalTimeline: Aktiver lokal tidslinje
|
||||||
|
enableRegistration: Tillat registrering av nye brukere
|
||||||
|
invite: Inviter
|
||||||
|
driveCapacityPerLocalAccount: Lagring pr lokale bruker
|
||||||
|
driveCapacityPerRemoteAccount: Lagring pr eksterne bruker
|
||||||
|
inMb: I megabyte
|
||||||
|
iconUrl: Ikon-URL
|
||||||
|
pinnedUsers: Festede brukere
|
||||||
|
pinnedPagesDescription: Legg inn stien (en per linje) til sidene du ønsker å vise
|
||||||
|
på topp for denne tjeneren.
|
||||||
|
antennaKeywords: Nøkkelord å lytte etter
|
||||||
|
antennaKeywordsDescription: Skill ordene med mellomrom for logisk "OG" og med linjeskift
|
||||||
|
for logisk "ELLER".
|
||||||
|
withReplies: Ta med svar
|
||||||
|
connectedTo: Følgende konto(er) er sammenkoblet
|
||||||
|
withFiles: Inkluder filer
|
||||||
|
lastUsed: Sist brukt
|
||||||
|
unregister: Avregistrer
|
||||||
|
passwordLessLogin: Passordløs innlogging
|
||||||
|
share: Del
|
||||||
|
markAsReadAllNotifications: Marker alle varsler som lest
|
||||||
|
markAsReadAllTalkMessages: Marker alle meldinger som lest
|
||||||
|
inputMessageHere: Skriv melding her
|
||||||
|
close: Lukk
|
||||||
|
groups: Grupper
|
||||||
|
quoteAttached: Siter
|
||||||
|
noMessagesYet: Ingen meldinger ennå
|
||||||
|
newMessageExists: Det er nye meldinger
|
||||||
|
invitations: Invitasjoner
|
||||||
|
usernameInvalidFormat: Du kan bruke store og små bokstaver, tall og understrek.
|
||||||
|
weakPassword: Svakt passord
|
||||||
|
normalPassword: Normalt passord
|
||||||
|
doing: Prosesserer...
|
||||||
|
category: Kategori
|
||||||
|
tags: Nøkkelord
|
||||||
|
docSource: Kilden til dette dokumentet
|
||||||
|
createAccount: Opprett konto
|
||||||
|
existingAccount: Eksisterende konto
|
||||||
|
regenerate: Regenerer
|
||||||
|
fontSize: Skriftstørrelse
|
||||||
|
objectStorageRegionDesc: Spesifiser en region som f.eks. 'xx-east-1'. Hvis tjenesten
|
||||||
|
ikke skiller mellom regioner kan du la feltet stå blankt eller skrive 'us-east-1'.
|
||||||
|
yourAccountSuspendedTitle: Denne kontoen er suspendert
|
||||||
|
themeEditor: Tema-editor
|
||||||
|
emptyToDisableSmtpAuth: La brukernavn og passord stå tomme for å ikke autentisere
|
||||||
|
mot SMTP-tjeneren
|
||||||
|
pinnedNote: Festet post
|
||||||
|
cacheRemoteFiles: Mellomlagre eksterne filer
|
||||||
|
setWallpaper: Set bakgrunn
|
||||||
|
noSuchUser: Finner ikke brukeren
|
||||||
|
lookup: Slå opp
|
||||||
|
saved: Lagret
|
||||||
|
enableGlobalTimeline: Aktiver global tidslinje
|
||||||
|
enableRecommendedTimeline: Aktiver foreslått tidslinje
|
||||||
|
pinnedClipId: Festede bokmerker
|
||||||
|
recaptcha: reCAPTCHA
|
||||||
|
notesAndReplies: Poster og svar
|
||||||
|
recentlyUpdatedUsers: Nylig aktive brukere
|
||||||
|
menu: Meny
|
||||||
|
silenceThisInstance: Stum denne tjeneren
|
||||||
|
pageLoadErrorDescription: Vanligvis skyldes dette nettverksfeil eller mellomlagring
|
||||||
|
i nettleseren. Prøv å tømme mellomlageret og prøv på nytt.
|
||||||
|
you: Du
|
||||||
|
showEmojisInReactionNotifications: Vis emojier i reaksjonsvarsler
|
||||||
|
rememberNoteVisibility: Husk synlighetsinnstillinger for poster
|
||||||
|
reactionSettingDescription2: Trekk for å flytte, klikk for å slette, trykk "+" for
|
||||||
|
å legge til.
|
||||||
|
reactionSetting: Reaksjoner som skal vises i reaksjonsvelgeren
|
||||||
|
blockConfirm: Er du sikker på at du vil blokkere denne kontoen?
|
||||||
|
unblockConfirm: Er du sikker på at du vil avblokkere denne kontoen?
|
||||||
|
flagAsBot: Merk denne kontoen som en bot
|
||||||
|
flagAsCat: Er du en katt? 😺
|
||||||
|
editWidgetsExit: Ferdig
|
||||||
|
editWidgets: Rediger skjermelementer
|
||||||
|
emoji: Emoji
|
||||||
|
emojis: Emoji
|
||||||
|
customEmojis: Egen emoji
|
||||||
|
autoAcceptFollowed: Automatisk tillat følgerforespørsler fra kontoer du følger
|
||||||
|
clearCachedFilesConfirm: Er du sikker på at du vil slette alle mellomlagrede eksterne
|
||||||
|
filer?
|
||||||
|
hiddenTagsDescription: 'List nøkkelordene (uten #) du ønsker å skjule fra trending
|
||||||
|
og utforsking. Utover dette kan nøkkelord som er skjulte fortsatt brukes.'
|
||||||
|
silenced: Stummet
|
||||||
|
blocked: Blokkert
|
||||||
|
processing: Prosesserer
|
||||||
|
preview: Forhåndsvisning
|
||||||
|
noCustomEmojis: Ingen emoji
|
||||||
|
noJobs: Ingen jobber
|
||||||
|
federating: Føderering
|
||||||
|
notResponding: Ingen svar
|
||||||
|
changePassword: Endre passord
|
||||||
|
retypedNotMatch: Innholdet i feltene stemmer ikke overens.
|
||||||
|
newPassword: Nytt passord
|
||||||
|
attachFile: Legg ved filer
|
||||||
|
announcements: Annonseringer
|
||||||
|
themeForDarkMode: Tema i mørk modus
|
||||||
|
light: Lyst
|
||||||
|
dark: Mørkt
|
||||||
|
syncDeviceDarkMode: Samkjør mørk modus med maskininnstillingene
|
||||||
|
deleteFolder: Slett katalogen
|
||||||
|
addFile: Legg til fil
|
||||||
|
tosUrl: URL til vilkår for tjenesten
|
||||||
|
circularReferenceFolder: Målkatalogen ligger under katalogen du prøver å flytte.
|
||||||
|
rename: Gi nytt navn
|
||||||
|
avatar: Brukerbilde
|
||||||
|
disablingTimelinesInfo: Administratorer og moderatorer vil alltid ha tilgang til alle
|
||||||
|
tidslinjer, selv om de ikke er aktiverte.
|
||||||
|
registration: Registrer
|
||||||
|
messagingWithGroup: Gruppechat
|
||||||
|
title: Tittel
|
||||||
|
text: Tekst
|
||||||
|
tooShort: For kort
|
||||||
|
tooLong: For langt
|
||||||
|
joinOrCreateGroup: Bli invitert inn til en gruppe eller lag dine egne.
|
||||||
|
tapSecurityKey: Trykk på sikkerhetsnøkkelen
|
||||||
|
or: Eller
|
||||||
|
language: Språk
|
||||||
|
uiLanguage: Språk for brukergrensesnitt
|
||||||
|
groupInvited: Du har blitt invitert til en gruppe
|
||||||
|
disableDrawer: Ikke bruk skuffe-menyer
|
||||||
|
expandOnNoteClick: Åpne post ved klikk
|
||||||
|
expandOnNoteClickDesc: Om du deaktiverer kan du fortsatt åpne poster fra objektmenyen
|
||||||
|
eller ved å klikke på tidsstempelet.
|
||||||
|
userSuspended: Denne brukeren er suspendert.
|
||||||
|
userSilenced: Denne brukeren er stummet.
|
||||||
|
channelFederationWarn: Kanaler blir ennå ikke federert til andre tjenere
|
||||||
|
enableInfiniteScroll: Last mer automatisk
|
||||||
|
visibility: Synlighet
|
||||||
|
regexpError: Feil i regulært uttrykk
|
||||||
|
instanceMute: Tjener-stumming
|
||||||
|
create: Opprett
|
||||||
|
messageRead: Les
|
||||||
|
noMoreHistory: Det er ikke mer historikk
|
||||||
|
emptyFolder: Katalogen er tom
|
||||||
|
thisYear: År
|
||||||
|
basicInfo: Basisinformasjon
|
||||||
|
help: Hjelp
|
||||||
|
members: Medlemmer
|
||||||
|
smtpPort: Port
|
||||||
|
error: Feil
|
||||||
|
somethingHappened: Noe gikk galt
|
||||||
|
retry: Prøv på nytt
|
||||||
|
manageLists: Administrer lister
|
||||||
|
loginFailed: Innlogging feilet
|
||||||
|
general: Generell
|
||||||
|
accountMoved: 'Brukeren har flyttet til en ny konto:'
|
||||||
|
wallpaper: Bakgrunn
|
||||||
|
addAccount: Legg til konto
|
||||||
|
showOnRemote: Vis opprinnelig side
|
||||||
|
host: Tjener
|
||||||
|
selectUser: Velg en bruker
|
||||||
|
operations: Operasjoner
|
||||||
|
software: Programvare
|
||||||
|
version: Versjon
|
||||||
|
metadata: Metadata
|
||||||
|
blockedInstances: Blokkerte tjenere
|
||||||
|
hiddenTags: Skjulte nøkkelord
|
||||||
|
suspended: Suspendert
|
||||||
|
instanceUsers: Brukere på denne tjeneren
|
||||||
|
security: Sikkerhet
|
||||||
|
currentPassword: Nåværende passord
|
||||||
|
more: Mer!
|
||||||
|
upload: Last opp
|
||||||
|
explore: Utforsk
|
||||||
|
themeForLightMode: Tema i lys modus
|
||||||
|
lightThemes: Lyse tema
|
||||||
|
fileName: Filnavn
|
||||||
|
selectFile: Velg en fil
|
||||||
|
selectFiles: Velg filer
|
||||||
|
selectFolder: Velg en katalog
|
||||||
|
selectFolders: Velg kataloger
|
||||||
|
exploreFediverse: Utforsk konføderasjonen
|
||||||
|
userList: Lister
|
||||||
|
about: Om
|
||||||
|
objectStorage: Objektlagring
|
||||||
|
useObjectStorage: Bruk objektlagring
|
||||||
|
objectStorageBaseUrl: Base-URL
|
||||||
|
installedApps: Autoriserte applikasjoner
|
||||||
|
nothing: Ikke noe å se her
|
||||||
|
deleteAllFilesConfirm: Er du sikker på at du vil slette alle filer?
|
||||||
|
updateRemoteUser: Oppdater informasjon om ekstern bruker
|
||||||
|
deleteAllFiles: Slett alle filer
|
||||||
|
enterFileDescription: Legg til bildetekst
|
||||||
|
leaveConfirm: Det er ulagrede endringer. Vil du forkaste dem?
|
||||||
|
enableAll: Slå på alle
|
||||||
|
generateAccessToken: Generer adgangstegn
|
||||||
|
disableAll: Slå av alle
|
||||||
|
permission: Tilganger
|
||||||
|
userSaysSomethingReason: '{name} skrev {reason}'
|
||||||
|
selectChannel: Velg en kanal
|
||||||
|
flagAsCatDescription: Du vil få katteører og snakke som en katt!
|
||||||
|
flagSpeakAsCat: Snakk som en katt
|
||||||
|
flagSpeakAsCatDescription: Postene dine vil bli nyanifiserte når du er i kattemodus
|
||||||
|
removeWallpaper: Fjern bakgrunn
|
||||||
|
flagShowTimelineRepliesDescription: Vis svar fra brukere på andre brukeres poster
|
||||||
|
i tidslinjen når aktivert.
|
||||||
|
proxyAccountDescription: 'En stedfortreder-konto er en konto som i noen situasjoner
|
||||||
|
oppfører seg som en ekstern følger for en bruker. For eksempel: når en ekstern bruker
|
||||||
|
blir lagt i en liste vil den eksterne brukeren ikke uten videre sendt til tjeneren
|
||||||
|
dersom ingen lokale brukere følger den eksterne brukeren. Da vil stedfortrederen
|
||||||
|
følge brukeren i stedet.'
|
||||||
|
instances: Tjenere
|
||||||
|
clearQueueConfirmTitle: Er du sikker på at du vil tømme køen?
|
||||||
|
clearQueueConfirmText: Alle usendte meldinger i køen vil ikke bli sendt til andre
|
||||||
|
tjenere. Vanligvis er denne operasjonen unødvendig.
|
||||||
|
clearCachedFiles: Tøm mellomlager
|
||||||
|
silencedInstancesDescription: Liste over tjenere du vil stumme. Kontoer på stumme
|
||||||
|
tjenere vil bare kunne lage følgerforespørsler, og kan ikke referere til lokale
|
||||||
|
kontoer med mindre de følges. Dette vil ikke påvirke de blokkerte tjenerne.
|
||||||
|
noUsers: Ingen brukere
|
||||||
|
uploadFromUrl: Last opp fra bildelenke
|
||||||
|
uploadFromUrlRequested: Opplasting forespurt
|
||||||
|
start: Start
|
||||||
|
disconnectedFromServer: Forbindelsen til tjeneren er brutt
|
||||||
|
reload: Last om
|
||||||
|
doNothing: Ignorer
|
||||||
|
theme: Tema
|
||||||
|
reloadConfirm: Vil du oppdatere tidslinjen?
|
||||||
|
watch: Følg med på
|
||||||
|
thisMonth: Måned
|
||||||
|
today: I dag
|
||||||
|
dayX: '{day}'
|
||||||
|
integration: Integrasjoner
|
||||||
|
yearX: '{year}'
|
||||||
|
pages: Sider
|
||||||
|
enableRecaptcha: Slå på reCAPTCHA
|
||||||
|
antennasDesc: "Antenner fanger opp nye poster etter et sett kriterier du setter opp!\n
|
||||||
|
Du kommer til dem fra tidslinje-siden."
|
||||||
|
manageAntennas: Administrer antenner
|
||||||
|
enableServiceworker: Slå på varsling for nettleseren
|
||||||
|
pinnedNotes: Festede poster
|
||||||
|
antennaUsersDescription: List opp ett brukernavn per linje
|
||||||
|
antennaInstancesDescription: List opp en tjener per linje
|
||||||
|
caseSensitive: Skill mellom små og store bokstaver
|
||||||
|
silence: Stillhet
|
||||||
|
silenceConfirm: Er du sikker på at du vil stumme denne brukeren?
|
||||||
|
unsilence: Avslutt stumming
|
||||||
|
recentlyRegisteredUsers: Nye brukere
|
||||||
|
token: Adgangstegn
|
||||||
|
popularTags: Populære nøkkelord
|
||||||
|
administrator: Administrator
|
||||||
|
twoStepAuthentication: Tofaktor-autentisering
|
||||||
|
securityKeyName: Nøkkelnavn
|
||||||
|
reduceUiAnimation: Demp animasjoner i brukergrensesnittet
|
||||||
|
notFound: Ikke funnet
|
||||||
|
uploadFolder: Standardkatalog for opplastinger
|
||||||
|
createGroup: Opprett gruppe
|
||||||
|
notFoundDescription: Fant ingen sider som samsvarer med enne URL-en.
|
||||||
|
ownedGroups: Grupper du eier
|
||||||
|
joinedGroups: Grupper du er med i
|
||||||
|
invites: Invitasjoner
|
||||||
|
messagingWithUser: Privat chat
|
||||||
|
enable: Slå på
|
||||||
|
quoteQuestion: Sett inn som sitat?
|
||||||
|
onlyOneFileCanBeAttached: Du kan bare legge ved én fil til en melding
|
||||||
|
signinRequired: Registrer eller logg inn før du fortsetter
|
||||||
|
invitationCode: Invitasjonskode
|
||||||
|
checking: Sjekker...
|
||||||
|
available: Tilgjengelig
|
||||||
|
unavailable: Ikke tilgjengelig
|
||||||
|
passwordMatched: Likt
|
||||||
|
passwordNotMatched: Stemmer ikke
|
||||||
|
signinWith: Logg inn med {x}
|
||||||
|
signinFailed: Klarer ikke å logge inn. Brukernavn eller passord er feil.
|
||||||
|
objectStorageBaseUrlDesc: "URL-en som brukes som referanse. Oppgi URL-en til leveringsnettverket
|
||||||
|
(CDN) eller proxy om du bruker en av dem.\nFor S3 kan du bruke 'https://<bucket>.s3.amazonaws.com'
|
||||||
|
og for GCS og tilsvarende tjenester 'https://storage.googleapis.com/<bucket>'."
|
||||||
|
objectStorageEndpoint: Endepunkt
|
||||||
|
objectStorageRegion: Region
|
||||||
|
installedDate: Autorisert
|
||||||
|
lastUsedDate: Sist brukt
|
||||||
|
state: Tilstand
|
||||||
|
sort: Sortering
|
||||||
|
yourAccountSuspendedDescription: Denne kontoen er suspendert fordi den har brutt tjenerens
|
||||||
|
retningslinjer eller lignende. Kontakt administratoren hvis du trenger en mer detaljert
|
||||||
|
grunn. Ikke opprett en ny konto.
|
||||||
|
useCw: Skjul innhold
|
||||||
|
enablePlayer: Åpne videospiller
|
||||||
|
disablePlayer: Lukk videospiller
|
||||||
|
describeFile: Legg til tekst
|
||||||
|
author: Forfatter
|
||||||
|
useFullReactionPicker: Bruk reaksjonsvelger i full størrelse
|
||||||
|
width: Bredde
|
||||||
|
regexpErrorDescription: 'En feil oppsto under det regulære uttrykket på linje {line}
|
||||||
|
av stumming av {tab}:'
|
||||||
|
userSaysSomethingReasonRenote: '{name} fremhevet en post som inneholdt {reason}'
|
||||||
|
userSaysSomethingReasonQuote: '{name} siterte en post som inneholdt {reason}'
|
||||||
|
userSaysSomething: '{name} sa noe'
|
||||||
|
metrics: Metrikker
|
||||||
|
overview: Oversikt
|
||||||
|
logs: Logger
|
||||||
|
delayed: Forsinket
|
||||||
|
channel: Kanaler
|
||||||
|
flagAsBotDescription: Slå på denne innstillingen dersom kontoen styres av et program.
|
||||||
|
Dette er et flagg til andre utviklere for å hindre uendelige kjeder der roboter
|
||||||
|
responderer på hverandres meldinger og lar den interne håndteringen av kontoen i
|
||||||
|
Firefish være tilpasset bot-brukere.
|
||||||
|
aboutFirefish: Om Firefish
|
||||||
|
youShouldUpgradeClient: Last siden på nytt for å oppdatere klienten.
|
||||||
|
serverIsDead: Tjeneren svarer ikke. Prøv på nytt om en stund.
|
||||||
|
sensitive: Sensitivt innhold
|
||||||
|
unmarkAsSensitive: Fjern merking som sensitivt innhold
|
||||||
|
enterFileName: Skriv inn filnavn
|
||||||
|
mute: Stum
|
||||||
|
unmute: Fjern stumming
|
||||||
|
block: Blokker
|
||||||
|
unsuspendConfirm: Er du sikker på at du vil fjerne suspensjonen av denne kontoen?
|
||||||
|
selectList: Velg en liste
|
||||||
|
selectAntenna: Velg en antenne
|
||||||
|
selectWidget: Velg et skjermelement
|
||||||
|
emojiName: Emoji-navn
|
||||||
|
emojiUrl: Emoji-URL
|
||||||
|
searchWith: 'Søk etter: {q}'
|
||||||
|
intro: Installasjonen av Firefish er fullført! Lag en admin-bruker.
|
||||||
|
youHaveNoLists: Du har ingen lister
|
||||||
|
followConfirm: Er du sikker på at du vil følge {name}?
|
||||||
|
proxyAccount: Stedfortreder-konto
|
||||||
|
statistics: Statistikk
|
||||||
|
default: Standard
|
||||||
|
defaultValueIs: 'Standard: {value}'
|
||||||
|
newPasswordRetype: Gjenta nytt passord
|
||||||
|
fromDrive: Fra disk
|
||||||
|
fromUrl: Fra URL
|
||||||
|
nUsersRead: lest av {n}
|
||||||
|
uploadFromUrlDescription: URL til filen du vil laste opp
|
||||||
|
agreeTo: Jeg samtykker til {0}
|
||||||
|
home: Hjem
|
||||||
|
emptyDrive: Disken din er tom
|
||||||
|
unableToDelete: Klarte ikke å slette
|
||||||
|
inputNewFileName: Oppgi nytt filnavn
|
||||||
|
hasChildFilesOrFolders: Katalogen er ikke tom og kan derfor ikke slettes.
|
||||||
|
avoidMultiCaptchaConfirm: Å bruke flere Captcha-systemer kan forårsake uønskede krysseffekter
|
||||||
|
mellom dem. Ønsker å du slå av det andre aktive CAPTCHA-systemet? Om du ønsker å
|
||||||
|
ha begge på, trykk "Avbryt".
|
||||||
|
name: Navn
|
||||||
|
antennas: Antenner
|
||||||
|
antennaSource: Antennekilde
|
||||||
|
notifyAntenna: Varsle om nye poster
|
||||||
|
antennaExcludeKeywords: Nøkkelord som skal ekskluderes
|
||||||
|
retype: Skriv inn igjen
|
||||||
|
inviteToGroup: Inviter til gruppe
|
||||||
|
next: Neste
|
||||||
|
useOsNativeEmojis: Bruk operativsystemets emojier
|
||||||
|
youHaveNoGroups: Du har ingen grupper
|
||||||
|
noHistory: Ingen historikk er tilgjengelig
|
||||||
|
aboutX: Om {x}
|
||||||
|
signinHistory: Innloggings-historikk
|
||||||
|
strongPassword: Sterkt passord
|
||||||
|
noFollowRequests: Du har ingen utestående følgeforespørsler
|
||||||
|
openImageInNewTab: Åpne bilder i ny fane
|
||||||
|
dashboard: Dashbord
|
||||||
|
local: Lokal
|
||||||
|
objectStoragePrefix: Prefiks
|
||||||
|
objectStoragePrefixDesc: Filer vil bli lagret under kataloger med dette prefikset.
|
||||||
|
objectStorageEndpointDesc: La dette stå tomt om du bruker AWS S3, ellers kan du spesifisere
|
||||||
|
endepunktet som '<tjener>' eller '<tjener>:<port>', avhengig av hvilken tjeneste
|
||||||
|
du bruker.
|
||||||
|
objectStorageUseSSL: Bruk TLS
|
||||||
|
objectStorageUseSSLDesc: Skru dette av dersom du ikke vil bruke HTTPS for API-forbindelser.
|
||||||
|
uninstall: Avinstaller
|
||||||
|
ascendingOrder: Økende
|
||||||
|
descendingOrder: Synkende
|
||||||
|
removeAllFollowing: Avfølg alle brukere
|
||||||
|
deletedNote: Slettet post
|
||||||
|
height: Høyde
|
||||||
|
enableEmail: Slå på epost-utsendelse
|
||||||
|
smtpPass: Passord
|
||||||
|
smtpSecureInfo: Slå av dette om du bruker STARTTLS
|
||||||
|
database: Database
|
||||||
|
display: Vis
|
||||||
|
notificationSetting: Varslingsinnstillinger
|
||||||
|
other: Annet
|
||||||
|
removeAllFollowingDescription: Dette vil avfølge alle kontoer på tjeneren {host}.
|
||||||
|
Kjør denne for eksempel om tjeneren ikke lenger finnes.
|
||||||
|
inboxUrl: Innboks-URL
|
||||||
|
description: Beskrivelse
|
||||||
|
smtpUser: Brukernavn
|
||||||
|
smtpSecure: Bruk implisitt SSL/TLS for SMTP-forbindelser
|
||||||
|
userSaysSomethingReasonReply: '{name} svarte på en post som inneholdt {reason}'
|
||||||
|
makeActive: Aktiver
|
||||||
|
copy: Kopier
|
||||||
|
regenerateLoginToken: Lag innloggingstegn på nytt
|
||||||
|
pinnedUsersDescription: Liste av brukere (en per linje) som skal festes øverst under
|
||||||
|
"Utforsk".
|
||||||
|
resetPassword: Tilbakestill passord
|
||||||
|
newPasswordIs: Nytt passord er "{password}"
|
||||||
|
all: Alle
|
||||||
|
keepOriginalUploadingDescription: Lagrer det opprinnelige opplastedet bildet slik
|
||||||
|
det var originalt. Hvis du slår det av vil en versjon for visning på nett bli generert
|
||||||
|
ved opplasting.
|
||||||
|
startMessaging: Start en ny chat
|
||||||
|
group: Gruppe
|
||||||
|
renote: Fremhev
|
||||||
|
banner: Topp-bilde
|
||||||
|
nsfw: Sensitivt innhold
|
||||||
|
bannerUrl: URL til fane-bilde
|
||||||
|
backgroundImageUrl: URL til bakgrunnsbilde
|
||||||
|
pinnedPages: Festede sider
|
||||||
|
hcaptcha: hCaptcha
|
||||||
|
enableHcaptcha: Slå på hCaptcha
|
||||||
|
invisibleNote: Usynlig post
|
||||||
|
withFileAntenna: Bare poster med filer
|
||||||
|
markAsReadAllUnreadNotes: Marker alle poster som lest
|
||||||
|
noteOf: Post av {user}
|
||||||
|
xl: Ekstra stor
|
||||||
|
large: Stor
|
||||||
|
medium: Medium
|
||||||
|
small: Liten
|
||||||
|
tokenRequested: Gi tilgang til konto
|
||||||
|
pluginTokenRequestedDescription: Dette programtillegget vil kunne bruke tillatelsene
|
||||||
|
som settes her.
|
||||||
|
emailConfigInfo: Brukt for å bekrefte epost-adresser ved opprettelse av kontoer og
|
||||||
|
dersom du glemmer passordet
|
||||||
|
email: Epost
|
||||||
|
emailAddress: Epostadresse
|
||||||
|
smtpConfig: Konfigurasjon av SMTP-tjener
|
||||||
|
smtpHost: Tjener
|
||||||
|
showInPage: Vis på side
|
||||||
|
volume: Volum
|
||||||
|
fileIdOrUrl: Fil-id eller URL
|
||||||
|
reporter: Rapportert av
|
||||||
|
yes: Ja
|
||||||
|
loadRawImages: Last originale bilder i stedet for å vise miniatyrbilder
|
||||||
|
fillAbuseReportDescription: Fyll inn detaljer om innrapporteringen. Hvis det handler
|
||||||
|
om en bestemt post, inkluder URL til den.
|
||||||
|
openInSideView: Åpne i sidefelt
|
||||||
|
pollVotesCount: Antall stemmer sendt
|
||||||
|
lockedAccountInfo: Med mindre du setter synlighet til "Bare følgere" vil postene dine
|
||||||
|
være synlige for alle, selv om du krever at følgere må godtas manuelt.
|
||||||
|
disableShowingAnimatedImages: Ikke spill animerte bilder
|
||||||
|
pageLikesCount: Antall likte Sider
|
||||||
|
pageLikedCount: Antall mottatte likes på Sider
|
||||||
|
contact: Kontakt
|
||||||
|
useSystemFont: Bruk standard skriftsnitt fra systemet
|
||||||
|
unableToProcess: Operasjonen kunne ikke fullføres
|
||||||
|
send: Send
|
||||||
|
clientSettings: Klientinnstillinger
|
||||||
|
accountSettings: Kontoinnstillinger
|
||||||
|
promotion: Promotert
|
||||||
|
deleteAll: Slett alle
|
||||||
|
showFixedPostForm: Vis post-feltet på toppen av tidslinjen
|
||||||
|
sounds: Lyder
|
||||||
|
details: Detaljer
|
||||||
|
openInNewTab: Åpne i ny fane
|
||||||
|
followersCount: Antall følgere
|
||||||
|
sentReactionsCount: Antall sendte reaksjoner
|
||||||
|
appearance: Utseende
|
||||||
|
hideThisNote: Skjul denne posten
|
||||||
|
popout: Pop ut
|
||||||
|
recentUsed: Nylig brukt
|
||||||
|
install: Installer
|
||||||
|
weekOverWeekChanges: Endringer til forrige uke
|
||||||
|
dayOverDayChanges: Endringer til i går
|
||||||
|
listen: Lytt
|
||||||
|
system: System
|
||||||
|
none: Ingen
|
||||||
|
chooseEmoji: Velg en emoji
|
||||||
|
setMultipleBySeparatingWithSpace: Separer ulike innslag med mellomrom.
|
||||||
|
reportAbuse: Rapport
|
||||||
|
reportAbuseOf: Rapport {name}
|
||||||
|
abuseReported: Rapporten er sendt. Takk.
|
||||||
|
random: Tilfeldig
|
||||||
|
repliesCount: Antall svar sendt
|
||||||
|
repliedCount: Antall mottatte svar
|
||||||
|
followingCount: Antall fulgte kontoer
|
||||||
|
notSet: Ikke sendt
|
||||||
|
i18nInfo: 'Firefish blir oversatt til ulike språk av frivillige. Du kan hjelpe her:
|
||||||
|
{link}.'
|
||||||
|
accessibility: Tilgjengelighet
|
||||||
|
promote: Promoter
|
||||||
|
numberOfDays: Antall dager
|
||||||
|
serverLogs: Tjenerlogger
|
||||||
|
objectStorageSetPublicRead: Sett til "offentlig lesing" ved opplasting
|
||||||
|
behavior: Oppførsel
|
||||||
|
abuseReports: Rapporter
|
||||||
|
defaultNavigationBehaviour: Standard navigeringsoppførsel
|
||||||
|
abuseMarkAsResolved: Marker rapporten som løst
|
||||||
|
emailVerified: Epost er verifisert
|
||||||
|
noteFavoritesCount: Antall bokmerkede poster
|
||||||
|
editTheseSettingsMayBreakAccount: Endringer i disse innstillingene kan ødelegge kontoen
|
||||||
|
din.
|
||||||
|
instanceTicker: Tjenerinformasjon for poster
|
||||||
|
waitingFor: Venter på {x}
|
||||||
|
notesCount: Antall poster
|
||||||
|
receivedReactionsCount: Antall mottatte reaksjoner
|
||||||
|
pollVotedCount: Antall mottatte stemmer
|
||||||
|
no: Nei
|
||||||
|
alwaysMarkSensitive: Merk som "Sensitivt innhold" som standard
|
||||||
|
verificationEmailSent: En verifiserings-epost er sendt. Følg lenken i eposten for
|
||||||
|
å fullføre verifiseringen.
|
||||||
|
newNoteRecived: Det er nye poster
|
||||||
|
|
|
@ -840,6 +840,7 @@ _aboutFirefish:
|
||||||
допомогти з його операційними витратами.
|
допомогти з його операційними витратами.
|
||||||
donateHost: Зробити внесок на рахунок {host}
|
donateHost: Зробити внесок на рахунок {host}
|
||||||
sponsors: Спонсори Firefish
|
sponsors: Спонсори Firefish
|
||||||
|
misskeyContributors: Контрибутори Misskey
|
||||||
_nsfw:
|
_nsfw:
|
||||||
respect: "Приховувати NSFW медіа"
|
respect: "Приховувати NSFW медіа"
|
||||||
ignore: "Не приховувати NSFW медіа"
|
ignore: "Не приховувати NSFW медіа"
|
||||||
|
@ -2148,3 +2149,5 @@ delete2fa: Вимкнути двофакторну авторизацію
|
||||||
inputNotMatch: Введене не співпадає
|
inputNotMatch: Введене не співпадає
|
||||||
deletePasskeysConfirm: Це видалить усі ключ-паролі і ключі безпеки на цьому обліковому
|
deletePasskeysConfirm: Це видалить усі ключ-паролі і ключі безпеки на цьому обліковому
|
||||||
записі без можливости відмінити цю дію. Продовжити?
|
записі без можливости відмінити цю дію. Продовжити?
|
||||||
|
addRe: Додати "re:" на початку коментаря у відповідь на запис із попередженням про
|
||||||
|
вміст
|
||||||
|
|
|
@ -265,7 +265,7 @@ uploadFromUrl: "Tải lên bằng một URL"
|
||||||
uploadFromUrlDescription: "URL của tập tin bạn muốn tải lên"
|
uploadFromUrlDescription: "URL của tập tin bạn muốn tải lên"
|
||||||
uploadFromUrlRequested: "Đã yêu cầu tải lên"
|
uploadFromUrlRequested: "Đã yêu cầu tải lên"
|
||||||
uploadFromUrlMayTakeTime: "Sẽ mất một khoảng thời gian để tải lên xong."
|
uploadFromUrlMayTakeTime: "Sẽ mất một khoảng thời gian để tải lên xong."
|
||||||
explore: "Khám phó"
|
explore: "Khám phá"
|
||||||
messageRead: "Đã đọc"
|
messageRead: "Đã đọc"
|
||||||
noMoreHistory: "Không còn gì để đọc"
|
noMoreHistory: "Không còn gì để đọc"
|
||||||
startMessaging: "Bắt đầu trò chuyện"
|
startMessaging: "Bắt đầu trò chuyện"
|
||||||
|
@ -1062,6 +1062,8 @@ _aboutFirefish:
|
||||||
pleaseDonateToFirefish: Hãy cân nhắc ủng hộ Firefish phát triển.
|
pleaseDonateToFirefish: Hãy cân nhắc ủng hộ Firefish phát triển.
|
||||||
donateHost: Ủng hộ {host}
|
donateHost: Ủng hộ {host}
|
||||||
pleaseDonateToHost: Cũng như ủng hộ chi phí vận hành máy chủ {host} của bạn.
|
pleaseDonateToHost: Cũng như ủng hộ chi phí vận hành máy chủ {host} của bạn.
|
||||||
|
sponsors: Nhà tài trợ Firefish
|
||||||
|
misskeyContributors: Người đóng góp Misskey
|
||||||
_nsfw:
|
_nsfw:
|
||||||
respect: "Ẩn nội dung NSFW"
|
respect: "Ẩn nội dung NSFW"
|
||||||
ignore: "Hiện nội dung NSFW"
|
ignore: "Hiện nội dung NSFW"
|
||||||
|
@ -1138,6 +1140,22 @@ _mfm:
|
||||||
stop: Dừng CĐN
|
stop: Dừng CĐN
|
||||||
play: Phát CĐN
|
play: Phát CĐN
|
||||||
warn: CĐN có thể gây đau mắt hoặc chóng mặt
|
warn: CĐN có thể gây đau mắt hoặc chóng mặt
|
||||||
|
alwaysPlay: Luôn tự phát các chuyển động nhanh
|
||||||
|
position: Vị trí
|
||||||
|
scaleDescription: Tỉ lệ nội dung theo số cụ thể.
|
||||||
|
advanced: Nâng cao MFM
|
||||||
|
positionDescription: Di chuyển nội dung theo một số cụ thể.
|
||||||
|
foregroundDescription: Đổi màu xung quanh văn bản.
|
||||||
|
background: Màu nền
|
||||||
|
advancedDescription: Nếu tắt, chỉ cho phép đánh dấu cơ bản trừ khi đang phát MFM
|
||||||
|
động
|
||||||
|
fade: Làm mờ
|
||||||
|
scale: Tỉ lệ
|
||||||
|
crop: Cắt
|
||||||
|
foreground: Màu nền xung quanh
|
||||||
|
fadeDescription: Làm mờ content vào và ra.
|
||||||
|
cropDescription: Cắt nội dung.
|
||||||
|
backgroundDescription: Đổi màu nền của văn bản.
|
||||||
_instanceTicker:
|
_instanceTicker:
|
||||||
none: "Không hiển thị"
|
none: "Không hiển thị"
|
||||||
remote: "Hiện cho người dùng từ máy chủ khác"
|
remote: "Hiện cho người dùng từ máy chủ khác"
|
||||||
|
@ -1146,6 +1164,7 @@ _serverDisconnectedBehavior:
|
||||||
reload: "Tự động tải lại"
|
reload: "Tự động tải lại"
|
||||||
dialog: "Hiện hộp thoại cảnh báo"
|
dialog: "Hiện hộp thoại cảnh báo"
|
||||||
quiet: "Hiển thị cảnh báo không phô trương"
|
quiet: "Hiển thị cảnh báo không phô trương"
|
||||||
|
nothing: không làm gì
|
||||||
_channel:
|
_channel:
|
||||||
create: "Tạo kênh"
|
create: "Tạo kênh"
|
||||||
edit: "Chỉnh sửa kênh"
|
edit: "Chỉnh sửa kênh"
|
||||||
|
@ -1156,6 +1175,8 @@ _channel:
|
||||||
following: "Đang theo dõi"
|
following: "Đang theo dõi"
|
||||||
usersCount: "{n} Thành viên"
|
usersCount: "{n} Thành viên"
|
||||||
notesCount: "{n} Tút"
|
notesCount: "{n} Tút"
|
||||||
|
nameOnly: Chỉ tên
|
||||||
|
nameAndDescription: Tên và mô tả
|
||||||
_menuDisplay:
|
_menuDisplay:
|
||||||
sideFull: "Thanh bên"
|
sideFull: "Thanh bên"
|
||||||
sideIcon: "Thanh bên (Biểu tượng)"
|
sideIcon: "Thanh bên (Biểu tượng)"
|
||||||
|
@ -1324,6 +1345,25 @@ _2fa:
|
||||||
securityKeyInfo: "Bên cạnh xác minh bằng vân tay hoặc mã PIN, bạn cũng có thể thiết
|
securityKeyInfo: "Bên cạnh xác minh bằng vân tay hoặc mã PIN, bạn cũng có thể thiết
|
||||||
lập xác minh thông qua khóa bảo mật phần cứng hỗ trợ FIDO2 để bảo mật hơn nữa
|
lập xác minh thông qua khóa bảo mật phần cứng hỗ trợ FIDO2 để bảo mật hơn nữa
|
||||||
cho tài khoản của mình."
|
cho tài khoản của mình."
|
||||||
|
registerTOTPBeforeKey: Vui lòng thiết lập một ứng dụng xác thực để đăng ký khóa
|
||||||
|
bảo mật hoặc mật khẩu.
|
||||||
|
tapSecurityKey: Vui lòng theo dõi trình duyệt của bạn để đăng ký mã bảo mật hoặc
|
||||||
|
mã khóa
|
||||||
|
renewTOTPConfirm: Điều này sẽ khiến mã xác minh từ ứng dụng trước của bạn ngừng
|
||||||
|
hoạt động
|
||||||
|
securityKeyName: Nhập tên mã khóa
|
||||||
|
step3Title: Nhập mã xác thực
|
||||||
|
chromePasskeyNotSupported: Mật khẩu Chrome hiện không được hỗ trợ.
|
||||||
|
removeKeyConfirm: Thực sự xóa khóa {name}?
|
||||||
|
whyTOTPOnlyRenew: Không thể xóa ứng dụng xác thực miễn là đã đăng ký khóa bảo mật.
|
||||||
|
renewTOTPOk: Cấu hình lại
|
||||||
|
renewTOTPCancel: Hủy bỏ
|
||||||
|
removeKey: Xóa khóa bảo mật
|
||||||
|
step2Click: Nhấn vào mã QR này sẽ cho phép bạn đăng ký 2FA cho khóa bảo mật hoặc
|
||||||
|
ứng dụng xác thực điện thoại của bạn.
|
||||||
|
securityKeyNotSupported: Trình duyệt của bạn không hỗ trợ khóa bảo mật.
|
||||||
|
renewTOTP: Định cấu hình lại ứng dụng xác thực
|
||||||
|
token: 2FA Token
|
||||||
_permissions:
|
_permissions:
|
||||||
"read:account": "Xem thông tin tài khoản của bạn"
|
"read:account": "Xem thông tin tài khoản của bạn"
|
||||||
"write:account": "Sửa thông tin tài khoản của bạn"
|
"write:account": "Sửa thông tin tài khoản của bạn"
|
||||||
|
@ -1365,12 +1405,15 @@ _auth:
|
||||||
pleaseGoBack: "Vui lòng quay lại ứng dụng"
|
pleaseGoBack: "Vui lòng quay lại ứng dụng"
|
||||||
callback: "Quay lại ứng dụng"
|
callback: "Quay lại ứng dụng"
|
||||||
denied: "Truy cập bị từ chối"
|
denied: "Truy cập bị từ chối"
|
||||||
|
allPermissions: Truy cập đầy đủ vào tài khoản
|
||||||
|
copyAsk: 'Vui lòng dán mã ủy quyền sau vào ứng dụng:'
|
||||||
_antennaSources:
|
_antennaSources:
|
||||||
all: "Toàn bộ tút"
|
all: "Toàn bộ tút"
|
||||||
homeTimeline: "Tút từ những người đã theo dõi"
|
homeTimeline: "Tút từ những người đã theo dõi"
|
||||||
users: "Tút từ những người cụ thể"
|
users: "Tút từ những người cụ thể"
|
||||||
userList: "Tút từ danh sách người dùng cụ thể"
|
userList: "Tút từ danh sách người dùng cụ thể"
|
||||||
userGroup: "Tút từ người dùng trong một nhóm cụ thể"
|
userGroup: "Tút từ người dùng trong một nhóm cụ thể"
|
||||||
|
instances: Tút từ mọi người trên máy chủ
|
||||||
_weekday:
|
_weekday:
|
||||||
sunday: "Chủ Nhật"
|
sunday: "Chủ Nhật"
|
||||||
monday: "Thứ Hai"
|
monday: "Thứ Hai"
|
||||||
|
@ -1402,6 +1445,13 @@ _widgets:
|
||||||
serverMetric: "Thống kê máy chủ"
|
serverMetric: "Thống kê máy chủ"
|
||||||
aiscript: "AiScript console"
|
aiscript: "AiScript console"
|
||||||
aichan: "Ai"
|
aichan: "Ai"
|
||||||
|
userList: Danh sách người dùng
|
||||||
|
_userList:
|
||||||
|
chooseList: Chọn một danh sách
|
||||||
|
meiliSize: Kích cỡ chỉ mục
|
||||||
|
meiliIndexCount: Tút đã lập chỉ mục
|
||||||
|
meiliStatus: Trạng thái máy chủ
|
||||||
|
serverInfo: Thông tin máy chủ
|
||||||
_cw:
|
_cw:
|
||||||
hide: "Ẩn"
|
hide: "Ẩn"
|
||||||
show: "Tải thêm"
|
show: "Tải thêm"
|
||||||
|
@ -1465,6 +1515,8 @@ _profile:
|
||||||
metadataContent: "Nội dung"
|
metadataContent: "Nội dung"
|
||||||
changeAvatar: "Đổi ảnh đại diện"
|
changeAvatar: "Đổi ảnh đại diện"
|
||||||
changeBanner: "Đổi ảnh bìa"
|
changeBanner: "Đổi ảnh bìa"
|
||||||
|
locationDescription: Nếu bạn nhập thành phố của mình trước, nó sẽ hiển thị giờ địa
|
||||||
|
phương của bạn cho những người dùng khác.
|
||||||
_exportOrImport:
|
_exportOrImport:
|
||||||
allNotes: "Toàn bộ tút"
|
allNotes: "Toàn bộ tút"
|
||||||
followingList: "Đang theo dõi"
|
followingList: "Đang theo dõi"
|
||||||
|
@ -1504,6 +1556,7 @@ _timelines:
|
||||||
local: "Máy chủ này"
|
local: "Máy chủ này"
|
||||||
social: "Xã hội"
|
social: "Xã hội"
|
||||||
global: "Liên hợp"
|
global: "Liên hợp"
|
||||||
|
recommended: Đề xuất
|
||||||
_pages:
|
_pages:
|
||||||
newPage: "Tạo Trang mới"
|
newPage: "Tạo Trang mới"
|
||||||
editPage: "Sửa Trang này"
|
editPage: "Sửa Trang này"
|
||||||
|
@ -1832,6 +1885,9 @@ _notification:
|
||||||
followBack: "đã theo dõi lại bạn"
|
followBack: "đã theo dõi lại bạn"
|
||||||
reply: "Trả lời"
|
reply: "Trả lời"
|
||||||
renote: "Đăng lại"
|
renote: "Đăng lại"
|
||||||
|
voted: đã bình chọn tút của bạn
|
||||||
|
reacted: đã biểu cảm tút của bạn
|
||||||
|
renoted: đã đăng lại tút của bạn
|
||||||
_deck:
|
_deck:
|
||||||
alwaysShowMainColumn: "Luôn hiện cột chính"
|
alwaysShowMainColumn: "Luôn hiện cột chính"
|
||||||
columnAlign: "Căn cột"
|
columnAlign: "Căn cột"
|
||||||
|
@ -1859,6 +1915,9 @@ _deck:
|
||||||
list: "Danh sách"
|
list: "Danh sách"
|
||||||
mentions: "Lượt nhắc"
|
mentions: "Lượt nhắc"
|
||||||
direct: "Nhắn riêng"
|
direct: "Nhắn riêng"
|
||||||
|
channel: Kênh
|
||||||
|
renameProfile: Đổi tên workspace
|
||||||
|
nameAlreadyExists: Tên workspace này đã tồn tại.
|
||||||
renoteMute: Ẩn lượt chia sẻ
|
renoteMute: Ẩn lượt chia sẻ
|
||||||
renoteUnmute: Bỏ ẩn lượt chia sẻ
|
renoteUnmute: Bỏ ẩn lượt chia sẻ
|
||||||
searchPlaceholder: Lướt Firefish
|
searchPlaceholder: Lướt Firefish
|
||||||
|
@ -1867,3 +1926,210 @@ findOtherInstance: Tìm máy chủ khác
|
||||||
noThankYou: Từ chối
|
noThankYou: Từ chối
|
||||||
_filters:
|
_filters:
|
||||||
withFile: Có file
|
withFile: Có file
|
||||||
|
notesAfter: Đăng sau
|
||||||
|
followersOnly: Chỉ người theo dõi
|
||||||
|
fromUser: Từ người dùng
|
||||||
|
notesBefore: Đăng trước
|
||||||
|
followingOnly: Đang theo dõi
|
||||||
|
fromDomain: Từ máy chủ
|
||||||
|
flagSpeakAsCatDescription: Tút của bạn sẽ biến hóa ngộ nghĩnh khi bật chế độ tôi là
|
||||||
|
mèo
|
||||||
|
secureModeInfo: Khi truy vấn từ máy chủ khác, không nhận nếu không có bằng chứng.
|
||||||
|
pushNotificationNotSupported: Trình duyệt hoặc máy chủ không hỗ trợ thông báo đẩy
|
||||||
|
sendPushNotificationReadMessage: Xóa thông báo đẩy sau khi thông báo hoặc tin nhắn
|
||||||
|
liên quan đã được đọc
|
||||||
|
adminCustomCssWarn: Cài đặt này chỉ nên được sử dụng nếu bạn biết rõ cách thức hoạt
|
||||||
|
động của nó. Việc nhập các giá trị không phù hợp có thể khiến app của MỌI NGƯỜI
|
||||||
|
ngừng hoạt động. Vui lòng đảm bảo rằng CSS của bạn hoạt động bình thường bằng cách
|
||||||
|
kiểm tra nó trong cài đặt người dùng của bạn.
|
||||||
|
enableCustomKaTeXMacro: Bật tùy chỉnh macro KaTeX
|
||||||
|
noGraze: Vui lòng tắt tiện ích mở rộng trình duyệt "Graze for Mastodon" vì tiện ích
|
||||||
|
này can thiệp vào Firefish.
|
||||||
|
addRe: Thêm "re:" vào đầu bình luận để trả lời tút có cảnh báo nội dung
|
||||||
|
_experiments:
|
||||||
|
postImportsCaption: Cho phép người dùng nhập các bài đăng của họ từ các tài khoản
|
||||||
|
Firefish, Misskey, Mastodon, Akkoma và Pleroma trước đây. Nó có thể gây chậm trong
|
||||||
|
quá trình tải nếu hàng đợi của bạn bị tắc nghẽn.
|
||||||
|
title: Thử nghiệm
|
||||||
|
enablePostImports: Bật nhập tút
|
||||||
|
_skinTones:
|
||||||
|
medium: Vừa
|
||||||
|
light: Sáng
|
||||||
|
dark: Đen
|
||||||
|
yellow: Vàng
|
||||||
|
mediumLight: Sáng Vừa
|
||||||
|
mediumDark: Đen Vừa
|
||||||
|
removeReaction: Xóa biểu cảm
|
||||||
|
enableRecommendedTimeline: Bật bảng tin đề xuất
|
||||||
|
antennasDesc: "Ăng-ten hiển thị tút mới phù hợp với tiêu chí bạn đặt!\n Chúng có thể
|
||||||
|
được truy cập từ trang bảng tin."
|
||||||
|
userSaysSomethingReasonQuote: '{name} trích dẫn một tút chứa {reason}'
|
||||||
|
allowedInstancesDescription: Host của máy chủ được đưa vào danh sách trắng để liên
|
||||||
|
hợp, mỗi máy chủ được phân tách bằng cách xuống dòng (chỉ áp dụng ở chế độ riêng
|
||||||
|
tư).
|
||||||
|
sendPushNotificationReadMessageCaption: Sẽ hiện thông báo "{emptyPushNotificationMessage}"
|
||||||
|
trong một khoảng thời gian ngắn. Điều này có thể gây tốn pin của thiết bị.
|
||||||
|
enterSendsMessage: Nhấn Trở lại trong Tin nhắn để gửi tin nhắn (tắt là Ctlr + Return)
|
||||||
|
showAdminUpdates: Thông báo có phiên bản Firefish mới (chỉ dành cho quản trị viên)
|
||||||
|
replayTutorial: Phát lại hướng dẫn
|
||||||
|
moveFrom: Chuyển từ tài khoản cũ sang
|
||||||
|
moveFromDescription: Thao tác này sẽ đặt bí danh cho tài khoản cũ của bạn để bạn có
|
||||||
|
thể chuyển từ tài khoản đó sang tài khoản hiện tại. Làm điều này TRƯỚC KHI di chuyển
|
||||||
|
từ tài khoản cũ của bạn. Vui lòng nhập định dạng @person@server.com
|
||||||
|
signupsDisabled: Máy chủ này hiện đang bị tắt đăng ký, nhưng bạn luôn có thể đăng
|
||||||
|
ký tại một máy chủ khác! Nếu bạn có mã mời cho máy chủ này, vui lòng nhập mã đó
|
||||||
|
vào bên dưới.
|
||||||
|
silencedWarning: Trang này đang hiển thị vì những người dùng này đến từ các máy chủ
|
||||||
|
mà quản trị viên của bạn đã ẩn, vì vậy họ có thể là spam.
|
||||||
|
_dialog:
|
||||||
|
charactersExceeded: 'Vượt quá giới hạn ký tự! Hiện tại: {current}/Tối đa: {max}'
|
||||||
|
charactersBelow: 'Không đủ ký tự tối thiểu! Hiện tại: {current}/Tối thiểu: {min}'
|
||||||
|
enableIdenticonGeneration: Bật tạo identicon
|
||||||
|
enableServerMachineStats: Bật thống kê phần cứng máy chủ
|
||||||
|
secureMode: Chế độ an toàn (Phê duyệt nạp)
|
||||||
|
_messaging:
|
||||||
|
dms: Riêng tư
|
||||||
|
groups: Nhóm
|
||||||
|
moveToLabel: 'Tài khoản bạn chuyển tới:'
|
||||||
|
reactionPickerSkinTone: Chọn màu da emoji
|
||||||
|
jumpToPrevious: Về trước
|
||||||
|
listsDesc: Danh sách cho phép bạn tạo các bảng tin với những người dùng chọn trước.
|
||||||
|
Xem danh sách ở trang bảng tin.
|
||||||
|
enableEmojiReactions: Bật biểu cảm bằng emoji
|
||||||
|
showEmojisInReactionNotifications: Hiện emoji trong thông báo biểu cảm
|
||||||
|
silencedInstancesDescription: Liệt kê địa chủ của các máy chủ mà bạn muốn ẩn. Tài
|
||||||
|
khoản trong các máy chủ được liệt kê được coi là "Ẩn", chỉ có thể thực hiện các
|
||||||
|
yêu cầu theo dõi và không thể nhắn riêng đến tài khoản máy chủ nếu không được theo
|
||||||
|
dõi. Điều này sẽ không ảnh hưởng đến các máy chủ bị chặn.
|
||||||
|
silenced: Đã ẩn
|
||||||
|
expandOnNoteClick: Mở tút khi nhấn vào
|
||||||
|
expandOnNoteClickDesc: Nếu tắt, bạn vẫn có thể chọn mở tút trong menu chuột phải hoặc
|
||||||
|
nhấn vào thời gian đăng.
|
||||||
|
userSaysSomethingReasonReply: '{name} trả lời một tút chứa {reason}'
|
||||||
|
userSaysSomethingReasonRenote: '{name} đăng lại một tút chứa {reason}'
|
||||||
|
channelFederationWarn: Kênh chưa thể liên hợp với máy chủ khác
|
||||||
|
clipsDesc: Ghim là những tút bạn muốn luôn hiển thị đầu tiên. Bạn có thể tạo ghim
|
||||||
|
từ menu của mỗi tút.
|
||||||
|
seperateRenoteQuote: Phân chia nút đăng lại và trích dẫn
|
||||||
|
subscribePushNotification: Bật thông báo đẩy
|
||||||
|
noteId: ID tút
|
||||||
|
moveAccount: Đã chuyển tài khoản!
|
||||||
|
sendModMail: Gửi lưu ý kiểm duyệt
|
||||||
|
verifiedLink: Liên kết xác minh
|
||||||
|
_feeds:
|
||||||
|
copyFeed: Sao chép feed
|
||||||
|
rss: RSS
|
||||||
|
atom: Atom
|
||||||
|
jsonFeed: JSON
|
||||||
|
hiddenTags: Những hashtag đã ẩn
|
||||||
|
cannotUploadBecauseExceedsFileSizeLimit: Không thể tải lên vì vượt quá dung lượng
|
||||||
|
cho phép.
|
||||||
|
pushNotificationAlreadySubscribed: Đã bật thông báo đẩy
|
||||||
|
splash: Splash Screen
|
||||||
|
alt: ALT
|
||||||
|
showAds: Hiện banner cộng đồng
|
||||||
|
migration: Chuyển máy chủ
|
||||||
|
swipeOnMobile: Cho phép vuốt giữa các trang
|
||||||
|
logoImageUrl: Đường dẫn hình ảnh logo
|
||||||
|
moveTo: Chuyển đến tài khoản mới
|
||||||
|
moveAccountDescription: Quá trình này là không thể đảo ngược. Đảm bảo rằng bạn đã
|
||||||
|
thiết lập bí danh cho tài khoản này trên tài khoản mới của mình trước khi di chuyển.
|
||||||
|
Vui lòng nhập định dạng @person@server.com
|
||||||
|
antennaInstancesDescription: Liệt kê mỗi máy chủ một dòng
|
||||||
|
privateModeInfo: Khi bật, chỉ các máy chủ trong danh sách trắng mới có thể liên hợp
|
||||||
|
với máy chủ của bạn. Tất cả tút sẽ được ẩn khỏi công khai.
|
||||||
|
unsubscribePushNotification: Tắt thông báo đẩy
|
||||||
|
customMOTD: Tùy chỉnh MOTD (tin nhắn lướt qua trên màn hình)
|
||||||
|
deleted: Đã xóa
|
||||||
|
editNote: Sửa tút
|
||||||
|
flagSpeakAsCat: Tôi là mèo
|
||||||
|
silenceThisInstance: Ẩn máy chủ này
|
||||||
|
silencedInstances: Những máy chủ đã ẩn
|
||||||
|
instanceSecurity: An toàn máy chủ
|
||||||
|
showUpdates: Hiện popup khi Firefish có cập nhật
|
||||||
|
selectChannel: Chọn kênh
|
||||||
|
isBot: Đây là tài khoản bot
|
||||||
|
isLocked: Tài khoản này duyệt theo dõi thủ công
|
||||||
|
origin: Gốc
|
||||||
|
newer: mới hơn
|
||||||
|
older: cũ hơn
|
||||||
|
accountMoved: 'Người này đã chuyển sang:'
|
||||||
|
hiddenTagsDescription: 'Liệt kê các hashtag (không có #) mà bạn muốn ẩn khỏi xu hướng
|
||||||
|
và khám phá. Các thẻ bắt đầu bằng # đã ẩn vẫn có thể được thấy ở các nơi khác.'
|
||||||
|
noInstances: Không có máy chủ nào
|
||||||
|
manageGroups: Quản lý nhóm
|
||||||
|
accessibility: Khả năng tiếp cận
|
||||||
|
indexNotice: Đang lập chỉ mục. Quá trình này có thể mất một lúc, vui lòng không khởi
|
||||||
|
động lại máy chủ của bạn sau ít nhất một giờ.
|
||||||
|
breakFollowConfirm: Bạn có chắc muốn xóa người theo dõi?
|
||||||
|
caption: Caption tự động
|
||||||
|
objectStorageS3ForcePathStyle: Sử dụng URL điểm cuối dựa trên đường dẫn
|
||||||
|
objectStorageS3ForcePathStyleDesc: Bật tính năng này để tạo URL điểm cuối ở định dạng
|
||||||
|
's3.amazonaws.com/<bucket>/' thay vì '<bucket>.s3.amazonaws.com'.
|
||||||
|
privateMode: Chế độ riêng tư
|
||||||
|
allowedInstances: Danh sách trắng
|
||||||
|
customMOTDDescription: Tùy chỉnh tin nhắn MOTD (splash screen) được phân tách bằng
|
||||||
|
dấu ngắt dòng để được hiển thị ngẫu nhiên mỗi khi người dùng tải/tải lại trang.
|
||||||
|
customSplashIcons: Tùy chỉnh biểu tượng splash screen (urls)
|
||||||
|
customSplashIconsDescription: URL cho các biểu tượng splash screen tùy chỉnh được
|
||||||
|
phân tách bằng dấu ngắt dòng sẽ được hiển thị ngẫu nhiên mỗi khi người dùng tải/tải
|
||||||
|
lại trang. Vui lòng đảm bảo rằng các hình ảnh nằm trên một URL tĩnh, tốt nhất là
|
||||||
|
tất cả đã được thay đổi kích thước thành 192x192.
|
||||||
|
recommendedInstances: Máy chủ đề xuất
|
||||||
|
updateAvailable: Có bản cập nhật mới!
|
||||||
|
swipeOnDesktop: Cho phép vuốt kiểu điện thoại trên máy tính
|
||||||
|
moveFromLabel: 'Tài khoản cũ của bạn:'
|
||||||
|
defaultReaction: Biểu cảm mặc định cho những tút đã đăng và sắp đăng
|
||||||
|
indexFromDescription: Để trống để lập chỉ mục toàn bộ
|
||||||
|
donationLink: Liên kết tới trang tài trợ
|
||||||
|
deletePasskeys: Xóa passkey
|
||||||
|
delete2faConfirm: Thao tác này sẽ xóa 2FA trên tài khoản này một cách không thể phục
|
||||||
|
hồi. Tiếp tục?
|
||||||
|
deletePasskeysConfirm: Thao tác này sẽ xóa hoàn toàn tất cả mật khẩu và khóa bảo mật
|
||||||
|
trên tài khoản này. Tiếp tục?
|
||||||
|
inputNotMatch: Không trùng khớp
|
||||||
|
addInstance: Thêm một máy chủ
|
||||||
|
delete2fa: Tắt 2FA
|
||||||
|
apps: App
|
||||||
|
image: Hình ảnh
|
||||||
|
video: Video
|
||||||
|
audio: Âm thanh
|
||||||
|
selectInstance: Chọn máy chủ
|
||||||
|
userSaysSomethingReason: '{name} cho biết {reason}'
|
||||||
|
pushNotification: Thông báo đẩy
|
||||||
|
indexPosts: Chỉ mục tút
|
||||||
|
indexFrom: Chỉ mục từ Post ID
|
||||||
|
customKaTeXMacro: Tùy chỉnh macro KaTeX
|
||||||
|
license: Giấy phép
|
||||||
|
cw: Nội dung ẩn
|
||||||
|
showPopup: Thông báo người dùng bằng popup
|
||||||
|
showWithSparkles: Hiện kèm hiệu ứng lấp lánh
|
||||||
|
youHaveUnreadAnnouncements: Bạn có thông báo chưa đọc
|
||||||
|
migrationConfirm: "Bạn có hoàn toàn chắc chắn muốn di chuyển tài khoản của mình sang
|
||||||
|
{account} không? Sau khi thực hiện việc này, bạn sẽ không thể đảo ngược nó và sẽ
|
||||||
|
không thể sử dụng lại tài khoản của mình một cách bình thường.\nNgoài ra, vui lòng
|
||||||
|
đảm bảo rằng bạn đã đặt tài khoản hiện tại này làm tài khoản mà bạn đang chuyển
|
||||||
|
từ đó."
|
||||||
|
xl: XL
|
||||||
|
neverShow: Không hiện lại nữa
|
||||||
|
remindMeLater: Để sau
|
||||||
|
removeQuote: Xóa trích dẫn
|
||||||
|
removeRecipient: Xóa người nhận
|
||||||
|
removeMember: Xóa thành viên
|
||||||
|
customKaTeXMacroDescription: 'Thiết lập macro để viết các biểu thức toán học một cách
|
||||||
|
dễ dàng! Ký hiệu tuân theo định nghĩa lệnh của LaTeX và được viết là \newcommand{\
|
||||||
|
name}{content} hoặc \newcommand{\name}[số lượng đối số]{content}. Ví dụ: \newcommand{\add}[2]{#1
|
||||||
|
+ #2} sẽ mở rộng \add{3}{foo} thành 3 + foo. Dấu ngoặc nhọn bao quanh tên macro
|
||||||
|
có thể được thay đổi thành dấu ngoặc tròn hoặc vuông. Điều này ảnh hưởng đến các
|
||||||
|
dấu ngoặc được sử dụng cho các đối số. Một (và chỉ một) macro có thể được xác định
|
||||||
|
trên mỗi dòng và bạn không thể ngắt dòng ở giữa định nghĩa. Các dòng không hợp lệ
|
||||||
|
chỉ đơn giản là bị bỏ qua. Chỉ hỗ trợ các hàm thay thế chuỗi đơn giản; cú pháp nâng
|
||||||
|
cao, chẳng hạn như phân nhánh có điều kiện, không thể được sử dụng ở đây.'
|
||||||
|
preventAiLearning: Chặn AI bot càn quét
|
||||||
|
preventAiLearningDescription: Yêu cầu các mô hình ngôn ngữ AI của bên thứ ba không
|
||||||
|
nghiên cứu nội dung bạn tải lên, chẳng hạn như tút và hình ảnh.
|
||||||
|
isModerator: Kiểm duyệt viên
|
||||||
|
isAdmin: Quản trị viên
|
||||||
|
isPatron: Người bảo trợ Firefish
|
||||||
|
recommendedInstancesDescription: Các máy chủ được đề xuất được phân tách bằng dấu
|
||||||
|
ngắt dòng để xuất hiện trong bảng tin đề xuất.
|
||||||
|
|
14
package.json
14
package.json
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "firefish",
|
"name": "firefish",
|
||||||
"version": "1.0.4-beta",
|
"version": "1.0.5-dev6",
|
||||||
"codename": "aqua",
|
"codename": "aqua",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.joinfirefish.org/firefish/firefish.git"
|
"url": "https://git.joinfirefish.org/firefish/firefish.git"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@8.6.9",
|
"packageManager": "pnpm@8.6.11",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"rebuild": "pnpm run clean && pnpm node ./scripts/build-greet.js && pnpm -r --parallel run build && pnpm run gulp",
|
"rebuild": "pnpm run clean && pnpm node ./scripts/build-greet.js && pnpm -r --parallel run build && pnpm run gulp",
|
||||||
|
@ -39,9 +39,9 @@
|
||||||
"chokidar": "^3.3.1"
|
"chokidar": "^3.3.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bull-board/api": "5.6.0",
|
"@bull-board/api": "5.7.2",
|
||||||
"@bull-board/ui": "5.6.0",
|
"@bull-board/ui": "5.7.2",
|
||||||
"@napi-rs/cli": "^2.16.1",
|
"@napi-rs/cli": "^2.16.2",
|
||||||
"@tensorflow/tfjs": "^3.21.0",
|
"@tensorflow/tfjs": "^3.21.0",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
"seedrandom": "^3.0.5"
|
"seedrandom": "^3.0.5"
|
||||||
|
@ -49,7 +49,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/gulp": "4.0.13",
|
"@types/gulp": "4.0.13",
|
||||||
"@types/gulp-rename": "2.0.2",
|
"@types/gulp-rename": "2.0.2",
|
||||||
"@types/node": "20.4.1",
|
"@types/node": "20.4.9",
|
||||||
"chalk": "4.1.2",
|
"chalk": "4.1.2",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"cypress": "10.11.0",
|
"cypress": "10.11.0",
|
||||||
|
@ -60,7 +60,7 @@
|
||||||
"gulp-replace": "1.1.4",
|
"gulp-replace": "1.1.4",
|
||||||
"gulp-terser": "2.1.0",
|
"gulp-terser": "2.1.0",
|
||||||
"install-peers": "^1.0.4",
|
"install-peers": "^1.0.4",
|
||||||
"rome": "^v12.1.3-nightly.f65b0d9",
|
"rome": "^12.1.3",
|
||||||
"start-server-and-test": "1.15.2",
|
"start-server-and-test": "1.15.2",
|
||||||
"typescript": "5.1.6"
|
"typescript": "5.1.6"
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,16 +28,16 @@
|
||||||
"@tensorflow/tfjs-node": "3.21.1"
|
"@tensorflow/tfjs-node": "3.21.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bull-board/api": "5.6.0",
|
"@bull-board/api": "5.7.2",
|
||||||
"@bull-board/koa": "5.6.0",
|
"@bull-board/koa": "5.7.2",
|
||||||
"@bull-board/ui": "5.6.0",
|
"@bull-board/ui": "5.7.2",
|
||||||
"@discordapp/twemoji": "14.1.2",
|
"@discordapp/twemoji": "14.1.2",
|
||||||
"@elastic/elasticsearch": "7.17.0",
|
"@elastic/elasticsearch": "7.17.0",
|
||||||
"@koa/cors": "3.4.3",
|
"@koa/cors": "3.4.3",
|
||||||
"@koa/multer": "3.0.2",
|
"@koa/multer": "3.0.2",
|
||||||
"@koa/router": "9.0.1",
|
"@koa/router": "9.0.1",
|
||||||
"@peertube/http-signature": "1.7.0",
|
"@peertube/http-signature": "1.7.0",
|
||||||
"@redocly/openapi-core": "1.0.0-beta.131",
|
"@redocly/openapi-core": "1.0.2",
|
||||||
"@sinonjs/fake-timers": "9.1.2",
|
"@sinonjs/fake-timers": "9.1.2",
|
||||||
"@syuilo/aiscript": "0.11.1",
|
"@syuilo/aiscript": "0.11.1",
|
||||||
"@tensorflow/tfjs": "^4.2.0",
|
"@tensorflow/tfjs": "^4.2.0",
|
||||||
|
@ -51,9 +51,9 @@
|
||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"blurhash": "2.0.5",
|
"blurhash": "2.0.5",
|
||||||
"bull": "4.10.4",
|
"bull": "4.11.2",
|
||||||
"cacheable-lookup": "7.0.0",
|
"cacheable-lookup": "7.0.0",
|
||||||
"cassandra-driver": "^4.6.4",
|
"cassandra-driver": "4.6.4",
|
||||||
"cbor": "8.1.0",
|
"cbor": "8.1.0",
|
||||||
"chalk": "5.3.0",
|
"chalk": "5.3.0",
|
||||||
"chalk-template": "0.4.0",
|
"chalk-template": "0.4.0",
|
||||||
|
@ -95,18 +95,18 @@
|
||||||
"meilisearch": "0.33.0",
|
"meilisearch": "0.33.0",
|
||||||
"mfm-js": "0.23.3",
|
"mfm-js": "0.23.3",
|
||||||
"mime-types": "2.1.35",
|
"mime-types": "2.1.35",
|
||||||
"msgpackr": "1.9.5",
|
"msgpackr": "1.9.6",
|
||||||
"multer": "1.4.4-lts.1",
|
"multer": "1.4.4-lts.1",
|
||||||
"native-utils": "link:native-utils",
|
"native-utils": "link:native-utils",
|
||||||
"nested-property": "4.0.0",
|
"nested-property": "4.0.0",
|
||||||
"node-fetch": "3.3.1",
|
"node-fetch": "3.3.2",
|
||||||
"nodemailer": "6.9.3",
|
"nodemailer": "6.9.4",
|
||||||
"nsfwjs": "2.4.2",
|
"nsfwjs": "2.4.2",
|
||||||
"oauth": "^0.10.0",
|
"oauth": "^0.10.0",
|
||||||
"os-utils": "0.0.14",
|
"os-utils": "0.0.14",
|
||||||
"otpauth": "^9.1.3",
|
"otpauth": "^9.1.4",
|
||||||
"parse5": "7.1.2",
|
"parse5": "7.1.2",
|
||||||
"pg": "8.11.1",
|
"pg": "8.11.2",
|
||||||
"private-ip": "2.3.4",
|
"private-ip": "2.3.4",
|
||||||
"probe-image-size": "7.2.3",
|
"probe-image-size": "7.2.3",
|
||||||
"promise-limit": "2.7.0",
|
"promise-limit": "2.7.0",
|
||||||
|
@ -116,37 +116,37 @@
|
||||||
"qs": "6.11.2",
|
"qs": "6.11.2",
|
||||||
"random-seed": "0.3.0",
|
"random-seed": "0.3.0",
|
||||||
"ratelimiter": "3.4.1",
|
"ratelimiter": "3.4.1",
|
||||||
"re2": "1.19.1",
|
"re2": "1.20.1",
|
||||||
"redis-lock": "0.1.4",
|
"redis-lock": "0.1.4",
|
||||||
"redis-semaphore": "5.3.1",
|
"redis-semaphore": "5.4.0",
|
||||||
"reflect-metadata": "0.1.13",
|
"reflect-metadata": "0.1.13",
|
||||||
"rename": "1.0.4",
|
"rename": "1.0.4",
|
||||||
"rndstr": "1.0.0",
|
"rndstr": "1.0.0",
|
||||||
"rss-parser": "3.13.0",
|
"rss-parser": "3.13.0",
|
||||||
"sanitize-html": "2.10.0",
|
"sanitize-html": "2.11.0",
|
||||||
"seedrandom": "^3.0.5",
|
"seedrandom": "^3.0.5",
|
||||||
"semver": "7.5.4",
|
"semver": "7.5.4",
|
||||||
"sharp": "0.32.1",
|
"sharp": "0.32.4",
|
||||||
"sonic-channel": "^1.3.1",
|
"sonic-channel": "^1.3.1",
|
||||||
"stringz": "2.1.0",
|
"stringz": "2.1.0",
|
||||||
"summaly": "2.7.0",
|
"summaly": "2.7.0",
|
||||||
"syslog-pro": "1.0.0",
|
"syslog-pro": "1.0.0",
|
||||||
"systeminformation": "5.17.17",
|
"systeminformation": "5.18.13",
|
||||||
"tar-stream": "^3.1.6",
|
"tar-stream": "^3.1.6",
|
||||||
"tesseract.js": "^3.0.3",
|
"tesseract.js": "^4.1.1",
|
||||||
"tinycolor2": "1.5.2",
|
"tinycolor2": "1.6.0",
|
||||||
"tmp": "0.2.1",
|
"tmp": "0.2.1",
|
||||||
"twemoji-parser": "14.0.0",
|
"twemoji-parser": "14.0.0",
|
||||||
"typeorm": "0.3.17",
|
"typeorm": "0.3.17",
|
||||||
"ulid": "2.3.0",
|
"ulid": "2.3.0",
|
||||||
"uuid": "9.0.0",
|
"uuid": "9.0.0",
|
||||||
"web-push": "3.6.3",
|
"web-push": "3.6.4",
|
||||||
"websocket": "1.0.34",
|
"websocket": "1.0.34",
|
||||||
"xev": "3.0.2"
|
"xev": "3.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@swc/cli": "^0.1.62",
|
"@swc/cli": "^0.1.62",
|
||||||
"@swc/core": "^1.3.68",
|
"@swc/core": "^1.3.75",
|
||||||
"@types/adm-zip": "^0.5.0",
|
"@types/adm-zip": "^0.5.0",
|
||||||
"@types/bcryptjs": "2.4.2",
|
"@types/bcryptjs": "2.4.2",
|
||||||
"@types/cbor": "6.0.0",
|
"@types/cbor": "6.0.0",
|
||||||
|
@ -156,7 +156,7 @@
|
||||||
"@types/jsdom": "21.1.1",
|
"@types/jsdom": "21.1.1",
|
||||||
"@types/jsonld": "1.5.9",
|
"@types/jsonld": "1.5.9",
|
||||||
"@types/jsrsasign": "10.5.8",
|
"@types/jsrsasign": "10.5.8",
|
||||||
"@types/koa": "2.13.6",
|
"@types/koa": "2.13.8",
|
||||||
"@types/koa-bodyparser": "4.3.10",
|
"@types/koa-bodyparser": "4.3.10",
|
||||||
"@types/koa-cors": "0.0.2",
|
"@types/koa-cors": "0.0.2",
|
||||||
"@types/koa-favicon": "2.0.21",
|
"@types/koa-favicon": "2.0.21",
|
||||||
|
@ -170,7 +170,7 @@
|
||||||
"@types/mocha": "9.1.1",
|
"@types/mocha": "9.1.1",
|
||||||
"@types/node": "18.11.18",
|
"@types/node": "18.11.18",
|
||||||
"@types/node-fetch": "3.0.3",
|
"@types/node-fetch": "3.0.3",
|
||||||
"@types/nodemailer": "6.4.8",
|
"@types/nodemailer": "6.4.9",
|
||||||
"@types/oauth": "0.9.1",
|
"@types/oauth": "0.9.1",
|
||||||
"@types/probe-image-size": "^7.2.0",
|
"@types/probe-image-size": "^7.2.0",
|
||||||
"@types/pug": "2.0.6",
|
"@types/pug": "2.0.6",
|
||||||
|
@ -191,7 +191,7 @@
|
||||||
"@types/websocket": "1.0.5",
|
"@types/websocket": "1.0.5",
|
||||||
"@types/ws": "8.5.5",
|
"@types/ws": "8.5.5",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"eslint": "^8.44.0",
|
"eslint": "^8.46.0",
|
||||||
"execa": "6.1.0",
|
"execa": "6.1.0",
|
||||||
"json5-loader": "4.0.1",
|
"json5-loader": "4.0.1",
|
||||||
"mocha": "10.2.0",
|
"mocha": "10.2.0",
|
||||||
|
@ -202,7 +202,7 @@
|
||||||
"ts-node": "10.9.1",
|
"ts-node": "10.9.1",
|
||||||
"tsconfig-paths": "4.2.0",
|
"tsconfig-paths": "4.2.0",
|
||||||
"typescript": "5.1.6",
|
"typescript": "5.1.6",
|
||||||
"webpack": "^5.88.1",
|
"webpack": "^5.88.2",
|
||||||
"ws": "8.13.0"
|
"ws": "8.13.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -226,6 +226,14 @@ export default hasConfig
|
||||||
return null;
|
return null;
|
||||||
} else if (term.startsWith("domain:")) {
|
} else if (term.startsWith("domain:")) {
|
||||||
const domain = term.slice(7);
|
const domain = term.slice(7);
|
||||||
|
if (
|
||||||
|
domain.length === 0 ||
|
||||||
|
domain === "local" ||
|
||||||
|
domain === config.hostname
|
||||||
|
) {
|
||||||
|
constructedFilters.push("userHost NOT EXISTS");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
constructedFilters.push(`userHost = ${domain}`);
|
constructedFilters.push(`userHost = ${domain}`);
|
||||||
return null;
|
return null;
|
||||||
} else if (term.startsWith("after:")) {
|
} else if (term.startsWith("after:")) {
|
||||||
|
|
|
@ -235,6 +235,21 @@ export async function createPerson(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let notesCount: number | undefined;
|
||||||
|
|
||||||
|
if (typeof person.outbox === "string") {
|
||||||
|
try {
|
||||||
|
let data = await fetch(person.outbox, {
|
||||||
|
headers: { Accept: "application/json" },
|
||||||
|
});
|
||||||
|
let json_data = JSON.parse(await data.text());
|
||||||
|
|
||||||
|
notesCount = json_data.totalItems;
|
||||||
|
} catch (e) {
|
||||||
|
notesCount = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create user
|
// Create user
|
||||||
let user: IRemoteUser;
|
let user: IRemoteUser;
|
||||||
try {
|
try {
|
||||||
|
@ -278,6 +293,14 @@ export async function createPerson(
|
||||||
isCollectionOrOrderedCollection(person.following)
|
isCollectionOrOrderedCollection(person.following)
|
||||||
? person.following.totalItems
|
? person.following.totalItems
|
||||||
: undefined,
|
: undefined,
|
||||||
|
notesCount:
|
||||||
|
notesCount !== undefined
|
||||||
|
? notesCount
|
||||||
|
: person.outbox &&
|
||||||
|
typeof person.outbox !== "string" &&
|
||||||
|
isCollectionOrOrderedCollection(person.outbox)
|
||||||
|
? person.outbox.totalItems
|
||||||
|
: undefined,
|
||||||
featured: person.featured ? getApId(person.featured) : undefined,
|
featured: person.featured ? getApId(person.featured) : undefined,
|
||||||
uri: person.id,
|
uri: person.id,
|
||||||
tags,
|
tags,
|
||||||
|
@ -480,6 +503,21 @@ export async function updatePerson(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let notesCount: number | undefined;
|
||||||
|
|
||||||
|
if (typeof person.outbox === "string") {
|
||||||
|
try {
|
||||||
|
let data = await fetch(person.outbox, {
|
||||||
|
headers: { Accept: "application/json" },
|
||||||
|
});
|
||||||
|
let json_data = JSON.parse(await data.text());
|
||||||
|
|
||||||
|
notesCount = json_data.totalItems;
|
||||||
|
} catch (e) {
|
||||||
|
notesCount = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const updates = {
|
const updates = {
|
||||||
lastFetchedAt: new Date(),
|
lastFetchedAt: new Date(),
|
||||||
inbox: person.inbox,
|
inbox: person.inbox,
|
||||||
|
@ -503,6 +541,14 @@ export async function updatePerson(
|
||||||
isCollectionOrOrderedCollection(person.following)
|
isCollectionOrOrderedCollection(person.following)
|
||||||
? person.following.totalItems
|
? person.following.totalItems
|
||||||
: undefined,
|
: undefined,
|
||||||
|
notesCount:
|
||||||
|
notesCount !== undefined
|
||||||
|
? notesCount
|
||||||
|
: person.outbox &&
|
||||||
|
typeof person.outbox !== "string" &&
|
||||||
|
isCollectionOrOrderedCollection(person.outbox)
|
||||||
|
? person.outbox.totalItems
|
||||||
|
: undefined,
|
||||||
featured: person.featured,
|
featured: person.featured,
|
||||||
emojis: emojiNames,
|
emojis: emojiNames,
|
||||||
name: truncate(person.name, nameLength),
|
name: truncate(person.name, nameLength),
|
||||||
|
@ -573,7 +619,7 @@ export async function updatePerson(
|
||||||
{
|
{
|
||||||
followerSharedInbox:
|
followerSharedInbox:
|
||||||
person.sharedInbox ||
|
person.sharedInbox ||
|
||||||
(person.endpoints ? person.endpoints.sharedInbox : undefined),
|
(person.endpoints ? person.endpoints.sharedInbox : null),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -682,7 +728,7 @@ export async function updateFeatured(userId: User["id"], resolver?: Resolver) {
|
||||||
? collection.items
|
? collection.items
|
||||||
: collection.orderedItems;
|
: collection.orderedItems;
|
||||||
const items = await Promise.all(
|
const items = await Promise.all(
|
||||||
toArray(unresolvedItems).map((x) => resolver.resolve(x)),
|
toArray(unresolvedItems).map((x) => resolver?.resolve(x)),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Resolve and regist Notes
|
// Resolve and regist Notes
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import define from "../../../define.js";
|
import define from "../../../define.js";
|
||||||
import { createImportCustomEmojisJob } from "@/queue/index.js";
|
import { createImportCustomEmojisJob } from "@/queue/index.js";
|
||||||
import ms from "ms";
|
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
secure: true,
|
secure: true,
|
||||||
|
|
|
@ -26,16 +26,15 @@ export const paramDef = {
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export default define(meta, paramDef, async (ps) => {
|
export default define(meta, paramDef, async (ps) => {
|
||||||
const worker = createWorker({
|
const worker = await createWorker();
|
||||||
logger: (m) => console.log(m),
|
|
||||||
});
|
|
||||||
|
|
||||||
await worker.load();
|
|
||||||
await worker.loadLanguage("eng");
|
await worker.loadLanguage("eng");
|
||||||
await worker.initialize("eng");
|
await worker.initialize("eng");
|
||||||
const {
|
const {
|
||||||
data: { text },
|
data: { text },
|
||||||
} = await worker.recognize(ps.url);
|
} = await worker.recognize(ps.url, {
|
||||||
|
rotateAuto: true,
|
||||||
|
});
|
||||||
await worker.terminate();
|
await worker.terminate();
|
||||||
|
|
||||||
return text;
|
return text;
|
||||||
|
|
|
@ -85,6 +85,7 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
|
|
||||||
query
|
query
|
||||||
.andWhere("note.text ILIKE :q", { q: `%${sqlLikeEscape(ps.query)}%` })
|
.andWhere("note.text ILIKE :q", { q: `%${sqlLikeEscape(ps.query)}%` })
|
||||||
|
.andWhere("note.visibility = 'public'")
|
||||||
.innerJoinAndSelect("note.user", "user")
|
.innerJoinAndSelect("note.user", "user")
|
||||||
.leftJoinAndSelect("user.avatar", "avatar")
|
.leftJoinAndSelect("user.avatar", "avatar")
|
||||||
.leftJoinAndSelect("user.banner", "banner")
|
.leftJoinAndSelect("user.banner", "banner")
|
||||||
|
|
|
@ -147,7 +147,7 @@
|
||||||
<span class="button-label-big">Refresh</span>
|
<span class="button-label-big">Refresh</span>
|
||||||
</button>
|
</button>
|
||||||
<p class="dont-worry">Don't worry, it's (probably) not your fault.</p>
|
<p class="dont-worry">Don't worry, it's (probably) not your fault.</p>
|
||||||
<p>Please make sure your browser is up-to-date and any AdBlockers are off.</p>
|
<p>Please make sure your browser is up-to-date and any AdBlockers are off (given they can sometimes errouniously interfere with loading assets).</p>
|
||||||
<p>If the problem persists after refreshing, please contact your instance's administrator.<br>You may also try the following options:</p>
|
<p>If the problem persists after refreshing, please contact your instance's administrator.<br>You may also try the following options:</p>
|
||||||
<a href="/flush">
|
<a href="/flush">
|
||||||
<button class="button-small">
|
<button class="button-small">
|
||||||
|
|
|
@ -892,7 +892,7 @@ async function insertNote(
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function index(note: Note, reindexing: boolean): Promise<void> {
|
export async function index(note: Note, reindexing: boolean): Promise<void> {
|
||||||
if (!note.text) return;
|
if (!note.text || note.visibility !== "public") return;
|
||||||
|
|
||||||
if (config.elasticsearch && es) {
|
if (config.elasticsearch && es) {
|
||||||
es.index({
|
es.index({
|
||||||
|
|
|
@ -36,13 +36,13 @@
|
||||||
"blurhash": "2.0.5",
|
"blurhash": "2.0.5",
|
||||||
"broadcast-channel": "5.1.0",
|
"broadcast-channel": "5.1.0",
|
||||||
"browser-image-resizer": "github:misskey-dev/browser-image-resizer",
|
"browser-image-resizer": "github:misskey-dev/browser-image-resizer",
|
||||||
"chart.js": "4.3.2",
|
"chart.js": "4.3.3",
|
||||||
"chartjs-adapter-date-fns": "3.0.0",
|
"chartjs-adapter-date-fns": "3.0.0",
|
||||||
"chartjs-chart-matrix": "^2.0.1",
|
"chartjs-chart-matrix": "^2.0.1",
|
||||||
"chartjs-plugin-gradient": "0.6.1",
|
"chartjs-plugin-gradient": "0.6.1",
|
||||||
"chartjs-plugin-zoom": "2.0.1",
|
"chartjs-plugin-zoom": "2.0.1",
|
||||||
"city-timezones": "^1.2.1",
|
"city-timezones": "^1.2.1",
|
||||||
"compare-versions": "6.0.0",
|
"compare-versions": "6.1.0",
|
||||||
"cropperjs": "2.0.0-beta.2",
|
"cropperjs": "2.0.0-beta.2",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"cypress": "10.11.0",
|
"cypress": "10.11.0",
|
||||||
|
@ -65,20 +65,20 @@
|
||||||
"mfm-js": "0.23.3",
|
"mfm-js": "0.23.3",
|
||||||
"paralint": "^1.2.1",
|
"paralint": "^1.2.1",
|
||||||
"photoswipe": "5.3.8",
|
"photoswipe": "5.3.8",
|
||||||
"prettier": "3.0.0",
|
"prettier": "3.0.1",
|
||||||
"prettier-plugin-vue": "1.1.6",
|
"prettier-plugin-vue": "1.1.6",
|
||||||
"prismjs": "1.29.0",
|
"prismjs": "1.29.0",
|
||||||
"punycode": "2.3.0",
|
"punycode": "2.3.0",
|
||||||
"querystring": "0.2.1",
|
"querystring": "0.2.1",
|
||||||
"rndstr": "1.0.0",
|
"rndstr": "1.0.0",
|
||||||
"rollup": "3.27.0",
|
"rollup": "3.27.2",
|
||||||
"s-age": "1.1.2",
|
"s-age": "1.1.2",
|
||||||
"sass": "1.64.1",
|
"sass": "1.64.2",
|
||||||
"seedrandom": "3.0.5",
|
"seedrandom": "3.0.5",
|
||||||
"start-server-and-test": "1.15.2",
|
"start-server-and-test": "1.15.2",
|
||||||
"strict-event-emitter-types": "2.0.0",
|
"strict-event-emitter-types": "2.0.0",
|
||||||
"stringz": "2.1.0",
|
"stringz": "2.1.0",
|
||||||
"swiper": "10.0.4",
|
"swiper": "10.1.0",
|
||||||
"syuilo-password-strength": "0.0.1",
|
"syuilo-password-strength": "0.0.1",
|
||||||
"textarea-caret": "3.1.0",
|
"textarea-caret": "3.1.0",
|
||||||
"three": "0.146.0",
|
"three": "0.146.0",
|
||||||
|
@ -91,10 +91,10 @@
|
||||||
"unicode-emoji-json": "^0.4.0",
|
"unicode-emoji-json": "^0.4.0",
|
||||||
"uuid": "9.0.0",
|
"uuid": "9.0.0",
|
||||||
"vanilla-tilt": "1.8.0",
|
"vanilla-tilt": "1.8.0",
|
||||||
"vite": "4.4.7",
|
"vite": "4.4.9",
|
||||||
"vite-plugin-compression": "^0.5.1",
|
"vite-plugin-compression": "^0.5.1",
|
||||||
"vue": "3.3.4",
|
"vue": "3.3.4",
|
||||||
"vue-draggable-plus": "^0.2.2",
|
"vue-draggable-plus": "^0.2.4",
|
||||||
"vue-isyourpasswordsafe": "^2.0.0",
|
"vue-isyourpasswordsafe": "^2.0.0",
|
||||||
"vue-plyr": "^7.0.0",
|
"vue-plyr": "^7.0.0",
|
||||||
"vue-prism-editor": "2.0.0-alpha.2"
|
"vue-prism-editor": "2.0.0-alpha.2"
|
||||||
|
|
|
@ -65,10 +65,11 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
import MkButton from "@/components/MkButton.vue";
|
import MkButton from "@/components/MkButton.vue";
|
||||||
import MkSwitch from "@/components/form/switch.vue";
|
import MkSwitch from "@/components/form/switch.vue";
|
||||||
import MkKeyValue from "@/components/MkKeyValue.vue";
|
import MkKeyValue from "@/components/MkKeyValue.vue";
|
||||||
import { acct, userPage } from "@/filters/user";
|
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
|
||||||
|
@ -80,11 +81,11 @@ const emit = defineEmits<{
|
||||||
(ev: "resolved", reportId: string): void;
|
(ev: "resolved", reportId: string): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const forward = $ref(props.report.forwarded);
|
const forward = ref(props.report.forwarded);
|
||||||
|
|
||||||
function resolve() {
|
function resolve() {
|
||||||
os.apiWithDialog("admin/resolve-abuse-user-report", {
|
os.apiWithDialog("admin/resolve-abuse-user-report", {
|
||||||
forward,
|
forward: forward.value,
|
||||||
reportId: props.report.id,
|
reportId: props.report.id,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
emit("resolved", props.report.id);
|
emit("resolved", props.report.id);
|
||||||
|
|
|
@ -108,14 +108,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {
|
import { computed, onBeforeUnmount, onMounted, ref } from "vue";
|
||||||
computed,
|
|
||||||
nextTick,
|
|
||||||
onBeforeUnmount,
|
|
||||||
onMounted,
|
|
||||||
ref,
|
|
||||||
shallowRef,
|
|
||||||
} from "vue";
|
|
||||||
import tinycolor from "tinycolor2";
|
import tinycolor from "tinycolor2";
|
||||||
import { globalEvents } from "@/events.js";
|
import { globalEvents } from "@/events.js";
|
||||||
|
|
||||||
|
@ -174,19 +167,19 @@ const texts = computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
let enabled = true,
|
let enabled = true,
|
||||||
majorGraduationColor = $ref<string>(),
|
majorGraduationColor = ref<string>(),
|
||||||
// let minorGraduationColor = $ref<string>();
|
// let minorGraduationColor = $ref<string>();
|
||||||
sHandColor = $ref<string>(),
|
sHandColor = ref<string>(),
|
||||||
mHandColor = $ref<string>(),
|
mHandColor = ref<string>(),
|
||||||
hHandColor = $ref<string>(),
|
hHandColor = ref<string>(),
|
||||||
nowColor = $ref<string>(),
|
nowColor = ref<string>(),
|
||||||
h = $ref<number>(0),
|
h = ref<number>(0),
|
||||||
m = $ref<number>(0),
|
m = ref<number>(0),
|
||||||
s = $ref<number>(0),
|
s = ref<number>(0),
|
||||||
hAngle = $ref<number>(0),
|
hAngle = ref<number>(0),
|
||||||
mAngle = $ref<number>(0),
|
mAngle = ref<number>(0),
|
||||||
sAngle = $ref<number>(0),
|
sAngle = ref<number>(0),
|
||||||
disableSAnimate = $ref(false),
|
disableSAnimate = ref(false),
|
||||||
sOneRound = false;
|
sOneRound = false;
|
||||||
|
|
||||||
function tick() {
|
function tick() {
|
||||||
|
@ -194,29 +187,31 @@ function tick() {
|
||||||
now.setMinutes(
|
now.setMinutes(
|
||||||
now.getMinutes() + (new Date().getTimezoneOffset() + props.offset),
|
now.getMinutes() + (new Date().getTimezoneOffset() + props.offset),
|
||||||
);
|
);
|
||||||
s = now.getSeconds();
|
s.value = now.getSeconds();
|
||||||
m = now.getMinutes();
|
m.value = now.getMinutes();
|
||||||
h = now.getHours();
|
h.value = now.getHours();
|
||||||
hAngle =
|
hAngle.value =
|
||||||
(Math.PI * ((h % (props.twentyfour ? 24 : 12)) + (m + s / 60) / 60)) /
|
(Math.PI *
|
||||||
|
((h.value % (props.twentyfour ? 24 : 12)) +
|
||||||
|
(m.value + s.value / 60) / 60)) /
|
||||||
(props.twentyfour ? 12 : 6);
|
(props.twentyfour ? 12 : 6);
|
||||||
mAngle = (Math.PI * (m + s / 60)) / 30;
|
mAngle.value = (Math.PI * (m.value + s.value / 60)) / 30;
|
||||||
if (sOneRound) {
|
if (sOneRound) {
|
||||||
// 秒針が一周した際のアニメーションをよしなに処理する(これが無いと秒が59->0になったときに期待したアニメーションにならない)
|
// 秒針が一周した際のアニメーションをよしなに処理する(これが無いと秒が59->0になったときに期待したアニメーションにならない)
|
||||||
sAngle = (Math.PI * 60) / 30;
|
sAngle.value = (Math.PI * 60) / 30;
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
disableSAnimate = true;
|
disableSAnimate.value = true;
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
sAngle = 0;
|
sAngle.value = 0;
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
disableSAnimate = false;
|
disableSAnimate.value = false;
|
||||||
}, 100);
|
}, 100);
|
||||||
}, 100);
|
}, 100);
|
||||||
}, 700);
|
}, 700);
|
||||||
} else {
|
} else {
|
||||||
sAngle = (Math.PI * s) / 30;
|
sAngle.value = (Math.PI * s.value) / 30;
|
||||||
}
|
}
|
||||||
sOneRound = s === 59;
|
sOneRound = s.value === 59;
|
||||||
}
|
}
|
||||||
|
|
||||||
tick();
|
tick();
|
||||||
|
@ -227,16 +222,16 @@ function calcColors() {
|
||||||
const accent = tinycolor(
|
const accent = tinycolor(
|
||||||
computedStyle.getPropertyValue("--accent"),
|
computedStyle.getPropertyValue("--accent"),
|
||||||
).toHexString();
|
).toHexString();
|
||||||
majorGraduationColor = dark
|
majorGraduationColor.value = dark
|
||||||
? "rgba(255, 255, 255, 0.3)"
|
? "rgba(255, 255, 255, 0.3)"
|
||||||
: "rgba(0, 0, 0, 0.3)";
|
: "rgba(0, 0, 0, 0.3)";
|
||||||
// minorGraduationColor = dark ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
|
// minorGraduationColor = dark ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
|
||||||
sHandColor = dark ? "rgba(255, 255, 255, 0.5)" : "rgba(0, 0, 0, 0.3)";
|
sHandColor.value = dark ? "rgba(255, 255, 255, 0.5)" : "rgba(0, 0, 0, 0.3)";
|
||||||
mHandColor = tinycolor(
|
mHandColor.value = tinycolor(
|
||||||
computedStyle.getPropertyValue("--fg"),
|
computedStyle.getPropertyValue("--fg"),
|
||||||
).toHexString();
|
).toHexString();
|
||||||
hHandColor = accent;
|
hHandColor.value = accent;
|
||||||
nowColor = accent;
|
nowColor.value = accent;
|
||||||
}
|
}
|
||||||
|
|
||||||
calcColors();
|
calcColors();
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { nextTick, onMounted } from "vue";
|
import { nextTick, onMounted, ref } from "vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
type?: "button" | "submit" | "reset";
|
type?: "button" | "submit" | "reset";
|
||||||
|
@ -49,13 +49,13 @@ const emit = defineEmits<{
|
||||||
(ev: "click", payload: MouseEvent): void;
|
(ev: "click", payload: MouseEvent): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const el = $ref<HTMLElement | null>(null);
|
const el = ref<HTMLElement | null>(null);
|
||||||
const ripples = $ref<HTMLElement | null>(null);
|
const ripples = ref<HTMLElement | null>(null);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (props.autofocus) {
|
if (props.autofocus) {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
el!.focus();
|
el.value!.focus();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -81,7 +81,7 @@ function onMousedown(evt: MouseEvent): void {
|
||||||
ripple.style.top = (evt.clientY - rect.top - 1).toString() + "px";
|
ripple.style.top = (evt.clientY - rect.top - 1).toString() + "px";
|
||||||
ripple.style.left = (evt.clientX - rect.left - 1).toString() + "px";
|
ripple.style.left = (evt.clientX - rect.left - 1).toString() + "px";
|
||||||
|
|
||||||
ripples!.appendChild(ripple);
|
ripples.value!.appendChild(ripple);
|
||||||
|
|
||||||
const circleCenterX = evt.clientX - rect.left;
|
const circleCenterX = evt.clientX - rect.left;
|
||||||
const circleCenterY = evt.clientY - rect.top;
|
const circleCenterY = evt.clientY - rect.top;
|
||||||
|
@ -101,7 +101,7 @@ function onMousedown(evt: MouseEvent): void {
|
||||||
ripple.style.opacity = "0";
|
ripple.style.opacity = "0";
|
||||||
}, 1000);
|
}, 1000);
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
if (ripples) ripples.removeChild(ripple);
|
if (ripples.value) ripples.value.removeChild(ripple);
|
||||||
}, 2000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { PropType } from "vue";
|
import type { PropType } from "vue";
|
||||||
import { onMounted, onUnmounted, ref, watch } from "vue";
|
import { onMounted, ref, watch } from "vue";
|
||||||
import {
|
import {
|
||||||
ArcElement,
|
ArcElement,
|
||||||
BarController,
|
BarController,
|
||||||
|
@ -183,9 +183,9 @@ const render = () => {
|
||||||
document.documentElement,
|
document.documentElement,
|
||||||
).getPropertyValue("--fg");
|
).getPropertyValue("--fg");
|
||||||
|
|
||||||
const maxes = chartData.series.map((x, i) =>
|
// const maxes = chartData.series.map((x, i) =>
|
||||||
Math.max(...x.data.map((d) => d.y)),
|
// Math.max(...x.data.map((d) => d.y)),
|
||||||
);
|
// );
|
||||||
|
|
||||||
chartInstance = new Chart(chartEl.value, {
|
chartInstance = new Chart(chartEl.value, {
|
||||||
type: props.bar ? "bar" : "line",
|
type: props.bar ? "bar" : "line",
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
import XModalWindow from "@/components/MkModalWindow.vue";
|
import XModalWindow from "@/components/MkModalWindow.vue";
|
||||||
import XCheatSheet from "@/pages/mfm-cheat-sheet.vue";
|
import XCheatSheet from "@/pages/mfm-cheat-sheet.vue";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
@ -20,11 +22,7 @@ const emit = defineEmits<{
|
||||||
(ev: "closed"): void;
|
(ev: "closed"): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const dialog = $ref<InstanceType<typeof XModalWindow>>();
|
const dialog = ref<InstanceType<typeof XModalWindow>>();
|
||||||
|
|
||||||
function close(res) {
|
|
||||||
dialog.close();
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
@ -12,9 +12,9 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onBeforeUnmount, onMounted } from "vue";
|
import { onBeforeUnmount, onMounted, ref } from "vue";
|
||||||
import MkMenu from "./MkMenu.vue";
|
import MkMenu from "@/components/MkMenu.vue";
|
||||||
import type { MenuItem } from "./types/menu.vue";
|
import type { MenuItem } from "@/types/menu";
|
||||||
import contains from "@/scripts/contains";
|
import contains from "@/scripts/contains";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
|
|
||||||
|
@ -27,16 +27,16 @@ const emit = defineEmits<{
|
||||||
(ev: "closed"): void;
|
(ev: "closed"): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const rootEl = $ref<HTMLDivElement>();
|
const rootEl = ref<HTMLDivElement>();
|
||||||
|
|
||||||
const zIndex = $ref<number>(os.claimZIndex("high"));
|
const zIndex = ref<number>(os.claimZIndex("high"));
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
let left = props.ev.pageX + 1, // 間違って右ダブルクリックした場合に意図せずアイテムがクリックされるのを防ぐため + 1
|
let left = props.ev.pageX + 1, // 間違って右ダブルクリックした場合に意図せずアイテムがクリックされるのを防ぐため + 1
|
||||||
top = props.ev.pageY + 1; // 間違って右ダブルクリックした場合に意図せずアイテムがクリックされるのを防ぐため + 1
|
top = props.ev.pageY + 1; // 間違って右ダブルクリックした場合に意図せずアイテムがクリックされるのを防ぐため + 1
|
||||||
|
|
||||||
const width = rootEl.offsetWidth;
|
const width = rootEl.value.offsetWidth;
|
||||||
const height = rootEl.offsetHeight;
|
const height = rootEl.value.offsetHeight;
|
||||||
|
|
||||||
if (left + width - window.pageXOffset > window.innerWidth) {
|
if (left + width - window.pageXOffset > window.innerWidth) {
|
||||||
left = window.innerWidth - width + window.pageXOffset;
|
left = window.innerWidth - width + window.pageXOffset;
|
||||||
|
@ -54,8 +54,8 @@ onMounted(() => {
|
||||||
left = 0;
|
left = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
rootEl.style.top = `${top}px`;
|
rootEl.value.style.top = `${top}px`;
|
||||||
rootEl.style.left = `${left}px`;
|
rootEl.value.style.left = `${left}px`;
|
||||||
|
|
||||||
document.body.addEventListener("mousedown", onMousedown);
|
document.body.addEventListener("mousedown", onMousedown);
|
||||||
});
|
});
|
||||||
|
@ -65,7 +65,8 @@ onBeforeUnmount(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
function onMousedown(evt: Event) {
|
function onMousedown(evt: Event) {
|
||||||
if (!contains(rootEl, evt.target) && rootEl !== evt.target) emit("closed");
|
if (!contains(rootEl.value, evt.target) && rootEl.value !== evt.target)
|
||||||
|
emit("closed");
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { nextTick, onMounted } from "vue";
|
import { onMounted, ref } from "vue";
|
||||||
import type * as misskey from "firefish-js";
|
import type * as misskey from "firefish-js";
|
||||||
import Cropper from "cropperjs";
|
import Cropper from "cropperjs";
|
||||||
import tinycolor from "tinycolor2";
|
import tinycolor from "tinycolor2";
|
||||||
|
@ -62,10 +62,10 @@ const props = defineProps<{
|
||||||
const imgUrl = `${url}/proxy/image.webp?${query({
|
const imgUrl = `${url}/proxy/image.webp?${query({
|
||||||
url: props.file.url,
|
url: props.file.url,
|
||||||
})}`;
|
})}`;
|
||||||
const dialogEl = $ref<InstanceType<typeof XModalWindow>>();
|
const dialogEl = ref<InstanceType<typeof XModalWindow>>();
|
||||||
const imgEl = $ref<HTMLImageElement>();
|
const imgEl = ref<HTMLImageElement>();
|
||||||
let cropper: Cropper | null = null,
|
let cropper: Cropper | null = null,
|
||||||
loading = $ref(true);
|
loading = ref(true);
|
||||||
|
|
||||||
const ok = async () => {
|
const ok = async () => {
|
||||||
const promise = new Promise<misskey.entities.DriveFile>(async (res) => {
|
const promise = new Promise<misskey.entities.DriveFile>(async (res) => {
|
||||||
|
@ -96,16 +96,16 @@ const ok = async () => {
|
||||||
const f = await promise;
|
const f = await promise;
|
||||||
|
|
||||||
emit("ok", f);
|
emit("ok", f);
|
||||||
dialogEl.close();
|
dialogEl.value.close();
|
||||||
};
|
};
|
||||||
|
|
||||||
const cancel = () => {
|
const cancel = () => {
|
||||||
emit("cancel");
|
emit("cancel");
|
||||||
dialogEl.close();
|
dialogEl.value.close();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onImageLoad = () => {
|
const onImageLoad = () => {
|
||||||
loading = false;
|
loading.value = false;
|
||||||
|
|
||||||
if (cropper) {
|
if (cropper) {
|
||||||
cropper.getCropperImage()!.$center("contain");
|
cropper.getCropperImage()!.$center("contain");
|
||||||
|
@ -114,7 +114,7 @@ const onImageLoad = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
cropper = new Cropper(imgEl, {});
|
cropper = new Cropper(imgEl.value, {});
|
||||||
|
|
||||||
const computedStyle = getComputedStyle(document.documentElement);
|
const computedStyle = getComputedStyle(document.documentElement);
|
||||||
|
|
||||||
|
|
|
@ -199,7 +199,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onBeforeUnmount, onMounted, ref, shallowRef } from "vue";
|
import { onBeforeUnmount, onMounted, ref, shallowRef, computed } from "vue";
|
||||||
import * as Acct from "firefish-js/built/acct";
|
import * as Acct from "firefish-js/built/acct";
|
||||||
import MkModal from "@/components/MkModal.vue";
|
import MkModal from "@/components/MkModal.vue";
|
||||||
import MkButton from "@/components/MkButton.vue";
|
import MkButton from "@/components/MkButton.vue";
|
||||||
|
@ -281,17 +281,15 @@ const modal = shallowRef<InstanceType<typeof MkModal>>();
|
||||||
const inputValue = ref<string | number | null>(props.input?.default ?? null);
|
const inputValue = ref<string | number | null>(props.input?.default ?? null);
|
||||||
const selectedValue = ref(props.select?.default ?? null);
|
const selectedValue = ref(props.select?.default ?? null);
|
||||||
|
|
||||||
let disabledReason = $ref<null | "charactersExceeded" | "charactersBelow">(
|
let disabledReason = ref<null | "charactersExceeded" | "charactersBelow">(null);
|
||||||
null,
|
const okButtonDisabled = computed<boolean>(() => {
|
||||||
);
|
|
||||||
const okButtonDisabled = $computed<boolean>(() => {
|
|
||||||
if (props.input) {
|
if (props.input) {
|
||||||
if (props.input.minLength) {
|
if (props.input.minLength) {
|
||||||
if (
|
if (
|
||||||
(inputValue.value || inputValue.value === "") &&
|
(inputValue.value || inputValue.value === "") &&
|
||||||
(inputValue.value as string).length < props.input.minLength
|
(inputValue.value as string).length < props.input.minLength
|
||||||
) {
|
) {
|
||||||
disabledReason = "charactersBelow";
|
disabledReason.value = "charactersBelow";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -300,7 +298,7 @@ const okButtonDisabled = $computed<boolean>(() => {
|
||||||
inputValue.value &&
|
inputValue.value &&
|
||||||
(inputValue.value as string).length > props.input.maxLength
|
(inputValue.value as string).length > props.input.maxLength
|
||||||
) {
|
) {
|
||||||
disabledReason = "charactersExceeded";
|
disabledReason.value = "charactersExceeded";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -198,10 +198,6 @@ function onDragend() {
|
||||||
emit("dragend");
|
emit("dragend");
|
||||||
}
|
}
|
||||||
|
|
||||||
function go() {
|
|
||||||
emit("move", props.folder.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
function rename() {
|
function rename() {
|
||||||
os.inputText({
|
os.inputText({
|
||||||
title: i18n.ts.renameFolder,
|
title: i18n.ts.renameFolder,
|
||||||
|
|
|
@ -42,14 +42,6 @@ function onClick() {
|
||||||
emit("move", props.folder);
|
emit("move", props.folder);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onMouseover() {
|
|
||||||
hover.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onMouseout() {
|
|
||||||
hover.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onDragover(ev: DragEvent) {
|
function onDragover(ev: DragEvent) {
|
||||||
if (!ev.dataTransfer) return;
|
if (!ev.dataTransfer) return;
|
||||||
|
|
||||||
|
|
|
@ -69,7 +69,7 @@
|
||||||
@dragend="isDragSource = false"
|
@dragend="isDragSource = false"
|
||||||
/>
|
/>
|
||||||
<!-- SEE: https://stackoverflow.com/questions/18744164/flex-box-align-last-row-to-grid -->
|
<!-- SEE: https://stackoverflow.com/questions/18744164/flex-box-align-last-row-to-grid -->
|
||||||
<div v-for="(n, i) in 16" :key="i" class="padding"></div>
|
<div v-for="(_, i) in 16" :key="i" class="padding"></div>
|
||||||
<MkButton v-if="moreFolders" ref="moreFolders">{{
|
<MkButton v-if="moreFolders" ref="moreFolders">{{
|
||||||
i18n.ts.loadMore
|
i18n.ts.loadMore
|
||||||
}}</MkButton>
|
}}</MkButton>
|
||||||
|
@ -94,7 +94,7 @@
|
||||||
@dragend="isDragSource = false"
|
@dragend="isDragSource = false"
|
||||||
/>
|
/>
|
||||||
<!-- SEE: https://stackoverflow.com/questions/18744164/flex-box-align-last-row-to-grid -->
|
<!-- SEE: https://stackoverflow.com/questions/18744164/flex-box-align-last-row-to-grid -->
|
||||||
<div v-for="(n, i) in 16" :key="i" class="padding"></div>
|
<div v-for="(_, i) in 16" :key="i" class="padding"></div>
|
||||||
<MkButton
|
<MkButton
|
||||||
v-show="moreFiles"
|
v-show="moreFiles"
|
||||||
ref="loadMoreFiles"
|
ref="loadMoreFiles"
|
||||||
|
@ -132,7 +132,6 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {
|
import {
|
||||||
markRaw,
|
|
||||||
nextTick,
|
nextTick,
|
||||||
onActivated,
|
onActivated,
|
||||||
onBeforeUnmount,
|
onBeforeUnmount,
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, onMounted, onBeforeUnmount } from "vue";
|
import { ref } from "vue";
|
||||||
import MkModal from "@/components/MkModal.vue";
|
import MkModal from "@/components/MkModal.vue";
|
||||||
import MkEmojiPicker from "@/components/MkEmojiPicker.vue";
|
import MkEmojiPicker from "@/components/MkEmojiPicker.vue";
|
||||||
import { defaultStore } from "@/store";
|
import { defaultStore } from "@/store";
|
||||||
|
|
|
@ -52,13 +52,10 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from "vue";
|
|
||||||
import * as Acct from "firefish-js/built/acct";
|
import * as Acct from "firefish-js/built/acct";
|
||||||
import MkSwitch from "@/components/ui/switch.vue";
|
|
||||||
import MkPagination from "@/components/MkPagination.vue";
|
import MkPagination from "@/components/MkPagination.vue";
|
||||||
import MkDriveFileThumbnail from "@/components/MkDriveFileThumbnail.vue";
|
import MkDriveFileThumbnail from "@/components/MkDriveFileThumbnail.vue";
|
||||||
import bytes from "@/filters/bytes";
|
import bytes from "@/filters/bytes";
|
||||||
import * as os from "@/os";
|
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|
|
@ -61,7 +61,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, onBeforeUnmount, onMounted } from "vue";
|
import { computed, onBeforeUnmount, onMounted, ref } from "vue";
|
||||||
import type * as Misskey from "firefish-js";
|
import type * as Misskey from "firefish-js";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { stream } from "@/stream";
|
import { stream } from "@/stream";
|
||||||
|
@ -88,13 +88,13 @@ const props = withDefaults(
|
||||||
|
|
||||||
const isBlocking = computed(() => props.user.isBlocking);
|
const isBlocking = computed(() => props.user.isBlocking);
|
||||||
|
|
||||||
let state = $ref(i18n.ts.processing);
|
let state = ref(i18n.ts.processing);
|
||||||
|
|
||||||
let isFollowing = $ref(props.user.isFollowing);
|
let isFollowing = ref(props.user.isFollowing);
|
||||||
let hasPendingFollowRequestFromYou = $ref(
|
let hasPendingFollowRequestFromYou = ref(
|
||||||
props.user.hasPendingFollowRequestFromYou,
|
props.user.hasPendingFollowRequestFromYou,
|
||||||
);
|
);
|
||||||
let wait = $ref(false);
|
let wait = ref(false);
|
||||||
const connection = stream.useChannel("main");
|
const connection = stream.useChannel("main");
|
||||||
|
|
||||||
if (props.user.isFollowing == null) {
|
if (props.user.isFollowing == null) {
|
||||||
|
@ -105,13 +105,14 @@ if (props.user.isFollowing == null) {
|
||||||
|
|
||||||
function onFollowChange(user: Misskey.entities.UserDetailed) {
|
function onFollowChange(user: Misskey.entities.UserDetailed) {
|
||||||
if (user.id === props.user.id) {
|
if (user.id === props.user.id) {
|
||||||
isFollowing = user.isFollowing;
|
isFollowing.value = user.isFollowing;
|
||||||
hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou;
|
hasPendingFollowRequestFromYou.value =
|
||||||
|
user.hasPendingFollowRequestFromYou;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onClick() {
|
async function onClick() {
|
||||||
wait = true;
|
wait.value = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (isBlocking.value) {
|
if (isBlocking.value) {
|
||||||
|
@ -130,7 +131,7 @@ async function onClick() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
emit("refresh");
|
emit("refresh");
|
||||||
} else if (isFollowing) {
|
} else if (isFollowing.value) {
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: "warning",
|
type: "warning",
|
||||||
text: i18n.t("unfollowConfirm", {
|
text: i18n.t("unfollowConfirm", {
|
||||||
|
@ -144,22 +145,22 @@ async function onClick() {
|
||||||
userId: props.user.id,
|
userId: props.user.id,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (hasPendingFollowRequestFromYou) {
|
if (hasPendingFollowRequestFromYou.value) {
|
||||||
await os.api("following/requests/cancel", {
|
await os.api("following/requests/cancel", {
|
||||||
userId: props.user.id,
|
userId: props.user.id,
|
||||||
});
|
});
|
||||||
hasPendingFollowRequestFromYou = false;
|
hasPendingFollowRequestFromYou.value = false;
|
||||||
} else {
|
} else {
|
||||||
await os.api("following/create", {
|
await os.api("following/create", {
|
||||||
userId: props.user.id,
|
userId: props.user.id,
|
||||||
});
|
});
|
||||||
hasPendingFollowRequestFromYou = true;
|
hasPendingFollowRequestFromYou.value = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
} finally {
|
} finally {
|
||||||
wait = false;
|
wait.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,13 +236,13 @@ onBeforeUnmount(() => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
// &:hover {
|
||||||
// background: mix($primary, #fff, 20);
|
// background: mix($primary, #fff, 20);
|
||||||
}
|
// }
|
||||||
|
|
||||||
&:active {
|
// &:active {
|
||||||
// background: mix($primary, #fff, 40);
|
// background: mix($primary, #fff, 40);
|
||||||
}
|
// }
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
color: var(--fgOnAccent);
|
color: var(--fgOnAccent);
|
||||||
|
|
|
@ -62,6 +62,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
import {} from "vue";
|
import {} from "vue";
|
||||||
import XModalWindow from "@/components/MkModalWindow.vue";
|
import XModalWindow from "@/components/MkModalWindow.vue";
|
||||||
import MkButton from "@/components/MkButton.vue";
|
import MkButton from "@/components/MkButton.vue";
|
||||||
|
@ -75,20 +77,20 @@ const emit = defineEmits<{
|
||||||
(ev: "closed"): void;
|
(ev: "closed"): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
let dialog: InstanceType<typeof XModalWindow> = $ref();
|
let dialog: InstanceType<typeof XModalWindow> = ref();
|
||||||
|
|
||||||
let username = $ref("");
|
let username = ref("");
|
||||||
let email = $ref("");
|
let email = ref("");
|
||||||
let processing = $ref(false);
|
let processing = ref(false);
|
||||||
|
|
||||||
async function onSubmit() {
|
async function onSubmit() {
|
||||||
processing = true;
|
processing.value = true;
|
||||||
await os.apiWithDialog("request-reset-password", {
|
await os.apiWithDialog("request-reset-password", {
|
||||||
username,
|
username: username.value,
|
||||||
email,
|
email: email.value,
|
||||||
});
|
});
|
||||||
emit("done");
|
emit("done");
|
||||||
dialog.close();
|
dialog.value.close();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<MkSpacer :margin-min="20" :margin-max="32">
|
<MkSpacer :margin-min="20" :margin-max="32">
|
||||||
<div class="xkpnjxcv _formRoot">
|
<div class="_formRoot">
|
||||||
<template
|
<template
|
||||||
v-for="item in Object.keys(form).filter(
|
v-for="item in Object.keys(form).filter(
|
||||||
(item) => !form[item].hidden,
|
(item) => !form[item].hidden,
|
||||||
|
@ -221,8 +221,3 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.xkpnjxcv {
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, defineAsyncComponent } from "vue";
|
import { defineComponent, defineAsyncComponent } from "vue";
|
||||||
import * as os from "@/os";
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
|
|
|
@ -19,8 +19,6 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {} from "vue";
|
|
||||||
import { userName } from "@/filters/user";
|
|
||||||
import ImgWithBlurhash from "@/components/MkImgWithBlurhash.vue";
|
import ImgWithBlurhash from "@/components/MkImgWithBlurhash.vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|
|
@ -8,21 +8,11 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {
|
import { onMounted, nextTick, watch, shallowRef, ref } from "vue";
|
||||||
markRaw,
|
|
||||||
version as vueVersion,
|
|
||||||
onMounted,
|
|
||||||
onBeforeUnmount,
|
|
||||||
nextTick,
|
|
||||||
watch,
|
|
||||||
} from "vue";
|
|
||||||
import { Chart } from "chart.js";
|
import { Chart } from "chart.js";
|
||||||
import tinycolor from "tinycolor2";
|
|
||||||
import { MatrixController, MatrixElement } from "chartjs-chart-matrix";
|
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { defaultStore } from "@/store";
|
import { defaultStore } from "@/store";
|
||||||
import { useChartTooltip } from "@/scripts/use-chart-tooltip";
|
import { useChartTooltip } from "@/scripts/use-chart-tooltip";
|
||||||
import { chartVLine } from "@/scripts/chart-vline";
|
|
||||||
import { alpha } from "@/scripts/color";
|
import { alpha } from "@/scripts/color";
|
||||||
import { initChart } from "@/scripts/init-chart";
|
import { initChart } from "@/scripts/init-chart";
|
||||||
import { $i } from "@/account";
|
import { $i } from "@/account";
|
||||||
|
@ -33,11 +23,11 @@ const props = defineProps<{
|
||||||
src: string;
|
src: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const rootEl = $shallowRef<HTMLDivElement>(null);
|
const rootEl = shallowRef<HTMLDivElement>(null);
|
||||||
const chartEl = $shallowRef<HTMLCanvasElement>(null);
|
const chartEl = shallowRef<HTMLCanvasElement>(null);
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
let chartInstance: Chart = null;
|
let chartInstance: Chart = null;
|
||||||
let fetching = $ref(true);
|
let fetching = ref(true);
|
||||||
|
|
||||||
const { handler: externalTooltipHandler } = useChartTooltip({
|
const { handler: externalTooltipHandler } = useChartTooltip({
|
||||||
position: "middle",
|
position: "middle",
|
||||||
|
@ -53,8 +43,8 @@ async function renderChart() {
|
||||||
chartInstance.destroy();
|
chartInstance.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
const wide = rootEl.offsetWidth > 700;
|
const wide = rootEl.value.offsetWidth > 700;
|
||||||
const narrow = rootEl.offsetWidth < 400;
|
const narrow = rootEl.value.offsetWidth < 400;
|
||||||
|
|
||||||
const weeks = wide ? 50 : narrow ? 10 : 25;
|
const weeks = wide ? 50 : narrow ? 10 : 25;
|
||||||
const chartLimit = 7 * weeks;
|
const chartLimit = 7 * weeks;
|
||||||
|
@ -123,7 +113,7 @@ async function renderChart() {
|
||||||
values = addArrays(raw.diffs.normal, raw.diffs.reply, raw.diffs.renote);
|
values = addArrays(raw.diffs.normal, raw.diffs.reply, raw.diffs.renote);
|
||||||
}
|
}
|
||||||
|
|
||||||
fetching = false;
|
fetching.value = false;
|
||||||
|
|
||||||
await nextTick();
|
await nextTick();
|
||||||
|
|
||||||
|
@ -141,7 +131,7 @@ async function renderChart() {
|
||||||
|
|
||||||
const marginEachCell = 4;
|
const marginEachCell = 4;
|
||||||
|
|
||||||
chartInstance = new Chart(chartEl, {
|
chartInstance = new Chart(chartEl.value, {
|
||||||
type: "matrix",
|
type: "matrix",
|
||||||
data: {
|
data: {
|
||||||
datasets: [
|
datasets: [
|
||||||
|
@ -257,7 +247,7 @@ async function renderChart() {
|
||||||
watch(
|
watch(
|
||||||
() => props.src,
|
() => props.src,
|
||||||
() => {
|
() => {
|
||||||
fetching = true;
|
fetching.value = true;
|
||||||
renderChart();
|
renderChart();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -26,6 +26,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
import {} from "vue";
|
import {} from "vue";
|
||||||
import type * as misskey from "firefish-js";
|
import type * as misskey from "firefish-js";
|
||||||
import bytes from "@/filters/bytes";
|
import bytes from "@/filters/bytes";
|
||||||
|
@ -43,7 +45,7 @@ const emit = defineEmits<{
|
||||||
(ev: "closed"): void;
|
(ev: "closed"): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const modal = $ref<InstanceType<typeof MkModal>>();
|
const modal = ref<InstanceType<typeof MkModal>>();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted } from "vue";
|
import { onMounted, ref } from "vue";
|
||||||
import { decodeBlurHash } from "fast-blurhash";
|
import { decodeBlurHash } from "fast-blurhash";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
|
@ -48,20 +48,20 @@ const props = withDefaults(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const canvas = $ref<HTMLCanvasElement>();
|
const canvas = ref<HTMLCanvasElement>();
|
||||||
let loaded = $ref(false);
|
let loaded = ref(false);
|
||||||
|
|
||||||
function draw() {
|
function draw() {
|
||||||
if (props.hash == null || canvas == null) return;
|
if (props.hash == null || canvas.value == null) return;
|
||||||
const pixels = decodeBlurHash(props.hash, props.size, props.size);
|
const pixels = decodeBlurHash(props.hash, props.size, props.size);
|
||||||
const ctx = canvas.getContext("2d");
|
const ctx = canvas.value.getContext("2d");
|
||||||
const imageData = ctx!.createImageData(props.size, props.size);
|
const imageData = ctx!.createImageData(props.size, props.size);
|
||||||
imageData.data.set(pixels);
|
imageData.data.set(pixels);
|
||||||
ctx!.putImageData(imageData, 0, 0);
|
ctx!.putImageData(imageData, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onLoad() {
|
function onLoad() {
|
||||||
loaded = true;
|
loaded.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|
|
@ -24,6 +24,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
import * as firefish from "firefish-js";
|
import * as firefish from "firefish-js";
|
||||||
import MkMiniChart from "@/components/MkMiniChart.vue";
|
import MkMiniChart from "@/components/MkMiniChart.vue";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
|
@ -33,7 +35,7 @@ const props = defineProps<{
|
||||||
instance: firefish.entities.Instance;
|
instance: firefish.entities.Instance;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
let chartValues = $ref<number[] | null>(null);
|
let chartValues = ref<number[] | null>(null);
|
||||||
|
|
||||||
os.apiGet("charts/instance", {
|
os.apiGet("charts/instance", {
|
||||||
host: props.instance.host,
|
host: props.instance.host,
|
||||||
|
@ -42,7 +44,7 @@ os.apiGet("charts/instance", {
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
// 今日のぶんの値はまだ途中の値であり、それも含めると大抵の場合前日よりも下降しているようなグラフになってしまうため今日は弾く
|
// 今日のぶんの値はまだ途中の値であり、それも含めると大抵の場合前日よりも下降しているようなグラフになってしまうため今日は弾く
|
||||||
res.requests.received.splice(0, 1);
|
res.requests.received.splice(0, 1);
|
||||||
chartValues = res.requests.received;
|
chartValues.value = res.requests.received;
|
||||||
});
|
});
|
||||||
|
|
||||||
function getInstanceIcon(instance): string {
|
function getInstanceIcon(instance): string {
|
||||||
|
|
|
@ -56,6 +56,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
import MkInput from "@/components/form/input.vue";
|
import MkInput from "@/components/form/input.vue";
|
||||||
import XModalWindow from "@/components/MkModalWindow.vue";
|
import XModalWindow from "@/components/MkModalWindow.vue";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
|
@ -68,28 +70,28 @@ const emit = defineEmits<{
|
||||||
(ev: "closed"): void;
|
(ev: "closed"): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
let hostname = $ref("");
|
let hostname = ref("");
|
||||||
let instances: Instance[] = $ref([]);
|
let instances: Instance[] = ref([]);
|
||||||
let selected: Instance | null = $ref(null);
|
let selected: Instance | null = ref(null);
|
||||||
let dialogEl = $ref<InstanceType<typeof XModalWindow>>();
|
let dialogEl = ref<InstanceType<typeof XModalWindow>>();
|
||||||
|
|
||||||
let searchOrderLatch = 0;
|
let searchOrderLatch = 0;
|
||||||
const search = () => {
|
const search = () => {
|
||||||
if (hostname === "") {
|
if (hostname.value === "") {
|
||||||
instances = [];
|
instances.value = [];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const searchId = ++searchOrderLatch;
|
const searchId = ++searchOrderLatch;
|
||||||
os.api("federation/instances", {
|
os.api("federation/instances", {
|
||||||
host: hostname,
|
host: hostname.value,
|
||||||
limit: 10,
|
limit: 10,
|
||||||
blocked: false,
|
blocked: false,
|
||||||
suspended: false,
|
suspended: false,
|
||||||
sort: "+pubSub",
|
sort: "+pubSub",
|
||||||
}).then((_instances) => {
|
}).then((_instances) => {
|
||||||
if (searchId !== searchOrderLatch) return;
|
if (searchId !== searchOrderLatch) return;
|
||||||
instances = _instances.map(
|
instances.value = _instances.map(
|
||||||
(x) =>
|
(x) =>
|
||||||
({
|
({
|
||||||
id: x.id,
|
id: x.id,
|
||||||
|
@ -101,14 +103,14 @@ const search = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const ok = () => {
|
const ok = () => {
|
||||||
if (selected == null) return;
|
if (selected.value == null) return;
|
||||||
emit("ok", selected);
|
emit("ok", selected.value);
|
||||||
dialogEl?.close();
|
dialogEl.value?.close();
|
||||||
};
|
};
|
||||||
|
|
||||||
const cancel = () => {
|
const cancel = () => {
|
||||||
emit("cancel");
|
emit("cancel");
|
||||||
dialogEl?.close();
|
dialogEl.value?.close();
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -102,7 +102,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted } from "vue";
|
import { onMounted, ref, shallowRef } from "vue";
|
||||||
import { Chart } from "chart.js";
|
import { Chart } from "chart.js";
|
||||||
import MkSelect from "@/components/form/select.vue";
|
import MkSelect from "@/components/form/select.vue";
|
||||||
import MkChart from "@/components/MkChart.vue";
|
import MkChart from "@/components/MkChart.vue";
|
||||||
|
@ -116,11 +116,11 @@ import { initChart } from "@/scripts/init-chart";
|
||||||
initChart();
|
initChart();
|
||||||
|
|
||||||
const chartLimit = 500;
|
const chartLimit = 500;
|
||||||
let chartSpan = $ref<"hour" | "day">("hour");
|
let chartSpan = ref<"hour" | "day">("hour");
|
||||||
let chartSrc = $ref("active-users");
|
let chartSrc = ref("active-users");
|
||||||
let heatmapSrc = $ref("active-users");
|
let heatmapSrc = ref("active-users");
|
||||||
let subDoughnutEl = $shallowRef<HTMLCanvasElement>();
|
let subDoughnutEl = shallowRef<HTMLCanvasElement>();
|
||||||
let pubDoughnutEl = $shallowRef<HTMLCanvasElement>();
|
let pubDoughnutEl = shallowRef<HTMLCanvasElement>();
|
||||||
|
|
||||||
const { handler: externalTooltipHandler1 } = useChartTooltip({
|
const { handler: externalTooltipHandler1 } = useChartTooltip({
|
||||||
position: "middle",
|
position: "middle",
|
||||||
|
@ -189,7 +189,7 @@ function createDoughnut(chartEl, tooltip, data) {
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
os.apiGet("federation/stats", { limit: 30 }).then((fedStats) => {
|
os.apiGet("federation/stats", { limit: 30 }).then((fedStats) => {
|
||||||
createDoughnut(
|
createDoughnut(
|
||||||
subDoughnutEl,
|
subDoughnutEl.value,
|
||||||
externalTooltipHandler1,
|
externalTooltipHandler1,
|
||||||
fedStats.topSubInstances
|
fedStats.topSubInstances
|
||||||
.map((x) => ({
|
.map((x) => ({
|
||||||
|
@ -210,7 +210,7 @@ onMounted(() => {
|
||||||
);
|
);
|
||||||
|
|
||||||
createDoughnut(
|
createDoughnut(
|
||||||
pubDoughnutEl,
|
pubDoughnutEl.value,
|
||||||
externalTooltipHandler2,
|
externalTooltipHandler2,
|
||||||
fedStats.topPubInstances
|
fedStats.topPubInstances
|
||||||
.map((x) => ({
|
.map((x) => ({
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
import { instanceName } from "@/config";
|
import { instanceName } from "@/config";
|
||||||
import { instance as Instance } from "@/instance";
|
import { instance as Instance } from "@/instance";
|
||||||
import { getProxiedImageUrlNullable } from "@/scripts/media-proxy";
|
import { getProxiedImageUrlNullable } from "@/scripts/media-proxy";
|
||||||
|
@ -24,7 +26,7 @@ const props = defineProps<{
|
||||||
};
|
};
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
let ticker = $ref<HTMLElement | null>(null);
|
let ticker = ref<HTMLElement | null>(null);
|
||||||
|
|
||||||
// if no instance data is given, this is for the local instance
|
// if no instance data is given, this is for the local instance
|
||||||
const instance = props.instance ?? {
|
const instance = props.instance ?? {
|
||||||
|
|
|
@ -62,14 +62,13 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {} from "vue";
|
import { ref } from "vue";
|
||||||
|
|
||||||
import MkModal from "@/components/MkModal.vue";
|
import MkModal from "@/components/MkModal.vue";
|
||||||
import { navbarItemDef } from "@/navbar";
|
import { navbarItemDef } from "@/navbar";
|
||||||
import { instanceName } from "@/config";
|
|
||||||
import { defaultStore } from "@/store";
|
import { defaultStore } from "@/store";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { deviceKind } from "@/scripts/device-kind";
|
import { deviceKind } from "@/scripts/device-kind";
|
||||||
import * as os from "@/os";
|
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
@ -92,7 +91,7 @@ const preferedModalType =
|
||||||
? "drawer"
|
? "drawer"
|
||||||
: "dialog";
|
: "dialog";
|
||||||
|
|
||||||
const modal = $ref<InstanceType<typeof MkModal>>();
|
const modal = ref<InstanceType<typeof MkModal>>();
|
||||||
|
|
||||||
const menu = defaultStore.state.menu;
|
const menu = defaultStore.state.menu;
|
||||||
|
|
||||||
|
@ -110,7 +109,7 @@ const items = Object.keys(navbarItemDef)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
function close() {
|
function close() {
|
||||||
modal.close();
|
modal.value.close();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { defineAsyncComponent } from "vue";
|
import { defineAsyncComponent, ref } from "vue";
|
||||||
import { url as local } from "@/config";
|
import { url as local } from "@/config";
|
||||||
import { useTooltip } from "@/scripts/use-tooltip";
|
import { useTooltip } from "@/scripts/use-tooltip";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
|
@ -35,9 +35,9 @@ const self = props.url.startsWith(local);
|
||||||
const attr = self ? "to" : "href";
|
const attr = self ? "to" : "href";
|
||||||
const target = self ? null : "_blank";
|
const target = self ? null : "_blank";
|
||||||
|
|
||||||
const el = $ref();
|
const el = ref();
|
||||||
|
|
||||||
useTooltip($$(el), (showing) => {
|
useTooltip(el, (showing) => {
|
||||||
os.popup(
|
os.popup(
|
||||||
defineAsyncComponent(
|
defineAsyncComponent(
|
||||||
() => import("@/components/MkUrlPreviewPopup.vue"),
|
() => import("@/components/MkUrlPreviewPopup.vue"),
|
||||||
|
@ -45,7 +45,7 @@ useTooltip($$(el), (showing) => {
|
||||||
{
|
{
|
||||||
showing,
|
showing,
|
||||||
url: props.url,
|
url: props.url,
|
||||||
source: el,
|
source: el.value,
|
||||||
},
|
},
|
||||||
{},
|
{},
|
||||||
"closed",
|
"closed",
|
||||||
|
|
|
@ -20,7 +20,6 @@ import { shallowRef } from "vue";
|
||||||
import MkModal from "@/components/MkModal.vue";
|
import MkModal from "@/components/MkModal.vue";
|
||||||
import MkButton from "@/components/MkButton.vue";
|
import MkButton from "@/components/MkButton.vue";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import * as os from "@/os";
|
|
||||||
|
|
||||||
const modal = shallowRef<InstanceType<typeof MkModal>>();
|
const modal = shallowRef<InstanceType<typeof MkModal>>();
|
||||||
const checkAnnouncements = () => {
|
const checkAnnouncements = () => {
|
||||||
|
|
|
@ -104,7 +104,7 @@ const props = defineProps<{
|
||||||
raw?: boolean;
|
raw?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
let hide = $ref(true);
|
let hide = ref(true);
|
||||||
|
|
||||||
const plyr = ref();
|
const plyr = ref();
|
||||||
|
|
||||||
|
@ -145,7 +145,7 @@ function captionPopup() {
|
||||||
watch(
|
watch(
|
||||||
() => props.media,
|
() => props.media,
|
||||||
() => {
|
() => {
|
||||||
hide =
|
hide.value =
|
||||||
defaultStore.state.nsfw === "force"
|
defaultStore.state.nsfw === "force"
|
||||||
? true
|
? true
|
||||||
: props.media.isSensitive &&
|
: props.media.isSensitive &&
|
||||||
|
|
|
@ -56,7 +56,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted } from "vue";
|
import { onMounted, ref } from "vue";
|
||||||
import VuePlyr from "vue-plyr";
|
import VuePlyr from "vue-plyr";
|
||||||
import type * as misskey from "firefish-js";
|
import type * as misskey from "firefish-js";
|
||||||
import { ColdDeviceStorage } from "@/store";
|
import { ColdDeviceStorage } from "@/store";
|
||||||
|
@ -70,15 +70,17 @@ const props = withDefaults(
|
||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
|
|
||||||
const audioEl = $ref<HTMLAudioElement | null>();
|
const audioEl = ref<HTMLAudioElement | null>();
|
||||||
let hide = $ref(true);
|
let hide = ref(true);
|
||||||
|
|
||||||
function volumechange() {
|
function volumechange() {
|
||||||
if (audioEl) ColdDeviceStorage.set("mediaVolume", audioEl.volume);
|
if (audioEl.value)
|
||||||
|
ColdDeviceStorage.set("mediaVolume", audioEl.value.volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (audioEl) audioEl.volume = ColdDeviceStorage.get("mediaVolume");
|
if (audioEl.value)
|
||||||
|
audioEl.value.volume = ColdDeviceStorage.get("mediaVolume");
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,6 @@ import XBanner from "@/components/MkMediaBanner.vue";
|
||||||
import XMedia from "@/components/MkMedia.vue";
|
import XMedia from "@/components/MkMedia.vue";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { FILE_TYPE_BROWSERSAFE } from "@/const";
|
import { FILE_TYPE_BROWSERSAFE } from "@/const";
|
||||||
import { defaultStore } from "@/store";
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
mediaList: misskey.entities.DriveFile[];
|
mediaList: misskey.entities.DriveFile[];
|
||||||
|
|
|
@ -12,15 +12,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { on } from "events";
|
import { nextTick, onMounted, ref } from "vue";
|
||||||
import {
|
|
||||||
nextTick,
|
|
||||||
onBeforeUnmount,
|
|
||||||
onMounted,
|
|
||||||
onUnmounted,
|
|
||||||
ref,
|
|
||||||
watch,
|
|
||||||
} from "vue";
|
|
||||||
import MkMenu from "./MkMenu.vue";
|
import MkMenu from "./MkMenu.vue";
|
||||||
import { MenuItem } from "@/types/menu";
|
import { MenuItem } from "@/types/menu";
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
@contextmenu.self="(e) => e.preventDefault()"
|
@contextmenu.self="(e) => e.preventDefault()"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<template v-for="(item, i) in items2">
|
<template v-for="item in items2">
|
||||||
<div v-if="item === null" class="divider"></div>
|
<div v-if="item === null" class="divider"></div>
|
||||||
<span v-else-if="item.type === 'label'" class="label item">
|
<span v-else-if="item.type === 'label'" class="label item">
|
||||||
<span :style="item.textStyle || ''">{{
|
<span :style="item.textStyle || ''">{{
|
||||||
|
@ -204,18 +204,12 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {
|
import {
|
||||||
computed,
|
|
||||||
menu,
|
|
||||||
defineAsyncComponent,
|
defineAsyncComponent,
|
||||||
nextTick,
|
|
||||||
onBeforeUnmount,
|
onBeforeUnmount,
|
||||||
onMounted,
|
onMounted,
|
||||||
onUnmounted,
|
|
||||||
Ref,
|
|
||||||
ref,
|
ref,
|
||||||
watch,
|
watch,
|
||||||
} from "vue";
|
} from "vue";
|
||||||
import { focusPrev, focusNext } from "@/scripts/focus";
|
|
||||||
import FormSwitch from "@/components/form/switch.vue";
|
import FormSwitch from "@/components/form/switch.vue";
|
||||||
import { MenuItem, InnerMenuItem, MenuPending, MenuAction } from "@/types/menu";
|
import { MenuItem, InnerMenuItem, MenuPending, MenuAction } from "@/types/menu";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
|
@ -239,13 +233,13 @@ const emit = defineEmits<{
|
||||||
(ev: "close", actioned?: boolean): void;
|
(ev: "close", actioned?: boolean): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
let itemsEl = $ref<HTMLDivElement>();
|
let itemsEl = ref<HTMLDivElement>();
|
||||||
|
|
||||||
let items2: InnerMenuItem[] = $ref([]);
|
let items2: InnerMenuItem[] = ref([]);
|
||||||
|
|
||||||
let child = $ref<InstanceType<typeof XChild>>();
|
let child = ref<InstanceType<typeof XChild>>();
|
||||||
|
|
||||||
let childShowingItem = $ref<MenuItem | null>();
|
let childShowingItem = ref<MenuItem | null>();
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.items,
|
() => props.items,
|
||||||
|
@ -261,24 +255,24 @@ watch(
|
||||||
// if item is Promise
|
// if item is Promise
|
||||||
items[i] = { type: "pending" };
|
items[i] = { type: "pending" };
|
||||||
item.then((actualItem) => {
|
item.then((actualItem) => {
|
||||||
items2[i] = actualItem;
|
items2.value[i] = actualItem;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
items2 = items as InnerMenuItem[];
|
items2.value = items as InnerMenuItem[];
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
immediate: true,
|
immediate: true,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let childMenu = $ref<MenuItem[] | null>();
|
let childMenu = ref<MenuItem[] | null>();
|
||||||
let childTarget = $ref<HTMLElement | null>();
|
let childTarget = ref<HTMLElement | null>();
|
||||||
|
|
||||||
function closeChild() {
|
function closeChild() {
|
||||||
childMenu = null;
|
childMenu.value = null;
|
||||||
childShowingItem = null;
|
childShowingItem.value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function childActioned() {
|
function childActioned() {
|
||||||
|
@ -288,11 +282,12 @@ function childActioned() {
|
||||||
|
|
||||||
function onGlobalMousedown(event: MouseEvent) {
|
function onGlobalMousedown(event: MouseEvent) {
|
||||||
if (
|
if (
|
||||||
childTarget &&
|
childTarget.value &&
|
||||||
(event.target === childTarget || childTarget.contains(event.target))
|
(event.target === childTarget.value ||
|
||||||
|
childTarget.value.contains(event.target))
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
if (child && child.checkHit(event)) return;
|
if (child.value && child.value.checkHit(event)) return;
|
||||||
closeChild();
|
closeChild();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -311,9 +306,9 @@ async function showChildren(item: MenuItem, ev: MouseEvent) {
|
||||||
os.popupMenu(item.children, ev.currentTarget ?? ev.target);
|
os.popupMenu(item.children, ev.currentTarget ?? ev.target);
|
||||||
close();
|
close();
|
||||||
} else {
|
} else {
|
||||||
childTarget = ev.currentTarget ?? ev.target;
|
childTarget.value = ev.currentTarget ?? ev.target;
|
||||||
childMenu = item.children;
|
childMenu.value = item.children;
|
||||||
childShowingItem = item;
|
childShowingItem.value = item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onUnmounted, watch } from "vue";
|
import { watch, ref } from "vue";
|
||||||
import { v4 as uuid } from "uuid";
|
import { v4 as uuid } from "uuid";
|
||||||
import tinycolor from "tinycolor2";
|
import tinycolor from "tinycolor2";
|
||||||
import { useInterval } from "@/scripts/use-interval";
|
import { useInterval } from "@/scripts/use-interval";
|
||||||
|
@ -37,11 +37,10 @@ const props = defineProps<{
|
||||||
const viewBoxX = 50;
|
const viewBoxX = 50;
|
||||||
const viewBoxY = 50;
|
const viewBoxY = 50;
|
||||||
const gradientId = uuid();
|
const gradientId = uuid();
|
||||||
let polylinePoints = $ref("");
|
let polylinePoints = ref("");
|
||||||
let polygonPoints = $ref("");
|
let polygonPoints = ref("");
|
||||||
let headX = $ref<number | null>(null);
|
let headX = ref<number | null>(null);
|
||||||
let headY = $ref<number | null>(null);
|
let headY = ref<number | null>(null);
|
||||||
let clock = $ref<number | null>(null);
|
|
||||||
const accent = tinycolor(
|
const accent = tinycolor(
|
||||||
getComputedStyle(document.documentElement).getPropertyValue("--accent"),
|
getComputedStyle(document.documentElement).getPropertyValue("--accent"),
|
||||||
);
|
);
|
||||||
|
@ -56,12 +55,14 @@ function draw(): void {
|
||||||
(1 - n / peak) * viewBoxY,
|
(1 - n / peak) * viewBoxY,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
polylinePoints = _polylinePoints.map((xy) => `${xy[0]},${xy[1]}`).join(" ");
|
polylinePoints.value = _polylinePoints
|
||||||
|
.map((xy) => `${xy[0]},${xy[1]}`)
|
||||||
|
.join(" ");
|
||||||
|
|
||||||
polygonPoints = `0,${viewBoxY} ${polylinePoints} ${viewBoxX},${viewBoxY}`;
|
polygonPoints.value = `0,${viewBoxY} ${polylinePoints.value} ${viewBoxX},${viewBoxY}`;
|
||||||
|
|
||||||
headX = _polylinePoints[_polylinePoints.length - 1][0];
|
headX.value = _polylinePoints[_polylinePoints.length - 1][0];
|
||||||
headY = _polylinePoints[_polylinePoints.length - 1][1];
|
headY.value = _polylinePoints[_polylinePoints.length - 1][1];
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(() => props.src, draw, { immediate: true });
|
watch(() => props.src, draw, { immediate: true });
|
||||||
|
|
|
@ -77,7 +77,16 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { nextTick, onMounted, watch, provide, onUnmounted } from "vue";
|
import {
|
||||||
|
nextTick,
|
||||||
|
onMounted,
|
||||||
|
watch,
|
||||||
|
provide,
|
||||||
|
onUnmounted,
|
||||||
|
ref,
|
||||||
|
shallowRef,
|
||||||
|
computed,
|
||||||
|
} from "vue";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { isTouchUsing } from "@/scripts/touch";
|
import { isTouchUsing } from "@/scripts/touch";
|
||||||
import { defaultStore } from "@/store";
|
import { defaultStore } from "@/store";
|
||||||
|
@ -130,14 +139,14 @@ const emit = defineEmits<{
|
||||||
|
|
||||||
provide("modal", true);
|
provide("modal", true);
|
||||||
|
|
||||||
let maxHeight = $ref<number>();
|
let maxHeight = ref<number>();
|
||||||
let fixed = $ref(false);
|
let fixed = ref(false);
|
||||||
let transformOrigin = $ref("center");
|
let transformOrigin = ref("center");
|
||||||
let showing = $ref(true);
|
let showing = ref(true);
|
||||||
let content = $shallowRef<HTMLElement>();
|
let content = shallowRef<HTMLElement>();
|
||||||
const zIndex = os.claimZIndex(props.zPriority);
|
const zIndex = os.claimZIndex(props.zPriority);
|
||||||
let useSendAnime = $ref(false);
|
let useSendAnime = ref(false);
|
||||||
const type = $computed<ModalTypes>(() => {
|
const type = computed<ModalTypes>(() => {
|
||||||
if (props.preferType === "auto") {
|
if (props.preferType === "auto") {
|
||||||
if (
|
if (
|
||||||
!defaultStore.state.disableDrawer &&
|
!defaultStore.state.disableDrawer &&
|
||||||
|
@ -152,28 +161,28 @@ const type = $computed<ModalTypes>(() => {
|
||||||
return props.preferType!;
|
return props.preferType!;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const isEnableBgTransparent = $computed(
|
const isEnableBgTransparent = computed(
|
||||||
() => props.transparentBg && type === "popup",
|
() => props.transparentBg && type.value === "popup",
|
||||||
);
|
);
|
||||||
let transitionName = $computed(() =>
|
let transitionName = computed(() =>
|
||||||
defaultStore.state.animation
|
defaultStore.state.animation
|
||||||
? useSendAnime
|
? useSendAnime.value
|
||||||
? "send"
|
? "send"
|
||||||
: type === "drawer"
|
: type.value === "drawer"
|
||||||
? "modal-drawer"
|
? "modal-drawer"
|
||||||
: type === "popup"
|
: type.value === "popup"
|
||||||
? "modal-popup"
|
? "modal-popup"
|
||||||
: "modal"
|
: "modal"
|
||||||
: "",
|
: "",
|
||||||
);
|
);
|
||||||
let transitionDuration = $computed(() =>
|
let transitionDuration = computed(() =>
|
||||||
transitionName === "send"
|
transitionName.value === "send"
|
||||||
? 400
|
? 400
|
||||||
: transitionName === "modal-popup"
|
: transitionName.value === "modal-popup"
|
||||||
? 100
|
? 100
|
||||||
: transitionName === "modal"
|
: transitionName.value === "modal"
|
||||||
? 200
|
? 200
|
||||||
: transitionName === "modal-drawer"
|
: transitionName.value === "modal-drawer"
|
||||||
? 200
|
? 200
|
||||||
: 0,
|
: 0,
|
||||||
);
|
);
|
||||||
|
@ -187,12 +196,12 @@ function close(ev, opts: { useSendAnimation?: boolean } = {}) {
|
||||||
// history.forward();
|
// history.forward();
|
||||||
// }
|
// }
|
||||||
if (opts.useSendAnimation) {
|
if (opts.useSendAnimation) {
|
||||||
useSendAnime = true;
|
useSendAnime.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line vue/no-mutating-props
|
// eslint-disable-next-line vue/no-mutating-props
|
||||||
if (props.src) props.src.style.pointerEvents = "auto";
|
if (props.src) props.src.style.pointerEvents = "auto";
|
||||||
showing = false;
|
showing.value = false;
|
||||||
emit("close");
|
emit("close");
|
||||||
if (!props.noReturnFocus) {
|
if (!props.noReturnFocus) {
|
||||||
focusedElement.focus();
|
focusedElement.focus();
|
||||||
|
@ -204,8 +213,8 @@ function onBgClick() {
|
||||||
emit("click");
|
emit("click");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === "drawer") {
|
if (type.value === "drawer") {
|
||||||
maxHeight = window.innerHeight / 1.5;
|
maxHeight.value = window.innerHeight / 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
const keymap = {
|
const keymap = {
|
||||||
|
@ -216,21 +225,21 @@ const MARGIN = 16;
|
||||||
|
|
||||||
const align = () => {
|
const align = () => {
|
||||||
if (props.src == null) return;
|
if (props.src == null) return;
|
||||||
if (type === "drawer") return;
|
if (type.value === "drawer") return;
|
||||||
if (type === "dialog") return;
|
if (type.value === "dialog") return;
|
||||||
|
|
||||||
if (content == null) return;
|
if (content.value == null) return;
|
||||||
|
|
||||||
const srcRect = props.src.getBoundingClientRect();
|
const srcRect = props.src.getBoundingClientRect();
|
||||||
|
|
||||||
const width = content!.offsetWidth;
|
const width = content.value!.offsetWidth;
|
||||||
const height = content!.offsetHeight;
|
const height = content.value!.offsetHeight;
|
||||||
|
|
||||||
let left;
|
let left;
|
||||||
let top;
|
let top;
|
||||||
|
|
||||||
const x = srcRect.left + (fixed ? 0 : window.pageXOffset);
|
const x = srcRect.left + (fixed.value ? 0 : window.pageXOffset);
|
||||||
const y = srcRect.top + (fixed ? 0 : window.pageYOffset);
|
const y = srcRect.top + (fixed.value ? 0 : window.pageYOffset);
|
||||||
|
|
||||||
if (props.anchor.x === "center") {
|
if (props.anchor.x === "center") {
|
||||||
left = x + props.src.offsetWidth / 2 - width / 2;
|
left = x + props.src.offsetWidth / 2 - width / 2;
|
||||||
|
@ -248,7 +257,7 @@ const align = () => {
|
||||||
top = y + props.src.offsetHeight;
|
top = y + props.src.offsetHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fixed) {
|
if (fixed.value) {
|
||||||
// 画面から横にはみ出る場合
|
// 画面から横にはみ出る場合
|
||||||
if (left + width > window.innerWidth) {
|
if (left + width > window.innerWidth) {
|
||||||
left = window.innerWidth - width;
|
left = window.innerWidth - width;
|
||||||
|
@ -261,16 +270,16 @@ const align = () => {
|
||||||
if (top + height > window.innerHeight - MARGIN) {
|
if (top + height > window.innerHeight - MARGIN) {
|
||||||
if (props.noOverlap && props.anchor.x === "center") {
|
if (props.noOverlap && props.anchor.x === "center") {
|
||||||
if (underSpace >= upperSpace / 3) {
|
if (underSpace >= upperSpace / 3) {
|
||||||
maxHeight = underSpace;
|
maxHeight.value = underSpace;
|
||||||
} else {
|
} else {
|
||||||
maxHeight = upperSpace;
|
maxHeight.value = upperSpace;
|
||||||
top = upperSpace + MARGIN - height;
|
top = upperSpace + MARGIN - height;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
top = window.innerHeight - MARGIN - height;
|
top = window.innerHeight - MARGIN - height;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
maxHeight = underSpace;
|
maxHeight.value = underSpace;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 画面から横にはみ出る場合
|
// 画面から横にはみ出る場合
|
||||||
|
@ -286,9 +295,9 @@ const align = () => {
|
||||||
if (top + height - window.scrollY > window.innerHeight - MARGIN) {
|
if (top + height - window.scrollY > window.innerHeight - MARGIN) {
|
||||||
if (props.noOverlap && props.anchor.x === "center") {
|
if (props.noOverlap && props.anchor.x === "center") {
|
||||||
if (underSpace >= upperSpace / 3) {
|
if (underSpace >= upperSpace / 3) {
|
||||||
maxHeight = underSpace;
|
maxHeight.value = underSpace;
|
||||||
} else {
|
} else {
|
||||||
maxHeight = upperSpace;
|
maxHeight.value = upperSpace;
|
||||||
top = window.scrollY + (upperSpace + MARGIN - height);
|
top = window.scrollY + (upperSpace + MARGIN - height);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -300,7 +309,7 @@ const align = () => {
|
||||||
1;
|
1;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
maxHeight = underSpace;
|
maxHeight.value = underSpace;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -317,36 +326,43 @@ const align = () => {
|
||||||
|
|
||||||
if (
|
if (
|
||||||
top >=
|
top >=
|
||||||
srcRect.top + props.src.offsetHeight + (fixed ? 0 : window.pageYOffset)
|
srcRect.top +
|
||||||
|
props.src.offsetHeight +
|
||||||
|
(fixed.value ? 0 : window.pageYOffset)
|
||||||
) {
|
) {
|
||||||
transformOriginY = "top";
|
transformOriginY = "top";
|
||||||
} else if (top + height <= srcRect.top + (fixed ? 0 : window.pageYOffset)) {
|
} else if (
|
||||||
|
top + height <=
|
||||||
|
srcRect.top + (fixed.value ? 0 : window.pageYOffset)
|
||||||
|
) {
|
||||||
transformOriginY = "bottom";
|
transformOriginY = "bottom";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
left >=
|
left >=
|
||||||
srcRect.left + props.src.offsetWidth + (fixed ? 0 : window.pageXOffset)
|
srcRect.left +
|
||||||
|
props.src.offsetWidth +
|
||||||
|
(fixed.value ? 0 : window.pageXOffset)
|
||||||
) {
|
) {
|
||||||
transformOriginX = "left";
|
transformOriginX = "left";
|
||||||
} else if (
|
} else if (
|
||||||
left + width <=
|
left + width <=
|
||||||
srcRect.left + (fixed ? 0 : window.pageXOffset)
|
srcRect.left + (fixed.value ? 0 : window.pageXOffset)
|
||||||
) {
|
) {
|
||||||
transformOriginX = "right";
|
transformOriginX = "right";
|
||||||
}
|
}
|
||||||
|
|
||||||
transformOrigin = `${transformOriginX} ${transformOriginY}`;
|
transformOrigin.value = `${transformOriginX} ${transformOriginY}`;
|
||||||
|
|
||||||
content.style.left = left + "px";
|
content.value.style.left = left + "px";
|
||||||
content.style.top = top + "px";
|
content.value.style.top = top + "px";
|
||||||
};
|
};
|
||||||
|
|
||||||
const onOpened = () => {
|
const onOpened = () => {
|
||||||
emit("opened");
|
emit("opened");
|
||||||
|
|
||||||
// モーダルコンテンツにマウスボタンが押され、コンテンツ外でマウスボタンが離されたときにモーダルバックグラウンドクリックと判定させないためにマウスイベントを監視しフラグ管理する
|
// モーダルコンテンツにマウスボタンが押され、コンテンツ外でマウスボタンが離されたときにモーダルバックグラウンドクリックと判定させないためにマウスイベントを監視しフラグ管理する
|
||||||
const el = content!.children[0];
|
const el = content.value!.children[0];
|
||||||
el.addEventListener(
|
el.addEventListener(
|
||||||
"mousedown",
|
"mousedown",
|
||||||
(ev) => {
|
(ev) => {
|
||||||
|
@ -378,7 +394,8 @@ onMounted(() => {
|
||||||
// eslint-disable-next-line vue/no-mutating-props
|
// eslint-disable-next-line vue/no-mutating-props
|
||||||
props.src.style.pointerEvents = "none";
|
props.src.style.pointerEvents = "none";
|
||||||
}
|
}
|
||||||
fixed = type === "drawer" || getFixedContainer(props.src) != null;
|
fixed.value =
|
||||||
|
type.value === "drawer" || getFixedContainer(props.src) != null;
|
||||||
|
|
||||||
await nextTick();
|
await nextTick();
|
||||||
|
|
||||||
|
@ -390,7 +407,7 @@ onMounted(() => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
new ResizeObserver((entries, observer) => {
|
new ResizeObserver((entries, observer) => {
|
||||||
align();
|
align();
|
||||||
}).observe(content!);
|
}).observe(content.value!);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
|
|
|
@ -52,7 +52,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ComputedRef, provide } from "vue";
|
import { ComputedRef, provide, ref, computed } from "vue";
|
||||||
import MkModal from "@/components/MkModal.vue";
|
import MkModal from "@/components/MkModal.vue";
|
||||||
import { popout as _popout } from "@/scripts/popout";
|
import { popout as _popout } from "@/scripts/popout";
|
||||||
import copyToClipboard from "@/scripts/copy-to-clipboard";
|
import copyToClipboard from "@/scripts/copy-to-clipboard";
|
||||||
|
@ -60,11 +60,7 @@ import { url } from "@/config";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { mainRouter, routes } from "@/router";
|
import { mainRouter, routes } from "@/router";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import {
|
import { PageMetadata, provideMetadataReceiver } from "@/scripts/page-metadata";
|
||||||
PageMetadata,
|
|
||||||
provideMetadataReceiver,
|
|
||||||
setPageMetadata,
|
|
||||||
} from "@/scripts/page-metadata";
|
|
||||||
import { Router } from "@/nirax";
|
import { Router } from "@/nirax";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
@ -80,27 +76,27 @@ const router = new Router(routes, props.initialPath);
|
||||||
|
|
||||||
router.addListener("push", (ctx) => {});
|
router.addListener("push", (ctx) => {});
|
||||||
|
|
||||||
let pageMetadata = $ref<null | ComputedRef<PageMetadata>>();
|
let pageMetadata = ref<null | ComputedRef<PageMetadata>>();
|
||||||
let rootEl = $ref();
|
let rootEl = ref();
|
||||||
let modal = $ref<InstanceType<typeof MkModal>>();
|
let modal = ref<InstanceType<typeof MkModal>>();
|
||||||
let path = $ref(props.initialPath);
|
let path = ref(props.initialPath);
|
||||||
let width = $ref(860);
|
let width = ref(860);
|
||||||
let height = $ref(660);
|
let height = ref(660);
|
||||||
const history = [];
|
const history = [];
|
||||||
|
|
||||||
provide("router", router);
|
provide("router", router);
|
||||||
provideMetadataReceiver((info) => {
|
provideMetadataReceiver((info) => {
|
||||||
pageMetadata = info;
|
pageMetadata.value = info;
|
||||||
});
|
});
|
||||||
provide("shouldOmitHeaderTitle", true);
|
provide("shouldOmitHeaderTitle", true);
|
||||||
provide("shouldHeaderThin", true);
|
provide("shouldHeaderThin", true);
|
||||||
|
|
||||||
const pageUrl = $computed(() => url + path);
|
const pageUrl = computed(() => url + path.value);
|
||||||
const contextmenu = $computed(() => {
|
const contextmenu = computed(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
type: "label",
|
type: "label",
|
||||||
text: path,
|
text: path.value,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "ph-arrows-out-simple ph-bold ph-lg",
|
icon: "ph-arrows-out-simple ph-bold ph-lg",
|
||||||
|
@ -117,15 +113,15 @@ const contextmenu = $computed(() => {
|
||||||
icon: "ph-arrow-square-out ph-bold ph-lg",
|
icon: "ph-arrow-square-out ph-bold ph-lg",
|
||||||
text: i18n.ts.openInNewTab,
|
text: i18n.ts.openInNewTab,
|
||||||
action: () => {
|
action: () => {
|
||||||
window.open(pageUrl, "_blank");
|
window.open(pageUrl.value, "_blank");
|
||||||
modal.close();
|
modal.value.close();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "ph-link-simple ph-bold ph-lg",
|
icon: "ph-link-simple ph-bold ph-lg",
|
||||||
text: i18n.ts.copyLink,
|
text: i18n.ts.copyLink,
|
||||||
action: () => {
|
action: () => {
|
||||||
copyToClipboard(pageUrl);
|
copyToClipboard(pageUrl.value);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -141,17 +137,17 @@ function back() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function expand() {
|
function expand() {
|
||||||
mainRouter.push(path);
|
mainRouter.push(path.value);
|
||||||
modal.close();
|
modal.value.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
function popout() {
|
function popout() {
|
||||||
_popout(path, rootEl);
|
_popout(path.value, rootEl.value);
|
||||||
modal.close();
|
modal.value.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onContextmenu(ev: MouseEvent) {
|
function onContextmenu(ev: MouseEvent) {
|
||||||
os.contextMenu(contextmenu, ev);
|
os.contextMenu(contextmenu.value, ev);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -62,6 +62,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { shallowRef } from "vue";
|
||||||
|
|
||||||
import { FocusTrap } from "focus-trap-vue";
|
import { FocusTrap } from "focus-trap-vue";
|
||||||
import MkModal from "./MkModal.vue";
|
import MkModal from "./MkModal.vue";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
@ -90,12 +92,12 @@ const emit = defineEmits<{
|
||||||
(event: "ok"): void;
|
(event: "ok"): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
let modal = $shallowRef<InstanceType<typeof MkModal>>();
|
let modal = shallowRef<InstanceType<typeof MkModal>>();
|
||||||
let rootEl = $shallowRef<HTMLElement>();
|
let rootEl = shallowRef<HTMLElement>();
|
||||||
let headerEl = $shallowRef<HTMLElement>();
|
let headerEl = shallowRef<HTMLElement>();
|
||||||
|
|
||||||
const close = (ev) => {
|
const close = (ev) => {
|
||||||
modal?.close(ev);
|
modal.value?.close(ev);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onBgClick = () => {
|
const onBgClick = () => {
|
||||||
|
|
|
@ -255,23 +255,18 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, inject, onMounted, onUnmounted, reactive, ref } from "vue";
|
import { computed, inject, onMounted, ref } from "vue";
|
||||||
import * as mfm from "mfm-js";
|
import * as mfm from "mfm-js";
|
||||||
import type { Ref } from "vue";
|
import type { Ref } from "vue";
|
||||||
import type * as misskey from "firefish-js";
|
import type * as misskey from "firefish-js";
|
||||||
import MkNoteSub from "@/components/MkNoteSub.vue";
|
import MkNoteSub from "@/components/MkNoteSub.vue";
|
||||||
import MkSubNoteContent from "./MkSubNoteContent.vue";
|
import MkSubNoteContent from "./MkSubNoteContent.vue";
|
||||||
import XNoteHeader from "@/components/MkNoteHeader.vue";
|
import XNoteHeader from "@/components/MkNoteHeader.vue";
|
||||||
import XNoteSimple from "@/components/MkNoteSimple.vue";
|
|
||||||
import XMediaList from "@/components/MkMediaList.vue";
|
|
||||||
import XCwButton from "@/components/MkCwButton.vue";
|
|
||||||
import XPoll from "@/components/MkPoll.vue";
|
|
||||||
import XRenoteButton from "@/components/MkRenoteButton.vue";
|
import XRenoteButton from "@/components/MkRenoteButton.vue";
|
||||||
import XReactionsViewer from "@/components/MkReactionsViewer.vue";
|
import XReactionsViewer from "@/components/MkReactionsViewer.vue";
|
||||||
import XStarButton from "@/components/MkStarButton.vue";
|
import XStarButton from "@/components/MkStarButton.vue";
|
||||||
import XStarButtonNoEmoji from "@/components/MkStarButtonNoEmoji.vue";
|
import XStarButtonNoEmoji from "@/components/MkStarButtonNoEmoji.vue";
|
||||||
import XQuoteButton from "@/components/MkQuoteButton.vue";
|
import XQuoteButton from "@/components/MkQuoteButton.vue";
|
||||||
import MkUrlPreview from "@/components/MkUrlPreview.vue";
|
|
||||||
import MkVisibility from "@/components/MkVisibility.vue";
|
import MkVisibility from "@/components/MkVisibility.vue";
|
||||||
import copyToClipboard from "@/scripts/copy-to-clipboard";
|
import copyToClipboard from "@/scripts/copy-to-clipboard";
|
||||||
import { url } from "@/config";
|
import { url } from "@/config";
|
||||||
|
@ -302,7 +297,7 @@ const props = defineProps<{
|
||||||
|
|
||||||
const inChannel = inject("inChannel", null);
|
const inChannel = inject("inChannel", null);
|
||||||
|
|
||||||
let note = $ref(deepClone(props.note));
|
let note = ref(deepClone(props.note));
|
||||||
|
|
||||||
const softMuteReasonI18nSrc = (what?: string) => {
|
const softMuteReasonI18nSrc = (what?: string) => {
|
||||||
if (what === "note") return i18n.ts.userSaysSomethingReason;
|
if (what === "note") return i18n.ts.userSaysSomethingReason;
|
||||||
|
@ -317,19 +312,19 @@ const softMuteReasonI18nSrc = (what?: string) => {
|
||||||
// plugin
|
// plugin
|
||||||
if (noteViewInterruptors.length > 0) {
|
if (noteViewInterruptors.length > 0) {
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
let result = deepClone(note);
|
let result = deepClone(note.value);
|
||||||
for (const interruptor of noteViewInterruptors) {
|
for (const interruptor of noteViewInterruptors) {
|
||||||
result = await interruptor.handler(result);
|
result = await interruptor.handler(result);
|
||||||
}
|
}
|
||||||
note = result;
|
note.value = result;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const isRenote =
|
const isRenote =
|
||||||
note.renote != null &&
|
note.value.renote != null &&
|
||||||
note.text == null &&
|
note.value.text == null &&
|
||||||
note.fileIds.length === 0 &&
|
note.value.fileIds.length === 0 &&
|
||||||
note.poll == null;
|
note.value.poll == null;
|
||||||
|
|
||||||
const el = ref<HTMLElement>();
|
const el = ref<HTMLElement>();
|
||||||
const footerEl = ref<HTMLElement>();
|
const footerEl = ref<HTMLElement>();
|
||||||
|
@ -338,13 +333,15 @@ const starButton = ref<InstanceType<typeof XStarButton>>();
|
||||||
const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
|
const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
|
||||||
const renoteTime = ref<HTMLElement>();
|
const renoteTime = ref<HTMLElement>();
|
||||||
const reactButton = ref<HTMLElement>();
|
const reactButton = ref<HTMLElement>();
|
||||||
let appearNote = $computed(() =>
|
let appearNote = computed(() =>
|
||||||
isRenote ? (note.renote as misskey.entities.Note) : note,
|
isRenote ? (note.value.renote as misskey.entities.Note) : note.value,
|
||||||
);
|
);
|
||||||
const isMyRenote = $i && $i.id === note.userId;
|
const isMyRenote = $i && $i.id === note.value.userId;
|
||||||
const showContent = ref(false);
|
const showContent = ref(false);
|
||||||
const isDeleted = ref(false);
|
const isDeleted = ref(false);
|
||||||
const muted = ref(getWordSoftMute(note, $i, defaultStore.state.mutedWords));
|
const muted = ref(
|
||||||
|
getWordSoftMute(note.value, $i, defaultStore.state.mutedWords),
|
||||||
|
);
|
||||||
const translation = ref(null);
|
const translation = ref(null);
|
||||||
const translating = ref(false);
|
const translating = ref(false);
|
||||||
const enableEmojiReactions = defaultStore.state.enableEmojiReactions;
|
const enableEmojiReactions = defaultStore.state.enableEmojiReactions;
|
||||||
|
@ -363,7 +360,7 @@ const keymap = {
|
||||||
|
|
||||||
useNoteCapture({
|
useNoteCapture({
|
||||||
rootEl: el,
|
rootEl: el,
|
||||||
note: $$(appearNote),
|
note: appearNote,
|
||||||
isDeletedRef: isDeleted,
|
isDeletedRef: isDeleted,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -371,7 +368,7 @@ function reply(viaKeyboard = false): void {
|
||||||
pleaseLogin();
|
pleaseLogin();
|
||||||
os.post(
|
os.post(
|
||||||
{
|
{
|
||||||
reply: appearNote,
|
reply: appearNote.value,
|
||||||
animation: !viaKeyboard,
|
animation: !viaKeyboard,
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
|
@ -387,7 +384,7 @@ function react(viaKeyboard = false): void {
|
||||||
reactButton.value,
|
reactButton.value,
|
||||||
(reaction) => {
|
(reaction) => {
|
||||||
os.api("notes/reactions/create", {
|
os.api("notes/reactions/create", {
|
||||||
noteId: appearNote.id,
|
noteId: appearNote.value.id,
|
||||||
reaction: reaction,
|
reaction: reaction,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -430,21 +427,24 @@ function onContextmenu(ev: MouseEvent): void {
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
type: "label",
|
type: "label",
|
||||||
text: notePage(appearNote),
|
text: notePage(appearNote.value),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "ph-browser ph-bold ph-lg",
|
icon: "ph-browser ph-bold ph-lg",
|
||||||
text: i18n.ts.openInWindow,
|
text: i18n.ts.openInWindow,
|
||||||
action: () => {
|
action: () => {
|
||||||
os.pageWindow(notePage(appearNote));
|
os.pageWindow(notePage(appearNote.value));
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
notePage(appearNote) != location.pathname
|
notePage(appearNote.value) != location.pathname
|
||||||
? {
|
? {
|
||||||
icon: "ph-arrows-out-simple ph-bold ph-lg",
|
icon: "ph-arrows-out-simple ph-bold ph-lg",
|
||||||
text: i18n.ts.showInPage,
|
text: i18n.ts.showInPage,
|
||||||
action: () => {
|
action: () => {
|
||||||
router.push(notePage(appearNote), "forcePage");
|
router.push(
|
||||||
|
notePage(appearNote.value),
|
||||||
|
"forcePage",
|
||||||
|
);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
|
@ -453,22 +453,25 @@ function onContextmenu(ev: MouseEvent): void {
|
||||||
type: "a",
|
type: "a",
|
||||||
icon: "ph-arrow-square-out ph-bold ph-lg",
|
icon: "ph-arrow-square-out ph-bold ph-lg",
|
||||||
text: i18n.ts.openInNewTab,
|
text: i18n.ts.openInNewTab,
|
||||||
href: notePage(appearNote),
|
href: notePage(appearNote.value),
|
||||||
target: "_blank",
|
target: "_blank",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "ph-link-simple ph-bold ph-lg",
|
icon: "ph-link-simple ph-bold ph-lg",
|
||||||
text: i18n.ts.copyLink,
|
text: i18n.ts.copyLink,
|
||||||
action: () => {
|
action: () => {
|
||||||
copyToClipboard(`${url}${notePage(appearNote)}`);
|
copyToClipboard(`${url}${notePage(appearNote.value)}`);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
appearNote.user.host != null
|
appearNote.value.user.host != null
|
||||||
? {
|
? {
|
||||||
type: "a",
|
type: "a",
|
||||||
icon: "ph-arrow-square-up-right ph-bold ph-lg",
|
icon: "ph-arrow-square-up-right ph-bold ph-lg",
|
||||||
text: i18n.ts.showOnRemote,
|
text: i18n.ts.showOnRemote,
|
||||||
href: appearNote.url ?? appearNote.uri ?? "",
|
href:
|
||||||
|
appearNote.value.url ??
|
||||||
|
appearNote.value.uri ??
|
||||||
|
"",
|
||||||
target: "_blank",
|
target: "_blank",
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
|
@ -481,7 +484,7 @@ function onContextmenu(ev: MouseEvent): void {
|
||||||
function menu(viaKeyboard = false): void {
|
function menu(viaKeyboard = false): void {
|
||||||
os.popupMenu(
|
os.popupMenu(
|
||||||
getNoteMenu({
|
getNoteMenu({
|
||||||
note: note,
|
note: note.value,
|
||||||
translating,
|
translating,
|
||||||
translation,
|
translation,
|
||||||
menuButton,
|
menuButton,
|
||||||
|
@ -505,7 +508,7 @@ function showRenoteMenu(viaKeyboard = false): void {
|
||||||
danger: true,
|
danger: true,
|
||||||
action: () => {
|
action: () => {
|
||||||
os.api("notes/delete", {
|
os.api("notes/delete", {
|
||||||
noteId: note.id,
|
noteId: note.value.id,
|
||||||
});
|
});
|
||||||
isDeleted.value = true;
|
isDeleted.value = true;
|
||||||
},
|
},
|
||||||
|
@ -546,13 +549,13 @@ function noteClick(e) {
|
||||||
) {
|
) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
} else {
|
} else {
|
||||||
router.push(notePage(appearNote));
|
router.push(notePage(appearNote.value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function readPromo() {
|
function readPromo() {
|
||||||
os.api("promo/read", {
|
os.api("promo/read", {
|
||||||
noteId: appearNote.id,
|
noteId: appearNote.value.id,
|
||||||
});
|
});
|
||||||
isDeleted.value = true;
|
isDeleted.value = true;
|
||||||
}
|
}
|
||||||
|
@ -564,28 +567,30 @@ function setPostExpanded(val: boolean) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const accessibleLabel = computed(() => {
|
const accessibleLabel = computed(() => {
|
||||||
let label = `${appearNote.user.username}; `;
|
let label = `${appearNote.value.user.username}; `;
|
||||||
if (appearNote.renote) {
|
if (appearNote.value.renote) {
|
||||||
label += `${i18n.t("renoted")} ${appearNote.renote.user.username}; `;
|
label += `${i18n.t("renoted")} ${
|
||||||
if (appearNote.renote.cw) {
|
appearNote.value.renote.user.username
|
||||||
label += `${i18n.t("cw")}: ${appearNote.renote.cw}; `;
|
}; `;
|
||||||
|
if (appearNote.value.renote.cw) {
|
||||||
|
label += `${i18n.t("cw")}: ${appearNote.value.renote.cw}; `;
|
||||||
if (postIsExpanded.value) {
|
if (postIsExpanded.value) {
|
||||||
label += `${appearNote.renote.text}; `;
|
label += `${appearNote.value.renote.text}; `;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
label += `${appearNote.renote.text}; `;
|
label += `${appearNote.value.renote.text}; `;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (appearNote.cw) {
|
if (appearNote.value.cw) {
|
||||||
label += `${i18n.t("cw")}: ${appearNote.cw}; `;
|
label += `${i18n.t("cw")}: ${appearNote.value.cw}; `;
|
||||||
if (postIsExpanded.value) {
|
if (postIsExpanded.value) {
|
||||||
label += `${appearNote.text}; `;
|
label += `${appearNote.value.text}; `;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
label += `${appearNote.text}; `;
|
label += `${appearNote.value.text}; `;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const date = new Date(appearNote.createdAt);
|
const date = new Date(appearNote.value.createdAt);
|
||||||
label += `${date.toLocaleTimeString()}`;
|
label += `${date.toLocaleTimeString()}`;
|
||||||
return label;
|
return label;
|
||||||
});
|
});
|
||||||
|
|
|
@ -150,22 +150,12 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {
|
import { onMounted, onUnmounted, onUpdated, ref } from "vue";
|
||||||
computed,
|
|
||||||
inject,
|
|
||||||
onMounted,
|
|
||||||
onUnmounted,
|
|
||||||
onUpdated,
|
|
||||||
reactive,
|
|
||||||
ref,
|
|
||||||
} from "vue";
|
|
||||||
import * as misskey from "firefish-js";
|
import * as misskey from "firefish-js";
|
||||||
import MkTab from "@/components/MkTab.vue";
|
import MkTab from "@/components/MkTab.vue";
|
||||||
import MkNote from "@/components/MkNote.vue";
|
import MkNote from "@/components/MkNote.vue";
|
||||||
import MkNoteSub from "@/components/MkNoteSub.vue";
|
import MkNoteSub from "@/components/MkNoteSub.vue";
|
||||||
import XStarButton from "@/components/MkStarButton.vue";
|
|
||||||
import XRenoteButton from "@/components/MkRenoteButton.vue";
|
import XRenoteButton from "@/components/MkRenoteButton.vue";
|
||||||
import MkPagination from "@/components/MkPagination.vue";
|
|
||||||
import MkUserCardMini from "@/components/MkUserCardMini.vue";
|
import MkUserCardMini from "@/components/MkUserCardMini.vue";
|
||||||
import MkReactedUsers from "@/components/MkReactedUsers.vue";
|
import MkReactedUsers from "@/components/MkReactedUsers.vue";
|
||||||
import { pleaseLogin } from "@/scripts/please-login";
|
import { pleaseLogin } from "@/scripts/please-login";
|
||||||
|
@ -181,16 +171,15 @@ import { useNoteCapture } from "@/scripts/use-note-capture";
|
||||||
import { deepClone } from "@/scripts/clone";
|
import { deepClone } from "@/scripts/clone";
|
||||||
import { stream } from "@/stream";
|
import { stream } from "@/stream";
|
||||||
import { NoteUpdatedEvent } from "firefish-js/built/streaming.types";
|
import { NoteUpdatedEvent } from "firefish-js/built/streaming.types";
|
||||||
import appear from "@/directives/appear";
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
note: misskey.entities.Note;
|
note: misskey.entities.Note;
|
||||||
pinned?: boolean;
|
pinned?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
let tab = $ref("replies");
|
let tab = ref("replies");
|
||||||
|
|
||||||
let note = $ref(deepClone(props.note));
|
let note = ref(deepClone(props.note));
|
||||||
|
|
||||||
const softMuteReasonI18nSrc = (what?: string) => {
|
const softMuteReasonI18nSrc = (what?: string) => {
|
||||||
if (what === "note") return i18n.ts.userSaysSomethingReason;
|
if (what === "note") return i18n.ts.userSaysSomethingReason;
|
||||||
|
@ -205,30 +194,32 @@ const softMuteReasonI18nSrc = (what?: string) => {
|
||||||
// plugin
|
// plugin
|
||||||
if (noteViewInterruptors.length > 0) {
|
if (noteViewInterruptors.length > 0) {
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
let result = deepClone(note);
|
let result = deepClone(note.value);
|
||||||
for (const interruptor of noteViewInterruptors) {
|
for (const interruptor of noteViewInterruptors) {
|
||||||
result = await interruptor.handler(result);
|
result = await interruptor.handler(result);
|
||||||
}
|
}
|
||||||
note = result;
|
note.value = result;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const el = ref<HTMLElement>();
|
const el = ref<HTMLElement>();
|
||||||
const noteEl = $ref();
|
const noteEl = ref();
|
||||||
const menuButton = ref<HTMLElement>();
|
const menuButton = ref<HTMLElement>();
|
||||||
const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
|
const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
|
||||||
const reactButton = ref<HTMLElement>();
|
const reactButton = ref<HTMLElement>();
|
||||||
const showContent = ref(false);
|
const showContent = ref(false);
|
||||||
const isDeleted = ref(false);
|
const isDeleted = ref(false);
|
||||||
const muted = ref(getWordSoftMute(note, $i, defaultStore.state.mutedWords));
|
const muted = ref(
|
||||||
|
getWordSoftMute(note.value, $i, defaultStore.state.mutedWords),
|
||||||
|
);
|
||||||
const translation = ref(null);
|
const translation = ref(null);
|
||||||
const translating = ref(false);
|
const translating = ref(false);
|
||||||
let conversation = $ref<null | misskey.entities.Note[]>([]);
|
let conversation = ref<null | misskey.entities.Note[]>([]);
|
||||||
const replies = ref<misskey.entities.Note[]>([]);
|
const replies = ref<misskey.entities.Note[]>([]);
|
||||||
let directReplies = $ref<null | misskey.entities.Note[]>([]);
|
let directReplies = ref<null | misskey.entities.Note[]>([]);
|
||||||
let directQuotes = $ref<null | misskey.entities.Note[]>([]);
|
let directQuotes = ref<null | misskey.entities.Note[]>([]);
|
||||||
let clips = $ref();
|
let clips = ref();
|
||||||
let renotes = $ref();
|
let renotes = ref();
|
||||||
let isScrolling;
|
let isScrolling;
|
||||||
|
|
||||||
const reactionsCount = Object.values(props.note.reactions).reduce(
|
const reactionsCount = Object.values(props.note.reactions).reduce(
|
||||||
|
@ -247,14 +238,14 @@ const keymap = {
|
||||||
|
|
||||||
useNoteCapture({
|
useNoteCapture({
|
||||||
rootEl: el,
|
rootEl: el,
|
||||||
note: $$(note),
|
note: note,
|
||||||
isDeletedRef: isDeleted,
|
isDeletedRef: isDeleted,
|
||||||
});
|
});
|
||||||
|
|
||||||
function reply(viaKeyboard = false): void {
|
function reply(viaKeyboard = false): void {
|
||||||
pleaseLogin();
|
pleaseLogin();
|
||||||
os.post({
|
os.post({
|
||||||
reply: note,
|
reply: note.value,
|
||||||
animation: !viaKeyboard,
|
animation: !viaKeyboard,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
focus();
|
focus();
|
||||||
|
@ -268,7 +259,7 @@ function react(viaKeyboard = false): void {
|
||||||
reactButton.value,
|
reactButton.value,
|
||||||
(reaction) => {
|
(reaction) => {
|
||||||
os.api("notes/reactions/create", {
|
os.api("notes/reactions/create", {
|
||||||
noteId: note.id,
|
noteId: note.value.id,
|
||||||
reaction: reaction,
|
reaction: reaction,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -302,7 +293,7 @@ function onContextmenu(ev: MouseEvent): void {
|
||||||
} else {
|
} else {
|
||||||
os.contextMenu(
|
os.contextMenu(
|
||||||
getNoteMenu({
|
getNoteMenu({
|
||||||
note: note,
|
note: note.value,
|
||||||
translating,
|
translating,
|
||||||
translation,
|
translation,
|
||||||
menuButton,
|
menuButton,
|
||||||
|
@ -316,7 +307,7 @@ function onContextmenu(ev: MouseEvent): void {
|
||||||
function menu(viaKeyboard = false): void {
|
function menu(viaKeyboard = false): void {
|
||||||
os.popupMenu(
|
os.popupMenu(
|
||||||
getNoteMenu({
|
getNoteMenu({
|
||||||
note: note,
|
note: note.value,
|
||||||
translating,
|
translating,
|
||||||
translation,
|
translation,
|
||||||
menuButton,
|
menuButton,
|
||||||
|
@ -330,48 +321,50 @@ function menu(viaKeyboard = false): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
function focus() {
|
function focus() {
|
||||||
noteEl.focus();
|
noteEl.value.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
function blur() {
|
function blur() {
|
||||||
noteEl.blur();
|
noteEl.value.blur();
|
||||||
}
|
}
|
||||||
|
|
||||||
directReplies = null;
|
directReplies.value = null;
|
||||||
os.api("notes/children", {
|
os.api("notes/children", {
|
||||||
noteId: note.id,
|
noteId: note.value.id,
|
||||||
limit: 30,
|
limit: 30,
|
||||||
depth: 12,
|
depth: 12,
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
res = res.reduce((acc, resNote) => {
|
res = res.reduce((acc, resNote) => {
|
||||||
if (resNote.userId == note.userId) {
|
if (resNote.userId == note.value.userId) {
|
||||||
return [...acc, resNote];
|
return [...acc, resNote];
|
||||||
}
|
}
|
||||||
return [resNote, ...acc];
|
return [resNote, ...acc];
|
||||||
}, []);
|
}, []);
|
||||||
replies.value = res;
|
replies.value = res;
|
||||||
directReplies = res
|
directReplies.value = res
|
||||||
.filter((resNote) => resNote.replyId === note.id)
|
.filter((resNote) => resNote.replyId === note.value.id)
|
||||||
.reverse();
|
.reverse();
|
||||||
directQuotes = res.filter((resNote) => resNote.renoteId === note.id);
|
directQuotes.value = res.filter(
|
||||||
|
(resNote) => resNote.renoteId === note.value.id,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
conversation = null;
|
conversation.value = null;
|
||||||
if (note.replyId) {
|
if (note.value.replyId) {
|
||||||
os.api("notes/conversation", {
|
os.api("notes/conversation", {
|
||||||
noteId: note.replyId,
|
noteId: note.value.replyId,
|
||||||
limit: 30,
|
limit: 30,
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
conversation = res.reverse();
|
conversation.value = res.reverse();
|
||||||
focus();
|
focus();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
clips = null;
|
clips.value = null;
|
||||||
os.api("notes/clips", {
|
os.api("notes/clips", {
|
||||||
noteId: note.id,
|
noteId: note.value.id,
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
clips = res;
|
clips.value = res;
|
||||||
});
|
});
|
||||||
|
|
||||||
// const pagination = {
|
// const pagination = {
|
||||||
|
@ -382,14 +375,14 @@ os.api("notes/clips", {
|
||||||
|
|
||||||
// const pagingComponent = $ref<InstanceType<typeof MkPagination>>();
|
// const pagingComponent = $ref<InstanceType<typeof MkPagination>>();
|
||||||
|
|
||||||
renotes = null;
|
renotes.value = null;
|
||||||
function loadTab() {
|
function loadTab() {
|
||||||
if (tab === "renotes" && !renotes) {
|
if (tab.value === "renotes" && !renotes.value) {
|
||||||
os.api("notes/renotes", {
|
os.api("notes/renotes", {
|
||||||
noteId: note.id,
|
noteId: note.value.id,
|
||||||
limit: 100,
|
limit: 100,
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
renotes = res;
|
renotes.value = res;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -398,7 +391,7 @@ async function onNoteUpdated(noteData: NoteUpdatedEvent): Promise<void> {
|
||||||
const { type, id, body } = noteData;
|
const { type, id, body } = noteData;
|
||||||
|
|
||||||
let found = -1;
|
let found = -1;
|
||||||
if (id === note.id) {
|
if (id === note.value.id) {
|
||||||
found = 0;
|
found = 0;
|
||||||
} else {
|
} else {
|
||||||
for (let i = 0; i < replies.value.length; i++) {
|
for (let i = 0; i < replies.value.length; i++) {
|
||||||
|
@ -423,7 +416,7 @@ async function onNoteUpdated(noteData: NoteUpdatedEvent): Promise<void> {
|
||||||
|
|
||||||
replies.value.splice(found, 0, replyNote);
|
replies.value.splice(found, 0, replyNote);
|
||||||
if (found === 0) {
|
if (found === 0) {
|
||||||
directReplies.push(replyNote);
|
directReplies.value.push(replyNote);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -444,12 +437,12 @@ document.addEventListener("wheel", () => {
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
stream.on("noteUpdated", onNoteUpdated);
|
stream.on("noteUpdated", onNoteUpdated);
|
||||||
isScrolling = false;
|
isScrolling = false;
|
||||||
noteEl.scrollIntoView();
|
noteEl.value.scrollIntoView();
|
||||||
});
|
});
|
||||||
|
|
||||||
onUpdated(() => {
|
onUpdated(() => {
|
||||||
if (!isScrolling) {
|
if (!isScrolling) {
|
||||||
noteEl.scrollIntoView();
|
noteEl.value.scrollIntoView();
|
||||||
if (location.hash) {
|
if (location.hash) {
|
||||||
location.replace(location.hash); // Jump to highlighted reply
|
location.replace(location.hash); // Jump to highlighted reply
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,9 +47,11 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
import {} from "vue";
|
import {} from "vue";
|
||||||
import type * as misskey from "firefish-js";
|
import type * as misskey from "firefish-js";
|
||||||
import { defaultStore, noteViewInterruptors } from "@/store";
|
import { defaultStore } from "@/store";
|
||||||
import MkVisibility from "@/components/MkVisibility.vue";
|
import MkVisibility from "@/components/MkVisibility.vue";
|
||||||
import MkInstanceTicker from "@/components/MkInstanceTicker.vue";
|
import MkInstanceTicker from "@/components/MkInstanceTicker.vue";
|
||||||
import { notePage } from "@/filters/note";
|
import { notePage } from "@/filters/note";
|
||||||
|
@ -61,11 +63,12 @@ const props = defineProps<{
|
||||||
pinned?: boolean;
|
pinned?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
let note = $ref(props.note);
|
let note = ref(props.note);
|
||||||
|
|
||||||
const showTicker =
|
const showTicker =
|
||||||
defaultStore.state.instanceTicker === "always" ||
|
defaultStore.state.instanceTicker === "always" ||
|
||||||
(defaultStore.state.instanceTicker === "remote" && note.user.instance);
|
(defaultStore.state.instanceTicker === "remote" &&
|
||||||
|
note.value.user.instance);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
@ -11,7 +11,6 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {} from "vue";
|
|
||||||
import * as misskey from "firefish-js";
|
import * as misskey from "firefish-js";
|
||||||
import XNoteHeader from "@/components/MkNoteHeader.vue";
|
import XNoteHeader from "@/components/MkNoteHeader.vue";
|
||||||
import MkSubNoteContent from "@/components/MkSubNoteContent.vue";
|
import MkSubNoteContent from "@/components/MkSubNoteContent.vue";
|
||||||
|
|
|
@ -177,7 +177,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { inject, ref } from "vue";
|
import { inject, ref, computed } from "vue";
|
||||||
import type { Ref } from "vue";
|
import type { Ref } from "vue";
|
||||||
import * as misskey from "firefish-js";
|
import * as misskey from "firefish-js";
|
||||||
import XNoteHeader from "@/components/MkNoteHeader.vue";
|
import XNoteHeader from "@/components/MkNoteHeader.vue";
|
||||||
|
@ -223,7 +223,7 @@ const props = withDefaults(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let note = $ref(deepClone(props.note));
|
let note = ref(deepClone(props.note));
|
||||||
|
|
||||||
const softMuteReasonI18nSrc = (what?: string) => {
|
const softMuteReasonI18nSrc = (what?: string) => {
|
||||||
if (what === "note") return i18n.ts.userSaysSomethingReason;
|
if (what === "note") return i18n.ts.userSaysSomethingReason;
|
||||||
|
@ -236,10 +236,10 @@ const softMuteReasonI18nSrc = (what?: string) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const isRenote =
|
const isRenote =
|
||||||
note.renote != null &&
|
note.value.renote != null &&
|
||||||
note.text == null &&
|
note.value.text == null &&
|
||||||
note.fileIds.length === 0 &&
|
note.value.fileIds.length === 0 &&
|
||||||
note.poll == null;
|
note.value.poll == null;
|
||||||
|
|
||||||
const el = ref<HTMLElement>();
|
const el = ref<HTMLElement>();
|
||||||
const footerEl = ref<HTMLElement>();
|
const footerEl = ref<HTMLElement>();
|
||||||
|
@ -247,11 +247,13 @@ const menuButton = ref<HTMLElement>();
|
||||||
const starButton = ref<InstanceType<typeof XStarButton>>();
|
const starButton = ref<InstanceType<typeof XStarButton>>();
|
||||||
const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
|
const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
|
||||||
const reactButton = ref<HTMLElement>();
|
const reactButton = ref<HTMLElement>();
|
||||||
let appearNote = $computed(() =>
|
let appearNote = computed(() =>
|
||||||
isRenote ? (note.renote as misskey.entities.Note) : note,
|
isRenote ? (note.value.renote as misskey.entities.Note) : note.value,
|
||||||
);
|
);
|
||||||
const isDeleted = ref(false);
|
const isDeleted = ref(false);
|
||||||
const muted = ref(getWordSoftMute(note, $i, defaultStore.state.mutedWords));
|
const muted = ref(
|
||||||
|
getWordSoftMute(note.value, $i, defaultStore.state.mutedWords),
|
||||||
|
);
|
||||||
const translation = ref(null);
|
const translation = ref(null);
|
||||||
const translating = ref(false);
|
const translating = ref(false);
|
||||||
const replies: misskey.entities.Note[] =
|
const replies: misskey.entities.Note[] =
|
||||||
|
@ -267,14 +269,14 @@ const expandOnNoteClick = defaultStore.state.expandOnNoteClick;
|
||||||
|
|
||||||
useNoteCapture({
|
useNoteCapture({
|
||||||
rootEl: el,
|
rootEl: el,
|
||||||
note: $$(appearNote),
|
note: appearNote,
|
||||||
isDeletedRef: isDeleted,
|
isDeletedRef: isDeleted,
|
||||||
});
|
});
|
||||||
|
|
||||||
function reply(viaKeyboard = false): void {
|
function reply(viaKeyboard = false): void {
|
||||||
pleaseLogin();
|
pleaseLogin();
|
||||||
os.post({
|
os.post({
|
||||||
reply: appearNote,
|
reply: appearNote.value,
|
||||||
animation: !viaKeyboard,
|
animation: !viaKeyboard,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
focus();
|
focus();
|
||||||
|
@ -288,7 +290,7 @@ function react(viaKeyboard = false): void {
|
||||||
reactButton.value,
|
reactButton.value,
|
||||||
(reaction) => {
|
(reaction) => {
|
||||||
os.api("notes/reactions/create", {
|
os.api("notes/reactions/create", {
|
||||||
noteId: appearNote.id,
|
noteId: appearNote.value.id,
|
||||||
reaction: reaction,
|
reaction: reaction,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -314,7 +316,7 @@ const currentClipPage = inject<Ref<misskey.entities.Clip> | null>(
|
||||||
function menu(viaKeyboard = false): void {
|
function menu(viaKeyboard = false): void {
|
||||||
os.popupMenu(
|
os.popupMenu(
|
||||||
getNoteMenu({
|
getNoteMenu({
|
||||||
note: note,
|
note: note.value,
|
||||||
translating,
|
translating,
|
||||||
translation,
|
translation,
|
||||||
menuButton,
|
menuButton,
|
||||||
|
@ -346,21 +348,24 @@ function onContextmenu(ev: MouseEvent): void {
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
type: "label",
|
type: "label",
|
||||||
text: notePage(appearNote),
|
text: notePage(appearNote.value),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "ph-browser ph-bold ph-lg",
|
icon: "ph-browser ph-bold ph-lg",
|
||||||
text: i18n.ts.openInWindow,
|
text: i18n.ts.openInWindow,
|
||||||
action: () => {
|
action: () => {
|
||||||
os.pageWindow(notePage(appearNote));
|
os.pageWindow(notePage(appearNote.value));
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
notePage(appearNote) != location.pathname
|
notePage(appearNote.value) != location.pathname
|
||||||
? {
|
? {
|
||||||
icon: "ph-arrows-out-simple ph-bold ph-lg",
|
icon: "ph-arrows-out-simple ph-bold ph-lg",
|
||||||
text: i18n.ts.showInPage,
|
text: i18n.ts.showInPage,
|
||||||
action: () => {
|
action: () => {
|
||||||
router.push(notePage(appearNote), "forcePage");
|
router.push(
|
||||||
|
notePage(appearNote.value),
|
||||||
|
"forcePage",
|
||||||
|
);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
|
@ -369,22 +374,22 @@ function onContextmenu(ev: MouseEvent): void {
|
||||||
type: "a",
|
type: "a",
|
||||||
icon: "ph-arrow-square-out ph-bold ph-lg",
|
icon: "ph-arrow-square-out ph-bold ph-lg",
|
||||||
text: i18n.ts.openInNewTab,
|
text: i18n.ts.openInNewTab,
|
||||||
href: notePage(appearNote),
|
href: notePage(appearNote.value),
|
||||||
target: "_blank",
|
target: "_blank",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "ph-link-simple ph-bold ph-lg",
|
icon: "ph-link-simple ph-bold ph-lg",
|
||||||
text: i18n.ts.copyLink,
|
text: i18n.ts.copyLink,
|
||||||
action: () => {
|
action: () => {
|
||||||
copyToClipboard(`${url}${notePage(appearNote)}`);
|
copyToClipboard(`${url}${notePage(appearNote.value)}`);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
note.user.host != null
|
note.value.user.host != null
|
||||||
? {
|
? {
|
||||||
type: "a",
|
type: "a",
|
||||||
icon: "ph-arrow-square-up-right ph-bold ph-lg",
|
icon: "ph-arrow-square-up-right ph-bold ph-lg",
|
||||||
text: i18n.ts.showOnRemote,
|
text: i18n.ts.showOnRemote,
|
||||||
href: note.url ?? note.uri ?? "",
|
href: note.value.url ?? note.value.uri ?? "",
|
||||||
target: "_blank",
|
target: "_blank",
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
|
|
|
@ -39,6 +39,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { computed, ref } from "vue";
|
||||||
|
|
||||||
import {} from "vue";
|
import {} from "vue";
|
||||||
import { notificationTypes } from "firefish-js";
|
import { notificationTypes } from "firefish-js";
|
||||||
import MkSwitch from "./form/switch.vue";
|
import MkSwitch from "./form/switch.vue";
|
||||||
|
@ -63,43 +65,45 @@ const props = withDefaults(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let includingTypes = $computed(() => props.includingTypes || []);
|
let includingTypes = computed(() => props.includingTypes || []);
|
||||||
|
|
||||||
const dialog = $ref<InstanceType<typeof XModalWindow>>();
|
const dialog = ref<InstanceType<typeof XModalWindow>>();
|
||||||
|
|
||||||
let typesMap = $ref<Record<(typeof notificationTypes)[number], boolean>>({});
|
let typesMap = ref<Record<(typeof notificationTypes)[number], boolean>>({});
|
||||||
let useGlobalSetting = $ref(
|
let useGlobalSetting = ref(
|
||||||
(includingTypes === null || includingTypes.length === 0) &&
|
(includingTypes.value === null || includingTypes.value.length === 0) &&
|
||||||
props.showGlobalToggle,
|
props.showGlobalToggle,
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const ntype of notificationTypes) {
|
for (const ntype of notificationTypes) {
|
||||||
typesMap[ntype] = includingTypes.includes(ntype);
|
typesMap.value[ntype] = includingTypes.value.includes(ntype);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ok() {
|
function ok() {
|
||||||
if (useGlobalSetting) {
|
if (useGlobalSetting.value) {
|
||||||
emit("done", { includingTypes: null });
|
emit("done", { includingTypes: null });
|
||||||
} else {
|
} else {
|
||||||
emit("done", {
|
emit("done", {
|
||||||
includingTypes: (
|
includingTypes: (
|
||||||
Object.keys(typesMap) as (typeof notificationTypes)[number][]
|
Object.keys(
|
||||||
).filter((type) => typesMap[type]),
|
typesMap.value,
|
||||||
|
) as (typeof notificationTypes)[number][]
|
||||||
|
).filter((type) => typesMap.value[type]),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog.close();
|
dialog.value.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
function disableAll() {
|
function disableAll() {
|
||||||
for (const type in typesMap) {
|
for (const type in typesMap.value) {
|
||||||
typesMap[type as (typeof notificationTypes)[number]] = false;
|
typesMap.value[type as (typeof notificationTypes)[number]] = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function enableAll() {
|
function enableAll() {
|
||||||
for (const type in typesMap) {
|
for (const type in typesMap.value) {
|
||||||
typesMap[type as (typeof notificationTypes)[number]] = true;
|
typesMap.value[type as (typeof notificationTypes)[number]] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted } from "vue";
|
import { onMounted, ref } from "vue";
|
||||||
import XNotification from "@/components/MkNotification.vue";
|
import XNotification from "@/components/MkNotification.vue";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
|
|
||||||
|
@ -28,11 +28,11 @@ const emit = defineEmits<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const zIndex = os.claimZIndex("high");
|
const zIndex = os.claimZIndex("high");
|
||||||
let showing = $ref(true);
|
let showing = ref(true);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
showing = false;
|
showing.value = false;
|
||||||
}, 6000);
|
}, 6000);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -46,20 +46,12 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {
|
import { onUnmounted, onMounted, computed, ref } from "vue";
|
||||||
defineComponent,
|
|
||||||
markRaw,
|
|
||||||
onUnmounted,
|
|
||||||
onMounted,
|
|
||||||
computed,
|
|
||||||
ref,
|
|
||||||
} from "vue";
|
|
||||||
import { notificationTypes } from "firefish-js";
|
import { notificationTypes } from "firefish-js";
|
||||||
import MkPagination, { Paging } from "@/components/MkPagination.vue";
|
import MkPagination, { Paging } from "@/components/MkPagination.vue";
|
||||||
import XNotification from "@/components/MkNotification.vue";
|
import XNotification from "@/components/MkNotification.vue";
|
||||||
import XList from "@/components/MkDateSeparatedList.vue";
|
import XList from "@/components/MkDateSeparatedList.vue";
|
||||||
import XNote from "@/components/MkNote.vue";
|
import XNote from "@/components/MkNote.vue";
|
||||||
import * as os from "@/os";
|
|
||||||
import { stream } from "@/stream";
|
import { stream } from "@/stream";
|
||||||
import { $i } from "@/account";
|
import { $i } from "@/account";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, reactive, watch } from "vue";
|
import { reactive, watch } from "vue";
|
||||||
import gsap from "gsap";
|
import gsap from "gsap";
|
||||||
import number from "@/filters/number";
|
import number from "@/filters/number";
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, reactive, ref } from "vue";
|
import { defineComponent, reactive } from "vue";
|
||||||
import number from "@/filters/number";
|
import number from "@/filters/number";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="zhyxdalp">
|
<div>
|
||||||
<XValue :value="value" :collapsed="false" />
|
<XValue :value="value" :collapsed="false" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -12,8 +12,3 @@ const props = defineProps<{
|
||||||
value: Record<string, unknown>;
|
value: Record<string, unknown>;
|
||||||
}>();
|
}>();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.zhyxdalp {
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -30,21 +30,16 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ComputedRef, inject, provide } from "vue";
|
import { ComputedRef, provide, ref, computed } from "vue";
|
||||||
import RouterView from "@/components/global/RouterView.vue";
|
import RouterView from "@/components/global/RouterView.vue";
|
||||||
import XWindow from "@/components/MkWindow.vue";
|
import XWindow from "@/components/MkWindow.vue";
|
||||||
import { popout as _popout } from "@/scripts/popout";
|
import { popout as _popout } from "@/scripts/popout";
|
||||||
import copyToClipboard from "@/scripts/copy-to-clipboard";
|
import copyToClipboard from "@/scripts/copy-to-clipboard";
|
||||||
import { url } from "@/config";
|
import { url } from "@/config";
|
||||||
import * as os from "@/os";
|
|
||||||
import { mainRouter, routes } from "@/router";
|
import { mainRouter, routes } from "@/router";
|
||||||
import { Router } from "@/nirax";
|
import { Router } from "@/nirax";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import {
|
import { PageMetadata, provideMetadataReceiver } from "@/scripts/page-metadata";
|
||||||
PageMetadata,
|
|
||||||
provideMetadataReceiver,
|
|
||||||
setPageMetadata,
|
|
||||||
} from "@/scripts/page-metadata";
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
initialPath: string;
|
initialPath: string;
|
||||||
|
@ -56,18 +51,18 @@ defineEmits<{
|
||||||
|
|
||||||
const router = new Router(routes, props.initialPath);
|
const router = new Router(routes, props.initialPath);
|
||||||
|
|
||||||
let pageMetadata = $ref<null | ComputedRef<PageMetadata>>();
|
let pageMetadata = ref<null | ComputedRef<PageMetadata>>();
|
||||||
let windowEl = $ref<InstanceType<typeof XWindow>>();
|
let windowEl = ref<InstanceType<typeof XWindow>>();
|
||||||
const history = $ref<{ path: string; key: any }[]>([
|
const history = ref<{ path: string; key: any }[]>([
|
||||||
{
|
{
|
||||||
path: router.getCurrentPath(),
|
path: router.getCurrentPath(),
|
||||||
key: router.getCurrentKey(),
|
key: router.getCurrentKey(),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
const buttonsLeft = $computed(() => {
|
const buttonsLeft = computed(() => {
|
||||||
const buttons = [];
|
const buttons = [];
|
||||||
|
|
||||||
if (history.length > 1) {
|
if (history.value.length > 1) {
|
||||||
buttons.push({
|
buttons.push({
|
||||||
icon: "ph-caret-left ph-bold ph-lg",
|
icon: "ph-caret-left ph-bold ph-lg",
|
||||||
onClick: back,
|
onClick: back,
|
||||||
|
@ -76,7 +71,7 @@ const buttonsLeft = $computed(() => {
|
||||||
|
|
||||||
return buttons;
|
return buttons;
|
||||||
});
|
});
|
||||||
const buttonsRight = $computed(() => {
|
const buttonsRight = computed(() => {
|
||||||
const buttons = [
|
const buttons = [
|
||||||
{
|
{
|
||||||
icon: "ph-arrows-out-simple ph-bold ph-lg",
|
icon: "ph-arrows-out-simple ph-bold ph-lg",
|
||||||
|
@ -89,18 +84,18 @@ const buttonsRight = $computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
router.addListener("push", (ctx) => {
|
router.addListener("push", (ctx) => {
|
||||||
history.push({ path: ctx.path, key: ctx.key });
|
history.value.push({ path: ctx.path, key: ctx.key });
|
||||||
});
|
});
|
||||||
|
|
||||||
provide("router", router);
|
provide("router", router);
|
||||||
provideMetadataReceiver((info) => {
|
provideMetadataReceiver((info) => {
|
||||||
pageMetadata = info;
|
pageMetadata.value = info;
|
||||||
});
|
});
|
||||||
provide("shouldOmitHeaderTitle", true);
|
provide("shouldOmitHeaderTitle", true);
|
||||||
provide("shouldBackButton", false);
|
provide("shouldBackButton", false);
|
||||||
provide("shouldHeaderThin", true);
|
provide("shouldHeaderThin", true);
|
||||||
|
|
||||||
const contextmenu = $computed(() => [
|
const contextmenu = computed(() => [
|
||||||
{
|
{
|
||||||
icon: "ph-arrows-out-simple ph-bold ph-lg",
|
icon: "ph-arrows-out-simple ph-bold ph-lg",
|
||||||
text: i18n.ts.showInPage,
|
text: i18n.ts.showInPage,
|
||||||
|
@ -116,7 +111,7 @@ const contextmenu = $computed(() => [
|
||||||
text: i18n.ts.openInNewTab,
|
text: i18n.ts.openInNewTab,
|
||||||
action: () => {
|
action: () => {
|
||||||
window.open(url + router.getCurrentPath(), "_blank");
|
window.open(url + router.getCurrentPath(), "_blank");
|
||||||
windowEl.close();
|
windowEl.value.close();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -128,30 +123,26 @@ const contextmenu = $computed(() => [
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
function menu(ev) {
|
|
||||||
os.popupMenu(contextmenu, ev.currentTarget ?? ev.target);
|
|
||||||
}
|
|
||||||
|
|
||||||
function back() {
|
function back() {
|
||||||
history.pop();
|
history.value.pop();
|
||||||
router.replace(
|
router.replace(
|
||||||
history[history.length - 1].path,
|
history.value[history.value.length - 1].path,
|
||||||
history[history.length - 1].key,
|
history.value[history.value.length - 1].key,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function close() {
|
function close() {
|
||||||
windowEl.close();
|
windowEl.value.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
function expand() {
|
function expand() {
|
||||||
mainRouter.push(router.getCurrentPath(), "forcePage");
|
mainRouter.push(router.getCurrentPath(), "forcePage");
|
||||||
windowEl.close();
|
windowEl.value.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
function popout() {
|
function popout() {
|
||||||
_popout(router.getCurrentPath(), windowEl.$el);
|
_popout(router.getCurrentPath(), windowEl.value.$el);
|
||||||
windowEl.close();
|
windowEl.value.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
|
|
|
@ -67,10 +67,8 @@ import {
|
||||||
computed,
|
computed,
|
||||||
ComputedRef,
|
ComputedRef,
|
||||||
isRef,
|
isRef,
|
||||||
markRaw,
|
|
||||||
onActivated,
|
onActivated,
|
||||||
onDeactivated,
|
onDeactivated,
|
||||||
Ref,
|
|
||||||
ref,
|
ref,
|
||||||
watch,
|
watch,
|
||||||
} from "vue";
|
} from "vue";
|
||||||
|
|
|
@ -24,7 +24,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {} from "vue";
|
import { ref } from "vue";
|
||||||
|
|
||||||
import MkModal from "./MkModal.vue";
|
import MkModal from "./MkModal.vue";
|
||||||
import MkMenu from "./MkMenu.vue";
|
import MkMenu from "./MkMenu.vue";
|
||||||
import { MenuItem } from "@/types/menu";
|
import { MenuItem } from "@/types/menu";
|
||||||
|
@ -42,7 +43,7 @@ const emit = defineEmits<{
|
||||||
(ev: "closed"): void;
|
(ev: "closed"): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
let modal = $ref<InstanceType<typeof MkModal>>();
|
let modal = ref<InstanceType<typeof MkModal>>();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
@ -233,7 +233,15 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { inject, watch, nextTick, onMounted, defineAsyncComponent } from "vue";
|
import {
|
||||||
|
inject,
|
||||||
|
watch,
|
||||||
|
nextTick,
|
||||||
|
onMounted,
|
||||||
|
defineAsyncComponent,
|
||||||
|
ref,
|
||||||
|
computed,
|
||||||
|
} from "vue";
|
||||||
import * as mfm from "mfm-js";
|
import * as mfm from "mfm-js";
|
||||||
import * as misskey from "firefish-js";
|
import * as misskey from "firefish-js";
|
||||||
import autosize from "autosize";
|
import autosize from "autosize";
|
||||||
|
@ -302,45 +310,45 @@ const emit = defineEmits<{
|
||||||
(ev: "esc"): void;
|
(ev: "esc"): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const textareaEl = $ref<HTMLTextAreaElement | null>(null);
|
const textareaEl = ref<HTMLTextAreaElement | null>(null);
|
||||||
const cwInputEl = $ref<HTMLInputElement | null>(null);
|
const cwInputEl = ref<HTMLInputElement | null>(null);
|
||||||
const hashtagsInputEl = $ref<HTMLInputElement | null>(null);
|
const hashtagsInputEl = ref<HTMLInputElement | null>(null);
|
||||||
const visibilityButton = $ref<HTMLElement | null>(null);
|
const visibilityButton = ref<HTMLElement | null>(null);
|
||||||
|
|
||||||
let posting = $ref(false);
|
let posting = ref(false);
|
||||||
let text = $ref(props.initialText ?? "");
|
let text = ref(props.initialText ?? "");
|
||||||
let files = $ref(props.initialFiles ?? []);
|
let files = ref(props.initialFiles ?? []);
|
||||||
let poll = $ref<{
|
let poll = ref<{
|
||||||
choices: string[];
|
choices: string[];
|
||||||
multiple: boolean;
|
multiple: boolean;
|
||||||
expiresAt: string | null;
|
expiresAt: string | null;
|
||||||
expiredAfter: string | null;
|
expiredAfter: string | null;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
let useCw = $ref(false);
|
let useCw = ref(false);
|
||||||
let showPreview = $ref(false);
|
let showPreview = ref(false);
|
||||||
let cw = $ref<string | null>(null);
|
let cw = ref<string | null>(null);
|
||||||
let localOnly = $ref<boolean>(
|
let localOnly = ref<boolean>(
|
||||||
props.initialLocalOnly ?? defaultStore.state.rememberNoteVisibility
|
props.initialLocalOnly ?? defaultStore.state.rememberNoteVisibility
|
||||||
? defaultStore.state.localOnly
|
? defaultStore.state.localOnly
|
||||||
: defaultStore.state.defaultNoteLocalOnly,
|
: defaultStore.state.defaultNoteLocalOnly,
|
||||||
);
|
);
|
||||||
let visibility = $ref(
|
let visibility = ref(
|
||||||
props.initialVisibility ??
|
props.initialVisibility ??
|
||||||
((defaultStore.state.rememberNoteVisibility
|
((defaultStore.state.rememberNoteVisibility
|
||||||
? defaultStore.state.visibility
|
? defaultStore.state.visibility
|
||||||
: defaultStore.state
|
: defaultStore.state
|
||||||
.defaultNoteVisibility) as (typeof misskey.noteVisibilities)[number]),
|
.defaultNoteVisibility) as (typeof misskey.noteVisibilities)[number]),
|
||||||
);
|
);
|
||||||
let visibleUsers = $ref([]);
|
let visibleUsers = ref([]);
|
||||||
if (props.initialVisibleUsers) {
|
if (props.initialVisibleUsers) {
|
||||||
props.initialVisibleUsers.forEach(pushVisibleUser);
|
props.initialVisibleUsers.forEach(pushVisibleUser);
|
||||||
}
|
}
|
||||||
let autocomplete = $ref(null);
|
let autocomplete = ref(null);
|
||||||
let draghover = $ref(false);
|
let draghover = ref(false);
|
||||||
let quoteId = $ref(null);
|
let quoteId = ref(null);
|
||||||
let hasNotSpecifiedMentions = $ref(false);
|
let hasNotSpecifiedMentions = ref(false);
|
||||||
let recentHashtags = $ref(JSON.parse(localStorage.getItem("hashtags") || "[]"));
|
let recentHashtags = ref(JSON.parse(localStorage.getItem("hashtags") || "[]"));
|
||||||
let imeText = $ref("");
|
let imeText = ref("");
|
||||||
|
|
||||||
const typing = throttle(3000, () => {
|
const typing = throttle(3000, () => {
|
||||||
if (props.channel) {
|
if (props.channel) {
|
||||||
|
@ -348,7 +356,7 @@ const typing = throttle(3000, () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const draftKey = $computed((): string => {
|
const draftKey = computed((): string => {
|
||||||
if (props.editId) {
|
if (props.editId) {
|
||||||
return `edit:${props.editId}`;
|
return `edit:${props.editId}`;
|
||||||
}
|
}
|
||||||
|
@ -366,7 +374,7 @@ const draftKey = $computed((): string => {
|
||||||
return key;
|
return key;
|
||||||
});
|
});
|
||||||
|
|
||||||
const placeholder = $computed((): string => {
|
const placeholder = computed((): string => {
|
||||||
if (props.renote) {
|
if (props.renote) {
|
||||||
return i18n.ts._postForm.quotePlaceholder;
|
return i18n.ts._postForm.quotePlaceholder;
|
||||||
} else if (props.reply) {
|
} else if (props.reply) {
|
||||||
|
@ -386,7 +394,7 @@ const placeholder = $computed((): string => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const submitText = $computed((): string => {
|
const submitText = computed((): string => {
|
||||||
return props.editId
|
return props.editId
|
||||||
? i18n.ts.edit
|
? i18n.ts.edit
|
||||||
: props.renote
|
: props.renote
|
||||||
|
@ -396,34 +404,37 @@ const submitText = $computed((): string => {
|
||||||
: i18n.ts.note;
|
: i18n.ts.note;
|
||||||
});
|
});
|
||||||
|
|
||||||
const textLength = $computed((): number => {
|
const textLength = computed((): number => {
|
||||||
return length((preprocess(text) + imeText).trim());
|
return length((preprocess(text.value) + imeText.value).trim());
|
||||||
});
|
});
|
||||||
|
|
||||||
const maxTextLength = $computed((): number => {
|
const maxTextLength = computed((): number => {
|
||||||
return instance ? instance.maxNoteTextLength : 3000;
|
return instance ? instance.maxNoteTextLength : 3000;
|
||||||
});
|
});
|
||||||
|
|
||||||
const canPost = $computed((): boolean => {
|
const canPost = computed((): boolean => {
|
||||||
return (
|
return (
|
||||||
!posting &&
|
!posting.value &&
|
||||||
(1 <= textLength || 1 <= files.length || !!poll || !!props.renote) &&
|
(1 <= textLength.value ||
|
||||||
textLength <= maxTextLength &&
|
1 <= files.value.length ||
|
||||||
(!poll || poll.choices.length >= 2)
|
!!poll.value ||
|
||||||
|
!!props.renote) &&
|
||||||
|
textLength.value <= maxTextLength.value &&
|
||||||
|
(!poll.value || poll.value.choices.length >= 2)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const withHashtags = $computed(
|
const withHashtags = computed(
|
||||||
defaultStore.makeGetterSetter("postFormWithHashtags"),
|
defaultStore.makeGetterSetter("postFormWithHashtags"),
|
||||||
);
|
);
|
||||||
const hashtags = $computed(defaultStore.makeGetterSetter("postFormHashtags"));
|
const hashtags = computed(defaultStore.makeGetterSetter("postFormHashtags"));
|
||||||
|
|
||||||
watch($$(text), () => {
|
watch(text, () => {
|
||||||
checkMissingMention();
|
checkMissingMention();
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
$$(visibleUsers),
|
visibleUsers,
|
||||||
() => {
|
() => {
|
||||||
checkMissingMention();
|
checkMissingMention();
|
||||||
},
|
},
|
||||||
|
@ -433,10 +444,10 @@ watch(
|
||||||
);
|
);
|
||||||
|
|
||||||
if (props.mention) {
|
if (props.mention) {
|
||||||
text = props.mention.host
|
text.value = props.mention.host
|
||||||
? `@${props.mention.username}@${toASCII(props.mention.host)}`
|
? `@${props.mention.username}@${toASCII(props.mention.host)}`
|
||||||
: `@${props.mention.username}`;
|
: `@${props.mention.username}`;
|
||||||
text += " ";
|
text.value += " ";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -444,7 +455,7 @@ if (
|
||||||
(props.reply.user.username !== $i.username ||
|
(props.reply.user.username !== $i.username ||
|
||||||
(props.reply.user.host != null && props.reply.user.host !== host))
|
(props.reply.user.host != null && props.reply.user.host !== host))
|
||||||
) {
|
) {
|
||||||
text = `@${props.reply.user.username}${
|
text.value = `@${props.reply.user.username}${
|
||||||
props.reply.user.host != null
|
props.reply.user.host != null
|
||||||
? "@" + toASCII(props.reply.user.host)
|
? "@" + toASCII(props.reply.user.host)
|
||||||
: ""
|
: ""
|
||||||
|
@ -467,15 +478,15 @@ if (props.reply && props.reply.text != null) {
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// 重複は除外
|
// 重複は除外
|
||||||
if (text.includes(`${mention} `)) continue;
|
if (text.value.includes(`${mention} `)) continue;
|
||||||
|
|
||||||
text += `${mention} `;
|
text.value += `${mention} `;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.channel) {
|
if (props.channel) {
|
||||||
visibility = "public";
|
visibility.value = "public";
|
||||||
localOnly = true; // TODO: チャンネルが連合するようになった折には消す
|
localOnly.value = true; // TODO: チャンネルが連合するようになった折には消す
|
||||||
}
|
}
|
||||||
|
|
||||||
// 公開以外へのリプライ時は元の公開範囲を引き継ぐ
|
// 公開以外へのリプライ時は元の公開範囲を引き継ぐ
|
||||||
|
@ -483,17 +494,17 @@ if (
|
||||||
props.reply &&
|
props.reply &&
|
||||||
["home", "followers", "specified"].includes(props.reply.visibility)
|
["home", "followers", "specified"].includes(props.reply.visibility)
|
||||||
) {
|
) {
|
||||||
if (props.reply.visibility === "home" && visibility === "followers") {
|
if (props.reply.visibility === "home" && visibility.value === "followers") {
|
||||||
visibility = "followers";
|
visibility.value = "followers";
|
||||||
} else if (
|
} else if (
|
||||||
["home", "followers"].includes(props.reply.visibility) &&
|
["home", "followers"].includes(props.reply.visibility) &&
|
||||||
visibility === "specified"
|
visibility.value === "specified"
|
||||||
) {
|
) {
|
||||||
visibility = "specified";
|
visibility.value = "specified";
|
||||||
} else {
|
} else {
|
||||||
visibility = props.reply.visibility;
|
visibility.value = props.reply.visibility;
|
||||||
}
|
}
|
||||||
if (visibility === "specified") {
|
if (visibility.value === "specified") {
|
||||||
if (props.reply.visibleUserIds) {
|
if (props.reply.visibleUserIds) {
|
||||||
os.api("users/show", {
|
os.api("users/show", {
|
||||||
userIds: props.reply.visibleUserIds.filter(
|
userIds: props.reply.visibleUserIds.filter(
|
||||||
|
@ -515,7 +526,7 @@ if (
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.specified) {
|
if (props.specified) {
|
||||||
visibility = "specified";
|
visibility.value = "specified";
|
||||||
pushVisibleUser(props.specified);
|
pushVisibleUser(props.specified);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -531,53 +542,53 @@ const addRe = (s: string) => {
|
||||||
|
|
||||||
// keep cw when reply
|
// keep cw when reply
|
||||||
if (defaultStore.state.keepCw && props.reply && props.reply.cw) {
|
if (defaultStore.state.keepCw && props.reply && props.reply.cw) {
|
||||||
useCw = true;
|
useCw.value = true;
|
||||||
cw =
|
cw.value =
|
||||||
props.reply.user.username === $i.username
|
props.reply.user.username === $i.username
|
||||||
? props.reply.cw
|
? props.reply.cw
|
||||||
: addRe(props.reply.cw);
|
: addRe(props.reply.cw);
|
||||||
}
|
}
|
||||||
|
|
||||||
function watchForDraft() {
|
function watchForDraft() {
|
||||||
watch($$(text), () => saveDraft());
|
watch(text, () => saveDraft());
|
||||||
watch($$(useCw), () => saveDraft());
|
watch(useCw, () => saveDraft());
|
||||||
watch($$(cw), () => saveDraft());
|
watch(cw, () => saveDraft());
|
||||||
watch($$(poll), () => saveDraft());
|
watch(poll, () => saveDraft());
|
||||||
watch($$(files), () => saveDraft(), { deep: true });
|
watch(files, () => saveDraft(), { deep: true });
|
||||||
watch($$(visibility), () => saveDraft());
|
watch(visibility, () => saveDraft());
|
||||||
watch($$(localOnly), () => saveDraft());
|
watch(localOnly, () => saveDraft());
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkMissingMention() {
|
function checkMissingMention() {
|
||||||
if (visibility === "specified") {
|
if (visibility.value === "specified") {
|
||||||
const ast = mfm.parse(text);
|
const ast = mfm.parse(text.value);
|
||||||
|
|
||||||
for (const x of extractMentions(ast)) {
|
for (const x of extractMentions(ast)) {
|
||||||
if (
|
if (
|
||||||
!visibleUsers.some(
|
!visibleUsers.value.some(
|
||||||
(u) => u.username === x.username && u.host === x.host,
|
(u) => u.username === x.username && u.host === x.host,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
hasNotSpecifiedMentions = true;
|
hasNotSpecifiedMentions.value = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hasNotSpecifiedMentions = false;
|
hasNotSpecifiedMentions.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function addMissingMention() {
|
function addMissingMention() {
|
||||||
const ast = mfm.parse(text);
|
const ast = mfm.parse(text.value);
|
||||||
|
|
||||||
for (const x of extractMentions(ast)) {
|
for (const x of extractMentions(ast)) {
|
||||||
if (
|
if (
|
||||||
!visibleUsers.some(
|
!visibleUsers.value.some(
|
||||||
(u) => u.username === x.username && u.host === x.host,
|
(u) => u.username === x.username && u.host === x.host,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
os.api("users/show", { username: x.username, host: x.host }).then(
|
os.api("users/show", { username: x.username, host: x.host }).then(
|
||||||
(user) => {
|
(user) => {
|
||||||
visibleUsers.push(user);
|
visibleUsers.value.push(user);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -585,10 +596,10 @@ function addMissingMention() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function togglePoll() {
|
function togglePoll() {
|
||||||
if (poll) {
|
if (poll.value) {
|
||||||
poll = null;
|
poll.value = null;
|
||||||
} else {
|
} else {
|
||||||
poll = {
|
poll.value = {
|
||||||
choices: ["", ""],
|
choices: ["", ""],
|
||||||
multiple: false,
|
multiple: false,
|
||||||
expiresAt: null,
|
expiresAt: null,
|
||||||
|
@ -598,15 +609,15 @@ function togglePoll() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function addTag(tag: string) {
|
function addTag(tag: string) {
|
||||||
insertTextAtCursor(textareaEl, ` #${tag} `);
|
insertTextAtCursor(textareaEl.value, ` #${tag} `);
|
||||||
}
|
}
|
||||||
|
|
||||||
function focus() {
|
function focus() {
|
||||||
if (textareaEl) {
|
if (textareaEl.value) {
|
||||||
textareaEl.focus();
|
textareaEl.value.focus();
|
||||||
textareaEl.setSelectionRange(
|
textareaEl.value.setSelectionRange(
|
||||||
textareaEl.value.length,
|
textareaEl.value.value.length,
|
||||||
textareaEl.value.length,
|
textareaEl.value.value.length,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -615,31 +626,32 @@ function chooseFileFrom(ev) {
|
||||||
selectFiles(ev.currentTarget ?? ev.target, i18n.ts.attachFile).then(
|
selectFiles(ev.currentTarget ?? ev.target, i18n.ts.attachFile).then(
|
||||||
(files_) => {
|
(files_) => {
|
||||||
for (const file of files_) {
|
for (const file of files_) {
|
||||||
files.push(file);
|
files.value.push(file);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function detachFile(id) {
|
function detachFile(id) {
|
||||||
files = files.filter((x) => x.id !== id);
|
files.value = files.value.filter((x) => x.id !== id);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateFiles(_files) {
|
function updateFiles(_files) {
|
||||||
files = _files;
|
files.value = _files;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateFileSensitive(file, sensitive) {
|
function updateFileSensitive(file, sensitive) {
|
||||||
files[files.findIndex((x) => x.id === file.id)].isSensitive = sensitive;
|
files.value[files.value.findIndex((x) => x.id === file.id)].isSensitive =
|
||||||
|
sensitive;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateFileName(file, name) {
|
function updateFileName(file, name) {
|
||||||
files[files.findIndex((x) => x.id === file.id)].name = name;
|
files.value[files.value.findIndex((x) => x.id === file.id)].name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
function upload(file: File, name?: string) {
|
function upload(file: File, name?: string) {
|
||||||
uploadFile(file, defaultStore.state.uploadFolder, name).then((res) => {
|
uploadFile(file, defaultStore.state.uploadFolder, name).then((res) => {
|
||||||
files.push(res);
|
files.value.push(res);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -654,21 +666,21 @@ function setVisibility() {
|
||||||
() => import("@/components/MkVisibilityPicker.vue"),
|
() => import("@/components/MkVisibilityPicker.vue"),
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
currentVisibility: visibility,
|
currentVisibility: visibility.value,
|
||||||
currentLocalOnly: localOnly,
|
currentLocalOnly: localOnly.value,
|
||||||
src: visibilityButton,
|
src: visibilityButton.value,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
changeVisibility: (v) => {
|
changeVisibility: (v) => {
|
||||||
visibility = v;
|
visibility.value = v;
|
||||||
if (defaultStore.state.rememberNoteVisibility) {
|
if (defaultStore.state.rememberNoteVisibility) {
|
||||||
defaultStore.set("visibility", visibility);
|
defaultStore.set("visibility", visibility.value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
changeLocalOnly: (v) => {
|
changeLocalOnly: (v) => {
|
||||||
localOnly = v;
|
localOnly.value = v;
|
||||||
if (defaultStore.state.rememberNoteVisibility) {
|
if (defaultStore.state.rememberNoteVisibility) {
|
||||||
defaultStore.set("localOnly", localOnly);
|
defaultStore.set("localOnly", localOnly.value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -678,11 +690,11 @@ function setVisibility() {
|
||||||
|
|
||||||
function pushVisibleUser(user) {
|
function pushVisibleUser(user) {
|
||||||
if (
|
if (
|
||||||
!visibleUsers.some(
|
!visibleUsers.value.some(
|
||||||
(u) => u.username === user.username && u.host === user.host,
|
(u) => u.username === user.username && u.host === user.host,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
visibleUsers.push(user);
|
visibleUsers.value.push(user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -693,21 +705,21 @@ function addVisibleUser() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeVisibleUser(user) {
|
function removeVisibleUser(user) {
|
||||||
visibleUsers = erase(user, visibleUsers);
|
visibleUsers.value = erase(user, visibleUsers.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function clear() {
|
function clear() {
|
||||||
text = "";
|
text.value = "";
|
||||||
files = [];
|
files.value = [];
|
||||||
poll = null;
|
poll.value = null;
|
||||||
quoteId = null;
|
quoteId.value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onKeydown(ev: KeyboardEvent) {
|
function onKeydown(ev: KeyboardEvent) {
|
||||||
if (
|
if (
|
||||||
(ev.which === 10 || ev.which === 13) &&
|
(ev.which === 10 || ev.which === 13) &&
|
||||||
(ev.ctrlKey || ev.metaKey) &&
|
(ev.ctrlKey || ev.metaKey) &&
|
||||||
canPost
|
canPost.value
|
||||||
)
|
)
|
||||||
post();
|
post();
|
||||||
if (ev.which === 27) emit("esc");
|
if (ev.which === 27) emit("esc");
|
||||||
|
@ -715,12 +727,12 @@ function onKeydown(ev: KeyboardEvent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function onCompositionUpdate(ev: CompositionEvent) {
|
function onCompositionUpdate(ev: CompositionEvent) {
|
||||||
imeText = ev.data;
|
imeText.value = ev.data;
|
||||||
typing();
|
typing();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onCompositionEnd(ev: CompositionEvent) {
|
function onCompositionEnd(ev: CompositionEvent) {
|
||||||
imeText = "";
|
imeText.value = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onPaste(ev: ClipboardEvent) {
|
async function onPaste(ev: ClipboardEvent) {
|
||||||
|
@ -741,7 +753,7 @@ async function onPaste(ev: ClipboardEvent) {
|
||||||
|
|
||||||
const paste = ev.clipboardData.getData("text");
|
const paste = ev.clipboardData.getData("text");
|
||||||
|
|
||||||
if (!props.renote && !quoteId && paste.startsWith(url + "/notes/")) {
|
if (!props.renote && !quoteId.value && paste.startsWith(url + "/notes/")) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
|
||||||
os.yesno({
|
os.yesno({
|
||||||
|
@ -749,11 +761,13 @@ async function onPaste(ev: ClipboardEvent) {
|
||||||
text: i18n.ts.quoteQuestion,
|
text: i18n.ts.quoteQuestion,
|
||||||
}).then(({ canceled }) => {
|
}).then(({ canceled }) => {
|
||||||
if (canceled) {
|
if (canceled) {
|
||||||
insertTextAtCursor(textareaEl, paste);
|
insertTextAtCursor(textareaEl.value, paste);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
quoteId = paste.substr(url.length).match(/^\/notes\/(.+?)\/?$/)[1];
|
quoteId.value = paste
|
||||||
|
.substr(url.length)
|
||||||
|
.match(/^\/notes\/(.+?)\/?$/)[1];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -764,7 +778,7 @@ function onDragover(ev) {
|
||||||
const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_;
|
const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_;
|
||||||
if (isFile || isDriveFile) {
|
if (isFile || isDriveFile) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
draghover = true;
|
draghover.value = true;
|
||||||
switch (ev.dataTransfer.effectAllowed) {
|
switch (ev.dataTransfer.effectAllowed) {
|
||||||
case "all":
|
case "all":
|
||||||
case "uninitialized":
|
case "uninitialized":
|
||||||
|
@ -785,15 +799,15 @@ function onDragover(ev) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function onDragenter(ev) {
|
function onDragenter(ev) {
|
||||||
draghover = true;
|
draghover.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onDragleave(ev) {
|
function onDragleave(ev) {
|
||||||
draghover = false;
|
draghover.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onDrop(ev): void {
|
function onDrop(ev): void {
|
||||||
draghover = false;
|
draghover.value = false;
|
||||||
|
|
||||||
// ファイルだったら
|
// ファイルだったら
|
||||||
if (ev.dataTransfer.files.length > 0) {
|
if (ev.dataTransfer.files.length > 0) {
|
||||||
|
@ -806,7 +820,7 @@ function onDrop(ev): void {
|
||||||
const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
|
const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
|
||||||
if (driveFile != null && driveFile !== "") {
|
if (driveFile != null && driveFile !== "") {
|
||||||
const file = JSON.parse(driveFile);
|
const file = JSON.parse(driveFile);
|
||||||
files.push(file);
|
files.value.push(file);
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
@ -815,16 +829,16 @@ function onDrop(ev): void {
|
||||||
function saveDraft() {
|
function saveDraft() {
|
||||||
const draftData = JSON.parse(localStorage.getItem("drafts") || "{}");
|
const draftData = JSON.parse(localStorage.getItem("drafts") || "{}");
|
||||||
|
|
||||||
draftData[draftKey] = {
|
draftData[draftKey.value] = {
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
data: {
|
data: {
|
||||||
text: text,
|
text: text.value,
|
||||||
useCw: useCw,
|
useCw: useCw.value,
|
||||||
cw: cw,
|
cw: cw.value,
|
||||||
visibility: visibility,
|
visibility: visibility.value,
|
||||||
localOnly: localOnly,
|
localOnly: localOnly.value,
|
||||||
files: files,
|
files: files.value,
|
||||||
poll: poll,
|
poll: poll.value,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -834,37 +848,38 @@ function saveDraft() {
|
||||||
function deleteDraft() {
|
function deleteDraft() {
|
||||||
const draftData = JSON.parse(localStorage.getItem("drafts") || "{}");
|
const draftData = JSON.parse(localStorage.getItem("drafts") || "{}");
|
||||||
|
|
||||||
delete draftData[draftKey];
|
delete draftData[draftKey.value];
|
||||||
|
|
||||||
localStorage.setItem("drafts", JSON.stringify(draftData));
|
localStorage.setItem("drafts", JSON.stringify(draftData));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function post() {
|
async function post() {
|
||||||
const processedText = preprocess(text);
|
const processedText = preprocess(text.value);
|
||||||
|
|
||||||
let postData = {
|
let postData = {
|
||||||
editId: props.editId ? props.editId : undefined,
|
editId: props.editId ? props.editId : undefined,
|
||||||
text: processedText === "" ? undefined : processedText,
|
text: processedText === "" ? undefined : processedText,
|
||||||
fileIds: files.length > 0 ? files.map((f) => f.id) : undefined,
|
fileIds:
|
||||||
|
files.value.length > 0 ? files.value.map((f) => f.id) : undefined,
|
||||||
replyId: props.reply ? props.reply.id : undefined,
|
replyId: props.reply ? props.reply.id : undefined,
|
||||||
renoteId: props.renote
|
renoteId: props.renote
|
||||||
? props.renote.id
|
? props.renote.id
|
||||||
: quoteId
|
: quoteId.value
|
||||||
? quoteId
|
? quoteId.value
|
||||||
: undefined,
|
: undefined,
|
||||||
channelId: props.channel ? props.channel.id : undefined,
|
channelId: props.channel ? props.channel.id : undefined,
|
||||||
poll: poll,
|
poll: poll.value,
|
||||||
cw: useCw ? cw || "" : undefined,
|
cw: useCw.value ? cw.value || "" : undefined,
|
||||||
localOnly: localOnly,
|
localOnly: localOnly.value,
|
||||||
visibility: visibility,
|
visibility: visibility.value,
|
||||||
visibleUserIds:
|
visibleUserIds:
|
||||||
visibility === "specified"
|
visibility.value === "specified"
|
||||||
? visibleUsers.map((u) => u.id)
|
? visibleUsers.value.map((u) => u.id)
|
||||||
: undefined,
|
: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (withHashtags && hashtags && hashtags.trim() !== "") {
|
if (withHashtags.value && hashtags.value && hashtags.value.trim() !== "") {
|
||||||
const hashtags_ = hashtags
|
const hashtags_ = hashtags.value
|
||||||
.trim()
|
.trim()
|
||||||
.split(" ")
|
.split(" ")
|
||||||
.map((x) => (x.startsWith("#") ? x : "#" + x))
|
.map((x) => (x.startsWith("#") ? x : "#" + x))
|
||||||
|
@ -883,12 +898,13 @@ async function post() {
|
||||||
|
|
||||||
let token = undefined;
|
let token = undefined;
|
||||||
|
|
||||||
if (postAccount) {
|
if (postAccount.value) {
|
||||||
const storedAccounts = await getAccounts();
|
const storedAccounts = await getAccounts();
|
||||||
token = storedAccounts.find((x) => x.id === postAccount.id)?.token;
|
token = storedAccounts.find((x) => x.id === postAccount.value.id)
|
||||||
|
?.token;
|
||||||
}
|
}
|
||||||
|
|
||||||
posting = true;
|
posting.value = true;
|
||||||
os.api(postData.editId ? "notes/edit" : "notes/create", postData, token)
|
os.api(postData.editId ? "notes/edit" : "notes/create", postData, token)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
clear();
|
clear();
|
||||||
|
@ -908,12 +924,12 @@ async function post() {
|
||||||
JSON.stringify(unique(hashtags_.concat(history))),
|
JSON.stringify(unique(hashtags_.concat(history))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
posting = false;
|
posting.value = false;
|
||||||
postAccount = null;
|
postAccount.value = null;
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
posting = false;
|
posting.value = false;
|
||||||
os.alert({
|
os.alert({
|
||||||
type: "error",
|
type: "error",
|
||||||
text: err.message + "\n" + (err as any).id,
|
text: err.message + "\n" + (err as any).id,
|
||||||
|
@ -927,12 +943,12 @@ function cancel() {
|
||||||
|
|
||||||
function insertMention() {
|
function insertMention() {
|
||||||
os.selectUser().then((user) => {
|
os.selectUser().then((user) => {
|
||||||
insertTextAtCursor(textareaEl, "@" + Acct.toString(user) + " ");
|
insertTextAtCursor(textareaEl.value, "@" + Acct.toString(user) + " ");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function insertEmoji(ev: MouseEvent) {
|
async function insertEmoji(ev: MouseEvent) {
|
||||||
os.openEmojiPicker(ev.currentTarget ?? ev.target, {}, textareaEl);
|
os.openEmojiPicker(ev.currentTarget ?? ev.target, {}, textareaEl.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function openCheatSheet(ev: MouseEvent) {
|
async function openCheatSheet(ev: MouseEvent) {
|
||||||
|
@ -946,11 +962,11 @@ function showActions(ev) {
|
||||||
action: () => {
|
action: () => {
|
||||||
action.handler(
|
action.handler(
|
||||||
{
|
{
|
||||||
text: text,
|
text: text.value,
|
||||||
},
|
},
|
||||||
(key, value) => {
|
(key, value) => {
|
||||||
if (key === "text") {
|
if (key === "text") {
|
||||||
text = value;
|
text.value = value;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -960,19 +976,19 @@ function showActions(ev) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let postAccount = $ref<misskey.entities.UserDetailed | null>(null);
|
let postAccount = ref<misskey.entities.UserDetailed | null>(null);
|
||||||
|
|
||||||
function openAccountMenu(ev: MouseEvent) {
|
function openAccountMenu(ev: MouseEvent) {
|
||||||
openAccountMenu_(
|
openAccountMenu_(
|
||||||
{
|
{
|
||||||
withExtraOperation: false,
|
withExtraOperation: false,
|
||||||
includeCurrentAccount: true,
|
includeCurrentAccount: true,
|
||||||
active: postAccount != null ? postAccount.id : $i.id,
|
active: postAccount.value != null ? postAccount.value.id : $i.id,
|
||||||
onChoose: (account) => {
|
onChoose: (account) => {
|
||||||
if (account.id === $i.id) {
|
if (account.id === $i.id) {
|
||||||
postAccount = null;
|
postAccount.value = null;
|
||||||
} else {
|
} else {
|
||||||
postAccount = account;
|
postAccount.value = account;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -990,30 +1006,30 @@ onMounted(() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: detach when unmount
|
// TODO: detach when unmount
|
||||||
new Autocomplete(textareaEl, $$(text));
|
new Autocomplete(textareaEl.value, text);
|
||||||
new Autocomplete(cwInputEl, $$(cw));
|
new Autocomplete(cwInputEl.value, cw);
|
||||||
new Autocomplete(hashtagsInputEl, $$(hashtags));
|
new Autocomplete(hashtagsInputEl.value, hashtags);
|
||||||
|
|
||||||
autosize(textareaEl);
|
autosize(textareaEl.value);
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
autosize(textareaEl);
|
autosize(textareaEl.value);
|
||||||
// 書きかけの投稿を復元
|
// 書きかけの投稿を復元
|
||||||
if (!props.instant && !props.mention && !props.specified) {
|
if (!props.instant && !props.mention && !props.specified) {
|
||||||
const draft = JSON.parse(localStorage.getItem("drafts") || "{}")[
|
const draft = JSON.parse(localStorage.getItem("drafts") || "{}")[
|
||||||
draftKey
|
draftKey.value
|
||||||
];
|
];
|
||||||
if (draft) {
|
if (draft) {
|
||||||
text = draft.data.text;
|
text.value = draft.data.text;
|
||||||
useCw = draft.data.useCw;
|
useCw.value = draft.data.useCw;
|
||||||
cw = draft.data.cw;
|
cw.value = draft.data.cw;
|
||||||
visibility = draft.data.visibility;
|
visibility.value = draft.data.visibility;
|
||||||
localOnly = draft.data.localOnly;
|
localOnly.value = draft.data.localOnly;
|
||||||
files = (draft.data.files || []).filter(
|
files.value = (draft.data.files || []).filter(
|
||||||
(draftFile) => draftFile,
|
(draftFile) => draftFile,
|
||||||
);
|
);
|
||||||
if (draft.data.poll) {
|
if (draft.data.poll) {
|
||||||
poll = draft.data.poll;
|
poll.value = draft.data.poll;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1021,21 +1037,21 @@ onMounted(() => {
|
||||||
// 削除して編集
|
// 削除して編集
|
||||||
if (props.initialNote) {
|
if (props.initialNote) {
|
||||||
const init = props.initialNote;
|
const init = props.initialNote;
|
||||||
text = init.text ? init.text : "";
|
text.value = init.text ? init.text : "";
|
||||||
files = init.files;
|
files.value = init.files;
|
||||||
cw = init.cw;
|
cw.value = init.cw;
|
||||||
useCw = init.cw != null;
|
useCw.value = init.cw != null;
|
||||||
if (init.poll) {
|
if (init.poll) {
|
||||||
poll = {
|
poll.value = {
|
||||||
choices: init.poll.choices.map((x) => x.text),
|
choices: init.poll.choices.map((x) => x.text),
|
||||||
multiple: init.poll.multiple,
|
multiple: init.poll.multiple,
|
||||||
expiresAt: init.poll.expiresAt,
|
expiresAt: init.poll.expiresAt,
|
||||||
expiredAfter: init.poll.expiredAfter,
|
expiredAfter: init.poll.expiredAfter,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
visibility = init.visibility;
|
visibility.value = init.visibility;
|
||||||
localOnly = init.localOnly;
|
localOnly.value = init.localOnly;
|
||||||
quoteId = init.renote ? init.renote.id : null;
|
quoteId.value = init.renote ? init.renote.id : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
nextTick(() => watchForDraft());
|
nextTick(() => watchForDraft());
|
||||||
|
|
|
@ -19,6 +19,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { shallowRef } from "vue";
|
||||||
|
|
||||||
import {} from "vue";
|
import {} from "vue";
|
||||||
import * as misskey from "firefish-js";
|
import * as misskey from "firefish-js";
|
||||||
import MkModal from "@/components/MkModal.vue";
|
import MkModal from "@/components/MkModal.vue";
|
||||||
|
@ -46,11 +48,11 @@ const emit = defineEmits<{
|
||||||
(ev: "closed"): void;
|
(ev: "closed"): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
let modal = $shallowRef<InstanceType<typeof MkModal>>();
|
let modal = shallowRef<InstanceType<typeof MkModal>>();
|
||||||
let form = $shallowRef<InstanceType<typeof MkPostForm>>();
|
let form = shallowRef<InstanceType<typeof MkPostForm>>();
|
||||||
|
|
||||||
function onPosted() {
|
function onPosted() {
|
||||||
modal.close({
|
modal.value.close({
|
||||||
useSendAnimation: true,
|
useSendAnimation: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
import { $i, getAccounts } from "@/account";
|
import { $i, getAccounts } from "@/account";
|
||||||
import MkButton from "@/components/MkButton.vue";
|
import MkButton from "@/components/MkButton.vue";
|
||||||
import { instance } from "@/instance";
|
import { instance } from "@/instance";
|
||||||
|
@ -74,12 +76,12 @@ defineProps<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
// ServiceWorker registration
|
// ServiceWorker registration
|
||||||
let registration = $ref<ServiceWorkerRegistration | undefined>();
|
let registration = ref<ServiceWorkerRegistration | undefined>();
|
||||||
// If this browser supports push notification
|
// If this browser supports push notification
|
||||||
let supported = $ref(false);
|
let supported = ref(false);
|
||||||
// If this browser has already subscribed to push notification
|
// If this browser has already subscribed to push notification
|
||||||
let pushSubscription = $ref<PushSubscription | null>(null);
|
let pushSubscription = ref<PushSubscription | null>(null);
|
||||||
let pushRegistrationInServer = $ref<
|
let pushRegistrationInServer = ref<
|
||||||
| {
|
| {
|
||||||
state?: string;
|
state?: string;
|
||||||
key?: string;
|
key?: string;
|
||||||
|
@ -91,11 +93,12 @@ let pushRegistrationInServer = $ref<
|
||||||
>();
|
>();
|
||||||
|
|
||||||
function subscribe() {
|
function subscribe() {
|
||||||
if (!registration || !supported || !instance.swPublickey) return;
|
if (!registration.value || !supported.value || !instance.swPublickey)
|
||||||
|
return;
|
||||||
|
|
||||||
// SEE: https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe#Parameters
|
// SEE: https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe#Parameters
|
||||||
return promiseDialog(
|
return promiseDialog(
|
||||||
registration.pushManager
|
registration.value.pushManager
|
||||||
.subscribe({
|
.subscribe({
|
||||||
userVisibleOnly: true,
|
userVisibleOnly: true,
|
||||||
applicationServerKey: urlBase64ToUint8Array(
|
applicationServerKey: urlBase64ToUint8Array(
|
||||||
|
@ -104,10 +107,10 @@ function subscribe() {
|
||||||
})
|
})
|
||||||
.then(
|
.then(
|
||||||
async (subscription) => {
|
async (subscription) => {
|
||||||
pushSubscription = subscription;
|
pushSubscription.value = subscription;
|
||||||
|
|
||||||
// Register
|
// Register
|
||||||
pushRegistrationInServer = await api("sw/register", {
|
pushRegistrationInServer.value = await api("sw/register", {
|
||||||
endpoint: subscription.endpoint,
|
endpoint: subscription.endpoint,
|
||||||
auth: encode(subscription.getKey("auth")),
|
auth: encode(subscription.getKey("auth")),
|
||||||
publickey: encode(subscription.getKey("p256dh")),
|
publickey: encode(subscription.getKey("p256dh")),
|
||||||
|
@ -136,12 +139,12 @@ function subscribe() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function unsubscribe() {
|
async function unsubscribe() {
|
||||||
if (!pushSubscription) return;
|
if (!pushSubscription.value) return;
|
||||||
|
|
||||||
const endpoint = pushSubscription.endpoint;
|
const endpoint = pushSubscription.value.endpoint;
|
||||||
const accounts = await getAccounts();
|
const accounts = await getAccounts();
|
||||||
|
|
||||||
pushRegistrationInServer = undefined;
|
pushRegistrationInServer.value = undefined;
|
||||||
|
|
||||||
if ($i && accounts.length >= 2) {
|
if ($i && accounts.length >= 2) {
|
||||||
apiWithDialog("sw/unregister", {
|
apiWithDialog("sw/unregister", {
|
||||||
|
@ -149,11 +152,11 @@ async function unsubscribe() {
|
||||||
endpoint,
|
endpoint,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
pushSubscription.unsubscribe();
|
pushSubscription.value.unsubscribe();
|
||||||
apiWithDialog("sw/unregister", {
|
apiWithDialog("sw/unregister", {
|
||||||
endpoint,
|
endpoint,
|
||||||
});
|
});
|
||||||
pushSubscription = null;
|
pushSubscription.value = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,20 +187,21 @@ if (navigator.serviceWorker == null) {
|
||||||
// TODO: よしなに?
|
// TODO: よしなに?
|
||||||
} else {
|
} else {
|
||||||
navigator.serviceWorker.ready.then(async (swr) => {
|
navigator.serviceWorker.ready.then(async (swr) => {
|
||||||
registration = swr;
|
registration.value = swr;
|
||||||
|
|
||||||
pushSubscription = await registration.pushManager.getSubscription();
|
pushSubscription.value =
|
||||||
|
await registration.value.pushManager.getSubscription();
|
||||||
|
|
||||||
if (instance.swPublickey && "PushManager" in window && $i && $i.token) {
|
if (instance.swPublickey && "PushManager" in window && $i && $i.token) {
|
||||||
supported = true;
|
supported.value = true;
|
||||||
|
|
||||||
if (pushSubscription) {
|
if (pushSubscription.value) {
|
||||||
const res = await api("sw/show-registration", {
|
const res = await api("sw/show-registration", {
|
||||||
endpoint: pushSubscription.endpoint,
|
endpoint: pushSubscription.value.endpoint,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res) {
|
if (res) {
|
||||||
pushRegistrationInServer = res;
|
pushRegistrationInServer.value = res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -205,6 +209,6 @@ if (navigator.serviceWorker == null) {
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
pushRegistrationInServer: $$(pushRegistrationInServer),
|
pushRegistrationInServer: pushRegistrationInServer,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -36,39 +36,38 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, watch } from "vue";
|
import { onMounted, watch, ref } from "vue";
|
||||||
import * as misskey from "firefish-js";
|
import * as misskey from "firefish-js";
|
||||||
import MkReactionIcon from "@/components/MkReactionIcon.vue";
|
import MkReactionIcon from "@/components/MkReactionIcon.vue";
|
||||||
import MkUserCardMini from "@/components/MkUserCardMini.vue";
|
import MkUserCardMini from "@/components/MkUserCardMini.vue";
|
||||||
import { i18n } from "@/i18n";
|
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
noteId: misskey.entities.Note["id"];
|
noteId: misskey.entities.Note["id"];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
let note = $ref<misskey.entities.Note>();
|
let note = ref<misskey.entities.Note>();
|
||||||
let tab = $ref<string>();
|
let tab = ref<string>();
|
||||||
let reactions = $ref<string[]>();
|
let reactions = ref<string[]>();
|
||||||
let users = $ref();
|
let users = ref();
|
||||||
|
|
||||||
watch($$(tab), async () => {
|
watch(tab, async () => {
|
||||||
const res = await os.api("notes/reactions", {
|
const res = await os.api("notes/reactions", {
|
||||||
noteId: props.noteId,
|
noteId: props.noteId,
|
||||||
type: tab,
|
type: tab.value,
|
||||||
limit: 30,
|
limit: 30,
|
||||||
});
|
});
|
||||||
|
|
||||||
users = res.map((x) => x.user);
|
users.value = res.map((x) => x.user);
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
os.api("notes/show", {
|
os.api("notes/show", {
|
||||||
noteId: props.noteId,
|
noteId: props.noteId,
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
reactions = Object.keys(res.reactions);
|
reactions.value = Object.keys(res.reactions);
|
||||||
tab = reactions[0];
|
tab.value = reactions.value[0];
|
||||||
note = res;
|
note.value = res;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -9,8 +9,6 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {} from "vue";
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
reaction: string;
|
reaction: string;
|
||||||
customEmojis?: any[]; // TODO
|
customEmojis?: any[]; // TODO
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {} from "vue";
|
|
||||||
import MkTooltip from "./MkTooltip.vue";
|
import MkTooltip from "./MkTooltip.vue";
|
||||||
import XReactionIcon from "@/components/MkReactionIcon.vue";
|
import XReactionIcon from "@/components/MkReactionIcon.vue";
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,6 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {} from "vue";
|
|
||||||
import MkTooltip from "./MkTooltip.vue";
|
import MkTooltip from "./MkTooltip.vue";
|
||||||
import XReactionIcon from "@/components/MkReactionIcon.vue";
|
import XReactionIcon from "@/components/MkReactionIcon.vue";
|
||||||
|
|
||||||
|
|
|
@ -70,13 +70,13 @@ useTooltip(buttonRef, async (showing) => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
let hasRenotedBefore = $ref(false);
|
let hasRenotedBefore = ref(false);
|
||||||
os.api("notes/renotes", {
|
os.api("notes/renotes", {
|
||||||
noteId: props.note.id,
|
noteId: props.note.id,
|
||||||
userId: $i.id,
|
userId: $i.id,
|
||||||
limit: 1,
|
limit: 1,
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
hasRenotedBefore = res.length > 0;
|
hasRenotedBefore.value = res.length > 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
const renote = (viaKeyboard = false, ev?: MouseEvent) => {
|
const renote = (viaKeyboard = false, ev?: MouseEvent) => {
|
||||||
|
@ -94,7 +94,7 @@ const renote = (viaKeyboard = false, ev?: MouseEvent) => {
|
||||||
renoteId: props.note.id,
|
renoteId: props.note.id,
|
||||||
visibility: "public",
|
visibility: "public",
|
||||||
});
|
});
|
||||||
hasRenotedBefore = true;
|
hasRenotedBefore.value = true;
|
||||||
const el =
|
const el =
|
||||||
ev &&
|
ev &&
|
||||||
((ev.currentTarget ?? ev.target) as
|
((ev.currentTarget ?? ev.target) as
|
||||||
|
@ -121,7 +121,7 @@ const renote = (viaKeyboard = false, ev?: MouseEvent) => {
|
||||||
renoteId: props.note.id,
|
renoteId: props.note.id,
|
||||||
visibility: "home",
|
visibility: "home",
|
||||||
});
|
});
|
||||||
hasRenotedBefore = true;
|
hasRenotedBefore.value = true;
|
||||||
const el =
|
const el =
|
||||||
ev &&
|
ev &&
|
||||||
((ev.currentTarget ?? ev.target) as
|
((ev.currentTarget ?? ev.target) as
|
||||||
|
@ -149,7 +149,7 @@ const renote = (viaKeyboard = false, ev?: MouseEvent) => {
|
||||||
visibility: "specified",
|
visibility: "specified",
|
||||||
visibleUserIds: props.note.visibleUserIds,
|
visibleUserIds: props.note.visibleUserIds,
|
||||||
});
|
});
|
||||||
hasRenotedBefore = true;
|
hasRenotedBefore.value = true;
|
||||||
const el =
|
const el =
|
||||||
ev &&
|
ev &&
|
||||||
((ev.currentTarget ?? ev.target) as
|
((ev.currentTarget ?? ev.target) as
|
||||||
|
@ -174,7 +174,7 @@ const renote = (viaKeyboard = false, ev?: MouseEvent) => {
|
||||||
renoteId: props.note.id,
|
renoteId: props.note.id,
|
||||||
visibility: "followers",
|
visibility: "followers",
|
||||||
});
|
});
|
||||||
hasRenotedBefore = true;
|
hasRenotedBefore.value = true;
|
||||||
const el =
|
const el =
|
||||||
ev &&
|
ev &&
|
||||||
((ev.currentTarget ?? ev.target) as
|
((ev.currentTarget ?? ev.target) as
|
||||||
|
@ -212,7 +212,7 @@ const renote = (viaKeyboard = false, ev?: MouseEvent) => {
|
||||||
localOnly: true,
|
localOnly: true,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
hasRenotedBefore = true;
|
hasRenotedBefore.value = true;
|
||||||
const el =
|
const el =
|
||||||
ev &&
|
ev &&
|
||||||
((ev.currentTarget ?? ev.target) as
|
((ev.currentTarget ?? ev.target) as
|
||||||
|
@ -242,7 +242,7 @@ const renote = (viaKeyboard = false, ev?: MouseEvent) => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasRenotedBefore) {
|
if (hasRenotedBefore.value) {
|
||||||
buttonActions.push({
|
buttonActions.push({
|
||||||
text: i18n.ts.unrenote,
|
text: i18n.ts.unrenote,
|
||||||
icon: "ph-trash ph-bold ph-lg",
|
icon: "ph-trash ph-bold ph-lg",
|
||||||
|
@ -251,7 +251,7 @@ const renote = (viaKeyboard = false, ev?: MouseEvent) => {
|
||||||
os.api("notes/unrenote", {
|
os.api("notes/unrenote", {
|
||||||
noteId: props.note.id,
|
noteId: props.note.id,
|
||||||
});
|
});
|
||||||
hasRenotedBefore = false;
|
hasRenotedBefore.value = false;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -160,8 +160,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import Vue3OtpInput from "vue3-otp-input";
|
import { defineAsyncComponent, ref, computed } from "vue";
|
||||||
import { defineAsyncComponent } from "vue";
|
|
||||||
import { toUnicode } from "punycode/";
|
import { toUnicode } from "punycode/";
|
||||||
import MkButton from "@/components/MkButton.vue";
|
import MkButton from "@/components/MkButton.vue";
|
||||||
import MkInput from "@/components/form/input.vue";
|
import MkInput from "@/components/form/input.vue";
|
||||||
|
@ -173,24 +172,19 @@ import { login } from "@/account";
|
||||||
import { instance } from "@/instance";
|
import { instance } from "@/instance";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
|
||||||
let signing = $ref(false);
|
let signing = ref(false);
|
||||||
let user = $ref(null);
|
let user = ref(null);
|
||||||
let username = $ref("");
|
let username = ref("");
|
||||||
let password = $ref("");
|
let password = ref("");
|
||||||
let token = $ref("");
|
let token = ref("");
|
||||||
let host = $ref(toUnicode(configHost));
|
let host = ref(toUnicode(configHost));
|
||||||
let totpLogin = $ref(false);
|
let totpLogin = ref(false);
|
||||||
let credential = $ref(null);
|
let challengeData = ref(null);
|
||||||
let challengeData = $ref(null);
|
let queryingKey = ref(false);
|
||||||
let queryingKey = $ref(false);
|
let hCaptchaResponse = ref(null);
|
||||||
let hCaptchaResponse = $ref(null);
|
let reCaptchaResponse = ref(null);
|
||||||
let reCaptchaResponse = $ref(null);
|
|
||||||
|
|
||||||
const updateToken = (value: string) => {
|
const meta = computed(() => instance);
|
||||||
token = value.toString();
|
|
||||||
};
|
|
||||||
|
|
||||||
const meta = $computed(() => instance);
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: "login", v: any): void;
|
(ev: "login", v: any): void;
|
||||||
|
@ -216,13 +210,13 @@ const props = defineProps({
|
||||||
|
|
||||||
function onUsernameChange() {
|
function onUsernameChange() {
|
||||||
os.api("users/show", {
|
os.api("users/show", {
|
||||||
username: username,
|
username: username.value,
|
||||||
}).then(
|
}).then(
|
||||||
(userResponse) => {
|
(userResponse) => {
|
||||||
user = userResponse;
|
user.value = userResponse;
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
user = null;
|
user.value = null;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -234,38 +228,40 @@ function onLogin(res) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function queryKey() {
|
function queryKey() {
|
||||||
queryingKey = true;
|
queryingKey.value = true;
|
||||||
return navigator.credentials
|
return navigator.credentials
|
||||||
.get({
|
.get({
|
||||||
publicKey: {
|
publicKey: {
|
||||||
challenge: byteify(challengeData.challenge, "base64"),
|
challenge: byteify(challengeData.value.challenge, "base64"),
|
||||||
allowCredentials: challengeData.securityKeys.map((key) => ({
|
allowCredentials: challengeData.value.securityKeys.map(
|
||||||
|
(key) => ({
|
||||||
id: byteify(key.id, "hex"),
|
id: byteify(key.id, "hex"),
|
||||||
type: "public-key",
|
type: "public-key",
|
||||||
transports: ["usb", "nfc", "ble", "internal"],
|
transports: ["usb", "nfc", "ble", "internal"],
|
||||||
})),
|
}),
|
||||||
|
),
|
||||||
timeout: 60 * 1000,
|
timeout: 60 * 1000,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
queryingKey = false;
|
queryingKey.value = false;
|
||||||
return Promise.reject(null);
|
return Promise.reject(null);
|
||||||
})
|
})
|
||||||
.then((credential) => {
|
.then((credential) => {
|
||||||
queryingKey = false;
|
queryingKey.value = false;
|
||||||
signing = true;
|
signing.value = true;
|
||||||
return os.api("signin", {
|
return os.api("signin", {
|
||||||
username,
|
username: username.value,
|
||||||
password,
|
password: password.value,
|
||||||
signature: hexify(credential.response.signature),
|
signature: hexify(credential.response.signature),
|
||||||
authenticatorData: hexify(
|
authenticatorData: hexify(
|
||||||
credential.response.authenticatorData,
|
credential.response.authenticatorData,
|
||||||
),
|
),
|
||||||
clientDataJSON: hexify(credential.response.clientDataJSON),
|
clientDataJSON: hexify(credential.response.clientDataJSON),
|
||||||
credentialId: credential.id,
|
credentialId: credential.id,
|
||||||
challengeId: challengeData.challengeId,
|
challengeId: challengeData.value.challengeId,
|
||||||
"hcaptcha-response": hCaptchaResponse,
|
"hcaptcha-response": hCaptchaResponse.value,
|
||||||
"g-recaptcha-response": reCaptchaResponse,
|
"g-recaptcha-response": reCaptchaResponse.value,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
|
@ -278,39 +274,42 @@ function queryKey() {
|
||||||
type: "error",
|
type: "error",
|
||||||
text: i18n.ts.signinFailed,
|
text: i18n.ts.signinFailed,
|
||||||
});
|
});
|
||||||
signing = false;
|
signing.value = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSubmit() {
|
function onSubmit() {
|
||||||
signing = true;
|
signing.value = true;
|
||||||
console.log("submit");
|
console.log("submit");
|
||||||
if (!totpLogin && user && user.twoFactorEnabled) {
|
if (!totpLogin.value && user.value && user.value.twoFactorEnabled) {
|
||||||
if (window.PublicKeyCredential && user.securityKeys) {
|
if (window.PublicKeyCredential && user.value.securityKeys) {
|
||||||
os.api("signin", {
|
os.api("signin", {
|
||||||
username,
|
username: username.value,
|
||||||
password,
|
password: password.value,
|
||||||
"hcaptcha-response": hCaptchaResponse,
|
"hcaptcha-response": hCaptchaResponse.value,
|
||||||
"g-recaptcha-response": reCaptchaResponse,
|
"g-recaptcha-response": reCaptchaResponse.value,
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
totpLogin = true;
|
totpLogin.value = true;
|
||||||
signing = false;
|
signing.value = false;
|
||||||
challengeData = res;
|
challengeData.value = res;
|
||||||
return queryKey();
|
return queryKey();
|
||||||
})
|
})
|
||||||
.catch(loginFailed);
|
.catch(loginFailed);
|
||||||
} else {
|
} else {
|
||||||
totpLogin = true;
|
totpLogin.value = true;
|
||||||
signing = false;
|
signing.value = false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
os.api("signin", {
|
os.api("signin", {
|
||||||
username,
|
username: username.value,
|
||||||
password,
|
password: password.value,
|
||||||
"hcaptcha-response": hCaptchaResponse,
|
"hcaptcha-response": hCaptchaResponse.value,
|
||||||
"g-recaptcha-response": reCaptchaResponse,
|
"g-recaptcha-response": reCaptchaResponse.value,
|
||||||
token: user && user.twoFactorEnabled ? token : undefined,
|
token:
|
||||||
|
user.value && user.value.twoFactorEnabled
|
||||||
|
? token.value
|
||||||
|
: undefined,
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
emit("login", res);
|
emit("login", res);
|
||||||
|
@ -360,9 +359,9 @@ function loginFailed(err) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
challengeData = null;
|
challengeData.value = null;
|
||||||
totpLogin = false;
|
totpLogin.value = false;
|
||||||
signing = false;
|
signing.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetPassword() {
|
function resetPassword() {
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
import {} from "vue";
|
import {} from "vue";
|
||||||
import MkSignin from "@/components/MkSignin.vue";
|
import MkSignin from "@/components/MkSignin.vue";
|
||||||
import XModalWindow from "@/components/MkModalWindow.vue";
|
import XModalWindow from "@/components/MkModalWindow.vue";
|
||||||
|
@ -34,15 +36,15 @@ const emit = defineEmits<{
|
||||||
(ev: "cancelled"): void;
|
(ev: "cancelled"): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const dialog = $ref<InstanceType<typeof XModalWindow>>();
|
const dialog = ref<InstanceType<typeof XModalWindow>>();
|
||||||
|
|
||||||
function onClose() {
|
function onClose() {
|
||||||
emit("cancelled");
|
emit("cancelled");
|
||||||
dialog.close();
|
dialog.value.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onLogin(res) {
|
function onLogin(res) {
|
||||||
emit("done", res);
|
emit("done", res);
|
||||||
dialog.close();
|
dialog.value.close();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -284,7 +284,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {} from "vue";
|
import { ref, computed } from "vue";
|
||||||
|
|
||||||
import getPasswordStrength from "syuilo-password-strength";
|
import getPasswordStrength from "syuilo-password-strength";
|
||||||
import { toUnicode } from "punycode/";
|
import { toUnicode } from "punycode/";
|
||||||
import MkButton from "./MkButton.vue";
|
import MkButton from "./MkButton.vue";
|
||||||
|
@ -313,14 +314,14 @@ const emit = defineEmits<{
|
||||||
|
|
||||||
const host = toUnicode(config.host);
|
const host = toUnicode(config.host);
|
||||||
|
|
||||||
let hcaptcha = $ref();
|
let hcaptcha = ref();
|
||||||
let recaptcha = $ref();
|
let recaptcha = ref();
|
||||||
|
|
||||||
let username: string = $ref("");
|
let username: string = ref("");
|
||||||
let password: string = $ref("");
|
let password: string = ref("");
|
||||||
let retypedPassword: string = $ref("");
|
let retypedPassword: string = ref("");
|
||||||
let invitationCode: string = $ref("");
|
let invitationCode: string = ref("");
|
||||||
let email = $ref("");
|
let email = ref("");
|
||||||
let usernameState:
|
let usernameState:
|
||||||
| null
|
| null
|
||||||
| "wait"
|
| "wait"
|
||||||
|
@ -329,8 +330,8 @@ let usernameState:
|
||||||
| "error"
|
| "error"
|
||||||
| "invalid-format"
|
| "invalid-format"
|
||||||
| "min-range"
|
| "min-range"
|
||||||
| "max-range" = $ref(null);
|
| "max-range" = ref(null);
|
||||||
let invitationState: null | "entered" = $ref(null);
|
let invitationState: null | "entered" = ref(null);
|
||||||
let emailState:
|
let emailState:
|
||||||
| null
|
| null
|
||||||
| "wait"
|
| "wait"
|
||||||
|
@ -341,79 +342,79 @@ let emailState:
|
||||||
| "unavailable:mx"
|
| "unavailable:mx"
|
||||||
| "unavailable:smtp"
|
| "unavailable:smtp"
|
||||||
| "unavailable"
|
| "unavailable"
|
||||||
| "error" = $ref(null);
|
| "error" = ref(null);
|
||||||
let passwordStrength: "" | "low" | "medium" | "high" = $ref("");
|
let passwordStrength: "" | "low" | "medium" | "high" = ref("");
|
||||||
let passwordRetypeState: null | "match" | "not-match" = $ref(null);
|
let passwordRetypeState: null | "match" | "not-match" = ref(null);
|
||||||
let submitting: boolean = $ref(false);
|
let submitting: boolean = ref(false);
|
||||||
let ToSAgreement: boolean = $ref(false);
|
let ToSAgreement: boolean = ref(false);
|
||||||
let hCaptchaResponse = $ref(null);
|
let hCaptchaResponse = ref(null);
|
||||||
let reCaptchaResponse = $ref(null);
|
let reCaptchaResponse = ref(null);
|
||||||
|
|
||||||
const shouldDisableSubmitting = $computed((): boolean => {
|
const shouldDisableSubmitting = computed((): boolean => {
|
||||||
return (
|
return (
|
||||||
submitting ||
|
submitting.value ||
|
||||||
(instance.tosUrl && !ToSAgreement) ||
|
(instance.tosUrl && !ToSAgreement.value) ||
|
||||||
(instance.enableHcaptcha && !hCaptchaResponse) ||
|
(instance.enableHcaptcha && !hCaptchaResponse.value) ||
|
||||||
(instance.enableRecaptcha && !reCaptchaResponse) ||
|
(instance.enableRecaptcha && !reCaptchaResponse.value) ||
|
||||||
passwordRetypeState === "not-match"
|
passwordRetypeState.value === "not-match"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
function onChangeInvitationCode(): void {
|
function onChangeInvitationCode(): void {
|
||||||
if (invitationCode === "") {
|
if (invitationCode.value === "") {
|
||||||
invitationState = null;
|
invitationState.value = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
invitationState = "entered";
|
invitationState.value = "entered";
|
||||||
}
|
}
|
||||||
|
|
||||||
function onChangeUsername(): void {
|
function onChangeUsername(): void {
|
||||||
if (username === "") {
|
if (username.value === "") {
|
||||||
usernameState = null;
|
usernameState.value = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const err = !username.match(/^[a-zA-Z0-9_]+$/)
|
const err = !username.value.match(/^[a-zA-Z0-9_]+$/)
|
||||||
? "invalid-format"
|
? "invalid-format"
|
||||||
: username.length < 1
|
: username.value.length < 1
|
||||||
? "min-range"
|
? "min-range"
|
||||||
: username.length > 20
|
: username.value.length > 20
|
||||||
? "max-range"
|
? "max-range"
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
if (err) {
|
if (err) {
|
||||||
usernameState = err;
|
usernameState.value = err;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
usernameState = "wait";
|
usernameState.value = "wait";
|
||||||
|
|
||||||
os.api("username/available", {
|
os.api("username/available", {
|
||||||
username,
|
username: username.value,
|
||||||
})
|
})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
usernameState = result.available ? "ok" : "unavailable";
|
usernameState.value = result.available ? "ok" : "unavailable";
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
usernameState = "error";
|
usernameState.value = "error";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function onChangeEmail(): void {
|
function onChangeEmail(): void {
|
||||||
if (email === "") {
|
if (email.value === "") {
|
||||||
emailState = null;
|
emailState.value = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
emailState = "wait";
|
emailState.value = "wait";
|
||||||
|
|
||||||
os.api("email-address/available", {
|
os.api("email-address/available", {
|
||||||
emailAddress: email,
|
emailAddress: email.value,
|
||||||
})
|
})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
emailState = result.available
|
emailState.value = result.available
|
||||||
? "ok"
|
? "ok"
|
||||||
: result.reason === "used"
|
: result.reason === "used"
|
||||||
? "unavailable:used"
|
? "unavailable:used"
|
||||||
|
@ -428,54 +429,55 @@ function onChangeEmail(): void {
|
||||||
: "unavailable";
|
: "unavailable";
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
emailState = "error";
|
emailState.value = "error";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function onChangePassword(): void {
|
function onChangePassword(): void {
|
||||||
if (password === "") {
|
if (password.value === "") {
|
||||||
passwordStrength = "";
|
passwordStrength.value = "";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const strength = getPasswordStrength(password);
|
const strength = getPasswordStrength(password.value);
|
||||||
passwordStrength =
|
passwordStrength.value =
|
||||||
strength > 0.7 ? "high" : strength > 0.3 ? "medium" : "low";
|
strength > 0.7 ? "high" : strength > 0.3 ? "medium" : "low";
|
||||||
}
|
}
|
||||||
|
|
||||||
function onChangePasswordRetype(): void {
|
function onChangePasswordRetype(): void {
|
||||||
if (retypedPassword === "") {
|
if (retypedPassword.value === "") {
|
||||||
passwordRetypeState = null;
|
passwordRetypeState.value = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
passwordRetypeState = password === retypedPassword ? "match" : "not-match";
|
passwordRetypeState.value =
|
||||||
|
password.value === retypedPassword.value ? "match" : "not-match";
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSubmit(): void {
|
function onSubmit(): void {
|
||||||
if (submitting) return;
|
if (submitting.value) return;
|
||||||
submitting = true;
|
submitting.value = true;
|
||||||
|
|
||||||
os.api("signup", {
|
os.api("signup", {
|
||||||
username,
|
username: username.value,
|
||||||
password,
|
password: password.value,
|
||||||
emailAddress: email,
|
emailAddress: email.value,
|
||||||
invitationCode,
|
invitationCode: invitationCode.value,
|
||||||
"hcaptcha-response": hCaptchaResponse,
|
"hcaptcha-response": hCaptchaResponse.value,
|
||||||
"g-recaptcha-response": reCaptchaResponse,
|
"g-recaptcha-response": reCaptchaResponse.value,
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (instance.emailRequiredForSignup) {
|
if (instance.emailRequiredForSignup) {
|
||||||
os.alert({
|
os.alert({
|
||||||
type: "success",
|
type: "success",
|
||||||
title: i18n.ts._signup.almostThere,
|
title: i18n.ts._signup.almostThere,
|
||||||
text: i18n.t("_signup.emailSent", { email }),
|
text: i18n.t("_signup.emailSent", { email: email.value }),
|
||||||
});
|
});
|
||||||
emit("signupEmailPending");
|
emit("signupEmailPending");
|
||||||
} else {
|
} else {
|
||||||
os.api("signin", {
|
os.api("signin", {
|
||||||
username,
|
username: username.value,
|
||||||
password,
|
password: password.value,
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
emit("signup", res);
|
emit("signup", res);
|
||||||
|
|
||||||
|
@ -486,9 +488,9 @@ function onSubmit(): void {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
submitting = false;
|
submitting.value = false;
|
||||||
hcaptcha.reset?.();
|
hcaptcha.value.reset?.();
|
||||||
recaptcha.reset?.();
|
recaptcha.value.reset?.();
|
||||||
|
|
||||||
os.alert({
|
os.alert({
|
||||||
type: "error",
|
type: "error",
|
||||||
|
|
|
@ -20,7 +20,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {} from "vue";
|
import { ref } from "vue";
|
||||||
|
|
||||||
import XSignup from "@/components/MkSignup.vue";
|
import XSignup from "@/components/MkSignup.vue";
|
||||||
import XModalWindow from "@/components/MkModalWindow.vue";
|
import XModalWindow from "@/components/MkModalWindow.vue";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
@ -39,14 +40,14 @@ const emit = defineEmits<{
|
||||||
(ev: "closed"): void;
|
(ev: "closed"): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const dialog = $ref<InstanceType<typeof XModalWindow>>();
|
const dialog = ref<InstanceType<typeof XModalWindow>>();
|
||||||
|
|
||||||
function onSignup(res) {
|
function onSignup(res) {
|
||||||
emit("done", res);
|
emit("done", res);
|
||||||
dialog?.close();
|
dialog.value?.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSignupEmailPending() {
|
function onSignupEmailPending() {
|
||||||
dialog?.close();
|
dialog.value?.close();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -47,7 +47,6 @@ import Ripple from "@/components/MkRipple.vue";
|
||||||
import XDetails from "@/components/MkUsersTooltip.vue";
|
import XDetails from "@/components/MkUsersTooltip.vue";
|
||||||
import { pleaseLogin } from "@/scripts/please-login";
|
import { pleaseLogin } from "@/scripts/please-login";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { defaultStore } from "@/store";
|
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { instance } from "@/instance";
|
import { instance } from "@/instance";
|
||||||
import { useTooltip } from "@/scripts/use-tooltip";
|
import { useTooltip } from "@/scripts/use-tooltip";
|
||||||
|
|
|
@ -217,23 +217,23 @@ const isLong =
|
||||||
(props.note.text.split("\n").length > 10 ||
|
(props.note.text.split("\n").length > 10 ||
|
||||||
props.note.text.length > 800)) ||
|
props.note.text.length > 800)) ||
|
||||||
props.note.files.length > 4);
|
props.note.files.length > 4);
|
||||||
const collapsed = $ref(props.note.cw == null && isLong);
|
const collapsed = ref(props.note.cw == null && isLong);
|
||||||
const urls = props.note.text
|
const urls = props.note.text
|
||||||
? extractUrlFromMfm(mfm.parse(props.note.text)).slice(0, 5)
|
? extractUrlFromMfm(mfm.parse(props.note.text)).slice(0, 5)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
let showContent = $ref(false);
|
let showContent = ref(false);
|
||||||
|
|
||||||
const mfms = props.note.text
|
const mfms = props.note.text
|
||||||
? extractMfmWithAnimation(mfm.parse(props.note.text))
|
? extractMfmWithAnimation(mfm.parse(props.note.text))
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const hasMfm = $ref(mfms && mfms.length > 0);
|
const hasMfm = ref(mfms && mfms.length > 0);
|
||||||
|
|
||||||
let disableMfm = $ref(defaultStore.state.animatedMfm);
|
let disableMfm = ref(defaultStore.state.animatedMfm);
|
||||||
|
|
||||||
async function toggleMfm() {
|
async function toggleMfm() {
|
||||||
if (disableMfm) {
|
if (disableMfm.value) {
|
||||||
if (!defaultStore.state.animatedMfmWarnShown) {
|
if (!defaultStore.state.animatedMfmWarnShown) {
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: "warning",
|
type: "warning",
|
||||||
|
@ -244,9 +244,9 @@ async function toggleMfm() {
|
||||||
defaultStore.set("animatedMfmWarnShown", true);
|
defaultStore.set("animatedMfmWarnShown", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
disableMfm = false;
|
disableMfm.value = false;
|
||||||
} else {
|
} else {
|
||||||
disableMfm = true;
|
disableMfm.value = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<div v-if="group.title" class="title">{{ group.title }}</div>
|
<div v-if="group.title" class="title">{{ group.title }}</div>
|
||||||
|
|
||||||
<div class="items">
|
<div class="items">
|
||||||
<template v-for="(item, i) in group.items">
|
<template v-for="item in group.items">
|
||||||
<a
|
<a
|
||||||
v-if="item.type === 'a'"
|
v-if="item.type === 'a'"
|
||||||
:href="item.href"
|
:href="item.href"
|
||||||
|
@ -53,7 +53,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, ref, unref } from "vue";
|
import { defineComponent } from "vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, ref, watch, PropType, onBeforeUnmount } from "vue";
|
import { onMounted, watch, onBeforeUnmount, ref } from "vue";
|
||||||
import tinycolor from "tinycolor2";
|
import tinycolor from "tinycolor2";
|
||||||
|
|
||||||
const loaded = !!window.TagCanvas;
|
const loaded = !!window.TagCanvas;
|
||||||
|
@ -39,13 +39,13 @@ const idForTags = Array.from(Array(16))
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.join("");
|
.join("");
|
||||||
let available = $ref(false);
|
let available = ref(false);
|
||||||
let rootEl = $ref<HTMLElement | null>(null);
|
let rootEl = ref<HTMLElement | null>(null);
|
||||||
let canvasEl = $ref<HTMLCanvasElement | null>(null);
|
let canvasEl = ref<HTMLCanvasElement | null>(null);
|
||||||
let tagsEl = $ref<HTMLElement | null>(null);
|
let tagsEl = ref<HTMLElement | null>(null);
|
||||||
let width = $ref(300);
|
let width = ref(300);
|
||||||
|
|
||||||
watch($$(available), () => {
|
watch(available, () => {
|
||||||
try {
|
try {
|
||||||
window.TagCanvas.Start(idForCanvas, idForTags, {
|
window.TagCanvas.Start(idForCanvas, idForTags, {
|
||||||
textColour: "#ffffff",
|
textColour: "#ffffff",
|
||||||
|
@ -70,10 +70,10 @@ watch($$(available), () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
width = rootEl.offsetWidth;
|
width.value = rootEl.value.offsetWidth;
|
||||||
|
|
||||||
if (loaded) {
|
if (loaded) {
|
||||||
available = true;
|
available.value = true;
|
||||||
} else {
|
} else {
|
||||||
document.head
|
document.head
|
||||||
.appendChild(
|
.appendChild(
|
||||||
|
@ -82,7 +82,7 @@ onMounted(() => {
|
||||||
src: "/client-assets/tagcanvas.min.js",
|
src: "/client-assets/tagcanvas.min.js",
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.addEventListener("load", () => (available = true));
|
.addEventListener("load", () => (available.value = true));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -28,10 +28,9 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, watch, computed, provide, onUnmounted } from "vue";
|
import { computed, provide, onUnmounted, ref } from "vue";
|
||||||
import XNotes from "@/components/MkNotes.vue";
|
import XNotes from "@/components/MkNotes.vue";
|
||||||
import MkInfo from "@/components/MkInfo.vue";
|
import MkInfo from "@/components/MkInfo.vue";
|
||||||
import * as os from "@/os";
|
|
||||||
import { stream } from "@/stream";
|
import { stream } from "@/stream";
|
||||||
import * as sound from "@/scripts/sound";
|
import * as sound from "@/scripts/sound";
|
||||||
import { $i } from "@/account";
|
import { $i } from "@/account";
|
||||||
|
@ -46,7 +45,7 @@ const props = defineProps<{
|
||||||
sound?: boolean;
|
sound?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
let queue = $ref(0);
|
let queue = ref(0);
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: "note"): void;
|
(ev: "note"): void;
|
||||||
|
@ -58,10 +57,10 @@ provide(
|
||||||
computed(() => props.src === "channel"),
|
computed(() => props.src === "channel"),
|
||||||
);
|
);
|
||||||
|
|
||||||
const tlComponent: InstanceType<typeof XNotes> = $ref();
|
const tlComponent: InstanceType<typeof XNotes> = ref();
|
||||||
|
|
||||||
const prepend = (note) => {
|
const prepend = (note) => {
|
||||||
tlComponent.pagingComponent?.prepend(note);
|
tlComponent.value.pagingComponent?.prepend(note);
|
||||||
|
|
||||||
emit("note");
|
emit("note");
|
||||||
|
|
||||||
|
@ -71,16 +70,16 @@ const prepend = (note) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const onUserAdded = () => {
|
const onUserAdded = () => {
|
||||||
tlComponent.pagingComponent?.reload();
|
tlComponent.value.pagingComponent?.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onUserRemoved = () => {
|
const onUserRemoved = () => {
|
||||||
tlComponent.pagingComponent?.reload();
|
tlComponent.value.pagingComponent?.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onChangeFollowing = () => {
|
const onChangeFollowing = () => {
|
||||||
if (!tlComponent.pagingComponent?.backed) {
|
if (!tlComponent.value.pagingComponent?.backed) {
|
||||||
tlComponent.pagingComponent?.reload();
|
tlComponent.value.pagingComponent?.reload();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -30,11 +30,11 @@ const emit = defineEmits<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const zIndex = os.claimZIndex("high");
|
const zIndex = os.claimZIndex("high");
|
||||||
let showing = $ref(true);
|
let showing = ref(true);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
showing = false;
|
showing.value = false;
|
||||||
}, 4000);
|
}, 4000);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -43,6 +43,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
import {} from "vue";
|
import {} from "vue";
|
||||||
import { permissions as kinds } from "firefish-js";
|
import { permissions as kinds } from "firefish-js";
|
||||||
import MkInput from "./form/input.vue";
|
import MkInput from "./form/input.vue";
|
||||||
|
@ -72,37 +74,39 @@ const emit = defineEmits<{
|
||||||
(ev: "done", result: { name: string | null; permissions: string[] }): void;
|
(ev: "done", result: { name: string | null; permissions: string[] }): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const dialog = $ref<InstanceType<typeof XModalWindow>>();
|
const dialog = ref<InstanceType<typeof XModalWindow>>();
|
||||||
let name = $ref(props.initialName);
|
let name = ref(props.initialName);
|
||||||
let permissions = $ref({});
|
let permissions = ref({});
|
||||||
|
|
||||||
if (props.initialPermissions) {
|
if (props.initialPermissions) {
|
||||||
for (const kind of props.initialPermissions) {
|
for (const kind of props.initialPermissions) {
|
||||||
permissions[kind] = true;
|
permissions.value[kind] = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (const kind of kinds) {
|
for (const kind of kinds) {
|
||||||
permissions[kind] = false;
|
permissions.value[kind] = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function ok(): void {
|
function ok(): void {
|
||||||
emit("done", {
|
emit("done", {
|
||||||
name: name,
|
name: name.value,
|
||||||
permissions: Object.keys(permissions).filter((p) => permissions[p]),
|
permissions: Object.keys(permissions.value).filter(
|
||||||
|
(p) => permissions.value[p],
|
||||||
|
),
|
||||||
});
|
});
|
||||||
dialog.close();
|
dialog.value.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
function disableAll(): void {
|
function disableAll(): void {
|
||||||
for (const p in permissions) {
|
for (const p in permissions.value) {
|
||||||
permissions[p] = false;
|
permissions.value[p] = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function enableAll(): void {
|
function enableAll(): void {
|
||||||
for (const p in permissions) {
|
for (const p in permissions.value) {
|
||||||
permissions[p] = true;
|
permissions.value[p] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -206,7 +206,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { reactive, computed } from "vue";
|
import { computed, ref } from "vue";
|
||||||
import XSettings from "@/pages/settings/profile.vue";
|
import XSettings from "@/pages/settings/profile.vue";
|
||||||
import XModalWindow from "@/components/MkModalWindow.vue";
|
import XModalWindow from "@/components/MkModalWindow.vue";
|
||||||
import MkButton from "@/components/MkButton.vue";
|
import MkButton from "@/components/MkButton.vue";
|
||||||
|
@ -250,7 +250,7 @@ const emit = defineEmits<{
|
||||||
(ev: "closed"): void;
|
(ev: "closed"): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const dialog = $ref<InstanceType<typeof XModalWindow>>();
|
const dialog = ref<InstanceType<typeof XModalWindow>>();
|
||||||
|
|
||||||
const tutorial = computed({
|
const tutorial = computed({
|
||||||
get() {
|
get() {
|
||||||
|
@ -278,7 +278,7 @@ const reduceAnimation = computed(
|
||||||
|
|
||||||
function close(res) {
|
function close(res) {
|
||||||
tutorial.value = -1;
|
tutorial.value = -1;
|
||||||
dialog.close();
|
dialog.value.close();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { shallowRef } from "vue";
|
import { shallowRef, ref } from "vue";
|
||||||
import MkModal from "@/components/MkModal.vue";
|
import MkModal from "@/components/MkModal.vue";
|
||||||
import MkSparkle from "@/components/MkSparkle.vue";
|
import MkSparkle from "@/components/MkSparkle.vue";
|
||||||
import MkButton from "@/components/MkButton.vue";
|
import MkButton from "@/components/MkButton.vue";
|
||||||
|
@ -43,18 +43,18 @@ import * as os from "@/os";
|
||||||
|
|
||||||
const modal = shallowRef<InstanceType<typeof MkModal>>();
|
const modal = shallowRef<InstanceType<typeof MkModal>>();
|
||||||
|
|
||||||
let newRelease = $ref(false);
|
let newRelease = ref(false);
|
||||||
let data = $ref(Object);
|
let data = ref(Object);
|
||||||
|
|
||||||
os.api("release").then((res) => {
|
os.api("release").then((res) => {
|
||||||
data = res;
|
data.value = res;
|
||||||
newRelease = version === data?.version;
|
newRelease.value = version === data.value?.version;
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`Version: ${version}`);
|
console.log(`Version: ${version}`);
|
||||||
console.log(`Data version: ${data.version}`);
|
console.log(`Data version: ${data.value.version}`);
|
||||||
console.log(newRelease);
|
console.log(newRelease.value);
|
||||||
console.log(data);
|
console.log(data.value);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
|
@ -99,7 +99,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, onUnmounted } from "vue";
|
import { onUnmounted, ref } from "vue";
|
||||||
import { url as local, lang } from "@/config";
|
import { url as local, lang } from "@/config";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { defaultStore } from "@/store";
|
import { defaultStore } from "@/store";
|
||||||
|
@ -117,22 +117,22 @@ const props = withDefaults(
|
||||||
const self = props.url.startsWith(local);
|
const self = props.url.startsWith(local);
|
||||||
const attr = self ? "to" : "href";
|
const attr = self ? "to" : "href";
|
||||||
const target = self ? null : "_blank";
|
const target = self ? null : "_blank";
|
||||||
let fetching = $ref(true);
|
let fetching = ref(true);
|
||||||
let title = $ref<string | null>(null);
|
let title = ref<string | null>(null);
|
||||||
let description = $ref<string | null>(null);
|
let description = ref<string | null>(null);
|
||||||
let thumbnail = $ref<string | null>(null);
|
let thumbnail = ref<string | null>(null);
|
||||||
let icon = $ref<string | null>(null);
|
let icon = ref<string | null>(null);
|
||||||
let sitename = $ref<string | null>(null);
|
let sitename = ref<string | null>(null);
|
||||||
let player = $ref({
|
let player = ref({
|
||||||
url: null,
|
url: null,
|
||||||
width: null,
|
width: null,
|
||||||
height: null,
|
height: null,
|
||||||
});
|
});
|
||||||
let playerEnabled = $ref(false);
|
let playerEnabled = ref(false);
|
||||||
let tweetId = $ref<string | null>(null);
|
let tweetId = ref<string | null>(null);
|
||||||
let tweetExpanded = $ref(props.detail);
|
let tweetExpanded = ref(props.detail);
|
||||||
const embedId = `embed${Math.random().toString().replace(/\D/, "")}`;
|
const embedId = `embed${Math.random().toString().replace(/\D/, "")}`;
|
||||||
let tweetHeight = $ref(150);
|
let tweetHeight = ref(150);
|
||||||
|
|
||||||
const requestUrl = new URL(props.url);
|
const requestUrl = new URL(props.url);
|
||||||
if (!["http:", "https:"].includes(requestUrl.protocol))
|
if (!["http:", "https:"].includes(requestUrl.protocol))
|
||||||
|
@ -143,7 +143,7 @@ if (
|
||||||
requestUrl.hostname === "mobile.twitter.com"
|
requestUrl.hostname === "mobile.twitter.com"
|
||||||
) {
|
) {
|
||||||
const m = requestUrl.pathname.match(/^\/.+\/status(?:es)?\/(\d+)/);
|
const m = requestUrl.pathname.match(/^\/.+\/status(?:es)?\/(\d+)/);
|
||||||
if (m) tweetId = m[1];
|
if (m) tweetId.value = m[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -162,13 +162,13 @@ fetch(
|
||||||
).then((res) => {
|
).then((res) => {
|
||||||
res.json().then((info) => {
|
res.json().then((info) => {
|
||||||
if (info.url == null) return;
|
if (info.url == null) return;
|
||||||
title = info.title;
|
title.value = info.title;
|
||||||
description = info.description;
|
description.value = info.description;
|
||||||
thumbnail = info.thumbnail;
|
thumbnail.value = info.thumbnail;
|
||||||
icon = info.icon;
|
icon.value = info.icon;
|
||||||
sitename = info.sitename;
|
sitename.value = info.sitename;
|
||||||
fetching = false;
|
fetching.value = false;
|
||||||
player = info.player;
|
player.value = info.player;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -178,7 +178,7 @@ function adjustTweetHeight(message: any) {
|
||||||
if (embed?.method !== "twttr.private.resize") return;
|
if (embed?.method !== "twttr.private.resize") return;
|
||||||
if (embed?.id !== embedId) return;
|
if (embed?.id !== embedId) return;
|
||||||
const height = embed?.params[0]?.height;
|
const height = embed?.params[0]?.height;
|
||||||
if (height) tweetHeight = height;
|
if (height) tweetHeight.value = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
(window as any).addEventListener("message", adjustTweetHeight);
|
(window as any).addEventListener("message", adjustTweetHeight);
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted } from "vue";
|
import { onMounted, ref } from "vue";
|
||||||
import MkUrlPreview from "@/components/MkUrlPreview.vue";
|
import MkUrlPreview from "@/components/MkUrlPreview.vue";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
|
|
||||||
|
@ -28,8 +28,8 @@ const emit = defineEmits<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const zIndex = os.claimZIndex("middle");
|
const zIndex = os.claimZIndex("middle");
|
||||||
let top = $ref(0);
|
let top = ref(0);
|
||||||
let left = $ref(0);
|
let left = ref(0);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const rect = props.source.getBoundingClientRect();
|
const rect = props.source.getBoundingClientRect();
|
||||||
|
@ -38,8 +38,8 @@ onMounted(() => {
|
||||||
window.pageXOffset;
|
window.pageXOffset;
|
||||||
const y = rect.top + props.source.offsetHeight + window.pageYOffset;
|
const y = rect.top + props.source.offsetHeight + window.pageYOffset;
|
||||||
|
|
||||||
top = y;
|
top.value = y;
|
||||||
left = x;
|
left.value = x;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue