forked from naskya/firefish
Firefish v1.0.4-beta31
This commit is contained in:
parent
a93f971703
commit
63b9905a71
56 changed files with 1621 additions and 1724 deletions
|
@ -474,3 +474,4 @@ _preferencesBackups:
|
|||
cannotLoad: Неуспешно зареждане
|
||||
editWidgetsExit: Готово
|
||||
done: Готово
|
||||
emailRequiredForSignup: Изискване за адрес на е-поща за регистриране
|
||||
|
|
|
@ -2190,3 +2190,5 @@ detectPostLanguage: Detecta l'idioma automàticament i mostra un botó per els a
|
|||
indexableDescription: Permet al cercador intern mostrar els missatges públics
|
||||
indexable: Indexable
|
||||
languageForTranslation: Idioma de traducció d'articles
|
||||
openServerInfo: Mostra la informació del servidor fent clic al símbol del servidor
|
||||
en un missatge
|
||||
|
|
|
@ -2103,7 +2103,7 @@ pwa: PWA installieren
|
|||
cw: Inhaltswarnung
|
||||
older: älter
|
||||
newer: neuer
|
||||
accessibility: Erreichbarkeit
|
||||
accessibility: Barrierefreiheit
|
||||
jumpToPrevious: Zum Vorherigen springen
|
||||
silencedWarning: Diese Meldung wird angezeigt, weil diese Nutzer von Servern stammen,
|
||||
die Ihr Administrator abgeschaltet hat, so dass es sich möglicherweise um Spam handelt.
|
||||
|
@ -2213,3 +2213,5 @@ indexableDescription: Der integrierten Suche erlauben, Ihre öffentlichen Beitr
|
|||
anzuzeigen
|
||||
indexable: Indexierbar
|
||||
languageForTranslation: Übersetzungssprache veröffentlichen
|
||||
openServerInfo: Anzeigen von Serverinformationen durch Anklicken des Server-Tickers
|
||||
in einem Beitrag
|
||||
|
|
|
@ -675,7 +675,7 @@ emptyToDisableSmtpAuth: "Leave username and password empty to disable SMTP verif
|
|||
smtpSecure: "Use implicit SSL/TLS for SMTP connections"
|
||||
smtpSecureInfo: "Turn this off when using STARTTLS"
|
||||
testEmail: "Test email delivery"
|
||||
wordMute: "Word mute"
|
||||
wordMute: "Word and language mutes"
|
||||
regexpError: "Regular Expression error"
|
||||
regexpErrorDescription: "An error occurred in the regular expression on line {line}
|
||||
of your {tab} word mutes:"
|
||||
|
@ -1151,6 +1151,8 @@ indexable: "Indexable"
|
|||
indexableDescription: "Allow built-in search to show your public posts"
|
||||
languageForTranslation: "Post translation language"
|
||||
detectPostLanguage: "Automatically detect the language and show a translate button for posts in foreign languages"
|
||||
vibrate: "Play vibrations"
|
||||
openServerInfo: "Show server information by clicking the server ticker on a post"
|
||||
|
||||
_sensitiveMediaDetection:
|
||||
description: "Reduces the effort of server moderation through automatically recognizing
|
||||
|
@ -1382,14 +1384,19 @@ _menuDisplay:
|
|||
hide: "Hide"
|
||||
_wordMute:
|
||||
muteWords: "Muted words"
|
||||
muteLangs: "Muted Languages"
|
||||
muteWordsDescription: "Separate with spaces for an AND condition or with line breaks
|
||||
for an OR condition."
|
||||
muteWordsDescription2: "Surround keywords with slashes to use regular expressions."
|
||||
muteLangsDescription: "Separate with spaces or line breaks for an OR condition."
|
||||
muteLangsDescription2: "Use language code e.g. en, fr, ja, zh."
|
||||
softDescription: "Hide posts that fulfil the set conditions from the timeline."
|
||||
langDescription: "Hide posts that match set language from the timeline."
|
||||
hardDescription: "Prevents posts fulfilling the set conditions from being added
|
||||
to the timeline. In addition, these posts will not be added to the timeline even
|
||||
if the conditions are changed."
|
||||
soft: "Soft"
|
||||
lang: "Language"
|
||||
hard: "Hard"
|
||||
mutedNotes: "Muted posts"
|
||||
_instanceMute:
|
||||
|
|
|
@ -724,8 +724,8 @@ no: "Non"
|
|||
driveFilesCount: "Nombre de fichiers dans le Drive"
|
||||
driveUsage: "Utilisation du Drive"
|
||||
noCrawle: "Refuser l'indexation par les robots"
|
||||
noCrawleDescription: "Demandez aux moteurs de recherche de ne pas indexer votre page
|
||||
de profil, vos publications, vos pages, etc."
|
||||
noCrawleDescription: "Demandez aux moteurs de recherche externes de ne pas indexer
|
||||
votre contenu."
|
||||
lockedAccountInfo: "À moins que vous ne définissiez la visibilité de votre publication
|
||||
sur \"Abonné-e-s\", vos publications sont visibles par tous, même si vous exigez
|
||||
que les demandes d'abonnement soient approuvées manuellement."
|
||||
|
@ -2210,3 +2210,11 @@ confirm: Confirmer
|
|||
importZip: Importer ZIP
|
||||
exportZip: Exporter ZIP
|
||||
emojiPackCreator: Créateur de pack d’émoji
|
||||
detectPostLanguage: Détecte automatiquement le langage et affiche un bouton de traduction
|
||||
pour les langues étrangères
|
||||
indexableDescription: Permettre à la recherche interne d’afficher vos publications
|
||||
publiques
|
||||
openServerInfo: Afficher les informations du serveur en cliquant sur le bandeau de
|
||||
serveur d’une publication
|
||||
indexable: Indexable
|
||||
languageForTranslation: Langage post-traduction
|
||||
|
|
|
@ -81,7 +81,7 @@ deleteAndEdit: Eliminar e editar
|
|||
blockConfirm: Tes a certeza de querer bloquear esta conta?
|
||||
deleteAndEditConfirm: Tes a certeza de querer eliminar esta publicación e editala?
|
||||
Perderás todas as súas reaccións, promocións e respostas.
|
||||
editNote: Editar nota
|
||||
editNote: Editar publicación
|
||||
edited: Editado o {date} {time}
|
||||
sendMessage: Enviar unha mensaxe
|
||||
copyUsername: Copiar identificador
|
||||
|
@ -282,3 +282,76 @@ storageUsage: Uso da almacenaxe
|
|||
charts: Gráficas
|
||||
perHour: Por hora
|
||||
followRequest: Solicitar seguimento
|
||||
messageRead: Ler
|
||||
noMoreHistory: Non hai máis historial
|
||||
images: Imaxes
|
||||
manageGroups: Xestionar grupos
|
||||
unableToDelete: Non se puido eliminar
|
||||
syncDeviceDarkMode: Syncr Modo Escuro cos axustes do teu dispositivo
|
||||
uploadFromUrl: Subir desde un URL
|
||||
emptyDrive: O teu Disco está baleiro
|
||||
copyUrl: Copiar URL
|
||||
nUsersRead: lido por {n}
|
||||
uploadFromUrlRequested: Solicitaches unha subida
|
||||
circularReferenceFolder: O cartafol de destino é un subcartafol do cartafol que queres
|
||||
mover.
|
||||
selectFile: Elexir un ficheiro
|
||||
inputNewFileName: Escribe o novo nome
|
||||
agreeTo: Acepto os {0}
|
||||
whenServerDisconnected: Cando se perda a conexión co servidor
|
||||
selectFolder: Elexir un cartafol
|
||||
saved: Gardado
|
||||
selectFiles: Elexir ficheiros
|
||||
fileName: Nome do ficheiro
|
||||
explore: Descubrir
|
||||
keepOriginalUploadingDescription: Garda a imaxe subida orixinalmente tal como é. Se
|
||||
o desactivas, a versión que se mostrará na web será creada ao subila.
|
||||
folderName: Nome do cartafol
|
||||
lightThemes: Decorados claros
|
||||
rename: Cambiar nome
|
||||
activity: Actividade
|
||||
fromUrl: Desde URL
|
||||
darkThemes: Decorados escuros
|
||||
birthday: Aniversario
|
||||
registeredDate: Conta creada en
|
||||
fromDrive: Desde Disco
|
||||
uploadFromUrlMayTakeTime: Podería tardar una anaco en completarse a subida.
|
||||
theme: Decorados
|
||||
renameFolder: Cambiar nome a este cartafol
|
||||
resetAreYouSure: Queres restablecer?
|
||||
startMessaging: Comezar un novo chat
|
||||
light: Claro
|
||||
themeForLightMode: Decorado a usar no Modo Claro
|
||||
inputNewDescription: Escribe a descrición
|
||||
start: Comezar
|
||||
selectFolders: Elexir cartafoles
|
||||
remoteUserCaution: A información das usuarias remotas podería estar incompleta.
|
||||
exportRequested: Solicitaches unha exportación. Vainos levar un pouco. Vai ser engadida
|
||||
ao teu Disco cando estea completa.
|
||||
deleteFolder: Eliminar este cartafol
|
||||
drive: Disco
|
||||
importRequested: Solicitaches unha importación. Vainos levar un anaco.
|
||||
uploadFromUrlDescription: URL do ficheiro que queres subir
|
||||
location: Localización
|
||||
unfollowConfirm: Tes a certeza de que queres deixar de seguir a {name}?
|
||||
banner: Cabeceira
|
||||
dark: Escuro
|
||||
home: Inicio
|
||||
keepOriginalUploading: Manter imaxe orixinal
|
||||
upload: Subir
|
||||
yearsOld: '{age} anos de idade'
|
||||
emptyFolder: Este cartafol está baleiro
|
||||
messaging: Chat
|
||||
nsfw: NSFW
|
||||
addFile: Engadir un ficheiro
|
||||
tos: Termos do Servizo
|
||||
themeForDarkMode: Decorado a usar no Modo Escuro
|
||||
deleteAreYouSure: Tes a certeza de querer desbotar "{x}"?
|
||||
createFolder: Crear un cartafol
|
||||
renameFile: Cambiar nome ao ficheiro
|
||||
lookup: Buscar
|
||||
avatar: Avatar
|
||||
driveFileDeleteConfirm: Tes a certeza de querer eliminar o ficheiro "{name}"? Vai
|
||||
ser retirado de todas as publicacións nas que estea como anexo.
|
||||
inputNewFolderName: Escribe o novo nome do cartafol
|
||||
hasChildFilesOrFolders: Como o cartafol non está baleiro, non pode ser eliminado.
|
||||
|
|
|
@ -245,7 +245,7 @@ currentPassword: "Kata sandi saat ini"
|
|||
newPassword: "Kata sandi baru"
|
||||
newPasswordRetype: "Ulangi kata sandi baru"
|
||||
attachFile: "Lampirkan berkas"
|
||||
more: "Lagi !"
|
||||
more: "Lagi!"
|
||||
featured: "Sorotan"
|
||||
usernameOrUserId: "Nama pengguna atau User ID"
|
||||
noSuchUser: "Pengguna tidak ditemukan"
|
||||
|
@ -2173,3 +2173,5 @@ detectPostLanguage: Deteksi bahasa secara otomatis dan tampilkan tombol terjemah
|
|||
indexableDescription: Perbolehkan pencarian di sini untuk menampilkan kiriman publikmu
|
||||
indexable: Dapat diindeks
|
||||
languageForTranslation: Bahasa terjemahan kiriman
|
||||
openServerInfo: Tampilkan informasi server dengan mengeklik ticker server di sebuah
|
||||
kiriman
|
||||
|
|
|
@ -2162,3 +2162,5 @@ detectPostLanguage: Riconosci la lingua automaticamente e mostra il bottone per
|
|||
indexableDescription: Mostra i tuoi post pubblici tramite il sistema di ricerca
|
||||
indexable: Indicizzabile
|
||||
languageForTranslation: Linguaggio di traduzione dei post
|
||||
openServerInfo: Mostra informazioni sul server cliccando sul riquadro del server in
|
||||
un post
|
||||
|
|
|
@ -66,7 +66,7 @@ import: "インポート"
|
|||
export: "エクスポート"
|
||||
files: "ファイル"
|
||||
download: "ダウンロード"
|
||||
driveFileDeleteConfirm: "ファイル「{name}」を削除しますか?これにより、このファイルが添付されている投稿も削除されます。"
|
||||
driveFileDeleteConfirm: "ファイル「{name}」を削除しますか?これにより、添付ファイルとして含まれているすべての投稿から削除されます。"
|
||||
unfollowConfirm: "{name}さんのフォローを解除しますか?"
|
||||
exportRequested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、「ドライブ」に追加されます。"
|
||||
importRequested: "インポートをリクエストしました。これには時間がかかる場合があります。"
|
||||
|
@ -1209,11 +1209,16 @@ _menuDisplay:
|
|||
hide: "隠す"
|
||||
_wordMute:
|
||||
muteWords: "ミュートするワード"
|
||||
muteLangs: "ミュートされた言語"
|
||||
muteWordsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります。"
|
||||
muteWordsDescription2: "キーワードをスラッシュで囲むと正規表現になります。"
|
||||
muteLangsDescription: "OR 条件の場合はスペースまたは改行で区切ります。"
|
||||
muteLangsDescription2: "言語コードを使用します。例: en, fr, ja, zh."
|
||||
softDescription: "指定した条件の投稿をタイムラインから隠します。"
|
||||
langDescription: "設定した言語に一致する投稿をタイムラインから非表示にします。"
|
||||
hardDescription: "指定した条件の投稿をタイムラインに追加しないようにします。追加されなかった投稿は、条件を変更しても除外されたままになります。"
|
||||
soft: "ソフト"
|
||||
lang: "言語"
|
||||
hard: "ハード"
|
||||
mutedNotes: "ミュートされた投稿"
|
||||
_instanceMute:
|
||||
|
@ -1982,7 +1987,7 @@ _feeds:
|
|||
rss: RSS
|
||||
jsonFeed: JSONフィード
|
||||
copyFeed: フィードのURLをコピー
|
||||
origin: オリジナル
|
||||
origin: 元のサーバー
|
||||
delete2fa: 2要素認証を無効化
|
||||
deletePasskeys: パスキーを削除
|
||||
delete2faConfirm: これで、このアカウントの2要素認証は完全に削除されます。続行しますか?
|
||||
|
@ -2003,3 +2008,4 @@ privacyForNerds: "身バレ防止"
|
|||
disableToast: "「おかえりなさい、◯◯さん」を表示しない"
|
||||
hideMyIcon: "自分のアイコンを表示しない"
|
||||
hideMyName: "自分の名前とIDを表示しない"
|
||||
openServerInfo: "投稿内のサーバー名をクリックでサーバー情報を開く"
|
||||
|
|
|
@ -578,7 +578,7 @@ disableAll: "全部使えへんようにする"
|
|||
tokenRequested: "アカウントへのアクセス許可"
|
||||
pluginTokenRequestedDescription: "このプラグインはここで設定した権限を使えるようになるで。"
|
||||
notificationType: "通知の種類"
|
||||
edit: "編集"
|
||||
edit: "投稿をいじる"
|
||||
emailServer: "メールサーバー"
|
||||
enableEmail: "メール配信を受け取る"
|
||||
emailConfigInfo: "メールアドレスの確認とかパスワードリセットの時に使うで"
|
||||
|
@ -735,7 +735,7 @@ showingPastTimeline: "過去のタイムラインを表示してるで"
|
|||
clear: "クリア"
|
||||
markAllAsRead: "もうみな読んでもうたわ"
|
||||
goBack: "戻る"
|
||||
unlikeConfirm: "いいね解除するんか?"
|
||||
unlikeConfirm: "ええやんを解除するんけ?"
|
||||
fullView: "フルビュー"
|
||||
quitFullView: "フルビュー解除"
|
||||
addDescription: "説明を追加するで"
|
||||
|
@ -810,7 +810,7 @@ searchByGoogle: "探す"
|
|||
indefinitely: "無期限"
|
||||
file: "ファイル"
|
||||
requireAdminForView: "これを見るには管理者アカウントでログインしとらなあかんで。"
|
||||
isSystemAccount: "システムが自動で作成・管理しとるアカウントやで。"
|
||||
isSystemAccount: "システムが自動で作成・管理しとるアカウントやで。モデレーション・編集・削除するとサーバーの動作が不正になる可能性があるので、操作せんといてください。"
|
||||
typeToConfirm: "この操作をやるんなら {x} と入力してなー"
|
||||
deleteAccount: "アカウント削除するで"
|
||||
document: "ドキュメント"
|
||||
|
@ -853,7 +853,9 @@ _ffVisibility:
|
|||
_ad:
|
||||
back: "戻る"
|
||||
_gallery:
|
||||
unlike: "良くないわ"
|
||||
unlike: "やっぱよくないわ"
|
||||
like: ええやん!
|
||||
liked: ええやんと思った投稿
|
||||
_email:
|
||||
_follow:
|
||||
title: "フォローされたで"
|
||||
|
@ -1069,9 +1071,16 @@ _poll:
|
|||
votesCount: "{n}票"
|
||||
vote: "投票する"
|
||||
_visibility:
|
||||
publicDescription: "みんなに公開"
|
||||
home: "ホーム"
|
||||
followers: "フォロワー"
|
||||
publicDescription: "うちの投稿、みんな見てや"
|
||||
home: "ホームタイムラインのみ"
|
||||
followers: "フォロワーのみ"
|
||||
localOnly: ローカルのみ
|
||||
followersDescription: フォロワーと返信相手だけに見せたる
|
||||
specified: ダイレクト
|
||||
localOnlyDescription: 他のサーバーには見せとうない
|
||||
specifiedDescription: 指定した相手だけに見せたる
|
||||
public: 公開
|
||||
homeDescription: ローカルTLやグローバルTLには流さへん
|
||||
_profile:
|
||||
name: "名前"
|
||||
username: "ユーザー名"
|
||||
|
@ -1121,7 +1130,7 @@ _pages:
|
|||
pageSetting: "ページ設定"
|
||||
viewPage: "ページを見る"
|
||||
like: "ええやん"
|
||||
unlike: "良くないわ"
|
||||
unlike: "やっぱ気に入らん"
|
||||
liked: "ええと思ったページ"
|
||||
contents: "コンテンツ"
|
||||
summary: "ページの要約"
|
||||
|
@ -1441,5 +1450,6 @@ _postForm:
|
|||
f: あんさん書くんを待っとるんどす...
|
||||
a: いまなにしとん?
|
||||
flagSpeakAsCat: 猫弁で話す
|
||||
flagSpeakAsCatDescription: 猫モードが有効の場合にオンにすると、ワレの投稿の「な」を「にゃ」に変換するで。
|
||||
flagSpeakAsCatDescription: オンにすると、ワレの投稿の「な」を「にゃ」に変換したるで。
|
||||
welcomeBackWithName: おおきに、{name}はん
|
||||
migration: アカウントの引っ越し
|
||||
|
|
|
@ -300,7 +300,7 @@ messagingWithGroup: Grup sohbeti
|
|||
next: Sonraki
|
||||
retype: Tekrar gir
|
||||
dashboard: Panel
|
||||
objectStorageBucket: Bucket
|
||||
objectStorageBucket: Kova
|
||||
objectStorageBucketDesc: Sağlayıcınız tarafından kullanınan bucket ismini yazın.
|
||||
showFixedPostForm: Gönderim formunu zaman çizelgesinin en üstünde görüntüleyin
|
||||
newNoteRecived: Yeni gönderiler mevcut
|
||||
|
@ -750,7 +750,7 @@ upload: Yükle
|
|||
fromUrl: URL'den
|
||||
agreeTo: '{0} kabul ediyorum'
|
||||
tos: Kullanım Koşulları
|
||||
drive: Drive
|
||||
drive: Sürücü
|
||||
selectFolder: Klasör seç
|
||||
inputNewFileName: Yeni dosya ismi gir
|
||||
whenServerDisconnected: Sunucuyla bağlantı kesildiğinde
|
||||
|
@ -916,7 +916,7 @@ aboutX: '{x} Hakkında'
|
|||
doing: İşleniyor...
|
||||
category: Kategori
|
||||
deleteAll: Hepsini sil
|
||||
objectStorageEndpoint: Endpoint
|
||||
objectStorageEndpoint: Uç noktası
|
||||
output: Çıkış
|
||||
userSuspended: Bu kullanıcı askıya alındı.
|
||||
userSilenced: Bu kullanıcı susturuldu.
|
||||
|
@ -1105,7 +1105,7 @@ clips: Ataçlar
|
|||
experimentalFeatures: Deneysel özellikler
|
||||
developer: Geliştirici
|
||||
left: Sol
|
||||
center: Orta
|
||||
center: Merkez
|
||||
wide: Geniş
|
||||
narrow: Dar
|
||||
reloadToApplySetting: Bu ayar yalnızca bir sayfa yeniden yüklendikten sonra geçerli
|
||||
|
|
|
@ -1111,11 +1111,16 @@ _menuDisplay:
|
|||
hide: "隐藏"
|
||||
_wordMute:
|
||||
muteWords: "过滤词"
|
||||
muteLangs: "过滤语言"
|
||||
muteWordsDescription: "AND 条件用空格分隔,OR 条件用换行符分隔。"
|
||||
muteWordsDescription2: "将关键字用斜线括起来表示正则表达式。"
|
||||
muteLangsDescription: "OR 条件用空格,换行符分隔"
|
||||
muteLangsDescription2: "使用语言代码。例: en, fr, ja, zh."
|
||||
softDescription: "隐藏时间线中指定条件的帖子。"
|
||||
langDescription: "从时间线中隐藏与设置语言匹配的帖子。"
|
||||
hardDescription: "防止将具有指定条件的帖子添加到时间线。 即使您更改条件,原先未添加的帖文也会被排除在外。"
|
||||
soft: "软过滤"
|
||||
lang: "语言"
|
||||
hard: "硬过滤"
|
||||
mutedNotes: "已过滤的帖子"
|
||||
_instanceMute:
|
||||
|
|
|
@ -236,7 +236,7 @@ imageUrl: "圖片URL"
|
|||
remove: "刪除"
|
||||
removed: "已成功刪除"
|
||||
removeAreYouSure: "確定要刪掉「{x}」嗎?"
|
||||
deleteAreYouSure: "確定要刪掉「{x}」嗎?"
|
||||
deleteAreYouSure: "確定要刪除「{x}」嗎?"
|
||||
resetAreYouSure: "確定要重設嗎?"
|
||||
saved: "已儲存"
|
||||
messaging: "訊息"
|
||||
|
@ -292,7 +292,7 @@ inputNewFileName: "輸入檔案名稱"
|
|||
inputNewDescription: "請輸入新標題"
|
||||
inputNewFolderName: "輸入新資料夾的名稱"
|
||||
circularReferenceFolder: "目標文件夾是您要移動的文件夾的子文件夾。"
|
||||
hasChildFilesOrFolders: "此文件夾不是空的,無法刪除。"
|
||||
hasChildFilesOrFolders: "此資料夾不是空的,無法刪除。"
|
||||
copyUrl: "複製網址"
|
||||
rename: "重新命名"
|
||||
avatar: "大頭貼"
|
||||
|
@ -525,14 +525,14 @@ state: "狀態"
|
|||
sort: "排序"
|
||||
ascendingOrder: "昇冪"
|
||||
descendingOrder: "降冪"
|
||||
scratchpad: "暫存記憶體"
|
||||
scratchpad: "AiScript控制台"
|
||||
scratchpadDescription: "AiScript控制台為AiScript提供了實驗環境。您可以在此編寫、執行和確認代碼與Firefish互動的结果。"
|
||||
output: "輸出"
|
||||
script: "腳本"
|
||||
disablePagesScript: "停用頁面的AiScript腳本"
|
||||
updateRemoteUser: "更新遠端使用者資訊"
|
||||
deleteAllFiles: "刪除所有檔案"
|
||||
deleteAllFilesConfirm: "要删除所有檔案嗎?"
|
||||
deleteAllFilesConfirm: "確定要刪除所有檔案嗎?"
|
||||
removeAllFollowing: "解除所有追蹤"
|
||||
removeAllFollowingDescription: "解除{host}所有的追蹤。在伺服器不再存在時執行。"
|
||||
userSuspended: "此使用者已被停用。"
|
||||
|
@ -773,7 +773,7 @@ gallery: "相簿"
|
|||
recentPosts: "最新貼文"
|
||||
popularPosts: "熱門的貼文"
|
||||
shareWithNote: "在貼文中分享"
|
||||
ads: "廣告"
|
||||
ads: "社群橫幅"
|
||||
expiration: "期限"
|
||||
memo: "備忘錄"
|
||||
priority: "優先級"
|
||||
|
@ -895,11 +895,11 @@ navbar: "導覽列"
|
|||
shuffle: "隨機"
|
||||
account: "帳戶"
|
||||
move: "移動"
|
||||
customKaTeXMacro: "自定義 KaTeX 宏"
|
||||
customKaTeXMacroDescription: "使用宏來輕鬆的輸入數學表達式吧!宏的用法與 LaTeX 中的命令定義相同。你可以使用 \\newcommand{\\
|
||||
name}{content} 或 \\newcommand{\\name}[number of arguments]{content} 來輸入數學表達式。舉個例子,\\
|
||||
newcommand{\\add}[2]{#1 + #2} 會將 \\add{3}{foo} 展開為 3 + foo。此外,宏名稱外的花括號 {} 可以被替換為圓括號
|
||||
() 和方括號 [],這會影響用於參數的括號。每行只能夠定義一個宏,無法在中間換行,且無效的行將被忽略。只支持簡單字符串替換功能,不支持高級語法,如條件分支等。"
|
||||
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: "您可以使用機器學習自動檢測敏感媒體並將其用於審核。 伺服器的負荷會稍微增加。"
|
||||
|
@ -991,6 +991,7 @@ _aboutFirefish:
|
|||
pleaseDonateToFirefish: 請考慮向 Firefish 贊助以支持其發展。
|
||||
pleaseDonateToHost: 還請考慮捐贈給您在使用的伺服器 {host},以支援龐大的運營成本。
|
||||
donateHost: 贊助給 {host}
|
||||
misskeyContributors: Misskey的貢獻者
|
||||
_nsfw:
|
||||
respect: "隱藏敏感內容"
|
||||
ignore: "不隱藏敏感內容"
|
||||
|
@ -1259,6 +1260,8 @@ _2fa:
|
|||
token: 兩步驟驗證金鑰
|
||||
registerTOTPBeforeKey: 請設置身份驗證器應用程式以註冊安全金鑰或密碼。
|
||||
renewTOTPOk: 重新配置
|
||||
step3Title: 輸入驗證碼
|
||||
securityKeyNotSupported: 您使用的瀏覧器不支援安全金鑰(Security key)。
|
||||
_permissions:
|
||||
"read:account": "查看我的帳戶資訊"
|
||||
"write:account": "更改我的帳戶資訊"
|
||||
|
@ -1803,6 +1806,7 @@ _deck:
|
|||
list: "清單"
|
||||
mentions: "提及"
|
||||
direct: "指定使用者"
|
||||
channel: 頻道
|
||||
secureMode: 安全模式(授權獲取)
|
||||
instanceSecurity: 伺服器安全性
|
||||
privateMode: 私人模式
|
||||
|
@ -1820,13 +1824,13 @@ adminCustomCssWarn: 除非你知道它的作用,否則請不要使用此設定
|
|||
CSS 正常工作。
|
||||
showUpdates: Firefish 更新時顯示彈出視窗
|
||||
recommendedInstances: 建議的伺服器
|
||||
caption: 自動字幕
|
||||
caption: 自動加上替代文字(alt)
|
||||
enterSendsMessage: 在 Messaging 中按 Return 發送消息 (如關閉則是 Ctrl + Return)
|
||||
migrationConfirm: "您確定要將你的帳戶遷移到 {account} 嗎? 一旦這樣做,你將無法復原,而你將無法再次正常使用您的帳戶。\n另外,請確保你已將此當前帳戶設置為您要遷移的帳戶。"
|
||||
customSplashIconsDescription: 每次用戶加載/重新加載頁面時,以換行符號分隔的自定啟動畫面圖標的網址將隨機顯示。請確保圖片位於靜態網址上,最好所有圖片解析度調整為
|
||||
192x192。
|
||||
accountMoved: '該使用者已遷移至新帳戶:'
|
||||
showAds: 顯示廣告
|
||||
showAds: 顯示社群橫幅
|
||||
noThankYou: 不用了,謝謝
|
||||
selectInstance: 選擇伺服器
|
||||
enableRecommendedTimeline: 啟用推薦時間線
|
||||
|
@ -1870,7 +1874,7 @@ hiddenTags: 隱藏主題標籤
|
|||
indexPosts: 索引貼文
|
||||
indexNotice: 現在開始索引。 這可能需要一段時間,請不要在一個小時內重啟你的伺服器。
|
||||
deleted: 已刪除
|
||||
editNote: 編輯筆記
|
||||
editNote: 編輯貼文
|
||||
edited: '於 {date} {time} 編輯'
|
||||
userSaysSomethingReason: '{name} 說了 {reason}'
|
||||
allowedInstancesDescription: 要加入聯邦白名單的服務器,每台伺服器用新行分隔(僅適用於私有模式)。
|
||||
|
@ -1893,7 +1897,7 @@ listsDesc: 清單可以創建一個只有您指定用戶的時間線。 可以
|
|||
flagSpeakAsCatDescription: 在喵咪模式下你的貼文會被喵化ヾ(•ω•`)o
|
||||
antennasDesc: "天線會顯示符合您設置條件的新貼文!\n 可以從時間線訪問它們。"
|
||||
expandOnNoteClick: 點擊以打開貼文
|
||||
expandOnNoteClickDesc: 如果禁用,您仍然可以通過右鍵單擊菜單或單擊時間戳來打開貼文。
|
||||
expandOnNoteClickDesc: 即使停用,您仍然可以從右鍵選單或單擊發文時間來打開貼文。
|
||||
hiddenTagsDescription: '列出您希望隱藏趨勢和探索的主題標籤(不帶 #)。 隱藏的主題標籤仍然可以通過其他方式發現。'
|
||||
userSaysSomethingReasonQuote: '{name} 引用了一篇包含 {reason} 的貼文'
|
||||
silencedInstancesDescription: 列出您想要靜音的伺服器的網址。 您列出的伺服器內的帳戶將被視為“沉默”,只能發出追隨請求,如果不追隨則不能提及本地帳戶。
|
||||
|
@ -1944,3 +1948,18 @@ _filters:
|
|||
withFile: 有檔案
|
||||
alt: 替代文字
|
||||
xl: 特大
|
||||
inputNotMatch: 輸入不一致
|
||||
delete2faConfirm: 二階段認證(2FA)將被完全刪除。是否繼續?
|
||||
_dialog:
|
||||
charactersBelow: 字數不足! 當前 {current} / 限制 {min}
|
||||
charactersExceeded: 超過字數限制! 當前 {current} / 限制 {max}
|
||||
_skinTones:
|
||||
yellow: 黃色
|
||||
exportZip: 匯出ZIP
|
||||
_feeds:
|
||||
atom: Atom
|
||||
rss: RSS
|
||||
emojiPackCreator: 表情包的作者
|
||||
importZip: 匯入ZIP
|
||||
delete2fa: 停用二階段認證(2FA)
|
||||
confirm: 確認
|
||||
|
|
|
@ -1 +1 @@
|
|||
980addc1fa9dedf79b18792b68feaa0cb21f9aa4
|
||||
c17fb8217b88ef3e4d4f0756a458e2114ba47088
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "firefish",
|
||||
"version": "1.0.5-dev12",
|
||||
"version": "1.0.4-beta31",
|
||||
"codename": "aqua",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://code.naskya.net/naskya/firefish"
|
||||
},
|
||||
"packageManager": "pnpm@8.7.4",
|
||||
"packageManager": "pnpm@8.7.6",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"rebuild": "pnpm run clean && ./scripts/build-greet.sh && pnpm -r --parallel run build && pnpm run gulp",
|
||||
|
|
|
@ -129,6 +129,7 @@
|
|||
"tar-stream": "^3.1.6",
|
||||
"tesseract.js": "^4.1.1",
|
||||
"tinycolor2": "1.6.0",
|
||||
"tinyld": "^1.3.4",
|
||||
"tmp": "0.2.1",
|
||||
"twemoji-parser": "14.0.0",
|
||||
"typeorm": "0.3.17",
|
||||
|
@ -140,7 +141,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@swc/cli": "^0.1.62",
|
||||
"@swc/core": "^1.3.75",
|
||||
"@swc/core": "1.3.78",
|
||||
"@types/adm-zip": "^0.5.0",
|
||||
"@types/bcryptjs": "2.4.2",
|
||||
"@types/escape-regexp": "0.0.1",
|
||||
|
|
7
packages/backend/src/@types/langdetect.d.ts
vendored
Normal file
7
packages/backend/src/@types/langdetect.d.ts
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
declare module "langdetect" {
|
||||
interface DetectResult {
|
||||
lang: string;
|
||||
prob: number;
|
||||
}
|
||||
export function detect(words: string): DetectResult[];
|
||||
}
|
|
@ -27,6 +27,7 @@ import {
|
|||
} from "@/misc/populate-emojis.js";
|
||||
import { db } from "@/db/postgre.js";
|
||||
import { IdentifiableError } from "@/misc/identifiable-error.js";
|
||||
import { detect as detectLanguage_ } from "tinyld";
|
||||
|
||||
export async function populatePoll(note: Note, meId: User["id"] | null) {
|
||||
const poll = await Polls.findOneByOrFail({ noteId: note.id });
|
||||
|
@ -201,6 +202,9 @@ export const NoteRepository = db.getRepository(Note).extend({
|
|||
note.emojis.concat(reactionEmojiNames),
|
||||
host,
|
||||
);
|
||||
|
||||
const lang =
|
||||
detectLanguage_(`${note.cw ?? ""}\n${note.text ?? ""}`) ?? "unknown";
|
||||
const reactionEmoji = await populateEmojis(reactionEmojiNames, host);
|
||||
const packed: Packed<"Note"> = await awaitAll({
|
||||
id: note.id,
|
||||
|
@ -260,6 +264,7 @@ export const NoteRepository = db.getRepository(Note).extend({
|
|||
: undefined,
|
||||
}
|
||||
: {}),
|
||||
lang: lang,
|
||||
});
|
||||
|
||||
if (packed.user.isCat && packed.user.speakAsCat && packed.text) {
|
||||
|
|
|
@ -87,8 +87,12 @@ export default define(meta, paramDef, async (ps, user) => {
|
|||
.andWhere(
|
||||
new Brackets((qb) => {
|
||||
qb.where("note.userId = :meId", { meId: user.id });
|
||||
if (hasFollowing)
|
||||
qb.orWhere(`note.userId IN (${followingQuery.getQuery()})`);
|
||||
if (hasFollowing) {
|
||||
qb.orWhere(
|
||||
`note.userId IN (${followingQuery.getQuery()})`,
|
||||
followingQuery.getParameters(),
|
||||
);
|
||||
}
|
||||
}),
|
||||
)
|
||||
.innerJoinAndSelect("note.user", "user")
|
||||
|
|
|
@ -1,19 +1,13 @@
|
|||
import { Entity } from "megalodon";
|
||||
import config from "@/config/index.js";
|
||||
import { fetchMeta } from "@/misc/fetch-meta.js";
|
||||
import { Users, Notes } from "@/models/index.js";
|
||||
import { IsNull } from "typeorm";
|
||||
import { MAX_NOTE_TEXT_LENGTH, FILE_TYPE_BROWSERSAFE } from "@/const.js";
|
||||
|
||||
export async function getInstance(
|
||||
response: Entity.Instance,
|
||||
contact: Entity.Account,
|
||||
) {
|
||||
const [meta, totalUsers, totalStatuses] = await Promise.all([
|
||||
fetchMeta(true),
|
||||
Users.count({ where: { host: IsNull() } }),
|
||||
Notes.count({ where: { userHost: IsNull() } }),
|
||||
]);
|
||||
const meta = await fetchMeta(true);
|
||||
|
||||
return {
|
||||
uri: response.uri,
|
||||
|
@ -27,8 +21,8 @@ export async function getInstance(
|
|||
version: `3.0.0 (compatible; Firefish ${config.version})`,
|
||||
urls: response.urls,
|
||||
stats: {
|
||||
user_count: await totalUsers,
|
||||
status_count: await totalStatuses,
|
||||
user_count: response.stats.user_count,
|
||||
status_count: response.stats.status_count,
|
||||
domain_count: response.stats.domain_count,
|
||||
},
|
||||
thumbnail: response.thumbnail || "/static-assets/transparent.png",
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, onMounted, ref } from "vue";
|
||||
import { vibrate } from "@/scripts/vibrate";
|
||||
|
||||
const props = defineProps<{
|
||||
type?: "button" | "submit" | "reset";
|
||||
|
@ -93,6 +94,8 @@ function onMousedown(evt: MouseEvent): void {
|
|||
circleCenterY,
|
||||
);
|
||||
|
||||
vibrate(10);
|
||||
|
||||
window.setTimeout(() => {
|
||||
ripple.style.transform = "scale(" + scale / 2 + ")";
|
||||
}, 1);
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
<button
|
||||
v-for="emoji in searchResultCustom"
|
||||
:key="emoji.id"
|
||||
v-vibrate="50"
|
||||
class="_button item"
|
||||
:title="emoji.name"
|
||||
tabindex="0"
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
<div class="title"><slot name="header"></slot></div>
|
||||
<div class="divider"></div>
|
||||
<button
|
||||
v-vibrate="5"
|
||||
class="_button"
|
||||
:aria-expanded="showBody"
|
||||
:aria-controls="bodyId"
|
||||
|
|
|
@ -69,6 +69,7 @@ import { i18n } from "@/i18n";
|
|||
import { $i } from "@/account";
|
||||
import { getUserMenu } from "@/scripts/get-user-menu";
|
||||
import { useRouter } from "@/router";
|
||||
import { vibrate } from "@/scripts/vibrate";
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
|
@ -157,6 +158,7 @@ async function onClick() {
|
|||
await os.api("following/create", {
|
||||
userId: props.user.id,
|
||||
});
|
||||
vibrate([30, 40, 100]);
|
||||
hasPendingFollowRequestFromYou.value = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
"
|
||||
class="hpaizdrt"
|
||||
:style="bg"
|
||||
@click.stop="os.pageWindow(instanceInfoUrl)"
|
||||
@click.stop="openServerInfo"
|
||||
>
|
||||
<img class="icon" :src="getInstanceIcon(instance)" aria-hidden="true" />
|
||||
<span class="name">{{ instance.name }}</span>
|
||||
|
@ -19,7 +19,8 @@ import { ref } from "vue";
|
|||
import { instanceName, version } from "@/config";
|
||||
import { instance as Instance } from "@/instance";
|
||||
import { getProxiedImageUrlNullable } from "@/scripts/media-proxy";
|
||||
import * as os from "@/os";
|
||||
import { defaultStore } from "@/store";
|
||||
import { pageWindow } from "@/os";
|
||||
|
||||
const props = defineProps<{
|
||||
instance?: {
|
||||
|
@ -93,6 +94,13 @@ function getInstanceIcon(instance): string {
|
|||
"/client-assets/dummy.png"
|
||||
);
|
||||
}
|
||||
|
||||
function openServerInfo() {
|
||||
if (!defaultStore.state.openServerInfo) return;
|
||||
const instanceInfoUrl =
|
||||
props.host == null ? "/about" : `/instance-info/${props.host}`;
|
||||
pageWindow(instanceInfoUrl);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
<div>
|
||||
<div
|
||||
ref="itemsEl"
|
||||
v-vibrate="5"
|
||||
class="rrevdjwt _popup _shadow"
|
||||
:class="{ center: align === 'center', asDrawer }"
|
||||
:style="{
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
ref="el"
|
||||
v-hotkey="keymap"
|
||||
v-size="{ max: [500, 350] }"
|
||||
v-vibrate="5"
|
||||
:aria-label="accessibleLabel"
|
||||
class="tkcbzcuz note-container"
|
||||
:tabindex="!isDeleted ? '-1' : null"
|
||||
|
@ -225,9 +226,9 @@
|
|||
isForeignLanguage &&
|
||||
translation == null
|
||||
"
|
||||
v-tooltip.noDelay.bottom="i18n.ts.translate"
|
||||
class="button _button"
|
||||
@click.stop="translate"
|
||||
v-tooltip.noDelay.bottom="i18n.ts.translate"
|
||||
>
|
||||
<i class="ph-translate ph-bold ph-lg"></i>
|
||||
</button>
|
||||
|
@ -353,7 +354,12 @@ const isMyRenote = $i && $i.id === note.value.userId;
|
|||
const showContent = ref(false);
|
||||
const isDeleted = ref(false);
|
||||
const muted = ref(
|
||||
getWordSoftMute(note.value, $i, defaultStore.state.mutedWords),
|
||||
getWordSoftMute(
|
||||
note.value,
|
||||
$i,
|
||||
defaultStore.state.mutedWords,
|
||||
defaultStore.state.mutedLangs,
|
||||
),
|
||||
);
|
||||
const translation = ref(null);
|
||||
const translating = ref(false);
|
||||
|
@ -385,8 +391,8 @@ const isForeignLanguage: boolean =
|
|||
|
||||
async function translate_(noteId, targetLang: string) {
|
||||
return await os.api("notes/translate", {
|
||||
noteId: noteId,
|
||||
targetLang: targetLang,
|
||||
noteId,
|
||||
targetLang,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -209,7 +209,12 @@ const reactButton = ref<HTMLElement>();
|
|||
const showContent = ref(false);
|
||||
const isDeleted = ref(false);
|
||||
const muted = ref(
|
||||
getWordSoftMute(note.value, $i, defaultStore.state.mutedWords),
|
||||
getWordSoftMute(
|
||||
note.value,
|
||||
$i,
|
||||
defaultStore.state.mutedWords,
|
||||
defaultStore.state.mutedLangs,
|
||||
),
|
||||
);
|
||||
const translation = ref(null);
|
||||
const translating = ref(false);
|
||||
|
|
|
@ -130,9 +130,9 @@
|
|||
isForeignLanguage &&
|
||||
translation == null
|
||||
"
|
||||
v-tooltip.noDelay.bottom="i18n.ts.translate"
|
||||
class="button _button"
|
||||
@click.stop="translate"
|
||||
v-tooltip.noDelay.bottom="i18n.ts.translate"
|
||||
>
|
||||
<i class="ph-translate ph-bold ph-lg"></i>
|
||||
</button>
|
||||
|
@ -266,7 +266,12 @@ const appearNote = computed(() =>
|
|||
);
|
||||
const isDeleted = ref(false);
|
||||
const muted = ref(
|
||||
getWordSoftMute(note.value, $i, defaultStore.state.mutedWords),
|
||||
getWordSoftMute(
|
||||
note.value,
|
||||
$i,
|
||||
defaultStore.state.mutedWords,
|
||||
defaultStore.state.mutedLangs,
|
||||
),
|
||||
);
|
||||
const translation = ref(null);
|
||||
const translating = ref(false);
|
||||
|
@ -306,8 +311,8 @@ const isForeignLanguage: boolean =
|
|||
|
||||
async function translate_(noteId, targetLang: string) {
|
||||
return await os.api("notes/translate", {
|
||||
noteId: noteId,
|
||||
targetLang: targetLang,
|
||||
noteId,
|
||||
targetLang,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
>{{ maxTextLength - textLength }}</span
|
||||
>
|
||||
<span v-if="localOnly" class="local-only"
|
||||
><i class="ph-hand-fist ph-bold ph-lg"></i
|
||||
><i class="ph-users ph-bold ph-lg"></i
|
||||
></span>
|
||||
<button
|
||||
ref="visibilityButton"
|
||||
|
@ -286,6 +286,7 @@ import {
|
|||
import { uploadFile } from "@/scripts/upload";
|
||||
import { deepClone } from "@/scripts/clone";
|
||||
import { preprocess } from "@/scripts/preprocess";
|
||||
import { vibrate } from "@/scripts/vibrate";
|
||||
|
||||
const modal = inject("modal");
|
||||
|
||||
|
@ -948,6 +949,7 @@ async function post() {
|
|||
text: err.message + "\n" + (err as any).id,
|
||||
});
|
||||
});
|
||||
vibrate([10, 20, 10, 20, 10, 20, 60]);
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
v-if="count > 0"
|
||||
ref="buttonRef"
|
||||
v-ripple="canToggle"
|
||||
v-vibrate="[10, 30, 40]"
|
||||
class="hkzvhatu _button"
|
||||
:class="{
|
||||
reacted: note.myReaction == reaction,
|
||||
|
|
|
@ -32,6 +32,7 @@ import { useTooltip } from "@/scripts/use-tooltip";
|
|||
import { i18n } from "@/i18n";
|
||||
import { defaultStore } from "@/store";
|
||||
import type { MenuItem } from "@/types/menu";
|
||||
import { vibrate } from "@/scripts/vibrate";
|
||||
|
||||
const props = defineProps<{
|
||||
note: misskey.entities.Note;
|
||||
|
@ -194,9 +195,10 @@ const renote = (viaKeyboard = false, ev?: MouseEvent) => {
|
|||
if (canRenote.value) {
|
||||
buttonActions.push({
|
||||
text: `${i18n.ts.renote} (${i18n.ts.local})`,
|
||||
icon: "ph-hand-fist ph-bold ph-lg",
|
||||
icon: "ph-users ph-bold ph-lg",
|
||||
danger: false,
|
||||
action: () => {
|
||||
vibrate([30, 30, 60]);
|
||||
os.api(
|
||||
"notes/create",
|
||||
props.note.visibility === "specified"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<template>
|
||||
<button
|
||||
v-tooltip.noDelay.bottom="i18n.ts._gallery.like"
|
||||
v-vibrate="[30, 50, 50]"
|
||||
class="button _button"
|
||||
@click.stop="star($event)"
|
||||
>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<button
|
||||
ref="buttonRef"
|
||||
v-tooltip.noDelay.bottom="i18n.ts._gallery.like"
|
||||
v-vibrate="[30, 50, 50]"
|
||||
class="button _button"
|
||||
:class="$style.root"
|
||||
@click.stop="toggleStar($event)"
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<span v-if="note.localOnly" :class="$style.localOnly"
|
||||
><i
|
||||
v-tooltip="i18n.ts._visibility.localOnly"
|
||||
class="ph-hand-fist ph-bold ph-lg"
|
||||
class="ph-users ph-bold ph-lg"
|
||||
></i
|
||||
></span>
|
||||
</template>
|
||||
|
|
|
@ -97,7 +97,7 @@
|
|||
@click="localOnly = !localOnly"
|
||||
>
|
||||
<div :class="$style.icon">
|
||||
<i class="ph-hand-fist ph-bold ph-lg"></i>
|
||||
<i class="ph-users ph-bold ph-lg"></i>
|
||||
</div>
|
||||
<div :class="$style.body">
|
||||
<span :class="$style.itemTitle">{{
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
<button
|
||||
v-if="displayBackButton"
|
||||
v-tooltip.noDelay="i18n.ts.goBack"
|
||||
v-vibrate="5"
|
||||
class="_buttonIcon button icon backButton"
|
||||
@click.stop="goBack()"
|
||||
@touchstart="preventDrag"
|
||||
|
@ -20,6 +21,7 @@
|
|||
</button>
|
||||
<MkAvatar
|
||||
v-if="narrow && props.displayMyAvatar && $i"
|
||||
v-vibrate="5"
|
||||
class="avatar button"
|
||||
:user="$i"
|
||||
:disable-preview="true"
|
||||
|
@ -77,6 +79,7 @@
|
|||
v-for="tab in tabs"
|
||||
:ref="(el) => (tabRefs[tab.key] = el)"
|
||||
v-tooltip.noDelay="tab.title"
|
||||
v-vibrate="5"
|
||||
class="tab _button"
|
||||
:class="{
|
||||
active: tab.key != null && tab.key === props.tab,
|
||||
|
@ -108,6 +111,7 @@
|
|||
<template v-for="action in actions">
|
||||
<button
|
||||
v-tooltip.noDelay="action.text"
|
||||
v-vibrate="5"
|
||||
class="_buttonIcon button"
|
||||
:class="{ highlighted: action.highlighted }"
|
||||
@click.stop="action.handler"
|
||||
|
|
|
@ -12,6 +12,7 @@ import clickAnime from "./click-anime";
|
|||
import panel from "./panel";
|
||||
import adaptiveBorder from "./adaptive-border";
|
||||
import focus from "./focus";
|
||||
import vibrate from "./vibrate";
|
||||
|
||||
export default function (app: App) {
|
||||
app.directive("userPreview", userPreview);
|
||||
|
@ -27,4 +28,5 @@ export default function (app: App) {
|
|||
app.directive("panel", panel);
|
||||
app.directive("adaptive-border", adaptiveBorder);
|
||||
app.directive("focus", focus);
|
||||
app.directive("vibrate", vibrate);
|
||||
}
|
||||
|
|
11
packages/client/src/directives/vibrate.ts
Normal file
11
packages/client/src/directives/vibrate.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import type { Directive } from "vue";
|
||||
import { vibrate } from "../scripts/vibrate";
|
||||
|
||||
export default {
|
||||
mounted(el, binding) {
|
||||
const pattern = (binding.value as VibratePattern) ?? 20;
|
||||
el.addEventListener("mousedown", () => {
|
||||
vibrate(pattern);
|
||||
});
|
||||
},
|
||||
} as Directive;
|
|
@ -431,7 +431,7 @@ const headerActions = computed(() => [
|
|||
const headerTabs = computed(() => [
|
||||
{
|
||||
key: "local",
|
||||
icon: "ph-hand-fist ph-bold ph-lg",
|
||||
icon: "ph-users ph-bold ph-lg",
|
||||
title: i18n.ts.local,
|
||||
},
|
||||
{
|
||||
|
|
|
@ -46,14 +46,13 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from "vue";
|
||||
import { onMounted, ref } from "vue";
|
||||
import XForm from "./auth.form.vue";
|
||||
import MkSignin from "@/components/MkSignin.vue";
|
||||
import MkKeyValue from "@/components/MkKeyValue.vue";
|
||||
import * as os from "@/os";
|
||||
import { login } from "@/account";
|
||||
import { $i, login } from "@/account";
|
||||
import { i18n } from "@/i18n";
|
||||
import { $i } from "@/account";
|
||||
|
||||
const props = defineProps<{
|
||||
token: string;
|
||||
|
@ -102,11 +101,7 @@ const accepted = () => {
|
|||
const isMastodon = !!getUrlParams().mastodon;
|
||||
if (session.value.app.callbackUrl && isMastodon) {
|
||||
const redirectUri = decodeURIComponent(getUrlParams().redirect_uri);
|
||||
if (
|
||||
!session.value.app.callbackUrl
|
||||
.split("\n")
|
||||
.some((p) => p === redirectUri)
|
||||
) {
|
||||
if (!session.value.app.callbackUrl.split("\n").includes(redirectUri)) {
|
||||
state.value = "fetch-session-error";
|
||||
fetching.value = false;
|
||||
throw new Error("Callback URI doesn't match registered app");
|
||||
|
|
|
@ -120,6 +120,7 @@ import {
|
|||
import * as os from "@/os";
|
||||
import { stream } from "@/stream";
|
||||
import * as sound from "@/scripts/sound";
|
||||
import { vibrate } from "@/scripts/vibrate";
|
||||
import { i18n } from "@/i18n";
|
||||
import { $i } from "@/account";
|
||||
import { defaultStore } from "@/store";
|
||||
|
@ -251,6 +252,7 @@ function onDrop(ev: DragEvent): void {
|
|||
|
||||
function onMessage(message) {
|
||||
sound.play("chat");
|
||||
vibrate([30, 30, 30]);
|
||||
|
||||
const _isBottom = isBottomVisible(rootEl.value, 64);
|
||||
|
||||
|
|
|
@ -89,6 +89,9 @@
|
|||
<FormSwitch v-model="detectPostLanguage" class="_formBlock">{{
|
||||
i18n.ts.detectPostLanguage
|
||||
}}</FormSwitch>
|
||||
<FormSwitch v-model="openServerInfo" class="_formBlock">{{
|
||||
i18n.ts.openServerInfo
|
||||
}}</FormSwitch>
|
||||
|
||||
<FormSelect v-model="serverDisconnectedBehavior" class="_formBlock">
|
||||
<template #label>{{ i18n.ts.whenServerDisconnected }}</template>
|
||||
|
@ -139,6 +142,12 @@
|
|||
class="_formBlock"
|
||||
>{{ i18n.ts.disableShowingAnimatedImages }}</FormSwitch
|
||||
>
|
||||
<FormSwitch
|
||||
v-model="vibrate"
|
||||
class="_formBlock"
|
||||
@click="demoVibrate"
|
||||
>{{ i18n.ts.vibrate }}
|
||||
</FormSwitch>
|
||||
<FormRadios v-model="fontSize" class="_formBlock">
|
||||
<template #label>{{ i18n.ts.fontSize }}</template>
|
||||
<option :value="null">
|
||||
|
@ -340,7 +349,7 @@ import FormSection from "@/components/form/section.vue";
|
|||
import FormLink from "@/components/form/link.vue";
|
||||
import MkLink from "@/components/MkLink.vue";
|
||||
import { langs } from "@/config";
|
||||
import { defaultStore } from "@/store";
|
||||
import { ColdDeviceStorage, defaultStore } from "@/store";
|
||||
import * as os from "@/os";
|
||||
import { unisonReload } from "@/scripts/unison-reload";
|
||||
import { i18n } from "@/i18n";
|
||||
|
@ -362,6 +371,10 @@ async function reloadAsk() {
|
|||
unisonReload();
|
||||
}
|
||||
|
||||
function demoVibrate() {
|
||||
window.navigator.vibrate(100);
|
||||
}
|
||||
|
||||
const overridedDeviceKind = computed(
|
||||
defaultStore.makeGetterSetter("overridedDeviceKind"),
|
||||
);
|
||||
|
@ -398,6 +411,7 @@ const disableDrawer = computed(defaultStore.makeGetterSetter("disableDrawer"));
|
|||
const disableShowingAnimatedImages = computed(
|
||||
defaultStore.makeGetterSetter("disableShowingAnimatedImages"),
|
||||
);
|
||||
const vibrate = computed(ColdDeviceStorage.makeGetterSetter("vibrate"));
|
||||
const loadRawImages = computed(defaultStore.makeGetterSetter("loadRawImages"));
|
||||
const imageNewTab = computed(defaultStore.makeGetterSetter("imageNewTab"));
|
||||
const nsfw = computed(defaultStore.makeGetterSetter("nsfw"));
|
||||
|
@ -464,6 +478,9 @@ const emphasizeFollowed = computed(
|
|||
const disableToast = computed(defaultStore.makeGetterSetter("disableToast"));
|
||||
const hideMyIcon = computed(defaultStore.makeGetterSetter("hideMyIcon"));
|
||||
const hideMyName = computed(defaultStore.makeGetterSetter("hideMyName"));
|
||||
const openServerInfo = computed(
|
||||
defaultStore.makeGetterSetter("openServerInfo"),
|
||||
);
|
||||
|
||||
watch(swipeOnDesktop, () => {
|
||||
defaultStore.set("swipeOnMobile", true);
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<template #empty
|
||||
><FormInfo>{{ i18n.ts.noUsers }}</FormInfo></template
|
||||
>
|
||||
<template #default="{ items }">
|
||||
<template #default="{ items }" class="_formlinks">
|
||||
<FormLink
|
||||
v-for="mute in items"
|
||||
:key="mute.id"
|
||||
|
@ -25,7 +25,7 @@
|
|||
<template #empty
|
||||
><FormInfo>{{ i18n.ts.noUsers }}</FormInfo></template
|
||||
>
|
||||
<template #default="{ items }">
|
||||
<template #default="{ items }" class="_formlinks">
|
||||
<FormLink
|
||||
v-for="block in items"
|
||||
:key="block.id"
|
||||
|
|
|
@ -117,6 +117,8 @@ const defaultStoreSaveKeys: (keyof (typeof defaultStore)["state"])[] = [
|
|||
"enableEmojiReactions",
|
||||
"showEmojisInReactionNotifications",
|
||||
"showTimelineReplies",
|
||||
"detectPostLanguage",
|
||||
"openServerInfo",
|
||||
];
|
||||
const coldDeviceStorageSaveKeys: (keyof typeof ColdDeviceStorage.default)[] = [
|
||||
"lightTheme",
|
||||
|
@ -124,6 +126,7 @@ const coldDeviceStorageSaveKeys: (keyof typeof ColdDeviceStorage.default)[] = [
|
|||
"syncDeviceDarkMode",
|
||||
"plugins",
|
||||
"mediaVolume",
|
||||
"vibrate",
|
||||
"sound_masterVolume",
|
||||
"sound_note",
|
||||
"sound_noteMy",
|
||||
|
|
|
@ -17,6 +17,17 @@
|
|||
}}</template
|
||||
>
|
||||
</FormTextarea>
|
||||
<MkInfo class="_formBlock">{{
|
||||
i18n.ts._wordMute.langDescription
|
||||
}}</MkInfo>
|
||||
<FormTextarea v-model="softMutedLangs" class="_formBlock">
|
||||
<span>{{ i18n.ts._wordMute.muteLangs }}</span>
|
||||
<template #caption
|
||||
>{{ i18n.ts._wordMute.muteLangsDescription }}<br />{{
|
||||
i18n.ts._wordMute.muteLangsDescription2
|
||||
}}</template
|
||||
>
|
||||
</FormTextarea>
|
||||
</div>
|
||||
<div v-show="tab === 'hard'">
|
||||
<MkInfo class="_formBlock"
|
||||
|
@ -76,6 +87,7 @@ const render = (mutedWords) =>
|
|||
|
||||
const tab = ref("soft");
|
||||
const softMutedWords = ref(render(defaultStore.state.mutedWords));
|
||||
const softMutedLangs = ref(render(defaultStore.state.mutedLangs));
|
||||
const hardMutedWords = ref(render($i!.mutedWords));
|
||||
const hardWordMutedNotesCount = ref(null);
|
||||
const changed = ref(false);
|
||||
|
@ -88,6 +100,10 @@ watch(softMutedWords, () => {
|
|||
changed.value = true;
|
||||
});
|
||||
|
||||
watch(softMutedLangs, () => {
|
||||
changed.value = true;
|
||||
});
|
||||
|
||||
watch(hardMutedWords, () => {
|
||||
changed.value = true;
|
||||
});
|
||||
|
@ -134,9 +150,10 @@ async function save() {
|
|||
return lines;
|
||||
};
|
||||
|
||||
let softMutes, hardMutes;
|
||||
let softMutes, softMLangs, hardMutes;
|
||||
try {
|
||||
softMutes = parseMutes(softMutedWords.value, i18n.ts._wordMute.soft);
|
||||
softMLangs = parseMutes(softMutedLangs.value, i18n.ts._wordMute.lang);
|
||||
hardMutes = parseMutes(hardMutedWords.value, i18n.ts._wordMute.hard);
|
||||
} catch (err) {
|
||||
// already displayed error message in parseMutes
|
||||
|
@ -144,6 +161,7 @@ async function save() {
|
|||
}
|
||||
|
||||
defaultStore.set("mutedWords", softMutes);
|
||||
defaultStore.set("mutedLangs", softMLangs);
|
||||
await os.api("i/update", {
|
||||
mutedWords: hardMutes,
|
||||
});
|
||||
|
|
|
@ -65,7 +65,7 @@ const visibleUsers = ref([] as Misskey.entities.User[]);
|
|||
|
||||
async function init() {
|
||||
let noteText = "";
|
||||
if (title.value) noteText += `[ ${title.value} ]\n`;
|
||||
if (title.value) noteText += `${title.value}\n`;
|
||||
// Googleニュース対策
|
||||
if (text?.startsWith(`${title.value}.\n`))
|
||||
noteText += text.replace(`${title.value}.\n`, "");
|
||||
|
|
|
@ -6,6 +6,19 @@ export interface Muted {
|
|||
|
||||
const NotMuted = { muted: false, matched: [] };
|
||||
|
||||
function checkLangMute(
|
||||
note: NoteLike,
|
||||
mutedLangs: Array<string | string[]>,
|
||||
): Muted {
|
||||
const mutedLangList = new Set(
|
||||
mutedLangs.reduce((arr, x) => [...arr, ...(Array.isArray(x) ? x : [x])]),
|
||||
);
|
||||
if (mutedLangList.has((note.lang?.[0]?.lang || "").split("-")[0])) {
|
||||
return { muted: true, matched: [note.lang?.[0]?.lang] };
|
||||
}
|
||||
return NotMuted;
|
||||
}
|
||||
|
||||
function checkWordMute(
|
||||
note: NoteLike,
|
||||
mutedWords: Array<string | string[]>,
|
||||
|
@ -62,6 +75,7 @@ export function getWordSoftMute(
|
|||
note: Record<string, any>,
|
||||
me: Record<string, any> | null | undefined,
|
||||
mutedWords: Array<string | string[]>,
|
||||
mutedLangs: Array<string | string[]>,
|
||||
): Muted {
|
||||
// 自分自身
|
||||
if (me && note.userId === me.id) {
|
||||
|
@ -91,6 +105,29 @@ export function getWordSoftMute(
|
|||
}
|
||||
}
|
||||
}
|
||||
if (mutedLangs.length > 0) {
|
||||
let noteLangMuted = checkLangMute(note, mutedLangs);
|
||||
if (noteLangMuted.muted) {
|
||||
noteLangMuted.what = "note";
|
||||
return noteLangMuted;
|
||||
}
|
||||
|
||||
if (note.renote) {
|
||||
let renoteLangMuted = checkLangMute(note, mutedLangs);
|
||||
if (renoteLangMuted.muted) {
|
||||
renoteLangMuted.what = note.text == null ? "renote" : "quote";
|
||||
return renoteLangMuted;
|
||||
}
|
||||
}
|
||||
|
||||
if (note.reply) {
|
||||
let replyLangMuted = checkLangMute(note, mutedLangs);
|
||||
if (replyLangMuted.muted) {
|
||||
replyLangMuted.what = "reply";
|
||||
return replyLangMuted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NotMuted;
|
||||
}
|
||||
|
|
|
@ -239,8 +239,8 @@ export function getNoteMenu(props: {
|
|||
|
||||
async function translate_(noteId: number, targetLang: string) {
|
||||
return await os.api("notes/translate", {
|
||||
noteId: noteId,
|
||||
targetLang: targetLang,
|
||||
noteId,
|
||||
targetLang,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -2,9 +2,11 @@ import { defineAsyncComponent } from "vue";
|
|||
import { $i } from "@/account";
|
||||
import { i18n } from "@/i18n";
|
||||
import { popup } from "@/os";
|
||||
import { vibrate } from "@/scripts/vibrate";
|
||||
|
||||
export function pleaseLogin(path?: string) {
|
||||
if ($i) return;
|
||||
vibrate(100);
|
||||
|
||||
popup(
|
||||
defineAsyncComponent(() => import("@/components/MkSigninDialog.vue")),
|
||||
|
|
6
packages/client/src/scripts/vibrate.ts
Normal file
6
packages/client/src/scripts/vibrate.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
import { ColdDeviceStorage } from "@/store";
|
||||
|
||||
export function vibrate(pattern: VibratePattern) {
|
||||
if (!ColdDeviceStorage.get("vibrate") || !window.navigator.vibrate) return;
|
||||
window.navigator.vibrate(pattern);
|
||||
}
|
|
@ -101,6 +101,10 @@ export const defaultStore = markRaw(
|
|||
where: "account",
|
||||
default: [],
|
||||
},
|
||||
mutedLangs: {
|
||||
where: "account",
|
||||
default: [],
|
||||
},
|
||||
mutedAds: {
|
||||
where: "account",
|
||||
default: [] as string[],
|
||||
|
@ -385,6 +389,10 @@ export const defaultStore = markRaw(
|
|||
where: "device",
|
||||
default: false,
|
||||
},
|
||||
openServerInfo: {
|
||||
where: "device",
|
||||
default: true,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
|
@ -414,6 +422,7 @@ export class ColdDeviceStorage {
|
|||
syncDeviceDarkMode: true,
|
||||
plugins: [] as Plugin[],
|
||||
mediaVolume: 0.5,
|
||||
vibrate: true,
|
||||
sound_masterVolume: 0.3,
|
||||
sound_note: { type: "none", volume: 0 },
|
||||
sound_noteMy: { type: "syuilo/up", volume: 1 },
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
|
||||
<button
|
||||
v-if="!isDesktop && !isMobile"
|
||||
v-vibrate="5"
|
||||
class="widgetButton _button"
|
||||
@click="widgetsShowing = true"
|
||||
>
|
||||
|
@ -33,6 +34,7 @@
|
|||
|
||||
<div v-if="isMobile" class="buttons">
|
||||
<button
|
||||
v-vibrate="5"
|
||||
:aria-label="i18n.t('menu')"
|
||||
class="button nav _button"
|
||||
@click="drawerMenuShowing = true"
|
||||
|
@ -48,6 +50,7 @@
|
|||
</div>
|
||||
</button>
|
||||
<button
|
||||
v-vibrate="5"
|
||||
:aria-label="i18n.t('home')"
|
||||
class="button home _button"
|
||||
@click="
|
||||
|
@ -65,6 +68,7 @@
|
|||
</div>
|
||||
</button>
|
||||
<button
|
||||
v-vibrate="5"
|
||||
:aria-label="i18n.t('notifications')"
|
||||
class="button notifications _button"
|
||||
@click="
|
||||
|
@ -73,6 +77,7 @@
|
|||
"
|
||||
>
|
||||
<div
|
||||
v-vibrate="5"
|
||||
class="button-wrapper"
|
||||
:class="buttonAnimIndex === 1 ? 'on' : ''"
|
||||
>
|
||||
|
@ -87,6 +92,7 @@
|
|||
</button>
|
||||
<button
|
||||
v-if="useAccountButton"
|
||||
v-vibrate="5"
|
||||
:aria-label="i18n.t('accounts')"
|
||||
class="button messaging _button"
|
||||
@click="openAccountMenu"
|
||||
|
@ -100,6 +106,7 @@
|
|||
</button>
|
||||
<button
|
||||
v-else
|
||||
v-vibrate="5"
|
||||
:aria-label="i18n.t('messaging')"
|
||||
class="button messaging _button"
|
||||
@click="
|
||||
|
@ -122,6 +129,7 @@
|
|||
</button>
|
||||
<button
|
||||
v-if="useReloadButton"
|
||||
v-vibrate="5"
|
||||
:aria-label="i18n.t('reload')"
|
||||
class="button widget _button"
|
||||
@click="reload"
|
||||
|
@ -132,6 +140,7 @@
|
|||
</button>
|
||||
<button
|
||||
v-else
|
||||
v-vibrate="5"
|
||||
:aria-label="i18n.t('_deck._columns.widgets')"
|
||||
class="button widget _button"
|
||||
@click="widgetsShowing = true"
|
||||
|
@ -144,6 +153,7 @@
|
|||
|
||||
<button
|
||||
v-if="isMobile && mainRouter.currentRoute.value.name === 'index'"
|
||||
v-vibrate="5"
|
||||
ref="postButton"
|
||||
:aria-label="i18n.t('note')"
|
||||
class="postButton button post _button"
|
||||
|
@ -266,7 +276,12 @@ provideMetadataReceiver((info) => {
|
|||
|
||||
const menuIndicated = computed(() => {
|
||||
for (const def in navbarItemDef) {
|
||||
if (def === "notifications") continue; // 通知は下にボタンとして表示されてるから
|
||||
if (def === "notifications") continue; // Notifications & Messaging are bottom nav buttons and thus shouldn't be highlighted in the sidebar
|
||||
if (
|
||||
!defaultStore.state.replaceChatButtonWithAccountButton &&
|
||||
def === "messaging"
|
||||
)
|
||||
continue;
|
||||
if (navbarItemDef[def].indicated) return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
@ -23,7 +23,8 @@
|
|||
"@microsoft/api-extractor": "^7.36.0",
|
||||
"@microsoft/api-documenter": "^7.22.21",
|
||||
"@swc/cli": "^0.1.62",
|
||||
"@swc/core": "^1.3.62",
|
||||
"@swc/core": "1.3.78",
|
||||
"@types/jest": "^27.4.0",
|
||||
"@types/node": "20.3.1",
|
||||
"mock-socket": "^9.0.8",
|
||||
"ts-node": "10.4.0",
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@swc/cli": "^0.1.62",
|
||||
"@swc/core": "^1.3.62",
|
||||
"@swc/core": "1.3.78",
|
||||
"@swc/core-android-arm64": "1.3.11",
|
||||
"firefish-js": "workspace:*",
|
||||
"idb-keyval": "^6.2.1",
|
||||
|
|
2868
pnpm-lock.yaml
2868
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue