Merge upstream
This commit is contained in:
commit
bc9acabd6c
65 changed files with 502 additions and 500 deletions
4
.github/unused/test-backend.yml
vendored
4
.github/unused/test-backend.yml
vendored
|
@ -57,7 +57,7 @@ jobs:
|
||||||
- name: Install FFmpeg
|
- name: Install FFmpeg
|
||||||
uses: FedericoCarboni/setup-ffmpeg@v3
|
uses: FedericoCarboni/setup-ffmpeg@v3
|
||||||
- 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'
|
||||||
|
@ -116,7 +116,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
run_install: false
|
run_install: false
|
||||||
- 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'
|
||||||
|
|
2
.github/workflows/api-misskey-js.yml
vendored
2
.github/workflows/api-misskey-js.yml
vendored
|
@ -26,7 +26,7 @@ jobs:
|
||||||
run_install: false
|
run_install: false
|
||||||
|
|
||||||
- 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'
|
||||||
|
|
6
.github/workflows/lint.yml
vendored
6
.github/workflows/lint.yml
vendored
|
@ -29,7 +29,7 @@ jobs:
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
run_install: false
|
run_install: false
|
||||||
- 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'
|
||||||
|
@ -54,7 +54,7 @@ jobs:
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
run_install: false
|
run_install: false
|
||||||
- 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'
|
||||||
|
@ -78,7 +78,7 @@ jobs:
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
run_install: false
|
run_install: false
|
||||||
- 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'
|
||||||
|
|
2
.github/workflows/test-frontend.yml
vendored
2
.github/workflows/test-frontend.yml
vendored
|
@ -37,7 +37,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
run_install: false
|
run_install: false
|
||||||
- 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'
|
||||||
|
|
2
.github/workflows/test-misskey-js.yml
vendored
2
.github/workflows/test-misskey-js.yml
vendored
|
@ -36,7 +36,7 @@ jobs:
|
||||||
run_install: false
|
run_install: false
|
||||||
|
|
||||||
- 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'
|
||||||
|
|
2
.github/workflows/test-production.yml
vendored
2
.github/workflows/test-production.yml
vendored
|
@ -27,7 +27,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
run_install: false
|
run_install: false
|
||||||
- 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'
|
||||||
|
|
2
.github/workflows/validate-api-json.yml
vendored
2
.github/workflows/validate-api-json.yml
vendored
|
@ -28,7 +28,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
run_install: false
|
run_install: false
|
||||||
- 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'
|
||||||
|
|
|
@ -115,25 +115,10 @@ pnpm dev
|
||||||
command.
|
command.
|
||||||
|
|
||||||
- Server-side source files and automatically builds them if they are modified. Automatically start the server process(es).
|
- Server-side source files and automatically builds them if they are modified. Automatically start the server process(es).
|
||||||
- Vite HMR (just the `vite` command) is available. The behavior may be different from production.
|
|
||||||
- Service Worker is watched by esbuild.
|
- Service Worker is watched by esbuild.
|
||||||
- The front end can be viewed by accessing `http://localhost:5173`.
|
- Vite HMR (just the `vite` command) is available. The behavior may be different from production.
|
||||||
- The backend listens on the port configured with `port` in .config/default.yml.
|
- Vite runs behind the backend (the backend will proxy Vite at /vite except for websocket used for HMR).
|
||||||
If you have not changed it from the default, it will be "http://localhost:3000".
|
|
||||||
If "port" in .config/default.yml is set to something other than 3000, you need to change the proxy settings in packages/frontend/vite.config.local-dev.ts.
|
|
||||||
|
|
||||||
### `MK_DEV_PREFER=backend pnpm dev`
|
|
||||||
pnpm dev has another mode with `MK_DEV_PREFER=backend`.
|
|
||||||
|
|
||||||
```
|
|
||||||
MK_DEV_PREFER=backend pnpm dev
|
|
||||||
```
|
|
||||||
|
|
||||||
- This mode is closer to the production environment than the default mode.
|
|
||||||
- Vite runs behind the backend (the backend will proxy Vite at /vite).
|
|
||||||
- You can see Misskey by accessing `http://localhost:3000` (Replace `3000` with the port configured with `port` in .config/default.yml).
|
- You can see Misskey by accessing `http://localhost:3000` (Replace `3000` with the port configured with `port` in .config/default.yml).
|
||||||
- To change the port of Vite, specify with `VITE_PORT` environment variable.
|
|
||||||
- HMR may not work in some environments such as Windows.
|
|
||||||
|
|
||||||
### Dev Container
|
### Dev Container
|
||||||
Instead of running `pnpm` locally, you can use Dev Container to set up your development environment.
|
Instead of running `pnpm` locally, you can use Dev Container to set up your development environment.
|
||||||
|
|
|
@ -1530,7 +1530,9 @@ _accountMigration:
|
||||||
migrationConfirm: "Really migrate this account to {account}? Once started, this process cannot be stopped or taken back, and you will not be able to use this account in its original state anymore."
|
migrationConfirm: "Really migrate this account to {account}? Once started, this process cannot be stopped or taken back, and you will not be able to use this account in its original state anymore."
|
||||||
movedAndCannotBeUndone: "\nThis account has been migrated.\nMigration cannot be reversed."
|
movedAndCannotBeUndone: "\nThis account has been migrated.\nMigration cannot be reversed."
|
||||||
postMigrationNote: "This account will unfollow all accounts it is currently following 24 hours after migration finishes.\nBoth the number of follows and followers will then become zero. To avoid your followers from being unable to see followers only posts of this account, they will however continue following this account."
|
postMigrationNote: "This account will unfollow all accounts it is currently following 24 hours after migration finishes.\nBoth the number of follows and followers will then become zero. To avoid your followers from being unable to see followers only posts of this account, they will however continue following this account."
|
||||||
movedTo: "New account:"
|
movedTo: "Migrated account:"
|
||||||
|
movedToServer: "Migrated server"
|
||||||
|
movedFromServer: "Original server"
|
||||||
_achievements:
|
_achievements:
|
||||||
earnedAt: "Unlocked at"
|
earnedAt: "Unlocked at"
|
||||||
_types:
|
_types:
|
||||||
|
|
8
locales/index.d.ts
vendored
8
locales/index.d.ts
vendored
|
@ -6296,6 +6296,14 @@ export interface Locale extends ILocale {
|
||||||
* 移行先のアカウント:
|
* 移行先のアカウント:
|
||||||
*/
|
*/
|
||||||
"movedTo": string;
|
"movedTo": string;
|
||||||
|
/**
|
||||||
|
* 移行先のサーバー
|
||||||
|
*/
|
||||||
|
"movedToServer": string;
|
||||||
|
/**
|
||||||
|
* 移行元のサーバー
|
||||||
|
*/
|
||||||
|
"movedFromServer": string;
|
||||||
};
|
};
|
||||||
"_achievements": {
|
"_achievements": {
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1586,6 +1586,8 @@ _accountMigration:
|
||||||
movedAndCannotBeUndone: "\nアカウントは移行されています。\n移行を取り消すことはできません。"
|
movedAndCannotBeUndone: "\nアカウントは移行されています。\n移行を取り消すことはできません。"
|
||||||
postMigrationNote: "このアカウントからのフォロー解除は移行操作から24時間後に実行されます。\nこのアカウントのフォロー・フォロワー数は0になっています。フォロワーの解除はされないため、あなたのフォロワーはこのアカウントのフォロワー向け投稿を引き続き閲覧できます。"
|
postMigrationNote: "このアカウントからのフォロー解除は移行操作から24時間後に実行されます。\nこのアカウントのフォロー・フォロワー数は0になっています。フォロワーの解除はされないため、あなたのフォロワーはこのアカウントのフォロワー向け投稿を引き続き閲覧できます。"
|
||||||
movedTo: "移行先のアカウント:"
|
movedTo: "移行先のアカウント:"
|
||||||
|
movedToServer: "移行先のサーバー"
|
||||||
|
movedFromServer: "移行元のサーバー"
|
||||||
|
|
||||||
_achievements:
|
_achievements:
|
||||||
earnedAt: "獲得日時"
|
earnedAt: "獲得日時"
|
||||||
|
|
|
@ -1568,6 +1568,8 @@ _accountMigration:
|
||||||
movedAndCannotBeUndone: "\n이사한 계정입니다.\n이사는 취소할 수 없습니다."
|
movedAndCannotBeUndone: "\n이사한 계정입니다.\n이사는 취소할 수 없습니다."
|
||||||
postMigrationNote: "이 계정의 팔로잉 해제는 이사 후 24시간 뒤에 실행됩니다.\n이 계정의 팔로우 및 팔로워 수는 0으로 표시됩니다. 팔로워 해제는 이루어지지 않으므로, 당신의 팔로워는 이 계정의 팔로워 한정 게시물을 계속해서 열람할 수 있습니다."
|
postMigrationNote: "이 계정의 팔로잉 해제는 이사 후 24시간 뒤에 실행됩니다.\n이 계정의 팔로우 및 팔로워 수는 0으로 표시됩니다. 팔로워 해제는 이루어지지 않으므로, 당신의 팔로워는 이 계정의 팔로워 한정 게시물을 계속해서 열람할 수 있습니다."
|
||||||
movedTo: "이사할 계정:"
|
movedTo: "이사할 계정:"
|
||||||
|
movedToServer: "이사한 서버"
|
||||||
|
movedFromServer: "기존 서버"
|
||||||
_achievements:
|
_achievements:
|
||||||
earnedAt: "달성 일시"
|
earnedAt: "달성 일시"
|
||||||
_types:
|
_types:
|
||||||
|
|
|
@ -33,16 +33,16 @@
|
||||||
"generate-api-json": "pnpm build && node ./scripts/generate_api_json.js"
|
"generate-api-json": "pnpm build && node ./scripts/generate_api_json.js"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@swc/core-darwin-arm64": "1.10.7",
|
"@swc/core-darwin-arm64": "1.10.12",
|
||||||
"@swc/core-darwin-x64": "1.10.7",
|
"@swc/core-darwin-x64": "1.10.12",
|
||||||
"@swc/core-linux-arm-gnueabihf": "1.10.7",
|
"@swc/core-linux-arm-gnueabihf": "1.10.12",
|
||||||
"@swc/core-linux-arm64-gnu": "1.10.7",
|
"@swc/core-linux-arm64-gnu": "1.10.12",
|
||||||
"@swc/core-linux-arm64-musl": "1.10.7",
|
"@swc/core-linux-arm64-musl": "1.10.12",
|
||||||
"@swc/core-linux-x64-gnu": "1.10.7",
|
"@swc/core-linux-x64-gnu": "1.10.12",
|
||||||
"@swc/core-linux-x64-musl": "1.10.7",
|
"@swc/core-linux-x64-musl": "1.10.12",
|
||||||
"@swc/core-win32-arm64-msvc": "1.10.7",
|
"@swc/core-win32-arm64-msvc": "1.10.12",
|
||||||
"@swc/core-win32-ia32-msvc": "1.10.7",
|
"@swc/core-win32-ia32-msvc": "1.10.12",
|
||||||
"@swc/core-win32-x64-msvc": "1.10.7",
|
"@swc/core-win32-x64-msvc": "1.10.12",
|
||||||
"@tensorflow/tfjs": "4.22.0",
|
"@tensorflow/tfjs": "4.22.0",
|
||||||
"@tensorflow/tfjs-node": "4.22.0",
|
"@tensorflow/tfjs-node": "4.22.0",
|
||||||
"bufferutil": "4.0.9",
|
"bufferutil": "4.0.9",
|
||||||
|
@ -63,11 +63,11 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@authenio/samlify-node-xmllint": "2.0.0",
|
"@authenio/samlify-node-xmllint": "2.0.0",
|
||||||
"@aws-sdk/client-s3": "3.729.0",
|
"@aws-sdk/client-s3": "3.740.0",
|
||||||
"@aws-sdk/lib-storage": "3.729.0",
|
"@aws-sdk/lib-storage": "3.740.0",
|
||||||
"@bull-board/api": "6.6.2",
|
"@bull-board/api": "6.7.4",
|
||||||
"@bull-board/fastify": "6.6.2",
|
"@bull-board/fastify": "6.7.4",
|
||||||
"@bull-board/ui": "6.6.2",
|
"@bull-board/ui": "6.7.4",
|
||||||
"@discordapp/twemoji": "15.1.0",
|
"@discordapp/twemoji": "15.1.0",
|
||||||
"@elastic/elasticsearch": "8.17.0",
|
"@elastic/elasticsearch": "8.17.0",
|
||||||
"@fastify/accepts": "5.0.2",
|
"@fastify/accepts": "5.0.2",
|
||||||
|
@ -76,21 +76,21 @@
|
||||||
"@fastify/express": "4.0.2",
|
"@fastify/express": "4.0.2",
|
||||||
"@fastify/formbody": "8.0.2",
|
"@fastify/formbody": "8.0.2",
|
||||||
"@fastify/http-proxy": "11.0.1",
|
"@fastify/http-proxy": "11.0.1",
|
||||||
"@fastify/multipart": "9.0.2",
|
"@fastify/multipart": "9.0.3",
|
||||||
"@fastify/static": "8.0.4",
|
"@fastify/static": "8.0.4",
|
||||||
"@fastify/view": "10.0.2",
|
"@fastify/view": "10.0.2",
|
||||||
"@misskey-dev/sharp-read-bmp": "1.2.0",
|
"@misskey-dev/sharp-read-bmp": "1.2.0",
|
||||||
"@misskey-dev/summaly": "github:MisskeyIO/summaly#5.1.3",
|
"@misskey-dev/summaly": "github:MisskeyIO/summaly#5.1.3",
|
||||||
"@napi-rs/canvas": "0.1.65",
|
"@napi-rs/canvas": "0.1.65",
|
||||||
"@nestjs/common": "10.4.15",
|
"@nestjs/common": "11.0.7",
|
||||||
"@nestjs/core": "10.4.15",
|
"@nestjs/core": "11.0.7",
|
||||||
"@nestjs/testing": "10.4.15",
|
"@nestjs/testing": "11.0.7",
|
||||||
"@peertube/http-signature": "1.7.0",
|
"@peertube/http-signature": "1.7.0",
|
||||||
"@simplewebauthn/server": "13.1.0",
|
"@simplewebauthn/server": "13.1.1",
|
||||||
"@sinonjs/fake-timers": "11.3.1",
|
"@sinonjs/fake-timers": "11.3.1",
|
||||||
"@smithy/node-http-handler": "4.0.2",
|
"@smithy/node-http-handler": "4.0.2",
|
||||||
"@swc/cli": "0.6.0",
|
"@swc/cli": "0.6.0",
|
||||||
"@swc/core": "1.10.7",
|
"@swc/core": "1.10.12",
|
||||||
"@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",
|
||||||
|
@ -99,7 +99,7 @@
|
||||||
"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.34.10",
|
"bullmq": "5.39.1",
|
||||||
"cacheable-lookup": "7.0.0",
|
"cacheable-lookup": "7.0.0",
|
||||||
"cbor": "10.0.3",
|
"cbor": "10.0.3",
|
||||||
"chalk": "5.4.1",
|
"chalk": "5.4.1",
|
||||||
|
@ -114,7 +114,7 @@
|
||||||
"fastify-http-errors-enhanced": "6.0.1",
|
"fastify-http-errors-enhanced": "6.0.1",
|
||||||
"fastify-raw-body": "5.0.0",
|
"fastify-raw-body": "5.0.0",
|
||||||
"feed": "4.2.2",
|
"feed": "4.2.2",
|
||||||
"file-type": "20.0.0",
|
"file-type": "20.0.1",
|
||||||
"fluent-ffmpeg": "2.1.3",
|
"fluent-ffmpeg": "2.1.3",
|
||||||
"form-data": "4.0.1",
|
"form-data": "4.0.1",
|
||||||
"got": "14.4.5",
|
"got": "14.4.5",
|
||||||
|
@ -131,7 +131,7 @@
|
||||||
"json5": "2.2.3",
|
"json5": "2.2.3",
|
||||||
"jsonld": "8.3.3",
|
"jsonld": "8.3.3",
|
||||||
"jsrsasign": "11.1.0",
|
"jsrsasign": "11.1.0",
|
||||||
"meilisearch": "0.48.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",
|
||||||
|
@ -142,7 +142,7 @@
|
||||||
"nested-property": "4.0.0",
|
"nested-property": "4.0.0",
|
||||||
"node-fetch": "3.3.2",
|
"node-fetch": "3.3.2",
|
||||||
"node-forge": "1.3.1",
|
"node-forge": "1.3.1",
|
||||||
"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",
|
||||||
|
@ -191,7 +191,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@jest/globals": "29.7.0",
|
"@jest/globals": "29.7.0",
|
||||||
"@misskey-dev/eslint-plugin": "1.0.0",
|
"@misskey-dev/eslint-plugin": "1.0.0",
|
||||||
"@nestjs/platform-express": "10.4.15",
|
"@nestjs/platform-express": "11.0.7",
|
||||||
"@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",
|
||||||
|
@ -208,14 +208,14 @@
|
||||||
"@types/jsonld": "1.5.15",
|
"@types/jsonld": "1.5.15",
|
||||||
"@types/jsrsasign": "10.5.15",
|
"@types/jsrsasign": "10.5.15",
|
||||||
"@types/mime-types": "2.1.4",
|
"@types/mime-types": "2.1.4",
|
||||||
"@types/ms": "0.7.34",
|
"@types/ms": "2.1.0",
|
||||||
"@types/node": "22.10.7",
|
"@types/node": "22.13.0",
|
||||||
"@types/node-forge": "1.3.11",
|
"@types/node-forge": "1.3.11",
|
||||||
"@types/nodemailer": "6.4.17",
|
"@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/psl": "1.1.3",
|
"@types/psl": "1.1.3",
|
||||||
"@types/pug": "2.0.10",
|
"@types/pug": "2.0.10",
|
||||||
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
||||||
|
@ -231,7 +231,7 @@
|
||||||
"@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.10.0",
|
"@typescript-eslint/eslint-plugin": "7.10.0",
|
||||||
"@typescript-eslint/parser": "7.10.0",
|
"@typescript-eslint/parser": "7.10.0",
|
||||||
"aws-sdk-client-mock": "4.1.0",
|
"aws-sdk-client-mock": "4.1.0",
|
||||||
|
@ -243,7 +243,7 @@
|
||||||
"jest": "29.7.0",
|
"jest": "29.7.0",
|
||||||
"jest-mock": "29.7.0",
|
"jest-mock": "29.7.0",
|
||||||
"nodemon": "3.1.9",
|
"nodemon": "3.1.9",
|
||||||
"pid-port": "1.0.0",
|
"pid-port": "1.0.2",
|
||||||
"simple-oauth2": "5.1.0"
|
"simple-oauth2": "5.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -356,7 +356,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
||||||
* 指定ユーザーのバッジロール一覧取得
|
* 指定ユーザーのバッジロール一覧取得
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
public async getUserBadgeRoles(userId: MiUser['id']) {
|
public async getUserBadgeRoles(userId: MiUser['id'], publicOnly: boolean) {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
let assigns = await this.roleAssignmentByUserIdCache.fetch(userId, () => this.roleAssignmentsRepository.findBy({ userId }));
|
let assigns = await this.roleAssignmentByUserIdCache.fetch(userId, () => this.roleAssignmentsRepository.findBy({ userId }));
|
||||||
// 期限切れのロールを除外
|
// 期限切れのロールを除外
|
||||||
|
@ -368,12 +368,25 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
||||||
if (badgeCondRoles.length > 0) {
|
if (badgeCondRoles.length > 0) {
|
||||||
const user = roles.some(r => r.target === 'conditional') ? await this.cacheService.findUserById(userId) : null;
|
const user = roles.some(r => r.target === 'conditional') ? await this.cacheService.findUserById(userId) : null;
|
||||||
const matchedBadgeCondRoles = badgeCondRoles.filter(r => this.evalCond(user!, assignedRoles, r.condFormula));
|
const matchedBadgeCondRoles = badgeCondRoles.filter(r => this.evalCond(user!, assignedRoles, r.condFormula));
|
||||||
return [...assignedBadgeRoles, ...matchedBadgeCondRoles];
|
return this.sortAndMapBadgeRoles([...assignedBadgeRoles, ...matchedBadgeCondRoles], publicOnly);
|
||||||
} else {
|
} else {
|
||||||
return assignedBadgeRoles;
|
return this.sortAndMapBadgeRoles(assignedBadgeRoles, publicOnly);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
private sortAndMapBadgeRoles(roles: MiRole[], publicOnly: boolean) {
|
||||||
|
return roles
|
||||||
|
.filter((r) => r.isPublic || !publicOnly)
|
||||||
|
.sort((a, b) => b.displayOrder - a.displayOrder)
|
||||||
|
.map((r) => ({
|
||||||
|
name: r.name,
|
||||||
|
iconUrl: r.iconUrl,
|
||||||
|
displayOrder: r.displayOrder,
|
||||||
|
behavior: r.badgeBehavior ?? undefined,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async getUserPolicies(userId: MiUser['id'] | null): Promise<RolePolicies> {
|
public async getUserPolicies(userId: MiUser['id'] | null): Promise<RolePolicies> {
|
||||||
const meta = await this.metaService.fetch();
|
const meta = await this.metaService.fetch();
|
||||||
|
|
|
@ -12,7 +12,7 @@ import type { Packed } from '@/misc/json-schema.js';
|
||||||
import { awaitAll } from '@/misc/prelude/await-all.js';
|
import { awaitAll } from '@/misc/prelude/await-all.js';
|
||||||
import type { MiUser } from '@/models/User.js';
|
import type { MiUser } from '@/models/User.js';
|
||||||
import type { MiDriveFile } from '@/models/DriveFile.js';
|
import type { MiDriveFile } from '@/models/DriveFile.js';
|
||||||
import { appendQuery, query } from '@/misc/prelude/url.js';
|
import { appendQuery, omitHttps, query } from '@/misc/prelude/url.js';
|
||||||
import { deepClone } from '@/misc/clone.js';
|
import { deepClone } from '@/misc/clone.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { isMimeImage } from '@/misc/is-mime-image.js';
|
import { isMimeImage } from '@/misc/is-mime-image.js';
|
||||||
|
@ -77,9 +77,8 @@ export class DriveFileEntityService {
|
||||||
@bindThis
|
@bindThis
|
||||||
private getProxiedUrl(url: string, mode?: 'static' | 'avatar'): string {
|
private getProxiedUrl(url: string, mode?: 'static' | 'avatar'): string {
|
||||||
return appendQuery(
|
return appendQuery(
|
||||||
`${this.config.mediaProxy}/${mode ?? 'image'}.webp`,
|
`${this.config.mediaProxy}/${mode ?? 'image'}/${encodeURIComponent(omitHttps(url))}`,
|
||||||
query({
|
query({
|
||||||
url,
|
|
||||||
...(mode ? { [mode]: '1' } : {}),
|
...(mode ? { [mode]: '1' } : {}),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
|
@ -518,16 +518,7 @@ export class UserEntityService implements OnModuleInit {
|
||||||
} : undefined) : undefined,
|
} : undefined) : undefined,
|
||||||
emojis: this.customEmojiService.populateEmojis(user.emojis, user.host),
|
emojis: this.customEmojiService.populateEmojis(user.emojis, user.host),
|
||||||
onlineStatus: this.getOnlineStatus(user),
|
onlineStatus: this.getOnlineStatus(user),
|
||||||
badgeRoles: this.roleService.getUserBadgeRoles(user.id).then((rs) => rs
|
badgeRoles: this.roleService.getUserBadgeRoles(user.id, !iAmModerator),
|
||||||
.filter((r) => r.isPublic || iAmModerator)
|
|
||||||
.sort((a, b) => b.displayOrder - a.displayOrder)
|
|
||||||
.map((r) => ({
|
|
||||||
name: r.name,
|
|
||||||
iconUrl: r.iconUrl,
|
|
||||||
displayOrder: r.displayOrder,
|
|
||||||
behavior: r.badgeBehavior ?? undefined,
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
|
|
||||||
...(isDetailed ? {
|
...(isDetailed ? {
|
||||||
url: profile?.url,
|
url: profile?.url,
|
||||||
|
|
|
@ -14,10 +14,16 @@ export function query(obj: Record<string, unknown>): string {
|
||||||
.reduce((a, [k, v]) => (a[k] = v, a), {} as Record<string, any>);
|
.reduce((a, [k, v]) => (a[k] = v, a), {} as Record<string, any>);
|
||||||
|
|
||||||
return Object.entries(params)
|
return Object.entries(params)
|
||||||
.map((e) => `${e[0]}=${encodeURIComponent(e[1])}`)
|
.map((p) => `${p[0]}=${encodeURIComponent(p[1])}`)
|
||||||
.join('&');
|
.join('&');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function appendQuery(url: string, query: string): string {
|
export function appendQuery(url: string, query: string): string {
|
||||||
return `${url}${/\?/.test(url) ? url.endsWith('?') ? '' : '&' : '?'}${query}`;
|
return `${url}${/\?/.test(url) ? url.endsWith('?') ? '' : '&' : '?'}${query}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function omitHttps(url: string): string {
|
||||||
|
if (url.startsWith('https://')) return url.slice(8);
|
||||||
|
if (url.startsWith('https%3A%2F%2F')) return url.slice(14);
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
|
@ -108,7 +108,7 @@ class MyCustomLogger implements Logger {
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public logQuery(query: string, parameters?: any[]) {
|
public logQuery(query: string, parameters?: any[]) {
|
||||||
sqlLogger.info(this.highlight(query));
|
sqlLogger.debug(this.highlight(query));
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
|
|
@ -26,6 +26,7 @@ import { FileInfoService } from '@/core/FileInfoService.js';
|
||||||
import { LoggerService } from '@/core/LoggerService.js';
|
import { LoggerService } from '@/core/LoggerService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { isMimeImage } from '@/misc/is-mime-image.js';
|
import { isMimeImage } from '@/misc/is-mime-image.js';
|
||||||
|
import { appendQuery, omitHttps, query } from '@/misc/prelude/url.js';
|
||||||
import { correctFilename } from '@/misc/correct-filename.js';
|
import { correctFilename } from '@/misc/correct-filename.js';
|
||||||
import { handleRequestRedirectToOmitSearch } from '@/misc/fastify-hook-handlers.js';
|
import { handleRequestRedirectToOmitSearch } from '@/misc/fastify-hook-handlers.js';
|
||||||
import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify';
|
import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify';
|
||||||
|
@ -35,6 +36,16 @@ const _dirname = dirname(_filename);
|
||||||
|
|
||||||
const assets = `${_dirname}/../../server/file/assets/`;
|
const assets = `${_dirname}/../../server/file/assets/`;
|
||||||
|
|
||||||
|
interface TransformQuery {
|
||||||
|
origin?: string;
|
||||||
|
fallback?: string;
|
||||||
|
emoji?: string;
|
||||||
|
avatar?: string;
|
||||||
|
static?: string;
|
||||||
|
preview?: string;
|
||||||
|
badge?: string;
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FileServerService {
|
export class FileServerService {
|
||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
|
@ -87,10 +98,18 @@ export class FileServerService {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
fastify.get<{
|
||||||
|
Params: { type: string; url: string; };
|
||||||
|
Querystring: { url?: string; } & TransformQuery;
|
||||||
|
}>('/proxy/:type/:url', async (request, reply) => {
|
||||||
|
return await this.proxyHandler(request, reply)
|
||||||
|
.catch(err => this.errorHandler(request, reply, err));
|
||||||
|
});
|
||||||
|
|
||||||
fastify.get<{
|
fastify.get<{
|
||||||
Params: { url: string; };
|
Params: { url: string; };
|
||||||
Querystring: { url?: string; };
|
Querystring: { url?: string; } & TransformQuery;
|
||||||
}>('/proxy/:url*', async (request, reply) => {
|
}>('/proxy/:url', async (request, reply) => {
|
||||||
return await this.proxyHandler(request, reply)
|
return await this.proxyHandler(request, reply)
|
||||||
.catch(err => this.errorHandler(request, reply, err));
|
.catch(err => this.errorHandler(request, reply, err));
|
||||||
});
|
});
|
||||||
|
@ -142,12 +161,15 @@ export class FileServerService {
|
||||||
if (isMimeImage(file.mime, 'sharp-convertible-image-with-bmp')) {
|
if (isMimeImage(file.mime, 'sharp-convertible-image-with-bmp')) {
|
||||||
reply.header('Cache-Control', 'max-age=31536000, immutable');
|
reply.header('Cache-Control', 'max-age=31536000, immutable');
|
||||||
|
|
||||||
const url = new URL(`${this.config.mediaProxy}/static.webp`);
|
const url = appendQuery(
|
||||||
url.searchParams.set('url', file.url);
|
`${this.config.mediaProxy}/static/${encodeURIComponent(omitHttps(file.url))}`,
|
||||||
url.searchParams.set('static', '1');
|
query({
|
||||||
|
static: '1',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
file.cleanup();
|
file.cleanup();
|
||||||
return await reply.redirect(url.toString(), 301);
|
return await reply.redirect(url, 301);
|
||||||
} else if (file.mime.startsWith('video/')) {
|
} else if (file.mime.startsWith('video/')) {
|
||||||
const externalThumbnail = this.videoProcessingService.getExternalVideoThumbnailUrl(file.url);
|
const externalThumbnail = this.videoProcessingService.getExternalVideoThumbnailUrl(file.url);
|
||||||
if (externalThumbnail) {
|
if (externalThumbnail) {
|
||||||
|
@ -163,11 +185,10 @@ export class FileServerService {
|
||||||
if (['image/svg+xml'].includes(file.mime)) {
|
if (['image/svg+xml'].includes(file.mime)) {
|
||||||
reply.header('Cache-Control', 'max-age=31536000, immutable');
|
reply.header('Cache-Control', 'max-age=31536000, immutable');
|
||||||
|
|
||||||
const url = new URL(`${this.config.mediaProxy}/svg.webp`);
|
const url = `${this.config.mediaProxy}/svg/${encodeURIComponent(omitHttps(file.url))}`;
|
||||||
url.searchParams.set('url', file.url);
|
|
||||||
|
|
||||||
file.cleanup();
|
file.cleanup();
|
||||||
return await reply.redirect(url.toString(), 301);
|
return await reply.redirect(url, 301);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -291,30 +312,43 @@ export class FileServerService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async proxyHandler(request: FastifyRequest<{ Params: { url: string; }; Querystring: { url?: string; }; }>, reply: FastifyReply) {
|
private async proxyHandler(request: FastifyRequest<{ Params: { type?: string; url: string; }; Querystring: { url?: string; } & TransformQuery; }>, reply: FastifyReply) {
|
||||||
const url = 'url' in request.query ? request.query.url : 'https://' + request.params.url;
|
let url: string;
|
||||||
|
if ('url' in request.query && request.query.url) {
|
||||||
|
url = request.query.url;
|
||||||
|
} else {
|
||||||
|
url = request.params.url;
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof url !== 'string') {
|
// noinspection HttpUrlsUsage
|
||||||
|
if (url
|
||||||
|
&& !url.startsWith('http://')
|
||||||
|
&& !url.startsWith('https://')
|
||||||
|
) {
|
||||||
|
url = 'https://' + url;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!url) {
|
||||||
reply.code(400);
|
reply.code(400);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// アバタークロップなど、どうしてもオリジンである必要がある場合
|
// アバタークロップなど、どうしてもオリジンである必要がある場合
|
||||||
const mustOrigin = 'origin' in request.query;
|
const mustOrigin = 'origin' in request.query;
|
||||||
|
const transformQuery = request.query as TransformQuery;
|
||||||
|
|
||||||
if (this.config.externalMediaProxyEnabled && !mustOrigin) {
|
if (this.config.externalMediaProxyEnabled && !mustOrigin) {
|
||||||
// 外部のメディアプロキシが有効なら、そちらにリダイレクト
|
// 外部のメディアプロキシが有効なら、そちらにリダイレクト
|
||||||
|
|
||||||
reply.header('Cache-Control', 'public, max-age=259200'); // 3 days
|
reply.header('Cache-Control', 'public, max-age=259200'); // 3 days
|
||||||
|
|
||||||
const url = new URL(`${this.config.mediaProxy}/${request.params.url || ''}`);
|
const redirectUrl = appendQuery(
|
||||||
|
`${this.config.mediaProxy}/redirect/${encodeURIComponent(omitHttps(url))}`,
|
||||||
for (const [key, value] of Object.entries(request.query)) {
|
query(transformQuery as Record<string, string>),
|
||||||
url.searchParams.append(key, value);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
return reply.redirect(
|
return reply.redirect(
|
||||||
url.toString(),
|
redirectUrl,
|
||||||
301,
|
301,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -344,11 +378,11 @@ export class FileServerService {
|
||||||
const isAnimationConvertibleImage = isMimeImage(file.mime, 'sharp-animation-convertible-image-with-bmp');
|
const isAnimationConvertibleImage = isMimeImage(file.mime, 'sharp-animation-convertible-image-with-bmp');
|
||||||
|
|
||||||
if (
|
if (
|
||||||
'emoji' in request.query ||
|
'emoji' in transformQuery ||
|
||||||
'avatar' in request.query ||
|
'avatar' in transformQuery ||
|
||||||
'static' in request.query ||
|
'static' in transformQuery ||
|
||||||
'preview' in request.query ||
|
'preview' in transformQuery ||
|
||||||
'badge' in request.query
|
'badge' in transformQuery
|
||||||
) {
|
) {
|
||||||
if (!isConvertibleImage) {
|
if (!isConvertibleImage) {
|
||||||
// 画像でないなら404でお茶を濁す
|
// 画像でないなら404でお茶を濁す
|
||||||
|
@ -357,17 +391,17 @@ export class FileServerService {
|
||||||
}
|
}
|
||||||
|
|
||||||
let image: IImageStreamable | null = null;
|
let image: IImageStreamable | null = null;
|
||||||
if ('emoji' in request.query || 'avatar' in request.query) {
|
if ('emoji' in transformQuery || 'avatar' in transformQuery) {
|
||||||
if (!isAnimationConvertibleImage && !('static' in request.query)) {
|
if (!isAnimationConvertibleImage && !('static' in transformQuery)) {
|
||||||
image = {
|
image = {
|
||||||
data: fs.createReadStream(file.path),
|
data: fs.createReadStream(file.path),
|
||||||
ext: file.ext,
|
ext: file.ext,
|
||||||
type: file.mime,
|
type: file.mime,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
const data = (await sharpBmp(file.path, file.mime, { animated: !('static' in request.query) }))
|
const data = (await sharpBmp(file.path, file.mime, { animated: !('static' in transformQuery) }))
|
||||||
.resize({
|
.resize({
|
||||||
height: 'emoji' in request.query ? 128 : 320,
|
height: 'emoji' in transformQuery ? 128 : 320,
|
||||||
withoutEnlargement: true,
|
withoutEnlargement: true,
|
||||||
})
|
})
|
||||||
.webp(webpDefault);
|
.webp(webpDefault);
|
||||||
|
@ -378,11 +412,11 @@ export class FileServerService {
|
||||||
type: 'image/webp',
|
type: 'image/webp',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else if ('static' in request.query) {
|
} else if ('static' in transformQuery) {
|
||||||
image = this.imageProcessingService.convertSharpToWebpStream(await sharpBmp(file.path, file.mime), 498, 422);
|
image = this.imageProcessingService.convertSharpToWebpStream(await sharpBmp(file.path, file.mime), 498, 422);
|
||||||
} else if ('preview' in request.query) {
|
} else if ('preview' in transformQuery) {
|
||||||
image = this.imageProcessingService.convertSharpToWebpStream(await sharpBmp(file.path, file.mime), 200, 200);
|
image = this.imageProcessingService.convertSharpToWebpStream(await sharpBmp(file.path, file.mime), 200, 200);
|
||||||
} else if ('badge' in request.query) {
|
} else if ('badge' in transformQuery) {
|
||||||
const mask = (await sharpBmp(file.path, file.mime))
|
const mask = (await sharpBmp(file.path, file.mime))
|
||||||
.resize(96, 96, {
|
.resize(96, 96, {
|
||||||
fit: 'contain',
|
fit: 'contain',
|
||||||
|
|
|
@ -19,6 +19,7 @@ import { DI } from '@/di-symbols.js';
|
||||||
import type Logger from '@/logger.js';
|
import type Logger from '@/logger.js';
|
||||||
import * as Acct from '@/misc/acct.js';
|
import * as Acct from '@/misc/acct.js';
|
||||||
import { genIdenticon } from '@/misc/gen-identicon.js';
|
import { genIdenticon } from '@/misc/gen-identicon.js';
|
||||||
|
import { appendQuery, omitHttps, query } from '@/misc/prelude/url.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { LoggerService } from '@/core/LoggerService.js';
|
import { LoggerService } from '@/core/LoggerService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
@ -77,8 +78,9 @@ export class ServerService implements OnApplicationShutdown {
|
||||||
@bindThis
|
@bindThis
|
||||||
public async launch(): Promise<void> {
|
public async launch(): Promise<void> {
|
||||||
const fastify = Fastify({
|
const fastify = Fastify({
|
||||||
trustProxy: true,
|
|
||||||
logger: false,
|
logger: false,
|
||||||
|
maxParamLength: 1024,
|
||||||
|
trustProxy: true,
|
||||||
});
|
});
|
||||||
this.#fastify = fastify;
|
this.#fastify = fastify;
|
||||||
|
|
||||||
|
@ -162,22 +164,28 @@ export class ServerService implements OnApplicationShutdown {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let url: URL;
|
let url: string;
|
||||||
if ('badge' in request.query) {
|
if ('badge' in request.query) {
|
||||||
url = new URL(`${this.config.mediaProxy}/emoji.png`);
|
url = appendQuery(
|
||||||
// || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ)
|
// || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ)
|
||||||
url.searchParams.set('url', emoji.publicUrl || emoji.originalUrl);
|
`${this.config.mediaProxy}/emoji/${encodeURIComponent(omitHttps(emoji.publicUrl || emoji.originalUrl))}`,
|
||||||
url.searchParams.set('badge', '1');
|
query({
|
||||||
|
badge: '1',
|
||||||
|
}),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
url = new URL(`${this.config.mediaProxy}/emoji.webp`);
|
url = appendQuery(
|
||||||
// || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ)
|
// || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ)
|
||||||
url.searchParams.set('url', emoji.publicUrl || emoji.originalUrl);
|
`${this.config.mediaProxy}/emoji/${encodeURIComponent(omitHttps(emoji.publicUrl || emoji.originalUrl))}`,
|
||||||
url.searchParams.set('emoji', '1');
|
query({
|
||||||
if ('static' in request.query) url.searchParams.set('static', '1');
|
emoji: '1',
|
||||||
|
...('static' in request.query ? { static: '1' } : {}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return reply.redirect(
|
return reply.redirect(
|
||||||
url.toString(),
|
url,
|
||||||
301,
|
301,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -15,6 +15,7 @@ import { bindThis } from '@/decorators.js';
|
||||||
import { CacheService } from '@/core/CacheService.js';
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
import { MiLocalUser } from '@/models/User.js';
|
import { MiLocalUser } from '@/models/User.js';
|
||||||
import { UserService } from '@/core/UserService.js';
|
import { UserService } from '@/core/UserService.js';
|
||||||
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
import { ChannelFollowingService } from '@/core/ChannelFollowingService.js';
|
import { ChannelFollowingService } from '@/core/ChannelFollowingService.js';
|
||||||
import { AuthenticateService, AuthenticationError } from './AuthenticateService.js';
|
import { AuthenticateService, AuthenticationError } from './AuthenticateService.js';
|
||||||
import MainStreamConnection from './stream/Connection.js';
|
import MainStreamConnection from './stream/Connection.js';
|
||||||
|
@ -40,6 +41,7 @@ export class StreamingApiServerService {
|
||||||
private channelsService: ChannelsService,
|
private channelsService: ChannelsService,
|
||||||
private notificationService: NotificationService,
|
private notificationService: NotificationService,
|
||||||
private usersService: UserService,
|
private usersService: UserService,
|
||||||
|
private roleService: RoleService,
|
||||||
private channelFollowingService: ChannelFollowingService,
|
private channelFollowingService: ChannelFollowingService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
@ -99,6 +101,7 @@ export class StreamingApiServerService {
|
||||||
this.noteReadService,
|
this.noteReadService,
|
||||||
this.notificationService,
|
this.notificationService,
|
||||||
this.cacheService,
|
this.cacheService,
|
||||||
|
this.roleService,
|
||||||
this.channelFollowingService,
|
this.channelFollowingService,
|
||||||
user, app,
|
user, app,
|
||||||
);
|
);
|
||||||
|
|
|
@ -62,6 +62,8 @@ export const paramDef = {
|
||||||
untilId: { type: 'string', format: 'misskey:id' },
|
untilId: { type: 'string', format: 'misskey:id' },
|
||||||
movedFromId: { type: 'string', format: 'misskey:id', nullable: true },
|
movedFromId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||||
movedToId: { type: 'string', format: 'misskey:id', nullable: true },
|
movedToId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||||
|
from: { type: 'string', enum: ['local', 'remote', 'all'], nullable: true },
|
||||||
|
to: { type: 'string', enum: ['local', 'remote', 'all'], nullable: true },
|
||||||
},
|
},
|
||||||
required: [],
|
required: [],
|
||||||
} as const;
|
} as const;
|
||||||
|
@ -86,6 +88,28 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
query.andWhere('accountMoveLogs.movedToId = :movedToId', { movedToId: ps.movedToId });
|
query.andWhere('accountMoveLogs.movedToId = :movedToId', { movedToId: ps.movedToId });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ps.from != null || ps.to != null) {
|
||||||
|
query
|
||||||
|
.innerJoin('accountMoveLogs.movedFrom', 'movedFrom')
|
||||||
|
.innerJoin('accountMoveLogs.movedTo', 'movedTo');
|
||||||
|
|
||||||
|
if (ps.from === 'local') {
|
||||||
|
query.andWhere('movedFrom.host IS NULL');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ps.from === 'remote') {
|
||||||
|
query.andWhere('movedFrom.host IS NOT NULL');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ps.to === 'local') {
|
||||||
|
query.andWhere('movedTo.host IS NULL');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ps.to === 'remote') {
|
||||||
|
query.andWhere('movedTo.host IS NOT NULL');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const accountMoveLogs = await query.limit(ps.limit).getMany();
|
const accountMoveLogs = await query.limit(ps.limit).getMany();
|
||||||
|
|
||||||
return await this.userAccountMoveLogEntityService.packMany(accountMoveLogs, me);
|
return await this.userAccountMoveLogEntityService.packMany(accountMoveLogs, me);
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { CacheService } from '@/core/CacheService.js';
|
||||||
import { MiFollowing, MiUserProfile } from '@/models/_.js';
|
import { MiFollowing, MiUserProfile } from '@/models/_.js';
|
||||||
import type { StreamEventEmitter, GlobalEvents } from '@/core/GlobalEventService.js';
|
import type { StreamEventEmitter, GlobalEvents } from '@/core/GlobalEventService.js';
|
||||||
import { ChannelFollowingService } from '@/core/ChannelFollowingService.js';
|
import { ChannelFollowingService } from '@/core/ChannelFollowingService.js';
|
||||||
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
import type { ChannelsService } from './ChannelsService.js';
|
import type { ChannelsService } from './ChannelsService.js';
|
||||||
import type { EventEmitter } from 'events';
|
import type { EventEmitter } from 'events';
|
||||||
import type Channel from './channel.js';
|
import type Channel from './channel.js';
|
||||||
|
@ -31,6 +32,7 @@ export default class Connection {
|
||||||
private subscribingNotes: any = {};
|
private subscribingNotes: any = {};
|
||||||
private cachedNotes: Packed<'Note'>[] = [];
|
private cachedNotes: Packed<'Note'>[] = [];
|
||||||
public userProfile: MiUserProfile | null = null;
|
public userProfile: MiUserProfile | null = null;
|
||||||
|
public isModerator = false;
|
||||||
public following: Record<string, Pick<MiFollowing, 'withReplies'> | undefined> = {};
|
public following: Record<string, Pick<MiFollowing, 'withReplies'> | undefined> = {};
|
||||||
public followingChannels: Set<string> = new Set();
|
public followingChannels: Set<string> = new Set();
|
||||||
public userIdsWhoMeMuting: Set<string> = new Set();
|
public userIdsWhoMeMuting: Set<string> = new Set();
|
||||||
|
@ -45,6 +47,7 @@ export default class Connection {
|
||||||
private noteReadService: NoteReadService,
|
private noteReadService: NoteReadService,
|
||||||
private notificationService: NotificationService,
|
private notificationService: NotificationService,
|
||||||
private cacheService: CacheService,
|
private cacheService: CacheService,
|
||||||
|
private roleService: RoleService,
|
||||||
private channelFollowingService: ChannelFollowingService,
|
private channelFollowingService: ChannelFollowingService,
|
||||||
|
|
||||||
user: MiUser | null | undefined,
|
user: MiUser | null | undefined,
|
||||||
|
@ -80,6 +83,7 @@ export default class Connection {
|
||||||
public async init() {
|
public async init() {
|
||||||
if (this.user != null) {
|
if (this.user != null) {
|
||||||
await this.fetch();
|
await this.fetch();
|
||||||
|
this.isModerator = await this.roleService.isModerator(this.user);
|
||||||
|
|
||||||
if (!this.fetchIntervalId) {
|
if (!this.fetchIntervalId) {
|
||||||
this.fetchIntervalId = setInterval(this.fetch, 1000 * 10);
|
this.fetchIntervalId = setInterval(this.fetch, 1000 * 10);
|
||||||
|
|
|
@ -30,6 +30,10 @@ export default abstract class Channel {
|
||||||
return this.connection.userProfile;
|
return this.connection.userProfile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected get iAmModerator() {
|
||||||
|
return this.connection.isModerator;
|
||||||
|
}
|
||||||
|
|
||||||
protected get following() {
|
protected get following() {
|
||||||
return this.connection.following;
|
return this.connection.following;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
|
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
||||||
import Channel, { type MiChannelService } from '../channel.js';
|
import Channel, { type MiChannelService } from '../channel.js';
|
||||||
|
|
||||||
|
@ -18,6 +19,7 @@ class AntennaChannel extends Channel {
|
||||||
private minimize: boolean;
|
private minimize: boolean;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
private roleService: RoleService,
|
||||||
private noteEntityService: NoteEntityService,
|
private noteEntityService: NoteEntityService,
|
||||||
|
|
||||||
id: string,
|
id: string,
|
||||||
|
@ -64,11 +66,14 @@ class AntennaChannel extends Channel {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.minimize && ['public', 'home'].includes(note.visibility)) {
|
if (this.minimize && ['public', 'home'].includes(note.visibility)) {
|
||||||
|
const badgeRoles = this.iAmModerator ? await this.roleService.getUserBadgeRoles(note.userId, false) : undefined;
|
||||||
|
|
||||||
this.send('note', {
|
this.send('note', {
|
||||||
id: note.id, myReaction: note.myReaction,
|
id: note.id, myReaction: note.myReaction,
|
||||||
poll: note.poll?.choices ? { choices: note.poll.choices } : undefined,
|
poll: note.poll?.choices ? { choices: note.poll.choices } : undefined,
|
||||||
reply: note.reply?.myReaction ? { myReaction: note.reply.myReaction } : undefined,
|
reply: note.reply?.myReaction ? { myReaction: note.reply.myReaction } : undefined,
|
||||||
renote: note.renote?.myReaction ? { myReaction: note.renote.myReaction } : undefined,
|
renote: note.renote?.myReaction ? { myReaction: note.renote.myReaction } : undefined,
|
||||||
|
...(badgeRoles?.length ? { user: { badgeRoles } } : {}),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.send('note', note);
|
this.send('note', note);
|
||||||
|
@ -92,6 +97,7 @@ export class AntennaChannelService implements MiChannelService<true> {
|
||||||
public readonly kind = AntennaChannel.kind;
|
public readonly kind = AntennaChannel.kind;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
private roleService: RoleService,
|
||||||
private noteEntityService: NoteEntityService,
|
private noteEntityService: NoteEntityService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
@ -99,6 +105,7 @@ export class AntennaChannelService implements MiChannelService<true> {
|
||||||
@bindThis
|
@bindThis
|
||||||
public create(id: string, connection: Channel['connection']): AntennaChannel {
|
public create(id: string, connection: Channel['connection']): AntennaChannel {
|
||||||
return new AntennaChannel(
|
return new AntennaChannel(
|
||||||
|
this.roleService,
|
||||||
this.noteEntityService,
|
this.noteEntityService,
|
||||||
id,
|
id,
|
||||||
connection,
|
connection,
|
||||||
|
|
|
@ -4,9 +4,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import type { Packed } from '@/misc/json-schema.js';
|
|
||||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import type { Packed } from '@/misc/json-schema.js';
|
||||||
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
|
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
||||||
import Channel, { type MiChannelService } from '../channel.js';
|
import Channel, { type MiChannelService } from '../channel.js';
|
||||||
|
|
||||||
|
@ -18,6 +19,7 @@ class ChannelChannel extends Channel {
|
||||||
private minimize: boolean;
|
private minimize: boolean;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
private roleService: RoleService,
|
||||||
private noteEntityService: NoteEntityService,
|
private noteEntityService: NoteEntityService,
|
||||||
|
|
||||||
id: string,
|
id: string,
|
||||||
|
@ -70,11 +72,14 @@ class ChannelChannel extends Channel {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.minimize && ['public', 'home'].includes(note.visibility)) {
|
if (this.minimize && ['public', 'home'].includes(note.visibility)) {
|
||||||
|
const badgeRoles = this.iAmModerator ? await this.roleService.getUserBadgeRoles(note.userId, false) : undefined;
|
||||||
|
|
||||||
this.send('note', {
|
this.send('note', {
|
||||||
id: note.id, myReaction: note.myReaction,
|
id: note.id, myReaction: note.myReaction,
|
||||||
poll: note.poll?.choices ? { choices: note.poll.choices } : undefined,
|
poll: note.poll?.choices ? { choices: note.poll.choices } : undefined,
|
||||||
reply: note.reply?.myReaction ? { myReaction: note.reply.myReaction } : undefined,
|
reply: note.reply?.myReaction ? { myReaction: note.reply.myReaction } : undefined,
|
||||||
renote: note.renote?.myReaction ? { myReaction: note.renote.myReaction } : undefined,
|
renote: note.renote?.myReaction ? { myReaction: note.renote.myReaction } : undefined,
|
||||||
|
...(badgeRoles?.length ? { user: { badgeRoles } } : {}),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.send('note', note);
|
this.send('note', note);
|
||||||
|
@ -95,6 +100,7 @@ export class ChannelChannelService implements MiChannelService<false> {
|
||||||
public readonly kind = ChannelChannel.kind;
|
public readonly kind = ChannelChannel.kind;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
private roleService: RoleService,
|
||||||
private noteEntityService: NoteEntityService,
|
private noteEntityService: NoteEntityService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
@ -102,6 +108,7 @@ export class ChannelChannelService implements MiChannelService<false> {
|
||||||
@bindThis
|
@bindThis
|
||||||
public create(id: string, connection: Channel['connection']): ChannelChannel {
|
public create(id: string, connection: Channel['connection']): ChannelChannel {
|
||||||
return new ChannelChannel(
|
return new ChannelChannel(
|
||||||
|
this.roleService,
|
||||||
this.noteEntityService,
|
this.noteEntityService,
|
||||||
id,
|
id,
|
||||||
connection,
|
connection,
|
||||||
|
|
|
@ -4,11 +4,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { bindThis } from '@/decorators.js';
|
||||||
import type { Packed } from '@/misc/json-schema.js';
|
import type { Packed } from '@/misc/json-schema.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
|
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
||||||
import Channel, { type MiChannelService } from '../channel.js';
|
import Channel, { type MiChannelService } from '../channel.js';
|
||||||
|
|
||||||
|
@ -100,11 +100,14 @@ class GlobalTimelineChannel extends Channel {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.minimize && ['public', 'home'].includes(note.visibility)) {
|
if (this.minimize && ['public', 'home'].includes(note.visibility)) {
|
||||||
|
const badgeRoles = this.iAmModerator ? await this.roleService.getUserBadgeRoles(note.userId, false) : undefined;
|
||||||
|
|
||||||
this.send('note', {
|
this.send('note', {
|
||||||
id: note.id, myReaction: note.myReaction,
|
id: note.id, myReaction: note.myReaction,
|
||||||
poll: note.poll?.choices ? { choices: note.poll.choices } : undefined,
|
poll: note.poll?.choices ? { choices: note.poll.choices } : undefined,
|
||||||
reply: note.reply?.myReaction ? { myReaction: note.reply.myReaction } : undefined,
|
reply: note.reply?.myReaction ? { myReaction: note.reply.myReaction } : undefined,
|
||||||
renote: note.renote?.myReaction ? { myReaction: note.renote.myReaction } : undefined,
|
renote: note.renote?.myReaction ? { myReaction: note.renote.myReaction } : undefined,
|
||||||
|
...(badgeRoles?.length ? { user: { badgeRoles } } : {}),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.send('note', note);
|
this.send('note', note);
|
||||||
|
|
|
@ -4,9 +4,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import type { Packed } from '@/misc/json-schema.js';
|
|
||||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import type { Packed } from '@/misc/json-schema.js';
|
||||||
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
|
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
||||||
import Channel, { type MiChannelService } from '../channel.js';
|
import Channel, { type MiChannelService } from '../channel.js';
|
||||||
|
|
||||||
|
@ -20,6 +21,7 @@ class HomeTimelineChannel extends Channel {
|
||||||
private minimize: boolean;
|
private minimize: boolean;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
private roleService: RoleService,
|
||||||
private noteEntityService: NoteEntityService,
|
private noteEntityService: NoteEntityService,
|
||||||
|
|
||||||
id: string,
|
id: string,
|
||||||
|
@ -101,11 +103,14 @@ class HomeTimelineChannel extends Channel {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.minimize && ['public', 'home'].includes(note.visibility)) {
|
if (this.minimize && ['public', 'home'].includes(note.visibility)) {
|
||||||
|
const badgeRoles = this.iAmModerator ? await this.roleService.getUserBadgeRoles(note.userId, false) : undefined;
|
||||||
|
|
||||||
this.send('note', {
|
this.send('note', {
|
||||||
id: note.id, myReaction: note.myReaction,
|
id: note.id, myReaction: note.myReaction,
|
||||||
poll: note.poll?.choices ? { choices: note.poll.choices } : undefined,
|
poll: note.poll?.choices ? { choices: note.poll.choices } : undefined,
|
||||||
reply: note.reply?.myReaction ? { myReaction: note.reply.myReaction } : undefined,
|
reply: note.reply?.myReaction ? { myReaction: note.reply.myReaction } : undefined,
|
||||||
renote: note.renote?.myReaction ? { myReaction: note.renote.myReaction } : undefined,
|
renote: note.renote?.myReaction ? { myReaction: note.renote.myReaction } : undefined,
|
||||||
|
...(badgeRoles?.length ? { user: { badgeRoles } } : {}),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.send('note', note);
|
this.send('note', note);
|
||||||
|
@ -126,6 +131,7 @@ export class HomeTimelineChannelService implements MiChannelService<true> {
|
||||||
public readonly kind = HomeTimelineChannel.kind;
|
public readonly kind = HomeTimelineChannel.kind;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
private roleService: RoleService,
|
||||||
private noteEntityService: NoteEntityService,
|
private noteEntityService: NoteEntityService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
@ -133,6 +139,7 @@ export class HomeTimelineChannelService implements MiChannelService<true> {
|
||||||
@bindThis
|
@bindThis
|
||||||
public create(id: string, connection: Channel['connection']): HomeTimelineChannel {
|
public create(id: string, connection: Channel['connection']): HomeTimelineChannel {
|
||||||
return new HomeTimelineChannel(
|
return new HomeTimelineChannel(
|
||||||
|
this.roleService,
|
||||||
this.noteEntityService,
|
this.noteEntityService,
|
||||||
id,
|
id,
|
||||||
connection,
|
connection,
|
||||||
|
|
|
@ -4,11 +4,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { bindThis } from '@/decorators.js';
|
||||||
import type { Packed } from '@/misc/json-schema.js';
|
import type { Packed } from '@/misc/json-schema.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
|
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
||||||
import Channel, { type MiChannelService } from '../channel.js';
|
import Channel, { type MiChannelService } from '../channel.js';
|
||||||
|
|
||||||
|
@ -117,11 +117,14 @@ class HybridTimelineChannel extends Channel {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.minimize && ['public', 'home'].includes(note.visibility)) {
|
if (this.minimize && ['public', 'home'].includes(note.visibility)) {
|
||||||
|
const badgeRoles = this.iAmModerator ? await this.roleService.getUserBadgeRoles(note.userId, false) : undefined;
|
||||||
|
|
||||||
this.send('note', {
|
this.send('note', {
|
||||||
id: note.id, myReaction: note.myReaction,
|
id: note.id, myReaction: note.myReaction,
|
||||||
poll: note.poll?.choices ? { choices: note.poll.choices } : undefined,
|
poll: note.poll?.choices ? { choices: note.poll.choices } : undefined,
|
||||||
reply: note.reply?.myReaction ? { myReaction: note.reply.myReaction } : undefined,
|
reply: note.reply?.myReaction ? { myReaction: note.reply.myReaction } : undefined,
|
||||||
renote: note.renote?.myReaction ? { myReaction: note.renote.myReaction } : undefined,
|
renote: note.renote?.myReaction ? { myReaction: note.renote.myReaction } : undefined,
|
||||||
|
...(badgeRoles?.length ? { user: { badgeRoles } } : {}),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.send('note', note);
|
this.send('note', note);
|
||||||
|
|
|
@ -4,11 +4,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { bindThis } from '@/decorators.js';
|
||||||
import type { Packed } from '@/misc/json-schema.js';
|
import type { Packed } from '@/misc/json-schema.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
|
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
import { isQuotePacked, isRenotePacked } from '@/misc/is-renote.js';
|
import { isQuotePacked, isRenotePacked } from '@/misc/is-renote.js';
|
||||||
import Channel, { type MiChannelService } from '../channel.js';
|
import Channel, { type MiChannelService } from '../channel.js';
|
||||||
|
|
||||||
|
@ -100,11 +100,14 @@ class LocalTimelineChannel extends Channel {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.minimize && ['public', 'home'].includes(note.visibility)) {
|
if (this.minimize && ['public', 'home'].includes(note.visibility)) {
|
||||||
|
const badgeRoles = this.iAmModerator ? await this.roleService.getUserBadgeRoles(note.userId, false) : undefined;
|
||||||
|
|
||||||
this.send('note', {
|
this.send('note', {
|
||||||
id: note.id, myReaction: note.myReaction,
|
id: note.id, myReaction: note.myReaction,
|
||||||
poll: note.poll?.choices ? { choices: note.poll.choices } : undefined,
|
poll: note.poll?.choices ? { choices: note.poll.choices } : undefined,
|
||||||
reply: note.reply?.myReaction ? { myReaction: note.reply.myReaction } : undefined,
|
reply: note.reply?.myReaction ? { myReaction: note.reply.myReaction } : undefined,
|
||||||
renote: note.renote?.myReaction ? { myReaction: note.renote.myReaction } : undefined,
|
renote: note.renote?.myReaction ? { myReaction: note.renote.myReaction } : undefined,
|
||||||
|
...(badgeRoles?.length ? { user: { badgeRoles } } : {}),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.send('note', note);
|
this.send('note', note);
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
|
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
||||||
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
||||||
import Channel, { type MiChannelService } from '../channel.js';
|
import Channel, { type MiChannelService } from '../channel.js';
|
||||||
|
@ -19,8 +19,8 @@ class RoleTimelineChannel extends Channel {
|
||||||
private minimize: boolean;
|
private minimize: boolean;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
private roleService: RoleService,
|
||||||
private noteEntityService: NoteEntityService,
|
private noteEntityService: NoteEntityService,
|
||||||
private roleservice: RoleService,
|
|
||||||
|
|
||||||
id: string,
|
id: string,
|
||||||
connection: Channel['connection'],
|
connection: Channel['connection'],
|
||||||
|
@ -42,7 +42,7 @@ class RoleTimelineChannel extends Channel {
|
||||||
if (data.type === 'note') {
|
if (data.type === 'note') {
|
||||||
const note = data.body;
|
const note = data.body;
|
||||||
|
|
||||||
if (!(await this.roleservice.isExplorable({ id: this.roleId }))) {
|
if (!(await this.roleService.isExplorable({ id: this.roleId }))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (note.visibility !== 'public') return;
|
if (note.visibility !== 'public') return;
|
||||||
|
@ -86,11 +86,14 @@ class RoleTimelineChannel extends Channel {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.minimize && ['public', 'home'].includes(note.visibility)) {
|
if (this.minimize && ['public', 'home'].includes(note.visibility)) {
|
||||||
|
const badgeRoles = this.iAmModerator ? await this.roleService.getUserBadgeRoles(note.userId, false) : undefined;
|
||||||
|
|
||||||
this.send('note', {
|
this.send('note', {
|
||||||
id: note.id, myReaction: note.myReaction,
|
id: note.id, myReaction: note.myReaction,
|
||||||
poll: note.poll?.choices ? { choices: note.poll.choices } : undefined,
|
poll: note.poll?.choices ? { choices: note.poll.choices } : undefined,
|
||||||
reply: note.reply?.myReaction ? { myReaction: note.reply.myReaction } : undefined,
|
reply: note.reply?.myReaction ? { myReaction: note.reply.myReaction } : undefined,
|
||||||
renote: note.renote?.myReaction ? { myReaction: note.renote.myReaction } : undefined,
|
renote: note.renote?.myReaction ? { myReaction: note.renote.myReaction } : undefined,
|
||||||
|
...(badgeRoles?.length ? { user: { badgeRoles } } : {}),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.send('note', note);
|
this.send('note', note);
|
||||||
|
@ -114,16 +117,16 @@ export class RoleTimelineChannelService implements MiChannelService<false> {
|
||||||
public readonly kind = RoleTimelineChannel.kind;
|
public readonly kind = RoleTimelineChannel.kind;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
private roleService: RoleService,
|
||||||
private noteEntityService: NoteEntityService,
|
private noteEntityService: NoteEntityService,
|
||||||
private roleservice: RoleService,
|
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public create(id: string, connection: Channel['connection']): RoleTimelineChannel {
|
public create(id: string, connection: Channel['connection']): RoleTimelineChannel {
|
||||||
return new RoleTimelineChannel(
|
return new RoleTimelineChannel(
|
||||||
|
this.roleService,
|
||||||
this.noteEntityService,
|
this.noteEntityService,
|
||||||
this.roleservice,
|
|
||||||
id,
|
id,
|
||||||
connection,
|
connection,
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,11 +4,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import type { MiUserListMembership, UserListMembershipsRepository, UserListsRepository } from '@/models/_.js';
|
|
||||||
import type { Packed } from '@/misc/json-schema.js';
|
|
||||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import type { MiUserListMembership, UserListMembershipsRepository, UserListsRepository } from '@/models/_.js';
|
||||||
|
import type { Packed } from '@/misc/json-schema.js';
|
||||||
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
|
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||||
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
|
||||||
import Channel, { type MiChannelService } from '../channel.js';
|
import Channel, { type MiChannelService } from '../channel.js';
|
||||||
|
|
||||||
|
@ -26,6 +27,7 @@ class UserListChannel extends Channel {
|
||||||
constructor(
|
constructor(
|
||||||
private userListsRepository: UserListsRepository,
|
private userListsRepository: UserListsRepository,
|
||||||
private userListMembershipsRepository: UserListMembershipsRepository,
|
private userListMembershipsRepository: UserListMembershipsRepository,
|
||||||
|
private roleService: RoleService,
|
||||||
private noteEntityService: NoteEntityService,
|
private noteEntityService: NoteEntityService,
|
||||||
|
|
||||||
id: string,
|
id: string,
|
||||||
|
@ -135,11 +137,14 @@ class UserListChannel extends Channel {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.minimize && ['public', 'home'].includes(note.visibility)) {
|
if (this.minimize && ['public', 'home'].includes(note.visibility)) {
|
||||||
|
const badgeRoles = this.iAmModerator ? await this.roleService.getUserBadgeRoles(note.userId, false) : undefined;
|
||||||
|
|
||||||
this.send('note', {
|
this.send('note', {
|
||||||
id: note.id, myReaction: note.myReaction,
|
id: note.id, myReaction: note.myReaction,
|
||||||
poll: note.poll?.choices ? { choices: note.poll.choices } : undefined,
|
poll: note.poll?.choices ? { choices: note.poll.choices } : undefined,
|
||||||
reply: note.reply?.myReaction ? { myReaction: note.reply.myReaction } : undefined,
|
reply: note.reply?.myReaction ? { myReaction: note.reply.myReaction } : undefined,
|
||||||
renote: note.renote?.myReaction ? { myReaction: note.renote.myReaction } : undefined,
|
renote: note.renote?.myReaction ? { myReaction: note.renote.myReaction } : undefined,
|
||||||
|
...(badgeRoles?.length ? { user: { badgeRoles } } : {}),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.send('note', note);
|
this.send('note', note);
|
||||||
|
@ -169,6 +174,7 @@ export class UserListChannelService implements MiChannelService<false> {
|
||||||
@Inject(DI.userListMembershipsRepository)
|
@Inject(DI.userListMembershipsRepository)
|
||||||
private userListMembershipsRepository: UserListMembershipsRepository,
|
private userListMembershipsRepository: UserListMembershipsRepository,
|
||||||
|
|
||||||
|
private roleService: RoleService,
|
||||||
private noteEntityService: NoteEntityService,
|
private noteEntityService: NoteEntityService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
@ -178,6 +184,7 @@ export class UserListChannelService implements MiChannelService<false> {
|
||||||
return new UserListChannel(
|
return new UserListChannel(
|
||||||
this.userListsRepository,
|
this.userListsRepository,
|
||||||
this.userListMembershipsRepository,
|
this.userListMembershipsRepository,
|
||||||
|
this.roleService,
|
||||||
this.noteEntityService,
|
this.noteEntityService,
|
||||||
id,
|
id,
|
||||||
connection,
|
connection,
|
||||||
|
|
|
@ -303,9 +303,12 @@ export class ClientServerService {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
const configUrl = new URL(this.config.url);
|
||||||
|
const urlOriginWithoutPort = configUrl.origin.replace(/:\d+$/, '');
|
||||||
|
|
||||||
const port = (process.env.VITE_PORT ?? '5173');
|
const port = (process.env.VITE_PORT ?? '5173');
|
||||||
fastify.register(fastifyProxy, {
|
fastify.register(fastifyProxy, {
|
||||||
upstream: 'http://localhost:' + port,
|
upstream: urlOriginWithoutPort + ':' + port,
|
||||||
prefix: '/vite',
|
prefix: '/vite',
|
||||||
rewritePrefix: '/vite',
|
rewritePrefix: '/vite',
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,7 +12,7 @@ import type { Config } from '@/config.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
||||||
import type Logger from '@/logger.js';
|
import type Logger from '@/logger.js';
|
||||||
import { query } from '@/misc/prelude/url.js';
|
import { appendQuery, omitHttps, query } from '@/misc/prelude/url.js';
|
||||||
import { LoggerService } from '@/core/LoggerService.js';
|
import { LoggerService } from '@/core/LoggerService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { ApiError } from '@/server/api/error.js';
|
import { ApiError } from '@/server/api/error.js';
|
||||||
|
@ -36,14 +36,15 @@ export class UrlPreviewService {
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private wrap(url?: string | null): string | null {
|
private wrap(url?: string | null): string | null {
|
||||||
return url != null
|
if (!url) return null;
|
||||||
? url.match(/^https?:\/\//)
|
if (!RegExp(/^https?:\/\//).exec(url)) return url;
|
||||||
? `${this.config.mediaProxy}/preview.webp?${query({
|
|
||||||
url,
|
return appendQuery(
|
||||||
|
`${this.config.mediaProxy}/preview/${encodeURIComponent(omitHttps(url))}`,
|
||||||
|
query({
|
||||||
preview: '1',
|
preview: '1',
|
||||||
})}`
|
}),
|
||||||
: url
|
);
|
||||||
: null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
|
|
@ -36,7 +36,8 @@ html
|
||||||
link(rel='prefetch' href=infoImageUrl)
|
link(rel='prefetch' href=infoImageUrl)
|
||||||
link(rel='prefetch' href=notFoundImageUrl)
|
link(rel='prefetch' href=notFoundImageUrl)
|
||||||
//- https://github.com/misskey-dev/misskey/issues/9842
|
//- https://github.com/misskey-dev/misskey/issues/9842
|
||||||
link(rel='stylesheet' href=`/assets/tabler-icons.${version}/dist/tabler-icons.min.css`)
|
link(rel='stylesheet' href=`/assets/tabler-icons.${version}/dist/tabler-icons-outline.min.css`)
|
||||||
|
link(rel='stylesheet' href=`/assets/tabler-icons.${version}/dist/tabler-icons-filled.min.css`)
|
||||||
link(rel='modulepreload' href=`/vite/${clientEntry.file}`)
|
link(rel='modulepreload' href=`/vite/${clientEntry.file}`)
|
||||||
|
|
||||||
if !config.clientManifestExists
|
if !config.clientManifestExists
|
||||||
|
|
|
@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<link rel="preload" href="https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/about-icon.png?raw=true" as="image" type="image/png" crossorigin="anonymous">
|
<link rel="preload" href="https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/about-icon.png?raw=true" as="image" type="image/png" crossorigin="anonymous">
|
||||||
<link rel="preload" href="https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/fedi.jpg?raw=true" as="image" type="image/jpeg" crossorigin="anonymous">
|
<link rel="preload" href="https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/fedi.jpg?raw=true" as="image" type="image/jpeg" crossorigin="anonymous">
|
||||||
<link rel="stylesheet" href="https://unpkg.com/@tabler/icons-webfont@3.5.0/dist/tabler-icons.min.css">
|
<link rel="stylesheet" href="https://unpkg.com/@tabler/icons-webfont@latest/dist/tabler-icons.min.css">
|
||||||
<link rel="stylesheet" href="https://unpkg.com/@fontsource/m-plus-rounded-1c/index.css">
|
<link rel="stylesheet" href="https://unpkg.com/@fontsource/m-plus-rounded-1c/index.css">
|
||||||
<style>
|
<style>
|
||||||
html {
|
html {
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"watch": "vite",
|
"watch": "vite",
|
||||||
"dev": "vite --config vite.config.local-dev.ts --debug hmr",
|
|
||||||
"build": "vite build",
|
"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\"",
|
"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",
|
"build-storybook-pre": "(tsc -p .storybook || echo done.) && node .storybook/generate.js && node .storybook/preload-locale.js && node .storybook/preload-theme.js",
|
||||||
|
@ -27,7 +26,7 @@
|
||||||
"@rollup/plugin-typescript": "12.1.2",
|
"@rollup/plugin-typescript": "12.1.2",
|
||||||
"@rollup/pluginutils": "5.1.4",
|
"@rollup/pluginutils": "5.1.4",
|
||||||
"@syuilo/aiscript": "0.19.0",
|
"@syuilo/aiscript": "0.19.0",
|
||||||
"@tabler/icons-webfont": "3.28.1",
|
"@tabler/icons-webfont": "3.29.0",
|
||||||
"@twemoji/parser": "15.1.1",
|
"@twemoji/parser": "15.1.1",
|
||||||
"@vitejs/plugin-vue": "5.2.1",
|
"@vitejs/plugin-vue": "5.2.1",
|
||||||
"@vue/compiler-sfc": "3.5.13",
|
"@vue/compiler-sfc": "3.5.13",
|
||||||
|
@ -41,7 +40,7 @@
|
||||||
"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.2.0",
|
"chartjs-plugin-zoom": "2.2.0",
|
||||||
"chromatic": "11.24.0",
|
"chromatic": "11.25.2",
|
||||||
"compare-versions": "6.1.1",
|
"compare-versions": "6.1.1",
|
||||||
"cropperjs": "2.0.0-rc.0",
|
"cropperjs": "2.0.0-rc.0",
|
||||||
"date-fns": "4.1.0",
|
"date-fns": "4.1.0",
|
||||||
|
@ -59,13 +58,13 @@
|
||||||
"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.30.1",
|
"rollup": "4.34.0",
|
||||||
"sanitize-html": "2.14.0",
|
"sanitize-html": "2.14.0",
|
||||||
"sass": "1.83.4",
|
"sass": "1.83.4",
|
||||||
"shiki": "1.27.2",
|
"shiki": "2.2.0",
|
||||||
"strict-event-emitter-types": "2.0.0",
|
"strict-event-emitter-types": "2.0.0",
|
||||||
"textarea-caret": "3.1.0",
|
"textarea-caret": "3.1.0",
|
||||||
"three": "0.172.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",
|
||||||
|
@ -73,7 +72,7 @@
|
||||||
"typescript": "5.7.3",
|
"typescript": "5.7.3",
|
||||||
"uuid": "11.0.5",
|
"uuid": "11.0.5",
|
||||||
"v-code-diff": "1.13.1",
|
"v-code-diff": "1.13.1",
|
||||||
"vite": "6.0.7",
|
"vite": "6.0.11",
|
||||||
"vue": "3.5.13",
|
"vue": "3.5.13",
|
||||||
"vue-gtag": "2.0.1",
|
"vue-gtag": "2.0.1",
|
||||||
"vuedraggable": "next",
|
"vuedraggable": "next",
|
||||||
|
@ -82,49 +81,49 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@misskey-dev/eslint-plugin": "1.0.0",
|
"@misskey-dev/eslint-plugin": "1.0.0",
|
||||||
"@misskey-dev/summaly": "github:MisskeyIO/summaly#5.1.3",
|
"@misskey-dev/summaly": "github:MisskeyIO/summaly#5.1.3",
|
||||||
"@storybook/addon-actions": "8.5.0",
|
"@storybook/addon-actions": "8.5.2",
|
||||||
"@storybook/addon-essentials": "8.5.0",
|
"@storybook/addon-essentials": "8.5.2",
|
||||||
"@storybook/addon-interactions": "8.5.0",
|
"@storybook/addon-interactions": "8.5.2",
|
||||||
"@storybook/addon-links": "8.5.0",
|
"@storybook/addon-links": "8.5.2",
|
||||||
"@storybook/addon-mdx-gfm": "8.5.0",
|
"@storybook/addon-mdx-gfm": "8.5.2",
|
||||||
"@storybook/addon-storysource": "8.5.0",
|
"@storybook/addon-storysource": "8.5.2",
|
||||||
"@storybook/blocks": "8.5.0",
|
"@storybook/blocks": "8.5.2",
|
||||||
"@storybook/components": "8.5.0",
|
"@storybook/components": "8.5.2",
|
||||||
"@storybook/core-events": "8.5.0",
|
"@storybook/core-events": "8.5.2",
|
||||||
"@storybook/manager-api": "8.5.0",
|
"@storybook/manager-api": "8.5.2",
|
||||||
"@storybook/preview-api": "8.5.0",
|
"@storybook/preview-api": "8.5.2",
|
||||||
"@storybook/react": "8.5.0",
|
"@storybook/react": "8.5.2",
|
||||||
"@storybook/react-vite": "8.5.0",
|
"@storybook/react-vite": "8.5.2",
|
||||||
"@storybook/test": "8.5.0",
|
"@storybook/test": "8.5.2",
|
||||||
"@storybook/theming": "8.5.0",
|
"@storybook/theming": "8.5.2",
|
||||||
"@storybook/types": "8.5.0",
|
"@storybook/types": "8.5.2",
|
||||||
"@storybook/vue3": "8.5.0",
|
"@storybook/vue3": "8.5.2",
|
||||||
"@storybook/vue3-vite": "8.5.0",
|
"@storybook/vue3-vite": "8.5.2",
|
||||||
"@testing-library/vue": "8.1.0",
|
"@testing-library/vue": "8.1.0",
|
||||||
"@types/canvas-confetti": "^1.6.4",
|
"@types/canvas-confetti": "^1.6.4",
|
||||||
"@types/escape-regexp": "0.0.3",
|
"@types/escape-regexp": "0.0.3",
|
||||||
"@types/estree": "1.0.6",
|
"@types/estree": "1.0.6",
|
||||||
"@types/matter-js": "0.19.8",
|
"@types/matter-js": "0.19.8",
|
||||||
"@types/micromatch": "4.0.9",
|
"@types/micromatch": "4.0.9",
|
||||||
"@types/node": "22.10.7",
|
"@types/node": "22.13.0",
|
||||||
"@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/three": "0.172.0",
|
"@types/three": "0.173.0",
|
||||||
"@types/throttle-debounce": "5.0.2",
|
"@types/throttle-debounce": "5.0.2",
|
||||||
"@types/tinycolor2": "1.4.6",
|
"@types/tinycolor2": "1.4.6",
|
||||||
"@types/ws": "8.5.13",
|
"@types/ws": "8.5.14",
|
||||||
"@typescript-eslint/eslint-plugin": "7.10.0",
|
"@typescript-eslint/eslint-plugin": "7.10.0",
|
||||||
"@typescript-eslint/parser": "7.10.0",
|
"@typescript-eslint/parser": "7.10.0",
|
||||||
"@vitest/coverage-v8": "2.1.8",
|
"@vitest/coverage-v8": "3.0.4",
|
||||||
"@vue/runtime-core": "3.5.13",
|
"@vue/runtime-core": "3.5.13",
|
||||||
"acorn": "8.14.0",
|
"acorn": "8.14.0",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"cypress": "13.17.0",
|
"cypress": "14.0.1",
|
||||||
"eslint": "8.57.1",
|
"eslint": "8.57.1",
|
||||||
"eslint-plugin-import": "2.31.0",
|
"eslint-plugin-import": "2.31.0",
|
||||||
"eslint-plugin-vue": "9.32.0",
|
"eslint-plugin-vue": "9.32.0",
|
||||||
"fast-glob": "3.3.3",
|
"fast-glob": "3.3.3",
|
||||||
"happy-dom": "16.6.0",
|
"happy-dom": "16.8.1",
|
||||||
"intersection-observer": "0.12.2",
|
"intersection-observer": "0.12.2",
|
||||||
"micromatch": "4.0.8",
|
"micromatch": "4.0.8",
|
||||||
"msw": "2.7.0",
|
"msw": "2.7.0",
|
||||||
|
@ -134,10 +133,10 @@
|
||||||
"react": "19.0.0",
|
"react": "19.0.0",
|
||||||
"react-dom": "19.0.0",
|
"react-dom": "19.0.0",
|
||||||
"start-server-and-test": "2.0.10",
|
"start-server-and-test": "2.0.10",
|
||||||
"storybook": "8.5.0",
|
"storybook": "8.5.2",
|
||||||
"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": "2.1.8",
|
"vitest": "3.0.4",
|
||||||
"vitest-fetch-mock": "0.3.0",
|
"vitest-fetch-mock": "0.3.0",
|
||||||
"vue-component-type-helpers": "2.2.0",
|
"vue-component-type-helpers": "2.2.0",
|
||||||
"vue-eslint-parser": "9.4.3",
|
"vue-eslint-parser": "9.4.3",
|
||||||
|
|
|
@ -1,110 +0,0 @@
|
||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
// devモードで起動される際(index.htmlを使うとき)はrouterが暴発してしまってうまく読み込めない。
|
|
||||||
// よって、devモードとして起動されるときはビルド時に組み込む形としておく。
|
|
||||||
// (pnpm start時はpugファイルの中で静的リソースとして読み込むようになっており、この問題は起こっていない)
|
|
||||||
import '@tabler/icons-webfont/dist/tabler-icons.scss';
|
|
||||||
|
|
||||||
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.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const metaRes = await window.fetch('/api/meta', {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify({}),
|
|
||||||
credentials: 'omit',
|
|
||||||
cache: 'no-cache',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (metaRes.status !== 200) {
|
|
||||||
renderError('META_FETCH');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const meta = await metaRes.json();
|
|
||||||
//#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 = 'ko-KR';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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(`--${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') ?? meta.backgroundImageUrl;
|
|
||||||
if (wallpaper) {
|
|
||||||
document.documentElement.style.background = `url(${wallpaper}) no-repeat fixed center`;
|
|
||||||
document.documentElement.style.backgroundSize = 'cover';
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
|
@ -43,6 +43,12 @@ export async function signout() {
|
||||||
if (!$i) return;
|
if (!$i) return;
|
||||||
|
|
||||||
waiting();
|
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');
|
miLocalStorage.removeItem('account');
|
||||||
await removeAccount($i.id);
|
await removeAccount($i.id);
|
||||||
const accounts = await getAccounts();
|
const accounts = await getAccounts();
|
||||||
|
|
|
@ -100,6 +100,11 @@ export async function common(createVue: () => App<Element>) {
|
||||||
// タッチデバイスでCSSの:hoverを機能させる
|
// タッチデバイスでCSSの:hoverを機能させる
|
||||||
document.addEventListener('touchend', () => {}, { passive: true });
|
document.addEventListener('touchend', () => {}, { passive: true });
|
||||||
|
|
||||||
|
// URLに#pswpを含む場合は取り除く
|
||||||
|
if (location.hash === '#pswp') {
|
||||||
|
history.replaceState(null, '', location.href.replace('#pswp', ''));
|
||||||
|
}
|
||||||
|
|
||||||
// 一斉リロード
|
// 一斉リロード
|
||||||
reloadChannel.addEventListener('message', path => {
|
reloadChannel.addEventListener('message', path => {
|
||||||
if (path !== null) location.href = path;
|
if (path !== null) location.href = path;
|
||||||
|
|
|
@ -26,22 +26,34 @@ let prevTime = 0;
|
||||||
let angle1 = 0;
|
let angle1 = 0;
|
||||||
let angle2 = 0;
|
let angle2 = 0;
|
||||||
|
|
||||||
let scene, camera, renderer, width, height, uniforms, texture, maskTexture, dataArray1, dataArray2, dataArrayOrigin, bufferLength: number;
|
const scene = new THREE.Scene();
|
||||||
|
const camera = new THREE.OrthographicCamera();
|
||||||
|
const renderer = new THREE.WebGLRenderer({ antialias: true });
|
||||||
|
|
||||||
|
let width: number;
|
||||||
|
let height: number;
|
||||||
|
let uniforms: { [p: string]: THREE.IUniform };
|
||||||
|
let texture: THREE.Texture;
|
||||||
|
let maskTexture: THREE.Texture;
|
||||||
|
let dataArray1: Uint8Array;
|
||||||
|
let dataArray2: Uint8Array;
|
||||||
|
let dataArrayOrigin: Uint8Array;
|
||||||
|
let bufferLength: number;
|
||||||
|
|
||||||
const init = () => {
|
const init = () => {
|
||||||
const parent = container.value ?? { offsetWidth: 0 };
|
const parent = container.value ?? { offsetWidth: 0 };
|
||||||
width = parent.offsetWidth;
|
width = parent.offsetWidth;
|
||||||
height = Math.floor(width * 9 / 16);
|
height = Math.floor(width * 9 / 16);
|
||||||
|
|
||||||
scene = new THREE.Scene();
|
scene.clear();
|
||||||
camera = new THREE.OrthographicCamera();
|
camera.clear();
|
||||||
|
|
||||||
camera.left = width / -2;
|
camera.left = width / -2;
|
||||||
camera.right = width / 2;
|
camera.right = width / 2;
|
||||||
camera.top = height / 2;
|
camera.top = height / 2;
|
||||||
camera.bottom = height / -2;
|
camera.bottom = height / -2;
|
||||||
camera.updateProjectionMatrix();
|
camera.updateProjectionMatrix();
|
||||||
|
|
||||||
renderer = new THREE.WebGLRenderer({ antialias: true });
|
|
||||||
renderer.setSize(width, height);
|
renderer.setSize(width, height);
|
||||||
|
|
||||||
if (container.value) {
|
if (container.value) {
|
||||||
|
@ -176,7 +188,7 @@ const animate = (time) => {
|
||||||
renderer.render(scene, camera);
|
renderer.render(scene, camera);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onResize = () => {
|
const resize = () => {
|
||||||
const parent = container.value ?? { offsetWidth: 0 };
|
const parent = container.value ?? { offsetWidth: 0 };
|
||||||
width = parent.offsetWidth;
|
width = parent.offsetWidth;
|
||||||
height = Math.floor(width * 9 / 16);
|
height = Math.floor(width * 9 / 16);
|
||||||
|
@ -189,17 +201,25 @@ const onResize = () => {
|
||||||
uniforms.resolution.value.set(width, height);
|
uniforms.resolution.value.set(width, height);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ro = new ResizeObserver((entries, observer) => {
|
||||||
|
resize();
|
||||||
|
});
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
nextTick().then(() => {
|
nextTick().then(() => {
|
||||||
init();
|
init();
|
||||||
window.addEventListener('resize', onResize);
|
resize();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!container.value) return;
|
||||||
|
ro.observe(container.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (renderer) {
|
if (renderer) {
|
||||||
renderer.dispose();
|
renderer.dispose();
|
||||||
}
|
}
|
||||||
|
ro.disconnect();
|
||||||
});
|
});
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
|
|
|
@ -48,8 +48,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</audio>
|
</audio>
|
||||||
<div :class="[$style.controlsChild, $style.controlsLeft]">
|
<div :class="[$style.controlsChild, $style.controlsLeft]">
|
||||||
<button class="_button" :class="$style.controlButton" @click.prevent.stop="togglePlayPause">
|
<button class="_button" :class="$style.controlButton" @click.prevent.stop="togglePlayPause">
|
||||||
<i v-if="isPlaying" class="ti ti-player-pause-filled"></i>
|
<i v-if="isPlaying" class="ti-filled ti-filled-player-pause"></i>
|
||||||
<i v-else class="ti ti-player-play-filled"></i>
|
<i v-else class="ti-filled ti-filled-player-play"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div :class="[$style.controlsChild, $style.controlsRight]">
|
<div :class="[$style.controlsChild, $style.controlsRight]">
|
||||||
|
|
|
@ -62,7 +62,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
>
|
>
|
||||||
<source :src="video.url">
|
<source :src="video.url">
|
||||||
</video>
|
</video>
|
||||||
<button v-if="isReady && !isPlaying" class="_button" :class="$style.videoOverlayPlayButton" @click="togglePlayPause"><i class="ti ti-player-play-filled"></i></button>
|
<button v-if="isReady && !isPlaying" class="_button" :class="$style.videoOverlayPlayButton" @click="togglePlayPause"><i class="ti-filled ti-filled-player-play"></i></button>
|
||||||
<div v-else-if="!isActuallyPlaying" :class="$style.videoLoading">
|
<div v-else-if="!isActuallyPlaying" :class="$style.videoLoading">
|
||||||
<MkLoading/>
|
<MkLoading/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -75,8 +75,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div v-if="videoControls" :class="$style.videoControls" @click.self="togglePlayPause">
|
<div v-if="videoControls" :class="$style.videoControls" @click.self="togglePlayPause">
|
||||||
<div :class="[$style.controlsChild, $style.controlsLeft]">
|
<div :class="[$style.controlsChild, $style.controlsLeft]">
|
||||||
<button class="_button" :class="$style.controlButton" @click.prevent.stop="togglePlayPause">
|
<button class="_button" :class="$style.controlButton" @click.prevent.stop="togglePlayPause">
|
||||||
<i v-if="isPlaying" class="ti ti-player-pause-filled"></i>
|
<i v-if="isPlaying" class="ti-filled ti-filled-player-pause"></i>
|
||||||
<i v-else class="ti ti-player-play-filled"></i>
|
<i v-else class="ti-filled ti-filled-player-play"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div :class="[$style.controlsChild, $style.controlsRight]">
|
<div :class="[$style.controlsChild, $style.controlsRight]">
|
||||||
|
|
|
@ -119,7 +119,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<i class="ti ti-ban"></i>
|
<i class="ti ti-ban"></i>
|
||||||
</button>
|
</button>
|
||||||
<button ref="reactButton" :class="$style.footerButton" class="_button" @click="toggleReact()">
|
<button ref="reactButton" :class="$style.footerButton" class="_button" @click="toggleReact()">
|
||||||
<i v-if=" (appearNote.reactionAcceptance === 'likeOnly' || !$i?.policies.canUseReaction) && appearNote.myReaction != null " class="ti ti-heart-filled" style="color: var(--eventReactionHeart);"></i>
|
<i v-if=" (appearNote.reactionAcceptance === 'likeOnly' || !$i?.policies.canUseReaction) && appearNote.myReaction != null " class="ti-filled ti-filled-heart" style="color: var(--eventReactionHeart);"></i>
|
||||||
<i v-else-if="appearNote.myReaction != null " class="ti ti-minus" style="color: var(--accent);"></i>
|
<i v-else-if="appearNote.myReaction != null " class="ti ti-minus" style="color: var(--accent);"></i>
|
||||||
<i v-else-if="appearNote.reactionAcceptance === 'likeOnly' || $i && !$i.policies.canUseReaction" class="ti ti-heart"></i>
|
<i v-else-if="appearNote.reactionAcceptance === 'likeOnly' || $i && !$i.policies.canUseReaction" class="ti ti-heart"></i>
|
||||||
<i v-else class="ti ti-plus"></i>
|
<i v-else class="ti ti-plus"></i>
|
||||||
|
@ -594,7 +594,7 @@ function emitUpdReaction(emoji: string, delta: number) {
|
||||||
contain: content;
|
contain: content;
|
||||||
|
|
||||||
content-visibility: auto;
|
content-visibility: auto;
|
||||||
contain-intrinsic-size: auto none auto 128px;
|
contain-intrinsic-size: none auto 128px;
|
||||||
|
|
||||||
&:focus-visible {
|
&:focus-visible {
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
|
@ -127,7 +127,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<i class="ti ti-ban"></i>
|
<i class="ti ti-ban"></i>
|
||||||
</button>
|
</button>
|
||||||
<button ref="reactButton" :class="$style.noteFooterButton" class="_button" @click="toggleReact()">
|
<button ref="reactButton" :class="$style.noteFooterButton" class="_button" @click="toggleReact()">
|
||||||
<i v-if=" (appearNote.reactionAcceptance === 'likeOnly' || !$i?.policies.canUseReaction) && appearNote.myReaction != null " class="ti ti-heart-filled" style="color: var(--eventReactionHeart);"></i>
|
<i v-if=" (appearNote.reactionAcceptance === 'likeOnly' || !$i?.policies.canUseReaction) && appearNote.myReaction != null " class="ti-filled ti-filled-heart" style="color: var(--eventReactionHeart);"></i>
|
||||||
<i v-else-if="appearNote.myReaction != null " class="ti ti-minus" style="color: var(--accent);"></i>
|
<i v-else-if="appearNote.myReaction != null " class="ti ti-minus" style="color: var(--accent);"></i>
|
||||||
<i v-else-if="appearNote.reactionAcceptance === 'likeOnly' || $i && !$i.policies.canUseReaction" class="ti ti-heart"></i>
|
<i v-else-if="appearNote.reactionAcceptance === 'likeOnly' || $i && !$i.policies.canUseReaction" class="ti ti-heart"></i>
|
||||||
<i v-else class="ti ti-plus"></i>
|
<i v-else class="ti ti-plus"></i>
|
||||||
|
|
|
@ -220,7 +220,7 @@ function getActualReactedUsersCount(notification: Misskey.entities.Notification)
|
||||||
contain: content;
|
contain: content;
|
||||||
|
|
||||||
content-visibility: auto;
|
content-visibility: auto;
|
||||||
contain-intrinsic-size: auto none auto 100px;
|
contain-intrinsic-size: none auto 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.head {
|
.head {
|
||||||
|
|
|
@ -24,7 +24,7 @@ import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
|
||||||
import { useStream } from '@/stream.js';
|
import { useStream } from '@/stream.js';
|
||||||
import * as sound from '@/scripts/sound.js';
|
import * as sound from '@/scripts/sound.js';
|
||||||
import { deepMerge } from '@/scripts/merge.js';
|
import { deepMerge } from '@/scripts/merge.js';
|
||||||
import { $i, iAmModerator } from '@/account.js';
|
import { $i } from '@/account.js';
|
||||||
import { instance } from '@/instance.js';
|
import { instance } from '@/instance.js';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
import { Paging } from '@/components/MkPagination.vue';
|
import { Paging } from '@/components/MkPagination.vue';
|
||||||
|
@ -108,7 +108,6 @@ async function prepend(data) {
|
||||||
let connection: Misskey.ChannelConnection | null = null;
|
let connection: Misskey.ChannelConnection | null = null;
|
||||||
let connection2: Misskey.ChannelConnection | null = null;
|
let connection2: Misskey.ChannelConnection | null = null;
|
||||||
let paginationQuery: Paging | null = null;
|
let paginationQuery: Paging | null = null;
|
||||||
const minimize = !iAmModerator;
|
|
||||||
|
|
||||||
const stream = useStream();
|
const stream = useStream();
|
||||||
|
|
||||||
|
@ -117,13 +116,13 @@ function connectChannel() {
|
||||||
if (props.antenna == null) return;
|
if (props.antenna == null) return;
|
||||||
connection = stream.useChannel('antenna', {
|
connection = stream.useChannel('antenna', {
|
||||||
antennaId: props.antenna,
|
antennaId: props.antenna,
|
||||||
minimize: minimize,
|
minimize: true,
|
||||||
});
|
});
|
||||||
} else if (props.src === 'home') {
|
} else if (props.src === 'home') {
|
||||||
connection = stream.useChannel('homeTimeline', {
|
connection = stream.useChannel('homeTimeline', {
|
||||||
withRenotes: props.withRenotes,
|
withRenotes: props.withRenotes,
|
||||||
withFiles: props.onlyFiles ? true : undefined,
|
withFiles: props.onlyFiles ? true : undefined,
|
||||||
minimize: minimize,
|
minimize: true,
|
||||||
});
|
});
|
||||||
connection2 = stream.useChannel('main');
|
connection2 = stream.useChannel('main');
|
||||||
} else if (props.src === 'local') {
|
} else if (props.src === 'local') {
|
||||||
|
@ -131,27 +130,27 @@ function connectChannel() {
|
||||||
withRenotes: props.withRenotes,
|
withRenotes: props.withRenotes,
|
||||||
withReplies: props.withReplies,
|
withReplies: props.withReplies,
|
||||||
withFiles: props.onlyFiles ? true : undefined,
|
withFiles: props.onlyFiles ? true : undefined,
|
||||||
minimize: minimize,
|
minimize: true,
|
||||||
});
|
});
|
||||||
} else if (props.src === 'media') {
|
} else if (props.src === 'media') {
|
||||||
connection = stream.useChannel('hybridTimeline', {
|
connection = stream.useChannel('hybridTimeline', {
|
||||||
withRenotes: props.withRenotes,
|
withRenotes: props.withRenotes,
|
||||||
withReplies: props.withReplies,
|
withReplies: props.withReplies,
|
||||||
withFiles: true,
|
withFiles: true,
|
||||||
minimize: minimize,
|
minimize: true,
|
||||||
});
|
});
|
||||||
} else if (props.src === 'social') {
|
} else if (props.src === 'social') {
|
||||||
connection = stream.useChannel('hybridTimeline', {
|
connection = stream.useChannel('hybridTimeline', {
|
||||||
withRenotes: props.withRenotes,
|
withRenotes: props.withRenotes,
|
||||||
withReplies: props.withReplies,
|
withReplies: props.withReplies,
|
||||||
withFiles: props.onlyFiles ? true : undefined,
|
withFiles: props.onlyFiles ? true : undefined,
|
||||||
minimize: minimize,
|
minimize: true,
|
||||||
});
|
});
|
||||||
} else if (props.src === 'global') {
|
} else if (props.src === 'global') {
|
||||||
connection = stream.useChannel('globalTimeline', {
|
connection = stream.useChannel('globalTimeline', {
|
||||||
withRenotes: props.withRenotes,
|
withRenotes: props.withRenotes,
|
||||||
withFiles: props.onlyFiles ? true : undefined,
|
withFiles: props.onlyFiles ? true : undefined,
|
||||||
minimize: minimize,
|
minimize: true,
|
||||||
});
|
});
|
||||||
} else if (props.src === 'mentions') {
|
} else if (props.src === 'mentions') {
|
||||||
connection = stream.useChannel('main');
|
connection = stream.useChannel('main');
|
||||||
|
@ -170,19 +169,19 @@ function connectChannel() {
|
||||||
withRenotes: props.withRenotes,
|
withRenotes: props.withRenotes,
|
||||||
withFiles: props.onlyFiles ? true : undefined,
|
withFiles: props.onlyFiles ? true : undefined,
|
||||||
listId: props.list,
|
listId: props.list,
|
||||||
minimize: minimize,
|
minimize: true,
|
||||||
});
|
});
|
||||||
} else if (props.src === 'channel') {
|
} else if (props.src === 'channel') {
|
||||||
if (props.channel == null) return;
|
if (props.channel == null) return;
|
||||||
connection = stream.useChannel('channel', {
|
connection = stream.useChannel('channel', {
|
||||||
channelId: props.channel,
|
channelId: props.channel,
|
||||||
minimize: minimize,
|
minimize: true,
|
||||||
});
|
});
|
||||||
} else if (props.src === 'role') {
|
} else if (props.src === 'role') {
|
||||||
if (props.role == null) return;
|
if (props.role == null) return;
|
||||||
connection = stream.useChannel('roleTimeline', {
|
connection = stream.useChannel('roleTimeline', {
|
||||||
roleId: props.role,
|
roleId: props.role,
|
||||||
minimize: minimize,
|
minimize: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (props.src !== 'directs' && props.src !== 'mentions') connection?.on('note', prepend);
|
if (props.src !== 'directs' && props.src !== 'mentions') connection?.on('note', prepend);
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
<!--
|
|
||||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!--
|
|
||||||
開発モードのviteはこのファイルを起点にサーバーを起動します。
|
|
||||||
このファイルに書かれた [t]js のリンクと (s)cssのリンクと、その依存関係にあるファイルはビルドされます
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<title>[DEV] Loading...</title>
|
|
||||||
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
|
|
||||||
<meta
|
|
||||||
http-equiv="Content-Security-Policy-Report-Only"
|
|
||||||
content="default-src 'self' https://newassets.hcaptcha.com/ https://challenges.cloudflare.com/ http://localhost:7493/ https://fonts.gstatic.com/ https://www.google-analytics.com/ https://www.googletagmanager.com/;
|
|
||||||
worker-src 'self';
|
|
||||||
script-src 'self' 'unsafe-eval' https://*.hcaptcha.com https://challenges.cloudflare.com https://www.googletagmanager.com https://esm.sh;
|
|
||||||
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://fonts.gstatic.com https://www.googletagmanager.com;
|
|
||||||
img-src 'self' data: blob: www.google.com xn--931a.moe localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000 https://fonts.gstatic.com https://www.googletagmanager.com;
|
|
||||||
media-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000;
|
|
||||||
connect-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000 https://newassets.hcaptcha.com https://api.pwnedpasswords.com https://www.google-analytics.com https://analytics.google.com;
|
|
||||||
frame-src *;"
|
|
||||||
/>
|
|
||||||
<meta property="og:site_name" content="[DEV BUILD] Misskey" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div id="misskey_app"></div>
|
|
||||||
<script type="module" src="./_dev_boot_.ts"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -18,6 +18,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div>
|
<div>
|
||||||
<div v-for="ad in ads" class="_panel _gaps_m" :class="$style.ad">
|
<div v-for="ad in ads" class="_panel _gaps_m" :class="$style.ad">
|
||||||
<MkAd v-if="ad.url" :key="ad.id" :specify="ad"/>
|
<MkAd v-if="ad.url" :key="ad.id" :specify="ad"/>
|
||||||
|
<MkInput v-if="ad.id" v-model="ad.id" :readonly="true">
|
||||||
|
<template #label>ID</template>
|
||||||
|
</MkInput>
|
||||||
<MkInput v-model="ad.url" type="url">
|
<MkInput v-model="ad.url" type="url">
|
||||||
<template #label>URL</template>
|
<template #label>URL</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
|
|
|
@ -2,14 +2,30 @@
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
||||||
<MkSpacer :contentMax="900">
|
<MkSpacer :contentMax="900">
|
||||||
<div style="display: flex; gap: var(--margin); flex-wrap: wrap;">
|
<div style="display: flex; flex-direction: column; gap: var(--margin); flex-wrap: wrap;">
|
||||||
<MkInput v-model="movedFromId" style="margin: 0; flex: 1;">
|
<div :class="$style.inputs">
|
||||||
|
<MkSelect v-model="from" :class="$style.input">
|
||||||
|
<template #label>{{ i18n.ts._accountMigration.movedFromServer }}</template>
|
||||||
|
<option value="all">{{ i18n.ts.all }}</option>
|
||||||
|
<option value="remote">{{ i18n.ts.remote }}</option>
|
||||||
|
<option value="local">{{ i18n.ts.local }}</option>
|
||||||
|
</MkSelect>
|
||||||
|
<MkSelect v-model="to" :class="$style.input">
|
||||||
|
<template #label>{{ i18n.ts._accountMigration.movedToServer }}</template>
|
||||||
|
<option value="all">{{ i18n.ts.all }}</option>
|
||||||
|
<option value="remote">{{ i18n.ts.remote }}</option>
|
||||||
|
<option value="local">{{ i18n.ts.local }}</option>
|
||||||
|
</MkSelect>
|
||||||
|
</div>
|
||||||
|
<div :class="$style.inputs">
|
||||||
|
<MkInput v-model="movedFromId" :class="$style.input">
|
||||||
<template #label> {{ i18n.ts.moveFromId }}</template>
|
<template #label> {{ i18n.ts.moveFromId }}</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
<MkInput v-model="movedToId" style="margin: 0; flex: 1;">
|
<MkInput v-model="movedToId" :class="$style.input">
|
||||||
<template #label> {{ i18n.ts.movedToId }}</template>
|
<template #label> {{ i18n.ts.movedToId }}</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<MkPagination v-slot="{items}" ref="logs" :pagination="pagination" style="margin-top: var(--margin);">
|
<MkPagination v-slot="{items}" ref="logs" :pagination="pagination" style="margin-top: var(--margin);">
|
||||||
<div class="_gaps_s">
|
<div class="_gaps_s">
|
||||||
|
@ -48,11 +64,15 @@ import { i18n } from '@/i18n.js';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import { userPage } from '@/filters/user.js';
|
import { userPage } from '@/filters/user.js';
|
||||||
import MkFolder from '@/components/MkFolder.vue';
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
|
import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
|
import MkSelect from '@/components/MkSelect.vue';
|
||||||
|
|
||||||
const logs = shallowRef<InstanceType<typeof MkPagination>>();
|
const logs = shallowRef<InstanceType<typeof MkPagination>>();
|
||||||
|
|
||||||
const movedToId = ref('');
|
const movedToId = ref('');
|
||||||
const movedFromId = ref('');
|
const movedFromId = ref('');
|
||||||
|
const from = ref('all');
|
||||||
|
const to = ref('all');
|
||||||
|
|
||||||
const pagination = {
|
const pagination = {
|
||||||
endpoint: 'admin/show-user-account-move-logs' as const,
|
endpoint: 'admin/show-user-account-move-logs' as const,
|
||||||
|
@ -60,6 +80,8 @@ const pagination = {
|
||||||
params: computed(() => ({
|
params: computed(() => ({
|
||||||
movedFromId: movedFromId.value === '' ? null : movedFromId.value,
|
movedFromId: movedFromId.value === '' ? null : movedFromId.value,
|
||||||
movedToId: movedToId.value === '' ? null : movedToId.value,
|
movedToId: movedToId.value === '' ? null : movedToId.value,
|
||||||
|
from: from.value,
|
||||||
|
to: to.value,
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -95,4 +117,14 @@ definePageMetadata(() => ({
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.inputs {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
margin: 0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -182,6 +182,9 @@ definePageMetadata(() => ({
|
||||||
}
|
}
|
||||||
|
|
||||||
.rkxwuolj {
|
.rkxwuolj {
|
||||||
|
> .body {
|
||||||
|
padding: 32px;
|
||||||
|
|
||||||
> .files {
|
> .files {
|
||||||
> .file {
|
> .file {
|
||||||
> img {
|
> img {
|
||||||
|
@ -197,9 +200,6 @@ definePageMetadata(() => ({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .body {
|
|
||||||
padding: 32px;
|
|
||||||
|
|
||||||
> .title {
|
> .title {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
|
|
|
@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div v-if="game.map == null"><i class="ti ti-dice"></i></div>
|
<div v-if="game.map == null"><i class="ti ti-dice"></i></div>
|
||||||
<div v-else :class="$style.board" :style="{ 'grid-template-rows': `repeat(${ game.map.length }, 1fr)`, 'grid-template-columns': `repeat(${ game.map[0].length }, 1fr)` }">
|
<div v-else :class="$style.board" :style="{ 'grid-template-rows': `repeat(${ game.map.length }, 1fr)`, 'grid-template-columns': `repeat(${ game.map[0].length }, 1fr)` }">
|
||||||
<div v-for="(x, i) in game.map.join('')" :class="[$style.boardCell, { [$style.boardCellNone]: x == ' ' }]" @click="onMapCellClick(i, x)">
|
<div v-for="(x, i) in game.map.join('')" :class="[$style.boardCell, { [$style.boardCellNone]: x == ' ' }]" @click="onMapCellClick(i, x)">
|
||||||
<i v-if="x === 'b' || x === 'w'" style="pointer-events: none; user-select: none;" :class="x === 'b' ? 'ti ti-circle-filled' : 'ti ti-circle'"></i>
|
<i v-if="x === 'b' || x === 'w'" style="pointer-events: none; user-select: none;" :class="x === 'b' ? 'ti-filled ti-filled-circle' : 'ti ti-circle'"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -145,7 +145,6 @@ import { i18n } from '@/i18n.js';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
|
||||||
import { infoImageUrl } from '@/instance.js';
|
import { infoImageUrl } from '@/instance.js';
|
||||||
import { signinRequired } from '@/account.js';
|
import { signinRequired } from '@/account.js';
|
||||||
import MkFolder from '@/components/MkFolder.vue';
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
|
@ -276,7 +275,7 @@ async function toggleBlockItem(item) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveMutedWords(mutedWords: (string | string[])[]) {
|
async function saveMutedWords(mutedWords: (string | string[])[]) {
|
||||||
await misskeyApi('i/update', { mutedWords });
|
await os.apiWithDialog('i/update', { mutedWords });
|
||||||
}
|
}
|
||||||
|
|
||||||
const headerActions = computed(() => []);
|
const headerActions = computed(() => []);
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { query } from '@/scripts/url.js';
|
import { appendQuery, omitHttps, query } from '@/scripts/url.js';
|
||||||
import { url } from '@/config.js';
|
import { url } from '@/config.js';
|
||||||
import { instance } from '@/instance.js';
|
import { instance } from '@/instance.js';
|
||||||
|
|
||||||
|
@ -12,18 +12,26 @@ export function getProxiedImageUrl(imageUrl: string, type?: 'preview' | 'emoji'
|
||||||
|
|
||||||
if (imageUrl.startsWith(instance.mediaProxy + '/') || imageUrl.startsWith('/proxy/') || imageUrl.startsWith(localProxy + '/')) {
|
if (imageUrl.startsWith(instance.mediaProxy + '/') || imageUrl.startsWith('/proxy/') || imageUrl.startsWith(localProxy + '/')) {
|
||||||
// もう既にproxyっぽそうだったらurlを取り出す
|
// もう既にproxyっぽそうだったらurlを取り出す
|
||||||
imageUrl = (new URL(imageUrl)).searchParams.get('url') ?? imageUrl;
|
const url = (new URL(imageUrl)).searchParams.get('url');
|
||||||
|
if (url) {
|
||||||
|
imageUrl = url;
|
||||||
|
} else if (imageUrl.startsWith(instance.mediaProxy + '/')) {
|
||||||
|
imageUrl = imageUrl.slice(instance.mediaProxy.length + 1);
|
||||||
|
} else if (imageUrl.startsWith('/proxy/')) {
|
||||||
|
imageUrl = imageUrl.slice('/proxy/'.length);
|
||||||
|
} else if (imageUrl.startsWith(localProxy + '/')) {
|
||||||
|
imageUrl = imageUrl.slice(localProxy.length + 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${mustOrigin ? localProxy : instance.mediaProxy}/${
|
return appendQuery(
|
||||||
type === 'preview' ? 'preview.webp'
|
`${mustOrigin ? localProxy : instance.mediaProxy}/${type === 'preview' ? 'preview' : 'image'}/${encodeURIComponent(omitHttps(imageUrl))}`,
|
||||||
: 'image.webp'
|
query({
|
||||||
}?${query({
|
|
||||||
url: imageUrl,
|
|
||||||
...(!noFallback ? { 'fallback': '1' } : {}),
|
...(!noFallback ? { 'fallback': '1' } : {}),
|
||||||
...(type ? { [type]: '1' } : {}),
|
...(type ? { [type]: '1' } : {}),
|
||||||
...(mustOrigin ? { origin: '1' } : {}),
|
...(mustOrigin ? { origin: '1' } : {}),
|
||||||
})}`;
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getProxiedImageUrlNullable(imageUrl: string | null | undefined, type?: 'preview'): string | null {
|
export function getProxiedImageUrlNullable(imageUrl: string | null | undefined, type?: 'preview'): string | null {
|
||||||
|
@ -46,8 +54,8 @@ export function getStaticImageUrl(baseUrl: string): string {
|
||||||
return u.href;
|
return u.href;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${instance.mediaProxy}/static.webp?${query({
|
return appendQuery(
|
||||||
url: u.href,
|
`${instance.mediaProxy}/static/${encodeURIComponent(omitHttps(u.href))}`,
|
||||||
static: '1',
|
query({ static: '1' }),
|
||||||
})}`;
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
* 2. プロパティがundefinedの時はクエリを付けない
|
* 2. プロパティがundefinedの時はクエリを付けない
|
||||||
* (new URLSearchParams(obj)ではそこまで丁寧なことをしてくれない)
|
* (new URLSearchParams(obj)ではそこまで丁寧なことをしてくれない)
|
||||||
*/
|
*/
|
||||||
export function query(obj: Record<string, any>): string {
|
export function query(obj: Record<string, unknown>): string {
|
||||||
const params = Object.entries(obj)
|
const params = Object.entries(obj)
|
||||||
.filter(([, v]) => Array.isArray(v) ? v.length : v !== undefined)
|
.filter(([, v]) => Array.isArray(v) ? v.length : v !== undefined)
|
||||||
.reduce((a, [k, v]) => (a[k] = v, a), {} as Record<string, any>);
|
.reduce((a, [k, v]) => (a[k] = v, a), {} as Record<string, any>);
|
||||||
|
@ -21,3 +21,9 @@ export function query(obj: Record<string, any>): string {
|
||||||
export function appendQuery(url: string, query: string): string {
|
export function appendQuery(url: string, query: string): string {
|
||||||
return `${url}${/\?/.test(url) ? url.endsWith('?') ? '' : '&' : '?'}${query}`;
|
return `${url}${/\?/.test(url) ? url.endsWith('?') ? '' : '&' : '?'}${query}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function omitHttps(url: string): string {
|
||||||
|
if (url.startsWith('https://')) return url.slice(8);
|
||||||
|
if (url.startsWith('https%3A%2F%2F')) return url.slice(14);
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
|
@ -177,6 +177,16 @@ rt {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ti-filled {
|
||||||
|
width: 1.28em;
|
||||||
|
vertical-align: -12%;
|
||||||
|
line-height: 1em;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
font-size: 128%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.ti-fw {
|
.ti-fw {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
|
@ -1,89 +0,0 @@
|
||||||
import dns from 'dns';
|
|
||||||
import { readFile } from 'node:fs/promises';
|
|
||||||
import { defineConfig } 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}/`;
|
|
||||||
|
|
||||||
const devConfig = {
|
|
||||||
// 基本の設定は vite.config.js から引き継ぐ
|
|
||||||
...defaultConfig,
|
|
||||||
root: 'src',
|
|
||||||
publicDir: '../assets',
|
|
||||||
base: './',
|
|
||||||
server: {
|
|
||||||
host: 'localhost',
|
|
||||||
port: 5173,
|
|
||||||
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,
|
|
||||||
'/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,
|
|
||||||
'/queue': httpUrl,
|
|
||||||
'/notes': {
|
|
||||||
target: httpUrl,
|
|
||||||
headers: {
|
|
||||||
'Accept': 'application/activity+json',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'/users': {
|
|
||||||
target: httpUrl,
|
|
||||||
headers: {
|
|
||||||
'Accept': 'application/activity+json',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'/.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);
|
|
||||||
|
|
|
@ -3,6 +3,8 @@ import pluginReplace from '@rollup/plugin-replace';
|
||||||
import pluginVue from '@vitejs/plugin-vue';
|
import pluginVue from '@vitejs/plugin-vue';
|
||||||
import typescript from '@rollup/plugin-typescript';
|
import typescript from '@rollup/plugin-typescript';
|
||||||
import { type UserConfig, defineConfig } from 'vite';
|
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 locales from '../../locales/index.js';
|
||||||
import meta from '../../package.json';
|
import meta from '../../package.json';
|
||||||
|
@ -10,6 +12,9 @@ import packageInfo from './package.json' with { type: 'json' };
|
||||||
import pluginUnwindCssModuleClassName from './lib/rollup-plugin-unwind-css-module-class-name.js';
|
import pluginUnwindCssModuleClassName from './lib/rollup-plugin-unwind-css-module-class-name.js';
|
||||||
import pluginJson5 from './vite.json5.js';
|
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'];
|
const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.json', '.json5', '.svg', '.sass', '.scss', '.css', '.vue'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -65,6 +70,7 @@ export function getConfig(): UserConfig {
|
||||||
base: '/vite/',
|
base: '/vite/',
|
||||||
|
|
||||||
server: {
|
server: {
|
||||||
|
host,
|
||||||
port: 5173,
|
port: 5173,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@misskey-dev/eslint-plugin": "1.0.0",
|
"@misskey-dev/eslint-plugin": "1.0.0",
|
||||||
"@types/matter-js": "0.19.8",
|
"@types/matter-js": "0.19.8",
|
||||||
"@types/node": "22.10.7",
|
"@types/node": "22.13.0",
|
||||||
"@types/seedrandom": "3.0.8",
|
"@types/seedrandom": "3.0.8",
|
||||||
"@typescript-eslint/eslint-plugin": "7.10.0",
|
"@typescript-eslint/eslint-plugin": "7.10.0",
|
||||||
"@typescript-eslint/parser": "7.10.0",
|
"@typescript-eslint/parser": "7.10.0",
|
||||||
|
|
|
@ -8,8 +8,8 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@misskey-dev/eslint-plugin": "1.0.0",
|
"@misskey-dev/eslint-plugin": "1.0.0",
|
||||||
"@readme/openapi-parser": "2.6.0",
|
"@readme/openapi-parser": "2.7.0",
|
||||||
"@types/node": "22.10.7",
|
"@types/node": "22.13.0",
|
||||||
"@typescript-eslint/eslint-plugin": "7.10.0",
|
"@typescript-eslint/eslint-plugin": "7.10.0",
|
||||||
"@typescript-eslint/parser": "7.10.0",
|
"@typescript-eslint/parser": "7.10.0",
|
||||||
"eslint": "8.57.1",
|
"eslint": "8.57.1",
|
||||||
|
|
|
@ -9957,6 +9957,10 @@ export type operations = {
|
||||||
movedFromId?: string | null;
|
movedFromId?: string | null;
|
||||||
/** Format: misskey:id */
|
/** Format: misskey:id */
|
||||||
movedToId?: string | null;
|
movedToId?: string | null;
|
||||||
|
/** @enum {string|null} */
|
||||||
|
from?: 'local' | 'remote' | 'all';
|
||||||
|
/** @enum {string|null} */
|
||||||
|
to?: 'local' | 'remote' | 'all';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@misskey-dev/eslint-plugin": "1.0.0",
|
"@misskey-dev/eslint-plugin": "1.0.0",
|
||||||
"@types/node": "22.10.7",
|
"@types/node": "22.13.0",
|
||||||
"@typescript-eslint/eslint-plugin": "7.10.0",
|
"@typescript-eslint/eslint-plugin": "7.10.0",
|
||||||
"@typescript-eslint/parser": "7.10.0",
|
"@typescript-eslint/parser": "7.10.0",
|
||||||
"eslint": "8.57.1",
|
"eslint": "8.57.1",
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@misskey-dev/eslint-plugin": "1.0.0",
|
"@misskey-dev/eslint-plugin": "1.0.0",
|
||||||
"@types/serviceworker": "0.0.113",
|
"@types/serviceworker": "0.0.118",
|
||||||
"@typescript-eslint/parser": "7.10.0",
|
"@typescript-eslint/parser": "7.10.0",
|
||||||
"eslint": "8.57.1",
|
"eslint": "8.57.1",
|
||||||
"eslint-plugin-import": "2.31.0",
|
"eslint-plugin-import": "2.31.0",
|
||||||
|
|
|
@ -21,6 +21,16 @@ async function copyFrontendFonts() {
|
||||||
|
|
||||||
async function copyFrontendTablerIcons() {
|
async function copyFrontendTablerIcons() {
|
||||||
await fs.cp('./packages/frontend/node_modules/@tabler/icons-webfont', `./built/_frontend_dist_/tabler-icons.${meta.version}`, { dereference: true, recursive: true });
|
await fs.cp('./packages/frontend/node_modules/@tabler/icons-webfont', `./built/_frontend_dist_/tabler-icons.${meta.version}`, { dereference: true, recursive: true });
|
||||||
|
for (const file of [
|
||||||
|
`./built/_frontend_dist_/tabler-icons.${meta.version}/dist/tabler-icons-filled.scss`,
|
||||||
|
`./built/_frontend_dist_/tabler-icons.${meta.version}/dist/tabler-icons-filled.css`,
|
||||||
|
`./built/_frontend_dist_/tabler-icons.${meta.version}/dist/tabler-icons-filled.min.css`,
|
||||||
|
]) {
|
||||||
|
let source = await fs.readFile(file, { encoding: 'utf-8' });
|
||||||
|
source = source.replaceAll('$ti-prefix: \'ti\'', '$ti-prefix: \'ti-filled\'');
|
||||||
|
source = source.replaceAll('.ti', '.ti-filled');
|
||||||
|
await fs.writeFile(file, source);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function copyFrontendLocales() {
|
async function copyFrontendLocales() {
|
||||||
|
|
|
@ -64,7 +64,7 @@ execa('pnpm', ['--filter', 'backend', 'dev'], {
|
||||||
stderr: process.stderr,
|
stderr: process.stderr,
|
||||||
});
|
});
|
||||||
|
|
||||||
execa('pnpm', ['--filter', 'frontend', process.env.MK_DEV_PREFER === 'backend' ? 'watch' : 'dev'], {
|
execa('pnpm', ['--filter', 'frontend', 'watch'], {
|
||||||
cwd: _dirname + '/../',
|
cwd: _dirname + '/../',
|
||||||
stdout: process.stdout,
|
stdout: process.stdout,
|
||||||
stderr: process.stderr,
|
stderr: process.stderr,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue