Firefish v1.0.5-dev19

This commit is contained in:
naskya 2023-10-19 08:49:03 +09:00
parent bd7273fd5e
commit be83fa27ac
Signed by: naskya
GPG key ID: 164DFF24E2D40139
40 changed files with 750 additions and 654 deletions

View file

@ -526,12 +526,12 @@ objectStorageBaseUrl: "Basis-URL"
objectStorageBaseUrlDesc: "Die als Referenz verwendete URL. Verwendest du einen CDN objectStorageBaseUrlDesc: "Die als Referenz verwendete URL. Verwendest du einen CDN
oder Proxy, gib dessen URL an. \nFür S3 verwende 'https://<bucket>.s3.amazonaws.com'. oder Proxy, gib dessen URL an. \nFür S3 verwende 'https://<bucket>.s3.amazonaws.com'.
Für GCS o.ä. verwende 'https://storage.googleapis.com/<bucket>'." Für GCS o.ä. verwende 'https://storage.googleapis.com/<bucket>'."
objectStorageBucket: "Eimer" objectStorageBucket: "Bucket"
objectStorageBucketDesc: "Bitte gib den Namen des Buckets an, der bei deinem Anbieter objectStorageBucketDesc: "Bitte gib den Namen des Buckets an, der bei deinem Anbieter
verwendet wird." verwendet wird."
objectStoragePrefix: "Prefix" objectStoragePrefix: "Prefix"
objectStoragePrefixDesc: "Dateien werden in Ordnern unter diesem Prefix gespeichert." objectStoragePrefixDesc: "Dateien werden in Ordnern unter diesem Prefix gespeichert."
objectStorageEndpoint: "Limit" objectStorageEndpoint: "Endpunkt"
objectStorageEndpointDesc: "Im Falle von S3 leerlassen, für andere Anbieter den relevanten objectStorageEndpointDesc: "Im Falle von S3 leerlassen, für andere Anbieter den relevanten
Endpoint im Format „<host>“ oder „<host>:<port>“ angeben." Endpoint im Format „<host>“ oder „<host>:<port>“ angeben."
objectStorageRegion: "Region" objectStorageRegion: "Region"
@ -1034,6 +1034,7 @@ _accountDelete:
_ad: _ad:
back: "Zurück" back: "Zurück"
reduceFrequencyOfThisAd: "Diese Werbeanzeige weniger anzeigen" reduceFrequencyOfThisAd: "Diese Werbeanzeige weniger anzeigen"
adsBy: Community-Banner von {by}
_forgotPassword: _forgotPassword:
enterEmail: "Gib die Email-Adresse ein, mit der du dich registriert hast. An diese enterEmail: "Gib die Email-Adresse ein, mit der du dich registriert hast. An diese
wird ein Link gesendet, mit dem du dein Passwort zurücksetzen kannst." wird ein Link gesendet, mit dem du dein Passwort zurücksetzen kannst."
@ -1240,6 +1241,7 @@ _wordMute:
soft: "Leicht" soft: "Leicht"
hard: "Schwer" hard: "Schwer"
mutedNotes: "Stummgeschaltete Beiträge" mutedNotes: "Stummgeschaltete Beiträge"
muteLangs: Stummgeschaltete Sprachen
_instanceMute: _instanceMute:
instanceMuteDescription: "Schaltet alle Beiträge/Boosts stumm, die von den gelisteten instanceMuteDescription: "Schaltet alle Beiträge/Boosts stumm, die von den gelisteten
Servern stammen, inklusive Antworten von Nutzern an einen Nutzer eines stummgeschalteten Servern stammen, inklusive Antworten von Nutzern an einen Nutzer eines stummgeschalteten
@ -2212,3 +2214,5 @@ indexable: Indexierbar
languageForTranslation: Übersetzungssprache veröffentlichen languageForTranslation: Übersetzungssprache veröffentlichen
openServerInfo: Anzeigen von Serverinformationen durch Anklicken des Server-Tickers openServerInfo: Anzeigen von Serverinformationen durch Anklicken des Server-Tickers
in einem Beitrag in einem Beitrag
vibrate: Vibrationen abspielen
clickToShowPatterns: Klicken um Modul-Muster anzuzeigen

View file

@ -616,7 +616,7 @@ emptyToDisableSmtpAuth: "ユーザー名とパスワードを空欄にするこ
smtpSecure: "SMTP 接続に暗黙的なSSL/TLSを使用する" smtpSecure: "SMTP 接続に暗黙的なSSL/TLSを使用する"
smtpSecureInfo: "STARTTLS使用時はオフにします。" smtpSecureInfo: "STARTTLS使用時はオフにします。"
testEmail: "配信テスト" testEmail: "配信テスト"
wordMute: "ワードミュート" wordMute: "単語または言語のミュート"
regexpError: "正規表現エラー" regexpError: "正規表現エラー"
regexpErrorDescription: "{tab}ワードミュートの{line}行目の正規表現にエラーが発生しました:" regexpErrorDescription: "{tab}ワードミュートの{line}行目の正規表現にエラーが発生しました:"
instanceMute: "サーバーミュート" instanceMute: "サーバーミュート"
@ -694,7 +694,7 @@ no: "いいえ"
driveFilesCount: "ドライブのファイル数" driveFilesCount: "ドライブのファイル数"
driveUsage: "ドライブ使用量" driveUsage: "ドライブ使用量"
noCrawle: "クローラーによるインデックスを拒否" noCrawle: "クローラーによるインデックスを拒否"
noCrawleDescription: "検索エンジンにあなたのプロフィールや投稿、ページなどのコンテンツを登録(インデックス)しないよう要請します。" noCrawleDescription: "Web検索にあなたのプロフィールや投稿、ページなどのコンテンツを登録(インデックス)しないよう要請します。"
lockedAccountInfo: "フォローを承認制にしても、投稿の公開範囲を「フォロワー」にしない限り、誰でもあなたの投稿を見られます。" lockedAccountInfo: "フォローを承認制にしても、投稿の公開範囲を「フォロワー」にしない限り、誰でもあなたの投稿を見られます。"
alwaysMarkSensitive: "デフォルトでメディアを閲覧注意にする" alwaysMarkSensitive: "デフォルトでメディアを閲覧注意にする"
loadRawImages: "添付画像のサムネイルをオリジナル画質にする" loadRawImages: "添付画像のサムネイルをオリジナル画質にする"
@ -811,7 +811,7 @@ instanceSecurity: "サーバーのセキュリティー"
secureModeInfo: "認証情報の無いリモートサーバーからのリクエストに応えません。" secureModeInfo: "認証情報の無いリモートサーバーからのリクエストに応えません。"
privateMode: "非公開モード" privateMode: "非公開モード"
privateModeInfo: "有効にすると、許可したサーバーのみからリクエストを受け付けます。" privateModeInfo: "有効にすると、許可したサーバーのみからリクエストを受け付けます。"
allowedInstances: "許可されたサーバー" allowedInstances: "許可するサーバー"
allowedInstancesDescription: "許可したいサーバーのホストを改行で区切って設定します。非公開モードだけで有効です。" allowedInstancesDescription: "許可したいサーバーのホストを改行で区切って設定します。非公開モードだけで有効です。"
previewNoteText: "本文をプレビュー" previewNoteText: "本文をプレビュー"
customCss: "カスタムCSS" customCss: "カスタムCSS"
@ -1518,7 +1518,7 @@ _profile:
youCanIncludeHashtags: "ハッシュタグを含められます。" youCanIncludeHashtags: "ハッシュタグを含められます。"
metadata: "追加情報" metadata: "追加情報"
metadataEdit: "追加情報を編集" metadataEdit: "追加情報を編集"
metadataDescription: "プロフィールに追加情報を表示できます。{a}タグまたは{l}タグを{rel}とともに追加すると、プロフィールのリンクを本人認証できます。" metadataDescription: "プロフィールに追加情報を表示できます。{rel}属性をつけた{a}タグや{l}タグを含むページをリンクすることで、リンクの本人確認もできます!"
metadataLabel: "ラベル" metadataLabel: "ラベル"
metadataContent: "内容" metadataContent: "内容"
changeAvatar: "アバター画像を変更" changeAvatar: "アバター画像を変更"
@ -2019,6 +2019,9 @@ postSearch: "このサーバーの投稿検索"
indexableDescription: MastodonやFirefishなどの検索機能に、あなたの投稿が表示されるのを許可します。 indexableDescription: MastodonやFirefishなどの検索機能に、あなたの投稿が表示されるのを許可します。
makePrivate: "秘密にする" makePrivate: "秘密にする"
makePrivateConfirm: "リモートサーバーに削除リクエストを送信し、投稿の公開範囲を「秘密」にして他の人から見られないようにします。実行しますか?" makePrivateConfirm: "リモートサーバーに削除リクエストを送信し、投稿の公開範囲を「秘密」にして他の人から見られないようにします。実行しますか?"
clickToShowPatterns: クリックしてトラックを表示
vibrate: 振動を有効にする
indexable: 投稿検索に登録
_iconSets: _iconSets:
bold: "太め" bold: "太め"
light: "細め" light: "細め"

