Merge branch 'develop' into feat/scylladb

This commit is contained in:
Namekuji 2023-08-11 22:45:06 -04:00
commit 43fb3375be
No known key found for this signature in database
GPG key ID: 1D62332C07FBA532
317 changed files with 6070 additions and 4826 deletions

5
.gitignore vendored
View file

@ -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

View file

@ -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
@ -88,4 +90,4 @@ dockerBuild:
only: only:
- main - main
- beta - beta
- tags - tags

View file

@ -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/* ]

View file

@ -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

View file

@ -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

View file

@ -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*

View file

@ -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 ]

View file

@ -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 . ./

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -53,7 +53,7 @@ sendMessage: "Envoyer un message"
copyUsername: "Copier le nom dutilisateur·rice" copyUsername: "Copier le nom dutilisateur·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 dutilisateur·rice ou ID utilisateur" usernameOrUserId: "Nom dutilisateur·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 dURL utilisé pour construire lURL vers le référencement objectStorageBaseUrlDesc: "Préfixe dURL utilisé pour construire lURL vers le référencement
dobjet (média). Spécifiez son URL si vous utilisez un CDN ou un proxy, sinon spécifiez dobjet (média). Spécifiez son URL si vous utilisez un CDN ou un proxy, sinon spécifiez
ladresse accessible au public selon le guide de service que vous allez utiliser.\n ladresse 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é"
@ -986,8 +986,8 @@ _plugin:
manage: "Gestion des plugins" manage: "Gestion des plugins"
_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"
@ -1104,7 +1105,7 @@ _mfm:
background: Couleur d'arrière-plan background: Couleur d'arrière-plan
plain: Simple plain: Simple
_instanceTicker: _instanceTicker:
none: "Cacher " none: "Cacher"
remote: "Montrer pour les utilisateur·ice·s distant·e·s" remote: "Montrer pour les utilisateur·ice·s distant·e·s"
always: "Toujours afficher" always: "Toujours afficher"
_serverDisconnectedBehavior: _serverDisconnectedBehavior:
@ -1169,17 +1170,17 @@ _theme:
color: "Couleur" color: "Couleur"
refProp: "Appeler une propriété" refProp: "Appeler une propriété"
refConst: "Appeler une constante" refConst: "Appeler une constante"
key: "Clé " key: "Clé"
func: "Fonction" func: "Fonction"
funcKind: "Type de fonction" funcKind: "Type de fonction"
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 dactualité partout !" step5_1: "Des fils, des fils dactualité 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
@ -1356,7 +1357,7 @@ _permissions:
_auth: _auth:
shareAccess: "Autoriser \"{name}\" à accéder à votre compte ?" shareAccess: "Autoriser \"{name}\" à accéder à votre compte ?"
shareAccessAsk: "Voulez-vous vraiment autoriser cette application à accéder à votre shareAccessAsk: "Voulez-vous vraiment autoriser cette application à accéder à votre
compte?" compte ?"
permissionAsk: "Cette application nécessite les autorisations suivantes :" permissionAsk: "Cette application nécessite les autorisations suivantes :"
pleaseGoBack: "Veuillez retourner à lapplication" pleaseGoBack: "Veuillez retourner à lapplication"
callback: "Retour vers lapplication" callback: "Retour vers lapplication"
@ -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"
@ -1467,7 +1468,7 @@ _profile:
metadataEdit: "Éditer les informations supplémentaires" metadataEdit: "Éditer les informations supplémentaires"
metadataDescription: "Vous pouvez afficher jusqu'à quatre informations supplémentaires metadataDescription: "Vous pouvez afficher jusqu'à quatre informations supplémentaires
dans votre profil. Vous pouvez ajouter une balise {a} ou une balise {l} avec {rel} dans votre profil. Vous pouvez ajouter une balise {a} ou une balise {l} avec {rel}
pour vérifier le lien sur votre profil!" pour vérifier le lien sur votre profil !"
metadataLabel: "Étiquette" metadataLabel: "Étiquette"
metadataContent: "Contenu" metadataContent: "Contenu"
changeAvatar: "Changer l'image de profil" changeAvatar: "Changer l'image de profil"
@ -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 dun avertissement de contenu (CW) en réponse à une
publication avec un avertissement de contenu

View file

@ -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

View file

@ -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:" на початку коментаря у відповідь на запис із попередженням про
вміст

View file

@ -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.

View file

@ -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"
} }

View file

@ -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"
} }
} }

View file

@ -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:")) {

View file

@ -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

View file

@ -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,

View file

@ -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;

View file

@ -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")

View file

@ -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">

View file

@ -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({

View file

@ -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"

View file

@ -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);

View file

@ -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();

View file

@ -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>

View file

@ -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",

View file

@ -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>

View file

@ -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>

View file

@ -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);

View file

@ -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;
} }
} }

View file

@ -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,

View file

@ -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;

View file

@ -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,

View file

@ -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";

View file

@ -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<{

View file

@ -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);

View file

@ -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>

View file

@ -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>

View file

@ -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: {

View file

@ -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<{

View file

@ -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();
}, },
); );

View file

@ -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>

View file

@ -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(() => {

View file

@ -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 {

View file

@ -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>

View file

@ -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) => ({

View file

@ -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 ?? {

View file

@ -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>

View file

@ -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",

View file

@ -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 = () => {

View file

@ -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 &&

View file

@ -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>

View file

@ -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[];

View file

@ -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";

View file

@ -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;
} }
} }

View file

@ -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 });

View file

@ -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(() => {

View file

@ -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>

View file

@ -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 = () => {

View file

@ -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;
}); });

View file

@ -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
} }

View file

@ -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>

View file

@ -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";

View file

@ -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,

View file

@ -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>

View file

@ -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>

View file

@ -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";

View file

@ -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";

View file

@ -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({

View file

@ -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>

View file

@ -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({

View file

@ -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";

View file

@ -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>

View file

@ -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());

View file

@ -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,
}); });
} }

View file

@ -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>

View file

@ -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>

View file

@ -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

View file

@ -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";

View file

@ -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";

View file

@ -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;
}, },
}); });
} }

View file

@ -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(
id: byteify(key.id, "hex"), (key) => ({
type: "public-key", id: byteify(key.id, "hex"),
transports: ["usb", "nfc", "ble", "internal"], type: "public-key",
})), 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() {

View file

@ -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>

View file

@ -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",

View file

@ -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>

View file

@ -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";

View file

@ -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;
} }
} }

View file

@ -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: {

View file

@ -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));
} }
}); });

View file

@ -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();
} }
}; };

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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);

View file

@ -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