Merge branch 'develop' into fix-fe-settings-migration

This commit is contained in:
かっこかり 2025-08-24 18:13:33 +09:00 committed by GitHub
commit 3890bf451e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
55 changed files with 1844 additions and 1120 deletions

View File

@ -16,7 +16,7 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.3.0
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@v4.1.0 uses: pnpm/action-setup@v4.1.0

View File

@ -12,7 +12,7 @@ jobs:
steps: steps:
- name: Checkout head - name: Checkout head
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.3.0
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v4.4.0 uses: actions/setup-node@v4.4.0
with: with:

View File

@ -18,7 +18,7 @@ jobs:
if: ${{ github.event.pull_request.mergeable == null || github.event.pull_request.mergeable == true }} if: ${{ github.event.pull_request.mergeable == null || github.event.pull_request.mergeable == true }}
steps: steps:
- name: checkout - name: checkout
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.3.0
with: with:
submodules: true submodules: true
persist-credentials: false persist-credentials: false
@ -66,7 +66,7 @@ jobs:
if: ${{ github.event.pull_request.mergeable == null || github.event.pull_request.mergeable == true }} if: ${{ github.event.pull_request.mergeable == null || github.event.pull_request.mergeable == true }}
steps: steps:
- name: checkout - name: checkout
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.3.0
with: with:
submodules: true submodules: true
persist-credentials: false persist-credentials: false

View File

@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.3.0
- name: Check version - name: Check version
run: | run: |
if [ "$(jq -r '.version' package.json)" != "$(jq -r '.version' packages/misskey-js/package.json)" ]; then if [ "$(jq -r '.version' package.json)" != "$(jq -r '.version' packages/misskey-js/package.json)" ]; then

View File

@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.3.0
- name: Check - name: Check
run: | run: |
counter=0 counter=0

View File

@ -10,7 +10,7 @@ jobs:
check_copyright_year: check_copyright_year:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4.2.2 - uses: actions/checkout@v4.3.0
- run: | - run: |
if [ "$(grep Copyright COPYING | sed -e 's/.*2014-\([0-9]*\) .*/\1/g')" -ne "$(date +%Y)" ]; then if [ "$(grep Copyright COPYING | sed -e 's/.*2014-\([0-9]*\) .*/\1/g')" -ne "$(date +%Y)" ]; then
echo "Please change copyright year!" echo "Please change copyright year!"

View File

@ -28,7 +28,7 @@ jobs:
wait_time: ${{ steps.get-wait-time.outputs.wait_time }} wait_time: ${{ steps.get-wait-time.outputs.wait_time }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.3.0
- name: Check allowed users - name: Check allowed users
id: check-allowed-users id: check-allowed-users

View File

@ -27,7 +27,7 @@ jobs:
platform=${{ matrix.platform }} platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Check out the repo - name: Check out the repo
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.3.0
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub - name: Log in to Docker Hub

View File

@ -32,7 +32,7 @@ jobs:
platform=${{ matrix.platform }} platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Check out the repo - name: Check out the repo
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.3.0
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: Docker meta - name: Docker meta

View File

@ -15,7 +15,7 @@ jobs:
DOCKER_CONTENT_TRUST: 1 DOCKER_CONTENT_TRUST: 1
DOCKLE_VERSION: 0.4.14 DOCKLE_VERSION: 0.4.14
steps: steps:
- uses: actions/checkout@v4.2.2 - uses: actions/checkout@v4.3.0
- name: Download and install dockle v${{ env.DOCKLE_VERSION }} - name: Download and install dockle v${{ env.DOCKLE_VERSION }}
run: | run: |
curl -L -o dockle.deb "https://github.com/goodwithtech/dockle/releases/download/v${DOCKLE_VERSION}/dockle_${DOCKLE_VERSION}_Linux-64bit.deb" curl -L -o dockle.deb "https://github.com/goodwithtech/dockle/releases/download/v${DOCKLE_VERSION}/dockle_${DOCKLE_VERSION}_Linux-64bit.deb"

View File

@ -25,7 +25,7 @@ jobs:
ref: refs/pull/${{ github.event.number }}/merge ref: refs/pull/${{ github.event.number }}/merge
steps: steps:
- uses: actions/checkout@v4.2.2 - uses: actions/checkout@v4.3.0
with: with:
ref: ${{ matrix.ref }} ref: ${{ matrix.ref }}
submodules: true submodules: true

View File

@ -36,7 +36,7 @@ jobs:
pnpm_install: pnpm_install:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4.2.2 - uses: actions/checkout@v4.3.0
with: with:
fetch-depth: 0 fetch-depth: 0
submodules: true submodules: true
@ -69,7 +69,7 @@ jobs:
eslint-cache-version: v1 eslint-cache-version: v1
eslint-cache-path: ${{ github.workspace }}/node_modules/.cache/eslint-${{ matrix.workspace }} eslint-cache-path: ${{ github.workspace }}/node_modules/.cache/eslint-${{ matrix.workspace }}
steps: steps:
- uses: actions/checkout@v4.2.2 - uses: actions/checkout@v4.3.0
with: with:
fetch-depth: 0 fetch-depth: 0
submodules: true submodules: true
@ -81,7 +81,7 @@ jobs:
cache: 'pnpm' cache: 'pnpm'
- run: pnpm i --frozen-lockfile - run: pnpm i --frozen-lockfile
- name: Restore eslint cache - name: Restore eslint cache
uses: actions/cache@v4.2.3 uses: actions/cache@v4.2.4
with: with:
path: ${{ env.eslint-cache-path }} path: ${{ env.eslint-cache-path }}
key: eslint-${{ env.eslint-cache-version }}-${{ matrix.workspace }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ github.ref_name }}-${{ github.sha }} key: eslint-${{ env.eslint-cache-version }}-${{ matrix.workspace }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ github.ref_name }}-${{ github.sha }}
@ -99,7 +99,7 @@ jobs:
- sw - sw
- misskey-js - misskey-js
steps: steps:
- uses: actions/checkout@v4.2.2 - uses: actions/checkout@v4.3.0
with: with:
fetch-depth: 0 fetch-depth: 0
submodules: true submodules: true

View File

@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
continue-on-error: true continue-on-error: true
steps: steps:
- uses: actions/checkout@v4.2.2 - uses: actions/checkout@v4.3.0
with: with:
fetch-depth: 0 fetch-depth: 0
submodules: true submodules: true

View File

@ -16,7 +16,7 @@ jobs:
id-token: write id-token: write
steps: steps:
- uses: actions/checkout@v4.2.2 - uses: actions/checkout@v4.3.0
with: with:
submodules: true submodules: true
- name: Setup pnpm - name: Setup pnpm

View File

@ -22,12 +22,12 @@ jobs:
NODE_OPTIONS: "--max_old_space_size=7168" NODE_OPTIONS: "--max_old_space_size=7168"
steps: steps:
- uses: actions/checkout@v4.2.2 - uses: actions/checkout@v4.3.0
if: github.event_name != 'pull_request_target' if: github.event_name != 'pull_request_target'
with: with:
fetch-depth: 0 fetch-depth: 0
submodules: true submodules: true
- uses: actions/checkout@v4.2.2 - uses: actions/checkout@v4.3.0
if: github.event_name == 'pull_request_target' if: github.event_name == 'pull_request_target'
with: with:
fetch-depth: 0 fetch-depth: 0

View File

@ -50,7 +50,7 @@ jobs:
- 56312:6379 - 56312:6379
steps: steps:
- uses: actions/checkout@v4.2.2 - uses: actions/checkout@v4.3.0
with: with:
submodules: true submodules: true
- name: Setup pnpm - name: Setup pnpm
@ -129,7 +129,7 @@ jobs:
- 56312:6379 - 56312:6379
steps: steps:
- uses: actions/checkout@v4.2.2 - uses: actions/checkout@v4.3.0
with: with:
submodules: true submodules: true
- name: Setup pnpm - name: Setup pnpm
@ -173,7 +173,7 @@ jobs:
POSTGRES_HOST_AUTH_METHOD: trust POSTGRES_HOST_AUTH_METHOD: trust
steps: steps:
- uses: actions/checkout@v4.2.2 - uses: actions/checkout@v4.3.0
with: with:
submodules: true submodules: true
- name: Setup pnpm - name: Setup pnpm

View File

@ -28,7 +28,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4.2.2 - uses: actions/checkout@v4.3.0
with: with:
submodules: true submodules: true
- name: Setup pnpm - name: Setup pnpm
@ -76,7 +76,7 @@ jobs:
- 56312:6379 - 56312:6379
steps: steps:
- uses: actions/checkout@v4.2.2 - uses: actions/checkout@v4.3.0
with: with:
submodules: true submodules: true
# https://github.com/cypress-io/cypress-docker-images/issues/150 # https://github.com/cypress-io/cypress-docker-images/issues/150

View File

@ -22,7 +22,7 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.3.0
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@v4.1.0 uses: pnpm/action-setup@v4.1.0

View File

@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4.2.2 - uses: actions/checkout@v4.3.0
with: with:
submodules: true submodules: true
- name: Setup pnpm - name: Setup pnpm

View File

@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4.2.2 - uses: actions/checkout@v4.3.0
with: with:
submodules: true submodules: true
- name: Setup pnpm - name: Setup pnpm

View File

@ -37,8 +37,8 @@
- コントロールパネル→ブランディング→エントランスページのスタイル - コントロールパネル→ブランディング→エントランスページのスタイル
- Feat: ページのタブバーを下部に表示できるように - Feat: ページのタブバーを下部に表示できるように
- Feat: (実験的)iOSでの触覚フィードバックを有効にできるように - Feat: (実験的)iOSでの触覚フィードバックを有効にできるように
- Feat: コントロールパネルを検索できるように
- Enhance: 「自動でもっと見る」オプションが有効になり、安定性が向上しました - Enhance: 「自動でもっと見る」オプションが有効になり、安定性が向上しました
- Enhance: コントロールパネルを検索できるように
- Enhance: トルコ語 (tr-TR) に対応 - Enhance: トルコ語 (tr-TR) に対応
- Enhance: 不必要な翻訳データを読み込まなくなり、パフォーマンスが向上しました - Enhance: 不必要な翻訳データを読み込まなくなり、パフォーマンスが向上しました
- Enhance: 画像エフェクトのパラメータ名の多言語対応 - Enhance: 画像エフェクトのパラメータ名の多言語対応
@ -53,7 +53,8 @@
- Fix: カラムの名前が正しくリスト/チャンネルの名前にならない問題を修正 - Fix: カラムの名前が正しくリスト/チャンネルの名前にならない問題を修正
- Fix: 複数のメンションを1行に記述した場合に、サジェストが正しく表示されない問題を修正 - Fix: 複数のメンションを1行に記述した場合に、サジェストが正しく表示されない問題を修正
- Fix: メンションとしての条件を満たしていても、特定の条件(`-`が含まれる場合など)で正しくサジェストされない問題を一部修正 - Fix: メンションとしての条件を満たしていても、特定の条件(`-`が含まれる場合など)で正しくサジェストされない問題を一部修正
- Fix: 設定データの移行が完了しているクライアントでは、設定データ移行用のプログラムが読み込まれないように(軽量化) - Fix: ユーザーの前後ノートを閲覧する機能が動作しない問題を修正
- Fix: 設定データの移行が完了しているクライアントでは、設定データ移行用のプログラムの読み込みをスキップするように
### Server ### Server
- Feat: サーバー管理コマンド - Feat: サーバー管理コマンド
@ -66,6 +67,7 @@
- Enhance: `clips/list` APIがページネーションに対応しました - Enhance: `clips/list` APIがページネーションに対応しました
- Fix: `notes/mentions` で場合によっては並び順が正しく返されない問題を修正 - Fix: `notes/mentions` で場合によっては並び順が正しく返されない問題を修正
- Fix: SystemWebhook設定でsecretを空に出来ない問題を修正 - Fix: SystemWebhook設定でsecretを空に出来ない問題を修正
- Fix: 削除されたユーザーがチャットメッセージにリアクションしている場合`chat/history`などでエラーになる問題を修正
## 2025.7.0 ## 2025.7.0

View File

@ -1245,7 +1245,7 @@ releaseToRefresh: "Deixar anar per actualitzar"
refreshing: "Recarregant..." refreshing: "Recarregant..."
pullDownToRefresh: "Llisca cap a baix per recarregar" pullDownToRefresh: "Llisca cap a baix per recarregar"
useGroupedNotifications: "Mostrar les notificacions agrupades " useGroupedNotifications: "Mostrar les notificacions agrupades "
signupPendingError: "Hi ha hagut un problema verificant l'adreça de correu electrònic. L'enllaç pot haver caducat." emailVerificationFailedError: "Hem tingut un problema en verificar la teva adreça de correu electrònic. És probable que l'enllaç estigui caducat."
cwNotationRequired: "Si està activat \"Amagar contingut\" s'ha d'escriure una descripció " cwNotationRequired: "Si està activat \"Amagar contingut\" s'ha d'escriure una descripció "
doReaction: "Afegeix una reacció " doReaction: "Afegeix una reacció "
code: "Codi" code: "Codi"

View File

@ -1243,7 +1243,6 @@ releaseToRefresh: "Zum Aktualisieren loslassen"
refreshing: "Wird aktualisiert..." refreshing: "Wird aktualisiert..."
pullDownToRefresh: "Zum Aktualisieren ziehen" pullDownToRefresh: "Zum Aktualisieren ziehen"
useGroupedNotifications: "Benachrichtigungen gruppieren" useGroupedNotifications: "Benachrichtigungen gruppieren"
signupPendingError: "Beim Überprüfen der Mailadresse ist etwas schiefgelaufen. Der Link könnte abgelaufen sein."
cwNotationRequired: "Ist \"Inhaltswarnung verwenden\" aktiviert, muss eine Beschreibung gegeben werden." cwNotationRequired: "Ist \"Inhaltswarnung verwenden\" aktiviert, muss eine Beschreibung gegeben werden."
doReaction: "Reagieren" doReaction: "Reagieren"
code: "Code" code: "Code"

View File

@ -1245,7 +1245,7 @@ releaseToRefresh: "Release to refresh"
refreshing: "Refreshing..." refreshing: "Refreshing..."
pullDownToRefresh: "Pull down to refresh" pullDownToRefresh: "Pull down to refresh"
useGroupedNotifications: "Display grouped notifications" useGroupedNotifications: "Display grouped notifications"
signupPendingError: "There was a problem verifying the email address. The link may have expired." emailVerificationFailedError: "A problem occurred while verifying your email address. The link may have expired."
cwNotationRequired: "If \"Hide content\" is enabled, a description must be provided." cwNotationRequired: "If \"Hide content\" is enabled, a description must be provided."
doReaction: "Add reaction" doReaction: "Add reaction"
code: "Code" code: "Code"
@ -1376,6 +1376,7 @@ safeModeEnabled: "Safe mode is enabled"
pluginsAreDisabledBecauseSafeMode: "All plugins are disabled because safe mode is enabled." pluginsAreDisabledBecauseSafeMode: "All plugins are disabled because safe mode is enabled."
customCssIsDisabledBecauseSafeMode: "Custom CSS is not applied because safe mode is enabled." customCssIsDisabledBecauseSafeMode: "Custom CSS is not applied because safe mode is enabled."
themeIsDefaultBecauseSafeMode: "While safe mode is active, the default theme is used. Disabling safe mode will revert these changes." themeIsDefaultBecauseSafeMode: "While safe mode is active, the default theme is used. Disabling safe mode will revert these changes."
thankYouForTestingBeta: "Thank you for helping us test the beta version!"
_order: _order:
newest: "Newest First" newest: "Newest First"
oldest: "Oldest First" oldest: "Oldest First"
@ -1665,6 +1666,9 @@ _serverSettings:
userGeneratedContentsVisibilityForVisitor_description2: "Unconditionally publishing all content on the server to the Internet, including remote content received by the server is risky. This is especially important for guests who are unaware of the distributed nature of the content, as they may mistakenly believe that even remote content is content created by users on the server." userGeneratedContentsVisibilityForVisitor_description2: "Unconditionally publishing all content on the server to the Internet, including remote content received by the server is risky. This is especially important for guests who are unaware of the distributed nature of the content, as they may mistakenly believe that even remote content is content created by users on the server."
restartServerSetupWizardConfirm_title: "Restart server setup wizard?" restartServerSetupWizardConfirm_title: "Restart server setup wizard?"
restartServerSetupWizardConfirm_text: "Some current settings will be reset." restartServerSetupWizardConfirm_text: "Some current settings will be reset."
entrancePageStyle: "Entrance page style"
showTimelineForVisitor: "Show timeline"
showActivityiesForVisitor: "Show activities"
_userGeneratedContentsVisibilityForVisitor: _userGeneratedContentsVisibilityForVisitor:
all: "Everything is public" all: "Everything is public"
localOnly: "Only local content is published, remote content is kept private" localOnly: "Only local content is published, remote content is kept private"

View File

@ -1054,6 +1054,7 @@ permissionDeniedError: "Operación denegada"
permissionDeniedErrorDescription: "Esta cuenta no tiene permisos para hacer esa acción." permissionDeniedErrorDescription: "Esta cuenta no tiene permisos para hacer esa acción."
preset: "Predefinido" preset: "Predefinido"
selectFromPresets: "Escoger desde predefinidos" selectFromPresets: "Escoger desde predefinidos"
custom: "Personalizado"
achievements: "Logros" achievements: "Logros"
gotInvalidResponseError: "Respuesta del servidor inválida" gotInvalidResponseError: "Respuesta del servidor inválida"
gotInvalidResponseErrorDescription: "Puede que el servidor esté caído o en mantenimiento. Favor de intentar más tarde" gotInvalidResponseErrorDescription: "Puede que el servidor esté caído o en mantenimiento. Favor de intentar más tarde"
@ -1244,7 +1245,7 @@ releaseToRefresh: "Soltar para recargar"
refreshing: "Recargando..." refreshing: "Recargando..."
pullDownToRefresh: "Tira hacia abajo para recargar" pullDownToRefresh: "Tira hacia abajo para recargar"
useGroupedNotifications: "Mostrar notificaciones agrupadas" useGroupedNotifications: "Mostrar notificaciones agrupadas"
signupPendingError: "Ha habido un problema al verificar tu dirección de correo electrónico. Es posible que el enlace haya caducado." emailVerificationFailedError: "Se ha producido un error al confirmar tu dirección de correo electrónico. Es posible que el enlace haya caducado."
cwNotationRequired: "Si se ha activado \"ocultar contenido\", es necesario proporcionar una descripción." cwNotationRequired: "Si se ha activado \"ocultar contenido\", es necesario proporcionar una descripción."
doReaction: "Añadir reacción" doReaction: "Añadir reacción"
code: "Código" code: "Código"
@ -1375,6 +1376,7 @@ safeModeEnabled: "El modo seguro está activado"
pluginsAreDisabledBecauseSafeMode: "El modo seguro está activado, por lo que todos los plugins están desactivados." pluginsAreDisabledBecauseSafeMode: "El modo seguro está activado, por lo que todos los plugins están desactivados."
customCssIsDisabledBecauseSafeMode: "El modo seguro está activado, por lo que no se aplica el CSS personalizado." customCssIsDisabledBecauseSafeMode: "El modo seguro está activado, por lo que no se aplica el CSS personalizado."
themeIsDefaultBecauseSafeMode: "Mientras el modo seguro esté activado, se utilizará el tema predeterminado. Cuando se desactive el modo seguro, se volverá al tema original." themeIsDefaultBecauseSafeMode: "Mientras el modo seguro esté activado, se utilizará el tema predeterminado. Cuando se desactive el modo seguro, se volverá al tema original."
thankYouForTestingBeta: "¡Gracias por tu colaboración en la prueba de la versión beta!"
_order: _order:
newest: "Los más recientes primero" newest: "Los más recientes primero"
oldest: "Los más antiguos primero" oldest: "Los más antiguos primero"
@ -1664,6 +1666,9 @@ _serverSettings:
userGeneratedContentsVisibilityForVisitor_description2: "Publicar incondicionalmente todo el contenido del servidor en Internet, incluido el contenido remoto recibido por el servidor, es arriesgado. Esto es especialmente importante para los invitados que desconocen la naturaleza distribuida del contenido, ya que pueden creer erróneamente que incluso el contenido remoto es contenido creado por usuarios en el servidor." userGeneratedContentsVisibilityForVisitor_description2: "Publicar incondicionalmente todo el contenido del servidor en Internet, incluido el contenido remoto recibido por el servidor, es arriesgado. Esto es especialmente importante para los invitados que desconocen la naturaleza distribuida del contenido, ya que pueden creer erróneamente que incluso el contenido remoto es contenido creado por usuarios en el servidor."
restartServerSetupWizardConfirm_title: "¿Reiniciar el asistente de configuración del servidor?" restartServerSetupWizardConfirm_title: "¿Reiniciar el asistente de configuración del servidor?"
restartServerSetupWizardConfirm_text: "Algunas configuraciones actuales se restablecerán" restartServerSetupWizardConfirm_text: "Algunas configuraciones actuales se restablecerán"
entrancePageStyle: "Estilo de la página de inicio"
showTimelineForVisitor: "Mostrar la línea de tiempo"
showActivityiesForVisitor: "Mostrar actividades"
_userGeneratedContentsVisibilityForVisitor: _userGeneratedContentsVisibilityForVisitor:
all: "Todo es público." all: "Todo es público."
localOnly: "Sólo se publica el contenido local, el remoto se mantiene privado" localOnly: "Sólo se publica el contenido local, el remoto se mantiene privado"
@ -2273,6 +2278,7 @@ _time:
minute: "Minutos" minute: "Minutos"
hour: "Horas" hour: "Horas"
day: "Días" day: "Días"
month: "Mes(es)"
_2fa: _2fa:
alreadyRegistered: "Ya has completado la configuración." alreadyRegistered: "Ya has completado la configuración."
registerTOTP: "Registrar aplicación autenticadora" registerTOTP: "Registrar aplicación autenticadora"

