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
*.code-workspace
.DS_Store
files
files/
ormconfig.json
packages/backend/assets/instance.css
packages/backend/assets/sounds/None.mp3
packages/backend/assets/LICENSE
!/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/.idea

View file

@ -16,7 +16,7 @@ stages:
testCommit:
stage: build
image: node:alpine
image: node:latest
# Pick zero or more services to be used on all builds.
# Only needed when using a docker container to run your tests in.
# Check out: https://docs.gitlab.com/ee/ci/services/index.html
@ -30,7 +30,9 @@ testCommit:
# POSTGRES_PASSWORD: $POSTGRES_PASSWORD
# POSTGRES_HOST_AUTH_METHOD: trust
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
- corepack enable
- corepack prepare pnpm@latest --activate

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
FROM alpine:3.18 as build
FROM node:latest as build
WORKDIR /firefish
# 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 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-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
# 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
## Runtime container
FROM alpine:3.18
FROM node:latest
WORKDIR /firefish
# 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 . ./

View file

@ -103,4 +103,4 @@ NODE_ENV=production pnpm run migrate
## 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"
makeFollowManuallyApprove: "Folgeanfragen bedürfen der Genehmigung"
defaultNoteVisibility: "Standard-Sichtbarkeit"
follow: "Folge ich"
follow: "Folgen"
followRequest: "Follow anfragen"
followRequests: "Follow-Anfragen"
unfollow: "Nicht mehr folgen"
@ -1106,6 +1106,7 @@ _aboutFirefish:
um bei der Deckung der Betriebskosten zu helfen.
sponsors: Firefish-Sponsoren
donateHost: Spende an {host}
misskeyContributors: Misskey-Mitwirkende
_nsfw:
respect: "Mit NSFW gekennzeichnete Mediendateien 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
Account gelöscht. Fortfahren?
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"
unableToDelete: "Unable to delete"
inputNewFileName: "Enter a new filename"
inputNewDescription: "Enter new caption"
inputNewDescription: "Enter new description"
inputNewFolderName: "Enter a new folder name"
circularReferenceFolder: "The destination folder is a subfolder of the folder you
wish to move."
@ -634,8 +634,8 @@ disablePlayer: "Close video player"
expandTweet: "Expand tweet"
themeEditor: "Theme editor"
description: "Description"
describeFile: "Add caption"
enterFileDescription: "Enter caption"
describeFile: "Add description"
enterFileDescription: "Enter description"
author: "Author"
leaveConfirm: "There are unsaved changes. Do you want to discard them?"
manage: "Management"
@ -1054,7 +1054,7 @@ showUpdates: "Show a popup when Firefish updates"
recommendedInstances: "Recommended servers"
recommendedInstancesDescription: "Recommended servers separated by line breaks to
appear in the recommended timeline."
caption: "Auto Caption"
caption: "Auto description"
splash: "Splash Screen"
updateAvailable: "There might be an update available!"
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?"
inputNotMatch: "Input does not match"
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:
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"
deleteAllFilesConfirm: "¿Desea borrar todos los archivos?"
removeAllFollowing: "Retener todos los siguientes"
removeAllFollowingDescription: "Cancelar todos los siguientes del servidor {host}.
Ejecutar en caso de que esta instancia haya dejado de existir."
removeAllFollowingDescription: "Al ejecutar esto todas las cuentas de {host} dejarán
de seguirse. Por favor, ejecuta esto si el servidor ya no existe."
userSuspended: "Este usuario ha sido suspendido."
userSilenced: "Este usuario ha sido silenciado."
yourAccountSuspendedTitle: "Esta cuenta ha sido suspendida"
@ -838,7 +838,7 @@ gallery: "Galería"
recentPosts: "Posts recientes"
popularPosts: "Más vistos"
shareWithNote: "Compartir con una publicación"
ads: "Banners"
ads: "Banners de la comunidad"
expiration: "Termina el"
memo: "Notas"
priority: "Prioridad"
@ -892,7 +892,7 @@ unmuteThread: "Mostrar hilo"
ffVisibility: "Visibilidad de seguidores y seguidos"
ffVisibilityDescription: "Puedes configurar quien puede ver a quienes sigues y quienes
te siguen"
continueThread: "Ver la continuación del hilo"
continueThread: "Continuar hilo"
deleteAccountConfirm: "La cuenta será borrada irreversiblemente. ¿Está seguro?"
incorrectPassword: "La contraseña es incorrecta"
voteConfirm: "¿Confirma su voto a {choice}?"
@ -906,12 +906,12 @@ overridedDeviceKind: "Tipo de dispositivo"
smartphone: "Teléfono smartphone"
tablet: "Tablet"
auto: "Automático"
themeColor: "Color del tema"
themeColor: "Color de la marquesina del servidor"
size: "Tamaño"
numberOfColumn: "Cantidad de columnas"
searchByGoogle: "Buscar"
instanceDefaultLightTheme: "Tema claro por defecto de la instancia"
instanceDefaultDarkTheme: "Tema oscuro por defecto de la instancia"
instanceDefaultLightTheme: "Tema claro por defecto del servidor"
instanceDefaultDarkTheme: "Tema oscuro por defecto del servidor"
instanceDefaultThemeDescription: "Ingrese el código del tema en formato objeto"
mutePeriod: "Período de silenciamiento"
indefinitely: "Sin límite de tiempo"
@ -968,7 +968,7 @@ beta: "Beta"
enableAutoSensitive: "Marcar automáticamente contenido NSFW"
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,
puede ser activado para toda la instancia."
puede ser activado para todo el servidor."
activeEmailValidationDescription: "Habilita la validación estricta de direcciones
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
@ -1085,6 +1085,7 @@ _aboutFirefish:
pleaseDonateToHost: También considera donar a tu propio servidor , {host}, para
ayudar con los costos de operación.
sponsors: Patrocinadores de Firefish
misskeyContributors: Contribuidores de Misskey
_nsfw:
respect: "Ocultar medios NSFW"
ignore: "No esconder medios NSFW "
@ -1177,8 +1178,10 @@ _mfm:
fade: Fundido
advanced: MFM avanzado
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
positionDescription: Mueve el contenido en una cantidad especificada.
fadeDescription: Funde el contenido dentro y fuera.
_instanceTicker:
none: "No mostrar"
remote: "Mostrar a usuarios remotos"
@ -1197,7 +1200,7 @@ _channel:
owned: "Dueño"
following: "Siguiendo"
usersCount: "{n} participantes"
notesCount: "{n} publicaciones"
notesCount: "{n} Publicaciones"
nameOnly: Nombre solamente
nameAndDescription: Nombre y descripción
_menuDisplay:
@ -1301,7 +1304,7 @@ _theme:
fgHighlighted: "Texto resaltado"
_sfx:
note: "Nueva publicación"
noteMy: "Nota (a mí mismo)"
noteMy: "Publicación propia"
notification: "Notificaciones"
chat: "Chat"
chatBg: "Chat (Fondo)"
@ -1310,11 +1313,11 @@ _sfx:
_ago:
future: "Futuro"
justNow: "Recién ahora"
secondsAgo: "Hace {n} segundo(s)"
minutesAgo: "Hace {n} minuto(s)"
secondsAgo: "Hace {n} s"
minutesAgo: "Hace {n} m"
hoursAgo: "Hace {n} hora(s)"
daysAgo: "Hace {n} día(s)"
weeksAgo: "Hace {n} semana(s)"
daysAgo: "Hace {n} d"
weeksAgo: "Hace {n} sem"
monthsAgo: "Hace {n} mes(es)"
yearsAgo: "Hace {n} año(s)"
_time:
@ -1339,15 +1342,15 @@ _tutorial:
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_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
de todos los demás en esta instancia."
step5_5: "La línea de tiempo {icon} recomendada es donde puedes ver las publicaciones
de las instancias que los administradores recomiendan."
step5_6: "La línea de tiempo Social {icon} es donde puedes ver las publicaciones
de los amigos de tus seguidores."
step5_7: "La línea de tiempo Global {icon} es donde puedes ver las publicaciones
de todas las demás instancias conectadas."
de todos los demás en este servidor."
step5_5: "La línea de tiempo {icon} social es una combinación de las líneas de tiempo
Inicio y Local."
step5_6: "La línea de tiempo {icon} recomendada es donde puedes ver las publicaciones
de los servidores que los administradores recomiendan."
step5_7: "La línea de tiempo {icon} global es donde puedes ver las publicaciones
de todos los demás servidores a los cuales este servidor conecta."
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,
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
de hardware que soporte FIDO2 o con un certificado de huella digital o con un
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:
"read:account": "Ver información de la cuenta"
"write:account": "Editar información de la cuenta"
@ -1383,7 +1406,7 @@ _permissions:
"write:messaging": "Administrar chat"
"read:mutes": "Ver usuarios silenciados"
"write:mutes": "Administrar usuarios silenciados"
"write:notes": "Crear/borrar notas"
"write:notes": "Crear o borrar publicaciones"
"read:notifications": "Ver notificaciones"
"write:notifications": "Administrar notificaciones"
"read:reactions": "Ver reacciones"
@ -1405,16 +1428,19 @@ _auth:
shareAccess: "¿Desea permitir el acceso a la cuenta \"{name}\"?"
shareAccessAsk: "¿Está seguro de que desea autorizar esta aplicación para acceder
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"
callback: "Volviendo a la aplicación"
denied: "Acceso denegado"
copyAsk: 'Por favor, pega el siguiente código de autorización en la aplicación:'
allPermissions: Acceso completo
_antennaSources:
all: "Todas las notas"
homeTimeline: "Notas de los usuarios que sigues"
users: "Notas de un usuario o varios"
userList: "Notas de los usuarios de una lista"
userGroup: "Notas de los usuarios de una grupo"
all: "Todas las publicaciones"
homeTimeline: "Publicaciones de los usuarios que sigues"
users: "Publicaciones de usuarios específicos"
userList: "Publicaciones de una lista de usuarios específica"
userGroup: "Publicaciones de usuarios de un grupo"
instances: Publicaciones de todos los usuarios en un servidor
_weekday:
sunday: "Domingo"
monday: "Lunes"
@ -1431,24 +1457,28 @@ _widgets:
trends: "Tendencias"
clock: "Reloj"
rss: "Lector RSS"
rssTicker: "Ticker-RSS"
rssTicker: "Marquesina RSS"
activity: "Actividad"
photos: "Fotos"
digitalClock: "Reloj digital"
unixClock: "Reloj UNIX"
federation: "Federación"
instanceCloud: "Nube de palabras de la instancia"
instanceCloud: "Nube de servidores"
postForm: "Formulario"
slideshow: "Diapositivas"
button: "Botón"
onlineUsers: "Usuarios en linea"
onlineUsers: "Usuarios en línea"
jobQueue: "Cola de trabajos"
serverMetric: "Estadísticas del servidor"
aiscript: "Consola de AiScript"
aichan: "indigo"
userList: Lista Usuarios
userList: Lista de usuarios
_userList:
chooseList: Seleccione una lista
serverInfo: Información del servidor
meiliStatus: Estado del servidor
meiliSize: Tamaño del índice
meiliIndexCount: Publicaciones indizadas
_cw:
hide: "Ocultar"
show: "Ver más"
@ -1478,18 +1508,18 @@ _poll:
remainingSeconds: "Quedan {s} segundos para que finalice"
_visibility:
public: "Público"
publicDescription: "Visible para todos los usuarios"
home: "Inicio"
publicDescription: "Tu publicación será visible en todas las líneas de tiempo"
home: "Sin listar (Inicio)"
homeDescription: "Visible sólo en la linea de tiempo de inicio"
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"
specifiedDescription: "Visible sólo para los usuarios elegidos"
localOnly: "Solo local"
localOnlyDescription: "Oculto para usuarios remotos"
_postForm:
replyPlaceholder: "Responder a esta nota"
quotePlaceholder: "Citar esta nota"
replyPlaceholder: "Responder a esta publicación..."
quotePlaceholder: "Citar esta publicación..."
channelPlaceholder: "Postear en el canal"
_placeholders:
a: "¿Qué haces?"
@ -1514,7 +1544,7 @@ _profile:
locationDescription: Si ingresas tu ciudad primero, el tiempo local tuyo será visible
para otros usuarios.
_exportOrImport:
allNotes: "Todas las notas"
allNotes: "Todas las publicaciones"
followingList: "Siguiendo"
muteList: "Silenciados"
blockingList: "Bloqueados"
@ -1527,10 +1557,10 @@ _charts:
usersIncDec: "Variación de usuarios"
usersTotal: "Total de usuarios"
activeUsers: "Cantidad de usuarios activos"
notesIncDec: "Variación de la cantidad de notas"
localNotesIncDec: "Variación de la cantidad de notas locales"
remoteNotesIncDec: "Variación de la cantidad de notas remotas"
notesTotal: "Total de notas"
notesIncDec: "Diferencia en la cantidad de publicaciones"
localNotesIncDec: "Diferencia en la cantidad de publicaciones locales"
remoteNotesIncDec: "Diferencia en el número de publicaciones remotas"
notesTotal: "Total de publicaciones"
filesIncDec: "Variación de cantidad de archivos"
filesTotal: "Total de archivos"
storageUsageIncDec: "Variación de uso del almacenamiento"
@ -1539,8 +1569,8 @@ _instanceCharts:
requests: "Pedidos"
users: "Variación de usuarios"
usersTotal: "Total acumulado de usuarios"
notes: "Variación de la cantidad de notas"
notesTotal: "Total acumulado de la cantidad de notas"
notes: "Diferencia en el número de publicaciones"
notesTotal: "Total acumulado de publicaciones"
ff: "Variación de cantidad de seguidos/seguidores"
ffTotal: "Total acumulado de cantidad de seguidos/seguidores"
cacheSize: "Variación del tamaño de la caché"
@ -1627,10 +1657,10 @@ _pages:
id: "Lienzo ID"
width: "Ancho"
height: "Altura"
note: "Nota embebida"
note: "Publicación incrustada"
_note:
id: "Id de la nota"
idDescription: "Pega la URL de la nota para configurarla"
id: "ID de la publicación"
idDescription: "Puedes también pegar la URL de la publicación aquí."
detailed: "Ver Detalles"
switch: "Interruptor"
_switch:
@ -1853,7 +1883,7 @@ _notification:
youGotMention: "Mención de {name}"
youGotReply: "Respuesta de {name}"
youGotQuote: "Citado por {name}"
youRenoted: "Renotado por {name}"
youRenoted: "Impulsado por {name}"
youGotPoll: "Encuestado por {name}"
youGotMessagingMessageFromUser: "{name} comenzó un chat contigo"
youGotMessagingMessageFromGroup: "Tienes un chat de {name}"
@ -1868,7 +1898,7 @@ _notification:
follow: "Siguiendo"
mention: "Menciones"
reply: "Respuestas"
renote: "Renotar"
renote: "Impulsos"
quote: "Citar"
reaction: "Reacción"
pollVote: "Votado en la encuesta"
@ -1880,7 +1910,10 @@ _notification:
_actions:
followBack: "Te sigue de vuelta"
reply: "Responder"
renote: "Renotar"
renote: "Impulsos"
renoted: impulsó tu publicación
reacted: reaccionó a tu publicación
voted: votó en tu encuesta
_deck:
alwaysShowMainColumn: "Siempre mostrar la columna principal"
columnAlign: "Alinear columnas"
@ -1892,9 +1925,9 @@ _deck:
swapDown: "Mover abajo"
stackLeft: "Apilar a la izquierda"
popRight: "Sacar a la derecha"
profile: "Perfil"
newProfile: "Nuevo perfil"
deleteProfile: "Eliminar perfil"
profile: "Espacio de trabajo"
newProfile: "Nuevo espacio de trabajo"
deleteProfile: "Eliminar espacio de trabajo"
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
columnas donde quieras."
@ -1905,10 +1938,13 @@ _deck:
widgets: "Widgets"
notifications: "Notificaciones"
tl: "Linea de tiempo"
antenna: "Antenas"
antenna: "Antena"
list: "Listas"
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
replayTutorial: Repetir Tutorial
privateMode: Modo privado
@ -1923,11 +1959,13 @@ breakFollowConfirm: ¿Estás seguro de que quieres eliminar el seguidor?
subscribePushNotification: Habilitar notificaciones
unsubscribePushNotification: Desactivar notificaciones
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!
moveFrom: Mueve a esta cuenta de una cuenta antigua
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
noThankYou: No gracias
userSaysSomethingReason: '{name} dijo {reason}'
@ -1938,7 +1976,7 @@ caption: Auto Subtítulos
showAds: Mostrar banners
enterSendsMessage: Presione "RETORNO" en los mensajes para enviar el mensaje (para
apagarlo es Ctrl + RETORNO)
recommendedInstances: Instancias Recomendadas
recommendedInstances: Servidores recomendados
instanceSecurity: Seguridad del servidor
seperateRenoteQuote: Separar botones de Impulsar y Citar
_messaging:
@ -1995,6 +2033,10 @@ _filters:
fromUser: Del usuario
fromDomain: Desde el dominio
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}'
userSaysSomethingReasonQuote: '{name} citó una publicación que contiene {reason}'
privateModeInfo: Al activar, solo servidores autorizados podrán federar con tu servidor.
@ -2022,3 +2064,103 @@ remindMeLater: Recordar nuevamente
removeQuote: Eliminar cita
removeRecipient: Eliminar destinatario
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"
searchUser: "Chercher un·e utilisateur·rice"
reply: "Répondre"
loadMore: "Afficher plus"
loadMore: "Charger plus"
showMore: "Afficher plus"
showLess: "Fermer"
youGotNewFollower: "Vous suit"
@ -245,7 +245,7 @@ currentPassword: "Mot de passe actuel"
newPassword: "Nouveau mot de passe"
newPasswordRetype: "Répéter le nouveau mot de passe"
attachFile: "Joindre un fichier"
more: "Plus"
more: "Plus !"
featured: "Tendances"
usernameOrUserId: "Nom dutilisateur·rice ou ID utilisateur"
noSuchUser: "Utilisateur·rice non trouvé·e"
@ -516,11 +516,11 @@ showFeaturedNotesInTimeline: "Afficher les publications des Tendances dans le fi
d'actualité"
objectStorage: "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
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
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."
objectStorageBucket: "Bucket"
objectStorageBucketDesc: "Veuillez spécifier le nom du compartiment utilisé sur le
@ -591,7 +591,7 @@ divider: "Séparateur"
addItem: "Ajouter un élément"
relays: "Relais"
addRelay: "Ajouter un relais"
inboxUrl: "Inbox URL"
inboxUrl: "URL de boîte de récéption"
addedRelays: "Relais ajoutés"
serviceworkerInfo: "Devrait être activé pour les notifications push."
deletedNote: "Publication supprimée"
@ -839,7 +839,7 @@ gallery: "Galerie"
recentPosts: "Publications récentes"
popularPosts: "Publications populaires"
shareWithNote: "Partager dans une publication"
ads: "Bannière communautaire"
ads: "Bannières communautaires"
expiration: "Échéance"
memo: "Pense-bête"
priority: "Priorité"
@ -986,8 +986,8 @@ _plugin:
manage: "Gestion des plugins"
_registry:
scope: "Portée"
key: "Clé "
keys: "Clé "
key: "Clé"
keys: "Clés"
domain: "Domaine"
createKey: "Créer une clé"
_aboutFirefish:
@ -1009,6 +1009,7 @@ _aboutFirefish:
donateHost: Faire un don à {host}
patronsList: Listé chronologiquement, pas par taille de donation. Faite un don avec
le lien ci-dessus pour avoir votre nom affiché ici !
misskeyContributors: Contributeurs Misskey
_nsfw:
respect: "Cacher les médias marqués comme contenu sensible"
ignore: "Afficher les médias sensibles"
@ -1020,7 +1021,7 @@ _mfm:
dummy: "La Fédiverse s'agrandit avec Firefish"
mention: "Mentionner"
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"
hashtagDescription: "Vous pouvez afficher les hashtags en utilisant un croisillon
et du texte."
@ -1033,7 +1034,7 @@ _mfm:
small: "Diminuer l'emphase"
smallDescription: "Le contenu peut être affiché en petit et fin."
center: "Centrer"
centerDescription: "Le contenu peut être centré"
centerDescription: "Centre le contenu sur la page."
inlineCode: "Code (inline)"
inlineCodeDescription: "Affiche la coloration syntaxique des lignes de code."
blockCode: "Bloc de code"
@ -1104,7 +1105,7 @@ _mfm:
background: Couleur d'arrière-plan
plain: Simple
_instanceTicker:
none: "Cacher "
none: "Cacher"
remote: "Montrer pour les utilisateur·ice·s distant·e·s"
always: "Toujours afficher"
_serverDisconnectedBehavior:
@ -1169,17 +1170,17 @@ _theme:
color: "Couleur"
refProp: "Appeler une propriété"
refConst: "Appeler une constante"
key: "Clé "
key: "Clé"
func: "Fonction"
funcKind: "Type de fonction"
argument: "Argument"
basedProp: "Nom de la propriété référencée"
alpha: "Transparence"
darken: "Sombre"
darken: "Assombrir"
lighten: "Clair"
inputConstantName: "Insérez un nom de constante"
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}
?"
keys:
@ -1251,9 +1252,9 @@ _time:
day: "j"
_tutorial:
title: "Comment utiliser Firefish"
step1_1: "Bienvenue!"
step1_2: "On va vous installer. Vous serez opérationnel en un rien de temps"
step2_1: "Tout d'abord, remplissez votre profil"
step1_1: "Bienvenue!"
step1_2: "On va vous installer. Vous serez opérationnel en un rien de temps !"
step2_1: "Tout d'abord, remplissez votre profil."
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."
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
{introduction} ou un simple 'Bonjour tout le monde !'"
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
de vos abonnements."
step5_4: "La fil {icon} Local est l'endroit où vous pouvez voir les publications
@ -1356,7 +1357,7 @@ _permissions:
_auth:
shareAccess: "Autoriser \"{name}\" à accéder à votre compte ?"
shareAccessAsk: "Voulez-vous vraiment autoriser cette application à accéder à votre
compte?"
compte ?"
permissionAsk: "Cette application nécessite les autorisations suivantes :"
pleaseGoBack: "Veuillez retourner à lapplication"
callback: "Retour vers lapplication"
@ -1411,7 +1412,7 @@ _widgets:
rssTicker: Bandeau RSS
_cw:
hide: "Masquer"
show: "Afficher plus …"
show: "Afficher contenu"
chars: "{count} caractères"
files: "{count} fichiers"
_poll:
@ -1421,8 +1422,8 @@ _poll:
canMultipleVote: "Autoriser le multi-choix"
expiration: "Fin du sondage"
infinite: "Illimité"
at: "Choisir une date"
after: "Choisir la durée"
at: "Expire le..."
after: "Expire après..."
deadlineDate: "Date de fin"
deadlineTime: "Heure de fin"
duration: "Durée"
@ -1467,7 +1468,7 @@ _profile:
metadataEdit: "Éditer les 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}
pour vérifier le lien sur votre profil!"
pour vérifier le lien sur votre profil !"
metadataLabel: "Étiquette"
metadataContent: "Contenu"
changeAvatar: "Changer l'image de profil"
@ -1502,7 +1503,7 @@ _instanceCharts:
usersTotal: "Total cumulé du nombre d'utilisateur·rice·s"
notes: "Variation du nombre de 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"
cacheSize: "Variation de la taille du cache"
cacheSizeTotal: "Total cumulé de la taille du cache"
@ -1811,7 +1812,7 @@ _relayStatus:
accepted: "Accepté"
rejected: "Refusée"
_notification:
fileUploaded: "Le fichier a été téléversé !"
fileUploaded: "Le fichier a été téléversé"
youGotMention: "{name} vous a mentionné"
youGotReply: "Réponse de {name}"
youGotQuote: "Cité·e par {name}"
@ -1992,7 +1993,7 @@ type: Type
speed: Vitesse
slow: Lent
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
(sinon Ctrl+Entrée)
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
noteId: ID des publications
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}.
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
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
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."
enableRecommendedTimeline: Activer le fil recommandé
silenceThisInstance: Masquer ce serveur
@ -2196,10 +2197,12 @@ _skinTones:
objectStorageS3ForcePathStyle: Utiliser des URL d'endpoints basées sur le chemin
objectStorageS3ForcePathStyleDesc: Activez cette option pour construire les URL d'endpoints
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
delete2faConfirm: Cela supprimera de manière irréversible la double authentification
sur ce compte. Souhaitez-vous continuer ?
inputNotMatch: L'entrée ne correspond pas
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 ?
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
edited: Redigert {date} {time}
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}
sponsors: Спонсори Firefish
misskeyContributors: Контрибутори Misskey
_nsfw:
respect: "Приховувати NSFW медіа"
ignore: "Не приховувати NSFW медіа"
@ -2148,3 +2149,5 @@ delete2fa: Вимкнути двофакторну авторизацію
inputNotMatch: Введене не співпадає
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"
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."
explore: "Khám phó"
explore: "Khám phá"
messageRead: "Đã đọc"
noMoreHistory: "Không còn gì để đọc"
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.
donateHost: Ủng hộ {host}
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:
respect: "Ẩn nội dung NSFW"
ignore: "Hiện nội dung NSFW"
@ -1138,6 +1140,22 @@ _mfm:
stop: Dừng CĐN
play: Phát CĐN
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:
none: "Không hiển thị"
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"
dialog: "Hiện hộp thoại cảnh báo"
quiet: "Hiển thị cảnh báo không phô trương"
nothing: không làm gì
_channel:
create: "Tạo kênh"
edit: "Chỉnh sửa kênh"
@ -1156,6 +1175,8 @@ _channel:
following: "Đang theo dõi"
usersCount: "{n} Thành viên"
notesCount: "{n} Tút"
nameOnly: Chỉ tên
nameAndDescription: Tên và mô tả
_menuDisplay:
sideFull: "Thanh bên"
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
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."
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:
"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"
@ -1365,12 +1405,15 @@ _auth:
pleaseGoBack: "Vui lòng quay lại ứng dụng"
callback: "Quay lại ứng dụng"
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:
all: "Toàn bộ tút"
homeTimeline: "Tút từ những người đã theo dõi"
users: "Tút từ những người 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ể"
instances: Tút từ mọi người trên máy chủ
_weekday:
sunday: "Chủ Nhật"
monday: "Thứ Hai"
@ -1402,6 +1445,13 @@ _widgets:
serverMetric: "Thống kê máy chủ"
aiscript: "AiScript console"
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:
hide: "Ẩn"
show: "Tải thêm"
@ -1465,6 +1515,8 @@ _profile:
metadataContent: "Nội dung"
changeAvatar: "Đổi ảnh đại diện"
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:
allNotes: "Toàn bộ tút"
followingList: "Đang theo dõi"
@ -1504,6 +1556,7 @@ _timelines:
local: "Máy chủ này"
social: "Xã hội"
global: "Liên hợp"
recommended: Đề xuất
_pages:
newPage: "Tạo Trang mới"
editPage: "Sửa Trang này"
@ -1832,6 +1885,9 @@ _notification:
followBack: "đã theo dõi lại bạn"
reply: "Trả 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:
alwaysShowMainColumn: "Luôn hiện cột chính"
columnAlign: "Căn cột"
@ -1859,6 +1915,9 @@ _deck:
list: "Danh sách"
mentions: "Lượt nhắc"
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ẻ
renoteUnmute: Bỏ ẩn lượt chia sẻ
searchPlaceholder: Lướt Firefish
@ -1867,3 +1926,210 @@ findOtherInstance: Tìm máy chủ khác
noThankYou: Từ chối
_filters:
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",
"version": "1.0.4-beta",
"version": "1.0.5-dev6",
"codename": "aqua",
"repository": {
"type": "git",
"url": "https://git.joinfirefish.org/firefish/firefish.git"
},
"packageManager": "pnpm@8.6.9",
"packageManager": "pnpm@8.6.11",
"private": true,
"scripts": {
"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"
},
"dependencies": {
"@bull-board/api": "5.6.0",
"@bull-board/ui": "5.6.0",
"@napi-rs/cli": "^2.16.1",
"@bull-board/api": "5.7.2",
"@bull-board/ui": "5.7.2",
"@napi-rs/cli": "^2.16.2",
"@tensorflow/tfjs": "^3.21.0",
"js-yaml": "4.1.0",
"seedrandom": "^3.0.5"
@ -49,7 +49,7 @@
"devDependencies": {
"@types/gulp": "4.0.13",
"@types/gulp-rename": "2.0.2",
"@types/node": "20.4.1",
"@types/node": "20.4.9",
"chalk": "4.1.2",
"cross-env": "7.0.3",
"cypress": "10.11.0",
@ -60,7 +60,7 @@
"gulp-replace": "1.1.4",
"gulp-terser": "2.1.0",
"install-peers": "^1.0.4",
"rome": "^v12.1.3-nightly.f65b0d9",
"rome": "^12.1.3",
"start-server-and-test": "1.15.2",
"typescript": "5.1.6"
}

View file

@ -28,16 +28,16 @@
"@tensorflow/tfjs-node": "3.21.1"
},
"dependencies": {
"@bull-board/api": "5.6.0",
"@bull-board/koa": "5.6.0",
"@bull-board/ui": "5.6.0",
"@bull-board/api": "5.7.2",
"@bull-board/koa": "5.7.2",
"@bull-board/ui": "5.7.2",
"@discordapp/twemoji": "14.1.2",
"@elastic/elasticsearch": "7.17.0",
"@koa/cors": "3.4.3",
"@koa/multer": "3.0.2",
"@koa/router": "9.0.1",
"@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",
"@syuilo/aiscript": "0.11.1",
"@tensorflow/tfjs": "^4.2.0",
@ -51,9 +51,9 @@
"axios": "^1.4.0",
"bcryptjs": "2.4.3",
"blurhash": "2.0.5",
"bull": "4.10.4",
"bull": "4.11.2",
"cacheable-lookup": "7.0.0",
"cassandra-driver": "^4.6.4",
"cassandra-driver": "4.6.4",
"cbor": "8.1.0",
"chalk": "5.3.0",
"chalk-template": "0.4.0",
@ -95,18 +95,18 @@
"meilisearch": "0.33.0",
"mfm-js": "0.23.3",
"mime-types": "2.1.35",
"msgpackr": "1.9.5",
"msgpackr": "1.9.6",
"multer": "1.4.4-lts.1",
"native-utils": "link:native-utils",
"nested-property": "4.0.0",
"node-fetch": "3.3.1",
"nodemailer": "6.9.3",
"node-fetch": "3.3.2",
"nodemailer": "6.9.4",
"nsfwjs": "2.4.2",
"oauth": "^0.10.0",
"os-utils": "0.0.14",
"otpauth": "^9.1.3",
"otpauth": "^9.1.4",
"parse5": "7.1.2",
"pg": "8.11.1",
"pg": "8.11.2",
"private-ip": "2.3.4",
"probe-image-size": "7.2.3",
"promise-limit": "2.7.0",
@ -116,37 +116,37 @@
"qs": "6.11.2",
"random-seed": "0.3.0",
"ratelimiter": "3.4.1",
"re2": "1.19.1",
"re2": "1.20.1",
"redis-lock": "0.1.4",
"redis-semaphore": "5.3.1",
"redis-semaphore": "5.4.0",
"reflect-metadata": "0.1.13",
"rename": "1.0.4",
"rndstr": "1.0.0",
"rss-parser": "3.13.0",
"sanitize-html": "2.10.0",
"sanitize-html": "2.11.0",
"seedrandom": "^3.0.5",
"semver": "7.5.4",
"sharp": "0.32.1",
"sharp": "0.32.4",
"sonic-channel": "^1.3.1",
"stringz": "2.1.0",
"summaly": "2.7.0",
"syslog-pro": "1.0.0",
"systeminformation": "5.17.17",
"systeminformation": "5.18.13",
"tar-stream": "^3.1.6",
"tesseract.js": "^3.0.3",
"tinycolor2": "1.5.2",
"tesseract.js": "^4.1.1",
"tinycolor2": "1.6.0",
"tmp": "0.2.1",
"twemoji-parser": "14.0.0",
"typeorm": "0.3.17",
"ulid": "2.3.0",
"uuid": "9.0.0",
"web-push": "3.6.3",
"web-push": "3.6.4",
"websocket": "1.0.34",
"xev": "3.0.2"
},
"devDependencies": {
"@swc/cli": "^0.1.62",
"@swc/core": "^1.3.68",
"@swc/core": "^1.3.75",
"@types/adm-zip": "^0.5.0",
"@types/bcryptjs": "2.4.2",
"@types/cbor": "6.0.0",
@ -156,7 +156,7 @@
"@types/jsdom": "21.1.1",
"@types/jsonld": "1.5.9",
"@types/jsrsasign": "10.5.8",
"@types/koa": "2.13.6",
"@types/koa": "2.13.8",
"@types/koa-bodyparser": "4.3.10",
"@types/koa-cors": "0.0.2",
"@types/koa-favicon": "2.0.21",
@ -170,7 +170,7 @@
"@types/mocha": "9.1.1",
"@types/node": "18.11.18",
"@types/node-fetch": "3.0.3",
"@types/nodemailer": "6.4.8",
"@types/nodemailer": "6.4.9",
"@types/oauth": "0.9.1",
"@types/probe-image-size": "^7.2.0",
"@types/pug": "2.0.6",
@ -191,7 +191,7 @@
"@types/websocket": "1.0.5",
"@types/ws": "8.5.5",
"cross-env": "7.0.3",
"eslint": "^8.44.0",
"eslint": "^8.46.0",
"execa": "6.1.0",
"json5-loader": "4.0.1",
"mocha": "10.2.0",
@ -202,7 +202,7 @@
"ts-node": "10.9.1",
"tsconfig-paths": "4.2.0",
"typescript": "5.1.6",
"webpack": "^5.88.1",
"webpack": "^5.88.2",
"ws": "8.13.0"
}
}

