Firefish v1.0.5-dev11
|
@ -143,11 +143,11 @@ reservedUsernames: [
|
||||||
# Whether disable HSTS
|
# Whether disable HSTS
|
||||||
#disableHsts: true
|
#disableHsts: true
|
||||||
|
|
||||||
# Number of worker processes
|
# Number of worker processes by type.
|
||||||
#clusterLimit: 1
|
# The sum must not exceed the number of available cores.
|
||||||
|
#clusterLimits:
|
||||||
# Worker only mode
|
# web: 1
|
||||||
#onlyQueueProcessor: 1
|
# queue: 1
|
||||||
|
|
||||||
# Job concurrency per worker
|
# Job concurrency per worker
|
||||||
# deliverJobConcurrency: 128
|
# deliverJobConcurrency: 128
|
||||||
|
|
3
.gitignore
vendored
|
@ -32,6 +32,9 @@ coverage
|
||||||
# docker dev config
|
# docker dev config
|
||||||
/dev/docker-compose.yml
|
/dev/docker-compose.yml
|
||||||
|
|
||||||
|
# ESLint
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
# misskey
|
# misskey
|
||||||
built
|
built
|
||||||
db
|
db
|
||||||
|
|
12
biome.json
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://biomejs.dev/schemas/1.0.0/schema.json",
|
||||||
|
"organizeImports": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"linter": {
|
||||||
|
"enabled": true,
|
||||||
|
"rules": {
|
||||||
|
"recommended": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -61,6 +61,8 @@ services:
|
||||||
# sonic:
|
# sonic:
|
||||||
# restart: unless-stopped
|
# restart: unless-stopped
|
||||||
# image: docker.io/valeriansaliou/sonic:v1.4.0
|
# image: docker.io/valeriansaliou/sonic:v1.4.0
|
||||||
|
# logging:
|
||||||
|
# driver: none
|
||||||
# networks:
|
# networks:
|
||||||
# - calcnet
|
# - calcnet
|
||||||
# volumes:
|
# volumes:
|
||||||
|
|
|
@ -762,8 +762,7 @@ no: "No"
|
||||||
driveFilesCount: "Number of Drive files"
|
driveFilesCount: "Number of Drive files"
|
||||||
driveUsage: "Drive space usage"
|
driveUsage: "Drive space usage"
|
||||||
noCrawle: "Reject crawler indexing"
|
noCrawle: "Reject crawler indexing"
|
||||||
noCrawleDescription: "Ask search engines to not index your profile page, posts, Pages,
|
noCrawleDescription: "Ask external search engines to not index your content."
|
||||||
etc."
|
|
||||||
lockedAccountInfo: "Unless you set your post visiblity to \"Followers only\", your
|
lockedAccountInfo: "Unless you set your post visiblity to \"Followers only\", your
|
||||||
posts will be visible to anyone, even if you require followers to be manually approved."
|
posts will be visible to anyone, even if you require followers to be manually approved."
|
||||||
alwaysMarkSensitive: "Mark as NSFW by default"
|
alwaysMarkSensitive: "Mark as NSFW by default"
|
||||||
|
@ -1141,8 +1140,6 @@ deletePasskeys: "Delete passkeys"
|
||||||
delete2faConfirm: "This will irreversibly delete 2FA on this account. Proceed?"
|
delete2faConfirm: "This will irreversibly delete 2FA on this account. Proceed?"
|
||||||
deletePasskeysConfirm: "This will irreversibly delete all passkeys and security keys on this account. Proceed?"
|
deletePasskeysConfirm: "This will irreversibly delete all passkeys and security keys on this account. Proceed?"
|
||||||
inputNotMatch: "Input does not match"
|
inputNotMatch: "Input does not match"
|
||||||
detectPostLanguage: "Automatically detect the language and show a translate button for posts in foreign languages"
|
|
||||||
languageForTranslation: "Language used for post translation"
|
|
||||||
addRe: "Add \"re:\" at the beginning of comment in reply to a post with a content warning"
|
addRe: "Add \"re:\" at the beginning of comment in reply to a post with a content warning"
|
||||||
showBigPostButton: "Show a bigger post button in the posting form"
|
showBigPostButton: "Show a bigger post button in the posting form"
|
||||||
confirm: "Confirm"
|
confirm: "Confirm"
|
||||||
|
@ -1150,6 +1147,10 @@ emphasizeFollowed: "Highlight the \"Follows you\" sign on your follower info"
|
||||||
importZip: "Import ZIP"
|
importZip: "Import ZIP"
|
||||||
exportZip: "Export ZIP"
|
exportZip: "Export ZIP"
|
||||||
emojiPackCreator: "Emoji pack creator"
|
emojiPackCreator: "Emoji pack creator"
|
||||||
|
indexable: "Indexable"
|
||||||
|
indexableDescription: "Allow built-in search to show your public posts"
|
||||||
|
languageForTranslation: "Post translation language"
|
||||||
|
detectPostLanguage: "Automatically detect the language and show a translate button for posts in foreign languages"
|
||||||
|
|
||||||
_sensitiveMediaDetection:
|
_sensitiveMediaDetection:
|
||||||
description: "Reduces the effort of server moderation through automatically recognizing
|
description: "Reduces the effort of server moderation through automatically recognizing
|
||||||
|
|
|
@ -874,7 +874,7 @@ pubSub: "Cuentas Pub/Sub"
|
||||||
lastCommunication: "Última comunicación"
|
lastCommunication: "Última comunicación"
|
||||||
resolved: "Resuelto"
|
resolved: "Resuelto"
|
||||||
unresolved: "Sin resolver"
|
unresolved: "Sin resolver"
|
||||||
breakFollow: "Dejar de seguir"
|
breakFollow: "Quitar seguidor"
|
||||||
itsOn: "¡Está encendido!"
|
itsOn: "¡Está encendido!"
|
||||||
itsOff: "¡Está apagado!"
|
itsOff: "¡Está apagado!"
|
||||||
emailRequiredForSignup: "Se requere una dirección de correo electrónico para el registro
|
emailRequiredForSignup: "Se requere una dirección de correo electrónico para el registro
|
||||||
|
|
|
@ -309,11 +309,11 @@ emptyDrive: "Le Drive est vide"
|
||||||
emptyFolder: "Le dossier est vide"
|
emptyFolder: "Le dossier est vide"
|
||||||
unableToDelete: "Suppression impossible"
|
unableToDelete: "Suppression impossible"
|
||||||
inputNewFileName: "Entrez un nouveau nom de fichier"
|
inputNewFileName: "Entrez un nouveau nom de fichier"
|
||||||
inputNewDescription: "Veuillez entrer une nouvelle description"
|
inputNewDescription: "Veuillez entrer une nouvelle description au fichier"
|
||||||
inputNewFolderName: "Entrez un nouveau nom de dossier"
|
inputNewFolderName: "Entrez un nouveau nom de dossier"
|
||||||
circularReferenceFolder: "Le dossier de destination est un sous-dossier du dossier
|
circularReferenceFolder: "Le dossier de destination est un sous-dossier du dossier
|
||||||
que vous souhaitez déplacer."
|
que vous souhaitez déplacer."
|
||||||
hasChildFilesOrFolders: "Impossible de supprimer ce dossier car il n'est pas vide."
|
hasChildFilesOrFolders: "Impossible de supprimer ce dossier, car il n'est pas vide."
|
||||||
copyUrl: "Copier l’URL"
|
copyUrl: "Copier l’URL"
|
||||||
rename: "Renommer"
|
rename: "Renommer"
|
||||||
avatar: "Avatar"
|
avatar: "Avatar"
|
||||||
|
@ -605,7 +605,7 @@ disablePlayer: "Fermer le lecteur vidéo"
|
||||||
expandTweet: "Étendre le tweet"
|
expandTweet: "Étendre le tweet"
|
||||||
themeEditor: "Éditeur de thèmes"
|
themeEditor: "Éditeur de thèmes"
|
||||||
description: "Description"
|
description: "Description"
|
||||||
describeFile: "Ajouter une description d'image"
|
describeFile: "Ajouter une description"
|
||||||
enterFileDescription: "Saisissez une description"
|
enterFileDescription: "Saisissez une description"
|
||||||
author: "Auteur·rice"
|
author: "Auteur·rice"
|
||||||
leaveConfirm: "Vous avez des modifications non-sauvegardées. Voulez-vous les ignorer
|
leaveConfirm: "Vous avez des modifications non-sauvegardées. Voulez-vous les ignorer
|
||||||
|
@ -2085,7 +2085,7 @@ silenceThisInstance: Masquer ce serveur
|
||||||
silencedInstances: Serveurs masqués
|
silencedInstances: Serveurs masqués
|
||||||
silenced: Masqué
|
silenced: Masqué
|
||||||
deleted: Effacé
|
deleted: Effacé
|
||||||
editNote: Modifier publication
|
editNote: Modifier la publication
|
||||||
edited: 'Modifié à {date} {time}'
|
edited: 'Modifié à {date} {time}'
|
||||||
flagShowTimelineRepliesDescription: Si activé, affiche dans le fil les réponses des
|
flagShowTimelineRepliesDescription: Si activé, affiche dans le fil les réponses des
|
||||||
utilisatieur·rice·s aux publications des autres.
|
utilisatieur·rice·s aux publications des autres.
|
||||||
|
@ -2209,4 +2209,4 @@ addRe: Ajouter "re:" au début d’un avertissement de contenu (CW) en réponse
|
||||||
confirm: Confirmer
|
confirm: Confirmer
|
||||||
importZip: Importer ZIP
|
importZip: Importer ZIP
|
||||||
exportZip: Exporter ZIP
|
exportZip: Exporter ZIP
|
||||||
emojiPackCreator: Créateur de pack d’emoji
|
emojiPackCreator: Créateur de pack d’émoji
|
||||||
|
|
|
@ -15,7 +15,7 @@ gotIt: "Ho capito!"
|
||||||
cancel: "Annulla"
|
cancel: "Annulla"
|
||||||
enterUsername: "Inserisci un nome utente"
|
enterUsername: "Inserisci un nome utente"
|
||||||
renotedBy: "Boost da {user}"
|
renotedBy: "Boost da {user}"
|
||||||
noNotes: "Nessuna nota!"
|
noNotes: "Nessun post"
|
||||||
noNotifications: "Nessuna notifica"
|
noNotifications: "Nessuna notifica"
|
||||||
instance: "Server"
|
instance: "Server"
|
||||||
settings: "Impostazioni"
|
settings: "Impostazioni"
|
||||||
|
@ -35,10 +35,10 @@ users: "Utenti"
|
||||||
addUser: "Aggiungi utente"
|
addUser: "Aggiungi utente"
|
||||||
favorite: "Aggiungi ai preferiti"
|
favorite: "Aggiungi ai preferiti"
|
||||||
favorites: "Preferiti"
|
favorites: "Preferiti"
|
||||||
unfavorite: "Rimuovi nota dai preferiti"
|
unfavorite: "Rimuovi post dai preferiti"
|
||||||
favorited: "Aggiunta ai tuoi preferiti."
|
favorited: "Aggiunto ai tuoi preferiti."
|
||||||
alreadyFavorited: "Già tra i tuoi preferiti."
|
alreadyFavorited: "Già tra i tuoi preferiti."
|
||||||
cantFavorite: "Impossibile aggiungere la nota ai preferiti."
|
cantFavorite: "Impossibile aggiungere il post ai preferiti."
|
||||||
pin: "Fissa sul profilo"
|
pin: "Fissa sul profilo"
|
||||||
unpin: "Non fissare sul profilo"
|
unpin: "Non fissare sul profilo"
|
||||||
copyContent: "Copia il contenuto"
|
copyContent: "Copia il contenuto"
|
||||||
|
@ -71,7 +71,7 @@ driveFileDeleteConfirm: "Vuoi davvero eliminare il file \"{name}\"? Sarà rimoss
|
||||||
unfollowConfirm: "Vuoi davvero smettere di seguire {name}?"
|
unfollowConfirm: "Vuoi davvero smettere di seguire {name}?"
|
||||||
exportRequested: "Hai richiesto un'esportazione, e potrebbe volerci tempo. Quando
|
exportRequested: "Hai richiesto un'esportazione, e potrebbe volerci tempo. Quando
|
||||||
sarà compiuta, il file verrà aggiunto direttamente al Drive."
|
sarà compiuta, il file verrà aggiunto direttamente al Drive."
|
||||||
importRequested: "Hai richiesto un'importazione. Può volerci tempo. "
|
importRequested: "Hai richiesto un'importazione. Potrebbe impiegare del tempo."
|
||||||
lists: "Liste"
|
lists: "Liste"
|
||||||
noLists: "Nessuna lista"
|
noLists: "Nessuna lista"
|
||||||
note: "Post"
|
note: "Post"
|
||||||
|
@ -84,10 +84,10 @@ manageLists: "Gestisci liste"
|
||||||
error: "Errore"
|
error: "Errore"
|
||||||
somethingHappened: "Si è verificato un problema"
|
somethingHappened: "Si è verificato un problema"
|
||||||
retry: "Riprova"
|
retry: "Riprova"
|
||||||
pageLoadError: "Caricamento pagina non riuscito. "
|
pageLoadError: "Errore nel caricamento della pagina."
|
||||||
pageLoadErrorDescription: "Questo viene normalmente causato dalla rete o dalla cache
|
pageLoadErrorDescription: "Di solito succede per errori di rete o a causa della cache
|
||||||
del browser. Si prega di pulire la cache, o di attendere e riprovare più tardi."
|
del browser. Prova a pulire la cache o a riprovare più tardi."
|
||||||
serverIsDead: "Il server non risponde. Si prega di attendere e riprovare più tardi."
|
serverIsDead: "Il server non risponde. Attendi e riprova più tardi."
|
||||||
youShouldUpgradeClient: "Per visualizzare la pagina è necessario aggiornare il client
|
youShouldUpgradeClient: "Per visualizzare la pagina è necessario aggiornare il client
|
||||||
alla nuova versione e ricaricare."
|
alla nuova versione e ricaricare."
|
||||||
enterListName: "Nome della lista"
|
enterListName: "Nome della lista"
|
||||||
|
@ -122,7 +122,7 @@ markAsSensitive: "Segna come sensibile"
|
||||||
unmarkAsSensitive: "Segna come non sensibile"
|
unmarkAsSensitive: "Segna come non sensibile"
|
||||||
enterFileName: "Nome del file"
|
enterFileName: "Nome del file"
|
||||||
mute: "Silenzia"
|
mute: "Silenzia"
|
||||||
unmute: "Riattiva"
|
unmute: "Non silenziare"
|
||||||
block: "Blocca"
|
block: "Blocca"
|
||||||
unblock: "Sblocca"
|
unblock: "Sblocca"
|
||||||
suspend: "Sospendi"
|
suspend: "Sospendi"
|
||||||
|
@ -135,25 +135,25 @@ selectList: "Seleziona una lista"
|
||||||
selectAntenna: "Scegli un'antenna"
|
selectAntenna: "Scegli un'antenna"
|
||||||
selectWidget: "Seleziona widget"
|
selectWidget: "Seleziona widget"
|
||||||
editWidgets: "Modifica i widget"
|
editWidgets: "Modifica i widget"
|
||||||
editWidgetsExit: "Modifica fine"
|
editWidgetsExit: "Fine modifica"
|
||||||
customEmojis: "Emoji personalizzati"
|
customEmojis: "Emoji personalizzati"
|
||||||
emoji: "Emoji"
|
emoji: "Emoji"
|
||||||
emojis: "Emoji"
|
emojis: "Emoji"
|
||||||
emojiName: "Nome dell'emoji"
|
emojiName: "Nome dell'emoji"
|
||||||
emojiUrl: "URL dell'emoji"
|
emojiUrl: "URL dell'emoji"
|
||||||
addEmoji: "Aggiungi un emoji"
|
addEmoji: "Aggiungi un emoji"
|
||||||
settingGuide: "Configurazione suggerita"
|
settingGuide: "Impostazioni suggerite"
|
||||||
cacheRemoteFiles: "Memorizzazione nella cache dei file remoti"
|
cacheRemoteFiles: "Memorizzazione nella cache dei file remoti"
|
||||||
cacheRemoteFilesDescription: "Disabilitando questa opzione, i file remoti verranno
|
cacheRemoteFilesDescription: "Disabilitando questa opzione, i file remoti verranno
|
||||||
scaricati direttamente dal loro server. L'opzione permette di risparmiare spazio
|
scaricati direttamente dal loro server. L'opzione permette di risparmiare spazio
|
||||||
ma aumenta il traffico di rete e non verranno generate anteprime."
|
ma aumenta il traffico di rete e non verranno generate anteprime."
|
||||||
flagAsBot: "Io sono un robot"
|
flagAsBot: "Questo account è un bot"
|
||||||
flagAsBotDescription: "Se l'account esegue principalmente operazioni automatiche,
|
flagAsBotDescription: "Se l'account esegue principalmente operazioni automatiche,
|
||||||
attiva quest'opzione. Quando attivata, opera come un segnalatore per gli altri sviluppatori
|
attiva quest'opzione. Quando attivata, opera come un segnalatore per gli altri sviluppatori
|
||||||
allo scopo di prevenire catene d’interazione senza fine con altri bot, e di adeguare
|
allo scopo di prevenire catene d’interazione senza fine con altri bot, e di adeguare
|
||||||
i sistemi interni di Firefish perché trattino questo account come un bot."
|
i sistemi interni di Firefish perché trattino questo account come un bot."
|
||||||
flagAsCat: "Io sono un gatto"
|
flagAsCat: "Sei un gatto? 😺"
|
||||||
flagAsCatDescription: "Abilita l'opzione \"Io sono un gatto\" per l'account."
|
flagAsCatDescription: "Ti compariranno le orecchie e parlerai come un gatto!"
|
||||||
autoAcceptFollowed: "Accetta in automatico i follow dagli account che segui"
|
autoAcceptFollowed: "Accetta in automatico i follow dagli account che segui"
|
||||||
addAccount: "Aggiungi account"
|
addAccount: "Aggiungi account"
|
||||||
loginFailed: "Accesso non riuscito"
|
loginFailed: "Accesso non riuscito"
|
||||||
|
@ -171,17 +171,17 @@ proxyAccountDescription: "Un account proxy è un account che funziona da followe
|
||||||
una lista, le attività di quell'utente potrebbero comunque non essere visualizzate
|
una lista, le attività di quell'utente potrebbero comunque non essere visualizzate
|
||||||
in locale se nessun altro utente lo segue su questo server, l'account proxy si occuperà
|
in locale se nessun altro utente lo segue su questo server, l'account proxy si occuperà
|
||||||
di seguire e acquisire i post."
|
di seguire e acquisire i post."
|
||||||
host: "Server remoto"
|
host: "Host"
|
||||||
selectUser: "Seleziona utente"
|
selectUser: "Seleziona utente"
|
||||||
recipient: "Destinatario"
|
recipient: "Destinatario(i)"
|
||||||
annotation: "Descrizione"
|
annotation: "Annotazioni"
|
||||||
federation: "Federazione"
|
federation: "Federazione"
|
||||||
instances: "Server"
|
instances: "Server"
|
||||||
registeredAt: "Registrato presso"
|
registeredAt: "Registrato presso"
|
||||||
latestRequestSentAt: "Ultima richiesta inviata"
|
latestRequestSentAt: "Ultima richiesta inviata"
|
||||||
latestRequestReceivedAt: "Ultima richiesta ricevuta"
|
latestRequestReceivedAt: "Ultima richiesta ricevuta"
|
||||||
latestStatus: "Ultimo stato"
|
latestStatus: "Ultimo stato"
|
||||||
storageUsage: "Volume di dischi"
|
storageUsage: "Spazio occupato"
|
||||||
charts: "Grafici"
|
charts: "Grafici"
|
||||||
perHour: "All'ora"
|
perHour: "All'ora"
|
||||||
perDay: "al giorno"
|
perDay: "al giorno"
|
||||||
|
@ -190,9 +190,9 @@ blockThisInstance: "Blocca questo server"
|
||||||
operations: "Operazioni"
|
operations: "Operazioni"
|
||||||
software: "Software"
|
software: "Software"
|
||||||
version: "Versione"
|
version: "Versione"
|
||||||
metadata: "Metadato"
|
metadata: "Metadati"
|
||||||
monitor: "Monitorare"
|
monitor: "Monitor"
|
||||||
jobQueue: "Coda di lavoro"
|
jobQueue: "Coda dei job"
|
||||||
cpuAndMemory: "CPU e Memoria"
|
cpuAndMemory: "CPU e Memoria"
|
||||||
network: "Rete"
|
network: "Rete"
|
||||||
disk: "Disco"
|
disk: "Disco"
|
||||||
|
@ -203,7 +203,7 @@ clearQueueConfirmTitle: "Vuoi davvero svuotare la coda?"
|
||||||
clearQueueConfirmText: "I post ancora in coda non verranno più federati. Solitamente,
|
clearQueueConfirmText: "I post ancora in coda non verranno più federati. Solitamente,
|
||||||
non è necessario eseguire questa operazione."
|
non è necessario eseguire questa operazione."
|
||||||
clearCachedFiles: "Svuota cache"
|
clearCachedFiles: "Svuota cache"
|
||||||
clearCachedFilesConfirm: "Vuoi davvero svuotare la cache da tutti i file remoti?"
|
clearCachedFilesConfirm: "Vuoi davvero svuotare la cache di tutti i file remoti?"
|
||||||
blockedInstances: "Server bloccati"
|
blockedInstances: "Server bloccati"
|
||||||
blockedInstancesDescription: "Elenca gli hostname dei server che vuoi bloccare. Non
|
blockedInstancesDescription: "Elenca gli hostname dei server che vuoi bloccare. Non
|
||||||
potranno più comunicare con il tuo server."
|
potranno più comunicare con il tuo server."
|
||||||
|
@ -213,7 +213,7 @@ blockedUsers: "Account bloccati"
|
||||||
noUsers: "Nessun utente trovato"
|
noUsers: "Nessun utente trovato"
|
||||||
editProfile: "Modifica profilo"
|
editProfile: "Modifica profilo"
|
||||||
noteDeleteConfirm: "Vuoi eliminare questo post?"
|
noteDeleteConfirm: "Vuoi eliminare questo post?"
|
||||||
pinLimitExceeded: "Non puoi fissare più post di così"
|
pinLimitExceeded: "Hai già fissato il massimo possibile di post"
|
||||||
intro: "L'installazione di Firefish è finita! Si prega di creare un account amministratore."
|
intro: "L'installazione di Firefish è finita! Si prega di creare un account amministratore."
|
||||||
done: "Fine"
|
done: "Fine"
|
||||||
processing: "Elaborazione in corso"
|
processing: "Elaborazione in corso"
|
||||||
|
@ -221,12 +221,12 @@ preview: "Anteprima"
|
||||||
default: "Predefinito"
|
default: "Predefinito"
|
||||||
noCustomEmojis: "Nessun emoji"
|
noCustomEmojis: "Nessun emoji"
|
||||||
noJobs: "Nessun lavoro"
|
noJobs: "Nessun lavoro"
|
||||||
federating: "Federando"
|
federating: "Federazione in corso"
|
||||||
blocked: "Bloccato"
|
blocked: "Bloccato"
|
||||||
suspended: "Sospes@"
|
suspended: "Sospeso"
|
||||||
all: "Tutti"
|
all: "Tutti"
|
||||||
subscribing: "Iscrivendo"
|
subscribing: "Sottoscrizione in corso"
|
||||||
publishing: "Pubblicando"
|
publishing: "Pubblicazione in corso"
|
||||||
notResponding: "Nessuna risposta"
|
notResponding: "Nessuna risposta"
|
||||||
instanceFollowing: "Seguiti da te su questo server"
|
instanceFollowing: "Seguiti da te su questo server"
|
||||||
instanceFollowers: "Chi ti segue su questo server"
|
instanceFollowers: "Chi ti segue su questo server"
|
||||||
|
@ -238,8 +238,8 @@ currentPassword: "Password attuale"
|
||||||
newPassword: "Nuova Password"
|
newPassword: "Nuova Password"
|
||||||
newPasswordRetype: "Conferma password"
|
newPasswordRetype: "Conferma password"
|
||||||
attachFile: "Allega file"
|
attachFile: "Allega file"
|
||||||
more: "Altri!"
|
more: "Altro!"
|
||||||
featured: "Tendenze"
|
featured: "In primo piano"
|
||||||
usernameOrUserId: "Nome utente o ID utente"
|
usernameOrUserId: "Nome utente o ID utente"
|
||||||
noSuchUser: "Nessun utente trovato"
|
noSuchUser: "Nessun utente trovato"
|
||||||
lookup: "Cercare"
|
lookup: "Cercare"
|
||||||
|
@ -249,7 +249,7 @@ remove: "Elimina"
|
||||||
removed: "Il tuo Tweet è stato eliminato"
|
removed: "Il tuo Tweet è stato eliminato"
|
||||||
removeAreYouSure: "Eliminare \"{x}\"?"
|
removeAreYouSure: "Eliminare \"{x}\"?"
|
||||||
deleteAreYouSure: "Eliminare \"{x}\"?"
|
deleteAreYouSure: "Eliminare \"{x}\"?"
|
||||||
resetAreYouSure: "Reimposta"
|
resetAreYouSure: "Vuoi reimpostare?"
|
||||||
saved: "Salvato"
|
saved: "Salvato"
|
||||||
messaging: "Messaggi"
|
messaging: "Messaggi"
|
||||||
upload: "Carica"
|
upload: "Carica"
|
||||||
|
@ -265,8 +265,8 @@ noMoreHistory: "Non c'è più cronologia da visualizzare"
|
||||||
startMessaging: "Nuovo messaggio"
|
startMessaging: "Nuovo messaggio"
|
||||||
nUsersRead: "Letto da {n} persone"
|
nUsersRead: "Letto da {n} persone"
|
||||||
agreeTo: "Sono d'accordo con {0}"
|
agreeTo: "Sono d'accordo con {0}"
|
||||||
tos: "Termini di servizio"
|
tos: "Termini d'uso"
|
||||||
start: "Inizia!"
|
start: "Inizia"
|
||||||
home: "Home"
|
home: "Home"
|
||||||
remoteUserCaution: "Può darsi che le informazioni siano incomplete perché questo è
|
remoteUserCaution: "Può darsi che le informazioni siano incomplete perché questo è
|
||||||
un utente remoto."
|
un utente remoto."
|
||||||
|
@ -274,7 +274,7 @@ activity: "Attività"
|
||||||
images: "Immagini"
|
images: "Immagini"
|
||||||
birthday: "Compleanno"
|
birthday: "Compleanno"
|
||||||
yearsOld: "{age}Anni"
|
yearsOld: "{age}Anni"
|
||||||
registeredDate: "Iscrizione a.."
|
registeredDate: "Iscrizione il"
|
||||||
location: "Posizione"
|
location: "Posizione"
|
||||||
theme: "Tema"
|
theme: "Tema"
|
||||||
themeForLightMode: "Tema da utilizzare per il modo chiaro"
|
themeForLightMode: "Tema da utilizzare per il modo chiaro"
|
||||||
|
@ -285,7 +285,7 @@ lightThemes: "Tema Chiaro"
|
||||||
darkThemes: "Tema Scuro"
|
darkThemes: "Tema Scuro"
|
||||||
syncDeviceDarkMode: "Sincronizza il tema scuro con le impostazioni del dispositivo"
|
syncDeviceDarkMode: "Sincronizza il tema scuro con le impostazioni del dispositivo"
|
||||||
drive: "Drive"
|
drive: "Drive"
|
||||||
fileName: "Nome dell'allegato"
|
fileName: "Nome file"
|
||||||
selectFile: "Scelta allegato"
|
selectFile: "Scelta allegato"
|
||||||
selectFiles: "Scelta allegato"
|
selectFiles: "Scelta allegato"
|
||||||
selectFolder: "Seleziona cartella"
|
selectFolder: "Seleziona cartella"
|
||||||
|
@ -298,25 +298,25 @@ deleteFolder: "Elimina cartella"
|
||||||
addFile: "Allega"
|
addFile: "Allega"
|
||||||
emptyDrive: "Il Drive è vuoto"
|
emptyDrive: "Il Drive è vuoto"
|
||||||
emptyFolder: "La cartella è vuota"
|
emptyFolder: "La cartella è vuota"
|
||||||
unableToDelete: "Eliminazione impossibile"
|
unableToDelete: "Impossibile rimuovere"
|
||||||
inputNewFileName: "Inserisci nome del nuovo file"
|
inputNewFileName: "Inserisci nome del nuovo file"
|
||||||
inputNewDescription: "Inserisci una nuova descrizione"
|
inputNewDescription: "Inserisci una nuova descrizione"
|
||||||
inputNewFolderName: "Inserisci nome della nuova cartella"
|
inputNewFolderName: "Inserisci nome della nuova cartella"
|
||||||
circularReferenceFolder: "La cartella di destinazione è una sottocartella della cartella
|
circularReferenceFolder: "La cartella di destinazione è una sottocartella della cartella
|
||||||
che vuoi spostare."
|
che vuoi spostare."
|
||||||
hasChildFilesOrFolders: "La cartella non può essere rimossa perché non è vuota"
|
hasChildFilesOrFolders: "La cartella non può essere rimossa perché non è vuota."
|
||||||
copyUrl: "Copia URL"
|
copyUrl: "Copia URL"
|
||||||
rename: "Modifica nome"
|
rename: "Modifica nome"
|
||||||
avatar: "Foto del profilo"
|
avatar: "Foto del profilo"
|
||||||
banner: "Intestazione"
|
banner: "Intestazione"
|
||||||
nsfw: "Contenuti sensibili"
|
nsfw: "Contenuti sensibili"
|
||||||
whenServerDisconnected: "Quando la connessione col server è persa"
|
whenServerDisconnected: "Quando la connessione col server è persa"
|
||||||
disconnectedFromServer: "Disconness@ dal server"
|
disconnectedFromServer: "Server disconnesso"
|
||||||
reload: "Ricarica"
|
reload: "Ricarica"
|
||||||
doNothing: "Nessun'azione"
|
doNothing: "Nessun'azione"
|
||||||
reloadConfirm: "Vuoi ricaricare?"
|
reloadConfirm: "Vuoi ricaricare?"
|
||||||
watch: "Osserva"
|
watch: "Osserva"
|
||||||
unwatch: "Smetti di Osserva"
|
unwatch: "Smetti di osservare"
|
||||||
accept: "Accetta"
|
accept: "Accetta"
|
||||||
reject: "Rifiuta"
|
reject: "Rifiuta"
|
||||||
normal: "Normale"
|
normal: "Normale"
|
||||||
|
@ -324,7 +324,7 @@ instanceName: "Nome del server"
|
||||||
instanceDescription: "Descrizione del server"
|
instanceDescription: "Descrizione del server"
|
||||||
maintainerName: "Nome dell'Amministratore"
|
maintainerName: "Nome dell'Amministratore"
|
||||||
maintainerEmail: "Indirizzo e-mail dell'Amministratore"
|
maintainerEmail: "Indirizzo e-mail dell'Amministratore"
|
||||||
tosUrl: "Termini di servizio URL"
|
tosUrl: "URL Termini d'uso"
|
||||||
thisYear: "Anno"
|
thisYear: "Anno"
|
||||||
thisMonth: "Mese"
|
thisMonth: "Mese"
|
||||||
today: "Oggi"
|
today: "Oggi"
|
||||||
|
@ -333,8 +333,8 @@ monthX: "{month}"
|
||||||
yearX: "{year}"
|
yearX: "{year}"
|
||||||
pages: "Pagine"
|
pages: "Pagine"
|
||||||
integration: "Integrazioni"
|
integration: "Integrazioni"
|
||||||
connectService: "Connessione"
|
connectService: "Connetti"
|
||||||
disconnectService: "Disconnessione "
|
disconnectService: "Disconnetti"
|
||||||
enableLocalTimeline: "Abilita Timeline locale"
|
enableLocalTimeline: "Abilita Timeline locale"
|
||||||
enableGlobalTimeline: "Abilita Timeline federata"
|
enableGlobalTimeline: "Abilita Timeline federata"
|
||||||
disablingTimelinesInfo: "Anche se disabiliti queste timeline, gli amministratori e
|
disablingTimelinesInfo: "Anche se disabiliti queste timeline, gli amministratori e
|
||||||
|
@ -342,16 +342,16 @@ disablingTimelinesInfo: "Anche se disabiliti queste timeline, gli amministratori
|
||||||
registration: "Iscriviti"
|
registration: "Iscriviti"
|
||||||
enableRegistration: "Permettere nuove registrazioni"
|
enableRegistration: "Permettere nuove registrazioni"
|
||||||
invite: "Invita"
|
invite: "Invita"
|
||||||
driveCapacityPerLocalAccount: "Volume del Drive per utente locale"
|
driveCapacityPerLocalAccount: "Dimensione Drive per utenti locali"
|
||||||
driveCapacityPerRemoteAccount: "Volume del Drive per utente remoto"
|
driveCapacityPerRemoteAccount: "Dimensione Drive per utenti remoti"
|
||||||
inMb: "in Megabytes"
|
inMb: "In megabytes"
|
||||||
iconUrl: "URL di icona (favicon, ecc.)"
|
iconUrl: "URL icona"
|
||||||
bannerUrl: "URL dell'immagine d'intestazione"
|
bannerUrl: "URL dell'immagine banner"
|
||||||
backgroundImageUrl: "URL dello sfondo"
|
backgroundImageUrl: "URL dello sfondo"
|
||||||
basicInfo: "Informazioni fondamentali"
|
basicInfo: "Informazioni di base"
|
||||||
pinnedUsers: "Utenti in evidenza"
|
pinnedUsers: "Utenti in fissati"
|
||||||
pinnedUsersDescription: "Elenca gli/le utenti che vuoi fissare in cima alla pagina
|
pinnedUsersDescription: "Elenca gli utenti che vuoi fissare in cima alla pagina \"\
|
||||||
\"Esplora\", un@ per riga."
|
Esplora\", uno per riga."
|
||||||
pinnedPages: "Pagine in evidenza"
|
pinnedPages: "Pagine in evidenza"
|
||||||
pinnedPagesDescription: "Specifica il percorso delle pagine che vuoi fissare in cima
|
pinnedPagesDescription: "Specifica il percorso delle pagine che vuoi fissare in cima
|
||||||
alla home page del server. Una pagina per riga."
|
alla home page del server. Una pagina per riga."
|
||||||
|
@ -371,20 +371,20 @@ avoidMultiCaptchaConfirm: "Utilizzare diversi Captcha può causare interferenze.
|
||||||
antennas: "Antenne"
|
antennas: "Antenne"
|
||||||
manageAntennas: "Gestore delle antenne"
|
manageAntennas: "Gestore delle antenne"
|
||||||
name: "Nome"
|
name: "Nome"
|
||||||
antennaSource: "Fonte dell'antenna"
|
antennaSource: "Origine dell'antenna"
|
||||||
antennaKeywords: "Parole chiavi da ricevere"
|
antennaKeywords: "Parole chiavi da ricevere"
|
||||||
antennaExcludeKeywords: "Parole chiavi da escludere"
|
antennaExcludeKeywords: "Parole chiavi da escludere"
|
||||||
antennaKeywordsDescription: "Separare con uno spazio indica la condizione \"E\". Separare
|
antennaKeywordsDescription: "Separare con uno spazio indica la condizione \"AND\"
|
||||||
con un'interruzzione riga indica la condizione \"O\"."
|
. Separare con un'interruzione riga indica la condizione \"OR\"."
|
||||||
notifyAntenna: "Notifica i nuovi post"
|
notifyAntenna: "Notifica i nuovi post"
|
||||||
withFileAntenna: "Solo post con file allegati"
|
withFileAntenna: "Solo post con allegati"
|
||||||
enableServiceworker: "Abilita ServiceWorker"
|
enableServiceworker: "Abilita ServiceWorker"
|
||||||
antennaUsersDescription: "Inserisci solo un nome utente per riga"
|
antennaUsersDescription: "Inserisci solo un nome utente per riga"
|
||||||
caseSensitive: "Sensibile alla distinzione tra maiuscole e minuscole"
|
caseSensitive: "Sensibile alla distinzione tra maiuscole e minuscole"
|
||||||
withReplies: "Includere le risposte"
|
withReplies: "Includere le risposte"
|
||||||
connectedTo: "Stai seguendo questi account"
|
connectedTo: "Stai seguendo questi account"
|
||||||
notesAndReplies: "Post e risposte"
|
notesAndReplies: "Post e risposte"
|
||||||
withFiles: "Con file in allegato"
|
withFiles: "Con file allegati"
|
||||||
silence: "Silenzia"
|
silence: "Silenzia"
|
||||||
silenceConfirm: "Vuoi davvero silenziare l'utente?"
|
silenceConfirm: "Vuoi davvero silenziare l'utente?"
|
||||||
unsilence: "Riattiva"
|
unsilence: "Riattiva"
|
||||||
|
@ -433,7 +433,7 @@ invites: "Inviti"
|
||||||
groupName: "Nome del gruppo"
|
groupName: "Nome del gruppo"
|
||||||
members: "Membri"
|
members: "Membri"
|
||||||
transfer: "Trasferisci"
|
transfer: "Trasferisci"
|
||||||
messagingWithUser: "Iniziare una chat con un altr@ utente"
|
messagingWithUser: "Chat privata"
|
||||||
messagingWithGroup: "Chattare in gruppo"
|
messagingWithGroup: "Chattare in gruppo"
|
||||||
title: "Titolo"
|
title: "Titolo"
|
||||||
text: "Testo"
|
text: "Testo"
|
||||||
|
@ -452,8 +452,8 @@ invitations: "Invita"
|
||||||
invitationCode: "Codice di invito"
|
invitationCode: "Codice di invito"
|
||||||
checking: "Confermando"
|
checking: "Confermando"
|
||||||
available: "Consigliati"
|
available: "Consigliati"
|
||||||
unavailable: "Il nome utente è già in uso"
|
unavailable: "Nome già in uso"
|
||||||
usernameInvalidFormat: "Il nome utente può contenere solo lettere, numeri e '_'"
|
usernameInvalidFormat: "Puoi usare solo lettere maiuscole, minuscole, numeri e '_'"
|
||||||
tooShort: "Troppo breve"
|
tooShort: "Troppo breve"
|
||||||
tooLong: "Troppo lungo"
|
tooLong: "Troppo lungo"
|
||||||
weakPassword: "Password debole"
|
weakPassword: "Password debole"
|
||||||
|
@ -556,14 +556,14 @@ scratchpadDescription: "Lo Scratchpad offre un ambiente per esperimenti di AiScr
|
||||||
output: "Uscita"
|
output: "Uscita"
|
||||||
script: "Script"
|
script: "Script"
|
||||||
disablePagesScript: "Disabilita AiScript nelle pagine"
|
disablePagesScript: "Disabilita AiScript nelle pagine"
|
||||||
updateRemoteUser: "Aggiornare le informazioni di utente remot@"
|
updateRemoteUser: "Aggiorna le informazioni dell'utente remoto"
|
||||||
deleteAllFiles: "Elimina tutti i file"
|
deleteAllFiles: "Elimina tutti i file"
|
||||||
deleteAllFilesConfirm: "Vuoi davvero eliminare tutti i file?"
|
deleteAllFilesConfirm: "Vuoi davvero eliminare tutti i file?"
|
||||||
removeAllFollowing: "Smetti di seguire tutti"
|
removeAllFollowing: "Smetti di seguire tutti"
|
||||||
removeAllFollowingDescription: "Smetti di seguire tutti gli account del server {host}.
|
removeAllFollowingDescription: "Smetti di seguire tutti gli account del server {host}.
|
||||||
È utile specialmente se il server non esiste più."
|
È utile specialmente se il server non esiste più."
|
||||||
userSuspended: "L'utente è sospes@."
|
userSuspended: "L'utente è sospeso."
|
||||||
userSilenced: "L'utente è silenziat@."
|
userSilenced: "L'utente è silenziato."
|
||||||
yourAccountSuspendedTitle: "Questo account è sospeso."
|
yourAccountSuspendedTitle: "Questo account è sospeso."
|
||||||
yourAccountSuspendedDescription: "Questo account è stato sospeso a causa di una violazione
|
yourAccountSuspendedDescription: "Questo account è stato sospeso a causa di una violazione
|
||||||
dei termini di servizio del server. Contattare l'amministrazione per i dettagli.
|
dei termini di servizio del server. Contattare l'amministrazione per i dettagli.
|
||||||
|
@ -906,8 +906,7 @@ _ad:
|
||||||
reduceFrequencyOfThisAd: "Visualizza questa pubblicità meno spesso"
|
reduceFrequencyOfThisAd: "Visualizza questa pubblicità meno spesso"
|
||||||
_forgotPassword:
|
_forgotPassword:
|
||||||
enterEmail: "Inserisci l'indirizzo di posta elettronica che hai registrato nel tuo
|
enterEmail: "Inserisci l'indirizzo di posta elettronica che hai registrato nel tuo
|
||||||
profilo. Il collegamento necessario per ripristinare la password verrà inviato
|
profilo. Il di link ripristino della password verrà inviato a questo indirizzo."
|
||||||
a questo indirizzo."
|
|
||||||
ifNoEmail: "Se non hai registrato alcun indirizzo e-mail, contatta l'admin del server."
|
ifNoEmail: "Se non hai registrato alcun indirizzo e-mail, contatta l'admin del server."
|
||||||
contactAdmin: "Poiché questo server non permette l'uso di indirizzi mail, contatta
|
contactAdmin: "Poiché questo server non permette l'uso di indirizzi mail, contatta
|
||||||
l'admin per poter ripristinare la password."
|
l'admin per poter ripristinare la password."
|
||||||
|
@ -970,7 +969,7 @@ _mfm:
|
||||||
blockCode: "Codice (blocco)"
|
blockCode: "Codice (blocco)"
|
||||||
inlineMath: "Espressione matematica(Immersione)"
|
inlineMath: "Espressione matematica(Immersione)"
|
||||||
blockMath: "Formula matematica (blocco)"
|
blockMath: "Formula matematica (blocco)"
|
||||||
quote: "Cita il nota"
|
quote: "Citazione"
|
||||||
emoji: "Emoji personalizzati"
|
emoji: "Emoji personalizzati"
|
||||||
search: "Cerca"
|
search: "Cerca"
|
||||||
flip: "Inverti"
|
flip: "Inverti"
|
||||||
|
@ -989,8 +988,8 @@ _mfm:
|
||||||
x4: "Estremamente più grande"
|
x4: "Estremamente più grande"
|
||||||
x4Description: "Mostra il contenuto estremamente più ingrandito."
|
x4Description: "Mostra il contenuto estremamente più ingrandito."
|
||||||
blur: "Sfocatura"
|
blur: "Sfocatura"
|
||||||
blurDescription: "È possibile rendere sfocato il contenuto. Spostando il cursore
|
blurDescription: "È possibile rendere sfocato il contenuto. Diventerà visibile al
|
||||||
su di esso tornerà visibile chiaramente."
|
passaggio del puntatore."
|
||||||
font: "Tipo di carattere"
|
font: "Tipo di carattere"
|
||||||
fontDescription: "Puoi scegliere il tipo di carattere per il contenuto."
|
fontDescription: "Puoi scegliere il tipo di carattere per il contenuto."
|
||||||
rainbow: "Arcobaleno"
|
rainbow: "Arcobaleno"
|
||||||
|
@ -1123,7 +1122,7 @@ _theme:
|
||||||
header: "Intestazione"
|
header: "Intestazione"
|
||||||
navBg: "Sfondo della barra laterale"
|
navBg: "Sfondo della barra laterale"
|
||||||
navFg: "Testo della barra laterale"
|
navFg: "Testo della barra laterale"
|
||||||
navHoverFg: "Testo della barra laterale (al passaggio del mouse)"
|
navHoverFg: "Testo della barra laterale (hover)"
|
||||||
navActive: "Testo della barra laterale (attivo)"
|
navActive: "Testo della barra laterale (attivo)"
|
||||||
navIndicator: "Indicatore di barra laterale"
|
navIndicator: "Indicatore di barra laterale"
|
||||||
link: "Link"
|
link: "Link"
|
||||||
|
@ -1138,19 +1137,19 @@ _theme:
|
||||||
infoWarnFg: "Testo di avviso"
|
infoWarnFg: "Testo di avviso"
|
||||||
cwBg: "Sfondo del CW"
|
cwBg: "Sfondo del CW"
|
||||||
cwFg: "Testo del pulsante CW"
|
cwFg: "Testo del pulsante CW"
|
||||||
cwHoverBg: "Sfondo del pulsante CW (sorvolato)"
|
cwHoverBg: "Sfondo del pulsante CW (hover)"
|
||||||
toastBg: "Sfondo di notifica a comparsa"
|
toastBg: "Sfondo di notifica a comparsa"
|
||||||
toastFg: "Testo di notifica a comparsa"
|
toastFg: "Testo di notifica a comparsa"
|
||||||
buttonBg: "Sfondo del pulsante"
|
buttonBg: "Sfondo del pulsante"
|
||||||
buttonHoverBg: "Sfondo del pulsante (sorvolato)"
|
buttonHoverBg: "Sfondo del pulsante (hover)"
|
||||||
inputBorder: "Inquadra casella di testo"
|
inputBorder: "Inquadra casella di testo"
|
||||||
listItemHoverBg: "Sfondo della voce di elenco (sorvolato)"
|
listItemHoverBg: "Sfondo della voce di elenco (hover)"
|
||||||
driveFolderBg: "Sfondo della cartella di disco"
|
driveFolderBg: "Sfondo della cartella di disco"
|
||||||
badge: "Distintivo"
|
badge: "Distintivo"
|
||||||
messageBg: "Sfondo della chat"
|
messageBg: "Sfondo della chat"
|
||||||
modalBg: Sfondo modale
|
modalBg: Sfondo modale
|
||||||
scrollbarHandle: Barra di scorrimento
|
scrollbarHandle: Barra di scorrimento
|
||||||
scrollbarHandleHover: Barra di scorrimento (Hover)
|
scrollbarHandleHover: Barra di scorrimento (hover)
|
||||||
accent: Accento
|
accent: Accento
|
||||||
fgHighlighted: Testo evidenziato
|
fgHighlighted: Testo evidenziato
|
||||||
accentLighten: Accento (chiaro)
|
accentLighten: Accento (chiaro)
|
||||||
|
@ -1512,7 +1511,7 @@ _pages:
|
||||||
width: "Larghezza"
|
width: "Larghezza"
|
||||||
height: "Altezza"
|
height: "Altezza"
|
||||||
id: Canvas ID
|
id: Canvas ID
|
||||||
note: "Post embedded"
|
note: "Post integrato"
|
||||||
_note:
|
_note:
|
||||||
id: "Post ID"
|
id: "Post ID"
|
||||||
idDescription: "In alternativa puoi incollare qui l'URL del post."
|
idDescription: "In alternativa puoi incollare qui l'URL del post."
|
||||||
|
@ -1670,7 +1669,7 @@ _pages:
|
||||||
arg2: Valore massimo
|
arg2: Valore massimo
|
||||||
strLen: Lunghezza del testo
|
strLen: Lunghezza del testo
|
||||||
join: Concatenazione testo
|
join: Concatenazione testo
|
||||||
splitStrByLine: Suddividi il testo al fine riga
|
splitStrByLine: Suddividi su più righe
|
||||||
subtract: Sottrazione
|
subtract: Sottrazione
|
||||||
lt: < A è minore di B
|
lt: < A è minore di B
|
||||||
gt: '> A è maggiore di B'
|
gt: '> A è maggiore di B'
|
||||||
|
@ -1741,7 +1740,7 @@ _notification:
|
||||||
fileUploaded: "File caricato correttamente"
|
fileUploaded: "File caricato correttamente"
|
||||||
youGotMention: "{name} ti ha menzionato"
|
youGotMention: "{name} ti ha menzionato"
|
||||||
youGotReply: "{name} ti ha risposto"
|
youGotReply: "{name} ti ha risposto"
|
||||||
youGotQuote: "{name} ha citato il tuo Nota e ha detto"
|
youGotQuote: "{name} ti ha citato"
|
||||||
youRenoted: "Boost da {name}"
|
youRenoted: "Boost da {name}"
|
||||||
youGotPoll: "{name} ha votato"
|
youGotPoll: "{name} ha votato"
|
||||||
youGotMessagingMessageFromUser: "{name} ti ha mandato un messaggio"
|
youGotMessagingMessageFromUser: "{name} ti ha mandato un messaggio"
|
||||||
|
@ -1789,7 +1788,7 @@ _deck:
|
||||||
widgets: "Widget"
|
widgets: "Widget"
|
||||||
notifications: "Notifiche"
|
notifications: "Notifiche"
|
||||||
tl: "Timeline"
|
tl: "Timeline"
|
||||||
antenna: "Antenne"
|
antenna: "Antenna"
|
||||||
list: "Liste"
|
list: "Liste"
|
||||||
mentions: "Menzioni"
|
mentions: "Menzioni"
|
||||||
direct: "Messaggi diretti"
|
direct: "Messaggi diretti"
|
||||||
|
@ -2060,7 +2059,7 @@ shuffle: Casuale
|
||||||
subscribePushNotification: Abilita le notifiche push
|
subscribePushNotification: Abilita le notifiche push
|
||||||
unsubscribePushNotification: Disabilita le notifiche push
|
unsubscribePushNotification: Disabilita le notifiche push
|
||||||
pushNotificationAlreadySubscribed: Le notifiche push sono già abilitate
|
pushNotificationAlreadySubscribed: Le notifiche push sono già abilitate
|
||||||
driveCapOverrideCaption: Imposta la capacità predefinita inserendo il valore 0.
|
driveCapOverrideCaption: Reimposta la capacità predefinita inserendo il valore 0.
|
||||||
numberOfPageCacheDescription: Aumentare questo numero migliorerà l'esperienza degli
|
numberOfPageCacheDescription: Aumentare questo numero migliorerà l'esperienza degli
|
||||||
utenti ma aumenterà il carico sul server e l'uso di memoria.
|
utenti ma aumenterà il carico sul server e l'uso di memoria.
|
||||||
type: Tipo
|
type: Tipo
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
|
||||||
_lang_: "Português"
|
_lang_: "Português"
|
||||||
headlineFirefish: "Uma rede ligada por notas"
|
headlineFirefish: "Uma rede ligada por notas"
|
||||||
introFirefish: "Bem-vindo! Firefish é um serviço de microblogue descentralizado de código aberto.\nCria \"notas\" e partilha o que te ocorre com todos à tua volta. 📡\nCom \"reações\" podes também expressar logo o que sentes às notas de todos. 👍\nExploremos um novo mundo! 🚀"
|
introFirefish: "Bem-vindo! Firefish é um serviço de microblogue descentralizado de
|
||||||
|
código aberto, gratuito para sempre! 🚀"
|
||||||
monthAndDay: "{day}/{month}"
|
monthAndDay: "{day}/{month}"
|
||||||
search: "Buscar"
|
search: "Buscar"
|
||||||
notifications: "Notificações"
|
notifications: "Notificações"
|
||||||
|
@ -44,7 +44,8 @@ copyContent: "Copiar conteúdos"
|
||||||
copyLink: "Copiar hiperligação"
|
copyLink: "Copiar hiperligação"
|
||||||
delete: "Eliminar"
|
delete: "Eliminar"
|
||||||
deleteAndEdit: "Eliminar e editar"
|
deleteAndEdit: "Eliminar e editar"
|
||||||
deleteAndEditConfirm: "Tens a certeza que pretendes eliminar esta nota e editá-la? Irás perder todas as suas reações, renotas e respostas."
|
deleteAndEditConfirm: "Tens a certeza que pretendes eliminar esta nota e editá-la?
|
||||||
|
Irás perder todas as suas reações, renotas e respostas."
|
||||||
addToList: "Adicionar a lista"
|
addToList: "Adicionar a lista"
|
||||||
sendMessage: "Enviar uma mensagem"
|
sendMessage: "Enviar uma mensagem"
|
||||||
copyUsername: "Copiar nome de utilizador"
|
copyUsername: "Copiar nome de utilizador"
|
||||||
|
@ -64,9 +65,11 @@ import: "Importar"
|
||||||
export: "Exportar"
|
export: "Exportar"
|
||||||
files: "Ficheiros"
|
files: "Ficheiros"
|
||||||
download: "Descarregar"
|
download: "Descarregar"
|
||||||
driveFileDeleteConfirm: "Tens a certeza que pretendes apagar o ficheiro \"{name}\"? As notas que tenham este ficheiro anexado serão também apagadas."
|
driveFileDeleteConfirm: "Tens a certeza que pretendes apagar o ficheiro \"{name}\"\
|
||||||
|
? As notas que tenham este ficheiro anexado serão também apagadas."
|
||||||
unfollowConfirm: "Tens a certeza que queres deixar de seguir {name}?"
|
unfollowConfirm: "Tens a certeza que queres deixar de seguir {name}?"
|
||||||
exportRequested: "Pediste uma exportação. Este processo pode demorar algum tempo. Será adicionado à tua Drive após a conclusão do processo."
|
exportRequested: "Pediste uma exportação. Este processo pode demorar algum tempo.
|
||||||
|
Será adicionado à tua Drive após a conclusão do processo."
|
||||||
importRequested: "Pediste uma importação. Este processo pode demorar algum tempo."
|
importRequested: "Pediste uma importação. Este processo pode demorar algum tempo."
|
||||||
lists: "Listas"
|
lists: "Listas"
|
||||||
noLists: "Não tens nenhuma lista"
|
noLists: "Não tens nenhuma lista"
|
||||||
|
@ -81,9 +84,12 @@ error: "Erro"
|
||||||
somethingHappened: "Ocorreu um erro"
|
somethingHappened: "Ocorreu um erro"
|
||||||
retry: "Tentar novamente"
|
retry: "Tentar novamente"
|
||||||
pageLoadError: "Ocorreu um erro ao carregar a página."
|
pageLoadError: "Ocorreu um erro ao carregar a página."
|
||||||
pageLoadErrorDescription: "Isto é normalmente causado por erros de rede ou pela cache do browser. Experimenta limpar a cache e tenta novamente após algum tempo."
|
pageLoadErrorDescription: "Isto é normalmente causado por erros de rede ou pela cache
|
||||||
serverIsDead: "O servidor não está respondendo. Por favor espere um pouco e tente novamente."
|
do browser. Experimenta limpar a cache e tenta novamente após algum tempo."
|
||||||
youShouldUpgradeClient: "Para visualizar essa página, por favor recarregue-a para atualizar seu cliente."
|
serverIsDead: "O servidor não está respondendo. Por favor espere um pouco e tente
|
||||||
|
novamente."
|
||||||
|
youShouldUpgradeClient: "Para visualizar essa página, por favor recarregue-a para
|
||||||
|
atualizar seu cliente."
|
||||||
enterListName: "Insira um nome para a lista"
|
enterListName: "Insira um nome para a lista"
|
||||||
privacy: "Privacidade"
|
privacy: "Privacidade"
|
||||||
makeFollowManuallyApprove: "Pedidos de seguimento precisam ser aprovados"
|
makeFollowManuallyApprove: "Pedidos de seguimento precisam ser aprovados"
|
||||||
|
@ -108,7 +114,8 @@ sensitive: "Conteúdo sensível"
|
||||||
add: "Adicionar"
|
add: "Adicionar"
|
||||||
reaction: "Reações"
|
reaction: "Reações"
|
||||||
reactionSetting: "Quais reações a mostrar no selecionador de reações"
|
reactionSetting: "Quais reações a mostrar no selecionador de reações"
|
||||||
reactionSettingDescription2: "Arraste para reordenar, clique para excluir, pressione + para adicionar."
|
reactionSettingDescription2: "Arraste para reordenar, clique para excluir, pressione
|
||||||
|
+ para adicionar."
|
||||||
rememberNoteVisibility: "Lembrar das configurações de visibilidade de notas"
|
rememberNoteVisibility: "Lembrar das configurações de visibilidade de notas"
|
||||||
attachCancel: "Remover anexo"
|
attachCancel: "Remover anexo"
|
||||||
markAsSensitive: "Marcar como sensível"
|
markAsSensitive: "Marcar como sensível"
|
||||||
|
@ -137,13 +144,18 @@ emojiUrl: "URL do Emoji"
|
||||||
addEmoji: "Adicionar um Emoji"
|
addEmoji: "Adicionar um Emoji"
|
||||||
settingGuide: "Guia de configuração"
|
settingGuide: "Guia de configuração"
|
||||||
cacheRemoteFiles: "Memória transitória de arquivos remotos"
|
cacheRemoteFiles: "Memória transitória de arquivos remotos"
|
||||||
cacheRemoteFilesDescription: "Se você desabilitar essa configuração, os arquivos remotos não serão armazenados em memória transitória e serão vinculados diretamente. Economiza o armazenamento do servidor, mas não gera miniaturas, o que aumenta o tráfego."
|
cacheRemoteFilesDescription: "Se você desabilitar essa configuração, os arquivos remotos
|
||||||
|
não serão armazenados em memória transitória e serão vinculados diretamente. Economiza
|
||||||
|
o armazenamento do servidor, mas não gera miniaturas, o que aumenta o tráfego."
|
||||||
flagAsBot: "Marcar conta como robô"
|
flagAsBot: "Marcar conta como robô"
|
||||||
flagAsBotDescription: "Se esta conta for operada por um programa, ative este sinalizador. Quando ativado, serve como um sinalizador para evitar o encadeamento de reações para outros programadores, e o manuseio do sistema do Firefish é adequado para ‘bots’."
|
flagAsBotDescription: "Se esta conta for operada por um programa, ative este sinalizador.
|
||||||
|
Quando ativado, serve como um sinalizador para evitar o encadeamento de reações
|
||||||
|
para outros programadores, e o manuseio do sistema do Firefish é adequado para ‘bots’."
|
||||||
flagAsCat: "Marcar conta como gato"
|
flagAsCat: "Marcar conta como gato"
|
||||||
flagAsCatDescription: "Ative essa opção para marcar essa conta como gato."
|
flagAsCatDescription: "Ative essa opção para marcar essa conta como gato."
|
||||||
flagShowTimelineReplies: "Mostrar respostas na linha de tempo"
|
flagShowTimelineReplies: "Mostrar respostas na linha de tempo"
|
||||||
flagShowTimelineRepliesDescription: "Quando ativado, a linha do tempo mostra as respostas às outras notas do utilizador, além da nota do utilizador."
|
flagShowTimelineRepliesDescription: "Quando ativado, a linha do tempo mostra as respostas
|
||||||
|
às outras notas do utilizador, além da nota do utilizador."
|
||||||
autoAcceptFollowed: "Aprove automaticamente os seguidores dos seguintes utilizadores"
|
autoAcceptFollowed: "Aprove automaticamente os seguidores dos seguintes utilizadores"
|
||||||
addAccount: "Adicionar Conta"
|
addAccount: "Adicionar Conta"
|
||||||
loginFailed: "Não consegui logar"
|
loginFailed: "Não consegui logar"
|
||||||
|
@ -156,7 +168,10 @@ searchWith: "Buscar: {q}"
|
||||||
youHaveNoLists: "Não tem nenhuma lista"
|
youHaveNoLists: "Não tem nenhuma lista"
|
||||||
followConfirm: "Tem certeza que quer deixar de seguir {name}?"
|
followConfirm: "Tem certeza que quer deixar de seguir {name}?"
|
||||||
proxyAccount: "Conta proxy"
|
proxyAccount: "Conta proxy"
|
||||||
proxyAccountDescription: "Uma conta proxy é uma conta que atua como seguidora remota para utilizadores sob determinadas condições. Por exemplo, quando um utilizador lista um utilizador remoto, a atividade não será entregue à instância, a menos que alguém esteja seguindo o utilizador listado, portanto, a conta proxy deve seguir."
|
proxyAccountDescription: "Uma conta proxy é uma conta que atua como seguidora remota
|
||||||
|
para utilizadores sob determinadas condições. Por exemplo, quando um utilizador
|
||||||
|
lista um utilizador remoto, a atividade não será entregue à instância, a menos que
|
||||||
|
alguém esteja seguindo o utilizador listado, portanto, a conta proxy deve seguir."
|
||||||
host: "hospedeiro"
|
host: "hospedeiro"
|
||||||
selectUser: "Selecionar utilizador"
|
selectUser: "Selecionar utilizador"
|
||||||
recipient: "Morada"
|
recipient: "Morada"
|
||||||
|
@ -186,11 +201,15 @@ instanceInfo: "Informações da instância"
|
||||||
statistics: "Estatisticas"
|
statistics: "Estatisticas"
|
||||||
clearQueue: "Limpar a fila"
|
clearQueue: "Limpar a fila"
|
||||||
clearQueueConfirmTitle: "Quer limpar a fila?"
|
clearQueueConfirmTitle: "Quer limpar a fila?"
|
||||||
clearQueueConfirmText: "Postagens não entregues não serão mais entregues. Normalmente você não precisa fazer isso."
|
clearQueueConfirmText: "Postagens não entregues não serão mais entregues. Normalmente
|
||||||
|
você não precisa fazer isso."
|
||||||
clearCachedFiles: "Limpar memória transitória"
|
clearCachedFiles: "Limpar memória transitória"
|
||||||
clearCachedFilesConfirm: "Tem certeza de que deseja excluir todos os arquivos remotos armazenados em memória transitória?"
|
clearCachedFilesConfirm: "Tem certeza de que deseja excluir todos os arquivos remotos
|
||||||
|
armazenados em memória transitória?"
|
||||||
blockedInstances: "Instância bloqueada"
|
blockedInstances: "Instância bloqueada"
|
||||||
blockedInstancesDescription: "Defina os anfitriões das instâncias que deseja bloquear, separados por quebras de linha. Uma instância bloqueada não poderá interagir com esta instância."
|
blockedInstancesDescription: "Defina os anfitriões das instâncias que deseja bloquear,
|
||||||
|
separados por quebras de linha. Uma instância bloqueada não poderá interagir com
|
||||||
|
esta instância."
|
||||||
muteAndBlock: "Silenciar e bloquear"
|
muteAndBlock: "Silenciar e bloquear"
|
||||||
mutedUsers: "Silenciar utilizador"
|
mutedUsers: "Silenciar utilizador"
|
||||||
blockedUsers: "Utilizadores bloqueados"
|
blockedUsers: "Utilizadores bloqueados"
|
||||||
|
@ -238,7 +257,9 @@ saved: "Salvo"
|
||||||
messaging: "Chat"
|
messaging: "Chat"
|
||||||
upload: "Enviando"
|
upload: "Enviando"
|
||||||
keepOriginalUploading: "Manter a imagem original"
|
keepOriginalUploading: "Manter a imagem original"
|
||||||
keepOriginalUploadingDescription: "Mantenha a versão original ao carregar a imagem. Quando desligado, a imagem para publicação na web será gerada no navegador no momento do upload."
|
keepOriginalUploadingDescription: "Mantenha a versão original ao carregar a imagem.
|
||||||
|
Quando desligado, a imagem para publicação na web será gerada no navegador no momento
|
||||||
|
do upload."
|
||||||
fromDrive: "\nDa unidade"
|
fromDrive: "\nDa unidade"
|
||||||
fromUrl: "Da URL"
|
fromUrl: "Da URL"
|
||||||
uploadFromUrl: "Carregamento de URL"
|
uploadFromUrl: "Carregamento de URL"
|
||||||
|
@ -262,8 +283,8 @@ yearsOld: "{age} anos"
|
||||||
registeredDate: "Data de registro"
|
registeredDate: "Data de registro"
|
||||||
location: "Lugar, colocar"
|
location: "Lugar, colocar"
|
||||||
theme: "tema"
|
theme: "tema"
|
||||||
themeForLightMode: "Temas usados no modo de luz"
|
themeForLightMode: "Tema a usar no Modo Diurno"
|
||||||
themeForDarkMode: "Temas usados no modo escuro"
|
themeForDarkMode: "Temas usados no Modo Noturno"
|
||||||
light: "Claro"
|
light: "Claro"
|
||||||
dark: "Escuro"
|
dark: "Escuro"
|
||||||
lightThemes: "Tema claro"
|
lightThemes: "Tema claro"
|
||||||
|
@ -271,7 +292,7 @@ darkThemes: "Tema escuro"
|
||||||
syncDeviceDarkMode: "Sincronize com o modo escuro do dispositivo"
|
syncDeviceDarkMode: "Sincronize com o modo escuro do dispositivo"
|
||||||
drive: "Unidades"
|
drive: "Unidades"
|
||||||
fileName: "Nome do Ficheiro"
|
fileName: "Nome do Ficheiro"
|
||||||
selectFile: "Selecione os arquivos"
|
selectFile: "Selecione o arquivo"
|
||||||
selectFiles: "Selecione os arquivos"
|
selectFiles: "Selecione os arquivos"
|
||||||
selectFolder: "Selecionar uma pasta"
|
selectFolder: "Selecionar uma pasta"
|
||||||
selectFolders: "Selecionar uma pasta"
|
selectFolders: "Selecionar uma pasta"
|
||||||
|
@ -286,8 +307,9 @@ emptyFolder: "A pasta está vazia"
|
||||||
unableToDelete: "Não é possível eliminar"
|
unableToDelete: "Não é possível eliminar"
|
||||||
inputNewFileName: "Por favor, digite um novo nome para a pasta!"
|
inputNewFileName: "Por favor, digite um novo nome para a pasta!"
|
||||||
inputNewDescription: "Insira uma nova legenda"
|
inputNewDescription: "Insira uma nova legenda"
|
||||||
inputNewFolderName: "Por favor, digite um novo nome para a pasta!"
|
inputNewFolderName: "Por favor, digite um novo nome para a pasta"
|
||||||
circularReferenceFolder: "A pasta de destino é uma subpasta da pasta que você deseja mover."
|
circularReferenceFolder: "A pasta de destino é uma subpasta da pasta que você deseja
|
||||||
|
mover."
|
||||||
hasChildFilesOrFolders: "Esta pasta não está vazia e não pode ser excluída."
|
hasChildFilesOrFolders: "Esta pasta não está vazia e não pode ser excluída."
|
||||||
copyUrl: "Copiar URL"
|
copyUrl: "Copiar URL"
|
||||||
rename: "Renomear"
|
rename: "Renomear"
|
||||||
|
@ -321,7 +343,8 @@ connectService: "Conectar"
|
||||||
disconnectService: "Desconectar"
|
disconnectService: "Desconectar"
|
||||||
enableLocalTimeline: "Ativar linha do tempo local"
|
enableLocalTimeline: "Ativar linha do tempo local"
|
||||||
enableGlobalTimeline: "Ativar linha do tempo global"
|
enableGlobalTimeline: "Ativar linha do tempo global"
|
||||||
disablingTimelinesInfo: "Se você desabilitar essas linhas do tempo, administradores e moderadores ainda poderão usá-las por conveniência."
|
disablingTimelinesInfo: "Se você desabilitar essas linhas do tempo, administradores
|
||||||
|
e moderadores ainda poderão usá-las por conveniência."
|
||||||
registration: "Registar"
|
registration: "Registar"
|
||||||
enableRegistration: "Permitir que qualquer pessoa se registre"
|
enableRegistration: "Permitir que qualquer pessoa se registre"
|
||||||
invite: "Convidar"
|
invite: "Convidar"
|
||||||
|
@ -333,9 +356,11 @@ bannerUrl: "URL da imagem do ‘banner’"
|
||||||
backgroundImageUrl: "URL da imagem de fundo"
|
backgroundImageUrl: "URL da imagem de fundo"
|
||||||
basicInfo: "Informações básicas"
|
basicInfo: "Informações básicas"
|
||||||
pinnedUsers: "Utilizador fixado"
|
pinnedUsers: "Utilizador fixado"
|
||||||
pinnedUsersDescription: "Descreva os utilizadores que você deseja fixar na página \"Localizar\", etc., separados por quebras de linha."
|
pinnedUsersDescription: "Descreva os utilizadores que você deseja fixar na página
|
||||||
|
\"Localizar\", etc., separados por quebras de linha."
|
||||||
pinnedPages: "Página fixada"
|
pinnedPages: "Página fixada"
|
||||||
pinnedPagesDescription: "Descreva o caminho da página que você deseja fixar na página superior da instância, separada por quebras de linha."
|
pinnedPagesDescription: "Descreva o caminho da página que você deseja fixar na página
|
||||||
|
superior da instância, separada por quebras de linha."
|
||||||
pinnedClipId: "ID do clipe a ser fixado"
|
pinnedClipId: "ID do clipe a ser fixado"
|
||||||
pinnedNotes: "Post fixado"
|
pinnedNotes: "Post fixado"
|
||||||
hcaptcha: "hCaptcha"
|
hcaptcha: "hCaptcha"
|
||||||
|
@ -346,18 +371,21 @@ recaptcha: "reCAPTCHA"
|
||||||
enableRecaptcha: "Habilitar reCAPTCHA"
|
enableRecaptcha: "Habilitar reCAPTCHA"
|
||||||
recaptchaSiteKey: "Chave do sítio ‘web’"
|
recaptchaSiteKey: "Chave do sítio ‘web’"
|
||||||
recaptchaSecretKey: "Chave secreta"
|
recaptchaSecretKey: "Chave secreta"
|
||||||
avoidMultiCaptchaConfirm: "O uso de vários captchas pode causar interferência. Deseja desativar outros captchas? Você também pode cancelar e deixar vários captchas ativados."
|
avoidMultiCaptchaConfirm: "O uso de vários captchas pode causar interferência. Deseja
|
||||||
|
desativar outros captchas? Você também pode cancelar e deixar vários captchas ativados."
|
||||||
antennas: "Antenas"
|
antennas: "Antenas"
|
||||||
manageAntennas: "Gestão de antena"
|
manageAntennas: "Gestão de antena"
|
||||||
name: "Nome"
|
name: "Nome"
|
||||||
antennaSource: "Origem de entrada"
|
antennaSource: "Origem de entrada"
|
||||||
antennaKeywords: "Palavras-chave recebidas"
|
antennaKeywords: "Palavras-chave recebidas"
|
||||||
antennaExcludeKeywords: "Palavras-chave negativas"
|
antennaExcludeKeywords: "Palavras-chave negativas"
|
||||||
antennaKeywordsDescription: "Se você separá-lo com um espaço, será uma especificação AND, e se você separá-lo com uma quebra de linha, será uma especificação OR."
|
antennaKeywordsDescription: "Se você separá-lo com um espaço, será uma especificação
|
||||||
|
AND, e se você separá-lo com uma quebra de linha, será uma especificação OR."
|
||||||
notifyAntenna: "Notificar novas notas"
|
notifyAntenna: "Notificar novas notas"
|
||||||
withFileAntenna: "Apenas notas com arquivos anexados"
|
withFileAntenna: "Apenas notas com arquivos anexados"
|
||||||
enableServiceworker: "Ative as notificações push para o seu navegador"
|
enableServiceworker: "Ative as notificações push para o seu navegador"
|
||||||
antennaUsersDescription: "Especificar nomes de utilizador separados por quebras de linha"
|
antennaUsersDescription: "Especificar nomes de utilizador separados por quebras de
|
||||||
|
linha"
|
||||||
caseSensitive: "Maiúsculas e minúsculas"
|
caseSensitive: "Maiúsculas e minúsculas"
|
||||||
withReplies: "Incluindo resposta"
|
withReplies: "Incluindo resposta"
|
||||||
connectedTo: "Você está conectado à seguinte conta"
|
connectedTo: "Você está conectado à seguinte conta"
|
||||||
|
@ -433,15 +461,19 @@ showFeaturedNotesInTimeline: "Mostrar notas recomendadas na linha do tempo"
|
||||||
objectStorage: "Armazenamento de objetos"
|
objectStorage: "Armazenamento de objetos"
|
||||||
useObjectStorage: "Usar armazenamento de objetos"
|
useObjectStorage: "Usar armazenamento de objetos"
|
||||||
objectStorageBaseUrl: "URL base"
|
objectStorageBaseUrl: "URL base"
|
||||||
objectStorageBaseUrlDesc: "O URL usado para referência. Se você estiver usando um CDN ou Proxy, seu URL, S3:'https: // <bucket> .s3.amazonaws.com', GCS, etc .:'https://storage.googleapis.com/ <bucket>' ."
|
objectStorageBaseUrlDesc: "O URL usado para referência. Se você estiver usando um
|
||||||
|
CDN ou Proxy, seu URL, S3:'https: // <bucket> .s3.amazonaws.com', GCS, etc .:'https://storage.googleapis.com/
|
||||||
|
<bucket>' ."
|
||||||
objectStorageBucket: "Bucket"
|
objectStorageBucket: "Bucket"
|
||||||
objectStorageBucketDesc: "Especifique o nome do bucket do serviço a ser usado."
|
objectStorageBucketDesc: "Especifique o nome do bucket do serviço a ser usado."
|
||||||
objectStoragePrefix: "Prefixo"
|
objectStoragePrefix: "Prefixo"
|
||||||
objectStoragePrefixDesc: "Ele é armazenado neste diretório de prefixo."
|
objectStoragePrefixDesc: "Ele é armazenado neste diretório de prefixo."
|
||||||
objectStorageEndpoint: "Ponto final"
|
objectStorageEndpoint: "Ponto final"
|
||||||
objectStorageEndpointDesc: "Especifique vazio para S3, caso contrário, especifique o ponto final para cada serviço. Especifique como'<host>'ou'<host>: <port>'."
|
objectStorageEndpointDesc: "Especifique vazio para S3, caso contrário, especifique
|
||||||
|
o ponto final para cada serviço. Especifique como'<host>'ou'<host>: <port>'."
|
||||||
objectStorageRegion: "Região"
|
objectStorageRegion: "Região"
|
||||||
objectStorageRegionDesc: "Especifique uma região como 'xx-east-1'. Caso seu serviço não tenha o conceito de região, ele deve estar vazio ou 'us-east-1'."
|
objectStorageRegionDesc: "Especifique uma região como 'xx-east-1'. Caso seu serviço
|
||||||
|
não tenha o conceito de região, ele deve estar vazio ou 'us-east-1'."
|
||||||
objectStorageUseSSL: "Usar SSL"
|
objectStorageUseSSL: "Usar SSL"
|
||||||
objectStorageUseSSLDesc: "Desative-o se não quiser usar https para conexões de API"
|
objectStorageUseSSLDesc: "Desative-o se não quiser usar https para conexões de API"
|
||||||
objectStorageUseProxy: "Usar proxy"
|
objectStorageUseProxy: "Usar proxy"
|
||||||
|
@ -449,7 +481,8 @@ objectStorageUseProxyDesc: "Se você não usa proxy para conexão de API, desati
|
||||||
objectStorageSetPublicRead: "Definir 'public-read' ao fazer o upload"
|
objectStorageSetPublicRead: "Definir 'public-read' ao fazer o upload"
|
||||||
serverLogs: "Registro do servidor"
|
serverLogs: "Registro do servidor"
|
||||||
deleteAll: "Apagar Tudo"
|
deleteAll: "Apagar Tudo"
|
||||||
showFixedPostForm: "Exibir o formulário de postagem na parte superior da linha do tempo"
|
showFixedPostForm: "Exibir o formulário de postagem na parte superior da linha do
|
||||||
|
tempo"
|
||||||
newNoteRecived: "Nova nota recebida"
|
newNoteRecived: "Nova nota recebida"
|
||||||
sounds: "Sons"
|
sounds: "Sons"
|
||||||
listen: "Ouvir"
|
listen: "Ouvir"
|
||||||
|
@ -618,7 +651,8 @@ _pages:
|
||||||
_dailyRannum:
|
_dailyRannum:
|
||||||
arg1: "Valor mínimo"
|
arg1: "Valor mínimo"
|
||||||
arg2: "Valor máximo"
|
arg2: "Valor máximo"
|
||||||
dailyRandomPick: "Escolher aleatoriamente de uma lista (Muda uma vez por dia para cada usuário)"
|
dailyRandomPick: "Escolher aleatoriamente de uma lista (Muda uma vez por dia
|
||||||
|
para cada usuário)"
|
||||||
_dailyRandomPick:
|
_dailyRandomPick:
|
||||||
arg1: "Listas"
|
arg1: "Listas"
|
||||||
seedRandom: "Aleatório (com semente)"
|
seedRandom: "Aleatório (com semente)"
|
||||||
|
@ -634,7 +668,8 @@ _pages:
|
||||||
_seedRandomPick:
|
_seedRandomPick:
|
||||||
arg1: "Semente"
|
arg1: "Semente"
|
||||||
arg2: "Listas"
|
arg2: "Listas"
|
||||||
DRPWPM: "Escolher aleatoriamente de uma lista ponderada (Muda uma vez por dia para cada usuário)"
|
DRPWPM: "Escolher aleatoriamente de uma lista ponderada (Muda uma vez por dia
|
||||||
|
para cada usuário)"
|
||||||
_DRPWPM:
|
_DRPWPM:
|
||||||
arg1: "Lista de texto"
|
arg1: "Lista de texto"
|
||||||
pick: "Escolhe a partir da lista"
|
pick: "Escolhe a partir da lista"
|
||||||
|
@ -665,7 +700,8 @@ _pages:
|
||||||
_for:
|
_for:
|
||||||
arg1: "Número de repetições"
|
arg1: "Número de repetições"
|
||||||
arg2: "Ação"
|
arg2: "Ação"
|
||||||
typeError: "Espaço {slot} aceita valores de tipo \"{expect}\", mas o valor dado é do tipo \"{actual}\"!"
|
typeError: "Espaço {slot} aceita valores de tipo \"{expect}\", mas o valor dado
|
||||||
|
é do tipo \"{actual}\"!"
|
||||||
thereIsEmptySlot: "O espaço {slot} está vazio!"
|
thereIsEmptySlot: "O espaço {slot} está vazio!"
|
||||||
types:
|
types:
|
||||||
string: "Texto"
|
string: "Texto"
|
||||||
|
@ -730,3 +766,5 @@ _deck:
|
||||||
list: "Listas"
|
list: "Listas"
|
||||||
mentions: "Menções"
|
mentions: "Menções"
|
||||||
direct: "Notas diretas"
|
direct: "Notas diretas"
|
||||||
|
editNote: Editar post
|
||||||
|
edited: Editado a {date} às {time}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
9ea14ceed6ec996fcfe139e5835e033895ca84f5
|
d9f7e2bede4f0715810b72374d181ba283cae0d5
|
||||||
|
|
23
package.json
|
@ -1,16 +1,16 @@
|
||||||
{
|
{
|
||||||
"name": "firefish",
|
"name": "firefish",
|
||||||
"version": "1.0.5-dev7",
|
"version": "1.0.5-dev11",
|
||||||
"codename": "aqua",
|
"codename": "aqua",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://code.naskya.net/naskya/firefish"
|
"url": "https://code.naskya.net/naskya/firefish"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@8.7.0",
|
"packageManager": "pnpm@8.7.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"rebuild": "pnpm run clean && pnpm node ./scripts/build-greet.js && pnpm -r --parallel run build && pnpm run gulp",
|
"rebuild": "pnpm run clean && ./scripts/build-greet.sh && pnpm -r --parallel run build && pnpm run gulp",
|
||||||
"build": "pnpm node ./scripts/build-greet.js && pnpm -r --parallel run build && pnpm run gulp",
|
"build": "./scripts/build-greet.sh && pnpm -r --parallel run build && pnpm run gulp",
|
||||||
"start": "pnpm --filter backend run start",
|
"start": "pnpm --filter backend run start",
|
||||||
"start:test": "pnpm --filter backend run start:test",
|
"start:test": "pnpm --filter backend run start:test",
|
||||||
"init": "pnpm run migrate",
|
"init": "pnpm run migrate",
|
||||||
|
@ -33,18 +33,19 @@
|
||||||
"chokidar": "^3.3.1"
|
"chokidar": "^3.3.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bull-board/api": "5.7.2",
|
"@bull-board/api": "5.8.0",
|
||||||
"@bull-board/ui": "5.7.2",
|
"@bull-board/ui": "5.8.0",
|
||||||
"@napi-rs/cli": "^2.16.2",
|
"@napi-rs/cli": "^2.16.2",
|
||||||
"@tensorflow/tfjs": "^3.21.0",
|
"@tensorflow/tfjs": "^4.10.0",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
"seedrandom": "^3.0.5"
|
"seedrandom": "^3.0.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@biomejs/biome": "1.0.0",
|
||||||
"@types/gulp": "4.0.13",
|
"@types/gulp": "4.0.13",
|
||||||
"@types/gulp-rename": "2.0.2",
|
"@types/gulp-rename": "2.0.2",
|
||||||
"@types/node": "20.4.9",
|
"@types/node": "20.5.8",
|
||||||
"chalk": "4.1.2",
|
"add": "2.0.6",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"execa": "5.1.1",
|
"execa": "5.1.1",
|
||||||
"gulp": "4.0.2",
|
"gulp": "4.0.2",
|
||||||
|
@ -53,7 +54,7 @@
|
||||||
"gulp-replace": "1.1.4",
|
"gulp-replace": "1.1.4",
|
||||||
"gulp-terser": "2.1.0",
|
"gulp-terser": "2.1.0",
|
||||||
"install-peers": "^1.0.4",
|
"install-peers": "^1.0.4",
|
||||||
"rome": "^12.1.3",
|
"pnpm": "8.7.1",
|
||||||
"typescript": "5.1.6"
|
"typescript": "5.2.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 8 KiB |
|
@ -6,6 +6,7 @@ mod m20230531_180824_drop_reversi;
|
||||||
mod m20230627_185451_index_note_url;
|
mod m20230627_185451_index_note_url;
|
||||||
mod m20230709_000510_move_antenna_to_cache;
|
mod m20230709_000510_move_antenna_to_cache;
|
||||||
mod m20230806_170616_fix_antenna_stream_ids;
|
mod m20230806_170616_fix_antenna_stream_ids;
|
||||||
|
mod m20230904_013244_is_indexable;
|
||||||
|
|
||||||
pub struct Migrator;
|
pub struct Migrator;
|
||||||
|
|
||||||
|
@ -17,6 +18,7 @@ impl MigratorTrait for Migrator {
|
||||||
Box::new(m20230627_185451_index_note_url::Migration),
|
Box::new(m20230627_185451_index_note_url::Migration),
|
||||||
Box::new(m20230709_000510_move_antenna_to_cache::Migration),
|
Box::new(m20230709_000510_move_antenna_to_cache::Migration),
|
||||||
Box::new(m20230806_170616_fix_antenna_stream_ids::Migration),
|
Box::new(m20230806_170616_fix_antenna_stream_ids::Migration),
|
||||||
|
Box::new(m20230904_013244_is_indexable::Migration),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
use sea_orm_migration::prelude::*;
|
||||||
|
|
||||||
|
#[derive(DeriveMigrationName)]
|
||||||
|
pub struct Migration;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl MigrationTrait for Migration {
|
||||||
|
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager
|
||||||
|
.alter_table(
|
||||||
|
Table::alter()
|
||||||
|
.table(User::Table)
|
||||||
|
.add_column(
|
||||||
|
ColumnDef::new(User::IsIndexable)
|
||||||
|
.boolean()
|
||||||
|
.not_null()
|
||||||
|
.default(true),
|
||||||
|
)
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
manager
|
||||||
|
.alter_table(
|
||||||
|
Table::alter()
|
||||||
|
.table(UserProfile::Table)
|
||||||
|
.add_column(
|
||||||
|
ColumnDef::new(UserProfile::IsIndexable)
|
||||||
|
.boolean()
|
||||||
|
.not_null()
|
||||||
|
.default(true),
|
||||||
|
)
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager
|
||||||
|
.alter_table(
|
||||||
|
Table::alter()
|
||||||
|
.table(User::Table)
|
||||||
|
.drop_column(User::IsIndexable)
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
manager
|
||||||
|
.alter_table(
|
||||||
|
Table::alter()
|
||||||
|
.table(UserProfile::Table)
|
||||||
|
.drop_column(UserProfile::IsIndexable)
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Learn more at https://docs.rs/sea-query#iden
|
||||||
|
#[derive(Iden)]
|
||||||
|
enum User {
|
||||||
|
Table,
|
||||||
|
#[iden = "isIndexable"]
|
||||||
|
IsIndexable,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Iden)]
|
||||||
|
enum UserProfile {
|
||||||
|
Table,
|
||||||
|
#[iden = "isIndexable"]
|
||||||
|
IsIndexable,
|
||||||
|
}
|
|
@ -41,7 +41,7 @@
|
||||||
"prepublishOnly": "napi prepublish -t npm",
|
"prepublishOnly": "napi prepublish -t npm",
|
||||||
"universal": "napi universal",
|
"universal": "napi universal",
|
||||||
"version": "napi version",
|
"version": "napi version",
|
||||||
"format": "cargo fmt --all",
|
"format": "cargo fmt --all -- --check",
|
||||||
"lint": "cargo clippy --fix"
|
"lint": "cargo clippy --fix --allow-dirty --allow-staged && cargo fmt --all -- --check"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,6 +74,8 @@ pub struct Model {
|
||||||
pub also_known_as: Option<String>,
|
pub also_known_as: Option<String>,
|
||||||
#[sea_orm(column_name = "speakAsCat")]
|
#[sea_orm(column_name = "speakAsCat")]
|
||||||
pub speak_as_cat: bool,
|
pub speak_as_cat: bool,
|
||||||
|
#[sea_orm(column_name = "isIndexable")]
|
||||||
|
pub is_indexable: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
|
|
@ -75,6 +75,8 @@ pub struct Model {
|
||||||
pub moderation_note: String,
|
pub moderation_note: String,
|
||||||
#[sea_orm(column_name = "preventAiLearning")]
|
#[sea_orm(column_name = "preventAiLearning")]
|
||||||
pub prevent_ai_learning: bool,
|
pub prevent_ai_learning: bool,
|
||||||
|
#[sea_orm(column_name = "isIndexable")]
|
||||||
|
pub is_indexable: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
|
|
@ -16,17 +16,17 @@
|
||||||
"build": "pnpm swc src -d built -D",
|
"build": "pnpm swc src -d built -D",
|
||||||
"build:debug": "pnpm swc src -d built -s -D",
|
"build:debug": "pnpm swc src -d built -s -D",
|
||||||
"watch": "pnpm swc src -d built -D -w",
|
"watch": "pnpm swc src -d built -D -w",
|
||||||
"lint": "pnpm rome check --apply *",
|
"lint": "pnpm biome check --apply **/*.ts ; pnpm run format",
|
||||||
"format": "pnpm rome format * --write"
|
"format": "pnpm biome format * --write"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@swc/core-android-arm64": "1.3.11",
|
"@swc/core-android-arm64": "1.3.11",
|
||||||
"@tensorflow/tfjs-node": "3.21.1"
|
"@tensorflow/tfjs-node": "3.21.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bull-board/api": "5.7.2",
|
"@bull-board/api": "5.8.0",
|
||||||
"@bull-board/koa": "5.7.2",
|
"@bull-board/koa": "5.8.0",
|
||||||
"@bull-board/ui": "5.7.2",
|
"@bull-board/ui": "5.8.0",
|
||||||
"@discordapp/twemoji": "14.1.2",
|
"@discordapp/twemoji": "14.1.2",
|
||||||
"@elastic/elasticsearch": "7.17.0",
|
"@elastic/elasticsearch": "7.17.0",
|
||||||
"@koa/cors": "3.4.3",
|
"@koa/cors": "3.4.3",
|
||||||
|
@ -39,17 +39,16 @@
|
||||||
"@tensorflow/tfjs": "^4.2.0",
|
"@tensorflow/tfjs": "^4.2.0",
|
||||||
"adm-zip": "^0.5.10",
|
"adm-zip": "^0.5.10",
|
||||||
"ajv": "8.12.0",
|
"ajv": "8.12.0",
|
||||||
"archiver": "5.3.1",
|
"archiver": "6.0.0",
|
||||||
"argon2": "^0.30.3",
|
"argon2": "^0.31.1",
|
||||||
"autolinker": "4.0.0",
|
"autolinker": "4.0.0",
|
||||||
"autwh": "0.1.0",
|
"autwh": "0.1.0",
|
||||||
"aws-sdk": "2.1413.0",
|
"aws-sdk": "2.1413.0",
|
||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"blurhash": "2.0.5",
|
"blurhash": "2.0.5",
|
||||||
"bull": "4.11.2",
|
"bull": "4.11.3",
|
||||||
"cacheable-lookup": "TheEssem/cacheable-lookup",
|
"cacheable-lookup": "TheEssem/cacheable-lookup",
|
||||||
"cbor": "8.1.0",
|
|
||||||
"chalk": "5.3.0",
|
"chalk": "5.3.0",
|
||||||
"chalk-template": "0.4.0",
|
"chalk-template": "0.4.0",
|
||||||
"chokidar": "^3.5.3",
|
"chokidar": "^3.5.3",
|
||||||
|
@ -61,19 +60,19 @@
|
||||||
"deep-email-validator": "0.1.21",
|
"deep-email-validator": "0.1.21",
|
||||||
"escape-regexp": "0.0.1",
|
"escape-regexp": "0.0.1",
|
||||||
"feed": "4.2.2",
|
"feed": "4.2.2",
|
||||||
"file-type": "17.1.6",
|
"file-type": "18.5.0",
|
||||||
"firefish-js": "workspace:*",
|
"firefish-js": "workspace:*",
|
||||||
"fluent-ffmpeg": "2.1.2",
|
"fluent-ffmpeg": "2.1.2",
|
||||||
"got": "12.5.3",
|
"got": "13.0.0",
|
||||||
"gunzip-maybe": "^1.4.2",
|
"gunzip-maybe": "^1.4.2",
|
||||||
"hpagent": "0.1.2",
|
"hpagent": "1.2.0",
|
||||||
"ioredis": "5.3.2",
|
"ioredis": "5.3.2",
|
||||||
"ip-cidr": "3.1.0",
|
"ip-cidr": "3.1.0",
|
||||||
"is-svg": "4.3.2",
|
"is-svg": "5.0.0",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
"jsdom": "20.0.3",
|
"jsdom": "22.1.0",
|
||||||
"json5": "2.2.3",
|
"json5": "2.2.3",
|
||||||
"jsonld": "8.2.0",
|
"jsonld": "8.2.1",
|
||||||
"jsrsasign": "10.8.6",
|
"jsrsasign": "10.8.6",
|
||||||
"koa": "2.14.2",
|
"koa": "2.14.2",
|
||||||
"koa-body": "^6.0.1",
|
"koa-body": "^6.0.1",
|
||||||
|
@ -87,10 +86,10 @@
|
||||||
"koa-slow": "2.1.0",
|
"koa-slow": "2.1.0",
|
||||||
"koa-views": "7.0.2",
|
"koa-views": "7.0.2",
|
||||||
"megalodon": "workspace:*",
|
"megalodon": "workspace:*",
|
||||||
"meilisearch": "0.33.0",
|
"meilisearch": "0.34.1",
|
||||||
"mfm-js": "0.23.3",
|
"mfm-js": "0.23.3",
|
||||||
"mime-types": "2.1.35",
|
"mime-types": "2.1.35",
|
||||||
"msgpackr": "1.9.6",
|
"msgpackr": "1.9.7",
|
||||||
"multer": "1.4.4-lts.1",
|
"multer": "1.4.4-lts.1",
|
||||||
"native-utils": "link:native-utils",
|
"native-utils": "link:native-utils",
|
||||||
"nested-property": "4.0.0",
|
"nested-property": "4.0.0",
|
||||||
|
@ -102,19 +101,18 @@
|
||||||
"os-utils": "0.0.14",
|
"os-utils": "0.0.14",
|
||||||
"otpauth": "^9.1.4",
|
"otpauth": "^9.1.4",
|
||||||
"parse5": "7.1.2",
|
"parse5": "7.1.2",
|
||||||
"pg": "8.11.2",
|
"pg": "8.11.3",
|
||||||
"private-ip": "2.3.4",
|
"private-ip": "3.0.1",
|
||||||
"probe-image-size": "7.2.3",
|
"probe-image-size": "7.2.3",
|
||||||
"promise-limit": "2.7.0",
|
"promise-limit": "2.7.0",
|
||||||
"punycode": "2.3.0",
|
"punycode": "2.3.0",
|
||||||
"pureimage": "0.3.15",
|
"pureimage": "0.4.8",
|
||||||
"qrcode": "1.5.3",
|
"qrcode": "1.5.3",
|
||||||
"qs": "6.11.2",
|
"qs": "6.11.2",
|
||||||
"random-seed": "0.3.0",
|
"random-seed": "0.3.0",
|
||||||
"ratelimiter": "3.4.1",
|
"ratelimiter": "3.4.1",
|
||||||
"re2": "1.20.1",
|
"re2": "1.20.3",
|
||||||
"redis-lock": "0.1.4",
|
"redis-semaphore": "5.5.0",
|
||||||
"redis-semaphore": "5.4.0",
|
|
||||||
"reflect-metadata": "0.1.13",
|
"reflect-metadata": "0.1.13",
|
||||||
"rename": "1.0.4",
|
"rename": "1.0.4",
|
||||||
"rndstr": "1.0.0",
|
"rndstr": "1.0.0",
|
||||||
|
@ -122,12 +120,12 @@
|
||||||
"sanitize-html": "2.11.0",
|
"sanitize-html": "2.11.0",
|
||||||
"seedrandom": "^3.0.5",
|
"seedrandom": "^3.0.5",
|
||||||
"semver": "7.5.4",
|
"semver": "7.5.4",
|
||||||
"sharp": "0.32.4",
|
"sharp": "0.32.5",
|
||||||
"sonic-channel": "^1.3.1",
|
"sonic-channel": "^1.3.1",
|
||||||
"stringz": "2.1.0",
|
"stringz": "2.1.0",
|
||||||
"summaly": "2.7.0",
|
"summaly": "2.7.0",
|
||||||
"syslog-pro": "1.0.0",
|
"syslog-pro": "1.0.0",
|
||||||
"systeminformation": "5.18.13",
|
"systeminformation": "5.21.3",
|
||||||
"tar-stream": "^3.1.6",
|
"tar-stream": "^3.1.6",
|
||||||
"tesseract.js": "^4.1.1",
|
"tesseract.js": "^4.1.1",
|
||||||
"tinycolor2": "1.6.0",
|
"tinycolor2": "1.6.0",
|
||||||
|
@ -136,7 +134,7 @@
|
||||||
"typeorm": "0.3.17",
|
"typeorm": "0.3.17",
|
||||||
"ulid": "2.3.0",
|
"ulid": "2.3.0",
|
||||||
"uuid": "9.0.0",
|
"uuid": "9.0.0",
|
||||||
"web-push": "3.6.4",
|
"web-push": "3.6.5",
|
||||||
"websocket": "1.0.34",
|
"websocket": "1.0.34",
|
||||||
"xev": "3.0.2"
|
"xev": "3.0.2"
|
||||||
},
|
},
|
||||||
|
@ -145,7 +143,6 @@
|
||||||
"@swc/core": "^1.3.75",
|
"@swc/core": "^1.3.75",
|
||||||
"@types/adm-zip": "^0.5.0",
|
"@types/adm-zip": "^0.5.0",
|
||||||
"@types/bcryptjs": "2.4.2",
|
"@types/bcryptjs": "2.4.2",
|
||||||
"@types/cbor": "6.0.0",
|
|
||||||
"@types/escape-regexp": "0.0.1",
|
"@types/escape-regexp": "0.0.1",
|
||||||
"@types/fluent-ffmpeg": "2.1.21",
|
"@types/fluent-ffmpeg": "2.1.21",
|
||||||
"@types/js-yaml": "4.0.5",
|
"@types/js-yaml": "4.0.5",
|
||||||
|
|
|
@ -19,7 +19,12 @@ const ev = new Xev();
|
||||||
* Init process
|
* Init process
|
||||||
*/
|
*/
|
||||||
export default async function () {
|
export default async function () {
|
||||||
process.title = `Firefish (${cluster.isPrimary ? "master" : "worker"})`;
|
const mode =
|
||||||
|
process.env.mode && ["web", "queue"].includes(process.env.mode)
|
||||||
|
? `(${process.env.mode})`
|
||||||
|
: "";
|
||||||
|
const type = cluster.isPrimary ? "(master)" : "(worker)";
|
||||||
|
process.title = `Firefish ${mode} ${type}`;
|
||||||
|
|
||||||
if (cluster.isPrimary || envOption.disableClustering) {
|
if (cluster.isPrimary || envOption.disableClustering) {
|
||||||
await masterMain();
|
await masterMain();
|
||||||
|
|
|
@ -30,40 +30,34 @@ const themeColor = chalk.hex("#31748f");
|
||||||
function greet() {
|
function greet() {
|
||||||
if (!envOption.quiet) {
|
if (!envOption.quiet) {
|
||||||
//#region Firefish logo
|
//#region Firefish logo
|
||||||
const v = `v${meta.version}`;
|
|
||||||
console.log(
|
console.log(
|
||||||
themeColor(
|
themeColor(
|
||||||
" ▄▄▄▄▄▄▄ ▄▄▄ ▄▄▄▄▄▄ ▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄ ▄▄▄ ▄▄▄▄▄▄▄ ▄▄ ▄▄ ◯ ",
|
"██████╗ ██╗██████╗ ███████╗███████╗██╗███████╗██╗ ██╗ ○ ▄ ▄ ",
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
console.log(
|
console.log(
|
||||||
themeColor(
|
themeColor(
|
||||||
"█ █ █ ▄ █ █ █ █ █ █ █ █ █ ○ ▄ ▄",
|
"██╔════╝██║██╔══██╗██╔════╝██╔════╝██║██╔════╝██║ ██║ ⚬ █▄▄ █▄▄ ",
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
console.log(
|
console.log(
|
||||||
themeColor(
|
themeColor(
|
||||||
"█ ▄▄▄█ █ █ █ █ █ ▄▄▄█ ▄▄▄█ █ ▄▄▄▄▄█ █▄█ █ ⚬ █▄▄ █▄▄ ",
|
"█████╗ ██║██████╔╝█████╗ █████╗ ██║███████╗███████║ ▄▄▄▄▄▄ ▄ ",
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
console.log(
|
console.log(
|
||||||
themeColor(
|
themeColor(
|
||||||
"█ █▄▄▄█ █ █▄▄█▄█ █▄▄▄█ █▄▄▄█ █ █▄▄▄▄▄█ █ ▄▄▄▄▄▄ ▄",
|
"██╔══╝ ██║██╔══██╗██╔══╝ ██╔══╝ ██║╚════██║██╔══██║ █ █ █▄▄ ",
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
console.log(
|
console.log(
|
||||||
themeColor(
|
themeColor(
|
||||||
"█ ▄▄▄█ █ ▄▄ █ ▄▄▄█ ▄▄▄█ █▄▄▄▄▄ █ ▄ █ █ █ █▄▄",
|
"██║ ██║██║ ██║███████╗██║ ██║███████║██║ ██║ █ ● ● █ ",
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
console.log(
|
console.log(
|
||||||
themeColor(
|
themeColor(
|
||||||
"█ █ █ █ █ █ █ █▄▄▄█ █ █ █▄▄▄▄▄█ █ █ █ █ █ ● ● █",
|
"╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ▀▄▄▄▄▄▄▀ ",
|
||||||
),
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
themeColor(
|
|
||||||
"█▄▄▄█ █▄▄▄█▄▄▄█ █▄█▄▄▄▄▄▄▄█▄▄▄█ █▄▄▄█▄▄▄▄▄▄▄█▄▄█ █▄▄█ ▀▄▄▄▄▄▄▀",
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
//#endregion
|
//#endregion
|
||||||
|
@ -117,7 +111,7 @@ export async function masterMain() {
|
||||||
bootLogger.succ("Firefish initialized");
|
bootLogger.succ("Firefish initialized");
|
||||||
|
|
||||||
if (!envOption.disableClustering) {
|
if (!envOption.disableClustering) {
|
||||||
await spawnWorkers(config.clusterLimit);
|
await spawnWorkers(config.clusterLimits);
|
||||||
}
|
}
|
||||||
|
|
||||||
bootLogger.succ(
|
bootLogger.succ(
|
||||||
|
@ -126,7 +120,11 @@ export async function masterMain() {
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!envOption.noDaemons && !config.onlyQueueProcessor) {
|
if (
|
||||||
|
!envOption.noDaemons &&
|
||||||
|
config.clusterLimits?.web &&
|
||||||
|
config.clusterLimits?.web >= 1
|
||||||
|
) {
|
||||||
import("../daemons/server-stats.js").then((x) => x.default());
|
import("../daemons/server-stats.js").then((x) => x.default());
|
||||||
import("../daemons/queue-stats.js").then((x) => x.default());
|
import("../daemons/queue-stats.js").then((x) => x.default());
|
||||||
import("../daemons/janitor.js").then((x) => x.default());
|
import("../daemons/janitor.js").then((x) => x.default());
|
||||||
|
@ -142,7 +140,7 @@ function showEnvironment(): void {
|
||||||
|
|
||||||
if (env !== "production") {
|
if (env !== "production") {
|
||||||
logger.warn("The environment is not in production mode.");
|
logger.warn("The environment is not in production mode.");
|
||||||
logger.warn("DO NOT USE FOR PRODUCTION PURPOSE!", null, true);
|
logger.warn("DO NOT USE THIS IN PRODUCTION!", null, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,19 +198,35 @@ async function connectDb(): Promise<void> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function spawnWorkers(limit = 1) {
|
async function spawnWorkers(
|
||||||
const workers = Math.min(limit, os.cpus().length);
|
clusterLimits: Required<Config["clusterLimits"]>,
|
||||||
bootLogger.info(`Starting ${workers} worker${workers === 1 ? "" : "s"}...`);
|
): Promise<void> {
|
||||||
await Promise.all([...Array(workers)].map(spawnWorker));
|
const modes = ["web", "queue"];
|
||||||
|
const cpus = os.cpus().length;
|
||||||
|
for (const mode of modes.filter((mode) => clusterLimits[mode] > cpus)) {
|
||||||
|
bootLogger.warn(
|
||||||
|
`configuration warning: cluster limit for ${mode} exceeds number of cores (${cpus})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const total = modes.reduce((acc, mode) => acc + clusterLimits[mode], 0);
|
||||||
|
const workers = new Array(total);
|
||||||
|
workers.fill("web", 0, clusterLimits?.web);
|
||||||
|
workers.fill("queue", clusterLimits?.web);
|
||||||
|
|
||||||
|
bootLogger.info(
|
||||||
|
`Starting ${clusterLimits?.web} web workers and ${clusterLimits?.queue} queue workers (total ${total})...`,
|
||||||
|
);
|
||||||
|
await Promise.all(workers.map((mode) => spawnWorker(mode)));
|
||||||
bootLogger.succ("All workers started");
|
bootLogger.succ("All workers started");
|
||||||
}
|
}
|
||||||
|
|
||||||
function spawnWorker(): Promise<void> {
|
function spawnWorker(mode: "web" | "queue"): Promise<void> {
|
||||||
return new Promise((res) => {
|
return new Promise((res) => {
|
||||||
const worker = cluster.fork();
|
const worker = cluster.fork({ mode });
|
||||||
worker.on("message", (message) => {
|
worker.on("message", (message) => {
|
||||||
if (message === "listenFailed") {
|
if (message === "listenFailed") {
|
||||||
bootLogger.error("The server Listen failed due to the previous error.");
|
bootLogger.error("The server listen failed due to the previous error.");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
if (message !== "ready") return;
|
if (message !== "ready") return;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import cluster from "node:cluster";
|
import cluster from "node:cluster";
|
||||||
import { initDb } from "../db/postgre.js";
|
import { initDb } from "../db/postgre.js";
|
||||||
import config from "@/config/index.js";
|
import config from "@/config/index.js";
|
||||||
|
import os from "node:os";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Init worker process
|
* Init worker process
|
||||||
|
@ -8,13 +9,20 @@ import config from "@/config/index.js";
|
||||||
export async function workerMain() {
|
export async function workerMain() {
|
||||||
await initDb();
|
await initDb();
|
||||||
|
|
||||||
if (!config.onlyQueueProcessor) {
|
if (!process.env.mode || process.env.mode === "web") {
|
||||||
// start server
|
// start server
|
||||||
await import("../server/index.js").then((x) => x.default());
|
await import("../server/index.js").then((x) => x.default());
|
||||||
}
|
}
|
||||||
|
|
||||||
// start job queue
|
if (!process.env.mode || process.env.mode === "queue") {
|
||||||
import("../queue/index.js").then((x) => x.default());
|
// start job queue
|
||||||
|
import("../queue/index.js").then((x) => x.default());
|
||||||
|
|
||||||
|
if (process.env.mode === "queue") {
|
||||||
|
// if this is an exclusive queue worker, renice to have higher priority
|
||||||
|
os.setPriority(os.constants.priority.PRIORITY_BELOW_NORMAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (cluster.isWorker) {
|
if (cluster.isWorker) {
|
||||||
// Send a 'ready' message to parent process
|
// Send a 'ready' message to parent process
|
||||||
|
|
|
@ -59,6 +59,23 @@ export default function load() {
|
||||||
if (config.cacheServer && !config.cacheServer.prefix)
|
if (config.cacheServer && !config.cacheServer.prefix)
|
||||||
config.cacheServer.prefix = mixin.hostname;
|
config.cacheServer.prefix = mixin.hostname;
|
||||||
|
|
||||||
|
if (!config.clusterLimits) {
|
||||||
|
config.clusterLimits = {
|
||||||
|
web: 1,
|
||||||
|
queue: 1,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
config.clusterLimits = {
|
||||||
|
web: 1,
|
||||||
|
queue: 1,
|
||||||
|
...config.clusterLimits,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (config.clusterLimits.web! < 1 || config.clusterLimits.queue! < 1) {
|
||||||
|
throw new Error("Invalid cluster limits");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Object.assign(config, mixin);
|
return Object.assign(config, mixin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -69,9 +69,10 @@ export type Source = {
|
||||||
|
|
||||||
accesslog?: string;
|
accesslog?: string;
|
||||||
|
|
||||||
clusterLimit?: number;
|
clusterLimits?: {
|
||||||
|
web?: number;
|
||||||
onlyQueueProcessor?: boolean;
|
queue?: number;
|
||||||
|
};
|
||||||
|
|
||||||
cuid?: {
|
cuid?: {
|
||||||
length?: number;
|
length?: number;
|
||||||
|
|
|
@ -1,33 +1,49 @@
|
||||||
import { redisClient } from "../db/redis.js";
|
import { redisClient } from "../db/redis.js";
|
||||||
import { promisify } from "node:util";
|
import { Mutex } from "redis-semaphore";
|
||||||
import redisLock from "redis-lock";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retry delay (ms) for lock acquisition
|
* Retry delay (ms) for lock acquisition
|
||||||
*/
|
*/
|
||||||
const retryDelay = 100;
|
const retryDelay = 100;
|
||||||
|
|
||||||
const lock: (key: string, timeout?: number) => Promise<() => void> = redisClient
|
|
||||||
? promisify(redisLock(redisClient, retryDelay))
|
|
||||||
: async () => () => {};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get AP Object lock
|
* Get AP Object lock
|
||||||
* @param uri AP object ID
|
* @param uri AP object ID
|
||||||
* @param timeout Lock timeout (ms), The timeout releases previous lock.
|
* @param timeout Lock timeout (ms), The timeout releases previous lock.
|
||||||
* @returns Unlock function
|
* @returns Unlock function
|
||||||
*/
|
*/
|
||||||
export function getApLock(uri: string, timeout = 30 * 1000) {
|
export async function getApLock(
|
||||||
return lock(`ap-object:${uri}`, timeout);
|
uri: string,
|
||||||
|
timeout = 30 * 1000,
|
||||||
|
): Promise<Mutex> {
|
||||||
|
const lock = new Mutex(redisClient, `ap-object:${uri}`, {
|
||||||
|
lockTimeout: timeout,
|
||||||
|
retryInterval: retryDelay,
|
||||||
|
});
|
||||||
|
await lock.acquire();
|
||||||
|
return lock;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getFetchInstanceMetadataLock(
|
export async function getFetchInstanceMetadataLock(
|
||||||
host: string,
|
host: string,
|
||||||
timeout = 30 * 1000,
|
timeout = 30 * 1000,
|
||||||
) {
|
): Promise<Mutex> {
|
||||||
return lock(`instance:${host}`, timeout);
|
const lock = new Mutex(redisClient, `instance:${host}`, {
|
||||||
|
lockTimeout: timeout,
|
||||||
|
retryInterval: retryDelay,
|
||||||
|
});
|
||||||
|
await lock.acquire();
|
||||||
|
return lock;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getChartInsertLock(lockKey: string, timeout = 30 * 1000) {
|
export async function getChartInsertLock(
|
||||||
return lock(`chart-insert:${lockKey}`, timeout);
|
lockKey: string,
|
||||||
|
timeout = 30 * 1000,
|
||||||
|
): Promise<Mutex> {
|
||||||
|
const lock = new Mutex(redisClient, `chart-insert:${lockKey}`, {
|
||||||
|
lockTimeout: timeout,
|
||||||
|
retryInterval: retryDelay,
|
||||||
|
});
|
||||||
|
await lock.acquire();
|
||||||
|
return lock;
|
||||||
}
|
}
|
||||||
|
|
|
@ -167,6 +167,12 @@ export class UserProfile {
|
||||||
})
|
})
|
||||||
public noCrawle: boolean;
|
public noCrawle: boolean;
|
||||||
|
|
||||||
|
@Column("boolean", {
|
||||||
|
default: true,
|
||||||
|
comment: "Whether User is indexable.",
|
||||||
|
})
|
||||||
|
public isIndexable: boolean;
|
||||||
|
|
||||||
@Column("boolean", {
|
@Column("boolean", {
|
||||||
default: true,
|
default: true,
|
||||||
})
|
})
|
||||||
|
|
|
@ -278,6 +278,13 @@ export class User {
|
||||||
})
|
})
|
||||||
public driveCapacityOverrideMb: number | null;
|
public driveCapacityOverrideMb: number | null;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column("boolean", {
|
||||||
|
default: true,
|
||||||
|
comment: "Whether the User is indexable.",
|
||||||
|
})
|
||||||
|
public isIndexable: boolean;
|
||||||
|
|
||||||
constructor(data: Partial<User>) {
|
constructor(data: Partial<User>) {
|
||||||
if (data == null) return;
|
if (data == null) return;
|
||||||
|
|
||||||
|
|
|
@ -455,6 +455,7 @@ export const UserRepository = db.getRepository(User).extend({
|
||||||
isModerator: user.isModerator || falsy,
|
isModerator: user.isModerator || falsy,
|
||||||
isBot: user.isBot || falsy,
|
isBot: user.isBot || falsy,
|
||||||
isLocked: user.isLocked,
|
isLocked: user.isLocked,
|
||||||
|
isIndexable: user.isIndexable,
|
||||||
isCat: user.isCat || falsy,
|
isCat: user.isCat || falsy,
|
||||||
speakAsCat: user.speakAsCat || falsy,
|
speakAsCat: user.speakAsCat || falsy,
|
||||||
instance: user.host
|
instance: user.host
|
||||||
|
|
|
@ -66,6 +66,11 @@ export const packedUserLiteSchema = {
|
||||||
nullable: false,
|
nullable: false,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
|
isIndexable: {
|
||||||
|
type: "boolean",
|
||||||
|
nullable: false,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
speakAsCat: {
|
speakAsCat: {
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
nullable: false,
|
nullable: false,
|
||||||
|
|
|
@ -32,7 +32,7 @@ export default async function (
|
||||||
// Interrupt if you block the announcement destination
|
// Interrupt if you block the announcement destination
|
||||||
if (await shouldBlockInstance(extractDbHost(uri))) return;
|
if (await shouldBlockInstance(extractDbHost(uri))) return;
|
||||||
|
|
||||||
const unlock = await getApLock(uri);
|
const lock = await getApLock(uri);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Check if something with the same URI is already registered
|
// Check if something with the same URI is already registered
|
||||||
|
@ -60,9 +60,10 @@ export default async function (
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(await Notes.isVisibleForMe(renote, actor.id)))
|
if (renote != null && !(await Notes.isVisibleForMe(renote, actor.id))) {
|
||||||
return "skip: invalid actor for this activity";
|
console.log("skip: invalid actor for this activity");
|
||||||
|
return;
|
||||||
|
}
|
||||||
logger.info(`Creating the (Re)Note: ${uri}`);
|
logger.info(`Creating the (Re)Note: ${uri}`);
|
||||||
|
|
||||||
const activityAudience = await parseAudience(
|
const activityAudience = await parseAudience(
|
||||||
|
@ -79,6 +80,6 @@ export default async function (
|
||||||
uri,
|
uri,
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
unlock();
|
await lock.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ export default async function (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const unlock = await getApLock(uri);
|
const lock = await getApLock(uri);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const exist = await fetchNote(note);
|
const exist = await fetchNote(note);
|
||||||
|
@ -46,6 +46,6 @@ export default async function (
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
unlock();
|
await lock.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ export default async function (
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
logger.info(`Deleting the Note: ${uri}`);
|
logger.info(`Deleting the Note: ${uri}`);
|
||||||
|
|
||||||
const unlock = await getApLock(uri);
|
const lock = await getApLock(uri);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const dbResolver = new DbResolver();
|
const dbResolver = new DbResolver();
|
||||||
|
@ -39,6 +39,6 @@ export default async function (
|
||||||
await deleteNode(actor, note);
|
await deleteNode(actor, note);
|
||||||
return "ok: note deleted";
|
return "ok: note deleted";
|
||||||
} finally {
|
} finally {
|
||||||
unlock();
|
await lock.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,13 +68,13 @@ export class LdSignature {
|
||||||
...options,
|
...options,
|
||||||
"@context": "https://w3id.org/identity/v1",
|
"@context": "https://w3id.org/identity/v1",
|
||||||
};
|
};
|
||||||
transformedOptions.type = undefined;
|
delete transformedOptions["type"];
|
||||||
transformedOptions.id = undefined;
|
delete transformedOptions["id"];
|
||||||
transformedOptions.signatureValue = undefined;
|
delete transformedOptions["signatureValue"];
|
||||||
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 };
|
||||||
transformedData.signature = undefined;
|
delete transformedData["signature"];
|
||||||
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);
|
||||||
|
|
|
@ -415,7 +415,7 @@ export async function resolveNote(
|
||||||
`host ${extractDbHost(uri)} is blocked`,
|
`host ${extractDbHost(uri)} is blocked`,
|
||||||
);
|
);
|
||||||
|
|
||||||
const unlock = await getApLock(uri);
|
const lock = await getApLock(uri);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
//#region Returns if already registered with this server
|
//#region Returns if already registered with this server
|
||||||
|
@ -439,7 +439,7 @@ export async function resolveNote(
|
||||||
// Since the attached Note Object may be disguised, always specify the uri and fetch it from the server.
|
// Since the attached Note Object may be disguised, always specify the uri and fetch it from the server.
|
||||||
return await createNote(uri, resolver, true);
|
return await createNote(uri, resolver, true);
|
||||||
} finally {
|
} finally {
|
||||||
unlock();
|
await lock.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -205,10 +205,10 @@ export async function createPerson(
|
||||||
|
|
||||||
if (typeof person.followers === "string") {
|
if (typeof person.followers === "string") {
|
||||||
try {
|
try {
|
||||||
let data = await fetch(person.followers, {
|
const data = await fetch(person.followers, {
|
||||||
headers: { Accept: "application/json" },
|
headers: { Accept: "application/json" },
|
||||||
});
|
});
|
||||||
let json_data = JSON.parse(await data.text());
|
const json_data = JSON.parse(await data.text());
|
||||||
|
|
||||||
followersCount = json_data.totalItems;
|
followersCount = json_data.totalItems;
|
||||||
} catch {
|
} catch {
|
||||||
|
@ -220,10 +220,10 @@ export async function createPerson(
|
||||||
|
|
||||||
if (typeof person.following === "string") {
|
if (typeof person.following === "string") {
|
||||||
try {
|
try {
|
||||||
let data = await fetch(person.following, {
|
const data = await fetch(person.following, {
|
||||||
headers: { Accept: "application/json" },
|
headers: { Accept: "application/json" },
|
||||||
});
|
});
|
||||||
let json_data = JSON.parse(await data.text());
|
const json_data = JSON.parse(await data.text());
|
||||||
|
|
||||||
followingCount = json_data.totalItems;
|
followingCount = json_data.totalItems;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -235,10 +235,10 @@ export async function createPerson(
|
||||||
|
|
||||||
if (typeof person.outbox === "string") {
|
if (typeof person.outbox === "string") {
|
||||||
try {
|
try {
|
||||||
let data = await fetch(person.outbox, {
|
const data = await fetch(person.outbox, {
|
||||||
headers: { Accept: "application/json" },
|
headers: { Accept: "application/json" },
|
||||||
});
|
});
|
||||||
let json_data = JSON.parse(await data.text());
|
const json_data = JSON.parse(await data.text());
|
||||||
|
|
||||||
notesCount = json_data.totalItems;
|
notesCount = json_data.totalItems;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -302,6 +302,7 @@ export async function createPerson(
|
||||||
tags,
|
tags,
|
||||||
isBot,
|
isBot,
|
||||||
isCat: (person as any).isCat === true,
|
isCat: (person as any).isCat === true,
|
||||||
|
isIndexable: person.indexable,
|
||||||
}),
|
}),
|
||||||
)) as IRemoteUser;
|
)) as IRemoteUser;
|
||||||
|
|
||||||
|
@ -547,6 +548,7 @@ export async function updatePerson(
|
||||||
tags,
|
tags,
|
||||||
isBot: getApType(object) !== "Person",
|
isBot: getApType(object) !== "Person",
|
||||||
isCat: (person as any).isCat === true,
|
isCat: (person as any).isCat === true,
|
||||||
|
isIndexable: person.indexable,
|
||||||
isLocked: !!person.manuallyApprovesFollowers,
|
isLocked: !!person.manuallyApprovesFollowers,
|
||||||
movedToUri: person.movedTo || null,
|
movedToUri: person.movedTo || null,
|
||||||
alsoKnownAs: person.alsoKnownAs || null,
|
alsoKnownAs: person.alsoKnownAs || null,
|
||||||
|
|
|
@ -30,6 +30,7 @@ export const renderActivity = (x: any): IActivity | null => {
|
||||||
Emoji: "toot:Emoji",
|
Emoji: "toot:Emoji",
|
||||||
featured: "toot:featured",
|
featured: "toot:featured",
|
||||||
discoverable: "toot:discoverable",
|
discoverable: "toot:discoverable",
|
||||||
|
indexable: "toot:indexable",
|
||||||
// schema
|
// schema
|
||||||
schema: "http://schema.org#",
|
schema: "http://schema.org#",
|
||||||
PropertyValue: "schema:PropertyValue",
|
PropertyValue: "schema:PropertyValue",
|
||||||
|
|
|
@ -81,6 +81,7 @@ export async function renderPerson(user: ILocalUser) {
|
||||||
discoverable: !!user.isExplorable,
|
discoverable: !!user.isExplorable,
|
||||||
publicKey: renderKey(user, keypair, "#main-key"),
|
publicKey: renderKey(user, keypair, "#main-key"),
|
||||||
isCat: user.isCat,
|
isCat: user.isCat,
|
||||||
|
indexable: user.isIndexable,
|
||||||
attachment: attachment.length ? attachment : undefined,
|
attachment: attachment.length ? attachment : undefined,
|
||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
|
|
|
@ -190,8 +190,9 @@ export interface IActor extends IObject {
|
||||||
movedTo?: string;
|
movedTo?: string;
|
||||||
alsoKnownAs?: string[];
|
alsoKnownAs?: string[];
|
||||||
discoverable?: boolean;
|
discoverable?: boolean;
|
||||||
|
indexable?: boolean;
|
||||||
inbox: string;
|
inbox: string;
|
||||||
sharedInbox?: string; // backward compatibility.. ig
|
sharedInbox?: string; // Backwards compatibility
|
||||||
publicKey?: {
|
publicKey?: {
|
||||||
id: string;
|
id: string;
|
||||||
publicKeyPem: string;
|
publicKeyPem: string;
|
||||||
|
|
|
@ -60,6 +60,7 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
emailVerified: profile.emailVerified,
|
emailVerified: profile.emailVerified,
|
||||||
autoAcceptFollowed: profile.autoAcceptFollowed,
|
autoAcceptFollowed: profile.autoAcceptFollowed,
|
||||||
noCrawle: profile.noCrawle,
|
noCrawle: profile.noCrawle,
|
||||||
|
isIndexable: profile.isIndexable,
|
||||||
preventAiLearning: profile.preventAiLearning,
|
preventAiLearning: profile.preventAiLearning,
|
||||||
alwaysMarkNsfw: profile.alwaysMarkNsfw,
|
alwaysMarkNsfw: profile.alwaysMarkNsfw,
|
||||||
autoSensitive: profile.autoSensitive,
|
autoSensitive: profile.autoSensitive,
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { promisify } from "node:util";
|
import { decode } from "msgpackr";
|
||||||
import * as cbor from "cbor";
|
|
||||||
import define from "../../../define.js";
|
import define from "../../../define.js";
|
||||||
import {
|
import {
|
||||||
UserProfiles,
|
UserProfiles,
|
||||||
|
@ -12,7 +11,6 @@ import { procedures, hash } from "../../../2fa.js";
|
||||||
import { publishMainStream } from "@/services/stream.js";
|
import { publishMainStream } from "@/services/stream.js";
|
||||||
import { comparePassword } from "@/misc/password.js";
|
import { comparePassword } from "@/misc/password.js";
|
||||||
|
|
||||||
const cborDecodeFirst = promisify(cbor.decodeFirst) as any;
|
|
||||||
const rpIdHashReal = hash(Buffer.from(config.hostname, "utf-8"));
|
const rpIdHashReal = hash(Buffer.from(config.hostname, "utf-8"));
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
|
@ -64,7 +62,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
|
|
||||||
const clientDataJSONHash = hash(Buffer.from(ps.clientDataJSON, "utf-8"));
|
const clientDataJSONHash = hash(Buffer.from(ps.clientDataJSON, "utf-8"));
|
||||||
|
|
||||||
const attestation = await cborDecodeFirst(ps.attestationObject);
|
const attestation = decode(Buffer.from(ps.attestationObject, "utf-8"));
|
||||||
|
|
||||||
const rpIdHash = attestation.authData.slice(0, 32);
|
const rpIdHash = attestation.authData.slice(0, 32);
|
||||||
if (!rpIdHashReal.equals(rpIdHash)) {
|
if (!rpIdHashReal.equals(rpIdHash)) {
|
||||||
|
@ -81,7 +79,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
const credentialIdLength = authData.readUInt16BE(53);
|
const credentialIdLength = authData.readUInt16BE(53);
|
||||||
const credentialId = authData.slice(55, 55 + credentialIdLength);
|
const credentialId = authData.slice(55, 55 + credentialIdLength);
|
||||||
const publicKeyData = authData.slice(55 + credentialIdLength);
|
const publicKeyData = authData.slice(55 + credentialIdLength);
|
||||||
const publicKey: Map<number, any> = await cborDecodeFirst(publicKeyData);
|
const publicKey: Map<number, any> = decode(publicKeyData);
|
||||||
if (publicKey.get(3) !== -7) {
|
if (publicKey.get(3) !== -7) {
|
||||||
throw new Error("alg mismatch");
|
throw new Error("alg mismatch");
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,6 +120,7 @@ export const paramDef = {
|
||||||
isBot: { type: "boolean" },
|
isBot: { type: "boolean" },
|
||||||
isCat: { type: "boolean" },
|
isCat: { type: "boolean" },
|
||||||
speakAsCat: { type: "boolean" },
|
speakAsCat: { type: "boolean" },
|
||||||
|
isIndexable: { type: "boolean" },
|
||||||
injectFeaturedNote: { type: "boolean" },
|
injectFeaturedNote: { type: "boolean" },
|
||||||
receiveAnnouncementEmail: { type: "boolean" },
|
receiveAnnouncementEmail: { type: "boolean" },
|
||||||
alwaysMarkNsfw: { type: "boolean" },
|
alwaysMarkNsfw: { type: "boolean" },
|
||||||
|
@ -206,6 +207,10 @@ export default define(meta, paramDef, async (ps, _user, token) => {
|
||||||
if (typeof ps.preventAiLearning === "boolean")
|
if (typeof ps.preventAiLearning === "boolean")
|
||||||
profileUpdates.preventAiLearning = ps.preventAiLearning;
|
profileUpdates.preventAiLearning = ps.preventAiLearning;
|
||||||
if (typeof ps.isCat === "boolean") updates.isCat = ps.isCat;
|
if (typeof ps.isCat === "boolean") updates.isCat = ps.isCat;
|
||||||
|
if (typeof ps.isIndexable === "boolean") {
|
||||||
|
updates.isIndexable = ps.isIndexable;
|
||||||
|
profileUpdates.isIndexable = ps.isIndexable;
|
||||||
|
}
|
||||||
if (typeof ps.speakAsCat === "boolean") updates.speakAsCat = ps.speakAsCat;
|
if (typeof ps.speakAsCat === "boolean") updates.speakAsCat = ps.speakAsCat;
|
||||||
if (typeof ps.injectFeaturedNote === "boolean")
|
if (typeof ps.injectFeaturedNote === "boolean")
|
||||||
profileUpdates.injectFeaturedNote = ps.injectFeaturedNote;
|
profileUpdates.injectFeaturedNote = ps.injectFeaturedNote;
|
||||||
|
|
|
@ -608,7 +608,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
throw new ApiError(meta.errors.noSuchNote);
|
throw new ApiError(meta.errors.noSuchNote);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (publishing) {
|
if (publishing && user.isIndexable) {
|
||||||
index(note, true);
|
index(note, true);
|
||||||
|
|
||||||
// Publish update event for the updated note details
|
// Publish update event for the updated note details
|
||||||
|
|
|
@ -4,7 +4,6 @@ import config from "@/config/index.js";
|
||||||
import { Converter } from "opencc-js";
|
import { Converter } from "opencc-js";
|
||||||
import { getAgentByUrl } from "@/misc/fetch.js";
|
import { getAgentByUrl } from "@/misc/fetch.js";
|
||||||
import { fetchMeta } from "@/misc/fetch-meta.js";
|
import { fetchMeta } from "@/misc/fetch-meta.js";
|
||||||
import { Notes } from "@/models/index.js";
|
|
||||||
import { ApiError } from "../../error.js";
|
import { ApiError } from "../../error.js";
|
||||||
import { getNote } from "../../common/getters.js";
|
import { getNote } from "../../common/getters.js";
|
||||||
import define from "../../define.js";
|
import define from "../../define.js";
|
||||||
|
@ -12,7 +11,7 @@ import define from "../../define.js";
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ["notes"],
|
tags: ["notes"],
|
||||||
|
|
||||||
requireCredential: false,
|
requireCredential: true,
|
||||||
requireCredentialPrivateMode: true,
|
requireCredentialPrivateMode: true,
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
|
|
|
@ -3,10 +3,11 @@
|
||||||
"name": "Firefish",
|
"name": "Firefish",
|
||||||
"description": "An open source, decentralized social media platform that's free forever!",
|
"description": "An open source, decentralized social media platform that's free forever!",
|
||||||
"start_url": "/",
|
"start_url": "/",
|
||||||
|
"scope": "/",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"background_color": "#1f1d2e",
|
"background_color": "#1f1d2e",
|
||||||
"theme_color": "#31748f",
|
"theme_color": "#31748f",
|
||||||
"orientation": "portrait-primary",
|
"orientation": "natural",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "/static-assets/icons/192.png",
|
"src": "/static-assets/icons/192.png",
|
||||||
|
|
|
@ -11,7 +11,6 @@ export const manifestHandler = async (ctx: Koa.Context) => {
|
||||||
const instance = await fetchMeta(true);
|
const instance = await fetchMeta(true);
|
||||||
|
|
||||||
res.short_name = instance.name || "Firefish";
|
res.short_name = instance.name || "Firefish";
|
||||||
res.name = instance.name || "Firefish";
|
|
||||||
if (instance.themeColor) res.theme_color = instance.themeColor;
|
if (instance.themeColor) res.theme_color = instance.themeColor;
|
||||||
for (const icon of res.icons) {
|
for (const icon of res.icons) {
|
||||||
icon.src = `${icon.src}?v=${config.version.replace(/[^0-9]/g, "")}`;
|
icon.src = `${icon.src}?v=${config.version.replace(/[^0-9]/g, "")}`;
|
||||||
|
|
|
@ -7,16 +7,16 @@ doctype html
|
||||||
|
|
||||||
//
|
//
|
||||||
-
|
-
|
||||||
▄▄▄▄▄▄▄ ▄▄▄ ▄▄▄▄▄▄ ▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄ ▄▄▄ ▄▄▄▄▄▄▄ ▄▄ ▄▄ ◯
|
|
||||||
█ █ █ ▄ █ █ █ █ █ █ █ █ █ ○ ▄ ▄
|
██████╗ ██╗██████╗ ███████╗███████╗██╗███████╗██╗ ██╗ ○ ▄ ▄
|
||||||
█ ▄▄▄█ █ █ █ █ █ ▄▄▄█ ▄▄▄█ █ ▄▄▄▄▄█ █▄█ █ ⚬ █▄▄ █▄▄
|
██╔════╝██║██╔══██╗██╔════╝██╔════╝██║██╔════╝██║ ██║ ⚬ █▄▄ █▄▄
|
||||||
█ █▄▄▄█ █ █▄▄█▄█ █▄▄▄█ █▄▄▄█ █ █▄▄▄▄▄█ █ ▄▄▄▄▄▄ ▄
|
█████╗ ██║██████╔╝█████╗ █████╗ ██║███████╗███████║ ▄▄▄▄▄▄ ▄
|
||||||
█ ▄▄▄█ █ ▄▄ █ ▄▄▄█ ▄▄▄█ █▄▄▄▄▄ █ ▄ █ █ █ █▄▄
|
██╔══╝ ██║██╔══██╗██╔══╝ ██╔══╝ ██║╚════██║██╔══██║ █ █ █▄▄
|
||||||
█ █ █ █ █ █ █ █▄▄▄█ █ █ █▄▄▄▄▄█ █ █ █ █ █ ● ● █
|
██║ ██║██║ ██║███████╗██║ ██║███████║██║ ██║ █ ● ● █
|
||||||
█▄▄▄█ █▄▄▄█▄▄▄█ █▄█▄▄▄▄▄▄▄█▄▄▄█ █▄▄▄█▄▄▄▄▄▄▄█▄▄█ █▄▄█ ▀▄▄▄▄▄▄▀
|
╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ▀▄▄▄▄▄▄▀
|
||||||
|
|
||||||
Thank you for using Firefish!
|
Thank you for using Firefish!
|
||||||
If you are reading this message... how about joining the development?
|
If you're reading this message... how about helping out with development?
|
||||||
https://git.joinfirefish.org/firefish/firefish
|
https://git.joinfirefish.org/firefish/firefish
|
||||||
|
|
||||||
html
|
html
|
||||||
|
|
|
@ -24,9 +24,11 @@ block meta
|
||||||
unless privateMode
|
unless privateMode
|
||||||
if profile.noCrawle
|
if profile.noCrawle
|
||||||
meta(name='robots' content='noindex,nofollow,noarchive,nocache,noimageindex')
|
meta(name='robots' content='noindex,nofollow,noarchive,nocache,noimageindex')
|
||||||
|
|
||||||
if profile.preventAiLearning
|
if profile.preventAiLearning
|
||||||
meta(name='robots' content='noai')
|
meta(name='robots' content='noai')
|
||||||
meta(name='robots' content='noimageai')
|
meta(name='robots' content='noimageai')
|
||||||
|
meta(name='GPTBot' content='noindex')
|
||||||
|
|
||||||
meta(name='misskey:user-username' content=user.username)
|
meta(name='misskey:user-username' content=user.username)
|
||||||
meta(name='misskey:user-id' content=user.id)
|
meta(name='misskey:user-id' content=user.id)
|
||||||
|
|
|
@ -430,7 +430,7 @@ export default abstract class Chart<T extends Schema> {
|
||||||
? `${this.name}:${date}:${span}:${group}`
|
? `${this.name}:${date}:${span}:${group}`
|
||||||
: `${this.name}:${date}:${span}`;
|
: `${this.name}:${date}:${span}`;
|
||||||
|
|
||||||
const unlock = await getChartInsertLock(lockKey);
|
const lock = await getChartInsertLock(lockKey);
|
||||||
try {
|
try {
|
||||||
// ロック内でもう1回チェックする
|
// ロック内でもう1回チェックする
|
||||||
const currentLog = (await repository.findOneBy({
|
const currentLog = (await repository.findOneBy({
|
||||||
|
@ -466,7 +466,7 @@ export default abstract class Chart<T extends Schema> {
|
||||||
|
|
||||||
return log;
|
return log;
|
||||||
} finally {
|
} finally {
|
||||||
unlock();
|
await lock.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ export async function fetchInstanceMetadata(
|
||||||
instance: Instance,
|
instance: Instance,
|
||||||
force = false,
|
force = false,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const unlock = await getFetchInstanceMetadataLock(instance.host);
|
const lock = await getFetchInstanceMetadataLock(instance.host);
|
||||||
|
|
||||||
if (!force) {
|
if (!force) {
|
||||||
const _instance = await Instances.findOneBy({ host: instance.host });
|
const _instance = await Instances.findOneBy({ host: instance.host });
|
||||||
|
@ -24,7 +24,7 @@ export async function fetchInstanceMetadata(
|
||||||
_instance?.infoUpdatedAt &&
|
_instance?.infoUpdatedAt &&
|
||||||
now - _instance.infoUpdatedAt.getTime() < 1000 * 60 * 60 * 24
|
now - _instance.infoUpdatedAt.getTime() < 1000 * 60 * 60 * 24
|
||||||
) {
|
) {
|
||||||
unlock();
|
await lock.release();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ export async function fetchInstanceMetadata(
|
||||||
} as Record<string, any>;
|
} as Record<string, any>;
|
||||||
|
|
||||||
if (info) {
|
if (info) {
|
||||||
updates.softwareName = info.software?.name.toLowerCase();
|
updates.softwareName = info.software?.name?.toLowerCase() || null;
|
||||||
updates.softwareVersion = info.software?.version;
|
updates.softwareVersion = info.software?.version;
|
||||||
updates.openRegistrations = info.openRegistrations;
|
updates.openRegistrations = info.openRegistrations;
|
||||||
updates.maintainerName = info.metadata
|
updates.maintainerName = info.metadata
|
||||||
|
@ -80,24 +80,24 @@ export async function fetchInstanceMetadata(
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error(`Failed to update metadata of ${instance.host}: ${e}`);
|
logger.error(`Failed to update metadata of ${instance.host}: ${e}`);
|
||||||
} finally {
|
} finally {
|
||||||
unlock();
|
await lock.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type NodeInfo = {
|
type NodeInfo = {
|
||||||
openRegistrations?: any;
|
openRegistrations?: boolean;
|
||||||
software?: {
|
software?: {
|
||||||
name?: any;
|
name?: string;
|
||||||
version?: any;
|
version?: string;
|
||||||
};
|
};
|
||||||
metadata?: {
|
metadata?: {
|
||||||
name?: any;
|
name?: string;
|
||||||
nodeName?: any;
|
nodeName?: string;
|
||||||
nodeDescription?: any;
|
nodeDescription?: string;
|
||||||
description?: any;
|
description?: string;
|
||||||
maintainer?: {
|
maintainer?: {
|
||||||
name?: any;
|
name?: string;
|
||||||
email?: any;
|
email?: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -165,6 +165,7 @@ export default async (
|
||||||
createdAt: User["createdAt"];
|
createdAt: User["createdAt"];
|
||||||
isBot: User["isBot"];
|
isBot: User["isBot"];
|
||||||
inbox?: User["inbox"];
|
inbox?: User["inbox"];
|
||||||
|
isIndexable?: User["isIndexable"];
|
||||||
},
|
},
|
||||||
data: Option,
|
data: Option,
|
||||||
silent = false,
|
silent = false,
|
||||||
|
@ -652,7 +653,9 @@ export default async (
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register to search database
|
// Register to search database
|
||||||
await index(note, false);
|
if (user.isIndexable) {
|
||||||
|
await index(note, false);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
async function renderNoteOrRenoteActivity(data: Option, note: Note) {
|
async function renderNoteOrRenoteActivity(data: Option, note: Note) {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"extends": ["@eslint-sets/vue3", "@eslint-sets/vue3-ts"],
|
"extends": ["@eslint-sets/vue3", "@eslint-sets/vue3-ts"],
|
||||||
"plugins": ["file-progress", "prettier"],
|
"plugins": ["file-progress", "prettier"],
|
||||||
|
"ignorePatterns": ["**/*.json5"],
|
||||||
"rules": {
|
"rules": {
|
||||||
"file-progress/activate": 1
|
"file-progress/activate": 1
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,9 @@
|
||||||
"watch": "pnpm vite build --watch --mode development",
|
"watch": "pnpm vite build --watch --mode development",
|
||||||
"build": "pnpm vite build",
|
"build": "pnpm vite build",
|
||||||
"build:debug": "pnpm run build",
|
"build:debug": "pnpm run build",
|
||||||
"lint": "pnpm rome check **/*.ts --apply && pnpm run lint:vue",
|
"lint": "pnpm biome check **/*.ts --apply ; pnpm run lint:vue",
|
||||||
"lint:vue": "pnpm paralint --ext .vue --fix '**/*.vue' --cache",
|
"lint:vue": "pnpm eslint src --fix '**/*.vue' --cache ; pnpm run format",
|
||||||
"format": "pnpm rome format * --write && pnpm prettier --write '**/*.{scss,vue}' --cache --cache-strategy metadata"
|
"format": "pnpm biome format * --write && pnpm prettier --write '**/*.{scss,vue}' --cache --cache-strategy metadata"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@discordapp/twemoji": "14.1.2",
|
"@discordapp/twemoji": "14.1.2",
|
||||||
|
@ -16,7 +16,7 @@
|
||||||
"@phosphor-icons/web": "^2.0.3",
|
"@phosphor-icons/web": "^2.0.3",
|
||||||
"@rollup/plugin-alias": "5.0.0",
|
"@rollup/plugin-alias": "5.0.0",
|
||||||
"@rollup/plugin-json": "6.0.0",
|
"@rollup/plugin-json": "6.0.0",
|
||||||
"@rollup/pluginutils": "^5.0.3",
|
"@rollup/pluginutils": "^5.0.4",
|
||||||
"@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",
|
||||||
|
@ -28,15 +28,15 @@
|
||||||
"@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": "9.0.2",
|
"@types/uuid": "9.0.3",
|
||||||
"@vitejs/plugin-vue": "4.3.1",
|
"@vitejs/plugin-vue": "4.3.4",
|
||||||
"@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.2.0",
|
"broadcast-channel": "5.3.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.4.0",
|
||||||
"chartjs-adapter-date-fns": "3.0.0",
|
"chartjs-adapter-date-fns": "3.0.0",
|
||||||
"chartjs-chart-matrix": "^2.0.1",
|
"chartjs-chart-matrix": "^2.0.1",
|
||||||
"chartjs-plugin-gradient": "0.6.1",
|
"chartjs-plugin-gradient": "0.6.1",
|
||||||
|
@ -62,37 +62,36 @@
|
||||||
"katex": "0.16.8",
|
"katex": "0.16.8",
|
||||||
"matter-js": "0.19.0",
|
"matter-js": "0.19.0",
|
||||||
"mfm-js": "0.23.3",
|
"mfm-js": "0.23.3",
|
||||||
"paralint": "^1.2.1",
|
"photoswipe": "5.3.9",
|
||||||
"photoswipe": "5.3.8",
|
"prettier": "3.0.3",
|
||||||
"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",
|
||||||
"rndstr": "1.0.0",
|
"rndstr": "1.0.0",
|
||||||
"rollup": "3.28.0",
|
"rollup": "3.28.1",
|
||||||
"s-age": "1.1.2",
|
"s-age": "1.1.2",
|
||||||
"sass": "1.66.0",
|
"sass": "1.66.1",
|
||||||
"seedrandom": "3.0.5",
|
"seedrandom": "3.0.5",
|
||||||
"strict-event-emitter-types": "2.0.0",
|
"strict-event-emitter-types": "2.0.0",
|
||||||
"stringz": "2.1.0",
|
"stringz": "2.1.0",
|
||||||
"swiper": "10.2.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.155.0",
|
"three": "0.156.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",
|
||||||
"tsc-alias": "1.8.7",
|
"tsc-alias": "1.8.7",
|
||||||
"tsconfig-paths": "4.2.0",
|
"tsconfig-paths": "4.2.0",
|
||||||
"twemoji-parser": "14.0.0",
|
"twemoji-parser": "14.0.0",
|
||||||
"typescript": "5.1.6",
|
"typescript": "5.2.2",
|
||||||
"unicode-emoji-json": "^0.4.0",
|
"unicode-emoji-json": "^0.4.0",
|
||||||
"uuid": "9.0.0",
|
"uuid": "9.0.0",
|
||||||
"vanilla-tilt": "1.8.1",
|
"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.5",
|
"vue-draggable-plus": "^0.2.6",
|
||||||
"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"
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { defineAsyncComponent, reactive } from "vue";
|
import { defineAsyncComponent, reactive } from "vue";
|
||||||
import * as misskey from "firefish-js";
|
import type * as misskey from "firefish-js";
|
||||||
import { i18n } from "./i18n";
|
import { i18n } from "./i18n";
|
||||||
import { del, get, set } from "@/scripts/idb-proxy";
|
import { del, get, set } from "@/scripts/idb-proxy";
|
||||||
import { apiUrl } from "@/config";
|
import { apiUrl } from "@/config";
|
||||||
import { waiting, api, popup, popupMenu, success, alert } from "@/os";
|
import { alert, api, popup, popupMenu, success, waiting } from "@/os";
|
||||||
import { unisonReload, reloadChannel } from "@/scripts/unison-reload";
|
import { reloadChannel, unisonReload } from "@/scripts/unison-reload";
|
||||||
|
|
||||||
// TODO: 他のタブと永続化されたstateを同期
|
// TODO: 他のタブと永続化されたstateを同期
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ export async function signout() {
|
||||||
|
|
||||||
const accounts = await getAccounts();
|
const accounts = await getAccounts();
|
||||||
|
|
||||||
//#region Remove service worker registration
|
// #region Remove service worker registration
|
||||||
try {
|
try {
|
||||||
if (navigator.serviceWorker.controller) {
|
if (navigator.serviceWorker.controller) {
|
||||||
const registration = await navigator.serviceWorker.ready;
|
const registration = await navigator.serviceWorker.ready;
|
||||||
|
@ -53,7 +53,7 @@ export async function signout() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
//#endregion
|
// #endregion
|
||||||
|
|
||||||
document.cookie = "igi=; path=/";
|
document.cookie = "igi=; path=/";
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,10 @@
|
||||||
<div :class="$style.time">
|
<div :class="$style.time">
|
||||||
<MkTime :time="announcement.createdAt" />
|
<MkTime :time="announcement.createdAt" />
|
||||||
<div v-if="announcement.updatedAt">
|
<div v-if="announcement.updatedAt">
|
||||||
{{ i18n.ts.updatedAt }}:
|
<small>
|
||||||
<MkTime :time="announcement.createdAt" />
|
{{ i18n.ts.updatedAt }}:
|
||||||
|
<MkTime :time="announcement.createdAt" />
|
||||||
|
</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Mfm :text="text" />
|
<Mfm :text="text" />
|
||||||
|
@ -80,6 +82,6 @@ const gotIt = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
.gotIt {
|
.gotIt {
|
||||||
margin: 8px 0 0 0;
|
margin: 1rem 0 1rem 2rem;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -199,7 +199,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onBeforeUnmount, onMounted, ref, shallowRef, computed } from "vue";
|
import { computed, onBeforeUnmount, onMounted, ref, shallowRef } 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,7 +281,9 @@ 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">(null);
|
const disabledReason = ref<null | "charactersExceeded" | "charactersBelow">(
|
||||||
|
null,
|
||||||
|
);
|
||||||
const okButtonDisabled = computed<boolean>(() => {
|
const okButtonDisabled = computed<boolean>(() => {
|
||||||
if (props.input) {
|
if (props.input) {
|
||||||
if (props.input.minLength) {
|
if (props.input.minLength) {
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, defineAsyncComponent, ref } from "vue";
|
import { computed, defineAsyncComponent, ref } from "vue";
|
||||||
import * as Misskey from "firefish-js";
|
import type * as Misskey from "firefish-js";
|
||||||
import copyToClipboard from "@/scripts/copy-to-clipboard";
|
import copyToClipboard from "@/scripts/copy-to-clipboard";
|
||||||
import MkDriveFileThumbnail from "@/components/MkDriveFileThumbnail.vue";
|
import MkDriveFileThumbnail from "@/components/MkDriveFileThumbnail.vue";
|
||||||
import bytes from "@/filters/bytes";
|
import bytes from "@/filters/bytes";
|
||||||
|
@ -160,7 +160,7 @@ function rename() {
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
os.api("drive/files/update", {
|
os.api("drive/files/update", {
|
||||||
fileId: props.file.id,
|
fileId: props.file.id,
|
||||||
name: name,
|
name,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -179,7 +179,7 @@ function describe() {
|
||||||
{
|
{
|
||||||
done: (result) => {
|
done: (result) => {
|
||||||
if (!result || result.canceled) return;
|
if (!result || result.canceled) return;
|
||||||
let comment = result.result;
|
const comment = result.result;
|
||||||
os.api("drive/files/update", {
|
os.api("drive/files/update", {
|
||||||
fileId: props.file.id,
|
fileId: props.file.id,
|
||||||
comment: comment.length === 0 ? null : comment,
|
comment: comment.length === 0 ? null : comment,
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, defineAsyncComponent, ref } from "vue";
|
import { computed, defineAsyncComponent, ref } from "vue";
|
||||||
import * as Misskey from "firefish-js";
|
import type * as Misskey from "firefish-js";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { defaultStore } from "@/store";
|
import { defaultStore } from "@/store";
|
||||||
|
@ -131,7 +131,7 @@ function onDrop(ev: DragEvent) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//#region ドライブのファイル
|
// #region ドライブのファイル
|
||||||
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);
|
||||||
|
@ -141,9 +141,9 @@ function onDrop(ev: DragEvent) {
|
||||||
folderId: props.folder.id,
|
folderId: props.folder.id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
//#endregion
|
// #endregion
|
||||||
|
|
||||||
//#region ドライブのフォルダ
|
// #region ドライブのフォルダ
|
||||||
const driveFolder = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_);
|
const driveFolder = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_);
|
||||||
if (driveFolder != null && driveFolder !== "") {
|
if (driveFolder != null && driveFolder !== "") {
|
||||||
const folder = JSON.parse(driveFolder);
|
const folder = JSON.parse(driveFolder);
|
||||||
|
@ -175,7 +175,7 @@ function onDrop(ev: DragEvent) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
//#endregion
|
// #endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
function onDragstart(ev: DragEvent) {
|
function onDragstart(ev: DragEvent) {
|
||||||
|
@ -207,7 +207,7 @@ function rename() {
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
os.api("drive/folders/update", {
|
os.api("drive/folders/update", {
|
||||||
folderId: props.folder.id,
|
folderId: props.folder.id,
|
||||||
name: name,
|
name,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import * as Misskey from "firefish-js";
|
import type * as Misskey from "firefish-js";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
|
||||||
|
@ -86,7 +86,7 @@ function onDrop(ev: DragEvent) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//#region ドライブのファイル
|
// #region ドライブのファイル
|
||||||
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);
|
||||||
|
@ -96,9 +96,9 @@ function onDrop(ev: DragEvent) {
|
||||||
folderId: props.folder ? props.folder.id : null,
|
folderId: props.folder ? props.folder.id : null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
//#endregion
|
// #endregion
|
||||||
|
|
||||||
//#region ドライブのフォルダ
|
// #region ドライブのフォルダ
|
||||||
const driveFolder = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_);
|
const driveFolder = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_);
|
||||||
if (driveFolder != null && driveFolder !== "") {
|
if (driveFolder != null && driveFolder !== "") {
|
||||||
const folder = JSON.parse(driveFolder);
|
const folder = JSON.parse(driveFolder);
|
||||||
|
@ -110,7 +110,7 @@ function onDrop(ev: DragEvent) {
|
||||||
parentId: props.folder ? props.folder.id : null,
|
parentId: props.folder ? props.folder.id : null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
//#endregion
|
// #endregion
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -139,7 +139,7 @@ import {
|
||||||
ref,
|
ref,
|
||||||
watch,
|
watch,
|
||||||
} from "vue";
|
} from "vue";
|
||||||
import * as Misskey from "firefish-js";
|
import type * as Misskey from "firefish-js";
|
||||||
import MkButton from "./MkButton.vue";
|
import MkButton from "./MkButton.vue";
|
||||||
import XNavFolder from "@/components/MkDrive.navFolder.vue";
|
import XNavFolder from "@/components/MkDrive.navFolder.vue";
|
||||||
import XFolder from "@/components/MkDrive.folder.vue";
|
import XFolder from "@/components/MkDrive.folder.vue";
|
||||||
|
@ -294,7 +294,7 @@ function onDrop(ev: DragEvent): any {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//#region ドライブのファイル
|
// #region ドライブのファイル
|
||||||
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);
|
||||||
|
@ -305,9 +305,9 @@ function onDrop(ev: DragEvent): any {
|
||||||
folderId: folder.value ? folder.value.id : null,
|
folderId: folder.value ? folder.value.id : null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
//#endregion
|
// #endregion
|
||||||
|
|
||||||
//#region ドライブのフォルダ
|
// #region ドライブのフォルダ
|
||||||
const driveFolder = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_);
|
const driveFolder = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_);
|
||||||
if (driveFolder != null && driveFolder !== "") {
|
if (driveFolder != null && driveFolder !== "") {
|
||||||
const droppedFolder = JSON.parse(driveFolder);
|
const droppedFolder = JSON.parse(driveFolder);
|
||||||
|
@ -339,7 +339,7 @@ function onDrop(ev: DragEvent): any {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
//#endregion
|
// #endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectLocalFile() {
|
function selectLocalFile() {
|
||||||
|
@ -354,7 +354,7 @@ function urlUpload() {
|
||||||
}).then(({ canceled, result: url }) => {
|
}).then(({ canceled, result: url }) => {
|
||||||
if (canceled || !url) return;
|
if (canceled || !url) return;
|
||||||
os.api("drive/files/upload-from-url", {
|
os.api("drive/files/upload-from-url", {
|
||||||
url: url,
|
url,
|
||||||
folderId: folder.value ? folder.value.id : undefined,
|
folderId: folder.value ? folder.value.id : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -372,7 +372,7 @@ function createFolder() {
|
||||||
}).then(({ canceled, result: name }) => {
|
}).then(({ canceled, result: name }) => {
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
os.api("drive/folders/create", {
|
os.api("drive/folders/create", {
|
||||||
name: name,
|
name,
|
||||||
parentId: folder.value ? folder.value.id : undefined,
|
parentId: folder.value ? folder.value.id : undefined,
|
||||||
}).then((createdFolder) => {
|
}).then((createdFolder) => {
|
||||||
addFolder(createdFolder, true);
|
addFolder(createdFolder, true);
|
||||||
|
@ -389,7 +389,7 @@ function renameFolder(folderToRename: Misskey.entities.DriveFolder) {
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
os.api("drive/folders/update", {
|
os.api("drive/folders/update", {
|
||||||
folderId: folderToRename.id,
|
folderId: folderToRename.id,
|
||||||
name: name,
|
name,
|
||||||
}).then((updatedFolder) => {
|
}).then((updatedFolder) => {
|
||||||
// FIXME: 画面を更新するために自分自身に移動
|
// FIXME: 画面を更新するために自分自身に移動
|
||||||
move(updatedFolder);
|
move(updatedFolder);
|
||||||
|
|
|
@ -68,7 +68,7 @@ const is = computed(() => {
|
||||||
"application/x-tar",
|
"application/x-tar",
|
||||||
"application/gzip",
|
"application/gzip",
|
||||||
"application/x-7z-compressed",
|
"application/x-7z-compressed",
|
||||||
].some((archiveType) => archiveType === props.file.type)
|
].includes(props.file.type)
|
||||||
)
|
)
|
||||||
return "archive";
|
return "archive";
|
||||||
return "unknown";
|
return "unknown";
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import * as Misskey from "firefish-js";
|
import type * as Misskey from "firefish-js";
|
||||||
import XDrive from "@/components/MkDrive.vue";
|
import XDrive from "@/components/MkDrive.vue";
|
||||||
import XModalWindow from "@/components/MkModalWindow.vue";
|
import XModalWindow from "@/components/MkModalWindow.vue";
|
||||||
import number from "@/filters/number";
|
import number from "@/filters/number";
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {} from "vue";
|
import {} from "vue";
|
||||||
import * as Misskey from "firefish-js";
|
import type * as Misskey from "firefish-js";
|
||||||
import XDrive from "@/components/MkDrive.vue";
|
import XDrive from "@/components/MkDrive.vue";
|
||||||
import XWindow from "@/components/MkWindow.vue";
|
import XWindow from "@/components/MkWindow.vue";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
|
|
@ -48,7 +48,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, watch, onMounted } from "vue";
|
import { onMounted, ref, watch } from "vue";
|
||||||
import { addSkinTone } from "@/scripts/emojilist";
|
import { addSkinTone } from "@/scripts/emojilist";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<FocusTrap v-bind:active="isActive">
|
<FocusTrap :active="isActive">
|
||||||
<div
|
<div
|
||||||
class="omfetrab"
|
class="omfetrab"
|
||||||
:class="['s' + size, 'w' + width, 'h' + height, { asDrawer }]"
|
:class="['s' + size, 'w' + width, 'h' + height, { asDrawer }]"
|
||||||
|
@ -163,14 +163,15 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed, watch, onMounted } from "vue";
|
import { computed, onMounted, ref, watch } from "vue";
|
||||||
import * as Misskey from "firefish-js";
|
import type * as Misskey from "firefish-js";
|
||||||
|
import { FocusTrap } from "focus-trap-vue";
|
||||||
import XSection from "@/components/MkEmojiPicker.section.vue";
|
import XSection from "@/components/MkEmojiPicker.section.vue";
|
||||||
|
import type { UnicodeEmojiDef } from "@/scripts/emojilist";
|
||||||
import {
|
import {
|
||||||
emojilist,
|
emojilist,
|
||||||
unicodeEmojiCategories,
|
|
||||||
UnicodeEmojiDef,
|
|
||||||
getNicelyLabeledCategory,
|
getNicelyLabeledCategory,
|
||||||
|
unicodeEmojiCategories,
|
||||||
} from "@/scripts/emojilist";
|
} from "@/scripts/emojilist";
|
||||||
import { getStaticImageUrl } from "@/scripts/get-static-image-url";
|
import { getStaticImageUrl } from "@/scripts/get-static-image-url";
|
||||||
import Ripple from "@/components/MkRipple.vue";
|
import Ripple from "@/components/MkRipple.vue";
|
||||||
|
@ -180,7 +181,6 @@ import { deviceKind } from "@/scripts/device-kind";
|
||||||
import { emojiCategories, instance } from "@/instance";
|
import { emojiCategories, instance } from "@/instance";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { defaultStore } from "@/store";
|
import { defaultStore } from "@/store";
|
||||||
import { FocusTrap } from "focus-trap-vue";
|
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import * as Misskey from "firefish-js";
|
import type * as Misskey from "firefish-js";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
|
|
||||||
const meta = ref<Misskey.entities.DetailedInstanceMetadata>();
|
const meta = ref<Misskey.entities.DetailedInstanceMetadata>();
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
<template>
|
<template>
|
||||||
<button
|
<button
|
||||||
v-if="!hideMenu"
|
v-if="!hideMenu"
|
||||||
|
v-tooltip="i18n.ts.menu"
|
||||||
class="menu _button"
|
class="menu _button"
|
||||||
@click.stop="menu"
|
@click.stop="menu"
|
||||||
v-tooltip="i18n.ts.menu"
|
|
||||||
>
|
>
|
||||||
<i class="ph-dots-three-outline ph-bold ph-lg"></i>
|
<i class="ph-dots-three-outline ph-bold ph-lg"></i>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="!hideFollowButton && $i != null && $i.id != user.id"
|
v-if="!hideFollowButton && $i != null && $i.id != user.id"
|
||||||
|
v-tooltip="full ? null : `${state} ${user.name || user.username}`"
|
||||||
class="kpoogebi _button follow-button"
|
class="kpoogebi _button follow-button"
|
||||||
:class="{
|
:class="{
|
||||||
wait,
|
wait,
|
||||||
|
@ -18,9 +19,8 @@
|
||||||
blocking: isBlocking,
|
blocking: isBlocking,
|
||||||
}"
|
}"
|
||||||
:disabled="wait"
|
:disabled="wait"
|
||||||
@click.stop="onClick"
|
|
||||||
:aria-label="`${state} ${user.name || user.username}`"
|
:aria-label="`${state} ${user.name || user.username}`"
|
||||||
v-tooltip="full ? null : `${state} ${user.name || user.username}`"
|
@click.stop="onClick"
|
||||||
>
|
>
|
||||||
<template v-if="!wait">
|
<template v-if="!wait">
|
||||||
<template v-if="isBlocking">
|
<template v-if="isBlocking">
|
||||||
|
@ -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);
|
const state = ref(i18n.ts.processing);
|
||||||
|
|
||||||
let isFollowing = ref(props.user.isFollowing);
|
const isFollowing = ref(props.user.isFollowing);
|
||||||
let hasPendingFollowRequestFromYou = ref(
|
const hasPendingFollowRequestFromYou = ref(
|
||||||
props.user.hasPendingFollowRequestFromYou,
|
props.user.hasPendingFollowRequestFromYou,
|
||||||
);
|
);
|
||||||
let wait = ref(false);
|
const wait = ref(false);
|
||||||
const connection = stream.useChannel("main");
|
const connection = stream.useChannel("main");
|
||||||
|
|
||||||
const hideFollowButton = props.hideFollowButton ?? false;
|
const hideFollowButton = props.hideFollowButton ?? false;
|
||||||
|
|
|
@ -64,7 +64,6 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from "vue";
|
import { ref } 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";
|
||||||
import MkInput from "@/components/form/input.vue";
|
import MkInput from "@/components/form/input.vue";
|
||||||
|
@ -77,11 +76,11 @@ const emit = defineEmits<{
|
||||||
(ev: "closed"): void;
|
(ev: "closed"): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
let dialog: InstanceType<typeof XModalWindow> = ref();
|
const dialog: InstanceType<typeof XModalWindow> = ref();
|
||||||
|
|
||||||
let username = ref("");
|
const username = ref("");
|
||||||
let email = ref("");
|
const email = ref("");
|
||||||
let processing = ref(false);
|
const processing = ref(false);
|
||||||
|
|
||||||
async function onSubmit() {
|
async function onSubmit() {
|
||||||
processing.value = true;
|
processing.value = true;
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, defineAsyncComponent } from "vue";
|
import { defineAsyncComponent, defineComponent } from "vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, nextTick, watch, shallowRef, ref } from "vue";
|
import { nextTick, onMounted, ref, shallowRef, watch } 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";
|
||||||
|
@ -26,8 +26,8 @@ const props = defineProps<{
|
||||||
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);
|
fetching = ref(true);
|
||||||
|
|
||||||
const { handler: externalTooltipHandler } = useChartTooltip({
|
const { handler: externalTooltipHandler } = useChartTooltip({
|
||||||
position: "middle",
|
position: "middle",
|
||||||
|
@ -233,7 +233,7 @@ async function renderChart() {
|
||||||
return ["Active: " + v.v];
|
return ["Active: " + v.v];
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
//mode: 'index',
|
// mode: 'index',
|
||||||
animation: {
|
animation: {
|
||||||
duration: 0,
|
duration: 0,
|
||||||
},
|
},
|
||||||
|
|
|
@ -28,7 +28,6 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from "vue";
|
import { ref } 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";
|
||||||
import number from "@/filters/number";
|
import number from "@/filters/number";
|
||||||
|
|
|
@ -49,7 +49,7 @@ const props = withDefaults(
|
||||||
);
|
);
|
||||||
|
|
||||||
const canvas = ref<HTMLCanvasElement>();
|
const canvas = ref<HTMLCanvasElement>();
|
||||||
let loaded = ref(false);
|
const loaded = ref(false);
|
||||||
|
|
||||||
function draw() {
|
function draw() {
|
||||||
if (props.hash == null || canvas.value == null) return;
|
if (props.hash == null || canvas.value == null) return;
|
||||||
|
|
|
@ -11,8 +11,8 @@
|
||||||
v-if="closeable"
|
v-if="closeable"
|
||||||
v-tooltip="i18n.ts.close"
|
v-tooltip="i18n.ts.close"
|
||||||
class="_buttonIcon close"
|
class="_buttonIcon close"
|
||||||
@click.stop="close"
|
|
||||||
:aria-label="i18n.t('close')"
|
:aria-label="i18n.t('close')"
|
||||||
|
@click.stop="close"
|
||||||
>
|
>
|
||||||
<i class="ph-x ph-bold ph-lg"></i>
|
<i class="ph-x ph-bold ph-lg"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
|
|
||||||
import * as firefish from "firefish-js";
|
import type * 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";
|
||||||
import { getProxiedImageUrlNullable } from "@/scripts/media-proxy";
|
import { getProxiedImageUrlNullable } from "@/scripts/media-proxy";
|
||||||
|
@ -35,7 +35,7 @@ const props = defineProps<{
|
||||||
instance: firefish.entities.Instance;
|
instance: firefish.entities.Instance;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
let chartValues = ref<number[] | null>(null);
|
const chartValues = ref<number[] | null>(null);
|
||||||
|
|
||||||
os.apiGet("charts/instance", {
|
os.apiGet("charts/instance", {
|
||||||
host: props.instance.host,
|
host: props.instance.host,
|
||||||
|
|
|
@ -58,11 +58,11 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
import type { Instance } from "firefish-js/built/entities";
|
||||||
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";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { Instance } from "firefish-js/built/entities";
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: "ok", selected: Instance): void;
|
(ev: "ok", selected: Instance): void;
|
||||||
|
@ -70,10 +70,10 @@ const emit = defineEmits<{
|
||||||
(ev: "closed"): void;
|
(ev: "closed"): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
let hostname = ref("");
|
const hostname = ref("");
|
||||||
let instances: Instance[] = ref([]);
|
const instances: Instance[] = ref([]);
|
||||||
let selected: Instance | null = ref(null);
|
const selected: Instance | null = ref(null);
|
||||||
let dialogEl = ref<InstanceType<typeof XModalWindow>>();
|
const dialogEl = ref<InstanceType<typeof XModalWindow>>();
|
||||||
|
|
||||||
let searchOrderLatch = 0;
|
let searchOrderLatch = 0;
|
||||||
const search = () => {
|
const search = () => {
|
||||||
|
|
|
@ -116,11 +116,11 @@ import { initChart } from "@/scripts/init-chart";
|
||||||
initChart();
|
initChart();
|
||||||
|
|
||||||
const chartLimit = 500;
|
const chartLimit = 500;
|
||||||
let chartSpan = ref<"hour" | "day">("hour");
|
const chartSpan = ref<"hour" | "day">("hour");
|
||||||
let chartSrc = ref("active-users");
|
const chartSrc = ref("active-users");
|
||||||
let heatmapSrc = ref("active-users");
|
const heatmapSrc = ref("active-users");
|
||||||
let subDoughnutEl = shallowRef<HTMLCanvasElement>();
|
const subDoughnutEl = shallowRef<HTMLCanvasElement>();
|
||||||
let pubDoughnutEl = shallowRef<HTMLCanvasElement>();
|
const pubDoughnutEl = shallowRef<HTMLCanvasElement>();
|
||||||
|
|
||||||
const { handler: externalTooltipHandler1 } = useChartTooltip({
|
const { handler: externalTooltipHandler1 } = useChartTooltip({
|
||||||
position: "middle",
|
position: "middle",
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="hpaizdrt"
|
ref="ticker"
|
||||||
v-tooltip="
|
v-tooltip="
|
||||||
`${capitalize(instance.softwareName)} ${instance.softwareVersion}`
|
`${capitalize(instance.softwareName)} ${instance.softwareVersion}`
|
||||||
"
|
"
|
||||||
ref="ticker"
|
class="hpaizdrt"
|
||||||
:style="bg"
|
:style="bg"
|
||||||
>
|
>
|
||||||
<img class="icon" :src="getInstanceIcon(instance)" aria-hidden="true" />
|
<img class="icon" :src="getInstanceIcon(instance)" aria-hidden="true" />
|
||||||
|
@ -29,7 +29,7 @@ const props = defineProps<{
|
||||||
};
|
};
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
let ticker = ref<HTMLElement | null>(null);
|
const 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 ?? {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="media" v-size="{ max: [350] }">
|
<div v-size="{ max: [350] }" class="media">
|
||||||
<button v-if="hide" class="hidden" @click="hide = false">
|
<button v-if="hide" class="hidden" @click="hide = false">
|
||||||
<ImgWithBlurhash
|
<ImgWithBlurhash
|
||||||
:hash="media.blurhash"
|
:hash="media.blurhash"
|
||||||
|
@ -89,7 +89,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { watch, ref, computed } from "vue";
|
import { computed, ref, watch } from "vue";
|
||||||
import VuePlyr from "vue-plyr";
|
import VuePlyr from "vue-plyr";
|
||||||
import "vue-plyr/dist/vue-plyr.css";
|
import "vue-plyr/dist/vue-plyr.css";
|
||||||
import type * as misskey from "firefish-js";
|
import type * as misskey from "firefish-js";
|
||||||
|
@ -104,7 +104,7 @@ const props = defineProps<{
|
||||||
raw?: boolean;
|
raw?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
let hide = ref(true);
|
const hide = ref(true);
|
||||||
|
|
||||||
const plyr = ref();
|
const plyr = ref();
|
||||||
|
|
||||||
|
|
|
@ -71,7 +71,7 @@ const props = withDefaults(
|
||||||
);
|
);
|
||||||
|
|
||||||
const audioEl = ref<HTMLAudioElement | null>();
|
const audioEl = ref<HTMLAudioElement | null>();
|
||||||
let hide = ref(true);
|
const hide = ref(true);
|
||||||
|
|
||||||
function volumechange() {
|
function volumechange() {
|
||||||
if (audioEl.value)
|
if (audioEl.value)
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, ref } from "vue";
|
import { onMounted, ref } from "vue";
|
||||||
import * as misskey from "firefish-js";
|
import type * as misskey from "firefish-js";
|
||||||
import PhotoSwipeLightbox from "photoswipe/lightbox";
|
import PhotoSwipeLightbox from "photoswipe/lightbox";
|
||||||
import PhotoSwipe from "photoswipe";
|
import PhotoSwipe from "photoswipe";
|
||||||
import "photoswipe/style.css";
|
import "photoswipe/style.css";
|
||||||
|
@ -125,11 +125,11 @@ onMounted(() => {
|
||||||
className: "pwsp__alt-text-container",
|
className: "pwsp__alt-text-container",
|
||||||
appendTo: "wrapper",
|
appendTo: "wrapper",
|
||||||
onInit: (el, pwsp) => {
|
onInit: (el, pwsp) => {
|
||||||
let textBox = document.createElement("p");
|
const textBox = document.createElement("p");
|
||||||
textBox.className = "pwsp__alt-text";
|
textBox.className = "pwsp__alt-text";
|
||||||
el.appendChild(textBox);
|
el.appendChild(textBox);
|
||||||
|
|
||||||
let preventProp = function (ev: Event): void {
|
const preventProp = function (ev: Event): void {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { nextTick, onMounted, ref } from "vue";
|
import { nextTick, onMounted, ref } from "vue";
|
||||||
import MkMenu from "./MkMenu.vue";
|
import MkMenu from "./MkMenu.vue";
|
||||||
import { MenuItem } from "@/types/menu";
|
import type { MenuItem } from "@/types/menu";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
items: MenuItem[];
|
items: MenuItem[];
|
||||||
|
|
|
@ -14,8 +14,8 @@
|
||||||
width: width && !asDrawer ? width + 'px' : '',
|
width: width && !asDrawer ? width + 'px' : '',
|
||||||
maxHeight: maxHeight ? maxHeight + 'px' : '',
|
maxHeight: maxHeight ? maxHeight + 'px' : '',
|
||||||
}"
|
}"
|
||||||
@contextmenu.self="(e) => e.preventDefault()"
|
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
|
@contextmenu.self="(e) => e.preventDefault()"
|
||||||
>
|
>
|
||||||
<template v-for="item in items2">
|
<template v-for="item in items2">
|
||||||
<div v-if="item === null" class="divider"></div>
|
<div v-if="item === null" class="divider"></div>
|
||||||
|
@ -47,7 +47,7 @@
|
||||||
v-if="item.avatar"
|
v-if="item.avatar"
|
||||||
:user="item.avatar"
|
:user="item.avatar"
|
||||||
class="avatar"
|
class="avatar"
|
||||||
disableLink
|
disable-link
|
||||||
/>
|
/>
|
||||||
<span :style="item.textStyle || ''">{{
|
<span :style="item.textStyle || ''">{{
|
||||||
item.text
|
item.text
|
||||||
|
@ -100,7 +100,7 @@
|
||||||
<MkAvatar
|
<MkAvatar
|
||||||
:user="item.user"
|
:user="item.user"
|
||||||
class="avatar"
|
class="avatar"
|
||||||
disableLink
|
disable-link
|
||||||
/><MkUserName :user="item.user" />
|
/><MkUserName :user="item.user" />
|
||||||
<span
|
<span
|
||||||
v-if="item.indicate"
|
v-if="item.indicate"
|
||||||
|
@ -168,7 +168,7 @@
|
||||||
v-if="item.avatar"
|
v-if="item.avatar"
|
||||||
:user="item.avatar"
|
:user="item.avatar"
|
||||||
class="avatar"
|
class="avatar"
|
||||||
disableLink
|
disable-link
|
||||||
/>
|
/>
|
||||||
<span :style="item.textStyle || ''">{{
|
<span :style="item.textStyle || ''">{{
|
||||||
item.text
|
item.text
|
||||||
|
@ -210,11 +210,16 @@ import {
|
||||||
ref,
|
ref,
|
||||||
watch,
|
watch,
|
||||||
} from "vue";
|
} from "vue";
|
||||||
|
import { FocusTrap } from "focus-trap-vue";
|
||||||
import FormSwitch from "@/components/form/switch.vue";
|
import FormSwitch from "@/components/form/switch.vue";
|
||||||
import { MenuItem, InnerMenuItem, MenuPending, MenuAction } from "@/types/menu";
|
import type {
|
||||||
|
InnerMenuItem,
|
||||||
|
MenuAction,
|
||||||
|
MenuItem,
|
||||||
|
MenuPending,
|
||||||
|
} from "@/types/menu";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { FocusTrap } from "focus-trap-vue";
|
|
||||||
|
|
||||||
const XChild = defineAsyncComponent(() => import("./MkMenu.child.vue"));
|
const XChild = defineAsyncComponent(() => import("./MkMenu.child.vue"));
|
||||||
const focusTrap = ref();
|
const focusTrap = ref();
|
||||||
|
@ -233,13 +238,13 @@ const emit = defineEmits<{
|
||||||
(ev: "close", actioned?: boolean): void;
|
(ev: "close", actioned?: boolean): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
let itemsEl = ref<HTMLDivElement>();
|
const itemsEl = ref<HTMLDivElement>();
|
||||||
|
|
||||||
let items2: InnerMenuItem[] = ref([]);
|
const items2: InnerMenuItem[] = ref([]);
|
||||||
|
|
||||||
let child = ref<InstanceType<typeof XChild>>();
|
const child = ref<InstanceType<typeof XChild>>();
|
||||||
|
|
||||||
let childShowingItem = ref<MenuItem | null>();
|
const childShowingItem = ref<MenuItem | null>();
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.items,
|
() => props.items,
|
||||||
|
@ -267,8 +272,8 @@ watch(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let childMenu = ref<MenuItem[] | null>();
|
const childMenu = ref<MenuItem[] | null>();
|
||||||
let childTarget = ref<HTMLElement | null>();
|
const childTarget = ref<HTMLElement | null>();
|
||||||
|
|
||||||
function closeChild() {
|
function closeChild() {
|
||||||
childMenu.value = null;
|
childMenu.value = null;
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { watch, ref } from "vue";
|
import { ref, watch } 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("");
|
const polylinePoints = ref("");
|
||||||
let polygonPoints = ref("");
|
const polygonPoints = ref("");
|
||||||
let headX = ref<number | null>(null);
|
const headX = ref<number | null>(null);
|
||||||
let headY = ref<number | null>(null);
|
const headY = ref<number | null>(null);
|
||||||
const accent = tinycolor(
|
const accent = tinycolor(
|
||||||
getComputedStyle(document.documentElement).getPropertyValue("--accent"),
|
getComputedStyle(document.documentElement).getPropertyValue("--accent"),
|
||||||
);
|
);
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
<div
|
<div
|
||||||
v-show="manualShowing != null ? manualShowing : showing"
|
v-show="manualShowing != null ? manualShowing : showing"
|
||||||
v-hotkey.global="keymap"
|
v-hotkey.global="keymap"
|
||||||
|
v-focus
|
||||||
:class="[
|
:class="[
|
||||||
$style.root,
|
$style.root,
|
||||||
{
|
{
|
||||||
|
@ -44,7 +45,6 @@
|
||||||
'--transformOrigin': transformOrigin,
|
'--transformOrigin': transformOrigin,
|
||||||
}"
|
}"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
v-focus
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="_modalBg data-cy-bg"
|
class="_modalBg data-cy-bg"
|
||||||
|
@ -78,20 +78,20 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {
|
import {
|
||||||
|
computed,
|
||||||
nextTick,
|
nextTick,
|
||||||
onMounted,
|
onMounted,
|
||||||
watch,
|
|
||||||
provide,
|
|
||||||
onUnmounted,
|
onUnmounted,
|
||||||
|
provide,
|
||||||
ref,
|
ref,
|
||||||
shallowRef,
|
shallowRef,
|
||||||
computed,
|
watch,
|
||||||
} from "vue";
|
} from "vue";
|
||||||
|
import { FocusTrap } from "focus-trap-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";
|
||||||
import { deviceKind } from "@/scripts/device-kind";
|
import { deviceKind } from "@/scripts/device-kind";
|
||||||
import { FocusTrap } from "focus-trap-vue";
|
|
||||||
|
|
||||||
function getFixedContainer(el: Element | null): Element | null {
|
function getFixedContainer(el: Element | null): Element | null {
|
||||||
if (el == null || el.tagName === "BODY") return null;
|
if (el == null || el.tagName === "BODY") return null;
|
||||||
|
@ -139,13 +139,13 @@ const emit = defineEmits<{
|
||||||
|
|
||||||
provide("modal", true);
|
provide("modal", true);
|
||||||
|
|
||||||
let maxHeight = ref<number>();
|
const maxHeight = ref<number>();
|
||||||
let fixed = ref(false);
|
const fixed = ref(false);
|
||||||
let transformOrigin = ref("center");
|
const transformOrigin = ref("center");
|
||||||
let showing = ref(true);
|
const showing = ref(true);
|
||||||
let content = shallowRef<HTMLElement>();
|
const content = shallowRef<HTMLElement>();
|
||||||
const zIndex = os.claimZIndex(props.zPriority);
|
const zIndex = os.claimZIndex(props.zPriority);
|
||||||
let useSendAnime = ref(false);
|
const useSendAnime = ref(false);
|
||||||
const type = computed<ModalTypes>(() => {
|
const type = computed<ModalTypes>(() => {
|
||||||
if (props.preferType === "auto") {
|
if (props.preferType === "auto") {
|
||||||
if (
|
if (
|
||||||
|
@ -164,7 +164,7 @@ const type = computed<ModalTypes>(() => {
|
||||||
const isEnableBgTransparent = computed(
|
const isEnableBgTransparent = computed(
|
||||||
() => props.transparentBg && type.value === "popup",
|
() => props.transparentBg && type.value === "popup",
|
||||||
);
|
);
|
||||||
let transitionName = computed(() =>
|
const transitionName = computed(() =>
|
||||||
defaultStore.state.animation
|
defaultStore.state.animation
|
||||||
? useSendAnime.value
|
? useSendAnime.value
|
||||||
? "send"
|
? "send"
|
||||||
|
@ -175,7 +175,7 @@ let transitionName = computed(() =>
|
||||||
: "modal"
|
: "modal"
|
||||||
: "",
|
: "",
|
||||||
);
|
);
|
||||||
let transitionDuration = computed(() =>
|
const transitionDuration = computed(() =>
|
||||||
transitionName.value === "send"
|
transitionName.value === "send"
|
||||||
? 400
|
? 400
|
||||||
: transitionName.value === "modal-popup"
|
: transitionName.value === "modal-popup"
|
||||||
|
@ -235,8 +235,7 @@ const align = () => {
|
||||||
const width = content.value!.offsetWidth;
|
const width = content.value!.offsetWidth;
|
||||||
const height = content.value!.offsetHeight;
|
const height = content.value!.offsetHeight;
|
||||||
|
|
||||||
let left;
|
let left, top;
|
||||||
let top;
|
|
||||||
|
|
||||||
const x = srcRect.left + (fixed.value ? 0 : window.pageXOffset);
|
const x = srcRect.left + (fixed.value ? 0 : window.pageXOffset);
|
||||||
const y = srcRect.top + (fixed.value ? 0 : window.pageYOffset);
|
const y = srcRect.top + (fixed.value ? 0 : window.pageYOffset);
|
||||||
|
@ -321,8 +320,8 @@ const align = () => {
|
||||||
left = 0;
|
left = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let transformOriginX = "center";
|
let transformOriginX = "center",
|
||||||
let transformOriginY = "center";
|
transformOriginY = "center";
|
||||||
|
|
||||||
if (
|
if (
|
||||||
top >=
|
top >=
|
||||||
|
|
|
@ -28,8 +28,8 @@
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
class="_button"
|
class="_button"
|
||||||
@click="$refs.modal.close()"
|
|
||||||
:aria-label="i18n.t('close')"
|
:aria-label="i18n.t('close')"
|
||||||
|
@click="$refs.modal.close()"
|
||||||
>
|
>
|
||||||
<i class="ph-x ph-bold ph-lg"></i>
|
<i class="ph-x ph-bold ph-lg"></i>
|
||||||
</button>
|
</button>
|
||||||
|
@ -52,7 +52,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ComputedRef, provide, ref, computed } from "vue";
|
import type { ComputedRef } from "vue";
|
||||||
|
import { computed, provide, ref } from "vue";
|
||||||
import MkModal from "@/components/MkModal.vue";
|
import MkModal from "@/components/MkModal.vue";
|
||||||
import { popout as _popout } from "@/scripts/popout";
|
import { popout as _popout } from "@/scripts/popout";
|
||||||
import copyToClipboard from "@/scripts/copy-to-clipboard";
|
import copyToClipboard from "@/scripts/copy-to-clipboard";
|
||||||
|
@ -60,7 +61,8 @@ import { url } from "@/config";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { mainRouter, routes } from "@/router";
|
import { mainRouter, routes } from "@/router";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { PageMetadata, provideMetadataReceiver } from "@/scripts/page-metadata";
|
import type { PageMetadata } from "@/scripts/page-metadata";
|
||||||
|
import { provideMetadataReceiver } from "@/scripts/page-metadata";
|
||||||
import { Router } from "@/nirax";
|
import { Router } from "@/nirax";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
@ -76,12 +78,12 @@ const router = new Router(routes, props.initialPath);
|
||||||
|
|
||||||
router.addListener("push", (ctx) => {});
|
router.addListener("push", (ctx) => {});
|
||||||
|
|
||||||
let pageMetadata = ref<null | ComputedRef<PageMetadata>>();
|
const pageMetadata = ref<null | ComputedRef<PageMetadata>>();
|
||||||
let rootEl = ref();
|
const rootEl = ref();
|
||||||
let modal = ref<InstanceType<typeof MkModal>>();
|
const modal = ref<InstanceType<typeof MkModal>>();
|
||||||
let path = ref(props.initialPath);
|
const path = ref(props.initialPath);
|
||||||
let width = ref(860);
|
const width = ref(860);
|
||||||
let height = ref(660);
|
const height = ref(660);
|
||||||
const history = [];
|
const history = [];
|
||||||
|
|
||||||
provide("router", router);
|
provide("router", router);
|
||||||
|
|
|
@ -25,10 +25,10 @@
|
||||||
<div ref="headerEl" class="header">
|
<div ref="headerEl" class="header">
|
||||||
<button
|
<button
|
||||||
v-if="props.withOkButton"
|
v-if="props.withOkButton"
|
||||||
|
v-tooltip="i18n.ts.close"
|
||||||
:aria-label="i18n.t('close')"
|
:aria-label="i18n.t('close')"
|
||||||
class="_button"
|
class="_button"
|
||||||
@click="$emit('close')"
|
@click="$emit('close')"
|
||||||
v-tooltip="i18n.ts.close"
|
|
||||||
>
|
>
|
||||||
<i class="ph-x ph-bold ph-lg"></i>
|
<i class="ph-x ph-bold ph-lg"></i>
|
||||||
</button>
|
</button>
|
||||||
|
@ -92,9 +92,9 @@ const emit = defineEmits<{
|
||||||
(event: "ok"): void;
|
(event: "ok"): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
let modal = shallowRef<InstanceType<typeof MkModal>>();
|
const modal = shallowRef<InstanceType<typeof MkModal>>();
|
||||||
let rootEl = shallowRef<HTMLElement>();
|
const rootEl = shallowRef<HTMLElement>();
|
||||||
let headerEl = shallowRef<HTMLElement>();
|
const headerEl = shallowRef<HTMLElement>();
|
||||||
|
|
||||||
const close = (ev) => {
|
const close = (ev) => {
|
||||||
modal.value?.close(ev);
|
modal.value?.close(ev);
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
:aria-label="accessibleLabel"
|
|
||||||
v-if="!muted.muted"
|
v-if="!muted.muted"
|
||||||
v-show="!isDeleted"
|
v-show="!isDeleted"
|
||||||
|
:id="appearNote.id"
|
||||||
ref="el"
|
ref="el"
|
||||||
v-hotkey="keymap"
|
v-hotkey="keymap"
|
||||||
v-size="{ max: [500, 350] }"
|
v-size="{ max: [500, 350] }"
|
||||||
|
:aria-label="accessibleLabel"
|
||||||
class="tkcbzcuz note-container"
|
class="tkcbzcuz note-container"
|
||||||
:tabindex="!isDeleted ? '-1' : null"
|
:tabindex="!isDeleted ? '-1' : null"
|
||||||
:class="{ renote: isRenote }"
|
:class="{ renote: isRenote }"
|
||||||
:id="appearNote.id"
|
|
||||||
>
|
>
|
||||||
<MkNoteSub
|
<MkNoteSub
|
||||||
v-if="appearNote.reply && !detailedView && !collapsedReply"
|
v-if="appearNote.reply && !detailedView && !collapsedReply"
|
||||||
|
@ -19,10 +19,10 @@
|
||||||
<div
|
<div
|
||||||
v-if="!detailedView"
|
v-if="!detailedView"
|
||||||
class="note-context"
|
class="note-context"
|
||||||
@click="noteClick"
|
|
||||||
:class="{
|
:class="{
|
||||||
collapsedReply: collapsedReply && appearNote.reply,
|
collapsedReply: collapsedReply && appearNote.reply,
|
||||||
}"
|
}"
|
||||||
|
@click="noteClick"
|
||||||
>
|
>
|
||||||
<div v-if="!collapsedReply" class="line"></div>
|
<div v-if="!collapsedReply" class="line"></div>
|
||||||
<div v-if="appearNote._prId_" class="info">
|
<div v-if="appearNote._prId_" class="info">
|
||||||
|
@ -87,11 +87,11 @@
|
||||||
</div>
|
</div>
|
||||||
<article
|
<article
|
||||||
class="article"
|
class="article"
|
||||||
@contextmenu.stop="onContextmenu"
|
|
||||||
@click="noteClick"
|
|
||||||
:style="{
|
:style="{
|
||||||
cursor: expandOnNoteClick && !detailedView ? 'pointer' : '',
|
cursor: expandOnNoteClick && !detailedView ? 'pointer' : '',
|
||||||
}"
|
}"
|
||||||
|
@contextmenu.stop="onContextmenu"
|
||||||
|
@click="noteClick"
|
||||||
>
|
>
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<div class="header-container">
|
<div class="header-container">
|
||||||
|
@ -103,15 +103,15 @@
|
||||||
class="text"
|
class="text"
|
||||||
:note="appearNote"
|
:note="appearNote"
|
||||||
:detailed="true"
|
:detailed="true"
|
||||||
:detailedView="detailedView"
|
:detailed-view="detailedView"
|
||||||
:parentId="appearNote.parentId"
|
:parent-id="appearNote.parentId"
|
||||||
@push="(e) => router.push(notePage(e))"
|
@push="(e) => router.push(notePage(e))"
|
||||||
@focusfooter="footerEl.focus()"
|
@focusfooter="footerEl.focus()"
|
||||||
@expanded="(e) => setPostExpanded(e)"
|
@expanded="(e) => setPostExpanded(e)"
|
||||||
></MkSubNoteContent>
|
></MkSubNoteContent>
|
||||||
<div v-if="translating || translation" class="translation">
|
<div v-if="translating || translation" class="translation">
|
||||||
<MkLoading v-if="translating" mini />
|
<MkLoading v-if="translating" mini />
|
||||||
<div v-else class="translated">
|
<div v-else-if="translation != null" class="translated">
|
||||||
<b
|
<b
|
||||||
>{{
|
>{{
|
||||||
i18n.t("translatedFrom", {
|
i18n.t("translatedFrom", {
|
||||||
|
@ -171,7 +171,7 @@
|
||||||
class="button"
|
class="button"
|
||||||
:note="appearNote"
|
:note="appearNote"
|
||||||
:count="appearNote.renoteCount"
|
:count="appearNote.renoteCount"
|
||||||
:detailedView="detailedView"
|
:detailed-view="detailedView"
|
||||||
/>
|
/>
|
||||||
<XStarButtonNoEmoji
|
<XStarButtonNoEmoji
|
||||||
v-if="!enableEmojiReactions"
|
v-if="!enableEmojiReactions"
|
||||||
|
@ -212,9 +212,9 @@
|
||||||
appearNote.myReaction != null
|
appearNote.myReaction != null
|
||||||
"
|
"
|
||||||
ref="reactButton"
|
ref="reactButton"
|
||||||
|
v-tooltip.noDelay.bottom="i18n.ts.removeReaction"
|
||||||
class="button _button reacted"
|
class="button _button reacted"
|
||||||
@click.stop="undoReact(appearNote)"
|
@click.stop="undoReact(appearNote)"
|
||||||
v-tooltip.noDelay.bottom="i18n.ts.removeReaction"
|
|
||||||
>
|
>
|
||||||
<i class="ph-minus ph-bold ph-lg"></i>
|
<i class="ph-minus ph-bold ph-lg"></i>
|
||||||
</button>
|
</button>
|
||||||
|
@ -225,7 +225,7 @@
|
||||||
isForeignLanguage &&
|
isForeignLanguage &&
|
||||||
translation == null
|
translation == null
|
||||||
"
|
"
|
||||||
class="button _button accent"
|
class="button _button"
|
||||||
@click.stop="translate"
|
@click.stop="translate"
|
||||||
v-tooltip.noDelay.bottom="i18n.ts.translate"
|
v-tooltip.noDelay.bottom="i18n.ts.translate"
|
||||||
>
|
>
|
||||||
|
@ -272,8 +272,8 @@ import * as mfm from "mfm-js";
|
||||||
import type { Ref } from "vue";
|
import type { Ref } from "vue";
|
||||||
import type * as misskey from "firefish-js";
|
import type * as misskey from "firefish-js";
|
||||||
import { detect as detectLanguage_ } from "tinyld";
|
import { detect as detectLanguage_ } from "tinyld";
|
||||||
import MkNoteSub from "@/components/MkNoteSub.vue";
|
|
||||||
import MkSubNoteContent from "./MkSubNoteContent.vue";
|
import MkSubNoteContent from "./MkSubNoteContent.vue";
|
||||||
|
import MkNoteSub from "@/components/MkNoteSub.vue";
|
||||||
import XNoteHeader from "@/components/MkNoteHeader.vue";
|
import XNoteHeader from "@/components/MkNoteHeader.vue";
|
||||||
import XRenoteButton from "@/components/MkRenoteButton.vue";
|
import XRenoteButton from "@/components/MkRenoteButton.vue";
|
||||||
import XReactionsViewer from "@/components/MkReactionsViewer.vue";
|
import XReactionsViewer from "@/components/MkReactionsViewer.vue";
|
||||||
|
@ -284,7 +284,7 @@ import MkVisibility from "@/components/MkVisibility.vue";
|
||||||
import copyToClipboard from "@/scripts/copy-to-clipboard";
|
import copyToClipboard from "@/scripts/copy-to-clipboard";
|
||||||
import { url } from "@/config";
|
import { url } from "@/config";
|
||||||
import { pleaseLogin } from "@/scripts/please-login";
|
import { pleaseLogin } from "@/scripts/please-login";
|
||||||
import { focusPrev, focusNext } from "@/scripts/focus";
|
import { focusNext, focusPrev } from "@/scripts/focus";
|
||||||
import { getWordSoftMute } from "@/scripts/check-word-mute";
|
import { getWordSoftMute } from "@/scripts/check-word-mute";
|
||||||
import { useRouter } from "@/router";
|
import { useRouter } from "@/router";
|
||||||
import { userPage } from "@/filters/user";
|
import { userPage } from "@/filters/user";
|
||||||
|
@ -310,7 +310,7 @@ const props = defineProps<{
|
||||||
|
|
||||||
const inChannel = inject("inChannel", null);
|
const inChannel = inject("inChannel", null);
|
||||||
|
|
||||||
let note = ref(deepClone(props.note));
|
const 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;
|
||||||
|
@ -346,7 +346,7 @@ 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(() =>
|
const appearNote = computed(() =>
|
||||||
isRenote ? (note.value.renote as misskey.entities.Note) : note.value,
|
isRenote ? (note.value.renote as misskey.entities.Note) : note.value,
|
||||||
);
|
);
|
||||||
const isMyRenote = $i && $i.id === note.value.userId;
|
const isMyRenote = $i && $i.id === note.value.userId;
|
||||||
|
@ -359,59 +359,57 @@ const translation = ref(null);
|
||||||
const translating = ref(false);
|
const translating = ref(false);
|
||||||
const enableEmojiReactions = defaultStore.state.enableEmojiReactions;
|
const enableEmojiReactions = defaultStore.state.enableEmojiReactions;
|
||||||
const expandOnNoteClick = defaultStore.state.expandOnNoteClick;
|
const expandOnNoteClick = defaultStore.state.expandOnNoteClick;
|
||||||
|
const lang = localStorage.getItem("lang");
|
||||||
|
const translateLang = localStorage.getItem("translateLang");
|
||||||
|
|
||||||
const detectLanguage = (src: string): string => {
|
function detectLanguage(text: string) {
|
||||||
const nodes = mfm.parse(src);
|
const nodes = mfm.parse(text);
|
||||||
const filtered = mfm.extract(nodes, (node) => {
|
const filtered = mfm.extract(nodes, (node) => {
|
||||||
return node.type === "text" || node.type === "quote";
|
return node.type === "text" || node.type === "quote";
|
||||||
});
|
});
|
||||||
const purified = mfm.toString(filtered).trim();
|
const purified = mfm.toString(filtered);
|
||||||
return detectLanguage_(purified);
|
return detectLanguage_(purified);
|
||||||
};
|
}
|
||||||
|
|
||||||
const localTranslateLang = localStorage.getItem("translateLang");
|
|
||||||
const localLang = localStorage.getItem("lang");
|
|
||||||
|
|
||||||
const isForeignLanguage: boolean =
|
const isForeignLanguage: boolean =
|
||||||
defaultStore.state.detectPostLanguage &&
|
defaultStore.state.detectPostLanguage &&
|
||||||
appearNote.value.text != null &&
|
appearNote.value.text != null &&
|
||||||
(() => {
|
(() => {
|
||||||
const targetLanguage = (
|
const targetLang = (translateLang || lang || navigator.language)?.slice(
|
||||||
localTranslateLang ||
|
0,
|
||||||
localLang ||
|
2,
|
||||||
navigator.language
|
);
|
||||||
)?.slice(0, 2);
|
const postLang = detectLanguage(appearNote.value.text);
|
||||||
const postLanguage = detectLanguage(appearNote.value.text);
|
return postLang !== "" && postLang !== targetLang;
|
||||||
return postLanguage !== "" && postLanguage !== targetLanguage;
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const translate_ = async (noteId: number, targetLang: string) => {
|
async function translate_(noteId: number, targetLang: string) {
|
||||||
return await os.api("notes/translate", {
|
return await os.api("notes/translate", {
|
||||||
noteId: noteId,
|
noteId: noteId,
|
||||||
targetLang: targetLang,
|
targetLang: targetLang,
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
const translate = async () => {
|
async function translate() {
|
||||||
if (translation.value != null) return;
|
if (translation.value != null) return;
|
||||||
translating.value = true;
|
translating.value = true;
|
||||||
translation.value = await translate_(
|
translation.value = await translate_(
|
||||||
appearNote.value.id,
|
appearNote.value.id,
|
||||||
localTranslateLang || localLang || navigator.language,
|
translateLang || lang || navigator.language,
|
||||||
);
|
);
|
||||||
|
|
||||||
// use UI language as the second translation target
|
// use UI language as the second translation language
|
||||||
if (
|
if (
|
||||||
localTranslateLang != null &&
|
translateLang != null &&
|
||||||
localLang != null &&
|
lang != null &&
|
||||||
localTranslateLang !== localLang &&
|
translateLang !== lang &&
|
||||||
(!translation.value ||
|
(!translation.value ||
|
||||||
translation.value.sourceLang.toLowerCase() ===
|
translation.value.sourceLang.toLowerCase() ===
|
||||||
localTranslateLang.slice(0, 2))
|
translateLang.slice(0, 2))
|
||||||
)
|
)
|
||||||
translation.value = await translate_(appearNote.value.id, localLang);
|
translation.value = await translate_(appearNote.value.id, lang);
|
||||||
translating.value = false;
|
translating.value = false;
|
||||||
};
|
}
|
||||||
|
|
||||||
const keymap = {
|
const keymap = {
|
||||||
r: () => reply(true),
|
r: () => reply(true),
|
||||||
|
@ -451,7 +449,7 @@ function react(viaKeyboard = false): void {
|
||||||
(reaction) => {
|
(reaction) => {
|
||||||
os.api("notes/reactions/create", {
|
os.api("notes/reactions/create", {
|
||||||
noteId: appearNote.value.id,
|
noteId: appearNote.value.id,
|
||||||
reaction: reaction,
|
reaction,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
|
@ -582,7 +580,7 @@ function showRenoteMenu(viaKeyboard = false): void {
|
||||||
],
|
],
|
||||||
renoteTime.value,
|
renoteTime.value,
|
||||||
{
|
{
|
||||||
viaKeyboard: viaKeyboard,
|
viaKeyboard,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -626,7 +624,7 @@ function readPromo() {
|
||||||
isDeleted.value = true;
|
isDeleted.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let postIsExpanded = ref(false);
|
const postIsExpanded = ref(false);
|
||||||
|
|
||||||
function setPostExpanded(val: boolean) {
|
function setPostExpanded(val: boolean) {
|
||||||
postIsExpanded.value = val;
|
postIsExpanded.value = val;
|
||||||
|
@ -985,10 +983,6 @@ defineExpose({
|
||||||
&.reacted {
|
&.reacted {
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.accent {
|
|
||||||
color: var(--accent);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,27 +10,27 @@
|
||||||
:class="{ renote: isRenote }"
|
:class="{ renote: isRenote }"
|
||||||
>
|
>
|
||||||
<MkNoteSub
|
<MkNoteSub
|
||||||
v-if="conversation"
|
|
||||||
v-for="note in conversation"
|
v-for="note in conversation"
|
||||||
|
v-if="conversation"
|
||||||
:key="note.id"
|
:key="note.id"
|
||||||
class="reply-to"
|
class="reply-to"
|
||||||
:note="note"
|
:note="note"
|
||||||
:detailedView="true"
|
:detailed-view="true"
|
||||||
/>
|
/>
|
||||||
<MkLoading v-else-if="note.reply" mini />
|
<MkLoading v-else-if="note.reply" mini />
|
||||||
<MkNoteSub
|
<MkNoteSub
|
||||||
v-if="note.reply"
|
v-if="note.reply"
|
||||||
:note="note.reply"
|
:note="note.reply"
|
||||||
class="reply-to"
|
class="reply-to"
|
||||||
:detailedView="true"
|
:detailed-view="true"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<MkNote
|
<MkNote
|
||||||
ref="noteEl"
|
ref="noteEl"
|
||||||
@contextmenu.stop="onContextmenu"
|
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
:note="note"
|
:note="note"
|
||||||
detailedView
|
detailed-view
|
||||||
|
@contextmenu.stop="onContextmenu"
|
||||||
></MkNote>
|
></MkNote>
|
||||||
|
|
||||||
<MkTab v-model="tab" :style="'underline'" @update:modelValue="loadTab">
|
<MkTab v-model="tab" :style="'underline'" @update:modelValue="loadTab">
|
||||||
|
@ -41,22 +41,22 @@
|
||||||
}}</span>
|
}}</span>
|
||||||
{{ i18n.ts._notification._types.reply }}
|
{{ i18n.ts._notification._types.reply }}
|
||||||
</option>
|
</option>
|
||||||
<option value="renotes" v-if="note.renoteCount > 0">
|
<option v-if="note.renoteCount > 0" value="renotes">
|
||||||
<!-- <i class="ph-repeat ph-bold ph-lg"></i> -->
|
<!-- <i class="ph-repeat ph-bold ph-lg"></i> -->
|
||||||
<span class="count">{{ note.renoteCount }}</span>
|
<span class="count">{{ note.renoteCount }}</span>
|
||||||
{{ i18n.ts._notification._types.renote }}
|
{{ i18n.ts._notification._types.renote }}
|
||||||
</option>
|
</option>
|
||||||
<option value="reactions" v-if="reactionsCount > 0">
|
<option v-if="reactionsCount > 0" value="reactions">
|
||||||
<!-- <i class="ph-smiley ph-bold ph-lg"></i> -->
|
<!-- <i class="ph-smiley ph-bold ph-lg"></i> -->
|
||||||
<span class="count">{{ reactionsCount }}</span>
|
<span class="count">{{ reactionsCount }}</span>
|
||||||
{{ i18n.ts.reaction }}
|
{{ i18n.ts.reaction }}
|
||||||
</option>
|
</option>
|
||||||
<option value="quotes" v-if="directQuotes?.length > 0">
|
<option v-if="directQuotes?.length > 0" value="quotes">
|
||||||
<!-- <i class="ph-quotes ph-bold ph-lg"></i> -->
|
<!-- <i class="ph-quotes ph-bold ph-lg"></i> -->
|
||||||
<span class="count">{{ directQuotes.length }}</span>
|
<span class="count">{{ directQuotes.length }}</span>
|
||||||
{{ i18n.ts._notification._types.quote }}
|
{{ i18n.ts._notification._types.quote }}
|
||||||
</option>
|
</option>
|
||||||
<option value="clips" v-if="clips?.length > 0">
|
<option v-if="clips?.length > 0" value="clips">
|
||||||
<!-- <i class="ph-paperclip ph-bold ph-lg"></i> -->
|
<!-- <i class="ph-paperclip ph-bold ph-lg"></i> -->
|
||||||
<span class="count">{{ clips.length }}</span>
|
<span class="count">{{ clips.length }}</span>
|
||||||
{{ i18n.ts.clips }}
|
{{ i18n.ts.clips }}
|
||||||
|
@ -64,26 +64,26 @@
|
||||||
</MkTab>
|
</MkTab>
|
||||||
|
|
||||||
<MkNoteSub
|
<MkNoteSub
|
||||||
v-if="directReplies && tab === 'replies'"
|
|
||||||
v-for="note in directReplies"
|
v-for="note in directReplies"
|
||||||
|
v-if="directReplies && tab === 'replies'"
|
||||||
:key="note.id"
|
:key="note.id"
|
||||||
:note="note"
|
:note="note"
|
||||||
class="reply"
|
class="reply"
|
||||||
:conversation="replies"
|
:conversation="replies"
|
||||||
:detailedView="true"
|
:detailed-view="true"
|
||||||
:parentId="note.id"
|
:parent-id="note.id"
|
||||||
/>
|
/>
|
||||||
<MkLoading v-else-if="tab === 'replies' && note.repliesCount > 0" />
|
<MkLoading v-else-if="tab === 'replies' && note.repliesCount > 0" />
|
||||||
|
|
||||||
<MkNoteSub
|
<MkNoteSub
|
||||||
v-if="directQuotes && tab === 'quotes'"
|
|
||||||
v-for="note in directQuotes"
|
v-for="note in directQuotes"
|
||||||
|
v-if="directQuotes && tab === 'quotes'"
|
||||||
:key="note.id"
|
:key="note.id"
|
||||||
:note="note"
|
:note="note"
|
||||||
class="reply"
|
class="reply"
|
||||||
:conversation="replies"
|
:conversation="replies"
|
||||||
:detailedView="true"
|
:detailed-view="true"
|
||||||
:parentId="note.id"
|
:parent-id="note.id"
|
||||||
/>
|
/>
|
||||||
<MkLoading v-else-if="tab === 'quotes' && directQuotes.length > 0" />
|
<MkLoading v-else-if="tab === 'quotes' && directQuotes.length > 0" />
|
||||||
|
|
||||||
|
@ -94,8 +94,8 @@
|
||||||
:pagination="pagination"
|
:pagination="pagination"
|
||||||
> -->
|
> -->
|
||||||
<MkUserCardMini
|
<MkUserCardMini
|
||||||
v-if="tab === 'renotes' && renotes"
|
|
||||||
v-for="item in renotes"
|
v-for="item in renotes"
|
||||||
|
v-if="tab === 'renotes' && renotes"
|
||||||
:key="item.user.id"
|
:key="item.user.id"
|
||||||
:user="item.user"
|
:user="item.user"
|
||||||
:with-chart="false"
|
:with-chart="false"
|
||||||
|
@ -151,11 +151,12 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, onUnmounted, onUpdated, ref } from "vue";
|
import { onMounted, onUnmounted, onUpdated, ref } from "vue";
|
||||||
import * as misskey from "firefish-js";
|
import type * as misskey from "firefish-js";
|
||||||
|
import type { NoteUpdatedEvent } from "firefish-js/built/streaming.types";
|
||||||
import MkTab from "@/components/MkTab.vue";
|
import MkTab from "@/components/MkTab.vue";
|
||||||
import MkNote from "@/components/MkNote.vue";
|
import MkNote from "@/components/MkNote.vue";
|
||||||
import MkNoteSub from "@/components/MkNoteSub.vue";
|
import MkNoteSub from "@/components/MkNoteSub.vue";
|
||||||
import XRenoteButton from "@/components/MkRenoteButton.vue";
|
import type XRenoteButton from "@/components/MkRenoteButton.vue";
|
||||||
import MkUserCardMini from "@/components/MkUserCardMini.vue";
|
import MkUserCardMini from "@/components/MkUserCardMini.vue";
|
||||||
import MkReactedUsers from "@/components/MkReactedUsers.vue";
|
import MkReactedUsers from "@/components/MkReactedUsers.vue";
|
||||||
import { pleaseLogin } from "@/scripts/please-login";
|
import { pleaseLogin } from "@/scripts/please-login";
|
||||||
|
@ -170,16 +171,15 @@ import { getNoteMenu } from "@/scripts/get-note-menu";
|
||||||
import { useNoteCapture } from "@/scripts/use-note-capture";
|
import { useNoteCapture } from "@/scripts/use-note-capture";
|
||||||
import { deepClone } from "@/scripts/clone";
|
import { deepClone } from "@/scripts/clone";
|
||||||
import { stream } from "@/stream";
|
import { stream } from "@/stream";
|
||||||
import { NoteUpdatedEvent } from "firefish-js/built/streaming.types";
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
note: misskey.entities.Note;
|
note: misskey.entities.Note;
|
||||||
pinned?: boolean;
|
pinned?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
let tab = ref("replies");
|
const tab = ref("replies");
|
||||||
|
|
||||||
let note = ref(deepClone(props.note));
|
const 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;
|
||||||
|
@ -214,12 +214,12 @@ const muted = ref(
|
||||||
);
|
);
|
||||||
const translation = ref(null);
|
const translation = ref(null);
|
||||||
const translating = ref(false);
|
const translating = ref(false);
|
||||||
let conversation = ref<null | misskey.entities.Note[]>([]);
|
const 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[]>([]);
|
const directReplies = ref<null | misskey.entities.Note[]>([]);
|
||||||
let directQuotes = ref<null | misskey.entities.Note[]>([]);
|
const directQuotes = ref<null | misskey.entities.Note[]>([]);
|
||||||
let clips = ref();
|
const clips = ref();
|
||||||
let renotes = ref();
|
const renotes = ref();
|
||||||
let isScrolling;
|
let isScrolling;
|
||||||
|
|
||||||
const reactionsCount = Object.values(props.note.reactions).reduce(
|
const reactionsCount = Object.values(props.note.reactions).reduce(
|
||||||
|
@ -238,7 +238,7 @@ const keymap = {
|
||||||
|
|
||||||
useNoteCapture({
|
useNoteCapture({
|
||||||
rootEl: el,
|
rootEl: el,
|
||||||
note: note,
|
note,
|
||||||
isDeletedRef: isDeleted,
|
isDeletedRef: isDeleted,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -260,7 +260,7 @@ function react(viaKeyboard = false): void {
|
||||||
(reaction) => {
|
(reaction) => {
|
||||||
os.api("notes/reactions/create", {
|
os.api("notes/reactions/create", {
|
||||||
noteId: note.value.id,
|
noteId: note.value.id,
|
||||||
reaction: reaction,
|
reaction,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
|
|
|
@ -49,7 +49,6 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from "vue";
|
import { ref } 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";
|
||||||
import MkVisibility from "@/components/MkVisibility.vue";
|
import MkVisibility from "@/components/MkVisibility.vue";
|
||||||
|
@ -63,7 +62,7 @@ const props = defineProps<{
|
||||||
pinned?: boolean;
|
pinned?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
let note = ref(props.note);
|
const note = ref(props.note);
|
||||||
|
|
||||||
const showTicker =
|
const showTicker =
|
||||||
defaultStore.state.instanceTicker === "always" ||
|
defaultStore.state.instanceTicker === "always" ||
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-size="{ min: [350, 500] }" class="fefdfafb">
|
<div v-size="{ min: [350, 500] }" class="fefdfafb">
|
||||||
<MkAvatar class="avatar" :user="$i" disableLink />
|
<MkAvatar class="avatar" :user="$i" disable-link />
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<MkUserName :user="$i" />
|
<MkUserName :user="$i" />
|
||||||
|
@ -11,7 +11,7 @@
|
||||||
:text="preprocess(text).trim()"
|
:text="preprocess(text).trim()"
|
||||||
:author="$i"
|
:author="$i"
|
||||||
:i="$i"
|
:i="$i"
|
||||||
advancedMfm
|
advanced-mfm
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import * as misskey from "firefish-js";
|
import type * as misskey from "firefish-js";
|
||||||
import XNoteHeader from "@/components/MkNoteHeader.vue";
|
import XNoteHeader from "@/components/MkNoteHeader.vue";
|
||||||
import MkSubNoteContent from "@/components/MkSubNoteContent.vue";
|
import MkSubNoteContent from "@/components/MkSubNoteContent.vue";
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<article
|
<article
|
||||||
v-if="!muted.muted || muted.what === 'reply'"
|
v-if="!muted.muted || muted.what === 'reply'"
|
||||||
|
:id="detailedView ? appearNote.id : null"
|
||||||
ref="el"
|
ref="el"
|
||||||
v-size="{ max: [450, 500] }"
|
v-size="{ max: [450, 500] }"
|
||||||
class="wrpstxzv"
|
class="wrpstxzv"
|
||||||
:id="detailedView ? appearNote.id : null"
|
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
:class="{
|
:class="{
|
||||||
children: depth > 1,
|
children: depth > 1,
|
||||||
|
@ -16,8 +16,8 @@
|
||||||
<div v-if="conversation && depth > 1" class="line"></div>
|
<div v-if="conversation && depth > 1" class="line"></div>
|
||||||
<div
|
<div
|
||||||
class="main"
|
class="main"
|
||||||
@click="noteClick"
|
|
||||||
:style="{ cursor: expandOnNoteClick ? 'pointer' : '' }"
|
:style="{ cursor: expandOnNoteClick ? 'pointer' : '' }"
|
||||||
|
@click="noteClick"
|
||||||
>
|
>
|
||||||
<div class="avatar-container">
|
<div class="avatar-container">
|
||||||
<MkAvatar class="avatar" :user="appearNote.user" />
|
<MkAvatar class="avatar" :user="appearNote.user" />
|
||||||
|
@ -32,9 +32,9 @@
|
||||||
<MkSubNoteContent
|
<MkSubNoteContent
|
||||||
class="text"
|
class="text"
|
||||||
:note="note"
|
:note="note"
|
||||||
:parentId="parentId"
|
:parent-id="parentId"
|
||||||
:conversation="conversation"
|
:conversation="conversation"
|
||||||
:detailedView="detailedView"
|
:detailed-view="detailedView"
|
||||||
@focusfooter="footerEl.focus()"
|
@focusfooter="footerEl.focus()"
|
||||||
/>
|
/>
|
||||||
<div v-if="translating || translation" class="translation">
|
<div v-if="translating || translation" class="translation">
|
||||||
|
@ -117,9 +117,9 @@
|
||||||
appearNote.myReaction != null
|
appearNote.myReaction != null
|
||||||
"
|
"
|
||||||
ref="reactButton"
|
ref="reactButton"
|
||||||
|
v-tooltip.noDelay.bottom="i18n.ts.removeReaction"
|
||||||
class="button _button reacted"
|
class="button _button reacted"
|
||||||
@click.stop="undoReact(appearNote)"
|
@click.stop="undoReact(appearNote)"
|
||||||
v-tooltip.noDelay.bottom="i18n.ts.removeReaction"
|
|
||||||
>
|
>
|
||||||
<i class="ph-minus ph-bold ph-lg"></i>
|
<i class="ph-minus ph-bold ph-lg"></i>
|
||||||
</button>
|
</button>
|
||||||
|
@ -130,7 +130,7 @@
|
||||||
isForeignLanguage &&
|
isForeignLanguage &&
|
||||||
translation == null
|
translation == null
|
||||||
"
|
"
|
||||||
class="button _button accent"
|
class="button _button"
|
||||||
@click.stop="translate"
|
@click.stop="translate"
|
||||||
v-tooltip.noDelay.bottom="i18n.ts.translate"
|
v-tooltip.noDelay.bottom="i18n.ts.translate"
|
||||||
>
|
>
|
||||||
|
@ -149,17 +149,17 @@
|
||||||
</div>
|
</div>
|
||||||
<template v-if="conversation">
|
<template v-if="conversation">
|
||||||
<MkNoteSub
|
<MkNoteSub
|
||||||
v-if="replyLevel < 11 && depth < 5"
|
|
||||||
v-for="reply in replies"
|
v-for="reply in replies"
|
||||||
|
v-if="replyLevel < 11 && depth < 5"
|
||||||
:key="reply.id"
|
:key="reply.id"
|
||||||
:note="reply"
|
:note="reply"
|
||||||
class="reply"
|
class="reply"
|
||||||
:class="{ single: replies.length == 1 }"
|
:class="{ single: replies.length == 1 }"
|
||||||
:conversation="conversation"
|
:conversation="conversation"
|
||||||
:depth="replies.length == 1 ? depth : depth + 1"
|
:depth="replies.length == 1 ? depth : depth + 1"
|
||||||
:replyLevel="replyLevel + 1"
|
:reply-level="replyLevel + 1"
|
||||||
:parentId="appearNote.id"
|
:parent-id="appearNote.id"
|
||||||
:detailedView="detailedView"
|
:detailed-view="detailedView"
|
||||||
/>
|
/>
|
||||||
<div v-else-if="replies.length > 0" class="more">
|
<div v-else-if="replies.length > 0" class="more">
|
||||||
<div class="line"></div>
|
<div class="line"></div>
|
||||||
|
@ -189,9 +189,9 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { inject, ref, computed } from "vue";
|
import { computed, inject, ref } from "vue";
|
||||||
import type { Ref } from "vue";
|
import type { Ref } from "vue";
|
||||||
import * as misskey from "firefish-js";
|
import type * as misskey from "firefish-js";
|
||||||
import * as mfm from "mfm-js";
|
import * as mfm from "mfm-js";
|
||||||
import { detect as detectLanguage_ } from "tinyld";
|
import { detect as detectLanguage_ } from "tinyld";
|
||||||
import XNoteHeader from "@/components/MkNoteHeader.vue";
|
import XNoteHeader from "@/components/MkNoteHeader.vue";
|
||||||
|
@ -237,7 +237,7 @@ const props = withDefaults(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let note = ref(deepClone(props.note));
|
const 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;
|
||||||
|
@ -261,7 +261,7 @@ 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(() =>
|
const appearNote = computed(() =>
|
||||||
isRenote ? (note.value.renote as misskey.entities.Note) : note.value,
|
isRenote ? (note.value.renote as misskey.entities.Note) : note.value,
|
||||||
);
|
);
|
||||||
const isDeleted = ref(false);
|
const isDeleted = ref(false);
|
||||||
|
@ -280,59 +280,57 @@ const replies: misskey.entities.Note[] =
|
||||||
.reverse() ?? [];
|
.reverse() ?? [];
|
||||||
const enableEmojiReactions = defaultStore.state.enableEmojiReactions;
|
const enableEmojiReactions = defaultStore.state.enableEmojiReactions;
|
||||||
const expandOnNoteClick = defaultStore.state.expandOnNoteClick;
|
const expandOnNoteClick = defaultStore.state.expandOnNoteClick;
|
||||||
|
const lang = localStorage.getItem("lang");
|
||||||
|
const translateLang = localStorage.getItem("translateLang");
|
||||||
|
|
||||||
const detectLanguage = (src: string): string => {
|
function detectLanguage(text: string) {
|
||||||
const nodes = mfm.parse(src);
|
const nodes = mfm.parse(text);
|
||||||
const filtered = mfm.extract(nodes, (node) => {
|
const filtered = mfm.extract(nodes, (node) => {
|
||||||
return node.type === "text" || node.type === "quote";
|
return node.type === "text" || node.type === "quote";
|
||||||
});
|
});
|
||||||
const purified = mfm.toString(filtered).trim();
|
const purified = mfm.toString(filtered);
|
||||||
return detectLanguage_(purified);
|
return detectLanguage_(purified);
|
||||||
};
|
}
|
||||||
|
|
||||||
const localTranslateLang = localStorage.getItem("translateLang");
|
|
||||||
const localLang = localStorage.getItem("lang");
|
|
||||||
|
|
||||||
const isForeignLanguage: boolean =
|
const isForeignLanguage: boolean =
|
||||||
defaultStore.state.detectPostLanguage &&
|
defaultStore.state.detectPostLanguage &&
|
||||||
appearNote.value.text != null &&
|
appearNote.value.text != null &&
|
||||||
(() => {
|
(() => {
|
||||||
const targetLanguage = (
|
const targetLang = (translateLang || lang || navigator.language)?.slice(
|
||||||
localTranslateLang ||
|
0,
|
||||||
localLang ||
|
2,
|
||||||
navigator.language
|
);
|
||||||
)?.slice(0, 2);
|
const postLang = detectLanguage(appearNote.value.text);
|
||||||
const postLanguage = detectLanguage(appearNote.value.text);
|
return postLang !== "" && postLang !== targetLang;
|
||||||
return postLanguage !== "" && postLanguage !== targetLanguage;
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const translate_ = async (noteId: number, targetLang: string) => {
|
async function translate_(noteId: number, targetLang: string) {
|
||||||
return await os.api("notes/translate", {
|
return await os.api("notes/translate", {
|
||||||
noteId: noteId,
|
noteId: noteId,
|
||||||
targetLang: targetLang,
|
targetLang: targetLang,
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
const translate = async () => {
|
async function translate() {
|
||||||
if (translation.value != null) return;
|
if (translation.value != null) return;
|
||||||
translating.value = true;
|
translating.value = true;
|
||||||
translation.value = await translate_(
|
translation.value = await translate_(
|
||||||
appearNote.value.id,
|
appearNote.value.id,
|
||||||
localTranslateLang || localLang || navigator.language,
|
translateLang || lang || navigator.language,
|
||||||
);
|
);
|
||||||
|
|
||||||
// use UI language as the second translation target
|
// use UI language as the second translation language
|
||||||
if (
|
if (
|
||||||
localTranslateLang != null &&
|
translateLang != null &&
|
||||||
localLang != null &&
|
lang != null &&
|
||||||
localTranslateLang !== localLang &&
|
translateLang !== lang &&
|
||||||
(!translation.value ||
|
(!translation.value ||
|
||||||
translation.value.sourceLang.toLowerCase() ===
|
translation.value.sourceLang.toLowerCase() ===
|
||||||
localTranslateLang.slice(0, 2))
|
translateLang.slice(0, 2))
|
||||||
)
|
)
|
||||||
translation.value = await translate_(appearNote.value.id, localLang);
|
translation.value = await translate_(appearNote.value.id, lang);
|
||||||
translating.value = false;
|
translating.value = false;
|
||||||
};
|
}
|
||||||
|
|
||||||
useNoteCapture({
|
useNoteCapture({
|
||||||
rootEl: el,
|
rootEl: el,
|
||||||
|
@ -358,7 +356,7 @@ function react(viaKeyboard = false): void {
|
||||||
(reaction) => {
|
(reaction) => {
|
||||||
os.api("notes/reactions/create", {
|
os.api("notes/reactions/create", {
|
||||||
noteId: appearNote.value.id,
|
noteId: appearNote.value.id,
|
||||||
reaction: reaction,
|
reaction,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #default="{ items: notes }">
|
<template #default="{ items: notes }">
|
||||||
<div class="giivymft" :class="{ noGap }" ref="tlEl">
|
<div ref="tlEl" class="giivymft" :class="{ noGap }">
|
||||||
<XList
|
<XList
|
||||||
ref="notes"
|
ref="notes"
|
||||||
v-slot="{ item: note }"
|
v-slot="{ item: note }"
|
||||||
|
|
|
@ -208,7 +208,7 @@
|
||||||
<MkFollowButton
|
<MkFollowButton
|
||||||
:user="notification.user"
|
:user="notification.user"
|
||||||
:full="true"
|
:full="true"
|
||||||
:hideMenu="true"
|
:hide-menu="true"
|
||||||
/></div
|
/></div
|
||||||
></span>
|
></span>
|
||||||
<span
|
<span
|
||||||
|
@ -262,8 +262,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, onMounted, onUnmounted, watch } from "vue";
|
import { onMounted, onUnmounted, ref, watch } from "vue";
|
||||||
import * as misskey from "firefish-js";
|
import type * as misskey from "firefish-js";
|
||||||
import XReactionIcon from "@/components/MkReactionIcon.vue";
|
import XReactionIcon from "@/components/MkReactionIcon.vue";
|
||||||
import MkFollowButton from "@/components/MkFollowButton.vue";
|
import MkFollowButton from "@/components/MkFollowButton.vue";
|
||||||
import XReactionTooltip from "@/components/MkReactionTooltip.vue";
|
import XReactionTooltip from "@/components/MkReactionTooltip.vue";
|
||||||
|
@ -300,8 +300,7 @@ const defaultReaction = ["⭐", "👍", "❤️"].includes(instance.defaultReact
|
||||||
? instance.defaultReaction
|
? instance.defaultReaction
|
||||||
: "⭐";
|
: "⭐";
|
||||||
|
|
||||||
let readObserver: IntersectionObserver | undefined;
|
let readObserver: IntersectionObserver | undefined, connection;
|
||||||
let connection;
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (!props.notification.isRead) {
|
if (!props.notification.isRead) {
|
||||||
|
|
|
@ -41,7 +41,6 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref } from "vue";
|
import { computed, ref } 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";
|
||||||
import MkInfo from "./MkInfo.vue";
|
import MkInfo from "./MkInfo.vue";
|
||||||
|
@ -65,12 +64,12 @@ const props = withDefaults(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let includingTypes = computed(() => props.includingTypes || []);
|
const includingTypes = computed(() => props.includingTypes || []);
|
||||||
|
|
||||||
const dialog = ref<InstanceType<typeof XModalWindow>>();
|
const dialog = ref<InstanceType<typeof XModalWindow>>();
|
||||||
|
|
||||||
let typesMap = ref<Record<(typeof notificationTypes)[number], boolean>>({});
|
const typesMap = ref<Record<(typeof notificationTypes)[number], boolean>>({});
|
||||||
let useGlobalSetting = ref(
|
const useGlobalSetting = ref(
|
||||||
(includingTypes.value === null || includingTypes.value.length === 0) &&
|
(includingTypes.value === null || includingTypes.value.length === 0) &&
|
||||||
props.showGlobalToggle,
|
props.showGlobalToggle,
|
||||||
);
|
);
|
||||||
|
|
|
@ -28,7 +28,7 @@ const emit = defineEmits<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const zIndex = os.claimZIndex("high");
|
const zIndex = os.claimZIndex("high");
|
||||||
let showing = ref(true);
|
const showing = ref(true);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
"
|
"
|
||||||
:key="notification.id"
|
:key="notification.id"
|
||||||
:note="notification.note"
|
:note="notification.note"
|
||||||
:collapsedReply="
|
:collapsed-reply="
|
||||||
notification.type === 'reply' ||
|
notification.type === 'reply' ||
|
||||||
(notification.type === 'mention' &&
|
(notification.type === 'mention' &&
|
||||||
notification.note.replyId != null)
|
notification.note.replyId != null)
|
||||||
|
@ -46,9 +46,10 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onUnmounted, onMounted, computed, ref } from "vue";
|
import { computed, onMounted, onUnmounted, ref } from "vue";
|
||||||
import { notificationTypes } from "firefish-js";
|
import type { notificationTypes } from "firefish-js";
|
||||||
import MkPagination, { Paging } from "@/components/MkPagination.vue";
|
import type { Paging } from "@/components/MkPagination.vue";
|
||||||
|
import MkPagination from "@/components/MkPagination.vue";
|
||||||
import XNotification from "@/components/MkNotification.vue";
|
import XNotification from "@/components/MkNotification.vue";
|
||||||
import XList from "@/components/MkDateSeparatedList.vue";
|
import XList from "@/components/MkDateSeparatedList.vue";
|
||||||
import XNote from "@/components/MkNote.vue";
|
import XNote from "@/components/MkNote.vue";
|
||||||
|
|
|
@ -8,8 +8,8 @@
|
||||||
:buttons-left="buttonsLeft"
|
:buttons-left="buttonsLeft"
|
||||||
:buttons-right="buttonsRight"
|
:buttons-right="buttonsRight"
|
||||||
:contextmenu="contextmenu"
|
:contextmenu="contextmenu"
|
||||||
@closed="$emit('closed')"
|
|
||||||
class="page-window"
|
class="page-window"
|
||||||
|
@closed="$emit('closed')"
|
||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<template v-if="pageMetadata?.value">
|
<template v-if="pageMetadata?.value">
|
||||||
|
@ -30,7 +30,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ComputedRef, provide, ref, computed } from "vue";
|
import type { ComputedRef } from "vue";
|
||||||
|
import { computed, provide, ref } 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";
|
||||||
|
@ -39,7 +40,8 @@ import { url } from "@/config";
|
||||||
import { mainRouter, routes } from "@/router";
|
import { mainRouter, routes } from "@/router";
|
||||||
import { Router } from "@/nirax";
|
import { Router } from "@/nirax";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { PageMetadata, provideMetadataReceiver } from "@/scripts/page-metadata";
|
import type { PageMetadata } from "@/scripts/page-metadata";
|
||||||
|
import { provideMetadataReceiver } from "@/scripts/page-metadata";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
initialPath: string;
|
initialPath: string;
|
||||||
|
@ -51,8 +53,8 @@ defineEmits<{
|
||||||
|
|
||||||
const router = new Router(routes, props.initialPath);
|
const router = new Router(routes, props.initialPath);
|
||||||
|
|
||||||
let pageMetadata = ref<null | ComputedRef<PageMetadata>>();
|
const pageMetadata = ref<null | ComputedRef<PageMetadata>>();
|
||||||
let windowEl = ref<InstanceType<typeof XWindow>>();
|
const 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(),
|
||||||
|
|