View File

@ -1208,7 +1208,6 @@ releaseToRefresh: "Relâcher pour rafraîchir"
refreshing: "Rafraîchissement..." refreshing: "Rafraîchissement..."
pullDownToRefresh: "Tirer vers le bas pour rafraîchir" pullDownToRefresh: "Tirer vers le bas pour rafraîchir"
useGroupedNotifications: "Grouper les notifications" useGroupedNotifications: "Grouper les notifications"
signupPendingError: "Un problème est survenu lors de la vérification de votre adresse e-mail. Le lien a peut-être expiré."
cwNotationRequired: "Si « Masquer le contenu » est activé, une description doit être fournie." cwNotationRequired: "Si « Masquer le contenu » est activé, une description doit être fournie."
doReaction: "Réagir" doReaction: "Réagir"
code: "Code" code: "Code"

View File

@ -1212,7 +1212,6 @@ releaseToRefresh: "Lepaskan untuk memuat ulang"
refreshing: "Sedang memuat ulang..." refreshing: "Sedang memuat ulang..."
pullDownToRefresh: "Tarik ke bawah untuk memuat ulang" pullDownToRefresh: "Tarik ke bawah untuk memuat ulang"
useGroupedNotifications: "Tampilkan notifikasi secara dikelompokkan" useGroupedNotifications: "Tampilkan notifikasi secara dikelompokkan"
signupPendingError: "Terdapat masalah ketika memverifikasi alamat surel. Tautan kemungkinan telah kedaluwarsa."
cwNotationRequired: "Jika \"Sembunyikan konten\" diaktifkan, deskripsi harus disediakan." cwNotationRequired: "Jika \"Sembunyikan konten\" diaktifkan, deskripsi harus disediakan."
doReaction: "Tambahkan reaksi" doReaction: "Tambahkan reaksi"
code: "Kode" code: "Kode"

View File

@ -1245,7 +1245,7 @@ releaseToRefresh: "Rilascia per aggiornare"
refreshing: "Aggiornamento..." refreshing: "Aggiornamento..."
pullDownToRefresh: "Trascinare per aggiornare" pullDownToRefresh: "Trascinare per aggiornare"
useGroupedNotifications: "Mostra le notifiche raggruppate" useGroupedNotifications: "Mostra le notifiche raggruppate"
signupPendingError: "Si è verificato un problema durante la verifica del tuo indirizzo email. Potrebbe essere scaduto il collegamento temporaneo." emailVerificationFailedError: "La verifica dell'indirizzo e-mail non è andata a buon fine. Il link potrebbe essere scaduto."
cwNotationRequired: "Devi indicare perché il contenuto è indicato come esplicito." cwNotationRequired: "Devi indicare perché il contenuto è indicato come esplicito."
doReaction: "Reagisci" doReaction: "Reagisci"
code: "Codice" code: "Codice"

View File

@ -1239,7 +1239,6 @@ releaseToRefresh: "離したらリロード"
refreshing: "リロードしとる" refreshing: "リロードしとる"
pullDownToRefresh: "引っ張ってリロードするで" pullDownToRefresh: "引っ張ってリロードするで"
useGroupedNotifications: "通知をグループ分けして出すで" useGroupedNotifications: "通知をグループ分けして出すで"
signupPendingError: "メアド確認してたらなんか変なことなったわ。リンクの期限切れてるかもしれん。"
cwNotationRequired: "「内容を隠す」んやったら注釈書かなアカンで。" cwNotationRequired: "「内容を隠す」んやったら注釈書かなアカンで。"
doReaction: "ツッコむで" doReaction: "ツッコむで"
code: "コード" code: "コード"

View File

@ -1245,7 +1245,7 @@ releaseToRefresh: "놓아서 새로고침"
refreshing: "새로고침 중" refreshing: "새로고침 중"
pullDownToRefresh: "아래로 내려서 새로고침" pullDownToRefresh: "아래로 내려서 새로고침"
useGroupedNotifications: "알림을 그룹화하고 표시" useGroupedNotifications: "알림을 그룹화하고 표시"
signupPendingError: "메일 주소 확인중에 문제가 발생했습니다. 링크의 유효기간이 지났을 가능성이 있습니다." emailVerificationFailedError: "메일 주소 확인에 실패했습니다. 확인에 필요한 URL의 유효기간이 지났을 가능성이 있습니다."
cwNotationRequired: "'내용을 숨기기'를 체크한 경우 주석을 써야 합니다." cwNotationRequired: "'내용을 숨기기'를 체크한 경우 주석을 써야 합니다."
doReaction: "리액션 추가" doReaction: "리액션 추가"
code: "문자열" code: "문자열"

View File

@ -1243,7 +1243,6 @@ releaseToRefresh: "Solte para atualizar"
refreshing: "Atualizando..." refreshing: "Atualizando..."
pullDownToRefresh: "Puxe para baixo para atualizar" pullDownToRefresh: "Puxe para baixo para atualizar"
useGroupedNotifications: "Agrupar notificações" useGroupedNotifications: "Agrupar notificações"
signupPendingError: "Houve um problema ao verificar o endereço de email. O link pode ter expirado."
cwNotationRequired: "Se \"Esconder conteúdo\" está habilitado, uma descrição deve ser adicionada." cwNotationRequired: "Se \"Esconder conteúdo\" está habilitado, uma descrição deve ser adicionada."
doReaction: "Adicionar reação" doReaction: "Adicionar reação"
code: "Código" code: "Código"

View File

@ -1220,7 +1220,6 @@ flip: "Переворот"
showAvatarDecorations: "Показать украшения для аватара" showAvatarDecorations: "Показать украшения для аватара"
pullDownToRefresh: "Опустите что бы обновить" pullDownToRefresh: "Опустите что бы обновить"
useGroupedNotifications: "Отображать уведомления сгруппировано" useGroupedNotifications: "Отображать уведомления сгруппировано"
signupPendingError: "Возникла проблема с подтверждением вашего адреса электронной почты. Возможно, срок действия ссылки истёк."
cwNotationRequired: "Если включена опция «Скрыть содержимое», необходимо написать аннотацию." cwNotationRequired: "Если включена опция «Скрыть содержимое», необходимо написать аннотацию."
doReaction: "Добавить реакцию" doReaction: "Добавить реакцию"
code: "Код" code: "Код"

View File

@ -1245,7 +1245,6 @@ releaseToRefresh: "ปล่อยเพื่อรีเฟรช"
refreshing: "กำลังรีเฟรช..." refreshing: "กำลังรีเฟรช..."
pullDownToRefresh: "ดึงลงเพื่อรีเฟรช" pullDownToRefresh: "ดึงลงเพื่อรีเฟรช"
useGroupedNotifications: "แสดงผลการแจ้งเตือนแบบกลุ่มแล้ว" useGroupedNotifications: "แสดงผลการแจ้งเตือนแบบกลุ่มแล้ว"
signupPendingError: "มีปัญหาในการตรวจสอบที่อยู่อีเมลลิงก์อาจหมดอายุแล้ว"
cwNotationRequired: "หากเปิดใช้งาน “ซ่อนเนื้อหา” จะต้องระบุคำอธิบาย" cwNotationRequired: "หากเปิดใช้งาน “ซ่อนเนื้อหา” จะต้องระบุคำอธิบาย"
doReaction: "เพิ่มรีแอคชั่น" doReaction: "เพิ่มรีแอคชั่น"
code: "โค้ด" code: "โค้ด"

View File

@ -31,7 +31,7 @@ openInWindow: "Pencerede aç"
profile: "Profil" profile: "Profil"
timeline: "Pano" timeline: "Pano"
noAccountDescription: "Bu kullanıcı henüz biyografisini yazmamış." noAccountDescription: "Bu kullanıcı henüz biyografisini yazmamış."
login: "Giriş Yap" login: "Oturum Aç"
loggingIn: "Giriş Yapılıyor..." loggingIn: "Giriş Yapılıyor..."
logout: ıkış Yap" logout: ıkış Yap"
signup: "Kaydol" signup: "Kaydol"
@ -310,7 +310,7 @@ agreeBelow: "Aşağıdakileri kabul ediyorum"
basicNotesBeforeCreateAccount: "Önemli notlar" basicNotesBeforeCreateAccount: "Önemli notlar"
termsOfService: "Hizmet Şartları" termsOfService: "Hizmet Şartları"
start: "Başla" start: "Başla"
home: "Ana sayfa" home: "Pano"
remoteUserCaution: "Bu kullanıcı uzak bir sunucudan geldiği için, gösterilen bilgiler eksik olabilir." remoteUserCaution: "Bu kullanıcı uzak bir sunucudan geldiği için, gösterilen bilgiler eksik olabilir."
activity: "Etkinlik" activity: "Etkinlik"
images: "Görseller" images: "Görseller"
@ -1054,6 +1054,7 @@ permissionDeniedError: "İşlem reddedildi"
permissionDeniedErrorDescription: "Bu hesap bu işlemi gerçekleştirmek için gerekli izne sahip değildir." permissionDeniedErrorDescription: "Bu hesap bu işlemi gerçekleştirmek için gerekli izne sahip değildir."
preset: "Ön ayar" preset: "Ön ayar"
selectFromPresets: "Ön ayarlardan seçim yapın" selectFromPresets: "Ön ayarlardan seçim yapın"
custom: "Özel"
achievements: "Başarılar" achievements: "Başarılar"
gotInvalidResponseError: "Geçersiz sunucu yanıtı" gotInvalidResponseError: "Geçersiz sunucu yanıtı"
gotInvalidResponseErrorDescription: "Sunucu erişilemez durumda olabilir veya bakım çalışması yapılmaktadır. Lütfen daha sonra tekrar dene." gotInvalidResponseErrorDescription: "Sunucu erişilemez durumda olabilir veya bakım çalışması yapılmaktadır. Lütfen daha sonra tekrar dene."
@ -1066,8 +1067,8 @@ collapseRenotesDescription: "Zaten yanıtladığın veya renote aldığın notla
internalServerError: "İç Sunucu Hatası" internalServerError: "İç Sunucu Hatası"
internalServerErrorDescription: "Sunucu beklenmedik bir hatayla karşılaştı." internalServerErrorDescription: "Sunucu beklenmedik bir hatayla karşılaştı."
copyErrorInfo: "Hata ayrıntılarını kopyala" copyErrorInfo: "Hata ayrıntılarını kopyala"
joinThisServer: "Bu sunucuda kaydolun" joinThisServer: "Kaydol"
exploreOtherServers: "Başka bir sunucu arayın" exploreOtherServers: "Diğer sunucuları keşfet"
letsLookAtTimeline: "Pano'ya bir göz atın" letsLookAtTimeline: "Pano'ya bir göz atın"
disableFederationConfirm: "Federasyonu cidden devre dışı bırakmak istiyor musun?" disableFederationConfirm: "Federasyonu cidden devre dışı bırakmak istiyor musun?"
disableFederationConfirmWarn: "Federasyondan ayrılsa bile, aksi belirtilmedikçe gönderiler herkese açık olmaya devam edecek. Genellikle bunu yapmanız gerekmez." disableFederationConfirmWarn: "Federasyondan ayrılsa bile, aksi belirtilmedikçe gönderiler herkese açık olmaya devam edecek. Genellikle bunu yapmanız gerekmez."
@ -1244,7 +1245,7 @@ releaseToRefresh: "Yenilemek için serbest bırak"
refreshing: "Yenileniyor..." refreshing: "Yenileniyor..."
pullDownToRefresh: "Yenilemek için aşağı çekin" pullDownToRefresh: "Yenilemek için aşağı çekin"
useGroupedNotifications: "Gruplandırılmış bildirimleri göster" useGroupedNotifications: "Gruplandırılmış bildirimleri göster"
signupPendingError: "E-posta adresini doğrulamada bir sorun oluştu. Bağlantının süresi dolmuş olabilir." emailVerificationFailedError: "E-posta adresi doğrulanırken bir sorun oluştu. Bağlantının geçerlilik süresi dolmuş olabilir."
cwNotationRequired: "“İçeriği gizle” seçeneği etkinleştirilirse, bir açıklama sağlanmalı." cwNotationRequired: "“İçeriği gizle” seçeneği etkinleştirilirse, bir açıklama sağlanmalı."
doReaction: "Tepki ekle" doReaction: "Tepki ekle"
code: "Kod" code: "Kod"
@ -1375,6 +1376,7 @@ safeModeEnabled: "Güvenli mod etkinleştirildi"
pluginsAreDisabledBecauseSafeMode: "Güvenli mod etkinleştirildiği için tüm eklentiler devre dışı bırakılmıştır." pluginsAreDisabledBecauseSafeMode: "Güvenli mod etkinleştirildiği için tüm eklentiler devre dışı bırakılmıştır."
customCssIsDisabledBecauseSafeMode: "Güvenli mod etkin olduğu için özel CSS uygulanmıyor." customCssIsDisabledBecauseSafeMode: "Güvenli mod etkin olduğu için özel CSS uygulanmıyor."
themeIsDefaultBecauseSafeMode: "Güvenli mod etkinken, varsayılan tema kullanılır. Güvenli modu devre dışı bırakmak bu değişiklikleri geri alır." themeIsDefaultBecauseSafeMode: "Güvenli mod etkinken, varsayılan tema kullanılır. Güvenli modu devre dışı bırakmak bu değişiklikleri geri alır."
thankYouForTestingBeta: "Beta sürümünü test ettiğin için teşekkür ederiz!"
_order: _order:
newest: "Önce yeni" newest: "Önce yeni"
oldest: "Önce eski" oldest: "Önce eski"
@ -1623,7 +1625,7 @@ _initialTutorial:
_timelineDescription: _timelineDescription:
home: "Ana Pano'da, takip ettiğin hesapların notlarını görebilirsin." home: "Ana Pano'da, takip ettiğin hesapların notlarını görebilirsin."
local: "Yerel Pano'da, bu sunucudaki tüm kullanıcıların notlarını görebilirsin." local: "Yerel Pano'da, bu sunucudaki tüm kullanıcıların notlarını görebilirsin."
social: "Sosyal Pano, Ana Sayfa ve Yerel Pano'dan gelen notları görüntüler." social: "Pano, Sosyal Pano ve Yerel Pano'dan gelen notları görüntüler."
global: "Global Pano'da, bağlı tüm sunuculardan gelen notları görebilirsin." global: "Global Pano'da, bağlı tüm sunuculardan gelen notları görebilirsin."
_serverRules: _serverRules:
description: "Kayıt öncesinde gösterilecek bir dizi kural. Hizmet Şartlarının özetini belirlemen önerilir." description: "Kayıt öncesinde gösterilecek bir dizi kural. Hizmet Şartlarının özetini belirlemen önerilir."
@ -1664,6 +1666,9 @@ _serverSettings:
userGeneratedContentsVisibilityForVisitor_description2: "Sunucu tarafından alınan uzak içerik dahil olmak üzere sunucudaki tüm içeriği koşulsuz olarak İnternet'e yayınlamak risklidir. Bu, içeriğin dağıtılmış yapısından haberdar olmayan misafirler için özellikle önemlidir, çünkü onlar yanlışlıkla uzak içeriğin bile sunucudaki kullanıcılar tarafından oluşturulan içerik olduğunu düşünebilirler." userGeneratedContentsVisibilityForVisitor_description2: "Sunucu tarafından alınan uzak içerik dahil olmak üzere sunucudaki tüm içeriği koşulsuz olarak İnternet'e yayınlamak risklidir. Bu, içeriğin dağıtılmış yapısından haberdar olmayan misafirler için özellikle önemlidir, çünkü onlar yanlışlıkla uzak içeriğin bile sunucudaki kullanıcılar tarafından oluşturulan içerik olduğunu düşünebilirler."
restartServerSetupWizardConfirm_title: "Sunucu kurulum sihirbazını yeniden başlatmak ister misin?" restartServerSetupWizardConfirm_title: "Sunucu kurulum sihirbazını yeniden başlatmak ister misin?"
restartServerSetupWizardConfirm_text: "Bazı mevcut ayarlar sıfırlanacaktır." restartServerSetupWizardConfirm_text: "Bazı mevcut ayarlar sıfırlanacaktır."
entrancePageStyle: "Giriş sayfası stili"
showTimelineForVisitor: "Panoyu göster"
showActivityiesForVisitor: "Aktiviteleri göster"
_userGeneratedContentsVisibilityForVisitor: _userGeneratedContentsVisibilityForVisitor:
all: "Her şey halka açıktır." all: "Her şey halka açıktır."
localOnly: "Yalnızca yerel içerik yayınlanır, uzak içerik gizli tutulur." localOnly: "Yalnızca yerel içerik yayınlanır, uzak içerik gizli tutulur."
@ -2273,6 +2278,7 @@ _time:
minute: "Dakika(lar)" minute: "Dakika(lar)"
hour: "Saat(ler)" hour: "Saat(ler)"
day: "Gün(ler)" day: "Gün(ler)"
month: "Ay"
_2fa: _2fa:
alreadyRegistered: "2fa kimlik doğrulama cihazını zaten kaydettin." alreadyRegistered: "2fa kimlik doğrulama cihazını zaten kaydettin."
registerTOTP: "Kimlik doğrulama uygulamasını kaydet" registerTOTP: "Kimlik doğrulama uygulamasını kaydet"
@ -2478,7 +2484,7 @@ _poll:
_visibility: _visibility:
public: "Halka açık" public: "Halka açık"
publicDescription: "Notunuz tüm kullanıcılar tarafından görülebilir olacaktır." publicDescription: "Notunuz tüm kullanıcılar tarafından görülebilir olacaktır."
home: "Ana sayfa" home: "Pano"
homeDescription: "Yalnızca ana zaman çizelgesine gönder" homeDescription: "Yalnızca ana zaman çizelgesine gönder"
followers: "Takipçiler" followers: "Takipçiler"
followersDescription: "Sadece takipçilerine görünür hale getir" followersDescription: "Sadece takipçilerine görünür hale getir"
@ -2554,7 +2560,7 @@ _instanceCharts:
files: "Dosya sayısındaki fark" files: "Dosya sayısındaki fark"
filesTotal: "Toplam dosya sayısı" filesTotal: "Toplam dosya sayısı"
_timelines: _timelines:
home: "Ana Sayfa" home: "Pano"
local: "Yerel" local: "Yerel"
social: "Sosyal" social: "Sosyal"
global: "Global" global: "Global"
@ -2672,7 +2678,7 @@ _notification:
chatRoomInvitationReceived: "Sohbet odasına davet edildi" chatRoomInvitationReceived: "Sohbet odasına davet edildi"
achievementEarned: "Başarı kilidi açıldı" achievementEarned: "Başarı kilidi açıldı"
exportCompleted: "İhracat işlemi tamamlandı." exportCompleted: "İhracat işlemi tamamlandı."
login: "Giriş Yap" login: "Oturum Aç"
createToken: "Erişim jetonu oluştur" createToken: "Erişim jetonu oluştur"
test: "Bildirim testi" test: "Bildirim testi"
app: "Bağlı uygulamalardan gelen bildirimler" app: "Bağlı uygulamalardan gelen bildirimler"
@ -2709,7 +2715,7 @@ _deck:
main: "Ana" main: "Ana"
widgets: "Widget'lar" widgets: "Widget'lar"
notifications: "Bildirimler" notifications: "Bildirimler"
tl: "Ana Sayfa" tl: "Pano"
antenna: "Antenler" antenna: "Antenler"
list: "Liste" list: "Liste"
channel: "Kanal" channel: "Kanal"

View File

@ -1196,7 +1196,6 @@ showAvatarDecorations: "Hiển thị trang trí ảnh đại diện"
releaseToRefresh: "Thả để làm mới" releaseToRefresh: "Thả để làm mới"
refreshing: "Đang làm mới" refreshing: "Đang làm mới"
pullDownToRefresh: "Kéo xuống để làm mới" pullDownToRefresh: "Kéo xuống để làm mới"
signupPendingError: "Đã xảy ra sự cố khi xác minh địa chỉ email của bạn. Liên kết có thể đã hết hạn."
cwNotationRequired: "Nếu \"Ẩn nội dung\" được bật thì cần phải có chú thích." cwNotationRequired: "Nếu \"Ẩn nội dung\" được bật thì cần phải có chú thích."
decorate: "Trang trí" decorate: "Trang trí"
lastNDays: "{n} ngày trước" lastNDays: "{n} ngày trước"

View File

@ -1245,7 +1245,7 @@ releaseToRefresh: "松开以刷新"
refreshing: "刷新中" refreshing: "刷新中"
pullDownToRefresh: "下拉以刷新" pullDownToRefresh: "下拉以刷新"
useGroupedNotifications: "分组显示通知" useGroupedNotifications: "分组显示通知"
signupPendingError: "确认电子邮件时出现错误。链接可能已过期。" emailVerificationFailedError: "确认电子邮件时出现错误。链接可能已过期。"
cwNotationRequired: "在启用「隐藏内容」时必须输入注释" cwNotationRequired: "在启用「隐藏内容」时必须输入注释"
doReaction: "回应" doReaction: "回应"
code: "代码" code: "代码"

View File

@ -1245,7 +1245,7 @@ releaseToRefresh: "放開以更新內容"
refreshing: "載入更新中" refreshing: "載入更新中"
pullDownToRefresh: "往下拉來更新內容" pullDownToRefresh: "往下拉來更新內容"
useGroupedNotifications: "分組顯示通知訊息" useGroupedNotifications: "分組顯示通知訊息"
signupPendingError: "驗證您的電子郵件地址時出現問題。連結可能已過期。" emailVerificationFailedError: "驗證您的電子郵件地址時出現問題。連結可能已過期。"
cwNotationRequired: "如果開啟「隱藏內容」,則需要註解說明。" cwNotationRequired: "如果開啟「隱藏內容」,則需要註解說明。"
doReaction: "做出反應" doReaction: "做出反應"
code: "程式碼" code: "程式碼"

View File

@ -1,12 +1,12 @@
{ {
"name": "misskey", "name": "misskey",
"version": "2025.8.0-beta.2", "version": "2025.8.0-beta.3",
"codename": "nasubi", "codename": "nasubi",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/misskey-dev/misskey.git" "url": "https://github.com/misskey-dev/misskey.git"
}, },
"packageManager": "pnpm@10.14.0", "packageManager": "pnpm@10.15.0",
"workspaces": [ "workspaces": [
"packages/frontend-shared", "packages/frontend-shared",
"packages/frontend", "packages/frontend",
@ -53,8 +53,8 @@
"lodash": "4.17.21" "lodash": "4.17.21"
}, },
"dependencies": { "dependencies": {
"cssnano": "7.1.0", "cssnano": "7.1.1",
"esbuild": "0.25.8", "esbuild": "0.25.9",
"execa": "9.6.0", "execa": "9.6.0",
"fast-glob": "3.3.3", "fast-glob": "3.3.3",
"glob": "11.0.3", "glob": "11.0.3",
@ -67,15 +67,15 @@
}, },
"devDependencies": { "devDependencies": {
"@misskey-dev/eslint-plugin": "2.1.0", "@misskey-dev/eslint-plugin": "2.1.0",
"@types/node": "22.17.1", "@types/node": "22.17.2",
"@typescript-eslint/eslint-plugin": "8.39.0", "@typescript-eslint/eslint-plugin": "8.40.0",
"@typescript-eslint/parser": "8.39.0", "@typescript-eslint/parser": "8.40.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"cypress": "14.5.4", "cypress": "14.5.4",
"eslint": "9.33.0", "eslint": "9.34.0",
"globals": "16.3.0", "globals": "16.3.0",
"ncp": "2.0.0", "ncp": "2.0.0",
"pnpm": "10.14.0", "pnpm": "10.15.0",
"start-server-and-test": "2.0.13" "start-server-and-test": "2.0.13"
}, },
"optionalDependencies": { "optionalDependencies": {

View File

@ -54,12 +54,13 @@ export class ChatEntityService {
const message = typeof src === 'object' ? src : await this.chatMessagesRepository.findOneByOrFail({ id: src }); const message = typeof src === 'object' ? src : await this.chatMessagesRepository.findOneByOrFail({ id: src });
const reactions: { user: Packed<'UserLite'>; reaction: string; }[] = []; // userは削除されている可能性があるのでnull許容
const reactions: { user: Packed<'UserLite'> | null; reaction: string; }[] = [];
for (const record of message.reactions) { for (const record of message.reactions) {
const [userId, reaction] = record.split('/'); const [userId, reaction] = record.split('/');
reactions.push({ reactions.push({
user: packedUsers?.get(userId) ?? await this.userEntityService.pack(userId), user: packedUsers?.get(userId) ?? await this.userEntityService.pack(userId).catch(() => null),
reaction, reaction,
}); });
} }
@ -76,7 +77,7 @@ export class ChatEntityService {
toRoom: message.toRoomId ? (packedRooms?.get(message.toRoomId) ?? await this.packRoom(message.toRoom ?? message.toRoomId, me)) : undefined, toRoom: message.toRoomId ? (packedRooms?.get(message.toRoomId) ?? await this.packRoom(message.toRoom ?? message.toRoomId, me)) : undefined,
fileId: message.fileId, fileId: message.fileId,
file: message.fileId ? (packedFiles?.get(message.fileId) ?? await this.driveFileEntityService.pack(message.file ?? message.fileId)) : null, file: message.fileId ? (packedFiles?.get(message.fileId) ?? await this.driveFileEntityService.pack(message.file ?? message.fileId)) : null,
reactions, reactions: reactions.filter((r): r is { user: Packed<'UserLite'>; reaction: string; } => r.user != null),
}; };
} }
@ -108,6 +109,7 @@ export class ChatEntityService {
} }
} }
// TODO: packedUsersに削除されたユーザーもnullとして含める
const [packedUsers, packedFiles, packedRooms] = await Promise.all([ const [packedUsers, packedFiles, packedRooms] = await Promise.all([
this.userEntityService.packMany(users, me) this.userEntityService.packMany(users, me)
.then(users => new Map(users.map(u => [u.id, u]))), .then(users => new Map(users.map(u => [u.id, u]))),
@ -183,12 +185,13 @@ export class ChatEntityService {
const message = typeof src === 'object' ? src : await this.chatMessagesRepository.findOneByOrFail({ id: src }); const message = typeof src === 'object' ? src : await this.chatMessagesRepository.findOneByOrFail({ id: src });
const reactions: { user: Packed<'UserLite'>; reaction: string; }[] = []; // userは削除されている可能性があるのでnull許容
const reactions: { user: Packed<'UserLite'> | null; reaction: string; }[] = [];
for (const record of message.reactions) { for (const record of message.reactions) {
const [userId, reaction] = record.split('/'); const [userId, reaction] = record.split('/');
reactions.push({ reactions.push({
user: packedUsers?.get(userId) ?? await this.userEntityService.pack(userId), user: packedUsers?.get(userId) ?? await this.userEntityService.pack(userId).catch(() => null),
reaction, reaction,
}); });
} }
@ -202,7 +205,7 @@ export class ChatEntityService {
toRoomId: message.toRoomId!, toRoomId: message.toRoomId!,
fileId: message.fileId, fileId: message.fileId,
file: message.fileId ? (packedFiles?.get(message.fileId) ?? await this.driveFileEntityService.pack(message.file ?? message.fileId)) : null, file: message.fileId ? (packedFiles?.get(message.fileId) ?? await this.driveFileEntityService.pack(message.file ?? message.fileId)) : null,
reactions, reactions: reactions.filter((r): r is { user: Packed<'UserLite'>; reaction: string; } => r.user != null),
}; };
} }

View File

@ -16,7 +16,7 @@
"@rollup/pluginutils": "5.2.0", "@rollup/pluginutils": "5.2.0",
"@twemoji/parser": "16.0.0", "@twemoji/parser": "16.0.0",
"@vitejs/plugin-vue": "6.0.1", "@vitejs/plugin-vue": "6.0.1",
"@vue/compiler-sfc": "3.5.18", "@vue/compiler-sfc": "3.5.19",
"astring": "1.9.0", "astring": "1.9.0",
"buraha": "0.0.1", "buraha": "0.0.1",
"estree-walker": "3.0.3", "estree-walker": "3.0.3",
@ -26,16 +26,16 @@
"mfm-js": "0.25.0", "mfm-js": "0.25.0",
"misskey-js": "workspace:*", "misskey-js": "workspace:*",
"punycode.js": "2.3.1", "punycode.js": "2.3.1",
"rollup": "4.46.2", "rollup": "4.48.0",
"sass": "1.89.2", "sass": "1.90.0",
"shiki": "3.9.1", "shiki": "3.11.0",
"tinycolor2": "1.6.0", "tinycolor2": "1.6.0",
"tsc-alias": "1.8.16", "tsc-alias": "1.8.16",
"tsconfig-paths": "4.2.0", "tsconfig-paths": "4.2.0",
"typescript": "5.9.2", "typescript": "5.9.2",
"uuid": "11.1.0", "uuid": "11.1.0",
"vite": "7.0.6", "vite": "7.1.3",
"vue": "3.5.18" "vue": "3.5.19"
}, },
"devDependencies": { "devDependencies": {
"@misskey-dev/summaly": "5.2.3", "@misskey-dev/summaly": "5.2.3",
@ -43,14 +43,14 @@
"@testing-library/vue": "8.1.0", "@testing-library/vue": "8.1.0",
"@types/estree": "1.0.8", "@types/estree": "1.0.8",
"@types/micromatch": "4.0.9", "@types/micromatch": "4.0.9",
"@types/node": "22.17.0", "@types/node": "22.17.2",
"@types/punycode.js": "npm:@types/punycode@2.1.4", "@types/punycode.js": "npm:@types/punycode@2.1.4",
"@types/tinycolor2": "1.4.6", "@types/tinycolor2": "1.4.6",
"@types/ws": "8.18.1", "@types/ws": "8.18.1",
"@typescript-eslint/eslint-plugin": "8.38.0", "@typescript-eslint/eslint-plugin": "8.40.0",
"@typescript-eslint/parser": "8.38.0", "@typescript-eslint/parser": "8.40.0",
"@vitest/coverage-v8": "3.2.4", "@vitest/coverage-v8": "3.2.4",
"@vue/runtime-core": "3.5.18", "@vue/runtime-core": "3.5.19",
"acorn": "8.15.0", "acorn": "8.15.0",
"cross-env": "10.0.0", "cross-env": "10.0.0",
"eslint-plugin-import": "2.32.0", "eslint-plugin-import": "2.32.0",
@ -59,14 +59,14 @@
"happy-dom": "18.0.1", "happy-dom": "18.0.1",
"intersection-observer": "0.12.2", "intersection-observer": "0.12.2",
"micromatch": "4.0.8", "micromatch": "4.0.8",
"msw": "2.10.4", "msw": "2.10.5",
"nodemon": "3.1.10", "nodemon": "3.1.10",
"prettier": "3.6.2", "prettier": "3.6.2",
"start-server-and-test": "2.0.12", "start-server-and-test": "2.0.13",
"tsx": "4.20.3", "tsx": "4.20.4",
"vite-plugin-turbosnap": "1.0.3", "vite-plugin-turbosnap": "1.0.3",
"vue-component-type-helpers": "3.0.5", "vue-component-type-helpers": "3.0.6",
"vue-eslint-parser": "10.2.0", "vue-eslint-parser": "10.2.0",
"vue-tsc": "3.0.5" "vue-tsc": "3.0.6"
} }
} }

