Compare commits

..

No commits in common. "develop" and "2025.8.0-beta.5" have entirely different histories.

121 changed files with 2697 additions and 2935 deletions

View file

@ -1,19 +1,3 @@
## 2025.9.0
### Client
- Enhance: AiScriptAppウィジェットで構文エラーを検知してもダイアログではなくウィジェット内にエラーを表示するように
- Enhance: /flushページでサイトキャッシュをクリアできるようになりました
- Enhance: クリップ/リスト/アンテナ/ロール追加系メニュー項目において、表示件数を拡張
- Enhance: 「キャッシュを削除」ボタンでブラウザの内部キャッシュの削除も行えるように
- Enhance: CtrlキーCommandキーを押下しながらリンクをクリックすると新しいタブで開くように
- Fix: プッシュ通知を有効にできない問題を修正
- Fix: RSSティッカーウィジェットが正しく動作しない問題を修正
- Fix: プロファイルを復元後アカウントの切り替えができない問題を修正
- Fix: エラー画像が横に引き伸ばされてしまう問題に対応
### Server
- Fix: webpなどの画像に対してセンシティブなメディアの検出が適用されていなかった問題を修正
## 2025.8.0 ## 2025.8.0
### Note ### Note
@ -22,12 +6,14 @@
### General ### General
- ノートを削除した際、関連するノートが同時に削除されないようになりました - ノートを削除した際、関連するノートが同時に削除されないようになりました
- APIで、「replyIdが存在しているのにreplyがnull」や「renoteIdが存在しているのにrenoteがnull」であるという、今までにはなかったパターンが表れることになります - APIで、「replyIdが存在しているのにreplyがnull」や「renoteIdが存在しているのにrenoteがnull」であるという、今までにはなかったパターンが表れることになります
- 定期的に古いリモートの投稿を削除する機能が実装されました - 定期的に参照されていない古いリモートの投稿を削除する機能が実装されました(コントロールパネル→パフォーマンス→Remote Notes Cleaning)
- コントロールパネル→パフォーマンス→Remote Notes Cleaning で有効化できます - 既存のサーバーでは**デフォルトでオフ**、新規サーバーでは**デフォルトでオン**になります
- データベースの肥大化を防止することが可能です - データベースの肥大化を防止することが可能です
- 既存のサーバーで当機能を有効化した場合は、処理量が多くなるため、一時的にストレージ使用量が増加する可能性があります。 - 既存のサーバーで当機能を有効化した場合は、処理量が多くなるため、一時的にストレージ使用量が増加する可能性があります。
- 増加量を抑えるには、最大処理継続時間をデフォルトより短くしてください。 - 増加量を抑えるには、最大処理継続時間をデフォルトより短くしてください。
- データベースサイズへの効果が見られない場合はautovacuumが有効になっているか確認してください - データベースサイズへの効果が見られない場合はautovacuumが有効になっているか確認してください
- ハイパーリンクによる参照は検知できないためリンク切れとなります。
- 現時点では、2023-10-01以前にクリップされたリモートのートは検知しないため削除対象となります。
- サーバーの初期設定が完了するまでは連合がオンにならないようになりました - サーバーの初期設定が完了するまでは連合がオンにならないようになりました
- 日本語における公開範囲名称の「ダイレクト」が「指名」に改称されました - 日本語における公開範囲名称の「ダイレクト」が「指名」に改称されました
- 実際の動作に即した名称になり、馴染みのない人でも理解しやすくなりました - 実際の動作に即した名称になり、馴染みのない人でも理解しやすくなりました
@ -60,11 +46,9 @@
- Enhance: トルコ語 (tr-TR) に対応 - Enhance: トルコ語 (tr-TR) に対応
- Enhance: 不必要な翻訳データを読み込まなくなり、パフォーマンスが向上しました - Enhance: 不必要な翻訳データを読み込まなくなり、パフォーマンスが向上しました
- Enhance: 画像エフェクトのパラメータ名の多言語対応 - Enhance: 画像エフェクトのパラメータ名の多言語対応
- Enhance: 依存ソフトウェアの更新
- Enhance: ートを非表示にする相対期間を1ヶ月単位で自由に指定できるように - Enhance: ートを非表示にする相対期間を1ヶ月単位で自由に指定できるように
- Enhance: メールアドレス確認画面のUIを改善 - Enhance: メールアドレス確認画面のUIを改善
- Enhance: アイコンのスクロール追従を無効化する際の適用範囲を強化
- Enhance: レンダリングパフォーマンスの向上
- Enhance: 依存ソフトウェアの更新
- Fix: 投稿フォームでファイルのアップロードが中止または失敗した際のハンドリングを修正 - Fix: 投稿フォームでファイルのアップロードが中止または失敗した際のハンドリングを修正
- Fix: 一部の設定検索結果が存在しないパスになる問題を修正 - Fix: 一部の設定検索結果が存在しないパスになる問題を修正
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/1171) (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/1171)
@ -76,7 +60,6 @@
- Fix: ユーザーの前後ノートを閲覧する機能が動作しない問題を修正 - Fix: ユーザーの前後ノートを閲覧する機能が動作しない問題を修正
- Fix: 照会ダイアログでap/showでローカルユーザーを解決した際@username@nullに飛ばされる問題を修正 - Fix: 照会ダイアログでap/showでローカルユーザーを解決した際@username@nullに飛ばされる問題を修正
- Fix: アイコンのデコレーションを付ける際にデコレーションが表示されなくなる問題を修正 - Fix: アイコンのデコレーションを付ける際にデコレーションが表示されなくなる問題を修正
- Fix: タッチ操作時にマウスホバー時のユーザープレビューが開くことがある問題を修正
- Fix: 管理中アカウント一覧で正しい表示が行われない問題を修正 - Fix: 管理中アカウント一覧で正しい表示が行われない問題を修正
- Fix: lookupページでリモートURLを指定した際に正しく動作しない問題を修正 - Fix: lookupページでリモートURLを指定した際に正しく動作しない問題を修正
@ -93,7 +76,6 @@
- Fix: SystemWebhook設定でsecretを空に出来ない問題を修正 - Fix: SystemWebhook設定でsecretを空に出来ない問題を修正
- Fix: 削除されたユーザーがチャットメッセージにリアクションしている場合`chat/history`などでエラーになる問題を修正 - Fix: 削除されたユーザーがチャットメッセージにリアクションしている場合`chat/history`などでエラーになる問題を修正
- Fix: Pageのアイキャッチ画像をドライブから消してもPageごと消えないように - Fix: Pageのアイキャッチ画像をドライブから消してもPageごと消えないように
- Fix: タイムラインAPIの withRenotes: false 時のレスポンスを修正
## 2025.7.0 ## 2025.7.0

View file

@ -1644,7 +1644,7 @@ _serverSettings:
reactionsBufferingDescription: "Quan s'activa aquesta opció millora bastant el rendiment en recuperar les línies de temps reduint la càrrega de la base. Com a contrapunt, augmentarà l'ús de memòria de Redís. Desactiva aquesta opció en cas de tenir un servidor amb poca memòria o si tens problemes d'inestabilitat." reactionsBufferingDescription: "Quan s'activa aquesta opció millora bastant el rendiment en recuperar les línies de temps reduint la càrrega de la base. Com a contrapunt, augmentarà l'ús de memòria de Redís. Desactiva aquesta opció en cas de tenir un servidor amb poca memòria o si tens problemes d'inestabilitat."
remoteNotesCleaning: "Neteja automàtica de notes remotes" remoteNotesCleaning: "Neteja automàtica de notes remotes"
remoteNotesCleaning_description: "Quan activis aquesta opció, periòdicament es netejaran les notes remotes que no es consultin, això evitarà que la base de dades se" remoteNotesCleaning_description: "Quan activis aquesta opció, periòdicament es netejaran les notes remotes que no es consultin, això evitarà que la base de dades se"
remoteNotesCleaningMaxProcessingDuration: "Duració màxima del temps de funcionament del procés de neteja" remoteNotesCleaningMaxProcessingDuration: "D'oració màxima del temps de funcionament del procés de neteja"
remoteNotesCleaningExpiryDaysForEachNotes: "Duració mínima de conservació de les notes" remoteNotesCleaningExpiryDaysForEachNotes: "Duració mínima de conservació de les notes"
inquiryUrl: "URL de consulta " inquiryUrl: "URL de consulta "
inquiryUrlDescription: "Escriu adreça URL per al formulari de consulta per al mantenidor del servidor o una pàgina web amb el contacte d'informació." inquiryUrlDescription: "Escriu adreça URL per al formulari de consulta per al mantenidor del servidor o una pàgina web amb el contacte d'informació."
@ -3120,6 +3120,7 @@ _serverSetupWizard:
youCanConfigureMoreFederationSettingsLater: "Les configuracions avançades, com especificar els servidors amb els quals es pot federar, es poden fer més tard." youCanConfigureMoreFederationSettingsLater: "Les configuracions avançades, com especificar els servidors amb els quals es pot federar, es poden fer més tard."
remoteContentsCleaning: "Neteja automàtica del contingut rebut" remoteContentsCleaning: "Neteja automàtica del contingut rebut"
remoteContentsCleaning_description: "Quan es comença a federar es rep un munt de contingut, quan s'activa la neteja automàtica el contingut antic que no es consulta serà eliminat del servidor, el que permet estalviar espai d'emmagatzematge." remoteContentsCleaning_description: "Quan es comença a federar es rep un munt de contingut, quan s'activa la neteja automàtica el contingut antic que no es consulta serà eliminat del servidor, el que permet estalviar espai d'emmagatzematge."
remoteContentsCleaning_description2: "Alguns mètodes de referència, com els enllaços, no poden ser detectats pel sistema."
adminInfo: "Informació de l'administrador " adminInfo: "Informació de l'administrador "
adminInfo_description: "Estableix la informació de l'administrador que es farà servir per rebre consultes." adminInfo_description: "Estableix la informació de l'administrador que es farà servir per rebre consultes."
adminInfo_mustBeFilled: "Aquesta informació ha de ser omplerta si el servidor té els registres oberts o la federació es troba activada." adminInfo_mustBeFilled: "Aquesta informació ha de ser omplerta si el servidor té els registres oberts o la federació es troba activada."

View file

@ -1668,7 +1668,6 @@ _serverSettings:
restartServerSetupWizardConfirm_text: "Some current settings will be reset." restartServerSetupWizardConfirm_text: "Some current settings will be reset."
entrancePageStyle: "Entrance page style" entrancePageStyle: "Entrance page style"
showTimelineForVisitor: "Show timeline" showTimelineForVisitor: "Show timeline"
showActivitiesForVisitor: "Show activities"
_userGeneratedContentsVisibilityForVisitor: _userGeneratedContentsVisibilityForVisitor:
all: "Everything is public" all: "Everything is public"
localOnly: "Only local content is published, remote content is kept private" localOnly: "Only local content is published, remote content is kept private"

View file

@ -2137,7 +2137,7 @@ _aboutMisskey:
_displayOfSensitiveMedia: _displayOfSensitiveMedia:
respect: "Esconder medios marcados como sensibles" respect: "Esconder medios marcados como sensibles"
ignore: "Mostrar medios marcados como sensibles" ignore: "Mostrar medios marcados como sensibles"
force: "Esconder toda la multimedia" force: "Esconder todala multimedia"
_instanceTicker: _instanceTicker:
none: "No mostrar" none: "No mostrar"
remote: "Mostrar a usuarios remotos" remote: "Mostrar a usuarios remotos"
@ -3120,6 +3120,7 @@ _serverSetupWizard:
youCanConfigureMoreFederationSettingsLater: "Los ajustes avanzados, como la especificación de servidores federados, pueden configurarse más adelante." youCanConfigureMoreFederationSettingsLater: "Los ajustes avanzados, como la especificación de servidores federados, pueden configurarse más adelante."
remoteContentsCleaning: "Limpieza automática de los contenidos recibidos" remoteContentsCleaning: "Limpieza automática de los contenidos recibidos"
remoteContentsCleaning_description: "La federación puede dar lugar a un flujo continuo de contenido. Al habilitar la limpieza automática, se eliminará del servidor el contenido obsoleto y sin referencias para ahorrar espacio de almacenamiento." remoteContentsCleaning_description: "La federación puede dar lugar a un flujo continuo de contenido. Al habilitar la limpieza automática, se eliminará del servidor el contenido obsoleto y sin referencias para ahorrar espacio de almacenamiento."
remoteContentsCleaning_description2: "Ciertos métodos de referencia, como los hipervínculos, no pueden ser detectados por el sistema."
adminInfo: "Información del administrador" adminInfo: "Información del administrador"
adminInfo_description: "Establece la información del administrador para recibir consultas." adminInfo_description: "Establece la información del administrador para recibir consultas."
adminInfo_mustBeFilled: "Esta información debe ser introducida en el caso de registros abiertos o la federación esté activada." adminInfo_mustBeFilled: "Esta información debe ser introducida en el caso de registros abiertos o la federación esté activada."

8
locales/index.d.ts vendored
View file

