diff --git a/.config/docker_example.yml b/.config/docker_example.yml index 18553237a0..b6cdbac473 100644 --- a/.config/docker_example.yml +++ b/.config/docker_example.yml @@ -114,9 +114,27 @@ redis: # #prefix: example-prefix # #db: 1 -# ┌───────────────────────────┐ -#───┘ MeiliSearch configuration └───────────────────────────── - +# ┌───────────────────────────────┐ +#───┘ Fulltext search configuration └───────────────────────────── + +# These are the setting items for the full-text search provider. +fulltextSearch: + # You can select the ID generation method. + # - sqlLike (default) + # Use SQL-like search. + # This is a standard feature of PostgreSQL, so no special extensions are required. + # - sqlPgroonga + # Use pgroonga. + # You need to install pgroonga and configure it as a PostgreSQL extension. + # In addition to the above, you need to create a pgroonga index on the text column of the note table. + # see: https://pgroonga.github.io/tutorial/ + # - meilisearch + # Use Meilisearch. + # You need to install Meilisearch and configure. + provider: sqlLike + +# For Meilisearch settings. +# If you select "meilisearch" for "fulltextSearch.provider", it must be set. # You can set scope to local (default value) or global # (include notes from remote). @@ -228,3 +246,13 @@ signToActivityPubGet: true # Upload or download file size limits (bytes) #maxFileSize: 262144000 + +# Log settings +# logging: +# sql: +# # Outputs query parameters during SQL execution to the log. +# # default: false +# enableQueryParamLogging: false +# # Disable query truncation. If set to true, the full text of the query will be output to the log. +# # default: false +# disableQueryTruncation: false diff --git a/.config/example.yml b/.config/example.yml index a499785ca7..091f8196ee 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -196,9 +196,27 @@ redis: # # You can specify more ioredis options... # #username: example-username -# ┌───────────────────────────┐ -#───┘ MeiliSearch configuration └───────────────────────────── - +# ┌───────────────────────────────┐ +#───┘ Fulltext search configuration └───────────────────────────── + +# These are the setting items for the full-text search provider. +fulltextSearch: + # You can select the ID generation method. + # - sqlLike (default) + # Use SQL-like search. + # This is a standard feature of PostgreSQL, so no special extensions are required. + # - sqlPgroonga + # Use pgroonga. + # You need to install pgroonga and configure it as a PostgreSQL extension. + # In addition to the above, you need to create a pgroonga index on the text column of the note table. + # see: https://pgroonga.github.io/tutorial/ + # - meilisearch + # Use Meilisearch. + # You need to install Meilisearch and configure. + provider: sqlLike + +# For Meilisearch settings. +# If you select "meilisearch" for "fulltextSearch.provider", it must be set. # You can set scope to local (default value) or global # (include notes from remote). @@ -330,3 +348,13 @@ signToActivityPubGet: true # PID File of master process #pidFile: /tmp/cherrypick.pid + +# Log settings +# logging: +# sql: +# # Outputs query parameters during SQL execution to the log. +# # default: false +# enableQueryParamLogging: false +# # Disable query truncation. If set to true, the full text of the query will be output to the log. +# # default: false +# disableQueryTruncation: false diff --git a/.github/ISSUE_TEMPLATE/01_bug-report.yml b/.github/ISSUE_TEMPLATE/01_bug-report.yml index fa5a003599..5494392fd9 100644 --- a/.github/ISSUE_TEMPLATE/01_bug-report.yml +++ b/.github/ISSUE_TEMPLATE/01_bug-report.yml @@ -54,7 +54,7 @@ body: * Model and OS of the device(s): MacBook Pro (14inch, 2021), macOS Ventura 13.4 * Browser: Chrome 113.0.5672.126 * Server URL: cherrypick.example.com - * CherryPick: 4.x.x (Misskey: 2024.x.x) + * CherryPick: 4.x.x (Misskey: 2025.x.x) value: | * Model and OS of the device(s): * Browser: @@ -74,7 +74,7 @@ body: Examples: * Installation Method or Hosting Service: docker compose, k8s/docker, systemd, "CherryPick install shell script", development environment - * CherryPick: 4.x.x (Misskey: 2024.x.x) + * CherryPick: 4.x.x (Misskey: 2025.x.x) * Node: 20.x.x * PostgreSQL: 15.x.x * Redis: 7.x.x diff --git a/.github/workflows/api-cherrypick-js.yml b/.github/workflows/api-cherrypick-js.yml index a9569a7b04..e6437aeb72 100644 --- a/.github/workflows/api-cherrypick-js.yml +++ b/.github/workflows/api-cherrypick-js.yml @@ -9,6 +9,10 @@ on: paths: - packages/cherrypick-js/** - .github/workflows/api-cherrypick-js.yml + +env: + COREPACK_DEFAULT_TO_LATEST: 0 + jobs: report: @@ -21,7 +25,7 @@ jobs: - run: corepack enable - name: Setup Node.js - uses: actions/setup-node@v4.0.4 + uses: actions/setup-node@v4.1.0 with: node-version-file: '.node-version' cache: 'pnpm' diff --git a/.github/workflows/changelog-check.yml b/.github/workflows/changelog-check.yml index 60f25cc290..2ab57f5f64 100644 --- a/.github/workflows/changelog-check.yml +++ b/.github/workflows/changelog-check.yml @@ -14,7 +14,7 @@ jobs: - name: Checkout head uses: actions/checkout@v4.1.1 - name: Setup Node.js - uses: actions/setup-node@v4.0.4 + uses: actions/setup-node@v4.1.0 with: node-version-file: '.node-version' diff --git a/.github/workflows/check-cherrypick-js-autogen.yml b/.github/workflows/check-cherrypick-js-autogen.yml index 9039cf5e67..538b43baf7 100644 --- a/.github/workflows/check-cherrypick-js-autogen.yml +++ b/.github/workflows/check-cherrypick-js-autogen.yml @@ -29,7 +29,7 @@ jobs: - name: setup node id: setup-node - uses: actions/setup-node@v4.0.4 + uses: actions/setup-node@v4.1.0 with: node-version-file: '.node-version' cache: pnpm diff --git a/.github/workflows/check-spdx-license-id.yml b/.github/workflows/check-spdx-license-id.yml index 05582008b5..5df26707d7 100644 --- a/.github/workflows/check-spdx-license-id.yml +++ b/.github/workflows/check-spdx-license-id.yml @@ -35,7 +35,7 @@ jobs: check() { local file="$1" if ! ( - grep -q "SPDX-FileCopyrightText: syuilo and misskey-project" "$file" || + grep -qE "SPDX-FileCopyrightText: (syuilo and misskey-project|noridev and cherrypick-project|syuilo and misskey-project & noridev and cherrypick-project)" "$file" || grep -q "SPDX-License-Identifier: AGPL-3.0-only" "$file" ); then echo "Missing: $file" diff --git a/.github/workflows/get-api-diff.yml b/.github/workflows/get-api-diff.yml index 8a9b3aa059..460f77871c 100644 --- a/.github/workflows/get-api-diff.yml +++ b/.github/workflows/get-api-diff.yml @@ -9,6 +9,10 @@ on: paths: - packages/backend/** - .github/workflows/get-api-diff.yml + +env: + COREPACK_DEFAULT_TO_LATEST: 0 + jobs: get-from-cherrypick: runs-on: ubuntu-latest @@ -33,7 +37,7 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.4 + uses: actions/setup-node@v4.1.0 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 4697e07396..b41fd229b2 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -28,6 +28,10 @@ on: - packages/misskey-reversi/** - packages/shared/eslint.config.js - .github/workflows/lint.yml + +env: + COREPACK_DEFAULT_TO_LATEST: 0 + jobs: pnpm_install: runs-on: ubuntu-latest @@ -37,7 +41,7 @@ jobs: fetch-depth: 0 submodules: true - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4.0.4 + - uses: actions/setup-node@v4.1.0 with: node-version-file: '.node-version' cache: 'pnpm' @@ -68,14 +72,14 @@ jobs: fetch-depth: 0 submodules: true - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4.0.4 + - uses: actions/setup-node@v4.1.0 with: node-version-file: '.node-version' cache: 'pnpm' - run: corepack enable - run: pnpm i --frozen-lockfile - name: Restore eslint cache - uses: actions/cache@v4.1.0 + uses: actions/cache@v4.2.0 with: path: ${{ env.eslint-cache-path }} key: eslint-${{ env.eslint-cache-version }}-${{ matrix.workspace }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ github.ref_name }}-${{ github.sha }} @@ -98,7 +102,7 @@ jobs: fetch-depth: 0 submodules: true - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4.0.4 + - uses: actions/setup-node@v4.1.0 with: node-version-file: '.node-version' cache: 'pnpm' diff --git a/.github/workflows/locale.yml b/.github/workflows/locale.yml index 6bc8860a11..95d29bf828 100644 --- a/.github/workflows/locale.yml +++ b/.github/workflows/locale.yml @@ -9,6 +9,10 @@ on: paths: - locales/** - .github/workflows/locale.yml + +env: + COREPACK_DEFAULT_TO_LATEST: 0 + jobs: locale_verify: runs-on: ubuntu-latest @@ -19,7 +23,7 @@ jobs: fetch-depth: 0 submodules: true - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4.0.4 + - uses: actions/setup-node@v4.1.0 with: node-version-file: '.node-version' cache: 'pnpm' diff --git a/.github/workflows/on-release-created.yml b/.github/workflows/on-release-created.yml index 8cfdbd49db..dbae888c1d 100644 --- a/.github/workflows/on-release-created.yml +++ b/.github/workflows/on-release-created.yml @@ -6,6 +6,9 @@ on: workflow_dispatch: +env: + COREPACK_DEFAULT_TO_LATEST: 0 + jobs: publish-cherrypick-js: name: Publish cherrypick-js @@ -26,7 +29,7 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.4 + uses: actions/setup-node@v4.1.0 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/release-with-ready.yml b/.github/workflows/release-with-ready.yml deleted file mode 100644 index 585375c20e..0000000000 --- a/.github/workflows/release-with-ready.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: "Release Manager: release RC when ready for review" - -on: - pull_request: - types: [ready_for_review] - -env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - -permissions: - contents: write - issues: write - pull-requests: write - -jobs: - check: - runs-on: ubuntu-latest - outputs: - head: ${{ steps.get_pr.outputs.head }} - base: ${{ steps.get_pr.outputs.base }} - steps: - - uses: actions/checkout@v4 - # PR情報を取得 - - name: Get PR - run: | - pr_json=$(gh pr view "$PR_NUMBER" --json isDraft,headRefName,baseRefName) - echo "head=$(echo $pr_json | jq -r '.headRefName')" >> $GITHUB_OUTPUT - echo "base=$(echo $pr_json | jq -r '.baseRefName')" >> $GITHUB_OUTPUT - id: get_pr - env: - PR_NUMBER: ${{ github.event.pull_request.number }} - release: - uses: misskey-dev/release-manager-actions/.github/workflows/create-prerelease.yml@v2 - needs: check - if: needs.check.outputs.head == github.event.repository.default_branch && needs.check.outputs.base == vars.STABLE_BRANCH - with: - pr_number: ${{ github.event.pull_request.number }} - user: 'github-actions[bot]' - package_jsons_to_rewrite: ${{ vars.PACKAGE_JSONS_TO_REWRITE }} - use_external_app_to_release: ${{ vars.USE_RELEASE_APP == 'true' }} - indent: ${{ vars.INDENT }} - draft_prerelease_channel: alpha - ready_start_prerelease_channel: beta - reset_number_on_channel_change: true - secrets: - RELEASE_APP_ID: ${{ secrets.RELEASE_APP_ID }} - RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} diff --git a/.github/workflows/storybook.yml b/.github/workflows/storybook.yml index a38f51b8c4..02e350012a 100644 --- a/.github/workflows/storybook.yml +++ b/.github/workflows/storybook.yml @@ -13,6 +13,9 @@ on: # This is a waste of chromatic build quota, so we don't run storybook CI on pull requests targets master. - master +env: + COREPACK_DEFAULT_TO_LATEST: 0 + jobs: build: # chromatic is not likely to be available for fork repositories, so we disable for fork repositories. @@ -43,7 +46,7 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 - name: Use Node.js 20.x - uses: actions/setup-node@v4.0.4 + uses: actions/setup-node@v4.1.0 with: node-version-file: '.node-version' cache: 'pnpm' diff --git a/.github/workflows/test-backend.yml b/.github/workflows/test-backend.yml index fd37054973..6f6f2966f3 100644 --- a/.github/workflows/test-backend.yml +++ b/.github/workflows/test-backend.yml @@ -10,14 +10,21 @@ on: # for permissions - packages/cherrypick-js/** - .github/workflows/test-backend.yml + - .github/cherrypick/test.yml pull_request: paths: - packages/backend/** # for permissions - packages/cherrypick-js/** - .github/workflows/test-backend.yml + - .github/cherrypick/test.yml + +env: + COREPACK_DEFAULT_TO_LATEST: 0 + jobs: unit: + name: Unit tests (backend) runs-on: ubuntu-latest strategy: @@ -44,9 +51,22 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 - name: Install FFmpeg - uses: FedericoCarboni/setup-ffmpeg@v3 + run: | + for i in {1..3}; do + echo "Attempt $i: Installing FFmpeg..." + curl -s -L https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz -o ffmpeg.tar.xz && \ + tar -xf ffmpeg.tar.xz && \ + mv ffmpeg-*-static/ffmpeg /usr/local/bin/ && \ + mv ffmpeg-*-static/ffprobe /usr/local/bin/ && \ + rm -rf ffmpeg.tar.xz ffmpeg-*-static/ && \ + break || sleep 10 + if [ $i -eq 3 ]; then + echo "Failed to install FFmpeg after 3 attempts" + exit 1 + fi + done - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.4 + uses: actions/setup-node@v4.1.0 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' @@ -67,6 +87,7 @@ jobs: files: ./packages/backend/coverage/coverage-final.json e2e: + name: E2E tests (backend) runs-on: ubuntu-latest strategy: @@ -93,7 +114,7 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.4 + uses: actions/setup-node@v4.1.0 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/test-cherrypick-js.yml b/.github/workflows/test-cherrypick-js.yml index 6d4a4fa10a..05720820eb 100644 --- a/.github/workflows/test-cherrypick-js.yml +++ b/.github/workflows/test-cherrypick-js.yml @@ -14,8 +14,13 @@ on: paths: - packages/cherrypick-js/** - .github/workflows/test-cherrypick-js.yml + +env: + COREPACK_DEFAULT_TO_LATEST: 0 + jobs: test: + name: Unit tests (cherrypick.js) runs-on: ubuntu-latest @@ -31,7 +36,7 @@ jobs: - run: corepack enable - name: Setup Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.4 + uses: actions/setup-node@v4.1.0 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/test-federation.yml b/.github/workflows/test-federation.yml index 734d7f425b..53648215a4 100644 --- a/.github/workflows/test-federation.yml +++ b/.github/workflows/test-federation.yml @@ -15,8 +15,12 @@ on: - packages/cherrypick-js/** - .github/workflows/test-federation.yml +env: + COREPACK_DEFAULT_TO_LATEST: 0 + jobs: test: + name: Federation test runs-on: ubuntu-latest strategy: matrix: @@ -28,9 +32,22 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 - name: Install FFmpeg - uses: FedericoCarboni/setup-ffmpeg@v3 + run: | + for i in {1..3}; do + echo "Attempt $i: Installing FFmpeg..." + curl -s -L https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz -o ffmpeg.tar.xz && \ + tar -xf ffmpeg.tar.xz && \ + mv ffmpeg-*-static/ffmpeg /usr/local/bin/ && \ + mv ffmpeg-*-static/ffprobe /usr/local/bin/ && \ + rm -rf ffmpeg.tar.xz ffmpeg-*-static/ && \ + break || sleep 10 + if [ $i -eq 3 ]; then + echo "Failed to install FFmpeg after 3 attempts" + exit 1 + fi + done - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.3 + uses: actions/setup-node@v4.1.0 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/test-frontend.yml b/.github/workflows/test-frontend.yml index 73009d6e0f..c4f64205e4 100644 --- a/.github/workflows/test-frontend.yml +++ b/.github/workflows/test-frontend.yml @@ -12,6 +12,7 @@ on: # for e2e - packages/backend/** - .github/workflows/test-frontend.yml + - .github/cherrypick/test.yml pull_request: paths: - packages/frontend/** @@ -20,8 +21,14 @@ on: # for e2e - packages/backend/** - .github/workflows/test-frontend.yml + - .github/cherrypick/test.yml + +env: + COREPACK_DEFAULT_TO_LATEST: 0 + jobs: vitest: + name: Unit tests (frontend) runs-on: ubuntu-latest strategy: @@ -35,7 +42,7 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.4 + uses: actions/setup-node@v4.1.0 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' @@ -56,6 +63,7 @@ jobs: files: ./packages/frontend/coverage/coverage-final.json e2e: + name: E2E tests (frontend) runs-on: ubuntu-latest strategy: @@ -90,7 +98,7 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.4 + uses: actions/setup-node@v4.1.0 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/test-production.yml b/.github/workflows/test-production.yml index 9583169b8d..e590db7103 100644 --- a/.github/workflows/test-production.yml +++ b/.github/workflows/test-production.yml @@ -9,9 +9,11 @@ on: env: NODE_ENV: production + COREPACK_DEFAULT_TO_LATEST: 0 jobs: production: + name: Production build runs-on: ubuntu-latest strategy: @@ -25,7 +27,7 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.4 + uses: actions/setup-node@v4.1.0 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/validate-api-json.yml b/.github/workflows/validate-api-json.yml index 835b2a9a24..65afcd4cd0 100644 --- a/.github/workflows/validate-api-json.yml +++ b/.github/workflows/validate-api-json.yml @@ -1,4 +1,4 @@ -name: Test (backend) +name: api.json validation on: push: @@ -12,6 +12,10 @@ on: paths: - packages/backend/** - .github/workflows/validate-api-json.yml + +env: + COREPACK_DEFAULT_TO_LATEST: 0 + jobs: validate-api-json: runs-on: ubuntu-latest @@ -27,7 +31,7 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.4 + uses: actions/setup-node@v4.1.0 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/CHANGELOG.md b/CHANGELOG.md index bb831f3f1e..b1c1685bf2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,24 +1,102 @@ -## 2024.11.1 +## 2025.2.0 ### General -- +- Fix: Docker のビルドに失敗する問題を修正 + (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/883) + +### Client +- Fix: パスキーでパスワードレスログインが出来ない問題を修正 +- Fix: 一部環境でセンシティブなファイルを含むノートの非表示が効かない問題 +- Fix: データセーバー有効時にもユーザーページの「ファイル」タブで画像が読み込まれてしまう問題を修正 +- Fix: MFMの `sparkle` エフェクトが正しく表示されない問題を修正 +- Fix: ページのURLにスラッシュが含まれている場合にページが正しく表示されない問題を修正 +- Fix: デッキのプロファイルが新規作成できない問題を修正 +- Fix: セキュリティに関する修正 +- ローカライゼーションの更新 +- Playが実装されたため、ページ機能の「ソースを見る」は削除されました + +### Server +- Enhance: ページのURLに使用可能な文字を限定するように +- Fix: 個別お知らせページのmetaタグ出力の条件が間違っていたのを修正 + +## 2025.1.0 + +### Note +- [重要] ノート検索プロバイダの追加に伴い、configファイル(default.ymlなど)の構成が少し変わります. + - 新しい設定項目"fulltextSearch.provider"が追加されました. sqlLike, sqlPgroonga, meilisearchのいずれかを設定出来ます. + - すでにMeilisearchをお使いの場合、 **"fulltextSearch.provider"を"meilisearch"に設定する必要** があります. + - 詳細は #14730 および `.config/example.yml` または `.config/docker_example.yml`の'Fulltext search configuration'をご参照願います. +- 【開発者向け】従来の開発モードでHMRが機能しない問題が修正されたため、バックエンド・フロントエンド分離型の開発モードが削除されました。開発環境においてconfigの変更が必要となる可能性があります。 + +### General +- Feat: カスタム絵文字管理画面をリニューアル #10996 + * β版として公開のため、旧画面も引き続き利用可能です ### Client - Enhance: PC画面でチャンネルが複数列で表示されるように (Cherry-picked from https://github.com/Otaku-Social/maniakey/pull/13) - Enhance: 照会に失敗した場合、その理由を表示するように +- Enhance: ワードミュートで検知されたワードを表示できるように +- Enhance: リモートのノートのリンクをコピーできるように +- Enhance: 連合がホワイトリスト化・無効化されているサーバー向けのデザイン修正 +- Enhance: AiScriptのセーブデータを明示的に削除する関数`Mk:remove`を追加 +- Enhance: ノートの添付ファイルを一覧で遡れる「ファイル」タブを追加 + (Based on https://github.com/Otaku-Social/maniakey/pull/14) +- Enhance: AiScriptの拡張API関数において引数の型チェックをより厳格に +- Enhance: クエリパラメータでuiを一時的に変更できるように #15240 +- Enhance: リモート絵文字のインポート時に詳細を確認できるように #15336 - Fix: 画面サイズが変わった際にナビゲーションバーが自動で折りたたまれない問題を修正 - Fix: サーバー情報メニューに区切り線が不足していたのを修正 - Fix: ノートがログインしているユーザーしか見れない場合にログインダイアログを閉じるとその後の動線がなくなる問題を修正 - Fix: 公開範囲がホームのノートの埋め込みウィジェットが読み込まれない問題を修正 (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/803) - Fix: 絵文字管理画面で一部の絵文字が表示されない問題を修正 +- Fix: プラグイン `register_note_view_interruptor` でノートのサーバー情報の書き換えができない問題を修正 +- Fix: Botプロテクションの設定変更時は実際に検証を通過しないと保存できないように( #15137 ) +- Fix: ノート検索が使用できない場合でもチャンネルのノート検索欄がでていた問題を修正 +- Fix: `Ui:C:select`で値の変更が画面に反映されない問題を修正 +- Fix: MiAuth認可画面で、認可処理に失敗した場合でもコールバックURLに遷移してしまう問題を修正 + (Cherry-picked from https://github.com/TeamNijimiss/misskey/commit/800359623e41a662551d774de15b0437b6849bb4) +- Fix: ノート作成画面でファイルの添付可能個数を超えてもノートボタンが押せていた問題を修正 +- Fix: 「アカウントを管理」画面で、ユーザー情報の取得に失敗したアカウント(削除されたアカウントなど)が表示されない問題を修正 +- Fix: MacOSでChrome系ブラウザを使用している場合に、Misskeyを閉じた際に他のタブのオーディオ機能と干渉する問題を修正 +- Fix: 言語データのキャッシュ状況によっては、埋め込みウィジェットが正しく起動しない問題を修正 +- Fix: 「削除して編集」でノートの引用を解除出来なかった問題を修正( #14476 ) +- Fix: RSSウィジェットが正しく表示されない問題を修正 + (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/857) +- Fix: ワードミュートの保存失敗時にAPIエラーが握りつぶされる事があるのを修正 +- Fix: アンケートでリモートの絵文字が正しく描画できない問題の修正 + (Cherry-picked from https://github.com/yojo-art/cherrypick/pull/153) +- Fix: 非ログイン時のサーバー概要画面のメニューボタンが押せないことがあるのを修正 + (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/656) +- Fix: URLにはじめから`#pswp`が含まれている場合に画像ビューワーがブラウザの戻るボタンで閉じられない問題を修正 +- Fix: ロール作成画面で設定できるアイコンデコレーションの最大取付個数を16に制限 +- Fix: Firefox Nightlyなどでアイコンが読み込めない問題を修正 ### Server +- Enhance: pg_bigmが利用できるよう、ノートの検索をILIKE演算子でなくLIKE演算子でLOWER()をかけたテキストに対して行うように +- Enhance: ノート検索の選択肢としてpgroongaに対応 ( #14730 ) +- Enhance: チャート更新時にDBに同時接続しないように + (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/830) +- Enhance: config(default.yml)からSQLログ全文を出力するか否かを設定可能に ( #15266 ) - Fix: ユーザーのプロフィール画面をアドレス入力などで直接表示した際に概要タブの描画に失敗する問題の修正( #15032 ) - Fix: 起動前の疎通チェックが機能しなくなっていた問題を修正 (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/737) +- Fix: ノートの閲覧にログイン必須にしてもFeedでノートが表示されてしまう問題を修正 +- Fix: 絵文字の連合でライセンス欄を相互にやり取りするように ( #10859, #14109 ) +- Fix: ロックダウンされた期間指定のノートがStreaming経由でLTLに出現するのを修正 ( #15200 ) +- Fix: disableClustering設定時の初期化ロジックを調整( #15223 ) +- Fix: URLとURIが異なるエンティティの照会に失敗する問題を修正( #15039 ) +- Fix: ActivityPubリクエストかどうかの判定が正しくない問題を修正 + (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/869) +- Fix: `/api/pages/update`にて`name`を指定せずにリクエストするとエラーが発生する問題を修正 +- Fix: AIセンシティブ判定が arm64 環境で動作しない問題を修正 +- Fix: 非Misskey系のソフトウェアからHTML``タグを含むノートを受信した場合、MFMの読み仮名(ルビ)文法に変換して表示 +- Fix: 連合OFFで投稿されたノートに対する冗長な処理を抑止 ( #15018 ) +- Fix: `/api.json`のレスポンスが2回目のリクエスト以降おかしくなる問題を修正 +### Misskey.js +- Feat: allow setting `binaryType` of WebSocket connection ## 2024.11.0 diff --git a/CHANGELOG_CHERRYPICK.md b/CHANGELOG_CHERRYPICK.md index 5e5712d9cb..a5ce703f73 100644 --- a/CHANGELOG_CHERRYPICK.md +++ b/CHANGELOG_CHERRYPICK.md @@ -1,8 +1,8 @@ - - - - - - - - [DEV] Loading... - - - - - - - -
- - - diff --git a/packages/frontend-embed/src/theme.ts b/packages/frontend-embed/src/theme.ts index ef217da75f..32dc8dc40a 100644 --- a/packages/frontend-embed/src/theme.ts +++ b/packages/frontend-embed/src/theme.ts @@ -75,16 +75,21 @@ function compile(theme: Theme): Record { return getColor(theme.props[val]); } else if (val[0] === ':') { // func const parts = val.split('<'); - const func = parts.shift().substring(1); - const arg = parseFloat(parts.shift()); - const color = getColor(parts.join('<')); - - switch (func) { - case 'darken': return color.darken(arg); - case 'lighten': return color.lighten(arg); - case 'alpha': return color.setAlpha(arg); - case 'hue': return color.spin(arg); - case 'saturate': return color.saturate(arg); + const funcTxt = parts.shift(); + const argTxt = parts.shift(); + + if (funcTxt && argTxt) { + const func = funcTxt.substring(1); + const arg = parseFloat(argTxt); + const color = getColor(parts.join('<')); + + switch (func) { + case 'darken': return color.darken(arg); + case 'lighten': return color.lighten(arg); + case 'alpha': return color.setAlpha(arg); + case 'hue': return color.spin(arg); + case 'saturate': return color.saturate(arg); + } } } diff --git a/packages/frontend-embed/tsconfig.json b/packages/frontend-embed/tsconfig.json index 45f933dc28..60164a7e3d 100644 --- a/packages/frontend-embed/tsconfig.json +++ b/packages/frontend-embed/tsconfig.json @@ -10,8 +10,8 @@ "declaration": false, "sourceMap": false, "target": "ES2022", - "module": "nodenext", - "moduleResolution": "nodenext", + "module": "ES2022", + "moduleResolution": "Bundler", "removeComments": false, "noLib": false, "strict": true, diff --git a/packages/frontend-embed/vite.config.local-dev.ts b/packages/frontend-embed/vite.config.local-dev.ts deleted file mode 100644 index bf2f478887..0000000000 --- a/packages/frontend-embed/vite.config.local-dev.ts +++ /dev/null @@ -1,96 +0,0 @@ -import dns from 'dns'; -import { readFile } from 'node:fs/promises'; -import type { IncomingMessage } from 'node:http'; -import { defineConfig } from 'vite'; -import type { UserConfig } from 'vite'; -import * as yaml from 'js-yaml'; -import locales from '../../locales/index.js'; -import { getConfig } from './vite.config.js'; - -dns.setDefaultResultOrder('ipv4first'); - -const defaultConfig = getConfig(); - -const { port } = yaml.load(await readFile('../../.config/default.yml', 'utf-8')); - -const httpUrl = `http://localhost:${port}/`; -const websocketUrl = `ws://localhost:${port}/`; - -// activitypubリクエストはProxyを通し、それ以外はViteの開発サーバーを返す -function varyHandler(req: IncomingMessage) { - if (req.headers.accept?.includes('application/activity+json')) { - return null; - } - return '/index.html'; -} - -const devConfig: UserConfig = { - // 基本の設定は vite.config.js から引き継ぐ - ...defaultConfig, - root: 'src', - publicDir: '../assets', - base: '/embed', - server: { - host: 'localhost', - port: 5174, - proxy: { - '/api': { - changeOrigin: true, - target: httpUrl, - }, - '/assets': httpUrl, - '/static-assets': httpUrl, - '/client-assets': httpUrl, - '/files': httpUrl, - '/twemoji': httpUrl, - '/fluent-emoji': httpUrl, - '/sw.js': httpUrl, - '/streaming': { - target: websocketUrl, - ws: true, - }, - '/favicon.ico': httpUrl, - '/robots.txt': httpUrl, - '/embed.js': httpUrl, - '/identicon': { - target: httpUrl, - rewrite(path) { - return path.replace('@localhost:5173', ''); - }, - }, - '/url': httpUrl, - '/proxy': httpUrl, - '/_info_card_': httpUrl, - '/bios': httpUrl, - '/cli': httpUrl, - '/inbox': httpUrl, - '/emoji/': httpUrl, - '/notes': { - target: httpUrl, - bypass: varyHandler, - }, - '/users': { - target: httpUrl, - bypass: varyHandler, - }, - '/.well-known': { - target: httpUrl, - }, - }, - }, - build: { - ...defaultConfig.build, - rollupOptions: { - ...defaultConfig.build?.rollupOptions, - input: 'index.html', - }, - }, - - define: { - ...defaultConfig.define, - _LANGS_FULL_: JSON.stringify(Object.entries(locales)), - }, -}; - -export default defineConfig(({ command, mode }) => devConfig); - diff --git a/packages/frontend-embed/vite.config.ts b/packages/frontend-embed/vite.config.ts index f78f0a1a3e..6445e41930 100644 --- a/packages/frontend-embed/vite.config.ts +++ b/packages/frontend-embed/vite.config.ts @@ -1,12 +1,17 @@ import path from 'path'; import pluginVue from '@vitejs/plugin-vue'; import { type UserConfig, defineConfig } from 'vite'; +import * as yaml from 'js-yaml'; +import { promises as fsp } from 'fs'; import locales from '../../locales/index.js'; import meta from '../../package.json'; import packageInfo from './package.json' with { type: 'json' }; import pluginJson5 from './vite.json5.js'; +const url = process.env.NODE_ENV === 'development' ? yaml.load(await fsp.readFile('../../.config/default.yml', 'utf-8')).url : null; +const host = url ? (new URL(url)).hostname : undefined; + const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.json', '.json5', '.svg', '.sass', '.scss', '.css', '.vue']; /** @@ -71,7 +76,14 @@ export function getConfig(): UserConfig { base: '/embed_vite/', server: { + host, port: 5174, + hmr: { + // バックエンド経由での起動時、Viteは5174経由でアセットを参照していると思い込んでいるが実際は3000から配信される + // そのため、バックエンドのWSサーバーにHMRのWSリクエストが吸収されてしまい、正しくHMRが機能しない + // クライアント側のWSポートをViteサーバーのポートに強制させることで、正しくHMRが機能するようになる + clientPort: 5174, + }, }, plugins: [ diff --git a/packages/frontend-shared/build.js b/packages/frontend-shared/build.js index 17b6da8d30..9941114757 100644 --- a/packages/frontend-shared/build.js +++ b/packages/frontend-shared/build.js @@ -23,10 +23,14 @@ const options = { sourcemap: 'linked', }; +const args = process.argv.slice(2).map(arg => arg.toLowerCase()); + // js-built配下をすべて削除する -fs.rmSync('./js-built', { recursive: true, force: true }); +if (!args.includes('--no-clean')) { + fs.rmSync('./js-built', { recursive: true, force: true }); +} -if (process.argv.map(arg => arg.toLowerCase()).includes('--watch')) { +if (args.includes('--watch')) { await watchSrc(); } else { await buildSrc(); diff --git a/packages/frontend-shared/js/const.ts b/packages/frontend-shared/js/const.ts index 6ae8cd7a79..eb2f654b99 100644 --- a/packages/frontend-shared/js/const.ts +++ b/packages/frontend-shared/js/const.ts @@ -112,6 +112,8 @@ export const ROLE_POLICIES = [ 'canImportFollowing', 'canImportMuting', 'canImportUserLists', + 'noteDraftLimit', + 'canSetFederationAvatarShape', ] as const; // なんか動かない diff --git a/packages/frontend-shared/package.json b/packages/frontend-shared/package.json index 9815ba237d..d845ef2a10 100644 --- a/packages/frontend-shared/package.json +++ b/packages/frontend-shared/package.json @@ -30,6 +30,7 @@ "@typescript-eslint/parser": "7.17.0", "esbuild": "0.24.0", "eslint-plugin-vue": "9.31.0", + "nodemon": "3.1.7", "typescript": "5.6.3", "vue-eslint-parser": "9.4.3" }, diff --git a/packages/frontend/.storybook/fake-utils.ts b/packages/frontend/.storybook/fake-utils.ts new file mode 100644 index 0000000000..c777cbbe72 --- /dev/null +++ b/packages/frontend/.storybook/fake-utils.ts @@ -0,0 +1,154 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import seedrandom from 'seedrandom'; + +/** + * AIで生成した無作為なファーストネーム + */ +export const firstNameDict = [ + 'Ethan', 'Olivia', 'Jackson', 'Emma', 'Liam', 'Ava', 'Aiden', 'Sophia', 'Mason', 'Isabella', + 'Noah', 'Mia', 'Lucas', 'Harper', 'Caleb', 'Abigail', 'Samuel', 'Emily', 'Logan', + 'Madison', 'Benjamin', 'Chloe', 'Elijah', 'Grace', 'Alexander', 'Scarlett', 'William', 'Zoey', 'James', 'Lily', +] + +/** + * AIで生成した無作為なラストネーム + */ +export const lastNameDict = [ + 'Anderson', 'Johnson', 'Thompson', 'Davis', 'Rodriguez', 'Smith', 'Patel', 'Williams', 'Lee', 'Brown', + 'Garcia', 'Jackson', 'Martinez', 'Taylor', 'Harris', 'Nguyen', 'Miller', 'Jones', 'Wilson', + 'White', 'Thomas', 'Garcia', 'Martinez', 'Robinson', 'Turner', 'Lewis', 'Hall', 'King', 'Baker', 'Cooper', +] + +/** + * AIで生成した無作為な国名 + */ +export const countryDict = [ + 'Japan', 'Canada', 'Brazil', 'Australia', 'Italy', 'SouthAfrica', 'Mexico', 'Sweden', 'Russia', 'India', + 'Germany', 'Argentina', 'South Korea', 'France', 'Nigeria', 'Turkey', 'Spain', 'Egypt', 'Thailand', + 'Vietnam', 'Kenya', 'Saudi Arabia', 'Netherlands', 'Colombia', 'Poland', 'Chile', 'Malaysia', 'Ukraine', 'New Zealand', 'Peru', +] + +export function text(length: number = 10, seed?: string): string { + let result = ""; + + // シード値を使う場合、同じ数値が羅列されるが、ランダム文字列という意味では満たせていると思うのでこのまま使っておく + const rand = seed ? seedrandom(seed)() : Math.random(); + while (result.length < length) { + result += rand.toString(36).substring(2); + } + + return result.substring(0, length); +} + +export function integer(min: number = 0, max: number = 9999, seed?: string): number { + const rand = seed ? seedrandom(seed)() : Math.random(); + return Math.floor(rand * (max - min)) + min; +} + +export function date(params?: { + yearMin?: number, + yearMax?: number, + monthMin?: number, + monthMax?: number, + dayMin?: number, + dayMax?: number, + hourMin?: number, + hourMax?: number, + minuteMin?: number, + minuteMax?: number, + secondMin?: number, + secondMax?: number, + millisecondMin?: number, + millisecondMax?: number, +}, seed?: string): Date { + const year = integer(params?.yearMin ?? 1970, params?.yearMax ?? (new Date()).getFullYear(), seed); + const month = integer(params?.monthMin ?? 1, params?.monthMax ?? 12, seed); + let day = integer(params?.dayMin ?? 1, params?.dayMax ?? 31, seed); + if (month === 2) { + day = Math.min(day, 28); + } else if ([4, 6, 9, 11].includes(month)) { + day = Math.min(day, 30); + } else { + day = Math.min(day, 31); + } + + const hour = integer(params?.hourMin ?? 0, params?.hourMax ?? 23, seed); + const minute = integer(params?.minuteMin ?? 0, params?.minuteMax ?? 59, seed); + const second = integer(params?.secondMin ?? 0, params?.secondMax ?? 59, seed); + const millisecond = integer(params?.millisecondMin ?? 0, params?.millisecondMax ?? 999, seed); + + return new Date(year, month - 1, day, hour, minute, second, millisecond); +} + +export function boolean(seed?: string): boolean { + const rand = seed ? seedrandom(seed)() : Math.random(); + return rand < 0.5; +} + +export function choose(array: T[], seed?: string): T { + const rand = seed ? seedrandom(seed)() : Math.random(); + return array[Math.floor(rand * array.length)]; +} + +export function firstName(seed?: string): string { + return choose(firstNameDict, seed); +} + +export function lastName(seed?: string): string { + return choose(lastNameDict, seed); +} + +export function country(seed?: string): string { + return choose(countryDict, seed); +} + +const TIME2000 = 946684800000; +export function fakeId(seed?: string): string { + let time = new Date().getTime(); + + time = time - TIME2000; + if (time < 0) time = 0; + + const timeStr = time.toString(36).padStart(8, '0'); + const noiseStr = text(2, seed); + + return timeStr + noiseStr; +} + +export function imageDataUrl(options?: { + size?: { + width?: number, + height?: number, + }, + color?: { + red?: number, + green?: number, + blue?: number, + alpha?: number, + } +}, seed?: string): string { + const canvas = document.createElement('canvas'); + canvas.width = options?.size?.width ?? 100; + canvas.height = options?.size?.height ?? 100; + + const ctx = canvas.getContext('2d'); + if (!ctx) { + throw new Error('Failed to get 2d context'); + } + + ctx.beginPath() + + const red = options?.color?.red ?? integer(0, 255, seed); + const green = options?.color?.green ?? integer(0, 255, seed); + const blue = options?.color?.blue ?? integer(0, 255, seed); + const alpha = options?.color?.alpha ?? 1; + ctx.arc(canvas.width / 2, canvas.height / 2, canvas.width / 2, 0, Math.PI * 2, true); + ctx.fillStyle = `rgba(${red}, ${green}, ${blue}, ${alpha})`; + ctx.fill(); + + return canvas.toDataURL('image/png', 1.0); +} diff --git a/packages/frontend/.storybook/fakes.ts b/packages/frontend/.storybook/fakes.ts index 2b5ec755a0..55a9e20283 100644 --- a/packages/frontend/.storybook/fakes.ts +++ b/packages/frontend/.storybook/fakes.ts @@ -5,6 +5,7 @@ import { AISCRIPT_VERSION } from '@syuilo/aiscript'; import type { entities } from 'cherrypick-js' +import { date, imageDataUrl, text } from "./fake-utils.js"; export function abuseUserReport() { return { @@ -301,3 +302,93 @@ export function inviteCode(isUsed = false, hasExpiration = false, isExpired = fa used: isUsed, } } + +export function role(params: { + id?: string, + name?: string, + color?: string | null, + iconUrl?: string | null, + description?: string, + isModerator?: boolean, + isAdministrator?: boolean, + displayOrder?: number, + createdAt?: string, + updatedAt?: string, + target?: 'manual' | 'conditional', + isPublic?: boolean, + isExplorable?: boolean, + asBadge?: boolean, + canEditMembersByModerator?: boolean, + usersCount?: number, +}, seed?: string): entities.Role { + const prefix = params.displayOrder ? params.displayOrder.toString().padStart(3, '0') + '-' : ''; + const genId = text(36, seed); + const createdAt = params.createdAt ?? date({}, seed).toISOString(); + const updatedAt = params.updatedAt ?? date({}, seed).toISOString(); + + return { + id: params.id ?? genId, + name: params.name ?? `${prefix}TestRole-${genId}`, + color: params.color ?? '#445566', + iconUrl: params.iconUrl ?? null, + description: params.description ?? '', + isModerator: params.isModerator ?? false, + isAdministrator: params.isAdministrator ?? false, + displayOrder: params.displayOrder ?? 0, + createdAt: createdAt, + updatedAt: updatedAt, + target: params.target ?? 'manual', + isPublic: params.isPublic ?? true, + isExplorable: params.isExplorable ?? true, + asBadge: params.asBadge ?? true, + canEditMembersByModerator: params.canEditMembersByModerator ?? false, + usersCount: params.usersCount ?? 10, + condFormula: { + id: '', + type: 'or', + values: [] + }, + policies: {}, + } +} + +export function emoji(params?: { + id?: string, + name?: string, + host?: string, + uri?: string, + publicUrl?: string, + originalUrl?: string, + type?: string, + aliases?: string[], + category?: string, + license?: string, + isSensitive?: boolean, + localOnly?: boolean, + roleIdsThatCanBeUsedThisEmojiAsReaction?: {id:string, name:string}[], + updatedAt?: string, +}, seed?: string): entities.EmojiDetailedAdmin { + const _seed = seed ?? (params?.id ?? "DEFAULT_SEED"); + const id = params?.id ?? text(32, _seed); + const name = params?.name ?? text(8, _seed); + const updatedAt = params?.updatedAt ?? date({}, _seed).toISOString(); + + const image = imageDataUrl({}, _seed) + + return { + id: id, + name: name, + host: params?.host ?? null, + uri: params?.uri ?? null, + publicUrl: params?.publicUrl ?? image, + originalUrl: params?.originalUrl ?? image, + type: params?.type ?? 'image/png', + aliases: params?.aliases ?? [`alias1-${name}`, `alias2-${name}`], + category: params?.category ?? null, + license: params?.license ?? null, + isSensitive: params?.isSensitive ?? false, + localOnly: params?.localOnly ?? false, + roleIdsThatCanBeUsedThisEmojiAsReaction: params?.roleIdsThatCanBeUsedThisEmojiAsReaction ?? [], + updatedAt: updatedAt, + } +} diff --git a/packages/frontend/.storybook/generate.tsx b/packages/frontend/.storybook/generate.tsx index 9e16003de7..e0189ea622 100644 --- a/packages/frontend/.storybook/generate.tsx +++ b/packages/frontend/.storybook/generate.tsx @@ -414,8 +414,13 @@ function toStories(component: string): Promise { glob('src/components/MkSignupServerRules.vue'), glob('src/components/MkUserSetupDialog.vue'), glob('src/components/MkUserSetupDialog.*.vue'), + glob('src/components/MkImgPreviewDialog.vue'), glob('src/components/MkInstanceCardMini.vue'), glob('src/components/MkInviteCode.vue'), + glob('src/components/MkTagItem.vue'), + glob('src/components/MkRoleSelectDialog.vue'), + glob('src/components/grid/MkGrid.vue'), + glob('src/pages/admin/custom-emojis-manager2.vue'), glob('src/pages/admin/overview.ap-requests.vue'), glob('src/pages/user/home.vue'), glob('src/pages/search.vue'), diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 3cba04b3bd..ad1c8c2184 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -4,7 +4,6 @@ "type": "module", "scripts": { "watch": "vite", - "dev": "vite --config vite.config.local-dev.ts --debug hmr", "build": "vite build", "storybook-dev": "nodemon --verbose --watch src --ext \"mdx,ts,vue\" --ignore \"*.stories.ts\" --exec \"pnpm build-storybook-pre && pnpm exec storybook dev -p 6006 --ci\"", "build-storybook-pre": "(tsc -p .storybook || echo done.) && node .storybook/generate.js && node .storybook/preload-locale.js && node .storybook/preload-theme.js", @@ -29,11 +28,11 @@ "@rollup/plugin-replace": "5.0.7", "@rollup/pluginutils": "5.1.3", "@syuilo/aiscript": "0.19.0", - "@tabler/icons-webfont": "3.3.0", + "@tabler/icons-webfont": "https://github.com/misskey-dev/tabler-icons/archive/refs/tags/3.29.0-mi.1913+5921534bc.tar.gz", "@twemoji/parser": "15.1.1", "@vitejs/plugin-vue": "5.2.0", "@vue/compiler-sfc": "3.5.12", - "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.11", + "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.15", "astring": "1.9.0", "autosize": "6.0.1", "broadcast-channel": "7.0.0", @@ -62,7 +61,7 @@ "misskey-reversi": "workspace:*", "photoswipe": "5.4.4", "prismjs": "1.29.0", - "punycode": "2.3.1", + "punycode.js": "2.3.1", "qrcode": "^1.5.4", "qrcode-vue3": "^1.7.1", "rollup": "4.26.0", @@ -115,7 +114,7 @@ "@types/micromatch": "4.0.9", "@types/node": "22.9.0", "@types/prismjs": "^1.26.0", - "@types/punycode": "2.1.4", + "@types/punycode.js": "npm:@types/punycode@2.1.4", "@types/qrcode": "^1.5.5", "@types/sanitize-html": "2.13.0", "@types/seedrandom": "3.0.8", diff --git a/packages/frontend/src/_dev_boot_.ts b/packages/frontend/src/_dev_boot_.ts deleted file mode 100644 index f312765dcf..0000000000 --- a/packages/frontend/src/_dev_boot_.ts +++ /dev/null @@ -1,90 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -await main(); - -import('@/_boot_.js'); - -/** - * backend/src/server/web/boot.jsで差し込まれている起動処理のうち、最低限必要なものを模倣するための処理 - */ -async function main() { - const forceError = localStorage.getItem('forceError'); - if (forceError != null) { - renderError('FORCED_ERROR', 'This error is forced by having forceError in local storage.'); - } - - //#region Detect language & fetch translations - - // dev-modeの場合は常に取り直す - const supportedLangs = _LANGS_.map(it => it[0]); - let lang: string | null | undefined = localStorage.getItem('lang'); - if (lang == null || !supportedLangs.includes(lang)) { - if (supportedLangs.includes(navigator.language)) { - lang = navigator.language; - } else { - lang = supportedLangs.find(x => x.split('-')[0] === navigator.language); - - // Fallback - if (lang == null) lang = 'en-US'; - } - } - - // TODO:今のままだと言語ファイル変更後はpnpm devをリスタートする必要があるので、chokidarを使ったり等で対応できるようにする - const locale = _LANGS_FULL_.find(it => it[0] === lang); - localStorage.setItem('lang', lang); - localStorage.setItem('locale', JSON.stringify(locale[1])); - localStorage.setItem('localeVersion', _VERSION_); - //#endregion - - //#region Theme - const theme = localStorage.getItem('theme'); - if (theme) { - for (const [k, v] of Object.entries(JSON.parse(theme))) { - document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString()); - - // HTMLの theme-color 適用 - if (k === 'htmlThemeColor') { - for (const tag of document.head.children) { - if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') { - tag.setAttribute('content', v); - break; - } - } - } - } - } - const colorScheme = localStorage.getItem('colorScheme'); - if (colorScheme) { - document.documentElement.style.setProperty('color-scheme', colorScheme); - } - //#endregion - - const fontSize = localStorage.getItem('fontSize'); - if (fontSize) { - document.documentElement.classList.add('f-' + fontSize); - } - - const useSystemFont = localStorage.getItem('useSystemFont'); - if (useSystemFont) { - document.documentElement.classList.add('useSystemFont'); - } - - const wallpaper = localStorage.getItem('wallpaper'); - if (wallpaper) { - document.documentElement.style.backgroundImage = `url(${wallpaper})`; - } - - const customCss = localStorage.getItem('customCss'); - if (customCss && customCss.length > 0) { - const style = document.createElement('style'); - style.innerHTML = customCss; - document.head.appendChild(style); - } -} - -function renderError(code: string, details?: string) { - console.log(code, details); -} diff --git a/packages/frontend/src/account.ts b/packages/frontend/src/account.ts index 38fbc22efa..581cd838e2 100644 --- a/packages/frontend/src/account.ts +++ b/packages/frontend/src/account.ts @@ -42,6 +42,12 @@ export async function signout() { if (!$i) return; waiting(); + document.cookie.split(';').forEach((cookie) => { + const cookieName = cookie.split('=')[0].trim(); + if (cookieName === 'token') { + document.cookie = `${cookieName}=; max-age=0; path=/`; + } + }); miLocalStorage.removeItem('account'); await removeAccount($i.id); const accounts = await getAccounts(); @@ -139,6 +145,9 @@ export async function removeAccount(idOrToken: Account['id']) { } function fetchAccount(token: string, id?: string, forceShowDialog?: boolean): Promise { + document.cookie = 'token=; path=/; max-age=0'; + document.cookie = `token=${token}; path=/queue; max-age=86400; SameSite=Strict; Secure`; // bull dashboardの認証とかで使う + return new Promise((done, fail) => { window.fetch(`${apiUrl}/i`, { method: 'POST', @@ -251,7 +260,6 @@ export async function login(token: Account['token'], redirect?: string) { throw reason; }); miLocalStorage.setItem('account', JSON.stringify(me)); - document.cookie = `token=${token}; path=/; max-age=31536000`; // bull dashboardの認証とかで使う await addAccount(me.id, token); if (redirect) { diff --git a/packages/frontend/src/boot/common.ts b/packages/frontend/src/boot/common.ts index f5093fbecb..22e8d3289e 100644 --- a/packages/frontend/src/boot/common.ts +++ b/packages/frontend/src/boot/common.ts @@ -110,6 +110,11 @@ export async function common(createVue: () => App) { // タッチデバイスでCSSの:hoverを機能させる document.addEventListener('touchend', () => {}, { passive: true }); + // URLに#pswpを含む場合は取り除く + if (location.hash === '#pswp') { + history.replaceState(null, '', location.href.replace('#pswp', '')); + } + // 一斉リロード reloadChannel.addEventListener('message', path => { if (path !== null) location.href = path; @@ -304,7 +309,11 @@ export async function common(createVue: () => App) { 'font-size: 16px;', 'font-size: 20px; font-weight: 700; color: #f00;', ); - console.log(i18n.tsx._selfXssPrevention.description3({ link: 'https://misskey-hub.net/docs/for-users/resources/self-xss/' })); + console.log( + `%c${i18n.tsx._selfXssPrevention.description3({ link: 'https://github.com/kokonect-link/cherrypick' })}`, + 'font-size: 14px;', + ); + console.log(i18n.tsx._selfXssPrevention.description4({ link: 'https://misskey-hub.net/docs/for-users/resources/self-xss/' })); //#endregion return { diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts index 3564e74819..7fc0e9f45a 100644 --- a/packages/frontend/src/boot/main-boot.ts +++ b/packages/frontend/src/boot/main-boot.ts @@ -7,6 +7,8 @@ import { createApp, defineAsyncComponent, markRaw } from 'vue'; import { ui } from '@@/js/config.js'; import { common } from './common.js'; import type * as Misskey from 'cherrypick-js'; +import type { Component } from 'vue'; +import * as os from '@/os.js'; import { i18n } from '@/i18n.js'; import { alert, confirm, popup, post, welcomeToast } from '@/os.js'; import { useStream } from '@/stream.js'; @@ -25,16 +27,44 @@ import { type Keymap, makeHotkey } from '@/scripts/hotkey.js'; import { addCustomEmoji, removeCustomEmojis, updateCustomEmojis } from '@/custom-emojis.js'; import { userName } from '@/filters/user.js'; import { vibrate } from '@/scripts/vibrate.js'; +import { misskeyApi } from '@/scripts/misskey-api.js'; export async function mainBoot() { - const { isClientUpdated, isClientMigrated } = await common(() => createApp( - new URLSearchParams(window.location.search).has('zen') || (ui === 'deck' && deckStore.state.useSimpleUiForNonRootPages && location.pathname !== '/') ? defineAsyncComponent(() => import('@/ui/zen.vue')) : - !$i ? defineAsyncComponent(() => import('@/ui/visitor.vue')) : - ui === 'deck' ? defineAsyncComponent(() => import('@/ui/deck.vue')) : - ui === 'classic' ? defineAsyncComponent(() => import('@/ui/classic.vue')) : - ui === 'default' ? defineAsyncComponent(() => import('@/ui/universal.vue')) : - defineAsyncComponent(() => import('@/ui/friendly.vue')), - )); + const { isClientUpdated, isClientMigrated } = await common(() => { + let uiStyle = ui; + const searchParams = new URLSearchParams(window.location.search); + + if (!$i) uiStyle = 'visitor'; + + if (searchParams.has('zen')) uiStyle = 'zen'; + if (uiStyle === 'deck' && deckStore.state.useSimpleUiForNonRootPages && location.pathname !== '/') uiStyle = 'zen'; + + if (searchParams.has('ui')) uiStyle = searchParams.get('ui'); + + let rootComponent: Component; + switch (uiStyle) { + case 'zen': + rootComponent = defineAsyncComponent(() => import('@/ui/zen.vue')); + break; + case 'deck': + rootComponent = defineAsyncComponent(() => import('@/ui/deck.vue')); + break; + case 'visitor': + rootComponent = defineAsyncComponent(() => import('@/ui/visitor.vue')); + break; + case 'classic': + rootComponent = defineAsyncComponent(() => import('@/ui/classic.vue')); + break; + case 'default': + rootComponent = defineAsyncComponent(() => import('@/ui/universal.vue')); + break; + default: + rootComponent = defineAsyncComponent(() => import('@/ui/friendly.vue')); + break; + } + + return createApp(rootComponent); + }); reactionPicker.init(); emojiPicker.init(); @@ -364,11 +394,11 @@ export async function mainBoot() { }); main.on('readAllMessagingMessages', () => { - updateAccount({ hasUnreadMessagingMessage: false }); + updateAccountPartial({ hasUnreadMessagingMessage: false }); }); main.on('unreadMessagingMessage', () => { - updateAccount({ hasUnreadMessagingMessage: true }); + updateAccountPartial({ hasUnreadMessagingMessage: true }); sound.playMisskeySfx('chatBg'); vibrate(defaultStore.state.vibrateChatBg ? [50, 40] : []); }); @@ -394,6 +424,19 @@ export async function mainBoot() { main.on('myTokenRegenerated', () => { signout(); }); + + // 프로필 아이콘 모양 설정 연합 초기화 + if ($i.policies.canSetFederationAvatarShape && defaultStore.state.setFederationAvatarShape) { + await misskeyApi('i/update', { + setFederationAvatarShape: true, + isSquareAvatars: defaultStore.state.squareAvatars, + }); + } else if (!$i.policies.canSetFederationAvatarShape && defaultStore.state.setFederationAvatarShape) { + await misskeyApi('i/update', { + setFederationAvatarShape: false, + isSquareAvatars: defaultStore.state.squareAvatars, + }); + } } // shortcut @@ -408,6 +451,10 @@ export async function mainBoot() { 's': () => { mainRouter.push('/search'); }, + // 環境によるかもしれないが?では反応しないため、shift+/にする必要がある + 'shift+/': () => { + os.popup(defineAsyncComponent(() => import('@/components/MkKeyboardShortcut.vue')), {}, {}); + }, } as const satisfies Keymap; document.addEventListener('keydown', makeHotkey(keymap), { passive: false }); diff --git a/packages/frontend/src/components/MkAsUi.vue b/packages/frontend/src/components/MkAsUi.vue index e52ab5ccad..365b767bd6 100644 --- a/packages/frontend/src/components/MkAsUi.vue +++ b/packages/frontend/src/components/MkAsUi.vue @@ -32,7 +32,7 @@ SPDX-License-Identifier: AGPL-3.0-only - + @@ -77,8 +77,8 @@ import MkPostForm from '@/components/MkPostForm.vue'; const props = withDefaults(defineProps<{ component: AsUiComponent; components: Ref[]; - size: 'small' | 'medium' | 'large'; - align: 'left' | 'center' | 'right'; + size?: 'small' | 'medium' | 'large'; + align?: 'left' | 'center' | 'right'; }>(), { size: 'medium', align: 'left', @@ -86,7 +86,7 @@ const props = withDefaults(defineProps<{ const c = props.component; -function g(id) { +function g(id: string) { const v = props.components.find(x => x.value.id === id)?.value; if (v) return v; @@ -122,13 +122,22 @@ const containerStyle = computed(() => { const valueForSwitch = ref('default' in c && typeof c.default === 'boolean' ? c.default : false); -function onSwitchUpdate(v) { +function onSwitchUpdate(v: boolean) { valueForSwitch.value = v; if ('onChange' in c && c.onChange) { c.onChange(v as never); } } +const valueForSelect = ref('default' in c && typeof c.default !== 'boolean' ? c.default ?? null : null); + +function onSelectUpdate(v) { + valueForSelect.value = v; + if ('onChange' in c && c.onChange) { + c.onChange(v as never); + } +} + function openPostForm() { const form = (c as AsUiPostFormButton).form; if (!form) return; diff --git a/packages/frontend/src/components/MkBalloon.vue b/packages/frontend/src/components/MkBalloon.vue index 308d4b4736..8b0d263975 100644 --- a/packages/frontend/src/components/MkBalloon.vue +++ b/packages/frontend/src/components/MkBalloon.vue @@ -1,5 +1,5 @@ diff --git a/packages/frontend/src/components/MkButton.vue b/packages/frontend/src/components/MkButton.vue index 667d248e71..d053dcb29c 100644 --- a/packages/frontend/src/components/MkButton.vue +++ b/packages/frontend/src/components/MkButton.vue @@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only @@ -48,6 +48,7 @@ const props = withDefaults(defineProps<{ thin?: boolean; naked?: boolean; foldable?: boolean; + onUnfold?: () => boolean; // return false to prevent unfolding scrollable?: boolean; expanded?: boolean; maxHeight?: number | null; @@ -101,6 +102,13 @@ const omitObserver = new ResizeObserver((entries, observer) => { calcOmit(); }); +function showMore() { + if (props.onUnfold && !props.onUnfold()) return; + + ignoreOmit.value = true; + omitted.value = false; +} + onMounted(() => { watch(showBody, v => { if (!rootEl.value) return; diff --git a/packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue b/packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue index ca5f4fe076..5f05f931f5 100644 --- a/packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue +++ b/packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue @@ -57,7 +57,7 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/components/MkFolder.vue b/packages/frontend/src/components/MkFolder.vue index e7e6c8d717..e3ff4220c7 100644 --- a/packages/frontend/src/components/MkFolder.vue +++ b/packages/frontend/src/components/MkFolder.vue @@ -39,7 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only >
- +
@@ -65,12 +65,16 @@ const props = withDefaults(defineProps<{ defaultOpen?: boolean; maxHeight?: number | null; withSpacer?: boolean; + spacerMin?: number; + spacerMax?: number; inactive?: boolean; isArchived? :boolean; }>(), { defaultOpen: false, maxHeight: null, withSpacer: true, + spacerMin: 14, + spacerMax: 22, inactive: false, isArchived: false, }); diff --git a/packages/frontend/src/components/MkFormFooter.vue b/packages/frontend/src/components/MkFormFooter.vue index f409f6ce50..96214a9542 100644 --- a/packages/frontend/src/components/MkFormFooter.vue +++ b/packages/frontend/src/components/MkFormFooter.vue @@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.tsx.thereAreNChanges({ n: form.modifiedCount.value }) }}
{{ i18n.ts.discard }} - {{ i18n.ts.save }} + {{ i18n.ts.save }}
@@ -18,7 +18,7 @@ import { } from 'vue'; import MkButton from './MkButton.vue'; import { i18n } from '@/i18n.js'; -const props = defineProps<{ +const props = withDefaults(defineProps<{ form: { modifiedCount: { value: number; @@ -26,7 +26,10 @@ const props = defineProps<{ discard: () => void; save: () => void; }; -}>(); + canSaving?: boolean; +}>(), { + canSaving: true, +}); diff --git a/packages/frontend/src/components/MkInfo.vue b/packages/frontend/src/components/MkInfo.vue index 4839b4630f..19d35b34fa 100644 --- a/packages/frontend/src/components/MkInfo.vue +++ b/packages/frontend/src/components/MkInfo.vue @@ -6,6 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/components/MkNoteHeader.vue b/packages/frontend/src/components/MkNoteHeader.vue index 81d0eeabd2..74c561e4e6 100644 --- a/packages/frontend/src/components/MkNoteHeader.vue +++ b/packages/frontend/src/components/MkNoteHeader.vue @@ -10,10 +10,12 @@ SPDX-License-Identifier: AGPL-3.0-only
- + -
bot
+
+
+
@@ -44,7 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
@@ -120,6 +122,10 @@ function showOnRemote() { } } +.userBadge { + margin: 0 .5em 0 0; +} + .isBot { flex-shrink: 0; align-self: center; @@ -164,6 +170,7 @@ function showOnRemote() { .badgeRole { height: 1.3em; vertical-align: -20%; + border-radius: 0.4em; & + .badgeRole { margin-left: 0.2em; diff --git a/packages/frontend/src/components/MkNoteMediaGrid.vue b/packages/frontend/src/components/MkNoteMediaGrid.vue new file mode 100644 index 0000000000..b3c0dda2be --- /dev/null +++ b/packages/frontend/src/components/MkNoteMediaGrid.vue @@ -0,0 +1,160 @@ + + + + + + + diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue index 61ca9aa67a..80fa833cd8 100644 --- a/packages/frontend/src/components/MkNotification.vue +++ b/packages/frontend/src/components/MkNotification.vue @@ -46,7 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only + + + + + + diff --git a/packages/frontend/src/components/MkPasswordDialog.vue b/packages/frontend/src/components/MkPasswordDialog.vue index 14750fc789..759a51414f 100644 --- a/packages/frontend/src/components/MkPasswordDialog.vue +++ b/packages/frontend/src/components/MkPasswordDialog.vue @@ -21,8 +21,9 @@ SPDX-License-Identifier: AGPL-3.0-only
- + + @@ -39,7 +40,7 @@ SPDX-License-Identifier: AGPL-3.0-only + + diff --git a/packages/frontend/src/components/MkPoll.vue b/packages/frontend/src/components/MkPoll.vue index 6a5fdd5811..288b9273f5 100644 --- a/packages/frontend/src/components/MkPoll.vue +++ b/packages/frontend/src/components/MkPoll.vue @@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
- + ({{ i18n.tsx._poll.votesCount({ n: choice.votes }) }}) @@ -43,6 +43,8 @@ const props = defineProps<{ noteId: string; poll: NonNullable; readOnly?: boolean; + emojiUrls?: Record; + author?: Misskey.entities.UserLite; isTranslation?: boolean; }>(); diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index 686ea6d17b..5061ceb8f0 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -19,8 +19,8 @@ SPDX-License-Identifier: AGPL-3.0-only
-