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