Firefish v1.0.5-dev6

This commit is contained in:
naskya 2023-08-25 07:19:12 +09:00
parent f2215e511d
commit d52e4b3c6a
Signed by: naskya
GPG key ID: 164DFF24E2D40139
313 changed files with 5296 additions and 3678 deletions

View file

@ -161,6 +161,9 @@ reservedUsernames: [
# deliverJobMaxAttempts: 12
# inboxJobMaxAttempts: 8
# Local address used for outgoing requests
#outgoingAddress: 127.0.0.1
# IP address family used for outgoing request (ipv4, ipv6 or dual)
#outgoingAddressFamily: ipv4

View file

@ -1,7 +1,6 @@
{
"recommendations": [
"editorconfig.editorconfig",
"eg2.vscode-npm-script",
"rome.rome",
"Vue.volar",
"Vue.vscode-typescript-vue-plugin",

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

@ -8900,7 +8900,7 @@ Resolve #7540
* truncate user information if it is too long
Some AP software allows for user names or summaries to be very long.
Misskey can not handle this and the profile page can not be opened and
Misskey cannot handle this and the profile page cannot be opened and
no activities from such users can be seen.
Instead, the user name and summary are cut off after the maximum length
@ -9902,7 +9902,7 @@ This duplicated processing can be avoided by querying the database directly.
Misskey will only use ActivityPub follow requests for users that are local
and are requesting to follow a remote user. This check is to ensure that
this endpoint can not be used by other services or instances.
this endpoint cannot be used by other services or instances.
* fix: missing import
@ -14921,7 +14921,7 @@ Defaults for `local` and `withFiles` are based on the behaviour of the endpoint.
* fix: define required fields
- `notes/create`: the default for `text` has been removed because ajv can not handle
- `notes/create`: the default for `text` has been removed because ajv cannot handle
`default` inside of `anyOf`, see
https://ajv.js.org/guide/modifying-data.html#assigning-defaults
and the default value cannot be `null` if text is `nullable: false` in the `anyOf`
@ -15551,7 +15551,7 @@ unnecessarily loaded.
* remove duplicate null check
The variable is checked for null in the lines above and the function
returns if so. Therefore, it can not be null at this point.
returns if so. Therefore, it cannot be null at this point.
* simplify `getJsonSchema`

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
@ -26,7 +31,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
@ -43,11 +48,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 . ./
@ -69,5 +74,5 @@ COPY --from=build /firefish/packages/backend/native-utils/built /firefish/packag
RUN corepack enable && corepack prepare pnpm@latest --activate
ENV NODE_ENV=production
VOLUME "/firefish/files"
ENTRYPOINT [ "/sbin/tini", "--" ]
ENTRYPOINT [ "/usr/bin/tini", "--" ]
CMD [ "pnpm", "run", "migrateandstart" ]

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

@ -1,5 +1,5 @@
_lang_: Български
cancel: Отмяна
cancel: Отмени
noNotes: Няма публикации
settings: Настройки
headlineFirefish: Децентрализирана социална медийна платформа с отворен код, която
@ -101,6 +101,10 @@ _filters:
followersOnly: Само последователи
notesAfter: Публикации след
fromDomain: От домейн
fromUser: От потребител
withFile: С файл
notesBefore: Публикации преди
followingOnly: Само последвани
_notification:
_types:
follow: Нови последователи
@ -113,10 +117,10 @@ noLists: Нямаш никакви списъци
markAsReadAllUnreadNotes: Маркирай всички публикации като прочетени
markAsReadAllTalkMessages: Маркирай всички съобщения като прочетени
_time:
second: Секунд(а/и)
hour: Час(а)
second: Секунди
hour: Часа
day: Дни
minute: Минут(а/и)
minute: Минути
create: Създай
lists: Списъци
reportAbuseOf: Докладвай {name}
@ -134,9 +138,9 @@ rename: Преименувай
customEmojis: Персонализирани емоджита
emoji: Емоджи
_aboutFirefish:
translation: Преведи Calckey
translation: Преведи Firefish
translatedFrom: Преведено от {x}
i18nInfo: Calckey се превежда на различни езици от доброволци. Можете да помогнете
i18nInfo: Firefish се превежда на различни езици от доброволци. Можете да помогнете
на адрес {link}.
image: Изображение
recipient: Получател(и)
@ -222,11 +226,11 @@ _mfm:
_messaging:
groups: Групи
apps: Приложения
introFirefish: Добре дошли! Calckey е децентрализирана социална медийна платформа
introFirefish: Добре дошли! Firefish е децентрализирана социална медийна платформа
с отворен код, която е безплатна завинаги! 🚀
monthAndDay: '{day}/{month}'
search: Търсене
searchPlaceholder: Търсене в Calckey
searchPlaceholder: Търсене в Firefish
username: Потребителско име
password: Парола
fetchingAsApObject: Извличане от федивърса
@ -257,7 +261,7 @@ alreadyFavorited: Вече е добавено в отметки.
cantFavorite: Неуспешно добавяне в отметки.
copyContent: Копирай съдържанието
deleteAndEdit: Изтрий и редактирай
editNote: Редактирай бележка
editNote: Редактирай публикация
edited: Редактирано на {date} {time}
addToList: Добави в списък
sendMessage: Изпрати съобщение
@ -422,3 +426,51 @@ _visibility:
followers: Последователи
explore: Разглеждане
theme: Теми
wallpaper: Тапет
setWallpaper: Задай тапет
removeWallpaper: Премахни тапет
themeForLightMode: Тема за използване в светъл режим
themeForDarkMode: Тема за използване в тъмен режим
light: Светло
dark: Тъмно
darkThemes: Тъмни теми
invitations: Покани
invitationCode: Код на поканата
checking: Проверка...
available: Свободно
unavailable: Не е свободно
tooShort: Твърде кратко
tooLong: Твърде дълго
weakPassword: Слаба парола
strongPassword: Силна парола
passwordMatched: Съвпада
passwordNotMatched: Не съвпада
signinWith: Вписване с {x}
aboutX: Относно {x}
openInNewTab: Отвори в нов раздел
_tutorial:
step2_1: Първо, моля, попълнете своя профил.
step2_2: Предоставянето на известна информация за това кой сте вие ще улесни другите
да разберат дали искат да видят вашите публикации или да ви следват.
title: Как се използва Firefish
step1_1: Добре дошли!
step5_1: Инфопотоци, инфопотоци навсякъде!
step3_1: Сега е време да последвате няколко хора!
step1_2: Нека да ви настроим. Ще бъдете готови за нула време!
openImageInNewTab: Отваряй изображенията в нов раздел
showOnRemote: Отвори оригиналната страница
lightThemes: Светли теми
syncDeviceDarkMode: Синхронизиране на тъмния режим с настройките на устройството
text: Текст
normalPassword: Средна парола
usernameInvalidFormat: Можете да използвате главни и малки букви, цифри и долни черти.
signinFailed: Неуспешно вписване. Въведените потребителско име или парола са неправилни.
signinRequired: Моля, регистрирайте се или се впишете, преди да продължите
start: Започни
confirm: Потвърди
failedToUpload: Неуспешно качване
_preferencesBackups:
cannotSave: Неуспешно запазване
cannotLoad: Неуспешно зареждане
editWidgetsExit: Готово
done: Готово

View file

@ -2074,7 +2074,7 @@ _relayStatus:
accepted: Acceptat
rejected: Rebutjat
deleted: Eliminat
editNote: Edita la nota
editNote: Edita la publicació
edited: 'Editat a {date} {time}'
findOtherInstance: Cercar un altre servidor
signupsDisabled: Actualment, les inscripcions en aquest servidor estan desactivades,
@ -2182,3 +2182,7 @@ delete2fa: Desactivar 2FA
delete2faConfirm: Això suprimirà irreversiblement 2FA en aquest compte. Procedir?
addRe: Afegeix "re:" al començament del comentari quant responguis a un missatge amb
avís de contingut
confirm: Confirmar
importZip: Importar ZIP
exportZip: Exportar ZIP
emojiPackCreator: Creador de paquets Emoji

View file

@ -77,7 +77,7 @@ lists: "Listen"
noLists: "Du hast keine Listen angelegt"
note: "Beitrag"
notes: "Beiträge"
following: "Folge ich"
following: "Folgen"
followers: "Folgen mir"
followsYou: "Folgt dir"
createList: "Liste erstellen"
@ -474,7 +474,7 @@ invitations: "Einladungen"
invitationCode: "Einladungscode"
checking: "Wird überprüft …"
available: "Verfügbar"
unavailable: "Unverfügbar"
unavailable: "Nicht verfügbar"
usernameInvalidFormat: "Du kannst Klein- und Großbuchstaben, Zahlen sowie Unterstriche
verwenden."
tooShort: "Zu kurz"
@ -1945,7 +1945,7 @@ _notification:
renote: "Renote"
voted: haben bei deiner Umfrage abgestimmt
reacted: hat auf deinen Beitrag reagiert
renoted: hat Ihren Beitrag geteilt
renoted: hat deinen Beitrag geteilt
_deck:
alwaysShowMainColumn: "Hauptspalte immer zeigen"
columnAlign: "Spaltenausrichtung"
@ -2099,7 +2099,7 @@ customKaTeXMacro: Individuelle KaTeX Makros
enableCustomKaTeXMacro: Individuelle KaTeX-Makros aktivieren
replayTutorial: Wiederhole die Benutzeranleitung
apps: Apps
caption: Automatische Untertitelung
caption: Automatische Beschreibung
pwa: PWA installieren
cw: Inhaltswarnung
older: älter
@ -2204,3 +2204,7 @@ deletePasskeysConfirm: Alle Passkeys und Security-Keys werden unwiderruflich von
inputNotMatch: Eingabe stimmt nicht überein
addRe: Ein "re:" am Anfang des Kommentars hinzufügen, um einem Beitrag mit einer Inhaltswarnung
zu antworten
confirm: Bestätigen
importZip: ZIP Importieren
emojiPackCreator: Emoji-Pack Ersteller
exportZip: ZIP Exportieren

View file

@ -51,7 +51,7 @@ deleted: "Deleted"
deleteAndEdit: "Delete and edit"
deleteAndEditConfirm: "Are you sure you want to delete this post and edit it? You
will lose all reactions, boosts and replies to it."
editNote: "Edit note"
editNote: "Edit post"
edited: "Edited at {date} {time}"
addToList: "Add to list"
sendMessage: "Send a message"
@ -337,11 +337,11 @@ 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."
hasChildFilesOrFolders: "Since this folder is not empty, it can not be deleted."
hasChildFilesOrFolders: "Since this folder is not empty, it cannot be deleted."
copyUrl: "Copy URL"
rename: "Rename"
avatar: "Avatar"
@ -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"
@ -1147,6 +1147,9 @@ addRe: "Add \"re:\" at the beginning of comment in reply to a post with a conten
showBigPostButton: "Show a bigger post button in the posting form"
confirm: "Confirm"
emphasizeFollowed: "Highlight the \"Follows you\" sign on your follower info"
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"
@ -120,8 +120,8 @@ reactionSettingDescription2: "Déplacer pour réorganiser, cliquer pour effacer,
« + » pour ajouter."
rememberNoteVisibility: "Se souvenir des paramètres de visibilité des publications"
attachCancel: "Supprimer le fichier attaché"
markAsSensitive: "Marquer comme sensible"
unmarkAsSensitive: "Supprimer le marquage comme sensible"
markAsSensitive: "Marquer comme sensible (NSFW)"
unmarkAsSensitive: "Supprimer le marquage comme sensible (NSFW)"
enterFileName: "Entrer le nom du fichier"
mute: "Masquer"
unmute: "Ne plus masquer"
@ -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"
@ -318,7 +318,7 @@ copyUrl: "Copier lURL"
rename: "Renommer"
avatar: "Avatar"
banner: "Bannière"
nsfw: "Contenu sensible"
nsfw: "Contenu sensible (NSFW)"
whenServerDisconnected: "Lorsque la connexion au serveur est perdue"
disconnectedFromServer: "Déconnecté·e du serveur"
reload: "Rafraîchir"
@ -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"
@ -729,7 +729,7 @@ noCrawleDescription: "Demandez aux moteurs de recherche de ne pas indexer votre
lockedAccountInfo: "À moins que vous ne définissiez la visibilité de votre publication
sur \"Abonné-e-s\", vos publications sont visibles par tous, même si vous exigez
que les demandes d'abonnement soient approuvées manuellement."
alwaysMarkSensitive: "Marquer les médias comme contenu sensible par défaut"
alwaysMarkSensitive: "Marquer les médias comme contenu sensible (NSFW) par défaut"
loadRawImages: "Affichage complet des images jointes au lieu des vignettes"
disableShowingAnimatedImages: "Désactiver l'animation des images"
verificationEmailSent: "Un e-mail de vérification a été envoyé. Veuillez accéder au
@ -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:
@ -1011,8 +1011,8 @@ _aboutFirefish:
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"
respect: "Cacher les médias marqués comme contenu sensible (NSFW)"
ignore: "Afficher les médias sensibles (NSFW)"
force: "Cacher tous les médias"
_mfm:
cheatSheet: "Antisèche MFM"
@ -1021,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."
@ -1034,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"
@ -1105,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:
@ -1170,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:
@ -1252,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 !"
@ -1265,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
@ -1357,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"
@ -1412,7 +1412,7 @@ _widgets:
rssTicker: Bandeau RSS
_cw:
hide: "Masquer"
show: "Afficher plus …"
show: "Afficher contenu"
chars: "{count} caractères"
files: "{count} fichiers"
_poll:
@ -1422,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"
@ -1468,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"
@ -1503,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"
@ -1812,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}"
@ -1993,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
@ -2071,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
@ -2197,7 +2197,7 @@ _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 ?
@ -2206,3 +2206,7 @@ deletePasskeysConfirm: Cela supprimera de manière irréversible toutes les clé
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
confirm: Confirmer
importZip: Importer ZIP
exportZip: Exporter ZIP
emojiPackCreator: Créateur de pack demoji

View file

@ -310,7 +310,7 @@ emptyDrive: "Drive kosong"
emptyFolder: "Folder kosong"
unableToDelete: "Tidak dapat menghapus"
inputNewFileName: "Masukkan nama berkas yang baru"
inputNewDescription: "Masukkan keterangan disini"
inputNewDescription: "Masukkan deskripsi baru"
inputNewFolderName: "Masukkan nama folder yang baru"
circularReferenceFolder: "Folder tujuan adalah subfolder dari folder yang ingin kamu
pindahkan."
@ -598,8 +598,8 @@ disablePlayer: "Tutup pemutar video"
expandTweet: "Perluas utas"
themeEditor: "Penyunting tema"
description: "Deskripsi"
describeFile: "Tambahkan keterangan"
enterFileDescription: "Masukkan keterangan"
describeFile: "Tambahkan deskripsi"
enterFileDescription: "Masukkan deskripsi"
author: "Pembuat"
leaveConfirm: "Ada perubahan yang belum disimpan. Apakah kamu ingin membuangnya?"
manage: "Manajemen"
@ -1904,7 +1904,7 @@ recommended: Direkomendasikan
silenceThisInstance: Bisukan server ini
hiddenTags: Tagar Tersembunyi
preferencesBackups: Preferensi cadangan
editNote: Sunting catatan
editNote: Sunting kiriman
deleted: Dihapus
edited: Disunting pada {date} {time}
selectInstance: Pilih server
@ -2019,7 +2019,7 @@ showAdminUpdates: Indikasi versi Firefish baru tersedia (hanya admin)
indexFrom: Indeks dari Post ID berikutnya
noteId: ID Postingan
findOtherInstance: Cari server lain
caption: Keterangan Otomatis
caption: Deskripsi itomatis
splash: Layar Percik
migration: Migrasi
moveTo: Pindahkan akun sekarang ke akun baru
@ -2165,3 +2165,7 @@ delete2faConfirm: Ini akan menghapus 2FA secara permanen pada akun ini. Lanjutka
deletePasskeysConfirm: Ini akan menghapus semua passkeys dan kunci keamanan pada akun
ini secara permanen. Lanjutkan?
addRe: Tambahkan "re:" pada awal komentar balasan postingan dengan peringatan konten
confirm: Konfirmasi
importZip: Impor ZIP
exportZip: Ekspor ZIP
emojiPackCreator: Pembuat paket emoji

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
_lang_: "日本語"
headlineFirefish: "ずっと無料でオープンソースの非中央集権型ソーシャルメディアプラットフォーム🚀"
introFirefish: "ようこそFirefishは、オープンソースの非中央集権型ソーシャルメディアプラットフォームです。\nいま起こっていることを共有したり、あなたについて皆に発信しましょう📡\n\
introFirefish: "ようこそFirefishは、オープンソースの非中央集権型ソーシャルメディアプラットフォームです。\nいま起こっていることを共有したり、あなたについて皆に発信したりしましょう📡\n\
「リアクション」機能で、皆の投稿に素早く反応を追加できます👍\n新しい世界を探検しよう🚀"
monthAndDay: "{month}月 {day}日"
search: "検索"
@ -303,7 +303,7 @@ emptyDrive: "ドライブは空です"
emptyFolder: "フォルダーは空です"
unableToDelete: "削除できません"
inputNewFileName: "新しいファイル名を入力してください"
inputNewDescription: "新しい説明を入力してください"
inputNewDescription: "新しい説明を入力"
inputNewFolderName: "新しいフォルダ名を入力してください"
circularReferenceFolder: "移動先のフォルダーは、移動するフォルダーのサブフォルダーです。"
hasChildFilesOrFolders: "このフォルダは空でないため、削除できません。"
@ -577,7 +577,7 @@ disablePlayer: "プレイヤーを閉じる"
expandTweet: "ツイートを展開する"
themeEditor: "テーマエディター"
description: "説明"
describeFile: "説明を付ける"
describeFile: "説明を追加"
enterFileDescription: "説明を入力"
author: "作者"
leaveConfirm: "未保存の変更があります。破棄しますか?"
@ -849,7 +849,7 @@ filter: "フィルタ"
controlPanel: "コントロールパネル"
manageAccounts: "アカウントを管理"
makeReactionsPublic: "リアクション一覧を公開する"
makeReactionsPublicDescription: "あなたがしたリアクション一覧を誰でも見れるようにします。"
makeReactionsPublicDescription: "あなたがしたリアクション一覧を誰でも見れるようにします。"
classic: "中央寄せ"
muteThread: "スレッドをミュート"
unmuteThread: "スレッドのミュートを解除"
@ -949,7 +949,7 @@ customSplashIconsDescription: "ユーザがページをロード/リロードす
showUpdates: "Firefishの更新時にポップアップを表示する"
recommendedInstances: "おすすめサーバー"
recommendedInstancesDescription: "おすすめタイムラインに表示するサーバーを改行区切りで入力してください。"
caption: "自動キャプション"
caption: "自動で説明をつける"
splash: "スプラッシュスクリーン"
updateAvailable: "アップデートがありますよ!"
swipeOnDesktop: "デスクトップでモバイルスタイルのスワイプを可能にする"
@ -1996,3 +1996,6 @@ _emojiModPerm:
add: "追加"
mod: "追加と変更"
full: "全て許可"
importZip: ZIPをインポート
emojiPackCreator: 絵文字パックの作者
exportZip: ZIPをエクスポート

View file

@ -1,7 +1,7 @@
---
_lang_: "日本語 (関西弁)"
headlineFirefish: "ノートでつながるネットワーク"
introFirefish: "ようお越しMisskeyは、オープンソースの分散型マイクロブログサービスやねん。\n「ート」を作って、いま起こっとることを共有したり、あんたについて皆に発信しよう📡\n「リアクション」機能で、皆のートに素早く反応を追加したりもできるで✌\nほな新しい世界を探検しよか🚀"
headlineFirefish: "ずっとタダでオープンソースの非中央集権型ソーシャルメディアプラットフォーム!🚀"
introFirefish: "おいでやす。Firefishは、オープンソースの分散型ソーシャルメディアプラットフォームどす。\nいま起きたはるもんを共有したり、あんさんについて皆に発信したりしとくれやす👘\n\
「リアクション」機能があるさかい、皆の投稿に素早う反応を送ることもできます🎎\nほんなら、新しい世界を探検しまひょか🎴"
monthAndDay: "{month}月 {day}日"
search: "探す"
notifications: "通知"
@ -13,10 +13,10 @@ ok: "OKや"
gotIt: "ほい"
cancel: "やめとく"
enterUsername: "ユーザー名を入れてや"
renotedBy: "{user}がRenote"
noNotes: "ノートはあらへん"
renotedBy: "{user}がブースト"
noNotes: "投稿はありまへん"
noNotifications: "通知はあらへん"
instance: "インスタンス"
instance: "サーバー"
settings: "設定"
basicSettings: "基本設定"
otherSettings: "その他の設定"
@ -44,7 +44,7 @@ copyContent: "内容をコピー"
copyLink: "リンクをコピー"
delete: "ほかす"
deleteAndEdit: "ほかして直す"
deleteAndEditConfirm: "このートをほかして書き直すんかこのートへのリアクション、Renote、返信も全部消えてまうで。"
deleteAndEditConfirm: "この投稿をほかして書き直すんか?この投稿へのリアクション、ブースト、返信もみんな消えてまうで。"
addToList: "リストに入れたる"
sendMessage: "メッセージを送る"
copyUsername: "ユーザー名をコピー"
@ -64,26 +64,28 @@ import: "インポート"
export: "エクスポート"
files: "ファイル"
download: "ダウンロード"
driveFileDeleteConfirm: "ファイル「{name}」を消してしもうてええか?このファイルを添付したノートも消えてまうで。"
driveFileDeleteConfirm: "ファイル「{name}」を消してしもうてええか?このファイルを添付した投稿も消えてまうで。"
unfollowConfirm: "{name}のフォローを解除してもええんか?"
exportRequested: "エクスポートしてな、ってリクエストしたけど、これ多分めっちゃ時間かかるで。エクスポート終わったら「ドライブ」に突っ込んどくで。"
importRequested: "インポートしてな、ってリクエストしたけど、これ多分めっちゃ時間かかるで。"
lists: "リスト"
noLists: "リストなんてあらへんで"
note: "ノート"
notes: "ノート"
note: "投稿"
notes: "投稿"
following: "フォロー"
followers: "フォロワー"
followsYou: "フォローされとるで"
createList: "リスト作る"
manageLists: "リストの管理"
error: "エラー"
somethingHappened: "なんかアカンことが起こったで"
somethingHappened: "なんやアカンことが起きたで"
retry: "もっぺんやる?"
pageLoadError: "ページの読み込みに失敗してしもうたで…"
pageLoadError: "ページの読み込みに失敗してもた… えろうすんまへん"
pageLoadErrorDescription: "これは普通、ネットワークかブラウザキャッシュが原因やからね。キャッシュをクリアするか、もうちっとだけ待ってくれへんか?"
serverIsDead: "The server is not responding. Please wait for a while before trying again."
youShouldUpgradeClient: "To display this page, please reload and use a new version client. "
serverIsDead: "The server is not responding. Please wait for a while before trying
again."
youShouldUpgradeClient: "To display this page, please reload and use a new version
client. "
enterListName: "リスト名を入れてや"
privacy: "プライバシー"
makeFollowManuallyApprove: "自分が認めた人だけがこのアカウントをフォローできるようにする"
@ -94,13 +96,13 @@ followRequests: "フォロー申請"
unfollow: "フォローやめる"
followRequestPending: "フォロー許してくれるん待っとる"
enterEmoji: "絵文字を入れてや"
renote: "Renote"
unrenote: "Renoteやめる"
renoted: "Renoteしたで。"
cantRenote: "この投稿はRenoteできへんらしい。"
cantReRenote: "Renote自体はRenoteできへんで。"
renote: "ブースト"
unrenote: "ブーストやめる"
renoted: "ブーストしたで。"
cantRenote: "この投稿はブーストでけへんらしい。"
cantReRenote: "ブースト自体はブーストでけへんで。"
quote: "引用"
pinnedNote: "ピン留めされとるノート"
pinnedNote: "ピン留めされとる投稿"
pinned: "ピン留めしとく"
you: "あんた"
clickToShow: "押したら見えるで"
@ -138,12 +140,13 @@ addEmoji: "絵文字を追加"
settingGuide: "ええ感じの設定"
cacheRemoteFiles: "リモートのファイルをキャッシュする"
cacheRemoteFilesDescription: "この設定を切っとくと、リモートファイルをキャッシュせず直リンクするようになるで。サーバーの容量は節約できるけど、サムネイルが作られんくなるから通信量が増えるで。"
flagAsBot: "Botやで"
flagAsBotDescription: "もしこのアカウントがプログラムによって運用されるんやったら、このフラグをオンにしてたのむで。オンにすると、反応の連鎖を防ぐためのフラグとして他の開発者に役立ったり、Misskeyのシステム上での扱いがBotに合ったもんになるんやで。"
flagAsCat: "Catやで"
flagAsCatDescription: "ワレ、猫ちゃんならこのフラグをつけてみ?"
flagAsBot: "ワイはBotや 🤖"
flagAsBotDescription: "もしこのアカウントがプログラムによって運用されるんやったら、このフラグをオンにしてたのむで。オンにすると、反応の連鎖を防ぐためのフラグとして他の開発者に役立ったり、Firefishのシステム上での扱いがBotに合ったもんになったりするんやで。"
flagAsCat: "ワイはCatや 🐯"
flagAsCatDescription: "自分、猫ちゃんならこのフラグつけてみ?"
flagShowTimelineReplies: "It will display the reply to the note in the timeline. "
flagShowTimelineRepliesDescription: "It will display the reply to notes other than the user notes in the timeline when you turn it on. "
flagShowTimelineRepliesDescription: "It will display the reply to notes other than
the user notes in the timeline when you turn it on. "
autoAcceptFollowed: "フォローしとるユーザーからのフォローリクエストを勝手に許可しとく"
addAccount: "アカウントを追加"
loginFailed: "ログインに失敗してしもうた…"
@ -162,7 +165,7 @@ selectUser: "ユーザーを選ぶ"
recipient: "宛先"
annotation: "注釈"
federation: "連合"
instances: "インスタンス"
instances: "サーバー"
registeredAt: "初観測"
latestRequestSentAt: "ちょっと前のリクエスト送信"
latestRequestReceivedAt: "ちょっと前のリクエスト受信"
@ -172,7 +175,7 @@ charts: "チャート"
perHour: "1時間ごと"
perDay: "1日ごと"
stopActivityDelivery: "アクティビティの配送をやめる"
blockThisInstance: "このインスタンスをブロック"
blockThisInstance: "このサーバーをブロック"
operations: "操作"
software: "ソフトウェア"
version: "バージョン"
@ -182,23 +185,23 @@ jobQueue: "ジョブキュー"
cpuAndMemory: "CPUとメモリ"
network: "ネットワーク"
disk: "ディスク"
instanceInfo: "インスタンス情報"
instanceInfo: "サーバー情報"
statistics: "統計"
clearQueue: "キューにさいなら"
clearQueueConfirmTitle: "キューをクリアしまっか?"
clearQueueConfirmText: "未配達の投稿は配送されなくなるで。通常この操作を行う必要はあらへんや。"
clearCachedFiles: "キャッシュにさいなら"
clearCachedFilesConfirm: "キャッシュされとるリモートファイルをみんなほかしてええか?"
blockedInstances: "インスタンスブロック"
blockedInstancesDescription: "ブロックしたいインスタンスのホストを改行で区切って設定してな。ブロックされてもうたインスタンスとはもう金輪際やり取りできひんくなるで。"
blockedInstances: "ブロックしたサーバー"
blockedInstancesDescription: "ブロックしたいサーバーのホストを改行で区切って設定してな。ブロックされてもうたサーバーとはもう金輪際やり取りできんくなるで。"
muteAndBlock: "ミュートとブロック"
mutedUsers: "ミュートしたユーザー"
blockedUsers: "ブロックしたユーザー"
noUsers: "ユーザーはおらへん"
editProfile: "プロフィールをいじる"
noteDeleteConfirm: "このノートを削除しまっか?"
noteDeleteConfirm: "この投稿を削除しまっか?"
pinLimitExceeded: "これ以上ピン留めできひん"
intro: "Misskeyのインストールが完了してん!管理者アカウントを作ってや。"
intro: "Firefishのインストールが完了してん!管理者アカウントを作ってや。"
done: "でけた"
processing: "処理しとる"
preview: "プレビュー"
@ -213,9 +216,9 @@ all: "みんな"
subscribing: "購読しとる"
publishing: "配信しとる"
notResponding: "応答してへんで"
instanceFollowing: "インスタンスのフォロー"
instanceFollowers: "インスタンスのフォロワー\n"
instanceUsers: "インスタンスのユーザー"
instanceFollowing: "サーバーのフォロー"
instanceFollowers: "サーバーのフォロワー"
instanceUsers: "このサーバーの利用者"
changePassword: "パスワード変える"
security: "セキュリティ"
retypedNotMatch: "そやないねん。"
@ -239,7 +242,8 @@ saved: "保存したで!"
messaging: "チャット"
upload: "アップロード"
keepOriginalUploading: "Retain the original image. "
keepOriginalUploadingDescription: "When uploading the clip, the original version will be retained. Turning it of then uploading will produce images for public use. "
keepOriginalUploadingDescription: "When uploading the clip, the original version will
be retained. Turning it of then uploading will produce images for public use. "
fromDrive: "ドライブから"
fromUrl: "URLから"
uploadFromUrl: "URLアップロード"
@ -286,7 +290,7 @@ emptyDrive: "ドライブにはなんも残っとらん"
emptyFolder: "ふぉろだーにはなんも残っとらん"
unableToDelete: "消そうおもってんけどな、あかんかったわ"
inputNewFileName: "今度のファイル名は何にするん?"
inputNewDescription: "新しいキャプションを入力しましょ"
inputNewDescription: "新しい説明文を入力しまひょ"
inputNewFolderName: "今度のフォルダ名は何にするん?"
circularReferenceFolder: "移動先のフォルダーは、移動するフォルダーのサブフォルダーや。"
hasChildFilesOrFolders: "このフォルダ、まだなんか入っとるから消されへん"
@ -305,8 +309,8 @@ unwatch: "ウォッチやめる"
accept: "ええで"
reject: "あかん"
normal: "ええ感じ"
instanceName: "インスタンス名"
instanceDescription: "インスタンスの紹介"
instanceName: "サーバー名"
instanceDescription: "サーバーの紹介"
maintainerName: "管理者の名前"
maintainerEmail: "管理者のメールアドレス"
tosUrl: "利用規約のURL"
@ -336,9 +340,9 @@ basicInfo: "基本情報"
pinnedUsers: "ピン留めしたユーザー"
pinnedUsersDescription: "「みつける」ページとかにピン留めしたいユーザーをここに書けばええんやで。他ん人との名前は改行で区切ればええんやで。"
pinnedPages: "ピン留めページ"
pinnedPagesDescription: "インスタンスのいっちゃん上にピン留めしたいページのパスを改行で区切って記述してな"
pinnedPagesDescription: "サーバーのいっちゃん上にピン留めしたいページのパスを改行で区切って記述してな"
pinnedClipId: "ピン留めするクリップのID"
pinnedNotes: "ピン留めされとるノート"
pinnedNotes: "ピン留めされとる投稿"
hcaptcha: "hCaptchaキャプチャ"
enableHcaptcha: "hCaptchaキャプチャをつけとく"
hcaptchaSiteKey: "サイトキー"
@ -355,8 +359,8 @@ antennaSource: "受信ソース(このソースは食われへん)"
antennaKeywords: "受信キーワード"
antennaExcludeKeywords: "除外キーワード"
antennaKeywordsDescription: "スペースで区切ったるとAND指定で、改行で区切ったるとOR指定や"
notifyAntenna: "新しいノートを通知すんで"
withFileAntenna: "なんか添付されたノートだけ"
notifyAntenna: "新しい投稿を通知すんで"
withFileAntenna: "ファイルが添付された投稿のみ"
enableServiceworker: "ServiceWorkerをつこて"
antennaUsersDescription: "ユーザー名を改行で区切ったってな"
caseSensitive: "大文字と小文字は別もんや"
@ -377,7 +381,7 @@ exploreFediverse: "Fediverseを探ってみる"
popularTags: "人気のタグ"
userList: "リスト"
about: "情報"
aboutFirefish: "Misskeyってなんや"
aboutFirefish: "Firefishってなんやねん"
administrator: "管理者"
token: "トークン"
twoStepAuthentication: "二段階認証"
@ -420,7 +424,7 @@ text: "テキスト"
enable: "有効にするで"
next: "次"
retype: "もっかい入力"
noteOf: "{user}のノート"
noteOf: "{user}の投稿"
inviteToGroup: "グループに招く"
quoteAttached: "引用付いとるで"
quoteQuestion: "引用として添付してもええか?"
@ -478,12 +482,13 @@ accountSettings: "アカウントの設定"
promotion: "宣伝"
promote: "宣伝"
numberOfDays: "日数"
hideThisNote: "このノートは表示せんでいい"
showFeaturedNotesInTimeline: "タイムラインにおすすめのノートを表示してや"
hideThisNote: "この投稿は表示せんでいい"
showFeaturedNotesInTimeline: "タイムラインにおすすめの投稿を表示してや"
objectStorage: "オブジェクトストレージ"
useObjectStorage: "オブジェクトストレージを使う"
objectStorageBaseUrl: "Base URL"
objectStorageBaseUrlDesc: "参照に使うにURLやで。CDNやProxyを使用してるんならそのURL、S3: 'https://<bucket>.s3.amazonaws.com'、GCSとかなら: 'https://storage.googleapis.com/<bucket>'。"
objectStorageBaseUrlDesc: "参照に使うにURLやで。CDNやProxyを使用してるんならそのURL、S3: 'https://<bucket>.s3.amazonaws.com'、GCSとかなら:
'https://storage.googleapis.com/<bucket>'。"
objectStorageBucket: "Bucket"
objectStorageBucketDesc: "使ってるサービスのbucket名を選んでな"
objectStoragePrefix: "Prefix"
@ -500,7 +505,7 @@ objectStorageSetPublicRead: "アップロードした時に'public-read'を設
serverLogs: "サーバーログ"
deleteAll: "全て削除してや"
showFixedPostForm: "タイムラインの上の方で投稿できるようにやってくれへん?"
newNoteRecived: "新しいノートがあるで"
newNoteRecived: "新しい投稿があるで"
sounds: "サウンド"
listen: "聴く"
none: "なし"
@ -523,7 +528,7 @@ sort: "仕分ける"
ascendingOrder: "小さい順"
descendingOrder: "大きい順"
scratchpad: "スクラッチパッド"
scratchpadDescription: "スクラッチパッドではAiScriptを色々試すことができるんや。Misskeyに対して色々できるコードを書いて動かしてみたり、結果を見たりできるで。"
scratchpadDescription: "スクラッチパッドではAiScriptを色々試すことができるんや。Firefishに対して色々できるコードを書いて動かしてみたり、結果を見たりできるで。"
output: "出力"
script: "スクリプト"
disablePagesScript: "Pagesのスクリプトを無効にしてや"
@ -531,7 +536,7 @@ updateRemoteUser: "リモートユーザー情報の更新してくれん?"
deleteAllFiles: "すべてのファイルを削除"
deleteAllFilesConfirm: "ホンマにすべてのファイルを削除するん?消したもんはもう戻ってこんのやで?"
removeAllFollowing: "フォローを全解除"
removeAllFollowingDescription: "{host}からのフォローをすべて解除するで。そのインスタンスが消えて無くなった時とかには便利な機能やで。"
removeAllFollowingDescription: "{host}からのフォローをすべて解除するで。そのサーバーが消えて無くなった時とかに便利な機能やで。"
userSuspended: "このユーザーは...凍結されとる。"
userSilenced: "このユーザーは...サイレンスされとる。"
yourAccountSuspendedTitle: "あんたのアカウント凍結されとるで"
@ -555,8 +560,8 @@ disablePlayer: "プレイヤーを閉じる"
expandTweet: "ツイートを展開する"
themeEditor: "テーマエディター"
description: "説明"
describeFile: "キャプションを付ける"
enterFileDescription: "キャプションを入力"
describeFile: "画像説明文を付ける"
enterFileDescription: ""
author: "作者"
leaveConfirm: "未保存の変更があるで!ほかしてええか?"
manage: "管理"
@ -595,7 +600,7 @@ testEmail: "配信テスト"
wordMute: "ワードミュート"
regexpError: "正規表現エラー"
regexpErrorDescription: "{tab}ワードミュートの{line}行目の正規表現にエラーが出てきたで:"
instanceMute: "インスタンスミュート"
instanceMute: "サーバーミュート"
userSaysSomething: "{name}が何か言ったようやで"
makeActive: "使うで"
display: "表示"
@ -621,20 +626,20 @@ sample: "サンプル"
abuseReports: "通報"
reportAbuse: "通報"
reportAbuseOf: "{name}を通報する"
fillAbuseReportDescription: "細かい通報理由を書いてなー。対象ノートがある時はそのURLも書いといてなー。"
fillAbuseReportDescription: "細かい通報理由を書いてなー。特定の投稿を通報するなら、そのURLも書いといてなー。"
abuseReported: "無事内容が送信されたみたいやで。おおきに〜。"
reporter: "通報者"
reporteeOrigin: "通報先"
reporterOrigin: "通報元"
forwardReport: "リモートインスタンスに通報を転送するで"
forwardReportIsAnonymous: "リモートインスタンスからはあんたの情報は見れへんくって、匿名のシステムアカウントとして表示されるで。"
forwardReport: "リモートサーバーに通報を転送するで"
forwardReportIsAnonymous: "リモートサーバーからはあんたの情報は見れへんくて、匿名のシステムアカウントとして表示されるで。"
send: "送信"
abuseMarkAsResolved: "対応したで"
openInNewTab: "新しいタブで開く"
openInSideView: "サイドビューで開く"
defaultNavigationBehaviour: "デフォルトのナビゲーション"
editTheseSettingsMayBreakAccount: "このへんの設定をようわからんままイジるとアカウントが壊れて使えんくなるかも知れへんで?"
instanceTicker: "ノートのインスタンス情報"
instanceTicker: "投稿のサーバー情報"
waitingFor: "{x}を待っとるで"
random: "ランダム"
system: "システム"
@ -645,16 +650,16 @@ createNew: "新しく作るで"
optional: "任意"
createNewClip: "新しいクリップを作るで"
unclip: "クリップ解除するで"
confirmToUnclipAlreadyClippedNote: "このノートはすでにクリップ「{name}」に含まれとるで。ノートをこのクリップから除外したる?"
confirmToUnclipAlreadyClippedNote: "この投稿はすでにクリップ「{name}」に含まれとるで。投稿をこのクリップから除外したる?"
public: "パブリック"
i18nInfo: "Firefishは有志によっていろんな言語に翻訳されとるで。{link}で翻訳に協力したってやー。"
manageAccessTokens: "アクセストークンの管理"
accountInfo: "アカウント情報"
notesCount: "ノートの数やで"
notesCount: "投稿の数やで"
repliesCount: "返信した数やで"
renotesCount: "Renoteした数やで"
renotesCount: "ブーストした数やで"
repliedCount: "返信された数やで"
renotedCount: "Renoteされた数やで"
renotedCount: "ブーストされた数やで"
followingCount: "フォロー数やで"
followersCount: "フォロワー数やで"
sentReactionsCount: "リアクションした数やで"
@ -666,15 +671,15 @@ no: "いいえ"
driveFilesCount: "ドライブのファイル数"
driveUsage: "ドライブ使用量やで"
noCrawle: "クローラーによるインデックスを拒否するで"
noCrawleDescription: "検索エンジンにあんたのユーザーページ、ート、Pagesとかのコンテンツを登録(インデックス)せぇへんように頼むで。"
lockedAccountInfo: "フォローを承認制にしとっても、ノートの公開範囲を「フォロワー」にせぇへん限り、誰でもあんたのノートを見れるで。"
noCrawleDescription: "検索エンジンにあんたのプロフィール、投稿、ページとかのコンテンツを登録(インデックス)せぇへんように頼むで。"
lockedAccountInfo: "フォローを承認制にしとっても、投稿の公開範囲を「フォロワー」にせん限り、誰でもあんたの投稿を見れるで。"
alwaysMarkSensitive: "デフォルトでメディアを閲覧注意にするで"
loadRawImages: "添付画像のサムネイルをオリジナル画質にするで"
disableShowingAnimatedImages: "アニメーション画像を再生しやへんで"
verificationEmailSent: "無事確認のメールを送れたで。メールに書いてあるリンクにアクセスして、設定を完了してなー。"
notSet: "未設定"
emailVerified: "メールアドレスは確認されたで"
noteFavoritesCount: "お気に入りノートの数やで"
noteFavoritesCount: "お気に入り投稿の数やで"
pageLikesCount: "Pageにええやんと思った数"
pageLikedCount: "Pageにええやんと思ってくれた数"
contact: "連絡先"
@ -684,7 +689,7 @@ experimentalFeatures: "実験的機能やで"
developer: "開発者やで"
makeExplorable: "アカウントを見つけやすくするで"
makeExplorableDescription: "オフにすると、「みつける」にアカウントが載らんくなるで。"
showGapBetweenNotesInTimeline: "タイムラインのノートを放して表示するで"
showGapBetweenNotesInTimeline: "タイムライン上の投稿を離して表示するで"
duplicate: "複製"
left: "左"
center: "中央"
@ -696,9 +701,10 @@ showTitlebar: "タイトルバーを見せる"
clearCache: "キャッシュをほかす"
onlineUsersCount: "{n}人が起きとるで"
nUsers: "{n}ユーザー"
nNotes: "{n}ノート"
nNotes: "{n}投稿"
sendErrorReports: "エラーリポートを送る"
sendErrorReportsDescription: "オンにしたら、なんか変なことが起きたときにエラーの詳細がMisskeyに共有されて、ソフトウェアの品質向上に役立てられるんや。エラー情報には、OSのバージョン、ブラウザの種類、行動履歴などが含まれるで。"
sendErrorReportsDescription: "オンにしたら、なんやけったいなことが起きたときにエラーの詳細がFirefishに共有されて、ソフトウェアの品質向上に役立てられるんや。\n\
エラー情報には、OSのバージョン、ブラウザの種類、行動履歴などが含まれるで。"
myTheme: "マイテーマ"
backgroundColor: "背景"
accentColor: "アクセント"
@ -722,7 +728,7 @@ capacity: "容量"
inUse: "使用中"
editCode: "コードを編集"
apply: "適用"
receiveAnnouncementFromInstance: "インスタンスからのお知らせを受け取る"
receiveAnnouncementFromInstance: "サーバーからのお知らせを受け取る"
emailNotification: "メール通知"
publish: "公開"
inChannelSearch: "チャンネル内検索"
@ -737,7 +743,7 @@ unlikeConfirm: "いいね解除するんか?"
fullView: "フルビュー"
quitFullView: "フルビュー解除"
addDescription: "説明を追加するで"
userPagePinTip: "個々のノートのメニューから「ピン留め」を選んどくと、ここにノートを表示しておけるで。"
userPagePinTip: "個々の投稿のメニューから「ピン留め」を選んどくと、ここにそいつを表示しておけるで。"
notSpecifiedMentionWarning: "宛先に含まれてへんメンションがあるで"
info: "情報"
userInfo: "ユーザー情報やで"
@ -750,7 +756,7 @@ active: "アクティブ"
offline: "オフライン"
notRecommended: "あんま推奨しやんで"
botProtection: "Botプロテクション"
instanceBlocking: "インスタンスブロック"
instanceBlocking: "連合の管理"
selectAccount: "アカウントを選んでなー"
switchAccount: "アカウントを変えるで"
enabled: "有効"
@ -767,7 +773,7 @@ postToGallery: "ギャラリーへ投稿"
gallery: "ギャラリー"
recentPosts: "最近の投稿"
popularPosts: "人気の投稿"
shareWithNote: "ノートで共有"
shareWithNote: "投稿で共有"
ads: "広告"
expiration: "期限"
memo: "メモ"
@ -789,7 +795,7 @@ hashtags: "ハッシュタグ"
troubleshooting: "トラブルシューティング"
useBlurEffect: "UIにぼかし効果を使うで"
learnMore: "詳しく"
misskeyUpdated: "Misskeyが更新されたで\nモデレーターの人らに感謝せなあかんで"
misskeyUpdated: "Firefishが更新されたで\nモデレーターの人らに感謝やね"
whatIsNew: "更新情報を見るで"
translate: "翻訳"
translatedFrom: "{x}から翻訳するで"
@ -834,7 +840,7 @@ cannotUploadBecauseInappropriate: "不適切な内容を含むかもしれへん
cannotUploadBecauseNoFreeSpace: "ドライブの空き容量が無いでアップロードできまへん。"
beta: "ベータ"
enableAutoSensitive: "自動NSFW判定"
enableAutoSensitiveDescription: "使える時は、機械学習を使って自動でメディアにNSFWフラグを設定するで。この機能をオフにしても、インスタンスによっては自動で設定されることがあるで。"
enableAutoSensitiveDescription: "いけるときは、機械学習を使って自動でメディアにNSFWフラグを設定するで。この機能をオフにしても、サーバーによっては自動で設定されることがあるで。"
activeEmailValidationDescription: "ユーザーのメールアドレスのバリデーションを、捨てアドかどうかや実際に通信可能かどうかとかを判定して積極的に行うで。オフにすると単に文字列として正しいかどうかだけチェックするで。"
navbar: "ナビゲーションバー"
shuffle: "シャッフルするで"
@ -868,14 +874,15 @@ _registry:
domain: "ドメイン"
createKey: "キーを作る"
_aboutFirefish:
about: "Misskeyはsyuiloが2014年からずっと作ってはる、オープンソースなソフトウェアや。"
about: "Firefishは、ThatOneCalculatorが2022年にMisskeyをいじって作った、オープンなソースのソフトウェアや。"
contributors: "主な貢献者"
allContributors: "全ての貢献者"
source: "ソースコード"
translation: "Misskeyを翻訳"
donate: "Misskeyに寄付"
translation: "Firefishを翻訳"
donate: "Firefishに寄付"
morePatrons: "他にもぎょうさんの人からサポートしてもろてんねん。ほんまおおきに🥰"
patrons: "支援者"
misskeyContributors: フォーク元のMisskeyを作らはった人ら
_mfm:
cheatSheet: "MFMチートシート"
mention: "メンション"
@ -896,6 +903,7 @@ _mfm:
blur: "ぼかし"
font: "フォント"
rotate: "回転"
intro: MFMは、MisskeyやFirefish、Akkomaなどの様々な場所で使用できるマークアップ言語なんよ。ここでは、MFMで使用可能な構文一覧が確認できるで。
_instanceTicker:
none: "表示せん"
remote: "リモートユーザーに表示"
@ -958,7 +966,7 @@ _theme:
hashtag: "ハッシュタグ"
mention: "メンション"
mentionMe: "うち宛てのメンション"
renote: "Renote"
renote: "ブースト"
modalBg: "モーダルの背景"
divider: "分割線"
scrollbarHandle: "スクロールバーの取っ手"
@ -985,8 +993,8 @@ _theme:
accentLighten: "アクセント (明るめ)"
fgHighlighted: "強調されとる文字"
_sfx:
note: "ノート"
noteMy: "ノート(自分)"
note: "投稿"
noteMy: "投稿(自分)"
notification: "通知"
chat: "チャット"
_ago:
@ -1017,8 +1025,8 @@ _permissions:
_auth:
permissionAsk: "このアプリは次の権限を要求しとるで"
_antennaSources:
all: "みんなのノート"
homeTimeline: "フォローしとるユーザーのノート"
all: "みんなの投稿"
homeTimeline: "フォローしとるユーザーの投稿"
_weekday:
sunday: "日曜日"
monday: "月曜日"
@ -1072,7 +1080,7 @@ _profile:
name: "名前"
username: "ユーザー名"
_exportOrImport:
allNotes: "全てのノート"
allNotes: "すべての投稿"
followingList: "フォロー"
muteList: "ミュート"
blockingList: "ブロック"
@ -1082,10 +1090,10 @@ _charts:
apRequest: "リクエスト"
usersTotal: "ユーザーの合計"
activeUsers: "アクティブユーザー数"
notesIncDec: "ノートの増減"
localNotesIncDec: "ローカルのノートの増減"
remoteNotesIncDec: "リモートのノートの増減"
notesTotal: "ノートの合計"
notesIncDec: "投稿の増減"
localNotesIncDec: "ローカルの投稿の増減"
remoteNotesIncDec: "リモートの投稿の増減"
notesTotal: "投稿の合計"
filesIncDec: "ファイルの増減"
filesTotal: "ファイルの合計"
storageUsageIncDec: "ストレージ使用量の増減"
@ -1094,8 +1102,8 @@ _instanceCharts:
requests: "リクエスト"
users: "ユーザーの増減"
usersTotal: "ユーザーの累積"
notes: "ノートの増減"
notesTotal: "ノートの累積"
notes: "投稿の増減"
notesTotal: "投稿の累積"
ff: "フォロー/フォロワーの増減"
ffTotal: "フォロー/フォロワーの累積"
cacheSize: "キャッシュサイズの増減"
@ -1165,9 +1173,9 @@ _pages:
id: "キャンバスID"
width: "幅"
height: "高さ"
note: "ノート埋め込み"
note: "投稿の埋め込み"
_note:
id: "ノートID"
id: "投稿のID"
detailed: "詳細な表示"
switch: "スイッチ"
_switch:
@ -1385,14 +1393,14 @@ _notification:
all: "すべて"
follow: "フォロー"
mention: "メンション"
renote: "Renote"
renote: "ブースト"
quote: "引用"
reaction: "リアクション"
receiveFollowRequest: "フォロー許可してほしいみたいやで"
followRequestAccepted: "フォローが受理されたで"
_actions:
reply: "返事"
renote: "Renote"
renote: "ブースト"
_deck:
alwaysShowMainColumn: "いつもメインカラムを表示"
columnAlign: "カラムの寄せ"
@ -1413,3 +1421,28 @@ _deck:
list: "リスト"
mentions: "あんた宛て"
direct: "ダイレクト"
_experiments:
postImportsCaption:
ユーザーが過去の投稿をFirefish・Misskey・Mastodon・Akkoma・Pleromaからインポートできるようにするで。キューが溜まっとるときにインポートするとサーバーに負荷がかかるかもしれんね。
searchPlaceholder: Firefishを検索
addInstance: サーバーを追加
editNote: 投稿を編集
edited: '編集済み: {date} {time}'
deleted: 削除済み
noThankYou: いらんわ
_tutorial:
step3_1: ほな、何人かフォローしてみまひょ
step1_1: おこしやす
step1_2: 使い始める前に、いくつか設定を済ませまひょ。すぐできますえ。
step2_1: 最初に、あんさんのプロフィールを作りまひょ
step2_2: プロフィールを設定しはることで、他ん人があんさんの投稿を見たり、フォローしたりするときの助けになってます。
_postForm:
_placeholders:
b: なんかおましたか?
e: ここに書いとくれやす
c: なに考えとりまっか?
d: なんや言いたいんちゃいますか?
f: あんさん書くんを待っとるんどす...
flagSpeakAsCat: 猫弁で話す
flagSpeakAsCatDescription: 猫モードが有効の場合にオンにすると、ワレの投稿の「な」を「にゃ」に変換するで。
welcomeBackWithName: おおきに、{name}はん

View file

@ -44,7 +44,7 @@ lists: Lister
listsDesc: Lister lar deg lage tidslinjer med utvalgte brukere. De kan hentes frem
fra tidslinje-siden.
deleted: Slettet
editNote: Rediger notat
editNote: Rediger post
followsYou: Følger deg
createList: Lag liste
newer: nyere
@ -155,7 +155,7 @@ drive: Disk
renameFile: Omdøp fil
folderName: Katalognavn
createFolder: Opprett katalog
inputNewDescription: Oppgi ny bildetekst
inputNewDescription: Skriv ny beskrivelse
inputNewFolderName: Oppgi nytt katalognavn
copyUrl: Kopier URL
hcaptchaSiteKey: hCaptcha-nøkkel for nettstedet
@ -409,7 +409,7 @@ 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
enterFileDescription: Skriv inn beskrivelse
leaveConfirm: Det er ulagrede endringer. Vil du forkaste dem?
enableAll: Slå på alle
generateAccessToken: Generer adgangstegn
@ -506,7 +506,7 @@ yourAccountSuspendedDescription: Denne kontoen er suspendert fordi den har brutt
useCw: Skjul innhold
enablePlayer: Åpne videospiller
disablePlayer: Lukk videospiller
describeFile: Legg til tekst
describeFile: Legg til beskrivelse
author: Forfatter
useFullReactionPicker: Bruk reaksjonsvelger i full størrelse
width: Bredde
@ -718,3 +718,405 @@ 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
scratchpadDescription: Kladdeblokka gir deg et miljø for å eksperimentere med AiScript.
Du kan skrive, kjøre og sjekke resultatene av at koden interagerer med Firefish.
disablePagesScript: Slå av AiScript på Sider
expandTweet: Ekspander tweet
public: Offentlig
clearCache: Slett mellomlager
onlineUsersCount: '{n} brukere er innlogget'
nNotes: '{n} poster'
sendErrorReports: Send feilmeldinger
deleteConfirm: Virkelig slette?
latestVersion: Nyeste versjon
receiveAnnouncementFromInstance: Motta varsler fra denne tjeneren
inChannelSearch: Søk i kanal
selectAccount: Velg konto
switch: Bytt
instanceDefaultDarkTheme: Standard mørkt tema på tjeneren
oneDay: En dag
driveCapOverrideCaption: Tilbakestill kapasiteten til standardverdien ved å legge
inn en verdi på 0 eller lavere.
sendModMail: Send modereringsvarsel
enableServerMachineStats: Slå på hardware-statistikk for tjeneren
_gallery:
liked: Likte poster
unlike: Fjern lik
my: Mitt galleri
like: Lik
_preferencesBackups:
loadFile: Last fra fil
cannotSave: Lagring feilet
deleteConfirm: Vil du slette sikkerhetskopien "{name}"?
saveConfirm: Lagre sikkerhetskopi som "{name}"?
noBackups: Ingen sikkerhetskopier er tatt. Du kan ta en backup av klientinnstillingene
dine på denne tjeneren ved å trykke "Lag ny sikkerhetskopi".
applyConfirm: Ønsker du å laste inn sikkerhetskopien "{name}" på denne enheten?
Eksisterende innstillinger vil bli overskrevet.
save: Lagre endringer
nameAlreadyExists: En sikkerhetskopi med navnet "{name}" finnes allerede. Skriv
inn et annet navn.
createdAt: 'Opprettet: {date} {time}'
apply: Bruk på denne enheten
renameConfirm: Endre navn på sikkerhetskopien fra "{old}" to "{new}"?
list: Opprettede sikkerhetskopier
saveNew: Ta ny sikkerhetskopi
inputName: Gi sikkerhetskopien et navn
updatedAt: 'Oppdatert: {date} {time}'
cannotLoad: Innlasting feilet
invalidFile: Ugyldig filformat
_ad:
back: Tilbake
reduceFrequencyOfThisAd: Vis annonsen sjeldnere
_mfm:
cheatSheet: Jukseark for tekstmarkering (MFM)
stop: Stopp animert markeringsspråk (MFM)
warn: Markeringsspråket (MFM) kan inneholde bevegelige eller blinkende animasjoner
alwaysPlay: Alltid spill av animert tekstmarkering (MFM)
play: Spill animert markeringsspråk (MFM)
intro: MFM er et markeringsspråk som burkes av Misskey, Firefish, Akkoma og andre.
Her kan du se en liste over tilgjengelig MFM-syntaks.
reactionPickerSkinTone: Foretrukket hudfarge i emojier
switchUi: Visningsoppsett
usageAmount: Bruk
memo: Memo
priority: Prioritet
high: Høy
secureMode: Sikker modus (Autorisert henting)
requireAdminForView: Du må logge inn på en administratorkonto for å se dette.
typeToConfirm: Skriv inn {x} for å bekrefte
replayTutorial: Kjør introduksjon på nytt
moveTo: Flytt denne kontoen til en ny konto
objectStorageBucketDesc: Skriv inn navnet på bøtta hos lagringstjenesten.
notRecommended: Ikke anbefalt
voteConfirm: Bekreft din stemme på "{choice}"?
oneHour: En time
_plugin:
installWarn: Ikke installer utvidelser du ikke stoler på.
install: Installer innstikk
manage: Oppsett av innstikk
preventAiLearning: Hindre tråling fra AI-boter
reporterOrigin: Kilden til den som rapporterer
center: Sentrert
wide: Bred
value: Verdi
createdAt: Opprettet
active: Aktiv
hideOnlineStatus: Skjul om du er pålogget
troubleshooting: Problemløsing
useBlurEffect: Bruk diffuseringseffekter i brukergrensesnittet
learnMore: Lær mer
usernameInfo: Et navn som identifiserer din konto på denne tjeneren. Du kan bruke
alfabetet (a-z,A-Z), sifre (0-9) og understrek (_). Brukernavn kan ikke endres senere.
resolved: Løst
unresolved: Uløst
welcomeBackWithName: Velkommen tilbake, {name}
clickToFinishEmailVerification: Klikk [{ok}] for å fullføre epost-verifisering.
cropImage: Beskjær bilde
numberOfPageCacheDescription: En økning i dette tallet vil gjøre brukeropplevelsen
bedre, men gi mer jobb til tjeneren og kreve mer minne.
logoutConfirm: Vil du logge ut?
numberOfPageCache: Antall mellomlagrede sider
lastActiveDate: Sist brukt
refreshInterval: 'Oppdateringsintervall '
swipeOnDesktop: Tillat mobil-lignende sveiping på skrivebords-PC
migration: Migrering
useDrawerReactionPickerForMobile: Vis reaksjosnvelger som en skuff på mobil
numberOfColumn: Antall kolonner
searchByGoogle: Søk
oneWeek: En uke
file: Fil
recentNHours: Siste {n} timer
noEmailServerWarning: E-post-tjener er ikke konfigurert.
thereIsUnresolvedAbuseReportWarning: Det er uløste rapporter.
colored: I farger
recommendedInstancesDescription: Anbefalte tjenere skilt med linjeskift for visning
i anbefalt-tidslinjen.
caption: Automatisk beskrivelse
updateAvailable: En oppdatering kan være tilgjengelig!
accentColor: Uthevet farge
textColor: Skriftfarge
saveAs: Lagre som...
swipeOnMobile: Tillat sveiping mellom sider
_accountDelete:
inProgress: Sletting pågår
remote: Ekstern
total: Total
registry: Register
closeAccount: Avslutt konto
currentVersion: Nåværende versjon
fullView: Full visning
gallery: Galleri
emailNotConfiguredWarning: E-post-adresse er ikke satt.
allowedInstancesDescription: Tjenernavn for tjenere som skal hvitelistes. En per linje.
(Vil bare bli brukt i privat modus).
previewNoteText: Forhåndsvisning
recentNDays: Siste {n} dager
indexPosts: Indekser poster
objectStorageUseProxy: Koble til gjennom en mellomtjener
objectStorageUseProxyDesc: Skru av dette dersom du ikke vil bruke mellomtjenere for
API-oppkoblinger
masterVolume: Hovedvolum
script: Skript
divider: Skille
addItem: Legg til element
manage: Oppsett
notificationType: Varseltype
useBlurEffectForModal: Bruk diffus-effekt for modale brukergrensesnitt-elementer
driveFilesCount: Antall filer på Disk
showGapBetweenNotesInTimeline: Legg inn et tomrom mellom postene i tidslinjen
newVersionOfClientAvailable: En nyere versjon av klienten er tilgjengelig.
capacity: Kapasitet
inUse: Brukt
publish: Publiser
quickAction: Hurtigvalg
privateMode: Privat modus
customCss: Egendefinert CSS
allowedInstances: Hvitelistede tjenere
lastCommunication: Siste kommunikasjon
breakFollowConfirm: Er du sikker på at du vil fjerne følgeren?
filter: Filter
makeReactionsPublicDescription: Dette vil gjøre listen over dine tidligere reaksjoner
synlige for alle.
indefinitely: Permanent
tenMinutes: 10 minutter
_email:
_follow:
title: Du har en ny følger
_receiveFollowRequest:
title: Du har mottatt en følgeforespørsel
_registry:
key: Nøkkel
scope: Omfang
domain: Domene
createKey: Opprettet nøkkel
keys: Nøkler
sendErrorReportsDescription: "Detaljert feilinformasjon vli bli delt med utviklerne
av Firefish, noe som hjelper til med feilretting og forbedring av programmet.\n
- Dette inkluderer informasjon som f.eks. versjonen på operativsystemet og nettleseren
din, og aktiviteten din i Firefish."
_aboutFirefish:
translation: Oversett Firefish
donate: Donér til Firefish
donateTitle: Liker du Firefish?
pleaseDonateToFirefish: Du kan vurdere å donere en slant til Firefish for å støtte
videre utvikling og feilretting.
donateHost: Donér til {host}
morePatrons: Vi er også takknemlige for bidragene fra mange andre som ikke er listet
her. Takk til dere alle! 🥰
contributors: Hovedutviklere
source: Kildekode
allContributors: Alle bidragsytere
misskeyContributors: Misskeys bidragsytere
pleaseDonateToHost: Du kan også vurdere å donere til hjemme-tjeneren din, {host},
for å hjelpe dem med driftskostnadene for tjenesten.
about: Firefish ble opprettet av ThatOneCalculator i 2022, basert på Misskey.
sponsors: Firefishs sponsorer
patrons: Firefishs patroner
patronsList: Listen er kronologisk, ikke etter donert beløp. Doner med lenken over
for å få navnet ditt her!
isBot: Denne kontoen er en bot
_nsfw:
respect: Skjul NSFW-merket media
force: Skjul alle media
ignore: Ikke skjul NSFW-media
disableAnimatedMfm: Slå av animert markeringsspråk
objectStorageBucket: Bøtte
scratchpad: Kladdeblokk
plugins: Innstikk
createNew: Lag ny
makeExplorable: Gjør kontoen synlig i "Utforsk"
needReloadToApply: Siden må lastes på nytt for at denne endringen skal tre inn.
customCssWarn: Bruk denne innstillingen bare hvis du vet hva den gjør. Feil innstilling
kan få klienten til å ikke fungere som den skal.
low: Lav
global: Global
recommended: Anbefalt
instanceSecurity: Tjenersikkerhet
squareAvatars: Vis firkantede avatarer
deleteAccount: Slett konto
customKaTeXMacro: Egne KaTeX-makroer
size: Størrelse
fast: Raskt
showAdminUpdates: Indikerer at en ny versjon av Firefish er tilgjengelig (bare admin)
moveAccount: Flytt konto!
license: Lisens
wordMute: Ordstumming
reporteeOrigin: Kilden til den som rapporteres
accountInfo: Kontoinformasjon
driveUsage: Brukt diskplass
noCrawle: Stopp robot-indeksering
noCrawleDescription: Be søkemotorer om å ikke indeksere din profil, poster, Sider
etc.
narrow: Smal
reloadToApplySetting: Denne innstillingen aktiveres ikke før du laster siden på nytt.
Vil du gjøre det nå?
showTitlebar: Vis tittellinje
nUsers: '{n} brukere'
myTheme: Mitt tema
backgroundColor: Bakgrunnsfarge
advanced: Avansert
updatedAt: Oppdatert
editCode: Rediger kode
addDescription: Legg til beskrivelse
userPagePinTip: Du kan vise poster her ved å klikke "Fest til profil" fra menyen til
en post.
unknown: Ukjent
onlineStatus: Påkoblet status
online: Pålogget
offline: Ikke pålogget
instanceBlocking: Innstillinger for føderering
accounts: Kontoer
noBotProtectionWarning: Bot-beskyttelse er ikke konfigurert.
configure: Konfigurer
postToGallery: Lag ny galleripost
recentPosts: Nylige sider
popularPosts: Populære sider
shareWithNote: Del med post
expiration: Frist
middle: Medium
sent: Sendt
makeReactionsPublic: La reaksjonshistorikken være offentlig
classic: Sentrert
muteThread: Stum en tråd
ffVisibilityDescription: Lar deg konfigurere hvem som kan se hvem du følger og hvem
som følger deg.
continueThread: Fortsett tråd
deleteAccountConfirm: Dette vil slette kontoen, og det går ikke å omgjøre etterpå.
Fortsette?
hide: Skjul
ffVisibility: Synlighet av følgere og folk du følger
leaveGroup: Forlat gruppe
leaveGroupConfirm: Er du sikker på at du vil forlate "{name}"?
overridedDeviceKind: Enhetstype
smartphone: Smarttelefon
tablet: Nettbrett
auto: Automatisk
image: Bilde
video: Video
driveCapOverrideLabel: Endre brukerens lagringskapasitet
isSystemAccount: Denne kontoen er opprettet og kontrollert av systemet. Ikke moderer,
rediger, slett eller på annen måte endre noe ved denne kontoen. Tjeneren kan slutte
å virke som den skal.
document: Dokumentasjon
statusbar: Statuslinje
pleaseSelect: Velg en innstilling
reverse: Reverser
slow: Sakte
moveFromLabel: 'Kontoen du flytter fra:'
silencedWarning: Denne siden vises fordi disse brukerne er fra tjenere administratoren
din har stummet, så de kan potensielt inneholde spam.
ads: Samfunnsbanner
_forgotPassword:
contactAdmin: Denne tjeneren støtter ikke bruk av e-post-adresser for gjenoppretting
av passord. Kontakt administratoren for tjeneren.
enterEmail: Skriv inn e-post-adressen du brukte da du registrerte kontoen. Du vil
motta en e-post med en lenke som lar deg endre passordet.
ifNoEmail: Dersom du ikke oppga en e-post-adresse da du registrerte kontoen, kontakt
administrator i stedet.
breakFollow: Slett følger
unmuteThread: Fjern stumming av tråden
incorrectPassword: Feil passord.
logoImageUrl: URL til logo-bilde
apps: Apper
audio: Lyd
moveToLabel: 'Kontoen du flytter til:'
moveFrom: Flytt fra en annen konto til denne kontoen
migrationConfirm: "Er du helt sikker på at du ønsker å flytte kontoen din til {account}?
Når du har gjort dette kan du ikke omgjøre det, og du vil ikke kunne bruke kontoen
normalt etterpå.\nPass på at du setter den kontoen du er innlogget på her som kontoen
du flytter fra."
jumpToSpecifiedDate: Hopp til en gitt dato
showingPastTimeline: Du ser nå en gammel tidslinje
noMaintainerInformationWarning: Eierinformasjon er ikke konfigueret.
notSpecifiedMentionWarning: Denne posten inneholder nevnelser av brukere som ikke
er inkludert som mottakere
saveConfirm: Lagre endringer?
clear: Tøm
switchAccount: Bytt konto
enabled: Påslått
disabled: Avslått
user: Bruker
administration: Konfigurasjon
invalidValue: Ugyldig verdi.
youAreRunningUpToDateClient: Du bruker nyeste versjon av klienten.
noteId: Post-id
noGraze: Slå av "Graze for Mastodon"-utdivdelsen i nettleseren. Den vil forstyrre
Firefish.
isModerator: Moderator
isAdmin: Administrator
objectStorageS3ForcePathStyle: Bruk sti-baserte URL-er til endepunktene
objectStorageS3ForcePathStyleDesc: Skru på dette for å lage endpunkts-URL-er i formatet
's3.amazonaws.com/<bøtte>/' i stedet for '<bøtte>.s3.amazonaws.com'.
output: Utputt
forwardReport: Videresend rapport til ekstern tjener
forwardReportIsAnonymous: I stedet for din konto vil en anonym systemkonto bli vist
som rapportør på den eksterne tjeneren.
optional: Valgfritt
manageAccessTokens: Styr adgangstegn
experimentalFeatures: Eksperimentelle funksjoner
developer: Utvikler
duplicate: Dupliser
left: Venstre
makeExplorableDescription: Dersom du slår av denne vil kontoen din ikke dukke opp
under "Utforsk".
apply: Bruk
emailNotification: Epostvarsler
useReactionPickerForContextMenu: Åpne reaksjonsvelger med høyreklikk
typingUsers: '{users} skriver'
markAllAsRead: Marker alle som lest
goBack: Tilbake
info: Om
userInfo: Brukerinformasjon
hideOnlineStatusDescription: Å skjule hvorvidt du er pålogget vil redusere enkelheten
av enkelte funksjoner slik som søk.
privateModeInfo: Bare hvitelistede tjenere kan federere med din tjener om du slår
på denne. Alle poster vil bli skjult for andre.
received: Mottatt
searchResult: Søkeresultater
hashtags: Emneknagger
keepCw: Behold innholdsadvarsler
misskeyUpdated: Firefish er oppdatert!
whatIsNew: Vis endringer
translate: Oversett
translatedFrom: Oversatt fra {x}
itsOn: Påslått
itsOff: Avslått
emailRequiredForSignup: Krev e-post-adresse for registrering
unread: Ulest
controlPanel: Kontrollpanel
manageAccounts: Styr kontoer
mutePeriod: Periode for stumming
instanceDefaultLightTheme: Standard lyst tema på tjeneren
reflectMayTakeTime: Det kan ta litt tid før endringen inntrer.
failedToFetchAccountInformation: Klarte ikke å hente kontoinformasjon
cropImageAsk: Ønsker du å beskjære dette bildet?
recommendedInstances: Anbefalte tjenere
moveAccountDescription: Denne prosessen er irreversibel! Vær sikker på at du har satt
opp et alias for denne kontoen på den nye kontoen før du fortsetter. Skriv inn navnet
på kontoen på formen @person@server.com
moveFromDescription: Dette vil sette opp et alias for din gamle kontoen slik at du
kan flytte fra den gamle kontoen til denne. Gjør dette FØR du flytter fra den gamle
kontoen. Skriv inn den gamle kontoen på formen @person@server.com
defaultReaction: Standard emoji-reaksjon for utgående og innkommende poster
indexFrom: Indekser poster fra post-id og fremover
indexNotice: Indekserer. Dette vil sannsynligvis ta litt tid, ikke restart tjeneren
før det har gått minst en time.
indexFromDescription: La stå tom for å indeksere alle poster
customKaTeXMacroDescription: 'Sett opp makroer for å skrive matematiske uttrykk enkelt.
Notasjonen følger LaTeX-kommandoer og er skrevet som \newcommand{\ navn}{uttrykk}
eller \newcommand{\navn}{antall argumenter}{uttrykk}. For eksempel vil \newcommand{\add}{2}{#1
+ #2} vil ekspandere \add{3}{foo} til 3 + foo. Klammeparentesene rundt makroen kan
også endres til parenteser eller hakeparenteser. Dette påvirker hvilken parentestype
du bruker for argumenter. En og bare en makro kan defineres pr linje, og du kan
ikke ha linjeskift inni definisjonen. Linjer som ikke inneholder gyldige makroer
vil bli ignorert. Bare enkle streng-erstatnings-makroer er støttet; avansert syntaks
f.eks. med flykontroll er ikke tillatt.'
signupsDisabled: Registreringer av nye konti på denne tjeneren er slått av, men du
kan alltids registrere deg på en annen tjener! Hvis du har en invitasjonskode for
denne tjeneren, skriv den inn under.
findOtherInstance: Finn en annen tjener
preventAiLearningDescription: Ber tredjeparts AI-språkmodeller om å ikke bruke innhold
du laster opp, sliks om poster og bilder.
enableCustomKaTeXMacro: Slå på egne KaTeX-makroer
showPopup: Varsle brukere med oppsprettsvindu

View file

@ -304,7 +304,7 @@ emptyDrive: "Диск пуст"
emptyFolder: "Папка пуста"
unableToDelete: "Удаление невозможно"
inputNewFileName: "Введите имя нового файла"
inputNewDescription: "Введите новую подпись"
inputNewDescription: "Введите новое описание"
inputNewFolderName: "Пожалуйста, введите новое имя папки"
circularReferenceFolder: "Вы пытаетесь переместить папку внутрь себя."
hasChildFilesOrFolders: "Эта папка не пуста и не может быть удалена."
@ -513,7 +513,7 @@ objectStorageBaseUrlDesc: "URL используемый для примера.
CDN или прокси, если вы используете любой из них.\nДля S3 используйте 'https://<bucket>.s3.amazonaws.com',
а для GCS и подобных сервисов используйте 'https://storage.googleapis.com/<bucket>',
и т.п."
objectStorageBucket: "Bucket"
objectStorageBucket: "Хранилище (Bucket)"
objectStorageBucketDesc: "Укажите название контейнера (Bucket) который используется
на выбранном сервисе."
objectStoragePrefix: "Префикс"
@ -596,8 +596,8 @@ disablePlayer: "Выключить проигрыватель"
expandTweet: "Развернуть твит"
themeEditor: "Редактор темы оформления"
description: "Описание"
describeFile: "Добавить подпись"
enterFileDescription: "Введите подпись"
describeFile: "Добавить описание"
enterFileDescription: "Введите описание"
author: "Автор"
leaveConfirm: "Вы не сохранили изменения. Хотите выйти и потерять их?"
manage: "Управление"
@ -826,7 +826,7 @@ gallery: "Галерея"
recentPosts: "Недавние публикации"
popularPosts: "Популярные публикации"
shareWithNote: "Поделиться постом"
ads: "Реклама"
ads: "Баннеры сообщества"
expiration: "Опрос длится"
memo: "Памятка"
priority: "Приоритет"
@ -1001,6 +1001,7 @@ _aboutFirefish:
pleaseDonateToHost: Также не забудьте поддержать ваш домашний сервер {host}, чтобы
помочь с его операционными расходами.
donateHost: Пожертвовать на {host}
misskeyContributors: Контрибьюторы Misskey
_nsfw:
respect: "Скрывать содержимое не для всех"
ignore: "Показывать содержимое не для всех"
@ -1024,7 +1025,7 @@ _mfm:
boldDescription: "Выделяет текст, делая буквы жирнее."
small: "Мелкий шрифт"
smallDescription: "Делает текст маленьким и незаметным."
center: "Выровнять элементы по центру"
center: "По центру"
centerDescription: "Так можно выровнять что-то по центру."
inlineCode: "Программа (в тексте)"
inlineCodeDescription: "Подсвечивает фрагмент программы внутри сплошного текста."
@ -1267,8 +1268,8 @@ _tutorial:
step6_1: "Итак, что это за место?"
step6_2: "Ну, вы не просто присоединились к Firefish. Вы вошли в Fediverse, взаимосвязанную
сеть из тысяч серверов."
step6_3: "Каждый сервер работает по-своему, и не на всех серверах работает Firefish.
Но этот работает! Это немного сложно, но вы быстро разберетесь."
step6_3: "Каждый сервер работает по-своему, и не все сервера работают на базе Firefish.
Но этот работает! Это сложновато, но вы быстро разберетесь."
step6_4: "Теперь идите, изучайте и развлекайтесь!"
_2fa:
alreadyRegistered: "Двухфакторная аутентификация уже настроена."
@ -1475,7 +1476,7 @@ _charts:
remoteNotesIncDec: "Изменения числа постов с других сайтов"
notesTotal: "Общее количество постов"
filesIncDec: "Изменения числа файлов"
filesTotal: "Суммарное количество файлов"
filesTotal: "Общее количество файлов"
storageUsageIncDec: "Изменения заполнения хранилища"
storageUsageTotal: "Суммарное заполнение хранилища"
_instanceCharts:
@ -1903,12 +1904,12 @@ customMOTDDescription: Пользовательские сообщения дл
загружает / перезагружает страницу.
recommendedInstancesDescription: Рекомендуемые инстансы, разделенные разрывами строк,
должны отображаться на рекомендуемой ленте.
caption: Автоматическая подпись
caption: Автоматическое описание
splash: Заставка
updateAvailable: Возможно, доступно обновление!
move: Переместить
swipeOnDesktop: Разрешить свайпы в мобильном стиле на десктопе
showAds: Показывать рекламу
showAds: Показывать баннеры сообщества
noEmailServerWarning: Почтовый сервер не настроен.
type: Тип
numberOfPageCacheDescription: Увеличение этого числа повысит удобство для пользователей,
@ -2137,3 +2138,19 @@ donationLink: Ссылка на страницу для взносов
isLocked: Этот аккаунт имеет одобрение запросов на подписку
removeRecipient: Удалить получателя
removeMember: Удалить участника
confirm: Подтвердить
importZip: Импортировать ZIP
exportZip: Экспортировать ZIP
emojiPackCreator: Генератор паков эмодзи
objectStorageS3ForcePathStyle: Использовать путь вместо домена в URL
objectStorageS3ForcePathStyleDesc: Включите, если хотите, чтобы URL был в формате
's3.amazonaws.com/<bucket>/' вместо '<bucket>.s3.amazonaws.com'.
origin: Источник
deletePasskeys: Удалить passkey
delete2faConfirm: Двухфакторная аутентификация на этом аккаунте будет безвозвратно
удалена. Продолжить?
delete2fa: Отключить двухфакторную аутентификацию
deletePasskeysConfirm: Это действие безвозвратно удалит все passkey и ключи безопасности
на этом аккаунте. Продолжить?
inputNotMatch: Введённые данные не совпадают
addRe: Добавить "re:" в начале комментария в ответ на запись с предупреждением о содержимом

View file

@ -305,7 +305,7 @@ emptyDrive: "Диск порожній"
emptyFolder: "Тека порожня"
unableToDelete: "Видалення неможливе"
inputNewFileName: "Введіть ім'я нового файлу"
inputNewDescription: "Введіть новий заголовок"
inputNewDescription: "Введіть новий опис"
inputNewFolderName: "Введіть ім'я нової теки"
circularReferenceFolder: "Ви намагаєтесь перемістити папку в її підпапку."
hasChildFilesOrFolders: "Ця тека не порожня і не може бути видалена."
@ -591,8 +591,8 @@ disablePlayer: "Закрити відеоплеєр"
expandTweet: "Розгорнути твіт"
themeEditor: "Редактор тем"
description: "Опис"
describeFile: "Додати підпис"
enterFileDescription: "Введіть підпис"
describeFile: "Додати опис"
enterFileDescription: "Введіть опис"
author: "Автор"
leaveConfirm: "Зміни не збережені. Ви дійсно хочете скасувати зміни?"
manage: "Управління"
@ -1985,7 +1985,7 @@ showUpdates: Показувати спливаюче вікно при онов
updateAvailable: Можливо, є доступне оновлення!
recommendedInstancesDescription: Рекомендовані сервери відокремлюються переведенням
рядка, щоб з'явитися на стрічці рекомендацій.
caption: Автоматичний підпис
caption: Автоматичний опис
showAdminUpdates: Вказати, що доступна нова версія Firefish (тільки для адміністратора)
defaultReaction: Емодзі реакція за замовчуванням для вихідних і вхідних записів
license: Ліцензія
@ -2151,3 +2151,7 @@ deletePasskeysConfirm: Це видалить усі ключ-паролі і к
записі без можливости відмінити цю дію. Продовжити?
addRe: Додати "re:" на початку коментаря у відповідь на запис із попередженням про
вміст
confirm: Підтвердити
importZip: Імпортувати ZIP
exportZip: Експортувати ZIP
emojiPackCreator: Генератор паків емодзі

View file

@ -290,7 +290,7 @@ emptyDrive: "网盘中无文件"
emptyFolder: "此文件夹中无文件"
unableToDelete: "无法删除"
inputNewFileName: "请输入新文件名"
inputNewDescription: "请输入新标题"
inputNewDescription: "请输入新描述"
inputNewFolderName: "请输入新文件夹名"
circularReferenceFolder: "目标文件夹是您要移动的文件夹的子文件夹。"
hasChildFilesOrFolders: "此文件夹中有文件,无法删除。"
@ -561,8 +561,8 @@ disablePlayer: "关闭播放器"
expandTweet: "展开帖子"
themeEditor: "主题编辑器"
description: "描述"
describeFile: "添加标题"
enterFileDescription: "输入标题"
describeFile: "添加描述"
enterFileDescription: "输入描述"
author: "作者"
leaveConfirm: "存在未保存的更改。要放弃更改吗?"
manage: "管理"
@ -776,7 +776,7 @@ gallery: "图库"
recentPosts: "最新发布"
popularPosts: "热门投稿"
shareWithNote: "在帖子中分享"
ads: "广告"
ads: "社区横幅"
expiration: "截止时间"
memo: "便笺"
priority: "优先级"
@ -825,7 +825,7 @@ unmuteThread: "取消静音帖子串"
ffVisibility: "关注/关注者 可见性"
ffVisibilityDescription: "您可以设置谁可以看到您的关注/关注者信息。"
continueThread: "查看更多帖子"
deleteAccountConfirm: "将不可逆的删除账号,是否继续?"
deleteAccountConfirm: "这将不可逆转地删除账号,是否继续?"
incorrectPassword: "密码错误。"
voteConfirm: "确定投给 “{choice}” "
hide: "隐藏"
@ -994,6 +994,7 @@ _aboutFirefish:
pleaseDonateToFirefish: 请考虑赞助 Firefish 以支持其开发。
pleaseDonateToHost: 也请考虑赞助您的主服务器 {host},以帮助支持其运营成本。
donateHost: 赞助 {host}
misskeyContributors: Misskey 贡献者
_nsfw:
respect: "隐藏敏感内容"
ignore: "不隐藏敏感内容"
@ -1857,7 +1858,7 @@ seperateRenoteQuote: 单独的转发和引用按钮
customSplashIcons: 自定义启动屏幕图标urls
alt: 替代文字
pushNotificationNotSupported: 您的浏览器或者服务器不支持推送通知
showAds: 显示广告
showAds: 显示社区横幅
enterSendsMessage: 按回车键发送信息(关闭则是 Ctrl + Retun 发送)
recommendedInstances: 推荐服务器
updateAvailable: 可能有可用更新!
@ -1876,7 +1877,7 @@ clipsDesc: 便签就像可共享的分类书签。您可以从各个帖子的菜
privateModeInfo: 当启用时,只有白名单上的服务器可以与您的服务器联合,所有的帖子都会对公共时间线隐藏。
allowedInstancesDescription: 要列入联合白名单的服务器的主机名,一行一个(仅适用于私密模式)。
breakFollowConfirm: 确定要移除关注者吗?
caption: 自动显示说明文字
caption: 自动显示描述文字
newer: 更新的
older: 更旧的
noInstances: 没有服务器
@ -1967,3 +1968,16 @@ removeQuote: 移除引用
removeRecipient: 移除接收者
removeMember: 移除成员
origin: 起源
confirm: 确认
importZip: 导入 ZIP
exportZip: 导出 ZIP
emojiPackCreator: 表情包创建工具
objectStorageS3ForcePathStyleDesc: 打开此选项可构建格式为 's3.amazonaws.com/<bucket>/' 而非 '<bucket>.s3.amazonaws.com'
的端点 URL。
objectStorageS3ForcePathStyle: 使用基于路径的端点 URL
delete2fa: 禁用 2FA
deletePasskeysConfirm: 这将不可逆转地删除此账号上的所有通行密钥和安全密钥。是否继续?
inputNotMatch: 输入不匹配
deletePasskeys: 删除通行密钥
delete2faConfirm: 这将不可逆转地删除此账户上的 2FA。是否继续
addRe: 在回复有内容警告的帖子时,在评论开头添加 "re:"

1
neko/UPSTREAM_COMMIT_ID Normal file
View file

@ -0,0 +1 @@
f309d17667d827f44e9d914f43ddc7af2aef8268

View file

@ -1,6 +1,6 @@
{
"name": "firefish",
"version": "1.0.5-dev5",
"version": "1.0.5-dev6",
"codename": "aqua",
"repository": {
"type": "git",

View file

@ -0,0 +1,119 @@
export class IncreaseHostCharLimit1692374635734 {
name = "IncreaseHostCharLimit1692374635734";
async up(queryRunner) {
await queryRunner.query(
`ALTER TABLE "drive_file" ALTER COLUMN "userHost" TYPE character varying(512)`,
);
await queryRunner.query(
`ALTER TABLE "user" ALTER COLUMN "host" TYPE character varying(512)`,
);
await queryRunner.query(
`ALTER TABLE "user_profile" ALTER COLUMN "userHost" TYPE character varying(512)`,
);
await queryRunner.query(
`ALTER TABLE "user_publickey" ALTER COLUMN "keyId" TYPE character varying(512)`,
);
await queryRunner.query(
`ALTER TABLE "emoji" ALTER COLUMN "host" TYPE character varying(512)`,
);
await queryRunner.query(
`ALTER TABLE "note" ALTER COLUMN "userHost" TYPE character varying(512)`,
);
await queryRunner.query(
`ALTER TABLE "note" ALTER COLUMN "replyUserHost" TYPE character varying(512)`,
);
await queryRunner.query(
`ALTER TABLE "note" ALTER COLUMN "renoteUserHost" TYPE character varying(512)`,
);
await queryRunner.query(
`ALTER TABLE "instance" ALTER COLUMN "host" TYPE character varying(512)`,
);
await queryRunner.query(
`ALTER TABLE "instance" ALTER COLUMN "iconUrl" TYPE character varying(4096)`,
);
await queryRunner.query(
`ALTER TABLE "instance" ALTER COLUMN "faviconUrl" TYPE character varying(4096)`,
);
await queryRunner.query(
`ALTER TABLE "poll" ALTER COLUMN "userHost" TYPE character varying(512)`,
);
await queryRunner.query(
`ALTER TABLE "abuse_user_report" ALTER COLUMN "targetUserHost" TYPE character varying(512)`,
);
await queryRunner.query(
`ALTER TABLE "abuse_user_report" ALTER COLUMN "reporterHost" TYPE character varying(512)`,
);
await queryRunner.query(
`ALTER TABLE "following" ALTER COLUMN "followeeHost" TYPE character varying(512)`,
);
await queryRunner.query(
`ALTER TABLE "following" ALTER COLUMN "followerHost" TYPE character varying(512)`,
);
await queryRunner.query(
`ALTER TABLE "follow_request" ALTER COLUMN "followeeHost" TYPE character varying(512)`,
);
await queryRunner.query(
`ALTER TABLE "follow_request" ALTER COLUMN "followerHost" TYPE character varying(512)`,
);
}
async down(queryRunner) {
await queryRunner.query(
`ALTER TABLE "drive_file" ALTER COLUMN "userHost" TYPE character varying(128)`,
);
await queryRunner.query(
`ALTER TABLE "user" ALTER COLUMN "host" TYPE character varying(128)`,
);
await queryRunner.query(
`ALTER TABLE "user_profile" ALTER COLUMN "userHost" TYPE character varying(128)`,
);
await queryRunner.query(
`ALTER TABLE "user_publickey" ALTER COLUMN "keyId" TYPE character varying(256)`,
);
await queryRunner.query(
`ALTER TABLE "emoji" ALTER COLUMN "host" TYPE character varying(128)`,
);
await queryRunner.query(
`ALTER TABLE "note" ALTER COLUMN "userHost" TYPE character varying(128)`,
);
await queryRunner.query(
`ALTER TABLE "note" ALTER COLUMN "replyUserHost" TYPE character varying(128)`,
);
await queryRunner.query(
`ALTER TABLE "note" ALTER COLUMN "renoteUserHost" TYPE character varying(128)`,
);
await queryRunner.query(
`ALTER TABLE "instance" ALTER COLUMN "host" TYPE character varying(128)`,
);
await queryRunner.query(
`ALTER TABLE "instance" ALTER COLUMN "iconUrl" TYPE character varying(256)`,
);
await queryRunner.query(
`ALTER TABLE "instance" ALTER COLUMN "faviconUrl" TYPE character varying(256)`,
);
await queryRunner.query(
`ALTER TABLE "poll" ALTER COLUMN "userHost" TYPE character varying(128)`,
);
await queryRunner.query(
`ALTER TABLE "abuse_user_report" ALTER COLUMN "targetUserHost" TYPE character varying(128)`,
);
await queryRunner.query(
`ALTER TABLE "abuse_user_report" ALTER COLUMN "reporterHost" TYPE character varying(128)`,
);
await queryRunner.query(
`ALTER TABLE "following" ALTER COLUMN "followeeHost" TYPE character varying(128)`,
);
await queryRunner.query(
`ALTER TABLE "following" ALTER COLUMN "followerHost" TYPE character varying(128)`,
);
await queryRunner.query(
`ALTER TABLE "follow_request" ALTER COLUMN "followeeHost" TYPE character varying(128)`,
);
await queryRunner.query(
`ALTER TABLE "follow_request" ALTER COLUMN "followerHost" TYPE character varying(128)`,
);
}
}

View file

@ -78,6 +78,7 @@ export type Source = {
fingerprint?: string;
};
outgoingAddress?: string;
outgoingAddressFamily?: "ipv4" | "ipv6" | "dual";
deliverJobConcurrency?: number;

View file

@ -92,7 +92,7 @@ export async function downloadUrl(url: string, path: string): Promise<void> {
logger.succ(`Download finished: ${chalk.cyan(url)}`);
}
function isPrivateIp(ip: string): boolean {
export function isPrivateIp(ip: string): boolean {
for (const net of config.allowedPrivateNetworks || []) {
const cidr = new IPCIDR(net);
if (cidr.contains(ip)) {

View file

@ -99,6 +99,7 @@ const _http = new http.Agent({
keepAlive: true,
keepAliveMsecs: 30 * 1000,
lookup: cache.lookup,
localAddress: config.outgoingAddress,
} as http.AgentOptions);
/**
@ -108,6 +109,7 @@ const _https = new https.Agent({
keepAlive: true,
keepAliveMsecs: 30 * 1000,
lookup: cache.lookup,
localAddress: config.outgoingAddress,
} as https.AgentOptions);
const maxSockets = Math.max(256, config.deliverJobConcurrency || 128);
@ -123,6 +125,7 @@ export const httpAgent = config.proxy
maxFreeSockets: 256,
scheduling: "lifo",
proxy: config.proxy,
localAddress: config.outgoingAddress,
})
: _http;
@ -137,6 +140,7 @@ export const httpsAgent = config.proxy
maxFreeSockets: 256,
scheduling: "lifo",
proxy: config.proxy,
localAddress: config.outgoingAddress,
})
: _https;

View file

@ -71,7 +71,7 @@ export class AbuseUserReport {
//#region Denormalized fields
@Index()
@Column("varchar", {
length: 128,
length: 512,
nullable: true,
comment: "[Denormalized]",
})
@ -79,7 +79,7 @@ export class AbuseUserReport {
@Index()
@Column("varchar", {
length: 128,
length: 512,
nullable: true,
comment: "[Denormalized]",
})

View file

@ -39,7 +39,7 @@ export class DriveFile {
@Index()
@Column("varchar", {
length: 128,
length: 512,
nullable: true,
comment: "The host of owner. It will be null if the user in local.",
})

View file

@ -20,7 +20,7 @@ export class Emoji {
@Index()
@Column("varchar", {
length: 128,
length: 512,
nullable: true,
})
public host: string | null;

View file

@ -50,7 +50,7 @@ export class Following {
//#region Denormalized fields
@Index()
@Column("varchar", {
length: 128,
length: 512,
nullable: true,
comment: "[Denormalized]",
})
@ -72,7 +72,7 @@ export class Following {
@Index()
@Column("varchar", {
length: 128,
length: 512,
nullable: true,
comment: "[Denormalized]",
})

View file

@ -20,7 +20,7 @@ export class Instance {
*/
@Index({ unique: true })
@Column("varchar", {
length: 128,
length: 512,
comment: "The host of the Instance.",
})
public host: string;
@ -149,13 +149,13 @@ export class Instance {
public maintainerEmail: string | null;
@Column("varchar", {
length: 256,
length: 4096,
nullable: true,
})
public iconUrl: string | null;
@Column("varchar", {
length: 256,
length: 4096,
nullable: true,
})
public faviconUrl: string | null;

View file

@ -217,7 +217,7 @@ export class Note {
//#region Denormalized fields
@Index()
@Column("varchar", {
length: 128,
length: 512,
nullable: true,
comment: "[Denormalized]",
})
@ -231,7 +231,7 @@ export class Note {
public replyUserId: User["id"] | null;
@Column("varchar", {
length: 128,
length: 512,
nullable: true,
comment: "[Denormalized]",
})
@ -245,7 +245,7 @@ export class Note {
public renoteUserId: User["id"] | null;
@Column("varchar", {
length: 128,
length: 512,
nullable: true,
comment: "[Denormalized]",
})

View file

@ -58,7 +58,7 @@ export class Poll {
@Index()
@Column("varchar", {
length: 128,
length: 512,
nullable: true,
comment: "[Denormalized]",
})

View file

@ -242,7 +242,7 @@ export class UserProfile {
//#region Denormalized fields
@Index()
@Column("varchar", {
length: 128,
length: 512,
nullable: true,
comment: "[Denormalized]",
})

View file

@ -22,7 +22,7 @@ export class UserPublickey {
@Index({ unique: true })
@Column("varchar", {
length: 256,
length: 512,
})
public keyId: string;

View file

@ -214,7 +214,7 @@ export class User {
@Index()
@Column("varchar", {
length: 128,
length: 512,
nullable: true,
comment:
"The host of the User. It will be null if the origin of the user is local.",

View file

@ -122,19 +122,31 @@ export default class DeliverManager {
)
.forEach((recipe) => inboxes.add(recipe.to.inbox!));
// Validate Inboxes first
const validInboxes = [];
for (const inbox of inboxes) {
try {
validInboxes.push({
inbox,
host: new URL(inbox).host,
});
} catch (error) {
console.error(error);
console.error(`Invalid Inbox ${inbox}`);
}
}
const instancesToSkip = await skippedInstances(
// get (unique) list of hosts
Array.from(
new Set(Array.from(inboxes).map((inbox) => new URL(inbox).host)),
),
Array.from(new Set(validInboxes.map((valid) => valid.host))),
);
// deliver
for (const inbox of inboxes) {
for (const valid of validInboxes) {
// skip instances as indicated
if (instancesToSkip.includes(new URL(inbox).host)) continue;
if (instancesToSkip.includes(valid.host)) continue;
deliver(this.actor, this.activity, inbox);
deliver(this.actor, this.activity, valid.inbox);
}
}
}

View file

@ -37,7 +37,7 @@ import remove from "./remove/index.js";
import block from "./block/index.js";
import flag from "./flag/index.js";
import move from "./move/index.js";
import type { IObject } from "../type.js";
import type { IObject, IActivity } from "../type.js";
import { extractDbHost } from "@/misc/convert-host.js";
import { shouldBlockInstance } from "@/misc/should-block-instance.js";
@ -106,6 +106,8 @@ async function performOneActivity(
} else if (isMove(activity)) {
await move(actor, activity);
} else {
apLogger.warn(`unrecognized activity type: ${(activity as any).type}`);
apLogger.warn(
`Unrecognized activity type: ${(activity as IActivity).type}`,
);
}
}

View file

@ -68,13 +68,13 @@ export class LdSignature {
...options,
"@context": "https://w3id.org/identity/v1",
};
delete transformedOptions["type"];
delete transformedOptions["id"];
delete transformedOptions["signatureValue"];
transformedOptions.type = undefined;
transformedOptions.id = undefined;
transformedOptions.signatureValue = undefined;
const canonizedOptions = await this.normalize(transformedOptions);
const optionsHash = this.sha256(canonizedOptions);
const transformedData = { ...data };
delete transformedData["signature"];
transformedData.signature = undefined;
const cannonidedData = await this.normalize(transformedData);
if (this.debug) console.debug(`cannonidedData: ${cannonidedData}`);
const documentHash = this.sha256(cannonidedData);

View file

@ -231,6 +231,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 {
@ -274,6 +289,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,
@ -472,6 +495,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,
@ -495,6 +533,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),
@ -554,7 +600,7 @@ export async function updatePerson(
{
followerSharedInbox:
person.sharedInbox ||
(person.endpoints ? person.endpoints.sharedInbox : undefined),
(person.endpoints ? person.endpoints.sharedInbox : null),
},
);
@ -663,7 +709,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,7 +1,6 @@
import define from "../../../define.js";
import { createImportCustomEmojisJob } from "@/queue/index.js";
import { ApiError } from "../../../error.js";
import ms from "ms";
export const meta = {
tags: ["admin", "emoji"],

View file

@ -1,4 +1,4 @@
import * as sanitizeHtml from "sanitize-html";
import sanitizeHtml from "sanitize-html";
import define from "../../define.js";
import { Users, UserProfiles } from "@/models/index.js";
import { ApiError } from "../../error.js";

View file

@ -24,7 +24,7 @@ export const meta = {
},
recursiveNesting: {
message: "It can not be structured like nesting folders recursively.",
message: "It cannot be structured like nesting folders recursively.",
code: "NO_SUCH_PARENT_FOLDER",
id: "ce104e3a-faaf-49d5-b459-10ff0cbbcaa1",
},

View file

@ -18,7 +18,7 @@ export const meta = {
},
pinLimitExceeded: {
message: "You can not pin notes any more.",
message: "You cannot pin notes any more.",
code: "PIN_LIMIT_EXCEEDED",
id: "72dab508-c64d-498f-8740-a8eec1ba385a",
},

View file

@ -15,9 +15,7 @@ export const paramDef = {
export default define(meta, paramDef, async () => {
let tag_name;
await fetch(
"https://codeberg.org/api/v1/repos/firefish/firefish/releases?draft=false&pre-release=false&page=1&limit=1",
)
await fetch("https://git.joinfirefish.org/api/v4/projects/7/releases")
.then((response) => response.json())
.then((data) => {
tag_name = data[0].tag_name;

View file

@ -48,7 +48,7 @@ export const meta = {
},
groupAccessDenied: {
message: "You can not read messages of groups that you have not joined.",
message: "You cannot read messages of groups that you have not joined.",
code: "GROUP_ACCESS_DENIED",
id: "a053a8dd-a491-4718-8f87-50775aad9284",
},

View file

@ -28,7 +28,7 @@ export const meta = {
errors: {
recipientIsYourself: {
message: "You can not send a message to yourself.",
message: "You cannot send a message to yourself.",
code: "RECIPIENT_IS_YOURSELF",
id: "17e2ba79-e22a-4cbc-bf91-d327643f4a7e",
},
@ -46,7 +46,7 @@ export const meta = {
},
groupAccessDenied: {
message: "You can not send messages to groups that you have not joined.",
message: "You cannot send messages to groups that you have not joined.",
code: "GROUP_ACCESS_DENIED",
id: "d96b3cca-5ad1-438b-ad8b-02f931308fbd",
},

View file

@ -52,7 +52,7 @@ export const meta = {
},
cannotReRenote: {
message: "You can not Renote a pure Renote.",
message: "You cannot Renote a pure Renote.",
code: "CANNOT_RENOTE_TO_A_PURE_RENOTE",
id: "fd4cc33e-2a37-48dd-99cc-9b806eb2031a",
},
@ -64,7 +64,7 @@ export const meta = {
},
cannotReplyToPureRenote: {
message: "You can not reply to a pure Renote.",
message: "You cannot reply to a pure Renote.",
code: "CANNOT_REPLY_TO_A_PURE_RENOTE",
id: "3ac74a84-8fd5-4bb0-870f-01804f82ce15",
},

View file

@ -70,7 +70,7 @@ export const meta = {
},
cannotReRenote: {
message: "You can not Renote a pure Renote.",
message: "You cannot Renote a pure Renote.",
code: "CANNOT_RENOTE_TO_A_PURE_RENOTE",
id: "fd4cc33e-2a37-48dd-99cc-9b806eb2031a",
},
@ -82,7 +82,7 @@ export const meta = {
},
cannotReplyToPureRenote: {
message: "You can not reply to a pure Renote.",
message: "You cannot reply to a pure Renote.",
code: "CANNOT_REPLY_TO_A_PURE_RENOTE",
id: "3ac74a84-8fd5-4bb0-870f-01804f82ce15",
},
@ -130,7 +130,7 @@ export const meta = {
},
cannotPrivateRenote: {
message: "You can not perform a private renote.",
message: "You cannot perform a private renote.",
code: "CANNOT_PRIVATE_RENOTE",
id: "19a50f1c-84fa-4e33-81d3-17834ccc0ad8",
},
@ -140,6 +140,18 @@ export const meta = {
code: "NOT_LOCAL_USER",
id: "b907f407-2aa0-4283-800b-a2c56290b822",
},
cannotChangeVisibility: {
message: "You cannot change the visibility of a note.",
code: "CANNOT_CHANGE_VISIBILITY",
id: "2917fd0b-da04-41de-949f-146835a006c6",
},
cannotQuoteOwnNote: {
message: "You cannot quote your own note.",
code: "CANNOT_QUOTE_OWN_NOTE",
id: "070eee98-5f8a-4eca-9dc0-830b4d4e52ac",
},
},
} as const;
@ -268,6 +280,10 @@ export default define(meta, paramDef, async (ps, user) => {
throw e;
});
if (ps.renoteId === note.id) {
throw new ApiError(meta.errors.cannotQuoteOwnNote);
}
if (renote.renoteId && !renote.text && !renote.fileIds && !renote.hasPoll) {
throw new ApiError(meta.errors.cannotReRenote);
}
@ -523,7 +539,8 @@ export default define(meta, paramDef, async (ps, user) => {
update.cw = null;
}
if (ps.visibility !== note.visibility) {
update.visibility = ps.visibility;
// update.visibility = ps.visibility;
throw new ApiError(meta.errors.cannotChangeVisibility);
}
if (ps.localOnly !== note.localOnly) {
update.localOnly = ps.localOnly;

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

@ -10,7 +10,7 @@ export const meta = {
kind: "write:user-groups",
description:
"Leave a group. The owner of a group can not leave. They must transfer ownership or delete the group instead.",
"Leave a group. The owner of a group cannot leave. They must transfer ownership or delete the group instead.",
errors: {
noSuchGroup: {

View file

@ -11,7 +11,7 @@ export const meta = {
kind: "write:user-groups",
description:
"Removes a specified user from a group. The owner can not be removed.",
"Removes a specified user from a group. The owner cannot be removed.",
errors: {
noSuchGroup: {

View file

@ -1,12 +1,14 @@
import * as sanitizeHtml from "sanitize-html";
import * as mfm from "mfm-js";
import sanitizeHtml from "sanitize-html";
import { publishAdminStream } from "@/services/stream.js";
import { AbuseUserReports, Users } from "@/models/index.js";
import { AbuseUserReports, UserProfiles, Users } from "@/models/index.js";
import { genId } from "@/misc/gen-id.js";
import { sendEmail } from "@/services/send-email.js";
import { fetchMeta } from "@/misc/fetch-meta.js";
import { getUser } from "../../common/getters.js";
import { ApiError } from "../../error.js";
import define from "../../define.js";
import { toHtml } from "@/mfm/to-html.js";
export const meta = {
tags: ["users"],
@ -84,6 +86,7 @@ export default define(meta, paramDef, async (ps, me) => {
],
});
const meta = await fetchMeta();
for (const moderator of moderators) {
publishAdminStream(moderator.id, "newAbuseUserReport", {
id: report.id,
@ -91,16 +94,16 @@ export default define(meta, paramDef, async (ps, me) => {
reporterId: report.reporterId,
comment: report.comment,
});
}
const meta = await fetchMeta();
if (meta.email) {
sendEmail(
meta.email,
"New abuse report",
sanitizeHtml(ps.comment),
sanitizeHtml(ps.comment),
);
const profile = await UserProfiles.findOneBy({ userId: moderator.id });
if (profile?.email) {
sendEmail(
profile.email,
"New abuse report",
sanitizeHtml(toHtml(mfm.parse(ps.comment))!),
sanitizeHtml(toHtml(mfm.parse(ps.comment))!),
);
}
}
});
});

View file

@ -74,3 +74,13 @@ export function convertStatus(status: Entity.Status) {
return status;
}
export function convertConversation(conversation: Entity.Conversation) {
conversation.id = convertId(conversation.id, IdType.MastodonId);
conversation.accounts = conversation.accounts.map(convertAccount);
if (conversation.last_status) {
conversation.last_status = convertStatus(conversation.last_status);
}
return conversation;
}

View file

@ -1,7 +1,12 @@
import Router from "@koa/router";
import { getClient } from "../ApiMastodonCompatibleService.js";
import { ParsedUrlQuery } from "querystring";
import { convertAccount, convertList, convertStatus } from "../converters.js";
import {
convertAccount,
convertConversation,
convertList,
convertStatus,
} from "../converters.js";
import { convertId, IdType } from "../../index.js";
export function limitToInt(q: ParsedUrlQuery) {
@ -136,7 +141,9 @@ export function apiTimelineMastodon(router: Router): void {
const data = await client.getConversationTimeline(
convertTimelinesArgsId(limitToInt(ctx.query)),
);
ctx.body = data.data;
ctx.body = data.data.map((conversation) =>
convertConversation(conversation),
);
} catch (e: any) {
console.error(e);
console.error(e.response.data);

View file

@ -167,7 +167,6 @@ export default async function (ctx: Koa.Context) {
return;
}
ctx.length = file.size;
ctx.set("Content-Disposition", contentDisposition("inline", filename));
ctx.set("Content-Type", contentType);
@ -192,7 +191,6 @@ export default async function (ctx: Koa.Context) {
ctx.set("Accept-Ranges", "bytes");
} else {
ctx.status = 206;
ctx.length = readable.size;
readable.on("close", async () => {
await fileHandle.close();
});

View file

@ -1,7 +1,7 @@
import { URL } from "node:url";
import type { User } from "@/models/entities/user.js";
import { createTemp } from "@/misc/create-temp.js";
import { downloadUrl } from "@/misc/download-url.js";
import { downloadUrl, isPrivateIp } from "@/misc/download-url.js";
import type { DriveFolder } from "@/models/entities/drive-folder.js";
import type { DriveFile } from "@/models/entities/drive-file.js";
import { DriveFiles } from "@/models/index.js";
@ -35,7 +35,15 @@ export async function uploadFromUrl({
requestIp = null,
requestHeaders = null,
}: Args): Promise<DriveFile> {
let name = new URL(url).pathname.split("/").pop() || null;
const parsedUrl = new URL(url);
if (
process.env.NODE_ENV === "production" &&
isPrivateIp(parsedUrl.hostname.replaceAll(/(\[)|(\])/g, ""))
) {
throw new Error("Private IP is not allowed");
}
let name = parsedUrl.pathname.split("/").pop() || null;
if (name == null || !DriveFiles.validateFileName(name)) {
name = null;
}

View file

@ -38,7 +38,7 @@ export async function addPinned(
if (pinings.length >= 15) {
throw new IdentifiableError(
"15a018eb-58e5-4da1-93be-330fcc5e4e1a",
"You can not pin notes any more.",
"You cannot pin notes any more.",
);
}

View file

@ -171,8 +171,7 @@ export default async (
) =>
// rome-ignore lint/suspicious/noAsyncPromiseExecutor: FIXME
new Promise<Note>(async (res, rej) => {
const dontFederateInitially =
data.localOnly || data.visibility?.startsWith("hidden");
const dontFederateInitially = data.visibility === "hidden";
// If you reply outside the channel, match the scope of the target.
// TODO (I think it's a process that could be done on the client side, but it's server side for now.)
@ -806,7 +805,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

@ -11,30 +11,30 @@
},
"devDependencies": {
"@discordapp/twemoji": "14.1.2",
"@eslint-sets/eslint-config-vue3": "^5.7.0",
"@eslint-sets/eslint-config-vue3": "^5.8.0",
"@eslint-sets/eslint-config-vue3-ts": "^3.3.0",
"@phosphor-icons/web": "^2.0.3",
"@rollup/plugin-alias": "3.1.9",
"@rollup/plugin-json": "4.1.0",
"@rollup/pluginutils": "^4.2.1",
"@rollup/plugin-alias": "5.0.0",
"@rollup/plugin-json": "6.0.0",
"@rollup/pluginutils": "^5.0.3",
"@syuilo/aiscript": "0.11.1",
"@types/escape-regexp": "0.0.1",
"@types/glob": "8.1.0",
"@types/gulp": "4.0.13",
"@types/gulp-rename": "2.0.2",
"@types/katex": "0.16.0",
"@types/matter-js": "0.18.2",
"@types/katex": "0.16.2",
"@types/matter-js": "0.19.0",
"@types/punycode": "2.1.0",
"@types/seedrandom": "3.0.5",
"@types/throttle-debounce": "5.0.0",
"@types/tinycolor2": "1.4.3",
"@types/uuid": "8.3.4",
"@vitejs/plugin-vue": "4.2.3",
"@types/uuid": "9.0.2",
"@vitejs/plugin-vue": "4.3.1",
"@vue/compiler-sfc": "3.3.4",
"autobind-decorator": "2.4.0",
"autosize": "6.0.1",
"blurhash": "2.0.5",
"broadcast-channel": "5.1.0",
"broadcast-channel": "5.2.0",
"browser-image-resizer": "github:misskey-dev/browser-image-resizer",
"chart.js": "4.3.3",
"chartjs-adapter-date-fns": "3.0.0",
@ -49,7 +49,7 @@
"date-fns": "2.30.0",
"emojilib": "github:thatonecalculator/emojilib",
"escape-regexp": "0.0.1",
"eslint-config-prettier": "^8.9.0",
"eslint-config-prettier": "9.0.0",
"eslint-plugin-file-progress": "^1.3.0",
"eventemitter3": "5.0.1",
"fast-blurhash": "^1.1.2",
@ -61,27 +61,25 @@
"insert-text-at-cursor": "0.3.0",
"json5": "2.2.3",
"katex": "0.16.8",
"matter-js": "0.18.0",
"matter-js": "0.19.0",
"mfm-js": "0.23.3",
"paralint": "^1.2.1",
"photoswipe": "5.3.8",
"prettier": "3.0.1",
"prettier": "3.0.2",
"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.2",
"rollup": "3.28.0",
"s-age": "1.1.2",
"sass": "1.64.2",
"sass": "1.66.0",
"seedrandom": "3.0.5",
"start-server-and-test": "1.15.2",
"strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0",
"swiper": "10.1.0",
"swiper": "10.2.0",
"syuilo-password-strength": "0.0.1",
"textarea-caret": "3.1.0",
"three": "0.146.0",
"three": "0.155.0",
"throttle-debounce": "5.0.0",
"tinycolor2": "1.6.0",
"tinyld": "1.3.4",
@ -91,11 +89,11 @@
"typescript": "5.1.6",
"unicode-emoji-json": "^0.4.0",
"uuid": "9.0.0",
"vanilla-tilt": "1.8.0",
"vanilla-tilt": "1.8.1",
"vite": "4.4.9",
"vite-plugin-compression": "^0.5.1",
"vue": "3.3.4",
"vue-draggable-plus": "^0.2.4",
"vue-draggable-plus": "^0.2.5",
"vue-isyourpasswordsafe": "^2.0.0",
"vue-plyr": "^7.0.0",
"vue-prism-editor": "2.0.0-alpha.2"

View file

@ -65,6 +65,8 @@
</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";
@ -79,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,7 +108,7 @@
</template>
<script lang="ts" setup>
import { computed, onBeforeUnmount, onMounted } from "vue";
import { computed, onBeforeUnmount, onMounted, ref } from "vue";
import tinycolor from "tinycolor2";
import { globalEvents } from "@/events.js";
@ -167,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() {
@ -187,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();
@ -220,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

@ -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,7 +22,7 @@ const emit = defineEmits<{
(ev: "closed"): void;
}>();
const dialog = $ref<InstanceType<typeof XModalWindow>>();
const dialog = ref<InstanceType<typeof XModalWindow>>();
</script>
<style lang="scss" scoped>

View file

@ -12,7 +12,7 @@
</template>
<script lang="ts" setup>
import { onBeforeUnmount, onMounted } from "vue";
import { onBeforeUnmount, onMounted, ref } from "vue";
import MkMenu from "@/components/MkMenu.vue";
import type { MenuItem } from "@/types/menu";
import contains from "@/scripts/contains";
@ -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 { 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

@ -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";
@ -89,13 +89,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");
const hideFollowButton = props.hideFollowButton ?? false;
@ -108,13 +108,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) {
@ -133,7 +134,7 @@ async function onClick() {
});
}
emit("refresh");
} else if (isFollowing) {
} else if (isFollowing.value) {
const { canceled } = await os.confirm({
type: "warning",
text: i18n.t("unfollowConfirm", {
@ -147,22 +148,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;
}
}

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

@ -8,7 +8,7 @@
</template>
<script lang="ts" setup>
import { onMounted, nextTick, watch } from "vue";
import { onMounted, nextTick, watch, shallowRef, ref } from "vue";
import { Chart } from "chart.js";
import * as os from "@/os";
import { defaultStore } from "@/store";
@ -23,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",
@ -43,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;
@ -113,7 +113,7 @@ async function renderChart() {
values = addArrays(raw.diffs.normal, raw.diffs.reply, raw.diffs.renote);
}
fetching = false;
fetching.value = false;
await nextTick();
@ -131,7 +131,7 @@ async function renderChart() {
const marginEachCell = 4;
chartInstance = new Chart(chartEl, {
chartInstance = new Chart(chartEl.value, {
type: "matrix",
data: {
datasets: [
@ -247,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

@ -13,6 +13,8 @@
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { instanceName, version } from "@/config";
import { instance as Instance } from "@/instance";
import { getProxiedImageUrlNullable } from "@/scripts/media-proxy";
@ -27,7 +29,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 ?? {
@ -46,12 +48,15 @@ const commonNames = new Map<string, string>([
["birdsitelive", "BirdsiteLIVE"],
["bookwyrm", "BookWyrm"],
["bridgy-fed", "Bridgy Fed"],
["castopod", "CastoPod"],
["foundkey", "FoundKey"],
["gnusocial", "GNU social"],
["gotosocial", "GoToSocial"],
["kbin", "/kbin"],
["microblogpub", "microblog.pub"],
["nextcloud social", "Nextcloud Social"],
["peertube", "PeerTube"],
["reel2bits", "reel2bits"],
["snac", "snac"],
["snac2", "snac2"],
["takahe", "Takahē"],

View file

@ -62,6 +62,8 @@
</template>
<script lang="ts" setup>
import { ref } from "vue";
import MkModal from "@/components/MkModal.vue";
import { navbarItemDef } from "@/navbar";
import { defaultStore } from "@/store";
@ -89,7 +91,7 @@ const preferedModalType =
? "drawer"
: "dialog";
const modal = $ref<InstanceType<typeof MkModal>>();
const modal = ref<InstanceType<typeof MkModal>>();
const menu = defaultStore.state.menu;
@ -107,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

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

@ -233,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,
@ -255,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() {
@ -282,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();
}
@ -305,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 { 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,10 +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 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"),
);
@ -55,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";
@ -76,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",
@ -113,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);
},
},
];
@ -137,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

@ -306,7 +306,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;
@ -321,19 +321,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>();
@ -342,13 +342,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;
@ -399,7 +401,7 @@ const keymap = {
useNoteCapture({
rootEl: el,
note: $$(appearNote),
note: appearNote,
isDeletedRef: isDeleted,
});
@ -407,7 +409,7 @@ function reply(viaKeyboard = false): void {
pleaseLogin();
os.post(
{
reply: appearNote,
reply: appearNote.value,
animation: !viaKeyboard,
},
() => {
@ -423,7 +425,7 @@ function react(viaKeyboard = false): void {
reactButton.value,
(reaction) => {
os.api("notes/reactions/create", {
noteId: appearNote.id,
noteId: appearNote.value.id,
reaction: reaction,
});
},
@ -466,21 +468,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,
@ -489,22 +494,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,
@ -517,7 +525,7 @@ function onContextmenu(ev: MouseEvent): void {
function menu(viaKeyboard = false): void {
os.popupMenu(
getNoteMenu({
note: note,
note: note.value,
translating,
translation,
menuButton,
@ -541,7 +549,7 @@ function showRenoteMenu(viaKeyboard = false): void {
danger: true,
action: () => {
os.api("notes/delete", {
noteId: note.id,
noteId: note.value.id,
});
isDeleted.value = true;
},
@ -582,13 +590,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;
}
@ -600,28 +608,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

@ -177,9 +177,9 @@ const props = defineProps<{
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;
@ -194,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(
@ -236,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();
@ -257,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,
});
},
@ -291,7 +293,7 @@ function onContextmenu(ev: MouseEvent): void {
} else {
os.contextMenu(
getNoteMenu({
note: note,
note: note.value,
translating,
translation,
menuButton,
@ -305,7 +307,7 @@ function onContextmenu(ev: MouseEvent): void {
function menu(viaKeyboard = false): void {
os.popupMenu(
getNoteMenu({
note: note,
note: note.value,
translating,
translation,
menuButton,
@ -319,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 = {
@ -371,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;
});
}
}
@ -387,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++) {
@ -412,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;
@ -433,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,6 +47,8 @@
</template>
<script lang="ts" setup>
import { ref } from "vue";
import {} from "vue";
import type * as misskey from "firefish-js";
import { defaultStore } from "@/store";
@ -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

@ -185,7 +185,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 * as mfm from "mfm-js";
@ -233,7 +233,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;
@ -246,10 +246,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>();
@ -257,11 +257,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[] =
@ -309,14 +311,14 @@ const translate = async () => {
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();
@ -330,7 +332,7 @@ function react(viaKeyboard = false): void {
reactButton.value,
(reaction) => {
os.api("notes/reactions/create", {
noteId: appearNote.id,
noteId: appearNote.value.id,
reaction: reaction,
});
},
@ -356,7 +358,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,
@ -388,21 +390,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,
@ -411,22 +416,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

@ -30,7 +30,7 @@
</template>
<script lang="ts" setup>
import { ComputedRef, 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";
@ -51,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,
@ -71,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",
@ -84,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,
@ -111,7 +111,7 @@ const contextmenu = $computed(() => [
text: i18n.ts.openInNewTab,
action: () => {
window.open(url + router.getCurrentPath(), "_blank");
windowEl.close();
windowEl.value.close();
},
},
{
@ -124,25 +124,25 @@ const contextmenu = $computed(() => [
]);
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

@ -24,6 +24,8 @@
</template>
<script lang="ts" setup>
import { ref } from "vue";
import MkModal from "./MkModal.vue";
import MkMenu from "./MkMenu.vue";
import { MenuItem } from "@/types/menu";
@ -41,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

@ -245,7 +245,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";
@ -311,45 +319,47 @@ 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);
const showBigPostButton = defaultStore.state.showBigPostButton;
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(defaultStore.state.showPreviewByDefault);
let cw = $ref<string | null>(null);
let localOnly = $ref<boolean>(
let useCw = ref(false);
let showPreview = ref(defaultStore.state.showPreviewByDefault);
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 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) {
@ -357,7 +367,7 @@ const typing = throttle(3000, () => {
}
});
const draftKey = $computed((): string => {
const draftKey = computed((): string => {
if (props.editId) {
return `edit:${props.editId}`;
}
@ -375,7 +385,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) {
@ -395,7 +405,7 @@ const placeholder = $computed((): string => {
}
});
const submitText = $computed((): string => {
const submitText = computed((): string => {
return props.editId
? i18n.ts.edit
: props.renote
@ -405,34 +415,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();
},
@ -442,10 +455,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 (
@ -453,7 +466,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)
: ""
@ -476,15 +489,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:
}
//
@ -492,17 +505,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(
@ -524,7 +537,7 @@ if (
}
if (props.specified) {
visibility = "specified";
visibility.value = "specified";
pushVisibleUser(props.specified);
}
@ -540,53 +553,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);
},
);
}
@ -594,10 +607,10 @@ function addMissingMention() {
}
function togglePoll() {
if (poll) {
poll = null;
if (poll.value) {
poll.value = null;
} else {
poll = {
poll.value = {
choices: ["", ""],
multiple: false,
expiresAt: null,
@ -606,16 +619,16 @@ function togglePoll() {
}
}
// function addTag(tag: string) {
// insertTextAtCursor(textareaEl, ` #${tag} `);
// }
function addTag(tag: string) {
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,
);
}
}
@ -624,31 +637,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);
});
}
@ -663,21 +677,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);
}
},
},
@ -687,11 +701,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);
}
}
@ -702,21 +716,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");
@ -724,12 +738,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) {
@ -750,7 +764,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({
@ -758,11 +772,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];
});
}
}
@ -773,7 +789,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":
@ -794,15 +810,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) {
@ -815,7 +831,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
@ -824,16 +840,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,
},
};
@ -843,37 +859,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))
@ -892,12 +909,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();
@ -917,12 +935,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,
@ -936,12 +954,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);
}
function showActions(ev) {
@ -951,11 +969,11 @@ function showActions(ev) {
action: () => {
action.handler(
{
text: text,
text: text.value,
},
(key, value) => {
if (key === "text") {
text = value;
text.value = value;
}
},
);
@ -965,19 +983,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;
}
},
},
@ -995,30 +1013,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;
}
}
}
@ -1026,21 +1044,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>

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