View File

@ -88,6 +88,7 @@ export const ROLE_POLICIES = [
'canManageCustomEmojis', 'canManageCustomEmojis',
'canManageAvatarDecorations', 'canManageAvatarDecorations',
'canSearchNotes', 'canSearchNotes',
'canSearchUsers',
'canUseTranslator', 'canUseTranslator',
'canHideAds', 'canHideAds',
'driveCapacityMb', 'driveCapacityMb',

View File

@ -21,10 +21,10 @@
"lint": "pnpm typecheck && pnpm eslint" "lint": "pnpm typecheck && pnpm eslint"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "22.17.0", "@types/node": "22.17.2",
"@typescript-eslint/eslint-plugin": "8.38.0", "@typescript-eslint/eslint-plugin": "8.40.0",
"@typescript-eslint/parser": "8.38.0", "@typescript-eslint/parser": "8.40.0",
"esbuild": "0.25.8", "esbuild": "0.25.9",
"eslint-plugin-vue": "10.4.0", "eslint-plugin-vue": "10.4.0",
"nodemon": "3.1.10", "nodemon": "3.1.10",
"typescript": "5.9.2", "typescript": "5.9.2",
@ -35,6 +35,6 @@
], ],
"dependencies": { "dependencies": {
"misskey-js": "workspace:*", "misskey-js": "workspace:*",
"vue": "3.5.18" "vue": "3.5.19"
} }
} }

View File

@ -24,14 +24,14 @@
"@rollup/plugin-json": "6.1.0", "@rollup/plugin-json": "6.1.0",
"@rollup/plugin-replace": "6.0.2", "@rollup/plugin-replace": "6.0.2",
"@rollup/pluginutils": "5.2.0", "@rollup/pluginutils": "5.2.0",
"@sentry/vue": "10.0.0", "@sentry/vue": "10.5.0",
"@syuilo/aiscript": "1.1.0", "@syuilo/aiscript": "1.1.0",
"@syuilo/aiscript-0-19-0": "npm:@syuilo/aiscript@^0.19.0", "@syuilo/aiscript-0-19-0": "npm:@syuilo/aiscript@^0.19.0",
"@twemoji/parser": "16.0.0", "@twemoji/parser": "16.0.0",
"@vitejs/plugin-vue": "6.0.1", "@vitejs/plugin-vue": "6.0.1",
"@vue/compiler-sfc": "3.5.18", "@vue/compiler-sfc": "3.5.19",
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.15", "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.15",
"analytics": "0.8.16", "analytics": "0.8.19",
"astring": "1.9.0", "astring": "1.9.0",
"broadcast-channel": "7.1.0", "broadcast-channel": "7.1.0",
"buraha": "0.0.1", "buraha": "0.0.1",
@ -55,7 +55,7 @@
"ios-haptics": "0.1.0", "ios-haptics": "0.1.0",
"is-file-animated": "1.0.2", "is-file-animated": "1.0.2",
"json5": "2.2.3", "json5": "2.2.3",
"magic-string": "0.30.17", "magic-string": "0.30.18",
"matter-js": "0.20.0", "matter-js": "0.20.0",
"mfm-js": "0.25.0", "mfm-js": "0.25.0",
"misskey-bubble-game": "workspace:*", "misskey-bubble-game": "workspace:*",
@ -63,10 +63,10 @@
"misskey-reversi": "workspace:*", "misskey-reversi": "workspace:*",
"photoswipe": "5.4.4", "photoswipe": "5.4.4",
"punycode.js": "2.3.1", "punycode.js": "2.3.1",
"rollup": "4.46.2", "rollup": "4.48.0",
"sanitize-html": "2.17.0", "sanitize-html": "2.17.0",
"sass": "1.89.2", "sass": "1.90.0",
"shiki": "3.9.1", "shiki": "3.11.0",
"strict-event-emitter-types": "2.0.0", "strict-event-emitter-types": "2.0.0",
"textarea-caret": "3.1.0", "textarea-caret": "3.1.0",
"three": "0.179.1", "three": "0.179.1",
@ -76,8 +76,8 @@
"tsconfig-paths": "4.2.0", "tsconfig-paths": "4.2.0",
"typescript": "5.9.2", "typescript": "5.9.2",
"v-code-diff": "1.13.1", "v-code-diff": "1.13.1",
"vite": "7.0.6", "vite": "7.1.3",
"vue": "3.5.18", "vue": "3.5.19",
"vuedraggable": "next", "vuedraggable": "next",
"wanakana": "5.3.1" "wanakana": "5.3.1"
}, },
@ -86,7 +86,7 @@
"@storybook/addon-actions": "9.0.8", "@storybook/addon-actions": "9.0.8",
"@storybook/addon-essentials": "8.6.14", "@storybook/addon-essentials": "8.6.14",
"@storybook/addon-interactions": "8.6.14", "@storybook/addon-interactions": "8.6.14",
"@storybook/addon-links": "9.1.0", "@storybook/addon-links": "9.1.3",
"@storybook/addon-mdx-gfm": "8.6.14", "@storybook/addon-mdx-gfm": "8.6.14",
"@storybook/addon-storysource": "8.6.14", "@storybook/addon-storysource": "8.6.14",
"@storybook/blocks": "8.6.14", "@storybook/blocks": "8.6.14",
@ -94,34 +94,34 @@
"@storybook/core-events": "8.6.14", "@storybook/core-events": "8.6.14",
"@storybook/manager-api": "8.6.14", "@storybook/manager-api": "8.6.14",
"@storybook/preview-api": "8.6.14", "@storybook/preview-api": "8.6.14",
"@storybook/react": "9.1.0", "@storybook/react": "9.1.3",
"@storybook/react-vite": "9.1.0", "@storybook/react-vite": "9.1.3",
"@storybook/test": "8.6.14", "@storybook/test": "8.6.14",
"@storybook/theming": "8.6.14", "@storybook/theming": "8.6.14",
"@storybook/types": "8.6.14", "@storybook/types": "8.6.14",
"@storybook/vue3": "9.1.0", "@storybook/vue3": "9.1.3",
"@storybook/vue3-vite": "9.1.0", "@storybook/vue3-vite": "9.1.3",
"@tabler/icons-webfont": "3.34.1", "@tabler/icons-webfont": "3.34.1",
"@testing-library/vue": "8.1.0", "@testing-library/vue": "8.1.0",
"@types/canvas-confetti": "1.9.0", "@types/canvas-confetti": "1.9.0",
"@types/estree": "1.0.8", "@types/estree": "1.0.8",
"@types/matter-js": "0.19.8", "@types/matter-js": "0.20.0",
"@types/micromatch": "4.0.9", "@types/micromatch": "4.0.9",
"@types/node": "22.17.0", "@types/node": "22.17.2",
"@types/punycode.js": "npm:@types/punycode@2.1.4", "@types/punycode.js": "npm:@types/punycode@2.1.4",
"@types/sanitize-html": "2.16.0", "@types/sanitize-html": "2.16.0",
"@types/seedrandom": "3.0.8", "@types/seedrandom": "3.0.8",
"@types/throttle-debounce": "5.0.2", "@types/throttle-debounce": "5.0.2",
"@types/tinycolor2": "1.4.6", "@types/tinycolor2": "1.4.6",
"@types/ws": "8.18.1", "@types/ws": "8.18.1",
"@typescript-eslint/eslint-plugin": "8.38.0", "@typescript-eslint/eslint-plugin": "8.40.0",
"@typescript-eslint/parser": "8.38.0", "@typescript-eslint/parser": "8.40.0",
"@vitest/coverage-v8": "3.2.4", "@vitest/coverage-v8": "3.2.4",
"@vue/compiler-core": "3.5.18", "@vue/compiler-core": "3.5.19",
"@vue/runtime-core": "3.5.18", "@vue/runtime-core": "3.5.19",
"acorn": "8.15.0", "acorn": "8.15.0",
"cross-env": "10.0.0", "cross-env": "10.0.0",
"cypress": "14.5.3", "cypress": "14.5.4",
"eslint-plugin-import": "2.32.0", "eslint-plugin-import": "2.32.0",
"eslint-plugin-vue": "10.4.0", "eslint-plugin-vue": "10.4.0",
"fast-glob": "3.3.3", "fast-glob": "3.3.3",
@ -129,22 +129,22 @@
"intersection-observer": "0.12.2", "intersection-observer": "0.12.2",
"micromatch": "4.0.8", "micromatch": "4.0.8",
"minimatch": "10.0.3", "minimatch": "10.0.3",
"msw": "2.10.4", "msw": "2.10.5",
"msw-storybook-addon": "2.0.5", "msw-storybook-addon": "2.0.5",
"nodemon": "3.1.10", "nodemon": "3.1.10",
"prettier": "3.6.2", "prettier": "3.6.2",
"react": "19.1.1", "react": "19.1.1",
"react-dom": "19.1.1", "react-dom": "19.1.1",
"seedrandom": "3.0.5", "seedrandom": "3.0.5",
"start-server-and-test": "2.0.12", "start-server-and-test": "2.0.13",
"storybook": "9.1.0", "storybook": "9.1.3",
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme", "storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
"tsx": "4.20.3", "tsx": "4.20.4",
"vite-plugin-turbosnap": "1.0.3", "vite-plugin-turbosnap": "1.0.3",
"vitest": "3.2.4", "vitest": "3.2.4",
"vitest-fetch-mock": "0.4.5", "vitest-fetch-mock": "0.4.5",
"vue-component-type-helpers": "3.0.5", "vue-component-type-helpers": "3.0.6",
"vue-eslint-parser": "10.2.0", "vue-eslint-parser": "10.2.0",
"vue-tsc": "3.0.5" "vue-tsc": "3.0.6"
} }
} }

View File

@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<MkPagination :paginator="paginator" :autoLoad="autoLoad" :pullToRefresh="pullToRefresh" :withControl="withControl"> <MkPagination :paginator="paginator" :direction="direction" :autoLoad="autoLoad" :pullToRefresh="pullToRefresh" :withControl="withControl">
<template #empty><MkResult type="empty" :text="i18n.ts.noNotes"/></template> <template #empty><MkResult type="empty" :text="i18n.ts.noNotes"/></template>
<template #default="{ items: notes }"> <template #default="{ items: notes }">
@ -50,11 +50,14 @@ import { isSeparatorNeeded, getSeparatorInfo } from '@/utility/timeline-date-sep
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
paginator: T; paginator: T;
noGap?: boolean; noGap?: boolean;
direction?: 'up' | 'down' | 'both';
autoLoad?: boolean; autoLoad?: boolean;
pullToRefresh?: boolean; pullToRefresh?: boolean;
withControl?: boolean; withControl?: boolean;
}>(), { }>(), {
autoLoad: true, autoLoad: true,
direction: 'down',
pullToRefresh: true, pullToRefresh: true,
withControl: true, withControl: true,
}); });

View File

@ -25,15 +25,15 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
<div v-else key="_root_" class="_gaps"> <div v-else key="_root_" class="_gaps">
<slot :items="unref(paginator.items)" :fetching="paginator.fetching.value || paginator.fetchingOlder.value"></slot> <div v-if="direction === 'up' || direction === 'both'" v-show="upButtonVisible">
<div v-if="paginator.order.value === 'oldest'"> <MkButton v-if="!upButtonLoading" :class="$style.more" primary rounded @click="upButtonClick">
<MkButton v-if="!paginator.fetchingNewer.value" :class="$style.more" :wait="paginator.fetchingNewer.value" primary rounded @click="paginator.fetchNewer()">
{{ i18n.ts.loadMore }} {{ i18n.ts.loadMore }}
</MkButton> </MkButton>
<MkLoading v-else/> <MkLoading v-else/>
</div> </div>
<div v-else v-show="paginator.canFetchOlder.value"> <slot :items="unref(paginator.items)" :fetching="paginator.fetching.value || paginator.fetchingOlder.value"></slot>
<MkButton v-if="!paginator.fetchingOlder.value" :class="$style.more" :wait="paginator.fetchingOlder.value" primary rounded @click="paginator.fetchOlder()"> <div v-if="direction === 'down' || direction === 'both'" v-show="downButtonVisible">
<MkButton v-if="!downButtonLoading" :class="$style.more" primary rounded @click="downButtonClick">
{{ i18n.ts.loadMore }} {{ i18n.ts.loadMore }}
</MkButton> </MkButton>
<MkLoading v-else/> <MkLoading v-else/>
@ -46,7 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup generic="T extends IPaginator"> <script lang="ts" setup generic="T extends IPaginator">
import { isLink } from '@@/js/is-link.js'; import { isLink } from '@@/js/is-link.js';
import { onMounted, watch, unref } from 'vue'; import { onMounted, computed, watch, unref } from 'vue';
import type { UnwrapRef } from 'vue'; import type { UnwrapRef } from 'vue';
import type { IPaginator } from '@/utility/paginator.js'; import type { IPaginator } from '@/utility/paginator.js';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
@ -58,11 +58,20 @@ import * as os from '@/os.js';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
paginator: T; paginator: T;
//
// up:
// down: (default)
// both:
// NOTE:
direction?: 'up' | 'down' | 'both';
autoLoad?: boolean; autoLoad?: boolean;
pullToRefresh?: boolean; pullToRefresh?: boolean;
withControl?: boolean; withControl?: boolean;
}>(), { }>(), {
autoLoad: true, autoLoad: true,
direction: 'down',
pullToRefresh: true, pullToRefresh: true,
withControl: false, withControl: false,
}); });
@ -93,6 +102,36 @@ if (props.paginator.computedParams) {
}, { immediate: false, deep: true }); }, { immediate: false, deep: true });
} }
const upButtonVisible = computed(() => {
return props.paginator.order.value === 'oldest' ? props.paginator.canFetchOlder.value : props.paginator.canFetchNewer.value;
});
const upButtonLoading = computed(() => {
return props.paginator.order.value === 'oldest' ? props.paginator.fetchingOlder.value : props.paginator.fetchingNewer.value;
});
function upButtonClick() {
if (props.paginator.order.value === 'oldest') {
props.paginator.fetchOlder();
} else {
props.paginator.fetchNewer();
}
}
const downButtonVisible = computed(() => {
return props.paginator.order.value === 'oldest' ? props.paginator.canFetchNewer.value : props.paginator.canFetchOlder.value;
});
const downButtonLoading = computed(() => {
return props.paginator.order.value === 'oldest' ? props.paginator.fetchingNewer.value : props.paginator.fetchingOlder.value;
});
function downButtonClick() {
if (props.paginator.order.value === 'oldest') {
props.paginator.fetchNewer();
} else {
props.paginator.fetchOlder();
}
}
defineSlots<{ defineSlots<{
empty: () => void; empty: () => void;
default: (props: { items: UnwrapRef<T['items']> }) => void; default: (props: { items: UnwrapRef<T['items']> }) => void;

View File

@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<Transition :name="prefer.s.animation ? 'fade' : ''" mode="out-in"> <Transition :name="prefer.s.animation ? 'fade' : ''" mode="out-in">
<div v-if="note"> <div v-if="note">
<div v-if="showNext" class="_margin"> <div v-if="showNext" class="_margin">
<MkNotesTimeline :withControl="false" :pullToRefresh="false" class="" :paginator="showNext === 'channel' ? nextChannelPaginator : nextUserPaginator" :noGap="true"/> <MkNotesTimeline direction="up" :withControl="false" :pullToRefresh="false" class="" :paginator="showNext === 'channel' ? nextChannelPaginator : nextUserPaginator" :noGap="true"/>
</div> </div>
<div class="_margin"> <div class="_margin">
@ -81,7 +81,6 @@ const error = ref();
const prevUserPaginator = markRaw(new Paginator('users/notes', { const prevUserPaginator = markRaw(new Paginator('users/notes', {
limit: 10, limit: 10,
initialId: props.noteId, initialId: props.noteId,
initialDirection: 'older',
computedParams: computed(() => note.value ? ({ computedParams: computed(() => note.value ? ({
userId: note.value.userId, userId: note.value.userId,
}) : undefined), }) : undefined),
@ -99,7 +98,6 @@ const nextUserPaginator = markRaw(new Paginator('users/notes', {
const prevChannelPaginator = markRaw(new Paginator('channels/timeline', { const prevChannelPaginator = markRaw(new Paginator('channels/timeline', {
limit: 10, limit: 10,
initialId: props.noteId, initialId: props.noteId,
initialDirection: 'older',
computedParams: computed(() => note.value && note.value.channelId != null ? ({ computedParams: computed(() => note.value && note.value.channelId != null ? ({
channelId: note.value.channelId, channelId: note.value.channelId,
}) : undefined), }) : undefined),

View File

@ -37,6 +37,7 @@ export interface IPaginator<T = unknown, _T = T & MisskeyEntity> {
fetchingOlder: Ref<boolean>; fetchingOlder: Ref<boolean>;
fetchingNewer: Ref<boolean>; fetchingNewer: Ref<boolean>;
canFetchOlder: Ref<boolean>; canFetchOlder: Ref<boolean>;
canFetchNewer: Ref<boolean>;
canSearch: boolean; canSearch: boolean;
error: Ref<boolean>; error: Ref<boolean>;
computedParams: ComputedRef<Misskey.Endpoints[PaginatorCompatibleEndpointPaths]['req'] | null | undefined> | null; computedParams: ComputedRef<Misskey.Endpoints[PaginatorCompatibleEndpointPaths]['req'] | null | undefined> | null;
@ -77,6 +78,7 @@ export class Paginator<
public fetchingOlder = ref(false); public fetchingOlder = ref(false);
public fetchingNewer = ref(false); public fetchingNewer = ref(false);
public canFetchOlder = ref(false); public canFetchOlder = ref(false);
public canFetchNewer = ref(false);
public canSearch = false; public canSearch = false;
public error = ref(false); public error = ref(false);
private endpoint: Endpoint; private endpoint: Endpoint;
@ -85,7 +87,12 @@ export class Paginator<
public computedParams: ComputedRef<E['req'] | null | undefined> | null; public computedParams: ComputedRef<E['req'] | null | undefined> | null;
public initialId: MisskeyEntity['id'] | null = null; public initialId: MisskeyEntity['id'] | null = null;
public initialDate: number | null = null; public initialDate: number | null = null;
// 初回読み込み時、initialIdを基準にそれより新しいものを取得するか古いものを取得するか
// newer: initialIdより新しいものを取得する
// older: initialIdより古いものを取得する (default)
public initialDirection: 'newer' | 'older'; public initialDirection: 'newer' | 'older';
private offsetMode: boolean; private offsetMode: boolean;
public noPaging: boolean; public noPaging: boolean;
public searchQuery = ref<null | string>(''); public searchQuery = ref<null | string>('');
@ -116,6 +123,7 @@ export class Paginator<
initialId?: MisskeyEntity['id']; initialId?: MisskeyEntity['id'];
initialDate?: number | null; initialDate?: number | null;
initialDirection?: 'newer' | 'older'; initialDirection?: 'newer' | 'older';
order?: 'newest' | 'oldest'; order?: 'newest' | 'oldest';
// 一部のAPIはさらに遡れる場合でもパフォーマンス上の理由でlimit以下の結果を返す場合があり、その場合はsafe、それ以外はlimitにすることを推奨 // 一部のAPIはさらに遡れる場合でもパフォーマンス上の理由でlimit以下の結果を返す場合があり、その場合はsafe、それ以外はlimitにすることを推奨
@ -222,15 +230,15 @@ export class Paginator<
if (this.canFetchDetection === 'limit') { if (this.canFetchDetection === 'limit') {
if (apiRes.length < FIRST_FETCH_LIMIT) { if (apiRes.length < FIRST_FETCH_LIMIT) {
this.canFetchOlder.value = false; (this.initialDirection === 'older' ? this.canFetchOlder : this.canFetchNewer).value = false;
} else { } else {
this.canFetchOlder.value = true; (this.initialDirection === 'older' ? this.canFetchOlder : this.canFetchNewer).value = true;
} }
} else if (this.canFetchDetection === 'safe' || this.canFetchDetection == null) { } else if (this.canFetchDetection === 'safe' || this.canFetchDetection == null) {
if (apiRes.length === 0 || this.noPaging) { if (apiRes.length === 0 || this.noPaging) {
this.canFetchOlder.value = false; (this.initialDirection === 'older' ? this.canFetchOlder : this.canFetchNewer).value = false;
} else { } else {
this.canFetchOlder.value = true; (this.initialDirection === 'older' ? this.canFetchOlder : this.canFetchNewer).value = true;
} }
} }
@ -273,7 +281,11 @@ export class Paginator<
if (i === 10) item._shouldInsertAd_ = true; if (i === 10) item._shouldInsertAd_ = true;
} }
if (this.order.value === 'oldest') {
this.unshiftItems(apiRes.toReversed(), false);
} else {
this.pushItems(apiRes); this.pushItems(apiRes);
}
if (this.canFetchDetection === 'limit') { if (this.canFetchDetection === 'limit') {
if (apiRes.length < FIRST_FETCH_LIMIT) { if (apiRes.length < FIRST_FETCH_LIMIT) {
@ -313,7 +325,11 @@ export class Paginator<
this.fetchingNewer.value = false; this.fetchingNewer.value = false;
if (apiRes == null || apiRes.length === 0) return; // これやらないと余計なre-renderが走る if (apiRes == null || apiRes.length === 0) {
this.canFetchNewer.value = false;
// 余計なre-renderを防止するためここで終了
return;
}
if (options.toQueue) { if (options.toQueue) {
this.aheadQueue.unshift(...apiRes.toReversed()); this.aheadQueue.unshift(...apiRes.toReversed());
@ -325,9 +341,19 @@ export class Paginator<
if (this.order.value === 'oldest') { if (this.order.value === 'oldest') {
this.pushItems(apiRes); this.pushItems(apiRes);
} else { } else {
this.unshiftItems(apiRes.toReversed()); this.unshiftItems(apiRes.toReversed(), false);
} }
} }
if (this.canFetchDetection === 'limit') {
if (apiRes.length < FIRST_FETCH_LIMIT) {
this.canFetchNewer.value = false;
} else {
this.canFetchNewer.value = true;
}
}
// canFetchDetectionが'safe'の場合・apiRes.length === 0 の場合は apiRes.length === 0 の場合に canFetchNewer.value = false になるが、
// 余計な re-render を防ぐために上部で処理している。そのため、ここでは何もしない
} }
public trim(trigger = true): void { public trim(trigger = true): void {
@ -336,10 +362,10 @@ export class Paginator<
if (this.useShallowRef && trigger) triggerRef(this.items); if (this.useShallowRef && trigger) triggerRef(this.items);
} }
public unshiftItems(newItems: T[]): void { public unshiftItems(newItems: T[], trim = true): void {
if (newItems.length === 0) return; // これやらないと余計なre-renderが走る if (newItems.length === 0) return; // これやらないと余計なre-renderが走る
this.items.value.unshift(...newItems.filter(x => !this.items.value.some(y => y.id === x.id))); // ストリーミングやポーリングのタイミングによっては重複することがあるため this.items.value.unshift(...newItems.filter(x => !this.items.value.some(y => y.id === x.id))); // ストリーミングやポーリングのタイミングによっては重複することがあるため
this.trim(false); if (trim) this.trim(true);
if (this.useShallowRef) triggerRef(this.items); if (this.useShallowRef) triggerRef(this.items);
} }

View File

@ -11,16 +11,16 @@
"lint": "pnpm typecheck && pnpm eslint" "lint": "pnpm typecheck && pnpm eslint"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "22.17.0", "@types/node": "22.17.2",
"@types/wawoff2": "1.0.2", "@types/wawoff2": "1.0.2",
"@typescript-eslint/eslint-plugin": "8.38.0", "@typescript-eslint/eslint-plugin": "8.40.0",
"@typescript-eslint/parser": "8.38.0" "@typescript-eslint/parser": "8.40.0"
}, },
"dependencies": { "dependencies": {
"@tabler/icons-webfont": "3.34.1", "@tabler/icons-webfont": "3.34.1",
"harfbuzzjs": "0.4.8", "harfbuzzjs": "0.4.9",
"tiny-glob": "0.2.9", "tiny-glob": "0.2.9",
"tsx": "4.20.3", "tsx": "4.20.4",
"typescript": "5.9.2", "typescript": "5.9.2",
"wawoff2": "2.0.1" "wawoff2": "2.0.1"
}, },

View File

@ -22,15 +22,15 @@
"lint": "pnpm typecheck && pnpm eslint" "lint": "pnpm typecheck && pnpm eslint"
}, },
"devDependencies": { "devDependencies": {
"@types/matter-js": "0.19.8", "@types/matter-js": "0.20.0",
"@types/seedrandom": "3.0.8", "@types/seedrandom": "3.0.8",
"@types/node": "22.17.0", "@types/node": "22.17.2",
"@typescript-eslint/eslint-plugin": "8.38.0", "@typescript-eslint/eslint-plugin": "8.40.0",
"@typescript-eslint/parser": "8.38.0", "@typescript-eslint/parser": "8.40.0",
"nodemon": "3.1.10", "nodemon": "3.1.10",
"execa": "9.6.0", "execa": "9.6.0",
"typescript": "5.9.2", "typescript": "5.9.2",
"esbuild": "0.25.8", "esbuild": "0.25.9",
"glob": "11.0.3" "glob": "11.0.3"
}, },
"files": [ "files": [

View File

@ -186,7 +186,7 @@ export class DropAndFusionGame extends EventEmitter<{
} }
private createBody(mono: Mono, x: number, y: number) { private createBody(mono: Mono, x: number, y: number) {
const options: Matter.IBodyDefinition = { const options = {
label: mono.id, label: mono.id,
density: this.gameMode === 'space' ? 0.01 : ((mono.sizeX * mono.sizeY) / 10000), density: this.gameMode === 'space' ? 0.01 : ((mono.sizeX * mono.sizeY) / 10000),
restitution: this.gameMode === 'space' ? 0.5 : 0.2, restitution: this.gameMode === 'space' ? 0.5 : 0.2,
@ -196,7 +196,7 @@ export class DropAndFusionGame extends EventEmitter<{
slop: this.gameMode === 'space' ? 0.01 : 0.7, slop: this.gameMode === 'space' ? 0.01 : 0.7,
//mass: 0, //mass: 0,
render: this.getMonoRenderOptions ? this.getMonoRenderOptions(mono) : undefined, render: this.getMonoRenderOptions ? this.getMonoRenderOptions(mono) : undefined,
}; } satisfies Matter.IChamferableBodyDefinition;
if (mono.shape === 'circle') { if (mono.shape === 'circle') {
return Matter.Bodies.circle(x, y, mono.sizeX / 2, options); return Matter.Bodies.circle(x, y, mono.sizeX / 2, options);
} else if (mono.shape === 'rectangle') { } else if (mono.shape === 'rectangle') {

View File

@ -8,13 +8,13 @@
}, },
"devDependencies": { "devDependencies": {
"@readme/openapi-parser": "5.0.1", "@readme/openapi-parser": "5.0.1",
"@types/node": "22.17.1", "@types/node": "22.17.2",
"@typescript-eslint/eslint-plugin": "8.39.0", "@typescript-eslint/eslint-plugin": "8.40.0",
"@typescript-eslint/parser": "8.39.0", "@typescript-eslint/parser": "8.40.0",
"openapi-types": "12.1.3", "openapi-types": "12.1.3",
"openapi-typescript": "7.8.0", "openapi-typescript": "7.9.1",
"ts-case-convert": "2.1.0", "ts-case-convert": "2.1.0",
"tsx": "4.20.3", "tsx": "4.20.5",
"typescript": "5.9.2" "typescript": "5.9.2"
}, },
"files": [ "files": [

View File

@ -1,7 +1,7 @@
{ {
"type": "module", "type": "module",
"name": "misskey-js", "name": "misskey-js",
"version": "2025.8.0-beta.2", "version": "2025.8.0-beta.3",
"description": "Misskey SDK for JavaScript", "description": "Misskey SDK for JavaScript",
"license": "MIT", "license": "MIT",
"main": "./built/index.js", "main": "./built/index.js",
@ -35,12 +35,12 @@
"directory": "packages/misskey-js" "directory": "packages/misskey-js"
}, },
"devDependencies": { "devDependencies": {
"@microsoft/api-extractor": "7.52.10", "@microsoft/api-extractor": "7.52.11",
"@types/node": "22.17.1", "@types/node": "22.17.2",
"@typescript-eslint/eslint-plugin": "8.39.0", "@typescript-eslint/eslint-plugin": "8.40.0",
"@typescript-eslint/parser": "8.39.0", "@typescript-eslint/parser": "8.40.0",
"@vitest/coverage-v8": "3.2.4", "@vitest/coverage-v8": "3.2.4",
"esbuild": "0.25.8", "esbuild": "0.25.9",
"execa": "9.6.0", "execa": "9.6.0",
"glob": "11.0.3", "glob": "11.0.3",
"ncp": "2.0.0", "ncp": "2.0.0",

View File

@ -22,13 +22,13 @@
"lint": "pnpm typecheck && pnpm eslint" "lint": "pnpm typecheck && pnpm eslint"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "22.17.0", "@types/node": "22.17.2",
"@typescript-eslint/eslint-plugin": "8.38.0", "@typescript-eslint/eslint-plugin": "8.40.0",
"@typescript-eslint/parser": "8.38.0", "@typescript-eslint/parser": "8.40.0",
"execa": "9.6.0", "execa": "9.6.0",
"nodemon": "3.1.10", "nodemon": "3.1.10",
"typescript": "5.9.2", "typescript": "5.9.2",
"esbuild": "0.25.8", "esbuild": "0.25.9",
"glob": "11.0.3" "glob": "11.0.3"
}, },
"files": [ "files": [

View File

@ -9,12 +9,12 @@
"lint": "pnpm typecheck && pnpm eslint" "lint": "pnpm typecheck && pnpm eslint"
}, },
"dependencies": { "dependencies": {
"esbuild": "0.25.8", "esbuild": "0.25.9",
"idb-keyval": "6.2.2", "idb-keyval": "6.2.2",
"misskey-js": "workspace:*" "misskey-js": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
"@typescript-eslint/parser": "8.38.0", "@typescript-eslint/parser": "8.40.0",
"@typescript/lib-webworker": "npm:@types/serviceworker@0.0.74", "@typescript/lib-webworker": "npm:@types/serviceworker@0.0.74",
"eslint-plugin-import": "2.32.0", "eslint-plugin-import": "2.32.0",
"nodemon": "3.1.10", "nodemon": "3.1.10",

File diff suppressed because it is too large Load Diff