Merge branch 'develop' into firefish-language
This commit is contained in:
commit
f8db9119c6
357 changed files with 2223 additions and 3425 deletions
|
@ -9,7 +9,7 @@ By submitting this merge request, you agree to follow our [Contribution Guidelin
|
||||||
- [ ] I have made sure to run `pnpm run format` before submitting this pull request
|
- [ ] I have made sure to run `pnpm run format` before submitting this pull request
|
||||||
|
|
||||||
If this merge request makes changes to the Firefish API, please update `docs/api-change.md`
|
If this merge request makes changes to the Firefish API, please update `docs/api-change.md`
|
||||||
- [ ] I updated the documentation
|
- [ ] I updated the document / This merge request doesn't include API changes
|
||||||
|
|
||||||
<!-- Uncomment if your merge request has multiple authors -->
|
<!-- Uncomment if your merge request has multiple authors -->
|
||||||
<!-- Co-authored-by: Name <email@email.com> -->
|
<!-- Co-authored-by: Name <email@email.com> -->
|
||||||
|
|
|
@ -122,7 +122,7 @@ db:
|
||||||
port: 5432
|
port: 5432
|
||||||
{{- else }}
|
{{- else }}
|
||||||
host: {{ .Values.postgresql.postgresqlHostname }}
|
host: {{ .Values.postgresql.postgresqlHostname }}
|
||||||
port: {{ .Values.postgresql.postgresqlPort | default "5432" | quote }}
|
port: {{ .Values.postgresql.postgresqlPort | default 5432 }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
# Database name
|
# Database name
|
||||||
|
@ -217,8 +217,7 @@ id: 'aid'
|
||||||
#maxCaptionLength: 1500
|
#maxCaptionLength: 1500
|
||||||
|
|
||||||
# Reserved usernames that only the administrator can register with
|
# Reserved usernames that only the administrator can register with
|
||||||
reservedUsernames:
|
reservedUsernames: {{ .Values.firefish.reservedUsernames | toJson }}
|
||||||
{{ .Values.firefish.reservedUsernames | toYaml }}
|
|
||||||
|
|
||||||
# Whether disable HSTS
|
# Whether disable HSTS
|
||||||
#disableHsts: true
|
#disableHsts: true
|
||||||
|
@ -265,8 +264,7 @@ reservedUsernames:
|
||||||
# Proxy remote files (default: false)
|
# Proxy remote files (default: false)
|
||||||
#proxyRemoteFiles: true
|
#proxyRemoteFiles: true
|
||||||
|
|
||||||
allowedPrivateNetworks:
|
allowedPrivateNetworks: {{ .Values.firefish.allowedPrivateNetworks | toJson }}
|
||||||
{{ .Values.firefish.allowedPrivateNetworks | toYaml }}
|
|
||||||
|
|
||||||
# TWA
|
# TWA
|
||||||
#twa:
|
#twa:
|
||||||
|
|
|
@ -4,6 +4,24 @@ Breaking changes are indicated by the :warning: icon.
|
||||||
|
|
||||||
## v1.0.5 (unreleased)
|
## v1.0.5 (unreleased)
|
||||||
|
|
||||||
|
### dev18
|
||||||
|
|
||||||
|
- :warning: response of `meta` no longer includes the following:
|
||||||
|
- `enableTwitterIntegration`
|
||||||
|
- `enableGithubIntegration`
|
||||||
|
- `enableDiscordIntegration`
|
||||||
|
- :warning: parameter of `admin/update-meta` and response of `admin/meta` no longer include the following:
|
||||||
|
- `enableTwitterIntegration`
|
||||||
|
- `enableGithubIntegration`
|
||||||
|
- `enableDiscordIntegration`
|
||||||
|
- `twitterConsumerKey`
|
||||||
|
- `twitterConsumerSecret`
|
||||||
|
- `githubClientId`
|
||||||
|
- `githubClientSecret`
|
||||||
|
- `discordClientId`
|
||||||
|
- `discordClientSecret`
|
||||||
|
- :warning: response of `admin/show-user` no longer includes `integrations`.
|
||||||
|
|
||||||
### dev17
|
### dev17
|
||||||
|
|
||||||
- Added `lang` parameter to `notes/create` and `notes/edit`.
|
- Added `lang` parameter to `notes/create` and `notes/edit`.
|
||||||
|
|
|
@ -312,9 +312,6 @@ dayX: "{day}"
|
||||||
monthX: "{month}"
|
monthX: "{month}"
|
||||||
yearX: "{year}"
|
yearX: "{year}"
|
||||||
pages: "الصفحات"
|
pages: "الصفحات"
|
||||||
integration: "التكامل"
|
|
||||||
connectService: "اتصل"
|
|
||||||
disconnectService: "اقطع الاتصال"
|
|
||||||
enableLocalTimeline: "تفعيل الخيط المحلي"
|
enableLocalTimeline: "تفعيل الخيط المحلي"
|
||||||
enableGlobalTimeline: "تفعيل الخيط الزمني الشامل"
|
enableGlobalTimeline: "تفعيل الخيط الزمني الشامل"
|
||||||
disablingTimelinesInfo: "سيتمكن المديرون والمشرفون من الوصول إلى كل الخيوط الزمنية حتى وإن لم تفعّل."
|
disablingTimelinesInfo: "سيتمكن المديرون والمشرفون من الوصول إلى كل الخيوط الزمنية حتى وإن لم تفعّل."
|
||||||
|
|
|
@ -316,9 +316,6 @@ dayX: "{day}"
|
||||||
monthX: "{month}"
|
monthX: "{month}"
|
||||||
yearX: "{year}"
|
yearX: "{year}"
|
||||||
pages: "পৃষ্ঠা"
|
pages: "পৃষ্ঠা"
|
||||||
integration: "ইন্টিগ্রেশন"
|
|
||||||
connectService: "সংযুক্ত করুন"
|
|
||||||
disconnectService: "সংযোগ বিচ্ছিন্ন করুন"
|
|
||||||
enableLocalTimeline: "স্থানীয় টাইমলাইন চালু করুন"
|
enableLocalTimeline: "স্থানীয় টাইমলাইন চালু করুন"
|
||||||
enableGlobalTimeline: "গ্লোবাল টাইমলাইন চালু করুন"
|
enableGlobalTimeline: "গ্লোবাল টাইমলাইন চালু করুন"
|
||||||
disablingTimelinesInfo: "আপনি এই টাইমলাইনগুলি বন্ধ করলেও প্রশাসক এবং মডারেটররা এই টাইমলাইনগুলি ব্যাবহার করতে পারবে"
|
disablingTimelinesInfo: "আপনি এই টাইমলাইনগুলি বন্ধ করলেও প্রশাসক এবং মডারেটররা এই টাইমলাইনগুলি ব্যাবহার করতে পারবে"
|
||||||
|
|
|
@ -944,7 +944,6 @@ dayX: '{day}'
|
||||||
tosUrl: URL de les Condicions d'ús
|
tosUrl: URL de les Condicions d'ús
|
||||||
thisYear: Any
|
thisYear: Any
|
||||||
thisMonth: Mes
|
thisMonth: Mes
|
||||||
integration: Integracions
|
|
||||||
driveCapacityPerRemoteAccount: Capacitat del Disc per usuari remot
|
driveCapacityPerRemoteAccount: Capacitat del Disc per usuari remot
|
||||||
inMb: En megabytes
|
inMb: En megabytes
|
||||||
iconUrl: Adreça URL de la icona
|
iconUrl: Adreça URL de la icona
|
||||||
|
@ -1036,8 +1035,6 @@ accept: Accepta
|
||||||
reject: Rebutja
|
reject: Rebutja
|
||||||
yearX: '{year}'
|
yearX: '{year}'
|
||||||
pages: Pàgines
|
pages: Pàgines
|
||||||
disconnectService: Desconnectar
|
|
||||||
connectService: Connectar
|
|
||||||
enableLocalTimeline: Activa la línea de temps local
|
enableLocalTimeline: Activa la línea de temps local
|
||||||
enableRecommendedTimeline: Activa la línea de temps de recomanacions
|
enableRecommendedTimeline: Activa la línea de temps de recomanacions
|
||||||
pinnedClipId: ID del clip que vols fixar
|
pinnedClipId: ID del clip que vols fixar
|
||||||
|
|
|
@ -310,9 +310,6 @@ dayX: "{day}"
|
||||||
monthX: "{month}"
|
monthX: "{month}"
|
||||||
yearX: "{year}"
|
yearX: "{year}"
|
||||||
pages: "Stránky"
|
pages: "Stránky"
|
||||||
integration: "Integrace"
|
|
||||||
connectService: "Připojit"
|
|
||||||
disconnectService: "Odpojit"
|
|
||||||
enableLocalTimeline: "Povolit lokální čas"
|
enableLocalTimeline: "Povolit lokální čas"
|
||||||
enableGlobalTimeline: "Povolit globální čas"
|
enableGlobalTimeline: "Povolit globální čas"
|
||||||
registration: "Registrace"
|
registration: "Registrace"
|
||||||
|
|
|
@ -350,9 +350,6 @@ dayX: "{day}"
|
||||||
monthX: "{month}"
|
monthX: "{month}"
|
||||||
yearX: "{year}"
|
yearX: "{year}"
|
||||||
pages: "Nutzer-Seiten"
|
pages: "Nutzer-Seiten"
|
||||||
integration: "Integration"
|
|
||||||
connectService: "Verbinden"
|
|
||||||
disconnectService: "Trennen"
|
|
||||||
enableLocalTimeline: "Local-Timeline aktivieren"
|
enableLocalTimeline: "Local-Timeline aktivieren"
|
||||||
enableGlobalTimeline: "Global-Timeline aktivieren"
|
enableGlobalTimeline: "Global-Timeline aktivieren"
|
||||||
disablingTimelinesInfo: "Administratoren und Moderatoren haben immer Zugriff auf alle
|
disablingTimelinesInfo: "Administratoren und Moderatoren haben immer Zugriff auf alle
|
||||||
|
@ -529,12 +526,12 @@ objectStorageBaseUrl: "Basis-URL"
|
||||||
objectStorageBaseUrlDesc: "Die als Referenz verwendete URL. Verwendest du einen CDN
|
objectStorageBaseUrlDesc: "Die als Referenz verwendete URL. Verwendest du einen CDN
|
||||||
oder Proxy, gib dessen URL an. \nFür S3 verwende 'https://<bucket>.s3.amazonaws.com'.
|
oder Proxy, gib dessen URL an. \nFür S3 verwende 'https://<bucket>.s3.amazonaws.com'.
|
||||||
Für GCS o.ä. verwende 'https://storage.googleapis.com/<bucket>'."
|
Für GCS o.ä. verwende 'https://storage.googleapis.com/<bucket>'."
|
||||||
objectStorageBucket: "Eimer"
|
objectStorageBucket: "Bucket"
|
||||||
objectStorageBucketDesc: "Bitte gib den Namen des Buckets an, der bei deinem Anbieter
|
objectStorageBucketDesc: "Bitte gib den Namen des Buckets an, der bei deinem Anbieter
|
||||||
verwendet wird."
|
verwendet wird."
|
||||||
objectStoragePrefix: "Prefix"
|
objectStoragePrefix: "Prefix"
|
||||||
objectStoragePrefixDesc: "Dateien werden in Ordnern unter diesem Prefix gespeichert."
|
objectStoragePrefixDesc: "Dateien werden in Ordnern unter diesem Prefix gespeichert."
|
||||||
objectStorageEndpoint: "Limit"
|
objectStorageEndpoint: "Endpunkt"
|
||||||
objectStorageEndpointDesc: "Im Falle von S3 leerlassen, für andere Anbieter den relevanten
|
objectStorageEndpointDesc: "Im Falle von S3 leerlassen, für andere Anbieter den relevanten
|
||||||
Endpoint im Format „<host>“ oder „<host>:<port>“ angeben."
|
Endpoint im Format „<host>“ oder „<host>:<port>“ angeben."
|
||||||
objectStorageRegion: "Region"
|
objectStorageRegion: "Region"
|
||||||
|
@ -1037,6 +1034,7 @@ _accountDelete:
|
||||||
_ad:
|
_ad:
|
||||||
back: "Zurück"
|
back: "Zurück"
|
||||||
reduceFrequencyOfThisAd: "Diese Werbeanzeige weniger anzeigen"
|
reduceFrequencyOfThisAd: "Diese Werbeanzeige weniger anzeigen"
|
||||||
|
adsBy: Community-Banner von {by}
|
||||||
_forgotPassword:
|
_forgotPassword:
|
||||||
enterEmail: "Gib die Email-Adresse ein, mit der du dich registriert hast. An diese
|
enterEmail: "Gib die Email-Adresse ein, mit der du dich registriert hast. An diese
|
||||||
wird ein Link gesendet, mit dem du dein Passwort zurücksetzen kannst."
|
wird ein Link gesendet, mit dem du dein Passwort zurücksetzen kannst."
|
||||||
|
@ -1243,6 +1241,7 @@ _wordMute:
|
||||||
soft: "Leicht"
|
soft: "Leicht"
|
||||||
hard: "Schwer"
|
hard: "Schwer"
|
||||||
mutedNotes: "Stummgeschaltete Beiträge"
|
mutedNotes: "Stummgeschaltete Beiträge"
|
||||||
|
muteLangs: Stummgeschaltete Sprachen
|
||||||
_instanceMute:
|
_instanceMute:
|
||||||
instanceMuteDescription: "Schaltet alle Beiträge/Boosts stumm, die von den gelisteten
|
instanceMuteDescription: "Schaltet alle Beiträge/Boosts stumm, die von den gelisteten
|
||||||
Servern stammen, inklusive Antworten von Nutzern an einen Nutzer eines stummgeschalteten
|
Servern stammen, inklusive Antworten von Nutzern an einen Nutzer eines stummgeschalteten
|
||||||
|
@ -2215,3 +2214,5 @@ indexable: Indexierbar
|
||||||
languageForTranslation: Übersetzungssprache veröffentlichen
|
languageForTranslation: Übersetzungssprache veröffentlichen
|
||||||
openServerInfo: Anzeigen von Serverinformationen durch Anklicken des Server-Tickers
|
openServerInfo: Anzeigen von Serverinformationen durch Anklicken des Server-Tickers
|
||||||
in einem Beitrag
|
in einem Beitrag
|
||||||
|
vibrate: Vibrationen abspielen
|
||||||
|
clickToShowPatterns: Klicken um Modul-Muster anzuzeigen
|
||||||
|
|
|
@ -215,8 +215,6 @@ thisMonth: "Μήνας"
|
||||||
today: "Σήμερα"
|
today: "Σήμερα"
|
||||||
dayX: "{day}"
|
dayX: "{day}"
|
||||||
pages: "Σελίδες"
|
pages: "Σελίδες"
|
||||||
connectService: "Σύνδεση"
|
|
||||||
disconnectService: "Αποσύνδεση"
|
|
||||||
registration: "Εγγραφή"
|
registration: "Εγγραφή"
|
||||||
pinnedPages: "Καρφιτσωμένες Σελίδες"
|
pinnedPages: "Καρφιτσωμένες Σελίδες"
|
||||||
pinnedNotes: "Καρφιτσωμένες δημοσιεύσεις"
|
pinnedNotes: "Καρφιτσωμένες δημοσιεύσεις"
|
||||||
|
@ -730,7 +728,6 @@ lightThemes: Φωτεινά θέματα
|
||||||
darkThemes: Σκοτεινά θέματα
|
darkThemes: Σκοτεινά θέματα
|
||||||
inputNewFolderName: Πληκτρολογήστε ένα νέο όνομα φακέλου
|
inputNewFolderName: Πληκτρολογήστε ένα νέο όνομα φακέλου
|
||||||
hasChildFilesOrFolders: Εφόσον αυτός ο φάκελος δεν είναι άδειος, δεν μπορεί να διαγραφεί.
|
hasChildFilesOrFolders: Εφόσον αυτός ο φάκελος δεν είναι άδειος, δεν μπορεί να διαγραφεί.
|
||||||
integration: Ενσωματώσεις
|
|
||||||
enableRecommendedTimeline: Ενεργοποίηση χρονολογίου προτεινόμενων
|
enableRecommendedTimeline: Ενεργοποίηση χρονολογίου προτεινόμενων
|
||||||
driveCapacityPerLocalAccount: Μέγεθος Αποθηκευτικού Χώρου ανά τοπικό μέλος
|
driveCapacityPerLocalAccount: Μέγεθος Αποθηκευτικού Χώρου ανά τοπικό μέλος
|
||||||
driveCapacityPerRemoteAccount: Μέγεθος Αποθηκευτικού Χώρου ανά απομακρυσμένο μέλος
|
driveCapacityPerRemoteAccount: Μέγεθος Αποθηκευτικού Χώρου ανά απομακρυσμένο μέλος
|
||||||
|
|
|
@ -58,6 +58,7 @@ sendMessage: "Send a message"
|
||||||
copyUsername: "Copy username"
|
copyUsername: "Copy username"
|
||||||
searchUser: "Search for a user"
|
searchUser: "Search for a user"
|
||||||
reply: "Reply"
|
reply: "Reply"
|
||||||
|
replies: "Replies"
|
||||||
jumpToPrevious: "Jump to previous"
|
jumpToPrevious: "Jump to previous"
|
||||||
loadMore: "Load more"
|
loadMore: "Load more"
|
||||||
showMore: "Show more"
|
showMore: "Show more"
|
||||||
|
@ -112,18 +113,21 @@ unfollow: "Unfollow"
|
||||||
followRequestPending: "Follow request pending"
|
followRequestPending: "Follow request pending"
|
||||||
enterEmoji: "Enter an emoji"
|
enterEmoji: "Enter an emoji"
|
||||||
renote: "Boost"
|
renote: "Boost"
|
||||||
|
renotes: "Boosts"
|
||||||
unrenote: "Take back boost"
|
unrenote: "Take back boost"
|
||||||
renoted: "Boosted."
|
renoted: "Boosted."
|
||||||
cantRenote: "This post can't be boosted."
|
cantRenote: "This post can't be boosted."
|
||||||
cantReRenote: "A boost can't be boosted."
|
cantReRenote: "A boost can't be boosted."
|
||||||
quote: "Quote"
|
quote: "Quote"
|
||||||
|
quotes: "Quotes"
|
||||||
pinnedNote: "Pinned post"
|
pinnedNote: "Pinned post"
|
||||||
pinned: "Pin to profile"
|
pinned: "Pin to profile"
|
||||||
you: "You"
|
you: "You"
|
||||||
clickToShow: "Click to show"
|
clickToShow: "Click to show"
|
||||||
sensitive: "NSFW"
|
sensitive: "NSFW"
|
||||||
add: "Add"
|
add: "Add"
|
||||||
reaction: "Reactions"
|
reaction: "Reaction"
|
||||||
|
reactions: "Reactions"
|
||||||
removeReaction: "Remove your reaction"
|
removeReaction: "Remove your reaction"
|
||||||
enableEmojiReactions: "Enable emoji reactions"
|
enableEmojiReactions: "Enable emoji reactions"
|
||||||
showEmojisInReactionNotifications: "Show emojis in reaction notifications"
|
showEmojisInReactionNotifications: "Show emojis in reaction notifications"
|
||||||
|
@ -371,9 +375,6 @@ dayX: "{day}"
|
||||||
monthX: "{month}"
|
monthX: "{month}"
|
||||||
yearX: "{year}"
|
yearX: "{year}"
|
||||||
pages: "Pages"
|
pages: "Pages"
|
||||||
integration: "Integrations"
|
|
||||||
connectService: "Connect"
|
|
||||||
disconnectService: "Disconnect"
|
|
||||||
enableLocalTimeline: "Enable local timeline"
|
enableLocalTimeline: "Enable local timeline"
|
||||||
enableGlobalTimeline: "Enable global timeline"
|
enableGlobalTimeline: "Enable global timeline"
|
||||||
enableRecommendedTimeline: "Enable recommended timeline"
|
enableRecommendedTimeline: "Enable recommended timeline"
|
||||||
|
@ -739,6 +740,7 @@ system: "System"
|
||||||
switchUi: "Layout"
|
switchUi: "Layout"
|
||||||
desktop: "Desktop"
|
desktop: "Desktop"
|
||||||
clip: "Clip"
|
clip: "Clip"
|
||||||
|
clips: "Clips"
|
||||||
createNew: "Create new"
|
createNew: "Create new"
|
||||||
optional: "Optional"
|
optional: "Optional"
|
||||||
createNewClip: "Create new clip"
|
createNewClip: "Create new clip"
|
||||||
|
@ -781,7 +783,6 @@ pageLikesCount: "Number of liked Pages"
|
||||||
pageLikedCount: "Number of received Page likes"
|
pageLikedCount: "Number of received Page likes"
|
||||||
contact: "Contact"
|
contact: "Contact"
|
||||||
useSystemFont: "Use the system's default font"
|
useSystemFont: "Use the system's default font"
|
||||||
clips: "Clips"
|
|
||||||
clipsDesc: "Clips are like share-able categorized bookmarks. You can create clips
|
clipsDesc: "Clips are like share-able categorized bookmarks. You can create clips
|
||||||
from the menu of individual posts."
|
from the menu of individual posts."
|
||||||
experimentalFeatures: "Experimental features"
|
experimentalFeatures: "Experimental features"
|
||||||
|
@ -1153,6 +1154,7 @@ detectPostLanguage: "Automatically detect the language and show a translate butt
|
||||||
for posts in foreign languages"
|
for posts in foreign languages"
|
||||||
vibrate: "Play vibrations"
|
vibrate: "Play vibrations"
|
||||||
openServerInfo: "Show server information by clicking the server ticker on a post"
|
openServerInfo: "Show server information by clicking the server ticker on a post"
|
||||||
|
iconSet: "Icon set"
|
||||||
|
|
||||||
_sensitiveMediaDetection:
|
_sensitiveMediaDetection:
|
||||||
description: "Reduces the effort of server moderation through automatically recognizing
|
description: "Reduces the effort of server moderation through automatically recognizing
|
||||||
|
@ -2152,3 +2154,9 @@ _feeds:
|
||||||
rss: "RSS"
|
rss: "RSS"
|
||||||
atom: "Atom"
|
atom: "Atom"
|
||||||
jsonFeed: "JSON feed"
|
jsonFeed: "JSON feed"
|
||||||
|
_iconSets:
|
||||||
|
bold: "Bold"
|
||||||
|
light: "Light"
|
||||||
|
regular: "Regular"
|
||||||
|
fill: "Filled"
|
||||||
|
duotone: "Duotone"
|
||||||
|
|
|
@ -340,9 +340,6 @@ dayX: "Día {day}"
|
||||||
monthX: "Mes {month}"
|
monthX: "Mes {month}"
|
||||||
yearX: "Año {year}"
|
yearX: "Año {year}"
|
||||||
pages: "Páginas"
|
pages: "Páginas"
|
||||||
integration: "Integraciones"
|
|
||||||
connectService: "Conectar"
|
|
||||||
disconnectService: "Desconectar"
|
|
||||||
enableLocalTimeline: "Habilitar linea de tiempo local"
|
enableLocalTimeline: "Habilitar linea de tiempo local"
|
||||||
enableGlobalTimeline: "Habilitar linea de tiempo global"
|
enableGlobalTimeline: "Habilitar linea de tiempo global"
|
||||||
disablingTimelinesInfo: "Aunque se desactiven estas lineas de tiempo, por conveniencia
|
disablingTimelinesInfo: "Aunque se desactiven estas lineas de tiempo, por conveniencia
|
||||||
|
|
|
@ -339,8 +339,6 @@ instanceName: Instanssin nimi
|
||||||
thisMonth: Kuukausi
|
thisMonth: Kuukausi
|
||||||
today: Tänään
|
today: Tänään
|
||||||
monthX: '{month}'
|
monthX: '{month}'
|
||||||
connectService: Yhdistä
|
|
||||||
disconnectService: Katkaise yhteys
|
|
||||||
enableLocalTimeline: Ota käyttöön paikallinen aikajana
|
enableLocalTimeline: Ota käyttöön paikallinen aikajana
|
||||||
enableGlobalTimeline: Ota käyttöön globaali aikajana
|
enableGlobalTimeline: Ota käyttöön globaali aikajana
|
||||||
enableRecommendedTimeline: Ota käyttöön suositellut -aikajana
|
enableRecommendedTimeline: Ota käyttöön suositellut -aikajana
|
||||||
|
@ -385,7 +383,6 @@ disablingTimelinesInfo: Järjestelmänvalvojilla ja moderaattoreilla on aina pä
|
||||||
dayX: '{day}'
|
dayX: '{day}'
|
||||||
yearX: '{year}'
|
yearX: '{year}'
|
||||||
pages: Sivut
|
pages: Sivut
|
||||||
integration: Integraatiot
|
|
||||||
instanceDescription: Instanssin kuvaus
|
instanceDescription: Instanssin kuvaus
|
||||||
invite: Kutsu
|
invite: Kutsu
|
||||||
iconUrl: Ikoni URL-linkki
|
iconUrl: Ikoni URL-linkki
|
||||||
|
|
|
@ -341,9 +341,6 @@ dayX: "{day}"
|
||||||
monthX: "{month}"
|
monthX: "{month}"
|
||||||
yearX: "{year}"
|
yearX: "{year}"
|
||||||
pages: "Pages"
|
pages: "Pages"
|
||||||
integration: "Intégrations"
|
|
||||||
connectService: "Connexion"
|
|
||||||
disconnectService: "Déconnexion"
|
|
||||||
enableLocalTimeline: "Activer le fil local"
|
enableLocalTimeline: "Activer le fil local"
|
||||||
enableGlobalTimeline: "Activer le fil global"
|
enableGlobalTimeline: "Activer le fil global"
|
||||||
disablingTimelinesInfo: "Même si vous désactivez ces fils, les administrateur·rice·s
|
disablingTimelinesInfo: "Même si vous désactivez ces fils, les administrateur·rice·s
|
||||||
|
@ -646,7 +643,7 @@ emptyToDisableSmtpAuth: "Laisser le nom d’utilisateur et le mot de passe vides
|
||||||
smtpSecure: "Utiliser SSL/TLS implicitement dans les connexions SMTP"
|
smtpSecure: "Utiliser SSL/TLS implicitement dans les connexions SMTP"
|
||||||
smtpSecureInfo: "Désactiver cette option lorsque STARTTLS est utilisé"
|
smtpSecureInfo: "Désactiver cette option lorsque STARTTLS est utilisé"
|
||||||
testEmail: "Tester la distribution de courriel"
|
testEmail: "Tester la distribution de courriel"
|
||||||
wordMute: "Filtre de mots"
|
wordMute: "Filtre de mots et langages"
|
||||||
regexpError: "Erreur d’expression régulière"
|
regexpError: "Erreur d’expression régulière"
|
||||||
instanceMute: "Serveur masqué"
|
instanceMute: "Serveur masqué"
|
||||||
userSaysSomething: "{name} a dit quelque chose"
|
userSaysSomething: "{name} a dit quelque chose"
|
||||||
|
@ -960,7 +957,8 @@ _accountDelete:
|
||||||
inProgress: "Suppression en cours"
|
inProgress: "Suppression en cours"
|
||||||
_ad:
|
_ad:
|
||||||
back: "Retour"
|
back: "Retour"
|
||||||
reduceFrequencyOfThisAd: "Voir cette publicité moins souvent"
|
reduceFrequencyOfThisAd: "Voir cette bannière moins souvent"
|
||||||
|
adsBy: Bannière communautaire par {by}
|
||||||
_forgotPassword:
|
_forgotPassword:
|
||||||
enterEmail: "Entrez ici l'adresse e-mail que vous avez enregistrée pour votre compte.
|
enterEmail: "Entrez ici l'adresse e-mail que vous avez enregistrée pour votre compte.
|
||||||
Un lien vous permettant de réinitialiser votre mot de passe sera envoyé à cette
|
Un lien vous permettant de réinitialiser votre mot de passe sera envoyé à cette
|
||||||
|
@ -1145,6 +1143,13 @@ _wordMute:
|
||||||
soft: "Doux"
|
soft: "Doux"
|
||||||
hard: "Strict"
|
hard: "Strict"
|
||||||
mutedNotes: "Publications masquées"
|
mutedNotes: "Publications masquées"
|
||||||
|
muteLangsDescription2: Utiliser les code de langage (i.e en, fr, ja, zh).
|
||||||
|
lang: Langage
|
||||||
|
langDescription: Cacher du fil de publication les publications qui correspondent
|
||||||
|
à ces langues.
|
||||||
|
muteLangs: Langages filtrés
|
||||||
|
muteLangsDescription: Séparer avec des espaces or des retours à la ligne pour une
|
||||||
|
condition OU (OR).
|
||||||
_instanceMute:
|
_instanceMute:
|
||||||
instanceMuteDescription2: "Séparer avec des sauts de lignes"
|
instanceMuteDescription2: "Séparer avec des sauts de lignes"
|
||||||
title: "Masque les publications provenant des serveurs listés."
|
title: "Masque les publications provenant des serveurs listés."
|
||||||
|
@ -2218,3 +2223,5 @@ openServerInfo: Afficher les informations du serveur en cliquant sur le bandeau
|
||||||
serveur d’une publication
|
serveur d’une publication
|
||||||
indexable: Indexable
|
indexable: Indexable
|
||||||
languageForTranslation: Langage post-traduction
|
languageForTranslation: Langage post-traduction
|
||||||
|
vibrate: Jouer les vibrations
|
||||||
|
clickToShowPatterns: Cliquer pour montrer les patrons de modules
|
||||||
|
|
|
@ -342,9 +342,6 @@ dayX: "{day}"
|
||||||
monthX: "{month}"
|
monthX: "{month}"
|
||||||
yearX: "{year}"
|
yearX: "{year}"
|
||||||
pages: "Halaman"
|
pages: "Halaman"
|
||||||
integration: "Integrasi"
|
|
||||||
connectService: "Sambungkan"
|
|
||||||
disconnectService: "Putuskan"
|
|
||||||
enableLocalTimeline: "Nyalakan linimasa lokal"
|
enableLocalTimeline: "Nyalakan linimasa lokal"
|
||||||
enableGlobalTimeline: "Nyalakan linimasa global"
|
enableGlobalTimeline: "Nyalakan linimasa global"
|
||||||
disablingTimelinesInfo: "Admin dan Moderator akan selalu memiliki akses ke semua linimasa
|
disablingTimelinesInfo: "Admin dan Moderator akan selalu memiliki akses ke semua linimasa
|
||||||
|
@ -1269,8 +1266,8 @@ _tutorial:
|
||||||
{introduction} atau \"Halo dunia!\" yang sederhana."
|
{introduction} atau \"Halo dunia!\" yang sederhana."
|
||||||
step5_1: "Linimasa, linimasa di mana-mana!"
|
step5_1: "Linimasa, linimasa di mana-mana!"
|
||||||
step5_2: "Servermu memiliki {timelines} lini masa berbeda yang diaktifkan."
|
step5_2: "Servermu memiliki {timelines} lini masa berbeda yang diaktifkan."
|
||||||
step5_3: "Lini masa Beranda {icon} adalah tempat di mana kamu bisa melihat postingan
|
step5_3: "Lini masa Beranda {icon} adalah tempat kamu bisa melihat postingan dari
|
||||||
dari akun yang kamu ikuti."
|
akun yang kamu ikuti."
|
||||||
step5_4: "Linimasa Lokal {icon} adalah tempat kamu dapat melihat postingan dari
|
step5_4: "Linimasa Lokal {icon} adalah tempat kamu dapat melihat postingan dari
|
||||||
siapa pun di server ini."
|
siapa pun di server ini."
|
||||||
step6_1: "Jadi, tempat apa ini?"
|
step6_1: "Jadi, tempat apa ini?"
|
||||||
|
|
|
@ -331,9 +331,6 @@ dayX: "{day}"
|
||||||
monthX: "{month}"
|
monthX: "{month}"
|
||||||
yearX: "{year}"
|
yearX: "{year}"
|
||||||
pages: "Pagine"
|
pages: "Pagine"
|
||||||
integration: "Integrazioni"
|
|
||||||
connectService: "Connetti"
|
|
||||||
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
|
||||||
|
|
|
@ -52,6 +52,7 @@ sendMessage: "メッセージを送信"
|
||||||
copyUsername: "ユーザー名をコピー"
|
copyUsername: "ユーザー名をコピー"
|
||||||
searchUser: "ユーザーを検索"
|
searchUser: "ユーザーを検索"
|
||||||
reply: "返信"
|
reply: "返信"
|
||||||
|
replies: "返信"
|
||||||
loadMore: "もっと読み込む"
|
loadMore: "もっと読み込む"
|
||||||
showMore: "もっと見る"
|
showMore: "もっと見る"
|
||||||
showLess: "閉じる"
|
showLess: "閉じる"
|
||||||
|
@ -97,11 +98,13 @@ unfollow: "フォロー解除"
|
||||||
followRequestPending: "フォロー許可待ち"
|
followRequestPending: "フォロー許可待ち"
|
||||||
enterEmoji: "絵文字を入力"
|
enterEmoji: "絵文字を入力"
|
||||||
renote: "ブースト"
|
renote: "ブースト"
|
||||||
|
renotes: "ブースト"
|
||||||
unrenote: "ブースト解除"
|
unrenote: "ブースト解除"
|
||||||
renoted: "ブーストしました。"
|
renoted: "ブーストしました。"
|
||||||
cantRenote: "この投稿はブーストできません。"
|
cantRenote: "この投稿はブーストできません。"
|
||||||
cantReRenote: "ブーストをブーストすることはできません。"
|
cantReRenote: "ブーストをブーストすることはできません。"
|
||||||
quote: "引用"
|
quote: "引用"
|
||||||
|
quotes: "引用"
|
||||||
pinnedNote: "ピン留めされた投稿"
|
pinnedNote: "ピン留めされた投稿"
|
||||||
pinned: "ピン留め"
|
pinned: "ピン留め"
|
||||||
you: "あなた"
|
you: "あなた"
|
||||||
|
@ -109,6 +112,7 @@ clickToShow: "クリックして表示"
|
||||||
sensitive: "閲覧注意"
|
sensitive: "閲覧注意"
|
||||||
add: "追加"
|
add: "追加"
|
||||||
reaction: "リアクション"
|
reaction: "リアクション"
|
||||||
|
reactions: "リアクション"
|
||||||
enableEmojiReactions: "絵文字リアクションを有効にする"
|
enableEmojiReactions: "絵文字リアクションを有効にする"
|
||||||
showEmojisInReactionNotifications: "自分の投稿に対するリアクションの通知で絵文字を表示する"
|
showEmojisInReactionNotifications: "自分の投稿に対するリアクションの通知で絵文字を表示する"
|
||||||
reactionSetting: "ピッカーに表示するリアクション"
|
reactionSetting: "ピッカーに表示するリアクション"
|
||||||
|
@ -334,9 +338,6 @@ dayX: "{day}日"
|
||||||
monthX: "{month}月"
|
monthX: "{month}月"
|
||||||
yearX: "{year}年"
|
yearX: "{year}年"
|
||||||
pages: "ページ"
|
pages: "ページ"
|
||||||
integration: "連携"
|
|
||||||
connectService: "接続する"
|
|
||||||
disconnectService: "切断する"
|
|
||||||
enableLocalTimeline: "ローカルタイムラインを有効にする"
|
enableLocalTimeline: "ローカルタイムラインを有効にする"
|
||||||
enableGlobalTimeline: "グローバルタイムラインを有効にする"
|
enableGlobalTimeline: "グローバルタイムラインを有効にする"
|
||||||
enableRecommendedTimeline: "おすすめタイムラインを有効にする"
|
enableRecommendedTimeline: "おすすめタイムラインを有効にする"
|
||||||
|
@ -615,7 +616,7 @@ emptyToDisableSmtpAuth: "ユーザー名とパスワードを空欄にするこ
|
||||||
smtpSecure: "SMTP 接続に暗黙的なSSL/TLSを使用する"
|
smtpSecure: "SMTP 接続に暗黙的なSSL/TLSを使用する"
|
||||||
smtpSecureInfo: "STARTTLS使用時はオフにします。"
|
smtpSecureInfo: "STARTTLS使用時はオフにします。"
|
||||||
testEmail: "配信テスト"
|
testEmail: "配信テスト"
|
||||||
wordMute: "ワードミュート"
|
wordMute: "単語または言語のミュート"
|
||||||
regexpError: "正規表現エラー"
|
regexpError: "正規表現エラー"
|
||||||
regexpErrorDescription: "{tab}ワードミュートの{line}行目の正規表現にエラーが発生しました:"
|
regexpErrorDescription: "{tab}ワードミュートの{line}行目の正規表現にエラーが発生しました:"
|
||||||
instanceMute: "サーバーミュート"
|
instanceMute: "サーバーミュート"
|
||||||
|
@ -693,7 +694,7 @@ no: "いいえ"
|
||||||
driveFilesCount: "ドライブのファイル数"
|
driveFilesCount: "ドライブのファイル数"
|
||||||
driveUsage: "ドライブ使用量"
|
driveUsage: "ドライブ使用量"
|
||||||
noCrawle: "クローラーによるインデックスを拒否"
|
noCrawle: "クローラーによるインデックスを拒否"
|
||||||
noCrawleDescription: "検索エンジンにあなたのプロフィールや投稿、ページなどのコンテンツを登録(インデックス)しないよう要請します。"
|
noCrawleDescription: "Web検索にあなたのプロフィールや投稿、ページなどのコンテンツを登録(インデックス)しないよう要請します。"
|
||||||
lockedAccountInfo: "フォローを承認制にしても、投稿の公開範囲を「フォロワー」にしない限り、誰でもあなたの投稿を見られます。"
|
lockedAccountInfo: "フォローを承認制にしても、投稿の公開範囲を「フォロワー」にしない限り、誰でもあなたの投稿を見られます。"
|
||||||
alwaysMarkSensitive: "デフォルトでメディアを閲覧注意にする"
|
alwaysMarkSensitive: "デフォルトでメディアを閲覧注意にする"
|
||||||
loadRawImages: "添付画像のサムネイルをオリジナル画質にする"
|
loadRawImages: "添付画像のサムネイルをオリジナル画質にする"
|
||||||
|
@ -810,7 +811,7 @@ instanceSecurity: "サーバーのセキュリティー"
|
||||||
secureModeInfo: "認証情報の無いリモートサーバーからのリクエストに応えません。"
|
secureModeInfo: "認証情報の無いリモートサーバーからのリクエストに応えません。"
|
||||||
privateMode: "非公開モード"
|
privateMode: "非公開モード"
|
||||||
privateModeInfo: "有効にすると、許可したサーバーのみからリクエストを受け付けます。"
|
privateModeInfo: "有効にすると、許可したサーバーのみからリクエストを受け付けます。"
|
||||||
allowedInstances: "許可されたサーバー"
|
allowedInstances: "許可するサーバー"
|
||||||
allowedInstancesDescription: "許可したいサーバーのホストを改行で区切って設定します。非公開モードだけで有効です。"
|
allowedInstancesDescription: "許可したいサーバーのホストを改行で区切って設定します。非公開モードだけで有効です。"
|
||||||
previewNoteText: "本文をプレビュー"
|
previewNoteText: "本文をプレビュー"
|
||||||
customCss: "カスタムCSS"
|
customCss: "カスタムCSS"
|
||||||
|
@ -990,6 +991,7 @@ remindMeLater: "また後で"
|
||||||
addRe: "閲覧注意の投稿への返信で、注釈の先頭に\"re:\"を追加する"
|
addRe: "閲覧注意の投稿への返信で、注釈の先頭に\"re:\"を追加する"
|
||||||
languageForTranslation: "投稿翻訳に使用する言語"
|
languageForTranslation: "投稿翻訳に使用する言語"
|
||||||
detectPostLanguage: "投稿の言語を自動検出し、外国語の投稿に翻訳ボタンを表示する"
|
detectPostLanguage: "投稿の言語を自動検出し、外国語の投稿に翻訳ボタンを表示する"
|
||||||
|
iconSet: "アイコンのスタイル"
|
||||||
|
|
||||||
_sensitiveMediaDetection:
|
_sensitiveMediaDetection:
|
||||||
description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てられます。サーバーの負荷が少し増えます。"
|
description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てられます。サーバーの負荷が少し増えます。"
|
||||||
|
@ -1504,7 +1506,7 @@ _profile:
|
||||||
youCanIncludeHashtags: "ハッシュタグを含められます。"
|
youCanIncludeHashtags: "ハッシュタグを含められます。"
|
||||||
metadata: "追加情報"
|
metadata: "追加情報"
|
||||||
metadataEdit: "追加情報を編集"
|
metadataEdit: "追加情報を編集"
|
||||||
metadataDescription: "プロフィールに表として追加情報を表示できます。{a}タグまたは{l}タグを{rel}とともに追加すると、プロフィールのリンクを確認できます。"
|
metadataDescription: "プロフィールに追加情報を表示できます。{rel}属性をつけた{a}タグや{l}タグを含むページをリンクすることで、リンクの本人確認もできます!"
|
||||||
metadataLabel: "ラベル"
|
metadataLabel: "ラベル"
|
||||||
metadataContent: "内容"
|
metadataContent: "内容"
|
||||||
changeAvatar: "アバター画像を変更"
|
changeAvatar: "アバター画像を変更"
|
||||||
|
@ -1990,3 +1992,12 @@ confirm: 確認
|
||||||
exportZip: ZIPをエクスポート
|
exportZip: ZIPをエクスポート
|
||||||
openServerInfo: "投稿内のサーバー名をクリックでサーバー情報を開く"
|
openServerInfo: "投稿内のサーバー名をクリックでサーバー情報を開く"
|
||||||
indexableDescription: MastodonやFirefishなどの検索機能に、あなたの投稿が表示されるのを許可します。
|
indexableDescription: MastodonやFirefishなどの検索機能に、あなたの投稿が表示されるのを許可します。
|
||||||
|
clickToShowPatterns: クリックしてトラックを表示
|
||||||
|
vibrate: 振動を有効にする
|
||||||
|
indexable: 投稿検索に登録
|
||||||
|
_iconSets:
|
||||||
|
bold: "太め"
|
||||||
|
light: "細め"
|
||||||
|
regular: "標準"
|
||||||
|
fill: "塗りつぶし"
|
||||||
|
duotone: "2色"
|
||||||
|
|
|
@ -138,9 +138,9 @@ addEmoji: "絵文字を追加"
|
||||||
settingGuide: "ええ感じの設定"
|
settingGuide: "ええ感じの設定"
|
||||||
cacheRemoteFiles: "リモートのファイルをキャッシュする"
|
cacheRemoteFiles: "リモートのファイルをキャッシュする"
|
||||||
cacheRemoteFilesDescription: "この設定を切っとくと、リモートファイルをキャッシュせず直リンクするようになるで。サーバーの容量は節約できるけど、サムネイルが作られんくなるから通信量が増えるで。"
|
cacheRemoteFilesDescription: "この設定を切っとくと、リモートファイルをキャッシュせず直リンクするようになるで。サーバーの容量は節約できるけど、サムネイルが作られんくなるから通信量が増えるで。"
|
||||||
flagAsBot: "ワイはBotや 🤖"
|
flagAsBot: "ワイはBotや🤖"
|
||||||
flagAsBotDescription: "もしこのアカウントがプログラムによって運用されるんやったら、このフラグをオンにしてたのむで。オンにすると、反応の連鎖を防ぐためのフラグとして他の開発者に役立ったり、Firefishのシステム上での扱いがBotに合ったもんになったりするんやで。"
|
flagAsBotDescription: "もしこのアカウントがプログラムによって運用されるんやったら、このフラグをオンにしてたのむで。オンにすると、反応の連鎖を防ぐためのフラグとして他の開発者に役立ったり、Firefishのシステム上での扱いがBotに合ったもんになったりするんやで。"
|
||||||
flagAsCat: "ワイはCatや 🐯"
|
flagAsCat: "ワイはCatや🐯"
|
||||||
flagAsCatDescription: "自分、猫ちゃんならこのフラグつけてみ?"
|
flagAsCatDescription: "自分、猫ちゃんならこのフラグつけてみ?"
|
||||||
flagShowTimelineReplies: "タイムラインに返信を表示させたる"
|
flagShowTimelineReplies: "タイムラインに返信を表示させたる"
|
||||||
flagShowTimelineRepliesDescription: "有効にすると、タイムラインに他のユーザー宛ての投稿も表示したるで。"
|
flagShowTimelineRepliesDescription: "有効にすると、タイムラインに他のユーザー宛ての投稿も表示したるで。"
|
||||||
|
@ -246,7 +246,7 @@ uploadFromUrl: "URLアップロード"
|
||||||
uploadFromUrlDescription: "このURLのファイルをアップロードしたいねん"
|
uploadFromUrlDescription: "このURLのファイルをアップロードしたいねん"
|
||||||
uploadFromUrlRequested: "アップロードしたい言うといたで"
|
uploadFromUrlRequested: "アップロードしたい言うといたで"
|
||||||
uploadFromUrlMayTakeTime: "アップロード終わるんにちょい時間かかるかもしれへんわ。"
|
uploadFromUrlMayTakeTime: "アップロード終わるんにちょい時間かかるかもしれへんわ。"
|
||||||
explore: "みつける"
|
explore: "みっける"
|
||||||
messageRead: "もう読まはった"
|
messageRead: "もう読まはった"
|
||||||
noMoreHistory: "これより過去の履歴はあらへんで"
|
noMoreHistory: "これより過去の履歴はあらへんで"
|
||||||
startMessaging: "チャットやるで"
|
startMessaging: "チャットやるで"
|
||||||
|
@ -317,9 +317,6 @@ dayX: "{day}日"
|
||||||
monthX: "{month}月"
|
monthX: "{month}月"
|
||||||
yearX: "{year}年"
|
yearX: "{year}年"
|
||||||
pages: "ページ"
|
pages: "ページ"
|
||||||
integration: "連携"
|
|
||||||
connectService: "つなげるで"
|
|
||||||
disconnectService: "切るで"
|
|
||||||
enableLocalTimeline: "ローカルタイムラインを使えるようにする"
|
enableLocalTimeline: "ローカルタイムラインを使えるようにする"
|
||||||
enableGlobalTimeline: "グローバルタイムラインを使えるようにする"
|
enableGlobalTimeline: "グローバルタイムラインを使えるようにする"
|
||||||
disablingTimelinesInfo: "ここらへんのタイムラインを使えんようにしてしもても、管理者とモデレーターは使えるままになってるで、そうやなかったら不便やからな。"
|
disablingTimelinesInfo: "ここらへんのタイムラインを使えんようにしてしもても、管理者とモデレーターは使えるままになってるで、そうやなかったら不便やからな。"
|
||||||
|
@ -334,7 +331,7 @@ bannerUrl: "バナー画像のURL"
|
||||||
backgroundImageUrl: "背景画像のURL"
|
backgroundImageUrl: "背景画像のURL"
|
||||||
basicInfo: "基本情報"
|
basicInfo: "基本情報"
|
||||||
pinnedUsers: "ピン留めしたユーザー"
|
pinnedUsers: "ピン留めしたユーザー"
|
||||||
pinnedUsersDescription: "「みつける」ページとかにピン留めしたいユーザーをここに書けばええんやで。他ん人との名前は改行で区切ればええんやで。"
|
pinnedUsersDescription: "「みっける」ページとかにピン留めしときたい兄ちゃんらをここに書いといたらええわ。名前は改行で区切ればええで。"
|
||||||
pinnedPages: "ピン留めページ"
|
pinnedPages: "ピン留めページ"
|
||||||
pinnedPagesDescription: "サーバーのいっちゃん上にピン留めしたいページのパスを、改行で区切って記述してな。"
|
pinnedPagesDescription: "サーバーのいっちゃん上にピン留めしたいページのパスを、改行で区切って記述してな。"
|
||||||
pinnedClipId: "ピン留めするクリップのID"
|
pinnedClipId: "ピン留めするクリップのID"
|
||||||
|
@ -684,7 +681,7 @@ clips: "クリップ"
|
||||||
experimentalFeatures: "実験的機能やで"
|
experimentalFeatures: "実験的機能やで"
|
||||||
developer: "開発者やで"
|
developer: "開発者やで"
|
||||||
makeExplorable: "アカウントを見つけやすくするで"
|
makeExplorable: "アカウントを見つけやすくするで"
|
||||||
makeExplorableDescription: "オフにすると、「みつける」にアカウントが載らんくなるで。"
|
makeExplorableDescription: "オフにすると、「みっける」ページに名前が載らんくなるで。"
|
||||||
showGapBetweenNotesInTimeline: "タイムライン上の投稿を離して表示するで"
|
showGapBetweenNotesInTimeline: "タイムライン上の投稿を離して表示するで"
|
||||||
duplicate: "複製"
|
duplicate: "複製"
|
||||||
left: "左"
|
left: "左"
|
||||||
|
@ -872,7 +869,7 @@ _registry:
|
||||||
domain: "ドメイン"
|
domain: "ドメイン"
|
||||||
createKey: "キーを作る"
|
createKey: "キーを作る"
|
||||||
_aboutFirefish:
|
_aboutFirefish:
|
||||||
about: "Firefishは、ThatOneCalculatorが2022年にMisskeyをいじって作った、オープンなソースのソフトウェアーや。"
|
about: "Firefishは、ThatOneCalculatorが2022年にMisskeyをいじって作った、オープンなソースのソフトウエアーや。"
|
||||||
contributors: "ごっつい貢献者"
|
contributors: "ごっつい貢献者"
|
||||||
allContributors: "全ての貢献者"
|
allContributors: "全ての貢献者"
|
||||||
source: "ソースコード"
|
source: "ソースコード"
|
||||||
|
@ -1442,6 +1439,12 @@ _tutorial:
|
||||||
step1_2: 使い始める前に、いくつか設定を済ませまひょ。すぐできますえ。
|
step1_2: 使い始める前に、いくつか設定を済ませまひょ。すぐできますえ。
|
||||||
step2_1: 最初に、あんさんのプロフィールを作りまひょ
|
step2_1: 最初に、あんさんのプロフィールを作りまひょ
|
||||||
step2_2: プロフィールを設定しはることで、他ん人があんさんの投稿を見たり、フォローしたりするときの助けになってます。
|
step2_2: プロフィールを設定しはることで、他ん人があんさんの投稿を見たり、フォローしたりするときの助けになってます。
|
||||||
|
step3_2: "あんさんのホームとソーシャルタイムラインは、どなたはんをフォローしはるかで決まります。ほな、いくつかアカウントをフォローしてみまひょ。\n\
|
||||||
|
プロフィールの右上にある、まあるい+ボタンをクリックしはるとフォローできますえ。"
|
||||||
|
step4_1: 投稿しとーみ
|
||||||
|
step5_1: タイムライン! 文字と写真の宝石箱や~
|
||||||
|
step5_2: うちのサーバーでは{timelines}種類のタイムラインをご用意しとります。
|
||||||
|
step4_2: 最初は{introduction}に投稿したり、シンプルに「ここは賑やかどすなぁ。うちはそこまで喋れまへんが、どうぞよろしゅうに」などと投稿しはる方もいてます。
|
||||||
_postForm:
|
_postForm:
|
||||||
_placeholders:
|
_placeholders:
|
||||||
b: なんかおましたか?
|
b: なんかおましたか?
|
||||||
|
|
|
@ -36,7 +36,6 @@ selectList: "Fren tabdart"
|
||||||
youHaveNoLists: "Ulac ɣur-k·m ula d yiwet n tabdart"
|
youHaveNoLists: "Ulac ɣur-k·m ula d yiwet n tabdart"
|
||||||
security: "Taɣellist"
|
security: "Taɣellist"
|
||||||
remove: "Kkes"
|
remove: "Kkes"
|
||||||
connectService: "Qqen"
|
|
||||||
userList: "Tibdarin"
|
userList: "Tibdarin"
|
||||||
securityKey: "Tasarutt n tɣellist"
|
securityKey: "Tasarutt n tɣellist"
|
||||||
securityKeyName: "Isem n tsarutt"
|
securityKeyName: "Isem n tsarutt"
|
||||||
|
|
|
@ -323,9 +323,6 @@ dayX: "{day}일"
|
||||||
monthX: "{month}월"
|
monthX: "{month}월"
|
||||||
yearX: "{year}년"
|
yearX: "{year}년"
|
||||||
pages: "페이지"
|
pages: "페이지"
|
||||||
integration: "연동"
|
|
||||||
connectService: "계정 연동"
|
|
||||||
disconnectService: "계정 연동 해제"
|
|
||||||
enableLocalTimeline: "로컬 타임라인 활성화"
|
enableLocalTimeline: "로컬 타임라인 활성화"
|
||||||
enableGlobalTimeline: "글로벌 타임라인 활성화"
|
enableGlobalTimeline: "글로벌 타임라인 활성화"
|
||||||
disablingTimelinesInfo: "특정 타임라인을 비활성화하더라도 관리자 및 모더레이터는 계속 사용할 수 있습니다."
|
disablingTimelinesInfo: "특정 타임라인을 비활성화하더라도 관리자 및 모더레이터는 계속 사용할 수 있습니다."
|
||||||
|
|
|
@ -483,8 +483,6 @@ accept: Accepteren
|
||||||
reject: Afwijzen
|
reject: Afwijzen
|
||||||
normal: Normaal
|
normal: Normaal
|
||||||
pages: Pagina's
|
pages: Pagina's
|
||||||
integration: Integraties
|
|
||||||
connectService: Koppelen
|
|
||||||
monthX: '{month}'
|
monthX: '{month}'
|
||||||
yearX: '{year}'
|
yearX: '{year}'
|
||||||
instanceName: Servernaam
|
instanceName: Servernaam
|
||||||
|
@ -492,7 +490,6 @@ instanceDescription: Server omschrijving
|
||||||
maintainerName: Onderhouder
|
maintainerName: Onderhouder
|
||||||
maintainerEmail: Onderhouder email
|
maintainerEmail: Onderhouder email
|
||||||
tosUrl: Algemene Voorwaarden URL
|
tosUrl: Algemene Voorwaarden URL
|
||||||
disconnectService: Ontkoppelen
|
|
||||||
unread: Ongelezen
|
unread: Ongelezen
|
||||||
manageGroups: Beheer groepen
|
manageGroups: Beheer groepen
|
||||||
subscribePushNotification: Pushmeldingen inschakelen
|
subscribePushNotification: Pushmeldingen inschakelen
|
||||||
|
|
|
@ -225,8 +225,6 @@ instanceDescription: Tjenerbeskrivelse
|
||||||
maintainerName: Administrator
|
maintainerName: Administrator
|
||||||
maintainerEmail: Administrator-epost
|
maintainerEmail: Administrator-epost
|
||||||
monthX: '{month}'
|
monthX: '{month}'
|
||||||
connectService: Koble til
|
|
||||||
disconnectService: Koble fra
|
|
||||||
enableLocalTimeline: Aktiver lokal tidslinje
|
enableLocalTimeline: Aktiver lokal tidslinje
|
||||||
enableRegistration: Tillat registrering av nye brukere
|
enableRegistration: Tillat registrering av nye brukere
|
||||||
invite: Inviter
|
invite: Inviter
|
||||||
|
@ -449,7 +447,6 @@ watch: Følg med på
|
||||||
thisMonth: Måned
|
thisMonth: Måned
|
||||||
today: I dag
|
today: I dag
|
||||||
dayX: '{day}'
|
dayX: '{day}'
|
||||||
integration: Integrasjoner
|
|
||||||
yearX: '{year}'
|
yearX: '{year}'
|
||||||
pages: Sider
|
pages: Sider
|
||||||
enableRecaptcha: Slå på reCAPTCHA
|
enableRecaptcha: Slå på reCAPTCHA
|
||||||
|
|
|
@ -331,9 +331,6 @@ dayX: "{day}"
|
||||||
monthX: "{month}"
|
monthX: "{month}"
|
||||||
yearX: "{year}"
|
yearX: "{year}"
|
||||||
pages: "Strony"
|
pages: "Strony"
|
||||||
integration: "Integracje"
|
|
||||||
connectService: "Połącz"
|
|
||||||
disconnectService: "Rozłącz"
|
|
||||||
enableLocalTimeline: "Włącz lokalną oś czasu"
|
enableLocalTimeline: "Włącz lokalną oś czasu"
|
||||||
enableGlobalTimeline: "Włącz globalną oś czasu"
|
enableGlobalTimeline: "Włącz globalną oś czasu"
|
||||||
disablingTimelinesInfo: "Administratorzy i moderatorzy będą zawsze mieć dostęp do
|
disablingTimelinesInfo: "Administratorzy i moderatorzy będą zawsze mieć dostęp do
|
||||||
|
|
|
@ -339,9 +339,6 @@ dayX: " Dia {day}"
|
||||||
monthX: "mês de {month}"
|
monthX: "mês de {month}"
|
||||||
yearX: "Ano {year}"
|
yearX: "Ano {year}"
|
||||||
pages: "Páginas"
|
pages: "Páginas"
|
||||||
integration: "Integração"
|
|
||||||
connectService: "Conectar"
|
|
||||||
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
|
disablingTimelinesInfo: "Se você desabilitar essas linhas do tempo, administradores
|
||||||
|
|
|
@ -316,9 +316,6 @@ dayX: "{day}"
|
||||||
monthX: "{month}"
|
monthX: "{month}"
|
||||||
yearX: "{year}"
|
yearX: "{year}"
|
||||||
pages: "Pagini"
|
pages: "Pagini"
|
||||||
integration: "Integrare"
|
|
||||||
connectService: "Conectează"
|
|
||||||
disconnectService: "Deconectează"
|
|
||||||
enableLocalTimeline: "Activează cronologia locală"
|
enableLocalTimeline: "Activează cronologia locală"
|
||||||
enableGlobalTimeline: "Activeaza cronologia globală"
|
enableGlobalTimeline: "Activeaza cronologia globală"
|
||||||
disablingTimelinesInfo: "Administratorii și Moderatorii vor avea mereu access la toate cronologiile, chiar dacă nu sunt activate."
|
disablingTimelinesInfo: "Administratorii și Moderatorii vor avea mereu access la toate cronologiile, chiar dacă nu sunt activate."
|
||||||
|
|
|
@ -335,9 +335,6 @@ dayX: "{day} день"
|
||||||
monthX: "{month} месяц"
|
monthX: "{month} месяц"
|
||||||
yearX: "{year} год"
|
yearX: "{year} год"
|
||||||
pages: "Страницы"
|
pages: "Страницы"
|
||||||
integration: "Интеграции"
|
|
||||||
connectService: "Подключиться"
|
|
||||||
disconnectService: "Отключиться"
|
|
||||||
enableLocalTimeline: "Включить локальную ленту"
|
enableLocalTimeline: "Включить локальную ленту"
|
||||||
enableGlobalTimeline: "Включить глобальную ленту"
|
enableGlobalTimeline: "Включить глобальную ленту"
|
||||||
disablingTimelinesInfo: "У администраторов и модераторов есть доступ ко всем лентам,
|
disablingTimelinesInfo: "У администраторов и модераторов есть доступ ко всем лентам,
|
||||||
|
|
|
@ -317,9 +317,6 @@ dayX: "{day}"
|
||||||
monthX: "{month}"
|
monthX: "{month}"
|
||||||
yearX: "{year}"
|
yearX: "{year}"
|
||||||
pages: "Stránky"
|
pages: "Stránky"
|
||||||
integration: "Integrácia"
|
|
||||||
connectService: "Pripojiť"
|
|
||||||
disconnectService: "Odpojiť"
|
|
||||||
enableLocalTimeline: "Povoliť lokálnu časovú os"
|
enableLocalTimeline: "Povoliť lokálnu časovú os"
|
||||||
enableGlobalTimeline: "Povoliť globálnu časovú os"
|
enableGlobalTimeline: "Povoliť globálnu časovú os"
|
||||||
disablingTimelinesInfo: "Administrátori a moderátori majú vždy prístup ku všetkým časovým osiam, aj keď sú vypnuté."
|
disablingTimelinesInfo: "Administrátori a moderátori majú vždy prístup ku všetkým časovým osiam, aj keď sú vypnuté."
|
||||||
|
|
|
@ -365,8 +365,6 @@ today: Idag
|
||||||
dayX: '{day}'
|
dayX: '{day}'
|
||||||
monthX: '{month}'
|
monthX: '{month}'
|
||||||
yearX: '{year}'
|
yearX: '{year}'
|
||||||
connectService: Anslut
|
|
||||||
disconnectService: Bortkoppla
|
|
||||||
enableLocalTimeline: Anslut till lokal tidslinje
|
enableLocalTimeline: Anslut till lokal tidslinje
|
||||||
invite: Bjud in
|
invite: Bjud in
|
||||||
driveCapacityPerLocalAccount: Enhetens kapacitet per lokal användare
|
driveCapacityPerLocalAccount: Enhetens kapacitet per lokal användare
|
||||||
|
@ -779,7 +777,6 @@ serverLogs: Serverloggar
|
||||||
deleteAll: Radera alla
|
deleteAll: Radera alla
|
||||||
removeAllFollowing: Sluta följa alla följda användare
|
removeAllFollowing: Sluta följa alla följda användare
|
||||||
medium: Mellan
|
medium: Mellan
|
||||||
integration: Integreringar
|
|
||||||
xl: XL
|
xl: XL
|
||||||
desktop: Skrivbord
|
desktop: Skrivbord
|
||||||
createNew: Skapa nya
|
createNew: Skapa nya
|
||||||
|
|
|
@ -327,9 +327,6 @@ dayX: "{วัน}"
|
||||||
monthX: "{เดือน}"
|
monthX: "{เดือน}"
|
||||||
yearX: "{ปี}"
|
yearX: "{ปี}"
|
||||||
pages: "หน้า"
|
pages: "หน้า"
|
||||||
integration: "รวบรวม"
|
|
||||||
connectService: "เชื่อมต่อ"
|
|
||||||
disconnectService: "ตัดการเชื่อมต่อ"
|
|
||||||
enableLocalTimeline: "เปิดใช้งานไทม์ไลน์ในพื้นที่"
|
enableLocalTimeline: "เปิดใช้งานไทม์ไลน์ในพื้นที่"
|
||||||
enableGlobalTimeline: "เปิดใช้งานไทม์ไลน์ทั่วโลก"
|
enableGlobalTimeline: "เปิดใช้งานไทม์ไลน์ทั่วโลก"
|
||||||
disablingTimelinesInfo: "ผู้ดูแลระบบและผู้ควบคุมจะสามารถเข้าถึงไทม์ไลน์ทั้งหมด ถึงแม้ว่าจะไม่ได้เปิดใช้งานก็ตาม"
|
disablingTimelinesInfo: "ผู้ดูแลระบบและผู้ควบคุมจะสามารถเข้าถึงไทม์ไลน์ทั้งหมด ถึงแม้ว่าจะไม่ได้เปิดใช้งานก็ตาม"
|
||||||
|
|
|
@ -16,10 +16,10 @@ noNotifications: "Bildirim bulunmuyor"
|
||||||
settings: "Ayarlar"
|
settings: "Ayarlar"
|
||||||
basicSettings: "Temel Ayarlar"
|
basicSettings: "Temel Ayarlar"
|
||||||
otherSettings: "Diğer Ayarlar"
|
otherSettings: "Diğer Ayarlar"
|
||||||
openInWindow: "Bir pencere ile aç"
|
openInWindow: "Açılır pencerede aç"
|
||||||
profile: "Profil"
|
profile: "Profil"
|
||||||
timeline: "Zaman çizelgesi"
|
timeline: "Akış"
|
||||||
noAccountDescription: "Bu kullanıcı henüz kendi hakkında kısmını yazmadı."
|
noAccountDescription: "Bu kullanıcı henüz \"hakkında\" kısmını yazmadı."
|
||||||
login: "Giriş Yap"
|
login: "Giriş Yap"
|
||||||
logout: "Çıkış Yap"
|
logout: "Çıkış Yap"
|
||||||
signup: "Kayıt Ol"
|
signup: "Kayıt Ol"
|
||||||
|
@ -29,7 +29,7 @@ addUser: "Kullanıcı Ekle"
|
||||||
favorite: "Favorilere ekle"
|
favorite: "Favorilere ekle"
|
||||||
favorites: "Favoriler"
|
favorites: "Favoriler"
|
||||||
unfavorite: "Favorilerden Kaldır"
|
unfavorite: "Favorilerden Kaldır"
|
||||||
favorited: "Favorilerime eklendi."
|
favorited: "Favorilere eklendi."
|
||||||
alreadyFavorited: "Zaten favorilerinizde kayıtlı."
|
alreadyFavorited: "Zaten favorilerinizde kayıtlı."
|
||||||
pin: "Sabitlenmiş"
|
pin: "Sabitlenmiş"
|
||||||
unpin: "Sabitlemeyi kaldır"
|
unpin: "Sabitlemeyi kaldır"
|
||||||
|
@ -41,9 +41,9 @@ deleteAndEditConfirm: "Bu gönderiyi silip yeniden düzenlemek istiyor musunuz?
|
||||||
ilişkin tüm tepkiler, destekler ve yanıtlar silinecektir."
|
ilişkin tüm tepkiler, destekler ve yanıtlar silinecektir."
|
||||||
addToList: "Listeye ekle"
|
addToList: "Listeye ekle"
|
||||||
sendMessage: "Mesaj Gönder"
|
sendMessage: "Mesaj Gönder"
|
||||||
copyUsername: "Kullanıcı Adını Kopyala"
|
copyUsername: "Kullanıcı Adını kopyala"
|
||||||
searchUser: "Kullanıcıları ara"
|
searchUser: "Kullanıcıları ara"
|
||||||
pinned: "Sabitlenmiş"
|
pinned: "Profile sabitle"
|
||||||
remove: "Sil"
|
remove: "Sil"
|
||||||
smtpUser: "Kullanıcı Adı"
|
smtpUser: "Kullanıcı Adı"
|
||||||
smtpPass: "Şifre"
|
smtpPass: "Şifre"
|
||||||
|
@ -240,7 +240,7 @@ instance: Sunucu
|
||||||
fetchingAsApObject: Fediverse'den çekiliyor
|
fetchingAsApObject: Fediverse'den çekiliyor
|
||||||
removeReaction: Tepkini sil
|
removeReaction: Tepkini sil
|
||||||
rememberNoteVisibility: Gönderi görünürlüğü ayarlarını hatırla
|
rememberNoteVisibility: Gönderi görünürlüğü ayarlarını hatırla
|
||||||
attachCancel: Eklentiyi kaldır
|
attachCancel: Ek'i kaldır
|
||||||
suspend: Askıya Al
|
suspend: Askıya Al
|
||||||
unsuspend: Askıya Almayı Kaldır
|
unsuspend: Askıya Almayı Kaldır
|
||||||
unmute: Susturmayı Kaldır
|
unmute: Susturmayı Kaldır
|
||||||
|
@ -248,13 +248,13 @@ blockConfirm: Bu hesabı engellemek istediğinize emin misiniz?
|
||||||
unblockConfirm: Bu hesabın engelini kaldırmak istediğinize emin misiniz?
|
unblockConfirm: Bu hesabın engelini kaldırmak istediğinize emin misiniz?
|
||||||
settingGuide: Tavsiye edilen ayarlar
|
settingGuide: Tavsiye edilen ayarlar
|
||||||
cacheRemoteFilesDescription: Bu ayar devre dışı bırakıldığında, uzak dosyalar doğrudan
|
cacheRemoteFilesDescription: Bu ayar devre dışı bırakıldığında, uzak dosyalar doğrudan
|
||||||
uzak sunucudan yüklenir. Bunun devre dışı bırakılması depolama kullanımını azaltacak,
|
dosyanın bulunduğu sunucudan yüklenir. Bunun devre dışı bırakılması depolama kullanımını
|
||||||
ancak küçük resimler oluşturulmayacağından trafiği artıracaktır.
|
azaltacak, ancak küçük resimler oluşturulmayacağından trafiği artıracaktır.
|
||||||
flagAsCatDescription: Kedi kulaklarına sahip olacak ve bir kedi gibi konuşacaksın!
|
flagAsCatDescription: Kedi kulaklarına sahip olacak ve bir kedi gibi konuşacaksın!
|
||||||
flagSpeakAsCat: Kedi gibi konuş
|
flagSpeakAsCat: Kedi gibi konuş
|
||||||
setWallpaper: Arkaplanı ayarla
|
setWallpaper: Arkaplanı ayarla
|
||||||
removeWallpaper: Arkaplanı sil
|
removeWallpaper: Arkaplanı sil
|
||||||
operations: Operasyonlar
|
operations: İşlemler
|
||||||
clearCachedFiles: Ön belleği temizle
|
clearCachedFiles: Ön belleği temizle
|
||||||
clearCachedFilesConfirm: Önbelleğe alınan tüm uzak dosyaları silmek istediğinizden
|
clearCachedFilesConfirm: Önbelleğe alınan tüm uzak dosyaları silmek istediğinizden
|
||||||
emin misiniz?
|
emin misiniz?
|
||||||
|
@ -357,13 +357,13 @@ whatIsNew: Değişiklikleri göster
|
||||||
translate: Çevir
|
translate: Çevir
|
||||||
breakFollow: Takipçiyi sil
|
breakFollow: Takipçiyi sil
|
||||||
breakFollowConfirm: Takipçiyi kaldırmak istediğinizden emin misiniz?
|
breakFollowConfirm: Takipçiyi kaldırmak istediğinizden emin misiniz?
|
||||||
unfollowConfirm: "{name}'i takibi bırakmak istediğinizden emin misiniz?"
|
unfollowConfirm: "{name} kullanıcısını takip etmeyi bırakmak istediğinizden emin misiniz?"
|
||||||
importRequested: Bir içe aktarma isteğinde bulundunuz. Bu biraz zaman alabilir.
|
importRequested: Bir içe aktarma isteğinde bulundunuz. Bu biraz zaman alabilir.
|
||||||
somethingHappened: Bir hata ile karşılaşıldı
|
somethingHappened: Bir hata ile karşılaşıldı
|
||||||
retry: Tekrar Dene
|
retry: Tekrar Dene
|
||||||
youShouldUpgradeClient: Bu sayfayı görüntülemek için, lütfen istemcinizi yenileyin.
|
youShouldUpgradeClient: Bu sayfayı görüntülemek için, lütfen istemcinizi yenileyin.
|
||||||
reactionSetting: Tepki seçicide gösterilecek tepkiler
|
reactionSetting: Tepki seçicide gösterilecek tepkiler
|
||||||
unmarkAsSensitive: NSFW işaretini kaldır
|
unmarkAsSensitive: NSFW (Müstehcen İçerik) işaretini kaldır
|
||||||
enterFileName: Dosya adı gir
|
enterFileName: Dosya adı gir
|
||||||
noJobs: Hiçbir iş yok
|
noJobs: Hiçbir iş yok
|
||||||
instanceFollowing: Sunucuda takip ediliyor
|
instanceFollowing: Sunucuda takip ediliyor
|
||||||
|
@ -481,8 +481,8 @@ mention: Bahset
|
||||||
download: İndir
|
download: İndir
|
||||||
lists: Listeler
|
lists: Listeler
|
||||||
noLists: Hiç listen yok
|
noLists: Hiç listen yok
|
||||||
cantRenote: Bu gönderi yükseltilemez.
|
cantRenote: Bu gönderi desteklenemez.
|
||||||
cantReRenote: Bir yükseltme tekrar yükseltilemez.
|
cantReRenote: Bir destek tekrardan desteklenemez.
|
||||||
mute: Sustur
|
mute: Sustur
|
||||||
block: Engelle
|
block: Engelle
|
||||||
editWidgetsExit: Tamamlandı
|
editWidgetsExit: Tamamlandı
|
||||||
|
@ -636,10 +636,10 @@ reactionSettingDescription2: Yeniden sıralamak için sürükleyin, silmek için
|
||||||
eklemek için "+"ya basın.
|
eklemek için "+"ya basın.
|
||||||
you: Sen
|
you: Sen
|
||||||
clickToShow: Görmek için tıkla
|
clickToShow: Görmek için tıkla
|
||||||
sensitive: NSFW
|
sensitive: NSFW (Müstehcen İçerik)
|
||||||
add: Ekle
|
add: Ekle
|
||||||
reaction: Tepkiler
|
reaction: Tepkiler
|
||||||
markAsSensitive: NSFW olarak işaretle
|
markAsSensitive: NSFW (Müstehcen İçerik) olarak işaretle
|
||||||
unblock: Engeli Kaldır
|
unblock: Engeli Kaldır
|
||||||
addAccount: Hesap ekle
|
addAccount: Hesap ekle
|
||||||
network: İnternet
|
network: İnternet
|
||||||
|
@ -722,11 +722,11 @@ moveAccountDescription: Bu süreç geri döndürülemez. Taşımadan önce yeni
|
||||||
şeklinde biçimlendirilmiş hesabın etiketini girin
|
şeklinde biçimlendirilmiş hesabın etiketini girin
|
||||||
emojis: Emoji
|
emojis: Emoji
|
||||||
flagAsCat: Kedi misin? 😺
|
flagAsCat: Kedi misin? 😺
|
||||||
selectChannel: Kanal seç
|
selectChannel: Bir kanal seç
|
||||||
emojiName: Emoji adı
|
emojiName: Emoji adı
|
||||||
showOnRemote: Orijinal sayfayı aç
|
showOnRemote: Orijinal sayfayı aç
|
||||||
flagSpeakAsCatDescription: Gönderileriniz kedi modundayken miyavdirilecektir
|
flagSpeakAsCatDescription: Gönderileriniz kedi modundayken miyavdirilecektir
|
||||||
flagShowTimelineReplies: Yanıtları zaman çizelgesinde göster
|
flagShowTimelineReplies: Yanıtları akışta göster
|
||||||
silenceThisInstance: Bu sunucuyu sustur
|
silenceThisInstance: Bu sunucuyu sustur
|
||||||
proxyAccountDescription: Vekil hesabı, belirli koşullar altında kullanıcılar için
|
proxyAccountDescription: Vekil hesabı, belirli koşullar altında kullanıcılar için
|
||||||
uzaktan takipçi işlevi gören bir hesaptır. Örneğin, bir kullanıcı listeye bir uzak
|
uzaktan takipçi işlevi gören bir hesaptır. Örneğin, bir kullanıcı listeye bir uzak
|
||||||
|
@ -760,7 +760,6 @@ banner: Banner
|
||||||
nsfw: NSFW
|
nsfw: NSFW
|
||||||
doNothing: Görmezden Gel
|
doNothing: Görmezden Gel
|
||||||
watch: İzle
|
watch: İzle
|
||||||
connectService: Bağlan
|
|
||||||
registration: Kayıt
|
registration: Kayıt
|
||||||
hcaptcha: hCaptcha
|
hcaptcha: hCaptcha
|
||||||
pinnedNotes: Sabitlenmiş gönderiler
|
pinnedNotes: Sabitlenmiş gönderiler
|
||||||
|
@ -845,8 +844,8 @@ pageLoadErrorDescription: Bu problem genelde ağ hataları veya tarayıcının
|
||||||
kaynaklanır. Önbelleği temizlemeyi deneyin ve biraz bekledikten sonra tekrar deneyin.
|
kaynaklanır. Önbelleği temizlemeyi deneyin ve biraz bekledikten sonra tekrar deneyin.
|
||||||
quote: Alıntıla
|
quote: Alıntıla
|
||||||
pinnedNote: Sabitlenmiş gönderi
|
pinnedNote: Sabitlenmiş gönderi
|
||||||
renote: Yükselt
|
renote: Destekle
|
||||||
unrenote: Yükseltmeyi geri al
|
unrenote: Desteklemeyi geri al
|
||||||
emojiUrl: Emoji URL'si
|
emojiUrl: Emoji URL'si
|
||||||
suspendConfirm: Bu hesabı askıya almak istediğinize emin misiniz?
|
suspendConfirm: Bu hesabı askıya almak istediğinize emin misiniz?
|
||||||
addEmoji: Ekle
|
addEmoji: Ekle
|
||||||
|
@ -858,7 +857,7 @@ wallpaper: Arkaplan
|
||||||
searchWith: 'Arat: {q}'
|
searchWith: 'Arat: {q}'
|
||||||
youHaveNoLists: Hiçbir listen yok
|
youHaveNoLists: Hiçbir listen yok
|
||||||
followConfirm: '{name} kullanıcısını takip etmek istediğine emin misin?'
|
followConfirm: '{name} kullanıcısını takip etmek istediğine emin misin?'
|
||||||
metadata: Metadata
|
metadata: Üstveri
|
||||||
monitor: Monitör
|
monitor: Monitör
|
||||||
jobQueue: İş Sırası
|
jobQueue: İş Sırası
|
||||||
noUsers: Kullanıcılar bulunamadı
|
noUsers: Kullanıcılar bulunamadı
|
||||||
|
@ -885,7 +884,6 @@ deleteFolder: Bu klasörü sil
|
||||||
addFile: Dosya ekle
|
addFile: Dosya ekle
|
||||||
dayX: '{day}'
|
dayX: '{day}'
|
||||||
enableLocalTimeline: Yerel zaman çizgisini aktif et
|
enableLocalTimeline: Yerel zaman çizgisini aktif et
|
||||||
disconnectService: Bağlantıyı kes
|
|
||||||
enableGlobalTimeline: Global zaman çizgisini aktif et
|
enableGlobalTimeline: Global zaman çizgisini aktif et
|
||||||
enableRegistration: Yeni kullanıcı kaydını aktif et
|
enableRegistration: Yeni kullanıcı kaydını aktif et
|
||||||
invite: Davet et
|
invite: Davet et
|
||||||
|
@ -1013,7 +1011,7 @@ incorrectPassword: Yanlış şifre.
|
||||||
voteConfirm: '"{choice}" için oyunuzu onaylıyor musunuz?'
|
voteConfirm: '"{choice}" için oyunuzu onaylıyor musunuz?'
|
||||||
failedToFetchAccountInformation: Hesap bilgileri getirilemedi
|
failedToFetchAccountInformation: Hesap bilgileri getirilemedi
|
||||||
rateLimitExceeded: Hız limiti aşıldı
|
rateLimitExceeded: Hız limiti aşıldı
|
||||||
renotedBy: '{user} Yükseltti'
|
renotedBy: '{user} destekledi'
|
||||||
host: Host
|
host: Host
|
||||||
objectStorage: Nesne Depolaması
|
objectStorage: Nesne Depolaması
|
||||||
objectStorageUseSSLDesc: API bağlantıları için HTTPS kullanmayacaksanız bunu kapatın
|
objectStorageUseSSLDesc: API bağlantıları için HTTPS kullanmayacaksanız bunu kapatın
|
||||||
|
@ -1026,8 +1024,8 @@ verificationEmailSent: Bir doğrulama maili gönderildi. Doğrulamayı tamamlama
|
||||||
lütfen verilen bağlantıyı takip edin.
|
lütfen verilen bağlantıyı takip edin.
|
||||||
hashtags: Etiketler
|
hashtags: Etiketler
|
||||||
resolved: Çözüldü
|
resolved: Çözüldü
|
||||||
flagShowTimelineRepliesDescription: Açıksa, kullanıcıların zaman çizelgesindeki diğer
|
flagShowTimelineRepliesDescription: Açıksa, kullanıcıların akıştaki diğer kullanıcıların
|
||||||
kullanıcıların gönderilerine verdiği yanıtları gösterir.
|
gönderilerine verdiği yanıtları gösterir.
|
||||||
clearQueueConfirmText: Kuyrukta kalan teslim edilmemiş gönderiler birleştirilmeyecektir.
|
clearQueueConfirmText: Kuyrukta kalan teslim edilmemiş gönderiler birleştirilmeyecektir.
|
||||||
Genellikle bu işleme gerek yoktur.
|
Genellikle bu işleme gerek yoktur.
|
||||||
image: Resim
|
image: Resim
|
||||||
|
@ -1040,8 +1038,8 @@ unsuspendConfirm: Bu hesabın askıya almasını kaldırmak istediğinize emin m
|
||||||
selectList: Liste seç
|
selectList: Liste seç
|
||||||
editWidgets: Widget'ları düzenle
|
editWidgets: Widget'ları düzenle
|
||||||
showEmojisInReactionNotifications: Tepki bildirimlerinde emojileri göster
|
showEmojisInReactionNotifications: Tepki bildirimlerinde emojileri göster
|
||||||
renoteMute: Yükseltmeleri sustur
|
renoteMute: Desteklemeleri sustur
|
||||||
renoteUnmute: Yükseltmeleri susturmayı kaldır
|
renoteUnmute: Desteklemelerde ki susturmayı kaldır
|
||||||
loginFailed: Giriş yapılamadı
|
loginFailed: Giriş yapılamadı
|
||||||
proxyAccount: Vekil Hesap
|
proxyAccount: Vekil Hesap
|
||||||
selectUser: Kullanıcı seç
|
selectUser: Kullanıcı seç
|
||||||
|
@ -1068,7 +1066,7 @@ hideThisNote: Bu gönderiyi gizle
|
||||||
file: Dosya
|
file: Dosya
|
||||||
enableEmojiReactions: Emoji tepkilerini aç
|
enableEmojiReactions: Emoji tepkilerini aç
|
||||||
cw: İçerik uyarısı
|
cw: İçerik uyarısı
|
||||||
makeFollowManuallyApprove: Onay gerektiren takip istekleri
|
makeFollowManuallyApprove: Onayınızı gerektiren takip istekleri
|
||||||
today: Bugün
|
today: Bugün
|
||||||
enableRecommendedTimeline: Tavsiye edilen zaman çizgisini aktive et
|
enableRecommendedTimeline: Tavsiye edilen zaman çizgisini aktive et
|
||||||
state: Durum
|
state: Durum
|
||||||
|
@ -1165,7 +1163,7 @@ indexFromDescription: Her gönderiyi dizine eklemek için boş bırakın
|
||||||
indexNotice: Şimdi indeksleniyor. Bu muhtemelen biraz zaman alacaktır, lütfen sunucunuzu
|
indexNotice: Şimdi indeksleniyor. Bu muhtemelen biraz zaman alacaktır, lütfen sunucunuzu
|
||||||
en az bir saat yeniden başlatmayın.
|
en az bir saat yeniden başlatmayın.
|
||||||
customKaTeXMacro: Özel KaTeX makroları
|
customKaTeXMacro: Özel KaTeX makroları
|
||||||
directNotes: Direkt Mesajlar
|
directNotes: Özel Mesajlar
|
||||||
import: İçeri Aktar
|
import: İçeri Aktar
|
||||||
export: Dışarı Aktar
|
export: Dışarı Aktar
|
||||||
mentions: Bahsetmeler
|
mentions: Bahsetmeler
|
||||||
|
@ -1173,8 +1171,8 @@ files: Dosyalar
|
||||||
driveFileDeleteConfirm: '"{name}" dosyasını silmek istediğinizden emin misiniz? Dosyayı
|
driveFileDeleteConfirm: '"{name}" dosyasını silmek istediğinizden emin misiniz? Dosyayı
|
||||||
"Ek" olarak içeren tüm gönderilerden kaldırılacaktır.'
|
"Ek" olarak içeren tüm gönderilerden kaldırılacaktır.'
|
||||||
createList: Liste oluştur
|
createList: Liste oluştur
|
||||||
listsDesc: Listeler, belirtilen kullanıcılarla zaman çizelgesi oluşturmanıza olanak
|
listsDesc: Listeler, belirtilen kullanıcıların içeriklerini içeren akışlar oluşturmanıza
|
||||||
tanır. Zaman Çizelgesi sayfasından erişilebilirler.
|
olanak tanır. Akış sayfasından erişilebilirler.
|
||||||
note: Gönder
|
note: Gönder
|
||||||
enterListName: Liste için isim gir
|
enterListName: Liste için isim gir
|
||||||
unfollow: Takipten Çık
|
unfollow: Takipten Çık
|
||||||
|
@ -1183,14 +1181,14 @@ followRequestPending: Takip isteği bekleniyor
|
||||||
enterEmoji: Bir emoji gir
|
enterEmoji: Bir emoji gir
|
||||||
followRequest: Takip İsteği
|
followRequest: Takip İsteği
|
||||||
followRequests: Takip istekleri
|
followRequests: Takip istekleri
|
||||||
renoted: Yükseldi.
|
renoted: Desteklendi.
|
||||||
emoji: Emoji
|
emoji: Emoji
|
||||||
cacheRemoteFiles: Uzak dosyaları önbelleğe al
|
cacheRemoteFiles: Uzak dosyaları önbelleğe al
|
||||||
flagAsBot: Bu hesabı robot olarak işaretle
|
flagAsBot: Bu hesabı robot olarak işaretle
|
||||||
flagAsBotDescription: Bu hesap bir program tarafından kontrol ediliyorsa bu seçeneği
|
flagAsBotDescription: Bu hesap bir program tarafından kontrol ediliyorsa bu seçeneği
|
||||||
etkinleştirin. Etkinleştirilirse, diğer geliştiricilerin botlarıyla sonsuz etkileşim
|
etkinleştirin. Etkinleştirilirse, diğer geliştiricilerin botlarıyla sonsuz etkileşim
|
||||||
zincirlerinin önlemesi ve Firefish'in dahili sistemlerinin bu hesabı bir bot olarak
|
zincirlerinin önlemesi ve Firefish'in dahili sistemlerinin bu hesabı bir bot olarak
|
||||||
ele alacak şekilde ayarlaması için bir bayrak görevi görür.
|
ele alacak şekilde ayarlaması için bir işaret görevi görür.
|
||||||
clearQueue: Sırayı Temizle
|
clearQueue: Sırayı Temizle
|
||||||
hiddenTags: Gizlenmiş Etiketler
|
hiddenTags: Gizlenmiş Etiketler
|
||||||
done: Tamamlandı
|
done: Tamamlandı
|
||||||
|
@ -1206,7 +1204,6 @@ location: Konum
|
||||||
registeredDate: Katılım tarihi
|
registeredDate: Katılım tarihi
|
||||||
yearX: '{year}'
|
yearX: '{year}'
|
||||||
pages: Sayfalar
|
pages: Sayfalar
|
||||||
integration: Entegrasyonlar
|
|
||||||
antennasDesc: "Antenler, belirlediğiniz kriterlere uyan yeni gönderiler görüntüler!\n
|
antennasDesc: "Antenler, belirlediğiniz kriterlere uyan yeni gönderiler görüntüler!\n
|
||||||
Zaman çizelgeleri sayfasından erişilebilirler."
|
Zaman çizelgeleri sayfasından erişilebilirler."
|
||||||
notesAndReplies: Gönderiler ve yanıtlar
|
notesAndReplies: Gönderiler ve yanıtlar
|
||||||
|
@ -2156,3 +2153,4 @@ importZip: ZIP içe aktar
|
||||||
indexable: Endekslenebilir
|
indexable: Endekslenebilir
|
||||||
languageForTranslation: Çeviri sonrası dili
|
languageForTranslation: Çeviri sonrası dili
|
||||||
confirm: Onayla
|
confirm: Onayla
|
||||||
|
clickToShowPatterns: Modülün örüntülerini göstermek için tıklayın
|
||||||
|
|
|
@ -336,9 +336,6 @@ dayX: "{day}"
|
||||||
monthX: "{month}"
|
monthX: "{month}"
|
||||||
yearX: "{year}"
|
yearX: "{year}"
|
||||||
pages: "Сторінки"
|
pages: "Сторінки"
|
||||||
integration: "Інтеграції"
|
|
||||||
connectService: "Під’єднати"
|
|
||||||
disconnectService: "Відключитися"
|
|
||||||
enableLocalTimeline: "Увімкнути локальну стрічку"
|
enableLocalTimeline: "Увімкнути локальну стрічку"
|
||||||
enableGlobalTimeline: "Увімкнути глобальну стрічку"
|
enableGlobalTimeline: "Увімкнути глобальну стрічку"
|
||||||
disablingTimelinesInfo: "Адміністратори та модератори завжди мають доступ до всіх
|
disablingTimelinesInfo: "Адміністратори та модератори завжди мають доступ до всіх
|
||||||
|
|
|
@ -338,9 +338,6 @@ dayX: "{day}"
|
||||||
monthX: "{month}"
|
monthX: "{month}"
|
||||||
yearX: "{year}"
|
yearX: "{year}"
|
||||||
pages: "Trang"
|
pages: "Trang"
|
||||||
integration: "Tương tác"
|
|
||||||
connectService: "Kết nối"
|
|
||||||
disconnectService: "Ngắt kết nối"
|
|
||||||
enableLocalTimeline: "Bật bảng tin máy chủ"
|
enableLocalTimeline: "Bật bảng tin máy chủ"
|
||||||
enableGlobalTimeline: "Bật bảng tin liên hợp"
|
enableGlobalTimeline: "Bật bảng tin liên hợp"
|
||||||
disablingTimelinesInfo: "Quản trị viên và Kiểm duyệt viên luôn có quyền truy cập mọi
|
disablingTimelinesInfo: "Quản trị viên và Kiểm duyệt viên luôn có quyền truy cập mọi
|
||||||
|
|
|
@ -49,6 +49,7 @@ sendMessage: "发送"
|
||||||
copyUsername: "复制用户名"
|
copyUsername: "复制用户名"
|
||||||
searchUser: "搜索用户"
|
searchUser: "搜索用户"
|
||||||
reply: "回复"
|
reply: "回复"
|
||||||
|
replies: "回复"
|
||||||
loadMore: "加载更多"
|
loadMore: "加载更多"
|
||||||
showMore: "查看更多"
|
showMore: "查看更多"
|
||||||
showLess: "关闭"
|
showLess: "关闭"
|
||||||
|
@ -94,11 +95,13 @@ unfollow: "取消关注"
|
||||||
followRequestPending: "关注请求待批准"
|
followRequestPending: "关注请求待批准"
|
||||||
enterEmoji: "输入表情符号"
|
enterEmoji: "输入表情符号"
|
||||||
renote: "转发"
|
renote: "转发"
|
||||||
|
renotes: "转发"
|
||||||
unrenote: "取消转发"
|
unrenote: "取消转发"
|
||||||
renoted: "已转发。"
|
renoted: "已转发。"
|
||||||
cantRenote: "此帖子无法被转发。"
|
cantRenote: "此帖子无法被转发。"
|
||||||
cantReRenote: "转发无法被再次转发。"
|
cantReRenote: "转发无法被再次转发。"
|
||||||
quote: "引用"
|
quote: "引用"
|
||||||
|
quotes: "引用"
|
||||||
pinnedNote: "已置顶的帖子"
|
pinnedNote: "已置顶的帖子"
|
||||||
pinned: "置顶"
|
pinned: "置顶"
|
||||||
you: "您"
|
you: "您"
|
||||||
|
@ -106,6 +109,7 @@ clickToShow: "点击以显示"
|
||||||
sensitive: "敏感内容"
|
sensitive: "敏感内容"
|
||||||
add: "添加"
|
add: "添加"
|
||||||
reaction: "回应"
|
reaction: "回应"
|
||||||
|
reactions: "回应"
|
||||||
enableEmojiReaction: "启用表情符号回应"
|
enableEmojiReaction: "启用表情符号回应"
|
||||||
showEmojisInReactionNotifications: "在回应通知中显示表情符号"
|
showEmojisInReactionNotifications: "在回应通知中显示表情符号"
|
||||||
reactionSetting: "在回应选择器中显示的回应"
|
reactionSetting: "在回应选择器中显示的回应"
|
||||||
|
@ -321,9 +325,6 @@ dayX: "{day} 日"
|
||||||
monthX: "{month} 月"
|
monthX: "{month} 月"
|
||||||
yearX: "{year} 年"
|
yearX: "{year} 年"
|
||||||
pages: "页面"
|
pages: "页面"
|
||||||
integration: "整合"
|
|
||||||
connectService: "连接"
|
|
||||||
disconnectService: "断开连接"
|
|
||||||
enableLocalTimeline: "启用本地时间线功能"
|
enableLocalTimeline: "启用本地时间线功能"
|
||||||
enableGlobalTimeline: "启用全局时间线"
|
enableGlobalTimeline: "启用全局时间线"
|
||||||
disablingTimelinesInfo: "管理员和监察员将始终拥有对所有时间线的访问权,即使它们没有被启用。"
|
disablingTimelinesInfo: "管理员和监察员将始终拥有对所有时间线的访问权,即使它们没有被启用。"
|
||||||
|
@ -936,7 +937,8 @@ _accountDelete:
|
||||||
inProgress: "正在删除"
|
inProgress: "正在删除"
|
||||||
_ad:
|
_ad:
|
||||||
back: "返回"
|
back: "返回"
|
||||||
reduceFrequencyOfThisAd: "减少此广告的频率"
|
reduceFrequencyOfThisAd: "减少此横幅的频率"
|
||||||
|
adsBy: 社区横幅(作者:{by})
|
||||||
_forgotPassword:
|
_forgotPassword:
|
||||||
enterEmail: "请输入您注册账号时用的电子邮箱地址,密码重置链接将发送至该邮箱上。"
|
enterEmail: "请输入您注册账号时用的电子邮箱地址,密码重置链接将发送至该邮箱上。"
|
||||||
ifNoEmail: "如果您在注册时没有输入电子邮件地址,请联系服务器管理员。"
|
ifNoEmail: "如果您在注册时没有输入电子邮件地址,请联系服务器管理员。"
|
||||||
|
@ -1992,3 +1994,4 @@ indexable: 可索引的
|
||||||
languageForTranslation: 帖子翻译语言
|
languageForTranslation: 帖子翻译语言
|
||||||
vibrate: 播放振动
|
vibrate: 播放振动
|
||||||
openServerInfo: 点击帖子上的服务器滚动条时显示服务器信息
|
openServerInfo: 点击帖子上的服务器滚动条时显示服务器信息
|
||||||
|
clickToShowPatterns: 点击显示模块模式
|
||||||
|
|
|
@ -21,7 +21,7 @@ basicSettings: "基本設定"
|
||||||
otherSettings: "其他設定"
|
otherSettings: "其他設定"
|
||||||
openInWindow: "在新視窗開啟"
|
openInWindow: "在新視窗開啟"
|
||||||
profile: "個人檔案"
|
profile: "個人檔案"
|
||||||
timeline: "時間線"
|
timeline: "時間軸"
|
||||||
noAccountDescription: "此用戶還沒有自我介紹。"
|
noAccountDescription: "此用戶還沒有自我介紹。"
|
||||||
login: "登入"
|
login: "登入"
|
||||||
loggingIn: "登入中"
|
loggingIn: "登入中"
|
||||||
|
@ -49,6 +49,7 @@ sendMessage: "發送訊息"
|
||||||
copyUsername: "複製使用者名稱"
|
copyUsername: "複製使用者名稱"
|
||||||
searchUser: "搜尋使用者"
|
searchUser: "搜尋使用者"
|
||||||
reply: "回覆"
|
reply: "回覆"
|
||||||
|
replies: "回覆"
|
||||||
loadMore: "載入更多"
|
loadMore: "載入更多"
|
||||||
showMore: "載入更多"
|
showMore: "載入更多"
|
||||||
showLess: "關閉"
|
showLess: "關閉"
|
||||||
|
@ -94,11 +95,13 @@ unfollow: "取消追隨"
|
||||||
followRequestPending: "追隨許可批准中"
|
followRequestPending: "追隨許可批准中"
|
||||||
enterEmoji: "輸入表情符號"
|
enterEmoji: "輸入表情符號"
|
||||||
renote: "轉發"
|
renote: "轉發"
|
||||||
|
renotes: "轉發"
|
||||||
unrenote: "取消轉發"
|
unrenote: "取消轉發"
|
||||||
renoted: "已轉發。"
|
renoted: "已轉發。"
|
||||||
cantRenote: "無法轉發此貼文。"
|
cantRenote: "無法轉發此貼文。"
|
||||||
cantReRenote: "無法轉發之前已經轉發過的內容。"
|
cantReRenote: "無法轉發之前已經轉發過的內容。"
|
||||||
quote: "引用"
|
quote: "引用"
|
||||||
|
quotes: "引用"
|
||||||
pinnedNote: "已置頂的貼文"
|
pinnedNote: "已置頂的貼文"
|
||||||
pinned: "置頂"
|
pinned: "置頂"
|
||||||
you: "您"
|
you: "您"
|
||||||
|
@ -106,6 +109,7 @@ clickToShow: "按一下以顯示"
|
||||||
sensitive: "敏感內容"
|
sensitive: "敏感內容"
|
||||||
add: "新增"
|
add: "新增"
|
||||||
reaction: "反應"
|
reaction: "反應"
|
||||||
|
reactions: "反應"
|
||||||
enableEmojiReaction: "啟用表情符號反應"
|
enableEmojiReaction: "啟用表情符號反應"
|
||||||
showEmojisInReactionNotifications: "在反應通知中顯示表情符號"
|
showEmojisInReactionNotifications: "在反應通知中顯示表情符號"
|
||||||
reactionSetting: "在選擇器中顯示反應"
|
reactionSetting: "在選擇器中顯示反應"
|
||||||
|
@ -145,12 +149,12 @@ flagAsBot: "標記此帳號是機器人"
|
||||||
flagAsBotDescription: "如果本帳戶是由程式控制,請啟用此選項。啟用後,會作為標示幫助其他開發者防止機器人之間產生無限互動的行為,並會調整Firefish內部系統將本帳戶識別為機器人。"
|
flagAsBotDescription: "如果本帳戶是由程式控制,請啟用此選項。啟用後,會作為標示幫助其他開發者防止機器人之間產生無限互動的行為,並會調整Firefish內部系統將本帳戶識別為機器人。"
|
||||||
flagAsCat: "你是喵咪嗎?w😺"
|
flagAsCat: "你是喵咪嗎?w😺"
|
||||||
flagAsCatDescription: "如果想將本帳戶標示為一隻貓,請開啟此標示!"
|
flagAsCatDescription: "如果想將本帳戶標示為一隻貓,請開啟此標示!"
|
||||||
flagShowTimelineReplies: "在時間線上顯示貼文的回覆"
|
flagShowTimelineReplies: "在時間軸上顯示貼文的回覆"
|
||||||
flagShowTimelineRepliesDescription: "啟用時,時間線除了顯示用戶的貼文以外,還會顯示用戶對其他貼文的回覆。"
|
flagShowTimelineRepliesDescription: "啟用時,時間軸除了顯示用戶的貼文以外,還會顯示用戶對其他貼文的回覆。"
|
||||||
autoAcceptFollowed: "自動准予追隨中使用者的追隨請求"
|
autoAcceptFollowed: "自動准予追隨中使用者的追隨請求"
|
||||||
addAccount: "添加帳戶"
|
addAccount: "添加帳戶"
|
||||||
loginFailed: "登入失敗"
|
loginFailed: "登入失敗"
|
||||||
showOnRemote: "轉到所在伺服器顯示"
|
showOnRemote: "開啟來源頁面"
|
||||||
general: "一般"
|
general: "一般"
|
||||||
wallpaper: "桌布"
|
wallpaper: "桌布"
|
||||||
setWallpaper: "設定桌布"
|
setWallpaper: "設定桌布"
|
||||||
|
@ -320,12 +324,9 @@ dayX: "{day}日"
|
||||||
monthX: "{month}月"
|
monthX: "{month}月"
|
||||||
yearX: "{year}年"
|
yearX: "{year}年"
|
||||||
pages: "頁面"
|
pages: "頁面"
|
||||||
integration: "整合"
|
enableLocalTimeline: "開啟本地時間軸"
|
||||||
connectService: "己連結"
|
enableGlobalTimeline: "啟用公開時間軸"
|
||||||
disconnectService: "己斷開"
|
disablingTimelinesInfo: "即使您關閉了時間軸功能,管理員和板主仍可訪問所有的時間軸。"
|
||||||
enableLocalTimeline: "開啟本地時間線"
|
|
||||||
enableGlobalTimeline: "啟用公開時間線"
|
|
||||||
disablingTimelinesInfo: "即使您關閉了時間線功能,管理員和板主仍可訪問所有的時間線。"
|
|
||||||
registration: "註冊"
|
registration: "註冊"
|
||||||
enableRegistration: "開啟新使用者註冊"
|
enableRegistration: "開啟新使用者註冊"
|
||||||
invite: "邀請"
|
invite: "邀請"
|
||||||
|
@ -385,7 +386,7 @@ administrator: "管理員"
|
||||||
token: "權杖"
|
token: "權杖"
|
||||||
twoStepAuthentication: "兩階段驗證"
|
twoStepAuthentication: "兩階段驗證"
|
||||||
moderator: "板主"
|
moderator: "板主"
|
||||||
moderation: "言論調節"
|
moderation: "管理"
|
||||||
nUsersMentioned: "提到了{n}"
|
nUsersMentioned: "提到了{n}"
|
||||||
securityKey: "安全金鑰"
|
securityKey: "安全金鑰"
|
||||||
securityKeyName: "金鑰名稱"
|
securityKeyName: "金鑰名稱"
|
||||||
|
@ -458,7 +459,7 @@ youHaveNoGroups: "找不到群組"
|
||||||
joinOrCreateGroup: "請加入現有群組,或創建新群組。"
|
joinOrCreateGroup: "請加入現有群組,或創建新群組。"
|
||||||
noHistory: "沒有歷史紀錄"
|
noHistory: "沒有歷史紀錄"
|
||||||
signinHistory: "登入歷史"
|
signinHistory: "登入歷史"
|
||||||
disableAnimatedMfm: "禁用MFM動畫"
|
disableAnimatedMfm: "停用MFM動畫"
|
||||||
doing: "正在處理..."
|
doing: "正在處理..."
|
||||||
category: "類別"
|
category: "類別"
|
||||||
tags: "標籤"
|
tags: "標籤"
|
||||||
|
@ -482,7 +483,7 @@ promotion: "推廣"
|
||||||
promote: "推廣"
|
promote: "推廣"
|
||||||
numberOfDays: "有效天數"
|
numberOfDays: "有效天數"
|
||||||
hideThisNote: "隱藏此貼文"
|
hideThisNote: "隱藏此貼文"
|
||||||
showFeaturedNotesInTimeline: "在時間線上顯示熱門推薦"
|
showFeaturedNotesInTimeline: "在時間軸上顯示熱門推薦"
|
||||||
objectStorage: "Object Storage (物件儲存)"
|
objectStorage: "Object Storage (物件儲存)"
|
||||||
useObjectStorage: "使用Object Storage"
|
useObjectStorage: "使用Object Storage"
|
||||||
objectStorageBaseUrl: "根URL"
|
objectStorageBaseUrl: "根URL"
|
||||||
|
@ -502,7 +503,7 @@ objectStorageUseProxyDesc: "如果不使用代理進行API連接,請關閉"
|
||||||
objectStorageSetPublicRead: "上傳時設定為\"public-read\""
|
objectStorageSetPublicRead: "上傳時設定為\"public-read\""
|
||||||
serverLogs: "伺服器日誌"
|
serverLogs: "伺服器日誌"
|
||||||
deleteAll: "刪除所有記錄"
|
deleteAll: "刪除所有記錄"
|
||||||
showFixedPostForm: "於時間線頁頂顯示「發送貼文」方框"
|
showFixedPostForm: "於時間軸頁頂顯示「發送貼文」方框"
|
||||||
newNoteRecived: "發現新的貼文"
|
newNoteRecived: "發現新的貼文"
|
||||||
sounds: "音效"
|
sounds: "音效"
|
||||||
listen: "聆聽"
|
listen: "聆聽"
|
||||||
|
@ -596,7 +597,7 @@ emptyToDisableSmtpAuth: "留空使用者名稱及密碼以關閉SMTP驗證"
|
||||||
smtpSecure: "在 SMTP 連接中使用隱式 SSL/TLS"
|
smtpSecure: "在 SMTP 連接中使用隱式 SSL/TLS"
|
||||||
smtpSecureInfo: "如使用STARTTLS,請關閉"
|
smtpSecureInfo: "如使用STARTTLS,請關閉"
|
||||||
testEmail: "測試郵件發送"
|
testEmail: "測試郵件發送"
|
||||||
wordMute: "被靜音的文字"
|
wordMute: "被靜音的文字及語言"
|
||||||
regexpError: "正規表達式錯誤"
|
regexpError: "正規表達式錯誤"
|
||||||
regexpErrorDescription: "{tab} 靜音文字的第 {line} 行的正規表達式有錯誤:"
|
regexpErrorDescription: "{tab} 靜音文字的第 {line} 行的正規表達式有錯誤:"
|
||||||
instanceMute: "伺服器的靜音"
|
instanceMute: "伺服器的靜音"
|
||||||
|
@ -670,7 +671,7 @@ no: "取消"
|
||||||
driveFilesCount: "雲端硬碟檔案數量"
|
driveFilesCount: "雲端硬碟檔案數量"
|
||||||
driveUsage: "雲端硬碟使用量"
|
driveUsage: "雲端硬碟使用量"
|
||||||
noCrawle: "拒絕搜尋引擎索引"
|
noCrawle: "拒絕搜尋引擎索引"
|
||||||
noCrawleDescription: "要求網路搜尋引擎不要索引你的個人資料頁、貼文及頁面等。"
|
noCrawleDescription: "要求外部搜尋引擎不要收錄(索引)你的內容(個人檔案、貼文、頁面等)。"
|
||||||
lockedAccountInfo: "即使你通過了追隨者請求,除非你將貼文的可見性設定為 「追隨者」,否則任何人都能看見你的貼文。"
|
lockedAccountInfo: "即使你通過了追隨者請求,除非你將貼文的可見性設定為 「追隨者」,否則任何人都能看見你的貼文。"
|
||||||
alwaysMarkSensitive: "默認將圖像/影像標記為敏感內容"
|
alwaysMarkSensitive: "默認將圖像/影像標記為敏感內容"
|
||||||
loadRawImages: "以原始圖檔顯示附件圖檔的縮圖"
|
loadRawImages: "以原始圖檔顯示附件圖檔的縮圖"
|
||||||
|
@ -688,7 +689,7 @@ experimentalFeatures: "實驗中的功能"
|
||||||
developer: "開發者"
|
developer: "開發者"
|
||||||
makeExplorable: "使自己的帳戶能夠在“探索”頁面中顯示"
|
makeExplorable: "使自己的帳戶能夠在“探索”頁面中顯示"
|
||||||
makeExplorableDescription: "如果關閉,帳戶將不會被顯示在\"探索\"頁面中。"
|
makeExplorableDescription: "如果關閉,帳戶將不會被顯示在\"探索\"頁面中。"
|
||||||
showGapBetweenNotesInTimeline: "分開顯示時間線上的貼文"
|
showGapBetweenNotesInTimeline: "分開顯示時間軸上的貼文"
|
||||||
duplicate: "複製"
|
duplicate: "複製"
|
||||||
left: "左"
|
left: "左"
|
||||||
center: "置中"
|
center: "置中"
|
||||||
|
@ -734,7 +735,7 @@ inChannelSearch: "頻道内搜尋"
|
||||||
useReactionPickerForContextMenu: "點擊右鍵開啟反應工具欄"
|
useReactionPickerForContextMenu: "點擊右鍵開啟反應工具欄"
|
||||||
typingUsers: "{users}輸入中"
|
typingUsers: "{users}輸入中"
|
||||||
jumpToSpecifiedDate: "跳轉到特定日期"
|
jumpToSpecifiedDate: "跳轉到特定日期"
|
||||||
showingPastTimeline: "顯示過往的時間線"
|
showingPastTimeline: "顯示過往的時間軸"
|
||||||
clear: "清除"
|
clear: "清除"
|
||||||
markAllAsRead: "全部標示為已讀"
|
markAllAsRead: "全部標示為已讀"
|
||||||
goBack: "返回"
|
goBack: "返回"
|
||||||
|
@ -765,7 +766,7 @@ user: "使用者"
|
||||||
administration: "管理"
|
administration: "管理"
|
||||||
accounts: "帳戶"
|
accounts: "帳戶"
|
||||||
switch: "切換"
|
switch: "切換"
|
||||||
noMaintainerInformationWarning: "尚未設定管理員信息。"
|
noMaintainerInformationWarning: "尚未設定管理員資訊。"
|
||||||
noBotProtectionWarning: "尚未設定Bot防護。"
|
noBotProtectionWarning: "尚未設定Bot防護。"
|
||||||
configure: "設定"
|
configure: "設定"
|
||||||
postToGallery: "發佈到相簿"
|
postToGallery: "發佈到相簿"
|
||||||
|
@ -786,7 +787,7 @@ previewNoteText: "預覽文本"
|
||||||
customCss: "自定義 CSS"
|
customCss: "自定義 CSS"
|
||||||
customCssWarn: "這個設定必須由具備相關知識的人員操作,不當的設定可能导致客戶端無法正常使用。"
|
customCssWarn: "這個設定必須由具備相關知識的人員操作,不當的設定可能导致客戶端無法正常使用。"
|
||||||
global: "公開"
|
global: "公開"
|
||||||
squareAvatars: "頭像以方形顯示"
|
squareAvatars: "大頭貼以方形顯示"
|
||||||
sent: "發送"
|
sent: "發送"
|
||||||
received: "收取"
|
received: "收取"
|
||||||
searchResult: "搜尋結果"
|
searchResult: "搜尋結果"
|
||||||
|
@ -822,7 +823,7 @@ unmuteThread: "將貼文串的靜音解除"
|
||||||
ffVisibility: "連接的公開範圍"
|
ffVisibility: "連接的公開範圍"
|
||||||
ffVisibilityDescription: "您可以設定您的關注/關注者資訊的公開範圍。"
|
ffVisibilityDescription: "您可以設定您的關注/關注者資訊的公開範圍。"
|
||||||
continueThread: "查看更多貼文"
|
continueThread: "查看更多貼文"
|
||||||
deleteAccountConfirm: "將要刪除帳戶。是否確定?"
|
deleteAccountConfirm: "此帳戶將被刪除,是否繼續?"
|
||||||
incorrectPassword: "密碼錯誤。"
|
incorrectPassword: "密碼錯誤。"
|
||||||
voteConfirm: "確定投給「{choice}」?"
|
voteConfirm: "確定投給「{choice}」?"
|
||||||
hide: "隱藏"
|
hide: "隱藏"
|
||||||
|
@ -899,7 +900,7 @@ customKaTeXMacro: "自訂KaTeX巨集"
|
||||||
customKaTeXMacroDescription: "使用巨集來輕鬆輸入數學表達式吧!巨集的用法與 LaTeX 中的命令定義相同。你可以使用 \\newcommand{\\
|
customKaTeXMacroDescription: "使用巨集來輕鬆輸入數學表達式吧!巨集的用法與 LaTeX 中的命令定義相同。你可以使用 \\newcommand{\\
|
||||||
name}{content} 或 \\newcommand{\\name}[number of arguments]{content} 來輸入數學表達式。舉例來說,\\
|
name}{content} 或 \\newcommand{\\name}[number of arguments]{content} 來輸入數學表達式。舉例來說,\\
|
||||||
newcommand{\\add}[2]{#1 + #2} 會將 \\add{3}{foo} 展開為 3 + foo。巨集名稱除了可用大括號 {} 括起來之外,也可使用小括號
|
newcommand{\\add}[2]{#1 + #2} 會將 \\add{3}{foo} 展開為 3 + foo。巨集名稱除了可用大括號 {} 括起來之外,也可使用小括號
|
||||||
() 和中括號 [],但使用於巨集參數的括號會有所變更。每行只能夠定義一個巨集,巨集中間無法間換。無效的行將被忽略。只支援簡單字串的替換功能,不支援條件分歧的高級語法。"
|
() 和中括號 [],但使用於巨集參數的括號會有所變更。每行只能夠定義一個巨集,巨集中間無法間換。無效的行將被忽略。只支援簡單字串的替換功能,不支援條件分歧的進階語法。"
|
||||||
enableCustomKaTeXMacro: "啟用自定義 KaTeX 宏"
|
enableCustomKaTeXMacro: "啟用自定義 KaTeX 宏"
|
||||||
_sensitiveMediaDetection:
|
_sensitiveMediaDetection:
|
||||||
description: "您可以使用機器學習自動檢測敏感媒體並將其用於審核。 伺服器的負荷會稍微增加。"
|
description: "您可以使用機器學習自動檢測敏感媒體並將其用於審核。 伺服器的負荷會稍微增加。"
|
||||||
|
@ -932,7 +933,8 @@ _accountDelete:
|
||||||
inProgress: "正在刪除"
|
inProgress: "正在刪除"
|
||||||
_ad:
|
_ad:
|
||||||
back: "返回"
|
back: "返回"
|
||||||
reduceFrequencyOfThisAd: "降低此廣告的頻率"
|
reduceFrequencyOfThisAd: "降低此橫幅的頻率"
|
||||||
|
adsBy: 社群橫幅(作者:{by})
|
||||||
_forgotPassword:
|
_forgotPassword:
|
||||||
enterEmail: "請輸入您的帳戶註冊的電子郵件地址。 密碼重置連結將被發送到該電子郵件地址。"
|
enterEmail: "請輸入您的帳戶註冊的電子郵件地址。 密碼重置連結將被發送到該電子郵件地址。"
|
||||||
ifNoEmail: "如果您還沒有註冊您的電子郵件地址,請聯繫管理員。"
|
ifNoEmail: "如果您還沒有註冊您的電子郵件地址,請聯繫管理員。"
|
||||||
|
@ -1026,7 +1028,7 @@ _mfm:
|
||||||
emoji: "自訂表情符號"
|
emoji: "自訂表情符號"
|
||||||
emojiDescription: "您可以通過將自定義表情符號名稱括在冒號中來顯示自定義表情符號。"
|
emojiDescription: "您可以通過將自定義表情符號名稱括在冒號中來顯示自定義表情符號。"
|
||||||
search: "搜尋"
|
search: "搜尋"
|
||||||
searchDescription: "您可以顯示所輸入的搜索框。"
|
searchDescription: "顯示含有指定文字的搜尋欄。"
|
||||||
flip: "翻轉"
|
flip: "翻轉"
|
||||||
flipDescription: "將內容上下或左右翻轉。"
|
flipDescription: "將內容上下或左右翻轉。"
|
||||||
jelly: "動畫(果凍)"
|
jelly: "動畫(果凍)"
|
||||||
|
@ -1068,7 +1070,7 @@ _mfm:
|
||||||
alwaysPlay: 自動播放所有MFM動畫
|
alwaysPlay: 自動播放所有MFM動畫
|
||||||
positionDescription: 按指定數量移動內容。
|
positionDescription: 按指定數量移動內容。
|
||||||
advancedDescription: 如果停用,僅顯示基礎MFM及正在播放的MFM動畫
|
advancedDescription: 如果停用,僅顯示基礎MFM及正在播放的MFM動畫
|
||||||
advanced: 高級MFM
|
advanced: 進階MFM
|
||||||
fade: 淡出
|
fade: 淡出
|
||||||
foreground: 文字顏色
|
foreground: 文字顏色
|
||||||
crop: 裁切
|
crop: 裁切
|
||||||
|
@ -1109,14 +1111,14 @@ _wordMute:
|
||||||
muteWords: "加入靜音文字"
|
muteWords: "加入靜音文字"
|
||||||
muteWordsDescription: "用空格分隔指定AND,用換行分隔指定OR。"
|
muteWordsDescription: "用空格分隔指定AND,用換行分隔指定OR。"
|
||||||
muteWordsDescription2: "將關鍵字用斜線括起來表示正規表達式。"
|
muteWordsDescription2: "將關鍵字用斜線括起來表示正規表達式。"
|
||||||
softDescription: "隱藏時間線中指定條件的貼文。"
|
softDescription: "隱藏時間軸中指定條件的貼文。"
|
||||||
hardDescription: "具有指定條件的貼文將不添加到時間線。 即使您更改條件,未被添加的貼文也會被排除在外。"
|
hardDescription: "符合指定條件的貼文將不添加到時間軸。 即使您更改條件,未被添加的貼文也會被排除在外。"
|
||||||
soft: "軟性靜音"
|
soft: "軟性靜音"
|
||||||
hard: "硬性靜音"
|
hard: "硬性靜音"
|
||||||
mutedNotes: "已靜音的貼文"
|
mutedNotes: "已靜音的貼文"
|
||||||
muteLangsDescription2: '使用語言代碼。例: en, fr, ja, zh.'
|
muteLangsDescription2: '使用語言代碼。例: en, fr, ja, zh.'
|
||||||
lang: 語言
|
lang: 語言
|
||||||
langDescription: 將指定語言的貼文從時間線中隱藏。
|
langDescription: 將指定語言的貼文從時間軸中隱藏。
|
||||||
muteLangs: 被靜音的語言
|
muteLangs: 被靜音的語言
|
||||||
muteLangsDescription: OR條件以空格或換行進行分隔。
|
muteLangsDescription: OR條件以空格或換行進行分隔。
|
||||||
_instanceMute:
|
_instanceMute:
|
||||||
|
@ -1228,16 +1230,16 @@ _tutorial:
|
||||||
step2_1: "首先,請完成你的個人資料。"
|
step2_1: "首先,請完成你的個人資料。"
|
||||||
step2_2: "通過提供一些關於你自己的資料,其他人會更容易了解他們是否想看到你的貼文或關注你。"
|
step2_2: "通過提供一些關於你自己的資料,其他人會更容易了解他們是否想看到你的貼文或關注你。"
|
||||||
step3_1: "現在是時候追隨一些人了!"
|
step3_1: "現在是時候追隨一些人了!"
|
||||||
step3_2: "你的主頁和社交時間線是基於你所追蹤的人,所以試著先追蹤幾個帳戶。\n點擊個人資料右上角的加號圈就可以關注它。"
|
step3_2: "你的主頁和社交時間軸是基於你所追蹤的人,所以試著先追蹤幾個帳戶。\n點擊個人資料右上角的加號按鈕就可以關注它。"
|
||||||
step4_1: "讓我們出去找你。"
|
step4_1: "讓我們出去找你。"
|
||||||
step4_2: "對於他們的第一條信息,有些人喜歡做 {introduction} 或一個簡單的 \"hello world!\""
|
step4_2: "作為第一則貼文,有些人喜歡發 {introduction} 或單純發一個 \"hello world!\""
|
||||||
step5_1: "時間線,到處都是時間線!"
|
step5_1: "時間軸,到處都是時間軸!"
|
||||||
step5_2: "您的伺服器已啟用了{timelines}個時間線。"
|
step5_2: "您的伺服器已啟用了{timelines}個時間軸。"
|
||||||
step5_3: "首頁 {icon} 時間線是顯示你追蹤的帳號的貼文。"
|
step5_3: "首頁 {icon} 時間軸是顯示你追蹤的帳號的貼文。"
|
||||||
step5_4: "本地 {icon} 時間線是你可以看到伺服器中所有其他用戶的貼文的時間線。"
|
step5_4: "本地 {icon} 時間軸是你可以看到伺服器中所有其他用戶的貼文的時間軸。"
|
||||||
step5_5: "社交 {icon} 時間線是你的 首頁時間線 和 本地時間線 的結合體。"
|
step5_5: "社交 {icon} 時間軸是你的 首頁時間軸 和 本地時間軸 的結合體。"
|
||||||
step5_6: "推薦 {icon} 時間線是顯示你的伺服器管理員推薦的貼文。"
|
step5_6: "推薦 {icon} 時間軸是顯示你的伺服器管理員推薦的貼文。"
|
||||||
step5_7: "全球 {icon} 時間線是顯示來自所有其他連接的伺服器的貼文。"
|
step5_7: "全球 {icon} 時間軸是顯示來自所有其他連接的伺服器的貼文。"
|
||||||
step6_1: "那麼,這裡是什麼地方?"
|
step6_1: "那麼,這裡是什麼地方?"
|
||||||
step6_2: "你不只是加入Firefish。你已經加入了Fediverse的一個門戶,這是一個由成千上萬台服務器組成的互聯網絡。"
|
step6_2: "你不只是加入Firefish。你已經加入了Fediverse的一個門戶,這是一個由成千上萬台服務器組成的互聯網絡。"
|
||||||
step6_3: "每個服務器也有不同,而並不是所有的服務器都運行Firefish。但這個服務器確實是運行Firefish的! 你可能會覺得有點複雜,但你很快就會明白的。"
|
step6_3: "每個服務器也有不同,而並不是所有的服務器都運行Firefish。但這個服務器確實是運行Firefish的! 你可能會覺得有點複雜,但你很快就會明白的。"
|
||||||
|
@ -1326,7 +1328,7 @@ _weekday:
|
||||||
_widgets:
|
_widgets:
|
||||||
memo: "備忘錄"
|
memo: "備忘錄"
|
||||||
notifications: "通知"
|
notifications: "通知"
|
||||||
timeline: "時間線"
|
timeline: "時間軸"
|
||||||
calendar: "行事曆"
|
calendar: "行事曆"
|
||||||
trends: "發燒貼文"
|
trends: "發燒貼文"
|
||||||
clock: "時鐘"
|
clock: "時鐘"
|
||||||
|
@ -1382,9 +1384,9 @@ _poll:
|
||||||
remainingSeconds: "{s}秒後截止"
|
remainingSeconds: "{s}秒後截止"
|
||||||
_visibility:
|
_visibility:
|
||||||
public: "公開"
|
public: "公開"
|
||||||
publicDescription: "發佈至公開時間線"
|
publicDescription: "發佈至公開時間軸"
|
||||||
home: "不在主頁顯示"
|
home: "不在主頁顯示"
|
||||||
homeDescription: "僅發送至首頁的時間線"
|
homeDescription: "僅發送至首頁的時間軸"
|
||||||
followers: "追隨者"
|
followers: "追隨者"
|
||||||
followersDescription: "僅發佈至關注者"
|
followersDescription: "僅發佈至關注者"
|
||||||
specified: "指定使用者"
|
specified: "指定使用者"
|
||||||
|
@ -1783,6 +1785,7 @@ _notification:
|
||||||
renote: "轉發"
|
renote: "轉發"
|
||||||
reacted: 對您的貼文做出了反應
|
reacted: 對您的貼文做出了反應
|
||||||
renoted: 轉發了您的貼文
|
renoted: 轉發了您的貼文
|
||||||
|
voted: 投了票
|
||||||
_deck:
|
_deck:
|
||||||
alwaysShowMainColumn: "總是顯示主欄"
|
alwaysShowMainColumn: "總是顯示主欄"
|
||||||
columnAlign: "對齊欄位"
|
columnAlign: "對齊欄位"
|
||||||
|
@ -1806,7 +1809,7 @@ _deck:
|
||||||
main: "主列"
|
main: "主列"
|
||||||
widgets: "小工具"
|
widgets: "小工具"
|
||||||
notifications: "通知"
|
notifications: "通知"
|
||||||
tl: "時間線"
|
tl: "時間軸"
|
||||||
antenna: "天線"
|
antenna: "天線"
|
||||||
list: "清單"
|
list: "清單"
|
||||||
mentions: "提及"
|
mentions: "提及"
|
||||||
|
@ -1815,7 +1818,7 @@ _deck:
|
||||||
secureMode: 安全模式(授權獲取)
|
secureMode: 安全模式(授權獲取)
|
||||||
instanceSecurity: 伺服器安全性
|
instanceSecurity: 伺服器安全性
|
||||||
privateMode: 私人模式
|
privateMode: 私人模式
|
||||||
allowedInstances: 列入白名單的伺服器
|
allowedInstances: 列入允許名單的伺服器
|
||||||
secureModeInfo: 當從其他伺服器請求時,不要在沒有證據的情況下發回。
|
secureModeInfo: 當從其他伺服器請求時,不要在沒有證據的情況下發回。
|
||||||
_messaging:
|
_messaging:
|
||||||
dms: 私訊
|
dms: 私訊
|
||||||
|
@ -1823,8 +1826,8 @@ _messaging:
|
||||||
manageGroups: 管理群組
|
manageGroups: 管理群組
|
||||||
replayTutorial: 重新播放教程
|
replayTutorial: 重新播放教程
|
||||||
moveFromLabel: '您想遷移的舊帳戶:'
|
moveFromLabel: '您想遷移的舊帳戶:'
|
||||||
customMOTDDescription: 每次用戶加載/重新加載頁面時,由換行符號分隔的 MOTD(啟動畫面)的自定信息將隨機顯示。
|
customMOTDDescription: 自訂MOTD(啟動畫面)訊息,一行一個。每次用戶載入/重新整理頁面時將會隨機顯示。
|
||||||
privateModeInfo: 啟用後,只有列入白名單的伺服器才能與你的伺服器聯合。所有貼文都將對公眾隱藏。
|
privateModeInfo: 啟用後,只有列入允許名單的伺服器才能與你的伺服器聯合。所有貼文都將對公眾隱藏。
|
||||||
adminCustomCssWarn: 除非你知道它的作用,否則請不要使用此設定。 輸入不正確的值可能會導致每個人的客戶端無法正常運行。你可在你的的用戶設定中測試,確保你的
|
adminCustomCssWarn: 除非你知道它的作用,否則請不要使用此設定。 輸入不正確的值可能會導致每個人的客戶端無法正常運行。你可在你的的用戶設定中測試,確保你的
|
||||||
CSS 正常工作。
|
CSS 正常工作。
|
||||||
showUpdates: Firefish 更新時顯示彈出視窗
|
showUpdates: Firefish 更新時顯示彈出視窗
|
||||||
|
@ -1838,7 +1841,7 @@ accountMoved: '該使用者已遷移至新帳戶:'
|
||||||
showAds: 顯示社群橫幅
|
showAds: 顯示社群橫幅
|
||||||
noThankYou: 不用了,謝謝
|
noThankYou: 不用了,謝謝
|
||||||
selectInstance: 選擇伺服器
|
selectInstance: 選擇伺服器
|
||||||
enableRecommendedTimeline: 啟用推薦時間線
|
enableRecommendedTimeline: 啟用推薦時間軸
|
||||||
antennaInstancesDescription: 分行列出一個伺服器
|
antennaInstancesDescription: 分行列出一個伺服器
|
||||||
moveTo: 遷移此帳戶到新帳戶
|
moveTo: 遷移此帳戶到新帳戶
|
||||||
moveToLabel: '請輸入你將會遷移到的帳戶:'
|
moveToLabel: '請輸入你將會遷移到的帳戶:'
|
||||||
|
@ -1852,7 +1855,7 @@ enableEmojiReactions: 啟用表情符號反應
|
||||||
breakFollowConfirm: 您確定要移除該關注者嗎?
|
breakFollowConfirm: 您確定要移除該關注者嗎?
|
||||||
socialTimeline: 社交時間軸
|
socialTimeline: 社交時間軸
|
||||||
cannotUploadBecauseExceedsFileSizeLimit: 因檔案太大而無法上傳。
|
cannotUploadBecauseExceedsFileSizeLimit: 因檔案太大而無法上傳。
|
||||||
customMOTD: 自定義MOTD (網頁載入時顯示的信息)
|
customMOTD: 自定義MOTD (網頁載入時顯示的訊息)
|
||||||
customSplashIcons: 啟動畫面圖標 (網址)
|
customSplashIcons: 啟動畫面圖標 (網址)
|
||||||
splash: 啟動畫面
|
splash: 啟動畫面
|
||||||
updateAvailable: 可能有可用的更新!
|
updateAvailable: 可能有可用的更新!
|
||||||
|
@ -1870,8 +1873,9 @@ silenced: 已靜音
|
||||||
_experiments:
|
_experiments:
|
||||||
title: 試驗功能
|
title: 試驗功能
|
||||||
enablePostImports: 啟用匯入貼文的功能
|
enablePostImports: 啟用匯入貼文的功能
|
||||||
|
postImportsCaption: 允許用戶從舊有的Firefish・Misskey・Mastodon・Akkoma・Pleroma帳號匯入貼文。在伺服器佇列堵塞時匯入貼文可能會導致載入速度變慢。
|
||||||
findOtherInstance: 找找另一個伺服器
|
findOtherInstance: 找找另一個伺服器
|
||||||
noGraze: 瀏覽器擴展 "Graze for Mastodon" 會與Firefish發生衝突,請停用該擴展。
|
noGraze: 瀏覽器擴充元件 "Graze for Mastodon" 會與Firefish發生衝突,請停用該擴充元件。
|
||||||
userSaysSomethingReasonRenote: '{name} 轉發了包含 {reason} 的貼文'
|
userSaysSomethingReasonRenote: '{name} 轉發了包含 {reason} 的貼文'
|
||||||
pushNotificationNotSupported: 你的瀏覽器或伺服器不支援推送通知
|
pushNotificationNotSupported: 你的瀏覽器或伺服器不支援推送通知
|
||||||
accessibility: 輔助功能
|
accessibility: 輔助功能
|
||||||
|
@ -1883,15 +1887,15 @@ deleted: 已刪除
|
||||||
editNote: 編輯貼文
|
editNote: 編輯貼文
|
||||||
edited: '於 {date} {time} 編輯'
|
edited: '於 {date} {time} 編輯'
|
||||||
userSaysSomethingReason: '{name} 說了 {reason}'
|
userSaysSomethingReason: '{name} 說了 {reason}'
|
||||||
allowedInstancesDescription: 要加入聯邦白名單的服務器,每台伺服器用新行分隔(僅適用於私有模式)。
|
allowedInstancesDescription: 允許聯邦的伺服器名單,一行一個(僅適用於私人模式)。
|
||||||
defaultReaction: 默認的表情符號反應
|
defaultReaction: 默認的表情符號反應
|
||||||
license: 授權
|
license: 授權
|
||||||
apps: 應用
|
apps: 應用
|
||||||
pushNotification: 推送通知
|
pushNotification: 推送通知
|
||||||
subscribePushNotification: 啟用推送通知
|
subscribePushNotification: 啟用推送通知
|
||||||
unsubscribePushNotification: 禁用推送通知
|
unsubscribePushNotification: 停用推送通知
|
||||||
pushNotificationAlreadySubscribed: 推送通知已經啟用
|
pushNotificationAlreadySubscribed: 推送通知已經啟用
|
||||||
recommendedInstancesDescription: 以每行分隔的推薦伺服器出現在推薦的時間線中。
|
recommendedInstancesDescription: 推薦的伺服器(將顯示在推薦時間軸中),一行一個。
|
||||||
searchPlaceholder: 在 Firefish 上搜尋
|
searchPlaceholder: 在 Firefish 上搜尋
|
||||||
cw: 內容警告
|
cw: 內容警告
|
||||||
selectChannel: 選擇一個頻道
|
selectChannel: 選擇一個頻道
|
||||||
|
@ -1899,9 +1903,9 @@ newer: 較新
|
||||||
older: 較舊
|
older: 較舊
|
||||||
jumpToPrevious: 跳到上一個
|
jumpToPrevious: 跳到上一個
|
||||||
removeReaction: 移除你的反應
|
removeReaction: 移除你的反應
|
||||||
listsDesc: 清單可以創建一個只有您指定用戶的時間線。 可以從時間線頁面訪問它們。
|
listsDesc: 清單可以創建一個只有您指定用戶的時間軸。 可以從時間軸頁面訪問它們。
|
||||||
flagSpeakAsCatDescription: 在喵咪模式下你的貼文會被喵化ヾ(•ω•`)o
|
flagSpeakAsCatDescription: 在喵咪模式下你的貼文會被喵化ヾ(•ω•`)o
|
||||||
antennasDesc: "天線會顯示符合您設置條件的新貼文!\n 可以從時間線訪問它們。"
|
antennasDesc: "天線會顯示符合您設置條件的新貼文!\n 可以從時間軸訪問它們。"
|
||||||
expandOnNoteClick: 點擊以打開貼文
|
expandOnNoteClick: 點擊以打開貼文
|
||||||
expandOnNoteClickDesc: 即使停用,您仍然可以從右鍵選單或單擊發文時間來打開貼文。
|
expandOnNoteClickDesc: 即使停用,您仍然可以從右鍵選單或單擊發文時間來打開貼文。
|
||||||
hiddenTagsDescription: '列出您希望隱藏趨勢和探索的主題標籤(不帶 #)。 隱藏的主題標籤仍然可以通過其他方式發現。'
|
hiddenTagsDescription: '列出您希望隱藏趨勢和探索的主題標籤(不帶 #)。 隱藏的主題標籤仍然可以通過其他方式發現。'
|
||||||
|
@ -1920,7 +1924,7 @@ seperateRenoteQuote: 分開轉發及引用的按鈕
|
||||||
clipsDesc: 摘錄就像一個可以分享的書籤。 你可以從每個貼文的菜單創建新摘錄或將貼文加入已有的摘錄。
|
clipsDesc: 摘錄就像一個可以分享的書籤。 你可以從每個貼文的菜單創建新摘錄或將貼文加入已有的摘錄。
|
||||||
noteId: 貼文 ID
|
noteId: 貼文 ID
|
||||||
sendModMail: 發送審核通知
|
sendModMail: 發送審核通知
|
||||||
enableIdenticonGeneration: 啟用碎片生成
|
enableIdenticonGeneration: 啟用Identicon生成
|
||||||
enableServerMachineStats: 啟用伺服器硬體統計資訊
|
enableServerMachineStats: 啟用伺服器硬體統計資訊
|
||||||
reactionPickerSkinTone: 首選表情符號膚色
|
reactionPickerSkinTone: 首選表情符號膚色
|
||||||
indexFromDescription: 留空以索引每個貼文
|
indexFromDescription: 留空以索引每個貼文
|
||||||
|
@ -1932,7 +1936,7 @@ isModerator: 板主
|
||||||
isAdmin: 管理員
|
isAdmin: 管理員
|
||||||
isPatron: Firefish 項目贊助者
|
isPatron: Firefish 項目贊助者
|
||||||
silencedWarning: 顯示此頁面是因為這些使用者來自您伺服器管理員已靜音的伺服器,因此他們可能是垃圾訊息。
|
silencedWarning: 顯示此頁面是因為這些使用者來自您伺服器管理員已靜音的伺服器,因此他們可能是垃圾訊息。
|
||||||
signupsDisabled: 該伺服器上的註冊當前已被禁用,但您隨時可以在另一台伺服器上註冊!或是您有該伺服器的邀請碼,請在下面輸入。
|
signupsDisabled: 此伺服器目前停止註冊,但您隨時可以在另一台伺服器上註冊!如果您有此伺服器的邀請碼,請在下面輸入。
|
||||||
showPopup: 通過彈出式視窗通知用戶
|
showPopup: 通過彈出式視窗通知用戶
|
||||||
showWithSparkles: 讓標題閃閃發光
|
showWithSparkles: 讓標題閃閃發光
|
||||||
youHaveUnreadAnnouncements: 您有未讀的公告
|
youHaveUnreadAnnouncements: 您有未讀的公告
|
||||||
|
@ -1961,10 +1965,17 @@ _dialog:
|
||||||
charactersExceeded: 超過字數限制! 當前 {current} / 限制 {max}
|
charactersExceeded: 超過字數限制! 當前 {current} / 限制 {max}
|
||||||
_skinTones:
|
_skinTones:
|
||||||
yellow: 黃色
|
yellow: 黃色
|
||||||
|
medium: 中等
|
||||||
|
dark: 深色
|
||||||
|
mediumDark: 中等偏深
|
||||||
|
light: 淺色
|
||||||
|
mediumLight: 中等偏淺
|
||||||
exportZip: 匯出ZIP
|
exportZip: 匯出ZIP
|
||||||
_feeds:
|
_feeds:
|
||||||
atom: Atom
|
atom: Atom
|
||||||
rss: RSS
|
rss: RSS
|
||||||
|
copyFeed: 複製訂閱URL
|
||||||
|
jsonFeed: JSON Feed
|
||||||
emojiPackCreator: 表情包的作者
|
emojiPackCreator: 表情包的作者
|
||||||
importZip: 匯入ZIP
|
importZip: 匯入ZIP
|
||||||
delete2fa: 停用二階段認證(2FA)
|
delete2fa: 停用二階段認證(2FA)
|
||||||
|
@ -1977,3 +1988,9 @@ addRe: 在回覆有內容警告的貼文時,在標題前面加上 "re:"
|
||||||
vibrate: 播放振動
|
vibrate: 播放振動
|
||||||
openServerInfo: 點擊貼文中的伺服器名稱以顯示伺服器資訊
|
openServerInfo: 點擊貼文中的伺服器名稱以顯示伺服器資訊
|
||||||
languageForTranslation: 貼文翻譯語言
|
languageForTranslation: 貼文翻譯語言
|
||||||
|
objectStorageS3ForcePathStyleDesc: 以 "s3.amazonaws.com/<bucket>/" 而非 "<bucket>.s3.amazonaws.com"
|
||||||
|
的格式建構端點(Endpoint)URL。
|
||||||
|
indexable: 登錄至貼文搜尋引擎
|
||||||
|
origin: 來源
|
||||||
|
objectStorageS3ForcePathStyle: 使用基於路徑的端點(Endpoint)URL
|
||||||
|
clickToShowPatterns: 點擊顯示模組模式(Module Pattern)
|
||||||
|
|
|
@ -1,4 +1,14 @@
|
||||||
user-agent: *
|
User-agent: *
|
||||||
allow: /
|
Allow: /
|
||||||
|
|
||||||
# todo: sitemap
|
# Uncomment the following to block CommonCrawl
|
||||||
|
#
|
||||||
|
# User-agent: CCBot
|
||||||
|
# User-agent: CCBot/2.0
|
||||||
|
# User-agent: CCBot/3.1
|
||||||
|
# Disallow: /
|
||||||
|
|
||||||
|
# Uncomment the following to block ChatGPT
|
||||||
|
#
|
||||||
|
# User-agent: GPTBot
|
||||||
|
# Disallow: /
|
||||||
|
|
|
@ -7,6 +7,7 @@ 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;
|
mod m20230904_013244_is_indexable;
|
||||||
|
mod m20231002_143323_remove_integrations;
|
||||||
|
|
||||||
pub struct Migrator;
|
pub struct Migrator;
|
||||||
|
|
||||||
|
@ -19,6 +20,7 @@ impl MigratorTrait for Migrator {
|
||||||
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),
|
Box::new(m20230904_013244_is_indexable::Migration),
|
||||||
|
Box::new(m20231002_143323_remove_integrations::Migration),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,117 @@
|
||||||
|
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(UserProfile::Table)
|
||||||
|
.drop_column(UserProfile::Integrations)
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
manager
|
||||||
|
.alter_table(
|
||||||
|
Table::alter()
|
||||||
|
.table(Meta::Table)
|
||||||
|
.drop_column(Meta::EnableTwitterIntegration)
|
||||||
|
.drop_column(Meta::TwitterConsumerKey)
|
||||||
|
.drop_column(Meta::TwitterConsumerSecret)
|
||||||
|
.drop_column(Meta::EnableGithubIntegration)
|
||||||
|
.drop_column(Meta::GithubClientId)
|
||||||
|
.drop_column(Meta::GithubClientSecret)
|
||||||
|
.drop_column(Meta::EnableDiscordIntegration)
|
||||||
|
.drop_column(Meta::DiscordClientId)
|
||||||
|
.drop_column(Meta::DiscordClientSecret)
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager
|
||||||
|
.alter_table(
|
||||||
|
Table::alter()
|
||||||
|
.table(Meta::Table)
|
||||||
|
.add_column(ColumnDef::new(Meta::DiscordClientSecret).string())
|
||||||
|
.add_column(ColumnDef::new(Meta::DiscordClientId).string())
|
||||||
|
.add_column(
|
||||||
|
ColumnDef::new(Meta::EnableDiscordIntegration)
|
||||||
|
.boolean()
|
||||||
|
.not_null()
|
||||||
|
.default(false),
|
||||||
|
)
|
||||||
|
.add_column(ColumnDef::new(Meta::GithubClientSecret).string())
|
||||||
|
.add_column(ColumnDef::new(Meta::GithubClientId).string())
|
||||||
|
.add_column(
|
||||||
|
ColumnDef::new(Meta::EnableGithubIntegration)
|
||||||
|
.boolean()
|
||||||
|
.not_null()
|
||||||
|
.default(false),
|
||||||
|
)
|
||||||
|
.add_column(ColumnDef::new(Meta::TwitterConsumerSecret).string())
|
||||||
|
.add_column(ColumnDef::new(Meta::TwitterConsumerKey).string())
|
||||||
|
.add_column(
|
||||||
|
ColumnDef::new(Meta::EnableTwitterIntegration)
|
||||||
|
.boolean()
|
||||||
|
.not_null()
|
||||||
|
.default(false),
|
||||||
|
)
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
manager
|
||||||
|
.alter_table(
|
||||||
|
Table::alter()
|
||||||
|
.table(UserProfile::Table)
|
||||||
|
.add_column(
|
||||||
|
ColumnDef::new(UserProfile::Integrations)
|
||||||
|
.json()
|
||||||
|
.default("{}"),
|
||||||
|
)
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Iden)]
|
||||||
|
|
||||||
|
enum UserProfile {
|
||||||
|
Table,
|
||||||
|
#[iden = "integrations"]
|
||||||
|
Integrations,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Iden)]
|
||||||
|
enum Meta {
|
||||||
|
Table,
|
||||||
|
#[iden = "enableTwitterIntegration"]
|
||||||
|
EnableTwitterIntegration,
|
||||||
|
#[iden = "twitterConsumerKey"]
|
||||||
|
TwitterConsumerKey,
|
||||||
|
#[iden = "twitterConsumerSecret"]
|
||||||
|
TwitterConsumerSecret,
|
||||||
|
#[iden = "enableGithubIntegration"]
|
||||||
|
EnableGithubIntegration,
|
||||||
|
#[iden = "githubClientId"]
|
||||||
|
GithubClientId,
|
||||||
|
#[iden = "githubClientSecret"]
|
||||||
|
GithubClientSecret,
|
||||||
|
#[iden = "enableDiscordIntegration"]
|
||||||
|
EnableDiscordIntegration,
|
||||||
|
#[iden = "discordClientId"]
|
||||||
|
DiscordClientId,
|
||||||
|
#[iden = "discordClientSecret"]
|
||||||
|
DiscordClientSecret,
|
||||||
|
}
|
|
@ -71,24 +71,6 @@ pub struct Model {
|
||||||
pub sw_public_key: Option<String>,
|
pub sw_public_key: Option<String>,
|
||||||
#[sea_orm(column_name = "swPrivateKey")]
|
#[sea_orm(column_name = "swPrivateKey")]
|
||||||
pub sw_private_key: Option<String>,
|
pub sw_private_key: Option<String>,
|
||||||
#[sea_orm(column_name = "enableTwitterIntegration")]
|
|
||||||
pub enable_twitter_integration: bool,
|
|
||||||
#[sea_orm(column_name = "twitterConsumerKey")]
|
|
||||||
pub twitter_consumer_key: Option<String>,
|
|
||||||
#[sea_orm(column_name = "twitterConsumerSecret")]
|
|
||||||
pub twitter_consumer_secret: Option<String>,
|
|
||||||
#[sea_orm(column_name = "enableGithubIntegration")]
|
|
||||||
pub enable_github_integration: bool,
|
|
||||||
#[sea_orm(column_name = "githubClientId")]
|
|
||||||
pub github_client_id: Option<String>,
|
|
||||||
#[sea_orm(column_name = "githubClientSecret")]
|
|
||||||
pub github_client_secret: Option<String>,
|
|
||||||
#[sea_orm(column_name = "enableDiscordIntegration")]
|
|
||||||
pub enable_discord_integration: bool,
|
|
||||||
#[sea_orm(column_name = "discordClientId")]
|
|
||||||
pub discord_client_id: Option<String>,
|
|
||||||
#[sea_orm(column_name = "discordClientSecret")]
|
|
||||||
pub discord_client_secret: Option<String>,
|
|
||||||
#[sea_orm(column_name = "pinnedUsers")]
|
#[sea_orm(column_name = "pinnedUsers")]
|
||||||
pub pinned_users: StringVec,
|
pub pinned_users: StringVec,
|
||||||
#[sea_orm(column_name = "ToSUrl")]
|
#[sea_orm(column_name = "ToSUrl")]
|
||||||
|
|
|
@ -46,8 +46,6 @@ pub struct Model {
|
||||||
pub pinned_page_id: Option<String>,
|
pub pinned_page_id: Option<String>,
|
||||||
#[sea_orm(column_type = "JsonBinary")]
|
#[sea_orm(column_type = "JsonBinary")]
|
||||||
pub room: Json,
|
pub room: Json,
|
||||||
#[sea_orm(column_type = "JsonBinary")]
|
|
||||||
pub integrations: Json,
|
|
||||||
#[sea_orm(column_name = "injectFeaturedNote")]
|
#[sea_orm(column_name = "injectFeaturedNote")]
|
||||||
pub inject_featured_note: bool,
|
pub inject_featured_note: bool,
|
||||||
#[sea_orm(column_name = "enableWordMute")]
|
#[sea_orm(column_name = "enableWordMute")]
|
||||||
|
|
|
@ -98,7 +98,6 @@
|
||||||
"node-fetch": "3.3.2",
|
"node-fetch": "3.3.2",
|
||||||
"nodemailer": "6.9.4",
|
"nodemailer": "6.9.4",
|
||||||
"nsfwjs": "2.4.2",
|
"nsfwjs": "2.4.2",
|
||||||
"oauth": "^0.10.0",
|
|
||||||
"opencc-js": "^1.0.5",
|
"opencc-js": "^1.0.5",
|
||||||
"os-utils": "0.0.14",
|
"os-utils": "0.0.14",
|
||||||
"otpauth": "^9.1.4",
|
"otpauth": "^9.1.4",
|
||||||
|
|
|
@ -1,76 +1,29 @@
|
||||||
import type { Antenna } from "@/models/entities/antenna.js";
|
import type { Antenna } from "@/models/entities/antenna.js";
|
||||||
import type { Note } from "@/models/entities/note.js";
|
import type { Note } from "@/models/entities/note.js";
|
||||||
import type { User } from "@/models/entities/user.js";
|
import type { User } from "@/models/entities/user.js";
|
||||||
import {
|
import { Blockings, UserProfiles } from "@/models/index.js";
|
||||||
UserListJoinings,
|
|
||||||
UserGroupJoinings,
|
|
||||||
Blockings,
|
|
||||||
} from "@/models/index.js";
|
|
||||||
import { getFullApAccount } from "./convert-host.js";
|
import { getFullApAccount } from "./convert-host.js";
|
||||||
import * as Acct from "@/misc/acct.js";
|
import * as Acct from "@/misc/acct.js";
|
||||||
import type { Packed } from "./schema.js";
|
import type { Packed } from "./schema.js";
|
||||||
import { Cache } from "./cache.js";
|
import { Cache } from "./cache.js";
|
||||||
|
import { getWordHardMute } from "./check-word-mute.js";
|
||||||
|
|
||||||
const blockingCache = new Cache<User["id"][]>("blocking", 60 * 5);
|
const blockingCache = new Cache<User["id"][]>("blocking", 60 * 5);
|
||||||
|
const mutedWordsCache = new Cache<string[][] | undefined>("mutedWords", 60 * 5);
|
||||||
|
|
||||||
// NOTE: フォローしているユーザーのノート、リストのユーザーのノート、グループのユーザーのノート指定はパフォーマンス上の理由で無効になっている
|
|
||||||
|
|
||||||
/**
|
|
||||||
* noteUserFollowers / antennaUserFollowing はどちらか一方が指定されていればよい
|
|
||||||
*/
|
|
||||||
export async function checkHitAntenna(
|
export async function checkHitAntenna(
|
||||||
antenna: Antenna,
|
antenna: Antenna,
|
||||||
note: Note | Packed<"Note">,
|
note: Note | Packed<"Note">,
|
||||||
noteUser: { id: User["id"]; username: string; host: string | null },
|
noteUser: { id: User["id"]; username: string; host: string | null },
|
||||||
noteUserFollowers?: User["id"][],
|
|
||||||
antennaUserFollowing?: User["id"][],
|
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
if (note.visibility === "specified") return false;
|
if (note.visibility === "specified") return false;
|
||||||
if (note.visibility === "home") return false;
|
if (note.visibility === "home") return false;
|
||||||
|
if (!antenna.withReplies && note.replyId != null) return false;
|
||||||
// アンテナ作成者がノート作成者にブロックされていたらスキップ
|
if (antenna.withFile) {
|
||||||
const blockings = await blockingCache.fetch(noteUser.id, () =>
|
if (note.fileIds && note.fileIds.length === 0) return false;
|
||||||
Blockings.findBy({ blockerId: noteUser.id }).then((res) =>
|
|
||||||
res.map((x) => x.blockeeId),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (blockings.some((blocking) => blocking === antenna.userId)) return false;
|
|
||||||
|
|
||||||
if (note.visibility === "followers") {
|
|
||||||
if (noteUserFollowers && !noteUserFollowers.includes(antenna.userId))
|
|
||||||
return false;
|
|
||||||
if (antennaUserFollowing && !antennaUserFollowing.includes(note.userId))
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!antenna.withReplies && note.replyId != null) return false;
|
if (antenna.src === "users") {
|
||||||
|
|
||||||
if (antenna.src === "home") {
|
|
||||||
if (noteUserFollowers && !noteUserFollowers.includes(antenna.userId))
|
|
||||||
return false;
|
|
||||||
if (antennaUserFollowing && !antennaUserFollowing.includes(note.userId))
|
|
||||||
return false;
|
|
||||||
} else if (antenna.src === "list") {
|
|
||||||
const listUsers = (
|
|
||||||
await UserListJoinings.findBy({
|
|
||||||
userListId: antenna.userListId!,
|
|
||||||
})
|
|
||||||
).map((x) => x.userId);
|
|
||||||
|
|
||||||
if (!listUsers.includes(note.userId)) return false;
|
|
||||||
} else if (antenna.src === "group") {
|
|
||||||
const joining = await UserGroupJoinings.findOneByOrFail({
|
|
||||||
id: antenna.userGroupJoiningId!,
|
|
||||||
});
|
|
||||||
|
|
||||||
const groupUsers = (
|
|
||||||
await UserGroupJoinings.findBy({
|
|
||||||
userGroupId: joining.userGroupId,
|
|
||||||
})
|
|
||||||
).map((x) => x.userId);
|
|
||||||
|
|
||||||
if (!groupUsers.includes(note.userId)) return false;
|
|
||||||
} else if (antenna.src === "users") {
|
|
||||||
const accts = antenna.users.map((x) => {
|
const accts = antenna.users.map((x) => {
|
||||||
const { username, host } = Acct.parse(x);
|
const { username, host } = Acct.parse(x);
|
||||||
return getFullApAccount(username, host).toLowerCase();
|
return getFullApAccount(username, host).toLowerCase();
|
||||||
|
@ -128,9 +81,20 @@ export async function checkHitAntenna(
|
||||||
if (matched) return false;
|
if (matched) return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (antenna.withFile) {
|
// アンテナ作成者がノート作成者にブロックされていたらスキップ
|
||||||
if (note.fileIds && note.fileIds.length === 0) return false;
|
const blockings = await blockingCache.fetch(noteUser.id, () =>
|
||||||
}
|
Blockings.findBy({ blockerId: noteUser.id }).then((res) =>
|
||||||
|
res.map((x) => x.blockeeId),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (blockings.includes(antenna.userId)) return false;
|
||||||
|
|
||||||
|
const mutedWords = await mutedWordsCache.fetch(antenna.userId, () =>
|
||||||
|
UserProfiles.findOneBy({ userId: antenna.userId }).then(
|
||||||
|
(profile) => profile?.mutedWords,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (await getWordHardMute(note, antenna.userId, mutedWords)) return false;
|
||||||
|
|
||||||
// TODO: eval expression
|
// TODO: eval expression
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import RE2 from "re2";
|
import RE2 from "re2";
|
||||||
import type { Note } from "@/models/entities/note.js";
|
import type { Note } from "@/models/entities/note.js";
|
||||||
import type { User } from "@/models/entities/user.js";
|
|
||||||
|
|
||||||
type NoteLike = {
|
type NoteLike = {
|
||||||
userId: Note["userId"];
|
userId: Note["userId"];
|
||||||
|
@ -9,10 +8,6 @@ type NoteLike = {
|
||||||
cw?: Note["cw"];
|
cw?: Note["cw"];
|
||||||
};
|
};
|
||||||
|
|
||||||
type UserLike = {
|
|
||||||
id: User["id"];
|
|
||||||
};
|
|
||||||
|
|
||||||
function checkWordMute(
|
function checkWordMute(
|
||||||
note: NoteLike,
|
note: NoteLike,
|
||||||
mutedWords: Array<string | string[]>,
|
mutedWords: Array<string | string[]>,
|
||||||
|
@ -61,13 +56,10 @@ function checkWordMute(
|
||||||
|
|
||||||
export async function getWordHardMute(
|
export async function getWordHardMute(
|
||||||
note: NoteLike,
|
note: NoteLike,
|
||||||
me: UserLike | null | undefined,
|
meId: string | null | undefined,
|
||||||
mutedWords: Array<string | string[]>,
|
mutedWords?: Array<string | string[]>,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
// 自分自身
|
if (note.userId === meId || mutedWords == null) return false;
|
||||||
if (me && note.userId === me.id) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mutedWords.length > 0) {
|
if (mutedWords.length > 0) {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -354,57 +354,6 @@ export class Meta {
|
||||||
})
|
})
|
||||||
public swPrivateKey: string | null;
|
public swPrivateKey: string | null;
|
||||||
|
|
||||||
@Column("boolean", {
|
|
||||||
default: false,
|
|
||||||
})
|
|
||||||
public enableTwitterIntegration: boolean;
|
|
||||||
|
|
||||||
@Column("varchar", {
|
|
||||||
length: 128,
|
|
||||||
nullable: true,
|
|
||||||
})
|
|
||||||
public twitterConsumerKey: string | null;
|
|
||||||
|
|
||||||
@Column("varchar", {
|
|
||||||
length: 128,
|
|
||||||
nullable: true,
|
|
||||||
})
|
|
||||||
public twitterConsumerSecret: string | null;
|
|
||||||
|
|
||||||
@Column("boolean", {
|
|
||||||
default: false,
|
|
||||||
})
|
|
||||||
public enableGithubIntegration: boolean;
|
|
||||||
|
|
||||||
@Column("varchar", {
|
|
||||||
length: 128,
|
|
||||||
nullable: true,
|
|
||||||
})
|
|
||||||
public githubClientId: string | null;
|
|
||||||
|
|
||||||
@Column("varchar", {
|
|
||||||
length: 128,
|
|
||||||
nullable: true,
|
|
||||||
})
|
|
||||||
public githubClientSecret: string | null;
|
|
||||||
|
|
||||||
@Column("boolean", {
|
|
||||||
default: false,
|
|
||||||
})
|
|
||||||
public enableDiscordIntegration: boolean;
|
|
||||||
|
|
||||||
@Column("varchar", {
|
|
||||||
length: 128,
|
|
||||||
nullable: true,
|
|
||||||
})
|
|
||||||
public discordClientId: string | null;
|
|
||||||
|
|
||||||
@Column("varchar", {
|
|
||||||
length: 128,
|
|
||||||
nullable: true,
|
|
||||||
})
|
|
||||||
public discordClientSecret: string | null;
|
|
||||||
|
|
||||||
@Column("varchar", {
|
@Column("varchar", {
|
||||||
length: 128,
|
length: 128,
|
||||||
nullable: true,
|
nullable: true,
|
||||||
|
|
|
@ -215,11 +215,6 @@ export class UserProfile {
|
||||||
@JoinColumn()
|
@JoinColumn()
|
||||||
public pinnedPage: Page | null;
|
public pinnedPage: Page | null;
|
||||||
|
|
||||||
@Column("jsonb", {
|
|
||||||
default: {},
|
|
||||||
})
|
|
||||||
public integrations: Record<string, any>;
|
|
||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
@Column("boolean", {
|
@Column("boolean", {
|
||||||
default: false,
|
default: false,
|
||||||
|
|
|
@ -565,7 +565,6 @@ export const UserRepository = db.getRepository(User).extend({
|
||||||
hasUnreadNotification: this.getHasUnreadNotification(user.id),
|
hasUnreadNotification: this.getHasUnreadNotification(user.id),
|
||||||
hasPendingReceivedFollowRequest:
|
hasPendingReceivedFollowRequest:
|
||||||
this.getHasPendingReceivedFollowRequest(user.id),
|
this.getHasPendingReceivedFollowRequest(user.id),
|
||||||
integrations: profile!.integrations,
|
|
||||||
mutedWords: profile!.mutedWords,
|
mutedWords: profile!.mutedWords,
|
||||||
mutedInstances: profile!.mutedInstances,
|
mutedInstances: profile!.mutedInstances,
|
||||||
mutingNotificationTypes: profile!.mutingNotificationTypes,
|
mutingNotificationTypes: profile!.mutingNotificationTypes,
|
||||||
|
|
|
@ -459,11 +459,6 @@ export const packedMeDetailedOnlySchema = {
|
||||||
nullable: false,
|
nullable: false,
|
||||||
optional: false,
|
optional: false,
|
||||||
},
|
},
|
||||||
integrations: {
|
|
||||||
type: "object",
|
|
||||||
nullable: true,
|
|
||||||
optional: false,
|
|
||||||
},
|
|
||||||
mutedWords: {
|
mutedWords: {
|
||||||
type: "array",
|
type: "array",
|
||||||
nullable: false,
|
nullable: false,
|
||||||
|
|
|
@ -95,11 +95,25 @@ export default async (job: Bull.Job<InboxJobData>): Promise<string> => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTP-Signatureの検証
|
// HTTP-Signatureの検証
|
||||||
const httpSignatureValidated = httpSignature.verifySignature(
|
let httpSignatureValidated = httpSignature.verifySignature(
|
||||||
signature,
|
signature,
|
||||||
authUser.key.keyPem,
|
authUser.key.keyPem,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// If signature validation failed, try refetching the actor
|
||||||
|
if (!httpSignatureValidated) {
|
||||||
|
authUser.key = await dbResolver.refetchPublicKeyForApId(authUser.user);
|
||||||
|
|
||||||
|
if (authUser.key == null) {
|
||||||
|
return "skip: failed to re-resolve user publicKey";
|
||||||
|
}
|
||||||
|
|
||||||
|
httpSignatureValidated = httpSignature.verifySignature(
|
||||||
|
signature,
|
||||||
|
authUser.key.keyPem,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// また、signatureのsignerは、activity.actorと一致する必要がある
|
// また、signatureのsignerは、activity.actorと一致する必要がある
|
||||||
if (!httpSignatureValidated || authUser.user.uri !== activity.actor) {
|
if (!httpSignatureValidated || authUser.user.uri !== activity.actor) {
|
||||||
// 一致しなくても、でもLD-Signatureがありそうならそっちも見る
|
// 一致しなくても、でもLD-Signatureがありそうならそっちも見る
|
||||||
|
|
|
@ -86,11 +86,25 @@ export async function checkFetch(req: IncomingMessage): Promise<number> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTP-Signatureの検証
|
// HTTP-Signatureの検証
|
||||||
const httpSignatureValidated = httpSignature.verifySignature(
|
let httpSignatureValidated = httpSignature.verifySignature(
|
||||||
signature,
|
signature,
|
||||||
authUser.key.keyPem,
|
authUser.key.keyPem,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// If signature validation failed, try refetching the actor
|
||||||
|
if (!httpSignatureValidated) {
|
||||||
|
authUser.key = await dbResolver.refetchPublicKeyForApId(authUser.user);
|
||||||
|
|
||||||
|
if (authUser.key == null) {
|
||||||
|
return 403;
|
||||||
|
}
|
||||||
|
|
||||||
|
httpSignatureValidated = httpSignature.verifySignature(
|
||||||
|
signature,
|
||||||
|
authUser.key.keyPem,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!httpSignatureValidated) {
|
if (!httpSignatureValidated) {
|
||||||
return 403;
|
return 403;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ import { Cache } from "@/misc/cache.js";
|
||||||
import { uriPersonCache, userByIdCache } from "@/services/user-cache.js";
|
import { uriPersonCache, userByIdCache } from "@/services/user-cache.js";
|
||||||
import type { IObject } from "./type.js";
|
import type { IObject } from "./type.js";
|
||||||
import { getApId } from "./type.js";
|
import { getApId } from "./type.js";
|
||||||
import { resolvePerson } from "./models/person.js";
|
import { resolvePerson, updatePerson } from "./models/person.js";
|
||||||
|
|
||||||
const publicKeyCache = new Cache<UserPublickey | null>("publicKey", 60 * 30);
|
const publicKeyCache = new Cache<UserPublickey | null>("publicKey", 60 * 30);
|
||||||
const publicKeyByUserIdCache = new Cache<UserPublickey | null>(
|
const publicKeyByUserIdCache = new Cache<UserPublickey | null>(
|
||||||
|
@ -151,7 +151,7 @@ export default class DbResolver {
|
||||||
*/
|
*/
|
||||||
public async getAuthUserFromKeyId(keyId: string): Promise<{
|
public async getAuthUserFromKeyId(keyId: string): Promise<{
|
||||||
user: CacheableRemoteUser;
|
user: CacheableRemoteUser;
|
||||||
key: UserPublickey;
|
key: UserPublickey | null;
|
||||||
} | null> {
|
} | null> {
|
||||||
const key = await publicKeyCache.fetch(
|
const key = await publicKeyCache.fetch(
|
||||||
keyId,
|
keyId,
|
||||||
|
@ -203,4 +203,15 @@ export default class DbResolver {
|
||||||
key,
|
key,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async refetchPublicKeyForApId(
|
||||||
|
user: CacheableRemoteUser,
|
||||||
|
): Promise<UserPublickey | null> {
|
||||||
|
await updatePerson(user.uri!, undefined, undefined, user);
|
||||||
|
const key = await UserPublickeys.findOneBy({ userId: user.id });
|
||||||
|
if (key != null) {
|
||||||
|
await publicKeyByUserIdCache.set(user.id, key);
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -185,7 +185,7 @@ export async function createPerson(
|
||||||
|
|
||||||
const host = toPuny(new URL(object.id).hostname);
|
const host = toPuny(new URL(object.id).hostname);
|
||||||
|
|
||||||
const { fields } = analyzeAttachments(person.attachment || []);
|
const fields = analyzeAttachments(person.attachment || []);
|
||||||
|
|
||||||
const tags = extractApHashtags(person.tag)
|
const tags = extractApHashtags(person.tag)
|
||||||
.map((tag) => normalizeForSearch(tag))
|
.map((tag) => normalizeForSearch(tag))
|
||||||
|
@ -642,39 +642,6 @@ export async function resolvePerson(
|
||||||
return await createPerson(uri, resolver);
|
return await createPerson(uri, resolver);
|
||||||
}
|
}
|
||||||
|
|
||||||
const services: {
|
|
||||||
[x: string]: (id: string, username: string) => any;
|
|
||||||
} = {
|
|
||||||
"misskey:authentication:twitter": (userId, screenName) => ({
|
|
||||||
userId,
|
|
||||||
screenName,
|
|
||||||
}),
|
|
||||||
"misskey:authentication:github": (id, login) => ({ id, login }),
|
|
||||||
"misskey:authentication:discord": (id, name) => $discord(id, name),
|
|
||||||
};
|
|
||||||
|
|
||||||
const $discord = (id: string, name: string) => {
|
|
||||||
if (typeof name !== "string") {
|
|
||||||
name = "unknown#0000";
|
|
||||||
}
|
|
||||||
const [username, discriminator] = name.split("#");
|
|
||||||
return { id, username, discriminator };
|
|
||||||
};
|
|
||||||
|
|
||||||
function addService(target: { [x: string]: any }, source: IApPropertyValue) {
|
|
||||||
const service = services[source.name];
|
|
||||||
|
|
||||||
if (typeof source.value !== "string") {
|
|
||||||
source.value = "unknown";
|
|
||||||
}
|
|
||||||
|
|
||||||
const [id, username] = source.value.split("@");
|
|
||||||
|
|
||||||
if (service) {
|
|
||||||
target[source.name.split(":")[2]] = service(id, username);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function analyzeAttachments(
|
export function analyzeAttachments(
|
||||||
attachments: IObject | IObject[] | undefined,
|
attachments: IObject | IObject[] | undefined,
|
||||||
) {
|
) {
|
||||||
|
@ -682,22 +649,17 @@ export function analyzeAttachments(
|
||||||
name: string;
|
name: string;
|
||||||
value: string;
|
value: string;
|
||||||
}[] = [];
|
}[] = [];
|
||||||
const services: { [x: string]: any } = {};
|
|
||||||
|
|
||||||
if (Array.isArray(attachments)) {
|
if (Array.isArray(attachments)) {
|
||||||
for (const attachment of attachments.filter(isPropertyValue)) {
|
for (const attachment of attachments.filter(isPropertyValue)) {
|
||||||
if (isPropertyValue(attachment.identifier)) {
|
|
||||||
addService(services, attachment.identifier);
|
|
||||||
} else {
|
|
||||||
fields.push({
|
fields.push({
|
||||||
name: attachment.name,
|
name: attachment.name,
|
||||||
value: fromHtml(attachment.value),
|
value: fromHtml(attachment.value),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return { fields, services };
|
return fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateFeatured(userId: User["id"], resolver?: Resolver) {
|
export async function updateFeatured(userId: User["id"], resolver?: Resolver) {
|
||||||
|
|
|
@ -170,21 +170,6 @@ export const meta = {
|
||||||
optional: false,
|
optional: false,
|
||||||
nullable: false,
|
nullable: false,
|
||||||
},
|
},
|
||||||
enableTwitterIntegration: {
|
|
||||||
type: "boolean",
|
|
||||||
optional: false,
|
|
||||||
nullable: false,
|
|
||||||
},
|
|
||||||
enableGithubIntegration: {
|
|
||||||
type: "boolean",
|
|
||||||
optional: false,
|
|
||||||
nullable: false,
|
|
||||||
},
|
|
||||||
enableDiscordIntegration: {
|
|
||||||
type: "boolean",
|
|
||||||
optional: false,
|
|
||||||
nullable: false,
|
|
||||||
},
|
|
||||||
enableServiceWorker: {
|
enableServiceWorker: {
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
optional: false,
|
optional: false,
|
||||||
|
@ -326,36 +311,6 @@ export const meta = {
|
||||||
nullable: true,
|
nullable: true,
|
||||||
format: "id",
|
format: "id",
|
||||||
},
|
},
|
||||||
twitterConsumerKey: {
|
|
||||||
type: "string",
|
|
||||||
optional: true,
|
|
||||||
nullable: true,
|
|
||||||
},
|
|
||||||
twitterConsumerSecret: {
|
|
||||||
type: "string",
|
|
||||||
optional: true,
|
|
||||||
nullable: true,
|
|
||||||
},
|
|
||||||
githubClientId: {
|
|
||||||
type: "string",
|
|
||||||
optional: true,
|
|
||||||
nullable: true,
|
|
||||||
},
|
|
||||||
githubClientSecret: {
|
|
||||||
type: "string",
|
|
||||||
optional: true,
|
|
||||||
nullable: true,
|
|
||||||
},
|
|
||||||
discordClientId: {
|
|
||||||
type: "string",
|
|
||||||
optional: true,
|
|
||||||
nullable: true,
|
|
||||||
},
|
|
||||||
discordClientSecret: {
|
|
||||||
type: "string",
|
|
||||||
optional: true,
|
|
||||||
nullable: true,
|
|
||||||
},
|
|
||||||
summaryProxy: {
|
summaryProxy: {
|
||||||
type: "string",
|
type: "string",
|
||||||
optional: true,
|
optional: true,
|
||||||
|
@ -544,9 +499,6 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
defaultLightTheme: instance.defaultLightTheme,
|
defaultLightTheme: instance.defaultLightTheme,
|
||||||
defaultDarkTheme: instance.defaultDarkTheme,
|
defaultDarkTheme: instance.defaultDarkTheme,
|
||||||
enableEmail: instance.enableEmail,
|
enableEmail: instance.enableEmail,
|
||||||
enableTwitterIntegration: instance.enableTwitterIntegration,
|
|
||||||
enableGithubIntegration: instance.enableGithubIntegration,
|
|
||||||
enableDiscordIntegration: instance.enableDiscordIntegration,
|
|
||||||
enableServiceWorker: instance.enableServiceWorker,
|
enableServiceWorker: instance.enableServiceWorker,
|
||||||
translatorAvailable:
|
translatorAvailable:
|
||||||
instance.deeplAuthKey != null || instance.libreTranslateApiUrl != null,
|
instance.deeplAuthKey != null || instance.libreTranslateApiUrl != null,
|
||||||
|
@ -573,12 +525,6 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
enableSensitiveMediaDetectionForVideos:
|
enableSensitiveMediaDetectionForVideos:
|
||||||
instance.enableSensitiveMediaDetectionForVideos,
|
instance.enableSensitiveMediaDetectionForVideos,
|
||||||
proxyAccountId: instance.proxyAccountId,
|
proxyAccountId: instance.proxyAccountId,
|
||||||
twitterConsumerKey: instance.twitterConsumerKey,
|
|
||||||
twitterConsumerSecret: instance.twitterConsumerSecret,
|
|
||||||
githubClientId: instance.githubClientId,
|
|
||||||
githubClientSecret: instance.githubClientSecret,
|
|
||||||
discordClientId: instance.discordClientId,
|
|
||||||
discordClientSecret: instance.discordClientSecret,
|
|
||||||
summalyProxy: instance.summalyProxy,
|
summalyProxy: instance.summalyProxy,
|
||||||
email: instance.email,
|
email: instance.email,
|
||||||
smtpSecure: instance.smtpSecure,
|
smtpSecure: instance.smtpSecure,
|
||||||
|
|
|
@ -46,13 +46,6 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const maskedKeys = ["accessToken", "accessTokenSecret", "refreshToken"];
|
|
||||||
Object.keys(profile.integrations).forEach((integration) => {
|
|
||||||
maskedKeys.forEach(
|
|
||||||
(key) => (profile.integrations[integration][key] = "<MASKED>"),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const signins = await Signins.findBy({ userId: user.id });
|
const signins = await Signins.findBy({ userId: user.id });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -67,7 +60,6 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
carefulBot: profile.carefulBot,
|
carefulBot: profile.carefulBot,
|
||||||
injectFeaturedNote: profile.injectFeaturedNote,
|
injectFeaturedNote: profile.injectFeaturedNote,
|
||||||
receiveAnnouncementEmail: profile.receiveAnnouncementEmail,
|
receiveAnnouncementEmail: profile.receiveAnnouncementEmail,
|
||||||
integrations: profile.integrations,
|
|
||||||
mutedWords: profile.mutedWords,
|
mutedWords: profile.mutedWords,
|
||||||
mutedInstances: profile.mutedInstances,
|
mutedInstances: profile.mutedInstances,
|
||||||
mutingNotificationTypes: profile.mutingNotificationTypes,
|
mutingNotificationTypes: profile.mutingNotificationTypes,
|
||||||
|
|
|
@ -132,15 +132,6 @@ export const paramDef = {
|
||||||
deeplIsPro: { type: "boolean" },
|
deeplIsPro: { type: "boolean" },
|
||||||
libreTranslateApiUrl: { type: "string", nullable: true },
|
libreTranslateApiUrl: { type: "string", nullable: true },
|
||||||
libreTranslateApiKey: { type: "string", nullable: true },
|
libreTranslateApiKey: { type: "string", nullable: true },
|
||||||
enableTwitterIntegration: { type: "boolean" },
|
|
||||||
twitterConsumerKey: { type: "string", nullable: true },
|
|
||||||
twitterConsumerSecret: { type: "string", nullable: true },
|
|
||||||
enableGithubIntegration: { type: "boolean" },
|
|
||||||
githubClientId: { type: "string", nullable: true },
|
|
||||||
githubClientSecret: { type: "string", nullable: true },
|
|
||||||
enableDiscordIntegration: { type: "boolean" },
|
|
||||||
discordClientId: { type: "string", nullable: true },
|
|
||||||
discordClientSecret: { type: "string", nullable: true },
|
|
||||||
enableEmail: { type: "boolean" },
|
enableEmail: { type: "boolean" },
|
||||||
email: { type: "string", nullable: true },
|
email: { type: "string", nullable: true },
|
||||||
smtpSecure: { type: "boolean" },
|
smtpSecure: { type: "boolean" },
|
||||||
|
@ -395,42 +386,6 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
set.summalyProxy = ps.summalyProxy;
|
set.summalyProxy = ps.summalyProxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ps.enableTwitterIntegration !== undefined) {
|
|
||||||
set.enableTwitterIntegration = ps.enableTwitterIntegration;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ps.twitterConsumerKey !== undefined) {
|
|
||||||
set.twitterConsumerKey = ps.twitterConsumerKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ps.twitterConsumerSecret !== undefined) {
|
|
||||||
set.twitterConsumerSecret = ps.twitterConsumerSecret;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ps.enableGithubIntegration !== undefined) {
|
|
||||||
set.enableGithubIntegration = ps.enableGithubIntegration;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ps.githubClientId !== undefined) {
|
|
||||||
set.githubClientId = ps.githubClientId;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ps.githubClientSecret !== undefined) {
|
|
||||||
set.githubClientSecret = ps.githubClientSecret;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ps.enableDiscordIntegration !== undefined) {
|
|
||||||
set.enableDiscordIntegration = ps.enableDiscordIntegration;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ps.discordClientId !== undefined) {
|
|
||||||
set.discordClientId = ps.discordClientId;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ps.discordClientSecret !== undefined) {
|
|
||||||
set.discordClientSecret = ps.discordClientSecret;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ps.enableEmail !== undefined) {
|
if (ps.enableEmail !== undefined) {
|
||||||
set.enableEmail = ps.enableEmail;
|
set.enableEmail = ps.enableEmail;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ export const meta = {
|
||||||
id: "4362f8dc-731f-4ad8-a694-be2a88922a24",
|
id: "4362f8dc-731f-4ad8-a694-be2a88922a24",
|
||||||
},
|
},
|
||||||
uriNull: {
|
uriNull: {
|
||||||
message: "User ActivityPup URI is null.",
|
message: "User ActivityPub URI is null.",
|
||||||
code: "URI_NULL",
|
code: "URI_NULL",
|
||||||
id: "bf326f31-d430-4f97-9933-5d61e4d48a23",
|
id: "bf326f31-d430-4f97-9933-5d61e4d48a23",
|
||||||
},
|
},
|
||||||
|
|
|
@ -53,12 +53,12 @@ export const meta = {
|
||||||
id: "fcd2eef9-a9b2-4c4f-8624-038099e90aa5",
|
id: "fcd2eef9-a9b2-4c4f-8624-038099e90aa5",
|
||||||
},
|
},
|
||||||
uriNull: {
|
uriNull: {
|
||||||
message: "User ActivityPup URI is null.",
|
message: "User ActivityPub URI is null.",
|
||||||
code: "URI_NULL",
|
code: "URI_NULL",
|
||||||
id: "bf326f31-d430-4f97-9933-5d61e4d48a23",
|
id: "bf326f31-d430-4f97-9933-5d61e4d48a23",
|
||||||
},
|
},
|
||||||
localUriNull: {
|
localUriNull: {
|
||||||
message: "Local User ActivityPup URI is null.",
|
message: "Local User ActivityPub URI is null.",
|
||||||
code: "URI_NULL",
|
code: "URI_NULL",
|
||||||
id: "95ba11b9-90e8-43a5-ba16-7acc1ab32e71",
|
id: "95ba11b9-90e8-43a5-ba16-7acc1ab32e71",
|
||||||
},
|
},
|
||||||
|
|
|
@ -268,21 +268,6 @@ export const meta = {
|
||||||
optional: false,
|
optional: false,
|
||||||
nullable: false,
|
nullable: false,
|
||||||
},
|
},
|
||||||
enableTwitterIntegration: {
|
|
||||||
type: "boolean",
|
|
||||||
optional: false,
|
|
||||||
nullable: false,
|
|
||||||
},
|
|
||||||
enableGithubIntegration: {
|
|
||||||
type: "boolean",
|
|
||||||
optional: false,
|
|
||||||
nullable: false,
|
|
||||||
},
|
|
||||||
enableDiscordIntegration: {
|
|
||||||
type: "boolean",
|
|
||||||
optional: false,
|
|
||||||
nullable: false,
|
|
||||||
},
|
|
||||||
enableServiceWorker: {
|
enableServiceWorker: {
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
optional: false,
|
optional: false,
|
||||||
|
@ -343,21 +328,6 @@ export const meta = {
|
||||||
optional: false,
|
optional: false,
|
||||||
nullable: false,
|
nullable: false,
|
||||||
},
|
},
|
||||||
twitter: {
|
|
||||||
type: "boolean",
|
|
||||||
optional: false,
|
|
||||||
nullable: false,
|
|
||||||
},
|
|
||||||
github: {
|
|
||||||
type: "boolean",
|
|
||||||
optional: false,
|
|
||||||
nullable: false,
|
|
||||||
},
|
|
||||||
discord: {
|
|
||||||
type: "boolean",
|
|
||||||
optional: false,
|
|
||||||
nullable: false,
|
|
||||||
},
|
|
||||||
serviceWorker: {
|
serviceWorker: {
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
optional: false,
|
optional: false,
|
||||||
|
@ -493,10 +463,6 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
})),
|
})),
|
||||||
enableEmail: instance.enableEmail,
|
enableEmail: instance.enableEmail,
|
||||||
|
|
||||||
enableTwitterIntegration: instance.enableTwitterIntegration,
|
|
||||||
enableGithubIntegration: instance.enableGithubIntegration,
|
|
||||||
enableDiscordIntegration: instance.enableDiscordIntegration,
|
|
||||||
|
|
||||||
enableServiceWorker: instance.enableServiceWorker,
|
enableServiceWorker: instance.enableServiceWorker,
|
||||||
|
|
||||||
translatorAvailable:
|
translatorAvailable:
|
||||||
|
@ -539,9 +505,6 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
hcaptcha: instance.enableHcaptcha,
|
hcaptcha: instance.enableHcaptcha,
|
||||||
recaptcha: instance.enableRecaptcha,
|
recaptcha: instance.enableRecaptcha,
|
||||||
objectStorage: instance.useObjectStorage,
|
objectStorage: instance.useObjectStorage,
|
||||||
twitter: instance.enableTwitterIntegration,
|
|
||||||
github: instance.enableGithubIntegration,
|
|
||||||
discord: instance.enableDiscordIntegration,
|
|
||||||
serviceWorker: instance.enableServiceWorker,
|
serviceWorker: instance.enableServiceWorker,
|
||||||
postEditing: true,
|
postEditing: true,
|
||||||
postImports: instance.experimentalFeatures?.postImports || false,
|
postImports: instance.experimentalFeatures?.postImports || false,
|
||||||
|
|
|
@ -21,9 +21,6 @@ import signup from "./private/signup.js";
|
||||||
import signin from "./private/signin.js";
|
import signin from "./private/signin.js";
|
||||||
import signupPending from "./private/signup-pending.js";
|
import signupPending from "./private/signup-pending.js";
|
||||||
import verifyEmail from "./private/verify-email.js";
|
import verifyEmail from "./private/verify-email.js";
|
||||||
import discord from "./service/discord.js";
|
|
||||||
import github from "./service/github.js";
|
|
||||||
import twitter from "./service/twitter.js";
|
|
||||||
import { koaBody } from "koa-body";
|
import { koaBody } from "koa-body";
|
||||||
import {
|
import {
|
||||||
convertId,
|
convertId,
|
||||||
|
@ -181,10 +178,6 @@ router.post("/signin", signin);
|
||||||
router.post("/signup-pending", signupPending);
|
router.post("/signup-pending", signupPending);
|
||||||
router.post("/verify-email", verifyEmail);
|
router.post("/verify-email", verifyEmail);
|
||||||
|
|
||||||
router.use(discord.routes());
|
|
||||||
router.use(github.routes());
|
|
||||||
router.use(twitter.routes());
|
|
||||||
|
|
||||||
router.post("/miauth/:session/check", async (ctx) => {
|
router.post("/miauth/:session/check", async (ctx) => {
|
||||||
const token = await AccessTokens.findOneBy({
|
const token = await AccessTokens.findOneBy({
|
||||||
session: ctx.params.session,
|
session: ctx.params.session,
|
||||||
|
|
|
@ -1,333 +0,0 @@
|
||||||
import type Koa from "koa";
|
|
||||||
import Router from "@koa/router";
|
|
||||||
import { OAuth2 } from "oauth";
|
|
||||||
import { v4 as uuid } from "uuid";
|
|
||||||
import { IsNull } from "typeorm";
|
|
||||||
import { getJson } from "@/misc/fetch.js";
|
|
||||||
import config from "@/config/index.js";
|
|
||||||
import { publishMainStream } from "@/services/stream.js";
|
|
||||||
import { fetchMeta } from "@/misc/fetch-meta.js";
|
|
||||||
import { Users, UserProfiles } from "@/models/index.js";
|
|
||||||
import type { ILocalUser } from "@/models/entities/user.js";
|
|
||||||
import { redisClient } from "../../../db/redis.js";
|
|
||||||
import signin from "../common/signin.js";
|
|
||||||
|
|
||||||
function getUserToken(ctx: Koa.BaseContext): string | null {
|
|
||||||
return ((ctx.headers["cookie"] || "").match(/igi=(\w+)/) || [null, null])[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
function compareOrigin(ctx: Koa.BaseContext): boolean {
|
|
||||||
function normalizeUrl(url?: string): string {
|
|
||||||
return url ? (url.endsWith("/") ? url.slice(0, url.length - 1) : url) : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
const referer = ctx.headers["referer"];
|
|
||||||
|
|
||||||
return normalizeUrl(referer) === normalizeUrl(config.url);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init router
|
|
||||||
const router = new Router();
|
|
||||||
|
|
||||||
router.get("/disconnect/discord", async (ctx) => {
|
|
||||||
if (!compareOrigin(ctx)) {
|
|
||||||
ctx.throw(400, "invalid origin");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const userToken = getUserToken(ctx);
|
|
||||||
if (!userToken) {
|
|
||||||
ctx.throw(400, "signin required");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = await Users.findOneByOrFail({
|
|
||||||
host: IsNull(),
|
|
||||||
token: userToken,
|
|
||||||
});
|
|
||||||
|
|
||||||
const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
|
|
||||||
|
|
||||||
profile.integrations.discord = undefined;
|
|
||||||
|
|
||||||
await UserProfiles.update(user.id, {
|
|
||||||
integrations: profile.integrations,
|
|
||||||
});
|
|
||||||
|
|
||||||
ctx.body = "Discordの連携を解除しました :v:";
|
|
||||||
|
|
||||||
// Publish i updated event
|
|
||||||
publishMainStream(
|
|
||||||
user.id,
|
|
||||||
"meUpdated",
|
|
||||||
await Users.pack(user, user, {
|
|
||||||
detail: true,
|
|
||||||
includeSecrets: true,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
async function getOAuth2() {
|
|
||||||
const meta = await fetchMeta(true);
|
|
||||||
|
|
||||||
if (meta.enableDiscordIntegration) {
|
|
||||||
return new OAuth2(
|
|
||||||
meta.discordClientId!,
|
|
||||||
meta.discordClientSecret!,
|
|
||||||
"https://discord.com/",
|
|
||||||
"api/oauth2/authorize",
|
|
||||||
"api/oauth2/token",
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
router.get("/connect/discord", async (ctx) => {
|
|
||||||
if (!compareOrigin(ctx)) {
|
|
||||||
ctx.throw(400, "invalid origin");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const userToken = getUserToken(ctx);
|
|
||||||
if (!userToken) {
|
|
||||||
ctx.throw(400, "signin required");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
redirect_uri: `${config.url}/api/dc/cb`,
|
|
||||||
scope: ["identify"],
|
|
||||||
state: uuid(),
|
|
||||||
response_type: "code",
|
|
||||||
};
|
|
||||||
|
|
||||||
redisClient.set(userToken, JSON.stringify(params));
|
|
||||||
|
|
||||||
const oauth2 = await getOAuth2();
|
|
||||||
ctx.redirect(oauth2!.getAuthorizeUrl(params));
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get("/signin/discord", async (ctx) => {
|
|
||||||
const sessid = uuid();
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
redirect_uri: `${config.url}/api/dc/cb`,
|
|
||||||
scope: ["identify"],
|
|
||||||
state: uuid(),
|
|
||||||
response_type: "code",
|
|
||||||
};
|
|
||||||
|
|
||||||
ctx.cookies.set("signin_with_discord_sid", sessid, {
|
|
||||||
path: "/",
|
|
||||||
secure: config.url.startsWith("https"),
|
|
||||||
httpOnly: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
redisClient.set(sessid, JSON.stringify(params));
|
|
||||||
|
|
||||||
const oauth2 = await getOAuth2();
|
|
||||||
ctx.redirect(oauth2!.getAuthorizeUrl(params));
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get("/dc/cb", async (ctx) => {
|
|
||||||
const userToken = getUserToken(ctx);
|
|
||||||
|
|
||||||
const oauth2 = await getOAuth2();
|
|
||||||
|
|
||||||
if (!userToken) {
|
|
||||||
const sessid = ctx.cookies.get("signin_with_discord_sid");
|
|
||||||
|
|
||||||
if (!sessid) {
|
|
||||||
ctx.throw(400, "invalid session");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const code = ctx.query.code;
|
|
||||||
|
|
||||||
if (!code || typeof code !== "string") {
|
|
||||||
ctx.throw(400, "invalid session");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { redirect_uri, state } = await new Promise<any>((res, rej) => {
|
|
||||||
redisClient.get(sessid, async (_, state) => {
|
|
||||||
res(JSON.parse(state));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (ctx.query.state !== state) {
|
|
||||||
ctx.throw(400, "invalid session");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { accessToken, refreshToken, expiresDate } = await new Promise<any>(
|
|
||||||
(res, rej) =>
|
|
||||||
oauth2!.getOAuthAccessToken(
|
|
||||||
code,
|
|
||||||
{
|
|
||||||
grant_type: "authorization_code",
|
|
||||||
redirect_uri,
|
|
||||||
},
|
|
||||||
(err, accessToken, refreshToken, result) => {
|
|
||||||
if (err) {
|
|
||||||
rej(err);
|
|
||||||
} else if (result.error) {
|
|
||||||
rej(result.error);
|
|
||||||
} else {
|
|
||||||
res({
|
|
||||||
accessToken,
|
|
||||||
refreshToken,
|
|
||||||
expiresDate: Date.now() + Number(result.expires_in) * 1000,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
const { id, username, discriminator } = (await getJson(
|
|
||||||
"https://discord.com/api/users/@me",
|
|
||||||
"*/*",
|
|
||||||
10 * 1000,
|
|
||||||
{
|
|
||||||
Authorization: `Bearer ${accessToken}`,
|
|
||||||
},
|
|
||||||
)) as Record<string, unknown>;
|
|
||||||
|
|
||||||
if (
|
|
||||||
typeof id !== "string" ||
|
|
||||||
typeof username !== "string" ||
|
|
||||||
typeof discriminator !== "string"
|
|
||||||
) {
|
|
||||||
ctx.throw(400, "invalid session");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const profile = await UserProfiles.createQueryBuilder()
|
|
||||||
.where("\"integrations\"->'discord'->>'id' = :id", { id: id })
|
|
||||||
.andWhere('"userHost" IS NULL')
|
|
||||||
.getOne();
|
|
||||||
|
|
||||||
if (profile == null) {
|
|
||||||
ctx.throw(
|
|
||||||
404,
|
|
||||||
`@${username}#${discriminator}と連携しているMisskeyアカウントはありませんでした...`,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await UserProfiles.update(profile.userId, {
|
|
||||||
integrations: {
|
|
||||||
...profile.integrations,
|
|
||||||
discord: {
|
|
||||||
id: id,
|
|
||||||
accessToken: accessToken,
|
|
||||||
refreshToken: refreshToken,
|
|
||||||
expiresDate: expiresDate,
|
|
||||||
username: username,
|
|
||||||
discriminator: discriminator,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
signin(
|
|
||||||
ctx,
|
|
||||||
(await Users.findOneBy({ id: profile.userId })) as ILocalUser,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
const code = ctx.query.code;
|
|
||||||
|
|
||||||
if (!code || typeof code !== "string") {
|
|
||||||
ctx.throw(400, "invalid session");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { redirect_uri, state } = await new Promise<any>((res, rej) => {
|
|
||||||
redisClient.get(userToken, async (_, state) => {
|
|
||||||
res(JSON.parse(state));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (ctx.query.state !== state) {
|
|
||||||
ctx.throw(400, "invalid session");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { accessToken, refreshToken, expiresDate } = await new Promise<any>(
|
|
||||||
(res, rej) =>
|
|
||||||
oauth2!.getOAuthAccessToken(
|
|
||||||
code,
|
|
||||||
{
|
|
||||||
grant_type: "authorization_code",
|
|
||||||
redirect_uri,
|
|
||||||
},
|
|
||||||
(err, accessToken, refreshToken, result) => {
|
|
||||||
if (err) {
|
|
||||||
rej(err);
|
|
||||||
} else if (result.error) {
|
|
||||||
rej(result.error);
|
|
||||||
} else {
|
|
||||||
res({
|
|
||||||
accessToken,
|
|
||||||
refreshToken,
|
|
||||||
expiresDate: Date.now() + Number(result.expires_in) * 1000,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
const { id, username, discriminator } = (await getJson(
|
|
||||||
"https://discord.com/api/users/@me",
|
|
||||||
"*/*",
|
|
||||||
10 * 1000,
|
|
||||||
{
|
|
||||||
Authorization: `Bearer ${accessToken}`,
|
|
||||||
},
|
|
||||||
)) as Record<string, unknown>;
|
|
||||||
if (
|
|
||||||
typeof id !== "string" ||
|
|
||||||
typeof username !== "string" ||
|
|
||||||
typeof discriminator !== "string"
|
|
||||||
) {
|
|
||||||
ctx.throw(400, "invalid session");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = await Users.findOneByOrFail({
|
|
||||||
host: IsNull(),
|
|
||||||
token: userToken,
|
|
||||||
});
|
|
||||||
|
|
||||||
const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
|
|
||||||
|
|
||||||
await UserProfiles.update(user.id, {
|
|
||||||
integrations: {
|
|
||||||
...profile.integrations,
|
|
||||||
discord: {
|
|
||||||
accessToken: accessToken,
|
|
||||||
refreshToken: refreshToken,
|
|
||||||
expiresDate: expiresDate,
|
|
||||||
id: id,
|
|
||||||
username: username,
|
|
||||||
discriminator: discriminator,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
ctx.body = `Discord: @${username}#${discriminator} を、Misskey: @${user.username} に接続しました!`;
|
|
||||||
|
|
||||||
// Publish i updated event
|
|
||||||
publishMainStream(
|
|
||||||
user.id,
|
|
||||||
"meUpdated",
|
|
||||||
await Users.pack(user, user, {
|
|
||||||
detail: true,
|
|
||||||
includeSecrets: true,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default router;
|
|
|
@ -1,296 +0,0 @@
|
||||||
import type Koa from "koa";
|
|
||||||
import Router from "@koa/router";
|
|
||||||
import { OAuth2 } from "oauth";
|
|
||||||
import { v4 as uuid } from "uuid";
|
|
||||||
import { IsNull } from "typeorm";
|
|
||||||
import { getJson } from "@/misc/fetch.js";
|
|
||||||
import config from "@/config/index.js";
|
|
||||||
import { publishMainStream } from "@/services/stream.js";
|
|
||||||
import { fetchMeta } from "@/misc/fetch-meta.js";
|
|
||||||
import { Users, UserProfiles } from "@/models/index.js";
|
|
||||||
import type { ILocalUser } from "@/models/entities/user.js";
|
|
||||||
import { redisClient } from "../../../db/redis.js";
|
|
||||||
import signin from "../common/signin.js";
|
|
||||||
|
|
||||||
function getUserToken(ctx: Koa.BaseContext): string | null {
|
|
||||||
return ((ctx.headers["cookie"] || "").match(/igi=(\w+)/) || [null, null])[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
function compareOrigin(ctx: Koa.BaseContext): boolean {
|
|
||||||
function normalizeUrl(url?: string): string {
|
|
||||||
return url ? (url.endsWith("/") ? url.slice(0, url.length - 1) : url) : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
const referer = ctx.headers["referer"];
|
|
||||||
|
|
||||||
return normalizeUrl(referer) === normalizeUrl(config.url);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init router
|
|
||||||
const router = new Router();
|
|
||||||
|
|
||||||
router.get("/disconnect/github", async (ctx) => {
|
|
||||||
if (!compareOrigin(ctx)) {
|
|
||||||
ctx.throw(400, "invalid origin");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const userToken = getUserToken(ctx);
|
|
||||||
if (!userToken) {
|
|
||||||
ctx.throw(400, "signin required");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = await Users.findOneByOrFail({
|
|
||||||
host: IsNull(),
|
|
||||||
token: userToken,
|
|
||||||
});
|
|
||||||
|
|
||||||
const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
|
|
||||||
|
|
||||||
profile.integrations.github = undefined;
|
|
||||||
|
|
||||||
await UserProfiles.update(user.id, {
|
|
||||||
integrations: profile.integrations,
|
|
||||||
});
|
|
||||||
|
|
||||||
ctx.body = "GitHubの連携を解除しました :v:";
|
|
||||||
|
|
||||||
// Publish i updated event
|
|
||||||
publishMainStream(
|
|
||||||
user.id,
|
|
||||||
"meUpdated",
|
|
||||||
await Users.pack(user, user, {
|
|
||||||
detail: true,
|
|
||||||
includeSecrets: true,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
async function getOath2() {
|
|
||||||
const meta = await fetchMeta(true);
|
|
||||||
|
|
||||||
if (
|
|
||||||
meta.enableGithubIntegration &&
|
|
||||||
meta.githubClientId &&
|
|
||||||
meta.githubClientSecret
|
|
||||||
) {
|
|
||||||
return new OAuth2(
|
|
||||||
meta.githubClientId,
|
|
||||||
meta.githubClientSecret,
|
|
||||||
"https://github.com/",
|
|
||||||
"login/oauth/authorize",
|
|
||||||
"login/oauth/access_token",
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
router.get("/connect/github", async (ctx) => {
|
|
||||||
if (!compareOrigin(ctx)) {
|
|
||||||
ctx.throw(400, "invalid origin");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const userToken = getUserToken(ctx);
|
|
||||||
if (!userToken) {
|
|
||||||
ctx.throw(400, "signin required");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
redirect_uri: `${config.url}/api/gh/cb`,
|
|
||||||
scope: ["read:user"],
|
|
||||||
state: uuid(),
|
|
||||||
};
|
|
||||||
|
|
||||||
redisClient.set(userToken, JSON.stringify(params));
|
|
||||||
|
|
||||||
const oauth2 = await getOath2();
|
|
||||||
ctx.redirect(oauth2!.getAuthorizeUrl(params));
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get("/signin/github", async (ctx) => {
|
|
||||||
const sessid = uuid();
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
redirect_uri: `${config.url}/api/gh/cb`,
|
|
||||||
scope: ["read:user"],
|
|
||||||
state: uuid(),
|
|
||||||
};
|
|
||||||
|
|
||||||
ctx.cookies.set("signin_with_github_sid", sessid, {
|
|
||||||
path: "/",
|
|
||||||
secure: config.url.startsWith("https"),
|
|
||||||
httpOnly: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
redisClient.set(sessid, JSON.stringify(params));
|
|
||||||
|
|
||||||
const oauth2 = await getOath2();
|
|
||||||
ctx.redirect(oauth2!.getAuthorizeUrl(params));
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get("/gh/cb", async (ctx) => {
|
|
||||||
const userToken = getUserToken(ctx);
|
|
||||||
|
|
||||||
const oauth2 = await getOath2();
|
|
||||||
|
|
||||||
if (!userToken) {
|
|
||||||
const sessid = ctx.cookies.get("signin_with_github_sid");
|
|
||||||
|
|
||||||
if (!sessid) {
|
|
||||||
ctx.throw(400, "invalid session");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const code = ctx.query.code;
|
|
||||||
|
|
||||||
if (!code || typeof code !== "string") {
|
|
||||||
ctx.throw(400, "invalid session");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { redirect_uri, state } = await new Promise<any>((res, rej) => {
|
|
||||||
redisClient.get(sessid, async (_, state) => {
|
|
||||||
res(JSON.parse(state));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (ctx.query.state !== state) {
|
|
||||||
ctx.throw(400, "invalid session");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { accessToken } = await new Promise<any>((res, rej) =>
|
|
||||||
oauth2!.getOAuthAccessToken(
|
|
||||||
code,
|
|
||||||
{
|
|
||||||
redirect_uri,
|
|
||||||
},
|
|
||||||
(err, accessToken, refresh, result) => {
|
|
||||||
if (err) {
|
|
||||||
rej(err);
|
|
||||||
} else if (result.error) {
|
|
||||||
rej(result.error);
|
|
||||||
} else {
|
|
||||||
res({ accessToken });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
const { login, id } = (await getJson(
|
|
||||||
"https://api.github.com/user",
|
|
||||||
"application/vnd.github.v3+json",
|
|
||||||
10 * 1000,
|
|
||||||
{
|
|
||||||
Authorization: `bearer ${accessToken}`,
|
|
||||||
},
|
|
||||||
)) as Record<string, unknown>;
|
|
||||||
if (typeof login !== "string" || typeof id !== "string") {
|
|
||||||
ctx.throw(400, "invalid session");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const link = await UserProfiles.createQueryBuilder()
|
|
||||||
.where("\"integrations\"->'github'->>'id' = :id", { id: id })
|
|
||||||
.andWhere('"userHost" IS NULL')
|
|
||||||
.getOne();
|
|
||||||
|
|
||||||
if (link == null) {
|
|
||||||
ctx.throw(
|
|
||||||
404,
|
|
||||||
`@${login}と連携しているMisskeyアカウントはありませんでした...`,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
signin(
|
|
||||||
ctx,
|
|
||||||
(await Users.findOneBy({ id: link.userId })) as ILocalUser,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
const code = ctx.query.code;
|
|
||||||
|
|
||||||
if (!code || typeof code !== "string") {
|
|
||||||
ctx.throw(400, "invalid session");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { redirect_uri, state } = await new Promise<any>((res, rej) => {
|
|
||||||
redisClient.get(userToken, async (_, state) => {
|
|
||||||
res(JSON.parse(state));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (ctx.query.state !== state) {
|
|
||||||
ctx.throw(400, "invalid session");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { accessToken } = await new Promise<any>((res, rej) =>
|
|
||||||
oauth2!.getOAuthAccessToken(
|
|
||||||
code,
|
|
||||||
{ redirect_uri },
|
|
||||||
(err, accessToken, refresh, result) => {
|
|
||||||
if (err) {
|
|
||||||
rej(err);
|
|
||||||
} else if (result.error) {
|
|
||||||
rej(result.error);
|
|
||||||
} else {
|
|
||||||
res({ accessToken });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
const { login, id } = (await getJson(
|
|
||||||
"https://api.github.com/user",
|
|
||||||
"application/vnd.github.v3+json",
|
|
||||||
10 * 1000,
|
|
||||||
{
|
|
||||||
Authorization: `bearer ${accessToken}`,
|
|
||||||
},
|
|
||||||
)) as Record<string, unknown>;
|
|
||||||
|
|
||||||
if (typeof login !== "string" || typeof id !== "string") {
|
|
||||||
ctx.throw(400, "invalid session");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = await Users.findOneByOrFail({
|
|
||||||
host: IsNull(),
|
|
||||||
token: userToken,
|
|
||||||
});
|
|
||||||
|
|
||||||
const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
|
|
||||||
|
|
||||||
await UserProfiles.update(user.id, {
|
|
||||||
integrations: {
|
|
||||||
...profile.integrations,
|
|
||||||
github: {
|
|
||||||
accessToken: accessToken,
|
|
||||||
id: id,
|
|
||||||
login: login,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
ctx.body = `GitHub: @${login} を、Misskey: @${user.username} に接続しました!`;
|
|
||||||
|
|
||||||
// Publish i updated event
|
|
||||||
publishMainStream(
|
|
||||||
user.id,
|
|
||||||
"meUpdated",
|
|
||||||
await Users.pack(user, user, {
|
|
||||||
detail: true,
|
|
||||||
includeSecrets: true,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default router;
|
|
|
@ -1,226 +0,0 @@
|
||||||
import type Koa from "koa";
|
|
||||||
import Router from "@koa/router";
|
|
||||||
import { v4 as uuid } from "uuid";
|
|
||||||
import autwh from "autwh";
|
|
||||||
import { IsNull } from "typeorm";
|
|
||||||
import { publishMainStream } from "@/services/stream.js";
|
|
||||||
import config from "@/config/index.js";
|
|
||||||
import { fetchMeta } from "@/misc/fetch-meta.js";
|
|
||||||
import { Users, UserProfiles } from "@/models/index.js";
|
|
||||||
import type { ILocalUser } from "@/models/entities/user.js";
|
|
||||||
import signin from "../common/signin.js";
|
|
||||||
import { redisClient } from "../../../db/redis.js";
|
|
||||||
|
|
||||||
function getUserToken(ctx: Koa.BaseContext): string | null {
|
|
||||||
return ((ctx.headers["cookie"] || "").match(/igi=(\w+)/) || [null, null])[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
function compareOrigin(ctx: Koa.BaseContext): boolean {
|
|
||||||
function normalizeUrl(url?: string): string {
|
|
||||||
return url == null
|
|
||||||
? ""
|
|
||||||
: url.endsWith("/")
|
|
||||||
? url.substr(0, url.length - 1)
|
|
||||||
: url;
|
|
||||||
}
|
|
||||||
|
|
||||||
const referer = ctx.headers["referer"];
|
|
||||||
|
|
||||||
return normalizeUrl(referer) === normalizeUrl(config.url);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init router
|
|
||||||
const router = new Router();
|
|
||||||
|
|
||||||
router.get("/disconnect/twitter", async (ctx) => {
|
|
||||||
if (!compareOrigin(ctx)) {
|
|
||||||
ctx.throw(400, "invalid origin");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const userToken = getUserToken(ctx);
|
|
||||||
if (userToken == null) {
|
|
||||||
ctx.throw(400, "signin required");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = await Users.findOneByOrFail({
|
|
||||||
host: IsNull(),
|
|
||||||
token: userToken,
|
|
||||||
});
|
|
||||||
|
|
||||||
const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
|
|
||||||
|
|
||||||
profile.integrations.twitter = undefined;
|
|
||||||
|
|
||||||
await UserProfiles.update(user.id, {
|
|
||||||
integrations: profile.integrations,
|
|
||||||
});
|
|
||||||
|
|
||||||
ctx.body = "Twitterの連携を解除しました :v:";
|
|
||||||
|
|
||||||
// Publish i updated event
|
|
||||||
publishMainStream(
|
|
||||||
user.id,
|
|
||||||
"meUpdated",
|
|
||||||
await Users.pack(user, user, {
|
|
||||||
detail: true,
|
|
||||||
includeSecrets: true,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
async function getTwAuth() {
|
|
||||||
const meta = await fetchMeta(true);
|
|
||||||
|
|
||||||
if (
|
|
||||||
meta.enableTwitterIntegration &&
|
|
||||||
meta.twitterConsumerKey &&
|
|
||||||
meta.twitterConsumerSecret
|
|
||||||
) {
|
|
||||||
return autwh({
|
|
||||||
consumerKey: meta.twitterConsumerKey,
|
|
||||||
consumerSecret: meta.twitterConsumerSecret,
|
|
||||||
callbackUrl: `${config.url}/api/tw/cb`,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
router.get("/connect/twitter", async (ctx) => {
|
|
||||||
if (!compareOrigin(ctx)) {
|
|
||||||
ctx.throw(400, "invalid origin");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const userToken = getUserToken(ctx);
|
|
||||||
if (userToken == null) {
|
|
||||||
ctx.throw(400, "signin required");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const twAuth = await getTwAuth();
|
|
||||||
const twCtx = await twAuth!.begin();
|
|
||||||
redisClient.set(userToken, JSON.stringify(twCtx));
|
|
||||||
ctx.redirect(twCtx.url);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get("/signin/twitter", async (ctx) => {
|
|
||||||
const twAuth = await getTwAuth();
|
|
||||||
const twCtx = await twAuth!.begin();
|
|
||||||
|
|
||||||
const sessid = uuid();
|
|
||||||
|
|
||||||
redisClient.set(sessid, JSON.stringify(twCtx));
|
|
||||||
|
|
||||||
ctx.cookies.set("signin_with_twitter_sid", sessid, {
|
|
||||||
path: "/",
|
|
||||||
secure: config.url.startsWith("https"),
|
|
||||||
httpOnly: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
ctx.redirect(twCtx.url);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get("/tw/cb", async (ctx) => {
|
|
||||||
const userToken = getUserToken(ctx);
|
|
||||||
|
|
||||||
const twAuth = await getTwAuth();
|
|
||||||
|
|
||||||
if (userToken == null) {
|
|
||||||
const sessid = ctx.cookies.get("signin_with_twitter_sid");
|
|
||||||
|
|
||||||
if (sessid == null) {
|
|
||||||
ctx.throw(400, "invalid session");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const get = new Promise<any>((res, rej) => {
|
|
||||||
redisClient.get(sessid, async (_, twCtx) => {
|
|
||||||
res(twCtx);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const twCtx = await get;
|
|
||||||
|
|
||||||
const verifier = ctx.query.oauth_verifier;
|
|
||||||
if (!verifier || typeof verifier !== "string") {
|
|
||||||
ctx.throw(400, "invalid session");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await twAuth!.done(JSON.parse(twCtx), verifier);
|
|
||||||
|
|
||||||
const link = await UserProfiles.createQueryBuilder()
|
|
||||||
.where("\"integrations\"->'twitter'->>'userId' = :id", {
|
|
||||||
id: result.userId,
|
|
||||||
})
|
|
||||||
.andWhere('"userHost" IS NULL')
|
|
||||||
.getOne();
|
|
||||||
|
|
||||||
if (link == null) {
|
|
||||||
ctx.throw(
|
|
||||||
404,
|
|
||||||
`@${result.screenName}と連携しているMisskeyアカウントはありませんでした...`,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
signin(
|
|
||||||
ctx,
|
|
||||||
(await Users.findOneBy({ id: link.userId })) as ILocalUser,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
const verifier = ctx.query.oauth_verifier;
|
|
||||||
|
|
||||||
if (!verifier || typeof verifier !== "string") {
|
|
||||||
ctx.throw(400, "invalid session");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const get = new Promise<any>((res, rej) => {
|
|
||||||
redisClient.get(userToken, async (_, twCtx) => {
|
|
||||||
res(twCtx);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const twCtx = await get;
|
|
||||||
|
|
||||||
const result = await twAuth!.done(JSON.parse(twCtx), verifier);
|
|
||||||
|
|
||||||
const user = await Users.findOneByOrFail({
|
|
||||||
host: IsNull(),
|
|
||||||
token: userToken,
|
|
||||||
});
|
|
||||||
|
|
||||||
const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
|
|
||||||
|
|
||||||
await UserProfiles.update(user.id, {
|
|
||||||
integrations: {
|
|
||||||
...profile.integrations,
|
|
||||||
twitter: {
|
|
||||||
accessToken: result.accessToken,
|
|
||||||
accessTokenSecret: result.accessTokenSecret,
|
|
||||||
userId: result.userId,
|
|
||||||
screenName: result.screenName,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
ctx.body = `Twitter: @${result.screenName} を、Misskey: @${user.username} に接続しました!`;
|
|
||||||
|
|
||||||
// Publish i updated event
|
|
||||||
publishMainStream(
|
|
||||||
user.id,
|
|
||||||
"meUpdated",
|
|
||||||
await Users.pack(user, user, {
|
|
||||||
detail: true,
|
|
||||||
includeSecrets: true,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default router;
|
|
|
@ -68,7 +68,7 @@ export default class extends Channel {
|
||||||
// そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる
|
// そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる
|
||||||
if (
|
if (
|
||||||
this.userProfile &&
|
this.userProfile &&
|
||||||
(await getWordHardMute(note, this.user, this.userProfile.mutedWords))
|
(await getWordHardMute(note, this.user?.id, this.userProfile.mutedWords))
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
|
@ -67,7 +67,7 @@ export default class extends Channel {
|
||||||
// そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる
|
// そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる
|
||||||
if (
|
if (
|
||||||
this.userProfile &&
|
this.userProfile &&
|
||||||
(await getWordHardMute(note, this.user, this.userProfile.mutedWords))
|
(await getWordHardMute(note, this.user?.id, this.userProfile.mutedWords))
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
|
@ -84,7 +84,7 @@ export default class extends Channel {
|
||||||
// そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる
|
// そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる
|
||||||
if (
|
if (
|
||||||
this.userProfile &&
|
this.userProfile &&
|
||||||
(await getWordHardMute(note, this.user, this.userProfile.mutedWords))
|
(await getWordHardMute(note, this.user?.id, this.userProfile.mutedWords))
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,7 @@ export default class extends Channel {
|
||||||
// そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる
|
// そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる
|
||||||
if (
|
if (
|
||||||
this.userProfile &&
|
this.userProfile &&
|
||||||
(await getWordHardMute(note, this.user, this.userProfile.mutedWords))
|
(await getWordHardMute(note, this.user?.id, this.userProfile.mutedWords))
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
|
@ -82,7 +82,7 @@ export default class extends Channel {
|
||||||
// そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる
|
// そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる
|
||||||
if (
|
if (
|
||||||
this.userProfile &&
|
this.userProfile &&
|
||||||
(await getWordHardMute(note, this.user, this.userProfile.mutedWords))
|
(await getWordHardMute(note, this.user?.id, this.userProfile.mutedWords))
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import * as mfm from "mfm-js";
|
import * as mfm from "mfm-js";
|
||||||
import es from "../../db/elasticsearch.js";
|
import es from "@/db/elasticsearch.js";
|
||||||
import sonic from "../../db/sonic.js";
|
import sonic from "@/db/sonic.js";
|
||||||
import {
|
import {
|
||||||
publishMainStream,
|
publishMainStream,
|
||||||
publishNotesStream,
|
publishNotesStream,
|
||||||
|
@ -380,8 +380,7 @@ export default async (
|
||||||
)
|
)
|
||||||
.then((us) => {
|
.then((us) => {
|
||||||
for (const u of us) {
|
for (const u of us) {
|
||||||
getWordHardMute(data, { id: u.userId }, u.mutedWords).then(
|
getWordHardMute(data, u.userId, u.mutedWords).then((shouldMute) => {
|
||||||
(shouldMute) => {
|
|
||||||
if (shouldMute) {
|
if (shouldMute) {
|
||||||
MutedNotes.insert({
|
MutedNotes.insert({
|
||||||
id: genId(),
|
id: genId(),
|
||||||
|
@ -390,8 +389,7 @@ export default async (
|
||||||
reason: "word",
|
reason: "word",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,6 @@ import {
|
||||||
} from "@/models/index.js";
|
} from "@/models/index.js";
|
||||||
import { Not, IsNull, In } from "typeorm";
|
import { Not, IsNull, In } from "typeorm";
|
||||||
import type { Channel } from "@/models/entities/channel.js";
|
import type { Channel } from "@/models/entities/channel.js";
|
||||||
import { checkHitAntenna } from "@/misc/check-hit-antenna.js";
|
|
||||||
import { getAntennas } from "@/misc/antenna-cache.js";
|
|
||||||
import { readNotificationByQuery } from "@/server/api/common/read-notification.js";
|
import { readNotificationByQuery } from "@/server/api/common/read-notification.js";
|
||||||
import type { Packed } from "@/misc/schema.js";
|
import type { Packed } from "@/misc/schema.js";
|
||||||
|
|
||||||
|
@ -66,23 +64,6 @@ export default async function (
|
||||||
if (note.channelId && followingChannels.has(note.channelId)) {
|
if (note.channelId && followingChannels.has(note.channelId)) {
|
||||||
readChannelNotes.push(note);
|
readChannelNotes.push(note);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (note.user != null) {
|
|
||||||
// // たぶんnullになることは無いはずだけど一応
|
|
||||||
// for (const antenna of myAntennas) {
|
|
||||||
// if (
|
|
||||||
// await checkHitAntenna(
|
|
||||||
// antenna,
|
|
||||||
// note,
|
|
||||||
// note.user,
|
|
||||||
// undefined,
|
|
||||||
// Array.from(following),
|
|
||||||
// )
|
|
||||||
// ) {
|
|
||||||
// readAntennaNotes.push(note);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
"@types/uuid": "9.0.3",
|
"@types/uuid": "9.0.3",
|
||||||
"@vitejs/plugin-vue": "4.3.4",
|
"@vitejs/plugin-vue": "4.3.4",
|
||||||
"@vue/compiler-sfc": "3.3.4",
|
"@vue/compiler-sfc": "3.3.4",
|
||||||
|
"@vue/runtime-core": "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",
|
||||||
|
|
|
@ -3,8 +3,9 @@ import type * as firefish 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 { alert, api, popup, popupMenu, success, waiting } from "@/os";
|
import { alert, api, popup, popupMenu, waiting } from "@/os";
|
||||||
import { reloadChannel, unisonReload } from "@/scripts/unison-reload";
|
import { reloadChannel, unisonReload } from "@/scripts/unison-reload";
|
||||||
|
import icon from "@/scripts/icon";
|
||||||
|
|
||||||
// TODO: 他のタブと永続化されたstateを同期
|
// TODO: 他のタブと永続化されたstateを同期
|
||||||
|
|
||||||
|
@ -249,7 +250,7 @@ export async function openAccountMenu(
|
||||||
...accountItemPromises,
|
...accountItemPromises,
|
||||||
{
|
{
|
||||||
type: "parent",
|
type: "parent",
|
||||||
icon: "ph-plus ph-bold ph-lg",
|
icon: `${icon("ph-plus")}`,
|
||||||
text: i18n.ts.addAccount,
|
text: i18n.ts.addAccount,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
|
@ -268,13 +269,13 @@ export async function openAccountMenu(
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "link",
|
type: "link",
|
||||||
icon: "ph-users ph-bold ph-lg",
|
icon: `${icon("ph-users")}`,
|
||||||
text: i18n.ts.manageAccounts,
|
text: i18n.ts.manageAccounts,
|
||||||
to: "/settings/accounts",
|
to: "/settings/accounts",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "button",
|
type: "button",
|
||||||
icon: "ph-sign-out ph-bold ph-lg",
|
icon: `${icon("ph-sign-out")}`,
|
||||||
text: i18n.ts.logout,
|
text: i18n.ts.logout,
|
||||||
action: () => {
|
action: () => {
|
||||||
signout();
|
signout();
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<i
|
<i
|
||||||
class="ph-warning-circle ph-bold ph-lg"
|
:class="icon('ph-warning-circle')"
|
||||||
style="margin-right: 0.5em"
|
style="margin-right: 0.5em"
|
||||||
></i>
|
></i>
|
||||||
<I18n :src="i18n.ts.reportAbuseOf" tag="span">
|
<I18n :src="i18n.ts.reportAbuseOf" tag="span">
|
||||||
|
@ -47,6 +47,7 @@ import MkTextarea from "@/components/form/textarea.vue";
|
||||||
import MkButton from "@/components/MkButton.vue";
|
import MkButton from "@/components/MkButton.vue";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
import icon from "@/scripts/icon";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
user: firefish.entities.User;
|
user: firefish.entities.User;
|
||||||
|
|
|
@ -8,16 +8,16 @@
|
||||||
<template v-if="!wait">
|
<template v-if="!wait">
|
||||||
<template v-if="isFollowing">
|
<template v-if="isFollowing">
|
||||||
<span v-if="full">{{ i18n.ts.unfollow }}</span
|
<span v-if="full">{{ i18n.ts.unfollow }}</span
|
||||||
><i class="ph-minus ph-bold ph-lg"></i>
|
><i :class="icon('ph-minus')"></i>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<span v-if="full">{{ i18n.ts.follow }}</span
|
<span v-if="full">{{ i18n.ts.follow }}</span
|
||||||
><i class="ph-plus ph-bold ph-lg"></i>
|
><i :class="icon('ph-plus')"></i>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<span v-if="full">{{ i18n.ts.processing }}</span
|
<span v-if="full">{{ i18n.ts.processing }}</span
|
||||||
><i class="ph-circle-notch ph-bold ph-lg fa-pulse ph-fw ph-lg"></i>
|
><i :class="icon('ph-circle-notch fa-pulse ph-fw')"></i>
|
||||||
</template>
|
</template>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
@ -26,6 +26,7 @@
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
import icon from "@/scripts/icon";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
|
|
@ -3,11 +3,12 @@
|
||||||
<div class="banner" :style="bannerStyle">
|
<div class="banner" :style="bannerStyle">
|
||||||
<div class="fade"></div>
|
<div class="fade"></div>
|
||||||
<div class="name">
|
<div class="name">
|
||||||
<i class="ph-television ph-bold ph-lg"></i> {{ channel.name }}
|
<i :class="icon('ph-television')"></i>
|
||||||
|
{{ channel.name }}
|
||||||
</div>
|
</div>
|
||||||
<div class="status">
|
<div class="status">
|
||||||
<div>
|
<div>
|
||||||
<i class="ph-users ph-bold ph-lg ph-fw ph-lg"></i>
|
<i :class="icon('ph-users ph-fw')"></i>
|
||||||
<I18n
|
<I18n
|
||||||
:src="i18n.ts._channel.usersCount"
|
:src="i18n.ts._channel.usersCount"
|
||||||
tag="span"
|
tag="span"
|
||||||
|
@ -19,7 +20,7 @@
|
||||||
</I18n>
|
</I18n>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<i class="ph-pencil ph-bold ph-lg ph-fw ph-lg"></i>
|
<i :class="icon('ph-pencil ph-fw')"></i>
|
||||||
<I18n
|
<I18n
|
||||||
:src="i18n.ts._channel.notesCount"
|
:src="i18n.ts._channel.notesCount"
|
||||||
tag="span"
|
tag="span"
|
||||||
|
@ -52,6 +53,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
import icon from "@/scripts/icon";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
channel: Record<string, any>;
|
channel: Record<string, any>;
|
||||||
|
|
|
@ -20,16 +20,16 @@
|
||||||
@click="() => (showBody = !showBody)"
|
@click="() => (showBody = !showBody)"
|
||||||
>
|
>
|
||||||
<template v-if="showBody"
|
<template v-if="showBody"
|
||||||
><i class="ph-caret-up ph-bold ph-lg"></i
|
><i :class="icon('ph-caret-up')"></i
|
||||||
></template>
|
></template>
|
||||||
<template v-else
|
<template v-else
|
||||||
><i class="ph-caret-down ph-bold ph-lg"></i
|
><i :class="icon('ph-caret-down')"></i
|
||||||
></template>
|
></template>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<transition
|
<transition
|
||||||
:name="$store.state.animation ? 'container-toggle' : ''"
|
:name="defaultStore.state.animation ? 'container-toggle' : ''"
|
||||||
@enter="enter"
|
@enter="enter"
|
||||||
@after-enter="afterEnter"
|
@after-enter="afterEnter"
|
||||||
@leave="leave"
|
@leave="leave"
|
||||||
|
@ -62,6 +62,8 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
import { defaultStore } from "@/store";
|
||||||
|
import icon from "@/scripts/icon";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
|
@ -107,6 +109,8 @@ export default defineComponent({
|
||||||
omitted: null,
|
omitted: null,
|
||||||
ignoreOmit: false,
|
ignoreOmit: false,
|
||||||
i18n,
|
i18n,
|
||||||
|
icon,
|
||||||
|
defaultStore,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<transition :name="$store.state.animation ? 'fade' : ''" appear>
|
<transition :name="defaultStore.state.animation ? 'fade' : ''" appear>
|
||||||
<div
|
<div
|
||||||
ref="rootEl"
|
ref="rootEl"
|
||||||
class="nvlagfpb"
|
class="nvlagfpb"
|
||||||
|
@ -17,6 +17,7 @@ import MkMenu from "@/components/MkMenu.vue";
|
||||||
import type { MenuItem } from "@/types/menu";
|
import type { MenuItem } from "@/types/menu";
|
||||||
import contains from "@/scripts/contains";
|
import contains from "@/scripts/contains";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
|
import { defaultStore } from "@/store";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
items: MenuItem[];
|
items: MenuItem[];
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { TransitionGroup, defineComponent, h } from "vue";
|
||||||
import MkAd from "@/components/global/MkAd.vue";
|
import MkAd from "@/components/global/MkAd.vue";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { defaultStore } from "@/store";
|
import { defaultStore } from "@/store";
|
||||||
|
import icon from "@/scripts/icon";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
|
@ -75,14 +76,14 @@ export default defineComponent({
|
||||||
[
|
[
|
||||||
h("span", [
|
h("span", [
|
||||||
h("i", {
|
h("i", {
|
||||||
class: "ph-caret-up ph-bold ph-lg icon",
|
class: `${icon("ph-caret-up")} icon`,
|
||||||
}),
|
}),
|
||||||
getDateText(item.createdAt),
|
getDateText(item.createdAt),
|
||||||
]),
|
]),
|
||||||
h("span", [
|
h("span", [
|
||||||
getDateText(props.items[i + 1].createdAt),
|
getDateText(props.items[i + 1].createdAt),
|
||||||
h("i", {
|
h("i", {
|
||||||
class: "ph-caret-down ph-bold ph-lg icon",
|
class: `${icon("ph-caret-down")} icon`,
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
|
|
|
@ -16,27 +16,27 @@
|
||||||
<i
|
<i
|
||||||
v-if="type === 'success'"
|
v-if="type === 'success'"
|
||||||
:class="$style.iconInner"
|
:class="$style.iconInner"
|
||||||
class="ph-check ph-bold ph-lg"
|
class="ph-check ph-lg"
|
||||||
></i>
|
></i>
|
||||||
<i
|
<i
|
||||||
v-else-if="type === 'error'"
|
v-else-if="type === 'error'"
|
||||||
:class="$style.iconInner"
|
:class="$style.iconInner"
|
||||||
class="ph-circle-wavy-warning ph-bold ph-lg"
|
class="ph-circle-wavy-warning ph-lg"
|
||||||
></i>
|
></i>
|
||||||
<i
|
<i
|
||||||
v-else-if="type === 'warning'"
|
v-else-if="type === 'warning'"
|
||||||
:class="$style.iconInner"
|
:class="$style.iconInner"
|
||||||
class="ph-warning ph-bold ph-lg"
|
class="ph-warning ph-lg"
|
||||||
></i>
|
></i>
|
||||||
<i
|
<i
|
||||||
v-else-if="type === 'info'"
|
v-else-if="type === 'info'"
|
||||||
:class="$style.iconInner"
|
:class="$style.iconInner"
|
||||||
class="ph-info ph-bold ph-lg"
|
class="ph-info ph-lg"
|
||||||
></i>
|
></i>
|
||||||
<i
|
<i
|
||||||
v-else-if="type === 'question'"
|
v-else-if="type === 'question'"
|
||||||
:class="$style.iconInner"
|
:class="$style.iconInner"
|
||||||
class="ph-circle-question ph-bold ph-lg"
|
class="ph-circle-question ph-lg"
|
||||||
></i>
|
></i>
|
||||||
<MkLoading
|
<MkLoading
|
||||||
v-else-if="type === 'waiting'"
|
v-else-if="type === 'waiting'"
|
||||||
|
@ -75,7 +75,7 @@
|
||||||
@keydown="onInputKeydown"
|
@keydown="onInputKeydown"
|
||||||
>
|
>
|
||||||
<template v-if="input.type === 'password'" #prefix
|
<template v-if="input.type === 'password'" #prefix
|
||||||
><i class="ph-password ph-bold ph-lg"></i
|
><i :class="iconClass('ph-password')"></i
|
||||||
></template>
|
></template>
|
||||||
<template #caption>
|
<template #caption>
|
||||||
<span
|
<span
|
||||||
|
@ -109,7 +109,7 @@
|
||||||
class="_buttonIcon"
|
class="_buttonIcon"
|
||||||
@click.stop="openSearchFilters"
|
@click.stop="openSearchFilters"
|
||||||
>
|
>
|
||||||
<i class="ph-funnel ph-bold"></i>
|
<i :class="iconClass('ph-funnel', false)"></i>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
|
@ -213,6 +213,7 @@ import MkTextarea from "@/components/form/textarea.vue";
|
||||||
import MkSelect from "@/components/form/select.vue";
|
import MkSelect from "@/components/form/select.vue";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
import iconClass from "@/scripts/icon";
|
||||||
|
|
||||||
interface Input {
|
interface Input {
|
||||||
type: HTMLInputElement["type"];
|
type: HTMLInputElement["type"];
|
||||||
|
@ -364,7 +365,7 @@ async function openSearchFilters(ev) {
|
||||||
await os.popupMenu(
|
await os.popupMenu(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
icon: "ph-user ph-bold ph-lg",
|
icon: `${icon("ph-user")}`,
|
||||||
text: i18n.ts._filters.fromUser,
|
text: i18n.ts._filters.fromUser,
|
||||||
action: () => {
|
action: () => {
|
||||||
os.selectUser().then((user) => {
|
os.selectUser().then((user) => {
|
||||||
|
@ -375,32 +376,32 @@ async function openSearchFilters(ev) {
|
||||||
{
|
{
|
||||||
type: "parent",
|
type: "parent",
|
||||||
text: i18n.ts._filters.withFile,
|
text: i18n.ts._filters.withFile,
|
||||||
icon: "ph-paperclip ph-bold ph-lg",
|
icon: `${icon("ph-paperclip")}`,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
text: i18n.ts.image,
|
text: i18n.ts.image,
|
||||||
icon: "ph-image-square ph-bold ph-lg",
|
icon: `${icon("ph-image-square")}`,
|
||||||
action: () => {
|
action: () => {
|
||||||
inputValue.value += " has:image";
|
inputValue.value += " has:image";
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: i18n.ts.video,
|
text: i18n.ts.video,
|
||||||
icon: "ph-video-camera ph-bold ph-lg",
|
icon: `${icon("ph-video-camera")}`,
|
||||||
action: () => {
|
action: () => {
|
||||||
inputValue.value += " has:video";
|
inputValue.value += " has:video";
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: i18n.ts.audio,
|
text: i18n.ts.audio,
|
||||||
icon: "ph-music-note ph-bold ph-lg",
|
icon: `${icon("ph-music-note")}`,
|
||||||
action: () => {
|
action: () => {
|
||||||
inputValue.value += " has:audio";
|
inputValue.value += " has:audio";
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: i18n.ts.file,
|
text: i18n.ts.file,
|
||||||
icon: "ph-file ph-bold ph-lg",
|
icon: `${icon("ph-file")}`,
|
||||||
action: () => {
|
action: () => {
|
||||||
inputValue.value += " has:file";
|
inputValue.value += " has:file";
|
||||||
},
|
},
|
||||||
|
@ -408,14 +409,14 @@ async function openSearchFilters(ev) {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "ph-link ph-bold ph-lg",
|
icon: `${icon("ph-link")}`,
|
||||||
text: i18n.ts._filters.fromDomain,
|
text: i18n.ts._filters.fromDomain,
|
||||||
action: () => {
|
action: () => {
|
||||||
inputValue.value += " domain:";
|
inputValue.value += " domain:";
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "ph-calendar-blank ph-bold ph-lg",
|
icon: `${icon("ph-calendar-blank")}`,
|
||||||
text: i18n.ts._filters.notesBefore,
|
text: i18n.ts._filters.notesBefore,
|
||||||
action: () => {
|
action: () => {
|
||||||
os.inputDate({
|
os.inputDate({
|
||||||
|
@ -428,7 +429,7 @@ async function openSearchFilters(ev) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "ph-calendar-blank ph-bold ph-lg",
|
icon: `${icon("ph-calendar-blank")}`,
|
||||||
text: i18n.ts._filters.notesAfter,
|
text: i18n.ts._filters.notesAfter,
|
||||||
action: () => {
|
action: () => {
|
||||||
os.inputDate({
|
os.inputDate({
|
||||||
|
@ -441,14 +442,14 @@ async function openSearchFilters(ev) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "ph-eye ph-bold ph-lg",
|
icon: `${icon("ph-eye")}`,
|
||||||
text: i18n.ts._filters.followingOnly,
|
text: i18n.ts._filters.followingOnly,
|
||||||
action: () => {
|
action: () => {
|
||||||
inputValue.value += " filter:following ";
|
inputValue.value += " filter:following ";
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "ph-users-three ph-bold ph-lg",
|
icon: `${icon("ph-users-three")}`,
|
||||||
text: i18n.ts._filters.followersOnly,
|
text: i18n.ts._filters.followersOnly,
|
||||||
action: () => {
|
action: () => {
|
||||||
inputValue.value += " filter:followers ";
|
inputValue.value += " filter:followers ";
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<transition name="slide-fade">
|
<transition name="slide-fade">
|
||||||
<div v-if="show" class="_panel _shadow _acrylic" :class="$style.root">
|
<div v-if="show" class="_panel _shadow _acrylic" :class="$style.root">
|
||||||
<div :class="$style.icon">
|
<div :class="$style.icon">
|
||||||
<i class="ph-hand-heart ph-bold ph-5x" />
|
<i :class="icon('ph-hand-heart ph-5x', false)" />
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.main">
|
<div :class="$style.main">
|
||||||
<div :class="$style.title">
|
<div :class="$style.title">
|
||||||
|
@ -52,7 +52,7 @@
|
||||||
:aria-label="i18n.t('close')"
|
:aria-label="i18n.t('close')"
|
||||||
@click="close"
|
@click="close"
|
||||||
>
|
>
|
||||||
<i class="ph-x ph-bold ph-lg"></i>
|
<i :class="icon('ph-x')"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
|
@ -65,6 +65,7 @@ import { host } from "@/config";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { instance } from "@/instance";
|
import { instance } from "@/instance";
|
||||||
|
import icon from "@/scripts/icon";
|
||||||
|
|
||||||
const show = ref(false);
|
const show = ref(false);
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,7 @@ import bytes from "@/filters/bytes";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { $i } from "@/account";
|
import { $i } from "@/account";
|
||||||
|
import icon from "@/scripts/icon";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
@ -75,7 +76,7 @@ function getMenu() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
text: i18n.ts.rename,
|
text: i18n.ts.rename,
|
||||||
icon: "ph-cursor-text ph-bold ph-lg",
|
icon: `${icon("ph-cursor-text")}`,
|
||||||
action: rename,
|
action: rename,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -83,19 +84,19 @@ function getMenu() {
|
||||||
? i18n.ts.unmarkAsSensitive
|
? i18n.ts.unmarkAsSensitive
|
||||||
: i18n.ts.markAsSensitive,
|
: i18n.ts.markAsSensitive,
|
||||||
icon: props.file.isSensitive
|
icon: props.file.isSensitive
|
||||||
? "ph-eye ph-bold ph-lg"
|
? "ph-eye ph-lg"
|
||||||
: "ph-eye-slash ph-bold ph-lg",
|
: "ph-eye-slash ph-lg",
|
||||||
action: toggleSensitive,
|
action: toggleSensitive,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: i18n.ts.describeFile,
|
text: i18n.ts.describeFile,
|
||||||
icon: "ph-subtitles ph-bold ph-lg",
|
icon: `${icon("ph-subtitles")}`,
|
||||||
action: describe,
|
action: describe,
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
{
|
{
|
||||||
text: i18n.ts.copyUrl,
|
text: i18n.ts.copyUrl,
|
||||||
icon: "ph-link-simple ph-bold ph-lg",
|
icon: `${icon("ph-link-simple")}`,
|
||||||
action: copyUrl,
|
action: copyUrl,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -103,13 +104,13 @@ function getMenu() {
|
||||||
href: props.file.url,
|
href: props.file.url,
|
||||||
target: "_blank",
|
target: "_blank",
|
||||||
text: i18n.ts.download,
|
text: i18n.ts.download,
|
||||||
icon: "ph-download-simple ph-bold ph-lg",
|
icon: `${icon("ph-download-simple")}`,
|
||||||
download: props.file.name,
|
download: props.file.name,
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
{
|
{
|
||||||
text: i18n.ts.delete,
|
text: i18n.ts.delete,
|
||||||
icon: "ph-trash ph-bold ph-lg",
|
icon: `${icon("ph-trash")}`,
|
||||||
danger: true,
|
danger: true,
|
||||||
action: deleteFile,
|
action: deleteFile,
|
||||||
},
|
},
|
||||||
|
|
|
@ -17,10 +17,10 @@
|
||||||
>
|
>
|
||||||
<p class="name">
|
<p class="name">
|
||||||
<template v-if="hover"
|
<template v-if="hover"
|
||||||
><i class="ph-folder-notch-open ph-bold ph-lg ph-fw ph-lg"></i
|
><i :class="icon('ph-folder-notch-open ph-fw')"></i
|
||||||
></template>
|
></template>
|
||||||
<template v-if="!hover"
|
<template v-if="!hover"
|
||||||
><i class="ph-folder-notch ph-bold ph-lg ph-fw ph-lg"></i
|
><i :class="icon('ph-folder-notch ph-fw')"></i
|
||||||
></template>
|
></template>
|
||||||
{{ folder.name }}
|
{{ folder.name }}
|
||||||
</p>
|
</p>
|
||||||
|
@ -42,6 +42,7 @@ import type * as firefish 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";
|
||||||
|
import icon from "@/scripts/icon";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
@ -248,7 +249,7 @@ function onContextmenu(ev: MouseEvent) {
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
text: i18n.ts.openInWindow,
|
text: i18n.ts.openInWindow,
|
||||||
icon: "ph-copy ph-bold ph-lg",
|
icon: `${icon("ph-copy")}`,
|
||||||
action: () => {
|
action: () => {
|
||||||
os.popup(
|
os.popup(
|
||||||
defineAsyncComponent(
|
defineAsyncComponent(
|
||||||
|
@ -265,13 +266,13 @@ function onContextmenu(ev: MouseEvent) {
|
||||||
null,
|
null,
|
||||||
{
|
{
|
||||||
text: i18n.ts.rename,
|
text: i18n.ts.rename,
|
||||||
icon: "ph-cursor-text ph-bold ph-lg",
|
icon: `${icon("ph-cursor-text")}`,
|
||||||
action: rename,
|
action: rename,
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
{
|
{
|
||||||
text: i18n.ts.delete,
|
text: i18n.ts.delete,
|
||||||
icon: "ph-trash ph-bold ph-lg",
|
icon: `${icon("ph-trash")}`,
|
||||||
danger: true,
|
danger: true,
|
||||||
action: deleteFolder,
|
action: deleteFolder,
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
@dragleave="onDragleave"
|
@dragleave="onDragleave"
|
||||||
@drop.stop="onDrop"
|
@drop.stop="onDrop"
|
||||||
>
|
>
|
||||||
<i v-if="folder == null" class="ph-cloud ph-bold ph-lg"></i>
|
<i v-if="folder == null" :class="icon('ph-cloud')"></i>
|
||||||
<span>{{ folder == null ? i18n.ts.drive : folder.name }}</span>
|
<span>{{ folder == null ? i18n.ts.drive : folder.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -18,6 +18,7 @@ import { ref } from "vue";
|
||||||
import type * as firefish from "firefish-js";
|
import type * as firefish from "firefish-js";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
import icon from "@/scripts/icon";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
folder?: firefish.entities.DriveFolder;
|
folder?: firefish.entities.DriveFolder;
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
/>
|
/>
|
||||||
<template v-for="f in hierarchyFolders">
|
<template v-for="f in hierarchyFolders">
|
||||||
<span class="separator"
|
<span class="separator"
|
||||||
><i class="ph-caret-right ph-bold ph-lg"></i
|
><i :class="icon('ph-caret-right')"></i
|
||||||
></span>
|
></span>
|
||||||
<XNavFolder
|
<XNavFolder
|
||||||
:folder="f"
|
:folder="f"
|
||||||
|
@ -24,14 +24,14 @@
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<span v-if="folder != null" class="separator"
|
<span v-if="folder != null" class="separator"
|
||||||
><i class="ph-caret-right ph-bold ph-lg"></i
|
><i :class="icon('ph-caret-right')"></i
|
||||||
></span>
|
></span>
|
||||||
<span v-if="folder != null" class="folder current">{{
|
<span v-if="folder != null" class="folder current">{{
|
||||||
folder.name
|
folder.name
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
<button class="menu _button" @click="showMenu">
|
<button class="menu _button" @click="showMenu">
|
||||||
<i class="ph-dots-three-outline ph-bold ph-lg"></i>
|
<i :class="icon('ph-dots-three-outline')"></i>
|
||||||
</button>
|
</button>
|
||||||
</nav>
|
</nav>
|
||||||
<div
|
<div
|
||||||
|
@ -149,6 +149,7 @@ import { stream } from "@/stream";
|
||||||
import { defaultStore } from "@/store";
|
import { defaultStore } from "@/store";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { uploadFile, uploads } from "@/scripts/upload";
|
import { uploadFile, uploads } from "@/scripts/upload";
|
||||||
|
import icon from "@/scripts/icon";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
@ -677,14 +678,14 @@ function getMenu() {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: i18n.ts.upload,
|
text: i18n.ts.upload,
|
||||||
icon: "ph-upload-simple ph-bold ph-lg",
|
icon: `${icon("ph-upload-simple")}`,
|
||||||
action: () => {
|
action: () => {
|
||||||
selectLocalFile();
|
selectLocalFile();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: i18n.ts.fromUrl,
|
text: i18n.ts.fromUrl,
|
||||||
icon: "ph-link-simple ph-bold ph-lg",
|
icon: `${icon("ph-link-simple")}`,
|
||||||
action: () => {
|
action: () => {
|
||||||
urlUpload();
|
urlUpload();
|
||||||
},
|
},
|
||||||
|
@ -697,7 +698,7 @@ function getMenu() {
|
||||||
folder.value
|
folder.value
|
||||||
? {
|
? {
|
||||||
text: i18n.ts.renameFolder,
|
text: i18n.ts.renameFolder,
|
||||||
icon: "ph-cursor-text ph-bold ph-lg",
|
icon: `${icon("ph-cursor-text")}`,
|
||||||
action: () => {
|
action: () => {
|
||||||
renameFolder(folder.value);
|
renameFolder(folder.value);
|
||||||
},
|
},
|
||||||
|
@ -706,7 +707,7 @@ function getMenu() {
|
||||||
folder.value
|
folder.value
|
||||||
? {
|
? {
|
||||||
text: i18n.ts.deleteFolder,
|
text: i18n.ts.deleteFolder,
|
||||||
icon: "ph-trash ph-bold ph-lg",
|
icon: `${icon("ph-trash")}`,
|
||||||
action: () => {
|
action: () => {
|
||||||
deleteFolder(
|
deleteFolder(
|
||||||
folder.value as firefish.entities.DriveFolder,
|
folder.value as firefish.entities.DriveFolder,
|
||||||
|
@ -716,7 +717,7 @@ function getMenu() {
|
||||||
: undefined,
|
: undefined,
|
||||||
{
|
{
|
||||||
text: i18n.ts.createFolder,
|
text: i18n.ts.createFolder,
|
||||||
icon: "ph-folder-notch-plus ph-bold ph-lg",
|
icon: `${icon("ph-folder-notch-plus")}`,
|
||||||
action: () => {
|
action: () => {
|
||||||
createFolder();
|
createFolder();
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,33 +8,21 @@
|
||||||
:title="file.name"
|
:title="file.name"
|
||||||
:cover="fit !== 'contain'"
|
:cover="fit !== 'contain'"
|
||||||
/>
|
/>
|
||||||
<i
|
<i v-else-if="is === 'image'" :class="icon('ph-file-image icon')"></i>
|
||||||
v-else-if="is === 'image'"
|
<i v-else-if="is === 'video'" :class="icon('ph-file-video icon')"></i>
|
||||||
class="ph-file-image ph-bold ph-lg icon"
|
|
||||||
></i>
|
|
||||||
<i
|
|
||||||
v-else-if="is === 'video'"
|
|
||||||
class="ph-file-video ph-bold ph-lg icon"
|
|
||||||
></i>
|
|
||||||
<i
|
<i
|
||||||
v-else-if="is === 'audio' || is === 'midi'"
|
v-else-if="is === 'audio' || is === 'midi'"
|
||||||
class="ph-file-audio ph-bold ph-lg icon"
|
:class="icon('ph-file-audio icon')"
|
||||||
></i>
|
></i>
|
||||||
<i v-else-if="is === 'csv'" class="ph-file-csv ph-bold ph-lg icon"></i>
|
<i v-else-if="is === 'csv'" :class="icon('ph-file-csv icon')"></i>
|
||||||
<i v-else-if="is === 'pdf'" class="ph-file-pdf ph-bold ph-lg icon"></i>
|
<i v-else-if="is === 'pdf'" :class="icon('ph-file-pdf icon')"></i>
|
||||||
<i
|
<i v-else-if="is === 'textfile'" :class="icon('ph-file-text icon')"></i>
|
||||||
v-else-if="is === 'textfile'"
|
<i v-else-if="is === 'archive'" :class="icon('ph-file-zip icon')"></i>
|
||||||
class="ph-file-text ph-bold ph-lg icon"
|
<i v-else :class="icon('ph-file icon')"></i>
|
||||||
></i>
|
|
||||||
<i
|
|
||||||
v-else-if="is === 'archive'"
|
|
||||||
class="ph-file-zip ph-bold ph-lg icon"
|
|
||||||
></i>
|
|
||||||
<i v-else class="ph-file ph-bold ph-lg icon"></i>
|
|
||||||
|
|
||||||
<i
|
<i
|
||||||
v-if="isThumbnailAvailable && is === 'video'"
|
v-if="isThumbnailAvailable && is === 'video'"
|
||||||
class="ph-file-video ph-bold ph-lg icon-sub"
|
:class="icon('ph-file-video icon-sub')"
|
||||||
></i>
|
></i>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
@ -43,6 +31,7 @@
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import type * as firefish from "firefish-js";
|
import type * as firefish from "firefish-js";
|
||||||
import ImgWithBlurhash from "@/components/MkImgWithBlurhash.vue";
|
import ImgWithBlurhash from "@/components/MkImgWithBlurhash.vue";
|
||||||
|
import icon from "@/scripts/icon";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
file: firefish.entities.DriveFile;
|
file: firefish.entities.DriveFile;
|
||||||
|
|
|
@ -4,9 +4,7 @@
|
||||||
<i
|
<i
|
||||||
class="toggle ph-fw ph-lg"
|
class="toggle ph-fw ph-lg"
|
||||||
:class="
|
:class="
|
||||||
shown
|
icon(shown ? 'ph-caret-down ph-lg' : 'ph-caret-up ph-lg')
|
||||||
? 'ph-caret-down ph-bold ph-lg'
|
|
||||||
: 'ph-caret-up ph-bold ph-lg'
|
|
||||||
"
|
"
|
||||||
></i>
|
></i>
|
||||||
<slot></slot> ({{ emojis.length }})
|
<slot></slot> ({{ emojis.length }})
|
||||||
|
@ -21,7 +19,7 @@
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
class="ph-circle ph-fill ph-fw ph-lg"
|
class="ph-circle ph-fill ph-fw"
|
||||||
:style="{ color: skinTone + ' !important' }"
|
:style="{ color: skinTone + ' !important' }"
|
||||||
:aria-label="
|
:aria-label="
|
||||||
props.skinToneLabels
|
props.skinToneLabels
|
||||||
|
@ -50,6 +48,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, ref, watch } from "vue";
|
import { onMounted, ref, watch } from "vue";
|
||||||
import { addSkinTone } from "@/scripts/emojilist";
|
import { addSkinTone } from "@/scripts/emojilist";
|
||||||
|
import icon from "@/scripts/icon";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
emojis: string[];
|
emojis: string[];
|
||||||
|
|
|
@ -75,7 +75,7 @@
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<header class="_acrylic">
|
<header class="_acrylic">
|
||||||
<i class="ph-alarm ph-bold ph-fw ph-lg"></i>
|
<i :class="icon('ph-alarm ph-fw')"></i>
|
||||||
{{ i18n.ts.recentUsed }}
|
{{ i18n.ts.recentUsed }}
|
||||||
</header>
|
</header>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
|
@ -135,28 +135,28 @@
|
||||||
:class="{ active: tab === 'index' }"
|
:class="{ active: tab === 'index' }"
|
||||||
@click="tab = 'index'"
|
@click="tab = 'index'"
|
||||||
>
|
>
|
||||||
<i class="ph-asterisk ph-bold ph-lg ph-fw ph-lg"></i>
|
<i :class="icon('ph-asterisk ph-fw')"></i>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="_button tab"
|
class="_button tab"
|
||||||
:class="{ active: tab === 'custom' }"
|
:class="{ active: tab === 'custom' }"
|
||||||
@click="tab = 'custom'"
|
@click="tab = 'custom'"
|
||||||
>
|
>
|
||||||
<i class="ph-smiley ph-bold ph-lg ph-fw ph-lg"></i>
|
<i :class="icon('ph-smiley ph-fw')"></i>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="_button tab"
|
class="_button tab"
|
||||||
:class="{ active: tab === 'unicode' }"
|
:class="{ active: tab === 'unicode' }"
|
||||||
@click="tab = 'unicode'"
|
@click="tab = 'unicode'"
|
||||||
>
|
>
|
||||||
<i class="ph-leaf ph-bold ph-lg ph-fw ph-lg"></i>
|
<i :class="icon('ph-leaf ph-fw')"></i>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="_button tab"
|
class="_button tab"
|
||||||
:class="{ active: tab === 'tags' }"
|
:class="{ active: tab === 'tags' }"
|
||||||
@click="tab = 'tags'"
|
@click="tab = 'tags'"
|
||||||
>
|
>
|
||||||
<i class="ph-hash ph-bold ph-lg ph-fw ph-lg"></i>
|
<i :class="icon('ph-hash ph-fw')"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -182,6 +182,7 @@ 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 icon from "@/scripts/icon";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
<template>
|
<template>
|
||||||
<span class="mk-file-type-icon">
|
<span class="mk-file-type-icon">
|
||||||
<template v-if="kind == 'image'"
|
<template v-if="kind == 'image'"
|
||||||
><i class="ph-file-image ph-bold ph-lg"></i
|
><i :class="icon('ph-file-image')"></i
|
||||||
></template>
|
></template>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
|
import icon from "@/scripts/icon";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
type: string;
|
type: string;
|
||||||
|
|
|
@ -10,15 +10,15 @@
|
||||||
:aria-controls="bodyId"
|
:aria-controls="bodyId"
|
||||||
>
|
>
|
||||||
<template v-if="showBody"
|
<template v-if="showBody"
|
||||||
><i class="ph-caret-up ph-bold ph-lg"></i
|
><i :class="icon('ph-caret-up')"></i
|
||||||
></template>
|
></template>
|
||||||
<template v-else
|
<template v-else
|
||||||
><i class="ph-caret-down ph-bold ph-lg"></i
|
><i :class="icon('ph-caret-down')"></i
|
||||||
></template>
|
></template>
|
||||||
</button>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
<transition
|
<transition
|
||||||
:name="$store.state.animation ? 'folder-toggle' : ''"
|
:name="defaultStore.state.animation ? 'folder-toggle' : ''"
|
||||||
@enter="enter"
|
@enter="enter"
|
||||||
@after-enter="afterEnter"
|
@after-enter="afterEnter"
|
||||||
@leave="leave"
|
@leave="leave"
|
||||||
|
@ -34,6 +34,8 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
import { getUniqueId } from "@/os";
|
import { getUniqueId } from "@/os";
|
||||||
|
import { defaultStore } from "@/store";
|
||||||
|
import icon from "@/scripts/icon";
|
||||||
|
|
||||||
const localStoragePrefix = "ui:folder:";
|
const localStoragePrefix = "ui:folder:";
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
class="menu _button"
|
class="menu _button"
|
||||||
@click.stop="menu"
|
@click.stop="menu"
|
||||||
>
|
>
|
||||||
<i class="ph-dots-three-outline ph-bold ph-lg"></i>
|
<i :class="icon('ph-dots-three-outline')"></i>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="$i != null && $i.id != user.id"
|
v-if="$i != null && $i.id != user.id"
|
||||||
|
@ -25,37 +25,37 @@
|
||||||
<template v-if="!wait">
|
<template v-if="!wait">
|
||||||
<template v-if="isBlocking">
|
<template v-if="isBlocking">
|
||||||
<span>{{ (state = i18n.ts.blocked) }}</span
|
<span>{{ (state = i18n.ts.blocked) }}</span
|
||||||
><i class="ph-prohibit ph-bold ph-lg"></i>
|
><i :class="icon('ph-prohibit')"></i>
|
||||||
</template>
|
</template>
|
||||||
<template
|
<template
|
||||||
v-else-if="hasPendingFollowRequestFromYou && user.isLocked"
|
v-else-if="hasPendingFollowRequestFromYou && user.isLocked"
|
||||||
>
|
>
|
||||||
<span>{{ (state = i18n.ts.followRequestPending) }}</span
|
<span>{{ (state = i18n.ts.followRequestPending) }}</span
|
||||||
><i class="ph-hourglass-medium ph-bold ph-lg"></i>
|
><i :class="icon('ph-hourglass-medium')"></i>
|
||||||
</template>
|
</template>
|
||||||
<template
|
<template
|
||||||
v-else-if="hasPendingFollowRequestFromYou && !user.isLocked"
|
v-else-if="hasPendingFollowRequestFromYou && !user.isLocked"
|
||||||
>
|
>
|
||||||
<!-- つまりリモートフォローの場合。 -->
|
<!-- つまりリモートフォローの場合。 -->
|
||||||
<span>{{ (state = i18n.ts.processing) }}</span
|
<span>{{ (state = i18n.ts.processing) }}</span
|
||||||
><i class="ph-circle-notch ph-bold ph-lg fa-pulse"></i>
|
><i :class="icon('ph-circle-notch fa-pulse')"></i>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="isFollowing">
|
<template v-else-if="isFollowing">
|
||||||
<span>{{ (state = i18n.ts.unfollow) }}</span
|
<span>{{ (state = i18n.ts.unfollow) }}</span
|
||||||
><i class="ph-minus ph-bold ph-lg"></i>
|
><i :class="icon('ph-minus')"></i>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="!isFollowing && user.isLocked">
|
<template v-else-if="!isFollowing && user.isLocked">
|
||||||
<span>{{ (state = i18n.ts.followRequest) }}</span
|
<span>{{ (state = i18n.ts.followRequest) }}</span
|
||||||
><i class="ph-lock-open ph-bold ph-lg"></i>
|
><i :class="icon('ph-lock-open')"></i>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="!isFollowing && !user.isLocked">
|
<template v-else-if="!isFollowing && !user.isLocked">
|
||||||
<span>{{ (state = i18n.ts.follow) }}</span
|
<span>{{ (state = i18n.ts.follow) }}</span
|
||||||
><i class="ph-plus ph-bold ph-lg"></i>
|
><i :class="icon('ph-plus')"></i>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<span>{{ (state = i18n.ts.processing) }}</span
|
<span>{{ (state = i18n.ts.processing) }}</span
|
||||||
><i class="ph-circle-notch ph-bold ph-lg fa-pulse ph-fw ph-lg"></i>
|
><i :class="icon('ph-circle-notch fa-pulse ph-fw')"></i>
|
||||||
</template>
|
</template>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
@ -70,6 +70,7 @@ import { $i } from "@/account";
|
||||||
import { getUserMenu } from "@/scripts/get-user-menu";
|
import { getUserMenu } from "@/scripts/get-user-menu";
|
||||||
import { useRouter } from "@/router";
|
import { useRouter } from "@/router";
|
||||||
import { vibrate } from "@/scripts/vibrate";
|
import { vibrate } from "@/scripts/vibrate";
|
||||||
|
import icon from "@/scripts/icon";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<div class="mk-google" @click.stop>
|
<div class="mk-google" @click.stop>
|
||||||
<input v-model="query" type="search" :placeholder="q" />
|
<input v-model="query" type="search" :placeholder="q" />
|
||||||
<button @click="search">
|
<button @click="search">
|
||||||
<i class="ph-magnifying-glass ph-bold ph-lg"></i>
|
<i :class="icon('ph-magnifying-glass')"></i>
|
||||||
{{ i18n.ts.searchByGoogle }}
|
{{ i18n.ts.searchByGoogle }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -12,6 +12,7 @@
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { useRouter } from "@/router";
|
import { useRouter } from "@/router";
|
||||||
|
import icon from "@/scripts/icon";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="visible" class="info" :class="{ warn, card }">
|
<div v-if="visible" class="info" :class="{ warn, card }">
|
||||||
<i v-if="warn" class="ph-warning ph-bold ph-lg"></i>
|
<i v-if="warn" :class="iconClass('ph-warning')"></i>
|
||||||
<i
|
<i v-else :class="iconClass(icon ? `ph-${icon}` : 'ph-info')"></i>
|
||||||
v-else
|
|
||||||
class="ph-bold ph-lg"
|
|
||||||
:class="icon ? `ph-${icon}` : 'ph-info'"
|
|
||||||
></i>
|
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
<button
|
<button
|
||||||
v-if="closeable"
|
v-if="closeable"
|
||||||
|
@ -14,7 +10,7 @@
|
||||||
:aria-label="i18n.t('close')"
|
:aria-label="i18n.t('close')"
|
||||||
@click.stop="close"
|
@click.stop="close"
|
||||||
>
|
>
|
||||||
<i class="ph-x ph-bold ph-lg"></i>
|
<i :class="iconClass('ph-x')"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -22,6 +18,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
import iconClass from "@/scripts/icon";
|
||||||
|
|
||||||
const visible = ref(true);
|
const visible = ref(true);
|
||||||
|
|
||||||
|
|
|
@ -12,17 +12,17 @@
|
||||||
style="margin-left: 0.5em"
|
style="margin-left: 0.5em"
|
||||||
@click="copy_"
|
@click="copy_"
|
||||||
>
|
>
|
||||||
<i class="ph-clipboard-text ph-bold"></i>
|
<i :class="icon('ph-clipboard-text', false)"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {} from "vue";
|
|
||||||
import copyToClipboard from "@/scripts/copy-to-clipboard";
|
import copyToClipboard from "@/scripts/copy-to-clipboard";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
import icon from "@/scripts/icon";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
v-if="item.indicate"
|
v-if="item.indicate"
|
||||||
class="indicator"
|
class="indicator"
|
||||||
:class="{
|
:class="{
|
||||||
animateIndicator: $store.state.animation,
|
animateIndicator: defaultStore.state.animation,
|
||||||
}"
|
}"
|
||||||
><i class="ph-circle ph-fill"></i
|
><i class="ph-circle ph-fill"></i
|
||||||
></span>
|
></span>
|
||||||
|
@ -50,7 +50,7 @@
|
||||||
v-if="item.indicate"
|
v-if="item.indicate"
|
||||||
class="indicator"
|
class="indicator"
|
||||||
:class="{
|
:class="{
|
||||||
animateIndicator: $store.state.animation,
|
animateIndicator: defaultStore.state.animation,
|
||||||
}"
|
}"
|
||||||
><i class="ph-circle ph-fill"></i
|
><i class="ph-circle ph-fill"></i
|
||||||
></span>
|
></span>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
:is="self ? 'MkA' : 'a'"
|
:is="self ? 'MkA' : 'a'"
|
||||||
ref="el"
|
ref="el"
|
||||||
class="xlcxczvw _link"
|
class="xlcxczvw _link"
|
||||||
:[attr]="self ? url.substr(local.length) : url"
|
:[attr]="self ? url.substring(local.length) : url"
|
||||||
:rel="rel"
|
:rel="rel"
|
||||||
:target="target"
|
:target="target"
|
||||||
:title="url"
|
:title="url"
|
||||||
|
@ -12,7 +12,7 @@
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
<i
|
<i
|
||||||
v-if="target === '_blank'"
|
v-if="target === '_blank'"
|
||||||
class="ph-arrow-square-out ph-bold ph-lg icon"
|
:class="icon('ph-arrow-square-out icon')"
|
||||||
></i>
|
></i>
|
||||||
</component>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
@ -22,6 +22,7 @@ import { defineAsyncComponent, ref } from "vue";
|
||||||
import { url as local } from "@/config";
|
import { url as local } from "@/config";
|
||||||
import { useTooltip } from "@/scripts/use-tooltip";
|
import { useTooltip } from "@/scripts/use-tooltip";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
|
import icon from "@/scripts/icon";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
<div class="text">
|
<div class="text">
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<b style="display: block"
|
<b style="display: block"
|
||||||
><i class="ph-warning ph-bold ph-lg"></i>
|
><i :class="icon('ph-warning')"></i>
|
||||||
{{ i18n.ts.sensitive }}</b
|
{{ i18n.ts.sensitive }}</b
|
||||||
>
|
>
|
||||||
<span style="display: block">{{
|
<span style="display: block">{{
|
||||||
|
@ -74,7 +74,7 @@
|
||||||
class="_button"
|
class="_button"
|
||||||
@click.stop="captionPopup"
|
@click.stop="captionPopup"
|
||||||
>
|
>
|
||||||
<i class="ph-subtitles ph-bold ph-lg"></i>
|
<i :class="icon('ph-subtitles')"></i>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="!hide"
|
v-if="!hide"
|
||||||
|
@ -82,7 +82,7 @@
|
||||||
class="_button"
|
class="_button"
|
||||||
@click.stop="hide = true"
|
@click.stop="hide = true"
|
||||||
>
|
>
|
||||||
<i class="ph-eye-slash ph-bold ph-lg"></i>
|
<i :class="icon('ph-eye-slash')"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -98,6 +98,7 @@ import ImgWithBlurhash from "@/components/MkImgWithBlurhash.vue";
|
||||||
import { defaultStore } from "@/store";
|
import { defaultStore } from "@/store";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
|
import icon from "@/scripts/icon";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
media: firefish.entities.DriveFile;
|
media: firefish.entities.DriveFile;
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
class="sensitive"
|
class="sensitive"
|
||||||
@click="hide = false"
|
@click="hide = false"
|
||||||
>
|
>
|
||||||
<span class="icon"><i class="ph-warning ph-bold ph-lg"></i></span>
|
<span class="icon"><i :class="icon('ph-warning')"></i></span>
|
||||||
<b>{{ i18n.ts.sensitive }}</b>
|
<b>{{ i18n.ts.sensitive }}</b>
|
||||||
<span>{{ i18n.ts.clickToShow }}</span>
|
<span>{{ i18n.ts.clickToShow }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -48,7 +48,7 @@
|
||||||
:download="media.name"
|
:download="media.name"
|
||||||
>
|
>
|
||||||
<span class="icon"
|
<span class="icon"
|
||||||
><i class="ph-download-simple ph-bold ph-lg"></i
|
><i :class="icon('ph-download-simple')"></i
|
||||||
></span>
|
></span>
|
||||||
<b>{{ media.name }}</b>
|
<b>{{ media.name }}</b>
|
||||||
</a>
|
</a>
|
||||||
|
@ -62,6 +62,7 @@ import type * as firefish from "firefish-js";
|
||||||
import { ColdDeviceStorage } from "@/store";
|
import { ColdDeviceStorage } from "@/store";
|
||||||
import "vue-plyr/dist/vue-plyr.css";
|
import "vue-plyr/dist/vue-plyr.css";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
import icon from "@/scripts/icon";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
<span class="main">
|
<span class="main">
|
||||||
<span class="username">@{{ username }}</span>
|
<span class="username">@{{ username }}</span>
|
||||||
<span
|
<span
|
||||||
v-if="host != localHost || $store.state.showFullAcct"
|
v-if="host != localHost || defaultStore.state.showFullAcct"
|
||||||
class="host"
|
class="host"
|
||||||
>@{{ toUnicode(host) }}</span
|
>@{{ toUnicode(host) }}</span
|
||||||
>
|
>
|
||||||
|
@ -38,6 +38,7 @@ import { toUnicode } from "punycode";
|
||||||
import {} from "vue";
|
import {} from "vue";
|
||||||
import { host as localHost } from "@/config";
|
import { host as localHost } from "@/config";
|
||||||
import { $i } from "@/account";
|
import { $i } from "@/account";
|
||||||
|
import { defaultStore } from "@/store";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
username: string;
|
username: string;
|
||||||
|
|
|
@ -57,7 +57,7 @@
|
||||||
v-if="item.indicate"
|
v-if="item.indicate"
|
||||||
class="indicator"
|
class="indicator"
|
||||||
:class="{
|
:class="{
|
||||||
animateIndicator: $store.state.animation,
|
animateIndicator: defaultStore.state.animation,
|
||||||
}"
|
}"
|
||||||
><i class="ph-circle ph-fill"></i
|
><i class="ph-circle ph-fill"></i
|
||||||
></span>
|
></span>
|
||||||
|
@ -74,8 +74,7 @@
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
v-if="item.icon"
|
v-if="item.icon"
|
||||||
class="ph-fw ph-lg"
|
:class="icon(`${item.icon} ph-fw`)"
|
||||||
:class="item.icon"
|
|
||||||
></i>
|
></i>
|
||||||
<span :style="item.textStyle || ''">{{
|
<span :style="item.textStyle || ''">{{
|
||||||
item.text
|
item.text
|
||||||
|
@ -84,7 +83,7 @@
|
||||||
v-if="item.indicate"
|
v-if="item.indicate"
|
||||||
class="indicator"
|
class="indicator"
|
||||||
:class="{
|
:class="{
|
||||||
animateIndicator: $store.state.animation,
|
animateIndicator: defaultStore.state.animation,
|
||||||
}"
|
}"
|
||||||
><i class="ph-circle ph-fill"></i
|
><i class="ph-circle ph-fill"></i
|
||||||
></span>
|
></span>
|
||||||
|
@ -107,7 +106,7 @@
|
||||||
v-if="item.indicate"
|
v-if="item.indicate"
|
||||||
class="indicator"
|
class="indicator"
|
||||||
:class="{
|
:class="{
|
||||||
animateIndicator: $store.state.animation,
|
animateIndicator: defaultStore.state.animation,
|
||||||
}"
|
}"
|
||||||
><i class="ph-circle ph-fill"></i
|
><i class="ph-circle ph-fill"></i
|
||||||
></span>
|
></span>
|
||||||
|
@ -135,16 +134,13 @@
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
v-if="item.icon"
|
v-if="item.icon"
|
||||||
class="ph-fw ph-lg"
|
:class="icon(`${item.icon} ph-fw`)"
|
||||||
:class="item.icon"
|
|
||||||
></i>
|
></i>
|
||||||
<span :style="item.textStyle || ''">{{
|
<span :style="item.textStyle || ''">{{
|
||||||
item.text
|
item.text
|
||||||
}}</span>
|
}}</span>
|
||||||
<span class="caret"
|
<span class="caret"
|
||||||
><i
|
><i :class="icon('ph-caret-right ph-fw')"></i
|
||||||
class="ph-caret-right ph-bold ph-lg ph-fw ph-lg"
|
|
||||||
></i
|
|
||||||
></span>
|
></span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
@ -162,8 +158,7 @@
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
v-if="item.icon"
|
v-if="item.icon"
|
||||||
class="ph-fw ph-lg"
|
:class="icon(`${item.icon} ph-fw`)"
|
||||||
:class="item.icon"
|
|
||||||
></i>
|
></i>
|
||||||
<MkAvatar
|
<MkAvatar
|
||||||
v-if="item.avatar"
|
v-if="item.avatar"
|
||||||
|
@ -178,7 +173,7 @@
|
||||||
v-if="item.indicate"
|
v-if="item.indicate"
|
||||||
class="indicator"
|
class="indicator"
|
||||||
:class="{
|
:class="{
|
||||||
animateIndicator: $store.state.animation,
|
animateIndicator: defaultStore.state.animation,
|
||||||
}"
|
}"
|
||||||
><i class="ph-circle ph-fill"></i
|
><i class="ph-circle ph-fill"></i
|
||||||
></span>
|
></span>
|
||||||
|
@ -190,6 +185,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div v-if="childMenu" class="child">
|
<div v-if="childMenu" class="child">
|
||||||
<XChild
|
<XChild
|
||||||
|
v-if="childTarget && itemsEl"
|
||||||
ref="child"
|
ref="child"
|
||||||
:items="childMenu"
|
:items="childMenu"
|
||||||
:target-element="childTarget"
|
:target-element="childTarget"
|
||||||
|
@ -221,6 +217,8 @@ import type {
|
||||||
} from "@/types/menu";
|
} from "@/types/menu";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
import { defaultStore } from "@/store";
|
||||||
|
import icon from "@/scripts/icon";
|
||||||
|
|
||||||
const XChild = defineAsyncComponent(() => import("./MkMenu.child.vue"));
|
const XChild = defineAsyncComponent(() => import("./MkMenu.child.vue"));
|
||||||
const focusTrap = ref();
|
const focusTrap = ref();
|
||||||
|
|
|
@ -5,10 +5,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="mod-player-disabled" v-else-if="hide" @click="toggleVisible()">
|
<div class="mod-player-disabled" v-else-if="hide" @click="toggleVisible()">
|
||||||
<div>
|
<div>
|
||||||
<b
|
<b><i class="ph-warning"></i> {{ i18n.ts.sensitive }}</b>
|
||||||
><i class="ph-warning ph-bold ph-lg"></i>
|
|
||||||
{{ i18n.ts.sensitive }}</b
|
|
||||||
>
|
|
||||||
<span>{{ i18n.ts.clickToShow }}</span>
|
<span>{{ i18n.ts.clickToShow }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -40,16 +37,16 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<button class="play" @click="playPause()" v-if="!loading">
|
<button class="play" @click="playPause()" v-if="!loading">
|
||||||
<i class="ph-pause ph-fill ph-lg" v-if="playing"></i>
|
<i class="ph-pause ph-fill" v-if="playing"></i>
|
||||||
<i class="ph-play ph-fill ph-lg" v-else></i>
|
<i class="ph-play ph-fill" v-else></i>
|
||||||
</button>
|
</button>
|
||||||
<MkLoading v-else :em="true" />
|
<MkLoading v-else :em="true" />
|
||||||
<button class="stop" @click="stop()">
|
<button class="stop" @click="stop()">
|
||||||
<i class="ph-stop ph-fill ph-lg"></i>
|
<i class="ph-stop ph-fill"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="loop" @click="toggleLoop()">
|
<button class="loop" @click="toggleLoop()">
|
||||||
<i class="ph-repeat ph-fill ph-lg" v-if="loop === -1"></i>
|
<i class="ph-repeat ph-fill" v-if="loop === -1"></i>
|
||||||
<i class="ph-repeat-once ph-fill ph-lg" v-else></i>
|
<i class="ph-repeat-once ph-fill" v-else></i>
|
||||||
</button>
|
</button>
|
||||||
<FormRange
|
<FormRange
|
||||||
class="progress"
|
class="progress"
|
||||||
|
@ -64,8 +61,8 @@
|
||||||
@update:modelValue="performSeek()"
|
@update:modelValue="performSeek()"
|
||||||
></FormRange>
|
></FormRange>
|
||||||
<button class="mute" @click="toggleMute()">
|
<button class="mute" @click="toggleMute()">
|
||||||
<i class="ph-speaker-simple-x ph-fill ph-lg" v-if="muted"></i>
|
<i class="ph-speaker-simple-x ph-fill" v-if="muted"></i>
|
||||||
<i class="ph-speaker-simple-high ph-fill ph-lg" v-else></i>
|
<i class="ph-speaker-simple-high ph-fill" v-else></i>
|
||||||
</button>
|
</button>
|
||||||
<FormRange
|
<FormRange
|
||||||
class="volume"
|
class="volume"
|
||||||
|
@ -84,7 +81,7 @@
|
||||||
:href="module.url"
|
:href="module.url"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<i class="ph-download-simple ph-fill ph-lg"></i>
|
<i class="ph-download-simple ph-fill"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
|
@ -94,7 +91,7 @@
|
||||||
class="_button"
|
class="_button"
|
||||||
@click.stop="captionPopup"
|
@click.stop="captionPopup"
|
||||||
>
|
>
|
||||||
<i class="ph-subtitles ph-bold ph-lg"></i>
|
<i class="ph-subtitles"></i>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="!hide"
|
v-if="!hide"
|
||||||
|
@ -102,7 +99,7 @@
|
||||||
class="_button"
|
class="_button"
|
||||||
@click.stop="toggleVisible()"
|
@click.stop="toggleVisible()"
|
||||||
>
|
>
|
||||||
<i class="ph-eye-slash ph-bold ph-lg"></i>
|
<i class="ph-eye-slash"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -116,6 +113,7 @@ import { i18n } from "@/i18n";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { defaultStore } from "@/store";
|
import { defaultStore } from "@/store";
|
||||||
import { ChiptuneJsPlayer, ChiptuneJsConfig } from "@/scripts/chiptune2";
|
import { ChiptuneJsPlayer, ChiptuneJsConfig } from "@/scripts/chiptune2";
|
||||||
|
import icon from "@/scripts/icon";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
module: firefish.entities.DriveFile;
|
module: firefish.entities.DriveFile;
|
||||||
|
|
|
@ -15,14 +15,14 @@
|
||||||
class="_button"
|
class="_button"
|
||||||
@click="back()"
|
@click="back()"
|
||||||
>
|
>
|
||||||
<i class="ph-caret-left ph-bold ph-lg"></i>
|
<i :class="icon('ph-caret-left')"></i>
|
||||||
</button>
|
</button>
|
||||||
<span v-else style="display: inline-block; width: 20px"></span>
|
<span v-else style="display: inline-block; width: 20px"></span>
|
||||||
<span v-if="pageMetadata?.value" class="title">
|
<span v-if="pageMetadata?.value" class="title">
|
||||||
<i
|
<i
|
||||||
v-if="pageMetadata?.value.icon"
|
v-if="pageMetadata?.value.icon"
|
||||||
class="icon"
|
class="icon"
|
||||||
:class="pageMetadata?.value.icon"
|
:class="icon(pageMetadata?.value.icon)"
|
||||||
></i>
|
></i>
|
||||||
<span>{{ pageMetadata?.value.title }}</span>
|
<span>{{ pageMetadata?.value.title }}</span>
|
||||||
</span>
|
</span>
|
||||||
|
@ -31,7 +31,7 @@
|
||||||
:aria-label="i18n.t('close')"
|
:aria-label="i18n.t('close')"
|
||||||
@click="$refs.modal.close()"
|
@click="$refs.modal.close()"
|
||||||
>
|
>
|
||||||
<i class="ph-x ph-bold ph-lg"></i>
|
<i :class="icon('ph-x')"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
|
@ -64,6 +64,8 @@ import { i18n } from "@/i18n";
|
||||||
import type { PageMetadata } from "@/scripts/page-metadata";
|
import type { PageMetadata } from "@/scripts/page-metadata";
|
||||||
import { provideMetadataReceiver } from "@/scripts/page-metadata";
|
import { provideMetadataReceiver } from "@/scripts/page-metadata";
|
||||||
import { Router } from "@/nirax";
|
import { Router } from "@/nirax";
|
||||||
|
import { defaultStore } from "@/store";
|
||||||
|
import icon from "@/scripts/icon";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
initialPath: string;
|
initialPath: string;
|
||||||
|
@ -101,18 +103,18 @@ const contextmenu = computed(() => {
|
||||||
text: path.value,
|
text: path.value,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "ph-arrows-out-simple ph-bold ph-lg",
|
icon: `${icon("ph-arrows-out-simple")}`,
|
||||||
text: i18n.ts.showInPage,
|
text: i18n.ts.showInPage,
|
||||||
action: expand,
|
action: expand,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "ph-arrow-square-out ph-bold ph-lg",
|
icon: `${icon("ph-arrow-square-out")}`,
|
||||||
text: i18n.ts.popout,
|
text: i18n.ts.popout,
|
||||||
action: popout,
|
action: popout,
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
{
|
{
|
||||||
icon: "ph-arrow-square-out ph-bold ph-lg",
|
icon: `${icon("ph-arrow-square-out")}`,
|
||||||
text: i18n.ts.openInNewTab,
|
text: i18n.ts.openInNewTab,
|
||||||
action: () => {
|
action: () => {
|
||||||
window.open(pageUrl.value, "_blank");
|
window.open(pageUrl.value, "_blank");
|
||||||
|
@ -120,7 +122,7 @@ const contextmenu = computed(() => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "ph-link-simple ph-bold ph-lg",
|
icon: `${icon("ph-link-simple")}`,
|
||||||
text: i18n.ts.copyLink,
|
text: i18n.ts.copyLink,
|
||||||
action: () => {
|
action: () => {
|
||||||
copyToClipboard(pageUrl.value);
|
copyToClipboard(pageUrl.value);
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
class="_button"
|
class="_button"
|
||||||
@click="$emit('close')"
|
@click="$emit('close')"
|
||||||
>
|
>
|
||||||
<i class="ph-x ph-bold ph-lg"></i>
|
<i :class="icon('ph-x')"></i>
|
||||||
</button>
|
</button>
|
||||||
<span class="title">
|
<span class="title">
|
||||||
<slot name="header"></slot>
|
<slot name="header"></slot>
|
||||||
|
@ -41,7 +41,7 @@
|
||||||
class="_button"
|
class="_button"
|
||||||
@click="$emit('close')"
|
@click="$emit('close')"
|
||||||
>
|
>
|
||||||
<i class="ph-x ph-bold ph-lg"></i>
|
<i :class="icon('ph-x')"></i>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="props.withOkButton"
|
v-if="props.withOkButton"
|
||||||
|
@ -50,7 +50,7 @@
|
||||||
:disabled="props.okButtonDisabled"
|
:disabled="props.okButtonDisabled"
|
||||||
@click="$emit('ok')"
|
@click="$emit('ok')"
|
||||||
>
|
>
|
||||||
<i class="ph-check ph-bold ph-lg"></i>
|
<i :class="icon('ph-check')"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
|
@ -67,6 +67,7 @@ import { shallowRef } from "vue";
|
||||||
import { FocusTrap } from "focus-trap-vue";
|
import { FocusTrap } from "focus-trap-vue";
|
||||||
import MkModal from "./MkModal.vue";
|
import MkModal from "./MkModal.vue";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
import icon from "@/scripts/icon";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="msjugskd _block">
|
<div class="msjugskd _block">
|
||||||
<i
|
<i :class="icon('ph-airplane-takeoff')" style="margin-right: 8px" />
|
||||||
class="ph-airplane-takeoff ph-bold ph-lg"
|
|
||||||
style="margin-right: 8px"
|
|
||||||
/>
|
|
||||||
{{ i18n.ts.accountMoved }}
|
{{ i18n.ts.accountMoved }}
|
||||||
<MkMention class="link" :username="acct" :host="host" />
|
<MkMention class="link" :username="acct" :host="host" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -12,6 +9,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import MkMention from "./MkMention.vue";
|
import MkMention from "./MkMention.vue";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
import icon from "@/scripts/icon";
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
acct: string;
|
acct: string;
|
||||||
|
|
|
@ -27,23 +27,22 @@
|
||||||
>
|
>
|
||||||
<div class="line"></div>
|
<div class="line"></div>
|
||||||
<div v-if="appearNote._prId_" class="info">
|
<div v-if="appearNote._prId_" class="info">
|
||||||
<i class="ph-megaphone-simple-bold ph-lg"></i>
|
<i :class="icon('ph-megaphone-simple-bold')"></i>
|
||||||
{{ i18n.ts.promotion
|
{{ i18n.ts.promotion
|
||||||
}}<button class="_textButton hide" @click.stop="readPromo()">
|
}}<button class="_textButton hide" @click.stop="readPromo()">
|
||||||
{{ i18n.ts.hideThisNote }}
|
{{ i18n.ts.hideThisNote }}
|
||||||
<i class="ph-x ph-bold ph-lg"></i>
|
<i :class="icon('ph-x')"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="appearNote._featuredId_" class="info">
|
<div v-if="appearNote._featuredId_" class="info">
|
||||||
<i class="ph-lightning ph-bold ph-lg"></i>
|
<i :class="icon('ph-lightning')"></i>
|
||||||
{{ i18n.ts.featured }}
|
{{ i18n.ts.featured }}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="pinned" class="info">
|
<div v-if="pinned" class="info">
|
||||||
<i class="ph-push-pin ph-bold ph-lg"></i
|
<i :class="icon('ph-push-pin')"></i>{{ i18n.ts.pinnedNote }}
|
||||||
>{{ i18n.ts.pinnedNote }}
|
|
||||||
</div>
|
</div>
|
||||||
<div v-if="isRenote" class="renote">
|
<div v-if="isRenote" class="renote">
|
||||||
<i class="ph-rocket-launch ph-bold ph-lg"></i>
|
<i :class="icon('ph-rocket-launch')"></i>
|
||||||
<I18n :src="i18n.ts.renotedBy" tag="span">
|
<I18n :src="i18n.ts.renotedBy" tag="span">
|
||||||
<template #user>
|
<template #user>
|
||||||
<MkA
|
<MkA
|
||||||
|
@ -64,7 +63,7 @@
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
v-if="isMyRenote"
|
v-if="isMyRenote"
|
||||||
class="ph-dots-three-outline ph-bold ph-lg dropdownIcon"
|
:class="icon('ph-dots-three-outline dropdownIcon')"
|
||||||
></i>
|
></i>
|
||||||
<MkTime :time="note.createdAt" />
|
<MkTime :time="note.createdAt" />
|
||||||
</button>
|
</button>
|
||||||
|
@ -149,7 +148,7 @@
|
||||||
class="channel"
|
class="channel"
|
||||||
:to="`/channels/${appearNote.channel.id}`"
|
:to="`/channels/${appearNote.channel.id}`"
|
||||||
@click.stop
|
@click.stop
|
||||||
><i class="ph-television ph-bold"></i>
|
><i :class="icon('ph-television', false)"></i>
|
||||||
{{ appearNote.channel.name }}</MkA
|
{{ appearNote.channel.name }}</MkA
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
@ -164,7 +163,7 @@
|
||||||
class="button _button"
|
class="button _button"
|
||||||
@click.stop="reply()"
|
@click.stop="reply()"
|
||||||
>
|
>
|
||||||
<i class="ph-arrow-u-up-left ph-bold ph-lg"></i>
|
<i :class="icon('ph-arrow-u-up-left')"></i>
|
||||||
<template
|
<template
|
||||||
v-if="appearNote.repliesCount > 0 && !detailedView"
|
v-if="appearNote.repliesCount > 0 && !detailedView"
|
||||||
>
|
>
|
||||||
|
@ -209,7 +208,7 @@
|
||||||
class="button _button"
|
class="button _button"
|
||||||
@click.stop="react()"
|
@click.stop="react()"
|
||||||
>
|
>
|
||||||
<i class="ph-smiley ph-bold ph-lg"></i>
|
<i :class="icon('ph-smiley')"></i>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="
|
v-if="
|
||||||
|
@ -221,7 +220,7 @@
|
||||||
class="button _button reacted"
|
class="button _button reacted"
|
||||||
@click.stop="undoReact(appearNote)"
|
@click.stop="undoReact(appearNote)"
|
||||||
>
|
>
|
||||||
<i class="ph-minus ph-bold ph-lg"></i>
|
<i :class="icon('ph-minus')"></i>
|
||||||
</button>
|
</button>
|
||||||
<XQuoteButton class="button" :note="appearNote" />
|
<XQuoteButton class="button" :note="appearNote" />
|
||||||
<button
|
<button
|
||||||
|
@ -234,7 +233,7 @@
|
||||||
class="button _button"
|
class="button _button"
|
||||||
@click.stop="translate"
|
@click.stop="translate"
|
||||||
>
|
>
|
||||||
<i class="ph-translate ph-bold ph-lg"></i>
|
<i :class="icon('ph-translate')"></i>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
ref="menuButton"
|
ref="menuButton"
|
||||||
|
@ -242,7 +241,7 @@
|
||||||
class="button _button"
|
class="button _button"
|
||||||
@click.stop="menu()"
|
@click.stop="menu()"
|
||||||
>
|
>
|
||||||
<i class="ph-dots-three-outline ph-bold ph-lg"></i>
|
<i :class="icon('ph-dots-three-outline')"></i>
|
||||||
</button>
|
</button>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
@ -273,7 +272,6 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, inject, onMounted, ref } from "vue";
|
import { computed, inject, onMounted, ref } from "vue";
|
||||||
import * as mfm from "mfm-js";
|
|
||||||
import type { Ref } from "vue";
|
import type { Ref } from "vue";
|
||||||
import type * as firefish from "firefish-js";
|
import type * as firefish from "firefish-js";
|
||||||
import MkSubNoteContent from "./MkSubNoteContent.vue";
|
import MkSubNoteContent from "./MkSubNoteContent.vue";
|
||||||
|
@ -303,6 +301,7 @@ import { useNoteCapture } from "@/scripts/use-note-capture";
|
||||||
import { notePage } from "@/filters/note";
|
import { notePage } from "@/filters/note";
|
||||||
import { deepClone } from "@/scripts/clone";
|
import { deepClone } from "@/scripts/clone";
|
||||||
import { getNoteSummary } from "@/scripts/get-note-summary";
|
import { getNoteSummary } from "@/scripts/get-note-summary";
|
||||||
|
import icon from "@/scripts/icon";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
@ -360,7 +359,7 @@ const isDeleted = ref(false);
|
||||||
const muted = ref(
|
const muted = ref(
|
||||||
getWordSoftMute(
|
getWordSoftMute(
|
||||||
note.value,
|
note.value,
|
||||||
$i,
|
$i.id,
|
||||||
defaultStore.state.mutedWords,
|
defaultStore.state.mutedWords,
|
||||||
defaultStore.state.mutedLangs,
|
defaultStore.state.mutedLangs,
|
||||||
),
|
),
|
||||||
|
@ -495,7 +494,7 @@ function onContextmenu(ev: MouseEvent): void {
|
||||||
text: notePage(appearNote.value),
|
text: notePage(appearNote.value),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "ph-browser ph-bold ph-lg",
|
icon: `${icon("ph-browser")}`,
|
||||||
text: i18n.ts.openInWindow,
|
text: i18n.ts.openInWindow,
|
||||||
action: () => {
|
action: () => {
|
||||||
os.pageWindow(notePage(appearNote.value));
|
os.pageWindow(notePage(appearNote.value));
|
||||||
|
@ -503,7 +502,7 @@ function onContextmenu(ev: MouseEvent): void {
|
||||||
},
|
},
|
||||||
notePage(appearNote.value) != location.pathname
|
notePage(appearNote.value) != location.pathname
|
||||||
? {
|
? {
|
||||||
icon: "ph-arrows-out-simple ph-bold ph-lg",
|
icon: `${icon("ph-arrows-out-simple")}`,
|
||||||
text: i18n.ts.showInPage,
|
text: i18n.ts.showInPage,
|
||||||
action: () => {
|
action: () => {
|
||||||
router.push(
|
router.push(
|
||||||
|
@ -516,13 +515,13 @@ function onContextmenu(ev: MouseEvent): void {
|
||||||
null,
|
null,
|
||||||
{
|
{
|
||||||
type: "a",
|
type: "a",
|
||||||
icon: "ph-arrow-square-out ph-bold ph-lg",
|
icon: `${icon("ph-arrow-square-out")}`,
|
||||||
text: i18n.ts.openInNewTab,
|
text: i18n.ts.openInNewTab,
|
||||||
href: notePage(appearNote.value),
|
href: notePage(appearNote.value),
|
||||||
target: "_blank",
|
target: "_blank",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "ph-link-simple ph-bold ph-lg",
|
icon: `${icon("ph-link-simple")}`,
|
||||||
text: i18n.ts.copyLink,
|
text: i18n.ts.copyLink,
|
||||||
action: () => {
|
action: () => {
|
||||||
copyToClipboard(`${url}${notePage(appearNote.value)}`);
|
copyToClipboard(`${url}${notePage(appearNote.value)}`);
|
||||||
|
@ -531,7 +530,7 @@ function onContextmenu(ev: MouseEvent): void {
|
||||||
appearNote.value.user.host != null
|
appearNote.value.user.host != null
|
||||||
? {
|
? {
|
||||||
type: "a",
|
type: "a",
|
||||||
icon: "ph-arrow-square-up-right ph-bold ph-lg",
|
icon: `${icon("ph-arrow-square-up-right")}`,
|
||||||
text: i18n.ts.showOnRemote,
|
text: i18n.ts.showOnRemote,
|
||||||
href:
|
href:
|
||||||
appearNote.value.url ??
|
appearNote.value.url ??
|
||||||
|
@ -569,7 +568,7 @@ function showRenoteMenu(viaKeyboard = false): void {
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
text: i18n.ts.unrenote,
|
text: i18n.ts.unrenote,
|
||||||
icon: "ph-trash ph-bold ph-lg",
|
icon: `${icon("ph-trash")}`,
|
||||||
danger: true,
|
danger: true,
|
||||||
action: () => {
|
action: () => {
|
||||||
os.api("notes/delete", {
|
os.api("notes/delete", {
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue