Merge branch 'develop' into enh-14810

This commit is contained in:
かっこかり 2025-02-16 19:37:49 +09:00 committed by GitHub
commit 68561fb852
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
139 changed files with 7098 additions and 6784 deletions
.devcontainer
.github
CHANGELOG.md
locales
package.json
packages

View File

@ -7,7 +7,9 @@
"ghcr.io/devcontainers/features/node:1": { "ghcr.io/devcontainers/features/node:1": {
"version": "22.11.0" "version": "22.11.0"
}, },
"ghcr.io/devcontainers-contrib/features/corepack:1": {} "ghcr.io/devcontainers-extra/features/corepack:1": {
"version": "0.31.0"
}
}, },
"forwardPorts": [3000], "forwardPorts": [3000],
"postCreateCommand": "/bin/bash .devcontainer/init.sh", "postCreateCommand": "/bin/bash .devcontainer/init.sh",

View File

@ -9,7 +9,7 @@ updates:
directory: "/" directory: "/"
schedule: schedule:
interval: daily interval: daily
open-pull-requests-limit: 100 open-pull-requests-limit: 0
# Add only the root, not each workspace item # Add only the root, not each workspace item
# https://github.com/dependabot/dependabot-core/issues/4993#issuecomment-1289133027 # https://github.com/dependabot/dependabot-core/issues/4993#issuecomment-1289133027
@ -17,7 +17,7 @@ updates:
directory: "/" directory: "/"
schedule: schedule:
interval: daily interval: daily
open-pull-requests-limit: 10 open-pull-requests-limit: 0
# List dependencies required to be updated together, sharing the same version numbers. # List dependencies required to be updated together, sharing the same version numbers.
# Those who simply have the common owner (e.g. @fastify) don't need to be listed. # Those who simply have the common owner (e.g. @fastify) don't need to be listed.
groups: groups:

View File

@ -20,12 +20,12 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.2.2
- run: corepack enable - run: corepack enable
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v4.1.0 uses: actions/setup-node@v4.2.0
with: with:
node-version-file: '.node-version' node-version-file: '.node-version'
cache: 'pnpm' cache: 'pnpm'

View File

@ -12,9 +12,9 @@ jobs:
steps: steps:
- name: Checkout head - name: Checkout head
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.2.2
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v4.1.0 uses: actions/setup-node@v4.2.0
with: with:
node-version-file: '.node-version' node-version-file: '.node-version'

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.1.1 uses: actions/checkout@v4.2.2
with: with:
submodules: true submodules: true
persist-credentials: false persist-credentials: false
@ -29,7 +29,7 @@ jobs:
- name: setup node - name: setup node
id: setup-node id: setup-node
uses: actions/setup-node@v4.1.0 uses: actions/setup-node@v4.2.0
with: with:
node-version-file: '.node-version' node-version-file: '.node-version'
cache: pnpm cache: pnpm
@ -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.1.1 uses: actions/checkout@v4.2.2
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.1.1 uses: actions/checkout@v4.2.2
- 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.1.1 uses: actions/checkout@v4.2.2
- 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.1.1 - uses: actions/checkout@v4.2.2
- 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.1.1 uses: actions/checkout@v4.2.2
- 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.1.1 uses: actions/checkout@v4.2.2
- 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.1.1 uses: actions/checkout@v4.2.2
- 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.1.1 - uses: actions/checkout@v4.2.2
- 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

@ -30,14 +30,14 @@ jobs:
ref: refs/pull/${{ github.event.number }}/merge ref: refs/pull/${{ github.event.number }}/merge
steps: steps:
- uses: actions/checkout@v4.1.1 - uses: actions/checkout@v4.2.2
with: with:
ref: ${{ matrix.ref }} ref: ${{ matrix.ref }}
submodules: true submodules: true
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@v4 uses: pnpm/action-setup@v4
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.1.0 uses: actions/setup-node@v4.2.0
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
cache: 'pnpm' cache: 'pnpm'

View File

@ -36,12 +36,12 @@ jobs:
pnpm_install: pnpm_install:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4.1.1 - uses: actions/checkout@v4.2.2
with: with:
fetch-depth: 0 fetch-depth: 0
submodules: true submodules: true
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4.1.0 - uses: actions/setup-node@v4.2.0
with: with:
node-version-file: '.node-version' node-version-file: '.node-version'
cache: 'pnpm' cache: 'pnpm'
@ -67,12 +67,12 @@ 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.1.1 - uses: actions/checkout@v4.2.2
with: with:
fetch-depth: 0 fetch-depth: 0
submodules: true submodules: true
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4.1.0 - uses: actions/setup-node@v4.2.0
with: with:
node-version-file: '.node-version' node-version-file: '.node-version'
cache: 'pnpm' cache: 'pnpm'
@ -97,12 +97,12 @@ jobs:
- sw - sw
- misskey-js - misskey-js
steps: steps:
- uses: actions/checkout@v4.1.1 - uses: actions/checkout@v4.2.2
with: with:
fetch-depth: 0 fetch-depth: 0
submodules: true submodules: true
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4.1.0 - uses: actions/setup-node@v4.2.0
with: with:
node-version-file: '.node-version' node-version-file: '.node-version'
cache: 'pnpm' cache: 'pnpm'

View File

@ -18,12 +18,12 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
continue-on-error: true continue-on-error: true
steps: steps:
- uses: actions/checkout@v4.1.1 - uses: actions/checkout@v4.2.2
with: with:
fetch-depth: 0 fetch-depth: 0
submodules: true submodules: true
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4.1.0 - uses: actions/setup-node@v4.2.0
with: with:
node-version-file: '.node-version' node-version-file: '.node-version'
cache: 'pnpm' cache: 'pnpm'

View File

@ -1,36 +0,0 @@
# If someone with write access comments "/ok-to-test" on a pull request, emit a repository_dispatch event
name: Ok To Test
on:
issue_comment:
types: [created]
jobs:
ok-to-test:
runs-on: ubuntu-latest
# Only run for PRs, not issue comments
if: ${{ github.event.issue.pull_request }}
steps:
# Generate a GitHub App installation access token from an App ID and private key
# To create a new GitHub App:
# https://developer.github.com/apps/building-github-apps/creating-a-github-app/
# See app.yml for an example app manifest
- name: Generate token
id: generate_token
uses: tibdex/github-app-token@v2
with:
app_id: ${{ secrets.DEPLOYBOT_APP_ID }}
private_key: ${{ secrets.DEPLOYBOT_PRIVATE_KEY }}
- name: Slash Command Dispatch
uses: peter-evans/slash-command-dispatch@v4
env:
TOKEN: ${{ steps.generate_token.outputs.token }}
with:
token: ${{ env.TOKEN }} # GitHub App installation access token
# token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} # PAT or OAuth token will also work
reaction-token: ${{ secrets.GITHUB_TOKEN }}
issue-type: pull-request
commands: deploy
named-args: true
permission: write

View File

@ -23,13 +23,13 @@ jobs:
node-version: [22.11.0] node-version: [22.11.0]
steps: steps:
- uses: actions/checkout@v4.1.1 - uses: actions/checkout@v4.2.2
with: with:
submodules: true submodules: true
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@v4 uses: pnpm/action-setup@v4
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.1.0 uses: actions/setup-node@v4.2.0
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
cache: 'pnpm' cache: 'pnpm'

View File

@ -1,92 +0,0 @@
# Run secret-dependent integration tests only after /deploy approval
on:
repository_dispatch:
types: [deploy-command]
name: Deploy preview environment
jobs:
# Repo owner has commented /deploy on a (fork-based) pull request
deploy-preview-environment:
runs-on: ubuntu-latest
if:
github.event.client_payload.slash_command.sha != '' &&
contains(github.event.client_payload.pull_request.head.sha, github.event.client_payload.slash_command.sha)
steps:
- uses: actions/github-script@v7.0.1
id: check-id
env:
number: ${{ github.event.client_payload.pull_request.number }}
job: ${{ github.job }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
result-encoding: string
script: |
const { data: pull } = await github.rest.pulls.get({
...context.repo,
pull_number: process.env.number
});
const ref = pull.head.sha;
const { data: checks } = await github.rest.checks.listForRef({
...context.repo,
ref
});
const check = checks.check_runs.filter(c => c.name === process.env.job);
return check[0].id;
- uses: actions/github-script@v7.0.1
env:
check_id: ${{ steps.check-id.outputs.result }}
details_url: ${{ github.server_url }}/${{ github.repository }}/runs/${{ github.run_id }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
await github.rest.checks.update({
...context.repo,
check_run_id: process.env.check_id,
status: 'in_progress',
details_url: process.env.details_url
});
# Check out merge commit
- name: Fork based /deploy checkout
uses: actions/checkout@v4.1.1
with:
ref: 'refs/pull/${{ github.event.client_payload.pull_request.number }}/merge'
# <insert integration tests needing secrets>
- name: Context
uses: okteto/context@latest
with:
token: ${{ secrets.OKTETO_TOKEN }}
- name: Deploy preview environment
uses: ikuradon/deploy-preview@latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
name: pr-${{ github.event.client_payload.pull_request.number }}-syuilo
timeout: 15m
# Update check run called "integration-fork"
- uses: actions/github-script@v7.0.1
id: update-check-run
if: ${{ always() }}
env:
# Conveniently, job.status maps to https://developer.github.com/v3/checks/runs/#update-a-check-run
conclusion: ${{ job.status }}
check_id: ${{ steps.check-id.outputs.result }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { data: result } = await github.rest.checks.update({
...context.repo,
check_run_id: process.env.check_id,
status: 'completed',
conclusion: process.env.conclusion
});
return result;

View File

@ -1,54 +0,0 @@
# file: .github/workflows/preview-closed.yaml
on:
pull_request:
types:
- closed
name: Destroy preview environment
jobs:
destroy-preview-environment:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v7.0.1
id: check-conclusion
env:
number: ${{ github.event.number }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
result-encoding: string
script: |
const { data: pull } = await github.rest.pulls.get({
...context.repo,
pull_number: process.env.number
});
const ref = pull.head.sha;
const { data: checks } = await github.rest.checks.listForRef({
...context.repo,
ref
});
const check = checks.check_runs.filter(c => c.name === 'deploy-preview-environment');
if (check.length === 0) {
return;
}
const { data: result } = await github.rest.checks.get({
...context.repo,
check_run_id: check[0].id,
});
return result.conclusion;
- name: Context
if: steps.check-conclusion.outputs.result == 'success'
uses: okteto/context@latest
with:
token: ${{ secrets.OKTETO_TOKEN }}
- name: Destroy preview environment
if: steps.check-conclusion.outputs.result == 'success'
uses: okteto/destroy-preview@latest
with:
name: pr-${{ github.event.number }}-syuilo

View File

@ -26,12 +26,12 @@ jobs:
NODE_OPTIONS: "--max_old_space_size=7168" NODE_OPTIONS: "--max_old_space_size=7168"
steps: steps:
- uses: actions/checkout@v4.1.1 - uses: actions/checkout@v4.2.2
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.1.1 - uses: actions/checkout@v4.2.2
if: github.event_name == 'pull_request_target' if: github.event_name == 'pull_request_target'
with: with:
fetch-depth: 0 fetch-depth: 0
@ -46,7 +46,7 @@ jobs:
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@v4 uses: pnpm/action-setup@v4
- name: Use Node.js 20.x - name: Use Node.js 20.x
uses: actions/setup-node@v4.1.0 uses: actions/setup-node@v4.2.0
with: with:
node-version-file: '.node-version' node-version-file: '.node-version'
cache: 'pnpm' cache: 'pnpm'

View File

@ -45,7 +45,7 @@ jobs:
- 56312:6379 - 56312:6379
steps: steps:
- uses: actions/checkout@v4.1.1 - uses: actions/checkout@v4.2.2
with: with:
submodules: true submodules: true
- name: Install pnpm - name: Install pnpm
@ -66,7 +66,7 @@ jobs:
fi fi
done done
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.1.0 uses: actions/setup-node@v4.2.0
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
cache: 'pnpm' cache: 'pnpm'
@ -108,13 +108,13 @@ jobs:
- 56312:6379 - 56312:6379
steps: steps:
- uses: actions/checkout@v4.1.1 - uses: actions/checkout@v4.2.2
with: with:
submodules: true submodules: true
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@v4 uses: pnpm/action-setup@v4
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.1.0 uses: actions/setup-node@v4.2.0
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
cache: 'pnpm' cache: 'pnpm'

View File

@ -47,7 +47,7 @@ jobs:
fi fi
done done
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.1.0 uses: actions/setup-node@v4.2.0
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
cache: 'pnpm' cache: 'pnpm'

View File

@ -36,13 +36,13 @@ jobs:
node-version: [22.11.0] node-version: [22.11.0]
steps: steps:
- uses: actions/checkout@v4.1.1 - uses: actions/checkout@v4.2.2
with: with:
submodules: true submodules: true
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@v4 uses: pnpm/action-setup@v4
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.1.0 uses: actions/setup-node@v4.2.0
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
cache: 'pnpm' cache: 'pnpm'
@ -86,7 +86,7 @@ jobs:
- 56312:6379 - 56312:6379
steps: steps:
- uses: actions/checkout@v4.1.1 - uses: actions/checkout@v4.2.2
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
@ -98,7 +98,7 @@ jobs:
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@v4 uses: pnpm/action-setup@v4
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.1.0 uses: actions/setup-node@v4.2.0
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
cache: 'pnpm' cache: 'pnpm'

View File

@ -31,12 +31,12 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.2.2
- run: corepack enable - run: corepack enable
- name: Setup Node.js ${{ matrix.node-version }} - name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.1.0 uses: actions/setup-node@v4.2.0
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
cache: 'pnpm' cache: 'pnpm'

View File

@ -21,13 +21,13 @@ jobs:
node-version: [22.11.0] node-version: [22.11.0]
steps: steps:
- uses: actions/checkout@v4.1.1 - uses: actions/checkout@v4.2.2
with: with:
submodules: true submodules: true
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@v4 uses: pnpm/action-setup@v4
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.1.0 uses: actions/setup-node@v4.2.0
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
cache: 'pnpm' cache: 'pnpm'

View File

@ -25,13 +25,13 @@ jobs:
node-version: [22.11.0] node-version: [22.11.0]
steps: steps:
- uses: actions/checkout@v4.1.1 - uses: actions/checkout@v4.2.2
with: with:
submodules: true submodules: true
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@v4 uses: pnpm/action-setup@v4
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.1.0 uses: actions/setup-node@v4.2.0
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
cache: 'pnpm' cache: 'pnpm'

View File

@ -1,16 +1,31 @@
## Unreleased ## 2025.2.1
### General ### General
- - Feat: アクセストークン発行時に通知するように
- 依存関係の更新
### Client ### Client
- Feat: 投稿フォームで画像をプレビュー可能に
- Enhance: 投稿フォームの「迷惑になる可能性があります」のダイアログを表示する条件においてCWを考慮するように - Enhance: 投稿フォームの「迷惑になる可能性があります」のダイアログを表示する条件においてCWを考慮するように
- Enhance: アンテナ、リスト等の名前をカラム名のデフォルト値にするように `#13992` - Enhance: アンテナ、リスト等の名前をカラム名のデフォルト値にするように `#13992`
- Enhance: クライアントエラー画面の多言語対応
- Enhance: 開発者モードでメニューからファイルIDをコピー出来るように `#15441'
- Enhance: ノートに埋め込まれたメディアのコンテキストメニューから管理者用のファイル管理画面を開けるように ( #15440 )
- Enhance: リアクションする際に確認ダイアログを表示できるように
- Enhance: CWの注釈で入力済みの文字数を表示
- Fix: コンディショナルロールを手動で割り当てできる導線を削除 `#13529`
- Fix: 埋め込みプレイヤーから外部ページに移動できない問題を修正
- Fix: Play の再読込時に UI が以前の状態を引き継いでしまう問題を修正 `#14378`
- Fix: カスタム絵文字管理画面(beta)にてisSensitive/localOnlyの絞り込みが上手くいかない問題の修正 ( #15445 )
- Fix: CWの注釈が100文字を超えている場合、ート投稿ボタンを非アクティブに
- Fix: デッキでリンクをダブルクリックすると、ウィンドウが2枚開いてしまう問題を修正 - Fix: デッキでリンクをダブルクリックすると、ウィンドウが2枚開いてしまう問題を修正
### Server ### Server
- - Fix: `following/invalidate`でフォロワーを解除しようとしているユーザーの情報を返すように
- Fix: オブジェクトストレージの設定でPrefixを設定していなかった場合nullまたは空文字になる問題を修正
- Fix: pgroongaでの検索時にはじめのキーワードのみが検索に使用される問題を修正
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/886)
- Fix: メールアドレスの形式が正しくなければ以降の処理を行わないように
## 2025.2.0 ## 2025.2.0

62
locales/index.d.ts vendored
View File

@ -5254,6 +5254,14 @@ export interface Locale extends ILocale {
* *
*/ */
"federationDisabled": string; "federationDisabled": string;
/**
*
*/
"confirmOnReact": string;
/**
* " {emoji} "
*/
"reactAreYouSure": ParameterizedString<"emoji">;
"_accountSettings": { "_accountSettings": {
/** /**
* *
@ -9472,6 +9480,14 @@ export interface Locale extends ILocale {
* *
*/ */
"login": string; "login": string;
/**
*
*/
"createToken": string;
/**
* {text}
*/
"createTokenDescription": ParameterizedString<"text">;
"_types": { "_types": {
/** /**
* *
@ -10944,6 +10960,52 @@ export interface Locale extends ILocale {
}; };
}; };
}; };
"_bootErrors": {
/**
*
*/
"title": string;
/**
* Error IDを添えてサーバー管理者に連絡してください
*/
"serverError": string;
/**
*
*/
"solution": string;
/**
* OSを最新バージョンに更新する
*/
"solution1": string;
/**
*
*/
"solution2": string;
/**
*
*/
"solution3": string;
/**
* (Tor Browser) dom.webaudio.enabledをtrueに設定する
*/
"solution4": string;
/**
*
*/
"otherOption": string;
/**
*
*/
"otherOption1": string;
/**
*
*/
"otherOption2": string;
/**
*
*/
"otherOption3": string;
};
} }
declare const locales: { declare const locales: {
[lang: string]: Locale; [lang: string]: Locale;

View File

@ -1309,6 +1309,8 @@ availableRoles: "利用可能なロール"
acknowledgeNotesAndEnable: "注意事項を理解した上でオンにします。" acknowledgeNotesAndEnable: "注意事項を理解した上でオンにします。"
federationSpecified: "このサーバーはホワイトリスト連合で運用されています。管理者が指定したサーバー以外とやり取りすることはできません。" federationSpecified: "このサーバーはホワイトリスト連合で運用されています。管理者が指定したサーバー以外とやり取りすることはできません。"
federationDisabled: "このサーバーは連合が無効化されています。他のサーバーのユーザーとやり取りすることはできません。" federationDisabled: "このサーバーは連合が無効化されています。他のサーバーのユーザーとやり取りすることはできません。"
confirmOnReact: "リアクションする際に確認する"
reactAreYouSure: "\" {emoji} \" をリアクションしますか?"
_accountSettings: _accountSettings:
requireSigninToViewContents: "コンテンツの表示にログインを必須にする" requireSigninToViewContents: "コンテンツの表示にログインを必須にする"
@ -2500,6 +2502,8 @@ _notification:
flushNotification: "通知の履歴をリセットする" flushNotification: "通知の履歴をリセットする"
exportOfXCompleted: "{x}のエクスポートが完了しました" exportOfXCompleted: "{x}のエクスポートが完了しました"
login: "ログインがありました" login: "ログインがありました"
createToken: "アクセストークンが作成されました"
createTokenDescription: "心当たりがない場合は「{text}」を通じてアクセストークンを削除してください。"
_types: _types:
all: "すべて" all: "すべて"
@ -2927,3 +2931,16 @@ _captcha:
_unknown: _unknown:
title: "CAPTCHAエラー" title: "CAPTCHAエラー"
text: "想定外のエラーが発生しました。" text: "想定外のエラーが発生しました。"
_bootErrors:
title: "読み込みに失敗しました"
serverError: "少し待ってからリロードしてもまだ問題が解決されない場合、以下のError IDを添えてサーバー管理者に連絡してください。"
solution: "以下を行うと解決する可能性があります。"
solution1: "ブラウザおよびOSを最新バージョンに更新する"
solution2: "アドブロッカーを無効にする"
solution3: "ブラウザのキャッシュをクリアする"
solution4: "(Tor Browser) dom.webaudio.enabledをtrueに設定する"
otherOption: "その他のオプション"
otherOption1: "クライアント設定とキャッシュを削除"
otherOption2: "簡易クライアントを起動"
otherOption3: "修復ツールを起動"

View File

@ -1,12 +1,12 @@
{ {
"name": "misskey", "name": "misskey",
"version": "2025.2.0", "version": "2025.2.1-alpha.0",
"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@9.6.0", "packageManager": "pnpm@9.15.4",
"workspaces": [ "workspaces": [
"packages/frontend-shared", "packages/frontend-shared",
"packages/frontend", "packages/frontend",
@ -47,35 +47,35 @@
"cleanall": "pnpm clean-all" "cleanall": "pnpm clean-all"
}, },
"resolutions": { "resolutions": {
"chokidar": "3.5.3", "chokidar": "3.6.0",
"lodash": "4.17.21" "lodash": "4.17.21"
}, },
"dependencies": { "dependencies": {
"cssnano": "6.1.2", "cssnano": "7.0.6",
"execa": "8.0.1", "execa": "8.0.1",
"fast-glob": "3.3.2", "fast-glob": "3.3.3",
"ignore-walk": "6.0.5", "ignore-walk": "6.0.5",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
"postcss": "8.4.49", "postcss": "8.5.2",
"tar": "6.2.1", "tar": "6.2.1",
"terser": "5.36.0", "terser": "5.39.0",
"typescript": "5.6.3", "typescript": "5.7.3",
"esbuild": "0.24.0", "esbuild": "0.25.0",
"glob": "11.0.0" "glob": "11.0.1"
}, },
"devDependencies": { "devDependencies": {
"@misskey-dev/eslint-plugin": "2.0.3", "@misskey-dev/eslint-plugin": "2.1.0",
"@types/node": "22.9.0", "@types/node": "22.13.4",
"@typescript-eslint/eslint-plugin": "7.17.0", "@typescript-eslint/eslint-plugin": "8.24.0",
"@typescript-eslint/parser": "7.17.0", "@typescript-eslint/parser": "8.24.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"cypress": "13.15.2", "cypress": "14.0.3",
"eslint": "9.14.0", "eslint": "9.20.1",
"globals": "15.12.0", "globals": "15.15.0",
"ncp": "2.0.0", "ncp": "2.0.0",
"start-server-and-test": "2.0.8" "start-server-and-test": "2.0.10"
}, },
"optionalDependencies": { "optionalDependencies": {
"@tensorflow/tfjs-core": "4.4.0" "@tensorflow/tfjs-core": "4.22.0"
} }
} }

View File

@ -37,20 +37,20 @@
}, },
"optionalDependencies": { "optionalDependencies": {
"@swc/core-android-arm64": "1.3.11", "@swc/core-android-arm64": "1.3.11",
"@swc/core-darwin-arm64": "1.3.56", "@swc/core-darwin-arm64": "1.10.16",
"@swc/core-darwin-x64": "1.3.56", "@swc/core-darwin-x64": "1.10.16",
"@swc/core-freebsd-x64": "1.3.11", "@swc/core-freebsd-x64": "1.3.11",
"@swc/core-linux-arm-gnueabihf": "1.3.56", "@swc/core-linux-arm-gnueabihf": "1.10.16",
"@swc/core-linux-arm64-gnu": "1.3.56", "@swc/core-linux-arm64-gnu": "1.10.16",
"@swc/core-linux-arm64-musl": "1.3.56", "@swc/core-linux-arm64-musl": "1.10.16",
"@swc/core-linux-x64-gnu": "1.3.56", "@swc/core-linux-x64-gnu": "1.10.16",
"@swc/core-linux-x64-musl": "1.3.56", "@swc/core-linux-x64-musl": "1.10.16",
"@swc/core-win32-arm64-msvc": "1.3.56", "@swc/core-win32-arm64-msvc": "1.10.16",
"@swc/core-win32-ia32-msvc": "1.3.56", "@swc/core-win32-ia32-msvc": "1.10.16",
"@swc/core-win32-x64-msvc": "1.3.56", "@swc/core-win32-x64-msvc": "1.10.16",
"@tensorflow/tfjs": "4.4.0", "@tensorflow/tfjs": "4.22.0",
"@tensorflow/tfjs-node": "4.4.0", "@tensorflow/tfjs-node": "4.22.0",
"bufferutil": "4.0.7", "bufferutil": "4.0.9",
"slacc-android-arm-eabi": "0.0.10", "slacc-android-arm-eabi": "0.0.10",
"slacc-android-arm64": "0.0.10", "slacc-android-arm64": "0.0.10",
"slacc-darwin-arm64": "0.0.10", "slacc-darwin-arm64": "0.0.10",
@ -64,37 +64,37 @@
"slacc-linux-x64-musl": "0.0.10", "slacc-linux-x64-musl": "0.0.10",
"slacc-win32-arm64-msvc": "0.0.10", "slacc-win32-arm64-msvc": "0.0.10",
"slacc-win32-x64-msvc": "0.0.10", "slacc-win32-x64-msvc": "0.0.10",
"utf-8-validate": "6.0.3" "utf-8-validate": "6.0.5"
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "3.620.0", "@aws-sdk/client-s3": "3.749.0",
"@aws-sdk/lib-storage": "3.620.0", "@aws-sdk/lib-storage": "3.749.0",
"@bull-board/api": "6.5.0", "@bull-board/api": "6.7.7",
"@bull-board/fastify": "6.5.0", "@bull-board/fastify": "6.7.7",
"@bull-board/ui": "6.5.0", "@bull-board/ui": "6.7.7",
"@discordapp/twemoji": "15.1.0", "@discordapp/twemoji": "15.1.0",
"@fastify/accepts": "5.0.1", "@fastify/accepts": "5.0.2",
"@fastify/cookie": "11.0.1", "@fastify/cookie": "11.0.2",
"@fastify/cors": "10.0.1", "@fastify/cors": "10.0.2",
"@fastify/express": "4.0.1", "@fastify/express": "4.0.2",
"@fastify/http-proxy": "10.0.1", "@fastify/http-proxy": "10.0.2",
"@fastify/multipart": "9.0.1", "@fastify/multipart": "9.0.3",
"@fastify/static": "8.0.2", "@fastify/static": "8.1.0",
"@fastify/view": "10.0.1", "@fastify/view": "10.0.2",
"@misskey-dev/sharp-read-bmp": "1.2.0", "@misskey-dev/sharp-read-bmp": "1.2.0",
"@misskey-dev/summaly": "5.1.0", "@misskey-dev/summaly": "5.2.0",
"@napi-rs/canvas": "0.1.56", "@napi-rs/canvas": "0.1.67",
"@nestjs/common": "10.4.7", "@nestjs/common": "11.0.9",
"@nestjs/core": "10.4.7", "@nestjs/core": "11.0.9",
"@nestjs/testing": "10.4.7", "@nestjs/testing": "11.0.9",
"@peertube/http-signature": "1.7.0", "@peertube/http-signature": "1.7.0",
"@sentry/node": "8.38.0", "@sentry/node": "8.55.0",
"@sentry/profiling-node": "8.38.0", "@sentry/profiling-node": "8.55.0",
"@simplewebauthn/server": "10.0.1", "@simplewebauthn/server": "12.0.0",
"@sinonjs/fake-timers": "11.2.2", "@sinonjs/fake-timers": "11.3.1",
"@smithy/node-http-handler": "2.5.0", "@smithy/node-http-handler": "2.5.0",
"@swc/cli": "0.3.12", "@swc/cli": "0.6.0",
"@swc/core": "1.9.2", "@swc/core": "1.10.16",
"@twemoji/parser": "15.1.1", "@twemoji/parser": "15.1.1",
"accepts": "1.3.8", "accepts": "1.3.8",
"ajv": "8.17.1", "ajv": "8.17.1",
@ -103,10 +103,10 @@
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"blurhash": "2.0.5", "blurhash": "2.0.5",
"body-parser": "1.20.3", "body-parser": "1.20.3",
"bullmq": "5.26.1", "bullmq": "5.41.1",
"cacheable-lookup": "7.0.0", "cacheable-lookup": "7.0.0",
"cbor": "9.0.2", "cbor": "9.0.2",
"chalk": "5.3.0", "chalk": "5.4.1",
"chalk-template": "1.1.0", "chalk-template": "1.1.0",
"chokidar": "3.6.0", "chokidar": "3.6.0",
"cli-highlight": "2.1.11", "cli-highlight": "2.1.11",
@ -114,46 +114,46 @@
"content-disposition": "0.5.4", "content-disposition": "0.5.4",
"date-fns": "2.30.0", "date-fns": "2.30.0",
"deep-email-validator": "0.1.21", "deep-email-validator": "0.1.21",
"fastify": "5.0.0", "fastify": "5.2.1",
"fastify-raw-body": "5.0.0", "fastify-raw-body": "5.0.0",
"feed": "4.2.2", "feed": "4.2.2",
"file-type": "19.6.0", "file-type": "19.6.0",
"fluent-ffmpeg": "2.1.3", "fluent-ffmpeg": "2.1.3",
"form-data": "4.0.1", "form-data": "4.0.2",
"got": "14.4.4", "got": "14.4.6",
"happy-dom": "15.11.4", "happy-dom": "16.8.1",
"hpagent": "1.2.0", "hpagent": "1.2.0",
"htmlescape": "1.1.1", "htmlescape": "1.1.1",
"http-link-header": "1.1.3", "http-link-header": "1.1.3",
"ioredis": "5.4.1", "ioredis": "5.5.0",
"ip-cidr": "4.0.2", "ip-cidr": "4.0.2",
"ipaddr.js": "2.2.0", "ipaddr.js": "2.2.0",
"is-svg": "5.1.0", "is-svg": "5.1.0",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
"jsdom": "24.1.1", "jsdom": "26.0.0",
"json5": "2.2.3", "json5": "2.2.3",
"jsonld": "8.3.2", "jsonld": "8.3.3",
"jsrsasign": "11.1.0", "jsrsasign": "11.1.0",
"juice": "11.0.0", "juice": "11.0.0",
"meilisearch": "0.45.0", "meilisearch": "0.48.2",
"mfm-js": "0.24.0", "mfm-js": "0.24.0",
"microformats-parser": "2.0.2", "microformats-parser": "2.0.2",
"mime-types": "2.1.35", "mime-types": "2.1.35",
"misskey-js": "workspace:*", "misskey-js": "workspace:*",
"misskey-reversi": "workspace:*", "misskey-reversi": "workspace:*",
"ms": "3.0.0-canary.1", "ms": "3.0.0-canary.1",
"nanoid": "5.0.8", "nanoid": "5.1.0",
"nested-property": "4.0.0", "nested-property": "4.0.0",
"node-fetch": "3.3.2", "node-fetch": "3.3.2",
"nodemailer": "6.9.16", "nodemailer": "6.10.0",
"nsfwjs": "4.2.0", "nsfwjs": "4.2.0",
"oauth": "0.10.0", "oauth": "0.10.0",
"oauth2orize": "1.12.0", "oauth2orize": "1.12.0",
"oauth2orize-pkce": "0.1.2", "oauth2orize-pkce": "0.1.2",
"os-utils": "0.0.14", "os-utils": "0.0.14",
"otpauth": "9.3.4", "otpauth": "9.3.6",
"parse5": "7.2.1", "parse5": "7.2.1",
"pg": "8.13.1", "pg": "8.13.3",
"pkce-challenge": "4.1.0", "pkce-challenge": "4.1.0",
"probe-image-size": "7.2.3", "probe-image-size": "7.2.3",
"promise-limit": "2.7.0", "promise-limit": "2.7.0",
@ -167,19 +167,19 @@
"rename": "1.0.4", "rename": "1.0.4",
"rss-parser": "3.13.0", "rss-parser": "3.13.0",
"rxjs": "7.8.1", "rxjs": "7.8.1",
"sanitize-html": "2.13.1", "sanitize-html": "2.14.0",
"secure-json-parse": "2.7.0", "secure-json-parse": "3.0.2",
"sharp": "0.33.5", "sharp": "0.33.5",
"slacc": "0.0.10", "slacc": "0.0.10",
"strict-event-emitter-types": "2.0.0", "strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0", "stringz": "2.1.0",
"systeminformation": "5.23.5", "systeminformation": "5.25.11",
"tinycolor2": "1.6.0", "tinycolor2": "1.6.0",
"tmp": "0.2.3", "tmp": "0.2.3",
"tsc-alias": "1.8.10", "tsc-alias": "1.8.10",
"tsconfig-paths": "4.2.0", "tsconfig-paths": "4.2.0",
"typeorm": "0.3.20", "typeorm": "0.3.20",
"typescript": "5.6.3", "typescript": "5.7.3",
"ulid": "2.3.0", "ulid": "2.3.0",
"vary": "1.1.2", "vary": "1.1.2",
"web-push": "3.6.7", "web-push": "3.6.7",
@ -188,8 +188,8 @@
}, },
"devDependencies": { "devDependencies": {
"@jest/globals": "29.7.0", "@jest/globals": "29.7.0",
"@nestjs/platform-express": "10.4.7", "@nestjs/platform-express": "10.4.15",
"@simplewebauthn/types": "10.0.0", "@simplewebauthn/types": "12.0.0",
"@swc/jest": "0.2.37", "@swc/jest": "0.2.37",
"@types/accepts": "1.3.7", "@types/accepts": "1.3.7",
"@types/archiver": "6.0.3", "@types/archiver": "6.0.3",
@ -204,15 +204,15 @@
"@types/js-yaml": "4.0.9", "@types/js-yaml": "4.0.9",
"@types/jsdom": "21.1.7", "@types/jsdom": "21.1.7",
"@types/jsonld": "1.5.15", "@types/jsonld": "1.5.15",
"@types/jsrsasign": "10.5.14", "@types/jsrsasign": "10.5.15",
"@types/mime-types": "2.1.4", "@types/mime-types": "2.1.4",
"@types/ms": "0.7.34", "@types/ms": "0.7.34",
"@types/node": "22.9.0", "@types/node": "22.13.4",
"@types/nodemailer": "6.4.16", "@types/nodemailer": "6.4.17",
"@types/oauth": "0.9.6", "@types/oauth": "0.9.6",
"@types/oauth2orize": "1.11.5", "@types/oauth2orize": "1.11.5",
"@types/oauth2orize-pkce": "0.1.2", "@types/oauth2orize-pkce": "0.1.2",
"@types/pg": "8.11.10", "@types/pg": "8.11.11",
"@types/pug": "2.0.10", "@types/pug": "2.0.10",
"@types/qrcode": "1.5.5", "@types/qrcode": "1.5.5",
"@types/random-seed": "0.3.5", "@types/random-seed": "0.3.5",
@ -226,18 +226,18 @@
"@types/tmp": "0.2.6", "@types/tmp": "0.2.6",
"@types/vary": "1.1.3", "@types/vary": "1.1.3",
"@types/web-push": "3.6.4", "@types/web-push": "3.6.4",
"@types/ws": "8.5.13", "@types/ws": "8.5.14",
"@typescript-eslint/eslint-plugin": "7.17.0", "@typescript-eslint/eslint-plugin": "8.24.0",
"@typescript-eslint/parser": "7.17.0", "@typescript-eslint/parser": "8.24.0",
"aws-sdk-client-mock": "4.0.1", "aws-sdk-client-mock": "4.1.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"eslint-plugin-import": "2.30.0", "eslint-plugin-import": "2.31.0",
"execa": "8.0.1", "execa": "8.0.1",
"fkill": "9.0.0", "fkill": "9.0.0",
"jest": "29.7.0", "jest": "29.7.0",
"jest-mock": "29.7.0", "jest-mock": "29.7.0",
"nodemon": "3.1.7", "nodemon": "3.1.9",
"pid-port": "1.0.0", "pid-port": "1.0.2",
"simple-oauth2": "5.1.0" "simple-oauth2": "5.1.0"
} }
} }

View File

@ -105,8 +105,8 @@ type Source = {
logging?: { logging?: {
sql?: { sql?: {
disableQueryTruncation? : boolean, disableQueryTruncation?: boolean,
enableQueryParamLogging? : boolean, enableQueryParamLogging?: boolean,
} }
} }
}; };
@ -166,8 +166,8 @@ export type Config = {
signToActivityPubGet: boolean | undefined; signToActivityPubGet: boolean | undefined;
logging?: { logging?: {
sql?: { sql?: {
disableQueryTruncation? : boolean, disableQueryTruncation?: boolean,
enableQueryParamLogging? : boolean, enableQueryParamLogging?: boolean,
} }
} }

View File

@ -43,7 +43,7 @@ export type CaptchaSetting = {
siteKey: string | null; siteKey: string | null;
secretKey: string | null; secretKey: string | null;
} }
} };
export class CaptchaError extends Error { export class CaptchaError extends Error {
public readonly code: CaptchaErrorCode; public readonly code: CaptchaErrorCode;
@ -59,11 +59,11 @@ export class CaptchaError extends Error {
export type CaptchaSaveSuccess = { export type CaptchaSaveSuccess = {
success: true; success: true;
} };
export type CaptchaSaveFailure = { export type CaptchaSaveFailure = {
success: false; success: false;
error: CaptchaError; error: CaptchaError;
} };
export type CaptchaSaveResult = CaptchaSaveSuccess | CaptchaSaveFailure; export type CaptchaSaveResult = CaptchaSaveSuccess | CaptchaSaveFailure;
type CaptchaResponse = { type CaptchaResponse = {

View File

@ -173,7 +173,8 @@ export class DriveService {
?? `${ this.meta.objectStorageUseSSL ? 'https' : 'http' }://${ this.meta.objectStorageEndpoint }${ this.meta.objectStoragePort ? `:${this.meta.objectStoragePort}` : '' }/${ this.meta.objectStorageBucket }`; ?? `${ this.meta.objectStorageUseSSL ? 'https' : 'http' }://${ this.meta.objectStorageEndpoint }${ this.meta.objectStoragePort ? `:${this.meta.objectStoragePort}` : '' }/${ this.meta.objectStorageBucket }`;
// for original // for original
const key = `${this.meta.objectStoragePrefix}/${randomUUID()}${ext}`; const prefix = this.meta.objectStoragePrefix ? `${this.meta.objectStoragePrefix}/` : '';
const key = `${prefix}${randomUUID()}${ext}`;
const url = `${ baseUrl }/${ key }`; const url = `${ baseUrl }/${ key }`;
// for alts // for alts
@ -190,7 +191,7 @@ export class DriveService {
]; ];
if (alts.webpublic) { if (alts.webpublic) {
webpublicKey = `${this.meta.objectStoragePrefix}/webpublic-${randomUUID()}.${alts.webpublic.ext}`; webpublicKey = `${prefix}webpublic-${randomUUID()}.${alts.webpublic.ext}`;
webpublicUrl = `${ baseUrl }/${ webpublicKey }`; webpublicUrl = `${ baseUrl }/${ webpublicKey }`;
this.registerLogger.info(`uploading webpublic: ${webpublicKey}`); this.registerLogger.info(`uploading webpublic: ${webpublicKey}`);
@ -198,7 +199,7 @@ export class DriveService {
} }
if (alts.thumbnail) { if (alts.thumbnail) {
thumbnailKey = `${this.meta.objectStoragePrefix}/thumbnail-${randomUUID()}.${alts.thumbnail.ext}`; thumbnailKey = `${prefix}thumbnail-${randomUUID()}.${alts.thumbnail.ext}`;
thumbnailUrl = `${ baseUrl }/${ thumbnailKey }`; thumbnailUrl = `${ baseUrl }/${ thumbnailKey }`;
this.registerLogger.info(`uploading thumbnail: ${thumbnailKey}`); this.registerLogger.info(`uploading thumbnail: ${thumbnailKey}`);

View File

@ -164,6 +164,13 @@ export class EmailService {
available: boolean; available: boolean;
reason: null | 'used' | 'format' | 'disposable' | 'mx' | 'smtp' | 'banned' | 'network' | 'blacklist'; reason: null | 'used' | 'format' | 'disposable' | 'mx' | 'smtp' | 'banned' | 'network' | 'blacklist';
}> { }> {
if (!this.utilityService.validateEmailFormat(emailAddress)) {
return {
available: false,
reason: 'format',
};
}
const exist = await this.userProfilesRepository.countBy({ const exist = await this.userProfilesRepository.countBy({
emailVerified: true, emailVerified: true,
email: emailAddress, email: emailAddress,

View File

@ -9,7 +9,7 @@ import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { IdService } from '@/core/IdService.js'; import { IdService } from '@/core/IdService.js';
export type FanoutTimelineName = export type FanoutTimelineName = (
// home timeline // home timeline
| `homeTimeline:${string}` | `homeTimeline:${string}`
| `homeTimelineWithFiles:${string}` // only notes with files are included | `homeTimelineWithFiles:${string}` // only notes with files are included
@ -37,6 +37,7 @@ export type FanoutTimelineName =
// role timelines // role timelines
| `roleTimeline:${string}` // any notes are included | `roleTimeline:${string}` // any notes are included
);
@Injectable() @Injectable()
export class FanoutTimelineService { export class FanoutTimelineService {

View File

@ -211,7 +211,7 @@ type SerializedAll<T> = {
type UndefinedAsNullAll<T> = { type UndefinedAsNullAll<T> = {
[K in keyof T]: T[K] extends undefined ? null : T[K]; [K in keyof T]: T[K] extends undefined ? null : T[K];
} };
export interface InternalEventTypes { export interface InternalEventTypes {
userChangeSuspendedState: { id: MiUser['id']; isSuspended: MiUser['isSuspended']; }; userChangeSuspendedState: { id: MiUser['id']; isSuspended: MiUser['isSuspended']; };

View File

@ -492,7 +492,8 @@ export class MfmService {
appendChildren(nodes, body); appendChildren(nodes, body);
const serialized = new XMLSerializer().serializeToString(body); // Remove the unnecessary namespace
const serialized = new XMLSerializer().serializeToString(body).replace(/^\s*<p xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">/, '<p>');
happyDOM.close().catch(err => {}); happyDOM.close().catch(err => {});

View File

@ -220,7 +220,7 @@ export class SearchService {
.leftJoinAndSelect('renote.user', 'renoteUser'); .leftJoinAndSelect('renote.user', 'renoteUser');
if (this.config.fulltextSearch?.provider === 'sqlPgroonga') { if (this.config.fulltextSearch?.provider === 'sqlPgroonga') {
query.andWhere('note.text &@ :q', { q }); query.andWhere('note.text &@~ :q', { q });
} else { } else {
query.andWhere('LOWER(note.text) LIKE :q', { q: `%${ sqlLikeEscape(q.toLowerCase()) }%` }); query.andWhere('LOWER(note.text) LIKE :q', { q: `%${ sqlLikeEscape(q.toLowerCase()) }%` });
} }

View File

@ -15,7 +15,7 @@ import { QueueService } from '@/core/QueueService.js';
import type { OnApplicationShutdown } from '@nestjs/common'; import type { OnApplicationShutdown } from '@nestjs/common';
export type UserWebhookPayload<T extends WebhookEventTypes> = export type UserWebhookPayload<T extends WebhookEventTypes> =
T extends 'note' | 'reply' | 'renote' |'mention' ? { T extends 'note' | 'reply' | 'renote' | 'mention' ? {
note: Packed<'Note'>, note: Packed<'Note'>,
} : } :
T extends 'follow' | 'unfollow' ? { T extends 'follow' | 'unfollow' ? {

View File

@ -38,6 +38,14 @@ export class UtilityService {
return this.punyHost(uri) === this.toPuny(this.config.host); return this.punyHost(uri) === this.toPuny(this.config.host);
} }
// メールアドレスのバリデーションを行う
// https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address
@bindThis
public validateEmailFormat(email: string): boolean {
const regexp = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
return regexp.test(email);
}
@bindThis @bindThis
public isBlockedHost(blockedHosts: string[], host: string | null): boolean { public isBlockedHost(blockedHosts: string[], host: string | null): boolean {
if (host == null) return false; if (host == null) return false;

View File

@ -127,11 +127,11 @@ export class WebAuthnService {
const { registrationInfo } = verification; const { registrationInfo } = verification;
return { return {
credentialID: registrationInfo.credentialID, credentialID: registrationInfo.credential.id,
credentialPublicKey: registrationInfo.credentialPublicKey, credentialPublicKey: registrationInfo.credential.publicKey,
attestationObject: registrationInfo.attestationObject, attestationObject: registrationInfo.attestationObject,
fmt: registrationInfo.fmt, fmt: registrationInfo.fmt,
counter: registrationInfo.counter, counter: registrationInfo.credential.counter,
userVerified: registrationInfo.userVerified, userVerified: registrationInfo.userVerified,
credentialDeviceType: registrationInfo.credentialDeviceType, credentialDeviceType: registrationInfo.credentialDeviceType,
credentialBackedUp: registrationInfo.credentialBackedUp, credentialBackedUp: registrationInfo.credentialBackedUp,
@ -212,9 +212,9 @@ export class WebAuthnService {
expectedChallenge: challenge, expectedChallenge: challenge,
expectedOrigin: relyingParty.origin, expectedOrigin: relyingParty.origin,
expectedRPID: relyingParty.rpId, expectedRPID: relyingParty.rpId,
authenticator: { credential: {
credentialID: key.id, id: key.id,
credentialPublicKey: Buffer.from(key.publicKey, 'base64url'), publicKey: Buffer.from(key.publicKey, 'base64url'),
counter: key.counter, counter: key.counter,
transports: key.transports ? key.transports as AuthenticatorTransportFuture[] : undefined, transports: key.transports ? key.transports as AuthenticatorTransportFuture[] : undefined,
}, },
@ -292,9 +292,9 @@ export class WebAuthnService {
expectedChallenge: challenge, expectedChallenge: challenge,
expectedOrigin: relyingParty.origin, expectedOrigin: relyingParty.origin,
expectedRPID: relyingParty.rpId, expectedRPID: relyingParty.rpId,
authenticator: { credential: {
credentialID: key.id, id: key.id,
credentialPublicKey: Buffer.from(key.publicKey, 'base64url'), publicKey: Buffer.from(key.publicKey, 'base64url'),
counter: key.counter, counter: key.counter,
transports: key.transports ? key.transports as AuthenticatorTransportFuture[] : undefined, transports: key.transports ? key.transports as AuthenticatorTransportFuture[] : undefined,
}, },

View File

@ -57,12 +57,14 @@ const ajv = new Ajv();
function isLocalUser(user: MiUser): user is MiLocalUser; function isLocalUser(user: MiUser): user is MiLocalUser;
function isLocalUser<T extends { host: MiUser['host'] }>(user: T): user is (T & { host: null; }); function isLocalUser<T extends { host: MiUser['host'] }>(user: T): user is (T & { host: null; });
function isLocalUser(user: MiUser | { host: MiUser['host'] }): boolean { function isLocalUser(user: MiUser | { host: MiUser['host'] }): boolean {
return user.host == null; return user.host == null;
} }
function isRemoteUser(user: MiUser): user is MiRemoteUser; function isRemoteUser(user: MiUser): user is MiRemoteUser;
function isRemoteUser<T extends { host: MiUser['host'] }>(user: T): user is (T & { host: string; }); function isRemoteUser<T extends { host: MiUser['host'] }>(user: T): user is (T & { host: string; });
function isRemoteUser(user: MiUser | { host: MiUser['host'] }): boolean { function isRemoteUser(user: MiUser | { host: MiUser['host'] }): boolean {
return !isLocalUser(user); return !isLocalUser(user);
} }
@ -78,7 +80,7 @@ export type UserRelation = {
isBlocked: boolean isBlocked: boolean
isMuted: boolean isMuted: boolean
isRenoteMuted: boolean isRenoteMuted: boolean
} };
@Injectable() @Injectable()
export class UserEntityService implements OnModuleInit { export class UserEntityService implements OnModuleInit {

View File

@ -143,7 +143,7 @@ type OfSchema = {
readonly anyOf?: ReadonlyArray<Schema>; readonly anyOf?: ReadonlyArray<Schema>;
readonly oneOf?: ReadonlyArray<Schema>; readonly oneOf?: ReadonlyArray<Schema>;
readonly allOf?: ReadonlyArray<Schema>; readonly allOf?: ReadonlyArray<Schema>;
} };
export interface Schema extends OfSchema { export interface Schema extends OfSchema {
readonly type?: TypeStringef; readonly type?: TypeStringef;
@ -217,7 +217,7 @@ type ObjectSchemaTypeDef<p extends Schema> =
: :
p['anyOf'] extends ReadonlyArray<Schema> ? never : // see CONTRIBUTING.md p['anyOf'] extends ReadonlyArray<Schema> ? never : // see CONTRIBUTING.md
p['allOf'] extends ReadonlyArray<Schema> ? UnionToIntersection<UnionSchemaType<p['allOf']>> : p['allOf'] extends ReadonlyArray<Schema> ? UnionToIntersection<UnionSchemaType<p['allOf']>> :
any any;
type ObjectSchemaType<p extends Schema> = NullOrUndefined<p, ObjectSchemaTypeDef<p>>; type ObjectSchemaType<p extends Schema> = NullOrUndefined<p, ObjectSchemaTypeDef<p>>;

View File

@ -4,7 +4,7 @@
*/ */
export type JsonValue = JsonArray | JsonObject | string | number | boolean | null; export type JsonValue = JsonArray | JsonObject | string | number | boolean | null;
export type JsonObject = {[K in string]?: JsonValue}; export type JsonObject = { [K in string]?: JsonValue };
export type JsonArray = JsonValue[]; export type JsonArray = JsonValue[];
export function isJsonObject(value: JsonValue | undefined): value is JsonObject { export function isJsonObject(value: JsonValue | undefined): value is JsonObject {

View File

@ -90,6 +90,10 @@ export type MiNotification = {
type: 'login'; type: 'login';
id: string; id: string;
createdAt: string; createdAt: string;
} | {
type: 'createToken';
id: string;
createdAt: string;
} | { } | {
type: 'app'; type: 'app';
id: string; id: string;

View File

@ -288,24 +288,24 @@ export class MiUser {
export type MiLocalUser = MiUser & { export type MiLocalUser = MiUser & {
host: null; host: null;
uri: null; uri: null;
} };
export type MiPartialLocalUser = Partial<MiUser> & { export type MiPartialLocalUser = Partial<MiUser> & {
id: MiUser['id']; id: MiUser['id'];
host: null; host: null;
uri: null; uri: null;
} };
export type MiRemoteUser = MiUser & { export type MiRemoteUser = MiUser & {
host: string; host: string;
uri: string; uri: string;
} };
export type MiPartialRemoteUser = Partial<MiUser> & { export type MiPartialRemoteUser = Partial<MiUser> & {
id: MiUser['id']; id: MiUser['id'];
host: string; host: string;
uri: string; uri: string;
} };
export const localUsernameSchema = { type: 'string', pattern: /^\w{1,20}$/.toString().slice(1, -1) } as const; export const localUsernameSchema = { type: 'string', pattern: /^\w{1,20}$/.toString().slice(1, -1) } as const;
export const passwordSchema = { type: 'string', minLength: 1 } as const; export const passwordSchema = { type: 'string', minLength: 1 } as const;

View File

@ -332,6 +332,16 @@ export const packedNotificationSchema = {
enum: ['login'], enum: ['login'],
}, },
}, },
}, {
type: 'object',
properties: {
...baseSchema.properties,
type: {
type: 'string',
optional: false, nullable: false,
enum: ['createToken'],
},
},
}, { }, {
type: 'object', type: 'object',
properties: { properties: {

View File

@ -92,7 +92,7 @@ const sqlLogger = dbLogger.createSubLogger('sql', 'gray');
export type LoggerProps = { export type LoggerProps = {
disableQueryTruncation?: boolean; disableQueryTruncation?: boolean;
enableQueryParamLogging?: boolean; enableQueryParamLogging?: boolean;
} };
function highlightSql(sql: string) { function highlightSql(sql: string) {
return highlight.highlight(sql, { return highlight.highlight(sql, {

View File

@ -29,7 +29,7 @@ export type ModeratorInactivityEvaluationResult = {
isModeratorsInactive: boolean; isModeratorsInactive: boolean;
inactiveModerators: MiUser[]; inactiveModerators: MiUser[];
remainingTime: ModeratorInactivityRemainingTime; remainingTime: ModeratorInactivityRemainingTime;
} };
export type ModeratorInactivityRemainingTime = { export type ModeratorInactivityRemainingTime = {
time: number; time: number;

View File

@ -38,7 +38,7 @@ export type RelationshipJobData = {
silent?: boolean; silent?: boolean;
requestId?: string; requestId?: string;
withReplies?: boolean; withReplies?: boolean;
} };
export type DbJobData<T extends keyof DbJobMap> = DbJobMap[T]; export type DbJobData<T extends keyof DbJobMap> = DbJobMap[T];
@ -61,11 +61,11 @@ export type DbJobMap = {
importUserLists: DbUserImportJobData; importUserLists: DbUserImportJobData;
importCustomEmojis: DbUserImportJobData; importCustomEmojis: DbUserImportJobData;
deleteAccount: DbUserDeleteJobData; deleteAccount: DbUserDeleteJobData;
} };
export type DbJobDataWithUser = { export type DbJobDataWithUser = {
user: ThinUser; user: ThinUser;
} };
export type DbExportFollowingData = { export type DbExportFollowingData = {
user: ThinUser; user: ThinUser;
@ -75,7 +75,7 @@ export type DbExportFollowingData = {
export type DBExportAntennasData = { export type DBExportAntennasData = {
user: ThinUser user: ThinUser
} };
export type DbUserDeleteJobData = { export type DbUserDeleteJobData = {
user: ThinUser; user: ThinUser;
@ -91,7 +91,7 @@ export type DbUserImportJobData = {
export type DBAntennaImportJobData = { export type DBAntennaImportJobData = {
user: ThinUser, user: ThinUser,
antenna: Antenna antenna: Antenna
} };
export type DbUserImportToDbJobData = { export type DbUserImportToDbJobData = {
user: ThinUser; user: ThinUser;

View File

@ -122,7 +122,7 @@ export type IEndpointMeta = (Omit<IEndpointMetaBase, 'requireCrential' | 'requir
}) | (Omit<IEndpointMetaBase, 'requireAdmin' | 'kind'> & { }) | (Omit<IEndpointMetaBase, 'requireAdmin' | 'kind'> & {
requireAdmin: true, requireAdmin: true,
kind: (typeof permissions)[number], kind: (typeof permissions)[number],
}) });
export interface IEndpoint { export interface IEndpoint {
name: string; name: string;

View File

@ -512,6 +512,7 @@ export const meta = {
}, },
federation: { federation: {
type: 'string', type: 'string',
enum: ['all', 'specified', 'none'],
optional: false, nullable: false, optional: false, nullable: false,
}, },
federationHosts: { federationHosts: {

View File

@ -96,7 +96,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
await this.userFollowingService.unfollow(follower, followee); await this.userFollowingService.unfollow(follower, followee);
return await this.userEntityService.pack(followee.id, me); return await this.userEntityService.pack(follower.id, me);
}); });
} }
} }

View File

@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js'; import { Endpoint } from '@/server/api/endpoint-base.js';
import type { AccessTokensRepository } from '@/models/_.js'; import type { AccessTokensRepository } from '@/models/_.js';
import { IdService } from '@/core/IdService.js'; import { IdService } from '@/core/IdService.js';
import { NotificationService } from '@/core/NotificationService.js';
import { secureRndstr } from '@/misc/secure-rndstr.js'; import { secureRndstr } from '@/misc/secure-rndstr.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
@ -50,6 +51,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private accessTokensRepository: AccessTokensRepository, private accessTokensRepository: AccessTokensRepository,
private idService: IdService, private idService: IdService,
private notificationService: NotificationService,
) { ) {
super(meta, paramDef, async (ps, me) => { super(meta, paramDef, async (ps, me) => {
// Generate access token // Generate access token
@ -71,6 +73,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
permission: ps.permission, permission: ps.permission,
}); });
// アクセストークンが生成されたことを通知
this.notificationService.createNotification(me.id, 'createToken', {});
return { return {
token: accessToken, token: accessToken,
}; };

View File

@ -210,9 +210,15 @@ export function genOpenapiSpec(config: Config, includeSelfRef = false) {
spec.paths['/' + endpoint.name] = { spec.paths['/' + endpoint.name] = {
...(endpoint.meta.allowGet ? { ...(endpoint.meta.allowGet ? {
get: info, get: {
...info,
operationId: 'get___' + info.operationId,
},
} : {}), } : {}),
post: info, post: {
...info,
operationId: 'post___' + info.operationId,
},
}; };
} }

View File

@ -82,8 +82,8 @@ export default abstract class Channel {
this.connection = connection; this.connection = connection;
} }
public send(payload: { type: string, body: JsonValue }): void public send(payload: { type: string, body: JsonValue }): void;
public send(type: string, payload: JsonValue): void public send(type: string, payload: JsonValue): void;
@bindThis @bindThis
public send(typeOrPayload: { type: string, body: JsonValue } | string, payload?: JsonValue) { public send(typeOrPayload: { type: string, body: JsonValue } | string, payload?: JsonValue) {
const type = payload === undefined ? (typeOrPayload as { type: string, body: JsonValue }).type : (typeOrPayload as string); const type = payload === undefined ? (typeOrPayload as { type: string, body: JsonValue }).type : (typeOrPayload as string);
@ -108,4 +108,4 @@ export type MiChannelService<T extends boolean> = {
requireCredential: T; requireCredential: T;
kind: T extends true ? string : string | null | undefined; kind: T extends true ? string : string | null | undefined;
create: (id: string, connection: Connection) => Channel; create: (id: string, connection: Connection) => Channel;
} };

View File

@ -114,13 +114,17 @@
if (document.readyState === 'loading') { if (document.readyState === 'loading') {
await new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve)); await new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve));
} }
const locale = JSON.parse(localStorage.getItem('locale') || '{}');
const title = locale?._bootErrors?.title || 'Failed to initialize Misskey';
const reload = locale?.reload || 'Reload';
document.body.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /><path d="M12 9v4" /><path d="M12 16v.01" /></svg> document.body.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /><path d="M12 9v4" /><path d="M12 16v.01" /></svg>
<div class="message">読み込みに失敗しました</div> <div class="message">${title}</div>
<div class="submessage">Failed to initialize Misskey</div>
<div class="submessage">Error Code: ${code}</div> <div class="submessage">Error Code: ${code}</div>
<button onclick="location.reload(!0)"> <button onclick="location.reload(!0)">
<div>リロード</div> <div>${reload}</div>
<div><small>Reload</small></div>
</button>`; </button>`;
addStyle(` addStyle(`
#misskey_app, #misskey_app,

View File

@ -151,6 +151,22 @@
await new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve)); await new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve));
} }
const locale = JSON.parse(localStorage.getItem('locale') || '{}');
const messages = Object.assign({
title: 'Failed to initialize Misskey',
solution: 'The following actions may solve the problem.',
solution1: 'Update your os and browser',
solution2: 'Disable an adblocker',
solution3: 'Clear the browser cache',
solution4: '(Tor Browser) Set dom.webaudio.enabled to true',
otherOption: 'Other options',
otherOption1: 'Clear preferences and cache',
otherOption2: 'Start the simple client',
otherOption3: 'Start the repair tool',
}, locale?._bootErrors || {});
const reload = locale?.reload || 'Reload';
let errorsElement = document.getElementById('errors'); let errorsElement = document.getElementById('errors');
if (!errorsElement) { if (!errorsElement) {
@ -160,32 +176,32 @@
<path d="M12 9v2m0 4v.01"></path> <path d="M12 9v2m0 4v.01"></path>
<path d="M5 19h14a2 2 0 0 0 1.84 -2.75l-7.1 -12.25a2 2 0 0 0 -3.5 0l-7.1 12.25a2 2 0 0 0 1.75 2.75"></path> <path d="M5 19h14a2 2 0 0 0 1.84 -2.75l-7.1 -12.25a2 2 0 0 0 -3.5 0l-7.1 12.25a2 2 0 0 0 1.75 2.75"></path>
</svg> </svg>
<h1>Failed to load<br>読み込みに失敗しました</h1> <h1>${messages.title}</h1>
<button class="button-big" onclick="location.reload(true);"> <button class="button-big" onclick="location.reload(true);">
<span class="button-label-big">Reload / リロード</span> <span class="button-label-big">${reload}</span>
</button> </button>
<p><b>The following actions may solve the problem. / 以下を行うと解決する可能性があります</b></p> <p><b>${messages.solution}</b></p>
<p>Update your os and browser / ブラウザおよびOSを最新バージョンに更新する</p> <p>${messages.solution1}</p>
<p>Disable an adblocker / アドブロッカーを無効にする</p> <p>${messages.solution2}</p>
<p>Clear the browser cache / ブラウザのキャッシュをクリアする</p> <p>${messages.solution3}</p>
<p>&#40;Tor Browser&#41; Set dom.webaudio.enabled to true / dom.webaudio.enabledをtrueに設定する</p> <p>${messages.solution4}</p>
<details style="color: #86b300;"> <details style="color: #86b300;">
<summary>Other options / その他のオプション</summary> <summary>${messages.otherOption}</summary>
<a href="/flush"> <a href="/flush">
<button class="button-small"> <button class="button-small">
<span class="button-label-small">Clear preferences and cache</span> <span class="button-label-small">${messages.otherOption1}</span>
</button> </button>
</a> </a>
<br> <br>
<a href="/cli"> <a href="/cli">
<button class="button-small"> <button class="button-small">
<span class="button-label-small">Start the simple client</span> <span class="button-label-small">${messages.otherOption2}</span>
</button> </button>
</a> </a>
<br> <br>
<a href="/bios"> <a href="/bios">
<button class="button-small"> <button class="button-small">
<span class="button-label-small">Start the repair tool</span> <span class="button-label-small">${messages.otherOption3}</span>
</button> </button>
</a> </a>
</details> </details>

View File

@ -18,6 +18,7 @@
* achievementEarned - * achievementEarned -
* exportCompleted - * exportCompleted -
* login - * login -
* createToken -
* app - * app -
* test - * test -
*/ */
@ -36,6 +37,7 @@ export const notificationTypes = [
'achievementEarned', 'achievementEarned',
'exportCompleted', 'exportCompleted',
'login', 'login',
'createToken',
'app', 'app',
'test', 'test',
] as const; ] as const;

View File

@ -22,7 +22,7 @@ export type LoginUser = SigninResponse & {
client: Misskey.api.APIClient; client: Misskey.api.APIClient;
username: string; username: string;
password: string; password: string;
} };
/** used for avoiding overload and some endpoints */ /** used for avoiding overload and some endpoints */
export type Request = < export type Request = <

View File

@ -1,5 +1,5 @@
{ {
"$schema": "https://json.schemastore.org/swcrc", "$schema": "https://swc.rs/schema.json",
"jsc": { "jsc": {
"parser": { "parser": {
"syntax": "typescript", "syntax": "typescript",

View File

@ -10,13 +10,13 @@ import { channel, clip, cookie, galleryPost, page, play, post, signup, simpleGet
import type { SimpleGetResponse } from '../utils.js'; import type { SimpleGetResponse } from '../utils.js';
import type * as misskey from 'misskey-js'; import type * as misskey from 'misskey-js';
// Request Accept // Request Accept in lowercase
const ONLY_AP = 'application/activity+json'; const ONLY_AP = 'application/activity+json';
const PREFER_AP = 'application/activity+json, */*'; const PREFER_AP = 'application/activity+json, */*';
const PREFER_HTML = 'text/html, */*'; const PREFER_HTML = 'text/html, */*';
const UNSPECIFIED = '*/*'; const UNSPECIFIED = '*/*';
// Response Content-Type // Response Content-Type in lowercase
const AP = 'application/activity+json; charset=utf-8'; const AP = 'application/activity+json; charset=utf-8';
const HTML = 'text/html; charset=utf-8'; const HTML = 'text/html; charset=utf-8';
const JSON_UTF8 = 'application/json; charset=utf-8'; const JSON_UTF8 = 'application/json; charset=utf-8';
@ -44,7 +44,8 @@ describe('Webリソース', () => {
const { path, accept, cookie, type } = param; const { path, accept, cookie, type } = param;
const res = await simpleGet(path, accept, cookie); const res = await simpleGet(path, accept, cookie);
assert.strictEqual(res.status, 200); assert.strictEqual(res.status, 200);
assert.strictEqual(res.type, type ?? HTML); // Header values are case-insensitive
assert.strictEqual(res.type?.toLowerCase(), (type ?? HTML).toLowerCase());
return res; return res;
}; };
@ -95,8 +96,7 @@ describe('Webリソース', () => {
describe.each([ describe.each([
{ path: '/', type: HTML }, { path: '/', type: HTML },
{ path: '/docs/ja-JP/about', type: HTML }, // "指定されたURLに該当するページはありませんでした。" { path: '/docs/ja-JP/about', type: HTML }, // "指定されたURLに該当するページはありませんでした。"
// fastify-static gives charset=UTF-8 instead of utf-8 and that's okay { path: '/api-doc', type: HTML },
{ path: '/api-doc', type: 'text/html; charset=UTF-8' },
{ path: '/api.json', type: JSON_UTF8 }, { path: '/api.json', type: JSON_UTF8 },
{ path: '/api-console', type: HTML }, { path: '/api-console', type: HTML },
{ path: '/_info_card_', type: HTML }, { path: '/_info_card_', type: HTML },

View File

@ -24,13 +24,13 @@ describe('MfmService', () => {
describe('toHtml', () => { describe('toHtml', () => {
test('br', () => { test('br', () => {
const input = 'foo\nbar\nbaz'; const input = 'foo\nbar\nbaz';
const output = '<p><span>foo<br>bar<br>baz</span></p>'; const output = '<p><span>foo<br />bar<br />baz</span></p>';
assert.equal(mfmService.toHtml(mfm.parse(input)), output); assert.equal(mfmService.toHtml(mfm.parse(input)), output);
}); });
test('br alt', () => { test('br alt', () => {
const input = 'foo\r\nbar\rbaz'; const input = 'foo\r\nbar\rbaz';
const output = '<p><span>foo<br>bar<br>baz</span></p>'; const output = '<p><span>foo<br />bar<br />baz</span></p>';
assert.equal(mfmService.toHtml(mfm.parse(input)), output); assert.equal(mfmService.toHtml(mfm.parse(input)), output);
}); });

View File

@ -12,12 +12,12 @@
"dependencies": { "dependencies": {
"@discordapp/twemoji": "15.1.0", "@discordapp/twemoji": "15.1.0",
"@rollup/plugin-json": "6.1.0", "@rollup/plugin-json": "6.1.0",
"@rollup/plugin-replace": "5.0.7", "@rollup/plugin-replace": "6.0.2",
"@rollup/pluginutils": "5.1.3", "@rollup/pluginutils": "5.1.4",
"@tabler/icons-webfont": "https://github.com/misskey-dev/tabler-icons/archive/refs/tags/3.29.0-mi.1913+5921534bc.tar.gz", "@tabler/icons-webfont": "https://github.com/misskey-dev/tabler-icons/archive/refs/tags/3.30.0-mi.1932+ab127beee.tar.gz",
"@twemoji/parser": "15.1.1", "@twemoji/parser": "15.1.1",
"@vitejs/plugin-vue": "5.2.0", "@vitejs/plugin-vue": "5.2.1",
"@vue/compiler-sfc": "3.5.12", "@vue/compiler-sfc": "3.5.13",
"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",
@ -25,47 +25,46 @@
"misskey-js": "workspace:*", "misskey-js": "workspace:*",
"frontend-shared": "workspace:*", "frontend-shared": "workspace:*",
"punycode.js": "2.3.1", "punycode.js": "2.3.1",
"rollup": "4.26.0", "rollup": "4.34.7",
"sass": "1.79.4", "sass": "1.85.0",
"shiki": "1.22.2", "shiki": "2.4.1",
"tinycolor2": "1.6.0", "tinycolor2": "1.6.0",
"tsc-alias": "1.8.10", "tsc-alias": "1.8.10",
"tsconfig-paths": "4.2.0", "tsconfig-paths": "4.2.0",
"typescript": "5.6.3", "typescript": "5.7.3",
"uuid": "10.0.0", "uuid": "11.0.5",
"json5": "2.2.3", "json5": "2.2.3",
"vite": "5.4.11", "vite": "6.1.0",
"vue": "3.5.12" "vue": "3.5.13"
}, },
"devDependencies": { "devDependencies": {
"@misskey-dev/summaly": "5.1.0", "@misskey-dev/summaly": "5.2.0",
"@testing-library/vue": "8.1.0", "@testing-library/vue": "8.1.0",
"@types/estree": "1.0.6", "@types/estree": "1.0.6",
"@types/micromatch": "4.0.9", "@types/micromatch": "4.0.9",
"@types/node": "22.9.0", "@types/node": "22.13.4",
"@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/uuid": "10.0.0", "@types/ws": "8.5.14",
"@types/ws": "8.5.13", "@typescript-eslint/eslint-plugin": "8.24.0",
"@typescript-eslint/eslint-plugin": "7.17.0", "@typescript-eslint/parser": "8.24.0",
"@typescript-eslint/parser": "7.17.0", "@vitest/coverage-v8": "3.0.5",
"@vitest/coverage-v8": "1.6.0", "@vue/runtime-core": "3.5.13",
"@vue/runtime-core": "3.5.12",
"acorn": "8.14.0", "acorn": "8.14.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"eslint-plugin-import": "2.31.0", "eslint-plugin-import": "2.31.0",
"eslint-plugin-vue": "9.31.0", "eslint-plugin-vue": "9.32.0",
"fast-glob": "3.3.2", "fast-glob": "3.3.3",
"happy-dom": "10.0.3", "happy-dom": "17.1.0",
"intersection-observer": "0.12.2", "intersection-observer": "0.12.2",
"micromatch": "4.0.8", "micromatch": "4.0.8",
"msw": "2.6.4", "msw": "2.7.0",
"nodemon": "3.1.7", "nodemon": "3.1.9",
"prettier": "3.3.3", "prettier": "3.5.1",
"start-server-and-test": "2.0.8", "start-server-and-test": "2.0.10",
"vite-plugin-turbosnap": "1.0.3", "vite-plugin-turbosnap": "1.0.3",
"vue-component-type-helpers": "2.1.10", "vue-component-type-helpers": "2.2.2",
"vue-eslint-parser": "9.4.3", "vue-eslint-parser": "9.4.3",
"vue-tsc": "2.1.10" "vue-tsc": "2.2.2"
} }
} }

View File

@ -21,7 +21,7 @@ export type MiPostMessageEvent<T extends PostMessageEventType = PostMessageEvent
type: T; type: T;
iframeId?: string; iframeId?: string;
payload?: PostMessageEventPayload[T]; payload?: PostMessageEventPayload[T];
} };
let defaultIframeId: string | null = null; let defaultIframeId: string | null = null;

View File

@ -98,4 +98,11 @@ export default [
'vue/attribute-hyphenation': ['error', 'never'], 'vue/attribute-hyphenation': ['error', 'never'],
}, },
}, },
{
ignores: [
// TODO: Error while loading rule '@typescript-eslint/naming-convention': Cannot use 'in' operator to search for 'type' in undefined のため一時的に無効化
// See https://github.com/misskey-dev/misskey/pull/15311
'js/i18n.ts',
],
},
]; ];

View File

@ -69,6 +69,7 @@ export const notificationTypes = [
'achievementEarned', 'achievementEarned',
'exportCompleted', 'exportCompleted',
'login', 'login',
'createToken',
'test', 'test',
'app', 'app',
] as const; ] as const;

View File

@ -6,7 +6,7 @@
//#region Embed関連の定義 //#region Embed関連の定義
/** 埋め込みの対象となるエンティティ(/embed/xxx の xxx の部分と対応させる) */ /** 埋め込みの対象となるエンティティ(/embed/xxx の xxx の部分と対応させる) */
const embeddableEntities = [ export const embeddableEntities = [
'notes', 'notes',
'user-timeline', 'user-timeline',
'clips', 'clips',

View File

@ -9,10 +9,10 @@ export type UnicodeEmojiDef = {
name: string; name: string;
char: string; char: string;
category: typeof unicodeEmojiCategories[number]; category: typeof unicodeEmojiCategories[number];
} };
// initial converted from https://github.com/muan/emojilib/commit/242fe68be86ed6536843b83f7e32f376468b38fb // initial converted from https://github.com/muan/emojilib/commit/242fe68be86ed6536843b83f7e32f376468b38fb
import _emojilist from './emojilist.json'; import _emojilist from './emojilist.json' with { type: 'json' };
export const emojilist: UnicodeEmojiDef[] = _emojilist.map(x => ({ export const emojilist: UnicodeEmojiDef[] = _emojilist.map(x => ({
name: x[1] as string, name: x[1] as string,

View File

@ -2,6 +2,7 @@
* SPDX-FileCopyrightText: syuilo and misskey-project * SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import type { ILocale, ParameterizedString } from '../../../locales/index.js'; import type { ILocale, ParameterizedString } from '../../../locales/index.js';
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any

View File

@ -21,13 +21,13 @@
"lint": "pnpm typecheck && pnpm eslint" "lint": "pnpm typecheck && pnpm eslint"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "22.9.0", "@types/node": "22.13.4",
"@typescript-eslint/eslint-plugin": "7.17.0", "@typescript-eslint/eslint-plugin": "8.24.0",
"@typescript-eslint/parser": "7.17.0", "@typescript-eslint/parser": "8.24.0",
"esbuild": "0.24.0", "esbuild": "0.25.0",
"eslint-plugin-vue": "9.31.0", "eslint-plugin-vue": "9.32.0",
"nodemon": "3.1.7", "nodemon": "3.1.9",
"typescript": "5.6.3", "typescript": "5.7.3",
"vue-eslint-parser": "9.4.3" "vue-eslint-parser": "9.4.3"
}, },
"files": [ "files": [
@ -35,6 +35,6 @@
], ],
"dependencies": { "dependencies": {
"misskey-js": "workspace:*", "misskey-js": "workspace:*",
"vue": "3.5.12" "vue": "3.5.13"
} }
} }

View File

@ -414,6 +414,7 @@ function toStories(component: string): Promise<string> {
glob('src/components/MkSignupServerRules.vue'), glob('src/components/MkSignupServerRules.vue'),
glob('src/components/MkUserSetupDialog.vue'), glob('src/components/MkUserSetupDialog.vue'),
glob('src/components/MkUserSetupDialog.*.vue'), glob('src/components/MkUserSetupDialog.*.vue'),
glob('src/components/MkImgPreviewDialog.vue'),
glob('src/components/MkInstanceCardMini.vue'), glob('src/components/MkInstanceCardMini.vue'),
glob('src/components/MkInviteCode.vue'), glob('src/components/MkInviteCode.vue'),
glob('src/components/MkTagItem.vue'), glob('src/components/MkTagItem.vue'),

View File

@ -47,6 +47,8 @@ export default [
'@typescript-eslint/no-empty-interface': ['error', { '@typescript-eslint/no-empty-interface': ['error', {
allowSingleExtends: true, allowSingleExtends: true,
}], }],
// defineExposeが誤検知されてしまう
'@typescript-eslint/no-unused-expressions': 'off',
'import/consistent-type-specifier-style': ['error', 'prefer-top-level'], 'import/consistent-type-specifier-style': ['error', 'prefer-top-level'],
// window の禁止理由: グローバルスコープと衝突し、予期せぬ結果を招くため // window の禁止理由: グローバルスコープと衝突し、予期せぬ結果を招くため
// e の禁止理由: error や event など、複数のキーワードの頭文字であり分かりにくいため // e の禁止理由: error や event など、複数のキーワードの頭文字であり分かりにくいため

View File

@ -21,27 +21,27 @@
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3", "@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
"@misskey-dev/browser-image-resizer": "2024.1.0", "@misskey-dev/browser-image-resizer": "2024.1.0",
"@rollup/plugin-json": "6.1.0", "@rollup/plugin-json": "6.1.0",
"@rollup/plugin-replace": "5.0.7", "@rollup/plugin-replace": "6.0.2",
"@rollup/pluginutils": "5.1.3", "@rollup/pluginutils": "5.1.4",
"@syuilo/aiscript": "0.19.0", "@syuilo/aiscript": "0.19.0",
"@tabler/icons-webfont": "https://github.com/misskey-dev/tabler-icons/archive/refs/tags/3.29.0-mi.1913+5921534bc.tar.gz", "@tabler/icons-webfont": "https://github.com/misskey-dev/tabler-icons/archive/refs/tags/3.30.0-mi.1932+ab127beee.tar.gz",
"@twemoji/parser": "15.1.1", "@twemoji/parser": "15.1.1",
"@vitejs/plugin-vue": "5.2.0", "@vitejs/plugin-vue": "5.2.1",
"@vue/compiler-sfc": "3.5.12", "@vue/compiler-sfc": "3.5.13",
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.15", "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.15",
"astring": "1.9.0", "astring": "1.9.0",
"broadcast-channel": "7.0.0", "broadcast-channel": "7.0.0",
"buraha": "0.0.1", "buraha": "0.0.1",
"canvas-confetti": "1.9.3", "canvas-confetti": "1.9.3",
"chart.js": "4.4.6", "chart.js": "4.4.7",
"chartjs-adapter-date-fns": "3.0.0", "chartjs-adapter-date-fns": "3.0.0",
"chartjs-chart-matrix": "2.0.1", "chartjs-chart-matrix": "2.0.1",
"chartjs-plugin-gradient": "0.6.1", "chartjs-plugin-gradient": "0.6.1",
"chartjs-plugin-zoom": "2.0.1", "chartjs-plugin-zoom": "2.2.0",
"chromatic": "11.18.1", "chromatic": "11.25.2",
"compare-versions": "6.1.1", "compare-versions": "6.1.1",
"cropperjs": "2.0.0-rc.2", "cropperjs": "2.0.0-rc.2",
"date-fns": "2.30.0", "date-fns": "4.1.0",
"estree-walker": "3.0.3", "estree-walker": "3.0.3",
"eventemitter3": "5.0.1", "eventemitter3": "5.0.1",
"frontend-shared": "workspace:*", "frontend-shared": "workspace:*",
@ -49,92 +49,91 @@
"insert-text-at-cursor": "0.3.0", "insert-text-at-cursor": "0.3.0",
"is-file-animated": "1.0.2", "is-file-animated": "1.0.2",
"json5": "2.2.3", "json5": "2.2.3",
"matter-js": "0.19.0", "matter-js": "0.20.0",
"mfm-js": "0.24.0", "mfm-js": "0.24.0",
"misskey-bubble-game": "workspace:*", "misskey-bubble-game": "workspace:*",
"misskey-js": "workspace:*", "misskey-js": "workspace:*",
"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.26.0", "rollup": "4.34.7",
"sanitize-html": "2.13.1", "sanitize-html": "2.14.0",
"sass": "1.79.3", "sass": "1.85.0",
"shiki": "1.22.2", "shiki": "2.4.1",
"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.169.0", "three": "0.173.0",
"throttle-debounce": "5.0.2", "throttle-debounce": "5.0.2",
"tinycolor2": "1.6.0", "tinycolor2": "1.6.0",
"tsc-alias": "1.8.10", "tsc-alias": "1.8.10",
"tsconfig-paths": "4.2.0", "tsconfig-paths": "4.2.0",
"typescript": "5.6.3", "typescript": "5.7.3",
"uuid": "10.0.0", "uuid": "11.0.5",
"v-code-diff": "1.13.1", "v-code-diff": "1.13.1",
"vite": "5.4.11", "vite": "6.1.0",
"vue": "3.5.12", "vue": "3.5.13",
"vuedraggable": "next" "vuedraggable": "next"
}, },
"devDependencies": { "devDependencies": {
"@misskey-dev/summaly": "5.1.0", "@misskey-dev/summaly": "5.2.0",
"@storybook/addon-actions": "8.4.4", "@storybook/addon-actions": "8.5.6",
"@storybook/addon-essentials": "8.4.4", "@storybook/addon-essentials": "8.5.6",
"@storybook/addon-interactions": "8.4.4", "@storybook/addon-interactions": "8.5.6",
"@storybook/addon-links": "8.4.4", "@storybook/addon-links": "8.5.6",
"@storybook/addon-mdx-gfm": "8.4.4", "@storybook/addon-mdx-gfm": "8.5.6",
"@storybook/addon-storysource": "8.4.4", "@storybook/addon-storysource": "8.5.6",
"@storybook/blocks": "8.4.4", "@storybook/blocks": "8.5.6",
"@storybook/components": "8.4.4", "@storybook/components": "8.5.6",
"@storybook/core-events": "8.4.4", "@storybook/core-events": "8.5.6",
"@storybook/manager-api": "8.4.4", "@storybook/manager-api": "8.5.6",
"@storybook/preview-api": "8.4.4", "@storybook/preview-api": "8.5.6",
"@storybook/react": "8.4.4", "@storybook/react": "8.5.6",
"@storybook/react-vite": "8.4.4", "@storybook/react-vite": "8.5.6",
"@storybook/test": "8.4.4", "@storybook/test": "8.5.6",
"@storybook/theming": "8.4.4", "@storybook/theming": "8.5.6",
"@storybook/types": "8.4.4", "@storybook/types": "8.5.6",
"@storybook/vue3": "8.4.4", "@storybook/vue3": "8.5.6",
"@storybook/vue3-vite": "8.4.4", "@storybook/vue3-vite": "8.5.6",
"@testing-library/vue": "8.1.0", "@testing-library/vue": "8.1.0",
"@types/canvas-confetti": "^1.6.4", "@types/canvas-confetti": "1.9.0",
"@types/estree": "1.0.6", "@types/estree": "1.0.6",
"@types/matter-js": "0.19.7", "@types/matter-js": "0.19.8",
"@types/micromatch": "4.0.9", "@types/micromatch": "4.0.9",
"@types/node": "22.9.0", "@types/node": "22.13.4",
"@types/punycode.js": "npm:@types/punycode@2.1.4", "@types/punycode.js": "npm:@types/punycode@2.1.4",
"@types/sanitize-html": "2.13.0", "@types/sanitize-html": "2.13.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/uuid": "10.0.0", "@types/ws": "8.5.14",
"@types/ws": "8.5.13", "@typescript-eslint/eslint-plugin": "8.24.0",
"@typescript-eslint/eslint-plugin": "7.17.0", "@typescript-eslint/parser": "8.24.0",
"@typescript-eslint/parser": "7.17.0", "@vitest/coverage-v8": "3.0.5",
"@vitest/coverage-v8": "1.6.0", "@vue/runtime-core": "3.5.13",
"@vue/runtime-core": "3.5.12",
"acorn": "8.14.0", "acorn": "8.14.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"cypress": "13.15.2", "cypress": "14.0.3",
"eslint-plugin-import": "2.31.0", "eslint-plugin-import": "2.31.0",
"eslint-plugin-vue": "9.31.0", "eslint-plugin-vue": "9.32.0",
"fast-glob": "3.3.2", "fast-glob": "3.3.3",
"happy-dom": "10.0.3", "happy-dom": "17.1.0",
"intersection-observer": "0.12.2", "intersection-observer": "0.12.2",
"micromatch": "4.0.8", "micromatch": "4.0.8",
"msw": "2.6.4", "msw": "2.7.0",
"msw-storybook-addon": "2.0.4", "msw-storybook-addon": "2.0.4",
"nodemon": "3.1.7", "nodemon": "3.1.9",
"prettier": "3.3.3", "prettier": "3.5.1",
"react": "18.3.1", "react": "19.0.0",
"react-dom": "18.3.1", "react-dom": "19.0.0",
"seedrandom": "3.0.5", "seedrandom": "3.0.5",
"start-server-and-test": "2.0.8", "start-server-and-test": "2.0.10",
"storybook": "8.4.4", "storybook": "8.5.6",
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme", "storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
"vite-plugin-turbosnap": "1.0.3", "vite-plugin-turbosnap": "1.0.3",
"vitest": "1.6.0", "vitest": "3.0.5",
"vitest-fetch-mock": "0.2.2", "vitest-fetch-mock": "0.4.3",
"vue-component-type-helpers": "2.1.10", "vue-component-type-helpers": "2.2.2",
"vue-eslint-parser": "9.4.3", "vue-eslint-parser": "9.4.3",
"vue-tsc": "2.1.10" "vue-tsc": "2.2.2"
} }
} }

View File

@ -50,6 +50,8 @@ type CaptchaContainer = {
}; };
declare global { declare global {
// Window
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
interface Window extends CaptchaContainer { } interface Window extends CaptchaContainer { }
} }

View File

@ -142,6 +142,7 @@ const okButtonDisabledReason = computed<null | 'charactersExceeded' | 'character
// overload function 使 lint // overload function 使 lint
function done(canceled: true): void; function done(canceled: true): void;
function done(canceled: false, result: Result): void; // eslint-disable-line no-redeclare function done(canceled: false, result: Result): void; // eslint-disable-line no-redeclare
function done(canceled: boolean, result?: Result): void { // eslint-disable-line no-redeclare function done(canceled: boolean, result?: Result): void { // eslint-disable-line no-redeclare
emit('done', { canceled, result } as { canceled: true } | { canceled: false, result: Result }); emit('done', { canceled, result } as { canceled: true } | { canceled: false, result: Result });
modal.value?.close(); modal.value?.close();

View File

@ -0,0 +1,40 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { StoryObj } from '@storybook/vue3';
import { file } from '../../.storybook/fakes.js';
import MkImgPreviewDialog from './MkImgPreviewDialog.vue';
export const Default = {
render(args) {
return {
components: {
MkImgPreviewDialog,
},
setup() {
return {
args,
};
},
computed: {
props() {
return {
...this.args,
};
},
},
template: '<MkImgPreviewDialog v-bind="props" />',
};
},
args: {
file: file(),
},
parameters: {
chromatic: {
// NOTE: ロードが終わるまで待つ
delay: 3000,
},
layout: 'centered',
},
} satisfies StoryObj<typeof MkImgPreviewDialog>;

View File

@ -0,0 +1,58 @@
<!--
SPDX-FileCopyrightText: syuilo and misskey-project
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<MkModalWindow
ref="modal"
:width="1800"
:height="900"
@close="close"
@esc="close"
@click="close"
>
<template #header>{{ file.name }}</template>
<div :class="$style.container">
<img :src="file.url" :alt="file.comment ?? file.name" :class="$style.img"/>
</div>
</MkModalWindow>
</template>
<script lang="ts" setup>
import { defineProps, ref } from 'vue';
import MkModalWindow from './MkModalWindow.vue';
import type * as Misskey from 'misskey-js';
defineProps<{
file: Misskey.entities.DriveFile;
}>();
const modal = ref<typeof MkModalWindow | null>(null);
function close() {
modal.value?.close();
}
</script>
<style lang="scss" module>
.container {
box-sizing: border-box;
width: 100%;
height: 100%;
min-height: 0;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
background-color: var(--MI_THEME-bg);
background-size: auto auto;
background-image: repeating-linear-gradient(135deg, transparent, transparent 6px, var(--MI_THEME-panel) 6px, var(--MI_THEME-panel) 12px);
}
.img {
width: 100%;
max-height: 100%;
object-fit: contain;
}
</style>

View File

@ -91,10 +91,11 @@ SPDX-License-Identifier: AGPL-3.0-only
import { shallowRef, watch, computed, ref, onDeactivated, onActivated, onMounted } from 'vue'; import { shallowRef, watch, computed, ref, onDeactivated, onActivated, onMounted } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import type { MenuItem } from '@/types/menu.js'; import type { MenuItem } from '@/types/menu.js';
import type { Keymap } from '@/scripts/hotkey.js';
import { copyToClipboard } from '@/scripts/copy-to-clipboard';
import { defaultStore } from '@/store.js'; import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import * as os from '@/os.js'; import * as os from '@/os.js';
import type { Keymap } from '@/scripts/hotkey.js';
import bytes from '@/filters/bytes.js'; import bytes from '@/filters/bytes.js';
import { hms } from '@/filters/hms.js'; import { hms } from '@/filters/hms.js';
import MkMediaRange from '@/components/MkMediaRange.vue'; import MkMediaRange from '@/components/MkMediaRange.vue';
@ -216,10 +217,9 @@ function showMenu(ev: MouseEvent) {
}); });
} }
const details: MenuItem[] = [];
if ($i?.id === props.audio.userId) { if ($i?.id === props.audio.userId) {
menu.push({ details.push({
type: 'divider',
}, {
type: 'link', type: 'link',
text: i18n.ts._fileViewer.title, text: i18n.ts._fileViewer.title,
icon: 'ti ti-info-circle', icon: 'ti ti-info-circle',
@ -227,6 +227,29 @@ function showMenu(ev: MouseEvent) {
}); });
} }
if (iAmModerator) {
details.push({
type: 'link',
text: i18n.ts.moderation,
icon: 'ti ti-photo-exclamation',
to: `/admin/file/${props.audio.id}`,
});
}
if (details.length > 0) {
menu.push({ type: 'divider' }, ...details);
}
if (defaultStore.state.devMode) {
menu.push({ type: 'divider' }, {
icon: 'ti ti-id',
text: i18n.ts.copyFileId,
action: () => {
copyToClipboard(props.audio.id);
},
});
}
menuShowing.value = true; menuShowing.value = true;
os.popupMenu(menu, ev.currentTarget ?? ev.target, { os.popupMenu(menu, ev.currentTarget ?? ev.target, {
align: 'right', align: 'right',

View File

@ -54,6 +54,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import { watch, ref, computed } from 'vue'; import { watch, ref, computed } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import type { MenuItem } from '@/types/menu.js'; import type { MenuItem } from '@/types/menu.js';
import { copyToClipboard } from '@/scripts/copy-to-clipboard';
import { getStaticImageUrl } from '@/scripts/media-proxy.js'; import { getStaticImageUrl } from '@/scripts/media-proxy.js';
import bytes from '@/filters/bytes.js'; import bytes from '@/filters/bytes.js';
import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue'; import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue';
@ -132,10 +133,9 @@ function showMenu(ev: MouseEvent) {
}); });
} }
const details: MenuItem[] = [];
if ($i?.id === props.image.userId) { if ($i?.id === props.image.userId) {
menuItems.push({ details.push({
type: 'divider',
}, {
type: 'link', type: 'link',
text: i18n.ts._fileViewer.title, text: i18n.ts._fileViewer.title,
icon: 'ti ti-info-circle', icon: 'ti ti-info-circle',
@ -143,6 +143,29 @@ function showMenu(ev: MouseEvent) {
}); });
} }
if (iAmModerator) {
details.push({
type: 'link',
text: i18n.ts.moderation,
icon: 'ti ti-photo-exclamation',
to: `/admin/file/${props.image.id}`,
});
}
if (details.length > 0) {
menuItems.push({ type: 'divider' }, ...details);
}
if (defaultStore.state.devMode) {
menuItems.push({ type: 'divider' }, {
icon: 'ti ti-id',
text: i18n.ts.copyFileId,
action: () => {
copyToClipboard(props.image.id);
},
});
}
os.popupMenu(menuItems, ev.currentTarget ?? ev.target); os.popupMenu(menuItems, ev.currentTarget ?? ev.target);
} }

View File

@ -113,6 +113,7 @@ import { ref, shallowRef, computed, watch, onDeactivated, onActivated, onMounted
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import type { MenuItem } from '@/types/menu.js'; import type { MenuItem } from '@/types/menu.js';
import type { Keymap } from '@/scripts/hotkey.js'; import type { Keymap } from '@/scripts/hotkey.js';
import { copyToClipboard } from '@/scripts/copy-to-clipboard';
import bytes from '@/filters/bytes.js'; import bytes from '@/filters/bytes.js';
import { hms } from '@/filters/hms.js'; import { hms } from '@/filters/hms.js';
import { defaultStore } from '@/store.js'; import { defaultStore } from '@/store.js';
@ -241,10 +242,9 @@ function showMenu(ev: MouseEvent) {
}); });
} }
const details: MenuItem[] = [];
if ($i?.id === props.video.userId) { if ($i?.id === props.video.userId) {
menu.push({ details.push({
type: 'divider',
}, {
type: 'link', type: 'link',
text: i18n.ts._fileViewer.title, text: i18n.ts._fileViewer.title,
icon: 'ti ti-info-circle', icon: 'ti ti-info-circle',
@ -252,6 +252,29 @@ function showMenu(ev: MouseEvent) {
}); });
} }
if (iAmModerator) {
details.push({
type: 'link',
text: i18n.ts.moderation,
icon: 'ti ti-photo-exclamation',
to: `/admin/file/${props.video.id}`,
});
}
if (details.length > 0) {
menu.push({ type: 'divider' }, ...details);
}
if (defaultStore.state.devMode) {
menu.push({ type: 'divider' }, {
icon: 'ti ti-id',
text: i18n.ts.copyFileId,
action: () => {
copyToClipboard(props.video.id);
},
});
}
menuShowing.value = true; menuShowing.value = true;
os.popupMenu(menu, ev.currentTarget ?? ev.target, { os.popupMenu(menu, ev.currentTarget ?? ev.target, {
align: 'right', align: 'right',

View File

@ -489,7 +489,16 @@ function react(): void {
} }
} else { } else {
blur(); blur();
reactionPicker.show(reactButton.value ?? null, note.value, reaction => { reactionPicker.show(reactButton.value ?? null, note.value, async (reaction) => {
if (defaultStore.state.confirmOnReact) {
const confirm = await os.confirm({
type: 'question',
text: i18n.tsx.reactAreYouSure({ emoji: reaction.replace('@.', '') }),
});
if (confirm.canceled) return;
}
sound.playMisskeySfx('reaction'); sound.playMisskeySfx('reaction');
if (props.mock) { if (props.mock) {

View File

@ -452,7 +452,16 @@ function react(): void {
} }
} else { } else {
blur(); blur();
reactionPicker.show(reactButton.value ?? null, note.value, reaction => { reactionPicker.show(reactButton.value ?? null, note.value, async (reaction) => {
if (defaultStore.state.confirmOnReact) {
const confirm = await os.confirm({
type: 'question',
text: i18n.tsx.reactAreYouSure({ emoji: reaction.replace('@.', '') }),
});
if (confirm.canceled) return;
}
sound.playMisskeySfx('reaction'); sound.playMisskeySfx('reaction');
misskeyApi('notes/reactions/create', { misskeyApi('notes/reactions/create', {

View File

@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.root"> <div :class="$style.root">
<div :class="$style.head"> <div :class="$style.head">
<MkAvatar v-if="['pollEnded', 'note'].includes(notification.type) && 'note' in notification" :class="$style.icon" :user="notification.note.user" link preview/> <MkAvatar v-if="['pollEnded', 'note'].includes(notification.type) && 'note' in notification" :class="$style.icon" :user="notification.note.user" link preview/>
<MkAvatar v-else-if="['roleAssigned', 'achievementEarned', 'exportCompleted', 'login'].includes(notification.type)" :class="$style.icon" :user="$i" link preview/> <MkAvatar v-else-if="['roleAssigned', 'achievementEarned', 'exportCompleted', 'login', 'createToken'].includes(notification.type)" :class="$style.icon" :user="$i" link preview/>
<div v-else-if="notification.type === 'reaction:grouped' && notification.note.reactionAcceptance === 'likeOnly'" :class="[$style.icon, $style.icon_reactionGroupHeart]"><i class="ti ti-heart" style="line-height: 1;"></i></div> <div v-else-if="notification.type === 'reaction:grouped' && notification.note.reactionAcceptance === 'likeOnly'" :class="[$style.icon, $style.icon_reactionGroupHeart]"><i class="ti ti-heart" style="line-height: 1;"></i></div>
<div v-else-if="notification.type === 'reaction:grouped'" :class="[$style.icon, $style.icon_reactionGroup]"><i class="ti ti-plus" style="line-height: 1;"></i></div> <div v-else-if="notification.type === 'reaction:grouped'" :class="[$style.icon, $style.icon_reactionGroup]"><i class="ti ti-plus" style="line-height: 1;"></i></div>
<div v-else-if="notification.type === 'renote:grouped'" :class="[$style.icon, $style.icon_renoteGroup]"><i class="ti ti-repeat" style="line-height: 1;"></i></div> <div v-else-if="notification.type === 'renote:grouped'" :class="[$style.icon, $style.icon_renoteGroup]"><i class="ti ti-repeat" style="line-height: 1;"></i></div>
@ -27,6 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
[$style.t_achievementEarned]: notification.type === 'achievementEarned', [$style.t_achievementEarned]: notification.type === 'achievementEarned',
[$style.t_exportCompleted]: notification.type === 'exportCompleted', [$style.t_exportCompleted]: notification.type === 'exportCompleted',
[$style.t_login]: notification.type === 'login', [$style.t_login]: notification.type === 'login',
[$style.t_createToken]: notification.type === 'createToken',
[$style.t_roleAssigned]: notification.type === 'roleAssigned' && notification.role.iconUrl == null, [$style.t_roleAssigned]: notification.type === 'roleAssigned' && notification.role.iconUrl == null,
}]" }]"
> >
@ -41,6 +42,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<i v-else-if="notification.type === 'achievementEarned'" class="ti ti-medal"></i> <i v-else-if="notification.type === 'achievementEarned'" class="ti ti-medal"></i>
<i v-else-if="notification.type === 'exportCompleted'" class="ti ti-archive"></i> <i v-else-if="notification.type === 'exportCompleted'" class="ti ti-archive"></i>
<i v-else-if="notification.type === 'login'" class="ti ti-login-2"></i> <i v-else-if="notification.type === 'login'" class="ti ti-login-2"></i>
<i v-else-if="notification.type === 'createToken'" class="ti ti-key"></i>
<template v-else-if="notification.type === 'roleAssigned'"> <template v-else-if="notification.type === 'roleAssigned'">
<img v-if="notification.role.iconUrl" style="height: 1.3em; vertical-align: -22%;" :src="notification.role.iconUrl" alt=""/> <img v-if="notification.role.iconUrl" style="height: 1.3em; vertical-align: -22%;" :src="notification.role.iconUrl" alt=""/>
<i v-else class="ti ti-badges"></i> <i v-else class="ti ti-badges"></i>
@ -61,6 +63,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<span v-else-if="notification.type === 'roleAssigned'">{{ i18n.ts._notification.roleAssigned }}</span> <span v-else-if="notification.type === 'roleAssigned'">{{ i18n.ts._notification.roleAssigned }}</span>
<span v-else-if="notification.type === 'achievementEarned'">{{ i18n.ts._notification.achievementEarned }}</span> <span v-else-if="notification.type === 'achievementEarned'">{{ i18n.ts._notification.achievementEarned }}</span>
<span v-else-if="notification.type === 'login'">{{ i18n.ts._notification.login }}</span> <span v-else-if="notification.type === 'login'">{{ i18n.ts._notification.login }}</span>
<span v-else-if="notification.type === 'createToken'">{{ i18n.ts._notification.createToken }}</span>
<span v-else-if="notification.type === 'test'">{{ i18n.ts._notification.testNotification }}</span> <span v-else-if="notification.type === 'test'">{{ i18n.ts._notification.testNotification }}</span>
<span v-else-if="notification.type === 'exportCompleted'">{{ i18n.tsx._notification.exportOfXCompleted({ x: exportEntityName[notification.exportedEntity] }) }}</span> <span v-else-if="notification.type === 'exportCompleted'">{{ i18n.tsx._notification.exportOfXCompleted({ x: exportEntityName[notification.exportedEntity] }) }}</span>
<MkA v-else-if="notification.type === 'follow' || notification.type === 'mention' || notification.type === 'reply' || notification.type === 'renote' || notification.type === 'quote' || notification.type === 'reaction' || notification.type === 'receiveFollowRequest' || notification.type === 'followRequestAccepted'" v-user-preview="notification.user.id" :class="$style.headerName" :to="userPage(notification.user)"><MkUserName :user="notification.user"/></MkA> <MkA v-else-if="notification.type === 'follow' || notification.type === 'mention' || notification.type === 'reply' || notification.type === 'renote' || notification.type === 'quote' || notification.type === 'reaction' || notification.type === 'receiveFollowRequest' || notification.type === 'followRequestAccepted'" v-user-preview="notification.user.id" :class="$style.headerName" :to="userPage(notification.user)"><MkUserName :user="notification.user"/></MkA>
@ -107,6 +110,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkA v-else-if="notification.type === 'exportCompleted'" :class="$style.text" :to="`/my/drive/file/${notification.fileId}`"> <MkA v-else-if="notification.type === 'exportCompleted'" :class="$style.text" :to="`/my/drive/file/${notification.fileId}`">
{{ i18n.ts.showFile }} {{ i18n.ts.showFile }}
</MkA> </MkA>
<MkA v-else-if="notification.type === 'createToken'" :class="$style.text" to="/settings/apps">
<Mfm :text="i18n.tsx._notification.createTokenDescription({ text: i18n.ts.manageAccessTokens })"/>
</MkA>
<template v-else-if="notification.type === 'follow'"> <template v-else-if="notification.type === 'follow'">
<span :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.youGotNewFollower }}</span> <span :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.youGotNewFollower }}</span>
</template> </template>
@ -357,6 +363,12 @@ function getActualReactedUsersCount(notification: Misskey.entities.Notification)
pointer-events: none; pointer-events: none;
} }
.t_createToken {
padding: 3px;
background: var(--eventOther);
pointer-events: none;
}
.tail { .tail {
flex: 1; flex: 1;
min-width: 0; min-width: 0;

View File

@ -39,7 +39,7 @@ import MkModalWindow from '@/components/MkModalWindow.vue';
import { notificationTypes } from '@@/js/const.js'; import { notificationTypes } from '@@/js/const.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
type TypesMap = Record<typeof notificationTypes[number], Ref<boolean>> type TypesMap = Record<typeof notificationTypes[number], Ref<boolean>>;
const emit = defineEmits<{ const emit = defineEmits<{
(ev: 'done', v: { excludeTypes: string[] }): void, (ev: 'done', v: { excludeTypes: string[] }): void,

View File

@ -65,7 +65,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
</div> </div>
<MkInfo v-if="hasNotSpecifiedMentions" warn :class="$style.hasNotSpecifiedMentions">{{ i18n.ts.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ i18n.ts.add }}</button></MkInfo> <MkInfo v-if="hasNotSpecifiedMentions" warn :class="$style.hasNotSpecifiedMentions">{{ i18n.ts.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ i18n.ts.add }}</button></MkInfo>
<input v-show="useCw" ref="cwInputEl" v-model="cw" :class="$style.cw" :placeholder="i18n.ts.annotation" @keydown="onKeydown" @keyup="onKeyup" @compositionend="onCompositionEnd"> <div v-show="useCw" :class="$style.cwOuter">
<input ref="cwInputEl" v-model="cw" :class="$style.cw" :placeholder="i18n.ts.annotation" @keydown="onKeydown" @keyup="onKeyup" @compositionend="onCompositionEnd">
<div v-if="maxCwTextLength - cwTextLength < 20" :class="['_acrylic', $style.cwTextCount, { [$style.cwTextOver]: cwTextLength > maxCwTextLength }]">{{ maxCwTextLength - cwTextLength }}</div>
</div>
<div :class="[$style.textOuter, { [$style.withCw]: useCw }]"> <div :class="[$style.textOuter, { [$style.withCw]: useCw }]">
<div v-if="channel" :class="$style.colorBar" :style="{ background: channel.color }"></div> <div v-if="channel" :class="$style.colorBar" :style="{ background: channel.color }"></div>
<textarea ref="textareaEl" v-model="text" :class="[$style.text]" :disabled="posting || posted" :readonly="textAreaReadOnly" :placeholder="placeholder" data-cy-post-form-text @keydown="onKeydown" @keyup="onKeyup" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd"/> <textarea ref="textareaEl" v-model="text" :class="[$style.text]" :disabled="posting || posted" :readonly="textAreaReadOnly" :placeholder="placeholder" data-cy-post-form-text @keydown="onKeydown" @keyup="onKeyup" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd"/>
@ -244,6 +247,12 @@ const maxTextLength = computed((): number => {
return instance ? instance.maxNoteTextLength : 1000; return instance ? instance.maxNoteTextLength : 1000;
}); });
const cwTextLength = computed((): number => {
return cw.value?.length ?? 0;
});
const maxCwTextLength = 100;
const canPost = computed((): boolean => { const canPost = computed((): boolean => {
return !props.mock && !posting.value && !posted.value && return !props.mock && !posting.value && !posted.value &&
( (
@ -254,6 +263,7 @@ const canPost = computed((): boolean => {
quoteId.value != null quoteId.value != null
) && ) &&
(textLength.value <= maxTextLength.value) && (textLength.value <= maxTextLength.value) &&
(cwTextLength.value <= maxCwTextLength) &&
(files.value.length <= 16) && (files.value.length <= 16) &&
(!poll.value || poll.value.choices.length >= 2); (!poll.value || poll.value.choices.length >= 2);
}); });
@ -1273,12 +1283,34 @@ html[data-color-scheme=light] .preview {
} }
} }
.cwOuter {
width: 100%;
position: relative;
}
.cw { .cw {
z-index: 1; z-index: 1;
padding-bottom: 8px; padding-bottom: 8px;
border-bottom: solid 0.5px var(--MI_THEME-divider); border-bottom: solid 0.5px var(--MI_THEME-divider);
} }
.cwTextCount {
position: absolute;
top: 0;
right: 2px;
padding: 2px 6px;
font-size: .9em;
color: var(--MI_THEME-warn);
border-radius: 6px;
max-width: 100%;
min-width: 1.6em;
text-align: center;
&.cwTextOver {
color: #ff2a2a;
}
}
.hashtags { .hashtags {
z-index: 1; z-index: 1;
padding-top: 8px; padding-top: 8px;

View File

@ -22,20 +22,26 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
</template> </template>
</Sortable> </Sortable>
<p :class="[$style.remain, { <p
[$style.exceeded]: props.modelValue.length > 16, :class="[$style.remain, {
}]">{{ 16 - props.modelValue.length }}/16</p> [$style.exceeded]: props.modelValue.length > 16,
}]"
>
{{ 16 - props.modelValue.length }}/16
</p>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { defineAsyncComponent, inject } from 'vue'; import { defineAsyncComponent, inject } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import type { MenuItem } from '@/types/menu';
import { defaultStore } from '@/store';
import { copyToClipboard } from '@/scripts/copy-to-clipboard';
import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue'; import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js'; import { misskeyApi } from '@/scripts/misskey-api.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import type { MenuItem } from '@/types/menu.js';
const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default)); const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
@ -168,6 +174,14 @@ function showFileMenu(file: Misskey.entities.DriveFile, ev: MouseEvent | Keyboar
text: i18n.ts.cropImage, text: i18n.ts.cropImage,
icon: 'ti ti-crop', icon: 'ti ti-crop',
action: () : void => { crop(file); }, action: () : void => { crop(file); },
}, {
text: i18n.ts.preview,
icon: 'ti ti-photo-search',
action: () => {
os.popup(defineAsyncComponent(() => import('@/components/MkImgPreviewDialog.vue')), {
file: file,
});
},
}); });
} }
@ -184,6 +198,16 @@ function showFileMenu(file: Misskey.entities.DriveFile, ev: MouseEvent | Keyboar
action: () => { detachAndDeleteMedia(file); }, action: () => { detachAndDeleteMedia(file); },
}); });
if (defaultStore.state.devMode) {
menuItems.push({ type: 'divider' }, {
icon: 'ti ti-id',
text: i18n.ts.copyFileId,
action: () => {
copyToClipboard(file.id);
},
});
}
os.popupMenu(menuItems, ev.currentTarget ?? ev.target).then(() => menuShowing = false); os.popupMenu(menuItems, ev.currentTarget ?? ev.target).then(() => menuShowing = false);
menuShowing = true; menuShowing = true;
} }

View File

@ -90,6 +90,15 @@ async function toggleReaction() {
} }
}); });
} else { } else {
if (defaultStore.state.confirmOnReact) {
const confirm = await os.confirm({
type: 'question',
text: i18n.tsx.reactAreYouSure({ emoji: props.reaction.replace('@.', '') }),
});
if (confirm.canceled) return;
}
sound.playMisskeySfx('reaction'); sound.playMisskeySfx('reaction');
if (mock) { if (mock) {

View File

@ -3,9 +3,9 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
export type SortOrderDirection = '+' | '-' export type SortOrderDirection = '+' | '-';
export type SortOrder<T extends string> = { export type SortOrder<T extends string> = {
key: T; key: T;
direction: SortOrderDirection; direction: SortOrderDirection;
} };

View File

@ -114,7 +114,7 @@ type EventType = {
userCreated: boolean; userCreated: boolean;
inactiveModeratorsWarning: boolean; inactiveModeratorsWarning: boolean;
inactiveModeratorsInvitationOnlyChanged: boolean; inactiveModeratorsInvitationOnlyChanged: boolean;
} };
const emit = defineEmits<{ const emit = defineEmits<{
(ev: 'submitted', result: MkSystemWebhookResult): void; (ev: 'submitted', result: MkSystemWebhookResult): void;

View File

@ -57,15 +57,15 @@ provide('tl_withSensitive', computed(() => props.withSensitive));
provide('inChannel', computed(() => props.src === 'channel')); provide('inChannel', computed(() => props.src === 'channel'));
type TimelineQueryType = { type TimelineQueryType = {
antennaId?: string, antennaId?: string,
withRenotes?: boolean, withRenotes?: boolean,
withReplies?: boolean, withReplies?: boolean,
withFiles?: boolean, withFiles?: boolean,
visibility?: string, visibility?: string,
listId?: string, listId?: string,
channelId?: string, channelId?: string,
roleId?: string roleId?: string
} };
const prComponent = shallowRef<InstanceType<typeof MkPullToRefresh>>(); const prComponent = shallowRef<InstanceType<typeof MkPullToRefresh>>();
const tlComponent = shallowRef<InstanceType<typeof MkNotes>>(); const tlComponent = shallowRef<InstanceType<typeof MkNotes>>();

View File

@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
> >
<iframe <iframe
v-if="player.url.startsWith('http://') || player.url.startsWith('https://')" v-if="player.url.startsWith('http://') || player.url.startsWith('https://')"
sandbox="allow-popups allow-scripts allow-storage-access-by-user-activation allow-same-origin" sandbox="allow-popups allow-popups-to-escape-sandbox allow-scripts allow-storage-access-by-user-activation allow-same-origin"
scrolling="no" scrolling="no"
:allow="player.allow == null ? 'autoplay;encrypted-media;fullscreen' : player.allow.filter(x => ['autoplay', 'clipboard-write', 'fullscreen', 'encrypted-media', 'picture-in-picture', 'web-share'].includes(x)).join(';')" :allow="player.allow == null ? 'autoplay;encrypted-media;fullscreen' : player.allow.filter(x => ['autoplay', 'clipboard-write', 'fullscreen', 'encrypted-media', 'picture-in-picture', 'web-share'].includes(x)).join(';')"
:class="$style.playerIframe" :class="$style.playerIframe"

View File

@ -62,7 +62,7 @@ import MkTextarea from '@/components/MkTextarea.vue';
import MkSwitch from '@/components/MkSwitch.vue'; import MkSwitch from '@/components/MkSwitch.vue';
import MkRadios from '@/components/MkRadios.vue'; import MkRadios from '@/components/MkRadios.vue';
type AdminAnnouncementType = Misskey.entities.AdminAnnouncementsCreateRequest & { id: string; } type AdminAnnouncementType = Misskey.entities.AdminAnnouncementsCreateRequest & { id: string; };
const props = defineProps<{ const props = defineProps<{
user: Misskey.entities.User, user: Misskey.entities.User,

View File

@ -79,7 +79,7 @@ type RowHolder = {
row: GridRow, row: GridRow,
cells: GridCell[], cells: GridCell[],
origin: DataSource, origin: DataSource,
} };
const emit = defineEmits<{ const emit = defineEmits<{
(ev: 'event', event: GridEvent, context: GridContext): void; (ev: 'event', event: GridEvent, context: GridContext): void;

View File

@ -18,25 +18,25 @@ export type ValidatorParams = {
export type ValidatorResult = { export type ValidatorResult = {
valid: boolean; valid: boolean;
message?: string; message?: string;
} };
export type GridCellValidator = { export type GridCellValidator = {
name?: string; name?: string;
ignoreViolation?: boolean; ignoreViolation?: boolean;
validate: (params: ValidatorParams) => ValidatorResult; validate: (params: ValidatorParams) => ValidatorResult;
} };
export type ValidateViolation = { export type ValidateViolation = {
valid: boolean; valid: boolean;
params: ValidatorParams; params: ValidatorParams;
violations: ValidateViolationItem[]; violations: ValidateViolationItem[];
} };
export type ValidateViolationItem = { export type ValidateViolationItem = {
valid: boolean; valid: boolean;
validator: GridCellValidator; validator: GridCellValidator;
result: ValidatorResult; result: ValidatorResult;
} };
export function cellValidation(allCells: GridCell[], cell: GridCell, newValue: CellValue): ValidateViolation { export function cellValidation(allCells: GridCell[], cell: GridCell, newValue: CellValue): ValidateViolation {
const { column, row } = cell; const { column, row } = cell;

View File

@ -15,7 +15,7 @@ export type CellValue = string | boolean | number | undefined | null | Array<unk
export type CellAddress = { export type CellAddress = {
row: number; row: number;
col: number; col: number;
} };
export const CELL_ADDRESS_NONE: CellAddress = { export const CELL_ADDRESS_NONE: CellAddress = {
row: -1, row: -1,
@ -32,13 +32,13 @@ export type GridCell = {
contentSize: Size; contentSize: Size;
setting: GridCellSetting; setting: GridCellSetting;
violation: ValidateViolation; violation: ValidateViolation;
} };
export type GridCellContextMenuFactory = (col: GridColumn, row: GridRow, value: CellValue, context: GridContext) => MenuItem[]; export type GridCellContextMenuFactory = (col: GridColumn, row: GridRow, value: CellValue, context: GridContext) => MenuItem[];
export type GridCellSetting = { export type GridCellSetting = {
contextMenuFactory?: GridCellContextMenuFactory; contextMenuFactory?: GridCellContextMenuFactory;
} };
export function createCell( export function createCell(
column: GridColumn, column: GridColumn,

View File

@ -40,7 +40,7 @@ export type GridColumn = {
setting: GridColumnSetting; setting: GridColumnSetting;
width: string; width: string;
contentSize: Size; contentSize: Size;
} };
export function createColumn(setting: GridColumnSetting, index: number): GridColumn { export function createColumn(setting: GridColumnSetting, index: number): GridColumn {
return { return {

View File

@ -21,7 +21,7 @@ export type GridSetting = {
export type DataSource = Record<string, CellValue>; export type DataSource = Record<string, CellValue>;
export type GridState = export type GridState = (
'normal' | 'normal' |
'cellSelecting' | 'cellSelecting' |
'cellEditing' | 'cellEditing' |
@ -29,19 +29,19 @@ export type GridState =
'colSelecting' | 'colSelecting' |
'rowSelecting' | 'rowSelecting' |
'hidden' 'hidden'
; );
export type Size = { export type Size = {
width: number; width: number;
height: number; height: number;
} };
export type SizeStyle = number | 'auto' | undefined; export type SizeStyle = number | 'auto' | undefined;
export type AdditionalStyle = { export type AdditionalStyle = {
className?: string; className?: string;
style?: Record<string, string | number>; style?: Record<string, string | number>;
} };
export class GridEventEmitter extends EventEmitter<{ export class GridEventEmitter extends EventEmitter<{
'forceRefreshContentSize': void; 'forceRefreshContentSize': void;

View File

@ -27,7 +27,7 @@ export type GridRowStyleRuleConditionParams = {
export type GridRowStyleRule = { export type GridRowStyleRule = {
condition: (params: GridRowStyleRuleConditionParams) => boolean; condition: (params: GridRowStyleRuleConditionParams) => boolean;
applyStyle: AdditionalStyle; applyStyle: AdditionalStyle;
} };
export type GridRowContextMenuFactory = (row: GridRow, context: GridContext) => MenuItem[]; export type GridRowContextMenuFactory = (row: GridRow, context: GridContext) => MenuItem[];
@ -40,7 +40,7 @@ export type GridRowSetting = {
events?: { events?: {
delete?: (rows: GridRow[]) => void; delete?: (rows: GridRow[]) => void;
} }
} };
export type GridRow = { export type GridRow = {
index: number; index: number;
@ -48,7 +48,7 @@ export type GridRow = {
using: boolean; using: boolean;
setting: GridRowSetting; setting: GridRowSetting;
additionalStyles: AdditionalStyle[]; additionalStyles: AdditionalStyle[];
} };
export function createRow(index: number, using: boolean, setting: GridRowSetting): GridRow { export function createRow(index: number, using: boolean, setting: GridRowSetting): GridRow {
return { return {

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