Merge remote-tracking branch 'misskey-original/develop' into develop

# Conflicts:
#	packages/frontend/src/components/MkEmojiEditDialog.vue
#	packages/frontend/src/components/MkMenu.vue
#	packages/frontend/src/pages/about.vue
#	packages/frontend/src/pages/admin/index.vue
#	packages/frontend/src/pages/custom-emojis-manager.vue
#	packages/frontend/src/pages/settings/mute-block.vue
#	packages/frontend/src/pages/settings/theme.vue
#	packages/frontend/src/ui/_common_/navbar-for-mobile.vue
This commit is contained in:
mattyatea 2023-12-26 23:38:43 +09:00
commit 8d058b0529
208 changed files with 1583 additions and 904 deletions

View file

@ -89,3 +89,9 @@ body:
render: markdown render: markdown
validations: validations:
required: false required: false
- type: checkboxes
attributes:
label: Do you want to address this bug yourself?
options:
- label: Yes, I will patch the bug myself and send a pull request

View file

@ -14,4 +14,9 @@ body:
label: Purpose label: Purpose
description: Describe the specific problem or need you think this feature will solve, and who it will help. description: Describe the specific problem or need you think this feature will solve, and who it will help.
validations: validations:
required: true required: true
- type: checkboxes
attributes:
label: Do you want to implement this feature yourself?
options:
- label: Yes, I will implement this by myself and send a pull request

View file

@ -56,7 +56,7 @@ jobs:
- name: Upload Artifact - name: Upload Artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: api-artifact name: api-artifact-${{ matrix.api-json-name }}
path: ${{ matrix.api-json-name }} path: ${{ matrix.api-json-name }}
save-pr-number: save-pr-number:
@ -69,5 +69,5 @@ jobs:
echo "$PR_NUMBER" > ./pr_number echo "$PR_NUMBER" > ./pr_number
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
with: with:
name: api-artifact name: api-artifact-pr-number
path: pr_number path: pr_number

View file

@ -19,24 +19,28 @@ jobs:
uses: actions/github-script@v7 uses: actions/github-script@v7
with: with:
script: | script: |
const fs = require('fs');
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({ let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner, owner: context.repo.owner,
repo: context.repo.repo, repo: context.repo.repo,
run_id: context.payload.workflow_run.id, run_id: context.payload.workflow_run.id,
}); });
let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => { let matchArtifacts = allArtifacts.data.artifacts.filter((artifact) => {
return artifact.name == "api-artifact" return artifact.name.startsWith("api-artifact-") || artifact.name == "api-artifact"
})[0];
let download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
}); });
let fs = require('fs'); await Promise.all(matchArtifacts.map(async (artifact) => {
fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/api-artifact.zip`, Buffer.from(download.data)); let download = await github.rest.actions.downloadArtifact({
- name: Extract artifact owner: context.repo.owner,
run: unzip api-artifact.zip -d artifacts repo: context.repo.repo,
artifact_id: artifact.id,
archive_format: 'zip',
});
await fs.promises.writeFile(`${process.env.GITHUB_WORKSPACE}/${artifact.name}.zip`, Buffer.from(download.data));
}));
- name: Extract all artifacts
run: |
find . -mindepth 1 -maxdepth 1 -type f -name '*.zip' -exec unzip {} -d artifacts ';'
ls -la
- name: Load PR Number - name: Load PR Number
id: load-pr-num id: load-pr-num
run: echo "pr-number=$(cat artifacts/pr_number)" >> "$GITHUB_OUTPUT" run: echo "pr-number=$(cat artifacts/pr_number)" >> "$GITHUB_OUTPUT"
@ -83,3 +87,11 @@ jobs:
pr_number: ${{ steps.load-pr-num.outputs.pr-number }} pr_number: ${{ steps.load-pr-num.outputs.pr-number }}
comment_tag: show_diff comment_tag: show_diff
filePath: ./output.md filePath: ./output.md
- name: Tell error to PR
uses: thollander/actions-comment-pull-request@v2
if: failure() && steps.load-pr-num.outputs.pr-number
with:
pr_number: ${{ steps.load-pr-num.outputs.pr-number }}
comment_tag: show_diff_error
message: |
api.jsonの差分作成中にエラーが発生しました。詳細は[Workflowのログ](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})を確認してください。

View file

@ -12,6 +12,22 @@
--> -->
## 2023.12.1
### General
- Enhance: ローカリゼーションの更新
- Fix: 自分のdirect noteがuser list timelineに追加されない
### Client
- Fix: 一部のモデログ(logYellowでの表示対象)について、表示の色が変わらない問題を修正
- Feat: AiScript専用のMFM構文`$[clickable.ev=EVENTNAME ...]`を追加。`Mk:C:mfm`のオプション`onClickEv`に関数を渡すと、クリック時に`EVENTNAME`を引数にして呼び出す
### Server
- Enhance: センシティブワードの設定がハッシュタグトレンドにも適用されるようになりました
- Fix: 1702718871541-ffVisibility.jsのdownが壊れている
- Fix:「非センシティブのみ(リモートはいいねのみ)」を設定していても、センシティブに設定されたカスタム絵文字をリアクションできる問題を修正
- Fix: ロールアサイン時の通知で,ロールアイコンが縮小されずに表示される問題を修正
## 2023.12.0 ## 2023.12.0
### Note ### Note
@ -98,6 +114,7 @@
- Enhance: MFM `$[ruby ]` が他ソフトウェアと連合されるように - Enhance: MFM `$[ruby ]` が他ソフトウェアと連合されるように
- Enhance: Meilisearchを有効にした検索で、ユーザーのミュートやブロックを考慮するように - Enhance: Meilisearchを有効にした検索で、ユーザーのミュートやブロックを考慮するように
- Enhance: カスタム絵文字のインポート時の動作を改善 - Enhance: カスタム絵文字のインポート時の動作を改善
- Enhance: json-schema(OpenAPIの戻り値として使用されるスキーマ定義)を出来る限り最新化 #12311
- Fix: 時間経過により無効化されたアンテナを再有効化したとき、サーバ再起動までその状況が反映されないのを修正 #12303 - Fix: 時間経過により無効化されたアンテナを再有効化したとき、サーバ再起動までその状況が反映されないのを修正 #12303
- Fix: ロールタイムラインが保存されない問題を修正 - Fix: ロールタイムラインが保存されない問題を修正
- Fix: api.jsonの生成ロジックを改善 #12402 - Fix: api.jsonの生成ロジックを改善 #12402
@ -125,7 +142,6 @@
- Feat: 管理者がコントロールパネルからメールアドレスの照会を行えるようになりました - Feat: 管理者がコントロールパネルからメールアドレスの照会を行えるようになりました
- Enhance: ローカリゼーションの更新 - Enhance: ローカリゼーションの更新
- Enhance: 依存関係の更新 - Enhance: 依存関係の更新
- Enhance: json-schema(OpenAPIの戻り値として使用されるスキーマ定義)を出来る限り最新化 #12311
### Client ### Client
- Enhance: MFMでルビを振れるように - Enhance: MFMでルビを振れるように

View file

@ -1,7 +1,6 @@
# Reporting Security Issues # Reporting Security Issues
If you discover a security issue in Misskey, please report it by sending an If you discover a security issue in Misskey, please report it by **[this form](https://github.com/misskey-dev/misskey/security/advisories/new)**.
email to [syuilotan@yahoo.co.jp](mailto:syuilotan@yahoo.co.jp).
This will allow us to assess the risk, and make a fix available before we add a This will allow us to assess the risk, and make a fix available before we add a
bug report to the GitHub repository. bug report to the GitHub repository.

View file

@ -121,6 +121,12 @@ sensitive: "Marcado como sensible"
add: "Agregar" add: "Agregar"
reaction: "Reacción" reaction: "Reacción"
reactions: "Reacción" reactions: "Reacción"
emojiPicker: "Selector de emojis"
pinnedEmojisForReactionSettingDescription: "Puedes seleccionar reacciones para fijarlos en el selector"
pinnedEmojisSettingDescription: "Puedes seleccionar emojis para fijarlos en el selector"
emojiPickerDisplay: "Mostrar el selector de emojis"
overwriteFromPinnedEmojisForReaction: "Sobreescribir las reacciones fijadas"
overwriteFromPinnedEmojis: "Sobreescribir los emojis fijados"
reactionSettingDescription2: "Arrastre para reordenar, click para borrar, apriete la tecla + para añadir." reactionSettingDescription2: "Arrastre para reordenar, click para borrar, apriete la tecla + para añadir."
rememberNoteVisibility: "Recordar visibilidad" rememberNoteVisibility: "Recordar visibilidad"
attachCancel: "Quitar adjunto" attachCancel: "Quitar adjunto"
@ -260,6 +266,7 @@ removed: "Borrado"
removeAreYouSure: "¿Desea borrar \"{x}\"?" removeAreYouSure: "¿Desea borrar \"{x}\"?"
deleteAreYouSure: "¿Desea borrar \"{x}\"?" deleteAreYouSure: "¿Desea borrar \"{x}\"?"
resetAreYouSure: "¿Desea reestablecer?" resetAreYouSure: "¿Desea reestablecer?"
areYouSure: "¿Estás conforme?"
saved: "Guardado" saved: "Guardado"
messaging: "Chat" messaging: "Chat"
upload: "Subir" upload: "Subir"
@ -640,6 +647,7 @@ smtpSecure: "Usar SSL/TLS implícito en la conexión SMTP"
smtpSecureInfo: "Apagar cuando se use STARTTLS" smtpSecureInfo: "Apagar cuando se use STARTTLS"
testEmail: "Prueba de envío" testEmail: "Prueba de envío"
wordMute: "Silenciar palabras" wordMute: "Silenciar palabras"
hardWordMute: "Filtro de palabra fuerte"
regexpError: "Error de la expresión regular" regexpError: "Error de la expresión regular"
regexpErrorDescription: "Ocurrió un error en la expresión regular en la linea {line} de las palabras muteadas {tab}" regexpErrorDescription: "Ocurrió un error en la expresión regular en la linea {line} de las palabras muteadas {tab}"
instanceMute: "Instancias silenciadas" instanceMute: "Instancias silenciadas"
@ -873,6 +881,8 @@ makeReactionsPublicDescription: "Todas las reacciones que hayas hecho serán pú
classic: "Clásico" classic: "Clásico"
muteThread: "Silenciar hilo" muteThread: "Silenciar hilo"
unmuteThread: "Mostrar hilo" unmuteThread: "Mostrar hilo"
followingVisibility: "Visibilidad de seguidos"
followersVisibility: "Visibilidad de seguidores"
continueThread: "Ver la continuación del hilo" continueThread: "Ver la continuación del hilo"
deleteAccountConfirm: "La cuenta será borrada. ¿Está seguro?" deleteAccountConfirm: "La cuenta será borrada. ¿Está seguro?"
incorrectPassword: "La contraseña es incorrecta" incorrectPassword: "La contraseña es incorrecta"
@ -1024,6 +1034,7 @@ sensitiveWords: "Palabras sensibles"
sensitiveWordsDescription: "La visibilidad de todas las notas que contienen cualquiera de las palabras configuradas serán puestas en \"Inicio\" automáticamente. Puedes enumerás varias separándolas con saltos de línea" sensitiveWordsDescription: "La visibilidad de todas las notas que contienen cualquiera de las palabras configuradas serán puestas en \"Inicio\" automáticamente. Puedes enumerás varias separándolas con saltos de línea"
sensitiveWordsDescription2: "Si se usan espacios se crearán expresiones AND y las palabras subsecuentes con barras inclinadas se convertirán en expresiones regulares." sensitiveWordsDescription2: "Si se usan espacios se crearán expresiones AND y las palabras subsecuentes con barras inclinadas se convertirán en expresiones regulares."
hiddenTags: "Hashtags ocultos" hiddenTags: "Hashtags ocultos"
hiddenTagsDescription: "Selecciona las etiquetas que no se mostrarán en tendencias. Una etiqueta por línea."
notesSearchNotAvailable: "No se puede buscar una nota" notesSearchNotAvailable: "No se puede buscar una nota"
license: "Licencia" license: "Licencia"
unfavoriteConfirm: "¿Desea quitar de favoritos?" unfavoriteConfirm: "¿Desea quitar de favoritos?"
@ -1152,6 +1163,7 @@ tosAndPrivacyPolicy: "Condiciones de Uso y Política de Privacidad"
avatarDecorations: "Decoraciones de avatar" avatarDecorations: "Decoraciones de avatar"
attach: "Acoplar" attach: "Acoplar"
detach: "Quitar" detach: "Quitar"
detachAll: "Quitar todo"
angle: "Ángulo" angle: "Ángulo"
flip: "Echar de un capirotazo" flip: "Echar de un capirotazo"
showAvatarDecorations: "Mostrar decoraciones de avatar" showAvatarDecorations: "Mostrar decoraciones de avatar"
@ -1165,6 +1177,10 @@ cwNotationRequired: "Si se ha activado \"ocultar contenido\", es necesario propo
doReaction: "Añadir reacción" doReaction: "Añadir reacción"
code: "Código" code: "Código"
reloadRequiredToApplySettings: "Es necesario recargar para que se aplique la configuración." reloadRequiredToApplySettings: "Es necesario recargar para que se aplique la configuración."
remainingN: "Faltan: {n}"
overwriteContentConfirm: "¿Quieres sustituir todo el contenido actual?"
seasonalScreenEffect: "Efectos de pantalla asociados a estaciones"
decorate: "Decorar"
_announcement: _announcement:
forExistingUsers: "Solo para usuarios registrados" forExistingUsers: "Solo para usuarios registrados"
forExistingUsersDescription: "Este anuncio solo se mostrará a aquellos usuarios registrados en el momento de su publicación. Si se deshabilita esta opción, aquellos usuarios que se registren tras su publicación también lo verán." forExistingUsersDescription: "Este anuncio solo se mostrará a aquellos usuarios registrados en el momento de su publicación. Si se deshabilita esta opción, aquellos usuarios que se registren tras su publicación también lo verán."
@ -1222,6 +1238,45 @@ _initialTutorial:
home: "Puedes ver los posts de las cuentas que sigues." home: "Puedes ver los posts de las cuentas que sigues."
local: "Puedes ver los posts de todos los usuarios de este servidor." local: "Puedes ver los posts de todos los usuarios de este servidor."
social: "Se ven los posts de la línea de tiempo de inicio junto con los de la línea de tiempo local." social: "Se ven los posts de la línea de tiempo de inicio junto con los de la línea de tiempo local."
global: "Puedes ver notas de todos los servidores conectados."
description2: "Puedes cambiar la línea de tiempo en la parte superior de la pantalla cuando quieras."
description3: "Además, hay listas de líneas de tiempo y listas de canales. Para más detalle, por favor visita este enlace: {link}"
_postNote:
title: "Ajustes de publicación de nota"
description1: "Cuando publicas una nota en Misskey, hay varias opciones disponibles. El formulario tiene este aspecto."
_visibility:
description: "Puedes limitar quién puede ver tu nota."
public: "Tu nota será visible para todos los usuarios."
home: "Publicar solo en la línea de tiempo de Inicio. La nota se verá en tu perfil, la verán tus seguidores y también cuando sea renotada."
followers: "Visible solo para seguidores. Sólo tus seguidores podrán ver la nota, y no podrá ser renotada por otras personas."
direct: "Visible sólo para usuarios específicos, y el destinatario será notificado. Puede usarse como alternativa a la mensajería directa."
doNotSendConfidencialOnDirect1: "¡Ten cuidado cuando vayas a enviar información sensible!"
doNotSendConfidencialOnDirect2: "Los administradores del servidor pueden leer lo que escribes. Ten cuidado cuando envíes información sensible en notas directas en servidores no confiables."
localOnly: "Publicando con esta opción seleccionada, la nota no se federará hacia otros servidores. Los usuarios de otros servidores no podrán ver estas notas directamente, sin importar los ajustes seleccionados más arriba."
_cw:
title: "Alerta de contenido (CW)"
description: "En lugar de mostrarse el contenido de la nota, se mostrará lo que escribas en el campo \"comentarios\". Pulsando en \"leer más\" desplegará el contenido de la nota."
_exampleNote:
cw: "¡Esto te hará tener hambre!"
note: "Acabo de comerme un donut de chocolate glaseado 🍩😋"
useCases: "Esto se usa cuando las normas del servidor lo requieren, o para ocultar spoilers o contenido sensible."
_howToMakeAttachmentsSensitive:
title: "¿Cómo puedo marcar adjuntos como contenido sensible?"
description: "Cuando las normas del servidor lo requieran, o el contenido lo requiera, marca la opción de \"contenido sensible\" para el adjunto."
tryThisFile: "¡Prueba a marcar la imagen adjunta como contenido sensible!"
_exampleNote:
note: "Ups, la he liado al abrir la tapa del natto..."
method: "Para marcar un adjunto como sensible, haz clic en la miniatura, abre el menú, y haz clic en \"Marcar como sensible\"."
sensitiveSucceeded: "Cuando adjuntes archivos, por favor, ten en cuenta las normas del servidor para marcarlos como contenido sensible."
doItToContinue: "Marca el archivo adjunto como sensible para continuar."
_done:
title: "¡Has completado el tutorial! 🎉"
description: "Las funciones que mostramos aquí son sólo una pequeña parte. Para más detalles sobre el funcionamiento de Misskey, pulsa en este enlace: {link}"
_timelineDescription:
home: "En la línea de tiempo de Inicio puedes ver las notas de las cuentas a las que sigues."
local: "En la línea de tiempo Local puedes ver las notas de todos los usuarios del servidor."
social: "En la línea de tiempo Social verás las notas de Inicio y Local a la vez."
global: "En la línea de tiempo Global verás las notas de todos los servidores conectados."
_serverRules: _serverRules:
description: "Un conjunto de reglas que serán mostradas antes del registro. Configurar un sumario de términos de servicio es recomendado." description: "Un conjunto de reglas que serán mostradas antes del registro. Configurar un sumario de términos de servicio es recomendado."
_serverSettings: _serverSettings:
@ -1233,6 +1288,7 @@ _serverSettings:
manifestJsonOverride: "Sobreescribir manifest.json" manifestJsonOverride: "Sobreescribir manifest.json"
shortName: "Nombre corto" shortName: "Nombre corto"
shortNameDescription: "Forma corta del nombre de la instancia que puede mostrarse si el nombre completo es demasiado largo." shortNameDescription: "Forma corta del nombre de la instancia que puede mostrarse si el nombre completo es demasiado largo."
fanoutTimelineDescription: "Incrementa el rendimiento de forma significativa cuando se obtienen las líneas de tiempo y reduce la carga en la base de datos. A cambio, el uso de la memoria en Redis incrementará. Considera desactivar esta opción en caso de que tu servidor tenga poca memoria o detectes inestabilidad."
_accountMigration: _accountMigration:
moveFrom: "Trasladar de otra cuenta a ésta" moveFrom: "Trasladar de otra cuenta a ésta"
moveFromSub: "Crear un alias para otra cuenta." moveFromSub: "Crear un alias para otra cuenta."
@ -1490,6 +1546,9 @@ _achievements:
_smashTestNotificationButton: _smashTestNotificationButton:
title: "Sobrecarga de pruebas" title: "Sobrecarga de pruebas"
description: "Envía muchas notificaciones de prueba en un corto espacio de tiempo" description: "Envía muchas notificaciones de prueba en un corto espacio de tiempo"
_tutorialCompleted:
title: "Diploma del Curso Básico de Misskey"
description: "Tutorial completado"
_role: _role:
new: "Crear rol" new: "Crear rol"
edit: "Editar rol" edit: "Editar rol"
@ -1500,7 +1559,9 @@ _role:
assignTarget: "Asignar objetivo" assignTarget: "Asignar objetivo"
descriptionOfAssignTarget: "<b>Manual</b> Para cambiar manualmente lo que se incluye en este rol.\n<b>Condicional</b> configura una condición, y los usuarios que cumplan la condición serán incluídos automáticamente." descriptionOfAssignTarget: "<b>Manual</b> Para cambiar manualmente lo que se incluye en este rol.\n<b>Condicional</b> configura una condición, y los usuarios que cumplan la condición serán incluídos automáticamente."
manual: "manual" manual: "manual"
manualRoles: "Roles manuales"
conditional: "condicional" conditional: "condicional"
conditionalRoles: "Roles condicionales"
condition: "condición" condition: "condición"
isConditionalRole: "Esto es un rol condicional" isConditionalRole: "Esto es un rol condicional"
isPublic: "Publicar rol" isPublic: "Publicar rol"
@ -1549,6 +1610,7 @@ _role:
canHideAds: "Puede ocultar anuncios" canHideAds: "Puede ocultar anuncios"
canSearchNotes: "Uso de la búsqueda de notas" canSearchNotes: "Uso de la búsqueda de notas"
canUseTranslator: "Uso de traductor" canUseTranslator: "Uso de traductor"
avatarDecorationLimit: "Número máximo de decoraciones de avatar"
_condition: _condition:
isLocal: "Usuario local" isLocal: "Usuario local"
isRemote: "Usuario remoto" isRemote: "Usuario remoto"
@ -1577,6 +1639,7 @@ _emailUnavailable:
disposable: "No es un correo reutilizable" disposable: "No es un correo reutilizable"
mx: "Servidor de correo inválido" mx: "Servidor de correo inválido"
smtp: "Servidor de correo no disponible" smtp: "Servidor de correo no disponible"
banned: "Email no disponible"
_ffVisibility: _ffVisibility:
public: "Publicar" public: "Publicar"
followers: "Visible solo para seguidores" followers: "Visible solo para seguidores"
@ -1653,6 +1716,7 @@ _aboutMisskey:
donate: "Donar a Misskey" donate: "Donar a Misskey"
morePatrons: "Muchas más personas nos apoyan. Muchas gracias🥰" morePatrons: "Muchas más personas nos apoyan. Muchas gracias🥰"
patrons: "Patrocinadores" patrons: "Patrocinadores"
projectMembers: "Miembros del proyecto"
_displayOfSensitiveMedia: _displayOfSensitiveMedia:
respect: "Esconder medios marcados como sensibles" respect: "Esconder medios marcados como sensibles"
ignore: "Mostrar medios marcados como sensibles" ignore: "Mostrar medios marcados como sensibles"
@ -1677,6 +1741,7 @@ _channel:
notesCount: "{n} notas" notesCount: "{n} notas"
nameAndDescription: "Nombre y descripción" nameAndDescription: "Nombre y descripción"
nameOnly: "Sólo nombre" nameOnly: "Sólo nombre"
allowRenoteToExternal: "Permitir renotas y menciones fuera del canal"
_menuDisplay: _menuDisplay:
sideFull: "Horizontal" sideFull: "Horizontal"
sideIcon: "Horizontal (ícono)" sideIcon: "Horizontal (ícono)"
@ -1780,6 +1845,12 @@ _ago:
yearsAgo: "Hace {n} años" yearsAgo: "Hace {n} años"
invalid: "No hay nada que ver aqui" invalid: "No hay nada que ver aqui"
_timeIn: _timeIn:
seconds: "En {n} segundos"
minutes: "En {n}m"
hours: "En {n}h"
days: "En {n}d"
weeks: "En {n}sem."
months: "En {n}M"
years: "En {n} años" years: "En {n} años"
_time: _time:
second: "Segundos" second: "Segundos"
@ -1906,6 +1977,7 @@ _widgets:
_userList: _userList:
chooseList: "Seleccione una lista" chooseList: "Seleccione una lista"
clicker: "Cliqueador" clicker: "Cliqueador"
birthdayFollowings: "Hoy cumplen años"
_cw: _cw:
hide: "Ocultar" hide: "Ocultar"
show: "Ver más" show: "Ver más"
@ -1968,6 +2040,7 @@ _profile:
changeAvatar: "Cambiar avatar" changeAvatar: "Cambiar avatar"
changeBanner: "Cambiar banner" changeBanner: "Cambiar banner"
verifiedLinkDescription: "Introduciendo una URL que contiene un enlace a tu perfil, se puede mostrar un icono de verificación de propiedad al lado del campo." verifiedLinkDescription: "Introduciendo una URL que contiene un enlace a tu perfil, se puede mostrar un icono de verificación de propiedad al lado del campo."
avatarDecorationMax: "Puedes añadir un máximo de {max} decoraciones de avatar."
_exportOrImport: _exportOrImport:
allNotes: "Todas las notas" allNotes: "Todas las notas"
favoritedNotes: "Notas favoritas" favoritedNotes: "Notas favoritas"
@ -2089,6 +2162,7 @@ _notification:
pollEnded: "Estan disponibles los resultados de la encuesta" pollEnded: "Estan disponibles los resultados de la encuesta"
newNote: "Nueva nota" newNote: "Nueva nota"
unreadAntennaNote: "Antena {name}" unreadAntennaNote: "Antena {name}"
roleAssigned: "Rol asignado"
emptyPushNotificationMessage: "Se han actualizado las notificaciones push" emptyPushNotificationMessage: "Se han actualizado las notificaciones push"
achievementEarned: "Logro desbloqueado" achievementEarned: "Logro desbloqueado"
testNotification: "Notificación de prueba" testNotification: "Notificación de prueba"
@ -2110,6 +2184,7 @@ _notification:
pollEnded: "La encuesta terminó" pollEnded: "La encuesta terminó"
receiveFollowRequest: "Recibió una solicitud de seguimiento" receiveFollowRequest: "Recibió una solicitud de seguimiento"
followRequestAccepted: "El seguimiento fue aceptado" followRequestAccepted: "El seguimiento fue aceptado"
roleAssigned: "Rol asignado"
achievementEarned: "Logro desbloqueado" achievementEarned: "Logro desbloqueado"
app: "Notificaciones desde aplicaciones" app: "Notificaciones desde aplicaciones"
_actions: _actions:
@ -2255,3 +2330,6 @@ _externalResourceInstaller:
_themeInstallFailed: _themeInstallFailed:
title: "Instalación de tema fallida" title: "Instalación de tema fallida"
description: "Ha ocurrido un problema al instalar el tema. Por favor, inténtalo de nuevo. Se pueden ver más detalles del error en la consola de Javascript." description: "Ha ocurrido un problema al instalar el tema. Por favor, inténtalo de nuevo. Se pueden ver más detalles del error en la consola de Javascript."
_dataSaver:
_media:
title: "Cargando Multimedia"

View file

@ -114,7 +114,7 @@ quote: "인용"
inChannelRenote: "채널 내 리노트" inChannelRenote: "채널 내 리노트"
inChannelQuote: "채널 내 인용" inChannelQuote: "채널 내 인용"
pinnedNote: "고정된 노트" pinnedNote: "고정된 노트"
pinned: "프로필에 고정" pinned: "고정하기"
you: "나" you: "나"
clickToShow: "클릭하여 보기" clickToShow: "클릭하여 보기"
sensitive: "열람 주의" sensitive: "열람 주의"
@ -1179,7 +1179,7 @@ code: "문자열"
reloadRequiredToApplySettings: "설정을 적용하려면 새로고침을 해야 합니다." reloadRequiredToApplySettings: "설정을 적용하려면 새로고침을 해야 합니다."
remainingN: "나머지: {n}" remainingN: "나머지: {n}"
overwriteContentConfirm: "현재 내용을 덮어쓰기 합니다. 계속 진행하시겠습니까?" overwriteContentConfirm: "현재 내용을 덮어쓰기 합니다. 계속 진행하시겠습니까?"
seasonalScreenEffect: "철에 맞는 화면으로 꾸미기" seasonalScreenEffect: "계절에 따른 효과 보이기"
decorate: "장식하기" decorate: "장식하기"
_announcement: _announcement:
forExistingUsers: "기존 유저에게만 알림" forExistingUsers: "기존 유저에게만 알림"
@ -1641,6 +1641,7 @@ _emailUnavailable:
disposable: "임시 이메일 주소는 사용할 수 없습니다" disposable: "임시 이메일 주소는 사용할 수 없습니다"
mx: "메일 서버가 올바르지 않습니다" mx: "메일 서버가 올바르지 않습니다"
smtp: "메일 서버가 응답하지 않습니다" smtp: "메일 서버가 응답하지 않습니다"
banned: "이 메일 주소는 사용할 수 없습니다"
_ffVisibility: _ffVisibility:
public: "공개" public: "공개"
followers: "팔로워에게만 공개" followers: "팔로워에게만 공개"

View file

@ -632,11 +632,11 @@ tokenRequested: "允許存取帳戶"
pluginTokenRequestedDescription: "此外掛將擁有在此設定的權限。" pluginTokenRequestedDescription: "此外掛將擁有在此設定的權限。"
notificationType: "通知形式" notificationType: "通知形式"
edit: "編輯" edit: "編輯"
emailServer: "電郵伺服器" emailServer: "電伺服器"
enableEmail: "啟用發送電郵功能" enableEmail: "啟用發送電功能"
emailConfigInfo: "用於確認電郵地址及密碼重置" emailConfigInfo: "用於確認電地址及密碼重置"
email: "電子郵件" email: "電子郵件"
emailAddress: "電郵地址" emailAddress: "電子郵件位址"
smtpConfig: "SMTP 伺服器設定" smtpConfig: "SMTP 伺服器設定"
smtpHost: "主機" smtpHost: "主機"
smtpPort: "埠" smtpPort: "埠"
@ -731,7 +731,7 @@ disableShowingAnimatedImages: "不播放動態圖檔"
highlightSensitiveMedia: "強調敏感標記" highlightSensitiveMedia: "強調敏感標記"
verificationEmailSent: "已發送驗證電子郵件。請點擊進入電子郵件中的鏈接完成驗證。" verificationEmailSent: "已發送驗證電子郵件。請點擊進入電子郵件中的鏈接完成驗證。"
notSet: "未設定" notSet: "未設定"
emailVerified: "已成功驗證您的電郵" emailVerified: "已成功驗證您的電件地址"
noteFavoritesCount: "我的最愛貼文的數目" noteFavoritesCount: "我的最愛貼文的數目"
pageLikesCount: "頁面被按讚次數" pageLikesCount: "頁面被按讚次數"
pageLikedCount: "頁面被按讚次數" pageLikedCount: "頁面被按讚次數"
@ -783,7 +783,7 @@ capacity: "容量"
inUse: "已使用" inUse: "已使用"
editCode: "編輯代碼" editCode: "編輯代碼"
apply: "套用" apply: "套用"
receiveAnnouncementFromInstance: "接收由本實例發出的電郵通知" receiveAnnouncementFromInstance: "接收來自伺服器的通知"
emailNotification: "郵件通知" emailNotification: "郵件通知"
publish: "發布" publish: "發布"
inChannelSearch: "頻道内搜尋" inChannelSearch: "頻道内搜尋"
@ -955,7 +955,7 @@ cannotUploadBecauseExceedsFileSizeLimit: "由於超過了檔案大小的限制
beta: "測試版" beta: "測試版"
enableAutoSensitive: "自動 NSFW 判定" enableAutoSensitive: "自動 NSFW 判定"
enableAutoSensitiveDescription: "如果可用,它將使用機器學習技術判斷檔案是否需要標記為敏感。即使關閉此功能,也可能會依實例規則而自動啟用。" enableAutoSensitiveDescription: "如果可用,它將使用機器學習技術判斷檔案是否需要標記為敏感。即使關閉此功能,也可能會依實例規則而自動啟用。"
activeEmailValidationDescription: "積極驗證使用者的電郵地址,以判斷它是否可以通訊。關閉此選項代表只會檢查地址是否符合格式。" activeEmailValidationDescription: "主動地驗證使用者的電子郵件地址,以確定是否是一次性地址以及是否可以真正與其進行通訊。關閉時,僅檢查格式是否正確。"
navbar: "導覽列" navbar: "導覽列"
shuffle: "隨機" shuffle: "隨機"
account: "帳戶" account: "帳戶"
@ -1641,6 +1641,7 @@ _emailUnavailable:
disposable: "不是永久可用的地址" disposable: "不是永久可用的地址"
mx: "郵件伺服器不正確" mx: "郵件伺服器不正確"
smtp: "郵件伺服器沒有應答" smtp: "郵件伺服器沒有應答"
banned: "無法使用此電子郵件地址註冊"
_ffVisibility: _ffVisibility:
public: "公開" public: "公開"
followers: "只有關注您的使用者能看到" followers: "只有關注您的使用者能看到"

View file

@ -24,9 +24,11 @@ export class ffVisibility1702718871541 {
async down(queryRunner) { async down(queryRunner) {
await queryRunner.query(`CREATE TYPE "public"."user_profile_ffvisibility_enum" AS ENUM('public', 'followers', 'private')`); await queryRunner.query(`CREATE TYPE "public"."user_profile_ffvisibility_enum" AS ENUM('public', 'followers', 'private')`);
await queryRunner.query(`ALTER TABLE "user_profile" ADD "ffVisibility" "public"."user_profile_ffvisibility_enum" NOT NULL DEFAULT 'public'`); await queryRunner.query(`ALTER TABLE "user_profile" ADD "ffVisibility" "public"."user_profile_ffvisibility_enum" NOT NULL DEFAULT 'public'`);
await queryRunner.query(`CREATE CAST ("public"."user_profile_followingvisibility_enum" AS "public"."user_profile_ffvisibility_enum") WITH INOUT AS ASSIGNMENT`); await queryRunner.query(`CREATE CAST ("public"."user_profile_followingvisibility_enum" AS "public"."user_profile_ffvisibility_enum") WITH INOUT AS ASSIGNMENT`);
await queryRunner.query(`UPDATE "user_profile" SET ffVisibility = "user_profile"."followingVisibility"`); await queryRunner.query(`UPDATE "user_profile" SET "ffVisibility" = "followingVisibility"`);
await queryRunner.query(`DROP CAST ("public"."user_profile_followingvisibility_enum" AS "public"."user_profile_ffvisibility_enum")`); await queryRunner.query(`DROP CAST ("public"."user_profile_followingvisibility_enum" AS "public"."user_profile_ffvisibility_enum")`);
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "followersVisibility"`); await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "followersVisibility"`);
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "followingVisibility"`); await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "followingVisibility"`);
await queryRunner.query(`DROP TYPE "public"."user_profile_followersVisibility_enum"`); await queryRunner.query(`DROP TYPE "public"."user_profile_followersVisibility_enum"`);

View file

@ -7,7 +7,6 @@ import { URLSearchParams } from 'node:url';
import * as nodemailer from 'nodemailer'; import * as nodemailer from 'nodemailer';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { validate as validateEmail } from 'deep-email-validator'; import { validate as validateEmail } from 'deep-email-validator';
import { SubOutputFormat } from 'deep-email-validator/dist/output/output.js';
import { MetaService } from '@/core/MetaService.js'; import { MetaService } from '@/core/MetaService.js';
import { UtilityService } from '@/core/UtilityService.js'; import { UtilityService } from '@/core/UtilityService.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
@ -166,7 +165,10 @@ export class EmailService {
email: emailAddress, email: emailAddress,
}); });
let validated; let validated: {
valid: boolean,
reason?: string | null,
};
if (meta.enableActiveEmailValidation) { if (meta.enableActiveEmailValidation) {
if (meta.enableVerifymailApi && meta.verifymailAuthKey != null) { if (meta.enableVerifymailApi && meta.verifymailAuthKey != null) {

View file

@ -15,6 +15,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { FeaturedService } from '@/core/FeaturedService.js'; import { FeaturedService } from '@/core/FeaturedService.js';
import { MetaService } from '@/core/MetaService.js'; import { MetaService } from '@/core/MetaService.js';
import { UtilityService } from '@/core/UtilityService.js';
@Injectable() @Injectable()
export class HashtagService { export class HashtagService {
@ -29,6 +30,7 @@ export class HashtagService {
private featuredService: FeaturedService, private featuredService: FeaturedService,
private idService: IdService, private idService: IdService,
private metaService: MetaService, private metaService: MetaService,
private utilityService: UtilityService,
) { ) {
} }
@ -161,6 +163,7 @@ export class HashtagService {
const instance = await this.metaService.fetch(); const instance = await this.metaService.fetch();
const hiddenTags = instance.hiddenTags.map(t => normalizeForSearch(t)); const hiddenTags = instance.hiddenTags.map(t => normalizeForSearch(t));
if (hiddenTags.includes(hashtag)) return; if (hiddenTags.includes(hashtag)) return;
if (this.utilityService.isSensitiveWordIncluded(hashtag, instance.sensitiveWords)) return;
// YYYYMMDDHHmm (10分間隔) // YYYYMMDDHHmm (10分間隔)
const now = new Date(); const now = new Date();

View file

@ -222,7 +222,7 @@ export class NoteCreateService implements OnApplicationShutdown {
if (data.visibility === 'public' && data.channel == null) { if (data.visibility === 'public' && data.channel == null) {
const sensitiveWords = meta.sensitiveWords; const sensitiveWords = meta.sensitiveWords;
if (this.isSensitive(data, sensitiveWords)) { if (this.utilityService.isSensitiveWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) {
data.visibility = 'home'; data.visibility = 'home';
} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) { } else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
data.visibility = 'home'; data.visibility = 'home';
@ -672,31 +672,6 @@ export class NoteCreateService implements OnApplicationShutdown {
this.index(note); this.index(note);
} }
@bindThis
private isSensitive(note: Option, sensitiveWord: string[]): boolean {
if (sensitiveWord.length > 0) {
const text = note.cw ?? note.text ?? '';
if (text === '') return false;
const matched = sensitiveWord.some(filter => {
// represents RegExp
const regexp = filter.match(/^\/(.+)\/(.*)$/);
// This should never happen due to input sanitisation.
if (!regexp) {
const words = filter.split(' ');
return words.every(keyword => text.includes(keyword));
}
try {
return new RE2(regexp[1], regexp[2]).test(text);
} catch (err) {
// This should never happen due to input sanitisation.
return false;
}
});
if (matched) return true;
}
return false;
}
@bindThis @bindThis
private isQuote(note: Option): note is Option & { renote: MiNote } { private isQuote(note: Option): note is Option & { renote: MiNote } {
// sync with misc/is-quote.ts // sync with misc/is-quote.ts
@ -880,6 +855,7 @@ export class NoteCreateService implements OnApplicationShutdown {
// ダイレクトのとき、そのリストが対象外のユーザーの場合 // ダイレクトのとき、そのリストが対象外のユーザーの場合
if ( if (
note.visibility === 'specified' && note.visibility === 'specified' &&
note.userId !== userListMembership.userListUserId &&
!note.visibleUserIds.some(v => v === userListMembership.userListUserId) !note.visibleUserIds.some(v => v === userListMembership.userListUserId)
) continue; ) continue;

View file

@ -138,7 +138,7 @@ export class ReactionService {
reaction = reacterHost ? `:${name}@${reacterHost}:` : `:${name}:`; reaction = reacterHost ? `:${name}@${reacterHost}:` : `:${name}:`;
// センシティブ // センシティブ
if ((note.reactionAcceptance === 'nonSensitiveOnly') && emoji.isSensitive) { if ((note.reactionAcceptance === 'nonSensitiveOnly' || note.reactionAcceptance === 'nonSensitiveOnlyForLocalLikeOnlyForRemote') && emoji.isSensitive) {
reaction = FALLBACK; reaction = FALLBACK;
} }
} else { } else {

View file

@ -6,6 +6,7 @@
import { URL } from 'node:url'; import { URL } from 'node:url';
import { toASCII } from 'punycode'; import { toASCII } from 'punycode';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import RE2 from 're2';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
@ -41,6 +42,33 @@ export class UtilityService {
return silencedHosts.some(x => `.${host.toLowerCase()}`.endsWith(`.${x}`)); return silencedHosts.some(x => `.${host.toLowerCase()}`.endsWith(`.${x}`));
} }
@bindThis
public isSensitiveWordIncluded(text: string, sensitiveWords: string[]): boolean {
if (sensitiveWords.length === 0) return false;
if (text === '') return false;
const regexpregexp = /^\/(.+)\/(.*)$/;
const matched = sensitiveWords.some(filter => {
// represents RegExp
const regexp = filter.match(regexpregexp);
// This should never happen due to input sanitisation.
if (!regexp) {
const words = filter.split(' ');
return words.every(keyword => text.includes(keyword));
}
try {
// TODO: RE2インスタンスをキャッシュ
return new RE2(regexp[1], regexp[2]).test(text);
} catch (err) {
// This should never happen due to input sanitisation.
return false;
}
});
return matched;
}
@bindThis @bindThis
public extractDbHost(uri: string): string { public extractDbHost(uri: string): string {
const url = new URL(uri); const url = new URL(uri);

View file

@ -381,6 +381,10 @@ export const meta = {
type: 'string', type: 'string',
optional: false, nullable: true, optional: false, nullable: true,
}, },
shortName: {
type: 'string',
optional: false, nullable: true,
},
objectStorageS3ForcePathStyle: { objectStorageS3ForcePathStyle: {
type: 'boolean', type: 'boolean',
optional: false, nullable: false, optional: false, nullable: false,

View file

@ -29,37 +29,10 @@ export const meta = {
optional: false, optional: false,
nullable: false, nullable: false,
items: { items: {
properties: { type: 'object',
id: { type: 'string' }, optional: false,
firstRetrievedAt: { type: 'string' }, nullable: false,
host: { type: 'string' }, ref: 'FederationInstance',
usersCount: { type: 'number' },
notesCount: { type: 'number' },
followingCount: { type: 'number' },
followersCount: { type: 'number' },
isNotResponding: { type: 'boolean' },
isSuspended: { type: 'boolean' },
isBlocked: { type: 'boolean' },
softwareName: { type: 'string' },
softwareVersion: { type: 'string' },
openRegistrations: { type: 'boolean' },
name: { type: 'string' },
description: { type: 'string' },
maintainerName: { type: 'string' },
maintainerEmail: { type: 'string' },
isSilenced: { type: 'boolean' },
iconUrl: { type: 'string' },
faviconUrl: { type: 'string' },
themeColor: { type: 'string' },
infoUpdatedAt: {
type: 'string',
nullable: true,
},
latestRequestReceivedAt: {
type: 'string',
nullable: true,
},
}
}, },
}, },
otherFollowersCount: { type: 'number' }, otherFollowersCount: { type: 'number' },
@ -68,42 +41,15 @@ export const meta = {
optional: false, optional: false,
nullable: false, nullable: false,
items: { items: {
properties: { type: 'object',
id: { type: 'string' }, optional: false,
firstRetrievedAt: { type: 'string' }, nullable: false,
host: { type: 'string' }, ref: 'FederationInstance',
usersCount: { type: 'number' },
notesCount: { type: 'number' },
followingCount: { type: 'number' },
followersCount: { type: 'number' },
isNotResponding: { type: 'boolean' },
isSuspended: { type: 'boolean' },
isBlocked: { type: 'boolean' },
softwareName: { type: 'string' },
softwareVersion: { type: 'string' },
openRegistrations: { type: 'boolean' },
name: { type: 'string' },
description: { type: 'string' },
maintainerName: { type: 'string' },
maintainerEmail: { type: 'string' },
isSilenced: { type: 'boolean' },
iconUrl: { type: 'string' },
faviconUrl: { type: 'string' },
themeColor: { type: 'string' },
infoUpdatedAt: {
type: 'string',
nullable: true,
},
latestRequestReceivedAt: {
type: 'string',
nullable: true,
},
}
}, },
}, },
otherFollowingCount: { type: 'number' }, otherFollowingCount: { type: 'number' },
}, },
} },
} as const; } as const;
export const paramDef = { export const paramDef = {

View file

@ -164,20 +164,34 @@ export const meta = {
type: 'object', type: 'object',
optional: false, nullable: false, optional: false, nullable: false,
properties: { properties: {
place: { id: {
type: 'string', type: 'string',
optional: false, nullable: false, optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
}, },
url: { url: {
type: 'string', type: 'string',
optional: false, nullable: false, optional: false, nullable: false,
format: 'url', format: 'url',
}, },
place: {
type: 'string',
optional: false, nullable: false,
},
ratio: {
type: 'number',
optional: false, nullable: false,
},
imageUrl: { imageUrl: {
type: 'string', type: 'string',
optional: false, nullable: false, optional: false, nullable: false,
format: 'url', format: 'url',
}, },
dayOfWeek: {
type: 'integer',
optional: false, nullable: false,
},
}, },
}, },
}, },

View file

@ -21,6 +21,10 @@ export const meta = {
res: { res: {
type: 'object', type: 'object',
optional: false, nullable: false, optional: false, nullable: false,
properties: {
sourceLang: { type: 'string' },
text: { type: 'string' },
},
}, },
errors: { errors: {

View file

@ -6,7 +6,7 @@
import { parse } from 'acorn'; import { parse } from 'acorn';
import { generate } from 'astring'; import { generate } from 'astring';
import { describe, expect, it } from 'vitest'; import { describe, expect, it } from 'vitest';
import { normalizeClass, unwindCssModuleClassName } from './rollup-plugin-unwind-css-module-class-name'; import { normalizeClass, unwindCssModuleClassName } from './rollup-plugin-unwind-css-module-class-name.js';
import type * as estree from 'estree'; import type * as estree from 'estree';
function parseExpression(code: string): estree.Expression { function parseExpression(code: string): estree.Expression {

View file

@ -7,8 +7,8 @@
import { action } from '@storybook/addon-actions'; import { action } from '@storybook/addon-actions';
import { StoryObj } from '@storybook/vue3'; import { StoryObj } from '@storybook/vue3';
import { rest } from 'msw'; import { rest } from 'msw';
import { abuseUserReport } from '../../.storybook/fakes'; import { abuseUserReport } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks'; import { commonHandlers } from '../../.storybook/mocks.js';
import MkAbuseReport from './MkAbuseReport.vue'; import MkAbuseReport from './MkAbuseReport.vue';
export const Default = { export const Default = {
render(args) { render(args) {

View file

@ -7,8 +7,8 @@
import { action } from '@storybook/addon-actions'; import { action } from '@storybook/addon-actions';
import { StoryObj } from '@storybook/vue3'; import { StoryObj } from '@storybook/vue3';
import { rest } from 'msw'; import { rest } from 'msw';
import { userDetailed } from '../../.storybook/fakes'; import { userDetailed } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks'; import { commonHandlers } from '../../.storybook/mocks.js';
import MkAbuseReportWindow from './MkAbuseReportWindow.vue'; import MkAbuseReportWindow from './MkAbuseReportWindow.vue';
export const Default = { export const Default = {
render(args) { render(args) {

View file

@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable @typescript-eslint/explicit-function-return-type */
import { StoryObj } from '@storybook/vue3'; import { StoryObj } from '@storybook/vue3';
import { userDetailed } from '../../.storybook/fakes'; import { userDetailed } from '../../.storybook/fakes.js';
import MkAccountMoved from './MkAccountMoved.vue'; import MkAccountMoved from './MkAccountMoved.vue';
export const Default = { export const Default = {
render(args) { render(args) {

View file

@ -6,8 +6,8 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable @typescript-eslint/explicit-function-return-type */
import { StoryObj } from '@storybook/vue3'; import { StoryObj } from '@storybook/vue3';
import { rest } from 'msw'; import { rest } from 'msw';
import { userDetailed } from '../../.storybook/fakes'; import { userDetailed } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks'; import { commonHandlers } from '../../.storybook/mocks.js';
import MkAchievements from './MkAchievements.vue'; import MkAchievements from './MkAchievements.vue';
import { ACHIEVEMENT_TYPES } from '@/scripts/achievements.js'; import { ACHIEVEMENT_TYPES } from '@/scripts/achievements.js';
export const Empty = { export const Empty = {

View file

@ -67,7 +67,7 @@ const props = withDefaults(defineProps<{
withDescription: true, withDescription: true,
}); });
const achievements = ref(); const achievements = ref<Misskey.entities.UsersAchievementsResponse | null>(null);
const lockedAchievements = computed(() => ACHIEVEMENT_TYPES.filter(x => !(achievements.value ?? []).some(a => a.name === x))); const lockedAchievements = computed(() => ACHIEVEMENT_TYPES.filter(x => !(achievements.value ?? []).some(a => a.name === x)));
function fetch() { function fetch() {

View file

@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
</div> </div>
<span v-else-if="c.type === 'text'" :class="{ [$style.fontSerif]: c.font === 'serif', [$style.fontMonospace]: c.font === 'monospace' }" :style="{ fontSize: c.size ? `${c.size * 100}%` : null, fontWeight: c.bold ? 'bold' : null, color: c.color ?? null }">{{ c.text }}</span> <span v-else-if="c.type === 'text'" :class="{ [$style.fontSerif]: c.font === 'serif', [$style.fontMonospace]: c.font === 'monospace' }" :style="{ fontSize: c.size ? `${c.size * 100}%` : null, fontWeight: c.bold ? 'bold' : null, color: c.color ?? null }">{{ c.text }}</span>
<Mfm v-else-if="c.type === 'mfm'" :class="{ [$style.fontSerif]: c.font === 'serif', [$style.fontMonospace]: c.font === 'monospace' }" :style="{ fontSize: c.size ? `${c.size * 100}%` : null, fontWeight: c.bold ? 'bold' : null, color: c.color ?? null }" :text="c.text"/> <Mfm v-else-if="c.type === 'mfm'" :class="{ [$style.fontSerif]: c.font === 'serif', [$style.fontMonospace]: c.font === 'monospace' }" :style="{ fontSize: c.size ? `${c.size * 100}%` : null, fontWeight: c.bold ? 'bold' : null, color: c.color ?? null }" :text="c.text" @clickEv="c.onClickEv"/>
<MkButton v-else-if="c.type === 'button'" :primary="c.primary" :rounded="c.rounded" :disabled="c.disabled" :small="size === 'small'" inline @click="c.onClick">{{ c.text }}</MkButton> <MkButton v-else-if="c.type === 'button'" :primary="c.primary" :rounded="c.rounded" :disabled="c.disabled" :small="size === 'small'" inline @click="c.onClick">{{ c.text }}</MkButton>
<div v-else-if="c.type === 'buttons'" class="_buttons" :style="{ justifyContent: align }"> <div v-else-if="c.type === 'buttons'" class="_buttons" :style="{ justifyContent: align }">
<MkButton v-for="button in c.buttons" :primary="button.primary" :rounded="button.rounded" :disabled="button.disabled" inline :small="size === 'small'" @click="button.onClick">{{ button.text }}</MkButton> <MkButton v-for="button in c.buttons" :primary="button.primary" :rounded="button.rounded" :disabled="button.disabled" inline :small="size === 'small'" @click="button.onClick">{{ button.text }}</MkButton>

View file

@ -9,8 +9,8 @@ import { expect } from '@storybook/jest';
import { userEvent, waitFor, within } from '@storybook/testing-library'; import { userEvent, waitFor, within } from '@storybook/testing-library';
import { StoryObj } from '@storybook/vue3'; import { StoryObj } from '@storybook/vue3';
import { rest } from 'msw'; import { rest } from 'msw';
import { userDetailed } from '../../.storybook/fakes'; import { userDetailed } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks'; import { commonHandlers } from '../../.storybook/mocks.js';
import MkAutocomplete from './MkAutocomplete.vue'; import MkAutocomplete from './MkAutocomplete.vue';
import MkInput from './MkInput.vue'; import MkInput from './MkInput.vue';
import { tick } from '@/scripts/test-utils.js'; import { tick } from '@/scripts/test-utils.js';

View file

@ -6,8 +6,8 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable @typescript-eslint/explicit-function-return-type */
import { StoryObj } from '@storybook/vue3'; import { StoryObj } from '@storybook/vue3';
import { rest } from 'msw'; import { rest } from 'msw';
import { userDetailed } from '../../.storybook/fakes'; import { userDetailed } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks'; import { commonHandlers } from '../../.storybook/mocks.js';
import MkAvatars from './MkAvatars.vue'; import MkAvatars from './MkAvatars.vue';
export const Default = { export const Default = {
render(args) { render(args) {

View file

@ -26,7 +26,7 @@ export type Captcha = {
getResponse(id: string): string; getResponse(id: string): string;
}; };
type CaptchaProvider = 'hcaptcha' | 'recaptcha' | 'turnstile'; export type CaptchaProvider = 'hcaptcha' | 'recaptcha' | 'turnstile';
type CaptchaContainer = { type CaptchaContainer = {
readonly [_ in CaptchaProvider]?: Captcha; readonly [_ in CaptchaProvider]?: Captcha;

View file

@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, onBeforeUnmount, shallowRef, ref } from 'vue'; import { onMounted, onBeforeUnmount, shallowRef, ref } from 'vue';
import MkMenu from './MkMenu.vue'; import MkMenu from './MkMenu.vue';
import { MenuItem } from './types/menu.vue'; import { MenuItem } from '@/types/menu.js';
import contains from '@/scripts/contains.js'; import contains from '@/scripts/contains.js';
import { defaultStore } from '@/store.js'; import { defaultStore } from '@/store.js';
import * as os from '@/os.js'; import * as os from '@/os.js';

View file

@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts"> <script lang="ts">
import { defineComponent, h, PropType, TransitionGroup, useCssModule } from 'vue'; import { defineComponent, h, PropType, TransitionGroup, useCssModule } from 'vue';
import MkAd from '@/components/global/MkAd.vue'; import MkAd from '@/components/global/MkAd.vue';
import { isDebuggerEnabled, stackTraceInstances } from '@/debug'; import { isDebuggerEnabled, stackTraceInstances } from '@/debug.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { defaultStore } from '@/store.js'; import { defaultStore } from '@/store.js';
import { MisskeyEntity } from '@/types/date-separated-list'; import { MisskeyEntity } from '@/types/date-separated-list.js';
export default defineComponent({ export default defineComponent({
props: { props: {

View file

@ -104,7 +104,7 @@ const props = defineProps<{
isRequest: boolean, isRequest: boolean,
}>(); }>();
let dialog = ref(null); let dialog = ref<InstanceType<typeof MkModalWindow> | null>(null);
let name = ref(props.emoji ? props.emoji.name : ''); let name = ref(props.emoji ? props.emoji.name : '');
let category = ref(props.emoji ? props.emoji.category : ''); let category = ref(props.emoji ? props.emoji.category : '');
let aliases = ref(props.emoji ? props.emoji.aliases.join(' ') : ''); let aliases = ref(props.emoji ? props.emoji.aliases.join(' ') : '');
@ -112,7 +112,7 @@ let license = ref(props.emoji ? (props.emoji.license ?? '') : '');
let isSensitive = ref(props.emoji ? props.emoji.isSensitive : false); let isSensitive = ref(props.emoji ? props.emoji.isSensitive : false);
let localOnly = ref(props.emoji ? props.emoji.localOnly : false); let localOnly = ref(props.emoji ? props.emoji.localOnly : false);
let roleIdsThatCanBeUsedThisEmojiAsReaction = ref((props.emoji && props.emoji.roleIdsThatCanBeUsedThisEmojiAsReaction) ? props.emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : []); let roleIdsThatCanBeUsedThisEmojiAsReaction = ref((props.emoji && props.emoji.roleIdsThatCanBeUsedThisEmojiAsReaction) ? props.emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : []);
let rolesThatCanBeUsedThisEmojiAsReaction = ref([]); let rolesThatCanBeUsedThisEmojiAsReaction = ref<Misskey.entities.Role[]>([]);
let file = ref<Misskey.entities.DriveFile>(); let file = ref<Misskey.entities.DriveFile>();
let isRequest = ref(props.isRequest ?? false); let isRequest = ref(props.isRequest ?? false);
watch((roleIdsThatCanBeUsedThisEmojiAsReaction), async () => { watch((roleIdsThatCanBeUsedThisEmojiAsReaction), async () => {

View file

@ -38,14 +38,14 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import MkPagination from '@/components/MkPagination.vue'; import MkPagination, { Paging } from '@/components/MkPagination.vue';
import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue'; import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
import bytes from '@/filters/bytes.js'; import bytes from '@/filters/bytes.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { dateString } from '@/filters/date.js'; import { dateString } from '@/filters/date.js';
const props = defineProps<{ const props = defineProps<{
pagination: any; pagination: Paging;
viewMode: 'grid' | 'list'; viewMode: 'grid' | 'list';
}>(); }>();
</script> </script>

View file

@ -7,7 +7,7 @@
import { expect } from '@storybook/jest'; import { expect } from '@storybook/jest';
import { userEvent, waitFor, within } from '@storybook/testing-library'; import { userEvent, waitFor, within } from '@storybook/testing-library';
import { StoryObj } from '@storybook/vue3'; import { StoryObj } from '@storybook/vue3';
import { galleryPost } from '../../.storybook/fakes'; import { galleryPost } from '../../.storybook/fakes.js';
import MkGalleryPostPreview from './MkGalleryPostPreview.vue'; import MkGalleryPostPreview from './MkGalleryPostPreview.vue';
export const Default = { export const Default = {
render(args) { render(args) {

View file

@ -6,8 +6,8 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable @typescript-eslint/explicit-function-return-type */
import { StoryObj } from '@storybook/vue3'; import { StoryObj } from '@storybook/vue3';
import { rest } from 'msw'; import { rest } from 'msw';
import { userDetailed, inviteCode } from '../../.storybook/fakes'; import { userDetailed, inviteCode } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks'; import { commonHandlers } from '../../.storybook/mocks.js';
import MkInviteCode from './MkInviteCode.vue'; import MkInviteCode from './MkInviteCode.vue';
export const Default = { export const Default = {

View file

@ -29,7 +29,7 @@ const self = props.url.startsWith(local);
const attr = self ? 'to' : 'href'; const attr = self ? 'to' : 'href';
const target = self ? null : '_blank'; const target = self ? null : '_blank';
const el = ref(); const el = ref<HTMLElement>();
useTooltip(el, (showing) => { useTooltip(el, (showing) => {
os.popup(defineAsyncComponent(() => import('@/components/MkUrlPreviewPopup.vue')), { os.popup(defineAsyncComponent(() => import('@/components/MkUrlPreviewPopup.vue')), {

View file

@ -27,7 +27,7 @@ export default {
}, },
}, },
setup(props) { setup(props) {
const contentEl = ref(); const contentEl = ref<HTMLElement>();
function calc() { function calc() {
const eachLength = contentEl.value.offsetWidth / props.repeat; const eachLength = contentEl.value.offsetWidth / props.repeat;

View file

@ -37,7 +37,7 @@ import XBanner from '@/components/MkMediaBanner.vue';
import XImage from '@/components/MkMediaImage.vue'; import XImage from '@/components/MkMediaImage.vue';
import XVideo from '@/components/MkMediaVideo.vue'; import XVideo from '@/components/MkMediaVideo.vue';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { FILE_TYPE_BROWSERSAFE } from '@/const'; import { FILE_TYPE_BROWSERSAFE } from '@/const.js';
import { defaultStore } from '@/store.js'; import { defaultStore } from '@/store.js';
const props = defineProps<{ const props = defineProps<{

View file

@ -76,7 +76,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts"> <script lang="ts">
import { computed, defineAsyncComponent, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue'; import { computed, defineAsyncComponent, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue';
import { focusPrev, focusNext } from '@/scripts/focus.js'; import { focusPrev, focusNext } from '@/scripts/focus.js';
import { MenuItem, InnerMenuItem, MenuPending, MenuAction, MenuSwitch, MenuParent } from '@/types/menu'; import { MenuItem, InnerMenuItem, MenuPending, MenuAction, MenuSwitch, MenuParent } from '@/types/menu.js';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { isTouchUsing } from '@/scripts/touch.js'; import { isTouchUsing } from '@/scripts/touch.js';

View file

@ -257,7 +257,7 @@ const collapsed = ref(appearNote.value.cw == null && isLong);
const isDeleted = ref(false); const isDeleted = ref(false);
const muted = ref(checkMute(appearNote.value, $i?.mutedWords)); const muted = ref(checkMute(appearNote.value, $i?.mutedWords));
const hardMuted = ref(props.withHardMute && checkMute(appearNote.value, $i?.hardMutedWords)); const hardMuted = ref(props.withHardMute && checkMute(appearNote.value, $i?.hardMutedWords));
const translation = ref<any>(null); const translation = ref<Misskey.entities.NotesTranslateResponse | null>(null);
const translating = ref(false); const translating = ref(false);
const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.value.user.instance); const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.value.user.instance);
const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || (appearNote.value.visibility === 'followers' && appearNote.value.userId === $i.id)); const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || (appearNote.value.visibility === 'followers' && appearNote.value.userId === $i.id));

View file

@ -218,7 +218,7 @@ import { claimAchievement } from '@/scripts/achievements.js';
import MkRippleEffect from '@/components/MkRippleEffect.vue'; import MkRippleEffect from '@/components/MkRippleEffect.vue';
import { showMovedDialog } from '@/scripts/show-moved-dialog.js'; import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
import MkUserCardMini from '@/components/MkUserCardMini.vue'; import MkUserCardMini from '@/components/MkUserCardMini.vue';
import MkPagination from '@/components/MkPagination.vue'; import MkPagination, { Paging } from '@/components/MkPagination.vue';
import MkReactionIcon from '@/components/MkReactionIcon.vue'; import MkReactionIcon from '@/components/MkReactionIcon.vue';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
@ -299,7 +299,7 @@ const isMyRenote = $i && ($i.id === note.value.userId);
const showContent = ref(false); const showContent = ref(false);
const isDeleted = ref(false); const isDeleted = ref(false);
const muted = ref($i ? checkWordMute(appearNote.value, $i, $i.mutedWords) : false); const muted = ref($i ? checkWordMute(appearNote.value, $i, $i.mutedWords) : false);
const translation = ref(null); const translation = ref<Misskey.entities.NotesTranslateResponse | null>(null);
const translating = ref(false); const translating = ref(false);
const parsed = appearNote.value.text ? mfm.parse(appearNote.value.text) : null; const parsed = appearNote.value.text ? mfm.parse(appearNote.value.text) : null;
const urls = parsed ? extractUrlFromMfm(parsed) : null; const urls = parsed ? extractUrlFromMfm(parsed) : null;
@ -325,7 +325,7 @@ provide('react', (reaction: string) => {
}); });
const tab = ref('replies'); const tab = ref('replies');
const reactionTabType = ref(null); const reactionTabType = ref<string | null>(null);
const renotesPagination = computed(() => ({ const renotesPagination = computed(() => ({
endpoint: 'notes/renotes', endpoint: 'notes/renotes',
@ -333,7 +333,7 @@ const renotesPagination = computed(() => ({
params: { params: {
noteId: appearNote.value.id, noteId: appearNote.value.id,
}, },
})); } satisfies Paging));
const reactionsPagination = computed(() => ({ const reactionsPagination = computed(() => ({
endpoint: 'notes/reactions', endpoint: 'notes/reactions',
@ -342,7 +342,7 @@ const reactionsPagination = computed(() => ({
noteId: appearNote.value.id, noteId: appearNote.value.id,
type: reactionTabType.value, type: reactionTabType.value,
}, },
})); } satisfies Paging));
useNoteCapture({ useNoteCapture({
rootEl: el, rootEl: el,

View file

@ -37,7 +37,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<i v-else-if="notification.type === 'quote'" class="ti ti-quote"></i> <i v-else-if="notification.type === 'quote'" class="ti ti-quote"></i>
<i v-else-if="notification.type === 'pollEnded'" class="ti ti-chart-arrows"></i> <i v-else-if="notification.type === 'pollEnded'" class="ti ti-chart-arrows"></i>
<i v-else-if="notification.type === 'achievementEarned'" class="ti ti-medal"></i> <i v-else-if="notification.type === 'achievementEarned'" class="ti ti-medal"></i>
<img v-else-if="notification.type === 'roleAssigned'" :src="notification.role.iconUrl" alt=""/> <img v-else-if="notification.type === 'roleAssigned'" style="height: 1.3em; vertical-align: -22%;" :src="notification.role.iconUrl" alt=""/>
<!-- notification.reaction null になることはまずないがここでoptional chaining使うと一部ブラウザで刺さるので念の為 --> <!-- notification.reaction null になることはまずないがここでoptional chaining使うと一部ブラウザで刺さるので念の為 -->
<MkReactionIcon <MkReactionIcon
v-else-if="notification.type === 'reaction'" v-else-if="notification.type === 'reaction'"

View file

@ -37,7 +37,7 @@ import copyToClipboard from '@/scripts/copy-to-clipboard.js';
import { url } from '@/config.js'; import { url } from '@/config.js';
import { mainRouter, routes, page } from '@/router.js'; import { mainRouter, routes, page } from '@/router.js';
import { $i } from '@/account.js'; import { $i } from '@/account.js';
import { Router, useScrollPositionManager } from '@/nirax'; import { Router, useScrollPositionManager } from '@/nirax.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata.js'; import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata.js';
import { openingWindowsCount } from '@/os.js'; import { openingWindowsCount } from '@/os.js';

View file

@ -49,7 +49,7 @@ import * as os from '@/os.js';
import { onScrollTop, isTopVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isBottomVisible } from '@/scripts/scroll.js'; import { onScrollTop, isTopVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isBottomVisible } from '@/scripts/scroll.js';
import { useDocumentVisibility } from '@/scripts/use-document-visibility.js'; import { useDocumentVisibility } from '@/scripts/use-document-visibility.js';
import { defaultStore } from '@/store.js'; import { defaultStore } from '@/store.js';
import { MisskeyEntity } from '@/types/date-separated-list'; import { MisskeyEntity } from '@/types/date-separated-list.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
const SECOND_FETCH_LIMIT = 30; const SECOND_FETCH_LIMIT = 30;

View file

@ -52,7 +52,7 @@ const emit = defineEmits<{
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>(); const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
const passwordInput = shallowRef<InstanceType<typeof MkInput>>(); const passwordInput = shallowRef<InstanceType<typeof MkInput>>();
const password = ref(''); const password = ref('');
const token = ref(null); const token = ref<string | null>(null);
function onClose() { function onClose() {
emit('cancelled'); emit('cancelled');

View file

@ -203,14 +203,14 @@ watch(showPreview, () => defaultStore.set('showPreview', showPreview.value));
const cw = ref<string | null>(props.initialCw ?? null); const cw = ref<string | null>(props.initialCw ?? null);
const localOnly = ref<boolean>(props.initialLocalOnly ?? defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly); const localOnly = ref<boolean>(props.initialLocalOnly ?? defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly);
const visibility = ref(props.initialVisibility ?? (defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility) as typeof Misskey.noteVisibilities[number]); const visibility = ref(props.initialVisibility ?? (defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility) as typeof Misskey.noteVisibilities[number]);
const visibleUsers = ref([]); const visibleUsers = ref<Misskey.entities.UserDetailed[]>([]);
if (props.initialVisibleUsers) { if (props.initialVisibleUsers) {
props.initialVisibleUsers.forEach(pushVisibleUser); props.initialVisibleUsers.forEach(pushVisibleUser);
} }
const reactionAcceptance = ref(defaultStore.state.reactionAcceptance); const reactionAcceptance = ref(defaultStore.state.reactionAcceptance);
const autocomplete = ref(null); const autocomplete = ref(null);
const draghover = ref(false); const draghover = ref(false);
const quoteId = ref(null); const quoteId = ref<string | null>(null);
const hasNotSpecifiedMentions = ref(false); const hasNotSpecifiedMentions = ref(false);
const recentHashtags = ref(JSON.parse(miLocalStorage.getItem('hashtags') ?? '[]')); const recentHashtags = ref(JSON.parse(miLocalStorage.getItem('hashtags') ?? '[]'));
const imeText = ref(''); const imeText = ref('');

View file

@ -28,10 +28,11 @@ 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 { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
role: any; role: Misskey.entities.Role;
forModeration: boolean; forModeration: boolean;
detailed: boolean; detailed: boolean;
}>(), { }>(), {

View file

@ -65,10 +65,10 @@ const opening = ref(false);
const changed = ref(false); const changed = ref(false);
const invalid = ref(false); const invalid = ref(false);
const filled = computed(() => v.value !== '' && v.value != null); const filled = computed(() => v.value !== '' && v.value != null);
const inputEl = ref(null); const inputEl = ref<HTMLObjectElement | null>(null);
const prefixEl = ref(null); const prefixEl = ref<HTMLElement | null>(null);
const suffixEl = ref(null); const suffixEl = ref<HTMLElement | null>(null);
const container = ref(null); const container = ref<HTMLElement | null>(null);
const height = const height =
props.small ? 33 : props.small ? 33 :
props.large ? 39 : props.large ? 39 :

View file

@ -71,8 +71,6 @@ const host = ref(toUnicode(configHost));
const totpLogin = ref(false); const totpLogin = ref(false);
const queryingKey = ref(false); const queryingKey = ref(false);
const credentialRequest = ref<CredentialRequestOptions | null>(null); const credentialRequest = ref<CredentialRequestOptions | null>(null);
const hCaptchaResponse = ref(null);
const reCaptchaResponse = ref(null);
const emit = defineEmits<{ const emit = defineEmits<{
(ev: 'login', v: any): void; (ev: 'login', v: any): void;
@ -126,8 +124,6 @@ async function queryKey(): Promise<void> {
username: username.value, username: username.value,
password: password.value, password: password.value,
credential: credential.toJSON(), credential: credential.toJSON(),
'hcaptcha-response': hCaptchaResponse.value,
'g-recaptcha-response': reCaptchaResponse.value,
}); });
}).then(res => { }).then(res => {
emit('login', res); emit('login', res);
@ -149,8 +145,6 @@ function onSubmit(): void {
os.api('signin', { os.api('signin', {
username: username.value, username: username.value,
password: password.value, password: password.value,
'hcaptcha-response': hCaptchaResponse.value,
'g-recaptcha-response': reCaptchaResponse.value,
}).then(res => { }).then(res => {
totpLogin.value = true; totpLogin.value = true;
signing.value = false; signing.value = false;
@ -168,8 +162,6 @@ function onSubmit(): void {
os.api('signin', { os.api('signin', {
username: username.value, username: username.value,
password: password.value, password: password.value,
'hcaptcha-response': hCaptchaResponse.value,
'g-recaptcha-response': reCaptchaResponse.value,
token: user.value?.twoFactorEnabled ? token.value : undefined, token: user.value?.twoFactorEnabled ? token.value : undefined,
}).then(res => { }).then(res => {
emit('login', res); emit('login', res);

View file

@ -154,9 +154,9 @@ const emailState = ref<null | 'wait' | 'ok' | 'unavailable:used' | 'unavailable:
const passwordStrength = ref<'' | 'low' | 'medium' | 'high'>(''); const passwordStrength = ref<'' | 'low' | 'medium' | 'high'>('');
const passwordRetypeState = ref<null | 'match' | 'not-match'>(null); const passwordRetypeState = ref<null | 'match' | 'not-match'>(null);
const submitting = ref<boolean>(false); const submitting = ref<boolean>(false);
const hCaptchaResponse = ref(null); const hCaptchaResponse = ref<string | null>(null);
const reCaptchaResponse = ref(null); const reCaptchaResponse = ref<string | null>(null);
const turnstileResponse = ref(null); const turnstileResponse = ref<string | null>(null);
const usernameAbortController = ref<null | AbortController>(null); const usernameAbortController = ref<null | AbortController>(null);
const emailAbortController = ref<null | AbortController>(null); const emailAbortController = ref<null | AbortController>(null);

View file

@ -72,7 +72,14 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, onUnmounted, ref, shallowRef } from 'vue'; import { onMounted, onUnmounted, ref, shallowRef } from 'vue';
const particles = ref([]); const particles = ref<{
id: string,
x: number,
y: number,
size: number,
dur: number,
color: string
}[]>([]);
const el = shallowRef<HTMLElement>(); const el = shallowRef<HTMLElement>();
const width = ref(0); const width = ref(0);
const height = ref(0); const height = ref(0);

View file

@ -66,7 +66,7 @@ const props = defineProps<{
announcement?: any, announcement?: any,
}>(); }>();
const dialog = ref(null); const dialog = ref<InstanceType<typeof MkModalWindow> | null>(null);
const title = ref<string>(props.announcement ? props.announcement.title : ''); const title = ref<string>(props.announcement ? props.announcement.title : '');
const text = ref<string>(props.announcement ? props.announcement.text : ''); const text = ref<string>(props.announcement ? props.announcement.text : '');
const icon = ref<string>(props.announcement ? props.announcement.icon : 'info'); const icon = ref<string>(props.announcement ? props.announcement.icon : 'info');

View file

@ -6,8 +6,8 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable @typescript-eslint/explicit-function-return-type */
import { StoryObj } from '@storybook/vue3'; import { StoryObj } from '@storybook/vue3';
import { rest } from 'msw'; import { rest } from 'msw';
import { commonHandlers } from '../../.storybook/mocks'; import { commonHandlers } from '../../.storybook/mocks.js';
import { userDetailed } from '../../.storybook/fakes'; import { userDetailed } from '../../.storybook/fakes.js';
import MkUserSetupDialog_Follow from './MkUserSetupDialog.Follow.vue'; import MkUserSetupDialog_Follow from './MkUserSetupDialog.Follow.vue';
export const Default = { export const Default = {
render(args) { render(args) {

View file

@ -37,15 +37,15 @@ SPDX-License-Identifier: AGPL-3.0-only
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import MkFolder from '@/components/MkFolder.vue'; import MkFolder from '@/components/MkFolder.vue';
import XUser from '@/components/MkUserSetupDialog.User.vue'; import XUser from '@/components/MkUserSetupDialog.User.vue';
import MkPagination from '@/components/MkPagination.vue'; import MkPagination, { Paging } from '@/components/MkPagination.vue';
const pinnedUsers = { endpoint: 'pinned-users', noPaging: true }; const pinnedUsers = { endpoint: 'pinned-users', noPaging: true } satisfies Paging;
const popularUsers = { endpoint: 'users', limit: 10, noPaging: true, params: { const popularUsers = { endpoint: 'users', limit: 10, noPaging: true, params: {
state: 'alive', state: 'alive',
origin: 'local', origin: 'local',
sort: '+follower', sort: '+follower',
} }; } } satisfies Paging;
</script> </script>
<style lang="scss" module> <style lang="scss" module>

View file

@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable @typescript-eslint/explicit-function-return-type */
import { StoryObj } from '@storybook/vue3'; import { StoryObj } from '@storybook/vue3';
import { userDetailed } from '../../.storybook/fakes'; import { userDetailed } from '../../.storybook/fakes.js';
import MkUserSetupDialog_User from './MkUserSetupDialog.User.vue'; import MkUserSetupDialog_User from './MkUserSetupDialog.User.vue';
export const Default = { export const Default = {
render(args) { render(args) {

View file

@ -6,8 +6,8 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable @typescript-eslint/explicit-function-return-type */
import { StoryObj } from '@storybook/vue3'; import { StoryObj } from '@storybook/vue3';
import { rest } from 'msw'; import { rest } from 'msw';
import { commonHandlers } from '../../.storybook/mocks'; import { commonHandlers } from '../../.storybook/mocks.js';
import { userDetailed } from '../../.storybook/fakes'; import { userDetailed } from '../../.storybook/fakes.js';
import MkUserSetupDialog from './MkUserSetupDialog.vue'; import MkUserSetupDialog from './MkUserSetupDialog.vue';
export const Default = { export const Default = {
render(args) { render(args) {

View file

@ -54,7 +54,7 @@ import { defineAsyncComponent, ref } from 'vue';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import MkSelect from '@/components/MkSelect.vue'; import MkSelect from '@/components/MkSelect.vue';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import { widgets as widgetDefs } from '@/widgets'; import { widgets as widgetDefs } from '@/widgets/index.js';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
@ -77,7 +77,7 @@ const widgetRefs = {};
const configWidget = (id: string) => { const configWidget = (id: string) => {
widgetRefs[id].configure(); widgetRefs[id].configure();
}; };
const widgetAdderSelected = ref(null); const widgetAdderSelected = ref<string | null>(null);
const addWidget = () => { const addWidget = () => {
if (widgetAdderSelected.value == null) return; if (widgetAdderSelected.value == null) return;

View file

@ -30,7 +30,7 @@ const props = defineProps<{
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(null); const result = ref<any>(null);
const process = () => { const process = () => {
if (props.p == null) { if (props.p == null) {

View file

@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable @typescript-eslint/explicit-function-return-type */
import { StoryObj } from '@storybook/vue3'; import { StoryObj } from '@storybook/vue3';
import { userDetailed } from '../../../.storybook/fakes'; import { userDetailed } from '../../../.storybook/fakes.js';
import MkAcct from './MkAcct.vue'; import MkAcct from './MkAcct.vue';
export const Default = { export const Default = {
render(args) { render(args) {

View file

@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable @typescript-eslint/explicit-function-return-type */
import { StoryObj } from '@storybook/vue3'; import { StoryObj } from '@storybook/vue3';
import { userDetailed } from '../../../.storybook/fakes'; import { userDetailed } from '../../../.storybook/fakes.js';
import MkAvatar from './MkAvatar.vue'; import MkAvatar from './MkAvatar.vue';
const common = { const common = {
render(args) { render(args) {

View file

@ -7,7 +7,7 @@
import { StoryObj } from '@storybook/vue3'; import { StoryObj } from '@storybook/vue3';
import { within } from '@storybook/testing-library'; import { within } from '@storybook/testing-library';
import { expect } from '@storybook/jest'; import { expect } from '@storybook/jest';
import MkMisskeyFlavoredMarkdown from './MkMisskeyFlavoredMarkdown.ts'; import MkMisskeyFlavoredMarkdown from './MkMisskeyFlavoredMarkdown.js';
export const Default = { export const Default = {
render(args) { render(args) {
return { return {

View file

@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { VNode, h } from 'vue'; import { VNode, h, SetupContext } from 'vue';
import * as mfm from 'mfm-js'; import * as mfm from 'mfm-js';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import MkUrl from '@/components/global/MkUrl.vue'; import MkUrl from '@/components/global/MkUrl.vue';
@ -77,8 +77,12 @@ type MfmProps = {
enableEmojiMenuReaction?: boolean; enableEmojiMenuReaction?: boolean;
}; };
type MfmEvents = {
clickEv(id: string): void;
};
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export
export default function(props: MfmProps) { export default function(props: MfmProps, context: SetupContext<MfmEvents>) {
const isNote = props.isNote ?? true; const isNote = props.isNote ?? true;
const shouldNyaize = props.nyaize ? props.nyaize === 'respect' ? props.author?.isCat : false : false; const shouldNyaize = props.nyaize ? props.nyaize === 'respect' ? props.author?.isCat : false : false;
const shouldUhoize = props.nyaize ? props.nyaize === 'respect' ? props.author?.isGorilla : false : false; const shouldUhoize = props.nyaize ? props.nyaize === 'respect' ? props.author?.isGorilla : false : false;
@ -354,6 +358,13 @@ export default function(props: MfmProps) {
}), }),
]); ]);
} }
case 'clickable': {
return h('span', { onClick(ev: MouseEvent): void {
ev.stopPropagation();
ev.preventDefault();
context.emit('clickEv', token.props.args.ev ?? '');
} }, genEl(token.children, scale));
}
} }
if (style === undefined) { if (style === undefined) {
return h('span', {}, ['$[', token.props.name, ' ', ...genEl(token.children, scale), ']']); return h('span', {}, ['$[', token.props.name, ' ', ...genEl(token.children, scale), ']']);

View file

@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, onUnmounted, provide, inject, Ref, ref, watch, shallowRef } from 'vue'; import { onMounted, onUnmounted, provide, inject, Ref, ref, watch, shallowRef } from 'vue';
import { CURRENT_STICKY_BOTTOM, CURRENT_STICKY_TOP } from '@/const'; import { CURRENT_STICKY_BOTTOM, CURRENT_STICKY_TOP } from '@/const.js';
const rootEl = shallowRef<HTMLElement>(); const rootEl = shallowRef<HTMLElement>();
const headerEl = shallowRef<HTMLElement>(); const headerEl = shallowRef<HTMLElement>();

View file

@ -8,7 +8,7 @@ import { expect } from '@storybook/jest';
import { userEvent, waitFor, within } from '@storybook/testing-library'; import { userEvent, waitFor, within } from '@storybook/testing-library';
import { StoryObj } from '@storybook/vue3'; import { StoryObj } from '@storybook/vue3';
import { rest } from 'msw'; import { rest } from 'msw';
import { commonHandlers } from '../../../.storybook/mocks'; import { commonHandlers } from '../../../.storybook/mocks.js';
import MkUrl from './MkUrl.vue'; import MkUrl from './MkUrl.vue';
export const Default = { export const Default = {
render(args) { render(args) {

View file

@ -6,7 +6,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable @typescript-eslint/explicit-function-return-type */
import { expect } from '@storybook/jest'; import { expect } from '@storybook/jest';
import { StoryObj } from '@storybook/vue3'; import { StoryObj } from '@storybook/vue3';
import { userDetailed } from '../../../.storybook/fakes'; import { userDetailed } from '../../../.storybook/fakes.js';
import MkUserName from './MkUserName.vue'; import MkUserName from './MkUserName.vue';
export const Default = { export const Default = {
render(args) { render(args) {

View file

@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { inject, onBeforeUnmount, provide, shallowRef, ref } from 'vue'; import { inject, onBeforeUnmount, provide, shallowRef, ref } from 'vue';
import { Resolved, Router } from '@/nirax'; import { Resolved, Router } from '@/nirax.js';
import { defaultStore } from '@/store.js'; import { defaultStore } from '@/store.js';
const props = defineProps<{ const props = defineProps<{

View file

@ -14,7 +14,7 @@ import XText from './page.text.vue';
import XSection from './page.section.vue'; import XSection from './page.section.vue';
import XImage from './page.image.vue'; import XImage from './page.image.vue';
import XNote from './page.note.vue'; import XNote from './page.note.vue';
import { Block } from './block.type'; import { Block } from './block.type.js';
function getComponent(type: string) { function getComponent(type: string) {
switch (type) { switch (type) {

View file

@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue'; import { ref } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import { ImageBlock } from './block.type'; import { ImageBlock } from './block.type.js';
import MediaImage from '@/components/MkMediaImage.vue'; import MediaImage from '@/components/MkMediaImage.vue';
const props = defineProps<{ const props = defineProps<{

View file

@ -11,9 +11,9 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, Ref, ref } from 'vue'; import { onMounted, ref } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import { NoteBlock } from './block.type'; import { NoteBlock } from './block.type.js';
import MkNote from '@/components/MkNote.vue'; import MkNote from '@/components/MkNote.vue';
import MkNoteDetailed from '@/components/MkNoteDetailed.vue'; import MkNoteDetailed from '@/components/MkNoteDetailed.vue';
import * as os from '@/os.js'; import * as os from '@/os.js';
@ -23,7 +23,7 @@ const props = defineProps<{
page: Misskey.entities.Page, page: Misskey.entities.Page,
}>(); }>();
const note: Ref<Misskey.entities.Note | null> = ref(null); const note = ref<Misskey.entities.Note | null>(null);
onMounted(() => { onMounted(() => {
os.api('notes/show', { noteId: props.block.note }) os.api('notes/show', { noteId: props.block.note })

View file

@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { defineAsyncComponent } from 'vue'; import { defineAsyncComponent } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import { SectionBlock } from './block.type'; import { SectionBlock } from './block.type.js';
const XBlock = defineAsyncComponent(() => import('./page.block.vue')); const XBlock = defineAsyncComponent(() => import('./page.block.vue'));

View file

@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import { defineAsyncComponent } from 'vue'; import { defineAsyncComponent } from 'vue';
import * as mfm from 'mfm-js'; import * as mfm from 'mfm-js';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import { TextBlock } from './block.type'; import { TextBlock } from './block.type.js';
import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm.js'; import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm.js';
const MkUrlPreview = defineAsyncComponent(() => import('@/components/MkUrlPreview.vue')); const MkUrlPreview = defineAsyncComponent(() => import('@/components/MkUrlPreview.vue'));

View file

@ -4,7 +4,7 @@
*/ */
import { Directive } from 'vue'; import { Directive } from 'vue';
import { makeHotkey } from '../scripts/hotkey'; import { makeHotkey } from '../scripts/hotkey.js';
export default { export default {
mounted(el, binding) { mounted(el, binding) {

View file

@ -5,17 +5,17 @@
import { App } from 'vue'; import { App } from 'vue';
import userPreview from './user-preview'; import userPreview from './user-preview.js';
import getSize from './get-size'; import getSize from './get-size.js';
import ripple from './ripple'; import ripple from './ripple.js';
import tooltip from './tooltip'; import tooltip from './tooltip.js';
import hotkey from './hotkey'; import hotkey from './hotkey.js';
import appear from './appear'; import appear from './appear.js';
import anim from './anim'; import anim from './anim.js';
import clickAnime from './click-anime'; import clickAnime from './click-anime.js';
import panel from './panel'; import panel from './panel.js';
import adaptiveBorder from './adaptive-border'; import adaptiveBorder from './adaptive-border.js';
import adaptiveBg from './adaptive-bg'; import adaptiveBg from './adaptive-bg.js';
export default function(app: App) { export default function(app: App) {
for (const [key, value] of Object.entries(directives)) { for (const [key, value] of Object.entries(directives)) {

View file

@ -313,8 +313,13 @@ const patrons = [
const thereIsTreasure = ref($i && !claimedAchievements.includes('foundTreasure')); const thereIsTreasure = ref($i && !claimedAchievements.includes('foundTreasure'));
let easterEggReady = false; let easterEggReady = false;
const easterEggEmojis = ref([]); const easterEggEmojis = ref<{
const easterEggEngine = ref(null); id: string,
top: number,
left: number,
emoji: string
}[]>([]);
const easterEggEngine = ref<{ stop: () => void } | null>(null);
const containerEl = shallowRef<HTMLElement>(); const containerEl = shallowRef<HTMLElement>();
function iconLoaded() { function iconLoaded() {

View file

@ -83,7 +83,7 @@ const pagination = {
state.value === 'notResponding' ? { notResponding: true } : state.value === 'notResponding' ? { notResponding: true } :
{}), {}),
})), })),
} as Paging; } satisfies Paging;
function getStatus(instance) { function getStatus(instance) {
if (instance.isSuspended) return 'Suspended'; if (instance.isSuspended) return 'Suspended';

View file

@ -101,6 +101,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import {computed, ref, watch } from 'vue'; import {computed, ref, watch } from 'vue';
import * as Misskey from 'misskey-js';
import XEmojis from './about.emojis.vue'; import XEmojis from './about.emojis.vue';
import XFederation from './about.federation.vue'; import XFederation from './about.federation.vue';
import { version, host } from '@/config.js'; import { version, host } from '@/config.js';
@ -125,7 +126,7 @@ const props = withDefaults(defineProps<{
initialTab: 'overview', initialTab: 'overview',
}); });
const stats = ref(null); const stats = ref<Misskey.entities.StatsResponse | null>(null);
const tab = ref(props.initialTab); const tab = ref(props.initialTab);
watch(tab, () => { watch(tab, () => {

View file

@ -68,6 +68,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import * as Misskey from 'misskey-js';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import MkSwitch from '@/components/MkSwitch.vue'; import MkSwitch from '@/components/MkSwitch.vue';
import MkObjectView from '@/components/MkObjectView.vue'; import MkObjectView from '@/components/MkObjectView.vue';
@ -83,8 +84,8 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
import { iAmAdmin, iAmModerator } from '@/account.js'; import { iAmAdmin, iAmModerator } from '@/account.js';
const tab = ref('overview'); const tab = ref('overview');
const file = ref<any>(null); const file = ref<Misskey.entities.DriveFile | null>(null);
const info = ref<any>(null); const info = ref<Misskey.entities.AdminDriveShowFileResponse | null>(null);
const isSensitive = ref<boolean>(false); const isSensitive = ref<boolean>(false);
const props = defineProps<{ const props = defineProps<{

View file

@ -225,7 +225,7 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { iAmAdmin, $i } from '@/account.js'; import { iAmAdmin, $i } from '@/account.js';
import MkRolePreview from '@/components/MkRolePreview.vue'; import MkRolePreview from '@/components/MkRolePreview.vue';
import MkPagination from '@/components/MkPagination.vue'; import MkPagination, { Paging } from '@/components/MkPagination.vue';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
userId: string; userId: string;
@ -238,9 +238,9 @@ const tab = ref(props.initialTab);
const chartSrc = ref('per-user-notes'); const chartSrc = ref('per-user-notes');
const user = ref<null | Misskey.entities.UserDetailed>(); const user = ref<null | Misskey.entities.UserDetailed>();
const init = ref<ReturnType<typeof createFetcher>>(); const init = ref<ReturnType<typeof createFetcher>>();
const info = ref(); const info = ref<any>();
const ips = ref(null); const ips = ref<Misskey.entities.AdminGetUserIpsResponse | null>(null);
const ap = ref(null); const ap = ref<any>(null);
const moderator = ref(false); const moderator = ref(false);
const silenced = ref(false); const silenced = ref(false);
const suspended = ref(false); const suspended = ref(false);
@ -258,7 +258,7 @@ const announcementsPagination = {
params: computed(() => ({ params: computed(() => ({
userId: props.userId, userId: props.userId,
})), })),
}; } satisfies Paging;
const expandedRoles = ref([]); const expandedRoles = ref([]);
function createFetcher() { function createFetcher() {

View file

@ -38,7 +38,7 @@ import tinycolor from 'tinycolor2';
import { popupMenu } from '@/os.js'; import { popupMenu } from '@/os.js';
import { scrollToTop } from '@/scripts/scroll.js'; import { scrollToTop } from '@/scripts/scroll.js';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import { globalEvents } from '@/events'; import { globalEvents } from '@/events.js';
import { injectPageMetadata } from '@/scripts/page-metadata.js'; import { injectPageMetadata } from '@/scripts/page-metadata.js';
type Tab = { type Tab = {
@ -70,7 +70,7 @@ const metadata = injectPageMetadata();
const el = shallowRef<HTMLElement>(null); const el = shallowRef<HTMLElement>(null);
const tabRefs = {}; const tabRefs = {};
const tabHighlightEl = shallowRef<HTMLElement | null>(null); const tabHighlightEl = shallowRef<HTMLElement | null>(null);
const bg = ref(null); const bg = ref<string | null>(null);
const height = ref(0); const height = ref(0);
const hasTabs = computed(() => { const hasTabs = computed(() => {
return props.tabs && props.tabs.length > 0; return props.tabs && props.tabs.length > 0;

View file

@ -56,7 +56,7 @@ import { computed, shallowRef, ref } from 'vue';
import XHeader from './_header_.vue'; import XHeader from './_header_.vue';
import MkSelect from '@/components/MkSelect.vue'; import MkSelect from '@/components/MkSelect.vue';
import MkPagination from '@/components/MkPagination.vue'; import MkPagination, { Paging } from '@/components/MkPagination.vue';
import XAbuseReport from '@/components/MkAbuseReport.vue'; import XAbuseReport from '@/components/MkAbuseReport.vue';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js'; import { definePageMetadata } from '@/scripts/page-metadata.js';
@ -77,7 +77,7 @@ const pagination = {
reporterOrigin: reporterOrigin.value, reporterOrigin: reporterOrigin.value,
targetUserOrigin: targetUserOrigin.value, targetUserOrigin: targetUserOrigin.value,
})), })),
}; } satisfies Paging;
function resolved(reportId) { function resolved(reportId) {
reports.value.removeItem(reportId); reports.value.removeItem(reportId);

View file

@ -86,6 +86,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 XHeader from './_header_.vue'; import XHeader from './_header_.vue';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue'; import MkInput from '@/components/MkInput.vue';
@ -98,7 +99,7 @@ import * as os from '@/os.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js'; import { definePageMetadata } from '@/scripts/page-metadata.js';
const ads = ref<any[]>([]); const ads = ref<Misskey.entities.Ad[]>([]);
// ISOTZUTCTZ // ISOTZUTCTZ
const localTime = new Date(); const localTime = new Date();

View file

@ -65,6 +65,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { defineAsyncComponent, ref } from 'vue'; import { defineAsyncComponent, ref } from 'vue';
import type { CaptchaProvider } from '@/components/MkCaptcha.vue';
import MkRadios from '@/components/MkRadios.vue'; import MkRadios from '@/components/MkRadios.vue';
import MkInput from '@/components/MkInput.vue'; import MkInput from '@/components/MkInput.vue';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
@ -76,7 +77,7 @@ import { i18n } from '@/i18n.js';
const MkCaptcha = defineAsyncComponent(() => import('@/components/MkCaptcha.vue')); const MkCaptcha = defineAsyncComponent(() => import('@/components/MkCaptcha.vue'));
const provider = ref(null); const provider = ref<CaptchaProvider | null>(null);
const hcaptchaSiteKey = ref<string | null>(null); const hcaptchaSiteKey = ref<string | null>(null);
const hcaptchaSecretKey = ref<string | null>(null); const hcaptchaSecretKey = ref<string | null>(null);
const recaptchaSiteKey = ref<string | null>(null); const recaptchaSiteKey = ref<string | null>(null);

View file

@ -113,9 +113,9 @@ const app192IconUrl = ref<string | null>(null);
const app512IconUrl = ref<string | null>(null); const app512IconUrl = ref<string | null>(null);
const bannerUrl = ref<string | null>(null); const bannerUrl = ref<string | null>(null);
const backgroundImageUrl = ref<string | null>(null); const backgroundImageUrl = ref<string | null>(null);
const themeColor = ref<any>(null); const themeColor = ref<string | null>(null);
const defaultLightTheme = ref<any>(null); const defaultLightTheme = ref<string | null>(null);
const defaultDarkTheme = ref<any>(null); const defaultDarkTheme = ref<string | null>(null);
const serverErrorImageUrl = ref<string | null>(null); const serverErrorImageUrl = ref<string | null>(null);
const infoImageUrl = ref<string | null>(null); const infoImageUrl = ref<string | null>(null);
const notFoundImageUrl = ref<string | null>(null); const notFoundImageUrl = ref<string | null>(null);

View file

@ -79,7 +79,7 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
const enableEmail = ref<boolean>(false); const enableEmail = ref<boolean>(false);
const email = ref<any>(null); const email = ref<string | null>(null);
const smtpSecure = ref<boolean>(false); const smtpSecure = ref<boolean>(false);
const smtpHost = ref<string>(''); const smtpHost = ref<string>('');
const smtpPort = ref<number>(0); const smtpPort = ref<number>(0);

View file

@ -62,7 +62,7 @@ import { computed, ref } from 'vue';
import XHeader from './_header_.vue'; import XHeader from './_header_.vue';
import MkInput from '@/components/MkInput.vue'; import MkInput from '@/components/MkInput.vue';
import MkSelect from '@/components/MkSelect.vue'; import MkSelect from '@/components/MkSelect.vue';
import MkPagination from '@/components/MkPagination.vue'; import MkPagination, { Paging } from '@/components/MkPagination.vue';
import MkInstanceCardMini from '@/components/MkInstanceCardMini.vue'; import MkInstanceCardMini from '@/components/MkInstanceCardMini.vue';
import FormSplit from '@/components/form/split.vue'; import FormSplit from '@/components/form/split.vue';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
@ -88,7 +88,7 @@ const pagination = {
state.value === 'notResponding' ? { notResponding: true } : state.value === 'notResponding' ? { notResponding: true } :
{}), {}),
})), })),
}; } satisfies Paging;
function getStatus(instance) { function getStatus(instance) {
if (instance.isSuspended) return 'Suspended'; if (instance.isSuspended) return 'Suspended';

View file

@ -46,7 +46,7 @@ import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js'; import { definePageMetadata } from '@/scripts/page-metadata.js';
const origin = ref('local'); const origin = ref('local');
const type = ref(null); const type = ref<string | null>(null);
const searchHost = ref(''); const searchHost = ref('');
const userId = ref(''); const userId = ref('');
const viewMode = ref('grid'); const viewMode = ref('grid');

View file

@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {computed, onActivated, onMounted, onUnmounted, provide, ref, watch } from 'vue'; import { watch,ComputedRef, Ref, onActivated, onMounted, onUnmounted, provide, watch, ref, computed } from 'vue';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import MkSuperMenu from '@/components/MkSuperMenu.vue'; import MkSuperMenu from '@/components/MkSuperMenu.vue';
import MkInfo from '@/components/MkInfo.vue'; import MkInfo from '@/components/MkInfo.vue';
@ -36,7 +36,7 @@ import { instance } from '@/instance.js';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { lookupUser, lookupUserByEmail } from '@/scripts/lookup-user.js'; import { lookupUser, lookupUserByEmail } from '@/scripts/lookup-user.js';
import { useRouter } from '@/router.js'; import { useRouter } from '@/router.js';
import { definePageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata.js'; import { PageMetadata, definePageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata.js';
import {bannerDark, bannerLight, defaultStore, iconDark, iconLight} from "@/store.js"; import {bannerDark, bannerLight, defaultStore, iconDark, iconLight} from "@/store.js";
const isEmpty = (x: string | null) => x == null || x === ''; const isEmpty = (x: string | null) => x == null || x === '';
@ -52,10 +52,10 @@ const indexInfo = {
provide('shouldOmitHeaderTitle', false); provide('shouldOmitHeaderTitle', false);
const INFO = ref(indexInfo); const INFO = ref(indexInfo);
const childInfo = ref(null); const childInfo: Ref<ComputedRef<PageMetadata> | null> = ref(null);
const narrow = ref(false); const narrow = ref(false);
const view = ref(null); const view = ref(null);
const el = ref(null); const el = ref<HTMLDivElement | null>(null);
const pageProps = ref({}); const pageProps = ref({});
let noMaintainerInformation = isEmpty(instance.maintainerName) || isEmpty(instance.maintainerEmail); let noMaintainerInformation = isEmpty(instance.maintainerName) || isEmpty(instance.maintainerEmail);
let noBotProtection = !instance.disableRegistration && !instance.enableHcaptcha && !instance.enableRecaptcha && !instance.enableTurnstile; let noBotProtection = !instance.disableRegistration && !instance.enableHcaptcha && !instance.enableRecaptcha && !instance.enableTurnstile;

View file

@ -73,7 +73,7 @@ const pagingComponent = shallowRef<InstanceType<typeof MkPagination>>();
const type = ref('all'); const type = ref('all');
const sort = ref('+createdAt'); const sort = ref('+createdAt');
const pagination: Paging = { const pagination = {
endpoint: 'admin/invite/list' as const, endpoint: 'admin/invite/list' as const,
limit: 10, limit: 10,
params: computed(() => ({ params: computed(() => ({
@ -81,7 +81,7 @@ const pagination: Paging = {
sort: sort.value, sort: sort.value,
})), })),
offsetMode: true, offsetMode: true,
}; } satisfies Paging;
const expiresAt = ref(''); const expiresAt = ref('');
const noExpirationDate = ref(true); const noExpirationDate = ref(true);
@ -97,10 +97,10 @@ async function createWithOptions() {
os.alert({ os.alert({
type: 'success', type: 'success',
title: i18n.ts.inviteCodeCreated, title: i18n.ts.inviteCodeCreated,
text: tickets?.map(x => x.code).join('\n'), text: tickets.map(x => x.code).join('\n'),
}); });
tickets?.forEach(ticket => pagingComponent.value?.prepend(ticket)); tickets.forEach(ticket => pagingComponent.value?.prepend(ticket));
} }
function deleted(id: string) { function deleted(id: string) {

View file

@ -145,7 +145,7 @@ const props = defineProps<{
} }
.logYellow { .logYellow {
color: var(--warning); color: var(--warn);
} }
.logRed { .logRed {

View file

@ -36,13 +36,13 @@ import XHeader from './_header_.vue';
import XModLog from './modlog.ModLog.vue'; import XModLog from './modlog.ModLog.vue';
import MkSelect from '@/components/MkSelect.vue'; import MkSelect from '@/components/MkSelect.vue';
import MkInput from '@/components/MkInput.vue'; import MkInput from '@/components/MkInput.vue';
import MkPagination from '@/components/MkPagination.vue'; import MkPagination, { Paging } from '@/components/MkPagination.vue';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js'; import { definePageMetadata } from '@/scripts/page-metadata.js';
const logs = shallowRef<InstanceType<typeof MkPagination>>(); const logs = shallowRef<InstanceType<typeof MkPagination>>();
const type = ref(null); const type = ref<string | null>(null);
const moderatorId = ref(''); const moderatorId = ref('');
const pagination = { const pagination = {
@ -52,7 +52,7 @@ const pagination = {
type: type.value, type: type.value,
userId: moderatorId.value === '' ? null : moderatorId.value, userId: moderatorId.value === '' ? null : moderatorId.value,
})), })),
}; } satisfies Paging;
console.log(Misskey); console.log(Misskey);

View file

@ -47,15 +47,15 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import XPie from './overview.pie.vue'; import XPie, { type InstanceForPie } from './overview.pie.vue';
import * as os from '@/os.js'; import * as os from '@/os.js';
import number from '@/filters/number.js'; import number from '@/filters/number.js';
import MkNumberDiff from '@/components/MkNumberDiff.vue'; import MkNumberDiff from '@/components/MkNumberDiff.vue';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { useChartTooltip } from '@/scripts/use-chart-tooltip.js'; import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
const topSubInstancesForPie = ref<any>(null); const topSubInstancesForPie = ref<InstanceForPie[] | null>(null);
const topPubInstancesForPie = ref<any>(null); const topPubInstancesForPie = ref<InstanceForPie[] | null>(null);
const federationPubActive = ref<number | null>(null); const federationPubActive = ref<number | null>(null);
const federationPubActiveDiff = ref<number | null>(null); const federationPubActiveDiff = ref<number | null>(null);
const federationSubActive = ref<number | null>(null); const federationSubActive = ref<number | null>(null);
@ -72,22 +72,28 @@ onMounted(async () => {
federationSubActiveDiff.value = chart.subActive[0] - chart.subActive[1]; federationSubActiveDiff.value = chart.subActive[0] - chart.subActive[1];
os.apiGet('federation/stats', { limit: 10 }).then(res => { os.apiGet('federation/stats', { limit: 10 }).then(res => {
topSubInstancesForPie.value = res.topSubInstances.map(x => ({ topSubInstancesForPie.value = [
name: x.host, ...res.topSubInstances.map(x => ({
color: x.themeColor, name: x.host,
value: x.followersCount, color: x.themeColor,
onClick: () => { value: x.followersCount,
os.pageWindow(`/instance-info/${x.host}`); onClick: () => {
}, os.pageWindow(`/instance-info/${x.host}`);
})).concat([{ name: '(other)', color: '#80808080', value: res.otherFollowersCount }]); },
topPubInstancesForPie.value = res.topPubInstances.map(x => ({ })),
name: x.host, { name: '(other)', color: '#80808080', value: res.otherFollowersCount },
color: x.themeColor, ];
value: x.followingCount, topPubInstancesForPie.value = [
onClick: () => { ...res.topPubInstances.map(x => ({
os.pageWindow(`/instance-info/${x.host}`); name: x.host,
}, color: x.themeColor,
})).concat([{ name: '(other)', color: '#80808080', value: res.otherFollowingCount }]); value: x.followingCount,
onClick: () => {
os.pageWindow(`/instance-info/${x.host}`);
},
})),
{ name: '(other)', color: '#80808080', value: res.otherFollowingCount },
];
}); });
fetching.value = false; fetching.value = false;

View file

@ -18,12 +18,13 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue'; import { ref } from 'vue';
import * as Misskey from 'misskey-js';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { useInterval } from '@/scripts/use-interval.js'; import { useInterval } from '@/scripts/use-interval.js';
import MkInstanceCardMini from '@/components/MkInstanceCardMini.vue'; import MkInstanceCardMini from '@/components/MkInstanceCardMini.vue';
import { defaultStore } from '@/store.js'; import { defaultStore } from '@/store.js';
const instances = ref([]); const instances = ref<Misskey.entities.FederationInstance[]>([]);
const fetching = ref(true); const fetching = ref(true);
const fetch = async () => { const fetch = async () => {

View file

@ -18,10 +18,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import * as Misskey from 'misskey-js';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { defaultStore } from '@/store.js'; import { defaultStore } from '@/store.js';
const moderators = ref<any>(null); const moderators = ref<Misskey.entities.UserDetailed[] | null>(null);
const fetching = ref(true); const fetching = ref(true);
onMounted(async () => { onMounted(async () => {

View file

@ -13,10 +13,17 @@ import { Chart } from 'chart.js';
import { useChartTooltip } from '@/scripts/use-chart-tooltip.js'; import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
import { initChart } from '@/scripts/init-chart.js'; import { initChart } from '@/scripts/init-chart.js';
export type InstanceForPie = {
name: string,
color: string | null,
value: number,
onClick?: () => void
};
initChart(); initChart();
const props = defineProps<{ const props = defineProps<{
data: { name: string; value: number; color: string; onClick?: () => void }[]; data: InstanceForPie[];
}>(); }>();
const chartEl = shallowRef<HTMLCanvasElement>(null); const chartEl = shallowRef<HTMLCanvasElement>(null);

View file

@ -62,6 +62,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import * as Misskey from 'misskey-js';
import * as os from '@/os.js'; import * as os from '@/os.js';
import MkNumberDiff from '@/components/MkNumberDiff.vue'; import MkNumberDiff from '@/components/MkNumberDiff.vue';
import MkNumber from '@/components/MkNumber.vue'; import MkNumber from '@/components/MkNumber.vue';
@ -69,7 +70,7 @@ import { i18n } from '@/i18n.js';
import { customEmojis } from '@/custom-emojis.js'; import { customEmojis } from '@/custom-emojis.js';
import { defaultStore } from '@/store.js'; import { defaultStore } from '@/store.js';
const stats = ref<any>(null); const stats = ref<Misskey.entities.StatsResponse | null>(null);
const usersComparedToThePrevDay = ref<number>(); const usersComparedToThePrevDay = ref<number>();
const notesComparedToThePrevDay = ref<number>(); const notesComparedToThePrevDay = ref<number>();
const onlineUsersCount = ref(0); const onlineUsersCount = ref(0);

View file

@ -18,12 +18,13 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue'; import { ref } from 'vue';
import * as Misskey from 'misskey-js';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { useInterval } from '@/scripts/use-interval.js'; import { useInterval } from '@/scripts/use-interval.js';
import MkUserCardMini from '@/components/MkUserCardMini.vue'; import MkUserCardMini from '@/components/MkUserCardMini.vue';
import { defaultStore } from '@/store.js'; import { defaultStore } from '@/store.js';
const newUsers = ref(null); const newUsers = ref<Misskey.entities.UserDetailed[] | null>(null);
const fetching = ref(true); const fetching = ref(true);
const fetch = async () => { const fetch = async () => {

View file

@ -66,6 +66,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { markRaw, onMounted, onBeforeUnmount, nextTick, shallowRef, ref, computed } from 'vue'; import { markRaw, onMounted, onBeforeUnmount, nextTick, shallowRef, ref, computed } from 'vue';
import * as Misskey from 'misskey-js';
import XFederation from './overview.federation.vue'; import XFederation from './overview.federation.vue';
import XInstances from './overview.instances.vue'; import XInstances from './overview.instances.vue';
import XQueue from './overview.queue.vue'; import XQueue from './overview.queue.vue';
@ -76,6 +77,7 @@ import XStats from './overview.stats.vue';
import XRetention from './overview.retention.vue'; import XRetention from './overview.retention.vue';
import XModerators from './overview.moderators.vue'; import XModerators from './overview.moderators.vue';
import XHeatmap from './overview.heatmap.vue'; import XHeatmap from './overview.heatmap.vue';
import type { InstanceForPie } from './overview.pie.vue';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { useStream } from '@/stream.js'; import { useStream } from '@/stream.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
@ -83,15 +85,15 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
import MkFoldableSection from '@/components/MkFoldableSection.vue'; import MkFoldableSection from '@/components/MkFoldableSection.vue';
const rootEl = shallowRef<HTMLElement>(); const rootEl = shallowRef<HTMLElement>();
const serverInfo = ref<any>(null); const serverInfo = ref<Misskey.entities.ServerInfoResponse | null>(null);
const topSubInstancesForPie = ref<any>(null); const topSubInstancesForPie = ref<InstanceForPie[] | null>(null);
const topPubInstancesForPie = ref<any>(null); const topPubInstancesForPie = ref<InstanceForPie[] | null>(null);
const federationPubActive = ref<number | null>(null); const federationPubActive = ref<number | null>(null);
const federationPubActiveDiff = ref<number | null>(null); const federationPubActiveDiff = ref<number | null>(null);
const federationSubActive = ref<number | null>(null); const federationSubActive = ref<number | null>(null);
const federationSubActiveDiff = ref<number | null>(null); const federationSubActiveDiff = ref<number | null>(null);
const newUsers = ref(null); const newUsers = ref<Misskey.entities.UserDetailed[] | null>(null);
const activeInstances = shallowRef(null); const activeInstances = shallowRef<Misskey.entities.FederationInstance | null>(null);
const queueStatsConnection = markRaw(useStream().useChannel('queueStats')); const queueStatsConnection = markRaw(useStream().useChannel('queueStats'));
const now = new Date(); const now = new Date();
const filesPagination = { const filesPagination = {
@ -123,22 +125,28 @@ onMounted(async () => {
}); });
os.apiGet('federation/stats', { limit: 10 }).then(res => { os.apiGet('federation/stats', { limit: 10 }).then(res => {
topSubInstancesForPie.value = res.topSubInstances.map(x => ({ topSubInstancesForPie.value = [
name: x.host, ...res.topSubInstances.map(x => ({
color: x.themeColor, name: x.host,
value: x.followersCount, color: x.themeColor,
onClick: () => { value: x.followersCount,
os.pageWindow(`/instance-info/${x.host}`); onClick: () => {
}, os.pageWindow(`/instance-info/${x.host}`);
})).concat([{ name: '(other)', color: '#80808080', value: res.otherFollowersCount }]); },
topPubInstancesForPie.value = res.topPubInstances.map(x => ({ })),
name: x.host, { name: '(other)', color: '#80808080', value: res.otherFollowersCount },
color: x.themeColor, ];
value: x.followingCount, topPubInstancesForPie.value = [
onClick: () => { ...res.topPubInstances.map(x => ({
os.pageWindow(`/instance-info/${x.host}`); name: x.host,
}, color: x.themeColor,
})).concat([{ name: '(other)', color: '#80808080', value: res.otherFollowingCount }]); value: x.followingCount,
onClick: () => {
os.pageWindow(`/instance-info/${x.host}`);
},
})),
{ name: '(other)', color: '#80808080', value: res.otherFollowingCount },
];
}); });
os.api('admin/server-info').then(serverInfoResponse => { os.api('admin/server-info').then(serverInfoResponse => {

View file

@ -22,6 +22,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 MkKeyValue from '@/components/MkKeyValue.vue'; import MkKeyValue from '@/components/MkKeyValue.vue';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import MkInfo from '@/components/MkInfo.vue'; import MkInfo from '@/components/MkInfo.vue';
@ -31,8 +32,8 @@ import { fetchInstance } from '@/instance.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js'; import { definePageMetadata } from '@/scripts/page-metadata.js';
const proxyAccount = ref<any>(null); const proxyAccount = ref<Misskey.entities.UserDetailed | null>(null);
const proxyAccountId = ref<any>(null); const proxyAccountId = ref<string | null>(null);
async function init() { async function init() {
const meta = await os.api('admin/meta'); const meta = await os.api('admin/meta');

View file

@ -62,7 +62,7 @@ const activeSincePrevTick = ref(0);
const active = ref(0); const active = ref(0);
const delayed = ref(0); const delayed = ref(0);
const waiting = ref(0); const waiting = ref(0);
const jobs = ref([]); const jobs = ref<(string | number)[][]>([]);
const chartProcess = shallowRef<InstanceType<typeof XChart>>(); const chartProcess = shallowRef<InstanceType<typeof XChart>>();
const chartActive = shallowRef<InstanceType<typeof XChart>>(); const chartActive = shallowRef<InstanceType<typeof XChart>>();
const chartDelayed = shallowRef<InstanceType<typeof XChart>>(); const chartDelayed = shallowRef<InstanceType<typeof XChart>>();
@ -104,9 +104,11 @@ const onStatsLog = (statsLog) => {
}; };
onMounted(() => { onMounted(() => {
os.api(props.domain === 'inbox' ? 'admin/queue/inbox-delayed' : props.domain === 'deliver' ? 'admin/queue/deliver-delayed' : null, {}).then(result => { if (props.domain === 'inbox' || props.domain === 'deliver') {
jobs.value = result; os.api(`admin/queue/${props.domain}-delayed`).then(result => {
}); jobs.value = result;
});
}
connection.on('stats', onStats); connection.on('stats', onStats);
connection.on('statsLog', onStatsLog); connection.on('statsLog', onStatsLog);

View file

@ -25,13 +25,14 @@ 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 XHeader from './_header_.vue'; import XHeader from './_header_.vue';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js'; import { definePageMetadata } from '@/scripts/page-metadata.js';
const relays = ref<any[]>([]); const relays = ref<Misskey.entities.AdminRelaysListResponse>([]);
async function addRelay() { async function addRelay() {
const { canceled, result: inbox } = await os.inputText({ const { canceled, result: inbox } = await os.inputText({
@ -66,7 +67,7 @@ function remove(inbox: string) {
} }
function refresh() { function refresh() {
os.api('admin/relays/list').then((relayList: any) => { os.api('admin/relays/list').then(relayList => {
relays.value = relayList; relays.value = relayList;
}); });
} }

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