mirror of
https://github.com/misskey-dev/misskey
synced 2025-09-05 10:52:49 +02:00
Compare commits
40 commits
2025.8.0-b
...
develop
Author | SHA1 | Date | |
---|---|---|---|
|
9b565728e7 | ||
|
a92fd8856a | ||
|
047773341d | ||
|
842670e100 | ||
|
ffc481a994 | ||
|
2ccf4f94cb | ||
|
3566bc207f | ||
|
4a0e968662 | ||
|
b1479ab1d8 | ||
|
18a9ccf7af | ||
|
959e72b2b3 | ||
|
a3d78b2f08 | ||
|
3c998e1f48 | ||
|
782c9f9852 | ||
|
d27c740ab0 | ||
|
08ecf7ca79 | ||
|
bdec4bf87a | ||
|
7000095b44 | ||
|
18e42cc83d | ||
|
11204eeb43 | ||
|
c95092903a | ||
|
21b2b9e5f8 | ||
|
665ec2c43c | ||
|
34bd840525 | ||
|
3d1cbcf094 | ||
|
5f5d88036f | ||
|
24739cd040 | ||
|
b491432daa | ||
|
ebe029458e | ||
|
d127d82c5b | ||
|
aabda5a956 | ||
|
bd5b38c9d9 | ||
|
647e03bf34 | ||
|
d16db7f311 | ||
|
ec4731dee4 | ||
|
65a4d77a7f | ||
|
328301ffc2 | ||
|
def148d7a6 | ||
|
aa85d701b9 | ||
|
f0833cffe9 |
96 changed files with 881 additions and 1064 deletions
26
CHANGELOG.md
26
CHANGELOG.md
|
@ -1,3 +1,17 @@
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
### General
|
||||||
|
-
|
||||||
|
|
||||||
|
### Client
|
||||||
|
- Fix: RSSティッカーウィジェットが正しく動作しない問題を修正
|
||||||
|
- Fix: エラー画像が横に引き伸ばされてしまう問題に対応
|
||||||
|
- Enhance: /flushページでサイトキャッシュをクリアできるようになりました
|
||||||
|
|
||||||
|
### Server
|
||||||
|
-
|
||||||
|
|
||||||
|
|
||||||
## 2025.8.0
|
## 2025.8.0
|
||||||
|
|
||||||
### Note
|
### Note
|
||||||
|
@ -6,14 +20,12 @@
|
||||||
### 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以前にクリップされたリモートのノートは検知しないため削除対象となります。
|
|
||||||
- サーバーの初期設定が完了するまでは連合がオンにならないようになりました
|
- サーバーの初期設定が完了するまでは連合がオンにならないようになりました
|
||||||
- 日本語における公開範囲名称の「ダイレクト」が「指名」に改称されました
|
- 日本語における公開範囲名称の「ダイレクト」が「指名」に改称されました
|
||||||
- 実際の動作に即した名称になり、馴染みのない人でも理解しやすくなりました
|
- 実際の動作に即した名称になり、馴染みのない人でも理解しやすくなりました
|
||||||
|
@ -46,9 +58,11 @@
|
||||||
- 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)
|
||||||
|
@ -60,6 +74,7 @@
|
||||||
- Fix: ユーザーの前後ノートを閲覧する機能が動作しない問題を修正
|
- Fix: ユーザーの前後ノートを閲覧する機能が動作しない問題を修正
|
||||||
- Fix: 照会ダイアログでap/showでローカルユーザーを解決した際@username@nullに飛ばされる問題を修正
|
- Fix: 照会ダイアログでap/showでローカルユーザーを解決した際@username@nullに飛ばされる問題を修正
|
||||||
- Fix: アイコンのデコレーションを付ける際にデコレーションが表示されなくなる問題を修正
|
- Fix: アイコンのデコレーションを付ける際にデコレーションが表示されなくなる問題を修正
|
||||||
|
- Fix: タッチ操作時にマウスホバー時のユーザープレビューが開くことがある問題を修正
|
||||||
- Fix: 管理中アカウント一覧で正しい表示が行われない問題を修正
|
- Fix: 管理中アカウント一覧で正しい表示が行われない問題を修正
|
||||||
- Fix: lookupページでリモートURLを指定した際に正しく動作しない問題を修正
|
- Fix: lookupページでリモートURLを指定した際に正しく動作しない問題を修正
|
||||||
|
|
||||||
|
@ -76,6 +91,7 @@
|
||||||
- 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
|
||||||
|
|
|
@ -3120,7 +3120,6 @@ _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."
|
||||||
|
|
|
@ -1668,6 +1668,7 @@ _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"
|
||||||
|
|
|
@ -3120,7 +3120,6 @@ _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
8
locales/index.d.ts
vendored
|
@ -6531,7 +6531,7 @@ export interface Locale extends ILocale {
|
||||||
*/
|
*/
|
||||||
"remoteNotesCleaning": string;
|
"remoteNotesCleaning": string;
|
||||||
/**
|
/**
|
||||||
* 有効にすると、参照されていない古いリモートの投稿を定期的にクリーンアップしてデータベースの肥大化を抑制します。
|
* 有効にすると、一定期間経過したリモートの投稿を定期的にクリーンアップしてデータベースの肥大化を抑制します。
|
||||||
*/
|
*/
|
||||||
"remoteNotesCleaning_description": string;
|
"remoteNotesCleaning_description": string;
|
||||||
/**
|
/**
|
||||||
|
@ -12036,13 +12036,9 @@ export interface Locale extends ILocale {
|
||||||
*/
|
*/
|
||||||
"remoteContentsCleaning": string;
|
"remoteContentsCleaning": string;
|
||||||
/**
|
/**
|
||||||
* 連合を行うと、継続して多くのコンテンツを受信します。自動クリーニングを有効にすると、参照されていない古くなったリモートコンテンツを自動でサーバーから削除し、ストレージを節約できます。
|
* 連合を行うと、継続して多くのコンテンツを受信します。自動クリーニングを有効にすると、一定期間経過したリモートコンテンツを自動でサーバーから削除し、ストレージを節約できます。
|
||||||
*/
|
*/
|
||||||
"remoteContentsCleaning_description": string;
|
"remoteContentsCleaning_description": string;
|
||||||
/**
|
|
||||||
* ローカル内リモートコンテンツへのハイパーリンクはリンク切れとなります。
|
|
||||||
*/
|
|
||||||
"remoteContentsCleaning_description2": string;
|
|
||||||
/**
|
/**
|
||||||
* 管理者情報
|
* 管理者情報
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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: "File da Drive eliminato"
|
deleteFile: "Elimina un file dal Drive"
|
||||||
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: "Renota"
|
renote: "Rinota"
|
||||||
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: "Tutto"
|
all: "Tutte"
|
||||||
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 chiuso."
|
pollEnded: "Sondaggio terminato"
|
||||||
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: "Creare un token di accesso"
|
createToken: "Aggiunto 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: "Ruolo creato"
|
createRole: "Crea un Ruolo"
|
||||||
deleteRole: "Ruolo eliminato"
|
deleteRole: "Elimina un Ruolo"
|
||||||
updateRole: "Ruolo aggiornato"
|
updateRole: "Modifica un ruolo"
|
||||||
assignRole: "Ruolo assegnato"
|
assignRole: "Assegna un Ruolo"
|
||||||
unassignRole: "Ruolo disassegnato"
|
unassignRole: "Toglie un Ruolo al Profilo"
|
||||||
suspend: "Sospensione"
|
suspend: "Sospende"
|
||||||
unsuspend: "Sospensione rimossa"
|
unsuspend: "Solleva la sospensione"
|
||||||
addCustomEmoji: "Emoji personalizzata aggiunta"
|
addCustomEmoji: "Aggiunge Emoji personalizzata"
|
||||||
updateCustomEmoji: "Emoji personalizzata aggiornata"
|
updateCustomEmoji: "Modifica Emoji personalizzata"
|
||||||
deleteCustomEmoji: "Emoji personalizzata eliminata"
|
deleteCustomEmoji: "Elimina Emoji personalizzata"
|
||||||
updateServerSettings: "Impostazioni del server aggiornate"
|
updateServerSettings: "Modifica le impostazioni del server"
|
||||||
updateUserNote: "Promemoria di moderazione aggiornato"
|
updateUserNote: "Modifica un promemoria di moderazione"
|
||||||
deleteDriveFile: "File da Drive eliminato"
|
deleteDriveFile: "Elimina un file dal Drive"
|
||||||
deleteNote: "Nota eliminata"
|
deleteNote: "Elimina una Nota"
|
||||||
createGlobalAnnouncement: "Annuncio globale creato"
|
createGlobalAnnouncement: "Crea un annuncio globale"
|
||||||
createUserAnnouncement: "Annuncio ai profili iscritti creato"
|
createUserAnnouncement: "Crea un annuncio ai profili già iscritti"
|
||||||
updateGlobalAnnouncement: "Annuncio globale aggiornato"
|
updateGlobalAnnouncement: "Modifica un annuncio globale"
|
||||||
updateUserAnnouncement: "Annuncio ai profili iscritti aggiornato"
|
updateUserAnnouncement: "Modifica un annuncio ai profili già iscritti"
|
||||||
deleteGlobalAnnouncement: "Annuncio globale eliminato"
|
deleteGlobalAnnouncement: "Elimina un annuncio globale"
|
||||||
deleteUserAnnouncement: "Annuncio ai profili iscritti eliminato"
|
deleteUserAnnouncement: "Elimina un annuncio ai profili già iscritti"
|
||||||
resetPassword: "Password azzerata"
|
resetPassword: "Azzera la password"
|
||||||
suspendRemoteInstance: "Istanza remota sospesa"
|
suspendRemoteInstance: "Sospende una istanza remota"
|
||||||
unsuspendRemoteInstance: "Istanza remota riattivata"
|
unsuspendRemoteInstance: "Riattiva una istanza remota"
|
||||||
updateRemoteInstanceNote: "Aggiornamento del promemoria di moderazione per il server remoto"
|
updateRemoteInstanceNote: "Modifica il promemoria di moderazione per il server remoto"
|
||||||
markSensitiveDriveFile: "File nel Drive segnato come esplicito"
|
markSensitiveDriveFile: "Aggiunge NSFW a un file nel Drive"
|
||||||
unmarkSensitiveDriveFile: "File nel Drive segnato come non esplicito"
|
unmarkSensitiveDriveFile: "Toglie NSFW da un file nel Drive"
|
||||||
resolveAbuseReport: "Segnalazione risolta"
|
resolveAbuseReport: "Risolve una segnalazione"
|
||||||
forwardAbuseReport: "Segnalazione inoltrata"
|
forwardAbuseReport: "Inoltra una segnalazione"
|
||||||
updateAbuseReportNote: "Ha aggiornato la segnalazione"
|
updateAbuseReportNote: "Modifica una segnalazione"
|
||||||
createInvitation: "Genera codice di invito"
|
createInvitation: "Genera un codice di invito"
|
||||||
createAd: "Banner creato"
|
createAd: "Aggiunge un Banner"
|
||||||
deleteAd: "Banner eliminato"
|
deleteAd: "Elimina un Banner"
|
||||||
updateAd: "Banner aggiornato"
|
updateAd: "Modifica un Banner"
|
||||||
createAvatarDecoration: "Creazione decorazione della foto profilo"
|
createAvatarDecoration: "Crea una decorazione della foto profilo"
|
||||||
updateAvatarDecoration: "Aggiornamento decorazione foto profilo"
|
updateAvatarDecoration: "Modifica una decorazione della foto profilo"
|
||||||
deleteAvatarDecoration: "Eliminazione decorazione della foto profilo"
|
deleteAvatarDecoration: "Elimina una decorazione della foto profilo"
|
||||||
unsetUserAvatar: "Rimossa foto profilo"
|
unsetUserAvatar: "Toglie una foto profilo"
|
||||||
unsetUserBanner: "Rimossa intestazione profilo"
|
unsetUserBanner: "Toglie una immagine di intestazione profilo"
|
||||||
createSystemWebhook: "Crea un SystemWebhook"
|
createSystemWebhook: "Aggiunge un System Webhook"
|
||||||
updateSystemWebhook: "Modifica SystemWebhook"
|
updateSystemWebhook: "Modifica un System Webhook"
|
||||||
deleteSystemWebhook: "Elimina SystemWebhook"
|
deleteSystemWebhook: "Elimina un System Webhook"
|
||||||
createAbuseReportNotificationRecipient: "Crea destinatario per le notifiche di segnalazioni"
|
createAbuseReportNotificationRecipient: "Crea destinatario per le notifiche di segnalazioni"
|
||||||
updateAbuseReportNotificationRecipient: "Aggiorna destinatario notifiche di segnalazioni"
|
updateAbuseReportNotificationRecipient: "Modifica un destinatario per le notifiche di segnalazioni"
|
||||||
deleteAbuseReportNotificationRecipient: "Elimina destinatario notifiche di segnalazioni"
|
deleteAbuseReportNotificationRecipient: "Elimina un destinatario per le notifiche di segnalazioni"
|
||||||
deleteAccount: "Quando viene eliminato un profilo"
|
deleteAccount: "Elimina un profilo"
|
||||||
deletePage: "Pagina eliminata"
|
deletePage: "Elimina una Pagina"
|
||||||
deleteFlash: "Play eliminato"
|
deleteFlash: "Elimina un Play"
|
||||||
deleteGalleryPost: "Eliminazione pubblicazione nella Galleria"
|
deleteGalleryPost: "Elimina pubblicazione nella Galleria"
|
||||||
deleteChatRoom: "Elimina chat"
|
deleteChatRoom: "Elimina una Chat"
|
||||||
updateProxyAccountDescription: "Aggiornata la descrizione del profilo proxy"
|
updateProxyAccountDescription: "Aggiorna la descrizione del profilo proxy"
|
||||||
_fileViewer:
|
_fileViewer:
|
||||||
title: "Dettagli del file"
|
title: "Dettagli del file"
|
||||||
type: "Tipo di file"
|
type: "Tipo di file"
|
||||||
|
@ -3120,7 +3120,6 @@ _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."
|
||||||
|
|
|
@ -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,8 +3217,7 @@ _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: "オープンサーバー、または連合がオンの場合は必ず入力が必要です。"
|
||||||
|
|
|
@ -3120,7 +3120,6 @@ _serverSetupWizard:
|
||||||
youCanConfigureMoreFederationSettingsLater: "나중에 연합 가능한 서버의 지정 등 고급 설정을 할 수 있습니다."
|
youCanConfigureMoreFederationSettingsLater: "나중에 연합 가능한 서버의 지정 등 고급 설정을 할 수 있습니다."
|
||||||
remoteContentsCleaning: "리모트 콘텐츠 자동 정리"
|
remoteContentsCleaning: "리모트 콘텐츠 자동 정리"
|
||||||
remoteContentsCleaning_description: "연합 중인 서버가 있는 경우, 리모트 서버에서 대단히 많은 콘텐츠를 받아오게 됩니다. 자동 정리 기능을 활성화하면, 오래되고 서버에서 더 이상 조회되지 않는 콘텐츠를 자동으로 서버에서 삭제하여, 스토리지를 절약할 수 있습니다."
|
remoteContentsCleaning_description: "연합 중인 서버가 있는 경우, 리모트 서버에서 대단히 많은 콘텐츠를 받아오게 됩니다. 자동 정리 기능을 활성화하면, 오래되고 서버에서 더 이상 조회되지 않는 콘텐츠를 자동으로 서버에서 삭제하여, 스토리지를 절약할 수 있습니다."
|
||||||
remoteContentsCleaning_description2: "로컬 내 원격 콘텐츠로의 하이퍼링크는 깨진 링크로 됩니다."
|
|
||||||
adminInfo: "관리자 정보"
|
adminInfo: "관리자 정보"
|
||||||
adminInfo_description: "문의 접수를 위해 사용되는 관리자 정보를 설정합니다."
|
adminInfo_description: "문의 접수를 위해 사용되는 관리자 정보를 설정합니다."
|
||||||
adminInfo_mustBeFilled: "오픈 서버 혹은 연합이 켜져 있는 경우 반드시 입력해야 합니다."
|
adminInfo_mustBeFilled: "오픈 서버 혹은 연합이 켜져 있는 경우 반드시 입력해야 합니다."
|
||||||
|
|
|
@ -3120,7 +3120,6 @@ _serverSetupWizard:
|
||||||
youCanConfigureMoreFederationSettingsLater: "可在之后进行如哪些服务器可以进行联合等高级设置。"
|
youCanConfigureMoreFederationSettingsLater: "可在之后进行如哪些服务器可以进行联合等高级设置。"
|
||||||
remoteContentsCleaning: "自动清理传入内容"
|
remoteContentsCleaning: "自动清理传入内容"
|
||||||
remoteContentsCleaning_description: "加入联合后,服务器将持续接收大量内容。打开自动清理后,将自动删除无法找到的旧内容,可节省存储空间。"
|
remoteContentsCleaning_description: "加入联合后,服务器将持续接收大量内容。打开自动清理后,将自动删除无法找到的旧内容,可节省存储空间。"
|
||||||
remoteContentsCleaning_description2: "如超链接之类的某些引用方法无法被系统检测到。"
|
|
||||||
adminInfo: "管理员信息"
|
adminInfo: "管理员信息"
|
||||||
adminInfo_description: "设置用于接受询问的管理员信息。"
|
adminInfo_description: "设置用于接受询问的管理员信息。"
|
||||||
adminInfo_mustBeFilled: "开放服务器或开启了联合的情况下必须输入。"
|
adminInfo_mustBeFilled: "开放服务器或开启了联合的情况下必须输入。"
|
||||||
|
|
|
@ -3120,7 +3120,6 @@ _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"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"version": "2025.8.0-beta.5",
|
"version": "2025.8.0",
|
||||||
"codename": "nasubi",
|
"codename": "nasubi",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -67,6 +67,7 @@
|
||||||
},
|
},
|
||||||
"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.17.2",
|
||||||
"@typescript-eslint/eslint-plugin": "8.40.0",
|
"@typescript-eslint/eslint-plugin": "8.40.0",
|
||||||
"@typescript-eslint/parser": "8.40.0",
|
"@typescript-eslint/parser": "8.40.0",
|
||||||
|
|
|
@ -31,6 +31,7 @@ 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;
|
||||||
|
|
|
@ -10,6 +10,7 @@ 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;
|
||||||
|
|
|
@ -91,6 +91,7 @@ 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)');
|
||||||
}));
|
}));
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
|
@ -242,6 +242,7 @@ 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)');
|
||||||
}));
|
}));
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
|
@ -223,6 +223,7 @@ 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)');
|
||||||
}));
|
}));
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
|
@ -201,6 +201,8 @@ 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: {
|
||||||
|
@ -239,7 +241,6 @@ 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');
|
||||||
|
@ -887,6 +888,22 @@ 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');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -6,41 +6,45 @@ 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>`;
|
||||||
|
|
||||||
message('Start flushing.');
|
if (!document.cookie) {
|
||||||
|
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.');
|
||||||
|
|
||||||
(async function() {
|
const idbPromises = ['MisskeyClient', 'keyval-store'].map((name, i, arr) => new Promise((res, rej) => {
|
||||||
try {
|
const delidb = indexedDB.deleteDatabase(name);
|
||||||
localStorage.clear();
|
delidb.onsuccess = () => res(message(`indexedDB "${name}" cleared. (${i + 1}/${arr.length})`));
|
||||||
message('localStorage cleared.');
|
delidb.onerror = e => rej(e)
|
||||||
|
}));
|
||||||
|
|
||||||
const idbPromises = ['MisskeyClient', 'keyval-store'].map((name, i, arr) => new Promise((res, rej) => {
|
await Promise.all(idbPromises);
|
||||||
const delidb = indexedDB.deleteDatabase(name);
|
|
||||||
delidb.onsuccess = () => res(message(`indexedDB "${name}" cleared. (${i + 1}/${arr.length})`));
|
|
||||||
delidb.onerror = e => rej(e)
|
|
||||||
}));
|
|
||||||
|
|
||||||
await Promise.all(idbPromises);
|
if (navigator.serviceWorker.controller) {
|
||||||
|
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) });
|
||||||
|
}
|
||||||
|
|
||||||
if (navigator.serviceWorker.controller) {
|
message(successText);
|
||||||
navigator.serviceWorker.controller.postMessage('clear');
|
} catch (e) {
|
||||||
await navigator.serviceWorker.getRegistrations()
|
message(`\n${e}\n\nFlush Failed. <a href="/flush">Please retry.</a>\n失敗しました。<a href="/flush">もう一度試してみてください。</a>`);
|
||||||
.then(registrations => {
|
message(`\nIf you retry more than 3 times, try manually clearing the browser cache or contact to instance admin.\n3回以上試しても失敗する場合、ブラウザのキャッシュを手動で消去し、それでもだめならインスタンス管理者に連絡してみてください。\n`)
|
||||||
return Promise.all(registrations.map(registration => registration.unregister()));
|
|
||||||
})
|
console.error(e);
|
||||||
.catch(e => { throw new Error(e) });
|
setTimeout(() => {
|
||||||
|
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>`)
|
||||||
|
|
|
@ -54,68 +54,6 @@ 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='],
|
||||||
|
|
|
@ -127,7 +127,7 @@ export function galleryPost(isSensitive = false) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function file(isSensitive = false) {
|
export function file(isSensitive = false): entities.DriveFile {
|
||||||
return {
|
return {
|
||||||
id: 'somefileid',
|
id: 'somefileid',
|
||||||
createdAt: '2016-12-28T22:49:51.000Z',
|
createdAt: '2016-12-28T22:49:51.000Z',
|
||||||
|
@ -207,6 +207,7 @@ 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,
|
||||||
|
@ -311,6 +312,8 @@ 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',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -378,6 +381,7 @@ 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',
|
||||||
|
|
|
@ -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, utils.valToJs(param) as object, actualToken).then(res => {
|
return misskeyApi(ep.value as keyof Misskey.Endpoints, 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));
|
||||||
|
|
|
@ -167,9 +167,13 @@ 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: accounts.find(a => a.id === user.id)!.token,
|
token: account.token,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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" :class="$style.emojiName" v-html="sanitizeHtml(emoji.name.replace(q, `<b>${q}</b>`))"></span>
|
<span v-if="q != null && typeof q === 'string'" :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="complete(type, q.params.toSpliced(-1, 1, param).join(','))" @keydown="onKeydown">
|
<li v-for="param in mfmParams" tabindex="-1" :class="$style.item" @click="completeMfmParam(param)" @keydown="onKeydown">
|
||||||
<span>{{ param }}</span>
|
<span>{{ param }}</span>
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
@ -194,6 +194,11 @@ 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');
|
||||||
|
|
|
@ -25,12 +25,12 @@ defineProps<{
|
||||||
showing: boolean;
|
showing: boolean;
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
title?: string;
|
title?: string | null;
|
||||||
series?: {
|
series?: {
|
||||||
backgroundColor: string;
|
backgroundColor: string;
|
||||||
borderColor: string;
|
borderColor: string;
|
||||||
text: string;
|
text: string;
|
||||||
}[];
|
}[] | null;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
|
|
@ -38,7 +38,7 @@ export const Default = {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
args: {
|
args: {
|
||||||
file: file(),
|
imageFile: file(),
|
||||||
aspectRatio: NaN,
|
aspectRatio: NaN,
|
||||||
},
|
},
|
||||||
parameters: {
|
parameters: {
|
||||||
|
|
|
@ -699,7 +699,7 @@ useGlobalEvent('driveFoldersDeleted', (folders) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let connection: Misskey.ChannelConnection<Misskey.Channels['drive']> | null = null;
|
let connection: Misskey.IChannelConnection<Misskey.Channels['drive']> | null = null;
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (store.s.realtimeMode) {
|
if (store.s.realtimeMode) {
|
||||||
|
|
|
@ -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 ?? undefined : 500);
|
const maxHeight = ref(props.params?.maxHeight !== 0 ? props.params?.maxHeight ?? null : 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);
|
||||||
|
|
|
@ -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="option.value" :value="option.value">{{ option.label }}</option>
|
<option v-for="option in v.enum" :key="getEnumKey(option)" :value="getEnumValue(option)">{{ getEnumLabel(option) }}</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="option.value" :value="option.value">{{ option.label }}</option>
|
<option v-for="option in v.options" :key="getRadioKey(option)" :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 { Form } from '@/utility/form.js';
|
import type { EnumItem, Form, RadioFormItem } 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,7 +99,11 @@ const dialog = useTemplateRef('dialog');
|
||||||
const values = reactive({});
|
const values = reactive({});
|
||||||
|
|
||||||
for (const item in props.form) {
|
for (const item in props.form) {
|
||||||
values[item] = props.form[item].default ?? null;
|
if ('default' in props.form[item]) {
|
||||||
|
values[item] = props.form[item].default ?? null;
|
||||||
|
} else {
|
||||||
|
values[item] = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function ok() {
|
function ok() {
|
||||||
|
@ -115,4 +119,20 @@ 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>
|
||||||
|
|
|
@ -8,7 +8,8 @@ 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>
|
||||||
|
@ -53,12 +54,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;
|
||||||
|
|
|
@ -43,7 +43,15 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts">
|
||||||
|
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';
|
||||||
|
@ -55,8 +63,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: string | number | null;
|
modelValue: ModelValueType<T> | null;
|
||||||
type?: InputHTMLAttributes['type'];
|
type?: T;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
@ -83,11 +91,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: string | number): void;
|
(ev: 'update:modelValue', value: ModelValueType<T>): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { modelValue, type, autofocus } = toRefs(props);
|
const { modelValue } = toRefs(props);
|
||||||
const v = ref(modelValue.value);
|
const v = ref<ModelValueType<T> | null>(modelValue.value);
|
||||||
const id = genId();
|
const id = genId();
|
||||||
const focused = ref(false);
|
const focused = ref(false);
|
||||||
const changed = ref(false);
|
const changed = ref(false);
|
||||||
|
@ -120,8 +128,8 @@ const onKeydown = (ev: KeyboardEvent) => {
|
||||||
|
|
||||||
const updated = () => {
|
const updated = () => {
|
||||||
changed.value = false;
|
changed.value = false;
|
||||||
if (type.value === 'number') {
|
if (props.type === 'number') {
|
||||||
emit('update:modelValue', typeof v.value === 'number' ? v.value : parseFloat(v.value ?? '0'));
|
emit('update:modelValue', typeof v.value === 'number' ? v.value as ModelValueType<T> : parseFloat(v.value ?? '0') as ModelValueType<T>);
|
||||||
} else {
|
} else {
|
||||||
emit('update:modelValue', v.value ?? '');
|
emit('update:modelValue', v.value ?? '');
|
||||||
}
|
}
|
||||||
|
@ -167,7 +175,7 @@ useInterval(() => {
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
if (autofocus.value) {
|
if (props.autofocus) {
|
||||||
focus();
|
focus();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -89,6 +89,7 @@ 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';
|
||||||
|
@ -107,7 +108,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('active-users');
|
const chartSrc = ref<ChartSrc>('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');
|
||||||
|
|
|
@ -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" :class="$style.replyTo"/>
|
<MkNoteSub v-if="appearNote.replyId && !renoteCollapsed" :note="appearNote?.reply ?? null" :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" :class="$style.quoteNote"/></div>
|
<div v-if="appearNote.renoteId" :class="$style.quote"><MkNoteSimple :note="appearNote?.renote ?? null" :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>
|
||||||
|
|
|
@ -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" :user="note.user" link preview/>
|
<MkAvatar :class="[$style.avatar, prefer.s.useStickyIcons ? $style.useSticky : null]" :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,6 +31,7 @@ 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;
|
||||||
|
@ -54,9 +55,12 @@ const showContent = ref(false);
|
||||||
width: 34px;
|
width: 34px;
|
||||||
height: 34px;
|
height: 34px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
position: sticky !important;
|
|
||||||
top: calc(16px + var(--MI-stickyTop, 0px));
|
&.useSticky {
|
||||||
left: 0;
|
position: sticky !important;
|
||||||
|
top: calc(16px + var(--MI-stickyTop, 0px));
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
|
|
|
@ -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 '@@/js/const.js';
|
import { notificationTypes } from 'misskey-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';
|
||||||
|
|
|
@ -31,7 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkButton>
|
</MkButton>
|
||||||
<MkLoading v-else/>
|
<MkLoading v-else/>
|
||||||
</div>
|
</div>
|
||||||
<slot :items="unref(paginator.items)" :fetching="paginator.fetching.value || paginator.fetchingOlder.value"></slot>
|
<slot :items="getValue(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,6 +90,10 @@ 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();
|
||||||
|
@ -134,7 +138,7 @@ function downButtonClick() {
|
||||||
|
|
||||||
defineSlots<{
|
defineSlots<{
|
||||||
empty: () => void;
|
empty: () => void;
|
||||||
default: (props: { items: UnwrapRef<T['items']> }) => void;
|
default: (props: { items: UnwrapRef<T['items']>, fetching: boolean }) => void;
|
||||||
}>();
|
}>();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -78,7 +78,7 @@ function subscribe() {
|
||||||
// SEE: https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe#Parameters
|
// SEE: https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe#Parameters
|
||||||
return promiseDialog(registration.value.pushManager.subscribe({
|
return promiseDialog(registration.value.pushManager.subscribe({
|
||||||
userVisibleOnly: true,
|
userVisibleOnly: true,
|
||||||
applicationServerKey: urlBase64ToUint8Array(instance.swPublickey),
|
applicationServerKey: urlBase64ToBase64(instance.swPublickey),
|
||||||
})
|
})
|
||||||
.then(async subscription => {
|
.then(async subscription => {
|
||||||
pushSubscription.value = subscription;
|
pushSubscription.value = subscription;
|
||||||
|
@ -131,22 +131,16 @@ function encode(buffer: ArrayBuffer | null) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert the URL safe base64 string to a Uint8Array
|
* Convert the URL safe base64 string to a base64 string
|
||||||
* @param base64String base64 string
|
* @param base64String base64 string
|
||||||
*/
|
*/
|
||||||
function urlBase64ToUint8Array(base64String: string): Uint8Array {
|
function urlBase64ToBase64(base64String: string): string {
|
||||||
const padding = '='.repeat((4 - base64String.length % 4) % 4);
|
const padding = '='.repeat((4 - base64String.length % 4) % 4);
|
||||||
const base64 = (base64String + padding)
|
const base64 = (base64String + padding)
|
||||||
.replace(/-/g, '+')
|
.replace(/-/g, '+')
|
||||||
.replace(/_/g, '/');
|
.replace(/_/g, '/');
|
||||||
|
|
||||||
const rawData = window.atob(base64);
|
return base64;
|
||||||
const outputArray = new Uint8Array(rawData.length);
|
|
||||||
|
|
||||||
for (let i = 0; i < rawData.length; ++i) {
|
|
||||||
outputArray[i] = rawData.charCodeAt(i);
|
|
||||||
}
|
|
||||||
return outputArray;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (navigator.serviceWorker == null) {
|
if (navigator.serviceWorker == null) {
|
||||||
|
|
|
@ -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 }} ({{ i18n.ts._serverSetupWizard.remoteContentsCleaning_description2 }})</template>
|
<template #caption>{{ i18n.ts._serverSetupWizard.remoteContentsCleaning_description }}</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,7 +191,6 @@ 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';
|
||||||
|
@ -240,12 +239,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<Record<typeof ROLE_POLICIES[number], any>>>(() => {
|
const defaultPolicies = computed<Partial<Misskey.entities.RolePolicies>>(() => {
|
||||||
let driveCapacityMb;
|
let driveCapacityMb: Misskey.entities.RolePolicies['driveCapacityMb'] | undefined;
|
||||||
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') {
|
||||||
|
@ -254,7 +253,7 @@ const defaultPolicies = computed<Partial<Record<typeof ROLE_POLICIES[number], an
|
||||||
driveCapacityMb = 100;
|
driveCapacityMb = 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
let rateLimitFactor;
|
let rateLimitFactor: Misskey.entities.RolePolicies['rateLimitFactor'] | undefined;
|
||||||
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') {
|
||||||
|
@ -269,7 +268,7 @@ const defaultPolicies = computed<Partial<Record<typeof ROLE_POLICIES[number], an
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let userListLimit;
|
let userListLimit: Misskey.entities.RolePolicies['userListLimit'] | undefined;
|
||||||
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') {
|
||||||
|
@ -278,7 +277,7 @@ const defaultPolicies = computed<Partial<Record<typeof ROLE_POLICIES[number], an
|
||||||
userListLimit = 3;
|
userListLimit = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
let antennaLimit;
|
let antennaLimit: Misskey.entities.RolePolicies['antennaLimit'] | undefined;
|
||||||
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') {
|
||||||
|
@ -287,7 +286,7 @@ const defaultPolicies = computed<Partial<Record<typeof ROLE_POLICIES[number], an
|
||||||
antennaLimit = 0;
|
antennaLimit = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let webhookLimit;
|
let webhookLimit: Misskey.entities.RolePolicies['webhookLimit'] | undefined;
|
||||||
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') {
|
||||||
|
@ -296,35 +295,35 @@ const defaultPolicies = computed<Partial<Record<typeof ROLE_POLICIES[number], an
|
||||||
webhookLimit = 0;
|
webhookLimit = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let canImportFollowing;
|
let canImportFollowing: Misskey.entities.RolePolicies['canImportFollowing'];
|
||||||
if (q_use.value === 'single') {
|
if (q_use.value === 'single') {
|
||||||
canImportFollowing = true;
|
canImportFollowing = true;
|
||||||
} else {
|
} else {
|
||||||
canImportFollowing = false;
|
canImportFollowing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let canImportMuting;
|
let canImportMuting: Misskey.entities.RolePolicies['canImportMuting'];
|
||||||
if (q_use.value === 'single') {
|
if (q_use.value === 'single') {
|
||||||
canImportMuting = true;
|
canImportMuting = true;
|
||||||
} else {
|
} else {
|
||||||
canImportMuting = false;
|
canImportMuting = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let canImportBlocking;
|
let canImportBlocking: Misskey.entities.RolePolicies['canImportBlocking'];
|
||||||
if (q_use.value === 'single') {
|
if (q_use.value === 'single') {
|
||||||
canImportBlocking = true;
|
canImportBlocking = true;
|
||||||
} else {
|
} else {
|
||||||
canImportBlocking = false;
|
canImportBlocking = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let canImportUserLists;
|
let canImportUserLists: Misskey.entities.RolePolicies['canImportUserLists'];
|
||||||
if (q_use.value === 'single') {
|
if (q_use.value === 'single') {
|
||||||
canImportUserLists = true;
|
canImportUserLists = true;
|
||||||
} else {
|
} else {
|
||||||
canImportUserLists = false;
|
canImportUserLists = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let canImportAntennas;
|
let canImportAntennas: Misskey.entities.RolePolicies['canImportAntennas'];
|
||||||
if (q_use.value === 'single') {
|
if (q_use.value === 'single') {
|
||||||
canImportAntennas = true;
|
canImportAntennas = true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -355,6 +354,7 @@ 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(() => {
|
||||||
|
|
|
@ -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"/>
|
<MkCaptcha v-if="instance.enableTestcaptcha" ref="testcaptcha" v-model="testcaptchaResponse" :class="$style.captcha" provider="testcaptcha" :sitekey="null"/>
|
||||||
<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"/>
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -84,7 +84,6 @@ 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;
|
||||||
});
|
});
|
||||||
|
|
|
@ -41,8 +41,7 @@ const props = defineProps<{
|
||||||
.img {
|
.img {
|
||||||
vertical-align: bottom;
|
vertical-align: bottom;
|
||||||
height: 128px;
|
height: 128px;
|
||||||
aspect-ratio: 1;
|
margin: auto auto 16px;
|
||||||
margin-bottom: 16px;
|
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
@ -1,70 +0,0 @@
|
||||||
<!--
|
<!--
|
||||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
@ -12,8 +13,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<div :class="$style.error">
|
<div :class="$style.error">
|
||||||
<div><i class="ti ti-alert-triangle"></i> {{ i18n.ts.somethingHappened }}</div>
|
<slot name="error" :error="error">
|
||||||
<MkButton inline style="margin-top: 16px;" @click="retry"><i class="ti ti-reload"></i> {{ i18n.ts.retry }}</MkButton>
|
<div><i class="ti ti-alert-triangle"></i> {{ i18n.ts.somethingHappened }}</div>
|
||||||
|
<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>
|
||||||
|
@ -27,15 +31,17 @@ 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;
|
||||||
|
@ -44,10 +50,12 @@ 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(() => {
|
promise.catch((_error) => {
|
||||||
pending.value = false;
|
pending.value = false;
|
||||||
rejected.value = true;
|
rejected.value = true;
|
||||||
|
error.value = _error;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -54,14 +54,10 @@ 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;
|
||||||
|
|
||||||
function _() {
|
currentPageComponent.value = resolved.route.component;
|
||||||
currentPageComponent.value = resolved.route.component;
|
currentPageProps.value = resolved.props;
|
||||||
currentPageProps.value = resolved.props;
|
key.value = router.getCurrentFullPath();
|
||||||
key.value = router.getCurrentFullPath();
|
currentRoutePath = resolved.route.path;
|
||||||
currentRoutePath = resolved.route.path;
|
|
||||||
}
|
|
||||||
|
|
||||||
_();
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,7 @@ 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"
|
||||||
|
|
|
@ -20,6 +20,7 @@ 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';
|
||||||
|
@ -60,6 +61,7 @@ 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,
|
||||||
|
@ -94,6 +96,7 @@ 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;
|
||||||
|
|
|
@ -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: Misskey.entities.PageBlock,
|
block: Extract<Misskey.entities.PageBlock, { type: 'image' }>,
|
||||||
page: Misskey.entities.Page,
|
page: Misskey.entities.Page,
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
|
|
@ -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: Misskey.entities.PageBlock,
|
block: Extract<Misskey.entities.PageBlock, { type: 'note' }>,
|
||||||
page: Misskey.entities.Page,
|
page: Misskey.entities.Page,
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
|
|
@ -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: Misskey.entities.PageBlock,
|
block: Extract<Misskey.entities.PageBlock, { type: 'section' }>,
|
||||||
h: number,
|
h: number,
|
||||||
page: Misskey.entities.Page,
|
page: Misskey.entities.Page,
|
||||||
}>();
|
}>();
|
||||||
|
|
|
@ -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: Misskey.entities.PageBlock,
|
block: Extract<Misskey.entities.PageBlock, { type: 'text' }>,
|
||||||
page: Misskey.entities.Page,
|
page: Misskey.entities.Page,
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
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;
|
||||||
|
@ -107,6 +108,7 @@ 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を使う
|
||||||
// ただメモリ的には↓の方が省メモリかもしれないので検討中
|
// ただメモリ的には↓の方が省メモリかもしれないので検討中
|
||||||
|
|
|
@ -51,3 +51,9 @@ export async function fetchInstance(force = false): Promise<Misskey.entities.Met
|
||||||
|
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ClientOptions = {
|
||||||
|
entrancePageStyle: 'classic' | 'simple';
|
||||||
|
showTimelineForVisitor: boolean;
|
||||||
|
showActivitiesForVisitor: boolean;
|
||||||
|
};
|
||||||
|
|
|
@ -96,7 +96,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</FormSection>
|
</FormSection>
|
||||||
|
|
||||||
<FormSuspense v-slot="{ result: stats }" :p="initStats">
|
<MkSuspense 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>
|
||||||
</FormSuspense>
|
</MkSuspense>
|
||||||
|
|
||||||
<FormSection>
|
<FormSection>
|
||||||
<template #label>Well-known resources</template>
|
<template #label>Well-known resources</template>
|
||||||
|
@ -134,7 +134,6 @@ 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';
|
||||||
|
|
182
packages/frontend/src/pages/admin-file.root.vue
Normal file
182
packages/frontend/src/pages/admin-file.root.vue
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
<!--
|
||||||
|
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>
|
|
@ -4,197 +4,37 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs">
|
<MkSuspense v-slot="{ result }" :p="_fetch_" @resolved="(result) => file = result.file">
|
||||||
<div v-if="file" class="_spacer" style="--MI_SPACER-w: 600px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;">
|
<XRoot v-if="result.file != null && result.info != null" :file="result.file" :info="result.info"/>
|
||||||
<div v-if="tab === 'overview'" class="cxqhhsmd _gaps_m">
|
</MkSuspense>
|
||||||
<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 { computed, defineAsyncComponent, ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import XRoot from './admin-file.root.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,
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
async function _fetch_() {
|
function _fetch_() {
|
||||||
file.value = await misskeyApi('drive/files/show', { fileId: props.fileId });
|
return Promise.all([
|
||||||
info.value = await misskeyApi('admin/drive/show-file', { fileId: props.fileId });
|
misskeyApi('drive/files/show', { fileId: props.fileId }),
|
||||||
isSensitive.value = file.value.isSensitive;
|
misskeyApi('admin/drive/show-file', { fileId: props.fileId }),
|
||||||
|
]).then((result) => ({
|
||||||
|
file: result[0],
|
||||||
|
info: result[1],
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
_fetch_();
|
const file = ref<Misskey.entities.DriveFile | null>(null);
|
||||||
|
|
||||||
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>
|
|
||||||
|
|
|
@ -152,6 +152,7 @@ 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';
|
||||||
|
@ -166,9 +167,13 @@ import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
|
|
||||||
const meta = await misskeyApi('admin/meta');
|
const meta = await misskeyApi('admin/meta');
|
||||||
|
|
||||||
const entrancePageStyle = ref(meta.clientOptions.entrancePageStyle ?? 'classic');
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
const showTimelineForVisitor = ref(meta.clientOptions.showTimelineForVisitor ?? true);
|
const entrancePageStyle = ref<ClientOptions['entrancePageStyle']>(meta.clientOptions.entrancePageStyle ?? 'classic');
|
||||||
const showActivitiesForVisitor = ref(meta.clientOptions.showActivitiesForVisitor ?? true);
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
|
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);
|
||||||
|
@ -186,11 +191,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,
|
||||||
|
|
|
@ -6,19 +6,18 @@ 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;">
|
||||||
<FormSuspense v-slot="{ result: database }" :p="databasePromiseFactory">
|
<MkSuspense 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>
|
||||||
</FormSuspense>
|
</MkSuspense>
|
||||||
</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';
|
||||||
|
|
|
@ -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: i18n.ts.destination,
|
title: 'To',
|
||||||
type: 'email',
|
type: 'email',
|
||||||
default: instance.maintainerEmail ?? '',
|
default: instance.maintainerEmail ?? '',
|
||||||
placeholder: 'test@example.com',
|
placeholder: 'test@example.com',
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
chartProcess.value.pushData(stats[props.domain].activeSincePrevTick);
|
if (chartProcess.value != null) chartProcess.value.pushData(stats[props.domain].activeSincePrevTick);
|
||||||
chartActive.value.pushData(stats[props.domain].active);
|
if (chartActive.value != null) chartActive.value.pushData(stats[props.domain].active);
|
||||||
chartDelayed.value.pushData(stats[props.domain].delayed);
|
if (chartDelayed.value != null) chartDelayed.value.pushData(stats[props.domain].delayed);
|
||||||
chartWaiting.value.pushData(stats[props.domain].waiting);
|
if (chartWaiting.value != null) 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
chartProcess.value.setData(dataProcess);
|
if (chartProcess.value != null) chartProcess.value.setData(dataProcess);
|
||||||
chartActive.value.setData(dataActive);
|
if (chartActive.value != null) chartActive.value.setData(dataActive);
|
||||||
chartDelayed.value.setData(dataDelayed);
|
if (chartDelayed.value != null) chartDelayed.value.setData(dataDelayed);
|
||||||
chartWaiting.value.setData(dataWaiting);
|
if (chartWaiting.value != null) chartWaiting.value.setData(dataWaiting);
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|
|
@ -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,
|
data: x as Misskey.entities.ModerationLog,
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -109,7 +109,6 @@ 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';
|
||||||
|
|
|
@ -37,6 +37,8 @@ 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();
|
||||||
|
@ -105,7 +107,6 @@ 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',
|
||||||
|
@ -149,7 +150,9 @@ async function renderChart() {
|
||||||
},
|
},
|
||||||
external: externalTooltipHandler,
|
external: externalTooltipHandler,
|
||||||
},
|
},
|
||||||
gradient,
|
...({ // TSを黙らすため
|
||||||
|
gradient,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [chartVLine(vLineColor)],
|
plugins: [chartVLine(vLineColor)],
|
||||||
|
|
|
@ -42,6 +42,9 @@ 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) => {
|
||||||
|
@ -122,7 +125,6 @@ onMounted(async () => {
|
||||||
stacked: true,
|
stacked: true,
|
||||||
offset: false,
|
offset: false,
|
||||||
time: {
|
time: {
|
||||||
stepSize: 1,
|
|
||||||
unit: 'day',
|
unit: 'day',
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
|
@ -144,7 +146,7 @@ onMounted(async () => {
|
||||||
ticks: {
|
ticks: {
|
||||||
display: true,
|
display: true,
|
||||||
//mirror: true,
|
//mirror: true,
|
||||||
callback: (value, index, values) => value < 0 ? -value : value,
|
callback: (value, index, values) => (value as number) < 0 ? -value : value,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -173,7 +175,9 @@ onMounted(async () => {
|
||||||
label: context => `${context.dataset.label}: ${Math.abs(context.parsed.y)}`,
|
label: context => `${context.dataset.label}: ${Math.abs(context.parsed.y)}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
gradient,
|
...({ // TSを黙らすため
|
||||||
|
gradient,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [chartVLine(vLineColor)],
|
plugins: [chartVLine(vLineColor)],
|
||||||
|
@ -213,7 +217,6 @@ 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',
|
||||||
|
@ -260,7 +263,9 @@ onMounted(async () => {
|
||||||
},
|
},
|
||||||
external: externalTooltipHandler2,
|
external: externalTooltipHandler2,
|
||||||
},
|
},
|
||||||
gradient,
|
...({ // TSを黙らすため
|
||||||
|
gradient,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [chartVLine(vLineColor)],
|
plugins: [chartVLine(vLineColor)],
|
||||||
|
|
|
@ -828,8 +828,9 @@ 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 { ROLE_POLICIES } from '@@/js/const.js';
|
import * as Misskey from 'misskey-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';
|
||||||
|
@ -854,7 +855,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 ROLE_POLICIES) {
|
for (const ROLE_POLICY of Misskey.rolePolicies) {
|
||||||
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,
|
||||||
|
|
|
@ -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 { ROLE_POLICIES } from '@@/js/const.js';
|
import * as Misskey from 'misskey-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 ROLE_POLICIES[number], any>>({});
|
const policies = reactive<Record<typeof Misskey.rolePolicies[number], any>>({});
|
||||||
for (const ROLE_POLICY of ROLE_POLICIES) {
|
for (const ROLE_POLICY of Misskey.rolePolicies) {
|
||||||
policies[ROLE_POLICY] = instance.policies[ROLE_POLICY];
|
policies[ROLE_POLICY] = instance.policies[ROLE_POLICY];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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' :
|
||||||
0,
|
null as never,
|
||||||
setSensitiveFlagAutomatically: state.setSensitiveFlagAutomatically,
|
setSensitiveFlagAutomatically: state.setSensitiveFlagAutomatically,
|
||||||
enableSensitiveMediaDetectionForVideos: state.enableSensitiveMediaDetectionForVideos,
|
enableSensitiveMediaDetectionForVideos: state.enableSensitiveMediaDetectionForVideos,
|
||||||
});
|
});
|
||||||
|
|
|
@ -138,9 +138,10 @@ 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) return;
|
if (canceled || value == null) return;
|
||||||
|
const fromUrl = value.includes('/') ? value.split('/').pop() : null;
|
||||||
const note = await os.apiWithDialog('notes/show', {
|
const note = await os.apiWithDialog('notes/show', {
|
||||||
noteId: value.includes('/') ? value.split('/').pop() : value,
|
noteId: fromUrl ?? value,
|
||||||
});
|
});
|
||||||
pinnedNotes.value = [{
|
pinnedNotes.value = [{
|
||||||
id: note.id,
|
id: note.id,
|
||||||
|
|
|
@ -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" :user="message.fromUser!" :link="!isMe" :preview="false"/>
|
<MkAvatar :class="[$style.avatar, prefer.s.useStickyIcons ? $style.useSticky : null]" :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,11 +231,14 @@ 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) {
|
||||||
|
|
|
@ -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 :link="true" style="width: 24px; height: 24px; margin-right: 4px;" :user="r.user"/>
|
<MkAvatar v-if="r.user" :link="true" style="width: 24px; height: 24px; margin-right: 4px;" :user="r.user"/>
|
||||||
<MkUserName :user="r.user" :nowrap="true"/>
|
<MkUserName v-if="r.user" :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,6 +87,7 @@ 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';
|
||||||
|
@ -98,7 +99,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(null);
|
const ranking = ref<Misskey.entities.BubbleGameRankingResponse | null>(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 });
|
||||||
|
|
|
@ -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">
|
<MkInput v-model="category" :datalist="customEmojiCategories.filter(x => x != null)">
|
||||||
<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">
|
||||||
|
|
145
packages/frontend/src/pages/gallery/edit.root.vue
Normal file
145
packages/frontend/src/pages/gallery/edit.root.vue
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
<!--
|
||||||
|
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>
|
|
@ -4,162 +4,35 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<PageWithHeader :actions="headerActions" :tabs="headerTabs">
|
<MkSuspense v-slot="{ result }" :p="_fetch_">
|
||||||
<div class="_spacer" style="--MI_SPACER-w: 800px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;">
|
<XRoot :post="result"/>
|
||||||
<FormSuspense :p="init" class="_gaps">
|
</MkSuspense>
|
||||||
<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 { computed, watch, ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import XRoot from './edit.root.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 { definePage } from '@/page.js';
|
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { useRouter } from '@/router.js';
|
import { definePage } from '@/page.js';
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
postId?: string;
|
postId?: string,
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const init = ref<(() => Promise<any>) | null>(null);
|
function _fetch_() {
|
||||||
const files = ref<Misskey.entities.DriveFile[]>([]);
|
if (props.postId == null) {
|
||||||
const description = ref<string | null>(null);
|
return Promise.resolve(null);
|
||||||
const title = ref<string | null>(null);
|
|
||||||
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,
|
|
||||||
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 {
|
} else {
|
||||||
const created = await os.apiWithDialog('gallery/posts/create', {
|
return misskeyApi('gallery/posts/show', {
|
||||||
title: title.value,
|
postId: props.postId,
|
||||||
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>
|
|
||||||
|
|
|
@ -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 '@@/js/const.js';
|
import { notificationTypes } from 'misskey-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';
|
||||||
|
|
|
@ -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 { genId } from '@/utility/id.js';
|
|
||||||
import XContainer from '../page-editor.container.vue';
|
import XContainer from '../page-editor.container.vue';
|
||||||
|
import { genId } from '@/utility/id.js';
|
||||||
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: Misskey.entities.PageBlock & { type: 'section'; },
|
modelValue: Extract<Misskey.entities.PageBlock, { type: 'section'; }>,
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'update:modelValue', value: Misskey.entities.PageBlock & { type: 'section' }): void;
|
(ev: 'update:modelValue', value: Extract<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) return;
|
if (canceled || title == null) return;
|
||||||
emit('update:modelValue', {
|
emit('update:modelValue', {
|
||||||
...props.modelValue,
|
...props.modelValue,
|
||||||
title,
|
title,
|
||||||
|
|
|
@ -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 | number | null>(null);
|
const token = ref<string | 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: typeof token.value === 'string' ? token.value : token.value.toString(),
|
token: token.value.toString(), // 実装ミスなどでnumberが入る可能性を払拭できないため念のためtoString
|
||||||
});
|
});
|
||||||
|
|
||||||
backupCodes.value = res.backupCodes;
|
backupCodes.value = res.backupCodes;
|
||||||
|
|
|
@ -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 '@@/js/const.js';
|
import { notificationTypes } from 'misskey-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';
|
||||||
|
|
|
@ -110,7 +110,6 @@ 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">
|
||||||
|
@ -118,7 +117,6 @@ 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']">
|
||||||
|
|
|
@ -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 { genId } from '@/utility/id.js';
|
|
||||||
import XStatusbar from './statusbar.statusbar.vue';
|
import XStatusbar from './statusbar.statusbar.vue';
|
||||||
|
import { genId } from '@/utility/id.js';
|
||||||
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,6 +39,7 @@ 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',
|
||||||
|
|
|
@ -40,6 +40,7 @@ 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';
|
||||||
|
@ -61,7 +62,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: string[] = [];
|
const events = [] as Misskey.entities.UserWebhook['on'];
|
||||||
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');
|
||||||
|
|
|
@ -117,7 +117,6 @@ 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',
|
||||||
|
@ -162,7 +161,9 @@ async function renderChart() {
|
||||||
},
|
},
|
||||||
external: externalTooltipHandler,
|
external: externalTooltipHandler,
|
||||||
},
|
},
|
||||||
gradient,
|
...({ // TSを黙らすため
|
||||||
|
gradient,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [chartVLine(vLineColor), chartLegend(legendEl.value)],
|
plugins: [chartVLine(vLineColor), chartLegend(legendEl.value)],
|
||||||
|
|
|
@ -116,7 +116,6 @@ 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,7 +160,9 @@ async function renderChart() {
|
||||||
},
|
},
|
||||||
external: externalTooltipHandler,
|
external: externalTooltipHandler,
|
||||||
},
|
},
|
||||||
gradient,
|
...({ // TSを黙らすため
|
||||||
|
gradient,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [chartVLine(vLineColor), chartLegend(legendEl.value)],
|
plugins: [chartVLine(vLineColor), chartLegend(legendEl.value)],
|
||||||
|
|
|
@ -117,7 +117,6 @@ 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',
|
||||||
|
@ -155,8 +154,6 @@ 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,
|
||||||
},
|
},
|
||||||
|
@ -172,7 +169,9 @@ async function renderChart() {
|
||||||
},
|
},
|
||||||
external: externalTooltipHandler,
|
external: externalTooltipHandler,
|
||||||
},
|
},
|
||||||
gradient,
|
...({ // TSを黙らすため
|
||||||
|
gradient,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [chartVLine(vLineColor), chartLegend(legendEl.value)],
|
plugins: [chartVLine(vLineColor), chartLegend(legendEl.value)],
|
||||||
|
|
|
@ -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,
|
||||||
});
|
});
|
||||||
|
|
|
@ -34,7 +34,7 @@ import { instance as meta } from '@/instance.js';
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
width: 80vw; // 100%からshapeの幅を引いている
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) => unknown;
|
handler: (note: Misskey.entities.Note) => Misskey.entities.Note;
|
||||||
};
|
};
|
||||||
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) => unknown;
|
handler: (page: Misskey.entities.Page) => Misskey.entities.Page;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,15 @@ export type SoundStore = {
|
||||||
volume: number;
|
volume: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type StatusbarStore = {
|
||||||
|
name: string | null;
|
||||||
|
id: string;
|
||||||
|
type: string | null;
|
||||||
|
size: 'verySmall' | 'small' | 'medium' | 'large' | 'veryLarge';
|
||||||
|
black: boolean;
|
||||||
|
props: Record<string, any>;
|
||||||
|
};
|
||||||
|
|
||||||
type OmitStrict<T, K extends keyof T> = T extends any ? Pick<T, Exclude<keyof T, K>> : never;
|
type OmitStrict<T, K extends keyof T> = T extends any ? Pick<T, Exclude<keyof T, K>> : never;
|
||||||
|
|
||||||
// NOTE: デフォルト値は他の設定の状態に依存してはならない(依存していた場合、ユーザーがその設定項目単体で「初期値にリセット」した場合不具合の原因になる)
|
// NOTE: デフォルト値は他の設定の状態に依存してはならない(依存していた場合、ユーザーがその設定項目単体で「初期値にリセット」した場合不具合の原因になる)
|
||||||
|
@ -182,14 +191,7 @@ export const PREF_DEF = definePreferences({
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
statusbars: {
|
statusbars: {
|
||||||
default: [] as {
|
default: [] as StatusbarStore[],
|
||||||
name: string;
|
|
||||||
id: string;
|
|
||||||
type: string;
|
|
||||||
size: 'verySmall' | 'small' | 'medium' | 'large' | 'veryLarge';
|
|
||||||
black: boolean;
|
|
||||||
props: Record<string, any>;
|
|
||||||
}[],
|
|
||||||
},
|
},
|
||||||
serverDisconnectedBehavior: {
|
serverDisconnectedBehavior: {
|
||||||
default: 'quiet' as 'quiet' | 'reload' | 'dialog',
|
default: 'quiet' as 'quiet' | 'reload' | 'dialog',
|
||||||
|
|
|
@ -469,6 +469,8 @@ export class PreferencesManager {
|
||||||
return local;
|
return local;
|
||||||
} else if (choice === 'merge') {
|
} else if (choice === 'merge') {
|
||||||
return mergedValue!;
|
return mergedValue!;
|
||||||
|
} else { // TSを黙らすため
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,9 +6,11 @@
|
||||||
import type { Plugin } from 'chart.js';
|
import type { Plugin } from 'chart.js';
|
||||||
import MkChartLegend from '@/components/MkChartLegend.vue';
|
import MkChartLegend from '@/components/MkChartLegend.vue';
|
||||||
|
|
||||||
export const chartLegend = (legend: InstanceType<typeof MkChartLegend>) => ({
|
export const chartLegend = (legend: InstanceType<typeof MkChartLegend> | null | undefined) => ({
|
||||||
id: 'htmlLegend',
|
id: 'htmlLegend',
|
||||||
afterUpdate(chart, args, options) {
|
afterUpdate(chart, args, options) {
|
||||||
|
if (legend == null) return;
|
||||||
|
|
||||||
// Reuse the built-in legendItems generator
|
// Reuse the built-in legendItems generator
|
||||||
const items = chart.options.plugins!.legend!.labels!.generateLabels!(chart);
|
const items = chart.options.plugins!.legend!.labels!.generateLabels!(chart);
|
||||||
|
|
||||||
|
|
|
@ -17,8 +17,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { defineAsyncComponent } from 'vue';
|
import { defineAsyncComponent } from 'vue';
|
||||||
import type { notificationTypes as notificationTypes_typeReferenceOnly } from '@@/js/const.js';
|
|
||||||
import { useWidgetPropsManager } from './widget.js';
|
import { useWidgetPropsManager } from './widget.js';
|
||||||
|
import type { notificationTypes as notificationTypes_typeReferenceOnly } from 'misskey-js';
|
||||||
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
||||||
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
||||||
import MkContainer from '@/components/MkContainer.vue';
|
import MkContainer from '@/components/MkContainer.vue';
|
||||||
|
|
|
@ -31,7 +31,7 @@ import { ref, watch, computed } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { useWidgetPropsManager } from './widget.js';
|
import { useWidgetPropsManager } from './widget.js';
|
||||||
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
||||||
import MarqueeText from '@/components/MkMarqueeText.vue';
|
import MkMarqueeText from '@/components/MkMarqueeText.vue';
|
||||||
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
||||||
import MkContainer from '@/components/MkContainer.vue';
|
import MkContainer from '@/components/MkContainer.vue';
|
||||||
import { shuffle } from '@/utility/shuffle.js';
|
import { shuffle } from '@/utility/shuffle.js';
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"useDefineForClassFields": true,
|
"useDefineForClassFields": true,
|
||||||
"verbatimModuleSyntax": true,
|
"verbatimModuleSyntax": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"],
|
"@/*": ["./src/*"],
|
||||||
|
@ -46,8 +47,6 @@
|
||||||
},
|
},
|
||||||
"compileOnSave": false,
|
"compileOnSave": false,
|
||||||
"include": [
|
"include": [
|
||||||
"./build.ts",
|
|
||||||
"./lib/**/*.ts",
|
|
||||||
"./src/**/*.ts",
|
"./src/**/*.ts",
|
||||||
"./src/**/*.vue",
|
"./src/**/*.vue",
|
||||||
"./test/**/*.ts",
|
"./test/**/*.ts",
|
||||||
|
@ -55,7 +54,6 @@
|
||||||
"./@types/**/*.ts"
|
"./@types/**/*.ts"
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
".storybook/**/*",
|
|
||||||
"./src/**/*.stories.ts"
|
"./src/**/*.stories.ts"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -2809,7 +2809,7 @@ type ModerationLog = {
|
||||||
id: ID;
|
id: ID;
|
||||||
createdAt: DateString;
|
createdAt: DateString;
|
||||||
userId: User['id'];
|
userId: User['id'];
|
||||||
user: UserDetailedNotMe | null;
|
user: UserDetailedNotMe;
|
||||||
} & ({
|
} & ({
|
||||||
type: 'updateServerSettings';
|
type: 'updateServerSettings';
|
||||||
info: ModerationLogPayloads['updateServerSettings'];
|
info: ModerationLogPayloads['updateServerSettings'];
|
||||||
|
@ -3210,7 +3210,7 @@ type Notification_2 = components['schemas']['Notification'];
|
||||||
type NotificationsCreateRequest = operations['notifications___create']['requestBody']['content']['application/json'];
|
type NotificationsCreateRequest = operations['notifications___create']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export const notificationTypes: readonly ["note", "follow", "mention", "reply", "renote", "quote", "reaction", "pollVote", "pollEnded", "receiveFollowRequest", "followRequestAccepted", "groupInvited", "app", "roleAssigned", "chatRoomInvitationReceived", "achievementEarned"];
|
export const notificationTypes: readonly ["note", "follow", "mention", "reply", "renote", "quote", "reaction", "pollEnded", "receiveFollowRequest", "followRequestAccepted", "groupInvited", "app", "roleAssigned", "chatRoomInvitationReceived", "achievementEarned", "exportCompleted", "test", "login", "createToken"];
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export function nyaize(text: string): string;
|
export function nyaize(text: string): string;
|
||||||
|
@ -3424,6 +3424,9 @@ type RoleLite = components['schemas']['RoleLite'];
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type RolePolicies = components['schemas']['RolePolicies'];
|
type RolePolicies = components['schemas']['RolePolicies'];
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
export const rolePolicies: readonly ["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"];
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type RolesListResponse = operations['roles___list']['responses']['200']['content']['application/json'];
|
type RolesListResponse = operations['roles___list']['responses']['200']['content']['application/json'];
|
||||||
|
|
||||||
|
@ -3538,6 +3541,7 @@ type SignupRequest = {
|
||||||
'g-recaptcha-response'?: string | null;
|
'g-recaptcha-response'?: string | null;
|
||||||
'turnstile-response'?: string | null;
|
'turnstile-response'?: string | null;
|
||||||
'm-captcha-response'?: string | null;
|
'm-captcha-response'?: string | null;
|
||||||
|
'testcaptcha-response'?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"name": "misskey-js",
|
"name": "misskey-js",
|
||||||
"version": "2025.8.0-beta.5",
|
"version": "2025.8.0",
|
||||||
"description": "Misskey SDK for JavaScript",
|
"description": "Misskey SDK for JavaScript",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./built/index.js",
|
"main": "./built/index.js",
|
||||||
|
|
|
@ -17,7 +17,27 @@ import type {
|
||||||
ChatRoom,
|
ChatRoom,
|
||||||
} from './autogen/models.js';
|
} from './autogen/models.js';
|
||||||
|
|
||||||
export const notificationTypes = ['note', 'follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app', 'roleAssigned', 'chatRoomInvitationReceived', 'achievementEarned'] as const;
|
export const notificationTypes = [
|
||||||
|
'note',
|
||||||
|
'follow',
|
||||||
|
'mention',
|
||||||
|
'reply',
|
||||||
|
'renote',
|
||||||
|
'quote',
|
||||||
|
'reaction',
|
||||||
|
'pollEnded',
|
||||||
|
'receiveFollowRequest',
|
||||||
|
'followRequestAccepted',
|
||||||
|
'groupInvited',
|
||||||
|
'app',
|
||||||
|
'roleAssigned',
|
||||||
|
'chatRoomInvitationReceived',
|
||||||
|
'achievementEarned',
|
||||||
|
'exportCompleted',
|
||||||
|
'test',
|
||||||
|
'login',
|
||||||
|
'createToken',
|
||||||
|
] as const;
|
||||||
|
|
||||||
export const noteVisibilities = ['public', 'home', 'followers', 'specified'] as const;
|
export const noteVisibilities = ['public', 'home', 'followers', 'specified'] as const;
|
||||||
|
|
||||||
|
@ -170,6 +190,46 @@ export const moderationLogTypes = [
|
||||||
'updateProxyAccountDescription',
|
'updateProxyAccountDescription',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
export const rolePolicies = [
|
||||||
|
'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 queueTypes = [
|
export const queueTypes = [
|
||||||
'system',
|
'system',
|
||||||
'endedPollNotification',
|
'endedPollNotification',
|
||||||
|
|
|
@ -49,7 +49,7 @@ export type ModerationLog = {
|
||||||
id: ID;
|
id: ID;
|
||||||
createdAt: DateString;
|
createdAt: DateString;
|
||||||
userId: User['id'];
|
userId: User['id'];
|
||||||
user: UserDetailedNotMe | null;
|
user: UserDetailedNotMe;
|
||||||
} & ({
|
} & ({
|
||||||
type: 'updateServerSettings';
|
type: 'updateServerSettings';
|
||||||
info: ModerationLogPayloads['updateServerSettings'];
|
info: ModerationLogPayloads['updateServerSettings'];
|
||||||
|
@ -269,6 +269,7 @@ export type SignupRequest = {
|
||||||
'g-recaptcha-response'?: string | null;
|
'g-recaptcha-response'?: string | null;
|
||||||
'turnstile-response'?: string | null;
|
'turnstile-response'?: string | null;
|
||||||
'm-captcha-response'?: string | null;
|
'm-captcha-response'?: string | null;
|
||||||
|
'testcaptcha-response'?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SignupResponse = MeDetailed & {
|
export type SignupResponse = MeDetailed & {
|
||||||
|
|
|
@ -13,6 +13,7 @@ export const mutedNoteReasons = consts.mutedNoteReasons;
|
||||||
export const followingVisibilities = consts.followingVisibilities;
|
export const followingVisibilities = consts.followingVisibilities;
|
||||||
export const followersVisibilities = consts.followersVisibilities;
|
export const followersVisibilities = consts.followersVisibilities;
|
||||||
export const moderationLogTypes = consts.moderationLogTypes;
|
export const moderationLogTypes = consts.moderationLogTypes;
|
||||||
|
export const rolePolicies = consts.rolePolicies;
|
||||||
export const queueTypes = consts.queueTypes;
|
export const queueTypes = consts.queueTypes;
|
||||||
export const reversiUpdateKeys = consts.reversiUpdateKeys;
|
export const reversiUpdateKeys = consts.reversiUpdateKeys;
|
||||||
|
|
||||||
|
|
486
pnpm-lock.yaml
generated
486
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue