Compare commits

...

19 Commits

Author SHA1 Message Date
taichan 2b8bd591a9
Merge 48232ca57b into fe38115883 2025-10-07 03:36:59 +09:00
syuilo fe38115883 lint 2025-10-06 20:01:19 +09:00
syuilo 6fba73ca13 Update pnpm-lock.yaml 2025-10-06 19:21:21 +09:00
syuilo 0d33e1f839 fix notes\drafts\create param defs 2025-10-06 19:21:17 +09:00
syuilo 74f33157a3
New Crowdin updates (#16601)
* New translations ja-jp.yml (Chinese Simplified)

* New translations ja-jp.yml (Spanish)

* New translations ja-jp.yml (Italian)

* New translations ja-jp.yml (Russian)

* New translations ja-jp.yml (Turkish)

* New translations ja-jp.yml (Chinese Traditional)

* New translations ja-jp.yml (French)

* New translations ja-jp.yml (Arabic)

* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (Czech)

* New translations ja-jp.yml (German)

* New translations ja-jp.yml (Greek)

* New translations ja-jp.yml (Korean)

* New translations ja-jp.yml (Polish)

* New translations ja-jp.yml (Portuguese)

* New translations ja-jp.yml (Slovak)

* New translations ja-jp.yml (Ukrainian)

* New translations ja-jp.yml (Chinese Simplified)

* New translations ja-jp.yml (English)

* New translations ja-jp.yml (Vietnamese)

* New translations ja-jp.yml (Indonesian)

* New translations ja-jp.yml (Bengali)

* New translations ja-jp.yml (Thai)

* New translations ja-jp.yml (Japanese, Kansai)

* New translations ja-jp.yml (Chinese Simplified)

* New translations ja-jp.yml (Catalan)
2025-10-06 15:39:18 +09:00
syuilo ae10cad9a7 perf(frontend): improve about#emojis performancce 2025-10-06 10:21:46 +09:00
syuilo ba9924abdb refactor(frontend): use useTemplateRef 2025-10-06 10:18:14 +09:00
github-actions[bot] 99686801a0 Bump version to 2025.10.0-beta.2 2025-10-06 01:12:30 +00:00
かっこかり f3e0713501
enhance(frontend): お問い合わせページからデバイス情報を出力できるように (#16598)
* enhance(frontend): デバイス情報を出力できるように

* fix lint

* Update Changelog

* enhance: getHighEntropyValuesが使用できなかった場合のフォールバックを追加

* fix lint

* fix: getHighEntropyValuesが使用できない場合は生のUAを返すように

* enhance: getHighEntropyValuesが使用できる場合でも生のUAを含めるように

* ✌️

* onHeaderClicked -> onOpened
2025-10-06 10:06:53 +09:00
かっこかり 7fcbf57a9d
fix(frontend): 存在しない翻訳を修正 (#16604) 2025-10-06 10:06:20 +09:00
taichanne30 48232ca57b
ユーザーTLではFTTのソースが空の際にDBにFallbackしないように 2024-07-25 16:01:41 +09:00
taichanne30 3564bf5c66
Refactor: const naming 2024-07-25 14:37:09 +09:00
taichanne30 685fc2bd9d
Fix: shouldFallbackToDbがすでにtrueの場合にそれが無視される 2024-07-25 14:30:10 +09:00
taichanne30 6cc0138d1e
Merge branch 'develop' of https://github.com/misskey-dev/misskey into fix-stl-note-fetch 2024-07-25 14:23:31 +09:00
taichan d3228d5570
Merge branch 'develop' into fix-stl-note-fetch 2024-03-07 02:04:23 +09:00
taichanne30 4a8ffe20a7
Fix timeline fetch when using sinceId 2024-03-07 01:55:57 +09:00
taichanne30 5615675991
Update CHANGELOG.md 2024-03-02 15:35:39 +09:00
taichan 0c65b8058a
Merge branch 'develop' into fix-stl-note-fetch 2024-03-02 15:28:45 +09:00
taichanne30 82bec76cd4
fix(backend): DBフォールバック有効時、複数のFTTソースから取得するタイムラインで取得漏れが起きる現象の修正 2024-03-02 15:23:25 +09:00
47 changed files with 514 additions and 729 deletions

View File

@ -20,7 +20,9 @@
- Enhance: ウォーターマークにアカウントのQRコードを追加できるように - Enhance: ウォーターマークにアカウントのQRコードを追加できるように
- Enhance: テーマをドラッグ&ドロップできるように - Enhance: テーマをドラッグ&ドロップできるように
- Enhance: 絵文字ピッカーのサイズをより大きくできるように - Enhance: 絵文字ピッカーのサイズをより大きくできるように
- Enhance: カスタム絵文字が多い場合にサーバーの絵文字一覧ページがフリーズしないように
- Enhance: 時刻計算のための基準値を一か所で管理するようにし、パフォーマンスを向上 - Enhance: 時刻計算のための基準値を一か所で管理するようにし、パフォーマンスを向上
- Enhance: 「お問い合わせ」ページから、バグの調査等に役立つ情報OSやブラウザのバージョン等を取得・コピーできるように
- Fix: iOSで、デバイスがダークモードだと初回読み込み時にエラーになる問題を修正 - Fix: iOSで、デバイスがダークモードだと初回読み込み時にエラーになる問題を修正
- Fix: アクティビティウィジェットのグラフモードが動作しない問題を修正 - Fix: アクティビティウィジェットのグラフモードが動作しない問題を修正
@ -1073,7 +1075,7 @@
- Fix: カスタム絵文字の画像読み込みに失敗した際はテキストではなくダミー画像を表示 #13487 - Fix: カスタム絵文字の画像読み込みに失敗した際はテキストではなくダミー画像を表示 #13487
### Server ### Server
- - Fix: FTT有効かつDBフォールバック有効時、STLのようにタイムラインのソースが複数だとFTTとDBのフォールバック間で取得されないートがある問題
## 2024.3.0 ## 2024.3.0

View File

@ -1010,6 +1010,7 @@ postForm: "أنشئ ملاحظة"
information: "عن" information: "عن"
inMinutes: "د" inMinutes: "د"
inDays: "ي" inDays: "ي"
widgets: "التطبيقات المُصغّرة"
_chat: _chat:
invitations: "دعوة" invitations: "دعوة"
noHistory: "السجل فارغ" noHistory: "السجل فارغ"

View File

@ -850,6 +850,7 @@ postForm: "নোট লিখুন"
information: "আপনার সম্পর্কে" information: "আপনার সম্পর্কে"
inMinutes: "মিনিট" inMinutes: "মিনিট"
inDays: "দিন" inDays: "দিন"
widgets: "উইজেটগুলি"
_chat: _chat:
invitations: "আমন্ত্রণ" invitations: "আমন্ত্রণ"
noHistory: "কোনো ইতিহাস নেই" noHistory: "কোনো ইতিহাস নেই"

View File

@ -334,6 +334,7 @@ fileName: "Nom del Fitxer"
selectFile: "Selecciona un fitxer" selectFile: "Selecciona un fitxer"
selectFiles: "Selecciona fitxers" selectFiles: "Selecciona fitxers"
selectFolder: "Selecció de carpeta" selectFolder: "Selecció de carpeta"
unselectFolder: "Deixa de seleccionar la carpeta"
selectFolders: "Selecció de carpetes" selectFolders: "Selecció de carpetes"
fileNotSelected: "Cap fitxer seleccionat" fileNotSelected: "Cap fitxer seleccionat"
renameFile: "Canvia el nom del fitxer" renameFile: "Canvia el nom del fitxer"
@ -346,6 +347,7 @@ addFile: "Afegeix un fitxer"
showFile: "Mostrar fitxer" showFile: "Mostrar fitxer"
emptyDrive: "El teu Disc és buit" emptyDrive: "El teu Disc és buit"
emptyFolder: "La carpeta està buida" emptyFolder: "La carpeta està buida"
dropHereToUpload: "Arrossega els arxius fins aquí per pujar-los al servidor"
unableToDelete: "No es pot eliminar" unableToDelete: "No es pot eliminar"
inputNewFileName: "Introduïu el nom de fitxer nou" inputNewFileName: "Introduïu el nom de fitxer nou"
inputNewDescription: "Escriu el peu de foto." inputNewDescription: "Escriu el peu de foto."
@ -1389,6 +1391,9 @@ scheduleToPostOnX: "Programar una nota per {x}"
scheduledToPostOnX: "S'ha programat la nota per {x}" scheduledToPostOnX: "S'ha programat la nota per {x}"
schedule: "Programa" schedule: "Programa"
scheduled: "Programat" scheduled: "Programat"
widgets: "Ginys"
deviceInfo: "Informació del dispositiu"
deviceInfoDescription: "En fer consultes tècniques influir la següent informació pot ajudar a resoldre'l més ràpidament."
_compression: _compression:
_quality: _quality:
high: "Qualitat alta" high: "Qualitat alta"
@ -2431,6 +2436,7 @@ _auth:
scopeUser: "Opera com si fossis aquest usuari" scopeUser: "Opera com si fossis aquest usuari"
pleaseLogin: "Si us plau, identificat per autoritzar l'aplicació." pleaseLogin: "Si us plau, identificat per autoritzar l'aplicació."
byClickingYouWillBeRedirectedToThisUrl: "Si es garanteix l'accés, seràs redirigit automàticament a la següent adreça URL" byClickingYouWillBeRedirectedToThisUrl: "Si es garanteix l'accés, seràs redirigit automàticament a la següent adreça URL"
alreadyAuthorized: "Aquesta aplicació ja té accés."
_antennaSources: _antennaSources:
all: "Totes les publicacions" all: "Totes les publicacions"
homeTimeline: "Publicacions dels usuaris seguits" homeTimeline: "Publicacions dels usuaris seguits"
@ -2697,6 +2703,8 @@ _notification:
quote: "Citar" quote: "Citar"
reaction: "Reaccions" reaction: "Reaccions"
pollEnded: "Enquesta terminada" pollEnded: "Enquesta terminada"
scheduledNotePosted: "Nota programada amb èxit "
scheduledNotePostFailed: "Ha fallat la programació de la nota"
receiveFollowRequest: "Rebuda una petició de seguiment" receiveFollowRequest: "Rebuda una petició de seguiment"
followRequestAccepted: "Petició de seguiment acceptada" followRequestAccepted: "Petició de seguiment acceptada"
roleAssigned: "Rol donat" roleAssigned: "Rol donat"

View File

@ -1109,6 +1109,7 @@ postForm: "Formulář pro odeslání"
information: "Informace" information: "Informace"
inMinutes: "Minut" inMinutes: "Minut"
inDays: "Dnů" inDays: "Dnů"
widgets: "Widgety"
_chat: _chat:
invitations: "Pozvat" invitations: "Pozvat"
noHistory: "Žádná historie" noHistory: "Žádná historie"

View File

@ -1370,6 +1370,7 @@ defaultImageCompressionLevel: "Standard-Bildkomprimierungsstufe"
defaultImageCompressionLevel_description: "Ein niedrigerer Wert erhält die Bildqualität, erhöht aber die Dateigröße. <br>Höhere Werte reduzieren die Dateigröße, verringern aber die Bildqualität." defaultImageCompressionLevel_description: "Ein niedrigerer Wert erhält die Bildqualität, erhöht aber die Dateigröße. <br>Höhere Werte reduzieren die Dateigröße, verringern aber die Bildqualität."
inMinutes: "Minute(n)" inMinutes: "Minute(n)"
inDays: "Tag(en)" inDays: "Tag(en)"
widgets: "Widgets"
_order: _order:
newest: "Neueste zuerst" newest: "Neueste zuerst"
oldest: "Älteste zuerst" oldest: "Älteste zuerst"

View File

@ -288,6 +288,7 @@ replies: "Απάντηση"
renotes: "Κοινοποίηση σημειώματος" renotes: "Κοινοποίηση σημειώματος"
postForm: "Φόρμα δημοσίευσης" postForm: "Φόρμα δημοσίευσης"
information: "Πληροφορίες" information: "Πληροφορίες"
widgets: "Μαραφέτια"
_chat: _chat:
members: "Μέλη" members: "Μέλη"
home: "Κεντρικό" home: "Κεντρικό"

View File

@ -1389,6 +1389,7 @@ scheduleToPostOnX: "Scheduled to note on {x}"
scheduledToPostOnX: "Note is scheduled for {x}" scheduledToPostOnX: "Note is scheduled for {x}"
schedule: "Schedule" schedule: "Schedule"
scheduled: "Scheduled" scheduled: "Scheduled"
widgets: "Widgets"
_compression: _compression:
_quality: _quality:
high: "High quality" high: "High quality"

View File

@ -1389,6 +1389,7 @@ scheduleToPostOnX: "Programar una nota para {x}"
scheduledToPostOnX: "La nota está programada para {x}." scheduledToPostOnX: "La nota está programada para {x}."
schedule: "Programado" schedule: "Programado"
scheduled: "Programado" scheduled: "Programado"
widgets: "Widgets"
_compression: _compression:
_quality: _quality:
high: "Calidad alta" high: "Calidad alta"

View File

@ -1273,6 +1273,7 @@ postForm: "Formulaire de publication"
information: "Informations" information: "Informations"
inMinutes: "min" inMinutes: "min"
inDays: "j" inDays: "j"
widgets: "Widgets"
_chat: _chat:
invitations: "Inviter" invitations: "Inviter"
noHistory: "Pas d'historique" noHistory: "Pas d'historique"

View File

@ -1264,6 +1264,7 @@ postForm: "Buat catatan"
information: "Informasi" information: "Informasi"
inMinutes: "menit" inMinutes: "menit"
inDays: "hari" inDays: "hari"
widgets: "Widget"
_chat: _chat:
invitations: "Undang" invitations: "Undang"
noHistory: "Tidak ada riwayat" noHistory: "Tidak ada riwayat"

20
locales/index.d.ts vendored
View File

@ -1354,6 +1354,10 @@ export interface Locale extends ILocale {
* *
*/ */
"selectFolder": string; "selectFolder": string;
/**
*
*/
"unselectFolder": string;
/** /**
* *
*/ */
@ -1402,6 +1406,10 @@ export interface Locale extends ILocale {
* *
*/ */
"emptyFolder": string; "emptyFolder": string;
/**
*
*/
"dropHereToUpload": string;
/** /**
* *
*/ */
@ -5581,6 +5589,14 @@ export interface Locale extends ILocale {
* *
*/ */
"widgets": string; "widgets": string;
/**
*
*/
"deviceInfo": string;
/**
*
*/
"deviceInfoDescription": string;
"_compression": { "_compression": {
"_quality": { "_quality": {
/** /**
@ -9460,6 +9476,10 @@ export interface Locale extends ILocale {
* URLに遷移します * URLに遷移します
*/ */
"byClickingYouWillBeRedirectedToThisUrl": string; "byClickingYouWillBeRedirectedToThisUrl": string;
/**
*
*/
"alreadyAuthorized": string;
}; };
"_antennaSources": { "_antennaSources": {
/** /**

View File

@ -1389,6 +1389,7 @@ scheduleToPostOnX: "Pianificare la pubblicazione {x}"
scheduledToPostOnX: "Pubblicazione pianificata {x}" scheduledToPostOnX: "Pubblicazione pianificata {x}"
schedule: "Pianificare" schedule: "Pianificare"
scheduled: "Pianificata" scheduled: "Pianificata"
widgets: "Riquadri"
_compression: _compression:
_quality: _quality:
high: "Alta qualità" high: "Alta qualità"

View File

@ -334,6 +334,7 @@ fileName: "ファイル名"
selectFile: "ファイルを選択" selectFile: "ファイルを選択"
selectFiles: "ファイルを選択" selectFiles: "ファイルを選択"
selectFolder: "フォルダーを選択" selectFolder: "フォルダーを選択"
unselectFolder: "フォルダーの選択を解除"
selectFolders: "フォルダーを選択" selectFolders: "フォルダーを選択"
fileNotSelected: "ファイルが選択されていません" fileNotSelected: "ファイルが選択されていません"
renameFile: "ファイル名を変更" renameFile: "ファイル名を変更"
@ -346,6 +347,7 @@ addFile: "ファイルを追加"
showFile: "ファイルを表示" showFile: "ファイルを表示"
emptyDrive: "ドライブは空です" emptyDrive: "ドライブは空です"
emptyFolder: "フォルダーは空です" emptyFolder: "フォルダーは空です"
dropHereToUpload: "ここにファイルをドロップしてアップロード"
unableToDelete: "削除できません" unableToDelete: "削除できません"
inputNewFileName: "新しいファイル名を入力してください" inputNewFileName: "新しいファイル名を入力してください"
inputNewDescription: "新しいキャプションを入力してください" inputNewDescription: "新しいキャプションを入力してください"
@ -1390,6 +1392,8 @@ scheduledToPostOnX: "{x}に投稿が予約されています"
schedule: "予約" schedule: "予約"
scheduled: "予約" scheduled: "予約"
widgets: "ウィジェット" widgets: "ウィジェット"
deviceInfo: "デバイス情報"
deviceInfoDescription: "技術的なお問い合わせの際に、以下の情報を併記すると問題の解決に役立つことがあります。"
_compression: _compression:
_quality: _quality:
@ -2484,6 +2488,7 @@ _auth:
scopeUser: "以下のユーザーとして操作しています" scopeUser: "以下のユーザーとして操作しています"
pleaseLogin: "アプリケーションにアクセス許可を与えるには、ログインが必要です。" pleaseLogin: "アプリケーションにアクセス許可を与えるには、ログインが必要です。"
byClickingYouWillBeRedirectedToThisUrl: "アクセスを許可すると、自動で以下のURLに遷移します" byClickingYouWillBeRedirectedToThisUrl: "アクセスを許可すると、自動で以下のURLに遷移します"
alreadyAuthorized: "このアプリケーションは既にアクセスが許可されています。"
_antennaSources: _antennaSources:
all: "全てのノート" all: "全てのノート"

View File

@ -1337,6 +1337,7 @@ safeModeEnabled: "セーフモードがオンになってるで"
pluginsAreDisabledBecauseSafeMode: "セーフモードがオンやから、プラグインは全部無効化されてるで。" pluginsAreDisabledBecauseSafeMode: "セーフモードがオンやから、プラグインは全部無効化されてるで。"
customCssIsDisabledBecauseSafeMode: "セーフモードがオンやから、カスタムCSSは適用されてへんで。" customCssIsDisabledBecauseSafeMode: "セーフモードがオンやから、カスタムCSSは適用されてへんで。"
themeIsDefaultBecauseSafeMode: "セーフモードがオンの間はデフォルトのテーマを使うで。セーフモードをオフにれば元に戻るで。" themeIsDefaultBecauseSafeMode: "セーフモードがオンの間はデフォルトのテーマを使うで。セーフモードをオフにれば元に戻るで。"
widgets: "ウィジェット"
_chat: _chat:
noMessagesYet: "まだメッセージはあらへんで" noMessagesYet: "まだメッセージはあらへんで"
individualChat_description: "特定のユーザーと一対一でチャットができるで。" individualChat_description: "特定のユーザーと一対一でチャットができるで。"

View File

@ -1389,6 +1389,7 @@ scheduleToPostOnX: "{x}에 게시를 예약합니다."
scheduledToPostOnX: "{x}에 게시가 예약돼있습니다." scheduledToPostOnX: "{x}에 게시가 예약돼있습니다."
schedule: "예약" schedule: "예약"
scheduled: "예약" scheduled: "예약"
widgets: "위젯"
_compression: _compression:
_quality: _quality:
high: "고품질" high: "고품질"

View File

@ -1042,6 +1042,7 @@ postForm: "Formularz tworzenia wpisu"
information: "Informacje" information: "Informacje"
inMinutes: "minuta" inMinutes: "minuta"
inDays: "dzień" inDays: "dzień"
widgets: "Widżety"
_chat: _chat:
invitations: "Zaproś" invitations: "Zaproś"
noHistory: "Brak historii" noHistory: "Brak historii"

View File

@ -1389,6 +1389,7 @@ scheduleToPostOnX: "Agendar nota para {x}"
scheduledToPostOnX: "A nota está agendada para {x}" scheduledToPostOnX: "A nota está agendada para {x}"
schedule: "Agendar" schedule: "Agendar"
scheduled: "Agendado" scheduled: "Agendado"
widgets: "Widgets"
_compression: _compression:
_quality: _quality:
high: "Qualidade alta" high: "Qualidade alta"

View File

@ -1277,6 +1277,7 @@ textCount: "Количество символов"
information: "Описание" information: "Описание"
inMinutes: "мин" inMinutes: "мин"
inDays: "сут" inDays: "сут"
widgets: "Виджеты"
_chat: _chat:
invitations: "Пригласить" invitations: "Пригласить"
noHistory: "История пока пуста" noHistory: "История пока пуста"

View File

@ -915,6 +915,7 @@ postForm: "Napísať poznámku"
information: "Informácie" information: "Informácie"
inMinutes: "min" inMinutes: "min"
inDays: "dní" inDays: "dní"
widgets: "Widgety"
_chat: _chat:
invitations: "Pozvať" invitations: "Pozvať"
noHistory: "Žiadna história" noHistory: "Žiadna história"

View File

@ -1389,6 +1389,7 @@ scheduleToPostOnX: "กำหนดเวลาให้โพสต์ไว้
scheduledToPostOnX: "มีการกำหนดเวลาให้โพสต์ไว้ที่ {x}" scheduledToPostOnX: "มีการกำหนดเวลาให้โพสต์ไว้ที่ {x}"
schedule: "กำหนดเวลา" schedule: "กำหนดเวลา"
scheduled: "กำหนดเวลา" scheduled: "กำหนดเวลา"
widgets: "วิดเจ็ต"
_compression: _compression:
_quality: _quality:
high: "คุณภาพสูง" high: "คุณภาพสูง"

View File

@ -1378,6 +1378,7 @@ pluginsAreDisabledBecauseSafeMode: "Güvenli mod etkinleştirildiği için tüm
customCssIsDisabledBecauseSafeMode: "Güvenli mod etkin olduğu için özel CSS uygulanmıyor." customCssIsDisabledBecauseSafeMode: "Güvenli mod etkin olduğu için özel CSS uygulanmıyor."
themeIsDefaultBecauseSafeMode: "Güvenli mod etkinken, varsayılan tema kullanılır. Güvenli modu devre dışı bırakmak bu değişiklikleri geri alır." themeIsDefaultBecauseSafeMode: "Güvenli mod etkinken, varsayılan tema kullanılır. Güvenli modu devre dışı bırakmak bu değişiklikleri geri alır."
thankYouForTestingBeta: "Beta sürümünü test ettiğin için teşekkür ederiz!" thankYouForTestingBeta: "Beta sürümünü test ettiğin için teşekkür ederiz!"
widgets: "Widget'lar"
_order: _order:
newest: "Önce yeni" newest: "Önce yeni"
oldest: "Önce eski" oldest: "Önce eski"

View File

@ -921,6 +921,7 @@ postForm: "Створення нотатки"
information: "Інформація" information: "Інформація"
inMinutes: "х" inMinutes: "х"
inDays: "д" inDays: "д"
widgets: "Віджети"
_chat: _chat:
invitations: "Запросити" invitations: "Запросити"
noHistory: "Історія порожня" noHistory: "Історія порожня"

View File

@ -1222,6 +1222,7 @@ migrateOldSettings: "Di chuyển cài đặt cũ"
migrateOldSettings_description: "Thông thường, quá trình này diễn ra tự động, nhưng nếu vì lý do nào đó mà quá trình di chuyển không thành công, bạn có thể kích hoạt thủ công quy trình di chuyển, quá trình này sẽ ghi đè lên thông tin cấu hình hiện tại của bạn." migrateOldSettings_description: "Thông thường, quá trình này diễn ra tự động, nhưng nếu vì lý do nào đó mà quá trình di chuyển không thành công, bạn có thể kích hoạt thủ công quy trình di chuyển, quá trình này sẽ ghi đè lên thông tin cấu hình hiện tại của bạn."
inMinutes: "phút" inMinutes: "phút"
inDays: "ngày" inDays: "ngày"
widgets: "Tiện ích"
_chat: _chat:
invitations: "Mời" invitations: "Mời"
noHistory: "Không có dữ liệu" noHistory: "Không có dữ liệu"

View File

@ -56,7 +56,7 @@ deleteAndEdit: "删除并编辑"
deleteAndEditConfirm: "要删除此帖并再次编辑吗?对此帖的所有回应、转发和回复也将被删除。" deleteAndEditConfirm: "要删除此帖并再次编辑吗?对此帖的所有回应、转发和回复也将被删除。"
addToList: "添加至列表" addToList: "添加至列表"
addToAntenna: "添加到天线" addToAntenna: "添加到天线"
sendMessage: "发送" sendMessage: "发送消息"
copyRSS: "复制RSS" copyRSS: "复制RSS"
copyUsername: "复制用户名" copyUsername: "复制用户名"
copyUserId: "复制用户 ID" copyUserId: "复制用户 ID"
@ -334,6 +334,7 @@ fileName: "文件名称"
selectFile: "选择文件" selectFile: "选择文件"
selectFiles: "选择文件" selectFiles: "选择文件"
selectFolder: "选择文件夹" selectFolder: "选择文件夹"
unselectFolder: "取消全选文件夹"
selectFolders: "选择多个文件夹" selectFolders: "选择多个文件夹"
fileNotSelected: "未选择文件" fileNotSelected: "未选择文件"
renameFile: "重命名文件" renameFile: "重命名文件"
@ -346,6 +347,7 @@ addFile: "添加文件"
showFile: "显示文件" showFile: "显示文件"
emptyDrive: "网盘中无文件" emptyDrive: "网盘中无文件"
emptyFolder: "此文件夹中无文件" emptyFolder: "此文件夹中无文件"
dropHereToUpload: "将文件拖动到这里来上传"
unableToDelete: "无法删除" unableToDelete: "无法删除"
inputNewFileName: "请输入新文件名" inputNewFileName: "请输入新文件名"
inputNewDescription: "请输入新标题" inputNewDescription: "请输入新标题"
@ -1389,6 +1391,9 @@ scheduleToPostOnX: "预定在 {x} 发出"
scheduledToPostOnX: "已预定在 {x} 发出" scheduledToPostOnX: "已预定在 {x} 发出"
schedule: "定时" schedule: "定时"
scheduled: "定时" scheduled: "定时"
widgets: "小工具"
deviceInfo: "设备信息"
deviceInfoDescription: "咨询技术问题时,将以下信息一并发送有助于解决问题。"
_compression: _compression:
_quality: _quality:
high: "高质量" high: "高质量"
@ -2431,6 +2436,7 @@ _auth:
scopeUser: "以下面的用户进行操作" scopeUser: "以下面的用户进行操作"
pleaseLogin: "在对应用进行授权许可之前,请先登录" pleaseLogin: "在对应用进行授权许可之前,请先登录"
byClickingYouWillBeRedirectedToThisUrl: "允许访问后将会自动重定向到以下 URL" byClickingYouWillBeRedirectedToThisUrl: "允许访问后将会自动重定向到以下 URL"
alreadyAuthorized: "此应用已有访问许可。"
_antennaSources: _antennaSources:
all: "所有帖子" all: "所有帖子"
homeTimeline: "已关注用户的帖子" homeTimeline: "已关注用户的帖子"
@ -2697,6 +2703,8 @@ _notification:
quote: "引用" quote: "引用"
reaction: "回应" reaction: "回应"
pollEnded: "问卷调查结束" pollEnded: "问卷调查结束"
scheduledNotePosted: "定时发送成功"
scheduledNotePostFailed: "定时发送失败"
receiveFollowRequest: "收到关注请求" receiveFollowRequest: "收到关注请求"
followRequestAccepted: "关注请求已通过" followRequestAccepted: "关注请求已通过"
roleAssigned: "授予的角色" roleAssigned: "授予的角色"

View File

@ -1389,6 +1389,7 @@ scheduleToPostOnX: "排定在 {x} 發布"
scheduledToPostOnX: "已排定在 {x} 發布貼文" scheduledToPostOnX: "已排定在 {x} 發布貼文"
schedule: "排定" schedule: "排定"
scheduled: "排定" scheduled: "排定"
widgets: "小工具"
_compression: _compression:
_quality: _quality:
high: "高品質" high: "高品質"

View File

@ -1,6 +1,6 @@
{ {
"name": "misskey", "name": "misskey",
"version": "2025.10.0-beta.1", "version": "2025.10.0-beta.2",
"codename": "nasubi", "codename": "nasubi",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -40,6 +40,7 @@ type TimelineOptions = {
excludePureRenotes: boolean; excludePureRenotes: boolean;
ignoreAuthorFromUserSuspension?: boolean; ignoreAuthorFromUserSuspension?: boolean;
dbFallback: (untilId: string | null, sinceId: string | null, limit: number) => Promise<MiNote[]>, dbFallback: (untilId: string | null, sinceId: string | null, limit: number) => Promise<MiNote[]>,
preventEmptyTimelineDbFallback?: boolean;
}; };
@Injectable() @Injectable()
@ -73,12 +74,20 @@ export class FanoutTimelineEndpointService {
const redisResult = await this.fanoutTimelineService.getMulti(ps.redisTimelines, ps.untilId, ps.sinceId); const redisResult = await this.fanoutTimelineService.getMulti(ps.redisTimelines, ps.untilId, ps.sinceId);
// TODO: いい感じにgetMulti内でソート済だからuniqするときにredisResultが全てソート済なのを利用して再ソートを避けたい // オプション無効時、取得したredisResultのうち、2つ以上ソースがあり、1つでも空であればDBにフォールバックする
const redisResultIds = Array.from(new Set(redisResult.flat(1))).sort(idCompare); let shouldFallbackToDb = ps.useDbFallback &&
(ps.preventEmptyTimelineDbFallback !== true && redisResult.length > 1 && redisResult.some(ids => ids.length === 0));
// 取得したresultの中で最古のIDのうち、最も新しいものを取得
const fttThresholdId = redisResult.map(ids => ids[0]).sort()[0];
// TODO: いい感じにgetMulti内でソート済だからuniqするときにredisResultが全てソート済なのを利用して再ソートを避けたい
const redisResultIds = shouldFallbackToDb ? [] : Array.from(new Set(redisResult.flat(1))).sort(idCompare);
let noteIds = redisResultIds.filter(id => id >= fttThresholdId).slice(0, ps.limit);
let noteIds = redisResultIds.slice(0, ps.limit);
const oldestNoteId = ascending ? redisResultIds[0] : redisResultIds[redisResultIds.length - 1]; const oldestNoteId = ascending ? redisResultIds[0] : redisResultIds[redisResultIds.length - 1];
const shouldFallbackToDb = noteIds.length === 0 || ps.sinceId != null && ps.sinceId < oldestNoteId; shouldFallbackToDb ||= ps.useDbFallback && (noteIds.length === 0 || ps.sinceId != null && ps.sinceId < oldestNoteId);
if (!shouldFallbackToDb) { if (!shouldFallbackToDb) {
let filter = ps.noteFilter ?? (_note => true) as NoteFilter; let filter = ps.noteFilter ?? (_note => true) as NoteFilter;

View File

@ -192,7 +192,7 @@ export const paramDef = {
scheduledAt: { type: 'integer', nullable: true }, scheduledAt: { type: 'integer', nullable: true },
isActuallyScheduled: { type: 'boolean', default: false }, isActuallyScheduled: { type: 'boolean', default: false },
}, },
required: ['visibility', 'visibleUserIds', 'cw', 'hashtag', 'localOnly', 'reactionAcceptance', 'replyId', 'renoteId', 'channelId', 'text', 'fileIds', 'poll', 'scheduledAt', 'isActuallyScheduled'], required: [],
} as const; } as const;
@Injectable() @Injectable()
@ -203,22 +203,22 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
) { ) {
super(meta, paramDef, async (ps, me) => { super(meta, paramDef, async (ps, me) => {
const draft = await this.noteDraftService.create(me, { const draft = await this.noteDraftService.create(me, {
fileIds: ps.fileIds, fileIds: ps.fileIds ?? [],
pollChoices: ps.poll?.choices ?? [], pollChoices: ps.poll?.choices ?? [],
pollMultiple: ps.poll?.multiple ?? false, pollMultiple: ps.poll?.multiple ?? false,
pollExpiresAt: ps.poll?.expiresAt ? new Date(ps.poll.expiresAt) : null, pollExpiresAt: ps.poll?.expiresAt ? new Date(ps.poll.expiresAt) : null,
pollExpiredAfter: ps.poll?.expiredAfter ?? null, pollExpiredAfter: ps.poll?.expiredAfter ?? null,
hasPoll: ps.poll != null, hasPoll: ps.poll != null,
text: ps.text, text: ps.text ?? null,
replyId: ps.replyId, replyId: ps.replyId ?? null,
renoteId: ps.renoteId, renoteId: ps.renoteId ?? null,
cw: ps.cw, cw: ps.cw ?? null,
hashtag: ps.hashtag, hashtag: ps.hashtag ?? null,
localOnly: ps.localOnly, localOnly: ps.localOnly,
reactionAcceptance: ps.reactionAcceptance, reactionAcceptance: ps.reactionAcceptance,
visibility: ps.visibility, visibility: ps.visibility,
visibleUserIds: ps.visibleUserIds, visibleUserIds: ps.visibleUserIds ?? [],
channelId: ps.channelId, channelId: ps.channelId ?? null,
scheduledAt: ps.scheduledAt ? new Date(ps.scheduledAt) : null, scheduledAt: ps.scheduledAt ? new Date(ps.scheduledAt) : null,
isActuallyScheduled: ps.isActuallyScheduled, isActuallyScheduled: ps.isActuallyScheduled,
}).catch((err) => { }).catch((err) => {

View File

@ -150,6 +150,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
withFiles: ps.withFiles, withFiles: ps.withFiles,
withRenotes: ps.withRenotes, withRenotes: ps.withRenotes,
}, me), }, me),
preventEmptyTimelineDbFallback: true,
}); });
return timeline; return timeline;

View File

@ -5,7 +5,13 @@ SPDX-License-Identifier: AGPL-3.0-only
<!-- eslint-disable vue/no-v-html --> <!-- eslint-disable vue/no-v-html -->
<template> <template>
<div :class="[$style.codeBlockRoot, { [$style.codeEditor]: codeEditor }, (darkMode ? $style.dark : $style.light)]" v-html="html"></div> <div
:class="[$style.codeBlockRoot, {
[$style.codeEditor]: codeEditor,
[$style.outerStyle]: !codeEditor && withOuterStyle,
[$style.dark]: darkMode,
[$style.light]: !darkMode,
}]" v-html="html"></div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -15,11 +21,15 @@ import type { BundledLanguage } from 'shiki/langs';
import { getHighlighter, getTheme } from '@/utility/code-highlighter.js'; import { getHighlighter, getTheme } from '@/utility/code-highlighter.js';
import { store } from '@/store.js'; import { store } from '@/store.js';
const props = defineProps<{ const props = withDefaults(defineProps<{
code: string; code: string;
lang?: string; lang?: string;
codeEditor?: boolean; codeEditor?: boolean;
}>(); withOuterStyle?: boolean;
}>(), {
codeEditor: false,
withOuterStyle: true,
});
const highlighter = await getHighlighter(); const highlighter = await getHighlighter();
const darkMode = store.r.darkMode; const darkMode = store.r.darkMode;
@ -73,17 +83,13 @@ watch(() => props.lang, (to) => {
<style module lang="scss"> <style module lang="scss">
.codeBlockRoot :global(.shiki) { .codeBlockRoot :global(.shiki) {
padding: 1em;
margin: 0;
overflow: auto; overflow: auto;
font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace; font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace;
color: var(--shiki-fallback); color: var(--shiki-fallback);
background-color: var(--shiki-fallback-bg);
& span { & span {
color: var(--shiki-fallback); color: var(--shiki-fallback);
background-color: var(--shiki-fallback-bg);
} }
& pre, & pre,
@ -92,26 +98,40 @@ watch(() => props.lang, (to) => {
} }
} }
.outerStyle.codeBlockRoot :global(.shiki) {
padding: 1em;
margin: 0;
border-radius: 8px;
border: 1px solid var(--MI_THEME-divider);
background-color: var(--shiki-fallback-bg);
}
.light.codeBlockRoot :global(.shiki) { .light.codeBlockRoot :global(.shiki) {
color: var(--shiki-light); color: var(--shiki-light);
background-color: var(--shiki-light-bg);
& span { & span {
color: var(--shiki-light); color: var(--shiki-light);
background-color: var(--shiki-light-bg);
} }
} }
.light.outerStyle.codeBlockRoot :global(.shiki),
.light.codeEditor.codeBlockRoot :global(.shiki) {
background-color: var(--shiki-light-bg);
}
.dark.codeBlockRoot :global(.shiki) { .dark.codeBlockRoot :global(.shiki) {
color: var(--shiki-dark); color: var(--shiki-dark);
background-color: var(--shiki-dark-bg);
& span { & span {
color: var(--shiki-dark); color: var(--shiki-dark);
background-color: var(--shiki-dark-bg);
} }
} }
.dark.outerStyle.codeBlockRoot :global(.shiki),
.dark.codeEditor.codeBlockRoot :global(.shiki) {
background-color: var(--shiki-dark-bg);
}
.codeBlockRoot.codeEditor { .codeBlockRoot.codeEditor {
min-width: 100%; min-width: 100%;
height: 100%; height: 100%;

View File

@ -5,15 +5,32 @@ SPDX-License-Identifier: AGPL-3.0-only
<template> <template>
<div :class="$style.codeBlockRoot"> <div :class="$style.codeBlockRoot">
<button v-if="copyButton" :class="$style.codeBlockCopyButton" class="_button" @click="copy"> <button v-if="copyButton" :class="[$style.codeBlockCopyButton, { [$style.withOuterStyle]: withOuterStyle }]" class="_button" @click="copy">
<i class="ti ti-copy"></i> <i class="ti ti-copy"></i>
</button> </button>
<Suspense> <Suspense>
<template #fallback> <template #fallback>
<MkLoading/> <pre
class="_selectable"
:class="[$style.codeBlockFallbackRoot, {
[$style.outerStyle]: withOuterStyle,
}]"
><code :class="$style.codeBlockFallbackCode">Loading...</code></pre>
</template> </template>
<XCode v-if="show && lang" class="_selectable" :code="code" :lang="lang"/> <XCode
<pre v-else-if="show" class="_selectable" :class="$style.codeBlockFallbackRoot"><code :class="$style.codeBlockFallbackCode">{{ code }}</code></pre> v-if="show && lang"
class="_selectable"
:code="code"
:lang="lang"
:withOuterStyle="withOuterStyle"
/>
<pre
v-else-if="show"
class="_selectable"
:class="[$style.codeBlockFallbackRoot, {
[$style.outerStyle]: withOuterStyle,
}]"
><code :class="$style.codeBlockFallbackCode">{{ code }}</code></pre>
<button v-else :class="$style.codePlaceholderRoot" @click="show = true"> <button v-else :class="$style.codePlaceholderRoot" @click="show = true">
<div :class="$style.codePlaceholderContainer"> <div :class="$style.codePlaceholderContainer">
<div><i class="ti ti-code"></i> {{ i18n.ts.code }}</div> <div><i class="ti ti-code"></i> {{ i18n.ts.code }}</div>
@ -26,8 +43,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { defineAsyncComponent, ref } from 'vue'; import { defineAsyncComponent, ref } from 'vue';
import * as os from '@/os.js';
import MkLoading from '@/components/global/MkLoading.vue';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { copyToClipboard } from '@/utility/copy-to-clipboard.js'; import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
import { prefer } from '@/preferences.js'; import { prefer } from '@/preferences.js';
@ -36,10 +51,12 @@ const props = withDefaults(defineProps<{
code: string; code: string;
forceShow?: boolean; forceShow?: boolean;
copyButton?: boolean; copyButton?: boolean;
withOuterStyle?: boolean;
lang?: string; lang?: string;
}>(), { }>(), {
copyButton: true, copyButton: true,
forceShow: false, forceShow: false,
withOuterStyle: true,
}); });
const show = ref(props.forceShow === true ? true : !prefer.s.dataSaver.code); const show = ref(props.forceShow === true ? true : !prefer.s.dataSaver.code);
@ -58,10 +75,16 @@ function copy() {
.codeBlockCopyButton { .codeBlockCopyButton {
position: absolute; position: absolute;
top: 8px;
right: 8px;
opacity: 0.5; opacity: 0.5;
top: 0;
right: 0;
&.withOuterStyle {
top: 8px;
right: 8px;
}
&:hover { &:hover {
opacity: 0.8; opacity: 0.8;
} }
@ -70,11 +93,17 @@ function copy() {
.codeBlockFallbackRoot { .codeBlockFallbackRoot {
display: block; display: block;
overflow-wrap: anywhere; overflow-wrap: anywhere;
padding: 1em;
margin: 0;
overflow: auto; overflow: auto;
} }
.outerStyle.codeBlockFallbackRoot {
background: var(--MI_THEME-bg);
padding: 1em;
margin: .5em 0;
border-radius: 8px;
border: 1px solid var(--MI_THEME-divider);
}
.codeBlockFallbackCode { .codeBlockFallbackCode {
font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace; font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace;
} }

View File

@ -35,18 +35,18 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-if="select === 'folder'"> <div v-if="select === 'folder'">
<template v-if="folder == null"> <template v-if="folder == null">
<MkButton v-if="!isRootSelected" @click="isRootSelected = true"> <MkButton v-if="!isRootSelected" @click="isRootSelected = true">
<i class="ti ti-square"></i> {{ i18n.ts.selectThisFolder }} <i class="ti ti-square"></i> {{ i18n.ts.selectFolder }}
</MkButton> </MkButton>
<MkButton v-else @click="isRootSelected = false"> <MkButton v-else @click="isRootSelected = false">
<i class="ti ti-checkbox"></i> {{ i18n.ts.unselectThisFolder }} <i class="ti ti-checkbox"></i> {{ i18n.ts.unselectFolder }}
</MkButton> </MkButton>
</template> </template>
<template v-else> <template v-else>
<MkButton v-if="!selectedFolders.some(f => f.id === folder!.id)" @click="selectedFolders.push(folder)"> <MkButton v-if="!selectedFolders.some(f => f.id === folder!.id)" @click="selectedFolders.push(folder)">
<i class="ti ti-square"></i> {{ i18n.ts.selectThisFolder }} <i class="ti ti-square"></i> {{ i18n.ts.selectFolder }}
</MkButton> </MkButton>
<MkButton v-else @click="selectedFolders = selectedFolders.filter(f => f.id !== folder!.id)"> <MkButton v-else @click="selectedFolders = selectedFolders.filter(f => f.id !== folder!.id)">
<i class="ti ti-checkbox"></i> {{ i18n.ts.unselectThisFolder }} <i class="ti ti-checkbox"></i> {{ i18n.ts.unselectFolder }}
</MkButton> </MkButton>
</template> </template>
</div> </div>
@ -112,7 +112,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkButton v-show="filesPaginator.canFetchOlder.value" :class="$style.loadMore" primary rounded @click="filesPaginator.fetchOlder()">{{ i18n.ts.loadMore }}</MkButton> <MkButton v-show="filesPaginator.canFetchOlder.value" :class="$style.loadMore" primary rounded @click="filesPaginator.fetchOlder()">{{ i18n.ts.loadMore }}</MkButton>
<div v-if="filesPaginator.items.value.length == 0 && foldersPaginator.items.value.length == 0 && !fetching" :class="$style.empty"> <div v-if="filesPaginator.items.value.length == 0 && foldersPaginator.items.value.length == 0 && !fetching" :class="$style.empty">
<div v-if="draghover">{{ i18n.ts['empty-draghover'] }}</div> <div v-if="draghover">{{ i18n.ts.dropHereToUpload }}</div>
<div v-if="!draghover && folder == null"><strong>{{ i18n.ts.emptyDrive }}</strong></div> <div v-if="!draghover && folder == null"><strong>{{ i18n.ts.emptyDrive }}</strong></div>
<div v-if="!draghover && folder != null">{{ i18n.ts.emptyFolder }}</div> <div v-if="!draghover && folder != null">{{ i18n.ts.emptyFolder }}</div>
</div> </div>

View File

@ -96,7 +96,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { nextTick, onMounted, ref, useTemplateRef } from 'vue'; import { nextTick, onMounted, ref, useTemplateRef, watch } from 'vue';
import { prefer } from '@/preferences.js'; import { prefer } from '@/preferences.js';
import { getBgColor } from '@/utility/get-bg-color.js'; import { getBgColor } from '@/utility/get-bg-color.js';
import { pageFolderTeleportCount, popup } from '@/os.js'; import { pageFolderTeleportCount, popup } from '@/os.js';
@ -119,6 +119,11 @@ const props = withDefaults(defineProps<{
canPage: true, canPage: true,
}); });
const emit = defineEmits<{
(ev: 'opened'): void;
(ev: 'closed'): void;
}>();
const rootEl = useTemplateRef('rootEl'); const rootEl = useTemplateRef('rootEl');
const asPage = props.canPage && deviceKind === 'smartphone' && prefer.s['experimental.enableFolderPageView']; const asPage = props.canPage && deviceKind === 'smartphone' && prefer.s['experimental.enableFolderPageView'];
const bgSame = ref(false); const bgSame = ref(false);
@ -164,7 +169,7 @@ function afterLeave(el: Element) {
let pageId = pageFolderTeleportCount.value; let pageId = pageFolderTeleportCount.value;
pageFolderTeleportCount.value += 1000; pageFolderTeleportCount.value += 1000;
async function toggle() { async function toggle(ev: MouseEvent) {
if (asPage && !opened.value) { if (asPage && !opened.value) {
pageId++; pageId++;
const { dispose } = await popup(MkFolderPage, { const { dispose } = await popup(MkFolderPage, {
@ -192,6 +197,14 @@ onMounted(() => {
const myBg = computedStyle.getPropertyValue('--MI_THEME-panel'); const myBg = computedStyle.getPropertyValue('--MI_THEME-panel');
bgSame.value = parentBg === myBg; bgSame.value = parentBg === myBg;
}); });
watch(opened, (isOpened) => {
if (isOpened) {
emit('opened');
} else {
emit('closed');
}
}, { flush: 'post' });
</script> </script>
<style lang="scss" module> <style lang="scss" module>

View File

@ -33,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
<MkKeyValue> <MkKeyValue>
<template #key>{{ i18n.ts.id }}</template> <template #key>{{ i18n.ts.name }}</template>
<template #value>{{ name }}</template> <template #value>{{ name }}</template>
</MkKeyValue> </MkKeyValue>
<MkKeyValue> <MkKeyValue>

View File

@ -68,7 +68,7 @@ export type GetMkSelectValueTypesFromDef<T extends MkSelectItem[]> = T[number] e
</script> </script>
<script lang="ts" setup generic="const ITEMS extends MkSelectItem[], MODELT extends OptionValue"> <script lang="ts" setup generic="const ITEMS extends MkSelectItem[], MODELT extends OptionValue">
import { onMounted, nextTick, ref, watch, computed, toRefs } from 'vue'; import { onMounted, nextTick, ref, watch, computed, toRefs, useTemplateRef } from 'vue';
import { useInterval } from '@@/js/use-interval.js'; import { useInterval } from '@@/js/use-interval.js';
import type { MenuItem } from '@/types/menu.js'; import type { MenuItem } from '@/types/menu.js';
import * as os from '@/os.js'; import * as os from '@/os.js';
@ -87,8 +87,8 @@ const props = defineProps<{
type ModelTChecked = MODELT & ( type ModelTChecked = MODELT & (
MODELT extends GetMkSelectValueTypesFromDef<ITEMS> MODELT extends GetMkSelectValueTypesFromDef<ITEMS>
? unknown ? unknown
: 'Error: The type of model does not match the type of items.' : 'Error: The type of model does not match the type of items.'
); );
const model = defineModel<ModelTChecked>({ required: true }); const model = defineModel<ModelTChecked>({ required: true });
@ -97,10 +97,10 @@ const { autofocus } = toRefs(props);
const focused = ref(false); const focused = ref(false);
const opening = ref(false); const opening = ref(false);
const currentValueText = ref<string | null>(null); const currentValueText = ref<string | null>(null);
const inputEl = ref<HTMLObjectElement | null>(null); const inputEl = useTemplateRef('inputEl');
const prefixEl = ref<HTMLElement | null>(null); const prefixEl = useTemplateRef('prefixEl');
const suffixEl = ref<HTMLElement | null>(null); const suffixEl = useTemplateRef('suffixEl');
const container = ref<HTMLElement | null>(null); const container = useTemplateRef('container');
const height = const height =
props.small ? 33 : props.small ? 33 :
props.large ? 39 : props.large ? 39 :

View File

@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
</MkFoldableSection> </MkFoldableSection>
<MkFoldableSection v-for="category in customEmojiCategories" v-once :key="category ?? '___root___'"> <MkFoldableSection v-for="category in customEmojiCategories" v-once :key="category ?? '___root___'" :expanded="false">
<template #header>{{ category || i18n.ts.other }}</template> <template #header>{{ category || i18n.ts.other }}</template>
<div :class="$style.emojis"> <div :class="$style.emojis">
<XEmoji v-for="emoji in customEmojis.filter(e => e.category === category)" :key="emoji.name" :emoji="emoji"/> <XEmoji v-for="emoji in customEmojis.filter(e => e.category === category)" :key="emoji.name" :emoji="emoji"/>

View File

@ -151,7 +151,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
<div v-else-if="tab === 'announcements'" class="_gaps"> <div v-else-if="tab === 'announcements'" class="_gaps">
<MkButton primary rounded @click="createAnnouncement"><i class="ti ti-plus"></i> {{ i18n.ts.new }}</MkButton> <MkButton primary rounded @click="createAnnouncement"><i class="ti ti-plus"></i> {{ i18n.ts.createNew }}</MkButton>
<MkSelect v-model="announcementsStatus" :items="announcementsStatusDef"> <MkSelect v-model="announcementsStatus" :items="announcementsStatusDef">
<template #label>{{ i18n.ts.filter }}</template> <template #label>{{ i18n.ts.filter }}</template>

View File

@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<h1>{{ i18n.ts._auth.denied }}</h1> <h1>{{ i18n.ts._auth.denied }}</h1>
</div> </div>
<div v-if="state == 'accepted' && session"> <div v-if="state == 'accepted' && session">
<h1>{{ session.app.isAuthorized ? i18n.ts['already-authorized'] : i18n.ts.allowed }}</h1> <h1>{{ session.app.isAuthorized ? i18n.ts._auth.alreadyAuthorized : i18n.ts._auth.accepted }}</h1>
<p v-if="session.app.callbackUrl"> <p v-if="session.app.callbackUrl">
{{ i18n.ts._auth.callback }} {{ i18n.ts._auth.callback }}
<MkEllipsis/> <MkEllipsis/>

View File

@ -28,17 +28,37 @@ SPDX-License-Identifier: AGPL-3.0-only
<span v-else style="opacity: 0.7;">({{ i18n.ts.none }})</span> <span v-else style="opacity: 0.7;">({{ i18n.ts.none }})</span>
</template> </template>
</MkKeyValue> </MkKeyValue>
<MkFolder @opened="onOpened">
<template #icon><i class="ti ti-report-search"></i></template>
<template #label>{{ i18n.ts.deviceInfo }}</template>
<template #caption>{{ i18n.ts.deviceInfoDescription }}</template>
<MkLoading v-if="userEnv == null" />
<MkCode v-else lang="json" :code="JSON.stringify(userEnv, null, 2)" style="max-height: 300px; overflow: auto;"/>
</MkFolder>
</div> </div>
</div> </div>
</PageWithHeader> </PageWithHeader>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js'; import { instance } from '@/instance.js';
import { definePage } from '@/page.js'; import { definePage } from '@/page.js';
import { getUserEnvironment } from '@/utility/get-user-environment.js';
import type { UserEnvironment } from '@/utility/get-user-environment.js';
import MkKeyValue from '@/components/MkKeyValue.vue'; import MkKeyValue from '@/components/MkKeyValue.vue';
import MkFolder from '@/components/MkFolder.vue';
import MkLink from '@/components/MkLink.vue'; import MkLink from '@/components/MkLink.vue';
import MkCode from '@/components/MkCode.vue';
const userEnv = ref<UserEnvironment | null>(null);
async function onOpened() {
if (userEnv.value == null) {
userEnv.value = await getUserEnvironment();
}
}
definePage(() => ({ definePage(() => ({
title: i18n.ts.inquiry, title: i18n.ts.inquiry,

View File

@ -21,7 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkButton primary @click="createKey">{{ i18n.ts._registry.createKey }}</MkButton> <MkButton primary @click="createKey">{{ i18n.ts._registry.createKey }}</MkButton>
<FormSection v-if="keys"> <FormSection v-if="keys">
<template #label>{{ i18n.ts.keys }}</template> <template #label>{{ i18n.ts._registry.keys }}</template>
<div class="_gaps_s"> <div class="_gaps_s">
<FormLink v-for="key in keys" :to="`/registry/value/${props.domain}/${scope.join('/')}/${key[0]}`" class="_monospace">{{ key[0] }}<template #suffix>{{ key[1].toUpperCase() }}</template></FormLink> <FormLink v-for="key in keys" :to="`/registry/value/${props.domain}/${scope.join('/')}/${key[0]}`" class="_monospace">{{ key[0] }}<template #suffix>{{ key[1].toUpperCase() }}</template></FormLink>
</div> </div>

View File

@ -159,7 +159,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { defineAsyncComponent, computed, onMounted, onUnmounted, onActivated, onDeactivated, nextTick, watch, ref } from 'vue'; import { defineAsyncComponent, computed, onMounted, onUnmounted, onActivated, onDeactivated, nextTick, watch, ref, useTemplateRef } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import { getScrollContainer } from '@@/js/scroll.js'; import { getScrollContainer } from '@@/js/scroll.js';
import MkNote from '@/components/MkNote.vue'; import MkNote from '@/components/MkNote.vue';
@ -222,9 +222,9 @@ const router = useRouter();
const user = ref(props.user); const user = ref(props.user);
const narrow = ref<null | boolean>(null); const narrow = ref<null | boolean>(null);
const rootEl = ref<null | HTMLElement>(null); const rootEl = useTemplateRef('rootEl');
const bannerEl = ref<null | HTMLElement>(null); const bannerEl = useTemplateRef('bannerEl');
const memoTextareaEl = ref<null | HTMLElement>(null); const memoTextareaEl = useTemplateRef('memoTextareaEl');
const memoDraft = ref(props.user.memo); const memoDraft = ref(props.user.memo);
const isEditingMemo = ref(false); const isEditingMemo = ref(false);
const moderationNote = ref(props.user.moderationNote ?? ''); const moderationNote = ref(props.user.moderationNote ?? '');

View File

@ -0,0 +1,66 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export type UserEnvironment = {
os: string;
browser: string;
userAgent: string;
screenWidth: number;
screenHeight: number;
viaGetHighEntropyValues: true;
} | {
userAgent: string;
screenWidth: number;
screenHeight: number;
viaGetHighEntropyValues: false;
};
export async function getUserEnvironment(): Promise<UserEnvironment> {
if ('userAgentData' in navigator && navigator.userAgentData != null) {
try {
const uaData: any = await navigator.userAgentData.getHighEntropyValues([
'fullVersionList',
'platformVersion',
]);
let osVersion = 'v' + uaData.platformVersion;
if (uaData.platform === 'Windows' && uaData.platformVersion != null) {
// https://learn.microsoft.com/ja-jp/microsoft-edge/web-platform/how-to-detect-win11
const majorPlatformVersion = parseInt(uaData.platformVersion.split('.')[0]);
if (majorPlatformVersion >= 13) {
osVersion = '11 or later';
} else if (majorPlatformVersion > 0) {
osVersion = '10';
} else {
osVersion = '8.1 or earlier';
}
}
const browserData = uaData.fullVersionList.find((item) => !/^\s*not.+a.+brand\s*$/i.test(item.brand));
return {
os: `${uaData.platform} ${osVersion}`,
browser: browserData ? `${browserData.brand} v${browserData.version}` : 'Unknown',
userAgent: navigator.userAgent,
screenWidth: window.innerWidth,
screenHeight: window.innerHeight,
viaGetHighEntropyValues: true,
};
} catch {
return getViaUa();
}
} else {
return getViaUa();
}
}
function getViaUa(): UserEnvironment {
return {
userAgent: navigator.userAgent,
screenWidth: window.innerWidth,
screenHeight: window.innerHeight,
viaGetHighEntropyValues: false,
};
}

View File

@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<p v-if="widgetProps.folderId == null"> <p v-if="widgetProps.folderId == null">
{{ i18n.ts.folder }} {{ i18n.ts.folder }}
</p> </p>
<p v-if="widgetProps.folderId != null && images.length === 0 && !fetching">{{ i18n.ts['no-image'] }}</p> <p v-if="widgetProps.folderId != null && images.length === 0 && !fetching">{{ i18n.ts.nothing }}</p>
<div ref="slideA" class="slide a"></div> <div ref="slideA" class="slide a"></div>
<div ref="slideB" class="slide b"></div> <div ref="slideB" class="slide b"></div>
</div> </div>

View File

@ -1,7 +1,7 @@
{ {
"type": "module", "type": "module",
"name": "misskey-js", "name": "misskey-js",
"version": "2025.10.0-beta.1", "version": "2025.10.0-beta.2",
"description": "Misskey SDK for JavaScript", "description": "Misskey SDK for JavaScript",
"license": "MIT", "license": "MIT",
"main": "./built/index.js", "main": "./built/index.js",

View File

@ -29205,34 +29205,34 @@ export interface operations {
* @default public * @default public
* @enum {string} * @enum {string}
*/ */
visibility: 'public' | 'home' | 'followers' | 'specified'; visibility?: 'public' | 'home' | 'followers' | 'specified';
visibleUserIds: string[]; visibleUserIds?: string[];
cw: string | null; cw?: string | null;
hashtag: string | null; hashtag?: string | null;
/** @default false */ /** @default false */
localOnly: boolean; localOnly?: boolean;
/** /**
* @default null * @default null
* @enum {string|null} * @enum {string|null}
*/ */
reactionAcceptance: null | 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote'; reactionAcceptance?: null | 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote';
/** Format: misskey:id */ /** Format: misskey:id */
replyId: string | null; replyId?: string | null;
/** Format: misskey:id */ /** Format: misskey:id */
renoteId: string | null; renoteId?: string | null;
/** Format: misskey:id */ /** Format: misskey:id */
channelId: string | null; channelId?: string | null;
text: string | null; text?: string | null;
fileIds: string[]; fileIds?: string[];
poll: { poll?: {
choices: string[]; choices: string[];
multiple?: boolean; multiple?: boolean;
expiresAt?: number | null; expiresAt?: number | null;
expiredAfter?: number | null; expiredAfter?: number | null;
} | null; } | null;
scheduledAt: number | null; scheduledAt?: number | null;
/** @default false */ /** @default false */
isActuallyScheduled: boolean; isActuallyScheduled?: boolean;
}; };
}; };
}; };

File diff suppressed because it is too large Load Diff