@ -6531,7 +6531,7 @@ export interface Locale extends ILocale {
*/ */
"remoteNotesCleaning": string; "remoteNotesCleaning": string;
/** /**
* 稿 * 稿
*/ */
"remoteNotesCleaning_description": string; "remoteNotesCleaning_description": string;
/** /**
@ -12036,9 +12036,13 @@ export interface Locale extends ILocale {
*/ */
"remoteContentsCleaning": string; "remoteContentsCleaning": string;
/** /**
* *
*/ */
"remoteContentsCleaning_description": string; "remoteContentsCleaning_description": string;
/**
*
*/
"remoteContentsCleaning_description2": string;
/** /**
* *
*/ */

View file

@ -139,7 +139,7 @@ overwriteFromPinnedEmojis: "Sovrascrivi con le impostazioni globali"
reactionSettingDescription2: "Trascina per riorganizzare, clicca per cancellare, usa il pulsante \"+\" per aggiungere." reactionSettingDescription2: "Trascina per riorganizzare, clicca per cancellare, usa il pulsante \"+\" per aggiungere."
rememberNoteVisibility: "Ricordare le impostazioni di visibilità delle note" rememberNoteVisibility: "Ricordare le impostazioni di visibilità delle note"
attachCancel: "Rimuovi allegato" attachCancel: "Rimuovi allegato"
deleteFile: "Elimina un file dal Drive" deleteFile: "File da Drive eliminato"
markAsSensitive: "Segna come esplicito" markAsSensitive: "Segna come esplicito"
unmarkAsSensitive: "Non segnare come esplicito " unmarkAsSensitive: "Non segnare come esplicito "
enterFileName: "Nome del file" enterFileName: "Nome del file"
@ -2222,7 +2222,7 @@ _theme:
hashtag: "Hashtag" hashtag: "Hashtag"
mention: "Menzioni" mention: "Menzioni"
mentionMe: "Menzioni (di me)" mentionMe: "Menzioni (di me)"
renote: "Rinota" renote: "Renota"
modalBg: "Sfondo modale." modalBg: "Sfondo modale."
divider: "Interruzione di linea" divider: "Interruzione di linea"
scrollbarHandle: "Maniglie della barra di scorrimento" scrollbarHandle: "Maniglie della barra di scorrimento"
@ -2663,7 +2663,7 @@ _notification:
createToken: "È stato creato un token di accesso" createToken: "È stato creato un token di accesso"
createTokenDescription: "In caso contrario, eliminare il token di accesso tramite ({text})." createTokenDescription: "In caso contrario, eliminare il token di accesso tramite ({text})."
_types: _types:
all: "Tutte" all: "Tutto"
note: "Nuove Note" note: "Nuove Note"
follow: "Follower" follow: "Follower"
mention: "Menzioni" mention: "Menzioni"
@ -2671,7 +2671,7 @@ _notification:
renote: "Rinota" renote: "Rinota"
quote: "Cita" quote: "Cita"
reaction: "Reazioni" reaction: "Reazioni"
pollEnded: "Sondaggio terminato" pollEnded: "Sondaggio chiuso."
receiveFollowRequest: "Richieste di follow in arrivo" receiveFollowRequest: "Richieste di follow in arrivo"
followRequestAccepted: "Richieste di follow accettate" followRequestAccepted: "Richieste di follow accettate"
roleAssigned: "Ruolo concesso" roleAssigned: "Ruolo concesso"
@ -2679,7 +2679,7 @@ _notification:
achievementEarned: "Risultato raggiunto" achievementEarned: "Risultato raggiunto"
exportCompleted: "Esportazione completata" exportCompleted: "Esportazione completata"
login: "Accessi" login: "Accessi"
createToken: "Aggiunto un token di accesso" createToken: "Creare un token di accesso"
test: "Notifiche di test" test: "Notifiche di test"
app: "Notifiche da applicazioni" app: "Notifiche da applicazioni"
_actions: _actions:
@ -2771,56 +2771,56 @@ _abuseReport:
notifiedWebhook: "Webhook da usare" notifiedWebhook: "Webhook da usare"
deleteConfirm: "Vuoi davvero rimuovere il destinatario della notifica?" deleteConfirm: "Vuoi davvero rimuovere il destinatario della notifica?"
_moderationLogTypes: _moderationLogTypes:
createRole: "Crea un Ruolo" createRole: "Ruolo creato"
deleteRole: "Elimina un Ruolo" deleteRole: "Ruolo eliminato"
updateRole: "Modifica un ruolo" updateRole: "Ruolo aggiornato"
assignRole: "Assegna un Ruolo" assignRole: "Ruolo assegnato"
unassignRole: "Toglie un Ruolo al Profilo" unassignRole: "Ruolo disassegnato"
suspend: "Sospende" suspend: "Sospensione"
unsuspend: "Solleva la sospensione" unsuspend: "Sospensione rimossa"
addCustomEmoji: "Aggiunge Emoji personalizzata" addCustomEmoji: "Emoji personalizzata aggiunta"
updateCustomEmoji: "Modifica Emoji personalizzata" updateCustomEmoji: "Emoji personalizzata aggiornata"
deleteCustomEmoji: "Elimina Emoji personalizzata" deleteCustomEmoji: "Emoji personalizzata eliminata"
updateServerSettings: "Modifica le impostazioni del server" updateServerSettings: "Impostazioni del server aggiornate"
updateUserNote: "Modifica un promemoria di moderazione" updateUserNote: "Promemoria di moderazione aggiornato"
deleteDriveFile: "Elimina un file dal Drive" deleteDriveFile: "File da Drive eliminato"
deleteNote: "Elimina una Nota" deleteNote: "Nota eliminata"
createGlobalAnnouncement: "Crea un annuncio globale" createGlobalAnnouncement: "Annuncio globale creato"
createUserAnnouncement: "Crea un annuncio ai profili già iscritti" createUserAnnouncement: "Annuncio ai profili iscritti creato"
updateGlobalAnnouncement: "Modifica un annuncio globale" updateGlobalAnnouncement: "Annuncio globale aggiornato"
updateUserAnnouncement: "Modifica un annuncio ai profili già iscritti" updateUserAnnouncement: "Annuncio ai profili iscritti aggiornato"
deleteGlobalAnnouncement: "Elimina un annuncio globale" deleteGlobalAnnouncement: "Annuncio globale eliminato"
deleteUserAnnouncement: "Elimina un annuncio ai profili già iscritti" deleteUserAnnouncement: "Annuncio ai profili iscritti eliminato"
resetPassword: "Azzera la password" resetPassword: "Password azzerata"
suspendRemoteInstance: "Sospende una istanza remota" suspendRemoteInstance: "Istanza remota sospesa"
unsuspendRemoteInstance: "Riattiva una istanza remota" unsuspendRemoteInstance: "Istanza remota riattivata"
updateRemoteInstanceNote: "Modifica il promemoria di moderazione per il server remoto" updateRemoteInstanceNote: "Aggiornamento del promemoria di moderazione per il server remoto"
markSensitiveDriveFile: "Aggiunge NSFW a un file nel Drive" markSensitiveDriveFile: "File nel Drive segnato come esplicito"
unmarkSensitiveDriveFile: "Toglie NSFW da un file nel Drive" unmarkSensitiveDriveFile: "File nel Drive segnato come non esplicito"
resolveAbuseReport: "Risolve una segnalazione" resolveAbuseReport: "Segnalazione risolta"
forwardAbuseReport: "Inoltra una segnalazione" forwardAbuseReport: "Segnalazione inoltrata"
updateAbuseReportNote: "Modifica una segnalazione" updateAbuseReportNote: "Ha aggiornato la segnalazione"
createInvitation: "Genera un codice di invito" createInvitation: "Genera codice di invito"
createAd: "Aggiunge un Banner" createAd: "Banner creato"
deleteAd: "Elimina un Banner" deleteAd: "Banner eliminato"
updateAd: "Modifica un Banner" updateAd: "Banner aggiornato"
createAvatarDecoration: "Crea una decorazione della foto profilo" createAvatarDecoration: "Creazione decorazione della foto profilo"
updateAvatarDecoration: "Modifica una decorazione della foto profilo" updateAvatarDecoration: "Aggiornamento decorazione foto profilo"
deleteAvatarDecoration: "Elimina una decorazione della foto profilo" deleteAvatarDecoration: "Eliminazione decorazione della foto profilo"
unsetUserAvatar: "Toglie una foto profilo" unsetUserAvatar: "Rimossa foto profilo"
unsetUserBanner: "Toglie una immagine di intestazione profilo" unsetUserBanner: "Rimossa intestazione profilo"
createSystemWebhook: "Aggiunge un System Webhook" createSystemWebhook: "Crea un SystemWebhook"
updateSystemWebhook: "Modifica un System Webhook" updateSystemWebhook: "Modifica SystemWebhook"
deleteSystemWebhook: "Elimina un System Webhook" deleteSystemWebhook: "Elimina SystemWebhook"
createAbuseReportNotificationRecipient: "Crea destinatario per le notifiche di segnalazioni" createAbuseReportNotificationRecipient: "Crea destinatario per le notifiche di segnalazioni"
updateAbuseReportNotificationRecipient: "Modifica un destinatario per le notifiche di segnalazioni" updateAbuseReportNotificationRecipient: "Aggiorna destinatario notifiche di segnalazioni"
deleteAbuseReportNotificationRecipient: "Elimina un destinatario per le notifiche di segnalazioni" deleteAbuseReportNotificationRecipient: "Elimina destinatario notifiche di segnalazioni"
deleteAccount: "Elimina un profilo" deleteAccount: "Quando viene eliminato un profilo"
deletePage: "Elimina una Pagina" deletePage: "Pagina eliminata"
deleteFlash: "Elimina un Play" deleteFlash: "Play eliminato"
deleteGalleryPost: "Elimina pubblicazione nella Galleria" deleteGalleryPost: "Eliminazione pubblicazione nella Galleria"
deleteChatRoom: "Elimina una Chat" deleteChatRoom: "Elimina chat"
updateProxyAccountDescription: "Aggiorna la descrizione del profilo proxy" updateProxyAccountDescription: "Aggiornata la descrizione del profilo proxy"
_fileViewer: _fileViewer:
title: "Dettagli del file" title: "Dettagli del file"
type: "Tipo di file" type: "Tipo di file"
@ -3120,6 +3120,7 @@ _serverSetupWizard:
youCanConfigureMoreFederationSettingsLater: "Puoi svolgere la configurazione avanzata anche dopo. Ad esempio specificando quali server possono federarsi." youCanConfigureMoreFederationSettingsLater: "Puoi svolgere la configurazione avanzata anche dopo. Ad esempio specificando quali server possono federarsi."
remoteContentsCleaning: "Pulizia automatica dei contenuti in arrivo" remoteContentsCleaning: "Pulizia automatica dei contenuti in arrivo"
remoteContentsCleaning_description: "Con la federazione funzionante, riceverai sempre più contenuti. Abilitando la pulizia automatica, i contenuti non referenziati e obsoleti verranno rimossi automaticamente dai tuoi server, risparmiando spazio di archiviazione." remoteContentsCleaning_description: "Con la federazione funzionante, riceverai sempre più contenuti. Abilitando la pulizia automatica, i contenuti non referenziati e obsoleti verranno rimossi automaticamente dai tuoi server, risparmiando spazio di archiviazione."
remoteContentsCleaning_description2: "Alcuni metodi di riferimento, come i collegamenti ipertestuali, non possono essere rilevati sul sistema."
adminInfo: "Informazioni sull'amministratore" adminInfo: "Informazioni sull'amministratore"
adminInfo_description: "Imposta le informazioni dell'amministratore utilizzate per accettare le richieste." adminInfo_description: "Imposta le informazioni dell'amministratore utilizzate per accettare le richieste."
adminInfo_mustBeFilled: "Questa operazione è necessaria su un server aperto o se è attiva la federazione." adminInfo_mustBeFilled: "Questa operazione è necessaria su un server aperto o se è attiva la federazione."

View file

@ -1660,7 +1660,7 @@ _serverSettings:
fanoutTimelineDbFallbackDescription: "有効にすると、タイムラインがキャッシュされていない場合にDBへ追加で問い合わせを行うフォールバック処理を行います。無効にすると、フォールバック処理を行わないことでさらにサーバーの負荷を軽減することができますが、タイムラインが取得できる範囲に制限が生じます。" fanoutTimelineDbFallbackDescription: "有効にすると、タイムラインがキャッシュされていない場合にDBへ追加で問い合わせを行うフォールバック処理を行います。無効にすると、フォールバック処理を行わないことでさらにサーバーの負荷を軽減することができますが、タイムラインが取得できる範囲に制限が生じます。"
reactionsBufferingDescription: "有効にすると、リアクション作成時のパフォーマンスが大幅に向上し、データベースへの負荷を軽減することが可能です。ただし、Redisのメモリ使用量は増加します。" reactionsBufferingDescription: "有効にすると、リアクション作成時のパフォーマンスが大幅に向上し、データベースへの負荷を軽減することが可能です。ただし、Redisのメモリ使用量は増加します。"
remoteNotesCleaning: "リモート投稿の自動クリーニング" remoteNotesCleaning: "リモート投稿の自動クリーニング"
remoteNotesCleaning_description: "有効にすると、一定期間経過したリモートの投稿を定期的にクリーンアップしてデータベースの肥大化を抑制します。" remoteNotesCleaning_description: "有効にすると、参照されていない古いリモートの投稿を定期的にクリーンアップしてデータベースの肥大化を抑制します。"
remoteNotesCleaningMaxProcessingDuration: "最大クリーニング処理継続時間" remoteNotesCleaningMaxProcessingDuration: "最大クリーニング処理継続時間"
remoteNotesCleaningExpiryDaysForEachNotes: "最低ノート保持日数" remoteNotesCleaningExpiryDaysForEachNotes: "最低ノート保持日数"
inquiryUrl: "問い合わせ先URL" inquiryUrl: "問い合わせ先URL"
@ -3217,7 +3217,8 @@ _serverSetupWizard:
doYouConnectToFediverse_description2: "Fediverseと接続することは「連合」とも呼ばれます。" doYouConnectToFediverse_description2: "Fediverseと接続することは「連合」とも呼ばれます。"
youCanConfigureMoreFederationSettingsLater: "連合可能なサーバーの指定など、高度な設定も後ほど可能です。" youCanConfigureMoreFederationSettingsLater: "連合可能なサーバーの指定など、高度な設定も後ほど可能です。"
remoteContentsCleaning: "リモートコンテンツの自動クリーニング" remoteContentsCleaning: "リモートコンテンツの自動クリーニング"
remoteContentsCleaning_description: "連合を行うと、継続して多くのコンテンツを受信します。自動クリーニングを有効にすると、一定期間経過したリモートコンテンツを自動でサーバーから削除し、ストレージを節約できます。" remoteContentsCleaning_description: "連合を行うと、継続して多くのコンテンツを受信します。自動クリーニングを有効にすると、参照されていない古くなったリモートコンテンツを自動でサーバーから削除し、ストレージを節約できます。"
remoteContentsCleaning_description2: "ローカル内リモートコンテンツへのハイパーリンクはリンク切れとなります。"
adminInfo: "管理者情報" adminInfo: "管理者情報"
adminInfo_description: "問い合わせを受け付けるために使用される管理者情報を設定します。" adminInfo_description: "問い合わせを受け付けるために使用される管理者情報を設定します。"
adminInfo_mustBeFilled: "オープンサーバー、または連合がオンの場合は必ず入力が必要です。" adminInfo_mustBeFilled: "オープンサーバー、または連合がオンの場合は必ず入力が必要です。"

View file

@ -3120,6 +3120,7 @@ _serverSetupWizard:
youCanConfigureMoreFederationSettingsLater: "나중에 연합 가능한 서버의 지정 등 고급 설정을 할 수 있습니다." youCanConfigureMoreFederationSettingsLater: "나중에 연합 가능한 서버의 지정 등 고급 설정을 할 수 있습니다."
remoteContentsCleaning: "리모트 콘텐츠 자동 정리" remoteContentsCleaning: "리모트 콘텐츠 자동 정리"
remoteContentsCleaning_description: "연합 중인 서버가 있는 경우, 리모트 서버에서 대단히 많은 콘텐츠를 받아오게 됩니다. 자동 정리 기능을 활성화하면, 오래되고 서버에서 더 이상 조회되지 않는 콘텐츠를 자동으로 서버에서 삭제하여, 스토리지를 절약할 수 있습니다." remoteContentsCleaning_description: "연합 중인 서버가 있는 경우, 리모트 서버에서 대단히 많은 콘텐츠를 받아오게 됩니다. 자동 정리 기능을 활성화하면, 오래되고 서버에서 더 이상 조회되지 않는 콘텐츠를 자동으로 서버에서 삭제하여, 스토리지를 절약할 수 있습니다."
remoteContentsCleaning_description2: "로컬 내 원격 콘텐츠로의 하이퍼링크는 깨진 링크로 됩니다."
adminInfo: "관리자 정보" adminInfo: "관리자 정보"
adminInfo_description: "문의 접수를 위해 사용되는 관리자 정보를 설정합니다." adminInfo_description: "문의 접수를 위해 사용되는 관리자 정보를 설정합니다."
adminInfo_mustBeFilled: "오픈 서버 혹은 연합이 켜져 있는 경우 반드시 입력해야 합니다." adminInfo_mustBeFilled: "오픈 서버 혹은 연합이 켜져 있는 경우 반드시 입력해야 합니다."

View file

@ -1215,7 +1215,6 @@ privacyPolicyUrl: "Ссылка на Политику Конфиденциаль
tosAndPrivacyPolicy: "Условия использования и политика конфиденциальности" tosAndPrivacyPolicy: "Условия использования и политика конфиденциальности"
avatarDecorations: "Украшения для аватара" avatarDecorations: "Украшения для аватара"
attach: "Прикрепить" attach: "Прикрепить"
detachAll: "Убрать всё"
angle: "Угол" angle: "Угол"
flip: "Переворот" flip: "Переворот"
showAvatarDecorations: "Показать украшения для аватара" showAvatarDecorations: "Показать украшения для аватара"
@ -1254,7 +1253,7 @@ clipNoteLimitExceeded: "К этому клипу больше нельзя до
performance: "Производительность" performance: "Производительность"
modified: "Изменено" modified: "Изменено"
signinWithPasskey: "Войдите в систему, используя свой пароль" signinWithPasskey: "Войдите в систему, используя свой пароль"
unknownWebAuthnKey: "Неизвестный ключ" unknownWebAuthnKey: "Не известный ключ "
passkeyVerificationFailed: "Ошибка проверка ключа доступа " passkeyVerificationFailed: "Ошибка проверка ключа доступа "
messageToFollower: "Сообщение подписчикам" messageToFollower: "Сообщение подписчикам"
testCaptchaWarning: "Эта функция предназначена для тестирования CAPTCHA. <strong>Не использовать это в рабочей среде</strong>" testCaptchaWarning: "Эта функция предназначена для тестирования CAPTCHA. <strong>Не использовать это в рабочей среде</strong>"
@ -1269,11 +1268,8 @@ availableRoles: "Доступные роли"
federationDisabled: "Федерация отключена для этого сервера. Вы не можете взаимодействовать с пользователями на других серверах." federationDisabled: "Федерация отключена для этого сервера. Вы не можете взаимодействовать с пользователями на других серверах."
draft: "Черновик" draft: "Черновик"
markAsSensitiveConfirm: "Отметить контент как чувствительный?" markAsSensitiveConfirm: "Отметить контент как чувствительный?"
preferences: "Основное"
resetToDefaultValue: "Сбросить настройки до стандартных" resetToDefaultValue: "Сбросить настройки до стандартных"
syncBetweenDevices: "Синхронизировать между устройствами"
postForm: "Форма отправки" postForm: "Форма отправки"
textCount: "Количество символов"
information: "Описание" information: "Описание"
inMinutes: "мин" inMinutes: "мин"
inDays: "сут" inDays: "сут"
@ -1285,11 +1281,6 @@ _chat:
send: "Отправить" send: "Отправить"
_settings: _settings:
webhook: "Вебхук" webhook: "Вебхук"
preferencesBanner: "Вы можете настроить общее поведение клиента по вашим предпочтениям"
timelineAndNote: "Лента и заметки"
_chat:
showSenderName: "Показывать имя отправителя"
sendOnEnter: "Использовать Enter для отправки"
_delivery: _delivery:
stop: "Заморожено" stop: "Заморожено"
_type: _type:
@ -1538,7 +1529,7 @@ _achievements:
description: "Нажато здесь" description: "Нажато здесь"
_justPlainLucky: _justPlainLucky:
title: "Чистая удача" title: "Чистая удача"
description: "Может достаться с вероятностью 0,005% каждые 10 секунд." description: "Может достаться с вероятностью 0,01% каждые 10 секунд."
_setNameToSyuilo: _setNameToSyuilo:
title: "Комплекс бога" title: "Комплекс бога"
description: "Установлено «syuilo» в качестве имени" description: "Установлено «syuilo» в качестве имени"
@ -1566,12 +1557,6 @@ _achievements:
title: "Brain Diver" title: "Brain Diver"
description: "Опубликована ссылка на песню «Brain Diver»" description: "Опубликована ссылка на песню «Brain Diver»"
flavor: "Мисски-Мисски Ла-Ту-Ма" flavor: "Мисски-Мисски Ла-Ту-Ма"
_bubbleGameExplodingHead:
title: "🤯"
description: "Самый большой объект в Bubble game"
_bubbleGameDoubleExplodingHead:
title: "Двойной🤯"
description: "Два самых больших объекта в Bubble game одновременно!"
_role: _role:
new: "Новая роль" new: "Новая роль"
edit: "Изменить роль" edit: "Изменить роль"

View file

@ -360,7 +360,7 @@ whenServerDisconnected: "Sunucu ile bağlantı kesildiğinde"
disconnectedFromServer: "Sunucu bağlantısı kesildi" disconnectedFromServer: "Sunucu bağlantısı kesildi"
reload: "Yenile" reload: "Yenile"
doNothing: "Yoksay" doNothing: "Yoksay"
reloadConfirm: "Panoyu yenilemek ister misin?" reloadConfirm: "Zaman çizelgesini yenilemek ister misin?"
watch: "İzle" watch: "İzle"
unwatch: "İzlemeyi bırak" unwatch: "İzlemeyi bırak"
accept: "Kabul et" accept: "Kabul et"
@ -573,9 +573,9 @@ objectStorageSetPublicRead: "Yükleme sırasında \"genel-okuma\" ayarını yap
s3ForcePathStyleDesc: "s3ForcePathStyle etkinleştirilirse, kova adı URL'nin ana bilgisayar adı yerine URL yoluna eklenmelidir. Kendi kendine barındırılan bir Minio örneği gibi hizmetleri kullanırken bu ayarı etkinleştirmen gerekebilir." s3ForcePathStyleDesc: "s3ForcePathStyle etkinleştirilirse, kova adı URL'nin ana bilgisayar adı yerine URL yoluna eklenmelidir. Kendi kendine barındırılan bir Minio örneği gibi hizmetleri kullanırken bu ayarı etkinleştirmen gerekebilir."
serverLogs: "Sunucu log kayıtları" serverLogs: "Sunucu log kayıtları"
deleteAll: "Tümünü sil" deleteAll: "Tümünü sil"
showFixedPostForm: "Gönderi formunu pano üstünde görüntüle" showFixedPostForm: "Gönderi formunu zaman çizelgesinin en üstünde görüntüle"
showFixedPostFormInChannel: "Gönderi formunu pano üstünde görüntüle (Kanallar)" showFixedPostFormInChannel: "Gönderi formunu zaman çizelgesinin en üstünde görüntüle (Kanallar)"
withRepliesByDefaultForNewlyFollowed: "Yeni takip edilen kullanıcıların yanıtlarını varsayılan olarak panoya dahil et" withRepliesByDefaultForNewlyFollowed: "Yeni takip edilen kullanıcıların yanıtlarını varsayılan olarak zaman çizelgesine dahil et"
newNoteRecived: "Yeni Not'lar var" newNoteRecived: "Yeni Not'lar var"
newNote: "Yeni Not" newNote: "Yeni Not"
sounds: "Sesler" sounds: "Sesler"
@ -1059,7 +1059,7 @@ achievements: "Başarılar"
gotInvalidResponseError: "Geçersiz sunucu yanıtı" gotInvalidResponseError: "Geçersiz sunucu yanıtı"
gotInvalidResponseErrorDescription: "Sunucu erişilemez durumda olabilir veya bakım çalışması yapılmaktadır. Lütfen daha sonra tekrar dene." gotInvalidResponseErrorDescription: "Sunucu erişilemez durumda olabilir veya bakım çalışması yapılmaktadır. Lütfen daha sonra tekrar dene."
thisPostMayBeAnnoying: "Bu not başkalarını rahatsız edebilir." thisPostMayBeAnnoying: "Bu not başkalarını rahatsız edebilir."
thisPostMayBeAnnoyingHome: "Ana panoya gönder" thisPostMayBeAnnoyingHome: "Ana zaman çizelgesine gönder"
thisPostMayBeAnnoyingCancel: "İptal" thisPostMayBeAnnoyingCancel: "İptal"
thisPostMayBeAnnoyingIgnore: "Yine de gönder" thisPostMayBeAnnoyingIgnore: "Yine de gönder"
collapseRenotes: "Daha önce görüntülenen Renote'lari kısaltılmış olarak göster" collapseRenotes: "Daha önce görüntülenen Renote'lari kısaltılmış olarak göster"
@ -1218,8 +1218,8 @@ showRepliesToOthersInTimeline: "Pano'da diğer kişilere verilen yanıtları
hideRepliesToOthersInTimeline: "Pano'dan diğer kişilerin yanıtlarını gizle" hideRepliesToOthersInTimeline: "Pano'dan diğer kişilerin yanıtlarını gizle"
showRepliesToOthersInTimelineAll: "Pano'da takip ettiğin herkesin diğerlerine verdiği yanıtları göster" showRepliesToOthersInTimelineAll: "Pano'da takip ettiğin herkesin diğerlerine verdiği yanıtları göster"
hideRepliesToOthersInTimelineAll: "Pano'da takip ettiğin herkesten diğer kişilere verilen yanıtları gizle" hideRepliesToOthersInTimelineAll: "Pano'da takip ettiğin herkesten diğer kişilere verilen yanıtları gizle"
confirmShowRepliesAll: "Bu işlem geri alınamaz. Takip ettiğin herkesin yanıtlarını panoda diğer kullanıcılara göstermek istiyor musun?" confirmShowRepliesAll: "Bu işlem geri alınamaz. Takip ettiğin herkesin yanıtlarını zaman çizelgende diğer kullanıcılara göstermek istiyor musun?"
confirmHideRepliesAll: "Bu işlem geri alınamaz. Şu anda takip ettiğin tüm kullanıcıların yanıtlarını panoda cidden göstermeyecek misin?" confirmHideRepliesAll: "Bu işlem geri alınamaz. Şu anda takip ettiğin tüm kullanıcıların yanıtlarını zaman tünelinde cidden göstermeyecek misin?"
externalServices: "Dış Hizmetler" externalServices: "Dış Hizmetler"
sourceCode: "Kaynak kodu" sourceCode: "Kaynak kodu"
sourceCodeIsNotYetProvided: "Kaynak kodu henüz mevcut değildir. Bu sorunu gidermek için yöneticiyle iletişime geçin." sourceCodeIsNotYetProvided: "Kaynak kodu henüz mevcut değildir. Bu sorunu gidermek için yöneticiyle iletişime geçin."
@ -1570,9 +1570,9 @@ _initialTutorial:
description: "Burada, Misskey'i kullanmanın temellerini ve özelliklerini öğrenebilirsin." description: "Burada, Misskey'i kullanmanın temellerini ve özelliklerini öğrenebilirsin."
_note: _note:
title: "Not nedir?" title: "Not nedir?"
description: "Misskey'deki gönderiler “Notlar” olarak adlandırılır. Notlar panoda kronolojik olarak düzenlenir ve gerçek zamanlı olarak güncellenir." description: "Misskey'deki gönderiler “Notlar” olarak adlandırılır. Notlar zaman çizelgesinde kronolojik olarak düzenlenir ve gerçek zamanlı olarak güncellenir."
reply: "Bir mesaja yanıt vermek için bu düğmeye tıklayın. Yanıtlara yanıt vermek de mümkündür, böylece konuşma bir konu başlığı gibi devam eder." reply: "Bir mesaja yanıt vermek için bu düğmeye tıklayın. Yanıtlara yanıt vermek de mümkündür, böylece konuşma bir konu başlığı gibi devam eder."
renote: "Bu notu kendi panonda paylaşabilirsin. Ayrıca yorumlarınla birlikte alıntı da yapabilirsin." renote: "Bu notu kendi zaman çizelgende paylaşabilirsiniz. Ayrıca yorumlarınızla birlikte alıntı da yapabilirsin."
reaction: "Not'a tepkiler ekleyebilirsin. Daha fazla ayrıntı bir sonraki sayfada açıklanacak." reaction: "Not'a tepkiler ekleyebilirsin. Daha fazla ayrıntı bir sonraki sayfada açıklanacak."
menu: "Not ayrıntılarını görüntüleyebilir, bağlantıları kopyalayabilir ve çeşitli diğer işlemleri gerçekleştirebilirsin." menu: "Not ayrıntılarını görüntüleyebilir, bağlantıları kopyalayabilir ve çeşitli diğer işlemleri gerçekleştirebilirsin."
_reaction: _reaction:
@ -1640,7 +1640,7 @@ _serverSettings:
shortNameDescription: "Resmi adın uzun olması durumunda görüntülenebilen, örneğin adının kısaltması." shortNameDescription: "Resmi adın uzun olması durumunda görüntülenebilen, örneğin adının kısaltması."
fanoutTimelineDescription: "Etkinleştirildiğinde Pano alma performansını büyük ölçüde artırır ve veritabanı yükünü azaltır. Bunun karşılığında Redis'in bellek kullanımı artacaktır. Sunucu belleği düşükse veya sunucu kararsızsa bunu devre dışı bırakmayı düşün." fanoutTimelineDescription: "Etkinleştirildiğinde Pano alma performansını büyük ölçüde artırır ve veritabanı yükünü azaltır. Bunun karşılığında Redis'in bellek kullanımı artacaktır. Sunucu belleği düşükse veya sunucu kararsızsa bunu devre dışı bırakmayı düşün."
fanoutTimelineDbFallback: "Veritabanına geri dön" fanoutTimelineDbFallback: "Veritabanına geri dön"
fanoutTimelineDbFallbackDescription: "Etkinleştirildiğinde, Pano önbelleğe alınmamışsa ek sorgular için veritabanına geri döner. Bu özelliği devre dışı bırakmak, geri dönüş sürecini ortadan kaldırarak sunucu yükünü daha da azaltır, ancak alınabilecek panoların aralığını sınırlar." fanoutTimelineDbFallbackDescription: "Etkinleştirildiğinde, Pano önbelleğe alınmamışsa ek sorgular için veritabanına geri döner. Bu özelliği devre dışı bırakmak, geri dönüş sürecini ortadan kaldırarak sunucu yükünü daha da azaltır, ancak alınabilecek zaman çizelgelerinin aralığını sınırlar."
reactionsBufferingDescription: "Etkinleştirildiğinde, reaksiyon oluşturma sırasında performans büyük ölçüde artacak ve veritabanı üzerindeki yük azalacaktır. Ancak, Redis bellek kullanımı artacakt." reactionsBufferingDescription: "Etkinleştirildiğinde, reaksiyon oluşturma sırasında performans büyük ölçüde artacak ve veritabanı üzerindeki yük azalacaktır. Ancak, Redis bellek kullanımı artacakt."
remoteNotesCleaning: "Uzak notların otomatik olarak temizlenmesi" remoteNotesCleaning: "Uzak notların otomatik olarak temizlenmesi"
remoteNotesCleaning_description: "Etkinleştirildiğinde, kullanılmayan ve güncelliğini yitirmiş uzak notlar, veritabanının şişmesini önlemek için periyodik olarak temizlenecek." remoteNotesCleaning_description: "Etkinleştirildiğinde, kullanılmayan ve güncelliğini yitirmiş uzak notlar, veritabanının şişmesini önlemek için periyodik olarak temizlenecek."
@ -1668,7 +1668,6 @@ _serverSettings:
restartServerSetupWizardConfirm_text: "Bazı mevcut ayarlar sıfırlanacaktır." restartServerSetupWizardConfirm_text: "Bazı mevcut ayarlar sıfırlanacaktır."
entrancePageStyle: "Giriş sayfası stili" entrancePageStyle: "Giriş sayfası stili"
showTimelineForVisitor: "Panoyu göster" showTimelineForVisitor: "Panoyu göster"
showActivitiesForVisitor: "Aktiviteleri göster"
_userGeneratedContentsVisibilityForVisitor: _userGeneratedContentsVisibilityForVisitor:
all: "Her şey halka açıktır." all: "Her şey halka açıktır."
localOnly: "Yalnızca yerel içerik yayınlanır, uzak içerik gizli tutulur." localOnly: "Yalnızca yerel içerik yayınlanır, uzak içerik gizli tutulur."
@ -1877,7 +1876,7 @@ _achievements:
title: "Öz Referans" title: "Öz Referans"
description: "Kendi notunuzu alıntı yapın" description: "Kendi notunuzu alıntı yapın"
_htl20npm: _htl20npm:
title: "Akış Panosu" title: "Akış Zaman Çizelgesi"
description: "Ev zaman çizelgenizin hızı 20 npm'yi (dakika başına not sayısı) aşıyor mu?" description: "Ev zaman çizelgenizin hızı 20 npm'yi (dakika başına not sayısı) aşıyor mu?"
_viewInstanceChart: _viewInstanceChart:
title: "Analist" title: "Analist"
@ -1966,7 +1965,7 @@ _role:
asBadge: "Rozet olarak göster" asBadge: "Rozet olarak göster"
descriptionOfAsBadge: "This role's icon will be displayed next to the username of users with this role if turned on." descriptionOfAsBadge: "This role's icon will be displayed next to the username of users with this role if turned on."
isExplorable: "Rolü keşfedilebilir hale getir" isExplorable: "Rolü keşfedilebilir hale getir"
descriptionOfIsExplorable: "Bu rolün panosu ve bu role sahip kullanıcıların listesi, etkinleştirilirse kamuya açık hale getirilecek." descriptionOfIsExplorable: "Bu rolün zaman çizelgesi ve bu role sahip kullanıcıların listesi, etkinleştirilirse kamuya açık hale getirilecek."
displayOrder: "Pozisyon" displayOrder: "Pozisyon"
descriptionOfDisplayOrder: "Sayı ne kadar yüksekse, UI pozisyonu da o kadar yüksek olur." descriptionOfDisplayOrder: "Sayı ne kadar yüksekse, UI pozisyonu da o kadar yüksek olur."
preserveAssignmentOnMoveAccount: "Geçiş sırasında rol atamalarını koruyun" preserveAssignmentOnMoveAccount: "Geçiş sırasında rol atamalarını koruyun"
@ -1980,7 +1979,7 @@ _role:
high: "Yüksek" high: "Yüksek"
_options: _options:
gtlAvailable: "Global Pano'yu görüntüleyebilir" gtlAvailable: "Global Pano'yu görüntüleyebilir"
ltlAvailable: "Yerel panoyu görüntüleyebilir" ltlAvailable: "Yerel zaman çizelgesini görüntüleyebilir"
canPublicNote: "Halka açık notlar gönderebilir" canPublicNote: "Halka açık notlar gönderebilir"
mentionMax: "Bir notta maksimum bahsetme sayısı" mentionMax: "Bir notta maksimum bahsetme sayısı"
canInvite: "Sunucu davet kodları oluşturabilir" canInvite: "Sunucu davet kodları oluşturabilir"
@ -2485,7 +2484,7 @@ _visibility:
public: "Halka açık" public: "Halka açık"
publicDescription: "Notunuz tüm kullanıcılar tarafından görülebilir olacaktır." publicDescription: "Notunuz tüm kullanıcılar tarafından görülebilir olacaktır."
home: "Pano" home: "Pano"
homeDescription: "Yalnızca ana panoya gönder" homeDescription: "Yalnızca ana zaman çizelgesine gönder"
followers: "Takipçiler" followers: "Takipçiler"
followersDescription: "Sadece takipçilerine görünür hale getir" followersDescription: "Sadece takipçilerine görünür hale getir"
specified: "Doğrudan" specified: "Doğrudan"
@ -2532,7 +2531,7 @@ _exportOrImport:
userLists: "Kullanıcı listeleri" userLists: "Kullanıcı listeleri"
excludeMutingUsers: "Sessize alınan kullanıcıları hariç tut" excludeMutingUsers: "Sessize alınan kullanıcıları hariç tut"
excludeInactiveUsers: "Etkin olmayan kullanıcıları hariç tut" excludeInactiveUsers: "Etkin olmayan kullanıcıları hariç tut"
withReplies: "İçe aktarılan kullanıcıların yanıtlarını panoya dahil edin" withReplies: "İçe aktarılan kullanıcıların yanıtlarını zaman çizelgesine dahil edin"
_charts: _charts:
federation: "Federasyon" federation: "Federasyon"
apRequest: "Talepler" apRequest: "Talepler"
@ -2926,7 +2925,7 @@ _reversi:
freeMatch: "Ücretsiz Eşleştirme" freeMatch: "Ücretsiz Eşleştirme"
lookingForPlayer: "Rakip aranıyor..." lookingForPlayer: "Rakip aranıyor..."
gameCanceled: "Oyun iptal edildi." gameCanceled: "Oyun iptal edildi."
shareToTlTheGameWhenStart: "Oyun başlatıldığında panoda paylaş" shareToTlTheGameWhenStart: "Oyun başlatıldığında zaman çizelgesinde paylaş"
iStartedAGame: "Oyun başladı! #MisskeyReversi" iStartedAGame: "Oyun başladı! #MisskeyReversi"
opponentHasSettingsChanged: "Rakip ayarlarını değiştirmiş." opponentHasSettingsChanged: "Rakip ayarlarını değiştirmiş."
allowIrregularRules: "Düzensiz kurallar (tamamen ücretsiz)" allowIrregularRules: "Düzensiz kurallar (tamamen ücretsiz)"
@ -3154,7 +3153,7 @@ _clientPerformanceIssueTip:
_clip: _clip:
tip: "Klip, notları gruplandırmanıza olanak tanıyan bir özelliktir." tip: "Klip, notları gruplandırmanıza olanak tanıyan bir özelliktir."
_userLists: _userLists:
tip: "Listeler, oluşturulurken belirttiğin herhangi bir kullanıcıyı içerebilir. Oluşturulan liste, yalnızca belirtilen kullanıcıları gösteren bir pano olarak görüntülenebilir." tip: "Listeler, oluşturulurken belirttiğin herhangi bir kullanıcıyı içerebilir. Oluşturulan liste, yalnızca belirtilen kullanıcıları gösteren bir zaman çizelgesi olarak görüntülenebilir."
watermark: "Filigran" watermark: "Filigran"
defaultPreset: "Varsayılan Ön Ayar" defaultPreset: "Varsayılan Ön Ayar"
_watermarkEditor: _watermarkEditor:

View file

@ -3120,6 +3120,7 @@ _serverSetupWizard:
youCanConfigureMoreFederationSettingsLater: "可在之后进行如哪些服务器可以进行联合等高级设置。" youCanConfigureMoreFederationSettingsLater: "可在之后进行如哪些服务器可以进行联合等高级设置。"
remoteContentsCleaning: "自动清理传入内容" remoteContentsCleaning: "自动清理传入内容"
remoteContentsCleaning_description: "加入联合后,服务器将持续接收大量内容。打开自动清理后,将自动删除无法找到的旧内容,可节省存储空间。" remoteContentsCleaning_description: "加入联合后,服务器将持续接收大量内容。打开自动清理后,将自动删除无法找到的旧内容,可节省存储空间。"
remoteContentsCleaning_description2: "如超链接之类的某些引用方法无法被系统检测到。"
adminInfo: "管理员信息" adminInfo: "管理员信息"
adminInfo_description: "设置用于接受询问的管理员信息。" adminInfo_description: "设置用于接受询问的管理员信息。"
adminInfo_mustBeFilled: "开放服务器或开启了联合的情况下必须输入。" adminInfo_mustBeFilled: "开放服务器或开启了联合的情况下必须输入。"

View file

@ -3120,6 +3120,7 @@ _serverSetupWizard:
youCanConfigureMoreFederationSettingsLater: "您可以在稍後進行更高級的設定,例如指定可以聯繫的伺服器等。\n" youCanConfigureMoreFederationSettingsLater: "您可以在稍後進行更高級的設定,例如指定可以聯繫的伺服器等。\n"
remoteContentsCleaning: "自動清理接收的內容" remoteContentsCleaning: "自動清理接收的內容"
remoteContentsCleaning_description: "進行聯邦後,會持續接收大量內容。啟用自動清理功能後,系統會自動從伺服器中刪除未被參照的過時內容,以節省儲存空間。" remoteContentsCleaning_description: "進行聯邦後,會持續接收大量內容。啟用自動清理功能後,系統會自動從伺服器中刪除未被參照的過時內容,以節省儲存空間。"
remoteContentsCleaning_description2: "有些引用方式系統上無法檢測到,例如超連結。"
adminInfo: "管理員資訊" adminInfo: "管理員資訊"
adminInfo_description: "設定用於接收查詢的管理者資訊。\n" adminInfo_description: "設定用於接收查詢的管理者資訊。\n"
adminInfo_mustBeFilled: "當設置為開放伺服器或啟用聯邦時,必須填寫此資訊。\n" adminInfo_mustBeFilled: "當設置為開放伺服器或啟用聯邦時,必須填寫此資訊。\n"

View file

@ -1,12 +1,12 @@
{ {
"name": "misskey", "name": "misskey",
"version": "2025.9.0-beta.0", "version": "2025.8.0-beta.5",
"codename": "nasubi", "codename": "nasubi",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/misskey-dev/misskey.git" "url": "https://github.com/misskey-dev/misskey.git"
}, },
"packageManager": "pnpm@10.15.1", "packageManager": "pnpm@10.15.0",
"workspaces": [ "workspaces": [
"packages/frontend-shared", "packages/frontend-shared",
"packages/frontend", "packages/frontend",
@ -62,22 +62,21 @@
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
"postcss": "8.5.6", "postcss": "8.5.6",
"tar": "7.4.3", "tar": "7.4.3",
"terser": "5.44.0", "terser": "5.43.1",
"typescript": "5.9.2" "typescript": "5.9.2"
}, },
"devDependencies": { "devDependencies": {
"@misskey-dev/eslint-plugin": "2.1.0", "@misskey-dev/eslint-plugin": "2.1.0",
"@types/js-yaml": "4.0.9", "@types/node": "22.17.2",
"@types/node": "22.18.1", "@typescript-eslint/eslint-plugin": "8.40.0",
"@typescript-eslint/eslint-plugin": "8.42.0", "@typescript-eslint/parser": "8.40.0",
"@typescript-eslint/parser": "8.42.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"cypress": "14.5.4", "cypress": "14.5.4",
"eslint": "9.35.0", "eslint": "9.34.0",
"globals": "16.3.0", "globals": "16.3.0",
"ncp": "2.0.0", "ncp": "2.0.0",
"pnpm": "10.15.1", "pnpm": "10.15.0",
"start-server-and-test": "2.1.0" "start-server-and-test": "2.0.13"
}, },
"optionalDependencies": { "optionalDependencies": {
"@tensorflow/tfjs-core": "4.22.0" "@tensorflow/tfjs-core": "4.22.0"

View file

@ -39,17 +39,17 @@
}, },
"optionalDependencies": { "optionalDependencies": {
"@swc/core-android-arm64": "1.3.11", "@swc/core-android-arm64": "1.3.11",
"@swc/core-darwin-arm64": "1.13.5", "@swc/core-darwin-arm64": "1.13.4",
"@swc/core-darwin-x64": "1.13.5", "@swc/core-darwin-x64": "1.13.4",
"@swc/core-freebsd-x64": "1.3.11", "@swc/core-freebsd-x64": "1.3.11",
"@swc/core-linux-arm-gnueabihf": "1.13.5", "@swc/core-linux-arm-gnueabihf": "1.13.4",
"@swc/core-linux-arm64-gnu": "1.13.5", "@swc/core-linux-arm64-gnu": "1.13.4",
"@swc/core-linux-arm64-musl": "1.13.5", "@swc/core-linux-arm64-musl": "1.13.4",
"@swc/core-linux-x64-gnu": "1.13.5", "@swc/core-linux-x64-gnu": "1.13.4",
"@swc/core-linux-x64-musl": "1.13.5", "@swc/core-linux-x64-musl": "1.13.4",
"@swc/core-win32-arm64-msvc": "1.13.5", "@swc/core-win32-arm64-msvc": "1.13.4",
"@swc/core-win32-ia32-msvc": "1.13.5", "@swc/core-win32-ia32-msvc": "1.13.4",
"@swc/core-win32-x64-msvc": "1.13.5", "@swc/core-win32-x64-msvc": "1.13.4",
"@tensorflow/tfjs": "4.22.0", "@tensorflow/tfjs": "4.22.0",
"@tensorflow/tfjs-node": "4.22.0", "@tensorflow/tfjs-node": "4.22.0",
"bufferutil": "4.0.9", "bufferutil": "4.0.9",
@ -69,20 +69,20 @@
"utf-8-validate": "6.0.5" "utf-8-validate": "6.0.5"
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "3.883.0", "@aws-sdk/client-s3": "3.873.0",
"@aws-sdk/lib-storage": "3.883.0", "@aws-sdk/lib-storage": "3.873.0",
"@discordapp/twemoji": "16.0.1", "@discordapp/twemoji": "16.0.1",
"@fastify/accepts": "5.0.2", "@fastify/accepts": "5.0.2",
"@fastify/cookie": "11.0.2", "@fastify/cookie": "11.0.2",
"@fastify/cors": "10.1.0", "@fastify/cors": "10.1.0",
"@fastify/express": "4.0.2", "@fastify/express": "4.0.2",
"@fastify/http-proxy": "10.0.2", "@fastify/http-proxy": "10.0.2",
"@fastify/multipart": "9.2.1", "@fastify/multipart": "9.0.3",
"@fastify/static": "8.2.0", "@fastify/static": "8.2.0",
"@fastify/view": "10.0.2", "@fastify/view": "10.0.2",
"@misskey-dev/sharp-read-bmp": "1.2.0", "@misskey-dev/sharp-read-bmp": "1.2.0",
"@misskey-dev/summaly": "5.2.3", "@misskey-dev/summaly": "5.2.3",
"@napi-rs/canvas": "0.1.79", "@napi-rs/canvas": "0.1.77",
"@nestjs/common": "11.1.6", "@nestjs/common": "11.1.6",
"@nestjs/core": "11.1.6", "@nestjs/core": "11.1.6",
"@nestjs/testing": "11.1.6", "@nestjs/testing": "11.1.6",
@ -93,7 +93,7 @@
"@sinonjs/fake-timers": "11.3.1", "@sinonjs/fake-timers": "11.3.1",
"@smithy/node-http-handler": "2.5.0", "@smithy/node-http-handler": "2.5.0",
"@swc/cli": "0.7.8", "@swc/cli": "0.7.8",
"@swc/core": "1.13.5", "@swc/core": "1.13.4",
"@twemoji/parser": "16.0.0", "@twemoji/parser": "16.0.0",
"@types/redis-info": "3.0.3", "@types/redis-info": "3.0.3",
"accepts": "1.3.8", "accepts": "1.3.8",
@ -103,7 +103,7 @@
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"blurhash": "2.0.5", "blurhash": "2.0.5",
"body-parser": "1.20.3", "body-parser": "1.20.3",
"bullmq": "5.58.5", "bullmq": "5.58.1",
"cacheable-lookup": "7.0.0", "cacheable-lookup": "7.0.0",
"cbor": "9.0.2", "cbor": "9.0.2",
"chalk": "5.6.0", "chalk": "5.6.0",
@ -114,13 +114,13 @@
"content-disposition": "0.5.4", "content-disposition": "0.5.4",
"date-fns": "2.30.0", "date-fns": "2.30.0",
"deep-email-validator": "0.1.21", "deep-email-validator": "0.1.21",
"fastify": "5.6.0", "fastify": "5.5.0",
"fastify-raw-body": "5.0.0", "fastify-raw-body": "5.0.0",
"feed": "4.2.2", "feed": "4.2.2",
"file-type": "19.6.0", "file-type": "19.6.0",
"fluent-ffmpeg": "2.1.3", "fluent-ffmpeg": "2.1.3",
"form-data": "4.0.4", "form-data": "4.0.4",
"got": "14.4.8", "got": "14.4.7",
"happy-dom": "16.8.1", "happy-dom": "16.8.1",
"hpagent": "1.2.0", "hpagent": "1.2.0",
"htmlescape": "1.1.1", "htmlescape": "1.1.1",
@ -141,7 +141,7 @@
"mime-types": "2.1.35", "mime-types": "2.1.35",
"misskey-js": "workspace:*", "misskey-js": "workspace:*",
"misskey-reversi": "workspace:*", "misskey-reversi": "workspace:*",
"ms": "3.0.0-canary.202508261828", "ms": "3.0.0-canary.1",
"nanoid": "5.1.5", "nanoid": "5.1.5",
"nested-property": "4.0.0", "nested-property": "4.0.0",
"node-fetch": "3.3.2", "node-fetch": "3.3.2",
@ -175,7 +175,7 @@
"slacc": "0.0.10", "slacc": "0.0.10",
"strict-event-emitter-types": "2.0.0", "strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0", "stringz": "2.1.0",
"systeminformation": "5.27.8", "systeminformation": "5.27.7",
"tinycolor2": "1.6.0", "tinycolor2": "1.6.0",
"tmp": "0.2.5", "tmp": "0.2.5",
"tsc-alias": "1.8.16", "tsc-alias": "1.8.16",
@ -210,7 +210,7 @@
"@types/jsrsasign": "10.5.15", "@types/jsrsasign": "10.5.15",
"@types/mime-types": "2.1.4", "@types/mime-types": "2.1.4",
"@types/ms": "0.7.34", "@types/ms": "0.7.34",
"@types/node": "22.18.1", "@types/node": "22.17.2",
"@types/nodemailer": "6.4.19", "@types/nodemailer": "6.4.19",
"@types/oauth": "0.9.6", "@types/oauth": "0.9.6",
"@types/oauth2orize": "1.11.5", "@types/oauth2orize": "1.11.5",
@ -222,7 +222,7 @@
"@types/ratelimiter": "3.4.6", "@types/ratelimiter": "3.4.6",
"@types/rename": "1.0.7", "@types/rename": "1.0.7",
"@types/sanitize-html": "2.16.0", "@types/sanitize-html": "2.16.0",
"@types/semver": "7.7.1", "@types/semver": "7.7.0",
"@types/simple-oauth2": "5.0.7", "@types/simple-oauth2": "5.0.7",
"@types/sinonjs__fake-timers": "8.1.5", "@types/sinonjs__fake-timers": "8.1.5",
"@types/supertest": "6.0.3", "@types/supertest": "6.0.3",
@ -231,8 +231,8 @@
"@types/vary": "1.1.3", "@types/vary": "1.1.3",
"@types/web-push": "3.6.4", "@types/web-push": "3.6.4",
"@types/ws": "8.18.1", "@types/ws": "8.18.1",
"@typescript-eslint/eslint-plugin": "8.42.0", "@typescript-eslint/eslint-plugin": "8.40.0",
"@typescript-eslint/parser": "8.42.0", "@typescript-eslint/parser": "8.40.0",
"aws-sdk-client-mock": "4.1.0", "aws-sdk-client-mock": "4.1.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"eslint-plugin-import": "2.32.0", "eslint-plugin-import": "2.32.0",

View file

@ -29,7 +29,7 @@ export class AiService {
} }
@bindThis @bindThis
public async detectSensitive(source: string | Buffer): Promise<nsfw.PredictionType[] | null> { public async detectSensitive(path: string): Promise<nsfw.PredictionType[] | null> {
try { try {
if (isSupportedCpu === undefined) { if (isSupportedCpu === undefined) {
isSupportedCpu = await this.computeIsSupportedCpu(); isSupportedCpu = await this.computeIsSupportedCpu();
@ -51,7 +51,7 @@ export class AiService {
}); });
} }
const buffer = source instanceof Buffer ? source : await fs.promises.readFile(source); const buffer = await fs.promises.readFile(path);
const image = await tf.node.decodeImage(buffer, 3) as any; const image = await tf.node.decodeImage(buffer, 3) as any;
try { try {
const predictions = await this.model.classify(image); const predictions = await this.model.classify(image);

View file

@ -21,7 +21,6 @@ import { LoggerService } from '@/core/LoggerService.js';
import type Logger from '@/logger.js'; import type Logger from '@/logger.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import type { PredictionType } from 'nsfwjs'; import type { PredictionType } from 'nsfwjs';
import { isMimeImage } from '@/misc/is-mime-image.js';
export type FileInfo = { export type FileInfo = {
size: number; size: number;
@ -205,7 +204,16 @@ export class FileInfoService {
return [sensitive, porn]; return [sensitive, porn];
} }
if (analyzeVideo && (mime === 'image/apng' || mime.startsWith('video/'))) { if ([
'image/jpeg',
'image/png',
'image/webp',
].includes(mime)) {
const result = await this.aiService.detectSensitive(source);
if (result) {
[sensitive, porn] = judgePrediction(result);
}
} else if (analyzeVideo && (mime === 'image/apng' || mime.startsWith('video/'))) {
const [outDir, disposeOutDir] = await createTempDir(); const [outDir, disposeOutDir] = await createTempDir();
try { try {
const command = FFmpeg() const command = FFmpeg()
@ -273,23 +281,6 @@ export class FileInfoService {
} finally { } finally {
disposeOutDir(); disposeOutDir();
} }
} else if (isMimeImage(mime, 'sharp-convertible-image-with-bmp')) {
/*
* tfjs-node sharp PNG
* 使299x299に事前にリサイズする
*/
const png = await (await sharpBmp(source, mime))
.resize(299, 299, {
withoutEnlargement: false,
})
.rotate()
.flatten({ background: { r: 119, g: 119, b: 119 } }) // 透過部分を18%グレーで塗りつぶす
.png()
.toBuffer();
const result = await this.aiService.detectSensitive(png);
if (result) {
[sensitive, porn] = judgePrediction(result);
}
} }
return [sensitive, porn]; return [sensitive, porn];

View file

@ -756,8 +756,8 @@ export class QueueService {
@bindThis @bindThis
public async queueRetryJob(queueType: typeof QUEUE_TYPES[number], jobId: string) { public async queueRetryJob(queueType: typeof QUEUE_TYPES[number], jobId: string) {
const queue = this.getQueue(queueType); const queue = this.getQueue(queueType);
const job = await queue.getJob(jobId); const job: Bull.Job | null = await queue.getJob(jobId);
if (job != null) { if (job) {
if (job.finishedOn != null) { if (job.finishedOn != null) {
await job.retry(); await job.retry();
} else { } else {
@ -769,8 +769,8 @@ export class QueueService {
@bindThis @bindThis
public async queueRemoveJob(queueType: typeof QUEUE_TYPES[number], jobId: string) { public async queueRemoveJob(queueType: typeof QUEUE_TYPES[number], jobId: string) {
const queue = this.getQueue(queueType); const queue = this.getQueue(queueType);
const job = await queue.getJob(jobId); const job: Bull.Job | null = await queue.getJob(jobId);
if (job != null) { if (job) {
await job.remove(); await job.remove();
} }
} }
@ -803,8 +803,8 @@ export class QueueService {
@bindThis @bindThis
public async queueGetJob(queueType: typeof QUEUE_TYPES[number], jobId: string) { public async queueGetJob(queueType: typeof QUEUE_TYPES[number], jobId: string) {
const queue = this.getQueue(queueType); const queue = this.getQueue(queueType);
const job = await queue.getJob(jobId); const job: Bull.Job | null = await queue.getJob(jobId);
if (job != null) { if (job) {
return this.packJobData(job); return this.packJobData(job);
} else { } else {
throw new Error(`Job not found: ${jobId}`); throw new Error(`Job not found: ${jobId}`);

View file

@ -31,7 +31,6 @@ import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
import { NotificationService } from '@/core/NotificationService.js'; import { NotificationService } from '@/core/NotificationService.js';
import type { OnApplicationShutdown, OnModuleInit } from '@nestjs/common'; import type { OnApplicationShutdown, OnModuleInit } from '@nestjs/common';
// misskey-js の rolePolicies と同期すべし
export type RolePolicies = { export type RolePolicies = {
gtlAvailable: boolean; gtlAvailable: boolean;
ltlAvailable: boolean; ltlAvailable: boolean;

View file

@ -10,7 +10,6 @@ import { MiAccessToken } from './AccessToken.js';
import { MiRole } from './Role.js'; import { MiRole } from './Role.js';
import { MiDriveFile } from './DriveFile.js'; import { MiDriveFile } from './DriveFile.js';
// misskey-js の notificationTypes と同期すべし
export type MiNotification = { export type MiNotification = {
type: 'note'; type: 'note';
id: string; id: string;

View file

@ -176,17 +176,6 @@ export class ApiServerService {
} }
}); });
fastify.all('/clear-browser-cache', (request, reply) => {
if (['GET', 'POST'].includes(request.method)) {
reply.header('Clear-Site-Data', '"cache", "prefetchCache", "prerenderCache", "executionContexts"');
reply.code(204);
reply.send();
} else {
reply.code(405);
reply.send();
}
});
// Make sure any unknown path under /api returns HTTP 404 Not Found, // Make sure any unknown path under /api returns HTTP 404 Not Found,
// because otherwise ClientServerService will return the base client HTML // because otherwise ClientServerService will return the base client HTML
// page with HTTP 200. // page with HTTP 200.

View file

@ -91,7 +91,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
qb.orWhere(new Brackets(qb => { qb.orWhere(new Brackets(qb => {
qb.where('note.text IS NOT NULL'); qb.where('note.text IS NOT NULL');
qb.orWhere('note.fileIds != \'{}\''); qb.orWhere('note.fileIds != \'{}\'');
qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
})); }));
})); }));
} }

View file

@ -242,7 +242,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
qb.orWhere(new Brackets(qb => { qb.orWhere(new Brackets(qb => {
qb.orWhere('note.text IS NOT NULL'); qb.orWhere('note.text IS NOT NULL');
qb.orWhere('note.fileIds != \'{}\''); qb.orWhere('note.fileIds != \'{}\'');
qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
})); }));
})); }));
} }

View file

@ -223,7 +223,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
qb.orWhere(new Brackets(qb => { qb.orWhere(new Brackets(qb => {
qb.orWhere('note.text IS NOT NULL'); qb.orWhere('note.text IS NOT NULL');
qb.orWhere('note.fileIds != \'{}\''); qb.orWhere('note.fileIds != \'{}\'');
qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
})); }));
})); }));
} }

View file

@ -201,8 +201,6 @@ export class ClientServerService {
@bindThis @bindThis
public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
const configUrl = new URL(this.config.url);
fastify.register(fastifyView, { fastify.register(fastifyView, {
root: _dirname + '/views', root: _dirname + '/views',
engine: { engine: {
@ -241,6 +239,7 @@ export class ClientServerService {
done(); done();
}); });
} else { } else {
const configUrl = new URL(this.config.url);
const urlOriginWithoutPort = configUrl.origin.replace(/:\d+$/, ''); const urlOriginWithoutPort = configUrl.origin.replace(/:\d+$/, '');
const port = (process.env.VITE_PORT ?? '5173'); const port = (process.env.VITE_PORT ?? '5173');
@ -888,22 +887,6 @@ export class ClientServerService {
[, ...target.split('/').filter(x => x), ...source.split('/').filter(x => x).splice(depth)].join('/'); [, ...target.split('/').filter(x => x), ...source.split('/').filter(x => x).splice(depth)].join('/');
fastify.get('/flush', async (request, reply) => { fastify.get('/flush', async (request, reply) => {
let sendHeader = true;
if (request.headers['origin']) {
const originURL = new URL(request.headers['origin']);
if (originURL.protocol !== 'https:') { // Clear-Site-Data only supports https
sendHeader = false;
}
if (originURL.host !== configUrl.host) {
sendHeader = false;
}
}
if (sendHeader) {
reply.header('Clear-Site-Data', '"*"');
}
reply.header('Set-Cookie', 'http-flush-failed=1; Path=/flush; Max-Age=60');
return await reply.view('flush'); return await reply.view('flush');
}); });

View file

@ -6,45 +6,41 @@ html
const msg = document.getElementById('msg'); const msg = document.getElementById('msg');
const successText = `\nSuccess Flush! <a href="/">Back to Misskey</a>\n成功しました。<a href="/">Misskeyを開き直してください。</a>`; const successText = `\nSuccess Flush! <a href="/">Back to Misskey</a>\n成功しました。<a href="/">Misskeyを開き直してください。</a>`;
if (!document.cookie) { message('Start flushing.');
message('Your site data is fully cleared by your browser.');
message(successText);
} else {
message('Your browser does not support Clear-Site-Data header. Start opportunistic flushing.');
(async function() {
try {
localStorage.clear();
message('localStorage cleared.');
const idbPromises = ['MisskeyClient', 'keyval-store'].map((name, i, arr) => new Promise((res, rej) => { (async function() {
const delidb = indexedDB.deleteDatabase(name); try {
delidb.onsuccess = () => res(message(`indexedDB "${name}" cleared. (${i + 1}/${arr.length})`)); localStorage.clear();
delidb.onerror = e => rej(e) message('localStorage cleared.');
}));
await Promise.all(idbPromises); const idbPromises = ['MisskeyClient', 'keyval-store'].map((name, i, arr) => new Promise((res, rej) => {
const delidb = indexedDB.deleteDatabase(name);
delidb.onsuccess = () => res(message(`indexedDB "${name}" cleared. (${i + 1}/${arr.length})`));
delidb.onerror = e => rej(e)
}));
if (navigator.serviceWorker.controller) { await Promise.all(idbPromises);
navigator.serviceWorker.controller.postMessage('clear');
await navigator.serviceWorker.getRegistrations()
.then(registrations => {
return Promise.all(registrations.map(registration => registration.unregister()));
})
.catch(e => { throw new Error(e) });
}
message(successText); if (navigator.serviceWorker.controller) {
} catch (e) { navigator.serviceWorker.controller.postMessage('clear');
message(`\n${e}\n\nFlush Failed. <a href="/flush">Please retry.</a>\n失敗しました。<a href="/flush">もう一度試してみてください。</a>`); await navigator.serviceWorker.getRegistrations()
message(`\nIf you retry more than 3 times, try manually clearing the browser cache or contact to instance admin.\n3回以上試しても失敗する場合、ブラウザのキャッシュを手動で消去し、それでもだめならインスタンス管理者に連絡してみてください。\n`) .then(registrations => {
return Promise.all(registrations.map(registration => registration.unregister()));
console.error(e); })
setTimeout(() => { .catch(e => { throw new Error(e) });
location = '/';
}, 10000)
} }
})();
} message(successText);
} catch (e) {
message(`\n${e}\n\nFlush Failed. <a href="/flush">Please retry.</a>\n失敗しました。<a href="/flush">もう一度試してみてください。</a>`);
message(`\nIf you retry more than 3 times, clear the browser cache or contact to instance admin.\n3回以上試しても失敗する場合、ブラウザのキャッシュを消去し、それでもだめならインスタンス管理者に連絡してみてください。\n`)
console.error(e);
setTimeout(() => {
location = '/';
}, 10000)
}
})();
function message(text) { function message(text) {
msg.insertAdjacentHTML('beforeend', `<p>[${(new Date()).toString()}] ${text.replace(/\n/g,'<br>')}</p>`) msg.insertAdjacentHTML('beforeend', `<p>[${(new Date()).toString()}] ${text.replace(/\n/g,'<br>')}</p>`)

View file

@ -13,10 +13,10 @@
"@discordapp/twemoji": "16.0.1", "@discordapp/twemoji": "16.0.1",
"@rollup/plugin-json": "6.1.0", "@rollup/plugin-json": "6.1.0",
"@rollup/plugin-replace": "6.0.2", "@rollup/plugin-replace": "6.0.2",
"@rollup/pluginutils": "5.3.0", "@rollup/pluginutils": "5.2.0",
"@twemoji/parser": "16.0.0", "@twemoji/parser": "16.0.0",
"@vitejs/plugin-vue": "6.0.1", "@vitejs/plugin-vue": "6.0.1",
"@vue/compiler-sfc": "3.5.21", "@vue/compiler-sfc": "3.5.19",
"astring": "1.9.0", "astring": "1.9.0",
"buraha": "0.0.1", "buraha": "0.0.1",
"estree-walker": "3.0.3", "estree-walker": "3.0.3",
@ -26,16 +26,16 @@
"mfm-js": "0.25.0", "mfm-js": "0.25.0",
"misskey-js": "workspace:*", "misskey-js": "workspace:*",
"punycode.js": "2.3.1", "punycode.js": "2.3.1",
"rollup": "4.50.1", "rollup": "4.48.0",
"sass": "1.92.1", "sass": "1.90.0",
"shiki": "3.12.2", "shiki": "3.11.0",
"tinycolor2": "1.6.0", "tinycolor2": "1.6.0",
"tsc-alias": "1.8.16", "tsc-alias": "1.8.16",
"tsconfig-paths": "4.2.0", "tsconfig-paths": "4.2.0",
"typescript": "5.9.2", "typescript": "5.9.2",
"uuid": "11.1.0", "uuid": "11.1.0",
"vite": "7.1.4", "vite": "7.1.3",
"vue": "3.5.21" "vue": "3.5.19"
}, },
"devDependencies": { "devDependencies": {
"@misskey-dev/summaly": "5.2.3", "@misskey-dev/summaly": "5.2.3",
@ -43,14 +43,14 @@
"@testing-library/vue": "8.1.0", "@testing-library/vue": "8.1.0",
"@types/estree": "1.0.8", "@types/estree": "1.0.8",
"@types/micromatch": "4.0.9", "@types/micromatch": "4.0.9",
"@types/node": "22.18.1", "@types/node": "22.17.2",
"@types/punycode.js": "npm:@types/punycode@2.1.4", "@types/punycode.js": "npm:@types/punycode@2.1.4",
"@types/tinycolor2": "1.4.6", "@types/tinycolor2": "1.4.6",
"@types/ws": "8.18.1", "@types/ws": "8.18.1",
"@typescript-eslint/eslint-plugin": "8.42.0", "@typescript-eslint/eslint-plugin": "8.40.0",
"@typescript-eslint/parser": "8.42.0", "@typescript-eslint/parser": "8.40.0",
"@vitest/coverage-v8": "3.2.4", "@vitest/coverage-v8": "3.2.4",
"@vue/runtime-core": "3.5.21", "@vue/runtime-core": "3.5.19",
"acorn": "8.15.0", "acorn": "8.15.0",
"cross-env": "10.0.0", "cross-env": "10.0.0",
"eslint-plugin-import": "2.32.0", "eslint-plugin-import": "2.32.0",
@ -59,11 +59,11 @@
"happy-dom": "18.0.1", "happy-dom": "18.0.1",
"intersection-observer": "0.12.2", "intersection-observer": "0.12.2",
"micromatch": "4.0.8", "micromatch": "4.0.8",
"msw": "2.11.1", "msw": "2.10.5",
"nodemon": "3.1.10", "nodemon": "3.1.10",
"prettier": "3.6.2", "prettier": "3.6.2",
"start-server-and-test": "2.1.0", "start-server-and-test": "2.0.13",
"tsx": "4.20.5", "tsx": "4.20.4",
"vite-plugin-turbosnap": "1.0.3", "vite-plugin-turbosnap": "1.0.3",
"vue-component-type-helpers": "3.0.6", "vue-component-type-helpers": "3.0.6",
"vue-eslint-parser": "10.2.0", "vue-eslint-parser": "10.2.0",

View file

@ -54,6 +54,68 @@ https://github.com/sindresorhus/file-type/blob/main/core.js
https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers
*/ */
export const notificationTypes = [
'note',
'follow',
'mention',
'reply',
'renote',
'quote',
'reaction',
'pollEnded',
'receiveFollowRequest',
'followRequestAccepted',
'roleAssigned',
'chatRoomInvitationReceived',
'achievementEarned',
'exportCompleted',
'login',
'createToken',
'test',
'app',
] as const;
export const obsoleteNotificationTypes = ['pollVote', 'groupInvited'] as const;
export const ROLE_POLICIES = [
'gtlAvailable',
'ltlAvailable',
'canPublicNote',
'mentionLimit',
'canInvite',
'inviteLimit',
'inviteLimitCycle',
'inviteExpirationTime',
'canManageCustomEmojis',
'canManageAvatarDecorations',
'canSearchNotes',
'canSearchUsers',
'canUseTranslator',
'canHideAds',
'driveCapacityMb',
'maxFileSizeMb',
'alwaysMarkNsfw',
'canUpdateBioMedia',
'pinLimit',
'antennaLimit',
'wordMuteLimit',
'webhookLimit',
'clipLimit',
'noteEachClipsLimit',
'userListLimit',
'userEachUserListsLimit',
'rateLimitFactor',
'avatarDecorationLimit',
'canImportAntennas',
'canImportBlocking',
'canImportFollowing',
'canImportMuting',
'canImportUserLists',
'chatAvailability',
'uploadableFileTypes',
'noteDraftLimit',
'watermarkAvailable',
] as const;
export const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'scale', 'position', 'fg', 'bg', 'border', 'font', 'blur', 'rainbow', 'sparkle', 'rotate', 'ruby', 'unixtime']; export const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'scale', 'position', 'fg', 'bg', 'border', 'font', 'blur', 'rainbow', 'sparkle', 'rotate', 'ruby', 'unixtime'];
export const MFM_PARAMS: Record<typeof MFM_TAGS[number], string[]> = { export const MFM_PARAMS: Record<typeof MFM_TAGS[number], string[]> = {
tada: ['speed=', 'delay='], tada: ['speed=', 'delay='],

View file

@ -21,9 +21,9 @@
"lint": "pnpm typecheck && pnpm eslint" "lint": "pnpm typecheck && pnpm eslint"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "22.18.1", "@types/node": "22.17.2",
"@typescript-eslint/eslint-plugin": "8.42.0", "@typescript-eslint/eslint-plugin": "8.40.0",
"@typescript-eslint/parser": "8.42.0", "@typescript-eslint/parser": "8.40.0",
"esbuild": "0.25.9", "esbuild": "0.25.9",
"eslint-plugin-vue": "10.4.0", "eslint-plugin-vue": "10.4.0",
"nodemon": "3.1.10", "nodemon": "3.1.10",
@ -35,6 +35,6 @@
], ],
"dependencies": { "dependencies": {
"misskey-js": "workspace:*", "misskey-js": "workspace:*",
"vue": "3.5.21" "vue": "3.5.19"
} }
} }

View file

@ -127,7 +127,7 @@ export function galleryPost(isSensitive = false) {
} }
} }
export function file(isSensitive = false): entities.DriveFile { export function file(isSensitive = false) {
return { return {
id: 'somefileid', id: 'somefileid',
createdAt: '2016-12-28T22:49:51.000Z', createdAt: '2016-12-28T22:49:51.000Z',
@ -207,7 +207,6 @@ export function federationInstance(): entities.FederationInstance {
isSuspended: false, isSuspended: false,
suspensionState: 'none', suspensionState: 'none',
isBlocked: false, isBlocked: false,
isMediaSilenced: false,
softwareName: 'misskey', softwareName: 'misskey',
softwareVersion: '2024.5.0', softwareVersion: '2024.5.0',
openRegistrations: false, openRegistrations: false,
@ -312,8 +311,6 @@ export function userDetailed(id = 'someuserid', username = 'miskist', host: enti
alsoKnownAs: null, alsoKnownAs: null,
notify: 'none', notify: 'none',
memo: null, memo: null,
canChat: true,
chatScope: 'everyone',
}; };
} }
@ -381,7 +378,6 @@ export function role(params: {
asBadge: params.asBadge ?? true, asBadge: params.asBadge ?? true,
canEditMembersByModerator: params.canEditMembersByModerator ?? false, canEditMembersByModerator: params.canEditMembersByModerator ?? false,
usersCount: params.usersCount ?? 10, usersCount: params.usersCount ?? 10,
preserveAssignmentOnMoveAccount: false,
condFormula: { condFormula: {
id: '', id: '',
type: 'or', type: 'or',

View file

@ -23,13 +23,13 @@
"@misskey-dev/browser-image-resizer": "2024.1.0", "@misskey-dev/browser-image-resizer": "2024.1.0",
"@rollup/plugin-json": "6.1.0", "@rollup/plugin-json": "6.1.0",
"@rollup/plugin-replace": "6.0.2", "@rollup/plugin-replace": "6.0.2",
"@rollup/pluginutils": "5.3.0", "@rollup/pluginutils": "5.2.0",
"@sentry/vue": "10.10.0", "@sentry/vue": "10.5.0",
"@syuilo/aiscript": "1.1.0", "@syuilo/aiscript": "1.1.0",
"@syuilo/aiscript-0-19-0": "npm:@syuilo/aiscript@^0.19.0", "@syuilo/aiscript-0-19-0": "npm:@syuilo/aiscript@^0.19.0",
"@twemoji/parser": "16.0.0", "@twemoji/parser": "16.0.0",
"@vitejs/plugin-vue": "6.0.1", "@vitejs/plugin-vue": "6.0.1",
"@vue/compiler-sfc": "3.5.21", "@vue/compiler-sfc": "3.5.19",
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.15", "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.15",
"analytics": "0.8.19", "analytics": "0.8.19",
"astring": "1.9.0", "astring": "1.9.0",
@ -41,7 +41,7 @@
"chartjs-chart-matrix": "3.0.0", "chartjs-chart-matrix": "3.0.0",
"chartjs-plugin-gradient": "0.6.1", "chartjs-plugin-gradient": "0.6.1",
"chartjs-plugin-zoom": "2.2.0", "chartjs-plugin-zoom": "2.2.0",
"chromatic": "13.1.4", "chromatic": "13.1.3",
"compare-versions": "6.1.1", "compare-versions": "6.1.1",
"cropperjs": "2.0.1", "cropperjs": "2.0.1",
"date-fns": "4.1.0", "date-fns": "4.1.0",
@ -63,21 +63,21 @@
"misskey-reversi": "workspace:*", "misskey-reversi": "workspace:*",
"photoswipe": "5.4.4", "photoswipe": "5.4.4",
"punycode.js": "2.3.1", "punycode.js": "2.3.1",
"rollup": "4.50.1", "rollup": "4.48.0",
"sanitize-html": "2.17.0", "sanitize-html": "2.17.0",
"sass": "1.92.1", "sass": "1.90.0",
"shiki": "3.12.2", "shiki": "3.11.0",
"strict-event-emitter-types": "2.0.0", "strict-event-emitter-types": "2.0.0",
"textarea-caret": "3.1.0", "textarea-caret": "3.1.0",
"three": "0.180.0", "three": "0.179.1",
"throttle-debounce": "5.0.2", "throttle-debounce": "5.0.2",
"tinycolor2": "1.6.0", "tinycolor2": "1.6.0",
"tsc-alias": "1.8.16", "tsc-alias": "1.8.16",
"tsconfig-paths": "4.2.0", "tsconfig-paths": "4.2.0",
"typescript": "5.9.2", "typescript": "5.9.2",
"v-code-diff": "1.13.1", "v-code-diff": "1.13.1",
"vite": "7.1.4", "vite": "7.1.3",
"vue": "3.5.21", "vue": "3.5.19",
"vuedraggable": "next", "vuedraggable": "next",
"wanakana": "5.3.1" "wanakana": "5.3.1"
}, },
@ -85,7 +85,7 @@
"@misskey-dev/summaly": "5.2.3", "@misskey-dev/summaly": "5.2.3",
"@storybook/addon-essentials": "8.6.14", "@storybook/addon-essentials": "8.6.14",
"@storybook/addon-interactions": "8.6.14", "@storybook/addon-interactions": "8.6.14",
"@storybook/addon-links": "9.1.5", "@storybook/addon-links": "9.1.3",
"@storybook/addon-mdx-gfm": "8.6.14", "@storybook/addon-mdx-gfm": "8.6.14",
"@storybook/addon-storysource": "8.6.14", "@storybook/addon-storysource": "8.6.14",
"@storybook/blocks": "8.6.14", "@storybook/blocks": "8.6.14",
@ -93,31 +93,31 @@
"@storybook/core-events": "8.6.14", "@storybook/core-events": "8.6.14",
"@storybook/manager-api": "8.6.14", "@storybook/manager-api": "8.6.14",
"@storybook/preview-api": "8.6.14", "@storybook/preview-api": "8.6.14",
"@storybook/react": "9.1.5", "@storybook/react": "9.1.3",
"@storybook/react-vite": "9.1.5", "@storybook/react-vite": "9.1.3",
"@storybook/test": "8.6.14", "@storybook/test": "8.6.14",
"@storybook/theming": "8.6.14", "@storybook/theming": "8.6.14",
"@storybook/types": "8.6.14", "@storybook/types": "8.6.14",
"@storybook/vue3": "9.1.5", "@storybook/vue3": "9.1.3",
"@storybook/vue3-vite": "9.1.5", "@storybook/vue3-vite": "9.1.3",
"@tabler/icons-webfont": "3.34.1", "@tabler/icons-webfont": "3.34.1",
"@testing-library/vue": "8.1.0", "@testing-library/vue": "8.1.0",
"@types/canvas-confetti": "1.9.0", "@types/canvas-confetti": "1.9.0",
"@types/estree": "1.0.8", "@types/estree": "1.0.8",
"@types/matter-js": "0.20.0", "@types/matter-js": "0.20.0",
"@types/micromatch": "4.0.9", "@types/micromatch": "4.0.9",
"@types/node": "22.18.1", "@types/node": "22.17.2",
"@types/punycode.js": "npm:@types/punycode@2.1.4", "@types/punycode.js": "npm:@types/punycode@2.1.4",
"@types/sanitize-html": "2.16.0", "@types/sanitize-html": "2.16.0",
"@types/seedrandom": "3.0.8", "@types/seedrandom": "3.0.8",
"@types/throttle-debounce": "5.0.2", "@types/throttle-debounce": "5.0.2",
"@types/tinycolor2": "1.4.6", "@types/tinycolor2": "1.4.6",
"@types/ws": "8.18.1", "@types/ws": "8.18.1",
"@typescript-eslint/eslint-plugin": "8.42.0", "@typescript-eslint/eslint-plugin": "8.40.0",
"@typescript-eslint/parser": "8.42.0", "@typescript-eslint/parser": "8.40.0",
"@vitest/coverage-v8": "3.2.4", "@vitest/coverage-v8": "3.2.4",
"@vue/compiler-core": "3.5.21", "@vue/compiler-core": "3.5.19",
"@vue/runtime-core": "3.5.21", "@vue/runtime-core": "3.5.19",
"acorn": "8.15.0", "acorn": "8.15.0",
"cross-env": "10.0.0", "cross-env": "10.0.0",
"cypress": "14.5.4", "cypress": "14.5.4",
@ -128,17 +128,17 @@
"intersection-observer": "0.12.2", "intersection-observer": "0.12.2",
"micromatch": "4.0.8", "micromatch": "4.0.8",
"minimatch": "10.0.3", "minimatch": "10.0.3",
"msw": "2.11.1", "msw": "2.10.5",
"msw-storybook-addon": "2.0.5", "msw-storybook-addon": "2.0.5",
"nodemon": "3.1.10", "nodemon": "3.1.10",
"prettier": "3.6.2", "prettier": "3.6.2",
"react": "19.1.1", "react": "19.1.1",
"react-dom": "19.1.1", "react-dom": "19.1.1",
"seedrandom": "3.0.5", "seedrandom": "3.0.5",
"start-server-and-test": "2.1.0", "start-server-and-test": "2.0.13",
"storybook": "9.1.5", "storybook": "9.1.3",
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme", "storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
"tsx": "4.20.5", "tsx": "4.20.4",
"vite-plugin-turbosnap": "1.0.3", "vite-plugin-turbosnap": "1.0.3",
"vitest": "3.2.4", "vitest": "3.2.4",
"vitest-fetch-mock": "0.4.5", "vitest-fetch-mock": "0.4.5",

View file

@ -251,30 +251,13 @@ export async function openAccountMenu(opts: {
} }
}, },
}; };
} else { // プロファイルを復元した場合などはアカウントのトークンや詳細情報はstoreにキャッシュされていない } else {
return { return {
type: 'button' as const, type: 'button' as const,
text: username, text: username,
active: opts.active != null ? opts.active === id : false, active: opts.active != null ? opts.active === id : false,
action: async () => { action: async () => {
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkSigninDialog.vue')), { // TODO
initialUsername: username,
}, {
done: async (res: Misskey.entities.SigninFlowResponse & { finished: true }) => {
store.set('accountTokens', { ...store.s.accountTokens, [host + '/' + res.id]: res.i });
if (callback) {
fetchAccount(res.i, id).then(account => {
callback(account);
});
} else {
switchAccount(host, id);
}
},
closed: () => {
dispose();
},
});
}, },
}; };
} }

