Compare commits
43 commits
l10n_devel
...
develop
Author | SHA1 | Date | |
---|---|---|---|
Mizah | a858ee31c6 | ||
Mizah | 0bd7ed8191 | ||
Mizah | ee574ae154 | ||
Mizah | a505f36252 | ||
Mizah | a516383c66 | ||
Mizah | 1613dafc39 | ||
Mizah | 8457fa9b3b | ||
Mizah | 2e51e779e7 | ||
Mizah | 7f6b486976 | ||
Mizah | 8c1508fae4 | ||
Mizah | aeb568664d | ||
Mizah | ecb990fb77 | ||
a11b77a415 | |||
1bc4f400c0 | |||
458c72c153 | |||
6bd3ed2074 | |||
31e5f0bd09 | |||
e0a83e9c9e | |||
1496700b37 | |||
00cbf9fe80 | |||
cf09aa21f0 | |||
9f7d41eb47 | |||
4a62051ce7 | |||
3a421837bf | |||
a4c5ce1413 | |||
e75b62f3f5 | |||
5b60ae810b | |||
98b4717c45 | |||
8a4ce16e90 | |||
794cb9ffe2 | |||
0b976064ca | |||
bca690f256 | |||
f1eb17f66c | |||
b1c82213a3 | |||
a896c39dbf | |||
6718a54f6f | |||
d57b8bf2e2 | |||
224bbd486f | |||
724dea8136 | |||
ceb60d61b0 | |||
17d9aca5a7 | |||
7fc8a2a7b0 | |||
a96f09cee3 |
19
CHANGELOG.md
19
CHANGELOG.md
|
@ -1,8 +1,10 @@
|
||||||
## 2024.10.2
|
## 2024.11.0
|
||||||
|
|
||||||
### General
|
### General
|
||||||
- Feat: コンテンツの表示にログインを必須にできるように
|
- Feat: コンテンツの表示にログインを必須にできるように
|
||||||
- Feat: 過去のノートを非公開化/フォロワーのみ表示可能にできるように
|
- Feat: 過去のノートを非公開化/フォロワーのみ表示可能にできるように
|
||||||
|
- Enhance: 依存関係の更新
|
||||||
|
- Enhance: l10nの更新
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
- Enhance: Bull DashboardでRelationship Queueの状態も確認できるように
|
- Enhance: Bull DashboardでRelationship Queueの状態も確認できるように
|
||||||
|
@ -15,22 +17,35 @@
|
||||||
- どのアカウントで認証しようとしているのかがわかるように
|
- どのアカウントで認証しようとしているのかがわかるように
|
||||||
- 認証するアカウントを切り替えられるように
|
- 認証するアカウントを切り替えられるように
|
||||||
- Enhance: Self-XSS防止用の警告を追加
|
- Enhance: Self-XSS防止用の警告を追加
|
||||||
- Enhance: カタルーニャ語 (ca-ES) に対応
|
- Enhance: カタルーニャ語 (ca-ES) に対応
|
||||||
|
- Enhance: 個別お知らせページではMetaタグを出力するように
|
||||||
- Fix: 通知の範囲指定の設定項目が必要ない通知設定でも範囲指定の設定がでている問題を修正
|
- Fix: 通知の範囲指定の設定項目が必要ない通知設定でも範囲指定の設定がでている問題を修正
|
||||||
- Fix: Turnstileが失敗・期限切れした際にも成功扱いとなってしまう問題を修正
|
- Fix: Turnstileが失敗・期限切れした際にも成功扱いとなってしまう問題を修正
|
||||||
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/768)
|
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/768)
|
||||||
- Fix: デッキのタイムラインカラムで「センシティブなファイルを含むノートを表示」設定が使用できなかった問題を修正
|
- Fix: デッキのタイムラインカラムで「センシティブなファイルを含むノートを表示」設定が使用できなかった問題を修正
|
||||||
- Fix: Encode RSS urls with escape sequences before fetching allowing query parameters to be used
|
- Fix: Encode RSS urls with escape sequences before fetching allowing query parameters to be used
|
||||||
- Fix: リンク切れを修正
|
- Fix: リンク切れを修正
|
||||||
|
= Fix: ノート投稿ボタンにホバー時のスタイルが適用されていないのを修正
|
||||||
|
(Cherry-picked from https://github.com/taiyme/misskey/pull/305)
|
||||||
|
- Fix: メールアドレス登録有効化時の「完了」ダイアログボックスの表示条件を修正
|
||||||
|
|
||||||
### Server
|
### Server
|
||||||
- Enhance: 起動前の疎通チェックで、DBとメイン以外のRedisの疎通確認も行うように
|
- Enhance: 起動前の疎通チェックで、DBとメイン以外のRedisの疎通確認も行うように
|
||||||
(Based on https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/588)
|
(Based on https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/588)
|
||||||
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/715)
|
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/715)
|
||||||
|
- Enhance: リモートユーザーの照会をオリジナルにリダイレクトするように
|
||||||
|
- Fix: フォロワーへのメッセージの絵文字をemojisに含めるように
|
||||||
- Fix: Nested proxy requestsを検出した際にブロックするように
|
- Fix: Nested proxy requestsを検出した際にブロックするように
|
||||||
[ghsa-gq5q-c77c-v236](https://github.com/misskey-dev/misskey/security/advisories/ghsa-gq5q-c77c-v236)
|
[ghsa-gq5q-c77c-v236](https://github.com/misskey-dev/misskey/security/advisories/ghsa-gq5q-c77c-v236)
|
||||||
- Fix: 招待コードの発行可能な残り数算出に使用すべきロールポリシーの値が違う問題を修正
|
- Fix: 招待コードの発行可能な残り数算出に使用すべきロールポリシーの値が違う問題を修正
|
||||||
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/706)
|
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/706)
|
||||||
|
- Fix: 連合への配信時に、acctの大小文字が区別されてしまい正しくメンションが処理されないことがある問題を修正
|
||||||
|
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/711)
|
||||||
|
- Fix: ローカルユーザーへのメンションを含むノートが連合される際に正しいURLに変換されないことがある問題を修正
|
||||||
|
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/712)
|
||||||
|
- Fix: FTT無効時にユーザーリストタイムラインが使用できない問題を修正
|
||||||
|
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/709)
|
||||||
|
- Fix: User Webhookテスト機能のMock Payloadを修正
|
||||||
|
|
||||||
### Misskey.js
|
### Misskey.js
|
||||||
- Fix: Stream初期化時、別途WebSocketを指定する場合の型定義を修正
|
- Fix: Stream初期化時、別途WebSocketを指定する場合の型定義を修正
|
||||||
|
|
|
@ -83,6 +83,10 @@ One should not add property that has defined before by other implementation, or
|
||||||
## Reviewers guide
|
## Reviewers guide
|
||||||
Be willing to comment on the good points and not just the things you want fixed 💯
|
Be willing to comment on the good points and not just the things you want fixed 💯
|
||||||
|
|
||||||
|
読んでおくといいやつ
|
||||||
|
- https://blog.lacolaco.net/posts/1e2cf439b3c2/
|
||||||
|
- https://konifar-zatsu.hatenadiary.jp/entry/2024/11/05/192421
|
||||||
|
|
||||||
### Review perspective
|
### Review perspective
|
||||||
- Scope
|
- Scope
|
||||||
- Are the goals of the PR clear?
|
- Are the goals of the PR clear?
|
||||||
|
|
|
@ -8,37 +8,37 @@ search: "Cercar"
|
||||||
notifications: "Notificacions"
|
notifications: "Notificacions"
|
||||||
username: "Nom d'usuari"
|
username: "Nom d'usuari"
|
||||||
password: "Contrasenya"
|
password: "Contrasenya"
|
||||||
initialPasswordForSetup: "Contrasenya inicial per fer la primera configuració "
|
initialPasswordForSetup: "Contrasenya inicial per la configuració inicial"
|
||||||
initialPasswordIsIncorrect: "La contrasenya no és correcta."
|
initialPasswordIsIncorrect: "La contrasenya no és correcta."
|
||||||
initialPasswordForSetupDescription: "Fes servir la contrasenya que has fet servir al fitxer de configuració, si tu mateix has instal·lat Misskey.\nSi fas servir una empresa d'allotjament de Misskey, fes servir la contrasenya que t'han donat.\nSi no has posat cap contrasenya deixar l'espai en blanc."
|
initialPasswordForSetupDescription: "Fes servir la contrasenya que has fet servir al fitxer de configuració, si tu mateix has instal·lat Misskey.\nSi fas servir una empresa d'allotjament de Misskey, fes servir la contrasenya que t'han donat.\nSi no has posat cap contrasenya deixar l'espai en blanc."
|
||||||
forgotPassword: "Restableix la contrasenya "
|
forgotPassword: "Contrasenya oblidada"
|
||||||
fetchingAsApObject: "Cercant al Fediverse..."
|
fetchingAsApObject: "Cercant en el Fediverse..."
|
||||||
ok: "OK"
|
ok: "OK"
|
||||||
gotIt: "D'acord "
|
gotIt: "Ho he entès!"
|
||||||
cancel: "Cancel·lar"
|
cancel: "Cancel·lar"
|
||||||
noThankYou: "No, gràcies"
|
noThankYou: "No, gràcies"
|
||||||
enterUsername: "Introdueix el teu nom d'usuari"
|
enterUsername: "Introdueix el teu nom d'usuari"
|
||||||
renotedBy: "Impulsat per {user}"
|
renotedBy: "Impulsat per {user}"
|
||||||
noNotes: "Cap nota"
|
noNotes: "Cap nota"
|
||||||
noNotifications: "Cap notificació"
|
noNotifications: "Cap notificació"
|
||||||
instance: "Instància "
|
instance: "Servidor"
|
||||||
settings: "Preferències"
|
settings: "Preferències"
|
||||||
notificationSettings: "Configurar les notificacions"
|
notificationSettings: "Paràmetres de notificacions"
|
||||||
basicSettings: "Configuració bàsica"
|
basicSettings: "Configuració bàsica"
|
||||||
otherSettings: "Altres configuracions"
|
otherSettings: "Configuració avançada"
|
||||||
openInWindow: "Obrir en una finestra nova"
|
openInWindow: "Obrir en una nova finestra"
|
||||||
profile: "Perfil"
|
profile: "Perfil"
|
||||||
timeline: "Línia de temps"
|
timeline: "Línia de temps"
|
||||||
noAccountDescription: "Aquest usuari encara no ha escrit la seva biografia."
|
noAccountDescription: "Aquest usuari encara no ha escrit la seva biografia."
|
||||||
login: "Iniciar sessió"
|
login: "Iniciar sessió"
|
||||||
loggingIn: "Iniciar la sessió "
|
loggingIn: "Identificant-se"
|
||||||
logout: "Tancar la sessió"
|
logout: "Tancar la sessió"
|
||||||
signup: "Registrar-se"
|
signup: "Registrar-se"
|
||||||
uploading: "Pujant..."
|
uploading: "Pujant..."
|
||||||
save: "Desa"
|
save: "Desa"
|
||||||
users: "Usuaris"
|
users: "Usuaris"
|
||||||
addUser: "Afegir un usuari"
|
addUser: "Afegir un usuari"
|
||||||
favorite: "Afegeix als preferits"
|
favorite: "Afegir a preferits"
|
||||||
favorites: "Favorits"
|
favorites: "Favorits"
|
||||||
unfavorite: "Eliminar dels preferits"
|
unfavorite: "Eliminar dels preferits"
|
||||||
favorited: "Afegit als preferits."
|
favorited: "Afegit als preferits."
|
||||||
|
@ -50,26 +50,26 @@ copyContent: "Copiar el contingut"
|
||||||
copyLink: "Copiar l'enllaç"
|
copyLink: "Copiar l'enllaç"
|
||||||
copyLinkRenote: "Copiar l'enllaç de la renota"
|
copyLinkRenote: "Copiar l'enllaç de la renota"
|
||||||
delete: "Elimina"
|
delete: "Elimina"
|
||||||
deleteAndEdit: "Eliminar i editar"
|
deleteAndEdit: "Elimina i edita"
|
||||||
deleteAndEditConfirm: "Segur que vols eliminar aquesta publicació i editar-la? Perdràs totes les reaccions, impulsos i respostes."
|
deleteAndEditConfirm: "Segur que vols eliminar aquesta publicació i editar-la? Perdràs totes les reaccions, impulsos i respostes."
|
||||||
addToList: "Afegir a una llista"
|
addToList: "Afegir a una llista"
|
||||||
addToAntenna: "Afegir a una antena"
|
addToAntenna: "Afegir a l'antena"
|
||||||
sendMessage: "Enviar un missatge"
|
sendMessage: "Enviar un missatge"
|
||||||
copyRSS: "Copiar RSS"
|
copyRSS: "Copiar RSS"
|
||||||
copyUsername: "Copiar nom d'usuari"
|
copyUsername: "Copiar nom d'usuari"
|
||||||
copyUserId: "Copiar ID d'usuari"
|
copyUserId: "Copiar ID d'usuari"
|
||||||
copyNoteId: "Copiar ID de la nota"
|
copyNoteId: "Copiar ID de nota"
|
||||||
copyFileId: "Copiar ID de l'arxiu"
|
copyFileId: "Copiar ID d'arxiu"
|
||||||
copyFolderId: "Copiar ID de la carpeta"
|
copyFolderId: "Copiar ID de carpeta"
|
||||||
copyProfileUrl: "Copiar adreça URL del perfil"
|
copyProfileUrl: "Copiar URL del perfil"
|
||||||
searchUser: "Cercar un usuari"
|
searchUser: "Cercar un usuari"
|
||||||
searchThisUsersNotes: "Cercar les publicacions de l'usuari"
|
searchThisUsersNotes: "Cerca les publicacions de l'usuari"
|
||||||
reply: "Respon"
|
reply: "Respondre"
|
||||||
loadMore: "Carregar més"
|
loadMore: "Carregar més"
|
||||||
showMore: "Veure més"
|
showMore: "Veure més"
|
||||||
showLess: "Mostrar menys"
|
showLess: "Mostra menys"
|
||||||
youGotNewFollower: "t'ha seguit"
|
youGotNewFollower: "t'ha seguit"
|
||||||
receiveFollowRequest: "Has rebut una sol·licitud de seguiment"
|
receiveFollowRequest: "Sol·licitud de seguiment rebuda"
|
||||||
followRequestAccepted: "Sol·licitud de seguiment acceptada"
|
followRequestAccepted: "Sol·licitud de seguiment acceptada"
|
||||||
mention: "Menció"
|
mention: "Menció"
|
||||||
mentions: "Mencions"
|
mentions: "Mencions"
|
||||||
|
@ -78,25 +78,25 @@ importAndExport: "Importar / Exportar"
|
||||||
import: "Importar"
|
import: "Importar"
|
||||||
export: "Exporta"
|
export: "Exporta"
|
||||||
files: "Fitxers"
|
files: "Fitxers"
|
||||||
download: "Descarregar"
|
download: "Baixar"
|
||||||
driveFileDeleteConfirm: "Estàs segur que vols suprimir el fitxer \"{name}\"? Les notes associades a aquest fitxer també seran esborrades."
|
driveFileDeleteConfirm: "Estàs segur que vols suprimir el fitxer \"{name}\"? Les notes associades a aquest fitxer adjunt també se suprimiran."
|
||||||
unfollowConfirm: "Segur que vols deixar de seguir a {name}?"
|
unfollowConfirm: "Estàs segur que vols deixar de seguir {name}?"
|
||||||
exportRequested: "Has sol·licitat una exportació de dades. Això pot trigar una estona. S'afegirà a la teva unitat de disc un cop estigui completada."
|
exportRequested: "Has sol·licitat una exportació. Això pot trigar una estona. S'afegirà a la teva unitat un cop completat."
|
||||||
importRequested: "Has sol·licitat una importació de dades. Això pot trigar una estona."
|
importRequested: "Has sol·licitat una importació. Això pot trigar una estona."
|
||||||
lists: "Llistes"
|
lists: "Llistes"
|
||||||
noLists: "No tens cap llista"
|
noLists: "No tens cap llista"
|
||||||
note: "Nota"
|
note: "Nota"
|
||||||
notes: "Notes"
|
notes: "Notes"
|
||||||
following: "Segueixes "
|
following: "Seguint"
|
||||||
followers: "Seguidors"
|
followers: "Seguidors"
|
||||||
followsYou: "Et segueix"
|
followsYou: "Et segueix"
|
||||||
createList: "Crear llista"
|
createList: "Crear llista"
|
||||||
manageLists: "Gestionar les llistes"
|
manageLists: "Gestionar les llistes"
|
||||||
error: "Error"
|
error: "Error"
|
||||||
somethingHappened: "S'ha produït un error"
|
somethingHappened: "S'ha produït un error"
|
||||||
retry: "Torna-ho a provar"
|
retry: "Torna-ho a intentar"
|
||||||
pageLoadError: "S'ha produït un error en carregar la pàgina"
|
pageLoadError: "S'ha produït un error en carregar la pàgina"
|
||||||
pageLoadErrorDescription: "Això normalment és a causa d'errors a la xarxa o a la memòria cau del navegador. Prova d'esborrar la memòria cau i torna-ho a provar després d'esperar un temps."
|
pageLoadErrorDescription: "Això normalment es deu a errors de xarxa o a la memòria cau del navegador. Prova d'esborrar la memòria cau i torna-ho a provar després d'esperar una estona."
|
||||||
serverIsDead: "Aquest servidor no respon. Espera una estona i torna-ho a provar."
|
serverIsDead: "Aquest servidor no respon. Espera una estona i torna-ho a provar."
|
||||||
youShouldUpgradeClient: "Per veure aquesta pàgina, actualitzeu-la per actualitzar el vostre client."
|
youShouldUpgradeClient: "Per veure aquesta pàgina, actualitzeu-la per actualitzar el vostre client."
|
||||||
enterListName: "Introdueix un nom per a la llista"
|
enterListName: "Introdueix un nom per a la llista"
|
||||||
|
@ -104,52 +104,52 @@ privacy: "Privadesa"
|
||||||
makeFollowManuallyApprove: "Les sol·licituds de seguiment requereixen aprovació"
|
makeFollowManuallyApprove: "Les sol·licituds de seguiment requereixen aprovació"
|
||||||
defaultNoteVisibility: "Visibilitat per defecte"
|
defaultNoteVisibility: "Visibilitat per defecte"
|
||||||
follow: "Seguint"
|
follow: "Seguint"
|
||||||
followRequest: "Enviar sol·licitud de seguiment"
|
followRequest: "Enviar la sol·licitud de seguiment"
|
||||||
followRequests: "Sol·licituds de seguiment"
|
followRequests: "Sol·licituds de seguiment"
|
||||||
unfollow: "Deixar de seguir"
|
unfollow: "Deixar de seguir"
|
||||||
followRequestPending: "Sol·licituds de seguiment pendents"
|
followRequestPending: "Sol·licituds de seguiment pendents"
|
||||||
enterEmoji: "Introduir un emoji"
|
enterEmoji: "Introduir un emoji"
|
||||||
renote: "Impulsar "
|
renote: "Impulsa"
|
||||||
unrenote: "Anul·la l'impuls"
|
unrenote: "Anul·la l'impuls"
|
||||||
renoted: "S'ha impulsat"
|
renoted: "S'ha impulsat"
|
||||||
renotedToX: "Impulsat per {name}."
|
renotedToX: "Impulsat per {name}."
|
||||||
cantRenote: "No es pot impulsar aquesta publicació"
|
cantRenote: "No es pot impulsar aquesta publicació"
|
||||||
cantReRenote: "No es pot impulsar un impuls."
|
cantReRenote: "No es pot impulsar l'impuls."
|
||||||
quote: "Cita"
|
quote: "Cita"
|
||||||
inChannelRenote: "Impulsar només a un canal"
|
inChannelRenote: "Renotar només al Canal"
|
||||||
inChannelQuote: "Citar només a un canal"
|
inChannelQuote: "Citar només al Canal"
|
||||||
renoteToChannel: "Impulsar a un canal"
|
renoteToChannel: "Impulsa a un canal"
|
||||||
renoteToOtherChannel: "Impulsar a un altre canal"
|
renoteToOtherChannel: "Impulsa a un altre canal"
|
||||||
pinnedNote: "Nota fixada"
|
pinnedNote: "Nota fixada"
|
||||||
pinned: "Fixar al perfil"
|
pinned: "Fixar al perfil"
|
||||||
you: "Tu"
|
you: "Tu"
|
||||||
clickToShow: "Fes clic per mostrar"
|
clickToShow: "Fes clic per mostrar"
|
||||||
sensitive: "Sensible"
|
sensitive: "NSFW"
|
||||||
add: "Afegir"
|
add: "Afegir"
|
||||||
reaction: "Reacció "
|
reaction: "Reaccions"
|
||||||
reactions: "Reaccions"
|
reactions: "Reaccions"
|
||||||
emojiPicker: "Selector d'emojis"
|
emojiPicker: "Selecció d'emojis"
|
||||||
pinnedEmojisForReactionSettingDescription: "Selecciona l'emoji amb qui vols reaccionar"
|
pinnedEmojisForReactionSettingDescription: "Selecciona l'emoji amb el qual reaccionar"
|
||||||
pinnedEmojisSettingDescription: "Selecciona quins emojis vols deixar fixats i es mostrin en obrir el selector d'emojis"
|
pinnedEmojisSettingDescription: "Selecciona l'emoji amb el qual reaccionar"
|
||||||
emojiPickerDisplay: "Mostrar el selector d'emojis"
|
emojiPickerDisplay: "Visualitza el selector d'emojis"
|
||||||
overwriteFromPinnedEmojisForReaction: "Reemplaça els emojis de la reacció"
|
overwriteFromPinnedEmojisForReaction: "Reemplaça els emojis de la reacció"
|
||||||
overwriteFromPinnedEmojis: "Sobreescriu els emojis fixats al panel de reaccions"
|
overwriteFromPinnedEmojis: "Sobreescriu des dels emojis fixats"
|
||||||
reactionSettingDescription2: "Arrossega per reordenar, fes clic per suprimir, prem \"+\" per afegir."
|
reactionSettingDescription2: "Arrossega per reordenar, fes clic per suprimir, prem \"+\" per afegir."
|
||||||
rememberNoteVisibility: "Recorda la configuració de visibilitat de les notes"
|
rememberNoteVisibility: "Recorda la configuració de visibilitat de les notes"
|
||||||
attachCancel: "Eliminar el fitxer adjunt"
|
attachCancel: "Eliminar el fitxer adjunt"
|
||||||
deleteFile: "Esborrar l'arxiu "
|
deleteFile: "Esborrar l'arxiu "
|
||||||
markAsSensitive: "Marcar com a sensible"
|
markAsSensitive: "Marcar com a NSFW"
|
||||||
unmarkAsSensitive: "Deixar de marcar com a sensible"
|
unmarkAsSensitive: "Deixar de marcar com a sensible"
|
||||||
enterFileName: "Defineix nom del fitxer"
|
enterFileName: "Defineix nom del fitxer"
|
||||||
mute: "Silencia"
|
mute: "Silencia"
|
||||||
unmute: "Deixa de silenciar"
|
unmute: "Deixa de silenciar"
|
||||||
renoteMute: "Silenciar impulsos"
|
renoteMute: "Silenciar Renotes"
|
||||||
renoteUnmute: "Treure el silenci dels impulsos"
|
renoteUnmute: "Treure el silenci de les renotes"
|
||||||
block: "Bloqueja"
|
block: "Bloqueja"
|
||||||
unblock: "Desbloqueja"
|
unblock: "Desbloqueja"
|
||||||
suspend: "Suspèn"
|
suspend: "Suspèn"
|
||||||
unsuspend: "Deixa de suspendre"
|
unsuspend: "Deixa de suspendre"
|
||||||
blockConfirm: "Vols bloquejar-lo?"
|
blockConfirm: "Vols bloquejar?"
|
||||||
unblockConfirm: "Vols desbloquejar-lo?"
|
unblockConfirm: "Vols desbloquejar-lo?"
|
||||||
suspendConfirm: "Estàs segur que vols suspendre aquest compte?"
|
suspendConfirm: "Estàs segur que vols suspendre aquest compte?"
|
||||||
unsuspendConfirm: "Estàs segur que vols treure la suspensió d'aquest compte?"
|
unsuspendConfirm: "Estàs segur que vols treure la suspensió d'aquest compte?"
|
||||||
|
@ -175,11 +175,11 @@ youCanCleanRemoteFilesCache: "Pots netejar la memòria cau fent clic al botó de
|
||||||
cacheRemoteSensitiveFiles: "Posar a la memòria cau arxius remots sensibles"
|
cacheRemoteSensitiveFiles: "Posar a la memòria cau arxius remots sensibles"
|
||||||
cacheRemoteSensitiveFilesDescription: "Quan aquesta opció és desactiva, els arxius remots sensibles es carregant directament del servidor d'origen sense que es guardin a la memòria cau."
|
cacheRemoteSensitiveFilesDescription: "Quan aquesta opció és desactiva, els arxius remots sensibles es carregant directament del servidor d'origen sense que es guardin a la memòria cau."
|
||||||
flagAsBot: "Marca aquest compte com a bot"
|
flagAsBot: "Marca aquest compte com a bot"
|
||||||
flagAsBotDescription: "Activa aquesta opció si el compte el controla un programa. Si s'activa, actuarà com un senyal per altres desenvolupadors per prevenir cadenes d'interacció sense fi i ajustar els paràmetres interns de Misskey pe tractar el compte com un bot."
|
flagAsBotDescription: "Marca aquest compte com a bot"
|
||||||
flagAsCat: "Marca aquest compte com a gat"
|
flagAsCat: "Marca aquest compte com a gat"
|
||||||
flagAsCatDescription: "Activeu aquesta opció per marcar aquest compte com a gat."
|
flagAsCatDescription: "Activeu aquesta opció per marcar aquest compte com a gat."
|
||||||
flagShowTimelineReplies: "Mostra les respostes a la línia de temps"
|
flagShowTimelineReplies: "Mostra les respostes a la línia de temps"
|
||||||
flagShowTimelineRepliesDescription: "Mostra les respostes dels usuaris a les notes d'altres usuaris a la línia de temps."
|
flagShowTimelineRepliesDescription: "Mostra les respostes a la línia de temps"
|
||||||
autoAcceptFollowed: "Aprova automàticament les sol·licituds de seguiment dels usuaris que segueixes"
|
autoAcceptFollowed: "Aprova automàticament les sol·licituds de seguiment dels usuaris que segueixes"
|
||||||
addAccount: "Afegeix un compte"
|
addAccount: "Afegeix un compte"
|
||||||
reloadAccountsList: "Recarregar la llista de contactes"
|
reloadAccountsList: "Recarregar la llista de contactes"
|
||||||
|
@ -204,7 +204,7 @@ selectUser: "Selecciona usuari/a"
|
||||||
recipient: "Destinatari"
|
recipient: "Destinatari"
|
||||||
annotation: "Comentaris"
|
annotation: "Comentaris"
|
||||||
federation: "Federació"
|
federation: "Federació"
|
||||||
instances: "Instàncies "
|
instances: "Servidors"
|
||||||
registeredAt: "Registrat a"
|
registeredAt: "Registrat a"
|
||||||
latestRequestReceivedAt: "Última petició rebuda"
|
latestRequestReceivedAt: "Última petició rebuda"
|
||||||
latestStatus: "Últim estat"
|
latestStatus: "Últim estat"
|
||||||
|
@ -213,7 +213,7 @@ charts: "Gràfics"
|
||||||
perHour: "Per hora"
|
perHour: "Per hora"
|
||||||
perDay: "Per dia"
|
perDay: "Per dia"
|
||||||
stopActivityDelivery: "Deixa d'enviar activitats"
|
stopActivityDelivery: "Deixa d'enviar activitats"
|
||||||
blockThisInstance: "Bloca aquesta instància "
|
blockThisInstance: "Deixa d'enviar activitats"
|
||||||
silenceThisInstance: "Silencia aquesta instància "
|
silenceThisInstance: "Silencia aquesta instància "
|
||||||
mediaSilenceThisInstance: "Silenciar els arxius d'aquesta instància "
|
mediaSilenceThisInstance: "Silenciar els arxius d'aquesta instància "
|
||||||
operations: "Accions"
|
operations: "Accions"
|
||||||
|
@ -228,7 +228,7 @@ network: "Xarxa"
|
||||||
disk: "Disc"
|
disk: "Disc"
|
||||||
instanceInfo: "Informació del fitxer d'instal·lació"
|
instanceInfo: "Informació del fitxer d'instal·lació"
|
||||||
statistics: "Estadístiques"
|
statistics: "Estadístiques"
|
||||||
clearQueue: "Esborra la cua de feina"
|
clearQueue: "Esborrar la cua"
|
||||||
clearQueueConfirmTitle: "Esteu segur que voleu esborrar la cua?"
|
clearQueueConfirmTitle: "Esteu segur que voleu esborrar la cua?"
|
||||||
clearQueueConfirmText: "Les notes no lliurades que quedin a la cua no es federaran. Normalment aquesta operació no és necessària."
|
clearQueueConfirmText: "Les notes no lliurades que quedin a la cua no es federaran. Normalment aquesta operació no és necessària."
|
||||||
clearCachedFiles: "Esborra la memòria cau"
|
clearCachedFiles: "Esborra la memòria cau"
|
||||||
|
@ -254,7 +254,7 @@ processing: "S'està processant..."
|
||||||
preview: "Vista prèvia"
|
preview: "Vista prèvia"
|
||||||
default: "Per defecte"
|
default: "Per defecte"
|
||||||
defaultValueIs: "Per defecte: {value}"
|
defaultValueIs: "Per defecte: {value}"
|
||||||
noCustomEmojis: "No hi ha emojis personalitzats"
|
noCustomEmojis: "Cap emoji personalitzat"
|
||||||
noJobs: "No hi ha feines"
|
noJobs: "No hi ha feines"
|
||||||
federating: "Federant"
|
federating: "Federant"
|
||||||
blocked: "Bloquejat"
|
blocked: "Bloquejat"
|
||||||
|
@ -268,11 +268,11 @@ instanceFollowers: "Seguidors del servidor"
|
||||||
instanceUsers: "Usuaris del servidor"
|
instanceUsers: "Usuaris del servidor"
|
||||||
changePassword: "Canvia la contrasenya"
|
changePassword: "Canvia la contrasenya"
|
||||||
security: "Seguretat"
|
security: "Seguretat"
|
||||||
retypedNotMatch: "Les entrades no coincideix"
|
retypedNotMatch: "L'entrada no coincideix"
|
||||||
currentPassword: "Contrasenya actual"
|
currentPassword: "Contrasenya actual"
|
||||||
newPassword: "Contrasenya nova"
|
newPassword: "Contrasenya nova"
|
||||||
newPasswordRetype: "Contrasenya nova (repeteix-la)"
|
newPasswordRetype: "Contrasenya nou (repeteix-la)"
|
||||||
attachFile: "Afegeix un arxiu"
|
attachFile: "Adjunta fitxers"
|
||||||
more: "Més"
|
more: "Més"
|
||||||
featured: "Destacat"
|
featured: "Destacat"
|
||||||
usernameOrUserId: "Nom o ID d'usuari"
|
usernameOrUserId: "Nom o ID d'usuari"
|
||||||
|
@ -282,25 +282,25 @@ announcements: "Anuncis"
|
||||||
imageUrl: "URL de la imatge"
|
imageUrl: "URL de la imatge"
|
||||||
remove: "Eliminar"
|
remove: "Eliminar"
|
||||||
removed: "Eliminat"
|
removed: "Eliminat"
|
||||||
removeAreYouSure: "Segur que vols esborrar «{x}»?"
|
removeAreYouSure: "Segur que voleu retirar «{x}»?"
|
||||||
deleteAreYouSure: "Segur que vols esborrar «{x}»?"
|
deleteAreYouSure: "Segur que voleu retirar «{x}»?"
|
||||||
resetAreYouSure: "Segur que vols restablir-ho?"
|
resetAreYouSure: "Segur que voleu restablir-ho?"
|
||||||
areYouSure: "Estàs segur?"
|
areYouSure: "Està segur?"
|
||||||
saved: "S'ha desat"
|
saved: "S'ha desat"
|
||||||
messaging: "Xat"
|
messaging: "Xat"
|
||||||
upload: "Puja"
|
upload: "Puja"
|
||||||
keepOriginalUploading: "Guarda la imatge original"
|
keepOriginalUploading: "Guarda la imatge original"
|
||||||
keepOriginalUploadingDescription: "Guarda la imatge pujada sense modificar. Si està desactivat, es generarà una versió per visualitzar a la web en pujar la imatge."
|
keepOriginalUploadingDescription: "Guarda la imatge pujada com hi és. Si està apagat, una versió per a la visualització a la xarxa serà generada quan sigui pujada."
|
||||||
fromDrive: "Des del Disc"
|
fromDrive: "Des de la unitat"
|
||||||
fromUrl: "Des d'un enllaç"
|
fromUrl: "Des d'un enllaç"
|
||||||
uploadFromUrl: "Carrega des d'un enllaç"
|
uploadFromUrl: "Carrega des d'un enllaç"
|
||||||
uploadFromUrlDescription: "Enllaç del fitxer que vols carregar"
|
uploadFromUrlDescription: "Enllaç del fitxer que vols carregar"
|
||||||
uploadFromUrlRequested: "Càrrega sol·licitada"
|
uploadFromUrlRequested: "Càrrega sol·licitada"
|
||||||
uploadFromUrlMayTakeTime: "La càrrega des de l'enllaç pot trigar un temps"
|
uploadFromUrlMayTakeTime: "La càrrega des de l'enllaç pot prendre un temps"
|
||||||
explore: "Explora"
|
explore: "Explora"
|
||||||
messageRead: "Vist"
|
messageRead: "Vist"
|
||||||
noMoreHistory: "No hi ha res més per veure"
|
noMoreHistory: "No hi resta més per veure"
|
||||||
startMessaging: "Comença a xatejar"
|
startMessaging: "Començar a xatejar"
|
||||||
nUsersRead: "Vist per {n}"
|
nUsersRead: "Vist per {n}"
|
||||||
agreeTo: "Accepto que {0}"
|
agreeTo: "Accepto que {0}"
|
||||||
agree: "Hi estic d'acord"
|
agree: "Hi estic d'acord"
|
||||||
|
@ -312,7 +312,7 @@ home: "Inici"
|
||||||
remoteUserCaution: "Ja que aquest usuari resideix a una instància remota, la informació mostrada es podria trobar incompleta."
|
remoteUserCaution: "Ja que aquest usuari resideix a una instància remota, la informació mostrada es podria trobar incompleta."
|
||||||
activity: "Activitat"
|
activity: "Activitat"
|
||||||
images: "Imatges"
|
images: "Imatges"
|
||||||
image: "Imatge"
|
image: "Imatges"
|
||||||
birthday: "Aniversari"
|
birthday: "Aniversari"
|
||||||
yearsOld: "{age} anys"
|
yearsOld: "{age} anys"
|
||||||
registeredDate: "Data de registre"
|
registeredDate: "Data de registre"
|
||||||
|
@ -327,10 +327,10 @@ darkThemes: "Temes foscos"
|
||||||
syncDeviceDarkMode: "Sincronitza el mode fosc amb la configuració del dispositiu"
|
syncDeviceDarkMode: "Sincronitza el mode fosc amb la configuració del dispositiu"
|
||||||
drive: "Unitat"
|
drive: "Unitat"
|
||||||
fileName: "Nom del Fitxer"
|
fileName: "Nom del Fitxer"
|
||||||
selectFile: "Selecciona un fitxer"
|
selectFile: "Selecciona fitxers"
|
||||||
selectFiles: "Selecciona fitxers"
|
selectFiles: "Selecciona fitxers"
|
||||||
selectFolder: "Selecció de carpeta"
|
selectFolder: "Selecció de carpeta"
|
||||||
selectFolders: "Selecció de carpetes"
|
selectFolders: "Selecció de carpeta"
|
||||||
fileNotSelected: "Cap fitxer seleccionat"
|
fileNotSelected: "Cap fitxer seleccionat"
|
||||||
renameFile: "Canvia el nom del fitxer"
|
renameFile: "Canvia el nom del fitxer"
|
||||||
folderName: "Nom de la carpeta"
|
folderName: "Nom de la carpeta"
|
||||||
|
@ -359,9 +359,9 @@ reload: "Actualitza"
|
||||||
doNothing: "Ignora"
|
doNothing: "Ignora"
|
||||||
reloadConfirm: "Vols recarregar?"
|
reloadConfirm: "Vols recarregar?"
|
||||||
watch: "Veure"
|
watch: "Veure"
|
||||||
unwatch: "Deixa de veure"
|
unwatch: "Deixar de veure"
|
||||||
accept: "Acceptar"
|
accept: "Acceptar"
|
||||||
reject: "Denega"
|
reject: "Denegar"
|
||||||
normal: "Normal"
|
normal: "Normal"
|
||||||
instanceName: "Nom del servidor"
|
instanceName: "Nom del servidor"
|
||||||
instanceDescription: "Descripció del servidor"
|
instanceDescription: "Descripció del servidor"
|
||||||
|
@ -382,7 +382,7 @@ enableLocalTimeline: "Activa la línia de temps local"
|
||||||
enableGlobalTimeline: "Activa la línia de temps global"
|
enableGlobalTimeline: "Activa la línia de temps global"
|
||||||
disablingTimelinesInfo: "Fins i tot si aquestes línies de temps són desactivades, els administradors i els moderadors poden continuar visualitzant per conveniència."
|
disablingTimelinesInfo: "Fins i tot si aquestes línies de temps són desactivades, els administradors i els moderadors poden continuar visualitzant per conveniència."
|
||||||
registration: "Registre"
|
registration: "Registre"
|
||||||
enableRegistration: "Permet el registre de nous usuaris"
|
enableRegistration: "Permet els registres d'usuaris"
|
||||||
invite: "Convida"
|
invite: "Convida"
|
||||||
driveCapacityPerLocalAccount: "Capacitat del disc per usuaris locals"
|
driveCapacityPerLocalAccount: "Capacitat del disc per usuaris locals"
|
||||||
driveCapacityPerRemoteAccount: "Capacitat del disc per usuaris remots"
|
driveCapacityPerRemoteAccount: "Capacitat del disc per usuaris remots"
|
||||||
|
@ -393,20 +393,20 @@ basicInfo: "Informació bàsica"
|
||||||
pinnedUsers: "Usuaris fixats"
|
pinnedUsers: "Usuaris fixats"
|
||||||
pinnedUsersDescription: "Llista d'usuaris, separats per salts de línia, que seran fixats a la pestanya \"Explorar\"."
|
pinnedUsersDescription: "Llista d'usuaris, separats per salts de línia, que seran fixats a la pestanya \"Explorar\"."
|
||||||
pinnedPages: "Pàgines fixades"
|
pinnedPages: "Pàgines fixades"
|
||||||
pinnedPagesDescription: "Escriu les adreces de les pàgines que vols fixar a la pàgina d'inici d'aquesta instància. Separades per salts de línia."
|
pinnedPagesDescription: "Escriu els camins de les pàgines que vols fixar a la pàgina d'inici d'aquesta instància. Separades per salts de línia."
|
||||||
pinnedClipId: "ID del retall fixat"
|
pinnedClipId: "ID del retall fixat"
|
||||||
pinnedNotes: "Nota fixada"
|
pinnedNotes: "Nota fixada"
|
||||||
hcaptcha: "hCaptcha"
|
hcaptcha: "hCaptcha"
|
||||||
enableHcaptcha: "Activa hCaptcha"
|
enableHcaptcha: "Activar hCaptcha"
|
||||||
hcaptchaSiteKey: "Clau del lloc"
|
hcaptchaSiteKey: "Clau del lloc"
|
||||||
hcaptchaSecretKey: "Clau secreta"
|
hcaptchaSecretKey: "Clau secreta"
|
||||||
mcaptcha: "mCaptcha"
|
mcaptcha: "mCaptcha"
|
||||||
enableMcaptcha: "Activa mCaptcha"
|
enableMcaptcha: "Activar mCaptcha"
|
||||||
mcaptchaSiteKey: "Clau del lloc"
|
mcaptchaSiteKey: "Clau del lloc"
|
||||||
mcaptchaSecretKey: "Clau secreta"
|
mcaptchaSecretKey: "Clau secreta"
|
||||||
mcaptchaInstanceUrl: "Adreça URL del servidor mCaptcha"
|
mcaptchaInstanceUrl: "Adreça URL del servidor mCaptcha"
|
||||||
recaptcha: "reCAPTCHA"
|
recaptcha: "reCAPTCHA"
|
||||||
enableRecaptcha: "Activa reCAPTCHA"
|
enableRecaptcha: "Activar reCAPTCHA"
|
||||||
recaptchaSiteKey: "Clau del lloc"
|
recaptchaSiteKey: "Clau del lloc"
|
||||||
recaptchaSecretKey: "Clau secreta"
|
recaptchaSecretKey: "Clau secreta"
|
||||||
turnstile: "Turnstile"
|
turnstile: "Turnstile"
|
||||||
|
@ -448,14 +448,14 @@ aboutMisskey: "Quant a Misskey"
|
||||||
administrator: "Administrador/a"
|
administrator: "Administrador/a"
|
||||||
token: "Codi de verificació"
|
token: "Codi de verificació"
|
||||||
2fa: "Autenticació de doble factor"
|
2fa: "Autenticació de doble factor"
|
||||||
setupOf2fa: "Configura l'autenticació de doble factor"
|
setupOf2fa: "Configurar l'autenticació de doble factor"
|
||||||
totp: "Aplicació d'autenticació"
|
totp: "Aplicació d'autenticació"
|
||||||
totpDescription: "Escriu una contrasenya d'un sol us fent servir l'aplicació d'autenticació"
|
totpDescription: "Escriu una contrasenya d'un sol us fent servir l'aplicació d'autenticació"
|
||||||
moderator: "Moderador/a"
|
moderator: "Moderador/a"
|
||||||
moderation: "Moderació"
|
moderation: "Moderació"
|
||||||
moderationNote: "Nota de moderació "
|
moderationNote: "Nota de moderació "
|
||||||
moderationNoteDescription: "Pots escriure notes que es compartiran entre els moderadors."
|
moderationNoteDescription: "Pots escriure notes que es compartiran entre els moderadors."
|
||||||
addModerationNote: "Afegeix una nota de moderació "
|
addModerationNote: "Afegir una nota de moderació "
|
||||||
moderationLogs: "Registre de moderació "
|
moderationLogs: "Registre de moderació "
|
||||||
nUsersMentioned: "{n} usuaris mencionats"
|
nUsersMentioned: "{n} usuaris mencionats"
|
||||||
securityKeyAndPasskey: "Clau de seguretat / Clau de pas"
|
securityKeyAndPasskey: "Clau de seguretat / Clau de pas"
|
||||||
|
@ -471,13 +471,13 @@ reduceUiAnimation: "Redueix les animacions de la interfície"
|
||||||
share: "Comparteix"
|
share: "Comparteix"
|
||||||
notFound: "No s'ha trobat"
|
notFound: "No s'ha trobat"
|
||||||
notFoundDescription: "No es troba cap pàgina que correspongui a aquesta adreça"
|
notFoundDescription: "No es troba cap pàgina que correspongui a aquesta adreça"
|
||||||
uploadFolder: "Carpeta per defecte on desar els arxius pujats"
|
uploadFolder: "Carpeta per defecte per pujades"
|
||||||
markAsReadAllNotifications: "Marca totes les notificacions com a llegides"
|
markAsReadAllNotifications: "Marca totes les notificacions com a llegides"
|
||||||
markAsReadAllUnreadNotes: "Marca-ho tot com a llegit"
|
markAsReadAllUnreadNotes: "Marca-ho tot com a llegit"
|
||||||
markAsReadAllTalkMessages: "Marcar tots els missatges com llegits"
|
markAsReadAllTalkMessages: "Marcar tots els missatges com llegits"
|
||||||
help: "Ajuda"
|
help: "Ajuda"
|
||||||
inputMessageHere: "Escriu aquí el teu missatge "
|
inputMessageHere: "Escriu aquí el teu missatge "
|
||||||
close: "Tanca"
|
close: "Tancar"
|
||||||
invites: "Convida"
|
invites: "Convida"
|
||||||
members: "Membres"
|
members: "Membres"
|
||||||
transfer: "Transferir"
|
transfer: "Transferir"
|
||||||
|
@ -508,7 +508,7 @@ normalPassword: "Bona contrasenya"
|
||||||
strongPassword: "Contrasenya segura"
|
strongPassword: "Contrasenya segura"
|
||||||
passwordMatched: "Correcte!"
|
passwordMatched: "Correcte!"
|
||||||
passwordNotMatched: "No coincideix"
|
passwordNotMatched: "No coincideix"
|
||||||
signinWith: "Inicia sessió amb {x}"
|
signinWith: "Inicia sessió amb amb {x}"
|
||||||
signinFailed: "Autenticació sense èxit. Intenta-ho un altre cop utilitzant la contrasenya i el nom correctes."
|
signinFailed: "Autenticació sense èxit. Intenta-ho un altre cop utilitzant la contrasenya i el nom correctes."
|
||||||
or: "O"
|
or: "O"
|
||||||
language: "Idioma"
|
language: "Idioma"
|
||||||
|
@ -591,7 +591,7 @@ chooseEmoji: "Tria un emoji"
|
||||||
unableToProcess: "L'operació no pot ser completada "
|
unableToProcess: "L'operació no pot ser completada "
|
||||||
recentUsed: "Utilitzat recentment"
|
recentUsed: "Utilitzat recentment"
|
||||||
install: "Instal·lació "
|
install: "Instal·lació "
|
||||||
uninstall: "Desinstal·la"
|
uninstall: "Desinstal·lar "
|
||||||
installedApps: "Aplicacions autoritzades "
|
installedApps: "Aplicacions autoritzades "
|
||||||
nothing: "No hi ha res per veure aquí "
|
nothing: "No hi ha res per veure aquí "
|
||||||
installedDate: "Data d'instal·lació"
|
installedDate: "Data d'instal·lació"
|
||||||
|
@ -608,13 +608,13 @@ output: "Sortida"
|
||||||
script: "Script"
|
script: "Script"
|
||||||
disablePagesScript: "Desactivar AiScript a les pàgines "
|
disablePagesScript: "Desactivar AiScript a les pàgines "
|
||||||
updateRemoteUser: "Actualitzar la informació de l'usuari remot"
|
updateRemoteUser: "Actualitzar la informació de l'usuari remot"
|
||||||
unsetUserAvatar: "Desactiva l'avatar "
|
unsetUserAvatar: "Desactivar l'avatar "
|
||||||
unsetUserAvatarConfirm: "Segur que vols desactivar l'avatar?"
|
unsetUserAvatarConfirm: "Segur que vols desactivar l'avatar?"
|
||||||
unsetUserBanner: "Desactiva el bàner "
|
unsetUserBanner: "Desactivar el bàner "
|
||||||
unsetUserBannerConfirm: "Segur que vols desactivar el bàner?"
|
unsetUserBannerConfirm: "Segur que vols desactivar el bàner?"
|
||||||
deleteAllFiles: "Esborra tots els arxius"
|
deleteAllFiles: "Esborrar tots els arxius"
|
||||||
deleteAllFilesConfirm: "Segur que vols esborrar tots els arxius?"
|
deleteAllFilesConfirm: "Segur que vols esborrar tots els arxius?"
|
||||||
removeAllFollowing: "Deixa de seguir tots els usuaris seguits"
|
removeAllFollowing: "Deixar de seguir tots els usuaris seguits"
|
||||||
removeAllFollowingDescription: "El fet d'executar això, et farà deixar de seguir a tots els usuaris de {host}. Si us plau, executa això si l'amfitrió, per exemple, ja no existeix."
|
removeAllFollowingDescription: "El fet d'executar això, et farà deixar de seguir a tots els usuaris de {host}. Si us plau, executa això si l'amfitrió, per exemple, ja no existeix."
|
||||||
userSuspended: "Aquest usuari ha sigut suspès"
|
userSuspended: "Aquest usuari ha sigut suspès"
|
||||||
userSilenced: "Aquest usuari està sent silenciat"
|
userSilenced: "Aquest usuari està sent silenciat"
|
||||||
|
@ -1183,8 +1183,8 @@ currentAnnouncements: "Informes actuals"
|
||||||
pastAnnouncements: "Informes passats"
|
pastAnnouncements: "Informes passats"
|
||||||
youHaveUnreadAnnouncements: "Tens informes per llegir."
|
youHaveUnreadAnnouncements: "Tens informes per llegir."
|
||||||
useSecurityKey: "Segueix les instruccions del teu navegador O dispositiu per fer servir el teu passkey."
|
useSecurityKey: "Segueix les instruccions del teu navegador O dispositiu per fer servir el teu passkey."
|
||||||
replies: "Respon"
|
replies: "Respondre"
|
||||||
renotes: "Impulsar "
|
renotes: "Impulsa"
|
||||||
loadReplies: "Mostrar les respostes"
|
loadReplies: "Mostrar les respostes"
|
||||||
loadConversation: "Mostrar la conversació "
|
loadConversation: "Mostrar la conversació "
|
||||||
pinnedList: "Llista fixada"
|
pinnedList: "Llista fixada"
|
||||||
|
@ -1299,7 +1299,6 @@ yourNameContainsProhibitedWordsDescription: "Si de veritat vols fer servir aques
|
||||||
thisContentsAreMarkedAsSigninRequiredByAuthor: "L'autor requereix l'inici de sessió per poder veure"
|
thisContentsAreMarkedAsSigninRequiredByAuthor: "L'autor requereix l'inici de sessió per poder veure"
|
||||||
lockdown: "Bloquejat"
|
lockdown: "Bloquejat"
|
||||||
pleaseSelectAccount: "Seleccionar un compte"
|
pleaseSelectAccount: "Seleccionar un compte"
|
||||||
availableRoles: "Roles disponibles "
|
|
||||||
_accountSettings:
|
_accountSettings:
|
||||||
requireSigninToViewContents: "És obligatori l'inici de sessió per poder veure el contingut"
|
requireSigninToViewContents: "És obligatori l'inici de sessió per poder veure el contingut"
|
||||||
requireSigninToViewContentsDescription1: "Es requereix l'inici de sessió per poder veure totes les notes i el contingut que has creat. Amb això esperem evitar que els rastrejadors recopilin informació."
|
requireSigninToViewContentsDescription1: "Es requereix l'inici de sessió per poder veure totes les notes i el contingut que has creat. Amb això esperem evitar que els rastrejadors recopilin informació."
|
||||||
|
|
|
@ -331,7 +331,6 @@ selectFile: "Select a file"
|
||||||
selectFiles: "Select files"
|
selectFiles: "Select files"
|
||||||
selectFolder: "Select a folder"
|
selectFolder: "Select a folder"
|
||||||
selectFolders: "Select folders"
|
selectFolders: "Select folders"
|
||||||
fileNotSelected: "No file selected"
|
|
||||||
renameFile: "Rename file"
|
renameFile: "Rename file"
|
||||||
folderName: "Folder name"
|
folderName: "Folder name"
|
||||||
createFolder: "Create a folder"
|
createFolder: "Create a folder"
|
||||||
|
@ -1299,7 +1298,6 @@ yourNameContainsProhibitedWordsDescription: "If you wish to use this name, pleas
|
||||||
thisContentsAreMarkedAsSigninRequiredByAuthor: "Set by the author to require login to view"
|
thisContentsAreMarkedAsSigninRequiredByAuthor: "Set by the author to require login to view"
|
||||||
lockdown: "Lockdown"
|
lockdown: "Lockdown"
|
||||||
pleaseSelectAccount: "Select an account"
|
pleaseSelectAccount: "Select an account"
|
||||||
availableRoles: "Available roles"
|
|
||||||
_accountSettings:
|
_accountSettings:
|
||||||
requireSigninToViewContents: "Require sign-in to view contents"
|
requireSigninToViewContents: "Require sign-in to view contents"
|
||||||
requireSigninToViewContentsDescription1: "Require login to view all notes and other content you have created. This will have the effect of preventing crawlers from collecting your information."
|
requireSigninToViewContentsDescription1: "Require login to view all notes and other content you have created. This will have the effect of preventing crawlers from collecting your information."
|
||||||
|
@ -2732,9 +2730,3 @@ _embedCodeGen:
|
||||||
generateCode: "Generate embed code"
|
generateCode: "Generate embed code"
|
||||||
codeGenerated: "The code has been generated"
|
codeGenerated: "The code has been generated"
|
||||||
codeGeneratedDescription: "Paste the generated code into your website to embed the content."
|
codeGeneratedDescription: "Paste the generated code into your website to embed the content."
|
||||||
_selfXssPrevention:
|
|
||||||
warning: "WARNING"
|
|
||||||
title: "\"Paste something on this screen\" is all a scam."
|
|
||||||
description1: "If you paste something here, a malicious user could hijack your account or steal your personal information."
|
|
||||||
description2: "If you do not understand exactly what you are trying to paste, %cstop working right now and close this window."
|
|
||||||
description3: "For more information, please refer to this. {link}"
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ favorite: "즐겨찾기"
|
||||||
favorites: "즐겨찾기"
|
favorites: "즐겨찾기"
|
||||||
unfavorite: "즐겨찾기에서 제거"
|
unfavorite: "즐겨찾기에서 제거"
|
||||||
favorited: "즐겨찾기에 등록했습니다."
|
favorited: "즐겨찾기에 등록했습니다."
|
||||||
alreadyFavorited: "이미 즐겨찾기에 등록되어 있습니다."
|
alreadyFavorited: "이미 즐겨찾기에 등록했습니다."
|
||||||
cantFavorite: "즐겨찾기에 등록하지 못했습니다."
|
cantFavorite: "즐겨찾기에 등록하지 못했습니다."
|
||||||
pin: "프로필에 고정"
|
pin: "프로필에 고정"
|
||||||
unpin: "프로필에서 고정 해제"
|
unpin: "프로필에서 고정 해제"
|
||||||
|
@ -947,9 +947,6 @@ oneHour: "1시간"
|
||||||
oneDay: "1일"
|
oneDay: "1일"
|
||||||
oneWeek: "일주일"
|
oneWeek: "일주일"
|
||||||
oneMonth: "1개월"
|
oneMonth: "1개월"
|
||||||
threeMonths: "3개월"
|
|
||||||
oneYear: "1년"
|
|
||||||
threeDays: "3일"
|
|
||||||
reflectMayTakeTime: "반영되기까지 시간이 걸릴 수 있습니다."
|
reflectMayTakeTime: "반영되기까지 시간이 걸릴 수 있습니다."
|
||||||
failedToFetchAccountInformation: "계정 정보를 가져오지 못했습니다"
|
failedToFetchAccountInformation: "계정 정보를 가져오지 못했습니다"
|
||||||
rateLimitExceeded: "요청 제한 횟수를 초과하였습니다"
|
rateLimitExceeded: "요청 제한 횟수를 초과하였습니다"
|
||||||
|
@ -1289,29 +1286,13 @@ signinWithPasskey: "패스키로 로그인"
|
||||||
unknownWebAuthnKey: "등록되지 않은 패스키입니다."
|
unknownWebAuthnKey: "등록되지 않은 패스키입니다."
|
||||||
passkeyVerificationFailed: "패스키 검증을 실패했습니다."
|
passkeyVerificationFailed: "패스키 검증을 실패했습니다."
|
||||||
passkeyVerificationSucceededButPasswordlessLoginDisabled: "패스키를 검증했으나, 비밀번호 없이 로그인하기가 꺼져 있습니다."
|
passkeyVerificationSucceededButPasswordlessLoginDisabled: "패스키를 검증했으나, 비밀번호 없이 로그인하기가 꺼져 있습니다."
|
||||||
messageToFollower: "팔로워에게 보낼 메시지"
|
messageToFollower: "팔로워에 보낼 메시지"
|
||||||
target: "대상"
|
target: "대상"
|
||||||
testCaptchaWarning: "CAPTCHA를 테스트하기 위한 기능입니다. <strong>실제 환경에서는 사용하지 마세요.</strong>"
|
testCaptchaWarning: "CAPTCHA를 테스트하기 위한 기능입니다. <strong>실제 환경에서는 사용하지 마세요.</strong>"
|
||||||
prohibitedWordsForNameOfUser: "금지 단어 (사용자 이름)"
|
prohibitedWordsForNameOfUser: "금지 단어 (사용자 이름)"
|
||||||
prohibitedWordsForNameOfUserDescription: "이 목록에 포함되는 키워드가 사용자 이름에 있는 경우, 일반 사용자는 이름을 바꿀 수 없습니다. 모더레이터 권한을 가진 사용자는 제한 대상에서 제외됩니다."
|
prohibitedWordsForNameOfUserDescription: "이 목록에 포함되는 키워드가 사용자 이름에 있는 경우, 일반 사용자는 이름을 바꿀 수 없습니다. 모더레이터 권한을 가진 사용자는 제한 대상에서 제외됩니다."
|
||||||
yourNameContainsProhibitedWords: "바꾸려는 이름에 금지된 키워드가 포함되어 있습니다."
|
yourNameContainsProhibitedWords: "바꾸려는 이름에 금지된 키워드가 포함되어 있습니다."
|
||||||
yourNameContainsProhibitedWordsDescription: "이름에 금지된 키워드가 있습니다. 이름을 사용해야 하는 경우, 서버 관리자에 문의하세요."
|
yourNameContainsProhibitedWordsDescription: "이름에 금지된 키워드가 있습니다. 이름을 사용해야 하는 경우, 서버 관리자에 문의하세요."
|
||||||
thisContentsAreMarkedAsSigninRequiredByAuthor: "게시자에 의해 로그인해야 볼 수 있도록 설정되어 있습니다."
|
|
||||||
lockdown: "잠금"
|
|
||||||
pleaseSelectAccount: "계정을 선택해주세요."
|
|
||||||
availableRoles: "사용 가능한 역할"
|
|
||||||
_accountSettings:
|
|
||||||
requireSigninToViewContents: "콘텐츠 열람을 위해 로그인으 필수로 설정하기"
|
|
||||||
requireSigninToViewContentsDescription1: "자신이 작성한 모든 노트 등의 콘텐츠를 보기 위해 로그인을 필수로 설정합니다. 크롤러가 정보 수집하는 것을 방지하는 효과를 기대할 수 있습니다."
|
|
||||||
requireSigninToViewContentsDescription2: "URL 미리보기(OGP), 웹페이지에 삽입, 노트 인용을 지원하지 않는 서버에서 볼 수 없게 됩니다."
|
|
||||||
requireSigninToViewContentsDescription3: "원격 서버에 연합된 콘텐츠에는 이러한 제한이 적용되지 않을 수 있습니다."
|
|
||||||
makeNotesFollowersOnlyBefore: "과거 노트는 팔로워만 볼 수 있도록 설정하기"
|
|
||||||
makeNotesFollowersOnlyBeforeDescription: "이 기능이 활성화되어 있는 동안, 설정된 날짜 및 시간보다 과거 또는 설정된 시간이 지난 노트는 팔로워만 볼 수 있게 됩니다.비활성화하면 노트의 공개 상태도 원래대로 돌아갑니다."
|
|
||||||
makeNotesHiddenBefore: "과거 노트 비공개로 전환하기"
|
|
||||||
makeNotesHiddenBeforeDescription: "이 기능이 활성화되어 있는 동안 설정한 날짜 및 시간보다 과거 또는 설정한 시간이 지난 노트는 본인만 볼 수 있게(비공개로 전환) 됩니다. 비활성화하면 노트의 공개 상태도 원래대로 돌아갑니다."
|
|
||||||
mayNotEffectForFederatedNotes: "원격 서버에 연합된 노트에는 효과가 없을 수도 있습니다."
|
|
||||||
notesHavePassedSpecifiedPeriod: "지정한 시간이 경과된 노트"
|
|
||||||
notesOlderThanSpecifiedDateAndTime: "지정된 날짜 및 시간 이전의 노트"
|
|
||||||
_abuseUserReport:
|
_abuseUserReport:
|
||||||
forward: "전달"
|
forward: "전달"
|
||||||
forwardDescription: "익명 시스템 계정을 사용하여 리모트 서버에 신고 내용을 전달할 수 있습니다."
|
forwardDescription: "익명 시스템 계정을 사용하여 리모트 서버에 신고 내용을 전달할 수 있습니다."
|
||||||
|
@ -2176,11 +2157,8 @@ _auth:
|
||||||
permissionAsk: "이 앱은 다음의 권한을 요청합니다"
|
permissionAsk: "이 앱은 다음의 권한을 요청합니다"
|
||||||
pleaseGoBack: "앱으로 돌아가서 시도해 주세요"
|
pleaseGoBack: "앱으로 돌아가서 시도해 주세요"
|
||||||
callback: "앱으로 돌아갑니다"
|
callback: "앱으로 돌아갑니다"
|
||||||
accepted: "접근 권한이 부여되었습니다."
|
|
||||||
denied: "접근이 거부되었습니다"
|
denied: "접근이 거부되었습니다"
|
||||||
scopeUser: "다음 사용자로 활동하고 있습니다."
|
|
||||||
pleaseLogin: "어플리케이션의 접근을 허가하려면 로그인하십시오."
|
pleaseLogin: "어플리케이션의 접근을 허가하려면 로그인하십시오."
|
||||||
byClickingYouWillBeRedirectedToThisUrl: "접근을 허용하면 자동으로 다음 URL로 이동합니다."
|
|
||||||
_antennaSources:
|
_antennaSources:
|
||||||
all: "모든 노트"
|
all: "모든 노트"
|
||||||
homeTimeline: "팔로우중인 유저의 노트"
|
homeTimeline: "팔로우중인 유저의 노트"
|
||||||
|
@ -2732,9 +2710,3 @@ _embedCodeGen:
|
||||||
generateCode: "임베디드 코드를 만들기"
|
generateCode: "임베디드 코드를 만들기"
|
||||||
codeGenerated: "코드를 만들었습니다."
|
codeGenerated: "코드를 만들었습니다."
|
||||||
codeGeneratedDescription: "만들어진 코드를 웹 사이트에 붙여서 사용하세요."
|
codeGeneratedDescription: "만들어진 코드를 웹 사이트에 붙여서 사용하세요."
|
||||||
_selfXssPrevention:
|
|
||||||
warning: "경고"
|
|
||||||
title: "“이 화면에 뭔가를 붙여넣어라\"는 것은 모두 사기입니다."
|
|
||||||
description1: "여기에 무언가를 붙여넣으면 악의적인 사용자에게 계정을 탈취당하거나 개인정보를 도용당할 수 있습니다."
|
|
||||||
description2: "붙여 넣으려는 항목이 무엇인지 정확히 이해하지 못하는 경우, %c지금 바로 작업을 중단하고 이 창을 닫으십시오."
|
|
||||||
description3: "자세한 내용은 여기를 확인해 주세요. {link}"
|
|
||||||
|
|
|
@ -8,9 +8,6 @@ search: "Tìm kiếm"
|
||||||
notifications: "Thông báo"
|
notifications: "Thông báo"
|
||||||
username: "Tên người dùng"
|
username: "Tên người dùng"
|
||||||
password: "Mật khẩu"
|
password: "Mật khẩu"
|
||||||
initialPasswordForSetup: "Mật khẩu ban đầu để thiết lập"
|
|
||||||
initialPasswordIsIncorrect: "Mật khẩu ban đầu đã nhập sai"
|
|
||||||
initialPasswordForSetupDescription: "Nếu bạn tự cài đặt Misskey, hãy sử dụng mật khẩu ban đầu của bạn đã nhập trong tệp cấu hình.\nNếu bạn đang sử dụng dịch vụ nào đó giống như dịch vụ lưu trữ của Misskey, hãy sử dụng mật khẩu ban đầu được cung cấp.\nNếu bạn chưa đặt mật khẩu ban đầu, vui lòng để trống và tiếp tục."
|
|
||||||
forgotPassword: "Quên mật khẩu"
|
forgotPassword: "Quên mật khẩu"
|
||||||
fetchingAsApObject: "Đang nạp dữ liệu từ Fediverse..."
|
fetchingAsApObject: "Đang nạp dữ liệu từ Fediverse..."
|
||||||
ok: "Đồng ý"
|
ok: "Đồng ý"
|
||||||
|
|
|
@ -1299,7 +1299,6 @@ yourNameContainsProhibitedWordsDescription: "用户名内含有违禁词。若
|
||||||
thisContentsAreMarkedAsSigninRequiredByAuthor: "根据发帖者的设定,需要登录才能显示"
|
thisContentsAreMarkedAsSigninRequiredByAuthor: "根据发帖者的设定,需要登录才能显示"
|
||||||
lockdown: "锁定"
|
lockdown: "锁定"
|
||||||
pleaseSelectAccount: "请选择帐户"
|
pleaseSelectAccount: "请选择帐户"
|
||||||
availableRoles: "可用角色"
|
|
||||||
_accountSettings:
|
_accountSettings:
|
||||||
requireSigninToViewContents: "需要登录才能显示内容"
|
requireSigninToViewContents: "需要登录才能显示内容"
|
||||||
requireSigninToViewContentsDescription1: "您发布的所有帖子将变成需要登入后才会显示。有望防止爬虫收集各种信息。"
|
requireSigninToViewContentsDescription1: "您发布的所有帖子将变成需要登入后才会显示。有望防止爬虫收集各种信息。"
|
||||||
|
|
|
@ -8,8 +8,8 @@ search: "搜尋"
|
||||||
notifications: "通知"
|
notifications: "通知"
|
||||||
username: "使用者名稱"
|
username: "使用者名稱"
|
||||||
password: "密碼"
|
password: "密碼"
|
||||||
initialPasswordForSetup: "啟動初始設定的密碼"
|
initialPasswordForSetup: "初始設定用的密碼"
|
||||||
initialPasswordIsIncorrect: "啟動初始設定的密碼錯誤。"
|
initialPasswordIsIncorrect: "初始設定用的密碼錯誤。"
|
||||||
initialPasswordForSetupDescription: "如果您自己安裝了 Misskey,請使用您在設定檔中輸入的密碼。\n如果您使用 Misskey 的託管服務之類的服務,請使用提供的密碼。\n如果您尚未設定密碼,請將其留空並繼續。"
|
initialPasswordForSetupDescription: "如果您自己安裝了 Misskey,請使用您在設定檔中輸入的密碼。\n如果您使用 Misskey 的託管服務之類的服務,請使用提供的密碼。\n如果您尚未設定密碼,請將其留空並繼續。"
|
||||||
forgotPassword: "忘記密碼"
|
forgotPassword: "忘記密碼"
|
||||||
fetchingAsApObject: "從聯邦宇宙取得中..."
|
fetchingAsApObject: "從聯邦宇宙取得中..."
|
||||||
|
@ -1299,7 +1299,6 @@ yourNameContainsProhibitedWordsDescription: "名稱中包含禁止使用的字
|
||||||
thisContentsAreMarkedAsSigninRequiredByAuthor: "作者將其設定為需要登入才能顯示。"
|
thisContentsAreMarkedAsSigninRequiredByAuthor: "作者將其設定為需要登入才能顯示。"
|
||||||
lockdown: "鎖定"
|
lockdown: "鎖定"
|
||||||
pleaseSelectAccount: "請選擇帳戶"
|
pleaseSelectAccount: "請選擇帳戶"
|
||||||
availableRoles: "可用角色"
|
|
||||||
_accountSettings:
|
_accountSettings:
|
||||||
requireSigninToViewContents: "須登入以顯示內容"
|
requireSigninToViewContents: "須登入以顯示內容"
|
||||||
requireSigninToViewContentsDescription1: "必須登入才會顯示您建立的貼文等內容。可望有效防止資訊被爬蟲蒐集。"
|
requireSigninToViewContentsDescription1: "必須登入才會顯示您建立的貼文等內容。可望有效防止資訊被爬蟲蒐集。"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"version": "2024.10.2-alpha.1",
|
"version": "2024.11.0-alpha.0",
|
||||||
"codename": "nasubi",
|
"codename": "nasubi",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
|
@ -406,8 +406,10 @@ export class MfmService {
|
||||||
mention: (node) => {
|
mention: (node) => {
|
||||||
const a = doc.createElement('a');
|
const a = doc.createElement('a');
|
||||||
const { username, host, acct } = node.props;
|
const { username, host, acct } = node.props;
|
||||||
const remoteUserInfo = mentionedRemoteUsers.find(remoteUser => remoteUser.username === username && remoteUser.host === host);
|
const remoteUserInfo = mentionedRemoteUsers.find(remoteUser => remoteUser.username.toLowerCase() === username.toLowerCase() && remoteUser.host?.toLowerCase() === host?.toLowerCase());
|
||||||
a.setAttribute('href', remoteUserInfo ? (remoteUserInfo.url ? remoteUserInfo.url : remoteUserInfo.uri) : `${this.config.url}/${acct}`);
|
a.setAttribute('href', remoteUserInfo
|
||||||
|
? (remoteUserInfo.url ? remoteUserInfo.url : remoteUserInfo.uri)
|
||||||
|
: `${this.config.url}/${acct.endsWith(`@${this.config.url}`) ? acct.substring(0, acct.length - this.config.url.length - 1) : acct}`);
|
||||||
a.className = 'u-url mention';
|
a.className = 'u-url mention';
|
||||||
a.textContent = acct;
|
a.textContent = acct;
|
||||||
return a;
|
return a;
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { randomUUID } from 'node:crypto';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import type { IActivity } from '@/core/activitypub/type.js';
|
import type { IActivity } from '@/core/activitypub/type.js';
|
||||||
import type { MiDriveFile } from '@/models/DriveFile.js';
|
import type { MiDriveFile } from '@/models/DriveFile.js';
|
||||||
import type { MiWebhook, webhookEventTypes } from '@/models/Webhook.js';
|
import type { MiWebhook, WebhookEventTypes, webhookEventTypes } from '@/models/Webhook.js';
|
||||||
import type { MiSystemWebhook, SystemWebhookEventType } from '@/models/SystemWebhook.js';
|
import type { MiSystemWebhook, SystemWebhookEventType } from '@/models/SystemWebhook.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
|
@ -35,6 +35,7 @@ import type {
|
||||||
} from './QueueModule.js';
|
} from './QueueModule.js';
|
||||||
import type httpSignature from '@peertube/http-signature';
|
import type httpSignature from '@peertube/http-signature';
|
||||||
import type * as Bull from 'bullmq';
|
import type * as Bull from 'bullmq';
|
||||||
|
import { type UserWebhookPayload } from './UserWebhookService.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class QueueService {
|
export class QueueService {
|
||||||
|
@ -468,10 +469,10 @@ export class QueueService {
|
||||||
* @see UserWebhookDeliverProcessorService
|
* @see UserWebhookDeliverProcessorService
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
public userWebhookDeliver(
|
public userWebhookDeliver<T extends WebhookEventTypes>(
|
||||||
webhook: MiWebhook,
|
webhook: MiWebhook,
|
||||||
type: typeof webhookEventTypes[number],
|
type: T,
|
||||||
content: unknown,
|
content: UserWebhookPayload<T>,
|
||||||
opts?: { attempts?: number },
|
opts?: { attempts?: number },
|
||||||
) {
|
) {
|
||||||
const data: UserWebhookDeliverJobData = {
|
const data: UserWebhookDeliverJobData = {
|
||||||
|
|
|
@ -6,11 +6,23 @@
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import * as Redis from 'ioredis';
|
import * as Redis from 'ioredis';
|
||||||
import { type WebhooksRepository } from '@/models/_.js';
|
import { type WebhooksRepository } from '@/models/_.js';
|
||||||
import { MiWebhook } from '@/models/Webhook.js';
|
import { MiWebhook, WebhookEventTypes } from '@/models/Webhook.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { GlobalEvents } from '@/core/GlobalEventService.js';
|
import { GlobalEvents } from '@/core/GlobalEventService.js';
|
||||||
import type { OnApplicationShutdown } from '@nestjs/common';
|
import type { OnApplicationShutdown } from '@nestjs/common';
|
||||||
|
import type { Packed } from '@/misc/json-schema.js';
|
||||||
|
|
||||||
|
export type UserWebhookPayload<T extends WebhookEventTypes> =
|
||||||
|
T extends 'note' | 'reply' | 'renote' |'mention' ? {
|
||||||
|
note: Packed<'Note'>,
|
||||||
|
} :
|
||||||
|
T extends 'follow' | 'unfollow' ? {
|
||||||
|
user: Packed<'UserDetailedNotMe'>,
|
||||||
|
} :
|
||||||
|
T extends 'followed' ? {
|
||||||
|
user: Packed<'UserLite'>,
|
||||||
|
} : never;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserWebhookService implements OnApplicationShutdown {
|
export class UserWebhookService implements OnApplicationShutdown {
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { MiSystemWebhook, type SystemWebhookEventType } from '@/models/SystemWeb
|
||||||
import { SystemWebhookService } from '@/core/SystemWebhookService.js';
|
import { SystemWebhookService } from '@/core/SystemWebhookService.js';
|
||||||
import { Packed } from '@/misc/json-schema.js';
|
import { Packed } from '@/misc/json-schema.js';
|
||||||
import { type WebhookEventTypes } from '@/models/Webhook.js';
|
import { type WebhookEventTypes } from '@/models/Webhook.js';
|
||||||
import { UserWebhookService } from '@/core/UserWebhookService.js';
|
import { type UserWebhookPayload, UserWebhookService } from '@/core/UserWebhookService.js';
|
||||||
import { QueueService } from '@/core/QueueService.js';
|
import { QueueService } from '@/core/QueueService.js';
|
||||||
import { ModeratorInactivityRemainingTime } from '@/queue/processors/CheckModeratorsActivityProcessorService.js';
|
import { ModeratorInactivityRemainingTime } from '@/queue/processors/CheckModeratorsActivityProcessorService.js';
|
||||||
|
|
||||||
|
@ -306,10 +306,10 @@ export class WebhookTestService {
|
||||||
* - 送信対象イベント(on)に関する設定
|
* - 送信対象イベント(on)に関する設定
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
public async testUserWebhook(
|
public async testUserWebhook<T extends WebhookEventTypes>(
|
||||||
params: {
|
params: {
|
||||||
webhookId: MiWebhook['id'],
|
webhookId: MiWebhook['id'],
|
||||||
type: WebhookEventTypes,
|
type: T,
|
||||||
override?: Partial<Omit<MiWebhook, 'id'>>,
|
override?: Partial<Omit<MiWebhook, 'id'>>,
|
||||||
},
|
},
|
||||||
sender: MiUser | null,
|
sender: MiUser | null,
|
||||||
|
@ -321,7 +321,7 @@ export class WebhookTestService {
|
||||||
}
|
}
|
||||||
|
|
||||||
const webhook = webhooks[0];
|
const webhook = webhooks[0];
|
||||||
const send = (contents: unknown) => {
|
const send = <U extends WebhookEventTypes>(type: U, contents: UserWebhookPayload<U>) => {
|
||||||
const merged = {
|
const merged = {
|
||||||
...webhook,
|
...webhook,
|
||||||
...params.override,
|
...params.override,
|
||||||
|
@ -329,7 +329,7 @@ export class WebhookTestService {
|
||||||
|
|
||||||
// テスト目的なのでUserWebhookServiceの機能を経由せず直接キューに追加する(チェック処理などをスキップする意図).
|
// テスト目的なのでUserWebhookServiceの機能を経由せず直接キューに追加する(チェック処理などをスキップする意図).
|
||||||
// また、Jobの試行回数も1回だけ.
|
// また、Jobの試行回数も1回だけ.
|
||||||
this.queueService.userWebhookDeliver(merged, params.type, contents, { attempts: 1 });
|
this.queueService.userWebhookDeliver(merged, type, contents, { attempts: 1 });
|
||||||
};
|
};
|
||||||
|
|
||||||
const dummyNote1 = generateDummyNote({
|
const dummyNote1 = generateDummyNote({
|
||||||
|
@ -361,33 +361,40 @@ export class WebhookTestService {
|
||||||
|
|
||||||
switch (params.type) {
|
switch (params.type) {
|
||||||
case 'note': {
|
case 'note': {
|
||||||
send(toPackedNote(dummyNote1));
|
send('note', { note: toPackedNote(dummyNote1) });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'reply': {
|
case 'reply': {
|
||||||
send(toPackedNote(dummyReply1));
|
send('reply', { note: toPackedNote(dummyReply1) });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'renote': {
|
case 'renote': {
|
||||||
send(toPackedNote(dummyRenote1));
|
send('renote', { note: toPackedNote(dummyRenote1) });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'mention': {
|
case 'mention': {
|
||||||
send(toPackedNote(dummyMention1));
|
send('mention', { note: toPackedNote(dummyMention1) });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'follow': {
|
case 'follow': {
|
||||||
send(toPackedUserDetailedNotMe(dummyUser1));
|
send('follow', { user: toPackedUserDetailedNotMe(dummyUser1) });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'followed': {
|
case 'followed': {
|
||||||
send(toPackedUserLite(dummyUser2));
|
send('followed', { user: toPackedUserLite(dummyUser2) });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'unfollow': {
|
case 'unfollow': {
|
||||||
send(toPackedUserDetailedNotMe(dummyUser3));
|
send('unfollow', { user: toPackedUserDetailedNotMe(dummyUser3) });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
// まだ実装されていない (#9485)
|
||||||
|
case 'reaction': return;
|
||||||
|
default: {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const _exhaustiveAssertion: never = params.type;
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,5 +4,5 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function sqlLikeEscape(s: string) {
|
export function sqlLikeEscape(s: string) {
|
||||||
return s.replace(/([%_])/g, '\\$1');
|
return s.replace(/([\\%_])/g, '\\$1');
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { IActivity } from '@/core/activitypub/type.js';
|
import { IActivity } from '@/core/activitypub/type.js';
|
||||||
import { isQuote, isRenote } from '@/misc/is-renote.js';
|
import { isQuote, isRenote } from '@/misc/is-renote.js';
|
||||||
|
import * as Acct from '@/misc/acct.js';
|
||||||
import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions, FastifyBodyParser } from 'fastify';
|
import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions, FastifyBodyParser } from 'fastify';
|
||||||
import type { FindOptionsWhere } from 'typeorm';
|
import type { FindOptionsWhere } from 'typeorm';
|
||||||
|
|
||||||
|
@ -486,6 +487,16 @@ export class ActivityPubServerService {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// リモートだったらリダイレクト
|
||||||
|
if (user.host != null) {
|
||||||
|
if (user.uri == null || this.utilityService.isSelfHost(user.host)) {
|
||||||
|
reply.code(500);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
reply.redirect(user.uri, 301);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
reply.header('Cache-Control', 'public, max-age=180');
|
reply.header('Cache-Control', 'public, max-age=180');
|
||||||
this.setResponseType(request, reply);
|
this.setResponseType(request, reply);
|
||||||
return (this.apRendererService.addContext(await this.apRendererService.renderPerson(user as MiLocalUser)));
|
return (this.apRendererService.addContext(await this.apRendererService.renderPerson(user as MiLocalUser)));
|
||||||
|
@ -654,19 +665,20 @@ export class ActivityPubServerService {
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({
|
const user = await this.usersRepository.findOneBy({
|
||||||
id: userId,
|
id: userId,
|
||||||
host: IsNull(),
|
|
||||||
isSuspended: false,
|
isSuspended: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
return await this.userInfo(request, reply, user);
|
return await this.userInfo(request, reply, user);
|
||||||
});
|
});
|
||||||
|
|
||||||
fastify.get<{ Params: { user: string; } }>('/@:user', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => {
|
fastify.get<{ Params: { acct: string; } }>('/@:acct', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => {
|
||||||
vary(reply.raw, 'Accept');
|
vary(reply.raw, 'Accept');
|
||||||
|
|
||||||
|
const acct = Acct.parse(request.params.acct);
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({
|
const user = await this.usersRepository.findOneBy({
|
||||||
usernameLower: request.params.user.toLowerCase(),
|
usernameLower: acct.username,
|
||||||
host: IsNull(),
|
host: acct.host ?? IsNull(),
|
||||||
isSuspended: false,
|
isSuspended: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -465,6 +465,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
const newName = updates.name === undefined ? user.name : updates.name;
|
const newName = updates.name === undefined ? user.name : updates.name;
|
||||||
const newDescription = profileUpdates.description === undefined ? profile.description : profileUpdates.description;
|
const newDescription = profileUpdates.description === undefined ? profile.description : profileUpdates.description;
|
||||||
const newFields = profileUpdates.fields === undefined ? profile.fields : profileUpdates.fields;
|
const newFields = profileUpdates.fields === undefined ? profile.fields : profileUpdates.fields;
|
||||||
|
const newFollowedMessage = profileUpdates.followedMessage === undefined ? profile.followedMessage : profileUpdates.followedMessage;
|
||||||
|
|
||||||
if (newName != null) {
|
if (newName != null) {
|
||||||
let hasProhibitedWords = false;
|
let hasProhibitedWords = false;
|
||||||
|
@ -494,6 +495,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (newFollowedMessage != null) {
|
||||||
|
const tokens = mfm.parse(newFollowedMessage);
|
||||||
|
emojis = emojis.concat(extractCustomEmojisFromMfm(tokens));
|
||||||
|
}
|
||||||
|
|
||||||
updates.emojis = emojis;
|
updates.emojis = emojis;
|
||||||
updates.tags = tags;
|
updates.tags = tags;
|
||||||
|
|
||||||
|
|
|
@ -112,7 +112,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
|
|
||||||
this.activeUsersChart.read(me);
|
this.activeUsersChart.read(me);
|
||||||
|
|
||||||
await this.noteEntityService.packMany(timeline, me);
|
return await this.noteEntityService.packMany(timeline, me);
|
||||||
}
|
}
|
||||||
|
|
||||||
const timeline = await this.fanoutTimelineEndpointService.timeline({
|
const timeline = await this.fanoutTimelineEndpointService.timeline({
|
||||||
|
|
|
@ -42,13 +42,26 @@ import { MetaEntityService } from '@/core/entities/MetaEntityService.js';
|
||||||
import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js';
|
import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js';
|
||||||
import { ClipEntityService } from '@/core/entities/ClipEntityService.js';
|
import { ClipEntityService } from '@/core/entities/ClipEntityService.js';
|
||||||
import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js';
|
import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js';
|
||||||
import type { ChannelsRepository, ClipsRepository, FlashsRepository, GalleryPostsRepository, MiMeta, NotesRepository, PagesRepository, ReversiGamesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
|
import type {
|
||||||
|
AnnouncementsRepository,
|
||||||
|
ChannelsRepository,
|
||||||
|
ClipsRepository,
|
||||||
|
FlashsRepository,
|
||||||
|
GalleryPostsRepository,
|
||||||
|
MiMeta,
|
||||||
|
NotesRepository,
|
||||||
|
PagesRepository,
|
||||||
|
ReversiGamesRepository,
|
||||||
|
UserProfilesRepository,
|
||||||
|
UsersRepository,
|
||||||
|
} from '@/models/_.js';
|
||||||
import type Logger from '@/logger.js';
|
import type Logger from '@/logger.js';
|
||||||
import { handleRequestRedirectToOmitSearch } from '@/misc/fastify-hook-handlers.js';
|
import { handleRequestRedirectToOmitSearch } from '@/misc/fastify-hook-handlers.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { FlashEntityService } from '@/core/entities/FlashEntityService.js';
|
import { FlashEntityService } from '@/core/entities/FlashEntityService.js';
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityService.js';
|
import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityService.js';
|
||||||
|
import { AnnouncementEntityService } from '@/core/entities/AnnouncementEntityService.js';
|
||||||
import { FeedService } from './FeedService.js';
|
import { FeedService } from './FeedService.js';
|
||||||
import { UrlPreviewService } from './UrlPreviewService.js';
|
import { UrlPreviewService } from './UrlPreviewService.js';
|
||||||
import { ClientLoggerService } from './ClientLoggerService.js';
|
import { ClientLoggerService } from './ClientLoggerService.js';
|
||||||
|
@ -103,6 +116,9 @@ export class ClientServerService {
|
||||||
@Inject(DI.reversiGamesRepository)
|
@Inject(DI.reversiGamesRepository)
|
||||||
private reversiGamesRepository: ReversiGamesRepository,
|
private reversiGamesRepository: ReversiGamesRepository,
|
||||||
|
|
||||||
|
@Inject(DI.announcementsRepository)
|
||||||
|
private announcementsRepository: AnnouncementsRepository,
|
||||||
|
|
||||||
private flashEntityService: FlashEntityService,
|
private flashEntityService: FlashEntityService,
|
||||||
private userEntityService: UserEntityService,
|
private userEntityService: UserEntityService,
|
||||||
private noteEntityService: NoteEntityService,
|
private noteEntityService: NoteEntityService,
|
||||||
|
@ -112,6 +128,7 @@ export class ClientServerService {
|
||||||
private clipEntityService: ClipEntityService,
|
private clipEntityService: ClipEntityService,
|
||||||
private channelEntityService: ChannelEntityService,
|
private channelEntityService: ChannelEntityService,
|
||||||
private reversiGameEntityService: ReversiGameEntityService,
|
private reversiGameEntityService: ReversiGameEntityService,
|
||||||
|
private announcementEntityService: AnnouncementEntityService,
|
||||||
private urlPreviewService: UrlPreviewService,
|
private urlPreviewService: UrlPreviewService,
|
||||||
private feedService: FeedService,
|
private feedService: FeedService,
|
||||||
private roleService: RoleService,
|
private roleService: RoleService,
|
||||||
|
@ -776,6 +793,24 @@ export class ClientServerService {
|
||||||
return await renderBase(reply);
|
return await renderBase(reply);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 個別お知らせページ
|
||||||
|
fastify.get<{ Params: { announcementId: string; } }>('/announcements/:announcementId', async (request, reply) => {
|
||||||
|
const announcement = await this.announcementsRepository.findOneBy({
|
||||||
|
id: request.params.announcementId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (announcement) {
|
||||||
|
const _announcement = await this.announcementEntityService.pack(announcement);
|
||||||
|
reply.header('Cache-Control', 'public, max-age=3600');
|
||||||
|
return await reply.view('announcement', {
|
||||||
|
announcement: _announcement,
|
||||||
|
...await this.generateCommonPugData(this.meta),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return await renderBase(reply);
|
||||||
|
}
|
||||||
|
});
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region noindex pages
|
//#region noindex pages
|
||||||
|
|
21
packages/backend/src/server/web/views/announcement.pug
Normal file
21
packages/backend/src/server/web/views/announcement.pug
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
extends ./base
|
||||||
|
|
||||||
|
block vars
|
||||||
|
- const title = announcement.title;
|
||||||
|
- const description = announcement.text.length > 100 ? announcement.text.slice(0, 100) + '…' : announcement.text;
|
||||||
|
- const url = `${config.url}/announcements/${announcement.id}`;
|
||||||
|
|
||||||
|
block title
|
||||||
|
= `${title} | ${instanceName}`
|
||||||
|
|
||||||
|
block desc
|
||||||
|
meta(name='description' content=description)
|
||||||
|
|
||||||
|
block og
|
||||||
|
meta(property='og:type' content='article')
|
||||||
|
meta(property='og:title' content= title)
|
||||||
|
meta(property='og:description' content= description)
|
||||||
|
meta(property='og:url' content= url)
|
||||||
|
if announcement.imageUrl
|
||||||
|
meta(property='og:image' content=announcement.imageUrl)
|
||||||
|
meta(property='twitter:card' content='summary_large_image')
|
|
@ -2,6 +2,7 @@ block vars
|
||||||
|
|
||||||
block loadClientEntry
|
block loadClientEntry
|
||||||
- const entry = config.frontendEntry;
|
- const entry = config.frontendEntry;
|
||||||
|
- const baseUrl = config.url;
|
||||||
|
|
||||||
doctype html
|
doctype html
|
||||||
|
|
||||||
|
@ -32,7 +33,7 @@ html
|
||||||
link(rel='icon' href= icon || '/favicon.ico')
|
link(rel='icon' href= icon || '/favicon.ico')
|
||||||
link(rel='apple-touch-icon' href= appleTouchIcon || '/apple-touch-icon.png')
|
link(rel='apple-touch-icon' href= appleTouchIcon || '/apple-touch-icon.png')
|
||||||
link(rel='manifest' href='/manifest.json')
|
link(rel='manifest' href='/manifest.json')
|
||||||
link(rel='search' type='application/opensearchdescription+xml' title=(title || "Misskey") href=`${url}/opensearch.xml`)
|
link(rel='search' type='application/opensearchdescription+xml' title=(title || "Misskey") href=`${baseUrl}/opensearch.xml`)
|
||||||
link(rel='prefetch' href=serverErrorImageUrl)
|
link(rel='prefetch' href=serverErrorImageUrl)
|
||||||
link(rel='prefetch' href=infoImageUrl)
|
link(rel='prefetch' href=infoImageUrl)
|
||||||
link(rel='prefetch' href=notFoundImageUrl)
|
link(rel='prefetch' href=notFoundImageUrl)
|
||||||
|
|
|
@ -230,6 +230,7 @@ describe('Webリソース', () => {
|
||||||
path: path('xxxxxxxxxx'),
|
path: path('xxxxxxxxxx'),
|
||||||
type: HTML,
|
type: HTML,
|
||||||
}));
|
}));
|
||||||
|
test.todo('HTMLとしてGETできる。(リモートユーザーでもリダイレクトせず)');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe.each([
|
describe.each([
|
||||||
|
@ -249,6 +250,7 @@ describe('Webリソース', () => {
|
||||||
path: path('xxxxxxxxxx'),
|
path: path('xxxxxxxxxx'),
|
||||||
accept,
|
accept,
|
||||||
}));
|
}));
|
||||||
|
test.todo('はオリジナルにリダイレクトされる。(リモートユーザー)');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { beforeAll, describe, jest } from '@jest/globals';
|
import { beforeAll, describe, jest } from '@jest/globals';
|
||||||
import { WebhookTestService } from '@/core/WebhookTestService.js';
|
import { WebhookTestService } from '@/core/WebhookTestService.js';
|
||||||
import { UserWebhookService } from '@/core/UserWebhookService.js';
|
import { UserWebhookPayload, UserWebhookService } from '@/core/UserWebhookService.js';
|
||||||
import { SystemWebhookService } from '@/core/SystemWebhookService.js';
|
import { SystemWebhookService } from '@/core/SystemWebhookService.js';
|
||||||
import { GlobalModule } from '@/GlobalModule.js';
|
import { GlobalModule } from '@/GlobalModule.js';
|
||||||
import { MiSystemWebhook, MiUser, MiWebhook, UserProfilesRepository, UsersRepository } from '@/models/_.js';
|
import { MiSystemWebhook, MiUser, MiWebhook, UserProfilesRepository, UsersRepository } from '@/models/_.js';
|
||||||
|
@ -122,7 +122,7 @@ describe('WebhookTestService', () => {
|
||||||
const calls = queueService.userWebhookDeliver.mock.calls[0];
|
const calls = queueService.userWebhookDeliver.mock.calls[0];
|
||||||
expect((calls[0] as any).id).toBe('dummy-webhook');
|
expect((calls[0] as any).id).toBe('dummy-webhook');
|
||||||
expect(calls[1]).toBe('note');
|
expect(calls[1]).toBe('note');
|
||||||
expect((calls[2] as any).id).toBe('dummy-note-1');
|
expect((calls[2] as UserWebhookPayload<'note'>).note.id).toBe('dummy-note-1');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('reply', async () => {
|
test('reply', async () => {
|
||||||
|
@ -131,7 +131,7 @@ describe('WebhookTestService', () => {
|
||||||
const calls = queueService.userWebhookDeliver.mock.calls[0];
|
const calls = queueService.userWebhookDeliver.mock.calls[0];
|
||||||
expect((calls[0] as any).id).toBe('dummy-webhook');
|
expect((calls[0] as any).id).toBe('dummy-webhook');
|
||||||
expect(calls[1]).toBe('reply');
|
expect(calls[1]).toBe('reply');
|
||||||
expect((calls[2] as any).id).toBe('dummy-reply-1');
|
expect((calls[2] as UserWebhookPayload<'reply'>).note.id).toBe('dummy-reply-1');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('renote', async () => {
|
test('renote', async () => {
|
||||||
|
@ -140,7 +140,7 @@ describe('WebhookTestService', () => {
|
||||||
const calls = queueService.userWebhookDeliver.mock.calls[0];
|
const calls = queueService.userWebhookDeliver.mock.calls[0];
|
||||||
expect((calls[0] as any).id).toBe('dummy-webhook');
|
expect((calls[0] as any).id).toBe('dummy-webhook');
|
||||||
expect(calls[1]).toBe('renote');
|
expect(calls[1]).toBe('renote');
|
||||||
expect((calls[2] as any).id).toBe('dummy-renote-1');
|
expect((calls[2] as UserWebhookPayload<'renote'>).note.id).toBe('dummy-renote-1');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('mention', async () => {
|
test('mention', async () => {
|
||||||
|
@ -149,7 +149,7 @@ describe('WebhookTestService', () => {
|
||||||
const calls = queueService.userWebhookDeliver.mock.calls[0];
|
const calls = queueService.userWebhookDeliver.mock.calls[0];
|
||||||
expect((calls[0] as any).id).toBe('dummy-webhook');
|
expect((calls[0] as any).id).toBe('dummy-webhook');
|
||||||
expect(calls[1]).toBe('mention');
|
expect(calls[1]).toBe('mention');
|
||||||
expect((calls[2] as any).id).toBe('dummy-mention-1');
|
expect((calls[2] as UserWebhookPayload<'mention'>).note.id).toBe('dummy-mention-1');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('follow', async () => {
|
test('follow', async () => {
|
||||||
|
@ -158,7 +158,7 @@ describe('WebhookTestService', () => {
|
||||||
const calls = queueService.userWebhookDeliver.mock.calls[0];
|
const calls = queueService.userWebhookDeliver.mock.calls[0];
|
||||||
expect((calls[0] as any).id).toBe('dummy-webhook');
|
expect((calls[0] as any).id).toBe('dummy-webhook');
|
||||||
expect(calls[1]).toBe('follow');
|
expect(calls[1]).toBe('follow');
|
||||||
expect((calls[2] as any).id).toBe('dummy-user-1');
|
expect((calls[2] as UserWebhookPayload<'follow'>).user.id).toBe('dummy-user-1');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('followed', async () => {
|
test('followed', async () => {
|
||||||
|
@ -167,7 +167,7 @@ describe('WebhookTestService', () => {
|
||||||
const calls = queueService.userWebhookDeliver.mock.calls[0];
|
const calls = queueService.userWebhookDeliver.mock.calls[0];
|
||||||
expect((calls[0] as any).id).toBe('dummy-webhook');
|
expect((calls[0] as any).id).toBe('dummy-webhook');
|
||||||
expect(calls[1]).toBe('followed');
|
expect(calls[1]).toBe('followed');
|
||||||
expect((calls[2] as any).id).toBe('dummy-user-2');
|
expect((calls[2] as UserWebhookPayload<'followed'>).user.id).toBe('dummy-user-2');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('unfollow', async () => {
|
test('unfollow', async () => {
|
||||||
|
@ -176,7 +176,7 @@ describe('WebhookTestService', () => {
|
||||||
const calls = queueService.userWebhookDeliver.mock.calls[0];
|
const calls = queueService.userWebhookDeliver.mock.calls[0];
|
||||||
expect((calls[0] as any).id).toBe('dummy-webhook');
|
expect((calls[0] as any).id).toBe('dummy-webhook');
|
||||||
expect(calls[1]).toBe('unfollow');
|
expect(calls[1]).toBe('unfollow');
|
||||||
expect((calls[2] as any).id).toBe('dummy-user-3');
|
expect((calls[2] as UserWebhookPayload<'unfollow'>).user.id).toBe('dummy-user-3');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('NoSuchWebhookError', () => {
|
describe('NoSuchWebhookError', () => {
|
||||||
|
|
|
@ -22,23 +22,66 @@ type Account = Misskey.entities.MeDetailed & { token: string };
|
||||||
const accountData = miLocalStorage.getItem('account');
|
const accountData = miLocalStorage.getItem('account');
|
||||||
|
|
||||||
// TODO: 外部からはreadonlyに
|
// TODO: 外部からはreadonlyに
|
||||||
|
/**
|
||||||
|
* Reactive state for the current account. "I" as in "I am logged in".
|
||||||
|
* Initialized from local storage if available, otherwise null.
|
||||||
|
*
|
||||||
|
* @type {Account | null}
|
||||||
|
*/
|
||||||
export const $i = accountData ? reactive(JSON.parse(accountData) as Account) : null;
|
export const $i = accountData ? reactive(JSON.parse(accountData) as Account) : null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the current account is a moderator.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
export const iAmModerator = $i != null && ($i.isAdmin === true || $i.isModerator === true);
|
export const iAmModerator = $i != null && ($i.isAdmin === true || $i.isModerator === true);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the current account is an administrator.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
export const iAmAdmin = $i != null && $i.isAdmin;
|
export const iAmAdmin = $i != null && $i.isAdmin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether it is necessary to sign in; checks if the current
|
||||||
|
* account is null and throws an error if so.
|
||||||
|
*
|
||||||
|
* @throws {Error} If the current account is null
|
||||||
|
* @returns {Account} The current account
|
||||||
|
*/
|
||||||
export function signinRequired() {
|
export function signinRequired() {
|
||||||
if ($i == null) throw new Error('signin required');
|
if ($i == null) throw new Error('signin required');
|
||||||
return $i;
|
return $i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the current number of notes from the current account.
|
||||||
|
*
|
||||||
|
* Note: This appears to only be used for the "notes1" achievement.
|
||||||
|
*
|
||||||
|
* Also, separating it like this might cause counts to get out-of-sync.
|
||||||
|
*/
|
||||||
export let notesCount = $i == null ? 0 : $i.notesCount;
|
export let notesCount = $i == null ? 0 : $i.notesCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increments the number of notes by one.
|
||||||
|
*
|
||||||
|
* Documentation TODO: What about $i.notesCount? Why not increment that?
|
||||||
|
*/
|
||||||
export function incNotesCount() {
|
export function incNotesCount() {
|
||||||
notesCount++;
|
notesCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function signout() {
|
export async function signout() {
|
||||||
if (!$i) return;
|
|
||||||
|
// If we're not signed in, there's nothing to do.
|
||||||
|
if (!$i) {
|
||||||
|
// Error log:
|
||||||
|
console.error('signout() called when not signed in');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
waiting();
|
waiting();
|
||||||
miLocalStorage.removeItem('account');
|
miLocalStorage.removeItem('account');
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { updateI18n, i18n } from '@/i18n.js';
|
||||||
import { $i, refreshAccount, login } from '@/account.js';
|
import { $i, refreshAccount, login } from '@/account.js';
|
||||||
import { defaultStore, ColdDeviceStorage } from '@/store.js';
|
import { defaultStore, ColdDeviceStorage } from '@/store.js';
|
||||||
import { fetchInstance, instance } from '@/instance.js';
|
import { fetchInstance, instance } from '@/instance.js';
|
||||||
import { deviceKind } from '@/scripts/device-kind.js';
|
import { deviceKind, updateDeviceKind } from '@/scripts/device-kind.js';
|
||||||
import { reloadChannel } from '@/scripts/unison-reload.js';
|
import { reloadChannel } from '@/scripts/unison-reload.js';
|
||||||
import { getUrlWithoutLoginId } from '@/scripts/login-id.js';
|
import { getUrlWithoutLoginId } from '@/scripts/login-id.js';
|
||||||
import { getAccountFromId } from '@/scripts/get-account-from-id.js';
|
import { getAccountFromId } from '@/scripts/get-account-from-id.js';
|
||||||
|
@ -185,6 +185,10 @@ export async function common(createVue: () => App<Element>) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
watch(defaultStore.reactiveState.overridedDeviceKind, (kind) => {
|
||||||
|
updateDeviceKind(kind);
|
||||||
|
}, { immediate: true });
|
||||||
|
|
||||||
watch(defaultStore.reactiveState.useBlurEffectForModal, v => {
|
watch(defaultStore.reactiveState.useBlurEffectForModal, v => {
|
||||||
document.documentElement.style.setProperty('--MI-modalBgFilter', v ? 'blur(4px)' : 'none');
|
document.documentElement.style.setProperty('--MI-modalBgFilter', v ? 'blur(4px)' : 'none');
|
||||||
}, { immediate: true });
|
}, { immediate: true });
|
||||||
|
|
|
@ -160,7 +160,7 @@ async function deleteAntenna() {
|
||||||
function addUser() {
|
function addUser() {
|
||||||
os.selectUser({ includeSelf: true }).then(user => {
|
os.selectUser({ includeSelf: true }).then(user => {
|
||||||
users.value = users.value.trim();
|
users.value = users.value.trim();
|
||||||
users.value += '\n@' + Misskey.acct.toString(user as any);
|
users.value += '\n@' + Misskey.acct.toString(user);
|
||||||
users.value = users.value.trim();
|
users.value = users.value.trim();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,11 +47,12 @@ 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 { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { miLocalStorage } from '@/local-storage.js';
|
import { miLocalStorage } from '@/local-storage.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
channel: Record<string, any>;
|
channel: Misskey.entities.Channel;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const getLastReadedAt = (): number | null => {
|
const getLastReadedAt = (): number | null => {
|
||||||
|
|
|
@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
:withOkButton="true"
|
:withOkButton="true"
|
||||||
@close="cancel()"
|
@close="cancel()"
|
||||||
@ok="ok()"
|
@ok="ok()"
|
||||||
@closed="$emit('closed')"
|
@closed="emit('closed')"
|
||||||
>
|
>
|
||||||
<template #header>{{ i18n.ts.cropImage }}</template>
|
<template #header>{{ i18n.ts.cropImage }}</template>
|
||||||
<template #default="{ width, height }">
|
<template #default="{ width, height }">
|
||||||
|
|
|
@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<MkModalWindow ref="dialogEl" @close="cancel()" @closed="$emit('closed')">
|
<MkModalWindow ref="dialogEl" @close="cancel()" @closed="emit('closed')">
|
||||||
<template #header>:{{ emoji.name }}:</template>
|
<template #header>:{{ emoji.name }}:</template>
|
||||||
<template #default>
|
<template #default>
|
||||||
<MkSpacer>
|
<MkSpacer>
|
||||||
|
|
|
@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
</MkSelect>
|
</MkSelect>
|
||||||
<div v-if="(showOkButton || showCancelButton) && !actions" :class="$style.buttons">
|
<div v-if="(showOkButton || showCancelButton) && !actions" :class="$style.buttons">
|
||||||
<MkButton v-if="showOkButton" data-cy-modal-dialog-ok inline primary rounded :autofocus="!input && !select" :disabled="okButtonDisabledReason" @click="ok">{{ okText ?? ((showCancelButton || input || select) ? i18n.ts.ok : i18n.ts.gotIt) }}</MkButton>
|
<MkButton v-if="showOkButton" data-cy-modal-dialog-ok inline primary rounded :autofocus="!input && !select" :disabled="okButtonDisabledReason != null" @click="ok">{{ okText ?? ((showCancelButton || input || select) ? i18n.ts.ok : i18n.ts.gotIt) }}</MkButton>
|
||||||
<MkButton v-if="showCancelButton || input || select" data-cy-modal-dialog-cancel inline rounded @click="cancel">{{ cancelText ?? i18n.ts.cancel }}</MkButton>
|
<MkButton v-if="showCancelButton || input || select" data-cy-modal-dialog-cancel inline rounded @click="cancel">{{ cancelText ?? i18n.ts.cancel }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="actions" :class="$style.buttons">
|
<div v-if="actions" :class="$style.buttons">
|
||||||
|
@ -98,7 +98,7 @@ const props = withDefaults(defineProps<{
|
||||||
text: string;
|
text: string;
|
||||||
primary?: boolean,
|
primary?: boolean,
|
||||||
danger?: boolean,
|
danger?: boolean,
|
||||||
callback: (...args: any[]) => void;
|
callback: (...args: unknown[]) => void;
|
||||||
}[];
|
}[];
|
||||||
showOkButton?: boolean;
|
showOkButton?: boolean;
|
||||||
showCancelButton?: boolean;
|
showCancelButton?: boolean;
|
||||||
|
|
|
@ -157,7 +157,7 @@ const ilFilesObserver = new IntersectionObserver(
|
||||||
(entries) => entries.some((entry) => entry.isIntersecting) && !fetching.value && moreFiles.value && fetchMoreFiles(),
|
(entries) => entries.some((entry) => entry.isIntersecting) && !fetching.value && moreFiles.value && fetchMoreFiles(),
|
||||||
);
|
);
|
||||||
|
|
||||||
const sortModeSelect = ref('+createdAt');
|
const sortModeSelect = ref<NonNullable<Misskey.entities.DriveFilesRequest['sort']>>('+createdAt');
|
||||||
|
|
||||||
watch(folder, () => emit('cd', folder.value));
|
watch(folder, () => emit('cd', folder.value));
|
||||||
watch(sortModeSelect, () => {
|
watch(sortModeSelect, () => {
|
||||||
|
@ -198,7 +198,7 @@ function onStreamDriveFolderDeleted(folderId: string) {
|
||||||
removeFolder(folderId);
|
removeFolder(folderId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onDragover(ev: DragEvent): any {
|
function onDragover(ev: DragEvent) {
|
||||||
if (!ev.dataTransfer) return;
|
if (!ev.dataTransfer) return;
|
||||||
|
|
||||||
// ドラッグ元が自分自身の所有するアイテムだったら
|
// ドラッグ元が自分自身の所有するアイテムだったら
|
||||||
|
@ -243,7 +243,7 @@ function onDragleave() {
|
||||||
draghover.value = false;
|
draghover.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onDrop(ev: DragEvent): any {
|
function onDrop(ev: DragEvent) {
|
||||||
draghover.value = false;
|
draghover.value = false;
|
||||||
|
|
||||||
if (!ev.dataTransfer) return;
|
if (!ev.dataTransfer) return;
|
||||||
|
@ -332,7 +332,7 @@ function createFolder() {
|
||||||
title: i18n.ts.createFolder,
|
title: i18n.ts.createFolder,
|
||||||
placeholder: i18n.ts.folderName,
|
placeholder: i18n.ts.folderName,
|
||||||
}).then(({ canceled, result: name }) => {
|
}).then(({ canceled, result: name }) => {
|
||||||
if (canceled) return;
|
if (canceled || name == null) return;
|
||||||
misskeyApi('drive/folders/create', {
|
misskeyApi('drive/folders/create', {
|
||||||
name: name,
|
name: name,
|
||||||
parentId: folder.value ? folder.value.id : undefined,
|
parentId: folder.value ? folder.value.id : undefined,
|
||||||
|
|
|
@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
:scroll="false"
|
:scroll="false"
|
||||||
:withOkButton="false"
|
:withOkButton="false"
|
||||||
@close="cancel()"
|
@close="cancel()"
|
||||||
@closed="$emit('closed')"
|
@closed="emit('closed')"
|
||||||
>
|
>
|
||||||
<template #header><i class="ti ti-code"></i> {{ i18n.ts._embedCodeGen.title }}</template>
|
<template #header><i class="ti ti-code"></i> {{ i18n.ts._embedCodeGen.title }}</template>
|
||||||
|
|
||||||
|
|
|
@ -90,7 +90,7 @@ function computeButtonTitle(ev: MouseEvent): void {
|
||||||
elm.title = getEmojiName(emoji);
|
elm.title = getEmojiName(emoji);
|
||||||
}
|
}
|
||||||
|
|
||||||
function nestedChosen(emoji: any, ev: MouseEvent) {
|
function nestedChosen(emoji: string, ev: MouseEvent) {
|
||||||
emit('chosen', emoji, ev);
|
emit('chosen', emoji, ev);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -409,7 +409,7 @@ function computeButtonTitle(ev: MouseEvent): void {
|
||||||
elm.title = getEmojiName(emoji);
|
elm.title = getEmojiName(emoji);
|
||||||
}
|
}
|
||||||
|
|
||||||
function chosen(emoji: any, ev?: MouseEvent) {
|
function chosen(emoji: string | Misskey.entities.EmojiSimple | UnicodeEmojiDef, ev?: MouseEvent) {
|
||||||
const el = ev && (ev.currentTarget ?? ev.target) as HTMLElement | null | undefined;
|
const el = ev && (ev.currentTarget ?? ev.target) as HTMLElement | null | undefined;
|
||||||
if (el) {
|
if (el) {
|
||||||
const rect = el.getBoundingClientRect();
|
const rect = el.getBoundingClientRect();
|
||||||
|
@ -426,7 +426,7 @@ function chosen(emoji: any, ev?: MouseEvent) {
|
||||||
// 最近使った絵文字更新
|
// 最近使った絵文字更新
|
||||||
if (!pinned.value?.includes(key)) {
|
if (!pinned.value?.includes(key)) {
|
||||||
let recents = defaultStore.state.recentlyUsedEmojis;
|
let recents = defaultStore.state.recentlyUsedEmojis;
|
||||||
recents = recents.filter((emoji: any) => emoji !== key);
|
recents = recents.filter((emoji) => emoji !== key);
|
||||||
recents.unshift(key);
|
recents.unshift(key);
|
||||||
defaultStore.set('recentlyUsedEmojis', recents.splice(0, 32));
|
defaultStore.set('recentlyUsedEmojis', recents.splice(0, 32));
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,7 +73,7 @@ export type Extension = {
|
||||||
author: string;
|
author: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
permissions?: string[];
|
permissions?: string[];
|
||||||
config?: Record<string, any>;
|
config?: Record<string, unknown>;
|
||||||
};
|
};
|
||||||
} | {
|
} | {
|
||||||
type: 'theme';
|
type: 'theme';
|
||||||
|
|
|
@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
@click="cancel()"
|
@click="cancel()"
|
||||||
@ok="ok()"
|
@ok="ok()"
|
||||||
@close="cancel()"
|
@close="cancel()"
|
||||||
@closed="$emit('closed')"
|
@closed="emit('closed')"
|
||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
{{ title }}
|
{{ title }}
|
||||||
|
|
|
@ -44,7 +44,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, onUnmounted, nextTick, ref, shallowRef, watch, computed, toRefs } from 'vue';
|
import { onMounted, onUnmounted, nextTick, ref, shallowRef, watch, computed, toRefs, InputHTMLAttributes } from 'vue';
|
||||||
import { debounce } from 'throttle-debounce';
|
import { debounce } from 'throttle-debounce';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import { useInterval } from '@@/js/use-interval.js';
|
import { useInterval } from '@@/js/use-interval.js';
|
||||||
|
@ -53,7 +53,7 @@ import { Autocomplete, SuggestionType } from '@/scripts/autocomplete.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: string | number | null;
|
modelValue: string | number | null;
|
||||||
type?: 'text' | 'number' | 'password' | 'email' | 'url' | 'date' | 'time' | 'search' | 'datetime-local';
|
type?: InputHTMLAttributes['type'];
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
@ -64,8 +64,8 @@ const props = defineProps<{
|
||||||
mfmAutocomplete?: boolean | SuggestionType[],
|
mfmAutocomplete?: boolean | SuggestionType[],
|
||||||
autocapitalize?: string;
|
autocapitalize?: string;
|
||||||
spellcheck?: boolean;
|
spellcheck?: boolean;
|
||||||
inputmode?: 'none' | 'text' | 'search' | 'email' | 'url' | 'numeric' | 'tel' | 'decimal';
|
inputmode?: InputHTMLAttributes['inputmode'];
|
||||||
step?: any;
|
step?: InputHTMLAttributes['step'];
|
||||||
datalist?: string[];
|
datalist?: string[];
|
||||||
min?: number;
|
min?: number;
|
||||||
max?: number;
|
max?: number;
|
||||||
|
|
|
@ -118,7 +118,7 @@ import { hms } from '@/filters/hms.js';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { isFullscreenNotSupported } from '@/scripts/device-kind.js';
|
import { exitFullscreen, requestFullscreen } from '@/scripts/fullscreen.js';
|
||||||
import hasAudio from '@/scripts/media-has-audio.js';
|
import hasAudio from '@/scripts/media-has-audio.js';
|
||||||
import MkMediaRange from '@/components/MkMediaRange.vue';
|
import MkMediaRange from '@/components/MkMediaRange.vue';
|
||||||
import { $i, iAmModerator } from '@/account.js';
|
import { $i, iAmModerator } from '@/account.js';
|
||||||
|
@ -334,26 +334,21 @@ function togglePlayPause() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleFullscreen() {
|
function toggleFullscreen() {
|
||||||
if (isFullscreenNotSupported && videoEl.value) {
|
if (playerEl.value == null || videoEl.value == null) return;
|
||||||
if (isFullscreen.value) {
|
if (isFullscreen.value) {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
exitFullscreen({
|
||||||
//@ts-ignore
|
videoEl: videoEl.value,
|
||||||
videoEl.value.webkitExitFullscreen();
|
});
|
||||||
isFullscreen.value = false;
|
isFullscreen.value = false;
|
||||||
} else {
|
} else {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
requestFullscreen({
|
||||||
//@ts-ignore
|
videoEl: videoEl.value,
|
||||||
videoEl.value.webkitEnterFullscreen();
|
playerEl: playerEl.value,
|
||||||
isFullscreen.value = true;
|
options: {
|
||||||
}
|
navigationUI: 'hide',
|
||||||
} else if (playerEl.value) {
|
},
|
||||||
if (isFullscreen.value) {
|
});
|
||||||
document.exitFullscreen();
|
isFullscreen.value = true;
|
||||||
isFullscreen.value = false;
|
|
||||||
} else {
|
|
||||||
playerEl.value.requestFullscreen({ navigationUI: 'hide' });
|
|
||||||
isFullscreen.value = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -454,8 +449,10 @@ watch(loop, (to) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(hide, (to) => {
|
watch(hide, (to) => {
|
||||||
if (to && isFullscreen.value) {
|
if (videoEl.value && to && isFullscreen.value) {
|
||||||
document.exitFullscreen();
|
exitFullscreen({
|
||||||
|
videoEl: videoEl.value,
|
||||||
|
});
|
||||||
isFullscreen.value = false;
|
isFullscreen.value = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -227,7 +227,7 @@ const emit = defineEmits<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const inTimeline = inject<boolean>('inTimeline', false);
|
const inTimeline = inject<boolean>('inTimeline', false);
|
||||||
const tl_withSensitive = inject<Ref<boolean>>('tl_withSensitive', ref(false));
|
const tl_withSensitive = inject<Ref<boolean>>('tl_withSensitive', ref(true));
|
||||||
const inChannel = inject('inChannel', null);
|
const inChannel = inject('inChannel', null);
|
||||||
const currentClip = inject<Ref<Misskey.entities.Clip> | null>('currentClip', null);
|
const currentClip = inject<Ref<Misskey.entities.Clip> | null>('currentClip', null);
|
||||||
|
|
||||||
|
@ -292,15 +292,18 @@ function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string
|
||||||
function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: false): boolean | 'sensitiveMute';
|
function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: false): boolean | 'sensitiveMute';
|
||||||
*/
|
*/
|
||||||
function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly = false): boolean | 'sensitiveMute' {
|
function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly = false): boolean | 'sensitiveMute' {
|
||||||
if (mutedWords == null) return false;
|
if (mutedWords != null) {
|
||||||
|
if (checkWordMute(noteToCheck, $i, mutedWords)) return true;
|
||||||
if (checkWordMute(noteToCheck, $i, mutedWords)) return true;
|
if (noteToCheck.reply && checkWordMute(noteToCheck.reply, $i, mutedWords)) return true;
|
||||||
if (noteToCheck.reply && checkWordMute(noteToCheck.reply, $i, mutedWords)) return true;
|
if (noteToCheck.renote && checkWordMute(noteToCheck.renote, $i, mutedWords)) return true;
|
||||||
if (noteToCheck.renote && checkWordMute(noteToCheck.renote, $i, mutedWords)) return true;
|
}
|
||||||
|
|
||||||
if (checkOnly) return false;
|
if (checkOnly) return false;
|
||||||
|
|
||||||
if (inTimeline && !tl_withSensitive.value && noteToCheck.files?.some((v) => v.isSensitive)) return 'sensitiveMute';
|
if (inTimeline && tl_withSensitive.value === false && noteToCheck.files?.some((v) => v.isSensitive)) {
|
||||||
|
return 'sensitiveMute';
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,7 @@ const props = withDefaults(defineProps<{
|
||||||
|
|
||||||
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
|
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
|
||||||
|
|
||||||
const typesMap: TypesMap = notificationTypes.reduce((p, t) => ({ ...p, [t]: ref<boolean>(!props.excludeTypes.includes(t)) }), {} as any);
|
const typesMap = notificationTypes.reduce((p, t) => ({ ...p, [t]: ref<boolean>(!props.excludeTypes.includes(t)) }), {} as TypesMap);
|
||||||
|
|
||||||
function ok() {
|
function ok() {
|
||||||
emit('done', {
|
emit('done', {
|
||||||
|
|
|
@ -39,7 +39,7 @@ import number from '@/filters/number.js';
|
||||||
import XValue from '@/components/MkObjectView.value.vue';
|
import XValue from '@/components/MkObjectView.value.vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
value: any;
|
value: unknown;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const collapsed = reactive({});
|
const collapsed = reactive({});
|
||||||
|
@ -50,19 +50,19 @@ if (isObject(props.value)) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isObject(v): boolean {
|
function isObject(v: unknown): v is Record<PropertyKey, unknown> {
|
||||||
return typeof v === 'object' && !Array.isArray(v) && v !== null;
|
return typeof v === 'object' && !Array.isArray(v) && v !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isArray(v): boolean {
|
function isArray(v: unknown): v is unknown[] {
|
||||||
return Array.isArray(v);
|
return Array.isArray(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isEmpty(v): boolean {
|
function isEmpty(v: unknown): v is Record<PropertyKey, never> | never[] {
|
||||||
return (isArray(v) && v.length === 0) || (isObject(v) && Object.keys(v).length === 0);
|
return (isArray(v) && v.length === 0) || (isObject(v) && Object.keys(v).length === 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function collapsable(v): boolean {
|
function collapsable(v: unknown): boolean {
|
||||||
return (isObject(v) || isArray(v)) && !isEmpty(v);
|
return (isObject(v) || isArray(v)) && !isEmpty(v);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
:buttonsLeft="buttonsLeft"
|
:buttonsLeft="buttonsLeft"
|
||||||
:buttonsRight="buttonsRight"
|
:buttonsRight="buttonsRight"
|
||||||
:contextmenu="contextmenu"
|
:contextmenu="contextmenu"
|
||||||
@closed="$emit('closed')"
|
@closed="emit('closed')"
|
||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<template v-if="pageMetadata">
|
<template v-if="pageMetadata">
|
||||||
|
@ -30,17 +30,17 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, onMounted, onUnmounted, provide, ref, shallowRef } from 'vue';
|
import { computed, onMounted, onUnmounted, provide, ref, shallowRef } from 'vue';
|
||||||
|
import { url } from '@@/js/config.js';
|
||||||
|
import { getScrollContainer } from '@@/js/scroll.js';
|
||||||
import RouterView from '@/components/global/RouterView.vue';
|
import RouterView from '@/components/global/RouterView.vue';
|
||||||
import MkWindow from '@/components/MkWindow.vue';
|
import MkWindow from '@/components/MkWindow.vue';
|
||||||
import { popout as _popout } from '@/scripts/popout.js';
|
import { popout as _popout } from '@/scripts/popout.js';
|
||||||
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
|
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
|
||||||
import { url } from '@@/js/config.js';
|
|
||||||
import { useScrollPositionManager } from '@/nirax.js';
|
import { useScrollPositionManager } from '@/nirax.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
|
import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
|
||||||
import { openingWindowsCount } from '@/os.js';
|
import { openingWindowsCount } from '@/os.js';
|
||||||
import { claimAchievement } from '@/scripts/achievements.js';
|
import { claimAchievement } from '@/scripts/achievements.js';
|
||||||
import { getScrollContainer } from '@@/js/scroll.js';
|
|
||||||
import { useRouterFactory } from '@/router/supplier.js';
|
import { useRouterFactory } from '@/router/supplier.js';
|
||||||
import { mainRouter } from '@/router/main.js';
|
import { mainRouter } from '@/router/main.js';
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ const props = defineProps<{
|
||||||
initialPath: string;
|
initialPath: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'closed'): void;
|
(ev: 'closed'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ const windowRouter = routerFactory(props.initialPath);
|
||||||
const contents = shallowRef<HTMLElement | null>(null);
|
const contents = shallowRef<HTMLElement | null>(null);
|
||||||
const pageMetadata = ref<null | PageMetadata>(null);
|
const pageMetadata = ref<null | PageMetadata>(null);
|
||||||
const windowEl = shallowRef<InstanceType<typeof MkWindow>>();
|
const windowEl = shallowRef<InstanceType<typeof MkWindow>>();
|
||||||
const history = ref<{ path: string; key: any; }[]>([{
|
const history = ref<{ path: string; key: string; }[]>([{
|
||||||
path: windowRouter.getCurrentPath(),
|
path: windowRouter.getCurrentPath(),
|
||||||
key: windowRouter.getCurrentKey(),
|
key: windowRouter.getCurrentKey(),
|
||||||
}]);
|
}]);
|
||||||
|
|
|
@ -19,7 +19,7 @@ defineProps<{
|
||||||
items: MenuItem[];
|
items: MenuItem[];
|
||||||
align?: 'center' | string;
|
align?: 'center' | string;
|
||||||
width?: number;
|
width?: number;
|
||||||
src?: any;
|
src?: HTMLElement | null;
|
||||||
returnFocusTo?: HTMLElement | null;
|
returnFocusTo?: HTMLElement | null;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
|
|
@ -129,25 +129,13 @@ import { miLocalStorage } from '@/local-storage.js';
|
||||||
import { claimAchievement } from '@/scripts/achievements.js';
|
import { claimAchievement } from '@/scripts/achievements.js';
|
||||||
import { emojiPicker } from '@/scripts/emoji-picker.js';
|
import { emojiPicker } from '@/scripts/emoji-picker.js';
|
||||||
import { mfmFunctionPicker } from '@/scripts/mfm-function-picker.js';
|
import { mfmFunctionPicker } from '@/scripts/mfm-function-picker.js';
|
||||||
|
import type { PostFormProps } from '@/types/post-form.js';
|
||||||
|
|
||||||
const $i = signinRequired();
|
const $i = signinRequired();
|
||||||
|
|
||||||
const modal = inject('modal');
|
const modal = inject('modal');
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<PostFormProps & {
|
||||||
reply?: Misskey.entities.Note;
|
|
||||||
renote?: Misskey.entities.Note;
|
|
||||||
channel?: Misskey.entities.Channel; // TODO
|
|
||||||
mention?: Misskey.entities.User;
|
|
||||||
specified?: Misskey.entities.UserDetailed;
|
|
||||||
initialText?: string;
|
|
||||||
initialCw?: string;
|
|
||||||
initialVisibility?: (typeof Misskey.noteVisibilities)[number];
|
|
||||||
initialFiles?: Misskey.entities.DriveFile[];
|
|
||||||
initialLocalOnly?: boolean;
|
|
||||||
initialVisibleUsers?: Misskey.entities.UserDetailed[];
|
|
||||||
initialNote?: Misskey.entities.Note;
|
|
||||||
instant?: boolean;
|
|
||||||
fixed?: boolean;
|
fixed?: boolean;
|
||||||
autofocus?: boolean;
|
autofocus?: boolean;
|
||||||
freezeAfterPosted?: boolean;
|
freezeAfterPosted?: boolean;
|
||||||
|
@ -955,8 +943,8 @@ function showActions(ev: MouseEvent) {
|
||||||
action.handler({
|
action.handler({
|
||||||
text: text.value,
|
text: text.value,
|
||||||
cw: cw.value,
|
cw: cw.value,
|
||||||
}, (key, value: any) => {
|
}, (key, value) => {
|
||||||
if (typeof key !== 'string') return;
|
if (typeof key !== 'string' || typeof value !== 'string') return;
|
||||||
if (key === 'text') { text.value = value; }
|
if (key === 'text') { text.value = value; }
|
||||||
if (key === 'cw') { useCw.value = value !== null; cw.value = value; }
|
if (key === 'cw') { useCw.value = value !== null; cw.value = value; }
|
||||||
});
|
});
|
||||||
|
@ -1120,7 +1108,7 @@ defineExpose({
|
||||||
&:focus-visible {
|
&:focus-visible {
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
||||||
.submitInner {
|
> .submitInner {
|
||||||
outline: 2px solid var(--MI_THEME-fgOnAccent);
|
outline: 2px solid var(--MI_THEME-fgOnAccent);
|
||||||
outline-offset: -4px;
|
outline-offset: -4px;
|
||||||
}
|
}
|
||||||
|
@ -1135,13 +1123,13 @@ defineExpose({
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not(:disabled):hover {
|
&:not(:disabled):hover {
|
||||||
> .inner {
|
> .submitInner {
|
||||||
background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5)));
|
background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not(:disabled):active {
|
&:not(:disabled):active {
|
||||||
> .inner {
|
> .submitInner {
|
||||||
background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5)));
|
background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template>
|
<template>
|
||||||
<div v-show="props.modelValue.length != 0" :class="$style.root">
|
<div v-show="props.modelValue.length != 0" :class="$style.root">
|
||||||
<Sortable :modelValue="props.modelValue" :class="$style.files" itemKey="id" :animation="150" :delay="100" :delayOnTouchOnly="true" @update:modelValue="v => emit('update:modelValue', v)">
|
<Sortable :modelValue="props.modelValue" :class="$style.files" itemKey="id" :animation="150" :delay="100" :delayOnTouchOnly="true" @update:modelValue="v => emit('update:modelValue', v)">
|
||||||
<template #item="{element}">
|
<template #item="{ element }">
|
||||||
<div
|
<div
|
||||||
:class="$style.file"
|
:class="$style.file"
|
||||||
role="button"
|
role="button"
|
||||||
|
@ -38,14 +38,14 @@ import type { MenuItem } from '@/types/menu.js';
|
||||||
const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
|
const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: any[];
|
modelValue: Misskey.entities.DriveFile[];
|
||||||
detachMediaFn?: (id: string) => void;
|
detachMediaFn?: (id: string) => void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const mock = inject<boolean>('mock', false);
|
const mock = inject<boolean>('mock', false);
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'update:modelValue', value: any[]): void;
|
(ev: 'update:modelValue', value: Misskey.entities.DriveFile[]): void;
|
||||||
(ev: 'detach', id: string): void;
|
(ev: 'detach', id: string): void;
|
||||||
(ev: 'changeSensitive', file: Misskey.entities.DriveFile, isSensitive: boolean): void;
|
(ev: 'changeSensitive', file: Misskey.entities.DriveFile, isSensitive: boolean): void;
|
||||||
(ev: 'changeName', file: Misskey.entities.DriveFile, newName: string): void;
|
(ev: 'changeName', file: Misskey.entities.DriveFile, newName: string): void;
|
||||||
|
@ -113,7 +113,7 @@ async function rename(file) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function describe(file) {
|
async function describe(file: Misskey.entities.DriveFile) {
|
||||||
if (mock) return;
|
if (mock) return;
|
||||||
|
|
||||||
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkFileCaptionEditWindow.vue')), {
|
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkFileCaptionEditWindow.vue')), {
|
||||||
|
|
|
@ -11,23 +11,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { shallowRef } from 'vue';
|
import { shallowRef } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
|
||||||
import MkModal from '@/components/MkModal.vue';
|
import MkModal from '@/components/MkModal.vue';
|
||||||
import MkPostForm from '@/components/MkPostForm.vue';
|
import MkPostForm from '@/components/MkPostForm.vue';
|
||||||
|
import type { PostFormProps } from '@/types/post-form.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<PostFormProps & {
|
||||||
reply?: Misskey.entities.Note;
|
|
||||||
renote?: Misskey.entities.Note;
|
|
||||||
channel?: any; // TODO
|
|
||||||
mention?: Misskey.entities.User;
|
|
||||||
specified?: Misskey.entities.UserDetailed;
|
|
||||||
initialText?: string;
|
|
||||||
initialCw?: string;
|
|
||||||
initialVisibility?: (typeof Misskey.noteVisibilities)[number];
|
|
||||||
initialFiles?: Misskey.entities.DriveFile[];
|
|
||||||
initialLocalOnly?: boolean;
|
|
||||||
initialVisibleUsers?: Misskey.entities.UserDetailed[];
|
|
||||||
initialNote?: Misskey.entities.Note;
|
|
||||||
instant?: boolean;
|
instant?: boolean;
|
||||||
fixed?: boolean;
|
fixed?: boolean;
|
||||||
autofocus?: boolean;
|
autofocus?: boolean;
|
||||||
|
|
|
@ -24,17 +24,17 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup generic="T extends unknown">
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: any;
|
modelValue: T;
|
||||||
value: any;
|
value: T;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'update:modelValue', value: any): void;
|
(ev: 'update:modelValue', value: T): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const checked = computed(() => props.modelValue === props.value);
|
const checked = computed(() => props.modelValue === props.value);
|
||||||
|
|
|
@ -23,6 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { } from 'vue';
|
import { } from 'vue';
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
import { getEmojiName } from '@@/js/emojilist.js';
|
import { getEmojiName } from '@@/js/emojilist.js';
|
||||||
import MkTooltip from './MkTooltip.vue';
|
import MkTooltip from './MkTooltip.vue';
|
||||||
import MkReactionIcon from '@/components/MkReactionIcon.vue';
|
import MkReactionIcon from '@/components/MkReactionIcon.vue';
|
||||||
|
@ -30,7 +31,7 @@ import MkReactionIcon from '@/components/MkReactionIcon.vue';
|
||||||
defineProps<{
|
defineProps<{
|
||||||
showing: boolean;
|
showing: boolean;
|
||||||
reaction: string;
|
reaction: string;
|
||||||
users: any[]; // TODO
|
users: Misskey.entities.UserLite[];
|
||||||
count: number;
|
count: number;
|
||||||
targetElement: HTMLElement;
|
targetElement: HTMLElement;
|
||||||
}>();
|
}>();
|
||||||
|
|
|
@ -277,7 +277,7 @@ async function onSubmit(): Promise<void> {
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res) {
|
if (res && res.ok) {
|
||||||
if (res.status === 204 || instance.emailRequiredForSignup) {
|
if (res.status === 204 || instance.emailRequiredForSignup) {
|
||||||
os.alert({
|
os.alert({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
|
@ -295,6 +295,8 @@ async function onSubmit(): Promise<void> {
|
||||||
await login(resJson.token);
|
await login(resJson.token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
onSignupApiError();
|
||||||
}
|
}
|
||||||
|
|
||||||
submitting.value = false;
|
submitting.value = false;
|
||||||
|
|
|
@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
:width="500"
|
:width="500"
|
||||||
:height="600"
|
:height="600"
|
||||||
@close="onClose"
|
@close="onClose"
|
||||||
@closed="$emit('closed')"
|
@closed="emit('closed')"
|
||||||
>
|
>
|
||||||
<template #header>{{ i18n.ts.signup }}</template>
|
<template #header>{{ i18n.ts.signup }}</template>
|
||||||
|
|
||||||
|
|
|
@ -28,11 +28,38 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts">
|
||||||
import { } from 'vue';
|
export type SuperMenuDef = {
|
||||||
|
title?: string;
|
||||||
|
items: ({
|
||||||
|
type: 'a';
|
||||||
|
href: string;
|
||||||
|
target?: string;
|
||||||
|
icon?: string;
|
||||||
|
text: string;
|
||||||
|
danger?: boolean;
|
||||||
|
active?: boolean;
|
||||||
|
} | {
|
||||||
|
type: 'button';
|
||||||
|
icon?: string;
|
||||||
|
text: string;
|
||||||
|
danger?: boolean;
|
||||||
|
active?: boolean;
|
||||||
|
action: (ev: MouseEvent) => void;
|
||||||
|
} | {
|
||||||
|
type: 'link';
|
||||||
|
to: string;
|
||||||
|
icon?: string;
|
||||||
|
text: string;
|
||||||
|
danger?: boolean;
|
||||||
|
active?: boolean;
|
||||||
|
})[];
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
defineProps<{
|
defineProps<{
|
||||||
def: any[];
|
def: SuperMenuDef[];
|
||||||
grid?: boolean;
|
grid?: boolean;
|
||||||
}>();
|
}>();
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -43,6 +43,7 @@ const props = withDefaults(defineProps<{
|
||||||
}>(), {
|
}>(), {
|
||||||
withRenotes: true,
|
withRenotes: true,
|
||||||
withReplies: false,
|
withReplies: false,
|
||||||
|
withSensitive: true,
|
||||||
onlyFiles: false,
|
onlyFiles: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
:okButtonDisabled="false"
|
:okButtonDisabled="false"
|
||||||
:canClose="false"
|
:canClose="false"
|
||||||
@close="dialog?.close()"
|
@close="dialog?.close()"
|
||||||
@closed="$emit('closed')"
|
@closed="emit('closed')"
|
||||||
@ok="ok()"
|
@ok="ok()"
|
||||||
>
|
>
|
||||||
<template #header>{{ title || i18n.ts.generateAccessToken }}</template>
|
<template #header>{{ title || i18n.ts.generateAccessToken }}</template>
|
||||||
|
|
|
@ -180,7 +180,7 @@ window.fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${versatileLa
|
||||||
sensitive.value = info.sensitive ?? false;
|
sensitive.value = info.sensitive ?? false;
|
||||||
});
|
});
|
||||||
|
|
||||||
function adjustTweetHeight(message: any) {
|
function adjustTweetHeight(message: MessageEvent) {
|
||||||
if (message.origin !== 'https://platform.twitter.com') return;
|
if (message.origin !== 'https://platform.twitter.com') return;
|
||||||
const embed = message.data?.['twttr.embed'];
|
const embed = message.data?.['twttr.embed'];
|
||||||
if (embed?.method !== 'twttr.private.resize') return;
|
if (embed?.method !== 'twttr.private.resize') return;
|
||||||
|
@ -193,14 +193,16 @@ function openPlayer(): void {
|
||||||
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkYouTubePlayer.vue')), {
|
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkYouTubePlayer.vue')), {
|
||||||
url: requestUrl.href,
|
url: requestUrl.href,
|
||||||
}, {
|
}, {
|
||||||
// TODO
|
closed: () => {
|
||||||
|
dispose();
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
(window as any).addEventListener('message', adjustTweetHeight);
|
window.addEventListener('message', adjustTweetHeight);
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
(window as any).removeEventListener('message', adjustTweetHeight);
|
window.removeEventListener('message', adjustTweetHeight);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
ref="dialog"
|
ref="dialog"
|
||||||
:width="400"
|
:width="400"
|
||||||
@close="dialog?.close()"
|
@close="dialog?.close()"
|
||||||
@closed="$emit('closed')"
|
@closed="emit('closed')"
|
||||||
>
|
>
|
||||||
<template v-if="announcement" #header>:{{ announcement.title }}:</template>
|
<template v-if="announcement" #header>:{{ announcement.title }}:</template>
|
||||||
<template v-else #header>New announcement</template>
|
<template v-else #header>New announcement</template>
|
||||||
|
@ -62,9 +62,16 @@ import MkTextarea from '@/components/MkTextarea.vue';
|
||||||
import MkSwitch from '@/components/MkSwitch.vue';
|
import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
import MkRadios from '@/components/MkRadios.vue';
|
import MkRadios from '@/components/MkRadios.vue';
|
||||||
|
|
||||||
|
type AdminAnnouncementType = Misskey.entities.AdminAnnouncementsCreateRequest & { id: string; }
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
user: Misskey.entities.User,
|
user: Misskey.entities.User,
|
||||||
announcement?: Misskey.entities.Announcement,
|
announcement?: Required<AdminAnnouncementType>,
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(ev: 'done', v: { deleted?: boolean; updated?: AdminAnnouncementType; created?: AdminAnnouncementType; }): void,
|
||||||
|
(ev: 'closed'): void
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const dialog = ref<InstanceType<typeof MkModalWindow> | null>(null);
|
const dialog = ref<InstanceType<typeof MkModalWindow> | null>(null);
|
||||||
|
@ -74,11 +81,6 @@ const icon = ref(props.announcement ? props.announcement.icon : 'info');
|
||||||
const display = ref(props.announcement ? props.announcement.display : 'dialog');
|
const display = ref(props.announcement ? props.announcement.display : 'dialog');
|
||||||
const needConfirmationToRead = ref(props.announcement ? props.announcement.needConfirmationToRead : false);
|
const needConfirmationToRead = ref(props.announcement ? props.announcement.needConfirmationToRead : false);
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(ev: 'done', v: { deleted?: boolean; updated?: any; created?: any }): void,
|
|
||||||
(ev: 'closed'): void
|
|
||||||
}>();
|
|
||||||
|
|
||||||
async function done() {
|
async function done() {
|
||||||
const params = {
|
const params = {
|
||||||
title: title.value,
|
title: title.value,
|
||||||
|
@ -88,7 +90,7 @@ async function done() {
|
||||||
display: display.value,
|
display: display.value,
|
||||||
needConfirmationToRead: needConfirmationToRead.value,
|
needConfirmationToRead: needConfirmationToRead.value,
|
||||||
userId: props.user.id,
|
userId: props.user.id,
|
||||||
};
|
} satisfies Misskey.entities.AdminAnnouncementsCreateRequest;
|
||||||
|
|
||||||
if (props.announcement) {
|
if (props.announcement) {
|
||||||
await os.apiWithDialog('admin/announcements/update', {
|
await os.apiWithDialog('admin/announcements/update', {
|
||||||
|
|
|
@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
@click="cancel()"
|
@click="cancel()"
|
||||||
@close="cancel()"
|
@close="cancel()"
|
||||||
@ok="ok()"
|
@ok="ok()"
|
||||||
@closed="$emit('closed')"
|
@closed="emit('closed')"
|
||||||
>
|
>
|
||||||
<template #header>{{ i18n.ts.selectUser }}</template>
|
<template #header>{{ i18n.ts.selectUser }}</template>
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -16,12 +16,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { } from 'vue';
|
import * as Misskey from 'misskey-js';
|
||||||
import MkTooltip from './MkTooltip.vue';
|
import MkTooltip from './MkTooltip.vue';
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
showing: boolean;
|
showing: boolean;
|
||||||
users: any[]; // TODO
|
users: Misskey.entities.UserLite[];
|
||||||
count: number;
|
count: number;
|
||||||
targetElement: HTMLElement;
|
targetElement: HTMLElement;
|
||||||
}>();
|
}>();
|
||||||
|
|
|
@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<option v-for="widget in widgetDefs" :key="widget" :value="widget">{{ i18n.ts._widgets[widget] }}</option>
|
<option v-for="widget in widgetDefs" :key="widget" :value="widget">{{ i18n.ts._widgets[widget] }}</option>
|
||||||
</MkSelect>
|
</MkSelect>
|
||||||
<MkButton inline primary data-cy-widget-add @click="addWidget"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
|
<MkButton inline primary data-cy-widget-add @click="addWidget"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
|
||||||
<MkButton inline @click="$emit('exit')">{{ i18n.ts.close }}</MkButton>
|
<MkButton inline @click="emit('exit')">{{ i18n.ts.close }}</MkButton>
|
||||||
</header>
|
</header>
|
||||||
<Sortable
|
<Sortable
|
||||||
:modelValue="props.widgets"
|
:modelValue="props.widgets"
|
||||||
|
|
|
@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
:enterFromClass="defaultStore.state.animation ? $style.transition_window_enterFrom : ''"
|
:enterFromClass="defaultStore.state.animation ? $style.transition_window_enterFrom : ''"
|
||||||
:leaveToClass="defaultStore.state.animation ? $style.transition_window_leaveTo : ''"
|
:leaveToClass="defaultStore.state.animation ? $style.transition_window_leaveTo : ''"
|
||||||
appear
|
appear
|
||||||
@afterLeave="$emit('closed')"
|
@afterLeave="emit('closed')"
|
||||||
>
|
>
|
||||||
<div v-if="showing" ref="rootEl" :class="[$style.root, { [$style.maximized]: maximized }]">
|
<div v-if="showing" ref="rootEl" :class="[$style.root, { [$style.maximized]: maximized }]">
|
||||||
<div :class="$style.body" class="_shadow" @mousedown="onBodyMousedown" @keydown="onKeydown">
|
<div :class="$style.body" class="_shadow" @mousedown="onBodyMousedown" @keydown="onKeydown">
|
||||||
|
@ -60,6 +60,13 @@ import * as os from '@/os.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
|
|
||||||
|
type WindowButton = {
|
||||||
|
title: string;
|
||||||
|
icon: string;
|
||||||
|
onClick: () => void;
|
||||||
|
highlighted?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
const minHeight = 50;
|
const minHeight = 50;
|
||||||
const minWidth = 250;
|
const minWidth = 250;
|
||||||
|
|
||||||
|
@ -87,8 +94,8 @@ const props = withDefaults(defineProps<{
|
||||||
mini?: boolean;
|
mini?: boolean;
|
||||||
front?: boolean;
|
front?: boolean;
|
||||||
contextmenu?: MenuItem[] | null;
|
contextmenu?: MenuItem[] | null;
|
||||||
buttonsLeft?: any[];
|
buttonsLeft?: WindowButton[];
|
||||||
buttonsRight?: any[];
|
buttonsRight?: WindowButton[];
|
||||||
}>(), {
|
}>(), {
|
||||||
initialWidth: 400,
|
initialWidth: 400,
|
||||||
initialHeight: null,
|
initialHeight: null,
|
||||||
|
|
|
@ -18,19 +18,19 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup generic="T extends unknown">
|
||||||
import { ref, watch } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
p: () => Promise<any>;
|
p: () => Promise<T>;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
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<any>(null);
|
const result = ref<T | null>(null);
|
||||||
|
|
||||||
const process = () => {
|
const process = () => {
|
||||||
if (props.p == null) {
|
if (props.p == null) {
|
||||||
|
|
|
@ -467,8 +467,8 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
|
||||||
}
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// @ts-expect-error 存在しないASTタイプ
|
||||||
console.error('unrecognized ast type:', (token as any).type);
|
console.error('unrecognized ast type:', token.type);
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,9 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A typesafe enum of keys for localStorage.
|
||||||
|
*/
|
||||||
export type Keys =
|
export type Keys =
|
||||||
'v' |
|
'v' |
|
||||||
'lastVersion' |
|
'lastVersion' |
|
||||||
|
@ -44,16 +47,45 @@ export type Keys =
|
||||||
// セッション毎に廃棄されるLocalStorage代替(セーフモードなどで使用できそう)
|
// セッション毎に廃棄されるLocalStorage代替(セーフモードなどで使用できそう)
|
||||||
//const safeSessionStorage = new Map<Keys, string>();
|
//const safeSessionStorage = new Map<Keys, string>();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A utility object for interacting with the browser's localStorage.
|
||||||
|
*
|
||||||
|
* It's mostly a small wrapper around window.localStorage, but it validates
|
||||||
|
* keys with a typesafe enum, and provides a few convenience methods for JSON.
|
||||||
|
*/
|
||||||
export const miLocalStorage = {
|
export const miLocalStorage = {
|
||||||
|
/**
|
||||||
|
* Retrieves an item from localStorage.
|
||||||
|
* @param {Keys} key - The key of the item to retrieve.
|
||||||
|
* @returns {string | null} The value of the item, or null if the item does not exist.
|
||||||
|
*/
|
||||||
getItem: (key: Keys): string | null => {
|
getItem: (key: Keys): string | null => {
|
||||||
return window.localStorage.getItem(key);
|
return window.localStorage.getItem(key);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores an item in localStorage.
|
||||||
|
* @param {Keys} key - The key of the item to store.
|
||||||
|
* @param {string} value - The value of the item to store.
|
||||||
|
*/
|
||||||
setItem: (key: Keys, value: string): void => {
|
setItem: (key: Keys, value: string): void => {
|
||||||
window.localStorage.setItem(key, value);
|
window.localStorage.setItem(key, value);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes an item from localStorage.
|
||||||
|
* @param {Keys} key - The key of the item to remove.
|
||||||
|
*/
|
||||||
removeItem: (key: Keys): void => {
|
removeItem: (key: Keys): void => {
|
||||||
window.localStorage.removeItem(key);
|
window.localStorage.removeItem(key);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves an item from localStorage and parses it as JSON.
|
||||||
|
* @param {Keys} key - The key of the item to retrieve.
|
||||||
|
* @returns {any | undefined} The parsed value of the item, or undefined if the item does not exist.
|
||||||
|
*/
|
||||||
getItemAsJson: (key: Keys): any | undefined => {
|
getItemAsJson: (key: Keys): any | undefined => {
|
||||||
const item = miLocalStorage.getItem(key);
|
const item = miLocalStorage.getItem(key);
|
||||||
if (item === null) {
|
if (item === null) {
|
||||||
|
@ -61,6 +93,12 @@ export const miLocalStorage = {
|
||||||
}
|
}
|
||||||
return JSON.parse(item);
|
return JSON.parse(item);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores an item in localStorage as a JSON string.
|
||||||
|
* @param {Keys} key - The key of the item to store.
|
||||||
|
* @param {any} value - The value of the item to store.
|
||||||
|
*/
|
||||||
setItemAsJson: (key: Keys, value: any): void => {
|
setItemAsJson: (key: Keys, value: any): void => {
|
||||||
miLocalStorage.setItem(key, JSON.stringify(value));
|
miLocalStorage.setItem(key, JSON.stringify(value));
|
||||||
},
|
},
|
||||||
|
|
|
@ -36,6 +36,8 @@ interface RouteDefWithRedirect extends RouteDefBase {
|
||||||
|
|
||||||
export type RouteDef = RouteDefWithComponent | RouteDefWithRedirect;
|
export type RouteDef = RouteDefWithComponent | RouteDefWithRedirect;
|
||||||
|
|
||||||
|
export type RouterFlag = 'forcePage';
|
||||||
|
|
||||||
type ParsedPath = (string | {
|
type ParsedPath = (string | {
|
||||||
name: string;
|
name: string;
|
||||||
startsWith?: string;
|
startsWith?: string;
|
||||||
|
@ -107,7 +109,7 @@ export interface IRouter extends EventEmitter<RouterEvent> {
|
||||||
current: Resolved;
|
current: Resolved;
|
||||||
currentRef: ShallowRef<Resolved>;
|
currentRef: ShallowRef<Resolved>;
|
||||||
currentRoute: ShallowRef<RouteDef>;
|
currentRoute: ShallowRef<RouteDef>;
|
||||||
navHook: ((path: string, flag?: any) => boolean) | null;
|
navHook: ((path: string, flag?: RouterFlag) => boolean) | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ルートの初期化(eventListenerの定義後に必ず呼び出すこと)
|
* ルートの初期化(eventListenerの定義後に必ず呼び出すこと)
|
||||||
|
@ -116,11 +118,11 @@ export interface IRouter extends EventEmitter<RouterEvent> {
|
||||||
|
|
||||||
resolve(path: string): Resolved | null;
|
resolve(path: string): Resolved | null;
|
||||||
|
|
||||||
getCurrentPath(): any;
|
getCurrentPath(): string;
|
||||||
|
|
||||||
getCurrentKey(): string;
|
getCurrentKey(): string;
|
||||||
|
|
||||||
push(path: string, flag?: any): void;
|
push(path: string, flag?: RouterFlag): void;
|
||||||
|
|
||||||
replace(path: string, key?: string | null): void;
|
replace(path: string, key?: string | null): void;
|
||||||
|
|
||||||
|
@ -197,7 +199,7 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
|
||||||
private currentKey = Date.now().toString();
|
private currentKey = Date.now().toString();
|
||||||
private redirectCount = 0;
|
private redirectCount = 0;
|
||||||
|
|
||||||
public navHook: ((path: string, flag?: any) => boolean) | null = null;
|
public navHook: ((path: string, flag?: RouterFlag) => boolean) | null = null;
|
||||||
|
|
||||||
constructor(routes: Router['routes'], currentPath: Router['currentPath'], isLoggedIn: boolean, notFoundPageComponent: Component) {
|
constructor(routes: Router['routes'], currentPath: Router['currentPath'], isLoggedIn: boolean, notFoundPageComponent: Component) {
|
||||||
super();
|
super();
|
||||||
|
@ -404,7 +406,7 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
|
||||||
return this.currentKey;
|
return this.currentKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public push(path: string, flag?: any) {
|
public push(path: string, flag?: RouterFlag) {
|
||||||
const beforePath = this.currentPath;
|
const beforePath = this.currentPath;
|
||||||
if (path === beforePath) {
|
if (path === beforePath) {
|
||||||
this.emit('same');
|
this.emit('same');
|
||||||
|
|
|
@ -28,12 +28,13 @@ import { pleaseLogin } from '@/scripts/please-login.js';
|
||||||
import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
|
import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
|
||||||
import { getHTMLElementOrNull } from '@/scripts/get-dom-node-or-null.js';
|
import { getHTMLElementOrNull } from '@/scripts/get-dom-node-or-null.js';
|
||||||
import { focusParent } from '@/scripts/focus.js';
|
import { focusParent } from '@/scripts/focus.js';
|
||||||
|
import type { PostFormProps } from '@/types/post-form.js';
|
||||||
|
|
||||||
export const openingWindowsCount = ref(0);
|
export const openingWindowsCount = ref(0);
|
||||||
|
|
||||||
export const apiWithDialog = (<E extends keyof Misskey.Endpoints = keyof Misskey.Endpoints, P extends Misskey.Endpoints[E]['req'] = Misskey.Endpoints[E]['req']>(
|
export const apiWithDialog = (<E extends keyof Misskey.Endpoints, P extends Misskey.Endpoints[E]['req'] = Misskey.Endpoints[E]['req']>(
|
||||||
endpoint: E,
|
endpoint: E,
|
||||||
data: P = {} as any,
|
data: P,
|
||||||
token?: string | null | undefined,
|
token?: string | null | undefined,
|
||||||
customErrors?: Record<string, { title?: string; text: string; }>,
|
customErrors?: Record<string, { title?: string; text: string; }>,
|
||||||
) => {
|
) => {
|
||||||
|
@ -94,7 +95,7 @@ export const apiWithDialog = (<E extends keyof Misskey.Endpoints = keyof Misskey
|
||||||
|
|
||||||
export function promiseDialog<T extends Promise<any>>(
|
export function promiseDialog<T extends Promise<any>>(
|
||||||
promise: T,
|
promise: T,
|
||||||
onSuccess?: ((res: any) => void) | null,
|
onSuccess?: ((res: Awaited<T>) => void) | null,
|
||||||
onFailure?: ((err: Misskey.api.APIError) => void) | null,
|
onFailure?: ((err: Misskey.api.APIError) => void) | null,
|
||||||
text?: string,
|
text?: string,
|
||||||
): T {
|
): T {
|
||||||
|
@ -135,20 +136,40 @@ export function promiseDialog<T extends Promise<any>>(
|
||||||
return promise;
|
return promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counter for generating unique popup IDs.
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
let popupIdCount = 0;
|
let popupIdCount = 0;
|
||||||
export const popups = ref([]) as Ref<{
|
|
||||||
|
/**
|
||||||
|
* A reactive list of the currently opened popups. This is used in a Vue component
|
||||||
|
* in a v-for loop to render the popups.
|
||||||
|
*/
|
||||||
|
export const popups = ref<{
|
||||||
id: number;
|
id: number;
|
||||||
component: Component;
|
component: Component;
|
||||||
props: Record<string, any>;
|
props: Record<string, any>;
|
||||||
events: Record<string, any>;
|
events: Record<string, any>;
|
||||||
}[]>;
|
}[]>([]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object containing z-index values for different priority levels.
|
||||||
|
*/
|
||||||
const zIndexes = {
|
const zIndexes = {
|
||||||
veryLow: 500000,
|
veryLow: 500000,
|
||||||
low: 1000000,
|
low: 1000000,
|
||||||
middle: 2000000,
|
middle: 2000000,
|
||||||
high: 3000000,
|
high: 3000000,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Claims a z-index value for a given priority level.
|
||||||
|
* Increments the z-index value for the specified priority by 100 and returns the new value.
|
||||||
|
*
|
||||||
|
* @param {keyof typeof zIndexes} [priority='low'] - The priority level for which to claim a z-index.
|
||||||
|
* @returns {number} The new z-index value for the specified priority.
|
||||||
|
*/
|
||||||
export function claimZIndex(priority: keyof typeof zIndexes = 'low'): number {
|
export function claimZIndex(priority: keyof typeof zIndexes = 'low'): number {
|
||||||
zIndexes[priority] += 100;
|
zIndexes[priority] += 100;
|
||||||
return zIndexes[priority];
|
return zIndexes[priority];
|
||||||
|
@ -176,6 +197,15 @@ type EmitsExtractor<T> = {
|
||||||
[K in keyof T as K extends `onVnode${string}` ? never : K extends `on${infer E}` ? Uncapitalize<E> : K extends string ? never : K]: T[K];
|
[K in keyof T as K extends `onVnode${string}` ? never : K extends `on${infer E}` ? Uncapitalize<E> : K extends string ? never : K]: T[K];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a popup with the specified component, props, and events.
|
||||||
|
*
|
||||||
|
* @template T - The type of the component.
|
||||||
|
* @param {T} component - The Vue component to display in the popup.
|
||||||
|
* @param {ComponentProps<T>} props - The props to pass to the component.
|
||||||
|
* @param {ComponentEmit<T>} [events={}] - The events to bind to the component.
|
||||||
|
* @returns {{ dispose: () => void }} An object containing a dispose function to close the popup.
|
||||||
|
*/
|
||||||
export function popup<T extends Component>(
|
export function popup<T extends Component>(
|
||||||
component: T,
|
component: T,
|
||||||
props: ComponentProps<T>,
|
props: ComponentProps<T>,
|
||||||
|
@ -183,13 +213,18 @@ export function popup<T extends Component>(
|
||||||
): { dispose: () => void } {
|
): { dispose: () => void } {
|
||||||
markRaw(component);
|
markRaw(component);
|
||||||
|
|
||||||
|
// Generate a unique ID for this popup.
|
||||||
const id = ++popupIdCount;
|
const id = ++popupIdCount;
|
||||||
|
|
||||||
|
// On disposal, remove this popup from the list of open popups.
|
||||||
const dispose = () => {
|
const dispose = () => {
|
||||||
// このsetTimeoutが無いと挙動がおかしくなる(autocompleteが閉じなくなる)。Vueのバグ?
|
// このsetTimeoutが無いと挙動がおかしくなる(autocompleteが閉じなくなる)。Vueのバグ?
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
popups.value = popups.value.filter(p => p.id !== id);
|
popups.value = popups.value.filter(p => p.id !== id);
|
||||||
}, 0);
|
}, 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Bundle the component, props, and events into a state object.
|
||||||
const state = {
|
const state = {
|
||||||
component,
|
component,
|
||||||
props,
|
props,
|
||||||
|
@ -197,13 +232,19 @@ export function popup<T extends Component>(
|
||||||
id,
|
id,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Add the popup to the list of open popups.
|
||||||
popups.value.push(state);
|
popups.value.push(state);
|
||||||
|
|
||||||
|
// Return a function that can be called to close the popup.
|
||||||
return {
|
return {
|
||||||
dispose,
|
dispose,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open the page with the given path in a pop-up window.
|
||||||
|
* @param path The path of the page to open.
|
||||||
|
*/
|
||||||
export function pageWindow(path: string) {
|
export function pageWindow(path: string) {
|
||||||
const { dispose } = popup(MkPageWindow, {
|
const { dispose } = popup(MkPageWindow, {
|
||||||
initialPath: path,
|
initialPath: path,
|
||||||
|
@ -212,6 +253,11 @@ export function pageWindow(path: string) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a toast message to the user.
|
||||||
|
*
|
||||||
|
* @param {string} message - The message to display in the toast.
|
||||||
|
*/
|
||||||
export function toast(message: string) {
|
export function toast(message: string) {
|
||||||
const { dispose } = popup(MkToast, {
|
const { dispose } = popup(MkToast, {
|
||||||
message,
|
message,
|
||||||
|
@ -220,6 +266,15 @@ export function toast(message: string) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays an alert dialog to the user.
|
||||||
|
*
|
||||||
|
* @param {Object} props - The properties for the alert dialog.
|
||||||
|
* @param {'error' | 'info' | 'success' | 'warning' | 'waiting' | 'question'} [props.type] - The type of the alert.
|
||||||
|
* @param {string} [props.title] - The title of the alert dialog.
|
||||||
|
* @param {string} [props.text] - The text content of the alert dialog.
|
||||||
|
* @returns {Promise<void>} A promise that resolves when the alert dialog is closed.
|
||||||
|
*/
|
||||||
export function alert(props: {
|
export function alert(props: {
|
||||||
type?: 'error' | 'info' | 'success' | 'warning' | 'waiting' | 'question';
|
type?: 'error' | 'info' | 'success' | 'warning' | 'waiting' | 'question';
|
||||||
title?: string;
|
title?: string;
|
||||||
|
@ -458,7 +513,7 @@ type SelectItem<C> = {
|
||||||
};
|
};
|
||||||
|
|
||||||
// default が指定されていたら result は null になり得ないことを保証する overload function
|
// default が指定されていたら result は null になり得ないことを保証する overload function
|
||||||
export function select<C = any>(props: {
|
export function select<C = unknown>(props: {
|
||||||
title?: string;
|
title?: string;
|
||||||
text?: string;
|
text?: string;
|
||||||
default: string;
|
default: string;
|
||||||
|
@ -471,7 +526,7 @@ export function select<C = any>(props: {
|
||||||
} | {
|
} | {
|
||||||
canceled: false; result: C;
|
canceled: false; result: C;
|
||||||
}>;
|
}>;
|
||||||
export function select<C = any>(props: {
|
export function select<C = unknown>(props: {
|
||||||
title?: string;
|
title?: string;
|
||||||
text?: string;
|
text?: string;
|
||||||
default?: string | null;
|
default?: string | null;
|
||||||
|
@ -484,7 +539,7 @@ export function select<C = any>(props: {
|
||||||
} | {
|
} | {
|
||||||
canceled: false; result: C | null;
|
canceled: false; result: C | null;
|
||||||
}>;
|
}>;
|
||||||
export function select<C = any>(props: {
|
export function select<C = unknown>(props: {
|
||||||
title?: string;
|
title?: string;
|
||||||
text?: string;
|
text?: string;
|
||||||
default?: string | null;
|
default?: string | null;
|
||||||
|
@ -687,13 +742,13 @@ export function contextMenu(items: MenuItem[], ev: MouseEvent): Promise<void> {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function post(props: Record<string, any> = {}): Promise<void> {
|
export function post(props: PostFormProps = {}): Promise<void> {
|
||||||
pleaseLogin({
|
pleaseLogin({
|
||||||
openOnRemote: (props.initialText || props.initialNote ? {
|
openOnRemote: (props.initialText || props.initialNote ? {
|
||||||
type: 'share',
|
type: 'share',
|
||||||
params: {
|
params: {
|
||||||
text: props.initialText ?? props.initialNote.text,
|
text: props.initialText ?? props.initialNote?.text ?? '',
|
||||||
visibility: props.initialVisibility ?? props.initialNote?.visibility,
|
visibility: props.initialVisibility ?? props.initialNote?.visibility ?? 'public',
|
||||||
localOnly: (props.initialLocalOnly || props.initialNote?.localOnly) ? '1' : '0',
|
localOnly: (props.initialLocalOnly || props.initialNote?.localOnly) ? '1' : '0',
|
||||||
},
|
},
|
||||||
} : undefined),
|
} : undefined),
|
||||||
|
|
|
@ -272,6 +272,9 @@ const patronsWithIcon = [{
|
||||||
}, {
|
}, {
|
||||||
name: 'Yatoigawa',
|
name: 'Yatoigawa',
|
||||||
icon: 'https://assets.misskey-hub.net/patrons/505e3568885a4a488431a8f22b4553d0.jpg',
|
icon: 'https://assets.misskey-hub.net/patrons/505e3568885a4a488431a8f22b4553d0.jpg',
|
||||||
|
}, {
|
||||||
|
name: '秋瀬カヲル',
|
||||||
|
icon: 'https://assets.misskey-hub.net/patrons/0f22aeb866484f4fa51db6721e3f9847.jpg',
|
||||||
}];
|
}];
|
||||||
|
|
||||||
const patrons = [
|
const patrons = [
|
||||||
|
@ -380,6 +383,7 @@ const patrons = [
|
||||||
'ケモナーのケシン',
|
'ケモナーのケシン',
|
||||||
'こまつぶり',
|
'こまつぶり',
|
||||||
'まゆつな空高',
|
'まゆつな空高',
|
||||||
|
'asata',
|
||||||
];
|
];
|
||||||
|
|
||||||
const thereIsTreasure = ref($i && !claimedAchievements.includes('foundTreasure'));
|
const thereIsTreasure = ref($i && !claimedAchievements.includes('foundTreasure'));
|
||||||
|
|
|
@ -99,19 +99,19 @@ async function addUser() {
|
||||||
const { canceled: canceled1, result: username } = await os.inputText({
|
const { canceled: canceled1, result: username } = await os.inputText({
|
||||||
title: i18n.ts.username,
|
title: i18n.ts.username,
|
||||||
});
|
});
|
||||||
if (canceled1) return;
|
if (canceled1 || username == null) return;
|
||||||
|
|
||||||
const { canceled: canceled2, result: password } = await os.inputText({
|
const { canceled: canceled2, result: password } = await os.inputText({
|
||||||
title: i18n.ts.password,
|
title: i18n.ts.password,
|
||||||
type: 'password',
|
type: 'password',
|
||||||
});
|
});
|
||||||
if (canceled2) return;
|
if (canceled2 || password == null) return;
|
||||||
|
|
||||||
os.apiWithDialog('admin/accounts/create', {
|
os.apiWithDialog('admin/accounts/create', {
|
||||||
username: username,
|
username: username,
|
||||||
password: password,
|
password: password,
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
paginationComponent.value.reload();
|
paginationComponent.value?.reload();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -103,7 +103,7 @@ const headerActions = computed(() => []);
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
definePageMetadata(() => ({
|
definePageMetadata(() => ({
|
||||||
title: announcement.value ? `${i18n.ts.announcements}: ${announcement.value.title}` : i18n.ts.announcements,
|
title: announcement.value ? announcement.value.title : i18n.ts.announcements,
|
||||||
icon: 'ti ti-speakerphone',
|
icon: 'ti ti-speakerphone',
|
||||||
}));
|
}));
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -62,7 +62,7 @@ function accepted() {
|
||||||
state.value = 'accepted';
|
state.value = 'accepted';
|
||||||
if (session.value && session.value.app.callbackUrl) {
|
if (session.value && session.value.app.callbackUrl) {
|
||||||
const url = new URL(session.value.app.callbackUrl);
|
const url = new URL(session.value.app.callbackUrl);
|
||||||
if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:'].includes(url.protocol)) throw new Error('invalid url');
|
if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:', 'vbscript:'].includes(url.protocol)) throw new Error('invalid url');
|
||||||
location.href = `${session.value.app.callbackUrl}?token=${session.value.token}`;
|
location.href = `${session.value.app.callbackUrl}?token=${session.value.token}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,7 +116,7 @@ const selectAll = () => {
|
||||||
if (selectedEmojis.value.length > 0) {
|
if (selectedEmojis.value.length > 0) {
|
||||||
selectedEmojis.value = [];
|
selectedEmojis.value = [];
|
||||||
} else {
|
} else {
|
||||||
selectedEmojis.value = Array.from(emojisPaginationComponent.value.items.values(), item => item.id);
|
selectedEmojis.value = Array.from(emojisPaginationComponent.value?.items.values(), item => item.id);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -133,7 +133,7 @@ const add = async (ev: MouseEvent) => {
|
||||||
}, {
|
}, {
|
||||||
done: result => {
|
done: result => {
|
||||||
if (result.created) {
|
if (result.created) {
|
||||||
emojisPaginationComponent.value.prepend(result.created);
|
emojisPaginationComponent.value?.prepend(result.created);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
closed: () => dispose(),
|
closed: () => dispose(),
|
||||||
|
@ -146,12 +146,12 @@ const edit = (emoji) => {
|
||||||
}, {
|
}, {
|
||||||
done: result => {
|
done: result => {
|
||||||
if (result.updated) {
|
if (result.updated) {
|
||||||
emojisPaginationComponent.value.updateItem(result.updated.id, (oldEmoji: any) => ({
|
emojisPaginationComponent.value?.updateItem(result.updated.id, (oldEmoji) => ({
|
||||||
...oldEmoji,
|
...oldEmoji,
|
||||||
...result.updated,
|
...result.updated,
|
||||||
}));
|
}));
|
||||||
} else if (result.deleted) {
|
} else if (result.deleted) {
|
||||||
emojisPaginationComponent.value.removeItem(emoji.id);
|
emojisPaginationComponent.value?.removeItem(emoji.id);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
closed: () => dispose(),
|
closed: () => dispose(),
|
||||||
|
@ -226,7 +226,7 @@ const setCategoryBulk = async () => {
|
||||||
ids: selectedEmojis.value,
|
ids: selectedEmojis.value,
|
||||||
category: result,
|
category: result,
|
||||||
});
|
});
|
||||||
emojisPaginationComponent.value.reload();
|
emojisPaginationComponent.value?.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
const setLicenseBulk = async () => {
|
const setLicenseBulk = async () => {
|
||||||
|
@ -238,43 +238,43 @@ const setLicenseBulk = async () => {
|
||||||
ids: selectedEmojis.value,
|
ids: selectedEmojis.value,
|
||||||
license: result,
|
license: result,
|
||||||
});
|
});
|
||||||
emojisPaginationComponent.value.reload();
|
emojisPaginationComponent.value?.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
const addTagBulk = async () => {
|
const addTagBulk = async () => {
|
||||||
const { canceled, result } = await os.inputText({
|
const { canceled, result } = await os.inputText({
|
||||||
title: 'Tag',
|
title: 'Tag',
|
||||||
});
|
});
|
||||||
if (canceled) return;
|
if (canceled || result == null) return;
|
||||||
await os.apiWithDialog('admin/emoji/add-aliases-bulk', {
|
await os.apiWithDialog('admin/emoji/add-aliases-bulk', {
|
||||||
ids: selectedEmojis.value,
|
ids: selectedEmojis.value,
|
||||||
aliases: result.split(' '),
|
aliases: result.split(' '),
|
||||||
});
|
});
|
||||||
emojisPaginationComponent.value.reload();
|
emojisPaginationComponent.value?.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeTagBulk = async () => {
|
const removeTagBulk = async () => {
|
||||||
const { canceled, result } = await os.inputText({
|
const { canceled, result } = await os.inputText({
|
||||||
title: 'Tag',
|
title: 'Tag',
|
||||||
});
|
});
|
||||||
if (canceled) return;
|
if (canceled || result == null) return;
|
||||||
await os.apiWithDialog('admin/emoji/remove-aliases-bulk', {
|
await os.apiWithDialog('admin/emoji/remove-aliases-bulk', {
|
||||||
ids: selectedEmojis.value,
|
ids: selectedEmojis.value,
|
||||||
aliases: result.split(' '),
|
aliases: result.split(' '),
|
||||||
});
|
});
|
||||||
emojisPaginationComponent.value.reload();
|
emojisPaginationComponent.value?.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
const setTagBulk = async () => {
|
const setTagBulk = async () => {
|
||||||
const { canceled, result } = await os.inputText({
|
const { canceled, result } = await os.inputText({
|
||||||
title: 'Tag',
|
title: 'Tag',
|
||||||
});
|
});
|
||||||
if (canceled) return;
|
if (canceled || result == null) return;
|
||||||
await os.apiWithDialog('admin/emoji/set-aliases-bulk', {
|
await os.apiWithDialog('admin/emoji/set-aliases-bulk', {
|
||||||
ids: selectedEmojis.value,
|
ids: selectedEmojis.value,
|
||||||
aliases: result.split(' '),
|
aliases: result.split(' '),
|
||||||
});
|
});
|
||||||
emojisPaginationComponent.value.reload();
|
emojisPaginationComponent.value?.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
const delBulk = async () => {
|
const delBulk = async () => {
|
||||||
|
@ -286,7 +286,7 @@ const delBulk = async () => {
|
||||||
await os.apiWithDialog('admin/emoji/delete-bulk', {
|
await os.apiWithDialog('admin/emoji/delete-bulk', {
|
||||||
ids: selectedEmojis.value,
|
ids: selectedEmojis.value,
|
||||||
});
|
});
|
||||||
emojisPaginationComponent.value.reload();
|
emojisPaginationComponent.value?.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
const headerActions = computed(() => [{
|
const headerActions = computed(() => [{
|
||||||
|
|
|
@ -9,8 +9,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
:initialWidth="400"
|
:initialWidth="400"
|
||||||
:initialHeight="500"
|
:initialHeight="500"
|
||||||
:canResize="true"
|
:canResize="true"
|
||||||
@close="windowEl.close()"
|
@close="windowEl?.close()"
|
||||||
@closed="$emit('closed')"
|
@closed="emit('closed')"
|
||||||
>
|
>
|
||||||
<template v-if="emoji" #header>:{{ emoji.name }}:</template>
|
<template v-if="emoji" #header>:{{ emoji.name }}:</template>
|
||||||
<template v-else #header>New emoji</template>
|
<template v-else #header>New emoji</template>
|
||||||
|
@ -95,14 +95,19 @@ import { selectFile } from '@/scripts/select-file.js';
|
||||||
import MkRolePreview from '@/components/MkRolePreview.vue';
|
import MkRolePreview from '@/components/MkRolePreview.vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
emoji?: any,
|
emoji?: Misskey.entities.EmojiDetailed,
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(ev: 'done', v: { deleted?: boolean; updated?: Misskey.entities.AdminEmojiUpdateRequest; created?: Misskey.entities.AdminEmojiUpdateRequest }): void,
|
||||||
|
(ev: 'closed'): void
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const windowEl = ref<InstanceType<typeof MkWindow> | null>(null);
|
const windowEl = ref<InstanceType<typeof MkWindow> | null>(null);
|
||||||
const name = ref<string>(props.emoji ? props.emoji.name : '');
|
const name = ref<string>(props.emoji ? props.emoji.name : '');
|
||||||
const category = ref<string>(props.emoji ? props.emoji.category : '');
|
const category = ref<string>(props.emoji?.category ? props.emoji.category : '');
|
||||||
const aliases = ref<string>(props.emoji ? props.emoji.aliases.join(' ') : '');
|
const aliases = ref<string>(props.emoji ? props.emoji.aliases.join(' ') : '');
|
||||||
const license = ref<string>(props.emoji ? (props.emoji.license ?? '') : '');
|
const license = ref<string>(props.emoji?.license ? props.emoji.license : '');
|
||||||
const isSensitive = ref(props.emoji ? props.emoji.isSensitive : false);
|
const isSensitive = ref(props.emoji ? props.emoji.isSensitive : false);
|
||||||
const localOnly = ref(props.emoji ? props.emoji.localOnly : false);
|
const localOnly = ref(props.emoji ? props.emoji.localOnly : false);
|
||||||
const roleIdsThatCanBeUsedThisEmojiAsReaction = ref(props.emoji ? props.emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : []);
|
const roleIdsThatCanBeUsedThisEmojiAsReaction = ref(props.emoji ? props.emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : []);
|
||||||
|
@ -115,12 +120,7 @@ watch(roleIdsThatCanBeUsedThisEmojiAsReaction, async () => {
|
||||||
|
|
||||||
const imgUrl = computed(() => file.value ? file.value.url : props.emoji ? `/emoji/${props.emoji.name}.webp` : null);
|
const imgUrl = computed(() => file.value ? file.value.url : props.emoji ? `/emoji/${props.emoji.name}.webp` : null);
|
||||||
|
|
||||||
const emit = defineEmits<{
|
async function changeImage(ev: Event) {
|
||||||
(ev: 'done', v: { deleted?: boolean; updated?: any; created?: any }): void,
|
|
||||||
(ev: 'closed'): void
|
|
||||||
}>();
|
|
||||||
|
|
||||||
async function changeImage(ev) {
|
|
||||||
file.value = await selectFile(ev.currentTarget ?? ev.target, null);
|
file.value = await selectFile(ev.currentTarget ?? ev.target, null);
|
||||||
const candidate = file.value.name.replace(/\.(.+)$/, '');
|
const candidate = file.value.name.replace(/\.(.+)$/, '');
|
||||||
if (candidate.match(/^[a-z0-9_]+$/)) {
|
if (candidate.match(/^[a-z0-9_]+$/)) {
|
||||||
|
@ -140,7 +140,7 @@ async function addRole() {
|
||||||
rolesThatCanBeUsedThisEmojiAsReaction.value.push(role);
|
rolesThatCanBeUsedThisEmojiAsReaction.value.push(role);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function removeRole(role, ev) {
|
async function removeRole(role: Misskey.entities.RoleLite, ev: Event) {
|
||||||
rolesThatCanBeUsedThisEmojiAsReaction.value = rolesThatCanBeUsedThisEmojiAsReaction.value.filter(x => x.id !== role.id);
|
rolesThatCanBeUsedThisEmojiAsReaction.value = rolesThatCanBeUsedThisEmojiAsReaction.value.filter(x => x.id !== role.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,7 +172,7 @@ async function done() {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
windowEl.value.close();
|
windowEl.value?.close();
|
||||||
} else {
|
} else {
|
||||||
const created = await os.apiWithDialog('admin/emoji/add', params);
|
const created = await os.apiWithDialog('admin/emoji/add', params);
|
||||||
|
|
||||||
|
@ -180,11 +180,12 @@ async function done() {
|
||||||
created: created,
|
created: created,
|
||||||
});
|
});
|
||||||
|
|
||||||
windowEl.value.close();
|
windowEl.value?.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function del() {
|
async function del() {
|
||||||
|
if (!props.emoji) return;
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
text: i18n.tsx.removeAreYouSure({ x: name.value }),
|
text: i18n.tsx.removeAreYouSure({ x: name.value }),
|
||||||
|
@ -197,7 +198,7 @@ async function del() {
|
||||||
emit('done', {
|
emit('done', {
|
||||||
deleted: true,
|
deleted: true,
|
||||||
});
|
});
|
||||||
windowEl.value.close();
|
windowEl.value?.close();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -55,13 +55,13 @@ const pagination = {
|
||||||
|
|
||||||
function accept(user) {
|
function accept(user) {
|
||||||
misskeyApi('following/requests/accept', { userId: user.id }).then(() => {
|
misskeyApi('following/requests/accept', { userId: user.id }).then(() => {
|
||||||
paginationComponent.value.reload();
|
paginationComponent.value?.reload();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function reject(user) {
|
function reject(user) {
|
||||||
misskeyApi('following/requests/reject', { userId: user.id }).then(() => {
|
misskeyApi('following/requests/reject', { userId: user.id }).then(() => {
|
||||||
paginationComponent.value.reload();
|
paginationComponent.value?.reload();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ function fetch() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let promise: Promise<any>;
|
let promise: Promise<unknown>;
|
||||||
|
|
||||||
if (uri.startsWith('https://')) {
|
if (uri.startsWith('https://')) {
|
||||||
promise = misskeyApi('ap/show', {
|
promise = misskeyApi('ap/show', {
|
||||||
|
|
|
@ -65,7 +65,7 @@ async function onAccept(token: string) {
|
||||||
|
|
||||||
if (props.callback && props.callback !== '') {
|
if (props.callback && props.callback !== '') {
|
||||||
const cbUrl = new URL(props.callback);
|
const cbUrl = new URL(props.callback);
|
||||||
if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:'].includes(cbUrl.protocol)) throw new Error('invalid url');
|
if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:', 'vbscript:'].includes(cbUrl.protocol)) throw new Error('invalid url');
|
||||||
cbUrl.searchParams.set('session', props.session);
|
cbUrl.searchParams.set('session', props.session);
|
||||||
location.href = cbUrl.toString();
|
location.href = cbUrl.toString();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -77,15 +77,15 @@ async function create() {
|
||||||
|
|
||||||
clipsCache.delete();
|
clipsCache.delete();
|
||||||
|
|
||||||
pagingComponent.value.reload();
|
pagingComponent.value?.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onClipCreated() {
|
function onClipCreated() {
|
||||||
pagingComponent.value.reload();
|
pagingComponent.value?.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onClipDeleted() {
|
function onClipDeleted() {
|
||||||
pagingComponent.value.reload();
|
pagingComponent.value?.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
const headerActions = computed(() => []);
|
const headerActions = computed(() => []);
|
||||||
|
|
|
@ -110,7 +110,7 @@ function addUser() {
|
||||||
listId: list.value.id,
|
listId: list.value.id,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
paginationEl.value.reload();
|
paginationEl.value?.reload();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -126,7 +126,7 @@ async function removeUser(item, ev) {
|
||||||
listId: list.value.id,
|
listId: list.value.id,
|
||||||
userId: item.userId,
|
userId: item.userId,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
paginationEl.value.removeItem(item.id);
|
paginationEl.value?.removeItem(item.id);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}], ev.currentTarget ?? ev.target);
|
}], ev.currentTarget ?? ev.target);
|
||||||
|
|
|
@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- eslint-disable vue/no-mutating-props -->
|
<!-- eslint-disable vue/no-mutating-props -->
|
||||||
<XContainer :draggable="true" @remove="() => $emit('remove')">
|
<XContainer :draggable="true" @remove="() => emit('remove')">
|
||||||
<template #header><i class="ti ti-photo"></i> {{ i18n.ts._pages.blocks.image }}</template>
|
<template #header><i class="ti ti-photo"></i> {{ i18n.ts._pages.blocks.image }}</template>
|
||||||
<template #func>
|
<template #func>
|
||||||
<button @click="choose()">
|
<button @click="choose()">
|
||||||
|
@ -30,11 +30,12 @@ import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: any
|
modelValue: Misskey.entities.PageBlock & { type: 'image' };
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'update:modelValue', value: any): void;
|
(ev: 'update:modelValue', value: Misskey.entities.PageBlock & { type: 'image' }): void;
|
||||||
|
(ev: 'remove'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const file = ref<Misskey.entities.DriveFile | null>(null);
|
const file = ref<Misskey.entities.DriveFile | null>(null);
|
||||||
|
|
|
@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- eslint-disable vue/no-mutating-props -->
|
<!-- eslint-disable vue/no-mutating-props -->
|
||||||
<XContainer :draggable="true" @remove="() => $emit('remove')">
|
<XContainer :draggable="true" @remove="() => emit('remove')">
|
||||||
<template #header><i class="ti ti-note"></i> {{ i18n.ts._pages.blocks.note }}</template>
|
<template #header><i class="ti ti-note"></i> {{ i18n.ts._pages.blocks.note }}</template>
|
||||||
|
|
||||||
<section style="padding: 16px;" class="_gaps_s">
|
<section style="padding: 16px;" class="_gaps_s">
|
||||||
|
@ -34,19 +34,24 @@ import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: any
|
modelValue: Misskey.entities.PageBlock & { type: 'note' };
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'update:modelValue', value: any): void;
|
(ev: 'update:modelValue', value: Misskey.entities.PageBlock & { type: 'note' }): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const id = ref<any>(props.modelValue.note);
|
const id = ref(props.modelValue.note);
|
||||||
const note = ref<Misskey.entities.Note | null>(null);
|
const note = ref<Misskey.entities.Note | null>(null);
|
||||||
|
|
||||||
watch(id, async () => {
|
watch(id, async () => {
|
||||||
if (id.value && (id.value.startsWith('http://') || id.value.startsWith('https://'))) {
|
if (id.value && (id.value.startsWith('http://') || id.value.startsWith('https://'))) {
|
||||||
id.value = (id.value.endsWith('/') ? id.value.slice(0, -1) : id.value).split('/').pop();
|
id.value = (id.value.endsWith('/') ? id.value.slice(0, -1) : id.value).split('/').pop() ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!id.value) {
|
||||||
|
note.value = null;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
emit('update:modelValue', {
|
emit('update:modelValue', {
|
||||||
|
|
|
@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- eslint-disable vue/no-mutating-props -->
|
<!-- eslint-disable vue/no-mutating-props -->
|
||||||
<XContainer :draggable="true" @remove="() => $emit('remove')">
|
<XContainer :draggable="true" @remove="() => emit('remove')">
|
||||||
<template #header><i class="ti ti-note"></i> {{ props.modelValue.title }}</template>
|
<template #header><i class="ti ti-note"></i> {{ props.modelValue.title }}</template>
|
||||||
<template #func>
|
<template #func>
|
||||||
<button class="_button" @click="rename()">
|
<button class="_button" @click="rename()">
|
||||||
|
@ -21,8 +21,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
/* eslint-disable vue/no-mutating-props */
|
|
||||||
import { defineAsyncComponent, inject, onMounted, watch, ref } from 'vue';
|
import { defineAsyncComponent, inject, onMounted, watch, ref } from 'vue';
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import XContainer from '../page-editor.container.vue';
|
import XContainer from '../page-editor.container.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
|
@ -33,14 +34,13 @@ 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 = withDefaults(defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: any,
|
modelValue: Misskey.entities.PageBlock & { type: 'section'; },
|
||||||
}>(), {
|
}>();
|
||||||
modelValue: {},
|
|
||||||
});
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'update:modelValue', value: any): void;
|
(ev: 'update:modelValue', value: Misskey.entities.PageBlock & { type: 'section' }): void;
|
||||||
|
(ev: 'remove'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const children = ref(deepClone(props.modelValue.children ?? []));
|
const children = ref(deepClone(props.modelValue.children ?? []));
|
||||||
|
|
|
@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- eslint-disable vue/no-mutating-props -->
|
<!-- eslint-disable vue/no-mutating-props -->
|
||||||
<XContainer :draggable="true" @remove="() => $emit('remove')">
|
<XContainer :draggable="true" @remove="() => emit('remove')">
|
||||||
<template #header><i class="ti ti-align-left"></i> {{ i18n.ts._pages.blocks.text }}</template>
|
<template #header><i class="ti ti-align-left"></i> {{ i18n.ts._pages.blocks.text }}</template>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
|
@ -15,18 +15,19 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
/* eslint-disable vue/no-mutating-props */
|
|
||||||
import { watch, ref, shallowRef, onMounted, onUnmounted } from 'vue';
|
import { watch, ref, shallowRef, onMounted, onUnmounted } from 'vue';
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
import XContainer from '../page-editor.container.vue';
|
import XContainer from '../page-editor.container.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { Autocomplete } from '@/scripts/autocomplete.js';
|
import { Autocomplete } from '@/scripts/autocomplete.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: any
|
modelValue: Misskey.entities.PageBlock & { type: 'text' }
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'update:modelValue', value: any): void;
|
(ev: 'update:modelValue', value: Misskey.entities.PageBlock & { type: 'text' }): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
let autocomplete: Autocomplete;
|
let autocomplete: Autocomplete;
|
||||||
|
|
|
@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Sortable :modelValue="modelValue" tag="div" itemKey="id" handle=".drag-handle" :group="{ name: 'blocks' }" :animation="150" :swapThreshold="0.5" @update:modelValue="v => $emit('update:modelValue', v)">
|
<Sortable :modelValue="modelValue" tag="div" itemKey="id" handle=".drag-handle" :group="{ name: 'blocks' }" :animation="150" :swapThreshold="0.5" @update:modelValue="v => emit('update:modelValue', v)">
|
||||||
<template #item="{element}">
|
<template #item="{element}">
|
||||||
<div :class="$style.item">
|
<div :class="$style.item">
|
||||||
<!-- divが無いとエラーになる https://github.com/SortableJS/vue.draggable.next/issues/189 -->
|
<!-- divが無いとエラーになる https://github.com/SortableJS/vue.draggable.next/issues/189 -->
|
||||||
|
|
|
@ -52,7 +52,7 @@ const props = defineProps<{
|
||||||
|
|
||||||
const scope = computed(() => props.path ? props.path.split('/') : []);
|
const scope = computed(() => props.path ? props.path.split('/') : []);
|
||||||
|
|
||||||
const keys = ref<any>(null);
|
const keys = ref<[string, string][]>([]);
|
||||||
|
|
||||||
function fetchKeys() {
|
function fetchKeys() {
|
||||||
misskeyApi('i/registry/keys-with-type', {
|
misskeyApi('i/registry/keys-with-type', {
|
||||||
|
|
|
@ -132,7 +132,7 @@ const mapCategories = Array.from(new Set(Object.values(Reversi.maps).map(x => x.
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
game: Misskey.entities.ReversiGameDetailed;
|
game: Misskey.entities.ReversiGameDetailed;
|
||||||
connection: Misskey.ChannelConnection;
|
connection: Misskey.ChannelConnection<Misskey.Channels['reversiGame']>;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const shareWhenStart = defineModel<boolean>('shareWhenStart', { default: false });
|
const shareWhenStart = defineModel<boolean>('shareWhenStart', { default: false });
|
||||||
|
@ -217,14 +217,14 @@ function onChangeReadyStates(states) {
|
||||||
game.value.user2Ready = states.user2;
|
game.value.user2Ready = states.user2;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSettings(key: keyof Misskey.entities.ReversiGameDetailed) {
|
function updateSettings(key: typeof Misskey.reversiUpdateKeys[number]) {
|
||||||
props.connection.send('updateSettings', {
|
props.connection.send('updateSettings', {
|
||||||
key: key,
|
key: key,
|
||||||
value: game.value[key],
|
value: game.value[key],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function onUpdateSettings({ userId, key, value }: { userId: string; key: keyof Misskey.entities.ReversiGameDetailed; value: any; }) {
|
function onUpdateSettings<K extends typeof Misskey.reversiUpdateKeys[number]>({ userId, key, value }: { userId: string; key: K; value: Misskey.entities.ReversiGameDetailed[K]; }) {
|
||||||
if (userId === $i.id) return;
|
if (userId === $i.id) return;
|
||||||
if (game.value[key] === value) return;
|
if (game.value[key] === value) return;
|
||||||
game.value[key] = value;
|
game.value[key] = value;
|
||||||
|
|
|
@ -76,7 +76,11 @@ import { claimAchievement } from '@/scripts/achievements.js';
|
||||||
const parser = new Parser();
|
const parser = new Parser();
|
||||||
let aiscript: Interpreter;
|
let aiscript: Interpreter;
|
||||||
const code = ref('');
|
const code = ref('');
|
||||||
const logs = ref<any[]>([]);
|
const logs = ref<{
|
||||||
|
id: number;
|
||||||
|
text: string;
|
||||||
|
print: boolean;
|
||||||
|
}[]>([]);
|
||||||
const root = ref<AsUiRoot>();
|
const root = ref<AsUiRoot>();
|
||||||
const components = ref<Ref<AsUiComponent>[]>([]);
|
const components = ref<Ref<AsUiComponent>[]>([]);
|
||||||
const uiKey = ref(0);
|
const uiKey = ref(0);
|
||||||
|
|
|
@ -138,12 +138,13 @@ const token = ref<string | number | null>(null);
|
||||||
const backupCodes = ref<string[]>();
|
const backupCodes = ref<string[]>();
|
||||||
|
|
||||||
function cancel() {
|
function cancel() {
|
||||||
dialog.value.close();
|
dialog.value?.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function tokenDone() {
|
async function tokenDone() {
|
||||||
|
if (token.value == null) return;
|
||||||
const res = await os.apiWithDialog('i/2fa/done', {
|
const res = await os.apiWithDialog('i/2fa/done', {
|
||||||
token: token.value.toString(),
|
token: typeof token.value === 'string' ? token.value : token.value.toString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
backupCodes.value = res.backupCodes;
|
backupCodes.value = res.backupCodes;
|
||||||
|
@ -166,7 +167,7 @@ function downloadBackupCodes() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function allDone() {
|
function allDone() {
|
||||||
dialog.value.close();
|
dialog.value?.close();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -90,7 +90,7 @@ function createAccount() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function switchAccount(account: any) {
|
async function switchAccount(account: Misskey.entities.UserDetailed) {
|
||||||
const fetchedAccounts = await getAccounts();
|
const fetchedAccounts = await getAccounts();
|
||||||
const token = fetchedAccounts.find(x => x.id === account.id)!.token;
|
const token = fetchedAccounts.find(x => x.id === account.id)!.token;
|
||||||
switchAccountWithToken(token);
|
switchAccountWithToken(token);
|
||||||
|
|
|
@ -55,6 +55,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 FormPagination from '@/components/MkPagination.vue';
|
import FormPagination from '@/components/MkPagination.vue';
|
||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
@ -77,7 +78,7 @@ const pagination = {
|
||||||
|
|
||||||
function revoke(token) {
|
function revoke(token) {
|
||||||
misskeyApi('i/revoke-token', { tokenId: token.id }).then(() => {
|
misskeyApi('i/revoke-token', { tokenId: token.id }).then(() => {
|
||||||
list.value.reload();
|
list.value?.reload();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -110,7 +110,7 @@ const decorationsForPreview = computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
function cancel() {
|
function cancel() {
|
||||||
dialog.value.close();
|
dialog.value?.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function update() {
|
async function update() {
|
||||||
|
@ -120,7 +120,7 @@ async function update() {
|
||||||
offsetX: offsetX.value,
|
offsetX: offsetX.value,
|
||||||
offsetY: offsetY.value,
|
offsetY: offsetY.value,
|
||||||
});
|
});
|
||||||
dialog.value.close();
|
dialog.value?.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function attach() {
|
async function attach() {
|
||||||
|
@ -130,12 +130,12 @@ async function attach() {
|
||||||
offsetX: offsetX.value,
|
offsetX: offsetX.value,
|
||||||
offsetY: offsetY.value,
|
offsetY: offsetY.value,
|
||||||
});
|
});
|
||||||
dialog.value.close();
|
dialog.value?.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function detach() {
|
async function detach() {
|
||||||
emit('detach');
|
emit('detach');
|
||||||
dialog.value.close();
|
dialog.value?.close();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkButton transparent :class="$style.testButton" :disabled="!(active && event_renote)" @click="test('renote')"><i class="ti ti-send"></i></MkButton>
|
<MkButton transparent :class="$style.testButton" :disabled="!(active && event_renote)" @click="test('renote')"><i class="ti ti-send"></i></MkButton>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.switchBox">
|
<div :class="$style.switchBox">
|
||||||
<MkSwitch v-model="event_reaction">{{ i18n.ts._webhookSettings._events.reaction }}</MkSwitch>
|
<MkSwitch v-model="event_reaction" :disabled="true">{{ i18n.ts._webhookSettings._events.reaction }}</MkSwitch>
|
||||||
<MkButton transparent :class="$style.testButton" :disabled="!(active && event_reaction)" @click="test('reaction')"><i class="ti ti-send"></i></MkButton>
|
<MkButton transparent :class="$style.testButton" :disabled="!(active && event_reaction)" @click="test('reaction')"><i class="ti ti-send"></i></MkButton>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.switchBox">
|
<div :class="$style.switchBox">
|
||||||
|
|
|
@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkSwitch v-model="event_note">{{ i18n.ts._webhookSettings._events.note }}</MkSwitch>
|
<MkSwitch v-model="event_note">{{ i18n.ts._webhookSettings._events.note }}</MkSwitch>
|
||||||
<MkSwitch v-model="event_reply">{{ i18n.ts._webhookSettings._events.reply }}</MkSwitch>
|
<MkSwitch v-model="event_reply">{{ i18n.ts._webhookSettings._events.reply }}</MkSwitch>
|
||||||
<MkSwitch v-model="event_renote">{{ i18n.ts._webhookSettings._events.renote }}</MkSwitch>
|
<MkSwitch v-model="event_renote">{{ i18n.ts._webhookSettings._events.renote }}</MkSwitch>
|
||||||
<MkSwitch v-model="event_reaction">{{ i18n.ts._webhookSettings._events.reaction }}</MkSwitch>
|
<MkSwitch v-model="event_reaction" :disabled="true">{{ i18n.ts._webhookSettings._events.reaction }}</MkSwitch>
|
||||||
<MkSwitch v-model="event_mention">{{ i18n.ts._webhookSettings._events.mention }}</MkSwitch>
|
<MkSwitch v-model="event_mention">{{ i18n.ts._webhookSettings._events.mention }}</MkSwitch>
|
||||||
</div>
|
</div>
|
||||||
</FormSection>
|
</FormSection>
|
||||||
|
|
|
@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div :class="$style.tl">
|
<div :class="$style.tl">
|
||||||
<MkTimeline
|
<MkTimeline
|
||||||
ref="tlComponent"
|
ref="tlComponent"
|
||||||
:key="src + withRenotes + withReplies + onlyFiles"
|
:key="src + withRenotes + withReplies + onlyFiles + withSensitive"
|
||||||
:src="src.split(':')[0]"
|
:src="src.split(':')[0]"
|
||||||
:list="src.split(':')[1]"
|
:list="src.split(':')[1]"
|
||||||
:withRenotes="withRenotes"
|
:withRenotes="withRenotes"
|
||||||
|
|
|
@ -257,7 +257,7 @@ function parallaxLoop() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function parallax() {
|
function parallax() {
|
||||||
const banner = bannerEl.value as any;
|
const banner = bannerEl.value;
|
||||||
if (banner == null) return;
|
if (banner == null) return;
|
||||||
|
|
||||||
const top = getScrollPosition(rootEl.value);
|
const top = getScrollPosition(rootEl.value);
|
||||||
|
|
|
@ -241,9 +241,13 @@ export class Storage<T extends StateDef> {
|
||||||
* 特定のキーの、簡易的なgetter/setterを作ります
|
* 特定のキーの、簡易的なgetter/setterを作ります
|
||||||
* 主にvue上で設定コントロールのmodelとして使う用
|
* 主にvue上で設定コントロールのmodelとして使う用
|
||||||
*/
|
*/
|
||||||
public makeGetterSetter<K extends keyof T>(key: K, getter?: (v: T[K]) => unknown, setter?: (v: unknown) => T[K]): {
|
public makeGetterSetter<K extends keyof T, R = T[K]['default']>(
|
||||||
get: () => T[K]['default'];
|
key: K,
|
||||||
set: (value: T[K]['default']) => void;
|
getter?: (v: T[K]['default']) => R,
|
||||||
|
setter?: (v: R) => T[K]['default'],
|
||||||
|
): {
|
||||||
|
get: () => R;
|
||||||
|
set: (value: R) => void;
|
||||||
} {
|
} {
|
||||||
const valueRef = ref(this.state[key]);
|
const valueRef = ref(this.state[key]);
|
||||||
|
|
||||||
|
@ -265,7 +269,7 @@ export class Storage<T extends StateDef> {
|
||||||
return valueRef.value;
|
return valueRef.value;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
set: (value: unknown) => {
|
set: (value) => {
|
||||||
const val = setter ? setter(value) : value;
|
const val = setter ? setter(value) : value;
|
||||||
this.set(key, val);
|
this.set(key, val);
|
||||||
valueRef.value = val;
|
valueRef.value = val;
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { $i, iAmModerator } from '@/account.js';
|
||||||
import MkLoading from '@/pages/_loading_.vue';
|
import MkLoading from '@/pages/_loading_.vue';
|
||||||
import MkError from '@/pages/_error_.vue';
|
import MkError from '@/pages/_error_.vue';
|
||||||
|
|
||||||
export const page = (loader: AsyncComponentLoader<any>) => defineAsyncComponent({
|
export const page = (loader: AsyncComponentLoader) => defineAsyncComponent({
|
||||||
loader: loader,
|
loader: loader,
|
||||||
loadingComponent: MkLoading,
|
loadingComponent: MkLoading,
|
||||||
errorComponent: MkError,
|
errorComponent: MkError,
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EventEmitter } from 'eventemitter3';
|
import { EventEmitter } from 'eventemitter3';
|
||||||
import { IRouter, Resolved, RouteDef, RouterEvent } from '@/nirax.js';
|
import { IRouter, Resolved, RouteDef, RouterEvent, RouterFlag } from '@/nirax.js';
|
||||||
|
|
||||||
import type { App, ShallowRef } from 'vue';
|
import type { App, ShallowRef } from 'vue';
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ class MainRouterProxy implements IRouter {
|
||||||
return this.supplier().currentRoute;
|
return this.supplier().currentRoute;
|
||||||
}
|
}
|
||||||
|
|
||||||
get navHook(): ((path: string, flag?: any) => boolean) | null {
|
get navHook(): ((path: string, flag?: RouterFlag) => boolean) | null {
|
||||||
return this.supplier().navHook;
|
return this.supplier().navHook;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,11 +91,11 @@ class MainRouterProxy implements IRouter {
|
||||||
return this.supplier().getCurrentKey();
|
return this.supplier().getCurrentKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentPath(): any {
|
getCurrentPath(): string {
|
||||||
return this.supplier().getCurrentPath();
|
return this.supplier().getCurrentPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
push(path: string, flag?: any): void {
|
push(path: string, flag?: RouterFlag): void {
|
||||||
this.supplier().push(path, flag);
|
this.supplier().push(path, flag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,9 @@
|
||||||
* 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
|
||||||
*/
|
*/
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
|
|
||||||
export function checkWordMute(note: Record<string, any>, me: Record<string, any> | null | undefined, mutedWords: Array<string | string[]>): boolean {
|
export function checkWordMute(note: Misskey.entities.Note, me: Misskey.entities.UserLite | null | undefined, mutedWords: Array<string | string[]>): boolean {
|
||||||
// 自分自身
|
// 自分自身
|
||||||
if (me && (note.userId === me.id)) return false;
|
if (me && (note.userId === me.id)) return false;
|
||||||
|
|
||||||
|
|
|
@ -3,22 +3,22 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { defaultStore } from '@/store.js';
|
export type DeviceKind = 'smartphone' | 'tablet' | 'desktop';
|
||||||
|
|
||||||
await defaultStore.ready;
|
|
||||||
|
|
||||||
const ua = navigator.userAgent.toLowerCase();
|
const ua = navigator.userAgent.toLowerCase();
|
||||||
const isTablet = /ipad/.test(ua) || (/mobile|iphone|android/.test(ua) && window.innerWidth > 700);
|
const isTablet = /ipad/.test(ua) || (/mobile|iphone|android/.test(ua) && window.innerWidth > 700);
|
||||||
const isSmartphone = !isTablet && /mobile|iphone|android/.test(ua);
|
const isSmartphone = !isTablet && /mobile|iphone|android/.test(ua);
|
||||||
|
|
||||||
const isIPhone = /iphone|ipod/gi.test(ua) && navigator.maxTouchPoints > 1;
|
export const DEFAULT_DEVICE_KIND: DeviceKind = (
|
||||||
// navigator.platform may be deprecated but this check is still required
|
isSmartphone
|
||||||
const isIPadOS = navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1;
|
? 'smartphone'
|
||||||
const isIos = /ipad|iphone|ipod/gi.test(ua) && navigator.maxTouchPoints > 1;
|
: isTablet
|
||||||
|
? 'tablet'
|
||||||
|
: 'desktop'
|
||||||
|
);
|
||||||
|
|
||||||
export const isFullscreenNotSupported = isIPhone || isIos;
|
export let deviceKind: DeviceKind = DEFAULT_DEVICE_KIND;
|
||||||
|
|
||||||
export const deviceKind: 'smartphone' | 'tablet' | 'desktop' = defaultStore.state.overridedDeviceKind ? defaultStore.state.overridedDeviceKind
|
export function updateDeviceKind(kind: DeviceKind | null) {
|
||||||
: isSmartphone ? 'smartphone'
|
deviceKind = kind ?? DEFAULT_DEVICE_KIND;
|
||||||
: isTablet ? 'tablet'
|
}
|
||||||
: 'desktop';
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ type Hidden = boolean | ((v: any) => boolean);
|
||||||
export type FormItem = {
|
export type FormItem = {
|
||||||
label?: string;
|
label?: string;
|
||||||
type: 'string';
|
type: 'string';
|
||||||
default: string | null;
|
default?: string | null;
|
||||||
description?: string;
|
description?: string;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
hidden?: Hidden;
|
hidden?: Hidden;
|
||||||
|
@ -24,7 +24,7 @@ export type FormItem = {
|
||||||
} | {
|
} | {
|
||||||
label?: string;
|
label?: string;
|
||||||
type: 'number';
|
type: 'number';
|
||||||
default: number | null;
|
default?: number | null;
|
||||||
description?: string;
|
description?: string;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
hidden?: Hidden;
|
hidden?: Hidden;
|
||||||
|
@ -32,20 +32,20 @@ export type FormItem = {
|
||||||
} | {
|
} | {
|
||||||
label?: string;
|
label?: string;
|
||||||
type: 'boolean';
|
type: 'boolean';
|
||||||
default: boolean | null;
|
default?: boolean | null;
|
||||||
description?: string;
|
description?: string;
|
||||||
hidden?: Hidden;
|
hidden?: Hidden;
|
||||||
} | {
|
} | {
|
||||||
label?: string;
|
label?: string;
|
||||||
type: 'enum';
|
type: 'enum';
|
||||||
default: string | null;
|
default?: string | null;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
hidden?: Hidden;
|
hidden?: Hidden;
|
||||||
enum: EnumItem[];
|
enum: EnumItem[];
|
||||||
} | {
|
} | {
|
||||||
label?: string;
|
label?: string;
|
||||||
type: 'radio';
|
type: 'radio';
|
||||||
default: unknown | null;
|
default?: unknown | null;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
hidden?: Hidden;
|
hidden?: Hidden;
|
||||||
options: {
|
options: {
|
||||||
|
@ -55,7 +55,7 @@ export type FormItem = {
|
||||||
} | {
|
} | {
|
||||||
label?: string;
|
label?: string;
|
||||||
type: 'range';
|
type: 'range';
|
||||||
default: number | null;
|
default?: number | null;
|
||||||
description?: string;
|
description?: string;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
step?: number;
|
step?: number;
|
||||||
|
@ -66,12 +66,12 @@ export type FormItem = {
|
||||||
} | {
|
} | {
|
||||||
label?: string;
|
label?: string;
|
||||||
type: 'object';
|
type: 'object';
|
||||||
default: Record<string, unknown> | null;
|
default?: Record<string, unknown> | null;
|
||||||
hidden: Hidden;
|
hidden: Hidden;
|
||||||
} | {
|
} | {
|
||||||
label?: string;
|
label?: string;
|
||||||
type: 'array';
|
type: 'array';
|
||||||
default: unknown[] | null;
|
default?: unknown[] | null;
|
||||||
hidden: Hidden;
|
hidden: Hidden;
|
||||||
} | {
|
} | {
|
||||||
type: 'button';
|
type: 'button';
|
||||||
|
|
46
packages/frontend/src/scripts/fullscreen.ts
Normal file
46
packages/frontend/src/scripts/fullscreen.ts
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
type PartiallyPartial<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
|
||||||
|
|
||||||
|
type VideoEl = PartiallyPartial<HTMLVideoElement, 'requestFullscreen'> & {
|
||||||
|
webkitEnterFullscreen?(): void;
|
||||||
|
webkitExitFullscreen?(): void;
|
||||||
|
};
|
||||||
|
|
||||||
|
type PlayerEl = PartiallyPartial<HTMLElement, 'requestFullscreen'>;
|
||||||
|
|
||||||
|
type RequestFullscreenProps = {
|
||||||
|
readonly videoEl: VideoEl;
|
||||||
|
readonly playerEl: PlayerEl;
|
||||||
|
readonly options?: FullscreenOptions | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ExitFullscreenProps = {
|
||||||
|
readonly videoEl: VideoEl;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const requestFullscreen = ({ videoEl, playerEl, options }: RequestFullscreenProps) => {
|
||||||
|
if (playerEl.requestFullscreen != null) {
|
||||||
|
playerEl.requestFullscreen(options ?? undefined);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (videoEl.webkitEnterFullscreen != null) {
|
||||||
|
videoEl.webkitEnterFullscreen();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const exitFullscreen = ({ videoEl }: ExitFullscreenProps) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
|
if (document.exitFullscreen != null) {
|
||||||
|
document.exitFullscreen();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (videoEl.webkitExitFullscreen != null) {
|
||||||
|
videoEl.webkitExitFullscreen();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
|
@ -26,6 +26,7 @@ if (window.Cypress) {
|
||||||
console.log('Cypress detected. It will use localStorage.');
|
console.log('Cypress detected. It will use localStorage.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for the availability of indexedDB.
|
||||||
if (idbAvailable) {
|
if (idbAvailable) {
|
||||||
await iset('idb-test', 'test')
|
await iset('idb-test', 'test')
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
|
@ -37,16 +38,36 @@ if (idbAvailable) {
|
||||||
console.error('indexedDB is unavailable. It will use localStorage.');
|
console.error('indexedDB is unavailable. It will use localStorage.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a value from indexedDB (or localStorage as a fallback).
|
||||||
|
*
|
||||||
|
* @param key The key of the item to retrieve.
|
||||||
|
*
|
||||||
|
* @returns The value of the item.
|
||||||
|
*/
|
||||||
export async function get(key: string) {
|
export async function get(key: string) {
|
||||||
if (idbAvailable) return iget(key);
|
if (idbAvailable) return iget(key);
|
||||||
return miLocalStorage.getItemAsJson(`${PREFIX}${key}`);
|
return miLocalStorage.getItemAsJson(`${PREFIX}${key}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a value in indexedDB (or localStorage as a fallback).
|
||||||
|
*
|
||||||
|
* @param {string} key - The key of the item to set.
|
||||||
|
* @param {any} val - The value of the item to set.
|
||||||
|
* @returns {Promise<void>} - A promise that resolves when the value has been set.`
|
||||||
|
*/
|
||||||
export async function set(key: string, val: any) {
|
export async function set(key: string, val: any) {
|
||||||
if (idbAvailable) return iset(key, val);
|
if (idbAvailable) return iset(key, val);
|
||||||
return miLocalStorage.setItemAsJson(`${PREFIX}${key}`, val);
|
return miLocalStorage.setItemAsJson(`${PREFIX}${key}`, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a value from indexedDB (or localStorage as a fallback).
|
||||||
|
*
|
||||||
|
* @param {string} key - The key of the item to delete.
|
||||||
|
* @returns {Promise<void>} - A promise that resolves when the value has been deleted.
|
||||||
|
*/
|
||||||
export async function del(key: string) {
|
export async function del(key: string) {
|
||||||
if (idbAvailable) return idel(key);
|
if (idbAvailable) return idel(key);
|
||||||
return miLocalStorage.removeItem(`${PREFIX}${key}`);
|
return miLocalStorage.removeItem(`${PREFIX}${key}`);
|
||||||
|
|
|
@ -17,7 +17,7 @@ export function misskeyApi<
|
||||||
_ResT = ResT extends void ? Misskey.api.SwitchCaseResponseType<E, P> : ResT,
|
_ResT = ResT extends void ? Misskey.api.SwitchCaseResponseType<E, P> : ResT,
|
||||||
>(
|
>(
|
||||||
endpoint: E,
|
endpoint: E,
|
||||||
data: P = {} as any,
|
data: P & { i?: string | null; } = {} as any,
|
||||||
token?: string | null | undefined,
|
token?: string | null | undefined,
|
||||||
signal?: AbortSignal,
|
signal?: AbortSignal,
|
||||||
): Promise<_ResT> {
|
): Promise<_ResT> {
|
||||||
|
@ -30,8 +30,8 @@ export function misskeyApi<
|
||||||
|
|
||||||
const promise = new Promise<_ResT>((resolve, reject) => {
|
const promise = new Promise<_ResT>((resolve, reject) => {
|
||||||
// Append a credential
|
// Append a credential
|
||||||
if ($i) (data as any).i = $i.token;
|
if ($i) data.i = $i.token;
|
||||||
if (token !== undefined) (data as any).i = token;
|
if (token !== undefined) data.i = token;
|
||||||
|
|
||||||
// Send request
|
// Send request
|
||||||
window.fetch(`${apiUrl}/${endpoint}`, {
|
window.fetch(`${apiUrl}/${endpoint}`, {
|
||||||
|
|
|
@ -80,7 +80,7 @@ export function chooseFileFromUrl(): Promise<Misskey.entities.DriveFile> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function select(src: any, label: string | null, multiple: boolean): Promise<Misskey.entities.DriveFile[]> {
|
function select(src: HTMLElement | EventTarget | null, label: string | null, multiple: boolean): Promise<Misskey.entities.DriveFile[]> {
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res, rej) => {
|
||||||
const keepOriginal = ref(defaultStore.state.keepOriginalUploading);
|
const keepOriginal = ref(defaultStore.state.keepOriginalUploading);
|
||||||
|
|
||||||
|
@ -107,10 +107,10 @@ function select(src: any, label: string | null, multiple: boolean): Promise<Miss
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function selectFile(src: any, label: string | null = null): Promise<Misskey.entities.DriveFile> {
|
export function selectFile(src: HTMLElement | EventTarget | null, label: string | null = null): Promise<Misskey.entities.DriveFile> {
|
||||||
return select(src, label, false).then(files => files[0]);
|
return select(src, label, false).then(files => files[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function selectFiles(src: any, label: string | null = null): Promise<Misskey.entities.DriveFile[]> {
|
export function selectFiles(src: HTMLElement | EventTarget | null, label: string | null = null): Promise<Misskey.entities.DriveFile[]> {
|
||||||
return select(src, label, true);
|
return select(src, label, true);
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue