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 # deliverJobMaxAttempts: 12
# inboxJobMaxAttempts: 8 # inboxJobMaxAttempts: 8
# Local address used for outgoing requests
#outgoingAddress: 127.0.0.1
# IP address family used for outgoing request (ipv4, ipv6 or dual) # IP address family used for outgoing request (ipv4, ipv6 or dual)
#outgoingAddressFamily: ipv4 #outgoingAddressFamily: ipv4

View file

@ -1,7 +1,6 @@
{ {
"recommendations": [ "recommendations": [
"editorconfig.editorconfig", "editorconfig.editorconfig",
"eg2.vscode-npm-script",
"rome.rome", "rome.rome",
"Vue.volar", "Vue.volar",
"Vue.vscode-typescript-vue-plugin", "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 * truncate user information if it is too long
Some AP software allows for user names or summaries to be very 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. no activities from such users can be seen.
Instead, the user name and summary are cut off after the maximum length 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 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 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 * fix: missing import
@ -14921,7 +14921,7 @@ Defaults for `local` and `withFiles` are based on the behaviour of the endpoint.
* fix: define required fields * 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 `default` inside of `anyOf`, see
https://ajv.js.org/guide/modifying-data.html#assigning-defaults 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` 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 * remove duplicate null check
The variable is checked for null in the lines above and the function 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` * simplify `getJsonSchema`

View file

@ -1,9 +1,14 @@
## Install dev and compilation dependencies, build files ## Install dev and compilation dependencies, build files
FROM alpine:3.18 as build FROM node:latest as build
WORKDIR /firefish WORKDIR /firefish
# Install compilation dependencies # Install compilation dependencies
RUN apk add --no-cache --no-progress git alpine-sdk python3 nodejs-current npm rust cargo vips RUN apt-get update && apt-get install -y libvips42 python3 git wget curl build-essential
RUN mkdir -m777 /opt/rust /opt/cargo
ENV RUSTUP_HOME=/opt/rust CARGO_HOME=/opt/cargo PATH=/opt/cargo/bin:$PATH
RUN wget --https-only --secure-protocol=TLSv1_2 -O- https://sh.rustup.rs | sh /dev/stdin -y
RUN printf '#!/bin/sh\nexport CARGO_HOME=/opt/cargo\nexec /bin/sh "$@"\n' >/usr/local/bin/sh
RUN chmod +x /usr/local/bin/sh
# Copy only the cargo dependency-related files first, to cache efficiently # Copy only the cargo dependency-related files first, to cache efficiently
COPY packages/backend/native-utils/Cargo.toml packages/backend/native-utils/Cargo.toml COPY packages/backend/native-utils/Cargo.toml packages/backend/native-utils/Cargo.toml
@ -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-x64-musl/package.json packages/backend/native-utils/npm/linux-x64-musl/package.json
COPY packages/backend/native-utils/npm/linux-arm64-musl/package.json packages/backend/native-utils/npm/linux-arm64-musl/package.json COPY packages/backend/native-utils/npm/linux-arm64-musl/package.json packages/backend/native-utils/npm/linux-arm64-musl/package.json
# Configure corepack and pnpm, and install dev mode dependencies for compilation # Configure pnpm, and install dev mode dependencies for compilation
RUN corepack enable && corepack prepare pnpm@latest --activate && pnpm i --frozen-lockfile RUN corepack enable && corepack prepare pnpm@latest --activate && pnpm i --frozen-lockfile
# Copy in the rest of the native-utils rust files # Copy in the rest of the native-utils rust files
@ -43,11 +48,11 @@ RUN env NODE_ENV=production sh -c "pnpm run --filter '!native-utils' build && pn
RUN pnpm i --prod --frozen-lockfile RUN pnpm i --prod --frozen-lockfile
## Runtime container ## Runtime container
FROM alpine:3.18 FROM node:latest
WORKDIR /firefish WORKDIR /firefish
# Install runtime dependencies # Install runtime dependencies
RUN apk add --no-cache --no-progress tini ffmpeg vips-dev zip unzip nodejs-current RUN apt-get update && apt-get install -y libvips-dev zip unzip tini ffmpeg
COPY . ./ COPY . ./
@ -69,5 +74,5 @@ COPY --from=build /firefish/packages/backend/native-utils/built /firefish/packag
RUN corepack enable && corepack prepare pnpm@latest --activate RUN corepack enable && corepack prepare pnpm@latest --activate
ENV NODE_ENV=production ENV NODE_ENV=production
VOLUME "/firefish/files" VOLUME "/firefish/files"
ENTRYPOINT [ "/sbin/tini", "--" ] ENTRYPOINT [ "/usr/bin/tini", "--" ]
CMD [ "pnpm", "run", "migrateandstart" ] CMD [ "pnpm", "run", "migrateandstart" ]

View file

@ -103,4 +103,4 @@ NODE_ENV=production pnpm run migrate
## Reverse ## Reverse
You ***cannot*** migrate back to Misskey from Firefish due to re-hashing passwords on signin with argon2. You can migrate from Calckey to FoundKey, although this is not recommended due to FoundKey being end-of-life, and may have some problems with alt-text. You ***cannot*** migrate back to Misskey from Firefish due to re-hashing passwords on signin with argon2. You can migrate from Firefish to FoundKey, although this is not recommended due to FoundKey being end-of-life, and may have some problems with alt-text.

View file

@ -1,5 +1,5 @@
_lang_: Български _lang_: Български
cancel: Отмяна cancel: Отмени
noNotes: Няма публикации noNotes: Няма публикации
settings: Настройки settings: Настройки
headlineFirefish: Децентрализирана социална медийна платформа с отворен код, която headlineFirefish: Децентрализирана социална медийна платформа с отворен код, която
@ -101,6 +101,10 @@ _filters:
followersOnly: Само последователи followersOnly: Само последователи
notesAfter: Публикации след notesAfter: Публикации след
fromDomain: От домейн fromDomain: От домейн
fromUser: От потребител
withFile: С файл
notesBefore: Публикации преди
followingOnly: Само последвани
_notification: _notification:
_types: _types:
follow: Нови последователи follow: Нови последователи
@ -113,10 +117,10 @@ noLists: Нямаш никакви списъци
markAsReadAllUnreadNotes: Маркирай всички публикации като прочетени markAsReadAllUnreadNotes: Маркирай всички публикации като прочетени
markAsReadAllTalkMessages: Маркирай всички съобщения като прочетени markAsReadAllTalkMessages: Маркирай всички съобщения като прочетени
_time: _time:
second: Секунд(а/и) second: Секунди
hour: Час(а) hour: Часа
day: Дни day: Дни
minute: Минут(а/и) minute: Минути
create: Създай create: Създай
lists: Списъци lists: Списъци
reportAbuseOf: Докладвай {name} reportAbuseOf: Докладвай {name}
@ -134,9 +138,9 @@ rename: Преименувай
customEmojis: Персонализирани емоджита customEmojis: Персонализирани емоджита
emoji: Емоджи emoji: Емоджи
_aboutFirefish: _aboutFirefish:
translation: Преведи Calckey translation: Преведи Firefish
translatedFrom: Преведено от {x} translatedFrom: Преведено от {x}
i18nInfo: Calckey се превежда на различни езици от доброволци. Можете да помогнете i18nInfo: Firefish се превежда на различни езици от доброволци. Можете да помогнете
на адрес {link}. на адрес {link}.
image: Изображение image: Изображение
recipient: Получател(и) recipient: Получател(и)
@ -222,11 +226,11 @@ _mfm:
_messaging: _messaging:
groups: Групи groups: Групи
apps: Приложения apps: Приложения
introFirefish: Добре дошли! Calckey е децентрализирана социална медийна платформа introFirefish: Добре дошли! Firefish е децентрализирана социална медийна платформа
с отворен код, която е безплатна завинаги! 🚀 с отворен код, която е безплатна завинаги! 🚀
monthAndDay: '{day}/{month}' monthAndDay: '{day}/{month}'
search: Търсене search: Търсене
searchPlaceholder: Търсене в Calckey searchPlaceholder: Търсене в Firefish
username: Потребителско име username: Потребителско име
password: Парола password: Парола
fetchingAsApObject: Извличане от федивърса fetchingAsApObject: Извличане от федивърса
@ -257,7 +261,7 @@ alreadyFavorited: Вече е добавено в отметки.
cantFavorite: Неуспешно добавяне в отметки. cantFavorite: Неуспешно добавяне в отметки.
copyContent: Копирай съдържанието copyContent: Копирай съдържанието
deleteAndEdit: Изтрий и редактирай deleteAndEdit: Изтрий и редактирай
editNote: Редактирай бележка editNote: Редактирай публикация
edited: Редактирано на {date} {time} edited: Редактирано на {date} {time}
addToList: Добави в списък addToList: Добави в списък
sendMessage: Изпрати съобщение sendMessage: Изпрати съобщение
@ -422,3 +426,51 @@ _visibility:
followers: Последователи followers: Последователи
explore: Разглеждане explore: Разглеждане
theme: Теми 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 accepted: Acceptat
rejected: Rebutjat rejected: Rebutjat
deleted: Eliminat deleted: Eliminat
editNote: Edita la nota editNote: Edita la publicació
edited: 'Editat a {date} {time}' edited: 'Editat a {date} {time}'
findOtherInstance: Cercar un altre servidor findOtherInstance: Cercar un altre servidor
signupsDisabled: Actualment, les inscripcions en aquest servidor estan desactivades, 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? delete2faConfirm: Això suprimirà irreversiblement 2FA en aquest compte. Procedir?
addRe: Afegeix "re:" al començament del comentari quant responguis a un missatge amb addRe: Afegeix "re:" al començament del comentari quant responguis a un missatge amb
avís de contingut 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" noLists: "Du hast keine Listen angelegt"
note: "Beitrag" note: "Beitrag"
notes: "Beiträge" notes: "Beiträge"
following: "Folge ich" following: "Folgen"
followers: "Folgen mir" followers: "Folgen mir"
followsYou: "Folgt dir" followsYou: "Folgt dir"
createList: "Liste erstellen" createList: "Liste erstellen"
@ -474,7 +474,7 @@ invitations: "Einladungen"
invitationCode: "Einladungscode" invitationCode: "Einladungscode"
checking: "Wird überprüft …" checking: "Wird überprüft …"
available: "Verfügbar" available: "Verfügbar"
unavailable: "Unverfügbar" unavailable: "Nicht verfügbar"
usernameInvalidFormat: "Du kannst Klein- und Großbuchstaben, Zahlen sowie Unterstriche usernameInvalidFormat: "Du kannst Klein- und Großbuchstaben, Zahlen sowie Unterstriche
verwenden." verwenden."
tooShort: "Zu kurz" tooShort: "Zu kurz"
@ -1945,7 +1945,7 @@ _notification:
renote: "Renote" renote: "Renote"
voted: haben bei deiner Umfrage abgestimmt voted: haben bei deiner Umfrage abgestimmt
reacted: hat auf deinen Beitrag reagiert reacted: hat auf deinen Beitrag reagiert
renoted: hat Ihren Beitrag geteilt renoted: hat deinen Beitrag geteilt
_deck: _deck:
alwaysShowMainColumn: "Hauptspalte immer zeigen" alwaysShowMainColumn: "Hauptspalte immer zeigen"
columnAlign: "Spaltenausrichtung" columnAlign: "Spaltenausrichtung"
@ -2099,7 +2099,7 @@ customKaTeXMacro: Individuelle KaTeX Makros
enableCustomKaTeXMacro: Individuelle KaTeX-Makros aktivieren enableCustomKaTeXMacro: Individuelle KaTeX-Makros aktivieren
replayTutorial: Wiederhole die Benutzeranleitung replayTutorial: Wiederhole die Benutzeranleitung
apps: Apps apps: Apps
caption: Automatische Untertitelung caption: Automatische Beschreibung
pwa: PWA installieren pwa: PWA installieren
cw: Inhaltswarnung cw: Inhaltswarnung
older: älter older: älter
@ -2204,3 +2204,7 @@ deletePasskeysConfirm: Alle Passkeys und Security-Keys werden unwiderruflich von
inputNotMatch: Eingabe stimmt nicht überein inputNotMatch: Eingabe stimmt nicht überein
addRe: Ein "re:" am Anfang des Kommentars hinzufügen, um einem Beitrag mit einer Inhaltswarnung addRe: Ein "re:" am Anfang des Kommentars hinzufügen, um einem Beitrag mit einer Inhaltswarnung
zu antworten 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" deleteAndEdit: "Delete and edit"
deleteAndEditConfirm: "Are you sure you want to delete this post and edit it? You deleteAndEditConfirm: "Are you sure you want to delete this post and edit it? You
will lose all reactions, boosts and replies to it." will lose all reactions, boosts and replies to it."
editNote: "Edit note" editNote: "Edit post"
edited: "Edited at {date} {time}" edited: "Edited at {date} {time}"
addToList: "Add to list" addToList: "Add to list"
sendMessage: "Send a message" sendMessage: "Send a message"
@ -337,11 +337,11 @@ emptyDrive: "Your Drive is empty"
emptyFolder: "This folder is empty" emptyFolder: "This folder is empty"
unableToDelete: "Unable to delete" unableToDelete: "Unable to delete"
inputNewFileName: "Enter a new filename" inputNewFileName: "Enter a new filename"
inputNewDescription: "Enter new caption" inputNewDescription: "Enter new description"
inputNewFolderName: "Enter a new folder name" inputNewFolderName: "Enter a new folder name"
circularReferenceFolder: "The destination folder is a subfolder of the folder you circularReferenceFolder: "The destination folder is a subfolder of the folder you
wish to move." wish to move."
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" copyUrl: "Copy URL"
rename: "Rename" rename: "Rename"
avatar: "Avatar" avatar: "Avatar"
@ -634,8 +634,8 @@ disablePlayer: "Close video player"
expandTweet: "Expand tweet" expandTweet: "Expand tweet"
themeEditor: "Theme editor" themeEditor: "Theme editor"
description: "Description" description: "Description"
describeFile: "Add caption" describeFile: "Add description"
enterFileDescription: "Enter caption" enterFileDescription: "Enter description"
author: "Author" author: "Author"
leaveConfirm: "There are unsaved changes. Do you want to discard them?" leaveConfirm: "There are unsaved changes. Do you want to discard them?"
manage: "Management" manage: "Management"
@ -1054,7 +1054,7 @@ showUpdates: "Show a popup when Firefish updates"
recommendedInstances: "Recommended servers" recommendedInstances: "Recommended servers"
recommendedInstancesDescription: "Recommended servers separated by line breaks to recommendedInstancesDescription: "Recommended servers separated by line breaks to
appear in the recommended timeline." appear in the recommended timeline."
caption: "Auto Caption" caption: "Auto description"
splash: "Splash Screen" splash: "Splash Screen"
updateAvailable: "There might be an update available!" updateAvailable: "There might be an update available!"
swipeOnMobile: "Allow swiping between pages" swipeOnMobile: "Allow swiping between pages"
@ -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" showBigPostButton: "Show a bigger post button in the posting form"
confirm: "Confirm" confirm: "Confirm"
emphasizeFollowed: "Highlight the \"Follows you\" sign on your follower info" emphasizeFollowed: "Highlight the \"Follows you\" sign on your follower info"
importZip: "Import ZIP"
exportZip: "Export ZIP"
emojiPackCreator: "Emoji pack creator"
_sensitiveMediaDetection: _sensitiveMediaDetection:
description: "Reduces the effort of server moderation through automatically recognizing description: "Reduces the effort of server moderation through automatically recognizing

View file

@ -572,8 +572,8 @@ updateRemoteUser: "Actualizar información de usuario remoto"
deleteAllFiles: "Borrar todos los archivos" deleteAllFiles: "Borrar todos los archivos"
deleteAllFilesConfirm: "¿Desea borrar todos los archivos?" deleteAllFilesConfirm: "¿Desea borrar todos los archivos?"
removeAllFollowing: "Retener todos los siguientes" removeAllFollowing: "Retener todos los siguientes"
removeAllFollowingDescription: "Cancelar todos los siguientes del servidor {host}. removeAllFollowingDescription: "Al ejecutar esto todas las cuentas de {host} dejarán
Ejecutar en caso de que esta instancia haya dejado de existir." de seguirse. Por favor, ejecuta esto si el servidor ya no existe."
userSuspended: "Este usuario ha sido suspendido." userSuspended: "Este usuario ha sido suspendido."
userSilenced: "Este usuario ha sido silenciado." userSilenced: "Este usuario ha sido silenciado."
yourAccountSuspendedTitle: "Esta cuenta ha sido suspendida" yourAccountSuspendedTitle: "Esta cuenta ha sido suspendida"
@ -838,7 +838,7 @@ gallery: "Galería"
recentPosts: "Posts recientes" recentPosts: "Posts recientes"
popularPosts: "Más vistos" popularPosts: "Más vistos"
shareWithNote: "Compartir con una publicación" shareWithNote: "Compartir con una publicación"
ads: "Banners" ads: "Banners de la comunidad"
expiration: "Termina el" expiration: "Termina el"
memo: "Notas" memo: "Notas"
priority: "Prioridad" priority: "Prioridad"
@ -892,7 +892,7 @@ unmuteThread: "Mostrar hilo"
ffVisibility: "Visibilidad de seguidores y seguidos" ffVisibility: "Visibilidad de seguidores y seguidos"
ffVisibilityDescription: "Puedes configurar quien puede ver a quienes sigues y quienes ffVisibilityDescription: "Puedes configurar quien puede ver a quienes sigues y quienes
te siguen" te siguen"
continueThread: "Ver la continuación del hilo" continueThread: "Continuar hilo"
deleteAccountConfirm: "La cuenta será borrada irreversiblemente. ¿Está seguro?" deleteAccountConfirm: "La cuenta será borrada irreversiblemente. ¿Está seguro?"
incorrectPassword: "La contraseña es incorrecta" incorrectPassword: "La contraseña es incorrecta"
voteConfirm: "¿Confirma su voto a {choice}?" voteConfirm: "¿Confirma su voto a {choice}?"
@ -906,12 +906,12 @@ overridedDeviceKind: "Tipo de dispositivo"
smartphone: "Teléfono smartphone" smartphone: "Teléfono smartphone"
tablet: "Tablet" tablet: "Tablet"
auto: "Automático" auto: "Automático"
themeColor: "Color del tema" themeColor: "Color de la marquesina del servidor"
size: "Tamaño" size: "Tamaño"
numberOfColumn: "Cantidad de columnas" numberOfColumn: "Cantidad de columnas"
searchByGoogle: "Buscar" searchByGoogle: "Buscar"
instanceDefaultLightTheme: "Tema claro por defecto de la instancia" instanceDefaultLightTheme: "Tema claro por defecto del servidor"
instanceDefaultDarkTheme: "Tema oscuro por defecto de la instancia" instanceDefaultDarkTheme: "Tema oscuro por defecto del servidor"
instanceDefaultThemeDescription: "Ingrese el código del tema en formato objeto" instanceDefaultThemeDescription: "Ingrese el código del tema en formato objeto"
mutePeriod: "Período de silenciamiento" mutePeriod: "Período de silenciamiento"
indefinitely: "Sin límite de tiempo" indefinitely: "Sin límite de tiempo"
@ -968,7 +968,7 @@ beta: "Beta"
enableAutoSensitive: "Marcar automáticamente contenido NSFW" enableAutoSensitive: "Marcar automáticamente contenido NSFW"
enableAutoSensitiveDescription: "Permite la detección y marcado automático de contenido enableAutoSensitiveDescription: "Permite la detección y marcado automático de contenido
NSFW usando 'Machine Learning' cuando sea posible. Incluso si esta opción está desactivada, NSFW usando 'Machine Learning' cuando sea posible. Incluso si esta opción está desactivada,
puede ser activado para toda la instancia." puede ser activado para todo el servidor."
activeEmailValidationDescription: "Habilita la validación estricta de direcciones activeEmailValidationDescription: "Habilita la validación estricta de direcciones
de correo electrónico, lo cual incluye la revisión de direcciones desechables y de correo electrónico, lo cual incluye la revisión de direcciones desechables y
si se puede comunicar con éstas. Cuando está deshabilitado, sólo el formato de la si se puede comunicar con éstas. Cuando está deshabilitado, sólo el formato de la
@ -1085,6 +1085,7 @@ _aboutFirefish:
pleaseDonateToHost: También considera donar a tu propio servidor , {host}, para pleaseDonateToHost: También considera donar a tu propio servidor , {host}, para
ayudar con los costos de operación. ayudar con los costos de operación.
sponsors: Patrocinadores de Firefish sponsors: Patrocinadores de Firefish
misskeyContributors: Contribuidores de Misskey
_nsfw: _nsfw:
respect: "Ocultar medios NSFW" respect: "Ocultar medios NSFW"
ignore: "No esconder medios NSFW " ignore: "No esconder medios NSFW "
@ -1177,8 +1178,10 @@ _mfm:
fade: Fundido fade: Fundido
advanced: MFM avanzado advanced: MFM avanzado
play: Reproducir MFM play: Reproducir MFM
foregroundDescription: Cambiar el color en primer plano del texto. foregroundDescription: Cambiar el color del texto en primer plano.
background: Color de fondo background: Color de fondo
positionDescription: Mueve el contenido en una cantidad especificada.
fadeDescription: Funde el contenido dentro y fuera.
_instanceTicker: _instanceTicker:
none: "No mostrar" none: "No mostrar"
remote: "Mostrar a usuarios remotos" remote: "Mostrar a usuarios remotos"
@ -1197,7 +1200,7 @@ _channel:
owned: "Dueño" owned: "Dueño"
following: "Siguiendo" following: "Siguiendo"
usersCount: "{n} participantes" usersCount: "{n} participantes"
notesCount: "{n} publicaciones" notesCount: "{n} Publicaciones"
nameOnly: Nombre solamente nameOnly: Nombre solamente
nameAndDescription: Nombre y descripción nameAndDescription: Nombre y descripción
_menuDisplay: _menuDisplay:
@ -1301,7 +1304,7 @@ _theme:
fgHighlighted: "Texto resaltado" fgHighlighted: "Texto resaltado"
_sfx: _sfx:
note: "Nueva publicación" note: "Nueva publicación"
noteMy: "Nota (a mí mismo)" noteMy: "Publicación propia"
notification: "Notificaciones" notification: "Notificaciones"
chat: "Chat" chat: "Chat"
chatBg: "Chat (Fondo)" chatBg: "Chat (Fondo)"
@ -1310,11 +1313,11 @@ _sfx:
_ago: _ago:
future: "Futuro" future: "Futuro"
justNow: "Recién ahora" justNow: "Recién ahora"
secondsAgo: "Hace {n} segundo(s)" secondsAgo: "Hace {n} s"
minutesAgo: "Hace {n} minuto(s)" minutesAgo: "Hace {n} m"
hoursAgo: "Hace {n} hora(s)" hoursAgo: "Hace {n} hora(s)"
daysAgo: "Hace {n} día(s)" daysAgo: "Hace {n} d"
weeksAgo: "Hace {n} semana(s)" weeksAgo: "Hace {n} sem"
monthsAgo: "Hace {n} mes(es)" monthsAgo: "Hace {n} mes(es)"
yearsAgo: "Hace {n} año(s)" yearsAgo: "Hace {n} año(s)"
_time: _time:
@ -1339,15 +1342,15 @@ _tutorial:
step5_1: "¡Líneas de tiempo, líneas de tiempo por todas partes!" step5_1: "¡Líneas de tiempo, líneas de tiempo por todas partes!"
step5_2: "Tu servidor tiene {timelines} diferentes líneas de tiempo habilitadas." step5_2: "Tu servidor tiene {timelines} diferentes líneas de tiempo habilitadas."
step5_3: "La línea de tiempo Inicio {icon} es donde puedes ver las publicaciones step5_3: "La línea de tiempo Inicio {icon} es donde puedes ver las publicaciones
de tus seguidores." de personas que sigues."
step5_4: "La línea de tiempo Local {icon} es donde puedes ver las publicaciones step5_4: "La línea de tiempo Local {icon} es donde puedes ver las publicaciones
de todos los demás en esta instancia." de todos los demás en este servidor."
step5_5: "La línea de tiempo {icon} recomendada es donde puedes ver las publicaciones step5_5: "La línea de tiempo {icon} social es una combinación de las líneas de tiempo
de las instancias que los administradores recomiendan." Inicio y Local."
step5_6: "La línea de tiempo Social {icon} es donde puedes ver las publicaciones step5_6: "La línea de tiempo {icon} recomendada es donde puedes ver las publicaciones
de los amigos de tus seguidores." de los servidores que los administradores recomiendan."
step5_7: "La línea de tiempo Global {icon} es donde puedes ver las publicaciones step5_7: "La línea de tiempo {icon} global es donde puedes ver las publicaciones
de todas las demás instancias conectadas." de todos los demás servidores a los cuales este servidor conecta."
step6_1: "Entonces, ¿qué es este lugar?" step6_1: "Entonces, ¿qué es este lugar?"
step6_2: "Bueno, no sólo te has unido a Firefish. Te has unido a un portal del Fediverso, step6_2: "Bueno, no sólo te has unido a Firefish. Te has unido a un portal del Fediverso,
una red interconectada de miles de servidores, llamada \"instancias\"" una red interconectada de miles de servidores, llamada \"instancias\""
@ -1368,6 +1371,26 @@ _2fa:
securityKeyInfo: "Se puede configurar el inicio de sesión usando una clave de seguridad securityKeyInfo: "Se puede configurar el inicio de sesión usando una clave de seguridad
de hardware que soporte FIDO2 o con un certificado de huella digital o con un de hardware que soporte FIDO2 o con un certificado de huella digital o con un
PIN" PIN"
chromePasskeyNotSupported: Contraseñas de Chrome no están soportadas.
removeKeyConfirm: ¿Realmente deseas borrar la clave {name}?
step3Title: Ingresa un código de autorización
renewTOTP: Reconfigurar la aplicación autorizadora
whyTOTPOnlyRenew: La aplicación autorizadora no puede ser quitada mientras la clave
de seguridad siga registrada.
renewTOTPConfirm: Esto causará que los códigos de verificación de la aplicación
anterior dejen de funcionar
renewTOTPOk: Reconfigurar
securityKeyNotSupported: Tu navegador no soporta claves de seguridad.
step2Click: Presionar este código QR te permitirá registrar la autorización 2FA
a tu clave de seguridad o aplicación autorizadora.
registerTOTPBeforeKey: Por favor configura una aplicación autorizadora para registrar
una clave de seguridad o de paso.
securityKeyName: Ingresa el nombre de la clave
tapSecurityKey: Por favor, espera al navegador para registrar la clave de seguridad
o de paso
renewTOTPCancel: Cancelar
token: Token 2FA
removeKey: Quitar clave de seguridad
_permissions: _permissions:
"read:account": "Ver información de la cuenta" "read:account": "Ver información de la cuenta"
"write:account": "Editar información de la cuenta" "write:account": "Editar información de la cuenta"
@ -1383,7 +1406,7 @@ _permissions:
"write:messaging": "Administrar chat" "write:messaging": "Administrar chat"
"read:mutes": "Ver usuarios silenciados" "read:mutes": "Ver usuarios silenciados"
"write:mutes": "Administrar usuarios silenciados" "write:mutes": "Administrar usuarios silenciados"
"write:notes": "Crear/borrar notas" "write:notes": "Crear o borrar publicaciones"
"read:notifications": "Ver notificaciones" "read:notifications": "Ver notificaciones"
"write:notifications": "Administrar notificaciones" "write:notifications": "Administrar notificaciones"
"read:reactions": "Ver reacciones" "read:reactions": "Ver reacciones"
@ -1405,16 +1428,19 @@ _auth:
shareAccess: "¿Desea permitir el acceso a la cuenta \"{name}\"?" shareAccess: "¿Desea permitir el acceso a la cuenta \"{name}\"?"
shareAccessAsk: "¿Está seguro de que desea autorizar esta aplicación para acceder shareAccessAsk: "¿Está seguro de que desea autorizar esta aplicación para acceder
a su cuenta?" a su cuenta?"
permissionAsk: "Esta aplicación requiere los siguientes permisos" permissionAsk: "Esta aplicación requiere los siguientes permisos:"
pleaseGoBack: "Por favor, vuelve a la aplicación" pleaseGoBack: "Por favor, vuelve a la aplicación"
callback: "Volviendo a la aplicación" callback: "Volviendo a la aplicación"
denied: "Acceso denegado" denied: "Acceso denegado"
copyAsk: 'Por favor, pega el siguiente código de autorización en la aplicación:'
allPermissions: Acceso completo
_antennaSources: _antennaSources:
all: "Todas las notas" all: "Todas las publicaciones"
homeTimeline: "Notas de los usuarios que sigues" homeTimeline: "Publicaciones de los usuarios que sigues"
users: "Notas de un usuario o varios" users: "Publicaciones de usuarios específicos"
userList: "Notas de los usuarios de una lista" userList: "Publicaciones de una lista de usuarios específica"
userGroup: "Notas de los usuarios de una grupo" userGroup: "Publicaciones de usuarios de un grupo"
instances: Publicaciones de todos los usuarios en un servidor
_weekday: _weekday:
sunday: "Domingo" sunday: "Domingo"
monday: "Lunes" monday: "Lunes"
@ -1431,24 +1457,28 @@ _widgets:
trends: "Tendencias" trends: "Tendencias"
clock: "Reloj" clock: "Reloj"
rss: "Lector RSS" rss: "Lector RSS"
rssTicker: "Ticker-RSS" rssTicker: "Marquesina RSS"
activity: "Actividad" activity: "Actividad"
photos: "Fotos" photos: "Fotos"
digitalClock: "Reloj digital" digitalClock: "Reloj digital"
unixClock: "Reloj UNIX" unixClock: "Reloj UNIX"
federation: "Federación" federation: "Federación"
instanceCloud: "Nube de palabras de la instancia" instanceCloud: "Nube de servidores"
postForm: "Formulario" postForm: "Formulario"
slideshow: "Diapositivas" slideshow: "Diapositivas"
button: "Botón" button: "Botón"
onlineUsers: "Usuarios en linea" onlineUsers: "Usuarios en línea"
jobQueue: "Cola de trabajos" jobQueue: "Cola de trabajos"
serverMetric: "Estadísticas del servidor" serverMetric: "Estadísticas del servidor"
aiscript: "Consola de AiScript" aiscript: "Consola de AiScript"
aichan: "indigo" aichan: "indigo"
userList: Lista Usuarios userList: Lista de usuarios
_userList: _userList:
chooseList: Seleccione una lista chooseList: Seleccione una lista
serverInfo: Información del servidor
meiliStatus: Estado del servidor
meiliSize: Tamaño del índice
meiliIndexCount: Publicaciones indizadas
_cw: _cw:
hide: "Ocultar" hide: "Ocultar"
show: "Ver más" show: "Ver más"
@ -1478,18 +1508,18 @@ _poll:
remainingSeconds: "Quedan {s} segundos para que finalice" remainingSeconds: "Quedan {s} segundos para que finalice"
_visibility: _visibility:
public: "Público" public: "Público"
publicDescription: "Visible para todos los usuarios" publicDescription: "Tu publicación será visible en todas las líneas de tiempo"
home: "Inicio" home: "Sin listar (Inicio)"
homeDescription: "Visible sólo en la linea de tiempo de inicio" homeDescription: "Visible sólo en la linea de tiempo de inicio"
followers: "Seguidores" followers: "Seguidores"
followersDescription: "Visible sólo para tus seguidores" followersDescription: "Hacer sólo visible sólo para tus seguidores y usuarios mencionados"
specified: "Mensaje directo" specified: "Mensaje directo"
specifiedDescription: "Visible sólo para los usuarios elegidos" specifiedDescription: "Visible sólo para los usuarios elegidos"
localOnly: "Solo local" localOnly: "Solo local"
localOnlyDescription: "Oculto para usuarios remotos" localOnlyDescription: "Oculto para usuarios remotos"
_postForm: _postForm:
replyPlaceholder: "Responder a esta nota" replyPlaceholder: "Responder a esta publicación..."
quotePlaceholder: "Citar esta nota" quotePlaceholder: "Citar esta publicación..."
channelPlaceholder: "Postear en el canal" channelPlaceholder: "Postear en el canal"
_placeholders: _placeholders:
a: "¿Qué haces?" a: "¿Qué haces?"
@ -1514,7 +1544,7 @@ _profile:
locationDescription: Si ingresas tu ciudad primero, el tiempo local tuyo será visible locationDescription: Si ingresas tu ciudad primero, el tiempo local tuyo será visible
para otros usuarios. para otros usuarios.
_exportOrImport: _exportOrImport:
allNotes: "Todas las notas" allNotes: "Todas las publicaciones"
followingList: "Siguiendo" followingList: "Siguiendo"
muteList: "Silenciados" muteList: "Silenciados"
blockingList: "Bloqueados" blockingList: "Bloqueados"
@ -1527,10 +1557,10 @@ _charts:
usersIncDec: "Variación de usuarios" usersIncDec: "Variación de usuarios"
usersTotal: "Total de usuarios" usersTotal: "Total de usuarios"
activeUsers: "Cantidad de usuarios activos" activeUsers: "Cantidad de usuarios activos"
notesIncDec: "Variación de la cantidad de notas" notesIncDec: "Diferencia en la cantidad de publicaciones"
localNotesIncDec: "Variación de la cantidad de notas locales" localNotesIncDec: "Diferencia en la cantidad de publicaciones locales"
remoteNotesIncDec: "Variación de la cantidad de notas remotas" remoteNotesIncDec: "Diferencia en el número de publicaciones remotas"
notesTotal: "Total de notas" notesTotal: "Total de publicaciones"
filesIncDec: "Variación de cantidad de archivos" filesIncDec: "Variación de cantidad de archivos"
filesTotal: "Total de archivos" filesTotal: "Total de archivos"
storageUsageIncDec: "Variación de uso del almacenamiento" storageUsageIncDec: "Variación de uso del almacenamiento"
@ -1539,8 +1569,8 @@ _instanceCharts:
requests: "Pedidos" requests: "Pedidos"
users: "Variación de usuarios" users: "Variación de usuarios"
usersTotal: "Total acumulado de usuarios" usersTotal: "Total acumulado de usuarios"
notes: "Variación de la cantidad de notas" notes: "Diferencia en el número de publicaciones"
notesTotal: "Total acumulado de la cantidad de notas" notesTotal: "Total acumulado de publicaciones"
ff: "Variación de cantidad de seguidos/seguidores" ff: "Variación de cantidad de seguidos/seguidores"
ffTotal: "Total acumulado de cantidad de seguidos/seguidores" ffTotal: "Total acumulado de cantidad de seguidos/seguidores"
cacheSize: "Variación del tamaño de la caché" cacheSize: "Variación del tamaño de la caché"
@ -1627,10 +1657,10 @@ _pages:
id: "Lienzo ID" id: "Lienzo ID"
width: "Ancho" width: "Ancho"
height: "Altura" height: "Altura"
note: "Nota embebida" note: "Publicación incrustada"
_note: _note:
id: "Id de la nota" id: "ID de la publicación"
idDescription: "Pega la URL de la nota para configurarla" idDescription: "Puedes también pegar la URL de la publicación aquí."
detailed: "Ver Detalles" detailed: "Ver Detalles"
switch: "Interruptor" switch: "Interruptor"
_switch: _switch:
@ -1853,7 +1883,7 @@ _notification:
youGotMention: "Mención de {name}" youGotMention: "Mención de {name}"
youGotReply: "Respuesta de {name}" youGotReply: "Respuesta de {name}"
youGotQuote: "Citado por {name}" youGotQuote: "Citado por {name}"
youRenoted: "Renotado por {name}" youRenoted: "Impulsado por {name}"
youGotPoll: "Encuestado por {name}" youGotPoll: "Encuestado por {name}"
youGotMessagingMessageFromUser: "{name} comenzó un chat contigo" youGotMessagingMessageFromUser: "{name} comenzó un chat contigo"
youGotMessagingMessageFromGroup: "Tienes un chat de {name}" youGotMessagingMessageFromGroup: "Tienes un chat de {name}"
@ -1868,7 +1898,7 @@ _notification:
follow: "Siguiendo" follow: "Siguiendo"
mention: "Menciones" mention: "Menciones"
reply: "Respuestas" reply: "Respuestas"
renote: "Renotar" renote: "Impulsos"
quote: "Citar" quote: "Citar"
reaction: "Reacción" reaction: "Reacción"
pollVote: "Votado en la encuesta" pollVote: "Votado en la encuesta"
@ -1880,7 +1910,10 @@ _notification:
_actions: _actions:
followBack: "Te sigue de vuelta" followBack: "Te sigue de vuelta"
reply: "Responder" reply: "Responder"
renote: "Renotar" renote: "Impulsos"
renoted: impulsó tu publicación
reacted: reaccionó a tu publicación
voted: votó en tu encuesta
_deck: _deck:
alwaysShowMainColumn: "Siempre mostrar la columna principal" alwaysShowMainColumn: "Siempre mostrar la columna principal"
columnAlign: "Alinear columnas" columnAlign: "Alinear columnas"
@ -1892,9 +1925,9 @@ _deck:
swapDown: "Mover abajo" swapDown: "Mover abajo"
stackLeft: "Apilar a la izquierda" stackLeft: "Apilar a la izquierda"
popRight: "Sacar a la derecha" popRight: "Sacar a la derecha"
profile: "Perfil" profile: "Espacio de trabajo"
newProfile: "Nuevo perfil" newProfile: "Nuevo espacio de trabajo"
deleteProfile: "Eliminar perfil" deleteProfile: "Eliminar espacio de trabajo"
introduction: "¡Crea la interfaz perfecta para tí organizando las columnas libremente!" introduction: "¡Crea la interfaz perfecta para tí organizando las columnas libremente!"
introduction2: "Presiona en la + de la derecha de la pantalla para añadir nuevas introduction2: "Presiona en la + de la derecha de la pantalla para añadir nuevas
columnas donde quieras." columnas donde quieras."
@ -1905,10 +1938,13 @@ _deck:
widgets: "Widgets" widgets: "Widgets"
notifications: "Notificaciones" notifications: "Notificaciones"
tl: "Linea de tiempo" tl: "Linea de tiempo"
antenna: "Antenas" antenna: "Antena"
list: "Listas" list: "Listas"
mentions: "Menciones" mentions: "Menciones"
direct: "Mensaje directo" direct: "Mensajes directos"
channel: Canal
renameProfile: Renombrar espacio de trabajo
nameAlreadyExists: Este nombre de espacio de trabajo ya existe.
manageGroups: Administrar grupos manageGroups: Administrar grupos
replayTutorial: Repetir Tutorial replayTutorial: Repetir Tutorial
privateMode: Modo privado privateMode: Modo privado
@ -1923,11 +1959,13 @@ breakFollowConfirm: ¿Estás seguro de que quieres eliminar el seguidor?
subscribePushNotification: Habilitar notificaciones subscribePushNotification: Habilitar notificaciones
unsubscribePushNotification: Desactivar notificaciones unsubscribePushNotification: Desactivar notificaciones
pushNotificationAlreadySubscribed: Las notificaciones ya están activados pushNotificationAlreadySubscribed: Las notificaciones ya están activados
pushNotificationNotSupported: Su navegador o instancia no admite notificaciones pushNotificationNotSupported: Su navegador o servidor no admite notificaciones
moveAccount: ¡Mover cuenta! moveAccount: ¡Mover cuenta!
moveFrom: Mueve a esta cuenta de una cuenta antigua moveFrom: Mueve a esta cuenta de una cuenta antigua
moveFromLabel: 'La cuenta que estás moviendo de:' moveFromLabel: 'La cuenta que estás moviendo de:'
moveAccountDescription: '' moveAccountDescription: 'Este proceso es irreversible. Asegúrate de configurar un
alias para ésta cuenta en tu cuenta nueva antes de comenzar. Por favor, ingresa
la etiqueta de la cuenta en el formato siguiente: @persona@servidor.tld'
license: Licencia license: Licencia
noThankYou: No gracias noThankYou: No gracias
userSaysSomethingReason: '{name} dijo {reason}' userSaysSomethingReason: '{name} dijo {reason}'
@ -1938,7 +1976,7 @@ caption: Auto Subtítulos
showAds: Mostrar banners showAds: Mostrar banners
enterSendsMessage: Presione "RETORNO" en los mensajes para enviar el mensaje (para enterSendsMessage: Presione "RETORNO" en los mensajes para enviar el mensaje (para
apagarlo es Ctrl + RETORNO) apagarlo es Ctrl + RETORNO)
recommendedInstances: Instancias Recomendadas recommendedInstances: Servidores recomendados
instanceSecurity: Seguridad del servidor instanceSecurity: Seguridad del servidor
seperateRenoteQuote: Separar botones de Impulsar y Citar seperateRenoteQuote: Separar botones de Impulsar y Citar
_messaging: _messaging:
@ -1995,6 +2033,10 @@ _filters:
fromUser: Del usuario fromUser: Del usuario
fromDomain: Desde el dominio fromDomain: Desde el dominio
notesAfter: Publicaciones posteriores notesAfter: Publicaciones posteriores
followingOnly: Sólo seguidos
withFile: Con archivo
followersOnly: Sólo seguidores
notesBefore: Publicaciones anteriores
userSaysSomethingReasonReply: '{name} respondió a una publicación que contiene {reason}' userSaysSomethingReasonReply: '{name} respondió a una publicación que contiene {reason}'
userSaysSomethingReasonQuote: '{name} citó una publicación que contiene {reason}' userSaysSomethingReasonQuote: '{name} citó una publicación que contiene {reason}'
privateModeInfo: Al activar, solo servidores autorizados podrán federar con tu servidor. privateModeInfo: Al activar, solo servidores autorizados podrán federar con tu servidor.
@ -2022,3 +2064,103 @@ remindMeLater: Recordar nuevamente
removeQuote: Eliminar cita removeQuote: Eliminar cita
removeRecipient: Eliminar destinatario removeRecipient: Eliminar destinatario
removeMember: Eliminar miembro removeMember: Eliminar miembro
_skinTones:
light: Claro
dark: Obscuro
yellow: Amarillo
medium: Medio
mediumLight: Claro medio
mediumDark: Obscuro medio
secureModeInfo: Al pedir a otros servidores, no mandar si no hay prueba de confianza.
enableIdenticonGeneration: Activar la generación de Identicon
sendModMail: Enviar aviso de moderación
reactionPickerSkinTone: Tono de piel preferido en emojis
_dialog:
charactersExceeded: '¡Límite de caracteres excedido! Actual: {current}/Límite: {max}'
charactersBelow: '¡Caracteres insuficientes! Actual: {current}/Límite: {min}'
expandOnNoteClick: Abrir publicación al hacer click
_experiments:
enablePostImports: Habilitar importación de publicaciones
title: Experimentos
postImportsCaption: Permite a los usuarios importar sus publicaciones desde Firefish,
Misskey, Mastodon, Akkoma y Pleroma. Puede causar una bajada en el rendimiento
del servidor si la cola de trabajos está atascada.
showUpdates: Mostrar una notificación emergente cuando Firefish se actualice
recommendedInstancesDescription: Servidores recomendados separador por saltos de línea
para que aparezcan el la línea de tiempo recomendados.
swipeOnMobile: Permitir el pase entre páginas
addRe: Añadir "re:" al comienzo del comentario en una respuesta a una publicación
sin aviso de contenido
showAdminUpdates: Avisar si hay una nueva versión de Firefish disponible (sólo adminsitrador)
_feeds:
rss: RSS
copyFeed: Copiar feed
atom: Atom
jsonFeed: Feed JSON
secureMode: Modo seguro (Recuperación Autorizada)
splash: Pantalla de bienvenida
moveToLabel: 'Cuenta a la cual estás migrando:'
alt: ALT
video: Video
audio: Audio
swipeOnDesktop: Permitir el pase de páginas del estilo móvil en el escritorio
enableCustomKaTeXMacro: Habilitar macros KaTeX personalizadas
noteId: ID de publicación
preventAiLearning: Prevenir el uso por parte de bots de IA
isLocked: Esta cuenta requiere aprobación de seguidores
origin: Origen
newer: reciente
older: antiguo
objectStorageS3ForcePathStyle: Usar URL de punto final basada en rutas
objectStorageS3ForcePathStyleDesc: Activa esto para construir puntos finales URL en
el formato 's3.amazonaws.com/<bucket>/' en lugar de '<bucket>.s3.amazonaws.com'.
customSplashIconsDescription: URL para los iconos de la pantalla de bienvenida separadas
por saltos de línea para ser mostrados al azar cada vez que el usuario carga/recarga
la página. Por favor, asegúrate que las imágenes sean URL estáticas, preferentemente
a 192x192.
updateAvailable: ¡Quizá hay una actualización disponible!
moveTo: Mover la cuenta actual a una cuenta nueva
moveFromDescription: 'Esto pondrá un alias en tu cuenta antigua para así poder migrar
desde esa cuenta a la nueva. Haz esto ANTES de migrar tu cuenta antigua. Por favor,
ingresa la etiqueta de la cuenta con el formato siguiente: @persona@servidor.tld'
defaultReaction: Emoji por defecto para reaccionar a las publicaciones entrantes y
salientes
indexFromDescription: Deja en blanco para indizar todas las publicaciones
deletePasskeys: Borrar claves de paso
deletePasskeysConfirm: Esto borrará irreversiblemente todas las claves de paso y de
seguridad en esta cuenta, ¿Proceder?
inputNotMatch: Las entradas no coinciden
indexFrom: Indizar desde la ID de la publicación en adelante
indexPosts: Indizar publicaciones
isModerator: Moderador
isAdmin: Administrador
isPatron: Mecenas de Firefish
logoImageUrl: URL de la imagen del logotipo
xl: XL
migrationConfirm: "¿Estás absolutamente seguro de que quieres migrar a tu cuenta a
{account}? Una vez hecho esto, no podrás revertir el cambio, ni tampoco usar tu
cuenta normalmente.\nTambién, asegúrate de que has configurado ésta cuenta como
la cuenta desde la cual estás migrando."
indexNotice: Indizando ahora. Esto puede llevar bastante tiempo, por favor, no reinicies
el servidor por lo menos hasta dentro de una hora.
customKaTeXMacro: Macros KaTeX personalizadas
customKaTeXMacroDescription: '¡Configura macros para escribir expresiones matemáticas
fácilmente! La notación es conforme la las definiciones de comandos LaTeX y puede
ser escrita como \nuevocomando{\ nombre}{contenido} o \nuevocomando{\nombre}[número
de argumentos]{contenido}. Por ejemplo, \nuevocomando{\añadir}[2]{#1 + #2} expanderá
\añadir{3}{foo} a 3 + foo. Las llaves que contienen al nombre de la macro serán
cambiadas a paréntesis o corchetes. Esto afecta a los corchetes usados para argumentos.
Una (y sólo una) macro puede ser definida por línea, y no podrás saltar la línea
en medio de la definición. Líneas erróneas son ignoradas. Sólo funciones de sustitución
simple son soportadas; sintaxis avanzada, como ramificación condicional no puede
ser usada aquí.'
signupsDisabled: Los registros en esta instancia están desactivados, pero, ¡siempre
podrás registrarte en otro servidor! Si tienes un código de invitación para este
servidor, por favor, rellena el campo siguiente.
preventAiLearningDescription: Pedir a los modelos de IA no analizar el contenido de
publicas, como publicaciones e imágenes.
noGraze: Por favor desactiva la extensión de navegador "Graze for Mastodon" ya que
interfiere con Firefish.
silencedWarning: Esta página se muestra debido a que estos usuarios son de servidores
que tu administrador ha silenciado, ya que son presumiblemente fuente de spam.
isBot: Esta cuenta es un bot

View file

@ -53,7 +53,7 @@ sendMessage: "Envoyer un message"
copyUsername: "Copier le nom dutilisateur·rice" copyUsername: "Copier le nom dutilisateur·rice"
searchUser: "Chercher un·e utilisateur·rice" searchUser: "Chercher un·e utilisateur·rice"
reply: "Répondre" reply: "Répondre"
loadMore: "Afficher plus" loadMore: "Charger plus"
showMore: "Afficher plus" showMore: "Afficher plus"
showLess: "Fermer" showLess: "Fermer"
youGotNewFollower: "Vous suit" youGotNewFollower: "Vous suit"
@ -120,8 +120,8 @@ reactionSettingDescription2: "Déplacer pour réorganiser, cliquer pour effacer,
« + » pour ajouter." « + » pour ajouter."
rememberNoteVisibility: "Se souvenir des paramètres de visibilité des publications" rememberNoteVisibility: "Se souvenir des paramètres de visibilité des publications"
attachCancel: "Supprimer le fichier attaché" attachCancel: "Supprimer le fichier attaché"
markAsSensitive: "Marquer comme sensible" markAsSensitive: "Marquer comme sensible (NSFW)"
unmarkAsSensitive: "Supprimer le marquage comme sensible" unmarkAsSensitive: "Supprimer le marquage comme sensible (NSFW)"
enterFileName: "Entrer le nom du fichier" enterFileName: "Entrer le nom du fichier"
mute: "Masquer" mute: "Masquer"
unmute: "Ne plus masquer" unmute: "Ne plus masquer"
@ -245,7 +245,7 @@ currentPassword: "Mot de passe actuel"
newPassword: "Nouveau mot de passe" newPassword: "Nouveau mot de passe"
newPasswordRetype: "Répéter le nouveau mot de passe" newPasswordRetype: "Répéter le nouveau mot de passe"
attachFile: "Joindre un fichier" attachFile: "Joindre un fichier"
more: "Plus" more: "Plus !"
featured: "Tendances" featured: "Tendances"
usernameOrUserId: "Nom dutilisateur·rice ou ID utilisateur" usernameOrUserId: "Nom dutilisateur·rice ou ID utilisateur"
noSuchUser: "Utilisateur·rice non trouvé·e" noSuchUser: "Utilisateur·rice non trouvé·e"
@ -318,7 +318,7 @@ copyUrl: "Copier lURL"
rename: "Renommer" rename: "Renommer"
avatar: "Avatar" avatar: "Avatar"
banner: "Bannière" banner: "Bannière"
nsfw: "Contenu sensible" nsfw: "Contenu sensible (NSFW)"
whenServerDisconnected: "Lorsque la connexion au serveur est perdue" whenServerDisconnected: "Lorsque la connexion au serveur est perdue"
disconnectedFromServer: "Déconnecté·e du serveur" disconnectedFromServer: "Déconnecté·e du serveur"
reload: "Rafraîchir" reload: "Rafraîchir"
@ -516,11 +516,11 @@ showFeaturedNotesInTimeline: "Afficher les publications des Tendances dans le fi
d'actualité" d'actualité"
objectStorage: "Stockage d'objets" objectStorage: "Stockage d'objets"
useObjectStorage: "Utiliser le stockage d'objets" useObjectStorage: "Utiliser le stockage d'objets"
objectStorageBaseUrl: "Base URL" objectStorageBaseUrl: "URL racine"
objectStorageBaseUrlDesc: "Préfixe dURL utilisé pour construire lURL vers le référencement objectStorageBaseUrlDesc: "Préfixe dURL utilisé pour construire lURL vers le référencement
dobjet (média). Spécifiez son URL si vous utilisez un CDN ou un proxy, sinon spécifiez dobjet (média). Spécifiez son URL si vous utilisez un CDN ou un proxy, sinon spécifiez
ladresse accessible au public selon le guide de service que vous allez utiliser.\n ladresse accessible au public selon le guide de service que vous allez utiliser.\n
Ex: 'https://<bucket>.s3.amazonaws.com' pour AWS S3 et 'https://storage.googleapis.com/<bucket>' Ex: 'https://<bucket>.s3.amazonaws.com' pour AWS S3 et 'https://storage.googleapis.com/<bucket>'
pour GCS." pour GCS."
objectStorageBucket: "Bucket" objectStorageBucket: "Bucket"
objectStorageBucketDesc: "Veuillez spécifier le nom du compartiment utilisé sur le objectStorageBucketDesc: "Veuillez spécifier le nom du compartiment utilisé sur le
@ -591,7 +591,7 @@ divider: "Séparateur"
addItem: "Ajouter un élément" addItem: "Ajouter un élément"
relays: "Relais" relays: "Relais"
addRelay: "Ajouter un relais" addRelay: "Ajouter un relais"
inboxUrl: "Inbox URL" inboxUrl: "URL de boîte de récéption"
addedRelays: "Relais ajoutés" addedRelays: "Relais ajoutés"
serviceworkerInfo: "Devrait être activé pour les notifications push." serviceworkerInfo: "Devrait être activé pour les notifications push."
deletedNote: "Publication supprimée" deletedNote: "Publication supprimée"
@ -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 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 sur \"Abonné-e-s\", vos publications sont visibles par tous, même si vous exigez
que les demandes d'abonnement soient approuvées manuellement." 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" loadRawImages: "Affichage complet des images jointes au lieu des vignettes"
disableShowingAnimatedImages: "Désactiver l'animation des images" disableShowingAnimatedImages: "Désactiver l'animation des images"
verificationEmailSent: "Un e-mail de vérification a été envoyé. Veuillez accéder au verificationEmailSent: "Un e-mail de vérification a été envoyé. Veuillez accéder au
@ -839,7 +839,7 @@ gallery: "Galerie"
recentPosts: "Publications récentes" recentPosts: "Publications récentes"
popularPosts: "Publications populaires" popularPosts: "Publications populaires"
shareWithNote: "Partager dans une publication" shareWithNote: "Partager dans une publication"
ads: "Bannière communautaire" ads: "Bannières communautaires"
expiration: "Échéance" expiration: "Échéance"
memo: "Pense-bête" memo: "Pense-bête"
priority: "Priorité" priority: "Priorité"
@ -986,8 +986,8 @@ _plugin:
manage: "Gestion des plugins" manage: "Gestion des plugins"
_registry: _registry:
scope: "Portée" scope: "Portée"
key: "Clé " key: "Clé"
keys: "Clé " keys: "Clés"
domain: "Domaine" domain: "Domaine"
createKey: "Créer une clé" createKey: "Créer une clé"
_aboutFirefish: _aboutFirefish:
@ -1011,8 +1011,8 @@ _aboutFirefish:
le lien ci-dessus pour avoir votre nom affiché ici ! le lien ci-dessus pour avoir votre nom affiché ici !
misskeyContributors: Contributeurs Misskey misskeyContributors: Contributeurs Misskey
_nsfw: _nsfw:
respect: "Cacher les médias marqués comme contenu sensible" respect: "Cacher les médias marqués comme contenu sensible (NSFW)"
ignore: "Afficher les médias sensibles" ignore: "Afficher les médias sensibles (NSFW)"
force: "Cacher tous les médias" force: "Cacher tous les médias"
_mfm: _mfm:
cheatSheet: "Antisèche MFM" cheatSheet: "Antisèche MFM"
@ -1021,7 +1021,7 @@ _mfm:
dummy: "La Fédiverse s'agrandit avec Firefish" dummy: "La Fédiverse s'agrandit avec Firefish"
mention: "Mentionner" mention: "Mentionner"
mentionDescription: "Vous pouvez afficher un utilisateur spécifique en indiquant mentionDescription: "Vous pouvez afficher un utilisateur spécifique en indiquant
une arobase suivie d'un nom d'utilisateur" le symbole d'arobase (@) suivie d'un nom d'utilisateur."
hashtag: "Hashtags" hashtag: "Hashtags"
hashtagDescription: "Vous pouvez afficher les hashtags en utilisant un croisillon hashtagDescription: "Vous pouvez afficher les hashtags en utilisant un croisillon
et du texte." et du texte."
@ -1034,7 +1034,7 @@ _mfm:
small: "Diminuer l'emphase" small: "Diminuer l'emphase"
smallDescription: "Le contenu peut être affiché en petit et fin." smallDescription: "Le contenu peut être affiché en petit et fin."
center: "Centrer" center: "Centrer"
centerDescription: "Le contenu peut être centré" centerDescription: "Centre le contenu sur la page."
inlineCode: "Code (inline)" inlineCode: "Code (inline)"
inlineCodeDescription: "Affiche la coloration syntaxique des lignes de code." inlineCodeDescription: "Affiche la coloration syntaxique des lignes de code."
blockCode: "Bloc de code" blockCode: "Bloc de code"
@ -1105,7 +1105,7 @@ _mfm:
background: Couleur d'arrière-plan background: Couleur d'arrière-plan
plain: Simple plain: Simple
_instanceTicker: _instanceTicker:
none: "Cacher " none: "Cacher"
remote: "Montrer pour les utilisateur·ice·s distant·e·s" remote: "Montrer pour les utilisateur·ice·s distant·e·s"
always: "Toujours afficher" always: "Toujours afficher"
_serverDisconnectedBehavior: _serverDisconnectedBehavior:
@ -1170,17 +1170,17 @@ _theme:
color: "Couleur" color: "Couleur"
refProp: "Appeler une propriété" refProp: "Appeler une propriété"
refConst: "Appeler une constante" refConst: "Appeler une constante"
key: "Clé " key: "Clé"
func: "Fonction" func: "Fonction"
funcKind: "Type de fonction" funcKind: "Type de fonction"
argument: "Argument" argument: "Argument"
basedProp: "Nom de la propriété référencée" basedProp: "Nom de la propriété référencée"
alpha: "Transparence" alpha: "Transparence"
darken: "Sombre" darken: "Assombrir"
lighten: "Clair" lighten: "Clair"
inputConstantName: "Insérez un nom de constante" inputConstantName: "Insérez un nom de constante"
importInfo: "Vous pouvez importer un thème vers léditeur de thèmes en saisissant importInfo: "Vous pouvez importer un thème vers léditeur de thèmes en saisissant
son code ici." son code ici"
deleteConstantConfirm: "Êtes-vous sûr·e de vouloir supprimer la constante {const} deleteConstantConfirm: "Êtes-vous sûr·e de vouloir supprimer la constante {const}
?" ?"
keys: keys:
@ -1252,9 +1252,9 @@ _time:
day: "j" day: "j"
_tutorial: _tutorial:
title: "Comment utiliser Firefish" title: "Comment utiliser Firefish"
step1_1: "Bienvenue!" step1_1: "Bienvenue!"
step1_2: "On va vous installer. Vous serez opérationnel en un rien de temps" step1_2: "On va vous installer. Vous serez opérationnel en un rien de temps !"
step2_1: "Tout d'abord, remplissez votre profil" step2_1: "Tout d'abord, remplissez votre profil."
step2_2: "En fournissant quelques informations sur qui vous êtes, il sera plus facile step2_2: "En fournissant quelques informations sur qui vous êtes, il sera plus facile
pour les autres de savoir s'ils veulent voir vos publcations ou vous suivre." pour les autres de savoir s'ils veulent voir vos publcations ou vous suivre."
step3_1: "Maintenant il est temps de suivre des gens !" step3_1: "Maintenant il est temps de suivre des gens !"
@ -1265,7 +1265,7 @@ _tutorial:
step4_2: "Pour votre première publication, certaines personnes aiment faire une step4_2: "Pour votre première publication, certaines personnes aiment faire une
{introduction} ou un simple 'Bonjour tout le monde !'" {introduction} ou un simple 'Bonjour tout le monde !'"
step5_1: "Des fils, des fils dactualité partout !" step5_1: "Des fils, des fils dactualité partout !"
step5_2: "Votre serveur a {timelines} fils différents disponibles !" step5_2: "Votre serveur a {timelines} fils différents activés."
step5_3: "Le fil {icon} Principal est l'endroit où vous pouvez voir les publications step5_3: "Le fil {icon} Principal est l'endroit où vous pouvez voir les publications
de vos abonnements." de vos abonnements."
step5_4: "La fil {icon} Local est l'endroit où vous pouvez voir les publications step5_4: "La fil {icon} Local est l'endroit où vous pouvez voir les publications
@ -1357,7 +1357,7 @@ _permissions:
_auth: _auth:
shareAccess: "Autoriser \"{name}\" à accéder à votre compte ?" shareAccess: "Autoriser \"{name}\" à accéder à votre compte ?"
shareAccessAsk: "Voulez-vous vraiment autoriser cette application à accéder à votre shareAccessAsk: "Voulez-vous vraiment autoriser cette application à accéder à votre
compte?" compte ?"
permissionAsk: "Cette application nécessite les autorisations suivantes :" permissionAsk: "Cette application nécessite les autorisations suivantes :"
pleaseGoBack: "Veuillez retourner à lapplication" pleaseGoBack: "Veuillez retourner à lapplication"
callback: "Retour vers lapplication" callback: "Retour vers lapplication"
@ -1412,7 +1412,7 @@ _widgets:
rssTicker: Bandeau RSS rssTicker: Bandeau RSS
_cw: _cw:
hide: "Masquer" hide: "Masquer"
show: "Afficher plus …" show: "Afficher contenu"
chars: "{count} caractères" chars: "{count} caractères"
files: "{count} fichiers" files: "{count} fichiers"
_poll: _poll:
@ -1422,8 +1422,8 @@ _poll:
canMultipleVote: "Autoriser le multi-choix" canMultipleVote: "Autoriser le multi-choix"
expiration: "Fin du sondage" expiration: "Fin du sondage"
infinite: "Illimité" infinite: "Illimité"
at: "Choisir une date" at: "Expire le..."
after: "Choisir la durée" after: "Expire après..."
deadlineDate: "Date de fin" deadlineDate: "Date de fin"
deadlineTime: "Heure de fin" deadlineTime: "Heure de fin"
duration: "Durée" duration: "Durée"
@ -1468,7 +1468,7 @@ _profile:
metadataEdit: "Éditer les informations supplémentaires" metadataEdit: "Éditer les informations supplémentaires"
metadataDescription: "Vous pouvez afficher jusqu'à quatre informations supplémentaires metadataDescription: "Vous pouvez afficher jusqu'à quatre informations supplémentaires
dans votre profil. Vous pouvez ajouter une balise {a} ou une balise {l} avec {rel} dans votre profil. Vous pouvez ajouter une balise {a} ou une balise {l} avec {rel}
pour vérifier le lien sur votre profil!" pour vérifier le lien sur votre profil !"
metadataLabel: "Étiquette" metadataLabel: "Étiquette"
metadataContent: "Contenu" metadataContent: "Contenu"
changeAvatar: "Changer l'image de profil" changeAvatar: "Changer l'image de profil"
@ -1503,7 +1503,7 @@ _instanceCharts:
usersTotal: "Total cumulé du nombre d'utilisateur·rice·s" usersTotal: "Total cumulé du nombre d'utilisateur·rice·s"
notes: "Variation du nombre de publications" notes: "Variation du nombre de publications"
notesTotal: "Nombre total cumulé des publications" notesTotal: "Nombre total cumulé des publications"
ff: "Variation des abonné·e·s / abonnements" ff: "Variation des abonnements / abonné·e·s "
ffTotal: "Total cumulé du nombre d'abonné·e·s / abonnements" ffTotal: "Total cumulé du nombre d'abonné·e·s / abonnements"
cacheSize: "Variation de la taille du cache" cacheSize: "Variation de la taille du cache"
cacheSizeTotal: "Total cumulé de la taille du cache" cacheSizeTotal: "Total cumulé de la taille du cache"
@ -1812,7 +1812,7 @@ _relayStatus:
accepted: "Accepté" accepted: "Accepté"
rejected: "Refusée" rejected: "Refusée"
_notification: _notification:
fileUploaded: "Le fichier a été téléversé !" fileUploaded: "Le fichier a été téléversé"
youGotMention: "{name} vous a mentionné" youGotMention: "{name} vous a mentionné"
youGotReply: "Réponse de {name}" youGotReply: "Réponse de {name}"
youGotQuote: "Cité·e par {name}" youGotQuote: "Cité·e par {name}"
@ -1993,7 +1993,7 @@ type: Type
speed: Vitesse speed: Vitesse
slow: Lent slow: Lent
move: Déplacer move: Déplacer
showAds: Afficher les bannières communautaire/publicités showAds: Afficher les bannières communautaire (publicités)
enterSendsMessage: Appuyer sur Entrée pendant la rédaction pour envoyer le message enterSendsMessage: Appuyer sur Entrée pendant la rédaction pour envoyer le message
(sinon Ctrl+Entrée) (sinon Ctrl+Entrée)
allowedInstancesDescription: Noms des serveurs autorisés pour la fédération, chacun allowedInstancesDescription: Noms des serveurs autorisés pour la fédération, chacun
@ -2071,14 +2071,14 @@ customKaTeXMacro: Macros KaTeX personnalisées
enableCustomKaTeXMacro: Activer les macros KaTeX personnalisées enableCustomKaTeXMacro: Activer les macros KaTeX personnalisées
noteId: ID des publications noteId: ID des publications
customKaTeXMacroDescription: "Définissez des macros pour écrire des expressions mathématiques customKaTeXMacroDescription: "Définissez des macros pour écrire des expressions mathématiques
simplement ! La notation se conforme aux définitions de commandes LaTeX et s'écrit simplement! La notation se conforme aux définitions de commandes LaTeX et s'écrit
\\newcommand{\\·name}{content} ou \\newcommand{\\name}[number of arguments]{content}. \\newcommand{\\·name}{content} ou \\newcommand{\\name}[number of arguments]{content}.
Par exemple, \\newcommand{\\add}[2]{#1 + #2} étendra \\add{3}{foo} en 3 + foo. Les Par exemple, \\newcommand{\\add}[2]{#1 + #2} étendra \\add{3}{foo} en 3 + foo. Les
accolades entourant le nom de la macro peuvent être changés pour des parenthèses accolades entourant le nom de la macro peuvent être changés pour des parenthèses
ou des crochets. Cela affectera les types de parenthèses utilisées pour les arguments. ou des crochets. Cela affectera les types de parenthèses utilisées pour les arguments.
Une (et une seule) macro peut être définie par ligne, et vous ne pouvez pas couper Une (et une seule) macro peut être définie par ligne, et vous ne pouvez pas couper
la ligne au milieu d'une définition. Les lignes invalides sont simplement ignorées. la ligne au milieu d'une définition. Les lignes invalides sont simplement ignorées.
Seulement de simples fonctions de substitution de chaines sont supportées; la syntaxe Seulement de simples fonctions de substitution de chaines sont supportées; la syntaxe
avancée, telle que la ramification conditionnelle, ne peut pas être utilisée ici." avancée, telle que la ramification conditionnelle, ne peut pas être utilisée ici."
enableRecommendedTimeline: Activer le fil recommandé enableRecommendedTimeline: Activer le fil recommandé
silenceThisInstance: Masquer ce serveur silenceThisInstance: Masquer ce serveur
@ -2197,7 +2197,7 @@ _skinTones:
objectStorageS3ForcePathStyle: Utiliser des URL d'endpoints basées sur le chemin objectStorageS3ForcePathStyle: Utiliser des URL d'endpoints basées sur le chemin
objectStorageS3ForcePathStyleDesc: Activez cette option pour construire les URL d'endpoints objectStorageS3ForcePathStyleDesc: Activez cette option pour construire les URL d'endpoints
au format 's3.amazonaws.com/<bucket>/' au lieu de '<bucket>.s3.amazonaws.com'. au format 's3.amazonaws.com/<bucket>/' au lieu de '<bucket>.s3.amazonaws.com'.
delete2fa: Supprimer 2FA delete2fa: Désativer A2F
deletePasskeys: Supprimer les clés d'accès deletePasskeys: Supprimer les clés d'accès
delete2faConfirm: Cela supprimera de manière irréversible la double authentification delete2faConfirm: Cela supprimera de manière irréversible la double authentification
sur ce compte. Souhaitez-vous continuer ? sur ce compte. Souhaitez-vous continuer ?
@ -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 ? 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 addRe: Ajouter "re:" au début dun avertissement de contenu (CW) en réponse à une
publication avec un avertissement de contenu 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" emptyFolder: "Folder kosong"
unableToDelete: "Tidak dapat menghapus" unableToDelete: "Tidak dapat menghapus"
inputNewFileName: "Masukkan nama berkas yang baru" inputNewFileName: "Masukkan nama berkas yang baru"
inputNewDescription: "Masukkan keterangan disini" inputNewDescription: "Masukkan deskripsi baru"
inputNewFolderName: "Masukkan nama folder yang baru" inputNewFolderName: "Masukkan nama folder yang baru"
circularReferenceFolder: "Folder tujuan adalah subfolder dari folder yang ingin kamu circularReferenceFolder: "Folder tujuan adalah subfolder dari folder yang ingin kamu
pindahkan." pindahkan."
@ -598,8 +598,8 @@ disablePlayer: "Tutup pemutar video"
expandTweet: "Perluas utas" expandTweet: "Perluas utas"
themeEditor: "Penyunting tema" themeEditor: "Penyunting tema"
description: "Deskripsi" description: "Deskripsi"
describeFile: "Tambahkan keterangan" describeFile: "Tambahkan deskripsi"
enterFileDescription: "Masukkan keterangan" enterFileDescription: "Masukkan deskripsi"
author: "Pembuat" author: "Pembuat"
leaveConfirm: "Ada perubahan yang belum disimpan. Apakah kamu ingin membuangnya?" leaveConfirm: "Ada perubahan yang belum disimpan. Apakah kamu ingin membuangnya?"
manage: "Manajemen" manage: "Manajemen"
@ -1904,7 +1904,7 @@ recommended: Direkomendasikan
silenceThisInstance: Bisukan server ini silenceThisInstance: Bisukan server ini
hiddenTags: Tagar Tersembunyi hiddenTags: Tagar Tersembunyi
preferencesBackups: Preferensi cadangan preferencesBackups: Preferensi cadangan
editNote: Sunting catatan editNote: Sunting kiriman
deleted: Dihapus deleted: Dihapus
edited: Disunting pada {date} {time} edited: Disunting pada {date} {time}
selectInstance: Pilih server selectInstance: Pilih server
@ -2019,7 +2019,7 @@ showAdminUpdates: Indikasi versi Firefish baru tersedia (hanya admin)
indexFrom: Indeks dari Post ID berikutnya indexFrom: Indeks dari Post ID berikutnya
noteId: ID Postingan noteId: ID Postingan
findOtherInstance: Cari server lain findOtherInstance: Cari server lain
caption: Keterangan Otomatis caption: Deskripsi itomatis
splash: Layar Percik splash: Layar Percik
migration: Migrasi migration: Migrasi
moveTo: Pindahkan akun sekarang ke akun baru 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 deletePasskeysConfirm: Ini akan menghapus semua passkeys dan kunci keamanan pada akun
ini secara permanen. Lanjutkan? ini secara permanen. Lanjutkan?
addRe: Tambahkan "re:" pada awal komentar balasan postingan dengan peringatan konten 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_: "日本語" _lang_: "日本語"
headlineFirefish: "ずっと無料でオープンソースの非中央集権型ソーシャルメディアプラットフォーム🚀" headlineFirefish: "ずっと無料でオープンソースの非中央集権型ソーシャルメディアプラットフォーム🚀"
introFirefish: "ようこそFirefishは、オープンソースの非中央集権型ソーシャルメディアプラットフォームです。\nいま起こっていることを共有したり、あなたについて皆に発信しましょう📡\n\ introFirefish: "ようこそFirefishは、オープンソースの非中央集権型ソーシャルメディアプラットフォームです。\nいま起こっていることを共有したり、あなたについて皆に発信したりしましょう📡\n\
「リアクション」機能で、皆の投稿に素早く反応を追加できます👍\n新しい世界を探検しよう🚀" 「リアクション」機能で、皆の投稿に素早く反応を追加できます👍\n新しい世界を探検しよう🚀"
monthAndDay: "{month}月 {day}日" monthAndDay: "{month}月 {day}日"
search: "検索" search: "検索"
@ -303,7 +303,7 @@ emptyDrive: "ドライブは空です"
emptyFolder: "フォルダーは空です" emptyFolder: "フォルダーは空です"
unableToDelete: "削除できません" unableToDelete: "削除できません"
inputNewFileName: "新しいファイル名を入力してください" inputNewFileName: "新しいファイル名を入力してください"
inputNewDescription: "新しい説明を入力してください" inputNewDescription: "新しい説明を入力"
inputNewFolderName: "新しいフォルダ名を入力してください" inputNewFolderName: "新しいフォルダ名を入力してください"
circularReferenceFolder: "移動先のフォルダーは、移動するフォルダーのサブフォルダーです。" circularReferenceFolder: "移動先のフォルダーは、移動するフォルダーのサブフォルダーです。"
hasChildFilesOrFolders: "このフォルダは空でないため、削除できません。" hasChildFilesOrFolders: "このフォルダは空でないため、削除できません。"
@ -577,7 +577,7 @@ disablePlayer: "プレイヤーを閉じる"
expandTweet: "ツイートを展開する" expandTweet: "ツイートを展開する"
themeEditor: "テーマエディター" themeEditor: "テーマエディター"
description: "説明" description: "説明"
describeFile: "説明を付ける" describeFile: "説明を追加"
enterFileDescription: "説明を入力" enterFileDescription: "説明を入力"
author: "作者" author: "作者"
leaveConfirm: "未保存の変更があります。破棄しますか?" leaveConfirm: "未保存の変更があります。破棄しますか?"
@ -849,7 +849,7 @@ filter: "フィルタ"
controlPanel: "コントロールパネル" controlPanel: "コントロールパネル"
manageAccounts: "アカウントを管理" manageAccounts: "アカウントを管理"
makeReactionsPublic: "リアクション一覧を公開する" makeReactionsPublic: "リアクション一覧を公開する"
makeReactionsPublicDescription: "あなたがしたリアクション一覧を誰でも見れるようにします。" makeReactionsPublicDescription: "あなたがしたリアクション一覧を誰でも見れるようにします。"
classic: "中央寄せ" classic: "中央寄せ"
muteThread: "スレッドをミュート" muteThread: "スレッドをミュート"
unmuteThread: "スレッドのミュートを解除" unmuteThread: "スレッドのミュートを解除"
@ -949,7 +949,7 @@ customSplashIconsDescription: "ユーザがページをロード/リロードす
showUpdates: "Firefishの更新時にポップアップを表示する" showUpdates: "Firefishの更新時にポップアップを表示する"
recommendedInstances: "おすすめサーバー" recommendedInstances: "おすすめサーバー"
recommendedInstancesDescription: "おすすめタイムラインに表示するサーバーを改行区切りで入力してください。" recommendedInstancesDescription: "おすすめタイムラインに表示するサーバーを改行区切りで入力してください。"
caption: "自動キャプション" caption: "自動で説明をつける"
splash: "スプラッシュスクリーン" splash: "スプラッシュスクリーン"
updateAvailable: "アップデートがありますよ!" updateAvailable: "アップデートがありますよ!"
swipeOnDesktop: "デスクトップでモバイルスタイルのスワイプを可能にする" swipeOnDesktop: "デスクトップでモバイルスタイルのスワイプを可能にする"
@ -1996,3 +1996,6 @@ _emojiModPerm:
add: "追加" add: "追加"
mod: "追加と変更" mod: "追加と変更"
full: "全て許可" full: "全て許可"
importZip: ZIPをインポート
emojiPackCreator: 絵文字パックの作者
exportZip: ZIPをエクスポート

View file

@ -1,7 +1,7 @@
---
_lang_: "日本語 (関西弁)" _lang_: "日本語 (関西弁)"
headlineFirefish: "ノートでつながるネットワーク" headlineFirefish: "ずっとタダでオープンソースの非中央集権型ソーシャルメディアプラットフォーム!🚀"
introFirefish: "ようお越しMisskeyは、オープンソースの分散型マイクロブログサービスやねん。\n「ート」を作って、いま起こっとることを共有したり、あんたについて皆に発信しよう📡\n「リアクション」機能で、皆のートに素早く反応を追加したりもできるで✌\nほな新しい世界を探検しよか🚀" introFirefish: "おいでやす。Firefishは、オープンソースの分散型ソーシャルメディアプラットフォームどす。\nいま起きたはるもんを共有したり、あんさんについて皆に発信したりしとくれやす👘\n\
「リアクション」機能があるさかい、皆の投稿に素早う反応を送ることもできます🎎\nほんなら、新しい世界を探検しまひょか🎴"
monthAndDay: "{month}月 {day}日" monthAndDay: "{month}月 {day}日"
search: "探す" search: "探す"
notifications: "通知" notifications: "通知"
@ -13,10 +13,10 @@ ok: "OKや"
gotIt: "ほい" gotIt: "ほい"
cancel: "やめとく" cancel: "やめとく"
enterUsername: "ユーザー名を入れてや" enterUsername: "ユーザー名を入れてや"
renotedBy: "{user}がRenote" renotedBy: "{user}がブースト"
noNotes: "ノートはあらへん" noNotes: "投稿はありまへん"
noNotifications: "通知はあらへん" noNotifications: "通知はあらへん"
instance: "インスタンス" instance: "サーバー"
settings: "設定" settings: "設定"
basicSettings: "基本設定" basicSettings: "基本設定"
otherSettings: "その他の設定" otherSettings: "その他の設定"
@ -44,7 +44,7 @@ copyContent: "内容をコピー"
copyLink: "リンクをコピー" copyLink: "リンクをコピー"
delete: "ほかす" delete: "ほかす"
deleteAndEdit: "ほかして直す" deleteAndEdit: "ほかして直す"
deleteAndEditConfirm: "このートをほかして書き直すんかこのートへのリアクション、Renote、返信も全部消えてまうで。" deleteAndEditConfirm: "この投稿をほかして書き直すんか?この投稿へのリアクション、ブースト、返信もみんな消えてまうで。"
addToList: "リストに入れたる" addToList: "リストに入れたる"
sendMessage: "メッセージを送る" sendMessage: "メッセージを送る"
copyUsername: "ユーザー名をコピー" copyUsername: "ユーザー名をコピー"
@ -64,26 +64,28 @@ import: "インポート"
export: "エクスポート" export: "エクスポート"
files: "ファイル" files: "ファイル"
download: "ダウンロード" download: "ダウンロード"
driveFileDeleteConfirm: "ファイル「{name}」を消してしもうてええか?このファイルを添付したノートも消えてまうで。" driveFileDeleteConfirm: "ファイル「{name}」を消してしもうてええか?このファイルを添付した投稿も消えてまうで。"
unfollowConfirm: "{name}のフォローを解除してもええんか?" unfollowConfirm: "{name}のフォローを解除してもええんか?"
exportRequested: "エクスポートしてな、ってリクエストしたけど、これ多分めっちゃ時間かかるで。エクスポート終わったら「ドライブ」に突っ込んどくで。" exportRequested: "エクスポートしてな、ってリクエストしたけど、これ多分めっちゃ時間かかるで。エクスポート終わったら「ドライブ」に突っ込んどくで。"
importRequested: "インポートしてな、ってリクエストしたけど、これ多分めっちゃ時間かかるで。" importRequested: "インポートしてな、ってリクエストしたけど、これ多分めっちゃ時間かかるで。"
lists: "リスト" lists: "リスト"
noLists: "リストなんてあらへんで" noLists: "リストなんてあらへんで"
note: "ノート" note: "投稿"
notes: "ノート" notes: "投稿"
following: "フォロー" following: "フォロー"
followers: "フォロワー" followers: "フォロワー"
followsYou: "フォローされとるで" followsYou: "フォローされとるで"
createList: "リスト作る" createList: "リスト作る"
manageLists: "リストの管理" manageLists: "リストの管理"
error: "エラー" error: "エラー"
somethingHappened: "なんかアカンことが起こったで" somethingHappened: "なんやアカンことが起きたで"
retry: "もっぺんやる?" retry: "もっぺんやる?"
pageLoadError: "ページの読み込みに失敗してしもうたで…" pageLoadError: "ページの読み込みに失敗してもた… えろうすんまへん"
pageLoadErrorDescription: "これは普通、ネットワークかブラウザキャッシュが原因やからね。キャッシュをクリアするか、もうちっとだけ待ってくれへんか?" pageLoadErrorDescription: "これは普通、ネットワークかブラウザキャッシュが原因やからね。キャッシュをクリアするか、もうちっとだけ待ってくれへんか?"
serverIsDead: "The server is not responding. Please wait for a while before trying again." serverIsDead: "The server is not responding. Please wait for a while before trying
youShouldUpgradeClient: "To display this page, please reload and use a new version client. " again."
youShouldUpgradeClient: "To display this page, please reload and use a new version
client. "
enterListName: "リスト名を入れてや" enterListName: "リスト名を入れてや"
privacy: "プライバシー" privacy: "プライバシー"
makeFollowManuallyApprove: "自分が認めた人だけがこのアカウントをフォローできるようにする" makeFollowManuallyApprove: "自分が認めた人だけがこのアカウントをフォローできるようにする"
@ -94,13 +96,13 @@ followRequests: "フォロー申請"
unfollow: "フォローやめる" unfollow: "フォローやめる"
followRequestPending: "フォロー許してくれるん待っとる" followRequestPending: "フォロー許してくれるん待っとる"
enterEmoji: "絵文字を入れてや" enterEmoji: "絵文字を入れてや"
renote: "Renote" renote: "ブースト"
unrenote: "Renoteやめる" unrenote: "ブーストやめる"
renoted: "Renoteしたで。" renoted: "ブーストしたで。"
cantRenote: "この投稿はRenoteできへんらしい。" cantRenote: "この投稿はブーストでけへんらしい。"
cantReRenote: "Renote自体はRenoteできへんで。" cantReRenote: "ブースト自体はブーストでけへんで。"
quote: "引用" quote: "引用"
pinnedNote: "ピン留めされとるノート" pinnedNote: "ピン留めされとる投稿"
pinned: "ピン留めしとく" pinned: "ピン留めしとく"
you: "あんた" you: "あんた"
clickToShow: "押したら見えるで" clickToShow: "押したら見えるで"
@ -138,12 +140,13 @@ addEmoji: "絵文字を追加"
settingGuide: "ええ感じの設定" settingGuide: "ええ感じの設定"
cacheRemoteFiles: "リモートのファイルをキャッシュする" cacheRemoteFiles: "リモートのファイルをキャッシュする"
cacheRemoteFilesDescription: "この設定を切っとくと、リモートファイルをキャッシュせず直リンクするようになるで。サーバーの容量は節約できるけど、サムネイルが作られんくなるから通信量が増えるで。" cacheRemoteFilesDescription: "この設定を切っとくと、リモートファイルをキャッシュせず直リンクするようになるで。サーバーの容量は節約できるけど、サムネイルが作られんくなるから通信量が増えるで。"
flagAsBot: "Botやで" flagAsBot: "ワイはBotや 🤖"
flagAsBotDescription: "もしこのアカウントがプログラムによって運用されるんやったら、このフラグをオンにしてたのむで。オンにすると、反応の連鎖を防ぐためのフラグとして他の開発者に役立ったり、Misskeyのシステム上での扱いがBotに合ったもんになるんやで。" flagAsBotDescription: "もしこのアカウントがプログラムによって運用されるんやったら、このフラグをオンにしてたのむで。オンにすると、反応の連鎖を防ぐためのフラグとして他の開発者に役立ったり、Firefishのシステム上での扱いがBotに合ったもんになったりするんやで。"
flagAsCat: "Catやで" flagAsCat: "ワイはCatや 🐯"
flagAsCatDescription: "ワレ、猫ちゃんならこのフラグをつけてみ?" flagAsCatDescription: "自分、猫ちゃんならこのフラグつけてみ?"
flagShowTimelineReplies: "It will display the reply to the note in the timeline. " 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: "フォローしとるユーザーからのフォローリクエストを勝手に許可しとく" autoAcceptFollowed: "フォローしとるユーザーからのフォローリクエストを勝手に許可しとく"
addAccount: "アカウントを追加" addAccount: "アカウントを追加"
loginFailed: "ログインに失敗してしもうた…" loginFailed: "ログインに失敗してしもうた…"
@ -162,7 +165,7 @@ selectUser: "ユーザーを選ぶ"
recipient: "宛先" recipient: "宛先"
annotation: "注釈" annotation: "注釈"
federation: "連合" federation: "連合"
instances: "インスタンス" instances: "サーバー"
registeredAt: "初観測" registeredAt: "初観測"
latestRequestSentAt: "ちょっと前のリクエスト送信" latestRequestSentAt: "ちょっと前のリクエスト送信"
latestRequestReceivedAt: "ちょっと前のリクエスト受信" latestRequestReceivedAt: "ちょっと前のリクエスト受信"
@ -172,7 +175,7 @@ charts: "チャート"
perHour: "1時間ごと" perHour: "1時間ごと"
perDay: "1日ごと" perDay: "1日ごと"
stopActivityDelivery: "アクティビティの配送をやめる" stopActivityDelivery: "アクティビティの配送をやめる"
blockThisInstance: "このインスタンスをブロック" blockThisInstance: "このサーバーをブロック"
operations: "操作" operations: "操作"
software: "ソフトウェア" software: "ソフトウェア"
version: "バージョン" version: "バージョン"
@ -182,23 +185,23 @@ jobQueue: "ジョブキュー"
cpuAndMemory: "CPUとメモリ" cpuAndMemory: "CPUとメモリ"
network: "ネットワーク" network: "ネットワーク"
disk: "ディスク" disk: "ディスク"
instanceInfo: "インスタンス情報" instanceInfo: "サーバー情報"
statistics: "統計" statistics: "統計"
clearQueue: "キューにさいなら" clearQueue: "キューにさいなら"
clearQueueConfirmTitle: "キューをクリアしまっか?" clearQueueConfirmTitle: "キューをクリアしまっか?"
clearQueueConfirmText: "未配達の投稿は配送されなくなるで。通常この操作を行う必要はあらへんや。" clearQueueConfirmText: "未配達の投稿は配送されなくなるで。通常この操作を行う必要はあらへんや。"
clearCachedFiles: "キャッシュにさいなら" clearCachedFiles: "キャッシュにさいなら"
clearCachedFilesConfirm: "キャッシュされとるリモートファイルをみんなほかしてええか?" clearCachedFilesConfirm: "キャッシュされとるリモートファイルをみんなほかしてええか?"
blockedInstances: "インスタンスブロック" blockedInstances: "ブロックしたサーバー"
blockedInstancesDescription: "ブロックしたいインスタンスのホストを改行で区切って設定してな。ブロックされてもうたインスタンスとはもう金輪際やり取りできひんくなるで。" blockedInstancesDescription: "ブロックしたいサーバーのホストを改行で区切って設定してな。ブロックされてもうたサーバーとはもう金輪際やり取りできんくなるで。"
muteAndBlock: "ミュートとブロック" muteAndBlock: "ミュートとブロック"
mutedUsers: "ミュートしたユーザー" mutedUsers: "ミュートしたユーザー"
blockedUsers: "ブロックしたユーザー" blockedUsers: "ブロックしたユーザー"
noUsers: "ユーザーはおらへん" noUsers: "ユーザーはおらへん"
editProfile: "プロフィールをいじる" editProfile: "プロフィールをいじる"
noteDeleteConfirm: "このノートを削除しまっか?" noteDeleteConfirm: "この投稿を削除しまっか?"
pinLimitExceeded: "これ以上ピン留めできひん" pinLimitExceeded: "これ以上ピン留めできひん"
intro: "Misskeyのインストールが完了してん!管理者アカウントを作ってや。" intro: "Firefishのインストールが完了してん!管理者アカウントを作ってや。"
done: "でけた" done: "でけた"
processing: "処理しとる" processing: "処理しとる"
preview: "プレビュー" preview: "プレビュー"
@ -213,9 +216,9 @@ all: "みんな"
subscribing: "購読しとる" subscribing: "購読しとる"
publishing: "配信しとる" publishing: "配信しとる"
notResponding: "応答してへんで" notResponding: "応答してへんで"
instanceFollowing: "インスタンスのフォロー" instanceFollowing: "サーバーのフォロー"
instanceFollowers: "インスタンスのフォロワー\n" instanceFollowers: "サーバーのフォロワー"
instanceUsers: "インスタンスのユーザー" instanceUsers: "このサーバーの利用者"
changePassword: "パスワード変える" changePassword: "パスワード変える"
security: "セキュリティ" security: "セキュリティ"
retypedNotMatch: "そやないねん。" retypedNotMatch: "そやないねん。"
@ -239,7 +242,8 @@ saved: "保存したで!"
messaging: "チャット" messaging: "チャット"
upload: "アップロード" upload: "アップロード"
keepOriginalUploading: "Retain the original image. " 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: "ドライブから" fromDrive: "ドライブから"
fromUrl: "URLから" fromUrl: "URLから"
uploadFromUrl: "URLアップロード" uploadFromUrl: "URLアップロード"
@ -286,7 +290,7 @@ emptyDrive: "ドライブにはなんも残っとらん"
emptyFolder: "ふぉろだーにはなんも残っとらん" emptyFolder: "ふぉろだーにはなんも残っとらん"
unableToDelete: "消そうおもってんけどな、あかんかったわ" unableToDelete: "消そうおもってんけどな、あかんかったわ"
inputNewFileName: "今度のファイル名は何にするん?" inputNewFileName: "今度のファイル名は何にするん?"
inputNewDescription: "新しいキャプションを入力しましょ" inputNewDescription: "新しい説明文を入力しまひょ"
inputNewFolderName: "今度のフォルダ名は何にするん?" inputNewFolderName: "今度のフォルダ名は何にするん?"
circularReferenceFolder: "移動先のフォルダーは、移動するフォルダーのサブフォルダーや。" circularReferenceFolder: "移動先のフォルダーは、移動するフォルダーのサブフォルダーや。"
hasChildFilesOrFolders: "このフォルダ、まだなんか入っとるから消されへん" hasChildFilesOrFolders: "このフォルダ、まだなんか入っとるから消されへん"
@ -305,8 +309,8 @@ unwatch: "ウォッチやめる"
accept: "ええで" accept: "ええで"
reject: "あかん" reject: "あかん"
normal: "ええ感じ" normal: "ええ感じ"
instanceName: "インスタンス名" instanceName: "サーバー名"
instanceDescription: "インスタンスの紹介" instanceDescription: "サーバーの紹介"
maintainerName: "管理者の名前" maintainerName: "管理者の名前"
maintainerEmail: "管理者のメールアドレス" maintainerEmail: "管理者のメールアドレス"
tosUrl: "利用規約のURL" tosUrl: "利用規約のURL"
@ -336,9 +340,9 @@ basicInfo: "基本情報"
pinnedUsers: "ピン留めしたユーザー" pinnedUsers: "ピン留めしたユーザー"
pinnedUsersDescription: "「みつける」ページとかにピン留めしたいユーザーをここに書けばええんやで。他ん人との名前は改行で区切ればええんやで。" pinnedUsersDescription: "「みつける」ページとかにピン留めしたいユーザーをここに書けばええんやで。他ん人との名前は改行で区切ればええんやで。"
pinnedPages: "ピン留めページ" pinnedPages: "ピン留めページ"
pinnedPagesDescription: "インスタンスのいっちゃん上にピン留めしたいページのパスを改行で区切って記述してな" pinnedPagesDescription: "サーバーのいっちゃん上にピン留めしたいページのパスを改行で区切って記述してな"
pinnedClipId: "ピン留めするクリップのID" pinnedClipId: "ピン留めするクリップのID"
pinnedNotes: "ピン留めされとるノート" pinnedNotes: "ピン留めされとる投稿"
hcaptcha: "hCaptchaキャプチャ" hcaptcha: "hCaptchaキャプチャ"
enableHcaptcha: "hCaptchaキャプチャをつけとく" enableHcaptcha: "hCaptchaキャプチャをつけとく"
hcaptchaSiteKey: "サイトキー" hcaptchaSiteKey: "サイトキー"
@ -355,8 +359,8 @@ antennaSource: "受信ソース(このソースは食われへん)"
antennaKeywords: "受信キーワード" antennaKeywords: "受信キーワード"
antennaExcludeKeywords: "除外キーワード" antennaExcludeKeywords: "除外キーワード"
antennaKeywordsDescription: "スペースで区切ったるとAND指定で、改行で区切ったるとOR指定や" antennaKeywordsDescription: "スペースで区切ったるとAND指定で、改行で区切ったるとOR指定や"
notifyAntenna: "新しいノートを通知すんで" notifyAntenna: "新しい投稿を通知すんで"
withFileAntenna: "なんか添付されたノートだけ" withFileAntenna: "ファイルが添付された投稿のみ"
enableServiceworker: "ServiceWorkerをつこて" enableServiceworker: "ServiceWorkerをつこて"
antennaUsersDescription: "ユーザー名を改行で区切ったってな" antennaUsersDescription: "ユーザー名を改行で区切ったってな"
caseSensitive: "大文字と小文字は別もんや" caseSensitive: "大文字と小文字は別もんや"
@ -377,7 +381,7 @@ exploreFediverse: "Fediverseを探ってみる"
popularTags: "人気のタグ" popularTags: "人気のタグ"
userList: "リスト" userList: "リスト"
about: "情報" about: "情報"
aboutFirefish: "Misskeyってなんや" aboutFirefish: "Firefishってなんやねん"
administrator: "管理者" administrator: "管理者"
token: "トークン" token: "トークン"
twoStepAuthentication: "二段階認証" twoStepAuthentication: "二段階認証"
@ -420,7 +424,7 @@ text: "テキスト"
enable: "有効にするで" enable: "有効にするで"
next: "次" next: "次"
retype: "もっかい入力" retype: "もっかい入力"
noteOf: "{user}のノート" noteOf: "{user}の投稿"
inviteToGroup: "グループに招く" inviteToGroup: "グループに招く"
quoteAttached: "引用付いとるで" quoteAttached: "引用付いとるで"
quoteQuestion: "引用として添付してもええか?" quoteQuestion: "引用として添付してもええか?"
@ -478,12 +482,13 @@ accountSettings: "アカウントの設定"
promotion: "宣伝" promotion: "宣伝"
promote: "宣伝" promote: "宣伝"
numberOfDays: "日数" numberOfDays: "日数"
hideThisNote: "このノートは表示せんでいい" hideThisNote: "この投稿は表示せんでいい"
showFeaturedNotesInTimeline: "タイムラインにおすすめのノートを表示してや" showFeaturedNotesInTimeline: "タイムラインにおすすめの投稿を表示してや"
objectStorage: "オブジェクトストレージ" objectStorage: "オブジェクトストレージ"
useObjectStorage: "オブジェクトストレージを使う" useObjectStorage: "オブジェクトストレージを使う"
objectStorageBaseUrl: "Base URL" 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" objectStorageBucket: "Bucket"
objectStorageBucketDesc: "使ってるサービスのbucket名を選んでな" objectStorageBucketDesc: "使ってるサービスのbucket名を選んでな"
objectStoragePrefix: "Prefix" objectStoragePrefix: "Prefix"
@ -500,7 +505,7 @@ objectStorageSetPublicRead: "アップロードした時に'public-read'を設
serverLogs: "サーバーログ" serverLogs: "サーバーログ"
deleteAll: "全て削除してや" deleteAll: "全て削除してや"
showFixedPostForm: "タイムラインの上の方で投稿できるようにやってくれへん?" showFixedPostForm: "タイムラインの上の方で投稿できるようにやってくれへん?"
newNoteRecived: "新しいノートがあるで" newNoteRecived: "新しい投稿があるで"
sounds: "サウンド" sounds: "サウンド"
listen: "聴く" listen: "聴く"
none: "なし" none: "なし"
@ -523,7 +528,7 @@ sort: "仕分ける"
ascendingOrder: "小さい順" ascendingOrder: "小さい順"
descendingOrder: "大きい順" descendingOrder: "大きい順"
scratchpad: "スクラッチパッド" scratchpad: "スクラッチパッド"
scratchpadDescription: "スクラッチパッドではAiScriptを色々試すことができるんや。Misskeyに対して色々できるコードを書いて動かしてみたり、結果を見たりできるで。" scratchpadDescription: "スクラッチパッドではAiScriptを色々試すことができるんや。Firefishに対して色々できるコードを書いて動かしてみたり、結果を見たりできるで。"
output: "出力" output: "出力"
script: "スクリプト" script: "スクリプト"
disablePagesScript: "Pagesのスクリプトを無効にしてや" disablePagesScript: "Pagesのスクリプトを無効にしてや"
@ -531,7 +536,7 @@ updateRemoteUser: "リモートユーザー情報の更新してくれん?"
deleteAllFiles: "すべてのファイルを削除" deleteAllFiles: "すべてのファイルを削除"
deleteAllFilesConfirm: "ホンマにすべてのファイルを削除するん?消したもんはもう戻ってこんのやで?" deleteAllFilesConfirm: "ホンマにすべてのファイルを削除するん?消したもんはもう戻ってこんのやで?"
removeAllFollowing: "フォローを全解除" removeAllFollowing: "フォローを全解除"
removeAllFollowingDescription: "{host}からのフォローをすべて解除するで。そのインスタンスが消えて無くなった時とかには便利な機能やで。" removeAllFollowingDescription: "{host}からのフォローをすべて解除するで。そのサーバーが消えて無くなった時とかに便利な機能やで。"
userSuspended: "このユーザーは...凍結されとる。" userSuspended: "このユーザーは...凍結されとる。"
userSilenced: "このユーザーは...サイレンスされとる。" userSilenced: "このユーザーは...サイレンスされとる。"
yourAccountSuspendedTitle: "あんたのアカウント凍結されとるで" yourAccountSuspendedTitle: "あんたのアカウント凍結されとるで"
@ -555,8 +560,8 @@ disablePlayer: "プレイヤーを閉じる"
expandTweet: "ツイートを展開する" expandTweet: "ツイートを展開する"
themeEditor: "テーマエディター" themeEditor: "テーマエディター"
description: "説明" description: "説明"
describeFile: "キャプションを付ける" describeFile: "画像説明文を付ける"
enterFileDescription: "キャプションを入力" enterFileDescription: ""
author: "作者" author: "作者"
leaveConfirm: "未保存の変更があるで!ほかしてええか?" leaveConfirm: "未保存の変更があるで!ほかしてええか?"
manage: "管理" manage: "管理"
@ -595,7 +600,7 @@ testEmail: "配信テスト"
wordMute: "ワードミュート" wordMute: "ワードミュート"
regexpError: "正規表現エラー" regexpError: "正規表現エラー"
regexpErrorDescription: "{tab}ワードミュートの{line}行目の正規表現にエラーが出てきたで:" regexpErrorDescription: "{tab}ワードミュートの{line}行目の正規表現にエラーが出てきたで:"
instanceMute: "インスタンスミュート" instanceMute: "サーバーミュート"
userSaysSomething: "{name}が何か言ったようやで" userSaysSomething: "{name}が何か言ったようやで"
makeActive: "使うで" makeActive: "使うで"
display: "表示" display: "表示"
@ -621,20 +626,20 @@ sample: "サンプル"
abuseReports: "通報" abuseReports: "通報"
reportAbuse: "通報" reportAbuse: "通報"
reportAbuseOf: "{name}を通報する" reportAbuseOf: "{name}を通報する"
fillAbuseReportDescription: "細かい通報理由を書いてなー。対象ノートがある時はそのURLも書いといてなー。" fillAbuseReportDescription: "細かい通報理由を書いてなー。特定の投稿を通報するなら、そのURLも書いといてなー。"
abuseReported: "無事内容が送信されたみたいやで。おおきに〜。" abuseReported: "無事内容が送信されたみたいやで。おおきに〜。"
reporter: "通報者" reporter: "通報者"
reporteeOrigin: "通報先" reporteeOrigin: "通報先"
reporterOrigin: "通報元" reporterOrigin: "通報元"
forwardReport: "リモートインスタンスに通報を転送するで" forwardReport: "リモートサーバーに通報を転送するで"
forwardReportIsAnonymous: "リモートインスタンスからはあんたの情報は見れへんくって、匿名のシステムアカウントとして表示されるで。" forwardReportIsAnonymous: "リモートサーバーからはあんたの情報は見れへんくて、匿名のシステムアカウントとして表示されるで。"
send: "送信" send: "送信"
abuseMarkAsResolved: "対応したで" abuseMarkAsResolved: "対応したで"
openInNewTab: "新しいタブで開く" openInNewTab: "新しいタブで開く"
openInSideView: "サイドビューで開く" openInSideView: "サイドビューで開く"
defaultNavigationBehaviour: "デフォルトのナビゲーション" defaultNavigationBehaviour: "デフォルトのナビゲーション"
editTheseSettingsMayBreakAccount: "このへんの設定をようわからんままイジるとアカウントが壊れて使えんくなるかも知れへんで?" editTheseSettingsMayBreakAccount: "このへんの設定をようわからんままイジるとアカウントが壊れて使えんくなるかも知れへんで?"
instanceTicker: "ノートのインスタンス情報" instanceTicker: "投稿のサーバー情報"
waitingFor: "{x}を待っとるで" waitingFor: "{x}を待っとるで"
random: "ランダム" random: "ランダム"
system: "システム" system: "システム"
@ -645,16 +650,16 @@ createNew: "新しく作るで"
optional: "任意" optional: "任意"
createNewClip: "新しいクリップを作るで" createNewClip: "新しいクリップを作るで"
unclip: "クリップ解除するで" unclip: "クリップ解除するで"
confirmToUnclipAlreadyClippedNote: "このノートはすでにクリップ「{name}」に含まれとるで。ノートをこのクリップから除外したる?" confirmToUnclipAlreadyClippedNote: "この投稿はすでにクリップ「{name}」に含まれとるで。投稿をこのクリップから除外したる?"
public: "パブリック" public: "パブリック"
i18nInfo: "Firefishは有志によっていろんな言語に翻訳されとるで。{link}で翻訳に協力したってやー。" i18nInfo: "Firefishは有志によっていろんな言語に翻訳されとるで。{link}で翻訳に協力したってやー。"
manageAccessTokens: "アクセストークンの管理" manageAccessTokens: "アクセストークンの管理"
accountInfo: "アカウント情報" accountInfo: "アカウント情報"
notesCount: "ノートの数やで" notesCount: "投稿の数やで"
repliesCount: "返信した数やで" repliesCount: "返信した数やで"
renotesCount: "Renoteした数やで" renotesCount: "ブーストした数やで"
repliedCount: "返信された数やで" repliedCount: "返信された数やで"
renotedCount: "Renoteされた数やで" renotedCount: "ブーストされた数やで"
followingCount: "フォロー数やで" followingCount: "フォロー数やで"
followersCount: "フォロワー数やで" followersCount: "フォロワー数やで"
sentReactionsCount: "リアクションした数やで" sentReactionsCount: "リアクションした数やで"
@ -666,15 +671,15 @@ no: "いいえ"
driveFilesCount: "ドライブのファイル数" driveFilesCount: "ドライブのファイル数"
driveUsage: "ドライブ使用量やで" driveUsage: "ドライブ使用量やで"
noCrawle: "クローラーによるインデックスを拒否するで" noCrawle: "クローラーによるインデックスを拒否するで"
noCrawleDescription: "検索エンジンにあんたのユーザーページ、ート、Pagesとかのコンテンツを登録(インデックス)せぇへんように頼むで。" noCrawleDescription: "検索エンジンにあんたのプロフィール、投稿、ページとかのコンテンツを登録(インデックス)せぇへんように頼むで。"
lockedAccountInfo: "フォローを承認制にしとっても、ノートの公開範囲を「フォロワー」にせぇへん限り、誰でもあんたのノートを見れるで。" lockedAccountInfo: "フォローを承認制にしとっても、投稿の公開範囲を「フォロワー」にせん限り、誰でもあんたの投稿を見れるで。"
alwaysMarkSensitive: "デフォルトでメディアを閲覧注意にするで" alwaysMarkSensitive: "デフォルトでメディアを閲覧注意にするで"
loadRawImages: "添付画像のサムネイルをオリジナル画質にするで" loadRawImages: "添付画像のサムネイルをオリジナル画質にするで"
disableShowingAnimatedImages: "アニメーション画像を再生しやへんで" disableShowingAnimatedImages: "アニメーション画像を再生しやへんで"
verificationEmailSent: "無事確認のメールを送れたで。メールに書いてあるリンクにアクセスして、設定を完了してなー。" verificationEmailSent: "無事確認のメールを送れたで。メールに書いてあるリンクにアクセスして、設定を完了してなー。"
notSet: "未設定" notSet: "未設定"
emailVerified: "メールアドレスは確認されたで" emailVerified: "メールアドレスは確認されたで"
noteFavoritesCount: "お気に入りノートの数やで" noteFavoritesCount: "お気に入り投稿の数やで"
pageLikesCount: "Pageにええやんと思った数" pageLikesCount: "Pageにええやんと思った数"
pageLikedCount: "Pageにええやんと思ってくれた数" pageLikedCount: "Pageにええやんと思ってくれた数"
contact: "連絡先" contact: "連絡先"
@ -684,7 +689,7 @@ experimentalFeatures: "実験的機能やで"
developer: "開発者やで" developer: "開発者やで"
makeExplorable: "アカウントを見つけやすくするで" makeExplorable: "アカウントを見つけやすくするで"
makeExplorableDescription: "オフにすると、「みつける」にアカウントが載らんくなるで。" makeExplorableDescription: "オフにすると、「みつける」にアカウントが載らんくなるで。"
showGapBetweenNotesInTimeline: "タイムラインのノートを放して表示するで" showGapBetweenNotesInTimeline: "タイムライン上の投稿を離して表示するで"
duplicate: "複製" duplicate: "複製"
left: "左" left: "左"
center: "中央" center: "中央"
@ -696,9 +701,10 @@ showTitlebar: "タイトルバーを見せる"
clearCache: "キャッシュをほかす" clearCache: "キャッシュをほかす"
onlineUsersCount: "{n}人が起きとるで" onlineUsersCount: "{n}人が起きとるで"
nUsers: "{n}ユーザー" nUsers: "{n}ユーザー"
nNotes: "{n}ノート" nNotes: "{n}投稿"
sendErrorReports: "エラーリポートを送る" sendErrorReports: "エラーリポートを送る"
sendErrorReportsDescription: "オンにしたら、なんか変なことが起きたときにエラーの詳細がMisskeyに共有されて、ソフトウェアの品質向上に役立てられるんや。エラー情報には、OSのバージョン、ブラウザの種類、行動履歴などが含まれるで。" sendErrorReportsDescription: "オンにしたら、なんやけったいなことが起きたときにエラーの詳細がFirefishに共有されて、ソフトウェアの品質向上に役立てられるんや。\n\
エラー情報には、OSのバージョン、ブラウザの種類、行動履歴などが含まれるで。"
myTheme: "マイテーマ" myTheme: "マイテーマ"
backgroundColor: "背景" backgroundColor: "背景"
accentColor: "アクセント" accentColor: "アクセント"
@ -722,7 +728,7 @@ capacity: "容量"
inUse: "使用中" inUse: "使用中"
editCode: "コードを編集" editCode: "コードを編集"
apply: "適用" apply: "適用"
receiveAnnouncementFromInstance: "インスタンスからのお知らせを受け取る" receiveAnnouncementFromInstance: "サーバーからのお知らせを受け取る"
emailNotification: "メール通知" emailNotification: "メール通知"
publish: "公開" publish: "公開"
inChannelSearch: "チャンネル内検索" inChannelSearch: "チャンネル内検索"
@ -737,7 +743,7 @@ unlikeConfirm: "いいね解除するんか?"
fullView: "フルビュー" fullView: "フルビュー"
quitFullView: "フルビュー解除" quitFullView: "フルビュー解除"
addDescription: "説明を追加するで" addDescription: "説明を追加するで"
userPagePinTip: "個々のノートのメニューから「ピン留め」を選んどくと、ここにノートを表示しておけるで。" userPagePinTip: "個々の投稿のメニューから「ピン留め」を選んどくと、ここにそいつを表示しておけるで。"
notSpecifiedMentionWarning: "宛先に含まれてへんメンションがあるで" notSpecifiedMentionWarning: "宛先に含まれてへんメンションがあるで"
info: "情報" info: "情報"
userInfo: "ユーザー情報やで" userInfo: "ユーザー情報やで"
@ -750,7 +756,7 @@ active: "アクティブ"
offline: "オフライン" offline: "オフライン"
notRecommended: "あんま推奨しやんで" notRecommended: "あんま推奨しやんで"
botProtection: "Botプロテクション" botProtection: "Botプロテクション"
instanceBlocking: "インスタンスブロック" instanceBlocking: "連合の管理"
selectAccount: "アカウントを選んでなー" selectAccount: "アカウントを選んでなー"
switchAccount: "アカウントを変えるで" switchAccount: "アカウントを変えるで"
enabled: "有効" enabled: "有効"
@ -767,7 +773,7 @@ postToGallery: "ギャラリーへ投稿"
gallery: "ギャラリー" gallery: "ギャラリー"
recentPosts: "最近の投稿" recentPosts: "最近の投稿"
popularPosts: "人気の投稿" popularPosts: "人気の投稿"
shareWithNote: "ノートで共有" shareWithNote: "投稿で共有"
ads: "広告" ads: "広告"
expiration: "期限" expiration: "期限"
memo: "メモ" memo: "メモ"
@ -789,7 +795,7 @@ hashtags: "ハッシュタグ"
troubleshooting: "トラブルシューティング" troubleshooting: "トラブルシューティング"
useBlurEffect: "UIにぼかし効果を使うで" useBlurEffect: "UIにぼかし効果を使うで"
learnMore: "詳しく" learnMore: "詳しく"
misskeyUpdated: "Misskeyが更新されたで\nモデレーターの人らに感謝せなあかんで" misskeyUpdated: "Firefishが更新されたで\nモデレーターの人らに感謝やね"
whatIsNew: "更新情報を見るで" whatIsNew: "更新情報を見るで"
translate: "翻訳" translate: "翻訳"
translatedFrom: "{x}から翻訳するで" translatedFrom: "{x}から翻訳するで"
@ -834,7 +840,7 @@ cannotUploadBecauseInappropriate: "不適切な内容を含むかもしれへん
cannotUploadBecauseNoFreeSpace: "ドライブの空き容量が無いでアップロードできまへん。" cannotUploadBecauseNoFreeSpace: "ドライブの空き容量が無いでアップロードできまへん。"
beta: "ベータ" beta: "ベータ"
enableAutoSensitive: "自動NSFW判定" enableAutoSensitive: "自動NSFW判定"
enableAutoSensitiveDescription: "使える時は、機械学習を使って自動でメディアにNSFWフラグを設定するで。この機能をオフにしても、インスタンスによっては自動で設定されることがあるで。" enableAutoSensitiveDescription: "いけるときは、機械学習を使って自動でメディアにNSFWフラグを設定するで。この機能をオフにしても、サーバーによっては自動で設定されることがあるで。"
activeEmailValidationDescription: "ユーザーのメールアドレスのバリデーションを、捨てアドかどうかや実際に通信可能かどうかとかを判定して積極的に行うで。オフにすると単に文字列として正しいかどうかだけチェックするで。" activeEmailValidationDescription: "ユーザーのメールアドレスのバリデーションを、捨てアドかどうかや実際に通信可能かどうかとかを判定して積極的に行うで。オフにすると単に文字列として正しいかどうかだけチェックするで。"
navbar: "ナビゲーションバー" navbar: "ナビゲーションバー"
shuffle: "シャッフルするで" shuffle: "シャッフルするで"
@ -868,14 +874,15 @@ _registry:
domain: "ドメイン" domain: "ドメイン"
createKey: "キーを作る" createKey: "キーを作る"
_aboutFirefish: _aboutFirefish:
about: "Misskeyはsyuiloが2014年からずっと作ってはる、オープンソースなソフトウェアや。" about: "Firefishは、ThatOneCalculatorが2022年にMisskeyをいじって作った、オープンなソースのソフトウェアや。"
contributors: "主な貢献者" contributors: "主な貢献者"
allContributors: "全ての貢献者" allContributors: "全ての貢献者"
source: "ソースコード" source: "ソースコード"
translation: "Misskeyを翻訳" translation: "Firefishを翻訳"
donate: "Misskeyに寄付" donate: "Firefishに寄付"
morePatrons: "他にもぎょうさんの人からサポートしてもろてんねん。ほんまおおきに🥰" morePatrons: "他にもぎょうさんの人からサポートしてもろてんねん。ほんまおおきに🥰"
patrons: "支援者" patrons: "支援者"
misskeyContributors: フォーク元のMisskeyを作らはった人ら
_mfm: _mfm:
cheatSheet: "MFMチートシート" cheatSheet: "MFMチートシート"
mention: "メンション" mention: "メンション"
@ -896,6 +903,7 @@ _mfm:
blur: "ぼかし" blur: "ぼかし"
font: "フォント" font: "フォント"
rotate: "回転" rotate: "回転"
intro: MFMは、MisskeyやFirefish、Akkomaなどの様々な場所で使用できるマークアップ言語なんよ。ここでは、MFMで使用可能な構文一覧が確認できるで。
_instanceTicker: _instanceTicker:
none: "表示せん" none: "表示せん"
remote: "リモートユーザーに表示" remote: "リモートユーザーに表示"
@ -958,7 +966,7 @@ _theme:
hashtag: "ハッシュタグ" hashtag: "ハッシュタグ"
mention: "メンション" mention: "メンション"
mentionMe: "うち宛てのメンション" mentionMe: "うち宛てのメンション"
renote: "Renote" renote: "ブースト"
modalBg: "モーダルの背景" modalBg: "モーダルの背景"
divider: "分割線" divider: "分割線"
scrollbarHandle: "スクロールバーの取っ手" scrollbarHandle: "スクロールバーの取っ手"
@ -985,8 +993,8 @@ _theme:
accentLighten: "アクセント (明るめ)" accentLighten: "アクセント (明るめ)"
fgHighlighted: "強調されとる文字" fgHighlighted: "強調されとる文字"
_sfx: _sfx:
note: "ノート" note: "投稿"
noteMy: "ノート(自分)" noteMy: "投稿(自分)"
notification: "通知" notification: "通知"
chat: "チャット" chat: "チャット"
_ago: _ago:
@ -1017,8 +1025,8 @@ _permissions:
_auth: _auth:
permissionAsk: "このアプリは次の権限を要求しとるで" permissionAsk: "このアプリは次の権限を要求しとるで"
_antennaSources: _antennaSources:
all: "みんなのノート" all: "みんなの投稿"
homeTimeline: "フォローしとるユーザーのノート" homeTimeline: "フォローしとるユーザーの投稿"
_weekday: _weekday:
sunday: "日曜日" sunday: "日曜日"
monday: "月曜日" monday: "月曜日"
@ -1072,7 +1080,7 @@ _profile:
name: "名前" name: "名前"
username: "ユーザー名" username: "ユーザー名"
_exportOrImport: _exportOrImport:
allNotes: "全てのノート" allNotes: "すべての投稿"
followingList: "フォロー" followingList: "フォロー"
muteList: "ミュート" muteList: "ミュート"
blockingList: "ブロック" blockingList: "ブロック"
@ -1082,10 +1090,10 @@ _charts:
apRequest: "リクエスト" apRequest: "リクエスト"
usersTotal: "ユーザーの合計" usersTotal: "ユーザーの合計"
activeUsers: "アクティブユーザー数" activeUsers: "アクティブユーザー数"
notesIncDec: "ノートの増減" notesIncDec: "投稿の増減"
localNotesIncDec: "ローカルのノートの増減" localNotesIncDec: "ローカルの投稿の増減"
remoteNotesIncDec: "リモートのノートの増減" remoteNotesIncDec: "リモートの投稿の増減"
notesTotal: "ノートの合計" notesTotal: "投稿の合計"
filesIncDec: "ファイルの増減" filesIncDec: "ファイルの増減"
filesTotal: "ファイルの合計" filesTotal: "ファイルの合計"
storageUsageIncDec: "ストレージ使用量の増減" storageUsageIncDec: "ストレージ使用量の増減"
@ -1094,8 +1102,8 @@ _instanceCharts:
requests: "リクエスト" requests: "リクエスト"
users: "ユーザーの増減" users: "ユーザーの増減"
usersTotal: "ユーザーの累積" usersTotal: "ユーザーの累積"
notes: "ノートの増減" notes: "投稿の増減"
notesTotal: "ノートの累積" notesTotal: "投稿の累積"
ff: "フォロー/フォロワーの増減" ff: "フォロー/フォロワーの増減"
ffTotal: "フォロー/フォロワーの累積" ffTotal: "フォロー/フォロワーの累積"
cacheSize: "キャッシュサイズの増減" cacheSize: "キャッシュサイズの増減"
@ -1165,9 +1173,9 @@ _pages:
id: "キャンバスID" id: "キャンバスID"
width: "幅" width: "幅"
height: "高さ" height: "高さ"
note: "ノート埋め込み" note: "投稿の埋め込み"
_note: _note:
id: "ノートID" id: "投稿のID"
detailed: "詳細な表示" detailed: "詳細な表示"
switch: "スイッチ" switch: "スイッチ"
_switch: _switch:
@ -1385,14 +1393,14 @@ _notification:
all: "すべて" all: "すべて"
follow: "フォロー" follow: "フォロー"
mention: "メンション" mention: "メンション"
renote: "Renote" renote: "ブースト"
quote: "引用" quote: "引用"
reaction: "リアクション" reaction: "リアクション"
receiveFollowRequest: "フォロー許可してほしいみたいやで" receiveFollowRequest: "フォロー許可してほしいみたいやで"
followRequestAccepted: "フォローが受理されたで" followRequestAccepted: "フォローが受理されたで"
_actions: _actions:
reply: "返事" reply: "返事"
renote: "Renote" renote: "ブースト"
_deck: _deck:
alwaysShowMainColumn: "いつもメインカラムを表示" alwaysShowMainColumn: "いつもメインカラムを表示"
columnAlign: "カラムの寄せ" columnAlign: "カラムの寄せ"
@ -1413,3 +1421,28 @@ _deck:
list: "リスト" list: "リスト"
mentions: "あんた宛て" mentions: "あんた宛て"
direct: "ダイレクト" 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 listsDesc: Lister lar deg lage tidslinjer med utvalgte brukere. De kan hentes frem
fra tidslinje-siden. fra tidslinje-siden.
deleted: Slettet deleted: Slettet
editNote: Rediger notat editNote: Rediger post
followsYou: Følger deg followsYou: Følger deg
createList: Lag liste createList: Lag liste
newer: nyere newer: nyere
@ -155,7 +155,7 @@ drive: Disk
renameFile: Omdøp fil renameFile: Omdøp fil
folderName: Katalognavn folderName: Katalognavn
createFolder: Opprett katalog createFolder: Opprett katalog
inputNewDescription: Oppgi ny bildetekst inputNewDescription: Skriv ny beskrivelse
inputNewFolderName: Oppgi nytt katalognavn inputNewFolderName: Oppgi nytt katalognavn
copyUrl: Kopier URL copyUrl: Kopier URL
hcaptchaSiteKey: hCaptcha-nøkkel for nettstedet 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? deleteAllFilesConfirm: Er du sikker på at du vil slette alle filer?
updateRemoteUser: Oppdater informasjon om ekstern bruker updateRemoteUser: Oppdater informasjon om ekstern bruker
deleteAllFiles: Slett alle filer deleteAllFiles: Slett alle filer
enterFileDescription: Legg til bildetekst enterFileDescription: Skriv inn beskrivelse
leaveConfirm: Det er ulagrede endringer. Vil du forkaste dem? leaveConfirm: Det er ulagrede endringer. Vil du forkaste dem?
enableAll: Slå på alle enableAll: Slå på alle
generateAccessToken: Generer adgangstegn generateAccessToken: Generer adgangstegn
@ -506,7 +506,7 @@ yourAccountSuspendedDescription: Denne kontoen er suspendert fordi den har brutt
useCw: Skjul innhold useCw: Skjul innhold
enablePlayer: Åpne videospiller enablePlayer: Åpne videospiller
disablePlayer: Lukk videospiller disablePlayer: Lukk videospiller
describeFile: Legg til tekst describeFile: Legg til beskrivelse
author: Forfatter author: Forfatter
useFullReactionPicker: Bruk reaksjonsvelger i full størrelse useFullReactionPicker: Bruk reaksjonsvelger i full størrelse
width: Bredde 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 verificationEmailSent: En verifiserings-epost er sendt. Følg lenken i eposten for
å fullføre verifiseringen. å fullføre verifiseringen.
newNoteRecived: Det er nye poster 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: "Папка пуста" emptyFolder: "Папка пуста"
unableToDelete: "Удаление невозможно" unableToDelete: "Удаление невозможно"
inputNewFileName: "Введите имя нового файла" inputNewFileName: "Введите имя нового файла"
inputNewDescription: "Введите новую подпись" inputNewDescription: "Введите новое описание"
inputNewFolderName: "Пожалуйста, введите новое имя папки" inputNewFolderName: "Пожалуйста, введите новое имя папки"
circularReferenceFolder: "Вы пытаетесь переместить папку внутрь себя." circularReferenceFolder: "Вы пытаетесь переместить папку внутрь себя."
hasChildFilesOrFolders: "Эта папка не пуста и не может быть удалена." hasChildFilesOrFolders: "Эта папка не пуста и не может быть удалена."
@ -513,7 +513,7 @@ objectStorageBaseUrlDesc: "URL используемый для примера.
CDN или прокси, если вы используете любой из них.\nДля S3 используйте 'https://<bucket>.s3.amazonaws.com', CDN или прокси, если вы используете любой из них.\nДля S3 используйте 'https://<bucket>.s3.amazonaws.com',
а для GCS и подобных сервисов используйте 'https://storage.googleapis.com/<bucket>', а для GCS и подобных сервисов используйте 'https://storage.googleapis.com/<bucket>',
и т.п." и т.п."
objectStorageBucket: "Bucket" objectStorageBucket: "Хранилище (Bucket)"
objectStorageBucketDesc: "Укажите название контейнера (Bucket) который используется objectStorageBucketDesc: "Укажите название контейнера (Bucket) который используется
на выбранном сервисе." на выбранном сервисе."
objectStoragePrefix: "Префикс" objectStoragePrefix: "Префикс"
@ -596,8 +596,8 @@ disablePlayer: "Выключить проигрыватель"
expandTweet: "Развернуть твит" expandTweet: "Развернуть твит"
themeEditor: "Редактор темы оформления" themeEditor: "Редактор темы оформления"
description: "Описание" description: "Описание"
describeFile: "Добавить подпись" describeFile: "Добавить описание"
enterFileDescription: "Введите подпись" enterFileDescription: "Введите описание"
author: "Автор" author: "Автор"
leaveConfirm: "Вы не сохранили изменения. Хотите выйти и потерять их?" leaveConfirm: "Вы не сохранили изменения. Хотите выйти и потерять их?"
manage: "Управление" manage: "Управление"
@ -826,7 +826,7 @@ gallery: "Галерея"
recentPosts: "Недавние публикации" recentPosts: "Недавние публикации"
popularPosts: "Популярные публикации" popularPosts: "Популярные публикации"
shareWithNote: "Поделиться постом" shareWithNote: "Поделиться постом"
ads: "Реклама" ads: "Баннеры сообщества"
expiration: "Опрос длится" expiration: "Опрос длится"
memo: "Памятка" memo: "Памятка"
priority: "Приоритет" priority: "Приоритет"
@ -1001,6 +1001,7 @@ _aboutFirefish:
pleaseDonateToHost: Также не забудьте поддержать ваш домашний сервер {host}, чтобы pleaseDonateToHost: Также не забудьте поддержать ваш домашний сервер {host}, чтобы
помочь с его операционными расходами. помочь с его операционными расходами.
donateHost: Пожертвовать на {host} donateHost: Пожертвовать на {host}
misskeyContributors: Контрибьюторы Misskey
_nsfw: _nsfw:
respect: "Скрывать содержимое не для всех" respect: "Скрывать содержимое не для всех"
ignore: "Показывать содержимое не для всех" ignore: "Показывать содержимое не для всех"
@ -1024,7 +1025,7 @@ _mfm:
boldDescription: "Выделяет текст, делая буквы жирнее." boldDescription: "Выделяет текст, делая буквы жирнее."
small: "Мелкий шрифт" small: "Мелкий шрифт"
smallDescription: "Делает текст маленьким и незаметным." smallDescription: "Делает текст маленьким и незаметным."
center: "Выровнять элементы по центру" center: "По центру"
centerDescription: "Так можно выровнять что-то по центру." centerDescription: "Так можно выровнять что-то по центру."
inlineCode: "Программа (в тексте)" inlineCode: "Программа (в тексте)"
inlineCodeDescription: "Подсвечивает фрагмент программы внутри сплошного текста." inlineCodeDescription: "Подсвечивает фрагмент программы внутри сплошного текста."
@ -1267,8 +1268,8 @@ _tutorial:
step6_1: "Итак, что это за место?" step6_1: "Итак, что это за место?"
step6_2: "Ну, вы не просто присоединились к Firefish. Вы вошли в Fediverse, взаимосвязанную step6_2: "Ну, вы не просто присоединились к Firefish. Вы вошли в Fediverse, взаимосвязанную
сеть из тысяч серверов." сеть из тысяч серверов."
step6_3: "Каждый сервер работает по-своему, и не на всех серверах работает Firefish. step6_3: "Каждый сервер работает по-своему, и не все сервера работают на базе Firefish.
Но этот работает! Это немного сложно, но вы быстро разберетесь." Но этот работает! Это сложновато, но вы быстро разберетесь."
step6_4: "Теперь идите, изучайте и развлекайтесь!" step6_4: "Теперь идите, изучайте и развлекайтесь!"
_2fa: _2fa:
alreadyRegistered: "Двухфакторная аутентификация уже настроена." alreadyRegistered: "Двухфакторная аутентификация уже настроена."
@ -1475,7 +1476,7 @@ _charts:
remoteNotesIncDec: "Изменения числа постов с других сайтов" remoteNotesIncDec: "Изменения числа постов с других сайтов"
notesTotal: "Общее количество постов" notesTotal: "Общее количество постов"
filesIncDec: "Изменения числа файлов" filesIncDec: "Изменения числа файлов"
filesTotal: "Суммарное количество файлов" filesTotal: "Общее количество файлов"
storageUsageIncDec: "Изменения заполнения хранилища" storageUsageIncDec: "Изменения заполнения хранилища"
storageUsageTotal: "Суммарное заполнение хранилища" storageUsageTotal: "Суммарное заполнение хранилища"
_instanceCharts: _instanceCharts:
@ -1903,12 +1904,12 @@ customMOTDDescription: Пользовательские сообщения дл
загружает / перезагружает страницу. загружает / перезагружает страницу.
recommendedInstancesDescription: Рекомендуемые инстансы, разделенные разрывами строк, recommendedInstancesDescription: Рекомендуемые инстансы, разделенные разрывами строк,
должны отображаться на рекомендуемой ленте. должны отображаться на рекомендуемой ленте.
caption: Автоматическая подпись caption: Автоматическое описание
splash: Заставка splash: Заставка
updateAvailable: Возможно, доступно обновление! updateAvailable: Возможно, доступно обновление!
move: Переместить move: Переместить
swipeOnDesktop: Разрешить свайпы в мобильном стиле на десктопе swipeOnDesktop: Разрешить свайпы в мобильном стиле на десктопе
showAds: Показывать рекламу showAds: Показывать баннеры сообщества
noEmailServerWarning: Почтовый сервер не настроен. noEmailServerWarning: Почтовый сервер не настроен.
type: Тип type: Тип
numberOfPageCacheDescription: Увеличение этого числа повысит удобство для пользователей, numberOfPageCacheDescription: Увеличение этого числа повысит удобство для пользователей,
@ -2137,3 +2138,19 @@ donationLink: Ссылка на страницу для взносов
isLocked: Этот аккаунт имеет одобрение запросов на подписку isLocked: Этот аккаунт имеет одобрение запросов на подписку
removeRecipient: Удалить получателя removeRecipient: Удалить получателя
removeMember: Удалить участника 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: "Тека порожня" emptyFolder: "Тека порожня"
unableToDelete: "Видалення неможливе" unableToDelete: "Видалення неможливе"
inputNewFileName: "Введіть ім'я нового файлу" inputNewFileName: "Введіть ім'я нового файлу"
inputNewDescription: "Введіть новий заголовок" inputNewDescription: "Введіть новий опис"
inputNewFolderName: "Введіть ім'я нової теки" inputNewFolderName: "Введіть ім'я нової теки"
circularReferenceFolder: "Ви намагаєтесь перемістити папку в її підпапку." circularReferenceFolder: "Ви намагаєтесь перемістити папку в її підпапку."
hasChildFilesOrFolders: "Ця тека не порожня і не може бути видалена." hasChildFilesOrFolders: "Ця тека не порожня і не може бути видалена."
@ -591,8 +591,8 @@ disablePlayer: "Закрити відеоплеєр"
expandTweet: "Розгорнути твіт" expandTweet: "Розгорнути твіт"
themeEditor: "Редактор тем" themeEditor: "Редактор тем"
description: "Опис" description: "Опис"
describeFile: "Додати підпис" describeFile: "Додати опис"
enterFileDescription: "Введіть підпис" enterFileDescription: "Введіть опис"
author: "Автор" author: "Автор"
leaveConfirm: "Зміни не збережені. Ви дійсно хочете скасувати зміни?" leaveConfirm: "Зміни не збережені. Ви дійсно хочете скасувати зміни?"
manage: "Управління" manage: "Управління"
@ -1985,7 +1985,7 @@ showUpdates: Показувати спливаюче вікно при онов
updateAvailable: Можливо, є доступне оновлення! updateAvailable: Можливо, є доступне оновлення!
recommendedInstancesDescription: Рекомендовані сервери відокремлюються переведенням recommendedInstancesDescription: Рекомендовані сервери відокремлюються переведенням
рядка, щоб з'явитися на стрічці рекомендацій. рядка, щоб з'явитися на стрічці рекомендацій.
caption: Автоматичний підпис caption: Автоматичний опис
showAdminUpdates: Вказати, що доступна нова версія Firefish (тільки для адміністратора) showAdminUpdates: Вказати, що доступна нова версія Firefish (тільки для адміністратора)
defaultReaction: Емодзі реакція за замовчуванням для вихідних і вхідних записів defaultReaction: Емодзі реакція за замовчуванням для вихідних і вхідних записів
license: Ліцензія license: Ліцензія
@ -2151,3 +2151,7 @@ deletePasskeysConfirm: Це видалить усі ключ-паролі і к
записі без можливости відмінити цю дію. Продовжити? записі без можливости відмінити цю дію. Продовжити?
addRe: Додати "re:" на початку коментаря у відповідь на запис із попередженням про addRe: Додати "re:" на початку коментаря у відповідь на запис із попередженням про
вміст вміст
confirm: Підтвердити
importZip: Імпортувати ZIP
exportZip: Експортувати ZIP
emojiPackCreator: Генератор паків емодзі

View file

@ -290,7 +290,7 @@ emptyDrive: "网盘中无文件"
emptyFolder: "此文件夹中无文件" emptyFolder: "此文件夹中无文件"
unableToDelete: "无法删除" unableToDelete: "无法删除"
inputNewFileName: "请输入新文件名" inputNewFileName: "请输入新文件名"
inputNewDescription: "请输入新标题" inputNewDescription: "请输入新描述"
inputNewFolderName: "请输入新文件夹名" inputNewFolderName: "请输入新文件夹名"
circularReferenceFolder: "目标文件夹是您要移动的文件夹的子文件夹。" circularReferenceFolder: "目标文件夹是您要移动的文件夹的子文件夹。"
hasChildFilesOrFolders: "此文件夹中有文件,无法删除。" hasChildFilesOrFolders: "此文件夹中有文件,无法删除。"
@ -561,8 +561,8 @@ disablePlayer: "关闭播放器"
expandTweet: "展开帖子" expandTweet: "展开帖子"
themeEditor: "主题编辑器" themeEditor: "主题编辑器"
description: "描述" description: "描述"
describeFile: "添加标题" describeFile: "添加描述"
enterFileDescription: "输入标题" enterFileDescription: "输入描述"
author: "作者" author: "作者"
leaveConfirm: "存在未保存的更改。要放弃更改吗?" leaveConfirm: "存在未保存的更改。要放弃更改吗?"
manage: "管理" manage: "管理"
@ -776,7 +776,7 @@ gallery: "图库"
recentPosts: "最新发布" recentPosts: "最新发布"
popularPosts: "热门投稿" popularPosts: "热门投稿"
shareWithNote: "在帖子中分享" shareWithNote: "在帖子中分享"
ads: "广告" ads: "社区横幅"
expiration: "截止时间" expiration: "截止时间"
memo: "便笺" memo: "便笺"
priority: "优先级" priority: "优先级"
@ -825,7 +825,7 @@ unmuteThread: "取消静音帖子串"
ffVisibility: "关注/关注者 可见性" ffVisibility: "关注/关注者 可见性"
ffVisibilityDescription: "您可以设置谁可以看到您的关注/关注者信息。" ffVisibilityDescription: "您可以设置谁可以看到您的关注/关注者信息。"
continueThread: "查看更多帖子" continueThread: "查看更多帖子"
deleteAccountConfirm: "将不可逆的删除账号,是否继续?" deleteAccountConfirm: "这将不可逆转地删除账号,是否继续?"
incorrectPassword: "密码错误。" incorrectPassword: "密码错误。"
voteConfirm: "确定投给 “{choice}” " voteConfirm: "确定投给 “{choice}” "
hide: "隐藏" hide: "隐藏"
@ -994,6 +994,7 @@ _aboutFirefish:
pleaseDonateToFirefish: 请考虑赞助 Firefish 以支持其开发。 pleaseDonateToFirefish: 请考虑赞助 Firefish 以支持其开发。
pleaseDonateToHost: 也请考虑赞助您的主服务器 {host},以帮助支持其运营成本。 pleaseDonateToHost: 也请考虑赞助您的主服务器 {host},以帮助支持其运营成本。
donateHost: 赞助 {host} donateHost: 赞助 {host}
misskeyContributors: Misskey 贡献者
_nsfw: _nsfw:
respect: "隐藏敏感内容" respect: "隐藏敏感内容"
ignore: "不隐藏敏感内容" ignore: "不隐藏敏感内容"
@ -1857,7 +1858,7 @@ seperateRenoteQuote: 单独的转发和引用按钮
customSplashIcons: 自定义启动屏幕图标urls customSplashIcons: 自定义启动屏幕图标urls
alt: 替代文字 alt: 替代文字
pushNotificationNotSupported: 您的浏览器或者服务器不支持推送通知 pushNotificationNotSupported: 您的浏览器或者服务器不支持推送通知
showAds: 显示广告 showAds: 显示社区横幅
enterSendsMessage: 按回车键发送信息(关闭则是 Ctrl + Retun 发送) enterSendsMessage: 按回车键发送信息(关闭则是 Ctrl + Retun 发送)
recommendedInstances: 推荐服务器 recommendedInstances: 推荐服务器
updateAvailable: 可能有可用更新! updateAvailable: 可能有可用更新!
@ -1876,7 +1877,7 @@ clipsDesc: 便签就像可共享的分类书签。您可以从各个帖子的菜
privateModeInfo: 当启用时,只有白名单上的服务器可以与您的服务器联合,所有的帖子都会对公共时间线隐藏。 privateModeInfo: 当启用时,只有白名单上的服务器可以与您的服务器联合,所有的帖子都会对公共时间线隐藏。
allowedInstancesDescription: 要列入联合白名单的服务器的主机名,一行一个(仅适用于私密模式)。 allowedInstancesDescription: 要列入联合白名单的服务器的主机名,一行一个(仅适用于私密模式)。
breakFollowConfirm: 确定要移除关注者吗? breakFollowConfirm: 确定要移除关注者吗?
caption: 自动显示说明文字 caption: 自动显示描述文字
newer: 更新的 newer: 更新的
older: 更旧的 older: 更旧的
noInstances: 没有服务器 noInstances: 没有服务器
@ -1967,3 +1968,16 @@ removeQuote: 移除引用
removeRecipient: 移除接收者 removeRecipient: 移除接收者
removeMember: 移除成员 removeMember: 移除成员
origin: 起源 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", "name": "firefish",
"version": "1.0.5-dev5", "version": "1.0.5-dev6",
"codename": "aqua", "codename": "aqua",
"repository": { "repository": {
"type": "git", "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; fingerprint?: string;
}; };
outgoingAddress?: string;
outgoingAddressFamily?: "ipv4" | "ipv6" | "dual"; outgoingAddressFamily?: "ipv4" | "ipv6" | "dual";
deliverJobConcurrency?: number; 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)}`); logger.succ(`Download finished: ${chalk.cyan(url)}`);
} }
function isPrivateIp(ip: string): boolean { export function isPrivateIp(ip: string): boolean {
for (const net of config.allowedPrivateNetworks || []) { for (const net of config.allowedPrivateNetworks || []) {
const cidr = new IPCIDR(net); const cidr = new IPCIDR(net);
if (cidr.contains(ip)) { if (cidr.contains(ip)) {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -214,7 +214,7 @@ export class User {
@Index() @Index()
@Column("varchar", { @Column("varchar", {
length: 128, length: 512,
nullable: true, nullable: true,
comment: comment:
"The host of the User. It will be null if the origin of the user is local.", "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!)); .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( const instancesToSkip = await skippedInstances(
// get (unique) list of hosts // get (unique) list of hosts
Array.from( Array.from(new Set(validInboxes.map((valid) => valid.host))),
new Set(Array.from(inboxes).map((inbox) => new URL(inbox).host)),
),
); );
// deliver // deliver
for (const inbox of inboxes) { for (const valid of validInboxes) {
// skip instances as indicated // 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 block from "./block/index.js";
import flag from "./flag/index.js"; import flag from "./flag/index.js";
import move from "./move/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 { extractDbHost } from "@/misc/convert-host.js";
import { shouldBlockInstance } from "@/misc/should-block-instance.js"; import { shouldBlockInstance } from "@/misc/should-block-instance.js";
@ -106,6 +106,8 @@ async function performOneActivity(
} else if (isMove(activity)) { } else if (isMove(activity)) {
await move(actor, activity); await move(actor, activity);
} else { } 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, ...options,
"@context": "https://w3id.org/identity/v1", "@context": "https://w3id.org/identity/v1",
}; };
delete transformedOptions["type"]; transformedOptions.type = undefined;
delete transformedOptions["id"]; transformedOptions.id = undefined;
delete transformedOptions["signatureValue"]; transformedOptions.signatureValue = undefined;
const canonizedOptions = await this.normalize(transformedOptions); const canonizedOptions = await this.normalize(transformedOptions);
const optionsHash = this.sha256(canonizedOptions); const optionsHash = this.sha256(canonizedOptions);
const transformedData = { ...data }; const transformedData = { ...data };
delete transformedData["signature"]; transformedData.signature = undefined;
const cannonidedData = await this.normalize(transformedData); const cannonidedData = await this.normalize(transformedData);
if (this.debug) console.debug(`cannonidedData: ${cannonidedData}`); if (this.debug) console.debug(`cannonidedData: ${cannonidedData}`);
const documentHash = this.sha256(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 // Create user
let user: IRemoteUser; let user: IRemoteUser;
try { try {
@ -274,6 +289,14 @@ export async function createPerson(
isCollectionOrOrderedCollection(person.following) isCollectionOrOrderedCollection(person.following)
? person.following.totalItems ? person.following.totalItems
: undefined, : undefined,
notesCount:
notesCount !== undefined
? notesCount
: person.outbox &&
typeof person.outbox !== "string" &&
isCollectionOrOrderedCollection(person.outbox)
? person.outbox.totalItems
: undefined,
featured: person.featured ? getApId(person.featured) : undefined, featured: person.featured ? getApId(person.featured) : undefined,
uri: person.id, uri: person.id,
tags, tags,
@ -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 = { const updates = {
lastFetchedAt: new Date(), lastFetchedAt: new Date(),
inbox: person.inbox, inbox: person.inbox,
@ -495,6 +533,14 @@ export async function updatePerson(
isCollectionOrOrderedCollection(person.following) isCollectionOrOrderedCollection(person.following)
? person.following.totalItems ? person.following.totalItems
: undefined, : undefined,
notesCount:
notesCount !== undefined
? notesCount
: person.outbox &&
typeof person.outbox !== "string" &&
isCollectionOrOrderedCollection(person.outbox)
? person.outbox.totalItems
: undefined,
featured: person.featured, featured: person.featured,
emojis: emojiNames, emojis: emojiNames,
name: truncate(person.name, nameLength), name: truncate(person.name, nameLength),
@ -554,7 +600,7 @@ export async function updatePerson(
{ {
followerSharedInbox: followerSharedInbox:
person.sharedInbox || 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.items
: collection.orderedItems; : collection.orderedItems;
const items = await Promise.all( const items = await Promise.all(
toArray(unresolvedItems).map((x) => resolver.resolve(x)), toArray(unresolvedItems).map((x) => resolver?.resolve(x)),
); );
// Resolve and regist Notes // Resolve and regist Notes

View file

@ -1,7 +1,6 @@
import define from "../../../define.js"; import define from "../../../define.js";
import { createImportCustomEmojisJob } from "@/queue/index.js"; import { createImportCustomEmojisJob } from "@/queue/index.js";
import { ApiError } from "../../../error.js"; import { ApiError } from "../../../error.js";
import ms from "ms";
export const meta = { export const meta = {
tags: ["admin", "emoji"], 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 define from "../../define.js";
import { Users, UserProfiles } from "@/models/index.js"; import { Users, UserProfiles } from "@/models/index.js";
import { ApiError } from "../../error.js"; import { ApiError } from "../../error.js";

View file

@ -24,7 +24,7 @@ export const meta = {
}, },
recursiveNesting: { 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", code: "NO_SUCH_PARENT_FOLDER",
id: "ce104e3a-faaf-49d5-b459-10ff0cbbcaa1", id: "ce104e3a-faaf-49d5-b459-10ff0cbbcaa1",
}, },

View file

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

View file

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

View file

@ -48,7 +48,7 @@ export const meta = {
}, },
groupAccessDenied: { 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", code: "GROUP_ACCESS_DENIED",
id: "a053a8dd-a491-4718-8f87-50775aad9284", id: "a053a8dd-a491-4718-8f87-50775aad9284",
}, },

View file

@ -28,7 +28,7 @@ export const meta = {
errors: { errors: {
recipientIsYourself: { recipientIsYourself: {
message: "You can not send a message to yourself.", message: "You cannot send a message to yourself.",
code: "RECIPIENT_IS_YOURSELF", code: "RECIPIENT_IS_YOURSELF",
id: "17e2ba79-e22a-4cbc-bf91-d327643f4a7e", id: "17e2ba79-e22a-4cbc-bf91-d327643f4a7e",
}, },
@ -46,7 +46,7 @@ export const meta = {
}, },
groupAccessDenied: { 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", code: "GROUP_ACCESS_DENIED",
id: "d96b3cca-5ad1-438b-ad8b-02f931308fbd", id: "d96b3cca-5ad1-438b-ad8b-02f931308fbd",
}, },

View file

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

View file

@ -70,7 +70,7 @@ export const meta = {
}, },
cannotReRenote: { cannotReRenote: {
message: "You can not Renote a pure Renote.", message: "You cannot Renote a pure Renote.",
code: "CANNOT_RENOTE_TO_A_PURE_RENOTE", code: "CANNOT_RENOTE_TO_A_PURE_RENOTE",
id: "fd4cc33e-2a37-48dd-99cc-9b806eb2031a", id: "fd4cc33e-2a37-48dd-99cc-9b806eb2031a",
}, },
@ -82,7 +82,7 @@ export const meta = {
}, },
cannotReplyToPureRenote: { 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", code: "CANNOT_REPLY_TO_A_PURE_RENOTE",
id: "3ac74a84-8fd5-4bb0-870f-01804f82ce15", id: "3ac74a84-8fd5-4bb0-870f-01804f82ce15",
}, },
@ -130,7 +130,7 @@ export const meta = {
}, },
cannotPrivateRenote: { cannotPrivateRenote: {
message: "You can not perform a private renote.", message: "You cannot perform a private renote.",
code: "CANNOT_PRIVATE_RENOTE", code: "CANNOT_PRIVATE_RENOTE",
id: "19a50f1c-84fa-4e33-81d3-17834ccc0ad8", id: "19a50f1c-84fa-4e33-81d3-17834ccc0ad8",
}, },
@ -140,6 +140,18 @@ export const meta = {
code: "NOT_LOCAL_USER", code: "NOT_LOCAL_USER",
id: "b907f407-2aa0-4283-800b-a2c56290b822", 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; } as const;
@ -268,6 +280,10 @@ export default define(meta, paramDef, async (ps, user) => {
throw e; throw e;
}); });
if (ps.renoteId === note.id) {
throw new ApiError(meta.errors.cannotQuoteOwnNote);
}
if (renote.renoteId && !renote.text && !renote.fileIds && !renote.hasPoll) { if (renote.renoteId && !renote.text && !renote.fileIds && !renote.hasPoll) {
throw new ApiError(meta.errors.cannotReRenote); throw new ApiError(meta.errors.cannotReRenote);
} }
@ -523,7 +539,8 @@ export default define(meta, paramDef, async (ps, user) => {
update.cw = null; update.cw = null;
} }
if (ps.visibility !== note.visibility) { if (ps.visibility !== note.visibility) {
update.visibility = ps.visibility; // update.visibility = ps.visibility;
throw new ApiError(meta.errors.cannotChangeVisibility);
} }
if (ps.localOnly !== note.localOnly) { if (ps.localOnly !== note.localOnly) {
update.localOnly = ps.localOnly; update.localOnly = ps.localOnly;

View file

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

View file

@ -10,7 +10,7 @@ export const meta = {
kind: "write:user-groups", kind: "write:user-groups",
description: 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: { errors: {
noSuchGroup: { noSuchGroup: {

View file

@ -11,7 +11,7 @@ export const meta = {
kind: "write:user-groups", kind: "write:user-groups",
description: 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: { errors: {
noSuchGroup: { 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 { 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 { genId } from "@/misc/gen-id.js";
import { sendEmail } from "@/services/send-email.js"; import { sendEmail } from "@/services/send-email.js";
import { fetchMeta } from "@/misc/fetch-meta.js"; import { fetchMeta } from "@/misc/fetch-meta.js";
import { getUser } from "../../common/getters.js"; import { getUser } from "../../common/getters.js";
import { ApiError } from "../../error.js"; import { ApiError } from "../../error.js";
import define from "../../define.js"; import define from "../../define.js";
import { toHtml } from "@/mfm/to-html.js";
export const meta = { export const meta = {
tags: ["users"], tags: ["users"],
@ -84,6 +86,7 @@ export default define(meta, paramDef, async (ps, me) => {
], ],
}); });
const meta = await fetchMeta();
for (const moderator of moderators) { for (const moderator of moderators) {
publishAdminStream(moderator.id, "newAbuseUserReport", { publishAdminStream(moderator.id, "newAbuseUserReport", {
id: report.id, id: report.id,
@ -91,16 +94,16 @@ export default define(meta, paramDef, async (ps, me) => {
reporterId: report.reporterId, reporterId: report.reporterId,
comment: report.comment, comment: report.comment,
}); });
}
const meta = await fetchMeta(); const profile = await UserProfiles.findOneBy({ userId: moderator.id });
if (meta.email) { if (profile?.email) {
sendEmail( sendEmail(
meta.email, profile.email,
"New abuse report", "New abuse report",
sanitizeHtml(ps.comment), sanitizeHtml(toHtml(mfm.parse(ps.comment))!),
sanitizeHtml(ps.comment), sanitizeHtml(toHtml(mfm.parse(ps.comment))!),
); );
}
} }
}); });
}); });

View file

@ -74,3 +74,13 @@ export function convertStatus(status: Entity.Status) {
return 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 Router from "@koa/router";
import { getClient } from "../ApiMastodonCompatibleService.js"; import { getClient } from "../ApiMastodonCompatibleService.js";
import { ParsedUrlQuery } from "querystring"; 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"; import { convertId, IdType } from "../../index.js";
export function limitToInt(q: ParsedUrlQuery) { export function limitToInt(q: ParsedUrlQuery) {
@ -136,7 +141,9 @@ export function apiTimelineMastodon(router: Router): void {
const data = await client.getConversationTimeline( const data = await client.getConversationTimeline(
convertTimelinesArgsId(limitToInt(ctx.query)), convertTimelinesArgsId(limitToInt(ctx.query)),
); );
ctx.body = data.data; ctx.body = data.data.map((conversation) =>
convertConversation(conversation),
);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);

View file

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

View file

@ -1,7 +1,7 @@
import { URL } from "node:url"; import { URL } from "node:url";
import type { User } from "@/models/entities/user.js"; import type { User } from "@/models/entities/user.js";
import { createTemp } from "@/misc/create-temp.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 { DriveFolder } from "@/models/entities/drive-folder.js";
import type { DriveFile } from "@/models/entities/drive-file.js"; import type { DriveFile } from "@/models/entities/drive-file.js";
import { DriveFiles } from "@/models/index.js"; import { DriveFiles } from "@/models/index.js";
@ -35,7 +35,15 @@ export async function uploadFromUrl({
requestIp = null, requestIp = null,
requestHeaders = null, requestHeaders = null,
}: Args): Promise<DriveFile> { }: 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)) { if (name == null || !DriveFiles.validateFileName(name)) {
name = null; name = null;
} }

View file

@ -38,7 +38,7 @@ export async function addPinned(
if (pinings.length >= 15) { if (pinings.length >= 15) {
throw new IdentifiableError( throw new IdentifiableError(
"15a018eb-58e5-4da1-93be-330fcc5e4e1a", "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 // rome-ignore lint/suspicious/noAsyncPromiseExecutor: FIXME
new Promise<Note>(async (res, rej) => { new Promise<Note>(async (res, rej) => {
const dontFederateInitially = const dontFederateInitially = data.visibility === "hidden";
data.localOnly || data.visibility?.startsWith("hidden");
// If you reply outside the channel, match the scope of the target. // 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.) // 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> { export async function index(note: Note, reindexing: boolean): Promise<void> {
if (!note.text) return; if (!note.text || note.visibility !== "public") return;
if (config.elasticsearch && es) { if (config.elasticsearch && es) {
es.index({ es.index({

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -12,7 +12,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onBeforeUnmount, onMounted } from "vue"; import { onBeforeUnmount, onMounted, ref } from "vue";
import MkMenu from "@/components/MkMenu.vue"; import MkMenu from "@/components/MkMenu.vue";
import type { MenuItem } from "@/types/menu"; import type { MenuItem } from "@/types/menu";
import contains from "@/scripts/contains"; import contains from "@/scripts/contains";
@ -27,16 +27,16 @@ const emit = defineEmits<{
(ev: "closed"): void; (ev: "closed"): void;
}>(); }>();
const rootEl = $ref<HTMLDivElement>(); const rootEl = ref<HTMLDivElement>();
const zIndex = $ref<number>(os.claimZIndex("high")); const zIndex = ref<number>(os.claimZIndex("high"));
onMounted(() => { onMounted(() => {
let left = props.ev.pageX + 1, // + 1 let left = props.ev.pageX + 1, // + 1
top = props.ev.pageY + 1; // + 1 top = props.ev.pageY + 1; // + 1
const width = rootEl.offsetWidth; const width = rootEl.value.offsetWidth;
const height = rootEl.offsetHeight; const height = rootEl.value.offsetHeight;
if (left + width - window.pageXOffset > window.innerWidth) { if (left + width - window.pageXOffset > window.innerWidth) {
left = window.innerWidth - width + window.pageXOffset; left = window.innerWidth - width + window.pageXOffset;
@ -54,8 +54,8 @@ onMounted(() => {
left = 0; left = 0;
} }
rootEl.style.top = `${top}px`; rootEl.value.style.top = `${top}px`;
rootEl.style.left = `${left}px`; rootEl.value.style.left = `${left}px`;
document.body.addEventListener("mousedown", onMousedown); document.body.addEventListener("mousedown", onMousedown);
}); });
@ -65,7 +65,8 @@ onBeforeUnmount(() => {
}); });
function onMousedown(evt: Event) { function onMousedown(evt: Event) {
if (!contains(rootEl, evt.target) && rootEl !== evt.target) emit("closed"); if (!contains(rootEl.value, evt.target) && rootEl.value !== evt.target)
emit("closed");
} }
</script> </script>

View file

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

View file

@ -199,7 +199,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onBeforeUnmount, onMounted, ref, shallowRef } from "vue"; import { onBeforeUnmount, onMounted, ref, shallowRef, computed } from "vue";
import * as Acct from "firefish-js/built/acct"; import * as Acct from "firefish-js/built/acct";
import MkModal from "@/components/MkModal.vue"; import MkModal from "@/components/MkModal.vue";
import MkButton from "@/components/MkButton.vue"; import MkButton from "@/components/MkButton.vue";
@ -281,17 +281,15 @@ const modal = shallowRef<InstanceType<typeof MkModal>>();
const inputValue = ref<string | number | null>(props.input?.default ?? null); const inputValue = ref<string | number | null>(props.input?.default ?? null);
const selectedValue = ref(props.select?.default ?? null); const selectedValue = ref(props.select?.default ?? null);
let disabledReason = $ref<null | "charactersExceeded" | "charactersBelow">( let disabledReason = ref<null | "charactersExceeded" | "charactersBelow">(null);
null, const okButtonDisabled = computed<boolean>(() => {
);
const okButtonDisabled = $computed<boolean>(() => {
if (props.input) { if (props.input) {
if (props.input.minLength) { if (props.input.minLength) {
if ( if (
(inputValue.value || inputValue.value === "") && (inputValue.value || inputValue.value === "") &&
(inputValue.value as string).length < props.input.minLength (inputValue.value as string).length < props.input.minLength
) { ) {
disabledReason = "charactersBelow"; disabledReason.value = "charactersBelow";
return true; return true;
} }
} }
@ -300,7 +298,7 @@ const okButtonDisabled = $computed<boolean>(() => {
inputValue.value && inputValue.value &&
(inputValue.value as string).length > props.input.maxLength (inputValue.value as string).length > props.input.maxLength
) { ) {
disabledReason = "charactersExceeded"; disabledReason.value = "charactersExceeded";
return true; return true;
} }
} }

View file

@ -61,7 +61,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, onBeforeUnmount, onMounted } from "vue"; import { computed, onBeforeUnmount, onMounted, ref } from "vue";
import type * as Misskey from "firefish-js"; import type * as Misskey from "firefish-js";
import * as os from "@/os"; import * as os from "@/os";
import { stream } from "@/stream"; import { stream } from "@/stream";
@ -89,13 +89,13 @@ const props = withDefaults(
const isBlocking = computed(() => props.user.isBlocking); const isBlocking = computed(() => props.user.isBlocking);
let state = $ref(i18n.ts.processing); let state = ref(i18n.ts.processing);
let isFollowing = $ref(props.user.isFollowing); let isFollowing = ref(props.user.isFollowing);
let hasPendingFollowRequestFromYou = $ref( let hasPendingFollowRequestFromYou = ref(
props.user.hasPendingFollowRequestFromYou, props.user.hasPendingFollowRequestFromYou,
); );
let wait = $ref(false); let wait = ref(false);
const connection = stream.useChannel("main"); const connection = stream.useChannel("main");
const hideFollowButton = props.hideFollowButton ?? false; const hideFollowButton = props.hideFollowButton ?? false;
@ -108,13 +108,14 @@ if (props.user.isFollowing == null) {
function onFollowChange(user: Misskey.entities.UserDetailed) { function onFollowChange(user: Misskey.entities.UserDetailed) {
if (user.id === props.user.id) { if (user.id === props.user.id) {
isFollowing = user.isFollowing; isFollowing.value = user.isFollowing;
hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou; hasPendingFollowRequestFromYou.value =
user.hasPendingFollowRequestFromYou;
} }
} }
async function onClick() { async function onClick() {
wait = true; wait.value = true;
try { try {
if (isBlocking.value) { if (isBlocking.value) {
@ -133,7 +134,7 @@ async function onClick() {
}); });
} }
emit("refresh"); emit("refresh");
} else if (isFollowing) { } else if (isFollowing.value) {
const { canceled } = await os.confirm({ const { canceled } = await os.confirm({
type: "warning", type: "warning",
text: i18n.t("unfollowConfirm", { text: i18n.t("unfollowConfirm", {
@ -147,22 +148,22 @@ async function onClick() {
userId: props.user.id, userId: props.user.id,
}); });
} else { } else {
if (hasPendingFollowRequestFromYou) { if (hasPendingFollowRequestFromYou.value) {
await os.api("following/requests/cancel", { await os.api("following/requests/cancel", {
userId: props.user.id, userId: props.user.id,
}); });
hasPendingFollowRequestFromYou = false; hasPendingFollowRequestFromYou.value = false;
} else { } else {
await os.api("following/create", { await os.api("following/create", {
userId: props.user.id, userId: props.user.id,
}); });
hasPendingFollowRequestFromYou = true; hasPendingFollowRequestFromYou.value = true;
} }
} }
} catch (err) { } catch (err) {
console.error(err); console.error(err);
} finally { } finally {
wait = false; wait.value = false;
} }
} }

View file

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

View file

@ -8,7 +8,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, nextTick, watch } from "vue"; import { onMounted, nextTick, watch, shallowRef, ref } from "vue";
import { Chart } from "chart.js"; import { Chart } from "chart.js";
import * as os from "@/os"; import * as os from "@/os";
import { defaultStore } from "@/store"; import { defaultStore } from "@/store";
@ -23,11 +23,11 @@ const props = defineProps<{
src: string; src: string;
}>(); }>();
const rootEl = $shallowRef<HTMLDivElement>(null); const rootEl = shallowRef<HTMLDivElement>(null);
const chartEl = $shallowRef<HTMLCanvasElement>(null); const chartEl = shallowRef<HTMLCanvasElement>(null);
const now = new Date(); const now = new Date();
let chartInstance: Chart = null; let chartInstance: Chart = null;
let fetching = $ref(true); let fetching = ref(true);
const { handler: externalTooltipHandler } = useChartTooltip({ const { handler: externalTooltipHandler } = useChartTooltip({
position: "middle", position: "middle",
@ -43,8 +43,8 @@ async function renderChart() {
chartInstance.destroy(); chartInstance.destroy();
} }
const wide = rootEl.offsetWidth > 700; const wide = rootEl.value.offsetWidth > 700;
const narrow = rootEl.offsetWidth < 400; const narrow = rootEl.value.offsetWidth < 400;
const weeks = wide ? 50 : narrow ? 10 : 25; const weeks = wide ? 50 : narrow ? 10 : 25;
const chartLimit = 7 * weeks; const chartLimit = 7 * weeks;
@ -113,7 +113,7 @@ async function renderChart() {
values = addArrays(raw.diffs.normal, raw.diffs.reply, raw.diffs.renote); values = addArrays(raw.diffs.normal, raw.diffs.reply, raw.diffs.renote);
} }
fetching = false; fetching.value = false;
await nextTick(); await nextTick();
@ -131,7 +131,7 @@ async function renderChart() {
const marginEachCell = 4; const marginEachCell = 4;
chartInstance = new Chart(chartEl, { chartInstance = new Chart(chartEl.value, {
type: "matrix", type: "matrix",
data: { data: {
datasets: [ datasets: [
@ -247,7 +247,7 @@ async function renderChart() {
watch( watch(
() => props.src, () => props.src,
() => { () => {
fetching = true; fetching.value = true;
renderChart(); renderChart();
}, },
); );

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -233,13 +233,13 @@ const emit = defineEmits<{
(ev: "close", actioned?: boolean): void; (ev: "close", actioned?: boolean): void;
}>(); }>();
let itemsEl = $ref<HTMLDivElement>(); let itemsEl = ref<HTMLDivElement>();
let items2: InnerMenuItem[] = $ref([]); let items2: InnerMenuItem[] = ref([]);
let child = $ref<InstanceType<typeof XChild>>(); let child = ref<InstanceType<typeof XChild>>();
let childShowingItem = $ref<MenuItem | null>(); let childShowingItem = ref<MenuItem | null>();
watch( watch(
() => props.items, () => props.items,
@ -255,24 +255,24 @@ watch(
// if item is Promise // if item is Promise
items[i] = { type: "pending" }; items[i] = { type: "pending" };
item.then((actualItem) => { item.then((actualItem) => {
items2[i] = actualItem; items2.value[i] = actualItem;
}); });
} }
} }
items2 = items as InnerMenuItem[]; items2.value = items as InnerMenuItem[];
}, },
{ {
immediate: true, immediate: true,
}, },
); );
let childMenu = $ref<MenuItem[] | null>(); let childMenu = ref<MenuItem[] | null>();
let childTarget = $ref<HTMLElement | null>(); let childTarget = ref<HTMLElement | null>();
function closeChild() { function closeChild() {
childMenu = null; childMenu.value = null;
childShowingItem = null; childShowingItem.value = null;
} }
function childActioned() { function childActioned() {
@ -282,11 +282,12 @@ function childActioned() {
function onGlobalMousedown(event: MouseEvent) { function onGlobalMousedown(event: MouseEvent) {
if ( if (
childTarget && childTarget.value &&
(event.target === childTarget || childTarget.contains(event.target)) (event.target === childTarget.value ||
childTarget.value.contains(event.target))
) )
return; return;
if (child && child.checkHit(event)) return; if (child.value && child.value.checkHit(event)) return;
closeChild(); closeChild();
} }
@ -305,9 +306,9 @@ async function showChildren(item: MenuItem, ev: MouseEvent) {
os.popupMenu(item.children, ev.currentTarget ?? ev.target); os.popupMenu(item.children, ev.currentTarget ?? ev.target);
close(); close();
} else { } else {
childTarget = ev.currentTarget ?? ev.target; childTarget.value = ev.currentTarget ?? ev.target;
childMenu = item.children; childMenu.value = item.children;
childShowingItem = item; childShowingItem.value = item;
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -306,7 +306,7 @@ const props = defineProps<{
const inChannel = inject("inChannel", null); const inChannel = inject("inChannel", null);
let note = $ref(deepClone(props.note)); let note = ref(deepClone(props.note));
const softMuteReasonI18nSrc = (what?: string) => { const softMuteReasonI18nSrc = (what?: string) => {
if (what === "note") return i18n.ts.userSaysSomethingReason; if (what === "note") return i18n.ts.userSaysSomethingReason;
@ -321,19 +321,19 @@ const softMuteReasonI18nSrc = (what?: string) => {
// plugin // plugin
if (noteViewInterruptors.length > 0) { if (noteViewInterruptors.length > 0) {
onMounted(async () => { onMounted(async () => {
let result = deepClone(note); let result = deepClone(note.value);
for (const interruptor of noteViewInterruptors) { for (const interruptor of noteViewInterruptors) {
result = await interruptor.handler(result); result = await interruptor.handler(result);
} }
note = result; note.value = result;
}); });
} }
const isRenote = const isRenote =
note.renote != null && note.value.renote != null &&
note.text == null && note.value.text == null &&
note.fileIds.length === 0 && note.value.fileIds.length === 0 &&
note.poll == null; note.value.poll == null;
const el = ref<HTMLElement>(); const el = ref<HTMLElement>();
const footerEl = ref<HTMLElement>(); const footerEl = ref<HTMLElement>();
@ -342,13 +342,15 @@ const starButton = ref<InstanceType<typeof XStarButton>>();
const renoteButton = ref<InstanceType<typeof XRenoteButton>>(); const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
const renoteTime = ref<HTMLElement>(); const renoteTime = ref<HTMLElement>();
const reactButton = ref<HTMLElement>(); const reactButton = ref<HTMLElement>();
let appearNote = $computed(() => let appearNote = computed(() =>
isRenote ? (note.renote as misskey.entities.Note) : note, isRenote ? (note.value.renote as misskey.entities.Note) : note.value,
); );
const isMyRenote = $i && $i.id === note.userId; const isMyRenote = $i && $i.id === note.value.userId;
const showContent = ref(false); const showContent = ref(false);
const isDeleted = ref(false); const isDeleted = ref(false);
const muted = ref(getWordSoftMute(note, $i, defaultStore.state.mutedWords)); const muted = ref(
getWordSoftMute(note.value, $i, defaultStore.state.mutedWords),
);
const translation = ref(null); const translation = ref(null);
const translating = ref(false); const translating = ref(false);
const enableEmojiReactions = defaultStore.state.enableEmojiReactions; const enableEmojiReactions = defaultStore.state.enableEmojiReactions;
@ -399,7 +401,7 @@ const keymap = {
useNoteCapture({ useNoteCapture({
rootEl: el, rootEl: el,
note: $$(appearNote), note: appearNote,
isDeletedRef: isDeleted, isDeletedRef: isDeleted,
}); });
@ -407,7 +409,7 @@ function reply(viaKeyboard = false): void {
pleaseLogin(); pleaseLogin();
os.post( os.post(
{ {
reply: appearNote, reply: appearNote.value,
animation: !viaKeyboard, animation: !viaKeyboard,
}, },
() => { () => {
@ -423,7 +425,7 @@ function react(viaKeyboard = false): void {
reactButton.value, reactButton.value,
(reaction) => { (reaction) => {
os.api("notes/reactions/create", { os.api("notes/reactions/create", {
noteId: appearNote.id, noteId: appearNote.value.id,
reaction: reaction, reaction: reaction,
}); });
}, },
@ -466,21 +468,24 @@ function onContextmenu(ev: MouseEvent): void {
[ [
{ {
type: "label", type: "label",
text: notePage(appearNote), text: notePage(appearNote.value),
}, },
{ {
icon: "ph-browser ph-bold ph-lg", icon: "ph-browser ph-bold ph-lg",
text: i18n.ts.openInWindow, text: i18n.ts.openInWindow,
action: () => { action: () => {
os.pageWindow(notePage(appearNote)); os.pageWindow(notePage(appearNote.value));
}, },
}, },
notePage(appearNote) != location.pathname notePage(appearNote.value) != location.pathname
? { ? {
icon: "ph-arrows-out-simple ph-bold ph-lg", icon: "ph-arrows-out-simple ph-bold ph-lg",
text: i18n.ts.showInPage, text: i18n.ts.showInPage,
action: () => { action: () => {
router.push(notePage(appearNote), "forcePage"); router.push(
notePage(appearNote.value),
"forcePage",
);
}, },
} }
: undefined, : undefined,
@ -489,22 +494,25 @@ function onContextmenu(ev: MouseEvent): void {
type: "a", type: "a",
icon: "ph-arrow-square-out ph-bold ph-lg", icon: "ph-arrow-square-out ph-bold ph-lg",
text: i18n.ts.openInNewTab, text: i18n.ts.openInNewTab,
href: notePage(appearNote), href: notePage(appearNote.value),
target: "_blank", target: "_blank",
}, },
{ {
icon: "ph-link-simple ph-bold ph-lg", icon: "ph-link-simple ph-bold ph-lg",
text: i18n.ts.copyLink, text: i18n.ts.copyLink,
action: () => { action: () => {
copyToClipboard(`${url}${notePage(appearNote)}`); copyToClipboard(`${url}${notePage(appearNote.value)}`);
}, },
}, },
appearNote.user.host != null appearNote.value.user.host != null
? { ? {
type: "a", type: "a",
icon: "ph-arrow-square-up-right ph-bold ph-lg", icon: "ph-arrow-square-up-right ph-bold ph-lg",
text: i18n.ts.showOnRemote, text: i18n.ts.showOnRemote,
href: appearNote.url ?? appearNote.uri ?? "", href:
appearNote.value.url ??
appearNote.value.uri ??
"",
target: "_blank", target: "_blank",
} }
: undefined, : undefined,
@ -517,7 +525,7 @@ function onContextmenu(ev: MouseEvent): void {
function menu(viaKeyboard = false): void { function menu(viaKeyboard = false): void {
os.popupMenu( os.popupMenu(
getNoteMenu({ getNoteMenu({
note: note, note: note.value,
translating, translating,
translation, translation,
menuButton, menuButton,
@ -541,7 +549,7 @@ function showRenoteMenu(viaKeyboard = false): void {
danger: true, danger: true,
action: () => { action: () => {
os.api("notes/delete", { os.api("notes/delete", {
noteId: note.id, noteId: note.value.id,
}); });
isDeleted.value = true; isDeleted.value = true;
}, },
@ -582,13 +590,13 @@ function noteClick(e) {
) { ) {
e.stopPropagation(); e.stopPropagation();
} else { } else {
router.push(notePage(appearNote)); router.push(notePage(appearNote.value));
} }
} }
function readPromo() { function readPromo() {
os.api("promo/read", { os.api("promo/read", {
noteId: appearNote.id, noteId: appearNote.value.id,
}); });
isDeleted.value = true; isDeleted.value = true;
} }
@ -600,28 +608,30 @@ function setPostExpanded(val: boolean) {
} }
const accessibleLabel = computed(() => { const accessibleLabel = computed(() => {
let label = `${appearNote.user.username}; `; let label = `${appearNote.value.user.username}; `;
if (appearNote.renote) { if (appearNote.value.renote) {
label += `${i18n.t("renoted")} ${appearNote.renote.user.username}; `; label += `${i18n.t("renoted")} ${
if (appearNote.renote.cw) { appearNote.value.renote.user.username
label += `${i18n.t("cw")}: ${appearNote.renote.cw}; `; }; `;
if (appearNote.value.renote.cw) {
label += `${i18n.t("cw")}: ${appearNote.value.renote.cw}; `;
if (postIsExpanded.value) { if (postIsExpanded.value) {
label += `${appearNote.renote.text}; `; label += `${appearNote.value.renote.text}; `;
} }
} else { } else {
label += `${appearNote.renote.text}; `; label += `${appearNote.value.renote.text}; `;
} }
} else { } else {
if (appearNote.cw) { if (appearNote.value.cw) {
label += `${i18n.t("cw")}: ${appearNote.cw}; `; label += `${i18n.t("cw")}: ${appearNote.value.cw}; `;
if (postIsExpanded.value) { if (postIsExpanded.value) {
label += `${appearNote.text}; `; label += `${appearNote.value.text}; `;
} }
} else { } else {
label += `${appearNote.text}; `; label += `${appearNote.value.text}; `;
} }
} }
const date = new Date(appearNote.createdAt); const date = new Date(appearNote.value.createdAt);
label += `${date.toLocaleTimeString()}`; label += `${date.toLocaleTimeString()}`;
return label; return label;
}); });

View file

@ -177,9 +177,9 @@ const props = defineProps<{
pinned?: boolean; pinned?: boolean;
}>(); }>();
let tab = $ref("replies"); let tab = ref("replies");
let note = $ref(deepClone(props.note)); let note = ref(deepClone(props.note));
const softMuteReasonI18nSrc = (what?: string) => { const softMuteReasonI18nSrc = (what?: string) => {
if (what === "note") return i18n.ts.userSaysSomethingReason; if (what === "note") return i18n.ts.userSaysSomethingReason;
@ -194,30 +194,32 @@ const softMuteReasonI18nSrc = (what?: string) => {
// plugin // plugin
if (noteViewInterruptors.length > 0) { if (noteViewInterruptors.length > 0) {
onMounted(async () => { onMounted(async () => {
let result = deepClone(note); let result = deepClone(note.value);
for (const interruptor of noteViewInterruptors) { for (const interruptor of noteViewInterruptors) {
result = await interruptor.handler(result); result = await interruptor.handler(result);
} }
note = result; note.value = result;
}); });
} }
const el = ref<HTMLElement>(); const el = ref<HTMLElement>();
const noteEl = $ref(); const noteEl = ref();
const menuButton = ref<HTMLElement>(); const menuButton = ref<HTMLElement>();
const renoteButton = ref<InstanceType<typeof XRenoteButton>>(); const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
const reactButton = ref<HTMLElement>(); const reactButton = ref<HTMLElement>();
const showContent = ref(false); const showContent = ref(false);
const isDeleted = ref(false); const isDeleted = ref(false);
const muted = ref(getWordSoftMute(note, $i, defaultStore.state.mutedWords)); const muted = ref(
getWordSoftMute(note.value, $i, defaultStore.state.mutedWords),
);
const translation = ref(null); const translation = ref(null);
const translating = ref(false); const translating = ref(false);
let conversation = $ref<null | misskey.entities.Note[]>([]); let conversation = ref<null | misskey.entities.Note[]>([]);
const replies = ref<misskey.entities.Note[]>([]); const replies = ref<misskey.entities.Note[]>([]);
let directReplies = $ref<null | misskey.entities.Note[]>([]); let directReplies = ref<null | misskey.entities.Note[]>([]);
let directQuotes = $ref<null | misskey.entities.Note[]>([]); let directQuotes = ref<null | misskey.entities.Note[]>([]);
let clips = $ref(); let clips = ref();
let renotes = $ref(); let renotes = ref();
let isScrolling; let isScrolling;
const reactionsCount = Object.values(props.note.reactions).reduce( const reactionsCount = Object.values(props.note.reactions).reduce(
@ -236,14 +238,14 @@ const keymap = {
useNoteCapture({ useNoteCapture({
rootEl: el, rootEl: el,
note: $$(note), note: note,
isDeletedRef: isDeleted, isDeletedRef: isDeleted,
}); });
function reply(viaKeyboard = false): void { function reply(viaKeyboard = false): void {
pleaseLogin(); pleaseLogin();
os.post({ os.post({
reply: note, reply: note.value,
animation: !viaKeyboard, animation: !viaKeyboard,
}).then(() => { }).then(() => {
focus(); focus();
@ -257,7 +259,7 @@ function react(viaKeyboard = false): void {
reactButton.value, reactButton.value,
(reaction) => { (reaction) => {
os.api("notes/reactions/create", { os.api("notes/reactions/create", {
noteId: note.id, noteId: note.value.id,
reaction: reaction, reaction: reaction,
}); });
}, },
@ -291,7 +293,7 @@ function onContextmenu(ev: MouseEvent): void {
} else { } else {
os.contextMenu( os.contextMenu(
getNoteMenu({ getNoteMenu({
note: note, note: note.value,
translating, translating,
translation, translation,
menuButton, menuButton,
@ -305,7 +307,7 @@ function onContextmenu(ev: MouseEvent): void {
function menu(viaKeyboard = false): void { function menu(viaKeyboard = false): void {
os.popupMenu( os.popupMenu(
getNoteMenu({ getNoteMenu({
note: note, note: note.value,
translating, translating,
translation, translation,
menuButton, menuButton,
@ -319,48 +321,50 @@ function menu(viaKeyboard = false): void {
} }
function focus() { function focus() {
noteEl.focus(); noteEl.value.focus();
} }
function blur() { function blur() {
noteEl.blur(); noteEl.value.blur();
} }
directReplies = null; directReplies.value = null;
os.api("notes/children", { os.api("notes/children", {
noteId: note.id, noteId: note.value.id,
limit: 30, limit: 30,
depth: 12, depth: 12,
}).then((res) => { }).then((res) => {
res = res.reduce((acc, resNote) => { res = res.reduce((acc, resNote) => {
if (resNote.userId == note.userId) { if (resNote.userId == note.value.userId) {
return [...acc, resNote]; return [...acc, resNote];
} }
return [resNote, ...acc]; return [resNote, ...acc];
}, []); }, []);
replies.value = res; replies.value = res;
directReplies = res directReplies.value = res
.filter((resNote) => resNote.replyId === note.id) .filter((resNote) => resNote.replyId === note.value.id)
.reverse(); .reverse();
directQuotes = res.filter((resNote) => resNote.renoteId === note.id); directQuotes.value = res.filter(
(resNote) => resNote.renoteId === note.value.id,
);
}); });
conversation = null; conversation.value = null;
if (note.replyId) { if (note.value.replyId) {
os.api("notes/conversation", { os.api("notes/conversation", {
noteId: note.replyId, noteId: note.value.replyId,
limit: 30, limit: 30,
}).then((res) => { }).then((res) => {
conversation = res.reverse(); conversation.value = res.reverse();
focus(); focus();
}); });
} }
clips = null; clips.value = null;
os.api("notes/clips", { os.api("notes/clips", {
noteId: note.id, noteId: note.value.id,
}).then((res) => { }).then((res) => {
clips = res; clips.value = res;
}); });
// const pagination = { // const pagination = {
@ -371,14 +375,14 @@ os.api("notes/clips", {
// const pagingComponent = $ref<InstanceType<typeof MkPagination>>(); // const pagingComponent = $ref<InstanceType<typeof MkPagination>>();
renotes = null; renotes.value = null;
function loadTab() { function loadTab() {
if (tab === "renotes" && !renotes) { if (tab.value === "renotes" && !renotes.value) {
os.api("notes/renotes", { os.api("notes/renotes", {
noteId: note.id, noteId: note.value.id,
limit: 100, limit: 100,
}).then((res) => { }).then((res) => {
renotes = res; renotes.value = res;
}); });
} }
} }
@ -387,7 +391,7 @@ async function onNoteUpdated(noteData: NoteUpdatedEvent): Promise<void> {
const { type, id, body } = noteData; const { type, id, body } = noteData;
let found = -1; let found = -1;
if (id === note.id) { if (id === note.value.id) {
found = 0; found = 0;
} else { } else {
for (let i = 0; i < replies.value.length; i++) { for (let i = 0; i < replies.value.length; i++) {
@ -412,7 +416,7 @@ async function onNoteUpdated(noteData: NoteUpdatedEvent): Promise<void> {
replies.value.splice(found, 0, replyNote); replies.value.splice(found, 0, replyNote);
if (found === 0) { if (found === 0) {
directReplies.push(replyNote); directReplies.value.push(replyNote);
} }
break; break;
@ -433,12 +437,12 @@ document.addEventListener("wheel", () => {
onMounted(() => { onMounted(() => {
stream.on("noteUpdated", onNoteUpdated); stream.on("noteUpdated", onNoteUpdated);
isScrolling = false; isScrolling = false;
noteEl.scrollIntoView(); noteEl.value.scrollIntoView();
}); });
onUpdated(() => { onUpdated(() => {
if (!isScrolling) { if (!isScrolling) {
noteEl.scrollIntoView(); noteEl.value.scrollIntoView();
if (location.hash) { if (location.hash) {
location.replace(location.hash); // Jump to highlighted reply location.replace(location.hash); // Jump to highlighted reply
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -30,7 +30,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ComputedRef, provide } from "vue"; import { ComputedRef, provide, ref, computed } from "vue";
import RouterView from "@/components/global/RouterView.vue"; import RouterView from "@/components/global/RouterView.vue";
import XWindow from "@/components/MkWindow.vue"; import XWindow from "@/components/MkWindow.vue";
import { popout as _popout } from "@/scripts/popout"; import { popout as _popout } from "@/scripts/popout";
@ -51,18 +51,18 @@ defineEmits<{
const router = new Router(routes, props.initialPath); const router = new Router(routes, props.initialPath);
let pageMetadata = $ref<null | ComputedRef<PageMetadata>>(); let pageMetadata = ref<null | ComputedRef<PageMetadata>>();
let windowEl = $ref<InstanceType<typeof XWindow>>(); let windowEl = ref<InstanceType<typeof XWindow>>();
const history = $ref<{ path: string; key: any }[]>([ const history = ref<{ path: string; key: any }[]>([
{ {
path: router.getCurrentPath(), path: router.getCurrentPath(),
key: router.getCurrentKey(), key: router.getCurrentKey(),
}, },
]); ]);
const buttonsLeft = $computed(() => { const buttonsLeft = computed(() => {
const buttons = []; const buttons = [];
if (history.length > 1) { if (history.value.length > 1) {
buttons.push({ buttons.push({
icon: "ph-caret-left ph-bold ph-lg", icon: "ph-caret-left ph-bold ph-lg",
onClick: back, onClick: back,
@ -71,7 +71,7 @@ const buttonsLeft = $computed(() => {
return buttons; return buttons;
}); });
const buttonsRight = $computed(() => { const buttonsRight = computed(() => {
const buttons = [ const buttons = [
{ {
icon: "ph-arrows-out-simple ph-bold ph-lg", icon: "ph-arrows-out-simple ph-bold ph-lg",
@ -84,18 +84,18 @@ const buttonsRight = $computed(() => {
}); });
router.addListener("push", (ctx) => { router.addListener("push", (ctx) => {
history.push({ path: ctx.path, key: ctx.key }); history.value.push({ path: ctx.path, key: ctx.key });
}); });
provide("router", router); provide("router", router);
provideMetadataReceiver((info) => { provideMetadataReceiver((info) => {
pageMetadata = info; pageMetadata.value = info;
}); });
provide("shouldOmitHeaderTitle", true); provide("shouldOmitHeaderTitle", true);
provide("shouldBackButton", false); provide("shouldBackButton", false);
provide("shouldHeaderThin", true); provide("shouldHeaderThin", true);
const contextmenu = $computed(() => [ const contextmenu = computed(() => [
{ {
icon: "ph-arrows-out-simple ph-bold ph-lg", icon: "ph-arrows-out-simple ph-bold ph-lg",
text: i18n.ts.showInPage, text: i18n.ts.showInPage,
@ -111,7 +111,7 @@ const contextmenu = $computed(() => [
text: i18n.ts.openInNewTab, text: i18n.ts.openInNewTab,
action: () => { action: () => {
window.open(url + router.getCurrentPath(), "_blank"); window.open(url + router.getCurrentPath(), "_blank");
windowEl.close(); windowEl.value.close();
}, },
}, },
{ {
@ -124,25 +124,25 @@ const contextmenu = $computed(() => [
]); ]);
function back() { function back() {
history.pop(); history.value.pop();
router.replace( router.replace(
history[history.length - 1].path, history.value[history.value.length - 1].path,
history[history.length - 1].key, history.value[history.value.length - 1].key,
); );
} }
function close() { function close() {
windowEl.close(); windowEl.value.close();
} }
function expand() { function expand() {
mainRouter.push(router.getCurrentPath(), "forcePage"); mainRouter.push(router.getCurrentPath(), "forcePage");
windowEl.close(); windowEl.value.close();
} }
function popout() { function popout() {
_popout(router.getCurrentPath(), windowEl.$el); _popout(router.getCurrentPath(), windowEl.value.$el);
windowEl.close(); windowEl.value.close();
} }
defineExpose({ defineExpose({

View file

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

View file

@ -245,7 +245,15 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { inject, watch, nextTick, onMounted, defineAsyncComponent } from "vue"; import {
inject,
watch,
nextTick,
onMounted,
defineAsyncComponent,
ref,
computed,
} from "vue";
import * as mfm from "mfm-js"; import * as mfm from "mfm-js";
import * as misskey from "firefish-js"; import * as misskey from "firefish-js";
import autosize from "autosize"; import autosize from "autosize";
@ -311,45 +319,47 @@ const emit = defineEmits<{
(ev: "esc"): void; (ev: "esc"): void;
}>(); }>();
const textareaEl = $ref<HTMLTextAreaElement | null>(null); const textareaEl = ref<HTMLTextAreaElement | null>(null);
const cwInputEl = $ref<HTMLInputElement | null>(null); const cwInputEl = ref<HTMLInputElement | null>(null);
const hashtagsInputEl = $ref<HTMLInputElement | null>(null); const hashtagsInputEl = ref<HTMLInputElement | null>(null);
const visibilityButton = $ref<HTMLElement | null>(null); const visibilityButton = ref<HTMLElement | null>(null);
const showBigPostButton = defaultStore.state.showBigPostButton; const showBigPostButton = defaultStore.state.showBigPostButton;
let posting = $ref(false); let posting = ref(false);
let text = $ref(props.initialText ?? ""); let text = ref(props.initialText ?? "");
let files = $ref(props.initialFiles ?? []); let files = ref(props.initialFiles ?? []);
let poll = $ref<{ let poll = ref<{
choices: string[]; choices: string[];
multiple: boolean; multiple: boolean;
expiresAt: string | null; expiresAt: string | null;
expiredAfter: string | null; expiredAfter: string | null;
} | null>(null); } | null>(null);
let useCw = $ref(false); let useCw = ref(false);
let showPreview = $ref(defaultStore.state.showPreviewByDefault); let showPreview = ref(defaultStore.state.showPreviewByDefault);
let cw = $ref<string | null>(null); let cw = ref<string | null>(null);
let localOnly = $ref<boolean>( let localOnly = ref<boolean>(
props.initialLocalOnly ?? defaultStore.state.rememberNoteVisibility props.initialLocalOnly ?? defaultStore.state.rememberNoteVisibility
? defaultStore.state.localOnly ? defaultStore.state.localOnly
: defaultStore.state.defaultNoteLocalOnly, : defaultStore.state.defaultNoteLocalOnly,
); );
let visibility = $ref( let visibility = ref(
props.initialVisibility ?? props.initialVisibility ??
((defaultStore.state.rememberNoteVisibility ((defaultStore.state.rememberNoteVisibility
? defaultStore.state.visibility ? defaultStore.state.visibility
: defaultStore.state : defaultStore.state
.defaultNoteVisibility) as (typeof misskey.noteVisibilities)[number]), .defaultNoteVisibility) as (typeof misskey.noteVisibilities)[number]),
); );
let visibleUsers = $ref([]); let visibleUsers = ref([]);
if (props.initialVisibleUsers) { if (props.initialVisibleUsers) {
props.initialVisibleUsers.forEach(pushVisibleUser); props.initialVisibleUsers.forEach(pushVisibleUser);
} }
let quoteId = $ref(null); let autocomplete = ref(null);
let hasNotSpecifiedMentions = $ref(false); let draghover = ref(false);
let recentHashtags = $ref(JSON.parse(localStorage.getItem("hashtags") || "[]")); let quoteId = ref(null);
let imeText = $ref(""); let hasNotSpecifiedMentions = ref(false);
let recentHashtags = ref(JSON.parse(localStorage.getItem("hashtags") || "[]"));
let imeText = ref("");
const typing = throttle(3000, () => { const typing = throttle(3000, () => {
if (props.channel) { if (props.channel) {
@ -357,7 +367,7 @@ const typing = throttle(3000, () => {
} }
}); });
const draftKey = $computed((): string => { const draftKey = computed((): string => {
if (props.editId) { if (props.editId) {
return `edit:${props.editId}`; return `edit:${props.editId}`;
} }
@ -375,7 +385,7 @@ const draftKey = $computed((): string => {
return key; return key;
}); });
const placeholder = $computed((): string => { const placeholder = computed((): string => {
if (props.renote) { if (props.renote) {
return i18n.ts._postForm.quotePlaceholder; return i18n.ts._postForm.quotePlaceholder;
} else if (props.reply) { } else if (props.reply) {
@ -395,7 +405,7 @@ const placeholder = $computed((): string => {
} }
}); });
const submitText = $computed((): string => { const submitText = computed((): string => {
return props.editId return props.editId
? i18n.ts.edit ? i18n.ts.edit
: props.renote : props.renote
@ -405,34 +415,37 @@ const submitText = $computed((): string => {
: i18n.ts.note; : i18n.ts.note;
}); });
const textLength = $computed((): number => { const textLength = computed((): number => {
return length((preprocess(text) + imeText).trim()); return length((preprocess(text.value) + imeText.value).trim());
}); });
const maxTextLength = $computed((): number => { const maxTextLength = computed((): number => {
return instance ? instance.maxNoteTextLength : 3000; return instance ? instance.maxNoteTextLength : 3000;
}); });
const canPost = $computed((): boolean => { const canPost = computed((): boolean => {
return ( return (
!posting && !posting.value &&
(1 <= textLength || 1 <= files.length || !!poll || !!props.renote) && (1 <= textLength.value ||
textLength <= maxTextLength && 1 <= files.value.length ||
(!poll || poll.choices.length >= 2) !!poll.value ||
!!props.renote) &&
textLength.value <= maxTextLength.value &&
(!poll.value || poll.value.choices.length >= 2)
); );
}); });
const withHashtags = $computed( const withHashtags = computed(
defaultStore.makeGetterSetter("postFormWithHashtags"), defaultStore.makeGetterSetter("postFormWithHashtags"),
); );
const hashtags = $computed(defaultStore.makeGetterSetter("postFormHashtags")); const hashtags = computed(defaultStore.makeGetterSetter("postFormHashtags"));
watch($$(text), () => { watch(text, () => {
checkMissingMention(); checkMissingMention();
}); });
watch( watch(
$$(visibleUsers), visibleUsers,
() => { () => {
checkMissingMention(); checkMissingMention();
}, },
@ -442,10 +455,10 @@ watch(
); );
if (props.mention) { if (props.mention) {
text = props.mention.host text.value = props.mention.host
? `@${props.mention.username}@${toASCII(props.mention.host)}` ? `@${props.mention.username}@${toASCII(props.mention.host)}`
: `@${props.mention.username}`; : `@${props.mention.username}`;
text += " "; text.value += " ";
} }
if ( if (
@ -453,7 +466,7 @@ if (
(props.reply.user.username !== $i.username || (props.reply.user.username !== $i.username ||
(props.reply.user.host != null && props.reply.user.host !== host)) (props.reply.user.host != null && props.reply.user.host !== host))
) { ) {
text = `@${props.reply.user.username}${ text.value = `@${props.reply.user.username}${
props.reply.user.host != null props.reply.user.host != null
? "@" + toASCII(props.reply.user.host) ? "@" + toASCII(props.reply.user.host)
: "" : ""
@ -476,15 +489,15 @@ if (props.reply && props.reply.text != null) {
continue; continue;
// //
if (text.includes(`${mention} `)) continue; if (text.value.includes(`${mention} `)) continue;
text += `${mention} `; text.value += `${mention} `;
} }
} }
if (props.channel) { if (props.channel) {
visibility = "public"; visibility.value = "public";
localOnly = true; // TODO: localOnly.value = true; // TODO:
} }
// //
@ -492,17 +505,17 @@ if (
props.reply && props.reply &&
["home", "followers", "specified"].includes(props.reply.visibility) ["home", "followers", "specified"].includes(props.reply.visibility)
) { ) {
if (props.reply.visibility === "home" && visibility === "followers") { if (props.reply.visibility === "home" && visibility.value === "followers") {
visibility = "followers"; visibility.value = "followers";
} else if ( } else if (
["home", "followers"].includes(props.reply.visibility) && ["home", "followers"].includes(props.reply.visibility) &&
visibility === "specified" visibility.value === "specified"
) { ) {
visibility = "specified"; visibility.value = "specified";
} else { } else {
visibility = props.reply.visibility; visibility.value = props.reply.visibility;
} }
if (visibility === "specified") { if (visibility.value === "specified") {
if (props.reply.visibleUserIds) { if (props.reply.visibleUserIds) {
os.api("users/show", { os.api("users/show", {
userIds: props.reply.visibleUserIds.filter( userIds: props.reply.visibleUserIds.filter(
@ -524,7 +537,7 @@ if (
} }
if (props.specified) { if (props.specified) {
visibility = "specified"; visibility.value = "specified";
pushVisibleUser(props.specified); pushVisibleUser(props.specified);
} }
@ -540,53 +553,53 @@ const addRe = (s: string) => {
// keep cw when reply // keep cw when reply
if (defaultStore.state.keepCw && props.reply && props.reply.cw) { if (defaultStore.state.keepCw && props.reply && props.reply.cw) {
useCw = true; useCw.value = true;
cw = cw.value =
props.reply.user.username === $i.username props.reply.user.username === $i.username
? props.reply.cw ? props.reply.cw
: addRe(props.reply.cw); : addRe(props.reply.cw);
} }
function watchForDraft() { function watchForDraft() {
watch($$(text), () => saveDraft()); watch(text, () => saveDraft());
watch($$(useCw), () => saveDraft()); watch(useCw, () => saveDraft());
watch($$(cw), () => saveDraft()); watch(cw, () => saveDraft());
watch($$(poll), () => saveDraft()); watch(poll, () => saveDraft());
watch($$(files), () => saveDraft(), { deep: true }); watch(files, () => saveDraft(), { deep: true });
watch($$(visibility), () => saveDraft()); watch(visibility, () => saveDraft());
watch($$(localOnly), () => saveDraft()); watch(localOnly, () => saveDraft());
} }
function checkMissingMention() { function checkMissingMention() {
if (visibility === "specified") { if (visibility.value === "specified") {
const ast = mfm.parse(text); const ast = mfm.parse(text.value);
for (const x of extractMentions(ast)) { for (const x of extractMentions(ast)) {
if ( if (
!visibleUsers.some( !visibleUsers.value.some(
(u) => u.username === x.username && u.host === x.host, (u) => u.username === x.username && u.host === x.host,
) )
) { ) {
hasNotSpecifiedMentions = true; hasNotSpecifiedMentions.value = true;
return; return;
} }
} }
hasNotSpecifiedMentions = false; hasNotSpecifiedMentions.value = false;
} }
} }
function addMissingMention() { function addMissingMention() {
const ast = mfm.parse(text); const ast = mfm.parse(text.value);
for (const x of extractMentions(ast)) { for (const x of extractMentions(ast)) {
if ( if (
!visibleUsers.some( !visibleUsers.value.some(
(u) => u.username === x.username && u.host === x.host, (u) => u.username === x.username && u.host === x.host,
) )
) { ) {
os.api("users/show", { username: x.username, host: x.host }).then( os.api("users/show", { username: x.username, host: x.host }).then(
(user) => { (user) => {
visibleUsers.push(user); visibleUsers.value.push(user);
}, },
); );
} }
@ -594,10 +607,10 @@ function addMissingMention() {
} }
function togglePoll() { function togglePoll() {
if (poll) { if (poll.value) {
poll = null; poll.value = null;
} else { } else {
poll = { poll.value = {
choices: ["", ""], choices: ["", ""],
multiple: false, multiple: false,
expiresAt: null, expiresAt: null,
@ -606,16 +619,16 @@ function togglePoll() {
} }
} }
// function addTag(tag: string) { function addTag(tag: string) {
// insertTextAtCursor(textareaEl, ` #${tag} `); insertTextAtCursor(textareaEl.value, ` #${tag} `);
// } }
function focus() { function focus() {
if (textareaEl) { if (textareaEl.value) {
textareaEl.focus(); textareaEl.value.focus();
textareaEl.setSelectionRange( textareaEl.value.setSelectionRange(
textareaEl.value.length, textareaEl.value.value.length,
textareaEl.value.length, textareaEl.value.value.length,
); );
} }
} }
@ -624,31 +637,32 @@ function chooseFileFrom(ev) {
selectFiles(ev.currentTarget ?? ev.target, i18n.ts.attachFile).then( selectFiles(ev.currentTarget ?? ev.target, i18n.ts.attachFile).then(
(files_) => { (files_) => {
for (const file of files_) { for (const file of files_) {
files.push(file); files.value.push(file);
} }
}, },
); );
} }
function detachFile(id) { function detachFile(id) {
files = files.filter((x) => x.id !== id); files.value = files.value.filter((x) => x.id !== id);
} }
function updateFiles(_files) { function updateFiles(_files) {
files = _files; files.value = _files;
} }
function updateFileSensitive(file, sensitive) { function updateFileSensitive(file, sensitive) {
files[files.findIndex((x) => x.id === file.id)].isSensitive = sensitive; files.value[files.value.findIndex((x) => x.id === file.id)].isSensitive =
sensitive;
} }
function updateFileName(file, name) { function updateFileName(file, name) {
files[files.findIndex((x) => x.id === file.id)].name = name; files.value[files.value.findIndex((x) => x.id === file.id)].name = name;
} }
function upload(file: File, name?: string) { function upload(file: File, name?: string) {
uploadFile(file, defaultStore.state.uploadFolder, name).then((res) => { uploadFile(file, defaultStore.state.uploadFolder, name).then((res) => {
files.push(res); files.value.push(res);
}); });
} }
@ -663,21 +677,21 @@ function setVisibility() {
() => import("@/components/MkVisibilityPicker.vue"), () => import("@/components/MkVisibilityPicker.vue"),
), ),
{ {
currentVisibility: visibility, currentVisibility: visibility.value,
currentLocalOnly: localOnly, currentLocalOnly: localOnly.value,
src: visibilityButton, src: visibilityButton.value,
}, },
{ {
changeVisibility: (v) => { changeVisibility: (v) => {
visibility = v; visibility.value = v;
if (defaultStore.state.rememberNoteVisibility) { if (defaultStore.state.rememberNoteVisibility) {
defaultStore.set("visibility", visibility); defaultStore.set("visibility", visibility.value);
} }
}, },
changeLocalOnly: (v) => { changeLocalOnly: (v) => {
localOnly = v; localOnly.value = v;
if (defaultStore.state.rememberNoteVisibility) { if (defaultStore.state.rememberNoteVisibility) {
defaultStore.set("localOnly", localOnly); defaultStore.set("localOnly", localOnly.value);
} }
}, },
}, },
@ -687,11 +701,11 @@ function setVisibility() {
function pushVisibleUser(user) { function pushVisibleUser(user) {
if ( if (
!visibleUsers.some( !visibleUsers.value.some(
(u) => u.username === user.username && u.host === user.host, (u) => u.username === user.username && u.host === user.host,
) )
) { ) {
visibleUsers.push(user); visibleUsers.value.push(user);
} }
} }
@ -702,21 +716,21 @@ function addVisibleUser() {
} }
function removeVisibleUser(user) { function removeVisibleUser(user) {
visibleUsers = erase(user, visibleUsers); visibleUsers.value = erase(user, visibleUsers.value);
} }
function clear() { function clear() {
text = ""; text.value = "";
files = []; files.value = [];
poll = null; poll.value = null;
quoteId = null; quoteId.value = null;
} }
function onKeydown(ev: KeyboardEvent) { function onKeydown(ev: KeyboardEvent) {
if ( if (
(ev.which === 10 || ev.which === 13) && (ev.which === 10 || ev.which === 13) &&
(ev.ctrlKey || ev.metaKey) && (ev.ctrlKey || ev.metaKey) &&
canPost canPost.value
) )
post(); post();
if (ev.which === 27) emit("esc"); if (ev.which === 27) emit("esc");
@ -724,12 +738,12 @@ function onKeydown(ev: KeyboardEvent) {
} }
function onCompositionUpdate(ev: CompositionEvent) { function onCompositionUpdate(ev: CompositionEvent) {
imeText = ev.data; imeText.value = ev.data;
typing(); typing();
} }
function onCompositionEnd(ev: CompositionEvent) { function onCompositionEnd(ev: CompositionEvent) {
imeText = ""; imeText.value = "";
} }
async function onPaste(ev: ClipboardEvent) { async function onPaste(ev: ClipboardEvent) {
@ -750,7 +764,7 @@ async function onPaste(ev: ClipboardEvent) {
const paste = ev.clipboardData.getData("text"); const paste = ev.clipboardData.getData("text");
if (!props.renote && !quoteId && paste.startsWith(url + "/notes/")) { if (!props.renote && !quoteId.value && paste.startsWith(url + "/notes/")) {
ev.preventDefault(); ev.preventDefault();
os.yesno({ os.yesno({
@ -758,11 +772,13 @@ async function onPaste(ev: ClipboardEvent) {
text: i18n.ts.quoteQuestion, text: i18n.ts.quoteQuestion,
}).then(({ canceled }) => { }).then(({ canceled }) => {
if (canceled) { if (canceled) {
insertTextAtCursor(textareaEl, paste); insertTextAtCursor(textareaEl.value, paste);
return; return;
} }
quoteId = paste.substr(url.length).match(/^\/notes\/(.+?)\/?$/)[1]; quoteId.value = paste
.substr(url.length)
.match(/^\/notes\/(.+?)\/?$/)[1];
}); });
} }
} }
@ -773,7 +789,7 @@ function onDragover(ev) {
const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_; const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_;
if (isFile || isDriveFile) { if (isFile || isDriveFile) {
ev.preventDefault(); ev.preventDefault();
draghover = true; draghover.value = true;
switch (ev.dataTransfer.effectAllowed) { switch (ev.dataTransfer.effectAllowed) {
case "all": case "all":
case "uninitialized": case "uninitialized":
@ -794,15 +810,15 @@ function onDragover(ev) {
} }
function onDragenter(ev) { function onDragenter(ev) {
draghover = true; draghover.value = true;
} }
function onDragleave(ev) { function onDragleave(ev) {
draghover = false; draghover.value = false;
} }
function onDrop(ev): void { function onDrop(ev): void {
draghover = false; draghover.value = false;
// //
if (ev.dataTransfer.files.length > 0) { if (ev.dataTransfer.files.length > 0) {
@ -815,7 +831,7 @@ function onDrop(ev): void {
const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
if (driveFile != null && driveFile !== "") { if (driveFile != null && driveFile !== "") {
const file = JSON.parse(driveFile); const file = JSON.parse(driveFile);
files.push(file); files.value.push(file);
ev.preventDefault(); ev.preventDefault();
} }
//#endregion //#endregion
@ -824,16 +840,16 @@ function onDrop(ev): void {
function saveDraft() { function saveDraft() {
const draftData = JSON.parse(localStorage.getItem("drafts") || "{}"); const draftData = JSON.parse(localStorage.getItem("drafts") || "{}");
draftData[draftKey] = { draftData[draftKey.value] = {
updatedAt: new Date(), updatedAt: new Date(),
data: { data: {
text: text, text: text.value,
useCw: useCw, useCw: useCw.value,
cw: cw, cw: cw.value,
visibility: visibility, visibility: visibility.value,
localOnly: localOnly, localOnly: localOnly.value,
files: files, files: files.value,
poll: poll, poll: poll.value,
}, },
}; };
@ -843,37 +859,38 @@ function saveDraft() {
function deleteDraft() { function deleteDraft() {
const draftData = JSON.parse(localStorage.getItem("drafts") || "{}"); const draftData = JSON.parse(localStorage.getItem("drafts") || "{}");
delete draftData[draftKey]; delete draftData[draftKey.value];
localStorage.setItem("drafts", JSON.stringify(draftData)); localStorage.setItem("drafts", JSON.stringify(draftData));
} }
async function post() { async function post() {
const processedText = preprocess(text); const processedText = preprocess(text.value);
let postData = { let postData = {
editId: props.editId ? props.editId : undefined, editId: props.editId ? props.editId : undefined,
text: processedText === "" ? undefined : processedText, text: processedText === "" ? undefined : processedText,
fileIds: files.length > 0 ? files.map((f) => f.id) : undefined, fileIds:
files.value.length > 0 ? files.value.map((f) => f.id) : undefined,
replyId: props.reply ? props.reply.id : undefined, replyId: props.reply ? props.reply.id : undefined,
renoteId: props.renote renoteId: props.renote
? props.renote.id ? props.renote.id
: quoteId : quoteId.value
? quoteId ? quoteId.value
: undefined, : undefined,
channelId: props.channel ? props.channel.id : undefined, channelId: props.channel ? props.channel.id : undefined,
poll: poll, poll: poll.value,
cw: useCw ? cw || "" : undefined, cw: useCw.value ? cw.value || "" : undefined,
localOnly: localOnly, localOnly: localOnly.value,
visibility: visibility, visibility: visibility.value,
visibleUserIds: visibleUserIds:
visibility === "specified" visibility.value === "specified"
? visibleUsers.map((u) => u.id) ? visibleUsers.value.map((u) => u.id)
: undefined, : undefined,
}; };
if (withHashtags && hashtags && hashtags.trim() !== "") { if (withHashtags.value && hashtags.value && hashtags.value.trim() !== "") {
const hashtags_ = hashtags const hashtags_ = hashtags.value
.trim() .trim()
.split(" ") .split(" ")
.map((x) => (x.startsWith("#") ? x : "#" + x)) .map((x) => (x.startsWith("#") ? x : "#" + x))
@ -892,12 +909,13 @@ async function post() {
let token = undefined; let token = undefined;
if (postAccount) { if (postAccount.value) {
const storedAccounts = await getAccounts(); const storedAccounts = await getAccounts();
token = storedAccounts.find((x) => x.id === postAccount.id)?.token; token = storedAccounts.find((x) => x.id === postAccount.value.id)
?.token;
} }
posting = true; posting.value = true;
os.api(postData.editId ? "notes/edit" : "notes/create", postData, token) os.api(postData.editId ? "notes/edit" : "notes/create", postData, token)
.then(() => { .then(() => {
clear(); clear();
@ -917,12 +935,12 @@ async function post() {
JSON.stringify(unique(hashtags_.concat(history))), JSON.stringify(unique(hashtags_.concat(history))),
); );
} }
posting = false; posting.value = false;
postAccount = null; postAccount.value = null;
}); });
}) })
.catch((err) => { .catch((err) => {
posting = false; posting.value = false;
os.alert({ os.alert({
type: "error", type: "error",
text: err.message + "\n" + (err as any).id, text: err.message + "\n" + (err as any).id,
@ -936,12 +954,12 @@ function cancel() {
function insertMention() { function insertMention() {
os.selectUser().then((user) => { os.selectUser().then((user) => {
insertTextAtCursor(textareaEl, "@" + Acct.toString(user) + " "); insertTextAtCursor(textareaEl.value, "@" + Acct.toString(user) + " ");
}); });
} }
async function insertEmoji(ev: MouseEvent) { async function insertEmoji(ev: MouseEvent) {
os.openEmojiPicker(ev.currentTarget ?? ev.target, {}, textareaEl); os.openEmojiPicker(ev.currentTarget ?? ev.target, {}, textareaEl.value);
} }
function showActions(ev) { function showActions(ev) {
@ -951,11 +969,11 @@ function showActions(ev) {
action: () => { action: () => {
action.handler( action.handler(
{ {
text: text, text: text.value,
}, },
(key, value) => { (key, value) => {
if (key === "text") { if (key === "text") {
text = value; text.value = value;
} }
}, },
); );
@ -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) { function openAccountMenu(ev: MouseEvent) {
openAccountMenu_( openAccountMenu_(
{ {
withExtraOperation: false, withExtraOperation: false,
includeCurrentAccount: true, includeCurrentAccount: true,
active: postAccount != null ? postAccount.id : $i.id, active: postAccount.value != null ? postAccount.value.id : $i.id,
onChoose: (account) => { onChoose: (account) => {
if (account.id === $i.id) { if (account.id === $i.id) {
postAccount = null; postAccount.value = null;
} else { } else {
postAccount = account; postAccount.value = account;
} }
}, },
}, },
@ -995,30 +1013,30 @@ onMounted(() => {
} }
// TODO: detach when unmount // TODO: detach when unmount
new Autocomplete(textareaEl, $$(text)); new Autocomplete(textareaEl.value, text);
new Autocomplete(cwInputEl, $$(cw)); new Autocomplete(cwInputEl.value, cw);
new Autocomplete(hashtagsInputEl, $$(hashtags)); new Autocomplete(hashtagsInputEl.value, hashtags);
autosize(textareaEl); autosize(textareaEl.value);
nextTick(() => { nextTick(() => {
autosize(textareaEl); autosize(textareaEl.value);
// 稿 // 稿
if (!props.instant && !props.mention && !props.specified) { if (!props.instant && !props.mention && !props.specified) {
const draft = JSON.parse(localStorage.getItem("drafts") || "{}")[ const draft = JSON.parse(localStorage.getItem("drafts") || "{}")[
draftKey draftKey.value
]; ];
if (draft) { if (draft) {
text = draft.data.text; text.value = draft.data.text;
useCw = draft.data.useCw; useCw.value = draft.data.useCw;
cw = draft.data.cw; cw.value = draft.data.cw;
visibility = draft.data.visibility; visibility.value = draft.data.visibility;
localOnly = draft.data.localOnly; localOnly.value = draft.data.localOnly;
files = (draft.data.files || []).filter( files.value = (draft.data.files || []).filter(
(draftFile) => draftFile, (draftFile) => draftFile,
); );
if (draft.data.poll) { if (draft.data.poll) {
poll = draft.data.poll; poll.value = draft.data.poll;
} }
} }
} }
@ -1026,21 +1044,21 @@ onMounted(() => {
// //
if (props.initialNote) { if (props.initialNote) {
const init = props.initialNote; const init = props.initialNote;
text = init.text ? init.text : ""; text.value = init.text ? init.text : "";
files = init.files; files.value = init.files;
cw = init.cw; cw.value = init.cw;
useCw = init.cw != null; useCw.value = init.cw != null;
if (init.poll) { if (init.poll) {
poll = { poll.value = {
choices: init.poll.choices.map((x) => x.text), choices: init.poll.choices.map((x) => x.text),
multiple: init.poll.multiple, multiple: init.poll.multiple,
expiresAt: init.poll.expiresAt, expiresAt: init.poll.expiresAt,
expiredAfter: init.poll.expiredAfter, expiredAfter: init.poll.expiredAfter,
}; };
} }
visibility = init.visibility; visibility.value = init.visibility;
localOnly = init.localOnly; localOnly.value = init.localOnly;
quoteId = init.renote ? init.renote.id : null; quoteId.value = init.renote ? init.renote.id : null;
} }
nextTick(() => watchForDraft()); nextTick(() => watchForDraft());

View file

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

View file

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

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