View file

@ -226,6 +226,14 @@ export default hasConfig
return null;
} else if (term.startsWith("domain:")) {
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}`);
return null;
} 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
let user: IRemoteUser;
try {
@ -278,6 +293,14 @@ export async function createPerson(
isCollectionOrOrderedCollection(person.following)
? person.following.totalItems
: 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,
uri: person.id,
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 = {
lastFetchedAt: new Date(),
inbox: person.inbox,
@ -503,6 +541,14 @@ export async function updatePerson(
isCollectionOrOrderedCollection(person.following)
? person.following.totalItems
: undefined,
notesCount:
notesCount !== undefined
? notesCount
: person.outbox &&
typeof person.outbox !== "string" &&
isCollectionOrOrderedCollection(person.outbox)
? person.outbox.totalItems
: undefined,
featured: person.featured,
emojis: emojiNames,
name: truncate(person.name, nameLength),
@ -573,7 +619,7 @@ export async function updatePerson(
{
followerSharedInbox:
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.orderedItems;
const items = await Promise.all(
toArray(unresolvedItems).map((x) => resolver.resolve(x)),
toArray(unresolvedItems).map((x) => resolver?.resolve(x)),
);
// Resolve and regist Notes

View file

@ -1,6 +1,5 @@
import define from "../../../define.js";
import { createImportCustomEmojisJob } from "@/queue/index.js";
import ms from "ms";
export const meta = {
secure: true,

View file

@ -26,16 +26,15 @@ export const paramDef = {
} as const;
export default define(meta, paramDef, async (ps) => {
const worker = createWorker({
logger: (m) => console.log(m),
});
const worker = await createWorker();
await worker.load();
await worker.loadLanguage("eng");
await worker.initialize("eng");
const {
data: { text },
} = await worker.recognize(ps.url);
} = await worker.recognize(ps.url, {
rotateAuto: true,
});
await worker.terminate();
return text;

View file

@ -85,6 +85,7 @@ export default define(meta, paramDef, async (ps, me) => {
query
.andWhere("note.text ILIKE :q", { q: `%${sqlLikeEscape(ps.query)}%` })
.andWhere("note.visibility = 'public'")
.innerJoinAndSelect("note.user", "user")
.leftJoinAndSelect("user.avatar", "avatar")
.leftJoinAndSelect("user.banner", "banner")

View file

@ -147,7 +147,7 @@
<span class="button-label-big">Refresh</span>
</button>
<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>
<a href="/flush">
<button class="button-small">

View file

@ -892,7 +892,7 @@ async function insertNote(
}
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) {
es.index({

View file

@ -36,13 +36,13 @@
"blurhash": "2.0.5",
"broadcast-channel": "5.1.0",
"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-chart-matrix": "^2.0.1",
"chartjs-plugin-gradient": "0.6.1",
"chartjs-plugin-zoom": "2.0.1",
"city-timezones": "^1.2.1",
"compare-versions": "6.0.0",
"compare-versions": "6.1.0",
"cropperjs": "2.0.0-beta.2",
"cross-env": "7.0.3",
"cypress": "10.11.0",
@ -65,20 +65,20 @@
"mfm-js": "0.23.3",
"paralint": "^1.2.1",
"photoswipe": "5.3.8",
"prettier": "3.0.0",
"prettier": "3.0.1",
"prettier-plugin-vue": "1.1.6",
"prismjs": "1.29.0",
"punycode": "2.3.0",
"querystring": "0.2.1",
"rndstr": "1.0.0",
"rollup": "3.27.0",
"rollup": "3.27.2",
"s-age": "1.1.2",
"sass": "1.64.1",
"sass": "1.64.2",
"seedrandom": "3.0.5",
"start-server-and-test": "1.15.2",
"strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0",
"swiper": "10.0.4",
"swiper": "10.1.0",
"syuilo-password-strength": "0.0.1",
"textarea-caret": "3.1.0",
"three": "0.146.0",
@ -91,10 +91,10 @@
"unicode-emoji-json": "^0.4.0",
"uuid": "9.0.0",
"vanilla-tilt": "1.8.0",
"vite": "4.4.7",
"vite": "4.4.9",
"vite-plugin-compression": "^0.5.1",
"vue": "3.3.4",
"vue-draggable-plus": "^0.2.2",
"vue-draggable-plus": "^0.2.4",
"vue-isyourpasswordsafe": "^2.0.0",
"vue-plyr": "^7.0.0",
"vue-prism-editor": "2.0.0-alpha.2"

View file

@ -65,10 +65,11 @@
</template>
<script lang="ts" setup>
import { ref } from "vue";
import MkButton from "@/components/MkButton.vue";
import MkSwitch from "@/components/form/switch.vue";
import MkKeyValue from "@/components/MkKeyValue.vue";
import { acct, userPage } from "@/filters/user";
import * as os from "@/os";
import { i18n } from "@/i18n";
@ -80,11 +81,11 @@ const emit = defineEmits<{
(ev: "resolved", reportId: string): void;
}>();
const forward = $ref(props.report.forwarded);
const forward = ref(props.report.forwarded);
function resolve() {
os.apiWithDialog("admin/resolve-abuse-user-report", {
forward,
forward: forward.value,
reportId: props.report.id,
}).then(() => {
emit("resolved", props.report.id);

View file

@ -108,14 +108,7 @@
</template>
<script lang="ts" setup>
import {
computed,
nextTick,
onBeforeUnmount,
onMounted,
ref,
shallowRef,
} from "vue";
import { computed, onBeforeUnmount, onMounted, ref } from "vue";
import tinycolor from "tinycolor2";
import { globalEvents } from "@/events.js";
@ -174,19 +167,19 @@ const texts = computed(() => {
});
let enabled = true,
majorGraduationColor = $ref<string>(),
majorGraduationColor = ref<string>(),
// let minorGraduationColor = $ref<string>();
sHandColor = $ref<string>(),
mHandColor = $ref<string>(),
hHandColor = $ref<string>(),
nowColor = $ref<string>(),
h = $ref<number>(0),
m = $ref<number>(0),
s = $ref<number>(0),
hAngle = $ref<number>(0),
mAngle = $ref<number>(0),
sAngle = $ref<number>(0),
disableSAnimate = $ref(false),
sHandColor = ref<string>(),
mHandColor = ref<string>(),
hHandColor = ref<string>(),
nowColor = ref<string>(),
h = ref<number>(0),
m = ref<number>(0),
s = ref<number>(0),
hAngle = ref<number>(0),
mAngle = ref<number>(0),
sAngle = ref<number>(0),
disableSAnimate = ref(false),
sOneRound = false;
function tick() {
@ -194,29 +187,31 @@ function tick() {
now.setMinutes(
now.getMinutes() + (new Date().getTimezoneOffset() + props.offset),
);
s = now.getSeconds();
m = now.getMinutes();
h = now.getHours();
hAngle =
(Math.PI * ((h % (props.twentyfour ? 24 : 12)) + (m + s / 60) / 60)) /
s.value = now.getSeconds();
m.value = now.getMinutes();
h.value = now.getHours();
hAngle.value =
(Math.PI *
((h.value % (props.twentyfour ? 24 : 12)) +
(m.value + s.value / 60) / 60)) /
(props.twentyfour ? 12 : 6);
mAngle = (Math.PI * (m + s / 60)) / 30;
mAngle.value = (Math.PI * (m.value + s.value / 60)) / 30;
if (sOneRound) {
// (59->0)
sAngle = (Math.PI * 60) / 30;
sAngle.value = (Math.PI * 60) / 30;
window.setTimeout(() => {
disableSAnimate = true;
disableSAnimate.value = true;
window.setTimeout(() => {
sAngle = 0;
sAngle.value = 0;
window.setTimeout(() => {
disableSAnimate = false;
disableSAnimate.value = false;
}, 100);
}, 100);
}, 700);
} else {
sAngle = (Math.PI * s) / 30;
sAngle.value = (Math.PI * s.value) / 30;
}
sOneRound = s === 59;
sOneRound = s.value === 59;
}
tick();
@ -227,16 +222,16 @@ function calcColors() {
const accent = tinycolor(
computedStyle.getPropertyValue("--accent"),
).toHexString();
majorGraduationColor = dark
majorGraduationColor.value = dark
? "rgba(255, 255, 255, 0.3)"
: "rgba(0, 0, 0, 0.3)";
// 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)";
mHandColor = tinycolor(
sHandColor.value = dark ? "rgba(255, 255, 255, 0.5)" : "rgba(0, 0, 0, 0.3)";
mHandColor.value = tinycolor(
computedStyle.getPropertyValue("--fg"),
).toHexString();
hHandColor = accent;
nowColor = accent;
hHandColor.value = accent;
nowColor.value = accent;
}
calcColors();

View file

@ -27,7 +27,7 @@
</template>
<script lang="ts" setup>
import { nextTick, onMounted } from "vue";
import { nextTick, onMounted, ref } from "vue";
const props = defineProps<{
type?: "button" | "submit" | "reset";
@ -49,13 +49,13 @@ const emit = defineEmits<{
(ev: "click", payload: MouseEvent): void;
}>();
const el = $ref<HTMLElement | null>(null);
const ripples = $ref<HTMLElement | null>(null);
const el = ref<HTMLElement | null>(null);
const ripples = ref<HTMLElement | null>(null);
onMounted(() => {
if (props.autofocus) {
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.left = (evt.clientX - rect.left - 1).toString() + "px";
ripples!.appendChild(ripple);
ripples.value!.appendChild(ripple);
const circleCenterX = evt.clientX - rect.left;
const circleCenterY = evt.clientY - rect.top;
@ -101,7 +101,7 @@ function onMousedown(evt: MouseEvent): void {
ripple.style.opacity = "0";
}, 1000);
window.setTimeout(() => {
if (ripples) ripples.removeChild(ripple);
if (ripples.value) ripples.value.removeChild(ripple);
}, 2000);
}
</script>

View file

@ -9,7 +9,7 @@
<script lang="ts" setup>
import type { PropType } from "vue";
import { onMounted, onUnmounted, ref, watch } from "vue";
import { onMounted, ref, watch } from "vue";
import {
ArcElement,
BarController,
@ -183,9 +183,9 @@ const render = () => {
document.documentElement,
).getPropertyValue("--fg");
const maxes = chartData.series.map((x, i) =>
Math.max(...x.data.map((d) => d.y)),
);
// const maxes = chartData.series.map((x, i) =>
// Math.max(...x.data.map((d) => d.y)),
// );
chartInstance = new Chart(chartEl.value, {
type: props.bar ? "bar" : "line",

View file

@ -11,6 +11,8 @@
</template>
<script lang="ts" setup>
import { ref } from "vue";
import XModalWindow from "@/components/MkModalWindow.vue";
import XCheatSheet from "@/pages/mfm-cheat-sheet.vue";
import { i18n } from "@/i18n";
@ -20,11 +22,7 @@ const emit = defineEmits<{
(ev: "closed"): void;
}>();
const dialog = $ref<InstanceType<typeof XModalWindow>>();
function close(res) {
dialog.close();
}
const dialog = ref<InstanceType<typeof XModalWindow>>();
</script>
<style lang="scss" scoped>

View file

@ -12,9 +12,9 @@
</template>
<script lang="ts" setup>
import { onBeforeUnmount, onMounted } from "vue";
import MkMenu from "./MkMenu.vue";
import type { MenuItem } from "./types/menu.vue";
import { onBeforeUnmount, onMounted, ref } from "vue";
import MkMenu from "@/components/MkMenu.vue";
import type { MenuItem } from "@/types/menu";
import contains from "@/scripts/contains";
import * as os from "@/os";
@ -27,16 +27,16 @@ const emit = defineEmits<{
(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(() => {
let left = props.ev.pageX + 1, // + 1
top = props.ev.pageY + 1; // + 1
const width = rootEl.offsetWidth;
const height = rootEl.offsetHeight;
const width = rootEl.value.offsetWidth;
const height = rootEl.value.offsetHeight;
if (left + width - window.pageXOffset > window.innerWidth) {
left = window.innerWidth - width + window.pageXOffset;
@ -54,8 +54,8 @@ onMounted(() => {
left = 0;
}
rootEl.style.top = `${top}px`;
rootEl.style.left = `${left}px`;
rootEl.value.style.top = `${top}px`;
rootEl.value.style.left = `${left}px`;
document.body.addEventListener("mousedown", onMousedown);
});
@ -65,7 +65,8 @@ onBeforeUnmount(() => {
});
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>

View file

@ -36,7 +36,7 @@
</template>
<script lang="ts" setup>
import { nextTick, onMounted } from "vue";
import { onMounted, ref } from "vue";
import type * as misskey from "firefish-js";
import Cropper from "cropperjs";
import tinycolor from "tinycolor2";
@ -62,10 +62,10 @@ const props = defineProps<{
const imgUrl = `${url}/proxy/image.webp?${query({
url: props.file.url,
})}`;
const dialogEl = $ref<InstanceType<typeof XModalWindow>>();
const imgEl = $ref<HTMLImageElement>();
const dialogEl = ref<InstanceType<typeof XModalWindow>>();
const imgEl = ref<HTMLImageElement>();
let cropper: Cropper | null = null,
loading = $ref(true);
loading = ref(true);
const ok = async () => {
const promise = new Promise<misskey.entities.DriveFile>(async (res) => {
@ -96,16 +96,16 @@ const ok = async () => {
const f = await promise;
emit("ok", f);
dialogEl.close();
dialogEl.value.close();
};
const cancel = () => {
emit("cancel");
dialogEl.close();
dialogEl.value.close();
};
const onImageLoad = () => {
loading = false;
loading.value = false;
if (cropper) {
cropper.getCropperImage()!.$center("contain");
@ -114,7 +114,7 @@ const onImageLoad = () => {
};
onMounted(() => {
cropper = new Cropper(imgEl, {});
cropper = new Cropper(imgEl.value, {});
const computedStyle = getComputedStyle(document.documentElement);

View file

@ -199,7 +199,7 @@
</template>
<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 MkModal from "@/components/MkModal.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 selectedValue = ref(props.select?.default ?? null);
let disabledReason = $ref<null | "charactersExceeded" | "charactersBelow">(
null,
);
const okButtonDisabled = $computed<boolean>(() => {
let disabledReason = ref<null | "charactersExceeded" | "charactersBelow">(null);
const okButtonDisabled = computed<boolean>(() => {
if (props.input) {
if (props.input.minLength) {
if (
(inputValue.value || inputValue.value === "") &&
(inputValue.value as string).length < props.input.minLength
) {
disabledReason = "charactersBelow";
disabledReason.value = "charactersBelow";
return true;
}
}
@ -300,7 +298,7 @@ const okButtonDisabled = $computed<boolean>(() => {
inputValue.value &&
(inputValue.value as string).length > props.input.maxLength
) {
disabledReason = "charactersExceeded";
disabledReason.value = "charactersExceeded";
return true;
}
}

View file

@ -198,10 +198,6 @@ function onDragend() {
emit("dragend");
}
function go() {
emit("move", props.folder.id);
}
function rename() {
os.inputText({
title: i18n.ts.renameFolder,

View file

@ -42,14 +42,6 @@ function onClick() {
emit("move", props.folder);
}
function onMouseover() {
hover.value = true;
}
function onMouseout() {
hover.value = false;
}
function onDragover(ev: DragEvent) {
if (!ev.dataTransfer) return;

View file

@ -69,7 +69,7 @@
@dragend="isDragSource = false"
/>
<!-- 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">{{
i18n.ts.loadMore
}}</MkButton>
@ -94,7 +94,7 @@
@dragend="isDragSource = false"
/>
<!-- 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-show="moreFiles"
ref="loadMoreFiles"
@ -132,7 +132,6 @@
<script lang="ts" setup>
import {
markRaw,
nextTick,
onActivated,
onBeforeUnmount,

View file

@ -31,7 +31,7 @@
</template>
<script lang="ts" setup>
import { ref, onMounted, onBeforeUnmount } from "vue";
import { ref } from "vue";
import MkModal from "@/components/MkModal.vue";
import MkEmojiPicker from "@/components/MkEmojiPicker.vue";
import { defaultStore } from "@/store";

View file

@ -52,13 +52,10 @@
</template>
<script lang="ts" setup>
import { computed } from "vue";
import * as Acct from "firefish-js/built/acct";
import MkSwitch from "@/components/ui/switch.vue";
import MkPagination from "@/components/MkPagination.vue";
import MkDriveFileThumbnail from "@/components/MkDriveFileThumbnail.vue";
import bytes from "@/filters/bytes";
import * as os from "@/os";
import { i18n } from "@/i18n";
const props = defineProps<{

View file

@ -61,7 +61,7 @@
</template>
<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 * as os from "@/os";
import { stream } from "@/stream";
@ -88,13 +88,13 @@ const props = withDefaults(
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 hasPendingFollowRequestFromYou = $ref(
let isFollowing = ref(props.user.isFollowing);
let hasPendingFollowRequestFromYou = ref(
props.user.hasPendingFollowRequestFromYou,
);
let wait = $ref(false);
let wait = ref(false);
const connection = stream.useChannel("main");
if (props.user.isFollowing == null) {
@ -105,13 +105,14 @@ if (props.user.isFollowing == null) {
function onFollowChange(user: Misskey.entities.UserDetailed) {
if (user.id === props.user.id) {
isFollowing = user.isFollowing;
hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou;
isFollowing.value = user.isFollowing;
hasPendingFollowRequestFromYou.value =
user.hasPendingFollowRequestFromYou;
}
}
async function onClick() {
wait = true;
wait.value = true;
try {
if (isBlocking.value) {
@ -130,7 +131,7 @@ async function onClick() {
});
}
emit("refresh");
} else if (isFollowing) {
} else if (isFollowing.value) {
const { canceled } = await os.confirm({
type: "warning",
text: i18n.t("unfollowConfirm", {
@ -144,22 +145,22 @@ async function onClick() {
userId: props.user.id,
});
} else {
if (hasPendingFollowRequestFromYou) {
if (hasPendingFollowRequestFromYou.value) {
await os.api("following/requests/cancel", {
userId: props.user.id,
});
hasPendingFollowRequestFromYou = false;
hasPendingFollowRequestFromYou.value = false;
} else {
await os.api("following/create", {
userId: props.user.id,
});
hasPendingFollowRequestFromYou = true;
hasPendingFollowRequestFromYou.value = true;
}
}
} catch (err) {
console.error(err);
} finally {
wait = false;
wait.value = false;
}
}
@ -235,13 +236,13 @@ onBeforeUnmount(() => {
}
}
&:hover {
//background: mix($primary, #fff, 20);
}
// &:hover {
// background: mix($primary, #fff, 20);
// }
&:active {
//background: mix($primary, #fff, 40);
}
// &:active {
// background: mix($primary, #fff, 40);
// }
&.active {
color: var(--fgOnAccent);

View file

@ -62,6 +62,8 @@
</template>
<script lang="ts" setup>
import { ref } from "vue";
import {} from "vue";
import XModalWindow from "@/components/MkModalWindow.vue";
import MkButton from "@/components/MkButton.vue";
@ -75,20 +77,20 @@ const emit = defineEmits<{
(ev: "closed"): void;
}>();
let dialog: InstanceType<typeof XModalWindow> = $ref();
let dialog: InstanceType<typeof XModalWindow> = ref();
let username = $ref("");
let email = $ref("");
let processing = $ref(false);
let username = ref("");
let email = ref("");
let processing = ref(false);
async function onSubmit() {
processing = true;
processing.value = true;
await os.apiWithDialog("request-reset-password", {
username,
email,
username: username.value,
email: email.value,
});
emit("done");
dialog.close();
dialog.value.close();
}
</script>

View file

@ -15,7 +15,7 @@
</template>
<MkSpacer :margin-min="20" :margin-max="32">
<div class="xkpnjxcv _formRoot">
<div class="_formRoot">
<template
v-for="item in Object.keys(form).filter(
(item) => !form[item].hidden,
@ -221,8 +221,3 @@ export default defineComponent({
},
});
</script>
<style lang="scss" scoped>
.xkpnjxcv {
}
</style>

View file

@ -4,7 +4,6 @@
<script lang="ts">
import { defineComponent, defineAsyncComponent } from "vue";
import * as os from "@/os";
export default defineComponent({
components: {

View file

@ -19,8 +19,6 @@
</template>
<script lang="ts" setup>
import {} from "vue";
import { userName } from "@/filters/user";
import ImgWithBlurhash from "@/components/MkImgWithBlurhash.vue";
const props = defineProps<{

View file

@ -8,21 +8,11 @@
</template>
<script lang="ts" setup>
import {
markRaw,
version as vueVersion,
onMounted,
onBeforeUnmount,
nextTick,
watch,
} from "vue";
import { onMounted, nextTick, watch, shallowRef, ref } from "vue";
import { Chart } from "chart.js";
import tinycolor from "tinycolor2";
import { MatrixController, MatrixElement } from "chartjs-chart-matrix";
import * as os from "@/os";
import { defaultStore } from "@/store";
import { useChartTooltip } from "@/scripts/use-chart-tooltip";
import { chartVLine } from "@/scripts/chart-vline";
import { alpha } from "@/scripts/color";
import { initChart } from "@/scripts/init-chart";
import { $i } from "@/account";
@ -33,11 +23,11 @@ const props = defineProps<{
src: string;
}>();
const rootEl = $shallowRef<HTMLDivElement>(null);
const chartEl = $shallowRef<HTMLCanvasElement>(null);
const rootEl = shallowRef<HTMLDivElement>(null);
const chartEl = shallowRef<HTMLCanvasElement>(null);
const now = new Date();
let chartInstance: Chart = null;
let fetching = $ref(true);
let fetching = ref(true);
const { handler: externalTooltipHandler } = useChartTooltip({
position: "middle",
@ -53,8 +43,8 @@ async function renderChart() {
chartInstance.destroy();
}
const wide = rootEl.offsetWidth > 700;
const narrow = rootEl.offsetWidth < 400;
const wide = rootEl.value.offsetWidth > 700;
const narrow = rootEl.value.offsetWidth < 400;
const weeks = wide ? 50 : narrow ? 10 : 25;
const chartLimit = 7 * weeks;
@ -123,7 +113,7 @@ async function renderChart() {
values = addArrays(raw.diffs.normal, raw.diffs.reply, raw.diffs.renote);
}
fetching = false;
fetching.value = false;
await nextTick();
@ -141,7 +131,7 @@ async function renderChart() {
const marginEachCell = 4;
chartInstance = new Chart(chartEl, {
chartInstance = new Chart(chartEl.value, {
type: "matrix",
data: {
datasets: [
@ -257,7 +247,7 @@ async function renderChart() {
watch(
() => props.src,
() => {
fetching = true;
fetching.value = true;
renderChart();
},
);

View file

@ -26,6 +26,8 @@
</template>
<script lang="ts" setup>
import { ref } from "vue";
import {} from "vue";
import type * as misskey from "firefish-js";
import bytes from "@/filters/bytes";
@ -43,7 +45,7 @@ const emit = defineEmits<{
(ev: "closed"): void;
}>();
const modal = $ref<InstanceType<typeof MkModal>>();
const modal = ref<InstanceType<typeof MkModal>>();
</script>
<style lang="scss" scoped>

View file

@ -24,7 +24,7 @@
</template>
<script lang="ts" setup>
import { onMounted } from "vue";
import { onMounted, ref } from "vue";
import { decodeBlurHash } from "fast-blurhash";
const props = withDefaults(
@ -48,20 +48,20 @@ const props = withDefaults(
},
);
const canvas = $ref<HTMLCanvasElement>();
let loaded = $ref(false);
const canvas = ref<HTMLCanvasElement>();
let loaded = ref(false);
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 ctx = canvas.getContext("2d");
const ctx = canvas.value.getContext("2d");
const imageData = ctx!.createImageData(props.size, props.size);
imageData.data.set(pixels);
ctx!.putImageData(imageData, 0, 0);
}
function onLoad() {
loaded = true;
loaded.value = true;
}
onMounted(() => {

View file

@ -24,6 +24,8 @@
</template>
<script lang="ts" setup>
import { ref } from "vue";
import * as firefish from "firefish-js";
import MkMiniChart from "@/components/MkMiniChart.vue";
import * as os from "@/os";
@ -33,7 +35,7 @@ const props = defineProps<{
instance: firefish.entities.Instance;
}>();
let chartValues = $ref<number[] | null>(null);
let chartValues = ref<number[] | null>(null);
os.apiGet("charts/instance", {
host: props.instance.host,
@ -42,7 +44,7 @@ os.apiGet("charts/instance", {
}).then((res) => {
//
res.requests.received.splice(0, 1);
chartValues = res.requests.received;
chartValues.value = res.requests.received;
});
function getInstanceIcon(instance): string {

View file

@ -56,6 +56,8 @@
</template>
<script lang="ts" setup>
import { ref } from "vue";
import MkInput from "@/components/form/input.vue";
import XModalWindow from "@/components/MkModalWindow.vue";
import * as os from "@/os";
@ -68,28 +70,28 @@ const emit = defineEmits<{
(ev: "closed"): void;
}>();
let hostname = $ref("");
let instances: Instance[] = $ref([]);
let selected: Instance | null = $ref(null);
let dialogEl = $ref<InstanceType<typeof XModalWindow>>();
let hostname = ref("");
let instances: Instance[] = ref([]);
let selected: Instance | null = ref(null);
let dialogEl = ref<InstanceType<typeof XModalWindow>>();
let searchOrderLatch = 0;
const search = () => {
if (hostname === "") {
instances = [];
if (hostname.value === "") {
instances.value = [];
return;
}
const searchId = ++searchOrderLatch;
os.api("federation/instances", {
host: hostname,
host: hostname.value,
limit: 10,
blocked: false,
suspended: false,
sort: "+pubSub",
}).then((_instances) => {
if (searchId !== searchOrderLatch) return;
instances = _instances.map(
instances.value = _instances.map(
(x) =>
({
id: x.id,
@ -101,14 +103,14 @@ const search = () => {
};
const ok = () => {
if (selected == null) return;
emit("ok", selected);
dialogEl?.close();
if (selected.value == null) return;
emit("ok", selected.value);
dialogEl.value?.close();
};
const cancel = () => {
emit("cancel");
dialogEl?.close();
dialogEl.value?.close();
};
</script>

View file

@ -102,7 +102,7 @@
</template>
<script lang="ts" setup>
import { onMounted } from "vue";
import { onMounted, ref, shallowRef } from "vue";
import { Chart } from "chart.js";
import MkSelect from "@/components/form/select.vue";
import MkChart from "@/components/MkChart.vue";
@ -116,11 +116,11 @@ import { initChart } from "@/scripts/init-chart";
initChart();
const chartLimit = 500;
let chartSpan = $ref<"hour" | "day">("hour");
let chartSrc = $ref("active-users");
let heatmapSrc = $ref("active-users");
let subDoughnutEl = $shallowRef<HTMLCanvasElement>();
let pubDoughnutEl = $shallowRef<HTMLCanvasElement>();
let chartSpan = ref<"hour" | "day">("hour");
let chartSrc = ref("active-users");
let heatmapSrc = ref("active-users");
let subDoughnutEl = shallowRef<HTMLCanvasElement>();
let pubDoughnutEl = shallowRef<HTMLCanvasElement>();
const { handler: externalTooltipHandler1 } = useChartTooltip({
position: "middle",
@ -189,7 +189,7 @@ function createDoughnut(chartEl, tooltip, data) {
onMounted(() => {
os.apiGet("federation/stats", { limit: 30 }).then((fedStats) => {
createDoughnut(
subDoughnutEl,
subDoughnutEl.value,
externalTooltipHandler1,
fedStats.topSubInstances
.map((x) => ({
@ -210,7 +210,7 @@ onMounted(() => {
);
createDoughnut(
pubDoughnutEl,
pubDoughnutEl.value,
externalTooltipHandler2,
fedStats.topPubInstances
.map((x) => ({

View file

@ -11,6 +11,8 @@
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { instanceName } from "@/config";
import { instance as Instance } from "@/instance";
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
const instance = props.instance ?? {

View file

@ -62,14 +62,13 @@
</template>
<script lang="ts" setup>
import {} from "vue";
import { ref } from "vue";
import MkModal from "@/components/MkModal.vue";
import { navbarItemDef } from "@/navbar";
import { instanceName } from "@/config";
import { defaultStore } from "@/store";
import { i18n } from "@/i18n";
import { deviceKind } from "@/scripts/device-kind";
import * as os from "@/os";
const props = withDefaults(
defineProps<{
@ -92,7 +91,7 @@ const preferedModalType =
? "drawer"
: "dialog";
const modal = $ref<InstanceType<typeof MkModal>>();
const modal = ref<InstanceType<typeof MkModal>>();
const menu = defaultStore.state.menu;
@ -110,7 +109,7 @@ const items = Object.keys(navbarItemDef)
}));
function close() {
modal.close();
modal.value.close();
}
</script>

View file

@ -18,7 +18,7 @@
</template>
<script lang="ts" setup>
import { defineAsyncComponent } from "vue";
import { defineAsyncComponent, ref } from "vue";
import { url as local } from "@/config";
import { useTooltip } from "@/scripts/use-tooltip";
import * as os from "@/os";
@ -35,9 +35,9 @@ const self = props.url.startsWith(local);
const attr = self ? "to" : "href";
const target = self ? null : "_blank";
const el = $ref();
const el = ref();
useTooltip($$(el), (showing) => {
useTooltip(el, (showing) => {
os.popup(
defineAsyncComponent(
() => import("@/components/MkUrlPreviewPopup.vue"),
@ -45,7 +45,7 @@ useTooltip($$(el), (showing) => {
{
showing,
url: props.url,
source: el,
source: el.value,
},
{},
"closed",

View file

@ -20,7 +20,6 @@ import { shallowRef } from "vue";
import MkModal from "@/components/MkModal.vue";
import MkButton from "@/components/MkButton.vue";
import { i18n } from "@/i18n";
import * as os from "@/os";
const modal = shallowRef<InstanceType<typeof MkModal>>();
const checkAnnouncements = () => {

View file

@ -104,7 +104,7 @@ const props = defineProps<{
raw?: boolean;
}>();
let hide = $ref(true);
let hide = ref(true);
const plyr = ref();
@ -145,7 +145,7 @@ function captionPopup() {
watch(
() => props.media,
() => {
hide =
hide.value =
defaultStore.state.nsfw === "force"
? true
: props.media.isSensitive &&

View file

@ -56,7 +56,7 @@
</template>
<script lang="ts" setup>
import { onMounted } from "vue";
import { onMounted, ref } from "vue";
import VuePlyr from "vue-plyr";
import type * as misskey from "firefish-js";
import { ColdDeviceStorage } from "@/store";
@ -70,15 +70,17 @@ const props = withDefaults(
{},
);
const audioEl = $ref<HTMLAudioElement | null>();
let hide = $ref(true);
const audioEl = ref<HTMLAudioElement | null>();
let hide = ref(true);
function volumechange() {
if (audioEl) ColdDeviceStorage.set("mediaVolume", audioEl.volume);
if (audioEl.value)
ColdDeviceStorage.set("mediaVolume", audioEl.value.volume);
}
onMounted(() => {
if (audioEl) audioEl.volume = ColdDeviceStorage.get("mediaVolume");
if (audioEl.value)
audioEl.value.volume = ColdDeviceStorage.get("mediaVolume");
});
</script>

View file

@ -37,7 +37,6 @@ import XBanner from "@/components/MkMediaBanner.vue";
import XMedia from "@/components/MkMedia.vue";
import * as os from "@/os";
import { FILE_TYPE_BROWSERSAFE } from "@/const";
import { defaultStore } from "@/store";
const props = defineProps<{
mediaList: misskey.entities.DriveFile[];

View file

@ -12,15 +12,7 @@
</template>
<script lang="ts" setup>
import { on } from "events";
import {
nextTick,
onBeforeUnmount,
onMounted,
onUnmounted,
ref,
watch,
} from "vue";
import { nextTick, onMounted, ref } from "vue";
import MkMenu from "./MkMenu.vue";
import { MenuItem } from "@/types/menu";

View file

@ -17,7 +17,7 @@
@contextmenu.self="(e) => e.preventDefault()"
tabindex="-1"
>
<template v-for="(item, i) in items2">
<template v-for="item in items2">
<div v-if="item === null" class="divider"></div>
<span v-else-if="item.type === 'label'" class="label item">
<span :style="item.textStyle || ''">{{
@ -204,18 +204,12 @@
<script lang="ts" setup>
import {
computed,
menu,
defineAsyncComponent,
nextTick,
onBeforeUnmount,
onMounted,
onUnmounted,
Ref,
ref,
watch,
} from "vue";
import { focusPrev, focusNext } from "@/scripts/focus";
import FormSwitch from "@/components/form/switch.vue";
import { MenuItem, InnerMenuItem, MenuPending, MenuAction } from "@/types/menu";
import * as os from "@/os";
@ -239,13 +233,13 @@ const emit = defineEmits<{
(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(
() => props.items,
@ -261,24 +255,24 @@ watch(
// if item is Promise
items[i] = { type: "pending" };
item.then((actualItem) => {
items2[i] = actualItem;
items2.value[i] = actualItem;
});
}
}
items2 = items as InnerMenuItem[];
items2.value = items as InnerMenuItem[];
},
{
immediate: true,
},
);
let childMenu = $ref<MenuItem[] | null>();
let childTarget = $ref<HTMLElement | null>();
let childMenu = ref<MenuItem[] | null>();
let childTarget = ref<HTMLElement | null>();
function closeChild() {
childMenu = null;
childShowingItem = null;
childMenu.value = null;
childShowingItem.value = null;
}
function childActioned() {
@ -288,11 +282,12 @@ function childActioned() {
function onGlobalMousedown(event: MouseEvent) {
if (
childTarget &&
(event.target === childTarget || childTarget.contains(event.target))
childTarget.value &&
(event.target === childTarget.value ||
childTarget.value.contains(event.target))
)
return;
if (child && child.checkHit(event)) return;
if (child.value && child.value.checkHit(event)) return;
closeChild();
}
@ -311,9 +306,9 @@ async function showChildren(item: MenuItem, ev: MouseEvent) {
os.popupMenu(item.children, ev.currentTarget ?? ev.target);
close();
} else {
childTarget = ev.currentTarget ?? ev.target;
childMenu = item.children;
childShowingItem = item;
childTarget.value = ev.currentTarget ?? ev.target;
childMenu.value = item.children;
childShowingItem.value = item;
}
}

View file

@ -25,7 +25,7 @@
</template>
<script lang="ts" setup>
import { onUnmounted, watch } from "vue";
import { watch, ref } from "vue";
import { v4 as uuid } from "uuid";
import tinycolor from "tinycolor2";
import { useInterval } from "@/scripts/use-interval";
@ -37,11 +37,10 @@ const props = defineProps<{
const viewBoxX = 50;
const viewBoxY = 50;
const gradientId = uuid();
let polylinePoints = $ref("");
let polygonPoints = $ref("");
let headX = $ref<number | null>(null);
let headY = $ref<number | null>(null);
let clock = $ref<number | null>(null);
let polylinePoints = ref("");
let polygonPoints = ref("");
let headX = ref<number | null>(null);
let headY = ref<number | null>(null);
const accent = tinycolor(
getComputedStyle(document.documentElement).getPropertyValue("--accent"),
);
@ -56,12 +55,14 @@ function draw(): void {
(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];
headY = _polylinePoints[_polylinePoints.length - 1][1];
headX.value = _polylinePoints[_polylinePoints.length - 1][0];
headY.value = _polylinePoints[_polylinePoints.length - 1][1];
}
watch(() => props.src, draw, { immediate: true });

View file

@ -77,7 +77,16 @@
</template>
<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 { isTouchUsing } from "@/scripts/touch";
import { defaultStore } from "@/store";
@ -130,14 +139,14 @@ const emit = defineEmits<{
provide("modal", true);
let maxHeight = $ref<number>();
let fixed = $ref(false);
let transformOrigin = $ref("center");
let showing = $ref(true);
let content = $shallowRef<HTMLElement>();
let maxHeight = ref<number>();
let fixed = ref(false);
let transformOrigin = ref("center");
let showing = ref(true);
let content = shallowRef<HTMLElement>();
const zIndex = os.claimZIndex(props.zPriority);
let useSendAnime = $ref(false);
const type = $computed<ModalTypes>(() => {
let useSendAnime = ref(false);
const type = computed<ModalTypes>(() => {
if (props.preferType === "auto") {
if (
!defaultStore.state.disableDrawer &&
@ -152,28 +161,28 @@ const type = $computed<ModalTypes>(() => {
return props.preferType!;
}
});
const isEnableBgTransparent = $computed(
() => props.transparentBg && type === "popup",
const isEnableBgTransparent = computed(
() => props.transparentBg && type.value === "popup",
);
let transitionName = $computed(() =>
let transitionName = computed(() =>
defaultStore.state.animation
? useSendAnime
? useSendAnime.value
? "send"
: type === "drawer"
: type.value === "drawer"
? "modal-drawer"
: type === "popup"
: type.value === "popup"
? "modal-popup"
: "modal"
: "",
);
let transitionDuration = $computed(() =>
transitionName === "send"
let transitionDuration = computed(() =>
transitionName.value === "send"
? 400
: transitionName === "modal-popup"
: transitionName.value === "modal-popup"
? 100
: transitionName === "modal"
: transitionName.value === "modal"
? 200
: transitionName === "modal-drawer"
: transitionName.value === "modal-drawer"
? 200
: 0,
);
@ -187,12 +196,12 @@ function close(ev, opts: { useSendAnimation?: boolean } = {}) {
// history.forward();
// }
if (opts.useSendAnimation) {
useSendAnime = true;
useSendAnime.value = true;
}
// eslint-disable-next-line vue/no-mutating-props
if (props.src) props.src.style.pointerEvents = "auto";
showing = false;
showing.value = false;
emit("close");
if (!props.noReturnFocus) {
focusedElement.focus();
@ -204,8 +213,8 @@ function onBgClick() {
emit("click");
}
if (type === "drawer") {
maxHeight = window.innerHeight / 1.5;
if (type.value === "drawer") {
maxHeight.value = window.innerHeight / 1.5;
}
const keymap = {
@ -216,21 +225,21 @@ const MARGIN = 16;
const align = () => {
if (props.src == null) return;
if (type === "drawer") return;
if (type === "dialog") return;
if (type.value === "drawer") return;
if (type.value === "dialog") return;
if (content == null) return;
if (content.value == null) return;
const srcRect = props.src.getBoundingClientRect();
const width = content!.offsetWidth;
const height = content!.offsetHeight;
const width = content.value!.offsetWidth;
const height = content.value!.offsetHeight;
let left;
let top;
const x = srcRect.left + (fixed ? 0 : window.pageXOffset);
const y = srcRect.top + (fixed ? 0 : window.pageYOffset);
const x = srcRect.left + (fixed.value ? 0 : window.pageXOffset);
const y = srcRect.top + (fixed.value ? 0 : window.pageYOffset);
if (props.anchor.x === "center") {
left = x + props.src.offsetWidth / 2 - width / 2;
@ -248,7 +257,7 @@ const align = () => {
top = y + props.src.offsetHeight;
}
if (fixed) {
if (fixed.value) {
//
if (left + width > window.innerWidth) {
left = window.innerWidth - width;
@ -261,16 +270,16 @@ const align = () => {
if (top + height > window.innerHeight - MARGIN) {
if (props.noOverlap && props.anchor.x === "center") {
if (underSpace >= upperSpace / 3) {
maxHeight = underSpace;
maxHeight.value = underSpace;
} else {
maxHeight = upperSpace;
maxHeight.value = upperSpace;
top = upperSpace + MARGIN - height;
}
} else {
top = window.innerHeight - MARGIN - height;
}
} else {
maxHeight = underSpace;
maxHeight.value = underSpace;
}
} else {
//
@ -286,9 +295,9 @@ const align = () => {
if (top + height - window.scrollY > window.innerHeight - MARGIN) {
if (props.noOverlap && props.anchor.x === "center") {
if (underSpace >= upperSpace / 3) {
maxHeight = underSpace;
maxHeight.value = underSpace;
} else {
maxHeight = upperSpace;
maxHeight.value = upperSpace;
top = window.scrollY + (upperSpace + MARGIN - height);
}
} else {
@ -300,7 +309,7 @@ const align = () => {
1;
}
} else {
maxHeight = underSpace;
maxHeight.value = underSpace;
}
}
@ -317,36 +326,43 @@ const align = () => {
if (
top >=
srcRect.top + props.src.offsetHeight + (fixed ? 0 : window.pageYOffset)
srcRect.top +
props.src.offsetHeight +
(fixed.value ? 0 : window.pageYOffset)
) {
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";
}
if (
left >=
srcRect.left + props.src.offsetWidth + (fixed ? 0 : window.pageXOffset)
srcRect.left +
props.src.offsetWidth +
(fixed.value ? 0 : window.pageXOffset)
) {
transformOriginX = "left";
} else if (
left + width <=
srcRect.left + (fixed ? 0 : window.pageXOffset)
srcRect.left + (fixed.value ? 0 : window.pageXOffset)
) {
transformOriginX = "right";
}
transformOrigin = `${transformOriginX} ${transformOriginY}`;
transformOrigin.value = `${transformOriginX} ${transformOriginY}`;
content.style.left = left + "px";
content.style.top = top + "px";
content.value.style.left = left + "px";
content.value.style.top = top + "px";
};
const onOpened = () => {
emit("opened");
//
const el = content!.children[0];
const el = content.value!.children[0];
el.addEventListener(
"mousedown",
(ev) => {
@ -378,7 +394,8 @@ onMounted(() => {
// eslint-disable-next-line vue/no-mutating-props
props.src.style.pointerEvents = "none";
}
fixed = type === "drawer" || getFixedContainer(props.src) != null;
fixed.value =
type.value === "drawer" || getFixedContainer(props.src) != null;
await nextTick();
@ -390,7 +407,7 @@ onMounted(() => {
nextTick(() => {
new ResizeObserver((entries, observer) => {
align();
}).observe(content!);
}).observe(content.value!);
});
});
onUnmounted(() => {

View file

@ -52,7 +52,7 @@
</template>
<script lang="ts" setup>
import { ComputedRef, provide } from "vue";
import { ComputedRef, provide, ref, computed } from "vue";
import MkModal from "@/components/MkModal.vue";
import { popout as _popout } from "@/scripts/popout";
import copyToClipboard from "@/scripts/copy-to-clipboard";
@ -60,11 +60,7 @@ import { url } from "@/config";
import * as os from "@/os";
import { mainRouter, routes } from "@/router";
import { i18n } from "@/i18n";
import {
PageMetadata,
provideMetadataReceiver,
setPageMetadata,
} from "@/scripts/page-metadata";
import { PageMetadata, provideMetadataReceiver } from "@/scripts/page-metadata";
import { Router } from "@/nirax";
const props = defineProps<{
@ -80,27 +76,27 @@ const router = new Router(routes, props.initialPath);
router.addListener("push", (ctx) => {});
let pageMetadata = $ref<null | ComputedRef<PageMetadata>>();
let rootEl = $ref();
let modal = $ref<InstanceType<typeof MkModal>>();
let path = $ref(props.initialPath);
let width = $ref(860);
let height = $ref(660);
let pageMetadata = ref<null | ComputedRef<PageMetadata>>();
let rootEl = ref();
let modal = ref<InstanceType<typeof MkModal>>();
let path = ref(props.initialPath);
let width = ref(860);
let height = ref(660);
const history = [];
provide("router", router);
provideMetadataReceiver((info) => {
pageMetadata = info;
pageMetadata.value = info;
});
provide("shouldOmitHeaderTitle", true);
provide("shouldHeaderThin", true);
const pageUrl = $computed(() => url + path);
const contextmenu = $computed(() => {
const pageUrl = computed(() => url + path.value);
const contextmenu = computed(() => {
return [
{
type: "label",
text: path,
text: path.value,
},
{
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",
text: i18n.ts.openInNewTab,
action: () => {
window.open(pageUrl, "_blank");
modal.close();
window.open(pageUrl.value, "_blank");
modal.value.close();
},
},
{
icon: "ph-link-simple ph-bold ph-lg",
text: i18n.ts.copyLink,
action: () => {
copyToClipboard(pageUrl);
copyToClipboard(pageUrl.value);
},
},
];
@ -141,17 +137,17 @@ function back() {
}
function expand() {
mainRouter.push(path);
modal.close();
mainRouter.push(path.value);
modal.value.close();
}
function popout() {
_popout(path, rootEl);
modal.close();
_popout(path.value, rootEl.value);
modal.value.close();
}
function onContextmenu(ev: MouseEvent) {
os.contextMenu(contextmenu, ev);
os.contextMenu(contextmenu.value, ev);
}
</script>

View file

@ -62,6 +62,8 @@
</template>
<script lang="ts" setup>
import { shallowRef } from "vue";
import { FocusTrap } from "focus-trap-vue";
import MkModal from "./MkModal.vue";
import { i18n } from "@/i18n";
@ -90,12 +92,12 @@ const emit = defineEmits<{
(event: "ok"): void;
}>();
let modal = $shallowRef<InstanceType<typeof MkModal>>();
let rootEl = $shallowRef<HTMLElement>();
let headerEl = $shallowRef<HTMLElement>();
let modal = shallowRef<InstanceType<typeof MkModal>>();
let rootEl = shallowRef<HTMLElement>();
let headerEl = shallowRef<HTMLElement>();
const close = (ev) => {
modal?.close(ev);
modal.value?.close(ev);
};
const onBgClick = () => {

View file

@ -255,23 +255,18 @@
</template>
<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 type { Ref } from "vue";
import type * as misskey from "firefish-js";
import MkNoteSub from "@/components/MkNoteSub.vue";
import MkSubNoteContent from "./MkSubNoteContent.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 XReactionsViewer from "@/components/MkReactionsViewer.vue";
import XStarButton from "@/components/MkStarButton.vue";
import XStarButtonNoEmoji from "@/components/MkStarButtonNoEmoji.vue";
import XQuoteButton from "@/components/MkQuoteButton.vue";
import MkUrlPreview from "@/components/MkUrlPreview.vue";
import MkVisibility from "@/components/MkVisibility.vue";
import copyToClipboard from "@/scripts/copy-to-clipboard";
import { url } from "@/config";
@ -302,7 +297,7 @@ const props = defineProps<{
const inChannel = inject("inChannel", null);
let note = $ref(deepClone(props.note));
let note = ref(deepClone(props.note));
const softMuteReasonI18nSrc = (what?: string) => {
if (what === "note") return i18n.ts.userSaysSomethingReason;
@ -317,19 +312,19 @@ const softMuteReasonI18nSrc = (what?: string) => {
// plugin
if (noteViewInterruptors.length > 0) {
onMounted(async () => {
let result = deepClone(note);
let result = deepClone(note.value);
for (const interruptor of noteViewInterruptors) {
result = await interruptor.handler(result);
}
note = result;
note.value = result;
});
}
const isRenote =
note.renote != null &&
note.text == null &&
note.fileIds.length === 0 &&
note.poll == null;
note.value.renote != null &&
note.value.text == null &&
note.value.fileIds.length === 0 &&
note.value.poll == null;
const el = ref<HTMLElement>();
const footerEl = ref<HTMLElement>();
@ -338,13 +333,15 @@ const starButton = ref<InstanceType<typeof XStarButton>>();
const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
const renoteTime = ref<HTMLElement>();
const reactButton = ref<HTMLElement>();
let appearNote = $computed(() =>
isRenote ? (note.renote as misskey.entities.Note) : note,
let appearNote = computed(() =>
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 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 translating = ref(false);
const enableEmojiReactions = defaultStore.state.enableEmojiReactions;
@ -363,7 +360,7 @@ const keymap = {
useNoteCapture({
rootEl: el,
note: $$(appearNote),
note: appearNote,
isDeletedRef: isDeleted,
});
@ -371,7 +368,7 @@ function reply(viaKeyboard = false): void {
pleaseLogin();
os.post(
{
reply: appearNote,
reply: appearNote.value,
animation: !viaKeyboard,
},
() => {
@ -387,7 +384,7 @@ function react(viaKeyboard = false): void {
reactButton.value,
(reaction) => {
os.api("notes/reactions/create", {
noteId: appearNote.id,
noteId: appearNote.value.id,
reaction: reaction,
});
},
@ -430,21 +427,24 @@ function onContextmenu(ev: MouseEvent): void {
[
{
type: "label",
text: notePage(appearNote),
text: notePage(appearNote.value),
},
{
icon: "ph-browser ph-bold ph-lg",
text: i18n.ts.openInWindow,
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",
text: i18n.ts.showInPage,
action: () => {
router.push(notePage(appearNote), "forcePage");
router.push(
notePage(appearNote.value),
"forcePage",
);
},
}
: undefined,
@ -453,22 +453,25 @@ function onContextmenu(ev: MouseEvent): void {
type: "a",
icon: "ph-arrow-square-out ph-bold ph-lg",
text: i18n.ts.openInNewTab,
href: notePage(appearNote),
href: notePage(appearNote.value),
target: "_blank",
},
{
icon: "ph-link-simple ph-bold ph-lg",
text: i18n.ts.copyLink,
action: () => {
copyToClipboard(`${url}${notePage(appearNote)}`);
copyToClipboard(`${url}${notePage(appearNote.value)}`);
},
},
appearNote.user.host != null
appearNote.value.user.host != null
? {
type: "a",
icon: "ph-arrow-square-up-right ph-bold ph-lg",
text: i18n.ts.showOnRemote,
href: appearNote.url ?? appearNote.uri ?? "",
href:
appearNote.value.url ??
appearNote.value.uri ??
"",
target: "_blank",
}
: undefined,
@ -481,7 +484,7 @@ function onContextmenu(ev: MouseEvent): void {
function menu(viaKeyboard = false): void {
os.popupMenu(
getNoteMenu({
note: note,
note: note.value,
translating,
translation,
menuButton,
@ -505,7 +508,7 @@ function showRenoteMenu(viaKeyboard = false): void {
danger: true,
action: () => {
os.api("notes/delete", {
noteId: note.id,
noteId: note.value.id,
});
isDeleted.value = true;
},
@ -546,13 +549,13 @@ function noteClick(e) {
) {
e.stopPropagation();
} else {
router.push(notePage(appearNote));
router.push(notePage(appearNote.value));
}
}
function readPromo() {
os.api("promo/read", {
noteId: appearNote.id,
noteId: appearNote.value.id,
});
isDeleted.value = true;
}
@ -564,28 +567,30 @@ function setPostExpanded(val: boolean) {
}
const accessibleLabel = computed(() => {
let label = `${appearNote.user.username}; `;
if (appearNote.renote) {
label += `${i18n.t("renoted")} ${appearNote.renote.user.username}; `;
if (appearNote.renote.cw) {
label += `${i18n.t("cw")}: ${appearNote.renote.cw}; `;
let label = `${appearNote.value.user.username}; `;
if (appearNote.value.renote) {
label += `${i18n.t("renoted")} ${
appearNote.value.renote.user.username
}; `;
if (appearNote.value.renote.cw) {
label += `${i18n.t("cw")}: ${appearNote.value.renote.cw}; `;
if (postIsExpanded.value) {
label += `${appearNote.renote.text}; `;
label += `${appearNote.value.renote.text}; `;
}
} else {
label += `${appearNote.renote.text}; `;
label += `${appearNote.value.renote.text}; `;
}
} else {
if (appearNote.cw) {
label += `${i18n.t("cw")}: ${appearNote.cw}; `;
if (appearNote.value.cw) {
label += `${i18n.t("cw")}: ${appearNote.value.cw}; `;
if (postIsExpanded.value) {
label += `${appearNote.text}; `;
label += `${appearNote.value.text}; `;
}
} else {
label += `${appearNote.text}; `;
label += `${appearNote.value.text}; `;
}
}
const date = new Date(appearNote.createdAt);
const date = new Date(appearNote.value.createdAt);
label += `${date.toLocaleTimeString()}`;
return label;
});

View file

@ -150,22 +150,12 @@
</template>
<script lang="ts" setup>
import {
computed,
inject,
onMounted,
onUnmounted,
onUpdated,
reactive,
ref,
} from "vue";
import { onMounted, onUnmounted, onUpdated, ref } from "vue";
import * as misskey from "firefish-js";
import MkTab from "@/components/MkTab.vue";
import MkNote from "@/components/MkNote.vue";
import MkNoteSub from "@/components/MkNoteSub.vue";
import XStarButton from "@/components/MkStarButton.vue";
import XRenoteButton from "@/components/MkRenoteButton.vue";
import MkPagination from "@/components/MkPagination.vue";
import MkUserCardMini from "@/components/MkUserCardMini.vue";
import MkReactedUsers from "@/components/MkReactedUsers.vue";
import { pleaseLogin } from "@/scripts/please-login";
@ -181,16 +171,15 @@ import { useNoteCapture } from "@/scripts/use-note-capture";
import { deepClone } from "@/scripts/clone";
import { stream } from "@/stream";
import { NoteUpdatedEvent } from "firefish-js/built/streaming.types";
import appear from "@/directives/appear";
const props = defineProps<{
note: misskey.entities.Note;
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) => {
if (what === "note") return i18n.ts.userSaysSomethingReason;
@ -205,30 +194,32 @@ const softMuteReasonI18nSrc = (what?: string) => {
// plugin
if (noteViewInterruptors.length > 0) {
onMounted(async () => {
let result = deepClone(note);
let result = deepClone(note.value);
for (const interruptor of noteViewInterruptors) {
result = await interruptor.handler(result);
}
note = result;
note.value = result;
});
}
const el = ref<HTMLElement>();
const noteEl = $ref();
const noteEl = ref();
const menuButton = ref<HTMLElement>();
const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
const reactButton = ref<HTMLElement>();
const showContent = 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 translating = ref(false);
let conversation = $ref<null | misskey.entities.Note[]>([]);
let conversation = ref<null | misskey.entities.Note[]>([]);
const replies = ref<misskey.entities.Note[]>([]);
let directReplies = $ref<null | misskey.entities.Note[]>([]);
let directQuotes = $ref<null | misskey.entities.Note[]>([]);
let clips = $ref();
let renotes = $ref();
let directReplies = ref<null | misskey.entities.Note[]>([]);
let directQuotes = ref<null | misskey.entities.Note[]>([]);
let clips = ref();
let renotes = ref();
let isScrolling;
const reactionsCount = Object.values(props.note.reactions).reduce(
@ -247,14 +238,14 @@ const keymap = {
useNoteCapture({
rootEl: el,
note: $$(note),
note: note,
isDeletedRef: isDeleted,
});
function reply(viaKeyboard = false): void {
pleaseLogin();
os.post({
reply: note,
reply: note.value,
animation: !viaKeyboard,
}).then(() => {
focus();
@ -268,7 +259,7 @@ function react(viaKeyboard = false): void {
reactButton.value,
(reaction) => {
os.api("notes/reactions/create", {
noteId: note.id,
noteId: note.value.id,
reaction: reaction,
});
},
@ -302,7 +293,7 @@ function onContextmenu(ev: MouseEvent): void {
} else {
os.contextMenu(
getNoteMenu({
note: note,
note: note.value,
translating,
translation,
menuButton,
@ -316,7 +307,7 @@ function onContextmenu(ev: MouseEvent): void {
function menu(viaKeyboard = false): void {
os.popupMenu(
getNoteMenu({
note: note,
note: note.value,
translating,
translation,
menuButton,
@ -330,48 +321,50 @@ function menu(viaKeyboard = false): void {
}
function focus() {
noteEl.focus();
noteEl.value.focus();
}
function blur() {
noteEl.blur();
noteEl.value.blur();
}
directReplies = null;
directReplies.value = null;
os.api("notes/children", {
noteId: note.id,
noteId: note.value.id,
limit: 30,
depth: 12,
}).then((res) => {
res = res.reduce((acc, resNote) => {
if (resNote.userId == note.userId) {
if (resNote.userId == note.value.userId) {
return [...acc, resNote];
}
return [resNote, ...acc];
}, []);
replies.value = res;
directReplies = res
.filter((resNote) => resNote.replyId === note.id)
directReplies.value = res
.filter((resNote) => resNote.replyId === note.value.id)
.reverse();
directQuotes = res.filter((resNote) => resNote.renoteId === note.id);
directQuotes.value = res.filter(
(resNote) => resNote.renoteId === note.value.id,
);
});
conversation = null;
if (note.replyId) {
conversation.value = null;
if (note.value.replyId) {
os.api("notes/conversation", {
noteId: note.replyId,
noteId: note.value.replyId,
limit: 30,
}).then((res) => {
conversation = res.reverse();
conversation.value = res.reverse();
focus();
});
}
clips = null;
clips.value = null;
os.api("notes/clips", {
noteId: note.id,
noteId: note.value.id,
}).then((res) => {
clips = res;
clips.value = res;
});
// const pagination = {
@ -382,14 +375,14 @@ os.api("notes/clips", {
// const pagingComponent = $ref<InstanceType<typeof MkPagination>>();
renotes = null;
renotes.value = null;
function loadTab() {
if (tab === "renotes" && !renotes) {
if (tab.value === "renotes" && !renotes.value) {
os.api("notes/renotes", {
noteId: note.id,
noteId: note.value.id,
limit: 100,
}).then((res) => {
renotes = res;
renotes.value = res;
});
}
}
@ -398,7 +391,7 @@ async function onNoteUpdated(noteData: NoteUpdatedEvent): Promise<void> {
const { type, id, body } = noteData;
let found = -1;
if (id === note.id) {
if (id === note.value.id) {
found = 0;
} else {
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);
if (found === 0) {
directReplies.push(replyNote);
directReplies.value.push(replyNote);
}
break;
@ -444,12 +437,12 @@ document.addEventListener("wheel", () => {
onMounted(() => {
stream.on("noteUpdated", onNoteUpdated);
isScrolling = false;
noteEl.scrollIntoView();
noteEl.value.scrollIntoView();
});
onUpdated(() => {
if (!isScrolling) {
noteEl.scrollIntoView();
noteEl.value.scrollIntoView();
if (location.hash) {
location.replace(location.hash); // Jump to highlighted reply
}

View file

@ -47,9 +47,11 @@
</template>
<script lang="ts" setup>
import { ref } from "vue";
import {} from "vue";
import type * as misskey from "firefish-js";
import { defaultStore, noteViewInterruptors } from "@/store";
import { defaultStore } from "@/store";
import MkVisibility from "@/components/MkVisibility.vue";
import MkInstanceTicker from "@/components/MkInstanceTicker.vue";
import { notePage } from "@/filters/note";
@ -61,11 +63,12 @@ const props = defineProps<{
pinned?: boolean;
}>();
let note = $ref(props.note);
let note = ref(props.note);
const showTicker =
defaultStore.state.instanceTicker === "always" ||
(defaultStore.state.instanceTicker === "remote" && note.user.instance);
(defaultStore.state.instanceTicker === "remote" &&
note.value.user.instance);
</script>
<style lang="scss" scoped>

View file

@ -11,7 +11,6 @@
</template>
<script lang="ts" setup>
import {} from "vue";
import * as misskey from "firefish-js";
import XNoteHeader from "@/components/MkNoteHeader.vue";
import MkSubNoteContent from "@/components/MkSubNoteContent.vue";

View file

@ -177,7 +177,7 @@
</template>
<script lang="ts" setup>
import { inject, ref } from "vue";
import { inject, ref, computed } from "vue";
import type { Ref } from "vue";
import * as misskey from "firefish-js";
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) => {
if (what === "note") return i18n.ts.userSaysSomethingReason;
@ -236,10 +236,10 @@ const softMuteReasonI18nSrc = (what?: string) => {
};
const isRenote =
note.renote != null &&
note.text == null &&
note.fileIds.length === 0 &&
note.poll == null;
note.value.renote != null &&
note.value.text == null &&
note.value.fileIds.length === 0 &&
note.value.poll == null;
const el = ref<HTMLElement>();
const footerEl = ref<HTMLElement>();
@ -247,11 +247,13 @@ const menuButton = ref<HTMLElement>();
const starButton = ref<InstanceType<typeof XStarButton>>();
const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
const reactButton = ref<HTMLElement>();
let appearNote = $computed(() =>
isRenote ? (note.renote as misskey.entities.Note) : note,
let appearNote = computed(() =>
isRenote ? (note.value.renote as misskey.entities.Note) : note.value,
);
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 translating = ref(false);
const replies: misskey.entities.Note[] =
@ -267,14 +269,14 @@ const expandOnNoteClick = defaultStore.state.expandOnNoteClick;
useNoteCapture({
rootEl: el,
note: $$(appearNote),
note: appearNote,
isDeletedRef: isDeleted,
});
function reply(viaKeyboard = false): void {
pleaseLogin();
os.post({
reply: appearNote,
reply: appearNote.value,
animation: !viaKeyboard,
}).then(() => {
focus();
@ -288,7 +290,7 @@ function react(viaKeyboard = false): void {
reactButton.value,
(reaction) => {
os.api("notes/reactions/create", {
noteId: appearNote.id,
noteId: appearNote.value.id,
reaction: reaction,
});
},
@ -314,7 +316,7 @@ const currentClipPage = inject<Ref<misskey.entities.Clip> | null>(
function menu(viaKeyboard = false): void {
os.popupMenu(
getNoteMenu({
note: note,
note: note.value,
translating,
translation,
menuButton,
@ -346,21 +348,24 @@ function onContextmenu(ev: MouseEvent): void {
[
{
type: "label",
text: notePage(appearNote),
text: notePage(appearNote.value),
},
{
icon: "ph-browser ph-bold ph-lg",
text: i18n.ts.openInWindow,
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",
text: i18n.ts.showInPage,
action: () => {
router.push(notePage(appearNote), "forcePage");
router.push(
notePage(appearNote.value),
"forcePage",
);
},
}
: undefined,
@ -369,22 +374,22 @@ function onContextmenu(ev: MouseEvent): void {
type: "a",
icon: "ph-arrow-square-out ph-bold ph-lg",
text: i18n.ts.openInNewTab,
href: notePage(appearNote),
href: notePage(appearNote.value),
target: "_blank",
},
{
icon: "ph-link-simple ph-bold ph-lg",
text: i18n.ts.copyLink,
action: () => {
copyToClipboard(`${url}${notePage(appearNote)}`);
copyToClipboard(`${url}${notePage(appearNote.value)}`);
},
},
note.user.host != null
note.value.user.host != null
? {
type: "a",
icon: "ph-arrow-square-up-right ph-bold ph-lg",
text: i18n.ts.showOnRemote,
href: note.url ?? note.uri ?? "",
href: note.value.url ?? note.value.uri ?? "",
target: "_blank",
}
: undefined,

View file

@ -39,6 +39,8 @@
</template>
<script lang="ts" setup>
import { computed, ref } from "vue";
import {} from "vue";
import { notificationTypes } from "firefish-js";
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 useGlobalSetting = $ref(
(includingTypes === null || includingTypes.length === 0) &&
let typesMap = ref<Record<(typeof notificationTypes)[number], boolean>>({});
let useGlobalSetting = ref(
(includingTypes.value === null || includingTypes.value.length === 0) &&
props.showGlobalToggle,
);
for (const ntype of notificationTypes) {
typesMap[ntype] = includingTypes.includes(ntype);
typesMap.value[ntype] = includingTypes.value.includes(ntype);
}
function ok() {
if (useGlobalSetting) {
if (useGlobalSetting.value) {
emit("done", { includingTypes: null });
} else {
emit("done", {
includingTypes: (
Object.keys(typesMap) as (typeof notificationTypes)[number][]
).filter((type) => typesMap[type]),
Object.keys(
typesMap.value,
) as (typeof notificationTypes)[number][]
).filter((type) => typesMap.value[type]),
});
}
dialog.close();
dialog.value.close();
}
function disableAll() {
for (const type in typesMap) {
typesMap[type as (typeof notificationTypes)[number]] = false;
for (const type in typesMap.value) {
typesMap.value[type as (typeof notificationTypes)[number]] = false;
}
}
function enableAll() {
for (const type in typesMap) {
typesMap[type as (typeof notificationTypes)[number]] = true;
for (const type in typesMap.value) {
typesMap.value[type as (typeof notificationTypes)[number]] = true;
}
}
</script>

View file

@ -15,7 +15,7 @@
</template>
<script lang="ts" setup>
import { onMounted } from "vue";
import { onMounted, ref } from "vue";
import XNotification from "@/components/MkNotification.vue";
import * as os from "@/os";
@ -28,11 +28,11 @@ const emit = defineEmits<{
}>();
const zIndex = os.claimZIndex("high");
let showing = $ref(true);
let showing = ref(true);
onMounted(() => {
window.setTimeout(() => {
showing = false;
showing.value = false;
}, 6000);
});
</script>

View file

@ -46,20 +46,12 @@
</template>
<script lang="ts" setup>
import {
defineComponent,
markRaw,
onUnmounted,
onMounted,
computed,
ref,
} from "vue";
import { onUnmounted, onMounted, computed, ref } from "vue";
import { notificationTypes } from "firefish-js";
import MkPagination, { Paging } from "@/components/MkPagination.vue";
import XNotification from "@/components/MkNotification.vue";
import XList from "@/components/MkDateSeparatedList.vue";
import XNote from "@/components/MkNote.vue";
import * as os from "@/os";
import { stream } from "@/stream";
import { $i } from "@/account";
import { i18n } from "@/i18n";

View file

@ -3,7 +3,7 @@
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from "vue";
import { reactive, watch } from "vue";
import gsap from "gsap";
import number from "@/filters/number";

View file

@ -56,7 +56,7 @@
</template>
<script lang="ts">
import { computed, defineComponent, reactive, ref } from "vue";
import { defineComponent, reactive } from "vue";
import number from "@/filters/number";
export default defineComponent({

View file

@ -1,5 +1,5 @@
<template>
<div class="zhyxdalp">
<div>
<XValue :value="value" :collapsed="false" />
</div>
</template>
@ -12,8 +12,3 @@ const props = defineProps<{
value: Record<string, unknown>;
}>();
</script>
<style lang="scss" scoped>
.zhyxdalp {
}
</style>

View file

@ -30,21 +30,16 @@
</template>
<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 XWindow from "@/components/MkWindow.vue";
import { popout as _popout } from "@/scripts/popout";
import copyToClipboard from "@/scripts/copy-to-clipboard";
import { url } from "@/config";
import * as os from "@/os";
import { mainRouter, routes } from "@/router";
import { Router } from "@/nirax";
import { i18n } from "@/i18n";
import {
PageMetadata,
provideMetadataReceiver,
setPageMetadata,
} from "@/scripts/page-metadata";
import { PageMetadata, provideMetadataReceiver } from "@/scripts/page-metadata";
const props = defineProps<{
initialPath: string;
@ -56,18 +51,18 @@ defineEmits<{
const router = new Router(routes, props.initialPath);
let pageMetadata = $ref<null | ComputedRef<PageMetadata>>();
let windowEl = $ref<InstanceType<typeof XWindow>>();
const history = $ref<{ path: string; key: any }[]>([
let pageMetadata = ref<null | ComputedRef<PageMetadata>>();
let windowEl = ref<InstanceType<typeof XWindow>>();
const history = ref<{ path: string; key: any }[]>([
{
path: router.getCurrentPath(),
key: router.getCurrentKey(),
},
]);
const buttonsLeft = $computed(() => {
const buttonsLeft = computed(() => {
const buttons = [];
if (history.length > 1) {
if (history.value.length > 1) {
buttons.push({
icon: "ph-caret-left ph-bold ph-lg",
onClick: back,
@ -76,7 +71,7 @@ const buttonsLeft = $computed(() => {
return buttons;
});
const buttonsRight = $computed(() => {
const buttonsRight = computed(() => {
const buttons = [
{
icon: "ph-arrows-out-simple ph-bold ph-lg",
@ -89,18 +84,18 @@ const buttonsRight = $computed(() => {
});
router.addListener("push", (ctx) => {
history.push({ path: ctx.path, key: ctx.key });
history.value.push({ path: ctx.path, key: ctx.key });
});
provide("router", router);
provideMetadataReceiver((info) => {
pageMetadata = info;
pageMetadata.value = info;
});
provide("shouldOmitHeaderTitle", true);
provide("shouldBackButton", false);
provide("shouldHeaderThin", true);
const contextmenu = $computed(() => [
const contextmenu = computed(() => [
{
icon: "ph-arrows-out-simple ph-bold ph-lg",
text: i18n.ts.showInPage,
@ -116,7 +111,7 @@ const contextmenu = $computed(() => [
text: i18n.ts.openInNewTab,
action: () => {
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() {
history.pop();
history.value.pop();
router.replace(
history[history.length - 1].path,
history[history.length - 1].key,
history.value[history.value.length - 1].path,
history.value[history.value.length - 1].key,
);
}
function close() {
windowEl.close();
windowEl.value.close();
}
function expand() {
mainRouter.push(router.getCurrentPath(), "forcePage");
windowEl.close();
windowEl.value.close();
}
function popout() {
_popout(router.getCurrentPath(), windowEl.$el);
windowEl.close();
_popout(router.getCurrentPath(), windowEl.value.$el);
windowEl.value.close();
}
defineExpose({

View file

@ -67,10 +67,8 @@ import {
computed,
ComputedRef,
isRef,
markRaw,
onActivated,
onDeactivated,
Ref,
ref,
watch,
} from "vue";

View file

@ -24,7 +24,8 @@
</template>
<script lang="ts" setup>
import {} from "vue";
import { ref } from "vue";
import MkModal from "./MkModal.vue";
import MkMenu from "./MkMenu.vue";
import { MenuItem } from "@/types/menu";
@ -42,7 +43,7 @@ const emit = defineEmits<{
(ev: "closed"): void;
}>();
let modal = $ref<InstanceType<typeof MkModal>>();
let modal = ref<InstanceType<typeof MkModal>>();
</script>
<style lang="scss" scoped>

View file

@ -233,7 +233,15 @@
</template>
<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 misskey from "firefish-js";
import autosize from "autosize";
@ -302,45 +310,45 @@ const emit = defineEmits<{
(ev: "esc"): void;
}>();
const textareaEl = $ref<HTMLTextAreaElement | null>(null);
const cwInputEl = $ref<HTMLInputElement | null>(null);
const hashtagsInputEl = $ref<HTMLInputElement | null>(null);
const visibilityButton = $ref<HTMLElement | null>(null);
const textareaEl = ref<HTMLTextAreaElement | null>(null);
const cwInputEl = ref<HTMLInputElement | null>(null);
const hashtagsInputEl = ref<HTMLInputElement | null>(null);
const visibilityButton = ref<HTMLElement | null>(null);
let posting = $ref(false);
let text = $ref(props.initialText ?? "");
let files = $ref(props.initialFiles ?? []);
let poll = $ref<{
let posting = ref(false);
let text = ref(props.initialText ?? "");
let files = ref(props.initialFiles ?? []);
let poll = ref<{
choices: string[];
multiple: boolean;
expiresAt: string | null;
expiredAfter: string | null;
} | null>(null);
let useCw = $ref(false);
let showPreview = $ref(false);
let cw = $ref<string | null>(null);
let localOnly = $ref<boolean>(
let useCw = ref(false);
let showPreview = ref(false);
let cw = ref<string | null>(null);
let localOnly = ref<boolean>(
props.initialLocalOnly ?? defaultStore.state.rememberNoteVisibility
? defaultStore.state.localOnly
: defaultStore.state.defaultNoteLocalOnly,
);
let visibility = $ref(
let visibility = ref(
props.initialVisibility ??
((defaultStore.state.rememberNoteVisibility
? defaultStore.state.visibility
: defaultStore.state
.defaultNoteVisibility) as (typeof misskey.noteVisibilities)[number]),
);
let visibleUsers = $ref([]);
let visibleUsers = ref([]);
if (props.initialVisibleUsers) {
props.initialVisibleUsers.forEach(pushVisibleUser);
}
let autocomplete = $ref(null);
let draghover = $ref(false);
let quoteId = $ref(null);
let hasNotSpecifiedMentions = $ref(false);
let recentHashtags = $ref(JSON.parse(localStorage.getItem("hashtags") || "[]"));
let imeText = $ref("");
let autocomplete = ref(null);
let draghover = ref(false);
let quoteId = ref(null);
let hasNotSpecifiedMentions = ref(false);
let recentHashtags = ref(JSON.parse(localStorage.getItem("hashtags") || "[]"));
let imeText = ref("");
const typing = throttle(3000, () => {
if (props.channel) {
@ -348,7 +356,7 @@ const typing = throttle(3000, () => {
}
});
const draftKey = $computed((): string => {
const draftKey = computed((): string => {
if (props.editId) {
return `edit:${props.editId}`;
}
@ -366,7 +374,7 @@ const draftKey = $computed((): string => {
return key;
});
const placeholder = $computed((): string => {
const placeholder = computed((): string => {
if (props.renote) {
return i18n.ts._postForm.quotePlaceholder;
} else if (props.reply) {
@ -386,7 +394,7 @@ const placeholder = $computed((): string => {
}
});
const submitText = $computed((): string => {
const submitText = computed((): string => {
return props.editId
? i18n.ts.edit
: props.renote
@ -396,34 +404,37 @@ const submitText = $computed((): string => {
: i18n.ts.note;
});
const textLength = $computed((): number => {
return length((preprocess(text) + imeText).trim());
const textLength = computed((): number => {
return length((preprocess(text.value) + imeText.value).trim());
});
const maxTextLength = $computed((): number => {
const maxTextLength = computed((): number => {
return instance ? instance.maxNoteTextLength : 3000;
});
const canPost = $computed((): boolean => {
const canPost = computed((): boolean => {
return (
!posting &&
(1 <= textLength || 1 <= files.length || !!poll || !!props.renote) &&
textLength <= maxTextLength &&
(!poll || poll.choices.length >= 2)
!posting.value &&
(1 <= textLength.value ||
1 <= files.value.length ||
!!poll.value ||
!!props.renote) &&
textLength.value <= maxTextLength.value &&
(!poll.value || poll.value.choices.length >= 2)
);
});
const withHashtags = $computed(
const withHashtags = computed(
defaultStore.makeGetterSetter("postFormWithHashtags"),
);
const hashtags = $computed(defaultStore.makeGetterSetter("postFormHashtags"));
const hashtags = computed(defaultStore.makeGetterSetter("postFormHashtags"));
watch($$(text), () => {
watch(text, () => {
checkMissingMention();
});
watch(
$$(visibleUsers),
visibleUsers,
() => {
checkMissingMention();
},
@ -433,10 +444,10 @@ watch(
);
if (props.mention) {
text = props.mention.host
text.value = props.mention.host
? `@${props.mention.username}@${toASCII(props.mention.host)}`
: `@${props.mention.username}`;
text += " ";
text.value += " ";
}
if (
@ -444,7 +455,7 @@ if (
(props.reply.user.username !== $i.username ||
(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
? "@" + toASCII(props.reply.user.host)
: ""
@ -467,15 +478,15 @@ if (props.reply && props.reply.text != null) {
continue;
//
if (text.includes(`${mention} `)) continue;
if (text.value.includes(`${mention} `)) continue;
text += `${mention} `;
text.value += `${mention} `;
}
}
if (props.channel) {
visibility = "public";
localOnly = true; // TODO:
visibility.value = "public";
localOnly.value = true; // TODO:
}
//
@ -483,17 +494,17 @@ if (
props.reply &&
["home", "followers", "specified"].includes(props.reply.visibility)
) {
if (props.reply.visibility === "home" && visibility === "followers") {
visibility = "followers";
if (props.reply.visibility === "home" && visibility.value === "followers") {
visibility.value = "followers";
} else if (
["home", "followers"].includes(props.reply.visibility) &&
visibility === "specified"
visibility.value === "specified"
) {
visibility = "specified";
visibility.value = "specified";
} else {
visibility = props.reply.visibility;
visibility.value = props.reply.visibility;
}
if (visibility === "specified") {
if (visibility.value === "specified") {
if (props.reply.visibleUserIds) {
os.api("users/show", {
userIds: props.reply.visibleUserIds.filter(
@ -515,7 +526,7 @@ if (
}
if (props.specified) {
visibility = "specified";
visibility.value = "specified";
pushVisibleUser(props.specified);
}
@ -531,53 +542,53 @@ const addRe = (s: string) => {
// keep cw when reply
if (defaultStore.state.keepCw && props.reply && props.reply.cw) {
useCw = true;
cw =
useCw.value = true;
cw.value =
props.reply.user.username === $i.username
? props.reply.cw
: addRe(props.reply.cw);
}
function watchForDraft() {
watch($$(text), () => saveDraft());
watch($$(useCw), () => saveDraft());
watch($$(cw), () => saveDraft());
watch($$(poll), () => saveDraft());
watch($$(files), () => saveDraft(), { deep: true });
watch($$(visibility), () => saveDraft());
watch($$(localOnly), () => saveDraft());
watch(text, () => saveDraft());
watch(useCw, () => saveDraft());
watch(cw, () => saveDraft());
watch(poll, () => saveDraft());
watch(files, () => saveDraft(), { deep: true });
watch(visibility, () => saveDraft());
watch(localOnly, () => saveDraft());
}
function checkMissingMention() {
if (visibility === "specified") {
const ast = mfm.parse(text);
if (visibility.value === "specified") {
const ast = mfm.parse(text.value);
for (const x of extractMentions(ast)) {
if (
!visibleUsers.some(
!visibleUsers.value.some(
(u) => u.username === x.username && u.host === x.host,
)
) {
hasNotSpecifiedMentions = true;
hasNotSpecifiedMentions.value = true;
return;
}
}
hasNotSpecifiedMentions = false;
hasNotSpecifiedMentions.value = false;
}
}
function addMissingMention() {
const ast = mfm.parse(text);
const ast = mfm.parse(text.value);
for (const x of extractMentions(ast)) {
if (
!visibleUsers.some(
!visibleUsers.value.some(
(u) => u.username === x.username && u.host === x.host,
)
) {
os.api("users/show", { username: x.username, host: x.host }).then(
(user) => {
visibleUsers.push(user);
visibleUsers.value.push(user);
},
);
}
@ -585,10 +596,10 @@ function addMissingMention() {
}
function togglePoll() {
if (poll) {
poll = null;
if (poll.value) {
poll.value = null;
} else {
poll = {
poll.value = {
choices: ["", ""],
multiple: false,
expiresAt: null,
@ -598,15 +609,15 @@ function togglePoll() {
}
function addTag(tag: string) {
insertTextAtCursor(textareaEl, ` #${tag} `);
insertTextAtCursor(textareaEl.value, ` #${tag} `);
}
function focus() {
if (textareaEl) {
textareaEl.focus();
textareaEl.setSelectionRange(
textareaEl.value.length,
textareaEl.value.length,
if (textareaEl.value) {
textareaEl.value.focus();
textareaEl.value.setSelectionRange(
textareaEl.value.value.length,
textareaEl.value.value.length,
);
}
}
@ -615,31 +626,32 @@ function chooseFileFrom(ev) {
selectFiles(ev.currentTarget ?? ev.target, i18n.ts.attachFile).then(
(files_) => {
for (const file of files_) {
files.push(file);
files.value.push(file);
}
},
);
}
function detachFile(id) {
files = files.filter((x) => x.id !== id);
files.value = files.value.filter((x) => x.id !== id);
}
function updateFiles(_files) {
files = _files;
files.value = _files;
}
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) {
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) {
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"),
),
{
currentVisibility: visibility,
currentLocalOnly: localOnly,
src: visibilityButton,
currentVisibility: visibility.value,
currentLocalOnly: localOnly.value,
src: visibilityButton.value,
},
{
changeVisibility: (v) => {
visibility = v;
visibility.value = v;
if (defaultStore.state.rememberNoteVisibility) {
defaultStore.set("visibility", visibility);
defaultStore.set("visibility", visibility.value);
}
},
changeLocalOnly: (v) => {
localOnly = v;
localOnly.value = v;
if (defaultStore.state.rememberNoteVisibility) {
defaultStore.set("localOnly", localOnly);
defaultStore.set("localOnly", localOnly.value);
}
},
},
@ -678,11 +690,11 @@ function setVisibility() {
function pushVisibleUser(user) {
if (
!visibleUsers.some(
!visibleUsers.value.some(
(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) {
visibleUsers = erase(user, visibleUsers);
visibleUsers.value = erase(user, visibleUsers.value);
}
function clear() {
text = "";
files = [];
poll = null;
quoteId = null;
text.value = "";
files.value = [];
poll.value = null;
quoteId.value = null;
}
function onKeydown(ev: KeyboardEvent) {
if (
(ev.which === 10 || ev.which === 13) &&
(ev.ctrlKey || ev.metaKey) &&
canPost
canPost.value
)
post();
if (ev.which === 27) emit("esc");
@ -715,12 +727,12 @@ function onKeydown(ev: KeyboardEvent) {
}
function onCompositionUpdate(ev: CompositionEvent) {
imeText = ev.data;
imeText.value = ev.data;
typing();
}
function onCompositionEnd(ev: CompositionEvent) {
imeText = "";
imeText.value = "";
}
async function onPaste(ev: ClipboardEvent) {
@ -741,7 +753,7 @@ async function onPaste(ev: ClipboardEvent) {
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();
os.yesno({
@ -749,11 +761,13 @@ async function onPaste(ev: ClipboardEvent) {
text: i18n.ts.quoteQuestion,
}).then(({ canceled }) => {
if (canceled) {
insertTextAtCursor(textareaEl, paste);
insertTextAtCursor(textareaEl.value, paste);
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_;
if (isFile || isDriveFile) {
ev.preventDefault();
draghover = true;
draghover.value = true;
switch (ev.dataTransfer.effectAllowed) {
case "all":
case "uninitialized":
@ -785,15 +799,15 @@ function onDragover(ev) {
}
function onDragenter(ev) {
draghover = true;
draghover.value = true;
}
function onDragleave(ev) {
draghover = false;
draghover.value = false;
}
function onDrop(ev): void {
draghover = false;
draghover.value = false;
//
if (ev.dataTransfer.files.length > 0) {
@ -806,7 +820,7 @@ function onDrop(ev): void {
const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
if (driveFile != null && driveFile !== "") {
const file = JSON.parse(driveFile);
files.push(file);
files.value.push(file);
ev.preventDefault();
}
//#endregion
@ -815,16 +829,16 @@ function onDrop(ev): void {
function saveDraft() {
const draftData = JSON.parse(localStorage.getItem("drafts") || "{}");
draftData[draftKey] = {
draftData[draftKey.value] = {
updatedAt: new Date(),
data: {
text: text,
useCw: useCw,
cw: cw,
visibility: visibility,
localOnly: localOnly,
files: files,
poll: poll,
text: text.value,
useCw: useCw.value,
cw: cw.value,
visibility: visibility.value,
localOnly: localOnly.value,
files: files.value,
poll: poll.value,
},
};
@ -834,37 +848,38 @@ function saveDraft() {
function deleteDraft() {
const draftData = JSON.parse(localStorage.getItem("drafts") || "{}");
delete draftData[draftKey];
delete draftData[draftKey.value];
localStorage.setItem("drafts", JSON.stringify(draftData));
}
async function post() {
const processedText = preprocess(text);
const processedText = preprocess(text.value);
let postData = {
editId: props.editId ? props.editId : undefined,
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,
renoteId: props.renote
? props.renote.id
: quoteId
? quoteId
: quoteId.value
? quoteId.value
: undefined,
channelId: props.channel ? props.channel.id : undefined,
poll: poll,
cw: useCw ? cw || "" : undefined,
localOnly: localOnly,
visibility: visibility,
poll: poll.value,
cw: useCw.value ? cw.value || "" : undefined,
localOnly: localOnly.value,
visibility: visibility.value,
visibleUserIds:
visibility === "specified"
? visibleUsers.map((u) => u.id)
visibility.value === "specified"
? visibleUsers.value.map((u) => u.id)
: undefined,
};
if (withHashtags && hashtags && hashtags.trim() !== "") {
const hashtags_ = hashtags
if (withHashtags.value && hashtags.value && hashtags.value.trim() !== "") {
const hashtags_ = hashtags.value
.trim()
.split(" ")
.map((x) => (x.startsWith("#") ? x : "#" + x))
@ -883,12 +898,13 @@ async function post() {
let token = undefined;
if (postAccount) {
if (postAccount.value) {
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)
.then(() => {
clear();
@ -908,12 +924,12 @@ async function post() {
JSON.stringify(unique(hashtags_.concat(history))),
);
}
posting = false;
postAccount = null;
posting.value = false;
postAccount.value = null;
});
})
.catch((err) => {
posting = false;
posting.value = false;
os.alert({
type: "error",
text: err.message + "\n" + (err as any).id,
@ -927,12 +943,12 @@ function cancel() {
function insertMention() {
os.selectUser().then((user) => {
insertTextAtCursor(textareaEl, "@" + Acct.toString(user) + " ");
insertTextAtCursor(textareaEl.value, "@" + Acct.toString(user) + " ");
});
}
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) {
@ -946,11 +962,11 @@ function showActions(ev) {
action: () => {
action.handler(
{
text: text,
text: text.value,
},
(key, value) => {
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) {
openAccountMenu_(
{
withExtraOperation: false,
includeCurrentAccount: true,
active: postAccount != null ? postAccount.id : $i.id,
active: postAccount.value != null ? postAccount.value.id : $i.id,
onChoose: (account) => {
if (account.id === $i.id) {
postAccount = null;
postAccount.value = null;
} else {
postAccount = account;
postAccount.value = account;
}
},
},
@ -990,30 +1006,30 @@ onMounted(() => {
}
// TODO: detach when unmount
new Autocomplete(textareaEl, $$(text));
new Autocomplete(cwInputEl, $$(cw));
new Autocomplete(hashtagsInputEl, $$(hashtags));
new Autocomplete(textareaEl.value, text);
new Autocomplete(cwInputEl.value, cw);
new Autocomplete(hashtagsInputEl.value, hashtags);
autosize(textareaEl);
autosize(textareaEl.value);
nextTick(() => {
autosize(textareaEl);
autosize(textareaEl.value);
// 稿
if (!props.instant && !props.mention && !props.specified) {
const draft = JSON.parse(localStorage.getItem("drafts") || "{}")[
draftKey
draftKey.value
];
if (draft) {
text = draft.data.text;
useCw = draft.data.useCw;
cw = draft.data.cw;
visibility = draft.data.visibility;
localOnly = draft.data.localOnly;
files = (draft.data.files || []).filter(
text.value = draft.data.text;
useCw.value = draft.data.useCw;
cw.value = draft.data.cw;
visibility.value = draft.data.visibility;
localOnly.value = draft.data.localOnly;
files.value = (draft.data.files || []).filter(
(draftFile) => draftFile,
);
if (draft.data.poll) {
poll = draft.data.poll;
poll.value = draft.data.poll;
}
}
}
@ -1021,21 +1037,21 @@ onMounted(() => {
//
if (props.initialNote) {
const init = props.initialNote;
text = init.text ? init.text : "";
files = init.files;
cw = init.cw;
useCw = init.cw != null;
text.value = init.text ? init.text : "";
files.value = init.files;
cw.value = init.cw;
useCw.value = init.cw != null;
if (init.poll) {
poll = {
poll.value = {
choices: init.poll.choices.map((x) => x.text),
multiple: init.poll.multiple,
expiresAt: init.poll.expiresAt,
expiredAfter: init.poll.expiredAfter,
};
}
visibility = init.visibility;
localOnly = init.localOnly;
quoteId = init.renote ? init.renote.id : null;
visibility.value = init.visibility;
localOnly.value = init.localOnly;
quoteId.value = init.renote ? init.renote.id : null;
}
nextTick(() => watchForDraft());

View file

@ -19,6 +19,8 @@
</template>
<script lang="ts" setup>
import { shallowRef } from "vue";
import {} from "vue";
import * as misskey from "firefish-js";
import MkModal from "@/components/MkModal.vue";
@ -46,11 +48,11 @@ const emit = defineEmits<{
(ev: "closed"): void;
}>();
let modal = $shallowRef<InstanceType<typeof MkModal>>();
let form = $shallowRef<InstanceType<typeof MkPostForm>>();
let modal = shallowRef<InstanceType<typeof MkModal>>();
let form = shallowRef<InstanceType<typeof MkPostForm>>();
function onPosted() {
modal.close({
modal.value.close({
useSendAnimation: true,
});
}

View file

@ -53,6 +53,8 @@
</template>
<script setup lang="ts">
import { ref } from "vue";
import { $i, getAccounts } from "@/account";
import MkButton from "@/components/MkButton.vue";
import { instance } from "@/instance";
@ -74,12 +76,12 @@ defineProps<{
}>();
// ServiceWorker registration
let registration = $ref<ServiceWorkerRegistration | undefined>();
let registration = ref<ServiceWorkerRegistration | undefined>();
// If this browser supports push notification
let supported = $ref(false);
let supported = ref(false);
// If this browser has already subscribed to push notification
let pushSubscription = $ref<PushSubscription | null>(null);
let pushRegistrationInServer = $ref<
let pushSubscription = ref<PushSubscription | null>(null);
let pushRegistrationInServer = ref<
| {
state?: string;
key?: string;
@ -91,11 +93,12 @@ let pushRegistrationInServer = $ref<
>();
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
return promiseDialog(
registration.pushManager
registration.value.pushManager
.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(
@ -104,10 +107,10 @@ function subscribe() {
})
.then(
async (subscription) => {
pushSubscription = subscription;
pushSubscription.value = subscription;
// Register
pushRegistrationInServer = await api("sw/register", {
pushRegistrationInServer.value = await api("sw/register", {
endpoint: subscription.endpoint,
auth: encode(subscription.getKey("auth")),
publickey: encode(subscription.getKey("p256dh")),
@ -136,12 +139,12 @@ function subscribe() {
}
async function unsubscribe() {
if (!pushSubscription) return;
if (!pushSubscription.value) return;
const endpoint = pushSubscription.endpoint;
const endpoint = pushSubscription.value.endpoint;
const accounts = await getAccounts();
pushRegistrationInServer = undefined;
pushRegistrationInServer.value = undefined;
if ($i && accounts.length >= 2) {
apiWithDialog("sw/unregister", {
@ -149,11 +152,11 @@ async function unsubscribe() {
endpoint,
});
} else {
pushSubscription.unsubscribe();
pushSubscription.value.unsubscribe();
apiWithDialog("sw/unregister", {
endpoint,
});
pushSubscription = null;
pushSubscription.value = null;
}
}
@ -184,20 +187,21 @@ if (navigator.serviceWorker == null) {
// TODO:
} else {
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) {
supported = true;
supported.value = true;
if (pushSubscription) {
if (pushSubscription.value) {
const res = await api("sw/show-registration", {
endpoint: pushSubscription.endpoint,
endpoint: pushSubscription.value.endpoint,
});
if (res) {
pushRegistrationInServer = res;
pushRegistrationInServer.value = res;
}
}
}
@ -205,6 +209,6 @@ if (navigator.serviceWorker == null) {
}
defineExpose({
pushRegistrationInServer: $$(pushRegistrationInServer),
pushRegistrationInServer: pushRegistrationInServer,
});
</script>

View file

@ -36,39 +36,38 @@
</template>
<script lang="ts" setup>
import { onMounted, watch } from "vue";
import { onMounted, watch, ref } from "vue";
import * as misskey from "firefish-js";
import MkReactionIcon from "@/components/MkReactionIcon.vue";
import MkUserCardMini from "@/components/MkUserCardMini.vue";
import { i18n } from "@/i18n";
import * as os from "@/os";
const props = defineProps<{
noteId: misskey.entities.Note["id"];
}>();
let note = $ref<misskey.entities.Note>();
let tab = $ref<string>();
let reactions = $ref<string[]>();
let users = $ref();
let note = ref<misskey.entities.Note>();
let tab = ref<string>();
let reactions = ref<string[]>();
let users = ref();
watch($$(tab), async () => {
watch(tab, async () => {
const res = await os.api("notes/reactions", {
noteId: props.noteId,
type: tab,
type: tab.value,
limit: 30,
});
users = res.map((x) => x.user);
users.value = res.map((x) => x.user);
});
onMounted(() => {
os.api("notes/show", {
noteId: props.noteId,
}).then((res) => {
reactions = Object.keys(res.reactions);
tab = reactions[0];
note = res;
reactions.value = Object.keys(res.reactions);
tab.value = reactions.value[0];
note.value = res;
});
});
</script>

View file

@ -9,8 +9,6 @@
</template>
<script lang="ts" setup>
import {} from "vue";
const props = defineProps<{
reaction: string;
customEmojis?: any[]; // TODO

View file

@ -18,7 +18,6 @@
</template>
<script lang="ts" setup>
import {} from "vue";
import MkTooltip from "./MkTooltip.vue";
import XReactionIcon from "@/components/MkReactionIcon.vue";

View file

@ -29,7 +29,6 @@
</template>
<script lang="ts" setup>
import {} from "vue";
import MkTooltip from "./MkTooltip.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", {
noteId: props.note.id,
userId: $i.id,
limit: 1,
}).then((res) => {
hasRenotedBefore = res.length > 0;
hasRenotedBefore.value = res.length > 0;
});
const renote = (viaKeyboard = false, ev?: MouseEvent) => {
@ -94,7 +94,7 @@ const renote = (viaKeyboard = false, ev?: MouseEvent) => {
renoteId: props.note.id,
visibility: "public",
});
hasRenotedBefore = true;
hasRenotedBefore.value = true;
const el =
ev &&
((ev.currentTarget ?? ev.target) as
@ -121,7 +121,7 @@ const renote = (viaKeyboard = false, ev?: MouseEvent) => {
renoteId: props.note.id,
visibility: "home",
});
hasRenotedBefore = true;
hasRenotedBefore.value = true;
const el =
ev &&
((ev.currentTarget ?? ev.target) as
@ -149,7 +149,7 @@ const renote = (viaKeyboard = false, ev?: MouseEvent) => {
visibility: "specified",
visibleUserIds: props.note.visibleUserIds,
});
hasRenotedBefore = true;
hasRenotedBefore.value = true;
const el =
ev &&
((ev.currentTarget ?? ev.target) as
@ -174,7 +174,7 @@ const renote = (viaKeyboard = false, ev?: MouseEvent) => {
renoteId: props.note.id,
visibility: "followers",
});
hasRenotedBefore = true;
hasRenotedBefore.value = true;
const el =
ev &&
((ev.currentTarget ?? ev.target) as
@ -212,7 +212,7 @@ const renote = (viaKeyboard = false, ev?: MouseEvent) => {
localOnly: true,
},
);
hasRenotedBefore = true;
hasRenotedBefore.value = true;
const el =
ev &&
((ev.currentTarget ?? ev.target) as
@ -242,7 +242,7 @@ const renote = (viaKeyboard = false, ev?: MouseEvent) => {
});
}
if (hasRenotedBefore) {
if (hasRenotedBefore.value) {
buttonActions.push({
text: i18n.ts.unrenote,
icon: "ph-trash ph-bold ph-lg",
@ -251,7 +251,7 @@ const renote = (viaKeyboard = false, ev?: MouseEvent) => {
os.api("notes/unrenote", {
noteId: props.note.id,
});
hasRenotedBefore = false;
hasRenotedBefore.value = false;
},
});
}

View file

@ -160,8 +160,7 @@
</template>
<script lang="ts" setup>
import Vue3OtpInput from "vue3-otp-input";
import { defineAsyncComponent } from "vue";
import { defineAsyncComponent, ref, computed } from "vue";
import { toUnicode } from "punycode/";
import MkButton from "@/components/MkButton.vue";
import MkInput from "@/components/form/input.vue";
@ -173,24 +172,19 @@ import { login } from "@/account";
import { instance } from "@/instance";
import { i18n } from "@/i18n";
let signing = $ref(false);
let user = $ref(null);
let username = $ref("");
let password = $ref("");
let token = $ref("");
let host = $ref(toUnicode(configHost));
let totpLogin = $ref(false);
let credential = $ref(null);
let challengeData = $ref(null);
let queryingKey = $ref(false);
let hCaptchaResponse = $ref(null);
let reCaptchaResponse = $ref(null);
let signing = ref(false);
let user = ref(null);
let username = ref("");
let password = ref("");
let token = ref("");
let host = ref(toUnicode(configHost));
let totpLogin = ref(false);
let challengeData = ref(null);
let queryingKey = ref(false);
let hCaptchaResponse = ref(null);
let reCaptchaResponse = ref(null);
const updateToken = (value: string) => {
token = value.toString();
};
const meta = $computed(() => instance);
const meta = computed(() => instance);
const emit = defineEmits<{
(ev: "login", v: any): void;
@ -216,13 +210,13 @@ const props = defineProps({
function onUsernameChange() {
os.api("users/show", {
username: username,
username: username.value,
}).then(
(userResponse) => {
user = userResponse;
user.value = userResponse;
},
() => {
user = null;
user.value = null;
},
);
}
@ -234,38 +228,40 @@ function onLogin(res) {
}
function queryKey() {
queryingKey = true;
queryingKey.value = true;
return navigator.credentials
.get({
publicKey: {
challenge: byteify(challengeData.challenge, "base64"),
allowCredentials: challengeData.securityKeys.map((key) => ({
id: byteify(key.id, "hex"),
type: "public-key",
transports: ["usb", "nfc", "ble", "internal"],
})),
challenge: byteify(challengeData.value.challenge, "base64"),
allowCredentials: challengeData.value.securityKeys.map(
(key) => ({
id: byteify(key.id, "hex"),
type: "public-key",
transports: ["usb", "nfc", "ble", "internal"],
}),
),
timeout: 60 * 1000,
},
})
.catch(() => {
queryingKey = false;
queryingKey.value = false;
return Promise.reject(null);
})
.then((credential) => {
queryingKey = false;
signing = true;
queryingKey.value = false;
signing.value = true;
return os.api("signin", {
username,
password,
username: username.value,
password: password.value,
signature: hexify(credential.response.signature),
authenticatorData: hexify(
credential.response.authenticatorData,
),
clientDataJSON: hexify(credential.response.clientDataJSON),
credentialId: credential.id,
challengeId: challengeData.challengeId,
"hcaptcha-response": hCaptchaResponse,
"g-recaptcha-response": reCaptchaResponse,
challengeId: challengeData.value.challengeId,
"hcaptcha-response": hCaptchaResponse.value,
"g-recaptcha-response": reCaptchaResponse.value,
});
})
.then((res) => {
@ -278,39 +274,42 @@ function queryKey() {
type: "error",
text: i18n.ts.signinFailed,
});
signing = false;
signing.value = false;
});
}
function onSubmit() {
signing = true;
signing.value = true;
console.log("submit");
if (!totpLogin && user && user.twoFactorEnabled) {
if (window.PublicKeyCredential && user.securityKeys) {
if (!totpLogin.value && user.value && user.value.twoFactorEnabled) {
if (window.PublicKeyCredential && user.value.securityKeys) {
os.api("signin", {
username,
password,
"hcaptcha-response": hCaptchaResponse,
"g-recaptcha-response": reCaptchaResponse,
username: username.value,
password: password.value,
"hcaptcha-response": hCaptchaResponse.value,
"g-recaptcha-response": reCaptchaResponse.value,
})
.then((res) => {
totpLogin = true;
signing = false;
challengeData = res;
totpLogin.value = true;
signing.value = false;
challengeData.value = res;
return queryKey();
})
.catch(loginFailed);
} else {
totpLogin = true;
signing = false;
totpLogin.value = true;
signing.value = false;
}
} else {
os.api("signin", {
username,
password,
"hcaptcha-response": hCaptchaResponse,
"g-recaptcha-response": reCaptchaResponse,
token: user && user.twoFactorEnabled ? token : undefined,
username: username.value,
password: password.value,
"hcaptcha-response": hCaptchaResponse.value,
"g-recaptcha-response": reCaptchaResponse.value,
token:
user.value && user.value.twoFactorEnabled
? token.value
: undefined,
})
.then((res) => {
emit("login", res);
@ -360,9 +359,9 @@ function loginFailed(err) {
}
}
challengeData = null;
totpLogin = false;
signing = false;
challengeData.value = null;
totpLogin.value = false;
signing.value = false;
}
function resetPassword() {

View file

@ -12,6 +12,8 @@
</template>
<script lang="ts" setup>
import { ref } from "vue";
import {} from "vue";
import MkSignin from "@/components/MkSignin.vue";
import XModalWindow from "@/components/MkModalWindow.vue";
@ -34,15 +36,15 @@ const emit = defineEmits<{
(ev: "cancelled"): void;
}>();
const dialog = $ref<InstanceType<typeof XModalWindow>>();
const dialog = ref<InstanceType<typeof XModalWindow>>();
function onClose() {
emit("cancelled");
dialog.close();
dialog.value.close();
}
function onLogin(res) {
emit("done", res);
dialog.close();
dialog.value.close();
}
</script>

View file

@ -284,7 +284,8 @@
</template>
<script lang="ts" setup>
import {} from "vue";
import { ref, computed } from "vue";
import getPasswordStrength from "syuilo-password-strength";
import { toUnicode } from "punycode/";
import MkButton from "./MkButton.vue";
@ -313,14 +314,14 @@ const emit = defineEmits<{
const host = toUnicode(config.host);
let hcaptcha = $ref();
let recaptcha = $ref();
let hcaptcha = ref();
let recaptcha = ref();
let username: string = $ref("");
let password: string = $ref("");
let retypedPassword: string = $ref("");
let invitationCode: string = $ref("");
let email = $ref("");
let username: string = ref("");
let password: string = ref("");
let retypedPassword: string = ref("");
let invitationCode: string = ref("");
let email = ref("");
let usernameState:
| null
| "wait"
@ -329,8 +330,8 @@ let usernameState:
| "error"
| "invalid-format"
| "min-range"
| "max-range" = $ref(null);
let invitationState: null | "entered" = $ref(null);
| "max-range" = ref(null);
let invitationState: null | "entered" = ref(null);
let emailState:
| null
| "wait"
@ -341,79 +342,79 @@ let emailState:
| "unavailable:mx"
| "unavailable:smtp"
| "unavailable"
| "error" = $ref(null);
let passwordStrength: "" | "low" | "medium" | "high" = $ref("");
let passwordRetypeState: null | "match" | "not-match" = $ref(null);
let submitting: boolean = $ref(false);
let ToSAgreement: boolean = $ref(false);
let hCaptchaResponse = $ref(null);
let reCaptchaResponse = $ref(null);
| "error" = ref(null);
let passwordStrength: "" | "low" | "medium" | "high" = ref("");
let passwordRetypeState: null | "match" | "not-match" = ref(null);
let submitting: boolean = ref(false);
let ToSAgreement: boolean = ref(false);
let hCaptchaResponse = ref(null);
let reCaptchaResponse = ref(null);
const shouldDisableSubmitting = $computed((): boolean => {
const shouldDisableSubmitting = computed((): boolean => {
return (
submitting ||
(instance.tosUrl && !ToSAgreement) ||
(instance.enableHcaptcha && !hCaptchaResponse) ||
(instance.enableRecaptcha && !reCaptchaResponse) ||
passwordRetypeState === "not-match"
submitting.value ||
(instance.tosUrl && !ToSAgreement.value) ||
(instance.enableHcaptcha && !hCaptchaResponse.value) ||
(instance.enableRecaptcha && !reCaptchaResponse.value) ||
passwordRetypeState.value === "not-match"
);
});
function onChangeInvitationCode(): void {
if (invitationCode === "") {
invitationState = null;
if (invitationCode.value === "") {
invitationState.value = null;
return;
}
invitationState = "entered";
invitationState.value = "entered";
}
function onChangeUsername(): void {
if (username === "") {
usernameState = null;
if (username.value === "") {
usernameState.value = null;
return;
}
{
const err = !username.match(/^[a-zA-Z0-9_]+$/)
const err = !username.value.match(/^[a-zA-Z0-9_]+$/)
? "invalid-format"
: username.length < 1
: username.value.length < 1
? "min-range"
: username.length > 20
: username.value.length > 20
? "max-range"
: null;
if (err) {
usernameState = err;
usernameState.value = err;
return;
}
}
usernameState = "wait";
usernameState.value = "wait";
os.api("username/available", {
username,
username: username.value,
})
.then((result) => {
usernameState = result.available ? "ok" : "unavailable";
usernameState.value = result.available ? "ok" : "unavailable";
})
.catch(() => {
usernameState = "error";
usernameState.value = "error";
});
}
function onChangeEmail(): void {
if (email === "") {
emailState = null;
if (email.value === "") {
emailState.value = null;
return;
}
emailState = "wait";
emailState.value = "wait";
os.api("email-address/available", {
emailAddress: email,
emailAddress: email.value,
})
.then((result) => {
emailState = result.available
emailState.value = result.available
? "ok"
: result.reason === "used"
? "unavailable:used"
@ -428,54 +429,55 @@ function onChangeEmail(): void {
: "unavailable";
})
.catch(() => {
emailState = "error";
emailState.value = "error";
});
}
function onChangePassword(): void {
if (password === "") {
passwordStrength = "";
if (password.value === "") {
passwordStrength.value = "";
return;
}
const strength = getPasswordStrength(password);
passwordStrength =
const strength = getPasswordStrength(password.value);
passwordStrength.value =
strength > 0.7 ? "high" : strength > 0.3 ? "medium" : "low";
}
function onChangePasswordRetype(): void {
if (retypedPassword === "") {
passwordRetypeState = null;
if (retypedPassword.value === "") {
passwordRetypeState.value = null;
return;
}
passwordRetypeState = password === retypedPassword ? "match" : "not-match";
passwordRetypeState.value =
password.value === retypedPassword.value ? "match" : "not-match";
}
function onSubmit(): void {
if (submitting) return;
submitting = true;
if (submitting.value) return;
submitting.value = true;
os.api("signup", {
username,
password,
emailAddress: email,
invitationCode,
"hcaptcha-response": hCaptchaResponse,
"g-recaptcha-response": reCaptchaResponse,
username: username.value,
password: password.value,
emailAddress: email.value,
invitationCode: invitationCode.value,
"hcaptcha-response": hCaptchaResponse.value,
"g-recaptcha-response": reCaptchaResponse.value,
})
.then(() => {
if (instance.emailRequiredForSignup) {
os.alert({
type: "success",
title: i18n.ts._signup.almostThere,
text: i18n.t("_signup.emailSent", { email }),
text: i18n.t("_signup.emailSent", { email: email.value }),
});
emit("signupEmailPending");
} else {
os.api("signin", {
username,
password,
username: username.value,
password: password.value,
}).then((res) => {
emit("signup", res);
@ -486,9 +488,9 @@ function onSubmit(): void {
}
})
.catch(() => {
submitting = false;
hcaptcha.reset?.();
recaptcha.reset?.();
submitting.value = false;
hcaptcha.value.reset?.();
recaptcha.value.reset?.();
os.alert({
type: "error",

View file

@ -20,7 +20,8 @@
</template>
<script lang="ts" setup>
import {} from "vue";
import { ref } from "vue";
import XSignup from "@/components/MkSignup.vue";
import XModalWindow from "@/components/MkModalWindow.vue";
import { i18n } from "@/i18n";
@ -39,14 +40,14 @@ const emit = defineEmits<{
(ev: "closed"): void;
}>();
const dialog = $ref<InstanceType<typeof XModalWindow>>();
const dialog = ref<InstanceType<typeof XModalWindow>>();
function onSignup(res) {
emit("done", res);
dialog?.close();
dialog.value?.close();
}
function onSignupEmailPending() {
dialog?.close();
dialog.value?.close();
}
</script>

View file

@ -47,7 +47,6 @@ import Ripple from "@/components/MkRipple.vue";
import XDetails from "@/components/MkUsersTooltip.vue";
import { pleaseLogin } from "@/scripts/please-login";
import * as os from "@/os";
import { defaultStore } from "@/store";
import { i18n } from "@/i18n";
import { instance } from "@/instance";
import { useTooltip } from "@/scripts/use-tooltip";

View file

@ -217,23 +217,23 @@ const isLong =
(props.note.text.split("\n").length > 10 ||
props.note.text.length > 800)) ||
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
? extractUrlFromMfm(mfm.parse(props.note.text)).slice(0, 5)
: null;
let showContent = $ref(false);
let showContent = ref(false);
const mfms = props.note.text
? extractMfmWithAnimation(mfm.parse(props.note.text))
: 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() {
if (disableMfm) {
if (disableMfm.value) {
if (!defaultStore.state.animatedMfmWarnShown) {
const { canceled } = await os.confirm({
type: "warning",
@ -244,9 +244,9 @@ async function toggleMfm() {
defaultStore.set("animatedMfmWarnShown", true);
}
disableMfm = false;
disableMfm.value = false;
} else {
disableMfm = true;
disableMfm.value = true;
}
}

View file

@ -4,7 +4,7 @@
<div v-if="group.title" class="title">{{ group.title }}</div>
<div class="items">
<template v-for="(item, i) in group.items">
<template v-for="item in group.items">
<a
v-if="item.type === 'a'"
:href="item.href"
@ -53,7 +53,7 @@
</template>
<script lang="ts">
import { defineComponent, ref, unref } from "vue";
import { defineComponent } from "vue";
export default defineComponent({
props: {

View file

@ -17,7 +17,7 @@
</template>
<script lang="ts" setup>
import { onMounted, ref, watch, PropType, onBeforeUnmount } from "vue";
import { onMounted, watch, onBeforeUnmount, ref } from "vue";
import tinycolor from "tinycolor2";
const loaded = !!window.TagCanvas;
@ -39,13 +39,13 @@ const idForTags = Array.from(Array(16))
],
)
.join("");
let available = $ref(false);
let rootEl = $ref<HTMLElement | null>(null);
let canvasEl = $ref<HTMLCanvasElement | null>(null);
let tagsEl = $ref<HTMLElement | null>(null);
let width = $ref(300);
let available = ref(false);
let rootEl = ref<HTMLElement | null>(null);
let canvasEl = ref<HTMLCanvasElement | null>(null);
let tagsEl = ref<HTMLElement | null>(null);
let width = ref(300);
watch($$(available), () => {
watch(available, () => {
try {
window.TagCanvas.Start(idForCanvas, idForTags, {
textColour: "#ffffff",
@ -70,10 +70,10 @@ watch($$(available), () => {
});
onMounted(() => {
width = rootEl.offsetWidth;
width.value = rootEl.value.offsetWidth;
if (loaded) {
available = true;
available.value = true;
} else {
document.head
.appendChild(
@ -82,7 +82,7 @@ onMounted(() => {
src: "/client-assets/tagcanvas.min.js",
}),
)
.addEventListener("load", () => (available = true));
.addEventListener("load", () => (available.value = true));
}
});

View file

@ -28,10 +28,9 @@
</template>
<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 MkInfo from "@/components/MkInfo.vue";
import * as os from "@/os";
import { stream } from "@/stream";
import * as sound from "@/scripts/sound";
import { $i } from "@/account";
@ -46,7 +45,7 @@ const props = defineProps<{
sound?: boolean;
}>();
let queue = $ref(0);
let queue = ref(0);
const emit = defineEmits<{
(ev: "note"): void;
@ -58,10 +57,10 @@ provide(
computed(() => props.src === "channel"),
);
const tlComponent: InstanceType<typeof XNotes> = $ref();
const tlComponent: InstanceType<typeof XNotes> = ref();
const prepend = (note) => {
tlComponent.pagingComponent?.prepend(note);
tlComponent.value.pagingComponent?.prepend(note);
emit("note");
@ -71,16 +70,16 @@ const prepend = (note) => {
};
const onUserAdded = () => {
tlComponent.pagingComponent?.reload();
tlComponent.value.pagingComponent?.reload();
};
const onUserRemoved = () => {
tlComponent.pagingComponent?.reload();
tlComponent.value.pagingComponent?.reload();
};
const onChangeFollowing = () => {
if (!tlComponent.pagingComponent?.backed) {
tlComponent.pagingComponent?.reload();
if (!tlComponent.value.pagingComponent?.backed) {
tlComponent.value.pagingComponent?.reload();
}
};

View file

@ -30,11 +30,11 @@ const emit = defineEmits<{
}>();
const zIndex = os.claimZIndex("high");
let showing = $ref(true);
let showing = ref(true);
onMounted(() => {
window.setTimeout(() => {
showing = false;
showing.value = false;
}, 4000);
});
</script>

View file

@ -43,6 +43,8 @@
</template>
<script lang="ts" setup>
import { ref } from "vue";
import {} from "vue";
import { permissions as kinds } from "firefish-js";
import MkInput from "./form/input.vue";
@ -72,37 +74,39 @@ const emit = defineEmits<{
(ev: "done", result: { name: string | null; permissions: string[] }): void;
}>();
const dialog = $ref<InstanceType<typeof XModalWindow>>();
let name = $ref(props.initialName);
let permissions = $ref({});
const dialog = ref<InstanceType<typeof XModalWindow>>();
let name = ref(props.initialName);
let permissions = ref({});
if (props.initialPermissions) {
for (const kind of props.initialPermissions) {
permissions[kind] = true;
permissions.value[kind] = true;
}
} else {
for (const kind of kinds) {
permissions[kind] = false;
permissions.value[kind] = false;
}
}
function ok(): void {
emit("done", {
name: name,
permissions: Object.keys(permissions).filter((p) => permissions[p]),
name: name.value,
permissions: Object.keys(permissions.value).filter(
(p) => permissions.value[p],
),
});
dialog.close();
dialog.value.close();
}
function disableAll(): void {
for (const p in permissions) {
permissions[p] = false;
for (const p in permissions.value) {
permissions.value[p] = false;
}
}
function enableAll(): void {
for (const p in permissions) {
permissions[p] = true;
for (const p in permissions.value) {
permissions.value[p] = true;
}
}
</script>

View file

@ -206,7 +206,7 @@
</template>
<script lang="ts" setup>
import { reactive, computed } from "vue";
import { computed, ref } from "vue";
import XSettings from "@/pages/settings/profile.vue";
import XModalWindow from "@/components/MkModalWindow.vue";
import MkButton from "@/components/MkButton.vue";
@ -250,7 +250,7 @@ const emit = defineEmits<{
(ev: "closed"): void;
}>();
const dialog = $ref<InstanceType<typeof XModalWindow>>();
const dialog = ref<InstanceType<typeof XModalWindow>>();
const tutorial = computed({
get() {
@ -278,7 +278,7 @@ const reduceAnimation = computed(
function close(res) {
tutorial.value = -1;
dialog.close();
dialog.value.close();
}
</script>

View file

@ -33,7 +33,7 @@
</template>
<script lang="ts" setup>
import { shallowRef } from "vue";
import { shallowRef, ref } from "vue";
import MkModal from "@/components/MkModal.vue";
import MkSparkle from "@/components/MkSparkle.vue";
import MkButton from "@/components/MkButton.vue";
@ -43,18 +43,18 @@ import * as os from "@/os";
const modal = shallowRef<InstanceType<typeof MkModal>>();
let newRelease = $ref(false);
let data = $ref(Object);
let newRelease = ref(false);
let data = ref(Object);
os.api("release").then((res) => {
data = res;
newRelease = version === data?.version;
data.value = res;
newRelease.value = version === data.value?.version;
});
console.log(`Version: ${version}`);
console.log(`Data version: ${data.version}`);
console.log(newRelease);
console.log(data);
console.log(`Data version: ${data.value.version}`);
console.log(newRelease.value);
console.log(data.value);
</script>
<style lang="scss" module>

View file

@ -99,7 +99,7 @@
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted } from "vue";
import { onUnmounted, ref } from "vue";
import { url as local, lang } from "@/config";
import { i18n } from "@/i18n";
import { defaultStore } from "@/store";
@ -117,22 +117,22 @@ const props = withDefaults(
const self = props.url.startsWith(local);
const attr = self ? "to" : "href";
const target = self ? null : "_blank";
let fetching = $ref(true);
let title = $ref<string | null>(null);
let description = $ref<string | null>(null);
let thumbnail = $ref<string | null>(null);
let icon = $ref<string | null>(null);
let sitename = $ref<string | null>(null);
let player = $ref({
let fetching = ref(true);
let title = ref<string | null>(null);
let description = ref<string | null>(null);
let thumbnail = ref<string | null>(null);
let icon = ref<string | null>(null);
let sitename = ref<string | null>(null);
let player = ref({
url: null,
width: null,
height: null,
});
let playerEnabled = $ref(false);
let tweetId = $ref<string | null>(null);
let tweetExpanded = $ref(props.detail);
let playerEnabled = ref(false);
let tweetId = ref<string | null>(null);
let tweetExpanded = ref(props.detail);
const embedId = `embed${Math.random().toString().replace(/\D/, "")}`;
let tweetHeight = $ref(150);
let tweetHeight = ref(150);
const requestUrl = new URL(props.url);
if (!["http:", "https:"].includes(requestUrl.protocol))
@ -143,7 +143,7 @@ if (
requestUrl.hostname === "mobile.twitter.com"
) {
const m = requestUrl.pathname.match(/^\/.+\/status(?:es)?\/(\d+)/);
if (m) tweetId = m[1];
if (m) tweetId.value = m[1];
}
if (
@ -162,13 +162,13 @@ fetch(
).then((res) => {
res.json().then((info) => {
if (info.url == null) return;
title = info.title;
description = info.description;
thumbnail = info.thumbnail;
icon = info.icon;
sitename = info.sitename;
fetching = false;
player = info.player;
title.value = info.title;
description.value = info.description;
thumbnail.value = info.thumbnail;
icon.value = info.icon;
sitename.value = info.sitename;
fetching.value = false;
player.value = info.player;
});
});
@ -178,7 +178,7 @@ function adjustTweetHeight(message: any) {
if (embed?.method !== "twttr.private.resize") return;
if (embed?.id !== embedId) return;
const height = embed?.params[0]?.height;
if (height) tweetHeight = height;
if (height) tweetHeight.value = height;
}
(window as any).addEventListener("message", adjustTweetHeight);

View file

@ -13,7 +13,7 @@
</template>
<script lang="ts" setup>
import { onMounted } from "vue";
import { onMounted, ref } from "vue";
import MkUrlPreview from "@/components/MkUrlPreview.vue";
import * as os from "@/os";
@ -28,8 +28,8 @@ const emit = defineEmits<{
}>();
const zIndex = os.claimZIndex("middle");
let top = $ref(0);
let left = $ref(0);
let top = ref(0);
let left = ref(0);
onMounted(() => {
const rect = props.source.getBoundingClientRect();
@ -38,8 +38,8 @@ onMounted(() => {
window.pageXOffset;
const y = rect.top + props.source.offsetHeight + window.pageYOffset;
top = y;
left = x;
top.value = y;
left.value = x;
});
</script>

Some files were not shown because too many files have changed in this diff Show more