View file

@ -937,7 +937,8 @@ _accountDelete:
inProgress: "正在删除" inProgress: "正在删除"
_ad: _ad:
back: "返回" back: "返回"
reduceFrequencyOfThisAd: "减少此广告的频率" reduceFrequencyOfThisAd: "减少此横幅的频率"
adsBy: 社区横幅(作者:{by}
_forgotPassword: _forgotPassword:
enterEmail: "请输入您注册账号时用的电子邮箱地址,密码重置链接将发送至该邮箱上。" enterEmail: "请输入您注册账号时用的电子邮箱地址,密码重置链接将发送至该邮箱上。"
ifNoEmail: "如果您在注册时没有输入电子邮件地址,请联系服务器管理员。" ifNoEmail: "如果您在注册时没有输入电子邮件地址,请联系服务器管理员。"
@ -1996,3 +1997,4 @@ indexable: 可索引的
languageForTranslation: 帖子翻译语言 languageForTranslation: 帖子翻译语言
vibrate: 播放振动 vibrate: 播放振动
openServerInfo: 点击帖子上的服务器滚动条时显示服务器信息 openServerInfo: 点击帖子上的服务器滚动条时显示服务器信息
clickToShowPatterns: 点击显示模块模式

View file

@ -21,7 +21,7 @@ basicSettings: "基本設定"
otherSettings: "其他設定" otherSettings: "其他設定"
openInWindow: "在新視窗開啟" openInWindow: "在新視窗開啟"
profile: "個人檔案" profile: "個人檔案"
timeline: "時間" timeline: "時間"
noAccountDescription: "此用戶還沒有自我介紹。" noAccountDescription: "此用戶還沒有自我介紹。"
login: "登入" login: "登入"
loggingIn: "登入中" loggingIn: "登入中"
@ -149,12 +149,12 @@ flagAsBot: "標記此帳號是機器人"
flagAsBotDescription: "如果本帳戶是由程式控制請啟用此選項。啟用後會作為標示幫助其他開發者防止機器人之間產生無限互動的行為並會調整Firefish內部系統將本帳戶識別為機器人。" flagAsBotDescription: "如果本帳戶是由程式控制請啟用此選項。啟用後會作為標示幫助其他開發者防止機器人之間產生無限互動的行為並會調整Firefish內部系統將本帳戶識別為機器人。"
flagAsCat: "你是喵咪嗎w😺" flagAsCat: "你是喵咪嗎w😺"
flagAsCatDescription: "如果想將本帳戶標示為一隻貓,請開啟此標示!" flagAsCatDescription: "如果想將本帳戶標示為一隻貓,請開啟此標示!"
flagShowTimelineReplies: "在時間上顯示貼文的回覆" flagShowTimelineReplies: "在時間上顯示貼文的回覆"
flagShowTimelineRepliesDescription: "啟用時,時間除了顯示用戶的貼文以外,還會顯示用戶對其他貼文的回覆。" flagShowTimelineRepliesDescription: "啟用時,時間除了顯示用戶的貼文以外,還會顯示用戶對其他貼文的回覆。"
autoAcceptFollowed: "自動准予追隨中使用者的追隨請求" autoAcceptFollowed: "自動准予追隨中使用者的追隨請求"
addAccount: "添加帳戶" addAccount: "添加帳戶"
loginFailed: "登入失敗" loginFailed: "登入失敗"
showOnRemote: "轉到所在伺服器顯示" showOnRemote: "開啟來源頁面"
general: "一般" general: "一般"
wallpaper: "桌布" wallpaper: "桌布"
setWallpaper: "設定桌布" setWallpaper: "設定桌布"
@ -324,9 +324,9 @@ dayX: "{day}日"
monthX: "{month}月" monthX: "{month}月"
yearX: "{year}年" yearX: "{year}年"
pages: "頁面" pages: "頁面"
enableLocalTimeline: "開啟本地時間" enableLocalTimeline: "開啟本地時間"
enableGlobalTimeline: "啟用公開時間" enableGlobalTimeline: "啟用公開時間"
disablingTimelinesInfo: "即使您關閉了時間線功能,管理員和板主仍可訪問所有的時間線。" disablingTimelinesInfo: "即使您關閉了時間軸功能,管理員和板主仍可訪問所有的時間軸。"
registration: "註冊" registration: "註冊"
enableRegistration: "開啟新使用者註冊" enableRegistration: "開啟新使用者註冊"
invite: "邀請" invite: "邀請"
@ -386,7 +386,7 @@ administrator: "管理員"
token: "權杖" token: "權杖"
twoStepAuthentication: "兩階段驗證" twoStepAuthentication: "兩階段驗證"
moderator: "板主" moderator: "板主"
moderation: "言論調節" moderation: "管理"
nUsersMentioned: "提到了{n}" nUsersMentioned: "提到了{n}"
securityKey: "安全金鑰" securityKey: "安全金鑰"
securityKeyName: "金鑰名稱" securityKeyName: "金鑰名稱"
@ -483,7 +483,7 @@ promotion: "推廣"
promote: "推廣" promote: "推廣"
numberOfDays: "有效天數" numberOfDays: "有效天數"
hideThisNote: "隱藏此貼文" hideThisNote: "隱藏此貼文"
showFeaturedNotesInTimeline: "在時間上顯示熱門推薦" showFeaturedNotesInTimeline: "在時間上顯示熱門推薦"
objectStorage: "Object Storage (物件儲存)" objectStorage: "Object Storage (物件儲存)"
useObjectStorage: "使用Object Storage" useObjectStorage: "使用Object Storage"
objectStorageBaseUrl: "根URL" objectStorageBaseUrl: "根URL"
@ -503,7 +503,7 @@ objectStorageUseProxyDesc: "如果不使用代理進行API連接請關閉"
objectStorageSetPublicRead: "上傳時設定為\"public-read\"" objectStorageSetPublicRead: "上傳時設定為\"public-read\""
serverLogs: "伺服器日誌" serverLogs: "伺服器日誌"
deleteAll: "刪除所有記錄" deleteAll: "刪除所有記錄"
showFixedPostForm: "於時間頁頂顯示「發送貼文」方框" showFixedPostForm: "於時間頁頂顯示「發送貼文」方框"
newNoteRecived: "發現新的貼文" newNoteRecived: "發現新的貼文"
sounds: "音效" sounds: "音效"
listen: "聆聽" listen: "聆聽"
@ -671,7 +671,7 @@ no: "取消"
driveFilesCount: "雲端硬碟檔案數量" driveFilesCount: "雲端硬碟檔案數量"
driveUsage: "雲端硬碟使用量" driveUsage: "雲端硬碟使用量"
noCrawle: "拒絕搜尋引擎索引" noCrawle: "拒絕搜尋引擎索引"
noCrawleDescription: "要求網路搜尋引擎不要索引你的個人資料頁、貼文及頁面等。" noCrawleDescription: "要求外部搜尋引擎不要收錄(索引)你的內容(個人檔案、貼文、頁面等)。"
lockedAccountInfo: "即使你通過了追隨者請求,除非你將貼文的可見性設定為 「追隨者」,否則任何人都能看見你的貼文。" lockedAccountInfo: "即使你通過了追隨者請求,除非你將貼文的可見性設定為 「追隨者」,否則任何人都能看見你的貼文。"
alwaysMarkSensitive: "默認將圖像/影像標記為敏感內容" alwaysMarkSensitive: "默認將圖像/影像標記為敏感內容"
loadRawImages: "以原始圖檔顯示附件圖檔的縮圖" loadRawImages: "以原始圖檔顯示附件圖檔的縮圖"
@ -689,7 +689,7 @@ experimentalFeatures: "實驗中的功能"
developer: "開發者" developer: "開發者"
makeExplorable: "使自己的帳戶能夠在“探索”頁面中顯示" makeExplorable: "使自己的帳戶能夠在“探索”頁面中顯示"
makeExplorableDescription: "如果關閉,帳戶將不會被顯示在\"探索\"頁面中。" makeExplorableDescription: "如果關閉,帳戶將不會被顯示在\"探索\"頁面中。"
showGapBetweenNotesInTimeline: "分開顯示時間上的貼文" showGapBetweenNotesInTimeline: "分開顯示時間上的貼文"
duplicate: "複製" duplicate: "複製"
left: "左" left: "左"
center: "置中" center: "置中"
@ -735,7 +735,7 @@ inChannelSearch: "頻道内搜尋"
useReactionPickerForContextMenu: "點擊右鍵開啟反應工具欄" useReactionPickerForContextMenu: "點擊右鍵開啟反應工具欄"
typingUsers: "{users}輸入中" typingUsers: "{users}輸入中"
jumpToSpecifiedDate: "跳轉到特定日期" jumpToSpecifiedDate: "跳轉到特定日期"
showingPastTimeline: "顯示過往的時間" showingPastTimeline: "顯示過往的時間"
clear: "清除" clear: "清除"
markAllAsRead: "全部標示為已讀" markAllAsRead: "全部標示為已讀"
goBack: "返回" goBack: "返回"
@ -823,7 +823,7 @@ unmuteThread: "將貼文串的靜音解除"
ffVisibility: "連接的公開範圍" ffVisibility: "連接的公開範圍"
ffVisibilityDescription: "您可以設定您的關注/關注者資訊的公開範圍。" ffVisibilityDescription: "您可以設定您的關注/關注者資訊的公開範圍。"
continueThread: "查看更多貼文" continueThread: "查看更多貼文"
deleteAccountConfirm: "將要刪除帳戶。是否確定" deleteAccountConfirm: "此帳戶將被刪除,是否繼續"
incorrectPassword: "密碼錯誤。" incorrectPassword: "密碼錯誤。"
voteConfirm: "確定投給「{choice}」?" voteConfirm: "確定投給「{choice}」?"
hide: "隱藏" hide: "隱藏"
@ -934,6 +934,7 @@ _accountDelete:
_ad: _ad:
back: "返回" back: "返回"
reduceFrequencyOfThisAd: "降低此橫幅的頻率" reduceFrequencyOfThisAd: "降低此橫幅的頻率"
adsBy: 社群橫幅(作者:{by}
_forgotPassword: _forgotPassword:
enterEmail: "請輸入您的帳戶註冊的電子郵件地址。 密碼重置連結將被發送到該電子郵件地址。" enterEmail: "請輸入您的帳戶註冊的電子郵件地址。 密碼重置連結將被發送到該電子郵件地址。"
ifNoEmail: "如果您還沒有註冊您的電子郵件地址,請聯繫管理員。" ifNoEmail: "如果您還沒有註冊您的電子郵件地址,請聯繫管理員。"
@ -1111,14 +1112,14 @@ _wordMute:
muteWords: "加入靜音文字" muteWords: "加入靜音文字"
muteWordsDescription: "用空格分隔指定AND用換行分隔指定OR。" muteWordsDescription: "用空格分隔指定AND用換行分隔指定OR。"
muteWordsDescription2: "將關鍵字用斜線括起來表示正規表達式。" muteWordsDescription2: "將關鍵字用斜線括起來表示正規表達式。"
softDescription: "隱藏時間中指定條件的貼文。" softDescription: "隱藏時間中指定條件的貼文。"
hardDescription: "具有指定條件的貼文將不添加到時間線。 即使您更改條件,未被添加的貼文也會被排除在外。" hardDescription: "符合指定條件的貼文將不添加到時間軸。 即使您更改條件,未被添加的貼文也會被排除在外。"
soft: "軟性靜音" soft: "軟性靜音"
hard: "硬性靜音" hard: "硬性靜音"
mutedNotes: "已靜音的貼文" mutedNotes: "已靜音的貼文"
muteLangsDescription2: '使用語言代碼。例: en, fr, ja, zh.' muteLangsDescription2: '使用語言代碼。例: en, fr, ja, zh.'
lang: 語言 lang: 語言
langDescription: 將指定語言的貼文從時間中隱藏。 langDescription: 將指定語言的貼文從時間中隱藏。
muteLangs: 被靜音的語言 muteLangs: 被靜音的語言
muteLangsDescription: OR條件以空格或換行進行分隔。 muteLangsDescription: OR條件以空格或換行進行分隔。
_instanceMute: _instanceMute:
@ -1230,16 +1231,16 @@ _tutorial:
step2_1: "首先,請完成你的個人資料。" step2_1: "首先,請完成你的個人資料。"
step2_2: "通過提供一些關於你自己的資料,其他人會更容易了解他們是否想看到你的貼文或關注你。" step2_2: "通過提供一些關於你自己的資料,其他人會更容易了解他們是否想看到你的貼文或關注你。"
step3_1: "現在是時候追隨一些人了!" step3_1: "現在是時候追隨一些人了!"
step3_2: "你的主頁和社交時間是基於你所追蹤的人,所以試著先追蹤幾個帳戶。\n點擊個人資料右上角的加號就可以關注它。" step3_2: "你的主頁和社交時間是基於你所追蹤的人,所以試著先追蹤幾個帳戶。\n點擊個人資料右上角的加號按鈕就可以關注它。"
step4_1: "讓我們出去找你。" step4_1: "讓我們出去找你。"
step4_2: "作為第一則貼文,有些人喜歡發 {introduction} 或單純發一個 \"hello world!\"" step4_2: "作為第一則貼文,有些人喜歡發 {introduction} 或單純發一個 \"hello world!\""
step5_1: "時間線,到處都是時間線" step5_1: "時間軸,到處都是時間軸"
step5_2: "您的伺服器已啟用了{timelines}個時間。" step5_2: "您的伺服器已啟用了{timelines}個時間。"
step5_3: "首頁 {icon} 時間是顯示你追蹤的帳號的貼文。" step5_3: "首頁 {icon} 時間是顯示你追蹤的帳號的貼文。"
step5_4: "本地 {icon} 時間線是你可以看到伺服器中所有其他用戶的貼文的時間線。" step5_4: "本地 {icon} 時間軸是你可以看到伺服器中所有其他用戶的貼文的時間軸。"
step5_5: "社交 {icon} 時間線是你的 首頁時間線 和 本地時間線 的結合體。" step5_5: "社交 {icon} 時間軸是你的 首頁時間軸 和 本地時間軸 的結合體。"
step5_6: "推薦 {icon} 時間是顯示你的伺服器管理員推薦的貼文。" step5_6: "推薦 {icon} 時間是顯示你的伺服器管理員推薦的貼文。"
step5_7: "全球 {icon} 時間是顯示來自所有其他連接的伺服器的貼文。" step5_7: "全球 {icon} 時間是顯示來自所有其他連接的伺服器的貼文。"
step6_1: "那麼,這裡是什麼地方?" step6_1: "那麼,這裡是什麼地方?"
step6_2: "你不只是加入Firefish。你已經加入了Fediverse的一個門戶這是一個由成千上萬台服務器組成的互聯網絡。" step6_2: "你不只是加入Firefish。你已經加入了Fediverse的一個門戶這是一個由成千上萬台服務器組成的互聯網絡。"
step6_3: "每個服務器也有不同而並不是所有的服務器都運行Firefish。但這個服務器確實是運行Firefish的! 你可能會覺得有點複雜,但你很快就會明白的。" step6_3: "每個服務器也有不同而並不是所有的服務器都運行Firefish。但這個服務器確實是運行Firefish的! 你可能會覺得有點複雜,但你很快就會明白的。"
@ -1328,7 +1329,7 @@ _weekday:
_widgets: _widgets:
memo: "備忘錄" memo: "備忘錄"
notifications: "通知" notifications: "通知"
timeline: "時間" timeline: "時間"
calendar: "行事曆" calendar: "行事曆"
trends: "發燒貼文" trends: "發燒貼文"
clock: "時鐘" clock: "時鐘"
@ -1384,9 +1385,9 @@ _poll:
remainingSeconds: "{s}秒後截止" remainingSeconds: "{s}秒後截止"
_visibility: _visibility:
public: "公開" public: "公開"
publicDescription: "發佈至公開時間" publicDescription: "發佈至公開時間"
home: "不在主頁顯示" home: "不在主頁顯示"
homeDescription: "僅發送至首頁的時間" homeDescription: "僅發送至首頁的時間"
followers: "追隨者" followers: "追隨者"
followersDescription: "僅發佈至關注者" followersDescription: "僅發佈至關注者"
specified: "指定使用者" specified: "指定使用者"
@ -1787,6 +1788,7 @@ _notification:
renote: "轉發" renote: "轉發"
reacted: 對您的貼文做出了反應 reacted: 對您的貼文做出了反應
renoted: 轉發了您的貼文 renoted: 轉發了您的貼文
voted: 投了票
_deck: _deck:
alwaysShowMainColumn: "總是顯示主欄" alwaysShowMainColumn: "總是顯示主欄"
columnAlign: "對齊欄位" columnAlign: "對齊欄位"
@ -1810,7 +1812,7 @@ _deck:
main: "主列" main: "主列"
widgets: "小工具" widgets: "小工具"
notifications: "通知" notifications: "通知"
tl: "時間" tl: "時間"
antenna: "天線" antenna: "天線"
list: "清單" list: "清單"
mentions: "提及" mentions: "提及"
@ -1842,7 +1844,7 @@ accountMoved: '該使用者已遷移至新帳戶:'
showAds: 顯示社群橫幅 showAds: 顯示社群橫幅
noThankYou: 不用了,謝謝 noThankYou: 不用了,謝謝
selectInstance: 選擇伺服器 selectInstance: 選擇伺服器
enableRecommendedTimeline: 啟用推薦時間 enableRecommendedTimeline: 啟用推薦時間
antennaInstancesDescription: 分行列出一個伺服器 antennaInstancesDescription: 分行列出一個伺服器
moveTo: 遷移此帳戶到新帳戶 moveTo: 遷移此帳戶到新帳戶
moveToLabel: '請輸入你將會遷移到的帳戶:' moveToLabel: '請輸入你將會遷移到的帳戶:'
@ -1874,6 +1876,7 @@ silenced: 已靜音
_experiments: _experiments:
title: 試驗功能 title: 試驗功能
enablePostImports: 啟用匯入貼文的功能 enablePostImports: 啟用匯入貼文的功能
postImportsCaption: 允許用戶從舊有的Firefish・Misskey・Mastodon・Akkoma・Pleroma帳號匯入貼文。在伺服器佇列堵塞時匯入貼文可能會導致載入速度變慢。
findOtherInstance: 找找另一個伺服器 findOtherInstance: 找找另一個伺服器
noGraze: 瀏覽器擴充元件 "Graze for Mastodon" 會與Firefish發生衝突請停用該擴充元件。 noGraze: 瀏覽器擴充元件 "Graze for Mastodon" 會與Firefish發生衝突請停用該擴充元件。
userSaysSomethingReasonRenote: '{name} 轉發了包含 {reason} 的貼文' userSaysSomethingReasonRenote: '{name} 轉發了包含 {reason} 的貼文'
@ -1895,7 +1898,7 @@ pushNotification: 推送通知
subscribePushNotification: 啟用推送通知 subscribePushNotification: 啟用推送通知
unsubscribePushNotification: 停用推送通知 unsubscribePushNotification: 停用推送通知
pushNotificationAlreadySubscribed: 推送通知已經啟用 pushNotificationAlreadySubscribed: 推送通知已經啟用
recommendedInstancesDescription: 以每行分隔的推薦伺服器出現在推薦的時間線中 recommendedInstancesDescription: 推薦的伺服器(將顯示在推薦時間軸中),一行一個
searchPlaceholder: 在 Firefish 上搜尋 searchPlaceholder: 在 Firefish 上搜尋
cw: 內容警告 cw: 內容警告
selectChannel: 選擇一個頻道 selectChannel: 選擇一個頻道
@ -1903,9 +1906,9 @@ newer: 較新
older: 較舊 older: 較舊
jumpToPrevious: 跳到上一個 jumpToPrevious: 跳到上一個
removeReaction: 移除你的反應 removeReaction: 移除你的反應
listsDesc: 清單可以創建一個只有您指定用戶的時間線。 可以從時間線頁面訪問它們。 listsDesc: 清單可以創建一個只有您指定用戶的時間軸。 可以從時間軸頁面訪問它們。
flagSpeakAsCatDescription: 在喵咪模式下你的貼文會被喵化ヾ(•ω•`)o flagSpeakAsCatDescription: 在喵咪模式下你的貼文會被喵化ヾ(•ω•`)o
antennasDesc: "天線會顯示符合您設置條件的新貼文!\n 可以從時間訪問它們。" antennasDesc: "天線會顯示符合您設置條件的新貼文!\n 可以從時間訪問它們。"
expandOnNoteClick: 點擊以打開貼文 expandOnNoteClick: 點擊以打開貼文
expandOnNoteClickDesc: 即使停用,您仍然可以從右鍵選單或單擊發文時間來打開貼文。 expandOnNoteClickDesc: 即使停用,您仍然可以從右鍵選單或單擊發文時間來打開貼文。
hiddenTagsDescription: '列出您希望隱藏趨勢和探索的主題標籤(不帶 #)。 隱藏的主題標籤仍然可以通過其他方式發現。' hiddenTagsDescription: '列出您希望隱藏趨勢和探索的主題標籤(不帶 #)。 隱藏的主題標籤仍然可以通過其他方式發現。'
@ -1924,7 +1927,7 @@ seperateRenoteQuote: 分開轉發及引用的按鈕
clipsDesc: 摘錄就像一個可以分享的書籤。 你可以從每個貼文的菜單創建新摘錄或將貼文加入已有的摘錄。 clipsDesc: 摘錄就像一個可以分享的書籤。 你可以從每個貼文的菜單創建新摘錄或將貼文加入已有的摘錄。
noteId: 貼文 ID noteId: 貼文 ID
sendModMail: 發送審核通知 sendModMail: 發送審核通知
enableIdenticonGeneration: 啟用碎片生成 enableIdenticonGeneration: 啟用Identicon生成
enableServerMachineStats: 啟用伺服器硬體統計資訊 enableServerMachineStats: 啟用伺服器硬體統計資訊
reactionPickerSkinTone: 首選表情符號膚色 reactionPickerSkinTone: 首選表情符號膚色
indexFromDescription: 留空以索引每個貼文 indexFromDescription: 留空以索引每個貼文
@ -1965,10 +1968,17 @@ _dialog:
charactersExceeded: 超過字數限制! 當前 {current} / 限制 {max} charactersExceeded: 超過字數限制! 當前 {current} / 限制 {max}
_skinTones: _skinTones:
yellow: 黃色 yellow: 黃色
medium: 中等
dark: 深色
mediumDark: 中等偏深
light: 淺色
mediumLight: 中等偏淺
exportZip: 匯出ZIP exportZip: 匯出ZIP
_feeds: _feeds:
atom: Atom atom: Atom
rss: RSS rss: RSS
copyFeed: 複製訂閱URL
jsonFeed: JSON Feed
emojiPackCreator: 表情包的作者 emojiPackCreator: 表情包的作者
importZip: 匯入ZIP importZip: 匯入ZIP
delete2fa: 停用二階段認證(2FA) delete2fa: 停用二階段認證(2FA)
@ -2012,3 +2022,9 @@ _iconSets:
regular: 標準 regular: 標準
fill: 填滿 fill: 填滿
duotone: 雙色 duotone: 雙色
objectStorageS3ForcePathStyleDesc: 以 "s3.amazonaws.com/<bucket>/" 而非 "<bucket>.s3.amazonaws.com"
的格式建構端點EndpointURL。
indexable: 登錄至貼文搜尋引擎
origin: 來源
objectStorageS3ForcePathStyle: 使用基於路徑的端點EndpointURL
clickToShowPatterns: 點擊顯示模組模式Module Pattern

View file

@ -1 +1 @@
696d3c6255b3608a8de59fcac5aea3d08b2eeebe 0a44d0652244240aa50fbae3ea88c616c8e1c136

View file

@ -1,12 +1,12 @@
{ {
"name": "firefish", "name": "firefish",
"version": "1.0.5-dev18", "version": "1.0.5-dev19",
"codename": "aqua", "codename": "aqua",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://code.naskya.net/naskya/firefish" "url": "https://code.naskya.net/naskya/firefish"
}, },
"packageManager": "pnpm@8.8.0", "packageManager": "pnpm@8.9.2",
"private": true, "private": true,
"scripts": { "scripts": {
"rebuild": "pnpm run clean && pnpm run build", "rebuild": "pnpm run clean && pnpm run build",
@ -57,7 +57,7 @@
"gulp-replace": "1.1.4", "gulp-replace": "1.1.4",
"gulp-terser": "2.1.0", "gulp-terser": "2.1.0",
"install-peers": "^1.0.4", "install-peers": "^1.0.4",
"pnpm": "8.8.0", "pnpm": "8.9.2",
"typescript": "5.2.2" "typescript": "5.2.2"
} }
} }

View file

@ -9,6 +9,7 @@ import { Cache } from "./cache.js";
import { getWordHardMute } from "./check-word-mute.js"; import { getWordHardMute } from "./check-word-mute.js";
const blockingCache = new Cache<User["id"][]>("blocking", 60 * 5); const blockingCache = new Cache<User["id"][]>("blocking", 60 * 5);
const mutedWordsCache = new Cache<string[][] | undefined>("mutedWords", 60 * 5);
export async function checkHitAntenna( export async function checkHitAntenna(
antenna: Antenna, antenna: Antenna,
@ -17,33 +18,16 @@ export async function checkHitAntenna(
antennaUserFollowing: User["id"][], antennaUserFollowing: User["id"][],
): Promise<boolean> { ): Promise<boolean> {
if (note.visibility === "specified") return false; if (note.visibility === "specified") 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) => if (!antenna.withReplies && note.replyId != null) return false;
res.map((x) => x.blockeeId),
),
);
if (blockings.some((blocking) => blocking === antenna.userId)) return false;
if (
await getWordHardMute(
note,
antenna.userId,
(
await UserProfiles.findOneBy({ userId: antenna.userId })
)?.mutedWords,
)
)
return false;
if (note.visibility === "followers" || note.visibility === "home") { if (note.visibility === "followers" || note.visibility === "home") {
if (antennaUserFollowing && !antennaUserFollowing.includes(note.userId)) if (antennaUserFollowing && !antennaUserFollowing.includes(note.userId))
return false; return false;
} }
if (!antenna.withReplies && note.replyId != null) return false;
if (antenna.src === "users") { if (antenna.src === "users") {
const accts = antenna.users.map((x) => { const accts = antenna.users.map((x) => {
const { username, host } = Acct.parse(x); const { username, host } = Acct.parse(x);
@ -107,9 +91,20 @@ export async function checkHitAntenna(
if (matched) return false; if (matched) return false;
} }
if (antenna.withFile) { // アンテナ作成者がノート作成者にブロックされていたらスキップ
if (note.fileIds && note.fileIds.length === 0) return false; const blockings = await blockingCache.fetch(noteUser.id, () =>
} Blockings.findBy({ blockerId: noteUser.id }).then((res) =>
res.map((x) => x.blockeeId),
),
);
if (blockings.includes(antenna.userId)) return false;
const mutedWords = await mutedWordsCache.fetch(antenna.userId, () =>
UserProfiles.findOneBy({ userId: antenna.userId }).then(
(profile) => profile?.mutedWords,
),
);
if (await getWordHardMute(note, antenna.userId, mutedWords)) return false;
// TODO: eval expression // TODO: eval expression

View file

@ -59,10 +59,7 @@ export async function getWordHardMute(
meId: string | null | undefined, meId: string | null | undefined,
mutedWords?: Array<string | string[]>, mutedWords?: Array<string | string[]>,
): Promise<boolean> { ): Promise<boolean> {
// 自分自身 if (note.userId === meId || mutedWords == null) return false;
if (note.userId === meId || mutedWords == null) {
return false;
}
if (mutedWords.length > 0) { if (mutedWords.length > 0) {
return ( return (

View file

@ -90,11 +90,25 @@ export default async (job: Bull.Job<InboxJobData>): Promise<string> => {
} }
// HTTP-Signatureの検証 // HTTP-Signatureの検証
const httpSignatureValidated = httpSignature.verifySignature( let httpSignatureValidated = httpSignature.verifySignature(
signature, signature,
authUser.key.keyPem, authUser.key.keyPem,
); );
// If signature validation failed, try refetching the actor
if (!httpSignatureValidated) {
authUser.key = await dbResolver.refetchPublicKeyForApId(authUser.user);
if (authUser.key == null) {
return "skip: failed to re-resolve user publicKey";
}
httpSignatureValidated = httpSignature.verifySignature(
signature,
authUser.key.keyPem,
);
}
// また、signatureのsignerは、activity.actorと一致する必要がある // また、signatureのsignerは、activity.actorと一致する必要がある
if (!httpSignatureValidated || authUser.user.uri !== activity.actor) { if (!httpSignatureValidated || authUser.user.uri !== activity.actor) {
// 一致しなくても、でもLD-Signatureがありそうならそっちも見る // 一致しなくても、でもLD-Signatureがありそうならそっちも見る

View file

@ -86,11 +86,25 @@ export async function checkFetch(req: IncomingMessage): Promise<number> {
} }
// HTTP-Signatureの検証 // HTTP-Signatureの検証
const httpSignatureValidated = httpSignature.verifySignature( let httpSignatureValidated = httpSignature.verifySignature(
signature, signature,
authUser.key.keyPem, authUser.key.keyPem,
); );
// If signature validation failed, try refetching the actor
if (!httpSignatureValidated) {
authUser.key = await dbResolver.refetchPublicKeyForApId(authUser.user);
if (authUser.key == null) {
return 403;
}
httpSignatureValidated = httpSignature.verifySignature(
signature,
authUser.key.keyPem,
);
}
if (!httpSignatureValidated) { if (!httpSignatureValidated) {
return 403; return 403;
} }

View file

@ -17,7 +17,7 @@ import { Cache } from "@/misc/cache.js";
import { uriPersonCache, userByIdCache } from "@/services/user-cache.js"; import { uriPersonCache, userByIdCache } from "@/services/user-cache.js";
import type { IObject } from "./type.js"; import type { IObject } from "./type.js";
import { getApId } from "./type.js"; import { getApId } from "./type.js";
import { resolvePerson } from "./models/person.js"; import { resolvePerson, updatePerson } from "./models/person.js";
const publicKeyCache = new Cache<UserPublickey | null>("publicKey", 60 * 30); const publicKeyCache = new Cache<UserPublickey | null>("publicKey", 60 * 30);
const publicKeyByUserIdCache = new Cache<UserPublickey | null>( const publicKeyByUserIdCache = new Cache<UserPublickey | null>(
@ -151,7 +151,7 @@ export default class DbResolver {
*/ */
public async getAuthUserFromKeyId(keyId: string): Promise<{ public async getAuthUserFromKeyId(keyId: string): Promise<{
user: CacheableRemoteUser; user: CacheableRemoteUser;
key: UserPublickey; key: UserPublickey | null;
} | null> { } | null> {
const key = await publicKeyCache.fetch( const key = await publicKeyCache.fetch(
keyId, keyId,
@ -203,4 +203,15 @@ export default class DbResolver {
key, key,
}; };
} }
public async refetchPublicKeyForApId(
user: CacheableRemoteUser,
): Promise<UserPublickey | null> {
await updatePerson(user.uri!, undefined, undefined, user);
const key = await UserPublickeys.findOneBy({ userId: user.id });
if (key != null) {
await publicKeyByUserIdCache.set(user.id, key);
}
return key;
}
} }

View file

@ -33,7 +33,7 @@ export const meta = {
id: "4362f8dc-731f-4ad8-a694-be2a88922a24", id: "4362f8dc-731f-4ad8-a694-be2a88922a24",
}, },
uriNull: { uriNull: {
message: "User ActivityPup URI is null.", message: "User ActivityPub URI is null.",
code: "URI_NULL", code: "URI_NULL",
id: "bf326f31-d430-4f97-9933-5d61e4d48a23", id: "bf326f31-d430-4f97-9933-5d61e4d48a23",
}, },

View file

@ -48,12 +48,12 @@ export const meta = {
id: "fcd2eef9-a9b2-4c4f-8624-038099e90aa5", id: "fcd2eef9-a9b2-4c4f-8624-038099e90aa5",
}, },
uriNull: { uriNull: {
message: "User ActivityPup URI is null.", message: "User ActivityPub URI is null.",
code: "URI_NULL", code: "URI_NULL",
id: "bf326f31-d430-4f97-9933-5d61e4d48a23", id: "bf326f31-d430-4f97-9933-5d61e4d48a23",
}, },
localUriNull: { localUriNull: {
message: "Local User ActivityPup URI is null.", message: "Local User ActivityPub URI is null.",
code: "URI_NULL", code: "URI_NULL",
id: "95ba11b9-90e8-43a5-ba16-7acc1ab32e71", id: "95ba11b9-90e8-43a5-ba16-7acc1ab32e71",
}, },

View file

@ -67,9 +67,8 @@ export default class extends Channel {
// レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。 // レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。
// そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる // そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる
if ( if (
this.user &&
this.userProfile && this.userProfile &&
(await getWordHardMute(note, this.user.id, this.userProfile.mutedWords)) (await getWordHardMute(note, this.user?.id, this.userProfile.mutedWords))
) )
return; return;

View file

@ -66,9 +66,8 @@ export default class extends Channel {
// レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。 // レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。
// そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる // そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる
if ( if (
this.user &&
this.userProfile && this.userProfile &&
(await getWordHardMute(note, this.user.id, this.userProfile.mutedWords)) (await getWordHardMute(note, this.user?.id, this.userProfile.mutedWords))
) )
return; return;

View file

@ -83,9 +83,8 @@ export default class extends Channel {
// レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。 // レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。
// そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる // そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる
if ( if (
this.user &&
this.userProfile && this.userProfile &&
(await getWordHardMute(note, this.user.id, this.userProfile.mutedWords)) (await getWordHardMute(note, this.user?.id, this.userProfile.mutedWords))
) )
return; return;

View file

@ -59,9 +59,8 @@ export default class extends Channel {
// レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。 // レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。
// そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる // そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる
if ( if (
this.user &&
this.userProfile && this.userProfile &&
(await getWordHardMute(note, this.user.id, this.userProfile.mutedWords)) (await getWordHardMute(note, this.user?.id, this.userProfile.mutedWords))
) )
return; return;

View file

@ -81,9 +81,8 @@ export default class extends Channel {
// レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。 // レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。
// そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる // そのためレコードが存在するかのチェックでは不十分なので、改めてgetWordHardMuteを呼んでいる
if ( if (
this.user &&
this.userProfile && this.userProfile &&
(await getWordHardMute(note, this.user.id, this.userProfile.mutedWords)) (await getWordHardMute(note, this.user?.id, this.userProfile.mutedWords))
) )
return; return;

View file

@ -1,6 +1,6 @@
import * as mfm from "mfm-js"; import * as mfm from "mfm-js";
import es from "../../db/elasticsearch.js"; import es from "@/db/elasticsearch.js";
import sonic from "../../db/sonic.js"; import sonic from "@/db/sonic.js";
import { import {
publishMainStream, publishMainStream,
publishNotesStream, publishNotesStream,

View file

@ -10,15 +10,15 @@
:aria-controls="bodyId" :aria-controls="bodyId"
> >
<template v-if="showBody" <template v-if="showBody"
><i class="ph-caret-up ph ph-lg"></i ><i :class="icon('ph-caret-up')"></i
></template> ></template>
<template v-else <template v-else
><i class="ph-caret-down ph ph-lg"></i ><i :class="icon('ph-caret-down')"></i
></template> ></template>
</button> </button>
</header> </header>
<transition <transition
name="" :name="defaultStore.state.animation ? 'folder-toggle' : ''"
@enter="enter" @enter="enter"
@after-enter="afterEnter" @after-enter="afterEnter"
@leave="leave" @leave="leave"
@ -34,8 +34,8 @@
<script lang="ts"> <script lang="ts">
import { defineComponent } from "vue"; import { defineComponent } from "vue";
import { getUniqueId } from "@/os"; import { getUniqueId } from "@/os";
// import { defaultStore } from "@/store"; import { defaultStore } from "@/store";
// import icon from "@/scripts/icon"; import icon from "@/scripts/icon";
const localStoragePrefix = "ui:folder:"; const localStoragePrefix = "ui:folder:";
@ -62,6 +62,8 @@ export default defineComponent({
localStoragePrefix + this.persistKey, localStoragePrefix + this.persistKey,
) === "t" ) === "t"
: this.expanded, : this.expanded,
defaultStore,
icon,
}; };
}, },
watch: { watch: {

View file

@ -359,7 +359,7 @@ const isDeleted = ref(false);
const muted = ref( const muted = ref(
getWordSoftMute( getWordSoftMute(
note.value, note.value,
$i, $i.id,
defaultStore.state.mutedWords, defaultStore.state.mutedWords,
defaultStore.state.mutedLangs, defaultStore.state.mutedLangs,
), ),

View file

@ -234,7 +234,7 @@ const isDeleted = ref(false);
const muted = ref( const muted = ref(
getWordSoftMute( getWordSoftMute(
note.value, note.value,
$i, $i.id,
defaultStore.state.mutedWords, defaultStore.state.mutedWords,
defaultStore.state.mutedLangs, defaultStore.state.mutedLangs,
), ),

View file

@ -268,7 +268,7 @@ const isDeleted = ref(false);
const muted = ref( const muted = ref(
getWordSoftMute( getWordSoftMute(
note.value, note.value,
$i, $i.id,
defaultStore.state.mutedWords, defaultStore.state.mutedWords,
defaultStore.state.mutedLangs, defaultStore.state.mutedLangs,
), ),

View file

@ -507,12 +507,9 @@ useTooltip(reactionRef, (showing) => {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
// > span:first-child { // > span:first-child {
// opacity: 0.7; // opacity: 0.7;
// &::after { // }
// content: ": ";
// }
// }
> i { > i {
vertical-align: super; vertical-align: super;

View file

@ -42,11 +42,11 @@ const props = defineProps<{
}>(); }>();
const note = ref<firefish.entities.Note>(); const note = ref<firefish.entities.Note>();
const tab = ref<string>(); const tab = ref<string | null>(null);
const reactions = ref<string[]>(); const reactions = ref<string[]>();
const users = ref(); const users = ref();
watch(tab, async () => { async function updateUsers(): void {
const res = await os.api("notes/reactions", { const res = await os.api("notes/reactions", {
noteId: props.noteId, noteId: props.noteId,
type: tab.value, type: tab.value,
@ -54,15 +54,17 @@ watch(tab, async () => {
}); });
users.value = res.map((x) => x.user); users.value = res.map((x) => x.user);
}); }
watch(tab, updateUsers);
onMounted(() => { onMounted(() => {
os.api("notes/show", { os.api("notes/show", {
noteId: props.noteId, noteId: props.noteId,
}).then((res) => { }).then(async (res) => {
reactions.value = Object.keys(res.reactions); reactions.value = Object.keys(res.reactions);
tab.value = reactions.value[0];
note.value = res; note.value = res;
await updateUsers();
}); });
}); });
</script> </script>

View file

@ -18,11 +18,14 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from "vue";
import icon from "@/scripts/icon"; import icon from "@/scripts/icon";
defineProps<{ const props = defineProps<{
defaultOpen: boolean; defaultOpen: boolean;
}>(); }>();
const opened = ref(props.defaultOpen);
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View file

@ -1,5 +1,8 @@
<template> <template>
<transition name="" mode="out-in"> <transition
:name="defaultStore.state.animation ? 'fade' : ''"
mode="out-in"
>
<div v-if="pending"> <div v-if="pending">
<MkLoading /> <MkLoading />
</div> </div>
@ -9,11 +12,11 @@
<div v-else> <div v-else>
<div class="wszdbhzo"> <div class="wszdbhzo">
<div> <div>
<i class="ph-warning ph ph-lg"></i> <i :class="iconClass('ph-warning')"></i>
{{ i18n.ts.somethingHappened }} {{ i18n.ts.somethingHappened }}
</div> </div>
<MkButton inline class="retry" @click="retry" <MkButton inline class="retry" @click="retry">
><i class="ph-arrow-clockwise ph ph-lg"></i> <i :class="iconClass('ph-arrow-clockwise')"></i>
{{ i18n.ts.retry }}</MkButton {{ i18n.ts.retry }}</MkButton
> >
</div> </div>
@ -26,8 +29,8 @@ import type { PropType } from "vue";
import { defineComponent, ref, watch } from "vue"; import { defineComponent, ref, watch } from "vue";
import MkButton from "@/components/MkButton.vue"; import MkButton from "@/components/MkButton.vue";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
// import { defaultStore } from "@/store"; import { defaultStore } from "@/store";
// import icon from "@/scripts/icon"; import iconClass from "@/scripts/icon";
export default defineComponent({ export default defineComponent({
components: { components: {
@ -87,6 +90,8 @@ export default defineComponent({
result, result,
retry, retry,
i18n, i18n,
defaultStore,
iconClass,
}; };
}, },
}); });

View file

@ -31,7 +31,8 @@
primary primary
class="save" class="save"
@click="updated" @click="updated"
><i class="ph-floppy-disk-back ph ph-lg"></i> >
<i :class="icon('ph-floppy-disk-back')"></i>
{{ i18n.ts.save }}</MkButton {{ i18n.ts.save }}</MkButton
> >
</div> </div>
@ -50,6 +51,7 @@ import {
import { debounce } from "throttle-debounce"; import { debounce } from "throttle-debounce";
import MkButton from "@/components/MkButton.vue"; import MkButton from "@/components/MkButton.vue";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
import icon from "@/scripts/icon";
export default defineComponent({ export default defineComponent({
components: { components: {
@ -188,6 +190,7 @@ export default defineComponent({
onKeydown, onKeydown,
updated, updated,
i18n, i18n,
icon,
}; };
}, },
}); });

View file

@ -24,7 +24,7 @@
@swiper="setSwiperRef" @swiper="setSwiperRef"
@slide-change="onSlideChange" @slide-change="onSlideChange"
> >
<swiper-slide> <swiper-slide v-if="true">
<MkFolder class="_gap"> <MkFolder class="_gap">
<template #header <template #header
><i :class="icon('ph-clock')"></i> ><i :class="icon('ph-clock')"></i>
@ -66,7 +66,7 @@
</MkPagination> </MkPagination>
</MkFolder> </MkFolder>
</swiper-slide> </swiper-slide>
<swiper-slide> <swiper-slide v-if="true">
<MkPagination <MkPagination
v-slot="{ items }" v-slot="{ items }"
:pagination="likedPostsPagination" :pagination="likedPostsPagination"
@ -81,7 +81,7 @@
</div> </div>
</MkPagination> </MkPagination>
</swiper-slide> </swiper-slide>
<swiper-slide> <swiper-slide v-if="true">
<MkA to="/gallery/new" class="_link" style="margin: 16px" <MkA to="/gallery/new" class="_link" style="margin: 16px"
><i :class="icon('ph-plus')"></i> ><i :class="icon('ph-plus')"></i>
{{ i18n.ts.postToGallery }}</MkA {{ i18n.ts.postToGallery }}</MkA

View file

@ -75,6 +75,7 @@ export default defineComponent({
return { return {
showBody: this.expanded, showBody: this.expanded,
i18n, i18n,
icon,
}; };
}, },
methods: { methods: {

View file

@ -17,7 +17,7 @@
> >
<template #func> <template #func>
<button class="_button" @click="changeType()"> <button class="_button" @click="changeType()">
<i :class="icon('ph-pencil')"></i> <i :class="iconClass('ph-pencil')"></i>
</button> </button>
</template> </template>
@ -158,7 +158,7 @@ import * as os from "@/os";
import { isLiteralValue } from "@/scripts/hpml/expr"; import { isLiteralValue } from "@/scripts/hpml/expr";
import { funcDefs } from "@/scripts/hpml/lib"; import { funcDefs } from "@/scripts/hpml/lib";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
import icon from "@/scripts/icon"; import iconClass from "@/scripts/icon";
export default defineComponent({ export default defineComponent({
components: { components: {
@ -207,6 +207,7 @@ export default defineComponent({
warn: null, warn: null,
slots: "", slots: "",
i18n, i18n,
iconClass,
}; };
}, },

View file

@ -23,7 +23,7 @@
@swiper="setSwiperRef" @swiper="setSwiperRef"
@slide-change="onSlideChange" @slide-change="onSlideChange"
> >
<swiper-slide> <swiper-slide v-if="true">
<div class="rknalgpo"> <div class="rknalgpo">
<MkPagination <MkPagination
v-slot="{ items }" v-slot="{ items }"
@ -38,7 +38,7 @@
</MkPagination> </MkPagination>
</div> </div>
</swiper-slide> </swiper-slide>
<swiper-slide> <swiper-slide v-if="true">
<div class="rknalgpo liked"> <div class="rknalgpo liked">
<MkPagination <MkPagination
v-slot="{ items }" v-slot="{ items }"
@ -53,7 +53,7 @@
</MkPagination> </MkPagination>
</div> </div>
</swiper-slide> </swiper-slide>
<swiper-slide> <swiper-slide v-if="true">
<div class="rknalgpo my"> <div class="rknalgpo my">
<div class="buttoncontainer"> <div class="buttoncontainer">
<MkButton class="new primary" @click="create()" <MkButton class="new primary" @click="create()"

View file

@ -1,3 +1,5 @@
import type * as firefish from "firefish-js";
export interface Muted { export interface Muted {
muted: boolean; muted: boolean;
matched: string[]; matched: string[];
@ -7,7 +9,7 @@ export interface Muted {
const NotMuted = { muted: false, matched: [] }; const NotMuted = { muted: false, matched: [] };
function checkLangMute( function checkLangMute(
note: NoteLike, note: firefish.entities.Note,
mutedLangs: Array<string | string[]>, mutedLangs: Array<string | string[]>,
): Muted { ): Muted {
const mutedLangList = new Set( const mutedLangList = new Set(
@ -20,7 +22,7 @@ function checkLangMute(
} }
function checkWordMute( function checkWordMute(
note: NoteLike, note: firefish.entities.Note,
mutedWords: Array<string | string[]>, mutedWords: Array<string | string[]>,
): Muted { ): Muted {
let text = `${note.cw ?? ""} ${note.text ?? ""}`; let text = `${note.cw ?? ""} ${note.text ?? ""}`;
@ -72,15 +74,12 @@ function checkWordMute(
} }
export function getWordSoftMute( export function getWordSoftMute(
note: Record<string, any>, note: firefish.entities.Note,
me: Record<string, any> | null | undefined, meId: string | null | undefined,
mutedWords: Array<string | string[]>, mutedWords: Array<string | string[]>,
mutedLangs: Array<string | string[]>, mutedLangs: Array<string | string[]>,
): Muted { ): Muted {
// 自分自身 if (note.userId === meId) return NotMuted;
if (me && note.userId === me.id) {
return NotMuted;
}
if (mutedWords.length > 0) { if (mutedWords.length > 0) {
const noteMuted = checkWordMute(note, mutedWords); const noteMuted = checkWordMute(note, mutedWords);

View file

@ -1,5 +1,5 @@
import { defaultStore } from "@/store"; import { defaultStore } from "@/store";
export default function icon(name: string, large = true): string { export default function (name: string, large = true): string {
return `${name} ${large ? "ph-lg" : ""} ${defaultStore.state.iconSet}`; return `${name} ${large ? "ph-lg" : ""} ${defaultStore.state.iconSet}`;
} }

View file

@ -2,7 +2,7 @@ import * as mfm from "mfm-js";
import { defaultStore } from "@/store"; import { defaultStore } from "@/store";
import { expandKaTeXMacro } from "@/scripts/katex-macro"; import { expandKaTeXMacro } from "@/scripts/katex-macro";
export default function preprocess(text: string): string { export default function (text: string): string {
if (defaultStore.state.enableCustomKaTeXMacro) { if (defaultStore.state.enableCustomKaTeXMacro) {
const parsedKaTeXMacro = const parsedKaTeXMacro =
localStorage.getItem("customKaTeXMacroParsed") ?? "{}"; localStorage.getItem("customKaTeXMacroParsed") ?? "{}";

View file

@ -41,23 +41,23 @@ export const defaultStore = markRaw(
default: 0, default: 0,
}, },
tlHomeHintClosed: { tlHomeHintClosed: {
where: "device", where: "account",
default: false, default: false,
}, },
tlLocalHintClosed: { tlLocalHintClosed: {
where: "device", where: "account",
default: false, default: false,
}, },
tlRecommendedHintClosed: { tlRecommendedHintClosed: {
where: "device", where: "account",
default: false, default: false,
}, },
tlSocialHintClosed: { tlSocialHintClosed: {
where: "device", where: "account",
default: false, default: false,
}, },
tlGlobalHintClosed: { tlGlobalHintClosed: {
where: "device", where: "account",
default: false, default: false,
}, },
keepCw: { keepCw: {

View file

@ -127,7 +127,9 @@
class="item _button help" class="item _button help"
@click="openHelpMenu" @click="openHelpMenu"
> >
<i :class="icon('ph-info ph-xl ph-fw help', false)"></i> <i
:class="icon('ph-info help icon ph-xl ph-fw', false)"
></i>
</button> </button>
</div> </div>
</div> </div>

View file

@ -12,7 +12,7 @@
"api": "pnpm api-extractor run --local --verbose", "api": "pnpm api-extractor run --local --verbose",
"api-prod": "pnpm api-extractor run --verbose", "api-prod": "pnpm api-extractor run --verbose",
"api-doc": "pnpm api-documenter markdown -i ./etc/", "api-doc": "pnpm api-documenter markdown -i ./etc/",
"lint": "pnpm biome check --apply **/*.ts", "lint": "pnpm biome check --apply *.ts",
"format": "pnpm biome format --write **/*.ts" "format": "pnpm biome format --write **/*.ts"
}, },
"repository": { "repository": {

View file

@ -5,7 +5,7 @@
"build": "pnpm vite build --emptyOutDir", "build": "pnpm vite build --emptyOutDir",
"build:debug": "pnpm run build", "build:debug": "pnpm run build",
"watch": "pnpm swc src -d built -D -w", "watch": "pnpm swc src -d built -D -w",
"lint": "pnpm biome check **/*.ts --apply", "lint": "pnpm biome check *.ts --apply",
"format": "pnpm biome format * --write" "format": "pnpm biome format * --write"
}, },
"devDependencies": { "devDependencies": {

File diff suppressed because it is too large Load diff