View file

@ -86,7 +86,7 @@ export function createAiScriptEnv(opts: { storageKey: string, token?: string })
throw new errors.AiScriptRuntimeError('expected param'); throw new errors.AiScriptRuntimeError('expected param');
} }
utils.assertObject(param); utils.assertObject(param);
return misskeyApi(ep.value as keyof Misskey.Endpoints, utils.valToJs(param) as object, actualToken).then(res => { return misskeyApi(ep.value, utils.valToJs(param) as object, actualToken).then(res => {
return utils.jsToVal(res); return utils.jsToVal(res);
}, err => { }, err => {
return values.ERROR('request_failed', utils.jsToVal(err)); return values.ERROR('request_failed', utils.jsToVal(err));

View file

@ -7,8 +7,8 @@ import * as Misskey from 'misskey-js';
import { Cache } from '@/utility/cache.js'; import { Cache } from '@/utility/cache.js';
import { misskeyApi } from '@/utility/misskey-api.js'; import { misskeyApi } from '@/utility/misskey-api.js';
export const clipsCache = new Cache<Misskey.entities.Clip[]>(1000 * 60 * 30, () => misskeyApi('clips/list', { limit: 30 })); export const clipsCache = new Cache<Misskey.entities.Clip[]>(1000 * 60 * 30, () => misskeyApi('clips/list'));
export const rolesCache = new Cache(1000 * 60 * 30, () => misskeyApi('admin/roles/list', { limit: 30 })); export const rolesCache = new Cache(1000 * 60 * 30, () => misskeyApi('admin/roles/list'));
export const userListsCache = new Cache<Misskey.entities.UserList[]>(1000 * 60 * 30, () => misskeyApi('users/lists/list')); export const userListsCache = new Cache<Misskey.entities.UserList[]>(1000 * 60 * 30, () => misskeyApi('users/lists/list'));
export const antennasCache = new Cache<Misskey.entities.Antenna[]>(1000 * 60 * 30, () => misskeyApi('antennas/list', { limit: 30 })); export const antennasCache = new Cache<Misskey.entities.Antenna[]>(1000 * 60 * 30, () => misskeyApi('antennas/list'));
export const favoritedChannelsCache = new Cache<Misskey.entities.Channel[]>(1000 * 60 * 30, () => misskeyApi('channels/my-favorites', { limit: 100 })); export const favoritedChannelsCache = new Cache<Misskey.entities.Channel[]>(1000 * 60 * 30, () => misskeyApi('channels/my-favorites', { limit: 100 }));

View file

@ -167,13 +167,9 @@ async function init() {
for (const user of usersRes) { for (const user of usersRes) {
if (users.value.has(user.id)) continue; if (users.value.has(user.id)) continue;
const account = accounts.find(a => a.id === user.id);
if (!account || account.token == null) continue;
users.value.set(user.id, { users.value.set(user.id, {
...user, ...user,
token: account.token, token: accounts.find(a => a.id === user.id)!.token,
}); });
} }
} }

View file

@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkCustomEmoji v-if="'isCustomEmoji' in emoji && emoji.isCustomEmoji" :name="emoji.emoji" :class="$style.emoji" :fallbackToImage="true"/> <MkCustomEmoji v-if="'isCustomEmoji' in emoji && emoji.isCustomEmoji" :name="emoji.emoji" :class="$style.emoji" :fallbackToImage="true"/>
<MkEmoji v-else :emoji="emoji.emoji" :class="$style.emoji"/> <MkEmoji v-else :emoji="emoji.emoji" :class="$style.emoji"/>
<!-- eslint-disable-next-line vue/no-v-html --> <!-- eslint-disable-next-line vue/no-v-html -->
<span v-if="q != null && typeof q === 'string'" :class="$style.emojiName" v-html="sanitizeHtml(emoji.name.replace(q, `<b>${q}</b>`))"></span> <span v-if="q" :class="$style.emojiName" v-html="sanitizeHtml(emoji.name.replace(q, `<b>${q}</b>`))"></span>
<span v-else v-text="emoji.name"></span> <span v-else v-text="emoji.name"></span>
<span v-if="emoji.aliasOf" :class="$style.emojiAlias">({{ emoji.aliasOf }})</span> <span v-if="emoji.aliasOf" :class="$style.emojiAlias">({{ emoji.aliasOf }})</span>
</li> </li>
@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</li> </li>
</ol> </ol>
<ol v-else-if="type === 'mfmParam' && mfmParams.length > 0" ref="suggests" :class="$style.list"> <ol v-else-if="type === 'mfmParam' && mfmParams.length > 0" ref="suggests" :class="$style.list">
<li v-for="param in mfmParams" tabindex="-1" :class="$style.item" @click="completeMfmParam(param)" @keydown="onKeydown"> <li v-for="param in mfmParams" tabindex="-1" :class="$style.item" @click="complete(type, q.params.toSpliced(-1, 1, param).join(','))" @keydown="onKeydown">
<span>{{ param }}</span> <span>{{ param }}</span>
</li> </li>
</ol> </ol>
@ -194,11 +194,6 @@ const mfmParams = ref<string[]>([]);
const select = ref(-1); const select = ref(-1);
const zIndex = os.claimZIndex('high'); const zIndex = os.claimZIndex('high');
function completeMfmParam(param: string) {
if (props.type !== 'mfmParam') throw new Error('Invalid type');
complete('mfmParam', props.q.params.toSpliced(-1, 1, param).join(','));
}
function complete<T extends keyof CompleteInfo>(type: T, value: CompleteInfo[T]['payload']) { function complete<T extends keyof CompleteInfo>(type: T, value: CompleteInfo[T]['payload']) {
emit('done', { type, value }); emit('done', { type, value });
emit('closed'); emit('closed');

View file

@ -25,12 +25,12 @@ defineProps<{
showing: boolean; showing: boolean;
x: number; x: number;
y: number; y: number;
title?: string | null; title?: string;
series?: { series?: {
backgroundColor: string; backgroundColor: string;
borderColor: string; borderColor: string;
text: string; text: string;
}[] | null; }[];
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{

View file

@ -38,7 +38,7 @@ export const Default = {
}; };
}, },
args: { args: {
imageFile: file(), file: file(),
aspectRatio: NaN, aspectRatio: NaN,
}, },
parameters: { parameters: {

View file

@ -699,7 +699,7 @@ useGlobalEvent('driveFoldersDeleted', (folders) => {
} }
}); });
let connection: Misskey.IChannelConnection<Misskey.Channels['drive']> | null = null; let connection: Misskey.ChannelConnection<Misskey.Channels['drive']> | null = null;
onMounted(() => { onMounted(() => {
if (store.s.realtimeMode) { if (store.s.realtimeMode) {

View file

@ -160,7 +160,7 @@ const embedPreviewUrl = computed(() => {
const isEmbedWithScrollbar = computed(() => embedRouteWithScrollbar.includes(props.entity)); const isEmbedWithScrollbar = computed(() => embedRouteWithScrollbar.includes(props.entity));
const header = ref(props.params?.header ?? true); const header = ref(props.params?.header ?? true);
const maxHeight = ref(props.params?.maxHeight !== 0 ? props.params?.maxHeight ?? null : 500); const maxHeight = ref(props.params?.maxHeight !== 0 ? props.params?.maxHeight ?? undefined : 500);
const colorMode = ref<'light' | 'dark' | 'auto'>(props.params?.colorMode ?? 'auto'); const colorMode = ref<'light' | 'dark' | 'auto'>(props.params?.colorMode ?? 'auto');
const rounded = ref(props.params?.rounded ?? true); const rounded = ref(props.params?.rounded ?? true);

View file

@ -41,11 +41,11 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkSwitch> </MkSwitch>
<MkSelect v-else-if="v.type === 'enum'" v-model="values[k]"> <MkSelect v-else-if="v.type === 'enum'" v-model="values[k]">
<template #label><span v-text="v.label || k"></span><span v-if="v.required === false"> ({{ i18n.ts.optional }})</span></template> <template #label><span v-text="v.label || k"></span><span v-if="v.required === false"> ({{ i18n.ts.optional }})</span></template>
<option v-for="option in v.enum" :key="getEnumKey(option)" :value="getEnumValue(option)">{{ getEnumLabel(option) }}</option> <option v-for="option in v.enum" :key="option.value" :value="option.value">{{ option.label }}</option>
</MkSelect> </MkSelect>
<MkRadios v-else-if="v.type === 'radio'" v-model="values[k]"> <MkRadios v-else-if="v.type === 'radio'" v-model="values[k]">
<template #label><span v-text="v.label || k"></span><span v-if="v.required === false"> ({{ i18n.ts.optional }})</span></template> <template #label><span v-text="v.label || k"></span><span v-if="v.required === false"> ({{ i18n.ts.optional }})</span></template>
<option v-for="option in v.options" :key="getRadioKey(option)" :value="option.value">{{ option.label }}</option> <option v-for="option in v.options" :key="option.value" :value="option.value">{{ option.label }}</option>
</MkRadios> </MkRadios>
<MkRange v-else-if="v.type === 'range'" v-model="values[k]" :min="v.min" :max="v.max" :step="v.step" :textConverter="v.textConverter"> <MkRange v-else-if="v.type === 'range'" v-model="values[k]" :min="v.min" :max="v.max" :step="v.step" :textConverter="v.textConverter">
<template #label><span v-text="v.label || k"></span><span v-if="v.required === false"> ({{ i18n.ts.optional }})</span></template> <template #label><span v-text="v.label || k"></span><span v-if="v.required === false"> ({{ i18n.ts.optional }})</span></template>
@ -77,7 +77,7 @@ import MkRange from './MkRange.vue';
import MkButton from './MkButton.vue'; import MkButton from './MkButton.vue';
import MkRadios from './MkRadios.vue'; import MkRadios from './MkRadios.vue';
import XFile from './MkFormDialog.file.vue'; import XFile from './MkFormDialog.file.vue';
import type { EnumItem, Form, RadioFormItem } from '@/utility/form.js'; import type { Form } from '@/utility/form.js';
import MkModalWindow from '@/components/MkModalWindow.vue'; import MkModalWindow from '@/components/MkModalWindow.vue';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
@ -99,11 +99,7 @@ const dialog = useTemplateRef('dialog');
const values = reactive({}); const values = reactive({});
for (const item in props.form) { for (const item in props.form) {
if ('default' in props.form[item]) { values[item] = props.form[item].default ?? null;
values[item] = props.form[item].default ?? null;
} else {
values[item] = null;
}
} }
function ok() { function ok() {
@ -119,20 +115,4 @@ function cancel() {
}); });
dialog.value?.close(); dialog.value?.close();
} }
function getEnumLabel(e: EnumItem) {
return typeof e === 'string' ? e : e.label;
}
function getEnumValue(e: EnumItem) {
return typeof e === 'string' ? e : e.value;
}
function getEnumKey(e: EnumItem) {
return typeof e === 'string' ? e : typeof e.value === 'string' ? e.value : JSON.stringify(e.value);
}
function getRadioKey(e: RadioFormItem['options'][number]) {
return typeof e.value === 'string' ? e.value : JSON.stringify(e.value);
}
</script> </script>

View file

@ -8,8 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-for="v, k in paramDefs" :key="k"> <div v-for="v, k in paramDefs" :key="k">
<MkSwitch <MkSwitch
v-if="v.type === 'boolean'" v-if="v.type === 'boolean'"
v-model="params[k]" v-model="params[k]">
>
<template #label>{{ v.label ?? k }}</template> <template #label>{{ v.label ?? k }}</template>
<template v-if="v.caption != null" #caption>{{ v.caption }}</template> <template v-if="v.caption != null" #caption>{{ v.caption }}</template>
</MkSwitch> </MkSwitch>
@ -54,12 +53,12 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { ImageEffectorRGB, ImageEffectorFxParamDefs } from '@/utility/image-effector/ImageEffector.js';
import MkInput from '@/components/MkInput.vue'; import MkInput from '@/components/MkInput.vue';
import MkRadios from '@/components/MkRadios.vue'; import MkRadios from '@/components/MkRadios.vue';
import MkSwitch from '@/components/MkSwitch.vue'; import MkSwitch from '@/components/MkSwitch.vue';
import MkRange from '@/components/MkRange.vue'; import MkRange from '@/components/MkRange.vue';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import type { ImageEffectorRGB, ImageEffectorFxParamDefs } from '@/utility/image-effector/ImageEffector.js';
defineProps<{ defineProps<{
paramDefs: ImageEffectorFxParamDefs; paramDefs: ImageEffectorFxParamDefs;

View file

@ -43,15 +43,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
type SupportedTypes = 'text' | 'password' | 'email' | 'url' | 'tel' | 'number' | 'search' | 'date' | 'time' | 'datetime-local' | 'color';
type ModelValueType<T extends SupportedTypes> =
T extends 'number' ? number :
T extends 'text' | 'password' | 'email' | 'url' | 'tel' | 'search' | 'date' | 'time' | 'datetime-local' | 'color' ? string :
never;
</script>
<script lang="ts" setup generic="T extends SupportedTypes = 'text'">
import { onMounted, onUnmounted, nextTick, ref, useTemplateRef, watch, computed, toRefs } from 'vue'; import { onMounted, onUnmounted, nextTick, ref, useTemplateRef, watch, computed, toRefs } from 'vue';
import { debounce } from 'throttle-debounce'; import { debounce } from 'throttle-debounce';
import { useInterval } from '@@/js/use-interval.js'; import { useInterval } from '@@/js/use-interval.js';
@ -63,8 +55,8 @@ import { Autocomplete } from '@/utility/autocomplete.js';
import { genId } from '@/utility/id.js'; import { genId } from '@/utility/id.js';
const props = defineProps<{ const props = defineProps<{
modelValue: ModelValueType<T> | null; modelValue: string | number | null;
type?: T; type?: InputHTMLAttributes['type'];
required?: boolean; required?: boolean;
readonly?: boolean; readonly?: boolean;
disabled?: boolean; disabled?: boolean;
@ -91,11 +83,11 @@ const emit = defineEmits<{
(ev: 'change', _ev: KeyboardEvent): void; (ev: 'change', _ev: KeyboardEvent): void;
(ev: 'keydown', _ev: KeyboardEvent): void; (ev: 'keydown', _ev: KeyboardEvent): void;
(ev: 'enter', _ev: KeyboardEvent): void; (ev: 'enter', _ev: KeyboardEvent): void;
(ev: 'update:modelValue', value: ModelValueType<T>): void; (ev: 'update:modelValue', value: string | number): void;
}>(); }>();
const { modelValue } = toRefs(props); const { modelValue, type, autofocus } = toRefs(props);
const v = ref<ModelValueType<T> | null>(modelValue.value); const v = ref(modelValue.value);
const id = genId(); const id = genId();
const focused = ref(false); const focused = ref(false);
const changed = ref(false); const changed = ref(false);
@ -128,8 +120,8 @@ const onKeydown = (ev: KeyboardEvent) => {
const updated = () => { const updated = () => {
changed.value = false; changed.value = false;
if (props.type === 'number') { if (type.value === 'number') {
emit('update:modelValue', typeof v.value === 'number' ? v.value as ModelValueType<T> : parseFloat(v.value ?? '0') as ModelValueType<T>); emit('update:modelValue', typeof v.value === 'number' ? v.value : parseFloat(v.value ?? '0'));
} else { } else {
emit('update:modelValue', v.value ?? ''); emit('update:modelValue', v.value ?? '');
} }
@ -175,7 +167,7 @@ useInterval(() => {
onMounted(() => { onMounted(() => {
nextTick(() => { nextTick(() => {
if (props.autofocus) { if (autofocus.value) {
focus(); focus();
} }
}); });

View file

@ -89,7 +89,6 @@ import { Chart } from 'chart.js';
import type { HeatmapSource } from '@/components/MkHeatmap.vue'; import type { HeatmapSource } from '@/components/MkHeatmap.vue';
import MkSelect from '@/components/MkSelect.vue'; import MkSelect from '@/components/MkSelect.vue';
import MkChart from '@/components/MkChart.vue'; import MkChart from '@/components/MkChart.vue';
import type { ChartSrc } from '@/components/MkChart.vue';
import { useChartTooltip } from '@/composables/use-chart-tooltip.js'; import { useChartTooltip } from '@/composables/use-chart-tooltip.js';
import { $i } from '@/i.js'; import { $i } from '@/i.js';
import * as os from '@/os.js'; import * as os from '@/os.js';
@ -108,7 +107,7 @@ const shouldShowFederation = computed(() => instance.federation !== 'none' || $i
const chartLimit = 500; const chartLimit = 500;
const chartSpan = ref<'hour' | 'day'>('hour'); const chartSpan = ref<'hour' | 'day'>('hour');
const chartSrc = ref<ChartSrc>('active-users'); const chartSrc = ref('active-users');
const heatmapSrc = ref<HeatmapSource>('active-users'); const heatmapSrc = ref<HeatmapSource>('active-users');
const subDoughnutEl = useTemplateRef('subDoughnutEl'); const subDoughnutEl = useTemplateRef('subDoughnutEl');
const pubDoughnutEl = useTemplateRef('pubDoughnutEl'); const pubDoughnutEl = useTemplateRef('pubDoughnutEl');

View file

@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:class="[$style.root, { [$style.showActionsOnlyHover]: prefer.s.showNoteActionsOnlyHover, [$style.skipRender]: prefer.s.skipNoteRender }]" :class="[$style.root, { [$style.showActionsOnlyHover]: prefer.s.showNoteActionsOnlyHover, [$style.skipRender]: prefer.s.skipNoteRender }]"
tabindex="0" tabindex="0"
> >
<MkNoteSub v-if="appearNote.replyId && !renoteCollapsed" :note="appearNote?.reply ?? null" :class="$style.replyTo"/> <MkNoteSub v-if="appearNote.replyId && !renoteCollapsed" :note="appearNote.reply" :class="$style.replyTo"/>
<div v-if="pinned" :class="$style.tip"><i class="ti ti-pin"></i> {{ i18n.ts.pinnedNote }}</div> <div v-if="pinned" :class="$style.tip"><i class="ti ti-pin"></i> {{ i18n.ts.pinnedNote }}</div>
<div v-if="isRenote" :class="$style.renote"> <div v-if="isRenote" :class="$style.renote">
<div v-if="note.channel" :class="$style.colorBar" :style="{ background: note.channel.color }"></div> <div v-if="note.channel" :class="$style.colorBar" :style="{ background: note.channel.color }"></div>
@ -99,7 +99,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-if="isEnabledUrlPreview"> <div v-if="isEnabledUrlPreview">
<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" :class="$style.urlPreview"/> <MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" :class="$style.urlPreview"/>
</div> </div>
<div v-if="appearNote.renoteId" :class="$style.quote"><MkNoteSimple :note="appearNote?.renote ?? null" :class="$style.quoteNote"/></div> <div v-if="appearNote.renoteId" :class="$style.quote"><MkNoteSimple :note="appearNote.renote" :class="$style.quoteNote"/></div>
<button v-if="isLong && collapsed" :class="$style.collapsed" class="_button" @click="collapsed = false"> <button v-if="isLong && collapsed" :class="$style.collapsed" class="_button" @click="collapsed = false">
<span :class="$style.collapsedLabel">{{ i18n.ts.showMore }}</span> <span :class="$style.collapsedLabel">{{ i18n.ts.showMore }}</span>
</button> </button>

View file

@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template> <template>
<div v-if="note" :class="$style.root"> <div v-if="note" :class="$style.root">
<MkAvatar :class="[$style.avatar, prefer.s.useStickyIcons ? $style.useSticky : null]" :user="note.user" link preview/> <MkAvatar :class="$style.avatar" :user="note.user" link preview/>
<div :class="$style.main"> <div :class="$style.main">
<MkNoteHeader :class="$style.header" :note="note" :mini="true"/> <MkNoteHeader :class="$style.header" :note="note" :mini="true"/>
<div> <div>
@ -31,7 +31,6 @@ import MkNoteHeader from '@/components/MkNoteHeader.vue';
import MkSubNoteContent from '@/components/MkSubNoteContent.vue'; import MkSubNoteContent from '@/components/MkSubNoteContent.vue';
import MkCwButton from '@/components/MkCwButton.vue'; import MkCwButton from '@/components/MkCwButton.vue';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { prefer } from '@/preferences.js';
const props = defineProps<{ const props = defineProps<{
note: Misskey.entities.Note | null; note: Misskey.entities.Note | null;
@ -55,12 +54,9 @@ const showContent = ref(false);
width: 34px; width: 34px;
height: 34px; height: 34px;
border-radius: 8px; border-radius: 8px;
position: sticky !important;
&.useSticky { top: calc(16px + var(--MI-stickyTop, 0px));
position: sticky !important; left: 0;
top: calc(16px + var(--MI-stickyTop, 0px));
left: 0;
}
} }
.main { .main {

View file

@ -31,7 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { ref, useTemplateRef } from 'vue'; import { ref, useTemplateRef } from 'vue';
import { notificationTypes } from 'misskey-js'; import { notificationTypes } from '@@/js/const.js';
import MkSwitch from './MkSwitch.vue'; import MkSwitch from './MkSwitch.vue';
import MkInfo from './MkInfo.vue'; import MkInfo from './MkInfo.vue';
import MkButton from './MkButton.vue'; import MkButton from './MkButton.vue';

View file

@ -31,7 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkButton> </MkButton>
<MkLoading v-else/> <MkLoading v-else/>
</div> </div>
<slot :items="getValue(paginator.items)" :fetching="paginator.fetching.value || paginator.fetchingOlder.value"></slot> <slot :items="unref(paginator.items)" :fetching="paginator.fetching.value || paginator.fetchingOlder.value"></slot>
<div v-if="direction === 'down' || direction === 'both'" v-show="downButtonVisible"> <div v-if="direction === 'down' || direction === 'both'" v-show="downButtonVisible">
<MkButton v-if="!downButtonLoading" :class="$style.more" primary rounded @click="downButtonClick"> <MkButton v-if="!downButtonLoading" :class="$style.more" primary rounded @click="downButtonClick">
{{ i18n.ts.loadMore }} {{ i18n.ts.loadMore }}
@ -90,10 +90,6 @@ function onContextmenu(ev: MouseEvent) {
}], ev); }], ev);
} }
function getValue(v: IPaginator['items']) {
return unref(v) as UnwrapRef<T['items']>;
}
if (props.autoLoad) { if (props.autoLoad) {
onMounted(() => { onMounted(() => {
props.paginator.init(); props.paginator.init();
@ -138,7 +134,7 @@ function downButtonClick() {
defineSlots<{ defineSlots<{
empty: () => void; empty: () => void;
default: (props: { items: UnwrapRef<T['items']>, fetching: boolean }) => void; default: (props: { items: UnwrapRef<T['items']> }) => void;
}>(); }>();
</script> </script>

View file

@ -66,7 +66,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-if="q_federation === 'yes'" v-model="q_remoteContentsCleaning"> <MkSwitch v-if="q_federation === 'yes'" v-model="q_remoteContentsCleaning">
<template #label>{{ i18n.ts._serverSetupWizard.remoteContentsCleaning }}</template> <template #label>{{ i18n.ts._serverSetupWizard.remoteContentsCleaning }}</template>
<template #caption>{{ i18n.ts._serverSetupWizard.remoteContentsCleaning_description }}</template> <template #caption>{{ i18n.ts._serverSetupWizard.remoteContentsCleaning_description }} ({{ i18n.ts._serverSetupWizard.remoteContentsCleaning_description2 }})</template>
</MkSwitch> </MkSwitch>
</div> </div>
</MkFolder> </MkFolder>
@ -134,7 +134,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div> <div>
<div><b>{{ i18n.ts._serverSettings.entrancePageStyle }}:</b></div> <div><b>{{ i18n.ts._serverSettings.entrancePageStyle }}:</b></div>
<div>{{ serverSettings.clientOptions?.entrancePageStyle }}</div> <div>{{ serverSettings.clientOptions.entrancePageStyle }}</div>
</div> </div>
<div> <div>
@ -191,6 +191,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import { ROLE_POLICIES } from '@@/js/const.js';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue'; import MkInput from '@/components/MkInput.vue';
import * as os from '@/os.js'; import * as os from '@/os.js';
@ -239,12 +240,12 @@ const serverSettings = computed<Misskey.entities.AdminUpdateMetaRequest>(() => {
enableReactionsBuffering, enableReactionsBuffering,
clientOptions: { clientOptions: {
entrancePageStyle: q_use.value === 'open' ? 'classic' : 'simple', entrancePageStyle: q_use.value === 'open' ? 'classic' : 'simple',
} as any, },
}; };
}); });
const defaultPolicies = computed<Partial<Misskey.entities.RolePolicies>>(() => { const defaultPolicies = computed<Partial<Record<typeof ROLE_POLICIES[number], any>>>(() => {
let driveCapacityMb: Misskey.entities.RolePolicies['driveCapacityMb'] | undefined; let driveCapacityMb;
if (q_use.value === 'single') { if (q_use.value === 'single') {
driveCapacityMb = 8192; driveCapacityMb = 8192;
} else if (q_use.value === 'group') { } else if (q_use.value === 'group') {
@ -253,7 +254,7 @@ const defaultPolicies = computed<Partial<Misskey.entities.RolePolicies>>(() => {
driveCapacityMb = 100; driveCapacityMb = 100;
} }
let rateLimitFactor: Misskey.entities.RolePolicies['rateLimitFactor'] | undefined; let rateLimitFactor;
if (q_use.value === 'single') { if (q_use.value === 'single') {
rateLimitFactor = 0.3; rateLimitFactor = 0.3;
} else if (q_use.value === 'group') { } else if (q_use.value === 'group') {
@ -268,7 +269,7 @@ const defaultPolicies = computed<Partial<Misskey.entities.RolePolicies>>(() => {
} }
} }
let userListLimit: Misskey.entities.RolePolicies['userListLimit'] | undefined; let userListLimit;
if (q_use.value === 'single') { if (q_use.value === 'single') {
userListLimit = 100; userListLimit = 100;
} else if (q_use.value === 'group') { } else if (q_use.value === 'group') {
@ -277,7 +278,7 @@ const defaultPolicies = computed<Partial<Misskey.entities.RolePolicies>>(() => {
userListLimit = 3; userListLimit = 3;
} }
let antennaLimit: Misskey.entities.RolePolicies['antennaLimit'] | undefined; let antennaLimit;
if (q_use.value === 'single') { if (q_use.value === 'single') {
antennaLimit = 100; antennaLimit = 100;
} else if (q_use.value === 'group') { } else if (q_use.value === 'group') {
@ -286,7 +287,7 @@ const defaultPolicies = computed<Partial<Misskey.entities.RolePolicies>>(() => {
antennaLimit = 0; antennaLimit = 0;
} }
let webhookLimit: Misskey.entities.RolePolicies['webhookLimit'] | undefined; let webhookLimit;
if (q_use.value === 'single') { if (q_use.value === 'single') {
webhookLimit = 100; webhookLimit = 100;
} else if (q_use.value === 'group') { } else if (q_use.value === 'group') {
@ -295,35 +296,35 @@ const defaultPolicies = computed<Partial<Misskey.entities.RolePolicies>>(() => {
webhookLimit = 0; webhookLimit = 0;
} }
let canImportFollowing: Misskey.entities.RolePolicies['canImportFollowing']; let canImportFollowing;
if (q_use.value === 'single') { if (q_use.value === 'single') {
canImportFollowing = true; canImportFollowing = true;
} else { } else {
canImportFollowing = false; canImportFollowing = false;
} }
let canImportMuting: Misskey.entities.RolePolicies['canImportMuting']; let canImportMuting;
if (q_use.value === 'single') { if (q_use.value === 'single') {
canImportMuting = true; canImportMuting = true;
} else { } else {
canImportMuting = false; canImportMuting = false;
} }
let canImportBlocking: Misskey.entities.RolePolicies['canImportBlocking']; let canImportBlocking;
if (q_use.value === 'single') { if (q_use.value === 'single') {
canImportBlocking = true; canImportBlocking = true;
} else { } else {
canImportBlocking = false; canImportBlocking = false;
} }
let canImportUserLists: Misskey.entities.RolePolicies['canImportUserLists']; let canImportUserLists;
if (q_use.value === 'single') { if (q_use.value === 'single') {
canImportUserLists = true; canImportUserLists = true;
} else { } else {
canImportUserLists = false; canImportUserLists = false;
} }
let canImportAntennas: Misskey.entities.RolePolicies['canImportAntennas']; let canImportAntennas;
if (q_use.value === 'single') { if (q_use.value === 'single') {
canImportAntennas = true; canImportAntennas = true;
} else { } else {
@ -354,7 +355,6 @@ function applySettings() {
maintainerEmail: q_adminEmail.value === '' ? undefined : q_adminEmail.value, maintainerEmail: q_adminEmail.value === '' ? undefined : q_adminEmail.value,
}, props.token), }, props.token),
misskeyApi('admin/roles/update-default-policies', { misskeyApi('admin/roles/update-default-policies', {
// @ts-expect-error
policies: defaultPolicies.value, policies: defaultPolicies.value,
}, props.token), }, props.token),
]).then(() => { ]).then(() => {

View file

@ -69,11 +69,9 @@ import MkInfo from '@/components/MkInfo.vue';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
message?: string, message?: string,
openOnRemote?: OpenOnRemoteOptions, openOnRemote?: OpenOnRemoteOptions,
initialUsername?: string;
}>(), { }>(), {
message: '', message: '',
openOnRemote: undefined, openOnRemote: undefined,
initialUsername: undefined,
}); });
const emit = defineEmits<{ const emit = defineEmits<{
@ -83,7 +81,7 @@ const emit = defineEmits<{
const host = toUnicode(configHost); const host = toUnicode(configHost);
const username = ref(props.initialUsername ?? ''); const username = ref('');
//#region Open on remote //#region Open on remote
function openRemote(options: OpenOnRemoteOptions, targetHost?: string): void { function openRemote(options: OpenOnRemoteOptions, targetHost?: string): void {

View file

@ -20,7 +20,6 @@ SPDX-License-Identifier: AGPL-3.0-only
key="input" key="input"
:message="message" :message="message"
:openOnRemote="openOnRemote" :openOnRemote="openOnRemote"
:initialUsername="initialUsername"
@usernameSubmitted="onUsernameSubmitted" @usernameSubmitted="onUsernameSubmitted"
@passkeyClick="onPasskeyLogin" @passkeyClick="onPasskeyLogin"
@ -90,12 +89,10 @@ const props = withDefaults(defineProps<{
autoSet?: boolean; autoSet?: boolean;
message?: string, message?: string,
openOnRemote?: OpenOnRemoteOptions, openOnRemote?: OpenOnRemoteOptions,
initialUsername?: string;
}>(), { }>(), {
autoSet: false, autoSet: false,
message: '', message: '',
openOnRemote: undefined, openOnRemote: undefined,
initialUsername: undefined,
}); });
const page = ref<'input' | 'password' | 'totp' | 'passkey'>('input'); const page = ref<'input' | 'password' | 'totp' | 'passkey'>('input');

View file

@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<button :class="$style.closeButton" class="_button" @click="onClose"><i class="ti ti-x"></i></button> <button :class="$style.closeButton" class="_button" @click="onClose"><i class="ti ti-x"></i></button>
</div> </div>
<div :class="$style.content"> <div :class="$style.content">
<MkSignin :autoSet="autoSet" :message="message" :openOnRemote="openOnRemote" :initialUsername="initialUsername" @login="onLogin"/> <MkSignin :autoSet="autoSet" :message="message" :openOnRemote="openOnRemote" @login="onLogin"/>
</div> </div>
</div> </div>
</MkModal> </MkModal>
@ -34,12 +34,10 @@ withDefaults(defineProps<{
autoSet?: boolean; autoSet?: boolean;
message?: string, message?: string,
openOnRemote?: OpenOnRemoteOptions, openOnRemote?: OpenOnRemoteOptions,
initialUsername?: string;
}>(), { }>(), {
autoSet: false, autoSet: false,
message: '', message: '',
openOnRemote: undefined, openOnRemote: undefined,
initialUsername: undefined,
}); });
const emit = defineEmits<{ const emit = defineEmits<{

View file

@ -66,7 +66,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkCaptcha v-if="instance.enableMcaptcha" ref="mcaptcha" v-model="mCaptchaResponse" :class="$style.captcha" provider="mcaptcha" :sitekey="instance.mcaptchaSiteKey" :instanceUrl="instance.mcaptchaInstanceUrl"/> <MkCaptcha v-if="instance.enableMcaptcha" ref="mcaptcha" v-model="mCaptchaResponse" :class="$style.captcha" provider="mcaptcha" :sitekey="instance.mcaptchaSiteKey" :instanceUrl="instance.mcaptchaInstanceUrl"/>
<MkCaptcha v-if="instance.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" :class="$style.captcha" provider="recaptcha" :sitekey="instance.recaptchaSiteKey"/> <MkCaptcha v-if="instance.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" :class="$style.captcha" provider="recaptcha" :sitekey="instance.recaptchaSiteKey"/>
<MkCaptcha v-if="instance.enableTurnstile" ref="turnstile" v-model="turnstileResponse" :class="$style.captcha" provider="turnstile" :sitekey="instance.turnstileSiteKey"/> <MkCaptcha v-if="instance.enableTurnstile" ref="turnstile" v-model="turnstileResponse" :class="$style.captcha" provider="turnstile" :sitekey="instance.turnstileSiteKey"/>
<MkCaptcha v-if="instance.enableTestcaptcha" ref="testcaptcha" v-model="testcaptchaResponse" :class="$style.captcha" provider="testcaptcha" :sitekey="null"/> <MkCaptcha v-if="instance.enableTestcaptcha" ref="testcaptcha" v-model="testcaptchaResponse" :class="$style.captcha" provider="testcaptcha"/>
<MkButton type="submit" :disabled="shouldDisableSubmitting" large gradate rounded data-cy-signup-submit style="margin: 0 auto;"> <MkButton type="submit" :disabled="shouldDisableSubmitting" large gradate rounded data-cy-signup-submit style="margin: 0 auto;">
<template v-if="submitting"> <template v-if="submitting">
<MkLoading :em="true" :colored="false"/> <MkLoading :em="true" :colored="false"/>

View file

@ -44,10 +44,10 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { onUnmounted, onMounted, computed, useTemplateRef, TransitionGroup, markRaw, watch } from 'vue'; import { onUnmounted, onMounted, computed, useTemplateRef, TransitionGroup, markRaw, watch } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import { notificationTypes } from 'misskey-js';
import { useInterval } from '@@/js/use-interval.js'; import { useInterval } from '@@/js/use-interval.js';
import { useDocumentVisibility } from '@@/js/use-document-visibility.js'; import { useDocumentVisibility } from '@@/js/use-document-visibility.js';
import { getScrollContainer, scrollToTop } from '@@/js/scroll.js'; import { getScrollContainer, scrollToTop } from '@@/js/scroll.js';
import type { notificationTypes } from '@@/js/const.js';
import XNotification from '@/components/MkNotification.vue'; import XNotification from '@/components/MkNotification.vue';
import MkNote from '@/components/MkNote.vue'; import MkNote from '@/components/MkNote.vue';
import { useStream } from '@/stream.js'; import { useStream } from '@/stream.js';

View file

@ -12,11 +12,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
<div v-else> <div v-else>
<div :class="$style.error"> <div :class="$style.error">
<slot name="error" :error="error"> <div><i class="ti ti-alert-triangle"></i> {{ i18n.ts.somethingHappened }}</div>
<div><i class="ti ti-alert-triangle"></i> {{ i18n.ts.somethingHappened }}</div> <MkButton inline style="margin-top: 16px;" @click="retry"><i class="ti ti-reload"></i> {{ i18n.ts.retry }}</MkButton>
<div v-if="error">{{ JSON.stringify(error) }}</div>
<MkButton inline style="margin-top: 16px;" @click="retry"><i class="ti ti-reload"></i> {{ i18n.ts.retry }}</MkButton>
</slot>
</div> </div>
</div> </div>
</template> </template>
@ -30,17 +27,15 @@ const props = defineProps<{
p: () => Promise<T>; p: () => Promise<T>;
}>(); }>();
const emit = defineEmits<{
(ev: 'resolved', result: T): void;
}>();
const pending = ref(true); const pending = ref(true);
const resolved = ref(false); const resolved = ref(false);
const rejected = ref(false); const rejected = ref(false);
const result = ref<T | null>(null); const result = ref<T | null>(null);
const error = ref<any | null>(null);
const process = () => { const process = () => {
if (props.p == null) {
return;
}
const promise = props.p(); const promise = props.p();
pending.value = true; pending.value = true;
resolved.value = false; resolved.value = false;
@ -49,12 +44,10 @@ const process = () => {
pending.value = false; pending.value = false;
resolved.value = true; resolved.value = true;
result.value = _result; result.value = _result;
emit('resolved', _result);
}); });
promise.catch((_error) => { promise.catch(() => {
pending.value = false; pending.value = false;
rejected.value = true; rejected.value = true;
error.value = _error;
}); });
}; };

View file

@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<a ref="el" :href="to" :class="active ? activeClass : null" @click="nav" @contextmenu.prevent.stop="onContextmenu"> <a ref="el" :href="to" :class="active ? activeClass : null" @click.prevent="nav" @contextmenu.prevent.stop="onContextmenu">
<slot></slot> <slot></slot>
</a> </a>
</template> </template>
@ -86,11 +86,6 @@ function openWindow() {
} }
function nav(ev: MouseEvent) { function nav(ev: MouseEvent) {
// shift
if (ev.metaKey || ev.altKey || ev.ctrlKey) return;
ev.preventDefault();
if (behavior === 'browser') { if (behavior === 'browser') {
window.location.href = props.to; window.location.href = props.to;
return; return;

View file

@ -84,6 +84,7 @@ const bound = computed(() => props.link
: {}); : {});
const url = computed(() => { const url = computed(() => {
if (props.user.avatarUrl == null) return null;
if (prefer.s.disableShowingAnimatedImages || prefer.s.dataSaver.avatar) return getStaticImageUrl(props.user.avatarUrl); if (prefer.s.disableShowingAnimatedImages || prefer.s.dataSaver.avatar) return getStaticImageUrl(props.user.avatarUrl);
return props.user.avatarUrl; return props.user.avatarUrl;
}); });

View file

@ -41,7 +41,8 @@ const props = defineProps<{
.img { .img {
vertical-align: bottom; vertical-align: bottom;
height: 128px; height: 128px;
margin: auto auto 16px; aspect-ratio: 1;
margin-bottom: 16px;
border-radius: 16px; border-radius: 16px;
} }

View file

@ -54,10 +54,14 @@ router.useListener('change', ({ resolved }) => {
if (resolved == null || 'redirect' in resolved.route) return; if (resolved == null || 'redirect' in resolved.route) return;
if (resolved.route.path === currentRoutePath && deepEqual(resolved.props, currentPageProps.value)) return; if (resolved.route.path === currentRoutePath && deepEqual(resolved.props, currentPageProps.value)) return;
currentPageComponent.value = resolved.route.component; function _() {
currentPageProps.value = resolved.props; currentPageComponent.value = resolved.route.component;
key.value = router.getCurrentFullPath(); currentPageProps.value = resolved.props;
currentRoutePath = resolved.route.path; key.value = router.getCurrentFullPath();
currentRoutePath = resolved.route.path;
}
_();
}); });
</script> </script>

View file

@ -48,7 +48,6 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
<div v-else-if="cellType === 'image'"> <div v-else-if="cellType === 'image'">
<img <img
v-if="cell.value && typeof cell.value === 'string'"
:src="cell.value" :src="cell.value"
:alt="cell.value" :alt="cell.value"
:class="$style.viewImage" :class="$style.viewImage"

View file

@ -20,7 +20,6 @@ import NestedRouterView from './global/NestedRouterView.vue';
import StackingRouterView from './global/StackingRouterView.vue'; import StackingRouterView from './global/StackingRouterView.vue';
import MkLoading from './global/MkLoading.vue'; import MkLoading from './global/MkLoading.vue';
import MkError from './global/MkError.vue'; import MkError from './global/MkError.vue';
import MkSuspense from './global/MkSuspense.vue';
import MkAd from './global/MkAd.vue'; import MkAd from './global/MkAd.vue';
import MkPageHeader from './global/MkPageHeader.vue'; import MkPageHeader from './global/MkPageHeader.vue';
import MkStickyContainer from './global/MkStickyContainer.vue'; import MkStickyContainer from './global/MkStickyContainer.vue';
@ -61,7 +60,6 @@ export const components = {
MkUrl: MkUrl, MkUrl: MkUrl,
MkLoading: MkLoading, MkLoading: MkLoading,
MkError: MkError, MkError: MkError,
MkSuspense: MkSuspense,
MkAd: MkAd, MkAd: MkAd,
MkPageHeader: MkPageHeader, MkPageHeader: MkPageHeader,
MkStickyContainer: MkStickyContainer, MkStickyContainer: MkStickyContainer,
@ -96,7 +94,6 @@ declare module '@vue/runtime-core' {
MkUrl: typeof MkUrl; MkUrl: typeof MkUrl;
MkLoading: typeof MkLoading; MkLoading: typeof MkLoading;
MkError: typeof MkError; MkError: typeof MkError;
MkSuspense: typeof MkSuspense;
MkAd: typeof MkAd; MkAd: typeof MkAd;
MkPageHeader: typeof MkPageHeader; MkPageHeader: typeof MkPageHeader;
MkStickyContainer: typeof MkStickyContainer; MkStickyContainer: typeof MkStickyContainer;

View file

@ -15,7 +15,7 @@ import * as Misskey from 'misskey-js';
import MkMediaList from '@/components/MkMediaList.vue'; import MkMediaList from '@/components/MkMediaList.vue';
const props = defineProps<{ const props = defineProps<{
block: Extract<Misskey.entities.PageBlock, { type: 'image' }>, block: Misskey.entities.PageBlock,
page: Misskey.entities.Page, page: Misskey.entities.Page,
}>(); }>();

View file

@ -18,7 +18,7 @@ import MkNoteDetailed from '@/components/MkNoteDetailed.vue';
import { misskeyApi } from '@/utility/misskey-api.js'; import { misskeyApi } from '@/utility/misskey-api.js';
const props = defineProps<{ const props = defineProps<{
block: Extract<Misskey.entities.PageBlock, { type: 'note' }>, block: Misskey.entities.PageBlock,
page: Misskey.entities.Page, page: Misskey.entities.Page,
}>(); }>();

View file

@ -29,7 +29,7 @@ import * as Misskey from 'misskey-js';
const XBlock = defineAsyncComponent(() => import('./page.block.vue')); const XBlock = defineAsyncComponent(() => import('./page.block.vue'));
defineProps<{ defineProps<{
block: Extract<Misskey.entities.PageBlock, { type: 'section' }>, block: Misskey.entities.PageBlock,
h: number, h: number,
page: Misskey.entities.Page, page: Misskey.entities.Page,
}>(); }>();

View file

@ -22,7 +22,7 @@ import { isEnabledUrlPreview } from '@/utility/url-preview.js';
const MkUrlPreview = defineAsyncComponent(() => import('@/components/MkUrlPreview.vue')); const MkUrlPreview = defineAsyncComponent(() => import('@/components/MkUrlPreview.vue'));
const props = defineProps<{ const props = defineProps<{
block: Extract<Misskey.entities.PageBlock, { type: 'text' }>, block: Misskey.entities.PageBlock,
page: Misskey.entities.Page, page: Misskey.entities.Page,
}>(); }>();

View file

@ -6,7 +6,6 @@
import { defineAsyncComponent, ref } from 'vue'; import { defineAsyncComponent, ref } from 'vue';
import type { Directive } from 'vue'; import type { Directive } from 'vue';
import { popup } from '@/os.js'; import { popup } from '@/os.js';
import { isTouchUsing } from '@/utility/touch.js';
export class UserPreview { export class UserPreview {
private el; private el;
@ -108,7 +107,6 @@ export class UserPreview {
export default { export default {
mounted(el: HTMLElement, binding, vn) { mounted(el: HTMLElement, binding, vn) {
if (binding.value == null) return; if (binding.value == null) return;
if (isTouchUsing) return;
// TODO: 新たにプロパティを作るのをやめMapを使う // TODO: 新たにプロパティを作るのをやめMapを使う
// ただメモリ的には↓の方が省メモリかもしれないので検討中 // ただメモリ的には↓の方が省メモリかもしれないので検討中

View file

@ -51,9 +51,3 @@ export async function fetchInstance(force = false): Promise<Misskey.entities.Met
return instance; return instance;
} }
export type ClientOptions = {
entrancePageStyle: 'classic' | 'simple';
showTimelineForVisitor: boolean;
showActivitiesForVisitor: boolean;
};

View file

@ -96,7 +96,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
</FormSection> </FormSection>
<MkSuspense v-slot="{ result: stats }" :p="initStats"> <FormSuspense v-slot="{ result: stats }" :p="initStats">
<FormSection> <FormSection>
<template #label>{{ i18n.ts.statistics }}</template> <template #label>{{ i18n.ts.statistics }}</template>
<FormSplit> <FormSplit>
@ -110,7 +110,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkKeyValue> </MkKeyValue>
</FormSplit> </FormSplit>
</FormSection> </FormSection>
</MkSuspense> </FormSuspense>
<FormSection> <FormSection>
<template #label>Well-known resources</template> <template #label>Well-known resources</template>
@ -134,6 +134,7 @@ import { misskeyApi } from '@/utility/misskey-api.js';
import FormLink from '@/components/form/link.vue'; import FormLink from '@/components/form/link.vue';
import FormSection from '@/components/form/section.vue'; import FormSection from '@/components/form/section.vue';
import FormSplit from '@/components/form/split.vue'; import FormSplit from '@/components/form/split.vue';
import FormSuspense from '@/components/form/suspense.vue';
import MkFolder from '@/components/MkFolder.vue'; import MkFolder from '@/components/MkFolder.vue';
import MkKeyValue from '@/components/MkKeyValue.vue'; import MkKeyValue from '@/components/MkKeyValue.vue';
import MkLink from '@/components/MkLink.vue'; import MkLink from '@/components/MkLink.vue';

View file

@ -1,182 +0,0 @@
<!--
SPDX-FileCopyrightText: syuilo and misskey-project
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs">
<div v-if="file" class="_spacer" style="--MI_SPACER-w: 600px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;">
<div v-if="tab === 'overview'" class="cxqhhsmd _gaps_m">
<a class="thumbnail" :href="file.url" target="_blank">
<MkDriveFileThumbnail class="thumbnail" :file="file" fit="contain"/>
</a>
<div>
<MkKeyValue :copy="file.type" oneline style="margin: 1em 0;">
<template #key>MIME Type</template>
<template #value><span class="_monospace">{{ file.type }}</span></template>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>Size</template>
<template #value><span class="_monospace">{{ bytes(file.size) }}</span></template>
</MkKeyValue>
<MkKeyValue :copy="file.id" oneline style="margin: 1em 0;">
<template #key>ID</template>
<template #value><span class="_monospace">{{ file.id }}</span></template>
</MkKeyValue>
<MkKeyValue :copy="file.md5" oneline style="margin: 1em 0;">
<template #key>MD5</template>
<template #value><span class="_monospace">{{ file.md5 }}</span></template>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ i18n.ts.createdAt }}</template>
<template #value><span class="_monospace"><MkTime :time="file.createdAt" mode="detail" style="display: block;"/></span></template>
</MkKeyValue>
</div>
<MkA v-if="file.user" class="user" :to="`/admin/user/${file.user.id}`">
<MkUserCardMini :user="file.user"/>
</MkA>
<div>
<MkSwitch :modelValue="isSensitive" @update:modelValue="toggleSensitive">{{ i18n.ts.sensitive }}</MkSwitch>
</div>
<div>
<MkButton danger @click="del"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
</div>
</div>
<div v-else-if="tab === 'usage' && info" class="_gaps_m">
<MkTabs
v-model:tab="usageTab"
:tabs="[{
key: 'note',
title: 'Note',
}, {
key: 'chat',
title: 'Chat',
}]"
/>
<XNotes v-if="usageTab === 'note'" :fileId="props.file.id"/>
<XChat v-else-if="usageTab === 'chat'" :fileId="props.file.id"/>
</div>
<div v-else-if="tab === 'ip' && info" class="_gaps_m">
<MkInfo v-if="!iAmAdmin" warn>{{ i18n.ts.requireAdminForView }}</MkInfo>
<MkKeyValue v-if="info.requestIp" class="_monospace" :copy="info.requestIp" oneline>
<template #key>IP</template>
<template #value>{{ info.requestIp }}</template>
</MkKeyValue>
<FormSection v-if="info.requestHeaders">
<template #label>Headers</template>
<MkKeyValue v-for="(v, k) in info.requestHeaders" :key="k" class="_monospace">
<template #key>{{ k }}</template>
<template #value>{{ v }}</template>
</MkKeyValue>
</FormSection>
</div>
<div v-else-if="tab === 'raw'" class="_gaps_m">
<MkObjectView v-if="info" tall :value="info">
</MkObjectView>
</div>
</div>
</PageWithHeader>
</template>
<script lang="ts" setup>
import { computed, defineAsyncComponent, ref } from 'vue';
import * as Misskey from 'misskey-js';
import MkButton from '@/components/MkButton.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkObjectView from '@/components/MkObjectView.vue';
import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
import MkKeyValue from '@/components/MkKeyValue.vue';
import FormSection from '@/components/form/section.vue';
import MkUserCardMini from '@/components/MkUserCardMini.vue';
import MkInfo from '@/components/MkInfo.vue';
import bytes from '@/filters/bytes.js';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { iAmAdmin, iAmModerator } from '@/i.js';
import MkTabs from '@/components/MkTabs.vue';
const props = defineProps<{
file: Misskey.entities.DriveFile,
info: Misskey.entities.AdminDriveShowFileResponse,
}>();
const tab = ref('overview');
const isSensitive = ref(props.file.isSensitive);
const usageTab = ref<'note' | 'chat'>('note');
const XNotes = defineAsyncComponent(() => import('./drive.file.notes.vue'));
const XChat = defineAsyncComponent(() => import('./admin-file.chat.vue'));
async function del() {
const { canceled } = await os.confirm({
type: 'warning',
text: i18n.tsx.removeAreYouSure({ x: props.file.name }),
});
if (canceled) return;
os.apiWithDialog('drive/files/delete', {
fileId: props.file.id,
});
}
async function toggleSensitive() {
const { canceled } = await os.confirm({
type: 'warning',
text: isSensitive.value ? i18n.ts.unmarkAsSensitiveConfirm : i18n.ts.markAsSensitiveConfirm,
});
if (canceled) return;
isSensitive.value = !isSensitive.value;
os.apiWithDialog('drive/files/update', {
fileId: props.file.id,
isSensitive: !props.file.isSensitive,
});
}
const headerActions = computed(() => [{
text: i18n.ts.openInNewTab,
icon: 'ti ti-external-link',
handler: () => {
window.open(props.file.url, '_blank', 'noopener');
},
}]);
const headerTabs = computed(() => [{
key: 'overview',
title: i18n.ts.overview,
icon: 'ti ti-info-circle',
}, iAmModerator ? {
key: 'usage',
title: i18n.ts._fileViewer.usage,
icon: 'ti ti-plus',
} : null, iAmModerator ? {
key: 'ip',
title: 'IP',
icon: 'ti ti-password',
} : null, {
key: 'raw',
title: 'Raw data',
icon: 'ti ti-code',
}].filter(x => x != null));
</script>
<style lang="scss" scoped>
.cxqhhsmd {
> .thumbnail {
display: block;
> .thumbnail {
height: 300px;
max-width: 100%;
}
}
> .user {
&:hover {
text-decoration: none;
}
}
}
</style>

View file

@ -4,37 +4,197 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<MkSuspense v-slot="{ result }" :p="_fetch_" @resolved="(result) => file = result.file"> <PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs">
<XRoot v-if="result.file != null && result.info != null" :file="result.file" :info="result.info"/> <div v-if="file" class="_spacer" style="--MI_SPACER-w: 600px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;">
</MkSuspense> <div v-if="tab === 'overview'" class="cxqhhsmd _gaps_m">
<a class="thumbnail" :href="file.url" target="_blank">
<MkDriveFileThumbnail class="thumbnail" :file="file" fit="contain"/>
</a>
<div>
<MkKeyValue :copy="file.type" oneline style="margin: 1em 0;">
<template #key>MIME Type</template>
<template #value><span class="_monospace">{{ file.type }}</span></template>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>Size</template>
<template #value><span class="_monospace">{{ bytes(file.size) }}</span></template>
</MkKeyValue>
<MkKeyValue :copy="file.id" oneline style="margin: 1em 0;">
<template #key>ID</template>
<template #value><span class="_monospace">{{ file.id }}</span></template>
</MkKeyValue>
<MkKeyValue :copy="file.md5" oneline style="margin: 1em 0;">
<template #key>MD5</template>
<template #value><span class="_monospace">{{ file.md5 }}</span></template>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ i18n.ts.createdAt }}</template>
<template #value><span class="_monospace"><MkTime :time="file.createdAt" mode="detail" style="display: block;"/></span></template>
</MkKeyValue>
</div>
<MkA v-if="file.user" class="user" :to="`/admin/user/${file.user.id}`">
<MkUserCardMini :user="file.user"/>
</MkA>
<div>
<MkSwitch :modelValue="isSensitive" @update:modelValue="toggleSensitive">{{ i18n.ts.sensitive }}</MkSwitch>
</div>
<div>
<MkButton danger @click="del"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
</div>
</div>
<div v-else-if="tab === 'usage' && info" class="_gaps_m">
<MkTabs
v-model:tab="usageTab"
:tabs="[{
key: 'note',
title: 'Note',
}, {
key: 'chat',
title: 'Chat',
}]"
/>
<XNotes v-if="usageTab === 'note'" :fileId="fileId"/>
<XChat v-else-if="usageTab === 'chat'" :fileId="fileId"/>
</div>
<div v-else-if="tab === 'ip' && info" class="_gaps_m">
<MkInfo v-if="!iAmAdmin" warn>{{ i18n.ts.requireAdminForView }}</MkInfo>
<MkKeyValue v-if="info.requestIp" class="_monospace" :copy="info.requestIp" oneline>
<template #key>IP</template>
<template #value>{{ info.requestIp }}</template>
</MkKeyValue>
<FormSection v-if="info.requestHeaders">
<template #label>Headers</template>
<MkKeyValue v-for="(v, k) in info.requestHeaders" :key="k" class="_monospace">
<template #key>{{ k }}</template>
<template #value>{{ v }}</template>
</MkKeyValue>
</FormSection>
</div>
<div v-else-if="tab === 'raw'" class="_gaps_m">
<MkObjectView v-if="info" tall :value="info">
</MkObjectView>
</div>
</div>
</PageWithHeader>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue'; import { computed, defineAsyncComponent, ref } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import XRoot from './admin-file.root.vue'; import MkButton from '@/components/MkButton.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkObjectView from '@/components/MkObjectView.vue';
import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
import MkKeyValue from '@/components/MkKeyValue.vue';
import FormSection from '@/components/form/section.vue';
import MkUserCardMini from '@/components/MkUserCardMini.vue';
import MkInfo from '@/components/MkInfo.vue';
import bytes from '@/filters/bytes.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js'; import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js'; import { definePage } from '@/page.js';
import { iAmAdmin, iAmModerator } from '@/i.js';
import MkTabs from '@/components/MkTabs.vue';
const tab = ref('overview');
const file = ref<Misskey.entities.DriveFile | null>(null);
const info = ref<Misskey.entities.AdminDriveShowFileResponse | null>(null);
const isSensitive = ref<boolean>(false);
const usageTab = ref<'note' | 'chat'>('note');
const XNotes = defineAsyncComponent(() => import('./drive.file.notes.vue'));
const XChat = defineAsyncComponent(() => import('./admin-file.chat.vue'));
const props = defineProps<{ const props = defineProps<{
fileId: string, fileId: string,
}>(); }>();
function _fetch_() { async function _fetch_() {
return Promise.all([ file.value = await misskeyApi('drive/files/show', { fileId: props.fileId });
misskeyApi('drive/files/show', { fileId: props.fileId }), info.value = await misskeyApi('admin/drive/show-file', { fileId: props.fileId });
misskeyApi('admin/drive/show-file', { fileId: props.fileId }), isSensitive.value = file.value.isSensitive;
]).then((result) => ({
file: result[0],
info: result[1],
}));
} }
const file = ref<Misskey.entities.DriveFile | null>(null); _fetch_();
async function del() {
const { canceled } = await os.confirm({
type: 'warning',
text: i18n.tsx.removeAreYouSure({ x: file.value.name }),
});
if (canceled) return;
os.apiWithDialog('drive/files/delete', {
fileId: file.value.id,
});
}
async function toggleSensitive() {
if (!file.value) return;
const { canceled } = await os.confirm({
type: 'warning',
text: isSensitive.value ? i18n.ts.unmarkAsSensitiveConfirm : i18n.ts.markAsSensitiveConfirm,
});
if (canceled) return;
isSensitive.value = !isSensitive.value;
os.apiWithDialog('drive/files/update', {
fileId: file.value.id,
isSensitive: !file.value.isSensitive,
});
}
const headerActions = computed(() => [{
text: i18n.ts.openInNewTab,
icon: 'ti ti-external-link',
handler: () => {
window.open(file.value.url, '_blank', 'noopener');
},
}]);
const headerTabs = computed(() => [{
key: 'overview',
title: i18n.ts.overview,
icon: 'ti ti-info-circle',
}, iAmModerator ? {
key: 'usage',
title: i18n.ts._fileViewer.usage,
icon: 'ti ti-plus',
} : null, iAmModerator ? {
key: 'ip',
title: 'IP',
icon: 'ti ti-password',
} : null, {
key: 'raw',
title: 'Raw data',
icon: 'ti ti-code',
}].filter(x => x != null));
definePage(() => ({ definePage(() => ({
title: file.value ? `${i18n.ts.file}: ${file.value.name}` : i18n.ts.file, title: file.value ? `${i18n.ts.file}: ${file.value.name}` : i18n.ts.file,
icon: 'ti ti-file', icon: 'ti ti-file',
})); }));
</script> </script>
<style lang="scss" scoped>
.cxqhhsmd {
> .thumbnail {
display: block;
> .thumbnail {
height: 300px;
max-width: 100%;
}
}
> .user {
&:hover {
text-decoration: none;
}
}
}
</style>

View file

@ -152,7 +152,6 @@ SPDX-License-Identifier: AGPL-3.0-only
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import JSON5 from 'json5'; import JSON5 from 'json5';
import { host } from '@@/js/config.js'; import { host } from '@@/js/config.js';
import type { ClientOptions } from '@/instance.js';
import MkInput from '@/components/MkInput.vue'; import MkInput from '@/components/MkInput.vue';
import MkTextarea from '@/components/MkTextarea.vue'; import MkTextarea from '@/components/MkTextarea.vue';
import * as os from '@/os.js'; import * as os from '@/os.js';
@ -167,13 +166,9 @@ import MkSwitch from '@/components/MkSwitch.vue';
const meta = await misskeyApi('admin/meta'); const meta = await misskeyApi('admin/meta');
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition const entrancePageStyle = ref(meta.clientOptions.entrancePageStyle ?? 'classic');
const entrancePageStyle = ref<ClientOptions['entrancePageStyle']>(meta.clientOptions.entrancePageStyle ?? 'classic'); const showTimelineForVisitor = ref(meta.clientOptions.showTimelineForVisitor ?? true);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition const showActivitiesForVisitor = ref(meta.clientOptions.showActivitiesForVisitor ?? true);
const showTimelineForVisitor = ref<ClientOptions['showTimelineForVisitor']>(meta.clientOptions.showTimelineForVisitor ?? true);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const showActivitiesForVisitor = ref<ClientOptions['showActivitiesForVisitor']>(meta.clientOptions.showActivitiesForVisitor ?? true);
const iconUrl = ref(meta.iconUrl); const iconUrl = ref(meta.iconUrl);
const app192IconUrl = ref(meta.app192IconUrl); const app192IconUrl = ref(meta.app192IconUrl);
const app512IconUrl = ref(meta.app512IconUrl); const app512IconUrl = ref(meta.app512IconUrl);
@ -191,11 +186,11 @@ const manifestJsonOverride = ref(meta.manifestJsonOverride === '' ? '{}' : JSON.
function save() { function save() {
os.apiWithDialog('admin/update-meta', { os.apiWithDialog('admin/update-meta', {
clientOptions: ({ clientOptions: {
entrancePageStyle: entrancePageStyle.value, entrancePageStyle: entrancePageStyle.value,
showTimelineForVisitor: showTimelineForVisitor.value, showTimelineForVisitor: showTimelineForVisitor.value,
showActivitiesForVisitor: showActivitiesForVisitor.value, showActivitiesForVisitor: showActivitiesForVisitor.value,
} as ClientOptions) as any, },
iconUrl: iconUrl.value, iconUrl: iconUrl.value,
app192IconUrl: app192IconUrl.value, app192IconUrl: app192IconUrl.value,
app512IconUrl: app512IconUrl.value, app512IconUrl: app512IconUrl.value,

View file

@ -6,18 +6,19 @@ SPDX-License-Identifier: AGPL-3.0-only
<template> <template>
<PageWithHeader :actions="headerActions" :tabs="headerTabs"> <PageWithHeader :actions="headerActions" :tabs="headerTabs">
<div class="_spacer" style="--MI_SPACER-w: 800px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;"> <div class="_spacer" style="--MI_SPACER-w: 800px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;">
<MkSuspense v-slot="{ result: database }" :p="databasePromiseFactory"> <FormSuspense v-slot="{ result: database }" :p="databasePromiseFactory">
<MkKeyValue v-for="table in database" :key="table[0]" oneline style="margin: 1em 0;"> <MkKeyValue v-for="table in database" :key="table[0]" oneline style="margin: 1em 0;">
<template #key>{{ table[0] }}</template> <template #key>{{ table[0] }}</template>
<template #value>{{ bytes(table[1].size) }} ({{ number(table[1].count) }} recs)</template> <template #value>{{ bytes(table[1].size) }} ({{ number(table[1].count) }} recs)</template>
</MkKeyValue> </MkKeyValue>
</MkSuspense> </FormSuspense>
</div> </div>
</PageWithHeader> </PageWithHeader>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from 'vue'; import { computed } from 'vue';
import FormSuspense from '@/components/form/suspense.vue';
import MkKeyValue from '@/components/MkKeyValue.vue'; import MkKeyValue from '@/components/MkKeyValue.vue';
import { misskeyApi } from '@/utility/misskey-api.js'; import { misskeyApi } from '@/utility/misskey-api.js';
import bytes from '@/filters/bytes.js'; import bytes from '@/filters/bytes.js';

View file

@ -107,7 +107,7 @@ const smtpPass = ref(meta.smtpPass);
async function testEmail() { async function testEmail() {
const { canceled, result: destination } = await os.inputText({ const { canceled, result: destination } = await os.inputText({
title: 'To', title: i18n.ts.destination,
type: 'email', type: 'email',
default: instance.maintainerEmail ?? '', default: instance.maintainerEmail ?? '',
placeholder: 'test@example.com', placeholder: 'test@example.com',

View file

@ -81,10 +81,10 @@ function onStats(stats: Misskey.entities.QueueStats) {
delayed.value = stats[props.domain].delayed; delayed.value = stats[props.domain].delayed;
waiting.value = stats[props.domain].waiting; waiting.value = stats[props.domain].waiting;
if (chartProcess.value != null) chartProcess.value.pushData(stats[props.domain].activeSincePrevTick); chartProcess.value.pushData(stats[props.domain].activeSincePrevTick);
if (chartActive.value != null) chartActive.value.pushData(stats[props.domain].active); chartActive.value.pushData(stats[props.domain].active);
if (chartDelayed.value != null) chartDelayed.value.pushData(stats[props.domain].delayed); chartDelayed.value.pushData(stats[props.domain].delayed);
if (chartWaiting.value != null) chartWaiting.value.pushData(stats[props.domain].waiting); chartWaiting.value.pushData(stats[props.domain].waiting);
} }
function onStatsLog(statsLog: Misskey.entities.QueueStatsLog) { function onStatsLog(statsLog: Misskey.entities.QueueStatsLog) {
@ -100,10 +100,10 @@ function onStatsLog(statsLog: Misskey.entities.QueueStatsLog) {
dataWaiting.push(stats[props.domain].waiting); dataWaiting.push(stats[props.domain].waiting);
} }
if (chartProcess.value != null) chartProcess.value.setData(dataProcess); chartProcess.value.setData(dataProcess);
if (chartActive.value != null) chartActive.value.setData(dataActive); chartActive.value.setData(dataActive);
if (chartDelayed.value != null) chartDelayed.value.setData(dataDelayed); chartDelayed.value.setData(dataDelayed);
if (chartWaiting.value != null) chartWaiting.value.setData(dataWaiting); chartWaiting.value.setData(dataWaiting);
} }
onMounted(() => { onMounted(() => {

View file

@ -78,7 +78,7 @@ const timeline = computed(() => {
return paginator.items.value.map(x => ({ return paginator.items.value.map(x => ({
id: x.id, id: x.id,
timestamp: new Date(x.createdAt).getTime(), timestamp: new Date(x.createdAt).getTime(),
data: x as Misskey.entities.ModerationLog, data: x,
})); }));
}); });

View file

@ -109,6 +109,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import MkSwitch from '@/components/MkSwitch.vue'; import MkSwitch from '@/components/MkSwitch.vue';
import MkInput from '@/components/MkInput.vue'; import MkInput from '@/components/MkInput.vue';
import FormSuspense from '@/components/form/suspense.vue';
import FormSplit from '@/components/form/split.vue'; import FormSplit from '@/components/form/split.vue';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js'; import { misskeyApi } from '@/utility/misskey-api.js';

View file

@ -37,8 +37,6 @@ async function renderChart() {
chartInstance.destroy(); chartInstance.destroy();
} }
if (chartEl.value == null) return;
const getDate = (ago: number) => { const getDate = (ago: number) => {
const y = now.getFullYear(); const y = now.getFullYear();
const m = now.getMonth(); const m = now.getMonth();
@ -107,6 +105,7 @@ async function renderChart() {
type: 'time', type: 'time',
offset: true, offset: true,
time: { time: {
stepSize: 1,
unit: 'day', unit: 'day',
displayFormats: { displayFormats: {
day: 'M/d', day: 'M/d',
@ -150,9 +149,7 @@ async function renderChart() {
}, },
external: externalTooltipHandler, external: externalTooltipHandler,
}, },
...({ // TS gradient,
gradient,
}),
}, },
}, },
plugins: [chartVLine(vLineColor)], plugins: [chartVLine(vLineColor)],

View file

@ -42,9 +42,6 @@ const { handler: externalTooltipHandler } = useChartTooltip();
const { handler: externalTooltipHandler2 } = useChartTooltip(); const { handler: externalTooltipHandler2 } = useChartTooltip();
onMounted(async () => { onMounted(async () => {
if (chartEl.value == null) return;
if (chartEl2.value == null) return;
const now = isChromatic() ? new Date('2024-08-31T10:00:00Z') : new Date(); const now = isChromatic() ? new Date('2024-08-31T10:00:00Z') : new Date();
const getDate = (ago: number) => { const getDate = (ago: number) => {
@ -125,6 +122,7 @@ onMounted(async () => {
stacked: true, stacked: true,
offset: false, offset: false,
time: { time: {
stepSize: 1,
unit: 'day', unit: 'day',
}, },
grid: { grid: {
@ -146,7 +144,7 @@ onMounted(async () => {
ticks: { ticks: {
display: true, display: true,
//mirror: true, //mirror: true,
callback: (value, index, values) => (value as number) < 0 ? -value : value, callback: (value, index, values) => value < 0 ? -value : value,
}, },
}, },
}, },
@ -175,9 +173,7 @@ onMounted(async () => {
label: context => `${context.dataset.label}: ${Math.abs(context.parsed.y)}`, label: context => `${context.dataset.label}: ${Math.abs(context.parsed.y)}`,
}, },
}, },
...({ // TS gradient,
gradient,
}),
}, },
}, },
plugins: [chartVLine(vLineColor)], plugins: [chartVLine(vLineColor)],
@ -217,6 +213,7 @@ onMounted(async () => {
type: 'time', type: 'time',
offset: false, offset: false,
time: { time: {
stepSize: 1,
unit: 'day', unit: 'day',
displayFormats: { displayFormats: {
day: 'M/d', day: 'M/d',
@ -263,9 +260,7 @@ onMounted(async () => {
}, },
external: externalTooltipHandler2, external: externalTooltipHandler2,
}, },
...({ // TS gradient,
gradient,
}),
}, },
}, },
plugins: [chartVLine(vLineColor)], plugins: [chartVLine(vLineColor)],

View file

@ -828,9 +828,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { watch, ref, computed } from 'vue'; import { watch, ref, computed } from 'vue';
import { throttle } from 'throttle-debounce'; import { throttle } from 'throttle-debounce';
import * as Misskey from 'misskey-js'; import { ROLE_POLICIES } from '@@/js/const.js';
import RolesEditorFormula from './RolesEditorFormula.vue'; import RolesEditorFormula from './RolesEditorFormula.vue';
import type { GetMkSelectValueTypesFromDef, MkSelectItem } from '@/components/MkSelect.vue';
import MkInput from '@/components/MkInput.vue'; import MkInput from '@/components/MkInput.vue';
import MkColorInput from '@/components/MkColorInput.vue'; import MkColorInput from '@/components/MkColorInput.vue';
import MkSelect from '@/components/MkSelect.vue'; import MkSelect from '@/components/MkSelect.vue';
@ -855,7 +854,7 @@ const props = defineProps<{
const role = ref(deepClone(props.modelValue)); const role = ref(deepClone(props.modelValue));
// fill missing policy // fill missing policy
for (const ROLE_POLICY of Misskey.rolePolicies) { for (const ROLE_POLICY of ROLE_POLICIES) {
if (role.value.policies[ROLE_POLICY] == null) { if (role.value.policies[ROLE_POLICY] == null) {
role.value.policies[ROLE_POLICY] = { role.value.policies[ROLE_POLICY] = {
useDefault: true, useDefault: true,

View file

@ -331,7 +331,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { computed, reactive, ref } from 'vue'; import { computed, reactive, ref } from 'vue';
import * as Misskey from 'misskey-js'; import { ROLE_POLICIES } from '@@/js/const.js';
import MkInput from '@/components/MkInput.vue'; import MkInput from '@/components/MkInput.vue';
import MkFolder from '@/components/MkFolder.vue'; import MkFolder from '@/components/MkFolder.vue';
import MkSwitch from '@/components/MkSwitch.vue'; import MkSwitch from '@/components/MkSwitch.vue';
@ -353,8 +353,8 @@ const baseRoleQ = ref('');
const roles = await misskeyApi('admin/roles/list'); const roles = await misskeyApi('admin/roles/list');
const policies = reactive<Record<typeof Misskey.rolePolicies[number], any>>({}); const policies = reactive<Record<typeof ROLE_POLICIES[number], any>>({});
for (const ROLE_POLICY of Misskey.rolePolicies) { for (const ROLE_POLICY of ROLE_POLICIES) {
policies[ROLE_POLICY] = instance.policies[ROLE_POLICY]; policies[ROLE_POLICY] = instance.policies[ROLE_POLICY];
} }

View file

@ -194,7 +194,7 @@ const sensitiveMediaDetectionForm = useForm({
state.sensitiveMediaDetectionSensitivity === 2 ? 'medium' : state.sensitiveMediaDetectionSensitivity === 2 ? 'medium' :
state.sensitiveMediaDetectionSensitivity === 3 ? 'high' : state.sensitiveMediaDetectionSensitivity === 3 ? 'high' :
state.sensitiveMediaDetectionSensitivity === 4 ? 'veryHigh' : state.sensitiveMediaDetectionSensitivity === 4 ? 'veryHigh' :
null as never, 0,
setSensitiveFlagAutomatically: state.setSensitiveFlagAutomatically, setSensitiveFlagAutomatically: state.setSensitiveFlagAutomatically,
enableSensitiveMediaDetectionForVideos: state.enableSensitiveMediaDetectionForVideos, enableSensitiveMediaDetectionForVideos: state.enableSensitiveMediaDetectionForVideos,
}); });

View file

@ -138,10 +138,9 @@ async function addPinnedNote() {
const { canceled, result: value } = await os.inputText({ const { canceled, result: value } = await os.inputText({
title: i18n.ts.noteIdOrUrl, title: i18n.ts.noteIdOrUrl,
}); });
if (canceled || value == null) return; if (canceled) return;
const fromUrl = value.includes('/') ? value.split('/').pop() : null;
const note = await os.apiWithDialog('notes/show', { const note = await os.apiWithDialog('notes/show', {
noteId: fromUrl ?? value, noteId: value.includes('/') ? value.split('/').pop() : value,
}); });
pinnedNotes.value = [{ pinnedNotes.value = [{
id: note.id, id: note.id,

View file

@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template> <template>
<div :class="[$style.root, { [$style.isMe]: isMe }]"> <div :class="[$style.root, { [$style.isMe]: isMe }]">
<MkAvatar :class="[$style.avatar, prefer.s.useStickyIcons ? $style.useSticky : null]" :user="message.fromUser!" :link="!isMe" :preview="false"/> <MkAvatar :class="$style.avatar" :user="message.fromUser!" :link="!isMe" :preview="false"/>
<div :class="[$style.body, message.file != null ? $style.fullWidth : null]" @contextmenu.stop="onContextmenu"> <div :class="[$style.body, message.file != null ? $style.fullWidth : null]" @contextmenu.stop="onContextmenu">
<div :class="$style.header"><MkUserName v-if="!isMe && prefer.s['chat.showSenderName'] && message.fromUser != null" :user="message.fromUser"/></div> <div :class="$style.header"><MkUserName v-if="!isMe && prefer.s['chat.showSenderName'] && message.fromUser != null" :user="message.fromUser"/></div>
<MkFukidashi :class="$style.fukidashi" :tail="isMe ? 'right' : 'left'" :fullWidth="message.file != null" :accented="isMe"> <MkFukidashi :class="$style.fukidashi" :tail="isMe ? 'right' : 'left'" :fullWidth="message.file != null" :accented="isMe">
@ -231,14 +231,11 @@ function showMenu(ev: MouseEvent, contextmenu = false) {
} }
.avatar { .avatar {
position: sticky;
top: calc(16px + var(--MI-stickyTop, 0px));
display: block; display: block;
width: 50px; width: 50px;
height: 50px; height: 50px;
&.useSticky {
position: sticky;
top: calc(16px + var(--MI-stickyTop, 0px));
}
} }
@container (max-width: 450px) { @container (max-width: 450px) {

View file

@ -48,8 +48,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<div><b>{{ i18n.tsx.lastNDays({ n: 7 }) }} {{ i18n.ts.ranking }}</b> ({{ gameMode.toUpperCase() }})</div> <div><b>{{ i18n.tsx.lastNDays({ n: 7 }) }} {{ i18n.ts.ranking }}</b> ({{ gameMode.toUpperCase() }})</div>
<div v-if="ranking" class="_gaps_s"> <div v-if="ranking" class="_gaps_s">
<div v-for="r in ranking" :key="r.id" :class="$style.rankingRecord"> <div v-for="r in ranking" :key="r.id" :class="$style.rankingRecord">
<MkAvatar v-if="r.user" :link="true" style="width: 24px; height: 24px; margin-right: 4px;" :user="r.user"/> <MkAvatar :link="true" style="width: 24px; height: 24px; margin-right: 4px;" :user="r.user"/>
<MkUserName v-if="r.user" :user="r.user" :nowrap="true"/> <MkUserName :user="r.user" :nowrap="true"/>
<b style="margin-left: auto;">{{ r.score.toLocaleString() }} {{ getScoreUnit(gameMode) }}</b> <b style="margin-left: auto;">{{ r.score.toLocaleString() }} {{ getScoreUnit(gameMode) }}</b>
</div> </div>
</div> </div>
@ -87,7 +87,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import * as Misskey from 'misskey-js';
import XGame from './drop-and-fusion.game.vue'; import XGame from './drop-and-fusion.game.vue';
import { definePage } from '@/page.js'; import { definePage } from '@/page.js';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
@ -99,7 +98,7 @@ import { misskeyApiGet } from '@/utility/misskey-api.js';
const gameMode = ref<'normal' | 'square' | 'yen' | 'sweets' | 'space'>('normal'); const gameMode = ref<'normal' | 'square' | 'yen' | 'sweets' | 'space'>('normal');
const gameStarted = ref(false); const gameStarted = ref(false);
const mute = ref(false); const mute = ref(false);
const ranking = ref<Misskey.entities.BubbleGameRankingResponse | null>(null); const ranking = ref(null);
watch(gameMode, async () => { watch(gameMode, async () => {
ranking.value = await misskeyApiGet('bubble-game/ranking', { gameMode: gameMode.value }); ranking.value = await misskeyApiGet('bubble-game/ranking', { gameMode: gameMode.value });

View file

@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkInput v-model="name" pattern="[a-z0-9_]" autocapitalize="off"> <MkInput v-model="name" pattern="[a-z0-9_]" autocapitalize="off">
<template #label>{{ i18n.ts.name }}</template> <template #label>{{ i18n.ts.name }}</template>
</MkInput> </MkInput>
<MkInput v-model="category" :datalist="customEmojiCategories.filter(x => x != null)"> <MkInput v-model="category" :datalist="customEmojiCategories">
<template #label>{{ i18n.ts.category }}</template> <template #label>{{ i18n.ts.category }}</template>
</MkInput> </MkInput>
<MkInput v-model="aliases" autocapitalize="off"> <MkInput v-model="aliases" autocapitalize="off">

View file

@ -1,145 +0,0 @@
<!--
SPDX-FileCopyrightText: syuilo and misskey-project
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<PageWithHeader :actions="headerActions" :tabs="headerTabs">
<div class="_spacer" style="--MI_SPACER-w: 800px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;">
<MkInput v-model="title">
<template #label>{{ i18n.ts.title }}</template>
</MkInput>
<MkTextarea v-model="description" :max="500">
<template #label>{{ i18n.ts.description }}</template>
</MkTextarea>
<div class="_gaps_s">
<div v-for="file in files" :key="file.id" class="wqugxsfx" :style="{ backgroundImage: file ? `url(${ file.thumbnailUrl })` : '' }">
<div class="name">{{ file.name }}</div>
<button v-tooltip="i18n.ts.remove" class="remove _button" @click="remove(file)"><i class="ti ti-x"></i></button>
</div>
<MkButton primary @click="chooseFile"><i class="ti ti-plus"></i> {{ i18n.ts.attachFile }}</MkButton>
</div>
<MkSwitch v-model="isSensitive">{{ i18n.ts.markAsSensitive }}</MkSwitch>
<div class="_buttons">
<MkButton v-if="props.post != null" primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
<MkButton v-else primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.publish }}</MkButton>
<MkButton v-if="props.post != null" danger @click="del"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
</div>
</div>
</PageWithHeader>
</template>
<script lang="ts" setup>
import { computed, watch, ref } from 'vue';
import * as Misskey from 'misskey-js';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
import MkTextarea from '@/components/MkTextarea.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import { selectFile } from '@/utility/drive.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
import { useRouter } from '@/router.js';
const router = useRouter();
const props = defineProps<{
post: Misskey.entities.GalleryPost | null;
}>();
const files = ref(props.post?.files ?? []);
const description = ref(props.post?.description ?? null);
const title = ref(props.post?.title ?? '');
const isSensitive = ref(props.post?.isSensitive ?? false);
function chooseFile(evt) {
selectFile({
anchorElement: evt.currentTarget ?? evt.target,
multiple: true,
}).then(selected => {
files.value = files.value.concat(selected);
});
}
function remove(file) {
files.value = files.value.filter(f => f.id !== file.id);
}
async function save() {
if (props.post != null) {
await os.apiWithDialog('gallery/posts/update', {
postId: props.post.id,
title: title.value,
description: description.value,
fileIds: files.value.map(file => file.id),
isSensitive: isSensitive.value,
});
router.push('/gallery/:postId', {
params: {
postId: props.post.id,
},
});
} else {
const created = await os.apiWithDialog('gallery/posts/create', {
title: title.value,
description: description.value,
fileIds: files.value.map(file => file.id),
isSensitive: isSensitive.value,
});
router.push('/gallery/:postId', {
params: {
postId: created.id,
},
});
}
}
async function del() {
if (props.post == null) return;
const { canceled } = await os.confirm({
type: 'warning',
text: i18n.ts.deleteConfirm,
});
if (canceled) return;
await os.apiWithDialog('gallery/posts/delete', {
postId: props.post.id,
});
router.push('/gallery');
}
const headerActions = computed(() => []);
const headerTabs = computed(() => []);
</script>
<style lang="scss" scoped>
.wqugxsfx {
height: 200px;
background-size: contain;
background-position: center;
background-repeat: no-repeat;
position: relative;
> .name {
position: absolute;
top: 8px;
left: 9px;
padding: 8px;
background: var(--MI_THEME-panel);
}
> .remove {
position: absolute;
top: 8px;
right: 9px;
padding: 8px;
background: var(--MI_THEME-panel);
}
}
</style>

View file

@ -4,35 +4,162 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<MkSuspense v-slot="{ result }" :p="_fetch_"> <PageWithHeader :actions="headerActions" :tabs="headerTabs">
<XRoot :post="result"/> <div class="_spacer" style="--MI_SPACER-w: 800px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;">
</MkSuspense> <FormSuspense :p="init" class="_gaps">
<MkInput v-model="title">
<template #label>{{ i18n.ts.title }}</template>
</MkInput>
<MkTextarea v-model="description" :max="500">
<template #label>{{ i18n.ts.description }}</template>
</MkTextarea>
<div class="_gaps_s">
<div v-for="file in files" :key="file.id" class="wqugxsfx" :style="{ backgroundImage: file ? `url(${ file.thumbnailUrl })` : '' }">
<div class="name">{{ file.name }}</div>
<button v-tooltip="i18n.ts.remove" class="remove _button" @click="remove(file)"><i class="ti ti-x"></i></button>
</div>
<MkButton primary @click="chooseFile"><i class="ti ti-plus"></i> {{ i18n.ts.attachFile }}</MkButton>
</div>
<MkSwitch v-model="isSensitive">{{ i18n.ts.markAsSensitive }}</MkSwitch>
<div class="_buttons">
<MkButton v-if="postId" primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
<MkButton v-else primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.publish }}</MkButton>
<MkButton v-if="postId" danger @click="del"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
</div>
</FormSuspense>
</div>
</PageWithHeader>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue'; import { computed, watch, ref } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import XRoot from './edit.root.vue'; import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
import MkTextarea from '@/components/MkTextarea.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import FormSuspense from '@/components/form/suspense.vue';
import { selectFile } from '@/utility/drive.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js'; import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js'; import { definePage } from '@/page.js';
import { i18n } from '@/i18n.js';
import { useRouter } from '@/router.js';
const router = useRouter();
const props = defineProps<{ const props = defineProps<{
postId?: string, postId?: string;
}>(); }>();
function _fetch_() { const init = ref<(() => Promise<any>) | null>(null);
if (props.postId == null) { const files = ref<Misskey.entities.DriveFile[]>([]);
return Promise.resolve(null); const description = ref<string | null>(null);
} else { const title = ref<string | null>(null);
return misskeyApi('gallery/posts/show', { const isSensitive = ref(false);
function chooseFile(evt) {
selectFile({
anchorElement: evt.currentTarget ?? evt.target,
multiple: true,
}).then(selected => {
files.value = files.value.concat(selected);
});
}
function remove(file) {
files.value = files.value.filter(f => f.id !== file.id);
}
async function save() {
if (props.postId) {
await os.apiWithDialog('gallery/posts/update', {
postId: props.postId, postId: props.postId,
title: title.value,
description: description.value,
fileIds: files.value.map(file => file.id),
isSensitive: isSensitive.value,
});
router.push('/gallery/:postId', {
params: {
postId: props.postId,
},
});
} else {
const created = await os.apiWithDialog('gallery/posts/create', {
title: title.value,
description: description.value,
fileIds: files.value.map(file => file.id),
isSensitive: isSensitive.value,
});
router.push('/gallery/:postId', {
params: {
postId: created.id,
},
}); });
} }
} }
async function del() {
const { canceled } = await os.confirm({
type: 'warning',
text: i18n.ts.deleteConfirm,
});
if (canceled) return;
await os.apiWithDialog('gallery/posts/delete', {
postId: props.postId,
});
router.push('/gallery');
}
watch(() => props.postId, () => {
init.value = () => props.postId ? misskeyApi('gallery/posts/show', {
postId: props.postId,
}).then(post => {
files.value = post.files ?? [];
title.value = post.title;
description.value = post.description;
isSensitive.value = post.isSensitive;
}) : Promise.resolve(null);
}, { immediate: true });
const headerActions = computed(() => []);
const headerTabs = computed(() => []);
definePage(() => ({ definePage(() => ({
title: props.postId ? i18n.ts.edit : i18n.ts.postToGallery, title: props.postId ? i18n.ts.edit : i18n.ts.postToGallery,
icon: 'ti ti-pencil', icon: 'ti ti-pencil',
})); }));
</script> </script>
<style lang="scss" scoped>
.wqugxsfx {
height: 200px;
background-size: contain;
background-position: center;
background-repeat: no-repeat;
position: relative;
> .name {
position: absolute;
top: 8px;
left: 9px;
padding: 8px;
background: var(--MI_THEME-panel);
}
> .remove {
position: absolute;
top: 8px;
right: 9px;
padding: 8px;
background: var(--MI_THEME-panel);
}
}
</style>

View file

@ -21,7 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { computed, markRaw, ref } from 'vue'; import { computed, markRaw, ref } from 'vue';
import { notificationTypes } from 'misskey-js'; import { notificationTypes } from '@@/js/const.js';
import MkStreamingNotificationsTimeline from '@/components/MkStreamingNotificationsTimeline.vue'; import MkStreamingNotificationsTimeline from '@/components/MkStreamingNotificationsTimeline.vue';
import MkNotesTimeline from '@/components/MkNotesTimeline.vue'; import MkNotesTimeline from '@/components/MkNotesTimeline.vue';
import * as os from '@/os.js'; import * as os from '@/os.js';

View file

@ -24,8 +24,8 @@ SPDX-License-Identifier: AGPL-3.0-only
import { defineAsyncComponent, inject, onMounted, watch, ref } from 'vue'; import { defineAsyncComponent, inject, onMounted, watch, ref } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import XContainer from '../page-editor.container.vue';
import { genId } from '@/utility/id.js'; import { genId } from '@/utility/id.js';
import XContainer from '../page-editor.container.vue';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { deepClone } from '@/utility/clone.js'; import { deepClone } from '@/utility/clone.js';
@ -35,11 +35,11 @@ import { getPageBlockList } from '@/pages/page-editor/common.js';
const XBlocks = defineAsyncComponent(() => import('../page-editor.blocks.vue')); const XBlocks = defineAsyncComponent(() => import('../page-editor.blocks.vue'));
const props = defineProps<{ const props = defineProps<{
modelValue: Extract<Misskey.entities.PageBlock, { type: 'section'; }>, modelValue: Misskey.entities.PageBlock & { type: 'section'; },
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
(ev: 'update:modelValue', value: Extract<Misskey.entities.PageBlock, { type: 'section'; }>): void; (ev: 'update:modelValue', value: Misskey.entities.PageBlock & { type: 'section' }): void;
(ev: 'remove'): void; (ev: 'remove'): void;
}>(); }>();
@ -59,7 +59,7 @@ async function rename() {
title: i18n.ts._pages.enterSectionTitle, title: i18n.ts._pages.enterSectionTitle,
default: props.modelValue.title, default: props.modelValue.title,
}); });
if (canceled || title == null) return; if (canceled) return;
emit('update:modelValue', { emit('update:modelValue', {
...props.modelValue, ...props.modelValue,
title, title,

View file

@ -135,7 +135,7 @@ const emit = defineEmits<{
const dialog = useTemplateRef('dialog'); const dialog = useTemplateRef('dialog');
const page = ref(0); const page = ref(0);
const token = ref<string | null>(null); const token = ref<string | number | null>(null);
const backupCodes = ref<string[]>(); const backupCodes = ref<string[]>();
function cancel() { function cancel() {
@ -145,7 +145,7 @@ function cancel() {
async function tokenDone() { async function tokenDone() {
if (token.value == null) return; if (token.value == null) return;
const res = await os.apiWithDialog('i/2fa/done', { const res = await os.apiWithDialog('i/2fa/done', {
token: token.value.toString(), // numbertoString token: typeof token.value === 'string' ? token.value : token.value.toString(),
}); });
backupCodes.value = res.backupCodes; backupCodes.value = res.backupCodes;

View file

@ -65,7 +65,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { useTemplateRef, computed } from 'vue'; import { useTemplateRef, computed } from 'vue';
import { notificationTypes } from 'misskey-js'; import { notificationTypes } from '@@/js/const.js';
import XNotificationConfig from './notifications.notification-config.vue'; import XNotificationConfig from './notifications.notification-config.vue';
import type { NotificationConfig } from './notifications.notification-config.vue'; import type { NotificationConfig } from './notifications.notification-config.vue';
import FormLink from '@/components/form/link.vue'; import FormLink from '@/components/form/link.vue';

View file

@ -131,10 +131,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<hr> <hr>
<MkButton @click="forceCloudBackup">Force cloud backup</MkButton>
<hr>
<template v-if="$i.policies.chatAvailability !== 'unavailable'"> <template v-if="$i.policies.chatAvailability !== 'unavailable'">
<MkButton @click="readAllChatMessages">Read all chat messages</MkButton> <MkButton @click="readAllChatMessages">Read all chat messages</MkButton>
@ -171,7 +167,6 @@ import { signout } from '@/signout.js';
import { migrateOldSettings } from '@/pref-migrate.js'; import { migrateOldSettings } from '@/pref-migrate.js';
import { hideAllTips as _hideAllTips, resetAllTips as _resetAllTips } from '@/tips.js'; import { hideAllTips as _hideAllTips, resetAllTips as _resetAllTips } from '@/tips.js';
import { suggestReload } from '@/utility/reload-suggest.js'; import { suggestReload } from '@/utility/reload-suggest.js';
import { cloudBackup } from '@/preferences/utility.js';
const $i = ensureSignin(); const $i = ensureSignin();
@ -229,11 +224,6 @@ function readAllChatMessages() {
os.apiWithDialog('chat/read-all', {}); os.apiWithDialog('chat/read-all', {});
} }
async function forceCloudBackup() {
await cloudBackup();
os.success();
}
const headerActions = computed(() => []); const headerActions = computed(() => []);
const headerTabs = computed(() => []); const headerTabs = computed(() => []);

View file

@ -110,6 +110,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkPreferenceContainer> </MkPreferenceContainer>
</SearchMarker> </SearchMarker>
<!--
<SearchMarker :keywords="['auto', 'load', 'auto', 'more', 'scroll']"> <SearchMarker :keywords="['auto', 'load', 'auto', 'more', 'scroll']">
<MkPreferenceContainer k="enableInfiniteScroll"> <MkPreferenceContainer k="enableInfiniteScroll">
<MkSwitch v-model="enableInfiniteScroll"> <MkSwitch v-model="enableInfiniteScroll">
@ -117,6 +118,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkSwitch> </MkSwitch>
</MkPreferenceContainer> </MkPreferenceContainer>
</SearchMarker> </SearchMarker>
-->
</div> </div>
<SearchMarker :keywords="['emoji', 'style', 'native', 'system', 'fluent', 'twemoji']"> <SearchMarker :keywords="['emoji', 'style', 'native', 'system', 'fluent', 'twemoji']">

View file

@ -17,8 +17,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, ref, computed } from 'vue'; import { onMounted, ref, computed } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import XStatusbar from './statusbar.statusbar.vue';
import { genId } from '@/utility/id.js'; import { genId } from '@/utility/id.js';
import XStatusbar from './statusbar.statusbar.vue';
import MkFolder from '@/components/MkFolder.vue'; import MkFolder from '@/components/MkFolder.vue';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import { misskeyApi } from '@/utility/misskey-api.js'; import { misskeyApi } from '@/utility/misskey-api.js';
@ -39,7 +39,6 @@ onMounted(() => {
async function add() { async function add() {
prefer.commit('statusbars', [...statusbars.value, { prefer.commit('statusbars', [...statusbars.value, {
id: genId(), id: genId(),
name: null,
type: null, type: null,
black: false, black: false,
size: 'medium', size: 'medium',

View file

@ -40,7 +40,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import * as Misskey from 'misskey-js';
import MkInput from '@/components/MkInput.vue'; import MkInput from '@/components/MkInput.vue';
import FormSection from '@/components/form/section.vue'; import FormSection from '@/components/form/section.vue';
import MkSwitch from '@/components/MkSwitch.vue'; import MkSwitch from '@/components/MkSwitch.vue';
@ -62,7 +61,7 @@ const event_reaction = ref(true);
const event_mention = ref(true); const event_mention = ref(true);
async function create(): Promise<void> { async function create(): Promise<void> {
const events = [] as Misskey.entities.UserWebhook['on']; const events: string[] = [];
if (event_follow.value) events.push('follow'); if (event_follow.value) events.push('follow');
if (event_followed.value) events.push('followed'); if (event_followed.value) events.push('followed');
if (event_note.value) events.push('note'); if (event_note.value) events.push('note');

View file

@ -117,6 +117,7 @@ async function renderChart() {
offset: true, offset: true,
stacked: true, stacked: true,
time: { time: {
stepSize: 1,
unit: 'day', unit: 'day',
displayFormats: { displayFormats: {
day: 'M/d', day: 'M/d',
@ -161,9 +162,7 @@ async function renderChart() {
}, },
external: externalTooltipHandler, external: externalTooltipHandler,
}, },
...({ // TS gradient,
gradient,
}),
}, },
}, },
plugins: [chartVLine(vLineColor), chartLegend(legendEl.value)], plugins: [chartVLine(vLineColor), chartLegend(legendEl.value)],

View file

@ -116,6 +116,7 @@ async function renderChart() {
offset: true, offset: true,
stacked: true, stacked: true,
time: { time: {
stepSize: 1,
unit: 'day', unit: 'day',
displayFormats: { displayFormats: {
day: 'M/d', day: 'M/d',
@ -160,9 +161,7 @@ async function renderChart() {
}, },
external: externalTooltipHandler, external: externalTooltipHandler,
}, },
...({ // TS gradient,
gradient,
}),
}, },
}, },
plugins: [chartVLine(vLineColor), chartLegend(legendEl.value)], plugins: [chartVLine(vLineColor), chartLegend(legendEl.value)],

View file

@ -117,6 +117,7 @@ async function renderChart() {
offset: true, offset: true,
stacked: true, stacked: true,
time: { time: {
stepSize: 1,
unit: 'day', unit: 'day',
displayFormats: { displayFormats: {
day: 'M/d', day: 'M/d',
@ -154,6 +155,8 @@ async function renderChart() {
display: true, display: true,
text: 'Unique/Natural PV', text: 'Unique/Natural PV',
padding: { padding: {
left: 0,
right: 0,
top: 0, top: 0,
bottom: 12, bottom: 12,
}, },
@ -169,9 +172,7 @@ async function renderChart() {
}, },
external: externalTooltipHandler, external: externalTooltipHandler,
}, },
...({ // TS gradient,
gradient,
}),
}, },
}, },
plugins: [chartVLine(vLineColor), chartLegend(legendEl.value)], plugins: [chartVLine(vLineColor), chartLegend(legendEl.value)],

View file

@ -209,7 +209,7 @@ const XTimeline = defineAsyncComponent(() => import('./index.timeline.vue'));
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
user: Misskey.entities.UserDetailed; user: Misskey.entities.UserDetailed;
/** Test only; MkNotesTimeline currently causes problems in vitest */ /** Test only; MkNotesTimeline currently causes problems in vitest */
disableNotes?: boolean; disableNotes: boolean;
}>(), { }>(), {
disableNotes: false, disableNotes: false,
}); });

View file

@ -34,7 +34,7 @@ import { instance as meta } from '@/instance.js';
position: fixed; position: fixed;
top: 0; top: 0;
right: 0; right: 0;
width: 100vw; width: 80vw; // 100%shape
height: 100vh; height: 100vh;
} }

View file

@ -205,13 +205,13 @@ type HandlerDef = {
handler: (note: Misskey.entities.Note) => void; handler: (note: Misskey.entities.Note) => void;
}; };
note_view_interruptor: { note_view_interruptor: {
handler: (note: Misskey.entities.Note) => Misskey.entities.Note | null; handler: (note: Misskey.entities.Note) => unknown;
}; };
note_post_interruptor: { note_post_interruptor: {
handler: (note: FIXME) => unknown; handler: (note: FIXME) => unknown;
}; };
page_view_interruptor: { page_view_interruptor: {
handler: (page: Misskey.entities.Page) => Misskey.entities.Page; handler: (page: Misskey.entities.Page) => unknown;
}; };
}; };

Some files were not shown because too many files have changed in this diff Show more