Merge upstream
This commit is contained in:
commit
3358fb7a9b
97 changed files with 3254 additions and 3066 deletions
2
.github/ISSUE_TEMPLATE/01_bug-report.yml
vendored
2
.github/ISSUE_TEMPLATE/01_bug-report.yml
vendored
|
@ -75,7 +75,7 @@ body:
|
||||||
Examples:
|
Examples:
|
||||||
* Installation Method or Hosting Service: docker compose, k8s/docker, systemd, "Misskey install shell script", development environment
|
* Installation Method or Hosting Service: docker compose, k8s/docker, systemd, "Misskey install shell script", development environment
|
||||||
* Misskey: 13.x.x
|
* Misskey: 13.x.x
|
||||||
* Node: 20.x.x
|
* Node: 22.x.x
|
||||||
* PostgreSQL: 15.x.x
|
* PostgreSQL: 15.x.x
|
||||||
* Redis: 7.x.x
|
* Redis: 7.x.x
|
||||||
* OS and Architecture: Ubuntu 22.04.2 LTS aarch64
|
* OS and Architecture: Ubuntu 22.04.2 LTS aarch64
|
||||||
|
|
8
.github/unused/test-backend.yml
vendored
8
.github/unused/test-backend.yml
vendored
|
@ -20,7 +20,7 @@ jobs:
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [20.x]
|
node-version: [22.x]
|
||||||
|
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
|
@ -72,7 +72,7 @@ jobs:
|
||||||
- name: Test
|
- name: Test
|
||||||
run: pnpm --filter backend test-and-coverage
|
run: pnpm --filter backend test-and-coverage
|
||||||
- name: Upload to Codecov
|
- name: Upload to Codecov
|
||||||
uses: codecov/codecov-action@v4
|
uses: codecov/codecov-action@v5
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
files: ./packages/backend/coverage/coverage-final.json
|
files: ./packages/backend/coverage/coverage-final.json
|
||||||
|
@ -82,7 +82,7 @@ jobs:
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [20.x]
|
node-version: [22.x]
|
||||||
|
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
|
@ -132,7 +132,7 @@ jobs:
|
||||||
- name: Test
|
- name: Test
|
||||||
run: pnpm --filter backend test-and-coverage:e2e
|
run: pnpm --filter backend test-and-coverage:e2e
|
||||||
- name: Upload to Codecov
|
- name: Upload to Codecov
|
||||||
uses: codecov/codecov-action@v4
|
uses: codecov/codecov-action@v5
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
files: ./packages/backend/coverage/coverage-final.json
|
files: ./packages/backend/coverage/coverage-final.json
|
||||||
|
|
4
.github/workflows/test-frontend.yml
vendored
4
.github/workflows/test-frontend.yml
vendored
|
@ -25,7 +25,7 @@ jobs:
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [20.x]
|
node-version: [22.x]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.1
|
- uses: actions/checkout@v4.1.1
|
||||||
|
@ -52,7 +52,7 @@ jobs:
|
||||||
- name: Test
|
- name: Test
|
||||||
run: pnpm --filter frontend test-and-coverage
|
run: pnpm --filter frontend test-and-coverage
|
||||||
- name: Upload Coverage
|
- name: Upload Coverage
|
||||||
uses: codecov/codecov-action@v4
|
uses: codecov/codecov-action@v5
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
files: ./packages/frontend/coverage/coverage-final.json
|
files: ./packages/frontend/coverage/coverage-final.json
|
||||||
|
|
4
.github/workflows/test-misskey-js.yml
vendored
4
.github/workflows/test-misskey-js.yml
vendored
|
@ -20,7 +20,7 @@ jobs:
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [20.x]
|
node-version: [22.x]
|
||||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
@ -50,7 +50,7 @@ jobs:
|
||||||
CI: true
|
CI: true
|
||||||
|
|
||||||
- name: Upload Coverage
|
- name: Upload Coverage
|
||||||
uses: codecov/codecov-action@v4
|
uses: codecov/codecov-action@v5
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
files: ./packages/misskey-js/coverage/coverage-final.json
|
files: ./packages/misskey-js/coverage/coverage-final.json
|
||||||
|
|
2
.github/workflows/test-production.yml
vendored
2
.github/workflows/test-production.yml
vendored
|
@ -15,7 +15,7 @@ jobs:
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [20.x]
|
node-version: [22.x]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.1
|
- uses: actions/checkout@v4.1.1
|
||||||
|
|
2
.github/workflows/validate-api-json.yml
vendored
2
.github/workflows/validate-api-json.yml
vendored
|
@ -16,7 +16,7 @@ jobs:
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [20.x]
|
node-version: [22.x]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.1
|
- uses: actions/checkout@v4.1.1
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
20
|
22
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# syntax = docker/dockerfile:1.4
|
# syntax = docker/dockerfile:1.4
|
||||||
|
|
||||||
ARG NODE_VERSION=20
|
ARG NODE_VERSION=22
|
||||||
|
|
||||||
# build assets & compile TypeScript
|
# build assets & compile TypeScript
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ ARG GID="991"
|
||||||
|
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install -y --no-install-recommends \
|
&& apt-get install -y --no-install-recommends \
|
||||||
ffmpeg tini curl libjemalloc-dev libjemalloc2 \
|
curl ffmpeg libjemalloc-dev libjemalloc2 tini \
|
||||||
&& ln -s /usr/lib/$(uname -m)-linux-gnu/libjemalloc.so.2 /usr/local/lib/libjemalloc.so \
|
&& ln -s /usr/lib/$(uname -m)-linux-gnu/libjemalloc.so.2 /usr/local/lib/libjemalloc.so \
|
||||||
&& corepack enable \
|
&& corepack enable \
|
||||||
&& groupadd -g "${GID}" misskey \
|
&& groupadd -g "${GID}" misskey \
|
||||||
|
@ -101,7 +101,8 @@ COPY --chown=misskey:misskey --from=native-builder /misskey/packages/backend/bui
|
||||||
COPY --chown=misskey:misskey --from=native-builder /misskey/fluent-emojis /misskey/fluent-emojis
|
COPY --chown=misskey:misskey --from=native-builder /misskey/fluent-emojis /misskey/fluent-emojis
|
||||||
COPY --chown=misskey:misskey . ./
|
COPY --chown=misskey:misskey . ./
|
||||||
|
|
||||||
RUN corepack pack
|
RUN corepack install \
|
||||||
|
&& corepack pack
|
||||||
|
|
||||||
ENV LD_PRELOAD=/usr/local/lib/libjemalloc.so
|
ENV LD_PRELOAD=/usr/local/lib/libjemalloc.so
|
||||||
ENV MALLOC_CONF=background_thread:true,metadata_thp:auto,dirty_decay_ms:30000,muzzy_decay_ms:30000
|
ENV MALLOC_CONF=background_thread:true,metadata_thp:auto,dirty_decay_ms:30000,muzzy_decay_ms:30000
|
||||||
|
|
12
locales/index.d.ts
vendored
12
locales/index.d.ts
vendored
|
@ -9835,6 +9835,18 @@ export interface Locale extends ILocale {
|
||||||
* 特殊
|
* 特殊
|
||||||
*/
|
*/
|
||||||
"specialBlocks": string;
|
"specialBlocks": string;
|
||||||
|
/**
|
||||||
|
* 公開範囲
|
||||||
|
*/
|
||||||
|
"visibility": string;
|
||||||
|
/**
|
||||||
|
* 公開
|
||||||
|
*/
|
||||||
|
"public": string;
|
||||||
|
/**
|
||||||
|
* 非公開
|
||||||
|
*/
|
||||||
|
"private": string;
|
||||||
"blocks": {
|
"blocks": {
|
||||||
/**
|
/**
|
||||||
* テキスト
|
* テキスト
|
||||||
|
|
|
@ -2583,6 +2583,9 @@ _pages:
|
||||||
contentBlocks: "コンテンツ"
|
contentBlocks: "コンテンツ"
|
||||||
inputBlocks: "入力"
|
inputBlocks: "入力"
|
||||||
specialBlocks: "特殊"
|
specialBlocks: "特殊"
|
||||||
|
visibility: "公開範囲"
|
||||||
|
public: "公開"
|
||||||
|
private: "非公開"
|
||||||
blocks:
|
blocks:
|
||||||
text: "テキスト"
|
text: "テキスト"
|
||||||
textarea: "テキストエリア"
|
textarea: "テキストエリア"
|
||||||
|
|
25
package.json
25
package.json
|
@ -6,7 +6,7 @@
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.psec.dev/oscar-surf/misskey.git"
|
"url": "https://git.psec.dev/oscar-surf/misskey.git"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.12.3",
|
"packageManager": "pnpm@9.15.0",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/frontend",
|
"packages/frontend",
|
||||||
"packages/backend",
|
"packages/backend",
|
||||||
|
@ -47,35 +47,36 @@
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"@tensorflow/tfjs-core": "4.22.0",
|
"@tensorflow/tfjs-core": "4.22.0",
|
||||||
"axios": "1.7.7",
|
"axios": "1.7.9",
|
||||||
"chokidar": "4.0.1",
|
"chokidar": "4.0.2",
|
||||||
"cookie": "1.0.1",
|
"cookie": "1.0.2",
|
||||||
"cookie-signature": "1.2.2",
|
"cookie-signature": "1.2.2",
|
||||||
"debug": "4.3.7",
|
"debug": "4.4.0",
|
||||||
"esbuild": "0.24.0",
|
"esbuild": "0.24.0",
|
||||||
"jpeg-js": "0.4.4",
|
"jpeg-js": "0.4.4",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
|
"punycode": "npm:punycode.js@2.3.1",
|
||||||
"sharp": "0.33.5",
|
"sharp": "0.33.5",
|
||||||
"tough-cookie": "5.0.0",
|
"tough-cookie": "5.0.0",
|
||||||
"web-streams-polyfill": "4.0.0"
|
"web-streams-polyfill": "4.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cssnano": "7.0.6",
|
"cssnano": "7.0.6",
|
||||||
"execa": "9.5.1",
|
"execa": "9.5.2",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
"postcss": "8.4.47",
|
"postcss": "8.4.49",
|
||||||
"terser": "5.36.0",
|
"terser": "5.37.0",
|
||||||
"typescript": "5.6.3"
|
"typescript": "5.7.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "22.9.0",
|
"@types/node": "22.10.2",
|
||||||
"@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",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"cypress": "13.15.2",
|
"cypress": "13.17.0",
|
||||||
"eslint": "8.57.1",
|
"eslint": "8.57.1",
|
||||||
"ncp": "2.0.0",
|
"ncp": "2.0.0",
|
||||||
"start-server-and-test": "2.0.8"
|
"start-server-and-test": "2.0.9"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@tensorflow/tfjs-core": "4.22.0"
|
"@tensorflow/tfjs-core": "4.22.0"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/swcrc",
|
"$schema": "https://swc.rs/schema.json",
|
||||||
"jsc": {
|
"jsc": {
|
||||||
"parser": {
|
"parser": {
|
||||||
"syntax": "typescript",
|
"syntax": "typescript",
|
||||||
|
@ -17,7 +17,8 @@
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["*"]
|
"@/*": ["*"]
|
||||||
},
|
},
|
||||||
"target": "es2022"
|
"target": "es2022",
|
||||||
|
"keepClassNames": true
|
||||||
},
|
},
|
||||||
"minify": false
|
"minify": false
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ const base = require('./jest.config.cjs')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
...base,
|
...base,
|
||||||
globalSetup: "<rootDir>/built-test/entry.js",
|
globalSetup: "<rootDir>/test-server/entry.mjs",
|
||||||
setupFilesAfterEnv: ["<rootDir>/test/jest.setup.ts"],
|
setupFilesAfterEnv: ["<rootDir>/test/jest.setup.ts"],
|
||||||
testMatch: [
|
testMatch: [
|
||||||
"<rootDir>/test/e2e/**/*.ts",
|
"<rootDir>/test/e2e/**/*.ts",
|
||||||
|
|
19
packages/backend/migration/1733563840208-page-visibility.js
Normal file
19
packages/backend/migration/1733563840208-page-visibility.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
export class PageVisibility1733563840208 {
|
||||||
|
name = 'PageVisibility1733563840208'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TYPE "public"."page_visibility_enum" RENAME TO "page_visibility_enum_old"`);
|
||||||
|
await queryRunner.query(`CREATE TYPE "public"."page_visibility_enum" AS ENUM('public', 'private')`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "page" ALTER COLUMN "visibility" TYPE "public"."page_visibility_enum" USING "visibility"::"text"::"public"."page_visibility_enum"`);
|
||||||
|
await queryRunner.query(`DROP TYPE "public"."page_visibility_enum_old"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "page" ALTER COLUMN "visibility" SET DEFAULT 'public'`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`CREATE TYPE "public"."page_visibility_enum_old" AS ENUM('followers', 'public', 'specified')`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "page" ALTER COLUMN "visibility" TYPE "public"."page_visibility_enum_old" USING "visibility"::"text"::"public"."page_visibility_enum_old"`);
|
||||||
|
await queryRunner.query(`DROP TYPE "public"."page_visibility_enum"`);
|
||||||
|
await queryRunner.query(`ALTER TYPE "public"."page_visibility_enum_old" RENAME TO "page_visibility_enum"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "page" ALTER COLUMN "visibility" DROP DEFAULT`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,7 +13,6 @@
|
||||||
"revert": "pnpm typeorm migration:revert -d ormconfig.js",
|
"revert": "pnpm typeorm migration:revert -d ormconfig.js",
|
||||||
"check:connect": "node ./scripts/check_connect.js",
|
"check:connect": "node ./scripts/check_connect.js",
|
||||||
"build": "swc src -d built -D --strip-leading-paths",
|
"build": "swc src -d built -D --strip-leading-paths",
|
||||||
"build:test": "swc test-server -d built-test -D --config-file test-server/.swcrc --strip-leading-paths",
|
|
||||||
"watch:swc": "swc src -d built -D -w --strip-leading-paths",
|
"watch:swc": "swc src -d built -D -w --strip-leading-paths",
|
||||||
"build:tsc": "tsc -p tsconfig.json && tsc-alias -p tsconfig.json",
|
"build:tsc": "tsc -p tsconfig.json && tsc-alias -p tsconfig.json",
|
||||||
"watch": "node ./scripts/watch.mjs",
|
"watch": "node ./scripts/watch.mjs",
|
||||||
|
@ -23,27 +22,27 @@
|
||||||
"eslint": "eslint --quiet \"src/**/*.ts\"",
|
"eslint": "eslint --quiet \"src/**/*.ts\"",
|
||||||
"lint": "pnpm typecheck && pnpm eslint",
|
"lint": "pnpm typecheck && pnpm eslint",
|
||||||
"jest": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --config jest.config.unit.cjs",
|
"jest": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --config jest.config.unit.cjs",
|
||||||
"jest:e2e": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --config jest.config.e2e.cjs",
|
"jest:e2e": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve --no-experimental-require-module node_modules/jest/bin/jest.js --forceExit --config jest.config.e2e.cjs",
|
||||||
"jest-and-coverage": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --coverage --forceExit --config jest.config.unit.cjs",
|
"jest-and-coverage": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --coverage --forceExit --config jest.config.unit.cjs",
|
||||||
"jest-and-coverage:e2e": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --coverage --forceExit --config jest.config.e2e.cjs",
|
"jest-and-coverage:e2e": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve --no-experimental-require-module node_modules/jest/bin/jest.js --coverage --forceExit --config jest.config.e2e.cjs",
|
||||||
"jest-clear": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --clearCache",
|
"jest-clear": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --clearCache",
|
||||||
"test": "pnpm jest",
|
"test": "pnpm jest",
|
||||||
"test:e2e": "pnpm build && pnpm build:test && pnpm jest:e2e",
|
"test:e2e": "pnpm build && pnpm jest:e2e",
|
||||||
"test-and-coverage": "pnpm jest-and-coverage",
|
"test-and-coverage": "pnpm jest-and-coverage",
|
||||||
"test-and-coverage:e2e": "pnpm build && pnpm build:test && pnpm jest-and-coverage:e2e",
|
"test-and-coverage:e2e": "pnpm build && pnpm jest-and-coverage:e2e",
|
||||||
"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.9.1",
|
"@swc/core-darwin-arm64": "1.10.1",
|
||||||
"@swc/core-darwin-x64": "1.9.1",
|
"@swc/core-darwin-x64": "1.10.1",
|
||||||
"@swc/core-linux-arm-gnueabihf": "1.9.1",
|
"@swc/core-linux-arm-gnueabihf": "1.10.1",
|
||||||
"@swc/core-linux-arm64-gnu": "1.9.1",
|
"@swc/core-linux-arm64-gnu": "1.10.1",
|
||||||
"@swc/core-linux-arm64-musl": "1.9.1",
|
"@swc/core-linux-arm64-musl": "1.10.1",
|
||||||
"@swc/core-linux-x64-gnu": "1.9.1",
|
"@swc/core-linux-x64-gnu": "1.10.1",
|
||||||
"@swc/core-linux-x64-musl": "1.9.1",
|
"@swc/core-linux-x64-musl": "1.10.1",
|
||||||
"@swc/core-win32-arm64-msvc": "1.9.1",
|
"@swc/core-win32-arm64-msvc": "1.10.1",
|
||||||
"@swc/core-win32-ia32-msvc": "1.9.1",
|
"@swc/core-win32-ia32-msvc": "1.10.1",
|
||||||
"@swc/core-win32-x64-msvc": "1.9.1",
|
"@swc/core-win32-x64-msvc": "1.10.1",
|
||||||
"@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.8",
|
"bufferutil": "4.0.8",
|
||||||
|
@ -64,34 +63,34 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@authenio/samlify-node-xmllint": "2.0.0",
|
"@authenio/samlify-node-xmllint": "2.0.0",
|
||||||
"@aws-sdk/client-s3": "3.687.0",
|
"@aws-sdk/client-s3": "3.714.0",
|
||||||
"@aws-sdk/lib-storage": "3.687.0",
|
"@aws-sdk/lib-storage": "3.714.0",
|
||||||
"@bull-board/api": "6.3.3",
|
"@bull-board/api": "6.5.3",
|
||||||
"@bull-board/fastify": "6.3.3",
|
"@bull-board/fastify": "6.5.3",
|
||||||
"@bull-board/ui": "6.3.3",
|
"@bull-board/ui": "6.5.3",
|
||||||
"@discordapp/twemoji": "15.1.0",
|
"@discordapp/twemoji": "15.1.0",
|
||||||
"@elastic/elasticsearch": "8.15.1",
|
"@elastic/elasticsearch": "8.17.0",
|
||||||
"@fastify/accepts": "5.0.1",
|
"@fastify/accepts": "5.0.2",
|
||||||
"@fastify/cookie": "11.0.1",
|
"@fastify/cookie": "11.0.1",
|
||||||
"@fastify/cors": "10.0.1",
|
"@fastify/cors": "10.0.1",
|
||||||
"@fastify/express": "4.0.1",
|
"@fastify/express": "4.0.1",
|
||||||
"@fastify/formbody": "8.0.1",
|
"@fastify/formbody": "8.0.1",
|
||||||
"@fastify/http-proxy": "10.0.1",
|
"@fastify/http-proxy": "11.0.0",
|
||||||
"@fastify/multipart": "9.0.1",
|
"@fastify/multipart": "9.0.1",
|
||||||
"@fastify/static": "8.0.2",
|
"@fastify/static": "8.0.3",
|
||||||
"@fastify/view": "10.0.1",
|
"@fastify/view": "10.0.1",
|
||||||
"@misskey-dev/sharp-read-bmp": "1.2.0",
|
"@misskey-dev/sharp-read-bmp": "1.2.0",
|
||||||
"@misskey-dev/summaly": "MisskeyIO/summaly#5.1.1",
|
"@misskey-dev/summaly": "MisskeyIO/summaly#5.1.2",
|
||||||
"@napi-rs/canvas": "0.1.60",
|
"@napi-rs/canvas": "0.1.65",
|
||||||
"@nestjs/common": "10.4.7",
|
"@nestjs/common": "10.4.15",
|
||||||
"@nestjs/core": "10.4.7",
|
"@nestjs/core": "10.4.15",
|
||||||
"@nestjs/testing": "10.4.7",
|
"@nestjs/testing": "10.4.15",
|
||||||
"@peertube/http-signature": "1.7.0",
|
"@peertube/http-signature": "1.7.0",
|
||||||
"@simplewebauthn/server": "11.0.0",
|
"@simplewebauthn/server": "13.0.0",
|
||||||
"@sinonjs/fake-timers": "11.3.1",
|
"@sinonjs/fake-timers": "11.3.1",
|
||||||
"@smithy/node-http-handler": "3.2.5",
|
"@smithy/node-http-handler": "3.3.2",
|
||||||
"@swc/cli": "0.5.0",
|
"@swc/cli": "0.5.2",
|
||||||
"@swc/core": "1.9.1",
|
"@swc/core": "1.10.1",
|
||||||
"@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",
|
||||||
|
@ -100,26 +99,26 @@
|
||||||
"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.25.2",
|
"bullmq": "5.34.2",
|
||||||
"cacheable-lookup": "7.0.0",
|
"cacheable-lookup": "7.0.0",
|
||||||
"cbor": "10.0.3",
|
"cbor": "10.0.3",
|
||||||
"chalk": "5.3.0",
|
"chalk": "5.3.0",
|
||||||
"chalk-template": "1.1.0",
|
"chalk-template": "1.1.0",
|
||||||
"chokidar": "4.0.1",
|
"chokidar": "4.0.2",
|
||||||
"cli-highlight": "2.1.11",
|
"cli-highlight": "2.1.11",
|
||||||
"color-convert": "2.0.1",
|
"color-convert": "2.0.1",
|
||||||
"content-disposition": "0.5.4",
|
"content-disposition": "0.5.4",
|
||||||
"date-fns": "4.1.0",
|
"date-fns": "4.1.0",
|
||||||
"deep-email-validator": "0.1.21",
|
"deep-email-validator": "0.1.21",
|
||||||
"fastify": "5.0.0",
|
"fastify": "5.2.0",
|
||||||
"fastify-http-errors-enhanced": "6.0.0",
|
"fastify-http-errors-enhanced": "6.0.0",
|
||||||
"fastify-raw-body": "5.0.0",
|
"fastify-raw-body": "5.0.0",
|
||||||
"feed": "4.2.2",
|
"feed": "4.2.2",
|
||||||
"file-type": "19.6.0",
|
"file-type": "19.6.0",
|
||||||
"fluent-ffmpeg": "2.1.3",
|
"fluent-ffmpeg": "2.1.3",
|
||||||
"form-data": "4.0.1",
|
"form-data": "4.0.1",
|
||||||
"got": "14.4.4",
|
"got": "14.4.5",
|
||||||
"happy-dom": "15.11.0",
|
"happy-dom": "15.11.7",
|
||||||
"hpagent": "1.2.0",
|
"hpagent": "1.2.0",
|
||||||
"htmlescape": "1.1.1",
|
"htmlescape": "1.1.1",
|
||||||
"http-link-header": "1.1.3",
|
"http-link-header": "1.1.3",
|
||||||
|
@ -133,33 +132,33 @@
|
||||||
"json5": "2.2.3",
|
"json5": "2.2.3",
|
||||||
"jsonld": "8.3.2",
|
"jsonld": "8.3.2",
|
||||||
"jsrsasign": "11.1.0",
|
"jsrsasign": "11.1.0",
|
||||||
"meilisearch": "0.45.0",
|
"meilisearch": "0.46.0",
|
||||||
"mfm-js": "0.24.0",
|
"mfm-js": "0.24.0",
|
||||||
"microformats-parser": "2.0.2",
|
"microformats-parser": "2.0.2",
|
||||||
"mime-types": "2.1.35",
|
"mime-types": "2.1.35",
|
||||||
"misskey-js": "workspace:*",
|
"misskey-js": "workspace:*",
|
||||||
"misskey-reversi": "workspace:*",
|
"misskey-reversi": "workspace:*",
|
||||||
"ms": "3.0.0-canary.1",
|
"ms": "3.0.0-canary.1",
|
||||||
"nanoid": "5.0.8",
|
"nanoid": "5.0.9",
|
||||||
"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.9.16",
|
||||||
"nsfwjs": "2.4.2",
|
"nsfwjs": "4.2.0",
|
||||||
"oauth": "0.10.0",
|
"oauth": "0.10.0",
|
||||||
"oauth2orize": "1.12.0",
|
"oauth2orize": "1.12.0",
|
||||||
"oauth2orize-pkce": "0.1.2",
|
"oauth2orize-pkce": "0.1.2",
|
||||||
"os-utils": "0.0.14",
|
"os-utils": "0.0.14",
|
||||||
"otpauth": "9.3.4",
|
"otpauth": "9.3.6",
|
||||||
"parse5": "7.2.1",
|
"parse5": "7.2.1",
|
||||||
"pg": "8.13.1",
|
"pg": "8.13.1",
|
||||||
"pino": "9.5.0",
|
"pino": "9.5.0",
|
||||||
"pino-pretty": "12.0.0",
|
"pino-pretty": "13.0.0",
|
||||||
"pkce-challenge": "4.1.0",
|
"pkce-challenge": "4.1.0",
|
||||||
"probe-image-size": "7.2.3",
|
"probe-image-size": "7.2.3",
|
||||||
"promise-limit": "2.7.0",
|
"promise-limit": "2.7.0",
|
||||||
"pug": "3.0.3",
|
"pug": "3.0.3",
|
||||||
"punycode": "2.3.1",
|
"punycode.js": "2.3.1",
|
||||||
"qrcode": "1.5.4",
|
"qrcode": "1.5.4",
|
||||||
"random-seed": "0.3.0",
|
"random-seed": "0.3.0",
|
||||||
"ratelimiter": "3.4.1",
|
"ratelimiter": "3.4.1",
|
||||||
|
@ -171,18 +170,18 @@
|
||||||
"rxjs": "7.8.1",
|
"rxjs": "7.8.1",
|
||||||
"samlify": "2.8.11",
|
"samlify": "2.8.11",
|
||||||
"sanitize-html": "2.13.1",
|
"sanitize-html": "2.13.1",
|
||||||
"secure-json-parse": "2.7.0",
|
"secure-json-parse": "3.0.1",
|
||||||
"sharp": "0.33.5",
|
"sharp": "0.33.5",
|
||||||
"slacc": "0.0.10",
|
"slacc": "0.0.10",
|
||||||
"strict-event-emitter-types": "2.0.0",
|
"strict-event-emitter-types": "2.0.0",
|
||||||
"stringz": "2.1.0",
|
"stringz": "2.1.0",
|
||||||
"systeminformation": "5.23.5",
|
"systeminformation": "5.23.13",
|
||||||
"tinycolor2": "1.6.0",
|
"tinycolor2": "1.6.0",
|
||||||
"tmp": "0.2.3",
|
"tmp": "0.2.3",
|
||||||
"tsc-alias": "1.8.10",
|
"tsc-alias": "1.8.10",
|
||||||
"tsconfig-paths": "4.2.0",
|
"tsconfig-paths": "4.2.0",
|
||||||
"typeorm": "0.3.20",
|
"typeorm": "0.3.20",
|
||||||
"typescript": "5.6.3",
|
"typescript": "5.7.2",
|
||||||
"ulid": "2.3.0",
|
"ulid": "2.3.0",
|
||||||
"vary": "1.1.2",
|
"vary": "1.1.2",
|
||||||
"web-push": "3.6.7",
|
"web-push": "3.6.7",
|
||||||
|
@ -193,8 +192,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.7",
|
"@nestjs/platform-express": "10.4.15",
|
||||||
"@simplewebauthn/types": "11.0.0",
|
|
||||||
"@swc/jest": "0.2.37",
|
"@swc/jest": "0.2.37",
|
||||||
"@types/accepts": "1.3.7",
|
"@types/accepts": "1.3.7",
|
||||||
"@types/archiver": "6.0.3",
|
"@types/archiver": "6.0.3",
|
||||||
|
@ -209,18 +207,18 @@
|
||||||
"@types/js-yaml": "4.0.9",
|
"@types/js-yaml": "4.0.9",
|
||||||
"@types/jsdom": "21.1.7",
|
"@types/jsdom": "21.1.7",
|
||||||
"@types/jsonld": "1.5.15",
|
"@types/jsonld": "1.5.15",
|
||||||
"@types/jsrsasign": "10.5.14",
|
"@types/jsrsasign": "10.5.15",
|
||||||
"@types/mime-types": "2.1.4",
|
"@types/mime-types": "2.1.4",
|
||||||
"@types/ms": "0.7.34",
|
"@types/ms": "0.7.34",
|
||||||
"@types/node": "22.9.0",
|
"@types/node": "22.10.2",
|
||||||
"@types/node-forge": "1.3.11",
|
"@types/node-forge": "1.3.11",
|
||||||
"@types/nodemailer": "6.4.16",
|
"@types/nodemailer": "6.4.17",
|
||||||
"@types/oauth": "0.9.6",
|
"@types/oauth": "0.9.6",
|
||||||
"@types/oauth2orize": "1.11.5",
|
"@types/oauth2orize": "1.11.5",
|
||||||
"@types/oauth2orize-pkce": "0.1.2",
|
"@types/oauth2orize-pkce": "0.1.2",
|
||||||
"@types/pg": "8.11.10",
|
"@types/pg": "8.11.10",
|
||||||
"@types/pug": "2.0.10",
|
"@types/pug": "2.0.10",
|
||||||
"@types/punycode": "2.1.4",
|
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
||||||
"@types/qrcode": "1.5.5",
|
"@types/qrcode": "1.5.5",
|
||||||
"@types/random-seed": "0.3.5",
|
"@types/random-seed": "0.3.5",
|
||||||
"@types/ratelimiter": "3.4.6",
|
"@types/ratelimiter": "3.4.6",
|
||||||
|
@ -240,11 +238,11 @@
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"eslint": "8.57.1",
|
"eslint": "8.57.1",
|
||||||
"eslint-plugin-import": "2.31.0",
|
"eslint-plugin-import": "2.31.0",
|
||||||
"execa": "9.5.1",
|
"execa": "9.5.2",
|
||||||
"fkill": "^9.0.0",
|
"fkill": "^9.0.0",
|
||||||
"jest": "29.7.0",
|
"jest": "29.7.0",
|
||||||
"jest-mock": "29.7.0",
|
"jest-mock": "29.7.0",
|
||||||
"nodemon": "3.1.7",
|
"nodemon": "3.1.9",
|
||||||
"pid-port": "1.0.0",
|
"pid-port": "1.0.0",
|
||||||
"simple-oauth2": "5.1.0"
|
"simple-oauth2": "5.1.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,13 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as fs from 'node:fs';
|
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { dirname } from 'node:path';
|
import { dirname } from 'node:path';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import * as nsfw from 'nsfwjs';
|
import * as nsfw from 'nsfwjs';
|
||||||
import si from 'systeminformation';
|
import si from 'systeminformation';
|
||||||
import { Mutex } from 'async-mutex';
|
import { Mutex } from 'async-mutex';
|
||||||
|
import { sharpBmp } from '@misskey-dev/sharp-read-bmp';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import type Logger from '@/logger.js';
|
import type Logger from '@/logger.js';
|
||||||
import { LoggerService } from '@/core/LoggerService.js';
|
import { LoggerService } from '@/core/LoggerService.js';
|
||||||
|
@ -33,7 +33,7 @@ export class AiService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async detectSensitive(path: string): Promise<nsfw.predictionType[] | null> {
|
public async detectSensitive(path: string, mime: string): Promise<nsfw.PredictionType[] | null> {
|
||||||
try {
|
try {
|
||||||
if (isSupportedCpu === undefined) {
|
if (isSupportedCpu === undefined) {
|
||||||
const cpuFlags = await this.getCpuFlags();
|
const cpuFlags = await this.getCpuFlags();
|
||||||
|
@ -55,11 +55,16 @@ export class AiService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const buffer = await fs.promises.readFile(path);
|
const sharp = await sharpBmp(path, mime);
|
||||||
const image = await tf.node.decodeImage(buffer, 3) as any;
|
const { data, info } = await sharp
|
||||||
|
.resize(299, 299, { fit: 'inside' })
|
||||||
|
.ensureAlpha()
|
||||||
|
.raw({ depth: 'int' })
|
||||||
|
.toBuffer({ resolveWithObject: true });
|
||||||
|
|
||||||
|
const image = tf.tensor3d(data, [info.height, info.width, info.channels], 'int32');
|
||||||
try {
|
try {
|
||||||
const predictions = await this.model.classify(image);
|
return await this.model.classify(image);
|
||||||
return predictions;
|
|
||||||
} finally {
|
} finally {
|
||||||
image.dispose();
|
image.dispose();
|
||||||
}
|
}
|
||||||
|
|
|
@ -304,6 +304,12 @@ export class EmailService {
|
||||||
reason: 'mx',
|
reason: 'mx',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (json.mx_host?.some(host => this.utilityService.isBlockedHost(meta.bannedEmailDomains, host))) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
reason: 'mx',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
valid: true,
|
valid: true,
|
||||||
|
|
|
@ -15,7 +15,6 @@ import { LoggerService } from '@/core/LoggerService.js';
|
||||||
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
||||||
import type { DOMWindow } from 'jsdom';
|
|
||||||
|
|
||||||
type NodeInfo = {
|
type NodeInfo = {
|
||||||
openRegistrations?: unknown;
|
openRegistrations?: unknown;
|
||||||
|
@ -170,7 +169,7 @@ export class FetchInstanceMetadataService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async fetchDom(instance: MiInstance): Promise<DOMWindow['document']> {
|
private async fetchDom(instance: MiInstance): Promise<Document> {
|
||||||
this.logger.info(`Fetching HTML of ${instance.host} ...`);
|
this.logger.info(`Fetching HTML of ${instance.host} ...`);
|
||||||
|
|
||||||
const url = 'https://' + instance.host;
|
const url = 'https://' + instance.host;
|
||||||
|
@ -178,9 +177,8 @@ export class FetchInstanceMetadataService {
|
||||||
const html = await this.httpRequestService.getHtml(url);
|
const html = await this.httpRequestService.getHtml(url);
|
||||||
|
|
||||||
const { window } = new JSDOM(html);
|
const { window } = new JSDOM(html);
|
||||||
const doc = window.document;
|
|
||||||
|
|
||||||
return doc;
|
return window.document as Document;
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
@ -195,7 +193,7 @@ export class FetchInstanceMetadataService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async fetchFaviconUrl(instance: MiInstance, doc: DOMWindow['document'] | null): Promise<string | null> {
|
private async fetchFaviconUrl(instance: MiInstance, doc: Document | null): Promise<string | null> {
|
||||||
const url = 'https://' + instance.host;
|
const url = 'https://' + instance.host;
|
||||||
|
|
||||||
if (doc) {
|
if (doc) {
|
||||||
|
@ -221,7 +219,7 @@ export class FetchInstanceMetadataService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async fetchIconUrl(instance: MiInstance, doc: DOMWindow['document'] | null, manifest: Record<string, any> | null): Promise<string | null> {
|
private async fetchIconUrl(instance: MiInstance, doc: Document | null, manifest: Record<string, any> | null): Promise<string | null> {
|
||||||
if (manifest && manifest.icons && manifest.icons.length > 0 && manifest.icons[0].src) {
|
if (manifest && manifest.icons && manifest.icons.length > 0 && manifest.icons[0].src) {
|
||||||
const url = 'https://' + instance.host;
|
const url = 'https://' + instance.host;
|
||||||
return (new URL(manifest.icons[0].src, url)).href;
|
return (new URL(manifest.icons[0].src, url)).href;
|
||||||
|
@ -250,7 +248,7 @@ export class FetchInstanceMetadataService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async getThemeColor(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record<string, any> | null): Promise<string | null> {
|
private async getThemeColor(info: NodeInfo | null, doc: Document | null, manifest: Record<string, any> | null): Promise<string | null> {
|
||||||
const themeColor = info?.metadata?.themeColor ?? doc?.querySelector('meta[name="theme-color"]')?.getAttribute('content') ?? manifest?.theme_color;
|
const themeColor = info?.metadata?.themeColor ?? doc?.querySelector('meta[name="theme-color"]')?.getAttribute('content') ?? manifest?.theme_color;
|
||||||
|
|
||||||
if (themeColor) {
|
if (themeColor) {
|
||||||
|
@ -262,7 +260,7 @@ export class FetchInstanceMetadataService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async getSiteName(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record<string, any> | null): Promise<string | null> {
|
private async getSiteName(info: NodeInfo | null, doc: Document | null, manifest: Record<string, any> | null): Promise<string | null> {
|
||||||
if (info && info.metadata) {
|
if (info && info.metadata) {
|
||||||
if (typeof info.metadata.nodeName === 'string') {
|
if (typeof info.metadata.nodeName === 'string') {
|
||||||
return info.metadata.nodeName;
|
return info.metadata.nodeName;
|
||||||
|
@ -287,7 +285,7 @@ export class FetchInstanceMetadataService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async getDescription(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record<string, any> | null): Promise<string | null> {
|
private async getDescription(info: NodeInfo | null, doc: Document | null, manifest: Record<string, any> | null): Promise<string | null> {
|
||||||
if (info && info.metadata) {
|
if (info && info.metadata) {
|
||||||
if (typeof info.metadata.nodeDescription === 'string') {
|
if (typeof info.metadata.nodeDescription === 'string') {
|
||||||
return info.metadata.nodeDescription;
|
return info.metadata.nodeDescription;
|
||||||
|
|
|
@ -13,7 +13,7 @@ import * as fileType from 'file-type';
|
||||||
import FFmpeg from 'fluent-ffmpeg';
|
import FFmpeg from 'fluent-ffmpeg';
|
||||||
import isSvg from 'is-svg';
|
import isSvg from 'is-svg';
|
||||||
import probeImageSize from 'probe-image-size';
|
import probeImageSize from 'probe-image-size';
|
||||||
import { type predictionType } from 'nsfwjs';
|
import { type PredictionType } from 'nsfwjs';
|
||||||
import { sharpBmp } from '@misskey-dev/sharp-read-bmp';
|
import { sharpBmp } from '@misskey-dev/sharp-read-bmp';
|
||||||
import { encode } from 'blurhash';
|
import { encode } from 'blurhash';
|
||||||
import { createTempDir } from '@/misc/create-temp.js';
|
import { createTempDir } from '@/misc/create-temp.js';
|
||||||
|
@ -170,7 +170,7 @@ export class FileInfoService {
|
||||||
let sensitive = false;
|
let sensitive = false;
|
||||||
let porn = false;
|
let porn = false;
|
||||||
|
|
||||||
function judgePrediction(result: readonly predictionType[]): [sensitive: boolean, porn: boolean] {
|
function judgePrediction(result: readonly PredictionType[]): [sensitive: boolean, porn: boolean] {
|
||||||
let sensitive = false;
|
let sensitive = false;
|
||||||
let porn = false;
|
let porn = false;
|
||||||
|
|
||||||
|
@ -188,7 +188,7 @@ export class FileInfoService {
|
||||||
'image/png',
|
'image/png',
|
||||||
'image/webp',
|
'image/webp',
|
||||||
].includes(mime)) {
|
].includes(mime)) {
|
||||||
const result = await this.aiService.detectSensitive(source);
|
const result = await this.aiService.detectSensitive(source, mime);
|
||||||
if (result) {
|
if (result) {
|
||||||
[sensitive, porn] = judgePrediction(result);
|
[sensitive, porn] = judgePrediction(result);
|
||||||
}
|
}
|
||||||
|
@ -247,7 +247,7 @@ export class FileInfoService {
|
||||||
}
|
}
|
||||||
targetIndex = nextIndex;
|
targetIndex = nextIndex;
|
||||||
nextIndex += index; // fibonacci sequence によってフレーム数制限を掛ける
|
nextIndex += index; // fibonacci sequence によってフレーム数制限を掛ける
|
||||||
const result = await this.aiService.detectSensitive(path);
|
const result = await this.aiService.detectSensitive(path, 'image/png');
|
||||||
if (result) {
|
if (result) {
|
||||||
results.push(judgePrediction(result));
|
results.push(judgePrediction(result));
|
||||||
}
|
}
|
||||||
|
@ -273,7 +273,9 @@ export class FileInfoService {
|
||||||
watcher.close();
|
watcher.close();
|
||||||
});
|
});
|
||||||
command.run();
|
command.run();
|
||||||
for (let i = 1; true; i++) { // eslint-disable-line @typescript-eslint/no-unnecessary-condition
|
let i = 0;
|
||||||
|
while (true) {
|
||||||
|
i++;
|
||||||
const current = `${i}.png`;
|
const current = `${i}.png`;
|
||||||
const next = `${i + 1}.png`;
|
const next = `${i + 1}.png`;
|
||||||
const framePath = join(cwd, current);
|
const framePath = join(cwd, current);
|
||||||
|
@ -455,9 +457,9 @@ export class FileInfoService {
|
||||||
private async getBlurhash(path: string, type: string): Promise<string> {
|
private async getBlurhash(path: string, type: string): Promise<string> {
|
||||||
const sharp = await sharpBmp(path, type);
|
const sharp = await sharpBmp(path, type);
|
||||||
const { data, info } = await sharp
|
const { data, info } = await sharp
|
||||||
.raw()
|
|
||||||
.ensureAlpha()
|
|
||||||
.resize(64, 64, { fit: 'inside' })
|
.resize(64, 64, { fit: 'inside' })
|
||||||
|
.ensureAlpha()
|
||||||
|
.raw()
|
||||||
.toBuffer({ resolveWithObject: true });
|
.toBuffer({ resolveWithObject: true });
|
||||||
|
|
||||||
return encode(new Uint8ClampedArray(data), info.width, info.height, 5, 5);
|
return encode(new Uint8ClampedArray(data), info.width, info.height, 5, 5);
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { URL } from 'node:url';
|
import { URL } from 'node:url';
|
||||||
import { toASCII } from 'punycode';
|
import punycode from 'punycode.js';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import RE2 from 're2';
|
import RE2 from 're2';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
|
@ -95,12 +95,12 @@ export class UtilityService {
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public toPuny(host: string): string {
|
public toPuny(host: string): string {
|
||||||
return toASCII(host.toLowerCase());
|
return punycode.toASCII(host.toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public toPunyNullable(host: string | null | undefined): string | null {
|
public toPunyNullable(host: string | null | undefined): string | null {
|
||||||
if (host == null) return null;
|
if (host == null) return null;
|
||||||
return toASCII(host.toLowerCase());
|
return punycode.toASCII(host.toLowerCase());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ import type {
|
||||||
PublicKeyCredentialCreationOptionsJSON,
|
PublicKeyCredentialCreationOptionsJSON,
|
||||||
PublicKeyCredentialRequestOptionsJSON,
|
PublicKeyCredentialRequestOptionsJSON,
|
||||||
RegistrationResponseJSON,
|
RegistrationResponseJSON,
|
||||||
} from '@simplewebauthn/types';
|
} from '@simplewebauthn/server';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class WebAuthnService {
|
export class WebAuthnService {
|
||||||
|
@ -78,7 +78,6 @@ export class WebAuthnService {
|
||||||
userID: isoUint8Array.fromUTF8String(userId),
|
userID: isoUint8Array.fromUTF8String(userId),
|
||||||
userName: userName,
|
userName: userName,
|
||||||
userDisplayName: userDisplayName,
|
userDisplayName: userDisplayName,
|
||||||
attestationType: 'indirect',
|
|
||||||
excludeCredentials: keys.map(key => (<{ id: string; transports?: AuthenticatorTransportFuture[]; }>{
|
excludeCredentials: keys.map(key => (<{ id: string; transports?: AuthenticatorTransportFuture[]; }>{
|
||||||
id: key.id,
|
id: key.id,
|
||||||
transports: key.transports ?? undefined,
|
transports: key.transports ?? undefined,
|
||||||
|
|
|
@ -105,6 +105,7 @@ export class PageEntityService {
|
||||||
attachedFiles: this.driveFileEntityService.packMany((await Promise.all(attachedFiles)).filter(isNotNull), me),
|
attachedFiles: this.driveFileEntityService.packMany((await Promise.all(attachedFiles)).filter(isNotNull), me),
|
||||||
likedCount: page.likedCount,
|
likedCount: page.likedCount,
|
||||||
isLiked: meId ? await this.pageLikesRepository.exists({ where: { pageId: page.id, userId: meId } }) : undefined,
|
isLiked: meId ? await this.pageLikesRepository.exists({ where: { pageId: page.id, userId: meId } }) : undefined,
|
||||||
|
visibility: page.visibility,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,8 +20,7 @@ export function bindThis(target: any, key: string, descriptor: any) {
|
||||||
return {
|
return {
|
||||||
configurable: true,
|
configurable: true,
|
||||||
get() {
|
get() {
|
||||||
// eslint-disable-next-line no-prototype-builtins
|
if (this === target.prototype || Object.hasOwn(this, key) ||
|
||||||
if (this === target.prototype || this.hasOwnProperty(key) ||
|
|
||||||
typeof fn !== 'function') {
|
typeof fn !== 'function') {
|
||||||
return fn;
|
return fn;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,21 +2,21 @@ import { EventEmitter } from 'node:events';
|
||||||
import * as Bull from 'bullmq';
|
import * as Bull from 'bullmq';
|
||||||
|
|
||||||
export class Queues<DataType = any, ResultType = any, NameType extends string = string> {
|
export class Queues<DataType = any, ResultType = any, NameType extends string = string> {
|
||||||
public readonly queues: ReadonlyArray<Bull.Queue<DataType, ResultType, NameType>>;
|
public readonly queues: ReadonlyArray<Bull.Queue<void, void, string, DataType, ResultType, NameType>>;
|
||||||
|
|
||||||
constructor(queues: Bull.Queue<DataType, ResultType, NameType>[]) {
|
constructor(queues: Bull.Queue<void, void, string, DataType, ResultType, NameType>[]) {
|
||||||
if (queues.length === 0) {
|
if (queues.length === 0) {
|
||||||
throw new Error('queues cannot be empty.');
|
throw new Error('queues cannot be empty.');
|
||||||
}
|
}
|
||||||
this.queues = queues;
|
this.queues = queues;
|
||||||
}
|
}
|
||||||
|
|
||||||
getRandomQueue(): Bull.Queue<DataType, ResultType, NameType> {
|
get randomQueue(): Bull.Queue<void, void, string, DataType, ResultType, NameType> {
|
||||||
return this.queues[Math.floor(Math.random() * this.queues.length)];
|
return this.queues[Math.floor(Math.random() * this.queues.length)];
|
||||||
}
|
}
|
||||||
|
|
||||||
add(name: NameType, data: DataType, opts?: Bull.JobsOptions): Promise<Bull.Job<DataType, ResultType, NameType>> {
|
add(name: NameType, data: DataType, opts?: Bull.JobsOptions): Promise<Bull.Job<DataType, ResultType, NameType>> {
|
||||||
return this.getRandomQueue().add(name, data, opts);
|
return this.randomQueue.add(name, data, opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
async addBulk(jobs: { name: NameType; data: DataType; opts?: Bull.BulkJobOptions }[]): Promise<Bull.Job<DataType, ResultType, NameType>[]> {
|
async addBulk(jobs: { name: NameType; data: DataType; opts?: Bull.BulkJobOptions }[]): Promise<Bull.Job<DataType, ResultType, NameType>[]> {
|
||||||
|
@ -30,7 +30,7 @@ export class Queues<DataType = any, ResultType = any, NameType extends string =
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDelayed(start?: number, end?: number): Promise<Bull.Job<DataType, ResultType, NameType>[]> {
|
async getDelayed(start?: number, end?: number): Promise<Bull.Job<DataType, ResultType, NameType>[]> {
|
||||||
return (await Promise.allSettled(this.queues.map(queue => queue.getDelayed(start, end))))
|
return (await Promise.allSettled(this.queues.map(queue => queue.getDelayed(start, end) as Promise<Bull.Job<DataType, ResultType, NameType>[]>)))
|
||||||
.filter((value): value is PromiseFulfilledResult<Bull.Job<DataType, ResultType, NameType>[]> => value.status === 'fulfilled')
|
.filter((value): value is PromiseFulfilledResult<Bull.Job<DataType, ResultType, NameType>[]> => value.status === 'fulfilled')
|
||||||
.flatMap(value => value.value);
|
.flatMap(value => value.value);
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ export class Queues<DataType = any, ResultType = any, NameType extends string =
|
||||||
}, {} as Record<string, number>);
|
}, {} as Record<string, number>);
|
||||||
}
|
}
|
||||||
|
|
||||||
once<U extends keyof Bull.QueueListener<DataType, ResultType, NameType>>(event: U, listener: Bull.QueueListener<DataType, ResultType, NameType>[U]): void {
|
once<U extends keyof Bull.QueueListener<Bull.Job<DataType, ResultType, NameType>>>(event: U, listener: Bull.QueueListener<Bull.Job<DataType, ResultType, NameType>>[U]): void {
|
||||||
const e = new EventEmitter();
|
const e = new EventEmitter();
|
||||||
e.once(event, listener);
|
e.once(event, listener);
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ export class Queues<DataType = any, ResultType = any, NameType extends string =
|
||||||
}
|
}
|
||||||
|
|
||||||
async getJobs(types?: Bull.JobType[] | Bull.JobType, start?: number, end?: number, asc?: boolean): Promise<Bull.Job<DataType, ResultType, NameType>[]> {
|
async getJobs(types?: Bull.JobType[] | Bull.JobType, start?: number, end?: number, asc?: boolean): Promise<Bull.Job<DataType, ResultType, NameType>[]> {
|
||||||
return (await Promise.allSettled(this.queues.map(queue => queue.getJobs(types, start, end, asc))))
|
return (await Promise.allSettled(this.queues.map(queue => queue.getJobs(types, start, end, asc) as Promise<Bull.Job<DataType, ResultType, NameType>[]>)))
|
||||||
.filter((value): value is PromiseFulfilledResult<Bull.Job<DataType, ResultType, NameType>[]> => value.status === 'fulfilled')
|
.filter((value): value is PromiseFulfilledResult<Bull.Job<DataType, ResultType, NameType>[]> => value.status === 'fulfilled')
|
||||||
.flatMap(value => value.value);
|
.flatMap(value => value.value);
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,18 +99,13 @@ export class MiPage {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* public ... 公開
|
* public ... 公開
|
||||||
* followers ... フォロワーのみ
|
* private ... 非公開
|
||||||
* specified ... visibleUserIds で指定したユーザーのみ
|
|
||||||
*/
|
*/
|
||||||
@Column('enum', { enum: ['public', 'followers', 'specified'] })
|
@Column('enum', {
|
||||||
public visibility: 'public' | 'followers' | 'specified';
|
enum: ['public', 'private'],
|
||||||
|
default: 'public',
|
||||||
@Index()
|
|
||||||
@Column({
|
|
||||||
...id(),
|
|
||||||
array: true, default: '{}',
|
|
||||||
})
|
})
|
||||||
public visibleUserIds: MiUser['id'][];
|
public visibility: 'public' | 'private';
|
||||||
|
|
||||||
@Column('integer', {
|
@Column('integer', {
|
||||||
default: 0,
|
default: 0,
|
||||||
|
|
|
@ -205,6 +205,11 @@ export const packedPageSchema = {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
optional: true, nullable: false,
|
optional: true, nullable: false,
|
||||||
},
|
},
|
||||||
|
visibility: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
enum: ['public', 'private'],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
|
|
@ -271,7 +271,7 @@ export class ServerService implements OnApplicationShutdown {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
fastify.listen({ port: this.config.port, host: '0.0.0.0' });
|
fastify.listen({ port: this.config.port, host: '::' });
|
||||||
}
|
}
|
||||||
|
|
||||||
await fastify.ready();
|
await fastify.ready();
|
||||||
|
|
|
@ -25,7 +25,7 @@ import { FastifyReplyError } from '@/misc/fastify-reply-error.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
import { RateLimiterService } from './RateLimiterService.js';
|
import { RateLimiterService } from './RateLimiterService.js';
|
||||||
import { SigninService } from './SigninService.js';
|
import { SigninService } from './SigninService.js';
|
||||||
import type { AuthenticationResponseJSON } from '@simplewebauthn/types';
|
import type { AuthenticationResponseJSON } from '@simplewebauthn/server';
|
||||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||||
import { randomUUID } from 'node:crypto';
|
import { randomUUID } from 'node:crypto';
|
||||||
|
|
||||||
|
|
|
@ -99,7 +99,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
} else if (ps.reportContentPattern === null) {
|
} else if (ps.reportContentPattern === null) {
|
||||||
properties.reportContentPattern = null;
|
properties.reportContentPattern = null;
|
||||||
}
|
}
|
||||||
if (ps.forward) properties.forward = ps.forward;
|
if (ps.forward !== undefined) properties.forward = ps.forward;
|
||||||
if (ps.expiresAt) {
|
if (ps.expiresAt) {
|
||||||
let expirationDate: Date | null = new Date();
|
let expirationDate: Date | null = new Date();
|
||||||
const previousMonth = expirationDate.getUTCMonth();
|
const previousMonth = expirationDate.getUTCMonth();
|
||||||
|
|
|
@ -563,7 +563,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
const html = await this.httpRequestService.getHtml(url);
|
const html = await this.httpRequestService.getHtml(url);
|
||||||
|
|
||||||
const { window } = new JSDOM(html);
|
const { window } = new JSDOM(html);
|
||||||
const doc = window.document;
|
const doc = window.document as Document;
|
||||||
|
|
||||||
const myLink = `${this.config.url}/@${user.username}`;
|
const myLink = `${this.config.url}/@${user.username}`;
|
||||||
|
|
||||||
|
|
|
@ -65,6 +65,7 @@ export const paramDef = {
|
||||||
font: { type: 'string', enum: ['serif', 'sans-serif'], default: 'sans-serif' },
|
font: { type: 'string', enum: ['serif', 'sans-serif'], default: 'sans-serif' },
|
||||||
alignCenter: { type: 'boolean', default: false },
|
alignCenter: { type: 'boolean', default: false },
|
||||||
hideTitleWhenPinned: { type: 'boolean', default: false },
|
hideTitleWhenPinned: { type: 'boolean', default: false },
|
||||||
|
visibility: { type: 'string', enum: ['public', 'private'] },
|
||||||
},
|
},
|
||||||
required: ['title', 'name', 'content', 'variables', 'script'],
|
required: ['title', 'name', 'content', 'variables', 'script'],
|
||||||
} as const;
|
} as const;
|
||||||
|
@ -114,7 +115,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
script: ps.script,
|
script: ps.script,
|
||||||
eyeCatchingImageId: eyeCatchingImage ? eyeCatchingImage.id : null,
|
eyeCatchingImageId: eyeCatchingImage ? eyeCatchingImage.id : null,
|
||||||
userId: me.id,
|
userId: me.id,
|
||||||
visibility: 'public',
|
visibility: ps.visibility,
|
||||||
alignCenter: ps.alignCenter,
|
alignCenter: ps.alignCenter,
|
||||||
hideTitleWhenPinned: ps.hideTitleWhenPinned,
|
hideTitleWhenPinned: ps.hideTitleWhenPinned,
|
||||||
font: ps.font,
|
font: ps.font,
|
||||||
|
|
|
@ -78,6 +78,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
throw new ApiError(meta.errors.noSuchPage);
|
throw new ApiError(meta.errors.noSuchPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (page.visibility === 'private' && (me == null || (page.userId !== me.id))) {
|
||||||
|
throw new ApiError(meta.errors.noSuchPage);
|
||||||
|
}
|
||||||
|
|
||||||
return await this.pageEntityService.pack(page, me);
|
return await this.pageEntityService.pack(page, me);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,6 +70,7 @@ export const paramDef = {
|
||||||
font: { type: 'string', enum: ['serif', 'sans-serif'] },
|
font: { type: 'string', enum: ['serif', 'sans-serif'] },
|
||||||
alignCenter: { type: 'boolean' },
|
alignCenter: { type: 'boolean' },
|
||||||
hideTitleWhenPinned: { type: 'boolean' },
|
hideTitleWhenPinned: { type: 'boolean' },
|
||||||
|
visibility: { type: 'string', enum: ['public', 'private'] },
|
||||||
},
|
},
|
||||||
required: ['pageId', 'title', 'name', 'content', 'variables', 'script'],
|
required: ['pageId', 'title', 'name', 'content', 'variables', 'script'],
|
||||||
} as const;
|
} as const;
|
||||||
|
@ -129,6 +130,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
hideTitleWhenPinned: ps.hideTitleWhenPinned === undefined ? page.hideTitleWhenPinned : ps.hideTitleWhenPinned,
|
hideTitleWhenPinned: ps.hideTitleWhenPinned === undefined ? page.hideTitleWhenPinned : ps.hideTitleWhenPinned,
|
||||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||||
font: ps.font === undefined ? page.font : ps.font,
|
font: ps.font === undefined ? page.font : ps.font,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||||
|
visibility: ps.visibility === undefined ? page.visibility : ps.visibility,
|
||||||
eyeCatchingImageId: ps.eyeCatchingImageId === null
|
eyeCatchingImageId: ps.eyeCatchingImageId === null
|
||||||
? null
|
? null
|
||||||
: ps.eyeCatchingImageId === undefined
|
: ps.eyeCatchingImageId === undefined
|
||||||
|
|
|
@ -195,7 +195,7 @@ function getQueryMode(issuerUrl: string): oauth2orize.grant.Options['modes'] {
|
||||||
parsed.searchParams.append(key, value as string);
|
parsed.searchParams.append(key, value as string);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (res as OAuthHttpResponse).redirect(parsed.toString());
|
(res as OAuthHttpResponse).redirect(parsed.toString());
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
* {
|
* {
|
||||||
font-family: Fira code, Fira Mono, Consolas, Menlo, Courier, monospace;
|
font-family: 'Fira code', 'Fira Mono', Consolas, Menlo, Courier, monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
|
|
|
@ -53,7 +53,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
//#region Detect language & fetch translations
|
//#region Detect language & fetch translations
|
||||||
if (!localStorage.hasOwnProperty('locale')) {
|
if (!localStorage.hasOwn('locale')) {
|
||||||
const supportedLangs = LANGS;
|
const supportedLangs = LANGS;
|
||||||
let lang = localStorage.getItem('lang');
|
let lang = localStorage.getItem('lang');
|
||||||
if (lang == null || !supportedLangs.includes(lang)) {
|
if (lang == null || !supportedLangs.includes(lang)) {
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
* {
|
* {
|
||||||
font-family: Fira code, Fira Mono, Consolas, Menlo, Courier, monospace;
|
font-family: 'Fira code', 'Fira Mono', Consolas, Menlo, Courier, monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
|
|
|
@ -39,7 +39,7 @@ message('Start flushing.');
|
||||||
|
|
||||||
console.error(e);
|
console.error(e);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
location = '/';
|
window.location = '/';
|
||||||
}, 10000)
|
}, 10000)
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
parserOptions: {
|
|
||||||
tsconfigRootDir: __dirname,
|
|
||||||
project: ['./tsconfig.json'],
|
|
||||||
},
|
|
||||||
extends: [
|
|
||||||
'../../shared/.eslintrc.js',
|
|
||||||
],
|
|
||||||
rules: {
|
|
||||||
'import/order': ['warn', {
|
|
||||||
'groups': ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
|
|
||||||
'pathGroups': [
|
|
||||||
{
|
|
||||||
'pattern': '@/**',
|
|
||||||
'group': 'external',
|
|
||||||
'position': 'after'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
}],
|
|
||||||
'no-restricted-globals': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
'name': '__dirname',
|
|
||||||
'message': 'Not in ESModule. Use `import.meta.url` instead.'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': '__filename',
|
|
||||||
'message': 'Not in ESModule. Use `import.meta.url` instead.'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -1,23 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "https://json.schemastore.org/swcrc",
|
|
||||||
"jsc": {
|
|
||||||
"parser": {
|
|
||||||
"syntax": "typescript",
|
|
||||||
"dynamicImport": true,
|
|
||||||
"decorators": true
|
|
||||||
},
|
|
||||||
"transform": {
|
|
||||||
"legacyDecorator": true,
|
|
||||||
"decoratorMetadata": true
|
|
||||||
},
|
|
||||||
"experimental": {
|
|
||||||
"keepImportAssertions": true
|
|
||||||
},
|
|
||||||
"baseUrl": "../built",
|
|
||||||
"paths": {
|
|
||||||
"@/*": ["*"]
|
|
||||||
},
|
|
||||||
"target": "es2022"
|
|
||||||
},
|
|
||||||
"minify": false
|
|
||||||
}
|
|
|
@ -2,10 +2,10 @@ import { portToPid } from 'pid-port';
|
||||||
import fkill from 'fkill';
|
import fkill from 'fkill';
|
||||||
import Fastify from 'fastify';
|
import Fastify from 'fastify';
|
||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
import { MainModule } from '@/MainModule.js';
|
import { MainModule } from '../built/MainModule.js';
|
||||||
import { ServerService } from '@/server/ServerService.js';
|
import { ServerService } from '../built/server/ServerService.js';
|
||||||
import { loadConfig } from '@/config.js';
|
import { loadConfig } from '../built/config.js';
|
||||||
import { NestLogger } from '@/NestLogger.js';
|
import { NestLogger } from '../built/NestLogger.js';
|
||||||
|
|
||||||
const config = loadConfig();
|
const config = loadConfig();
|
||||||
const originEnv = JSON.stringify(process.env);
|
const originEnv = JSON.stringify(process.env);
|
||||||
|
@ -56,7 +56,7 @@ async function killTestServer() {
|
||||||
async function startControllerEndpoints(port = config.port + 1000) {
|
async function startControllerEndpoints(port = config.port + 1000) {
|
||||||
const fastify = Fastify();
|
const fastify = Fastify();
|
||||||
|
|
||||||
fastify.post<{ Body: { key?: string, value?: string } }>('/env', async (req, res) => {
|
fastify.post('/env', async (req, res) => {
|
||||||
console.log(req.body);
|
console.log(req.body);
|
||||||
const key = req.body['key'];
|
const key = req.body['key'];
|
||||||
if (!key) {
|
if (!key) {
|
||||||
|
@ -69,7 +69,7 @@ async function startControllerEndpoints(port = config.port + 1000) {
|
||||||
res.code(200).send({ success: true });
|
res.code(200).send({ success: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
fastify.post<{ Body: { key?: string, value?: string } }>('/env-reset', async (req, res) => {
|
fastify.post('/env-reset', async (req, res) => {
|
||||||
process.env = JSON.parse(originEnv);
|
process.env = JSON.parse(originEnv);
|
||||||
res.code(200).send({ success: true });
|
res.code(200).send({ success: true });
|
||||||
});
|
});
|
|
@ -1,52 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"allowJs": true,
|
|
||||||
"noEmitOnError": true,
|
|
||||||
"noImplicitAny": true,
|
|
||||||
"noImplicitReturns": true,
|
|
||||||
"noUnusedParameters": false,
|
|
||||||
"noUnusedLocals": false,
|
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
"declaration": false,
|
|
||||||
"sourceMap": true,
|
|
||||||
"target": "ES2022",
|
|
||||||
"module": "nodenext",
|
|
||||||
"moduleResolution": "nodenext",
|
|
||||||
"allowSyntheticDefaultImports": true,
|
|
||||||
"removeComments": false,
|
|
||||||
"noLib": false,
|
|
||||||
"strict": true,
|
|
||||||
"strictNullChecks": true,
|
|
||||||
"strictPropertyInitialization": false,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"experimentalDecorators": true,
|
|
||||||
"emitDecoratorMetadata": true,
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"rootDir": "../src",
|
|
||||||
"baseUrl": "./",
|
|
||||||
"paths": {
|
|
||||||
"@/*": ["../src/*"]
|
|
||||||
},
|
|
||||||
"outDir": "../built-test",
|
|
||||||
"types": [
|
|
||||||
"node"
|
|
||||||
],
|
|
||||||
"typeRoots": [
|
|
||||||
"../src/@types",
|
|
||||||
"../node_modules/@types",
|
|
||||||
"../node_modules"
|
|
||||||
],
|
|
||||||
"lib": [
|
|
||||||
"esnext"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"compileOnSave": false,
|
|
||||||
"include": [
|
|
||||||
"./**/*.ts",
|
|
||||||
"../src/**/*.ts"
|
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"../src/**/*.test.ts"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -18,7 +18,7 @@ import type {
|
||||||
PublicKeyCredentialCreationOptionsJSON,
|
PublicKeyCredentialCreationOptionsJSON,
|
||||||
PublicKeyCredentialRequestOptionsJSON,
|
PublicKeyCredentialRequestOptionsJSON,
|
||||||
RegistrationResponseJSON,
|
RegistrationResponseJSON,
|
||||||
} from '@simplewebauthn/types';
|
} from '@simplewebauthn/server';
|
||||||
import type * as misskey from 'misskey-js';
|
import type * as misskey from 'misskey-js';
|
||||||
|
|
||||||
describe('2要素認証', () => {
|
describe('2要素認証', () => {
|
||||||
|
|
|
@ -95,15 +95,14 @@ describe('Webリソース', () => {
|
||||||
describe.each([
|
describe.each([
|
||||||
{ path: '/', type: HTML },
|
{ path: '/', type: HTML },
|
||||||
{ path: '/docs/ja-JP/about', type: HTML }, // "指定されたURLに該当するページはありませんでした。"
|
{ path: '/docs/ja-JP/about', type: HTML }, // "指定されたURLに該当するページはありませんでした。"
|
||||||
// fastify-static gives charset=UTF-8 instead of utf-8 and that's okay
|
{ path: '/api-doc', type: 'text/html; charset=utf-8' },
|
||||||
{ path: '/api-doc', type: 'text/html; charset=UTF-8' },
|
|
||||||
{ path: '/api.json', type: JSON_UTF8 },
|
{ path: '/api.json', type: JSON_UTF8 },
|
||||||
{ path: '/api-console', type: HTML },
|
{ path: '/api-console', type: HTML },
|
||||||
{ path: '/_info_card_', type: HTML },
|
{ path: '/_info_card_', type: HTML },
|
||||||
{ path: '/bios', type: HTML },
|
{ path: '/bios', type: HTML },
|
||||||
{ path: '/cli', type: HTML },
|
{ path: '/cli', type: HTML },
|
||||||
{ path: '/flush', type: HTML },
|
{ path: '/flush', type: HTML },
|
||||||
{ path: '/robots.txt', type: 'text/plain; charset=UTF-8' },
|
{ path: '/robots.txt', type: 'text/plain; charset=utf-8' },
|
||||||
{ path: '/favicon.ico', type: 'image/vnd.microsoft.icon' },
|
{ path: '/favicon.ico', type: 'image/vnd.microsoft.icon' },
|
||||||
{ path: '/opensearch.xml', type: 'application/opensearchdescription+xml' },
|
{ path: '/opensearch.xml', type: 'application/opensearchdescription+xml' },
|
||||||
{ path: '/apple-touch-icon.png', type: 'image/png' },
|
{ path: '/apple-touch-icon.png', type: 'image/png' },
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"declaration": false,
|
"declaration": false,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"target": "ES2022",
|
"target": "es2022",
|
||||||
"module": "nodenext",
|
"module": "nodenext",
|
||||||
"moduleResolution": "nodenext",
|
"moduleResolution": "nodenext",
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
|
@ -28,8 +28,9 @@
|
||||||
"@/*": ["../src/*"]
|
"@/*": ["../src/*"]
|
||||||
},
|
},
|
||||||
"typeRoots": [
|
"typeRoots": [
|
||||||
|
"../src/@types",
|
||||||
"../node_modules/@types",
|
"../node_modules/@types",
|
||||||
"../src/@types"
|
"../node_modules"
|
||||||
],
|
],
|
||||||
"lib": [
|
"lib": [
|
||||||
"esnext"
|
"esnext"
|
||||||
|
|
|
@ -9,7 +9,7 @@ import httpSignature from '@peertube/http-signature';
|
||||||
import { genRsaKeyPair } from '@/misc/gen-key-pair.js';
|
import { genRsaKeyPair } from '@/misc/gen-key-pair.js';
|
||||||
import { ApRequestCreator } from '@/core/activitypub/ApRequestService.js';
|
import { ApRequestCreator } from '@/core/activitypub/ApRequestService.js';
|
||||||
|
|
||||||
export const buildParsedSignature = (signingString: string, signature: string, algorithm: string) => {
|
const buildParsedSignature = (signingString: string, signature: string, algorithm: string) => {
|
||||||
return {
|
return {
|
||||||
scheme: 'Signature',
|
scheme: 'Signature',
|
||||||
params: {
|
params: {
|
||||||
|
|
|
@ -196,6 +196,7 @@ export const page = async (user: UserToken, page: Partial<misskey.entities.Page>
|
||||||
eyeCatchingImageId: null,
|
eyeCatchingImageId: null,
|
||||||
font: 'sans-serif' as FIXME,
|
font: 'sans-serif' as FIXME,
|
||||||
hideTitleWhenPinned: false,
|
hideTitleWhenPinned: false,
|
||||||
|
visibility: 'public',
|
||||||
name: '1678594845072',
|
name: '1678594845072',
|
||||||
script: '',
|
script: '',
|
||||||
summary: null,
|
summary: null,
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"declaration": false,
|
"declaration": false,
|
||||||
"sourceMap": false,
|
"sourceMap": false,
|
||||||
"target": "ES2022",
|
"target": "es2022",
|
||||||
"module": "nodenext",
|
"module": "nodenext",
|
||||||
"moduleResolution": "nodenext",
|
"moduleResolution": "nodenext",
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
|
|
|
@ -23,25 +23,25 @@
|
||||||
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
||||||
"@misskey-dev/browser-image-resizer": "2024.1.0",
|
"@misskey-dev/browser-image-resizer": "2024.1.0",
|
||||||
"@rollup/plugin-json": "6.1.0",
|
"@rollup/plugin-json": "6.1.0",
|
||||||
"@rollup/plugin-replace": "6.0.1",
|
"@rollup/plugin-replace": "6.0.2",
|
||||||
"@rollup/plugin-typescript": "12.1.1",
|
"@rollup/plugin-typescript": "12.1.2",
|
||||||
"@rollup/pluginutils": "5.1.3",
|
"@rollup/pluginutils": "5.1.4",
|
||||||
"@syuilo/aiscript": "0.19.0",
|
"@syuilo/aiscript": "0.19.0",
|
||||||
"@tabler/icons-webfont": "3.21.0",
|
"@tabler/icons-webfont": "3.26.0",
|
||||||
"@twemoji/parser": "15.1.1",
|
"@twemoji/parser": "15.1.1",
|
||||||
"@vitejs/plugin-vue": "5.1.4",
|
"@vitejs/plugin-vue": "5.2.1",
|
||||||
"@vue/compiler-sfc": "3.5.12",
|
"@vue/compiler-sfc": "3.5.13",
|
||||||
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.9",
|
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.9",
|
||||||
"astring": "1.9.0",
|
"astring": "1.9.0",
|
||||||
"broadcast-channel": "7.0.0",
|
"broadcast-channel": "7.0.0",
|
||||||
"buraha": "0.0.1",
|
"buraha": "0.0.1",
|
||||||
"canvas-confetti": "1.9.3",
|
"canvas-confetti": "1.9.3",
|
||||||
"chart.js": "4.4.6",
|
"chart.js": "4.4.7",
|
||||||
"chartjs-adapter-date-fns": "3.0.0",
|
"chartjs-adapter-date-fns": "3.0.0",
|
||||||
"chartjs-chart-matrix": "2.0.1",
|
"chartjs-chart-matrix": "2.0.1",
|
||||||
"chartjs-plugin-gradient": "0.6.1",
|
"chartjs-plugin-gradient": "0.6.1",
|
||||||
"chartjs-plugin-zoom": "2.0.1",
|
"chartjs-plugin-zoom": "2.2.0",
|
||||||
"chromatic": "11.18.0",
|
"chromatic": "11.20.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",
|
||||||
|
@ -58,84 +58,86 @@
|
||||||
"misskey-js": "workspace:*",
|
"misskey-js": "workspace:*",
|
||||||
"misskey-reversi": "workspace:*",
|
"misskey-reversi": "workspace:*",
|
||||||
"photoswipe": "5.4.4",
|
"photoswipe": "5.4.4",
|
||||||
"punycode": "2.3.1",
|
"punycode.js": "2.3.1",
|
||||||
"rollup": "4.24.4",
|
"rollup": "4.28.1",
|
||||||
"sanitize-html": "2.13.1",
|
"sanitize-html": "2.13.1",
|
||||||
"sass": "1.80.6",
|
"sass": "1.83.0",
|
||||||
"shiki": "1.22.2",
|
"shiki": "1.24.2",
|
||||||
"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.170.0",
|
"three": "0.171.0",
|
||||||
"throttle-debounce": "5.0.2",
|
"throttle-debounce": "5.0.2",
|
||||||
"tinycolor2": "1.6.0",
|
"tinycolor2": "1.6.0",
|
||||||
"tsc-alias": "1.8.10",
|
"tsc-alias": "1.8.10",
|
||||||
"tsconfig-paths": "4.2.0",
|
"tsconfig-paths": "4.2.0",
|
||||||
"typescript": "5.6.3",
|
"typescript": "5.7.2",
|
||||||
"uuid": "11.0.2",
|
"uuid": "11.0.3",
|
||||||
"v-code-diff": "1.13.1",
|
"v-code-diff": "1.13.1",
|
||||||
"vite": "5.4.10",
|
"vite": "6.0.3",
|
||||||
"vue": "3.5.12",
|
"vue": "3.5.13",
|
||||||
"vue-gtag": "2.0.1",
|
"vue-gtag": "2.0.1",
|
||||||
"vuedraggable": "next"
|
"vuedraggable": "next",
|
||||||
|
"webgl-audiovisualizer": "github:tar-bin/webgl-audiovisualizer"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@misskey-dev/eslint-plugin": "1.0.0",
|
"@misskey-dev/eslint-plugin": "1.0.0",
|
||||||
"@misskey-dev/summaly": "MisskeyIO/summaly#5.1.1",
|
"@misskey-dev/summaly": "MisskeyIO/summaly#5.1.2",
|
||||||
"@storybook/addon-actions": "8.4.2",
|
"@storybook/addon-actions": "8.4.7",
|
||||||
"@storybook/addon-essentials": "8.4.2",
|
"@storybook/addon-essentials": "8.4.7",
|
||||||
"@storybook/addon-interactions": "8.4.2",
|
"@storybook/addon-interactions": "8.4.7",
|
||||||
"@storybook/addon-links": "8.4.2",
|
"@storybook/addon-links": "8.4.7",
|
||||||
"@storybook/addon-mdx-gfm": "8.4.2",
|
"@storybook/addon-mdx-gfm": "8.4.7",
|
||||||
"@storybook/addon-storysource": "8.4.2",
|
"@storybook/addon-storysource": "8.4.7",
|
||||||
"@storybook/blocks": "8.4.2",
|
"@storybook/blocks": "8.4.7",
|
||||||
"@storybook/components": "8.4.2",
|
"@storybook/components": "8.4.7",
|
||||||
"@storybook/core-events": "8.4.2",
|
"@storybook/core-events": "8.4.7",
|
||||||
"@storybook/manager-api": "8.4.2",
|
"@storybook/manager-api": "8.4.7",
|
||||||
"@storybook/preview-api": "8.4.2",
|
"@storybook/preview-api": "8.4.7",
|
||||||
"@storybook/react": "8.4.2",
|
"@storybook/react": "8.4.7",
|
||||||
"@storybook/react-vite": "8.4.2",
|
"@storybook/react-vite": "8.4.7",
|
||||||
"@storybook/test": "8.4.2",
|
"@storybook/test": "8.4.7",
|
||||||
"@storybook/theming": "8.4.2",
|
"@storybook/theming": "8.4.7",
|
||||||
"@storybook/types": "8.4.2",
|
"@storybook/types": "8.4.7",
|
||||||
"@storybook/vue3": "8.4.2",
|
"@storybook/vue3": "8.4.7",
|
||||||
"@storybook/vue3-vite": "8.4.2",
|
"@storybook/vue3-vite": "8.4.7",
|
||||||
"@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.7",
|
"@types/matter-js": "0.19.8",
|
||||||
"@types/micromatch": "4.0.9",
|
"@types/micromatch": "4.0.9",
|
||||||
"@types/node": "22.9.0",
|
"@types/node": "22.10.2",
|
||||||
"@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.171.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.13",
|
||||||
"@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.4",
|
"@vitest/coverage-v8": "2.1.8",
|
||||||
"@vue/runtime-core": "3.5.12",
|
"@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.15.2",
|
"cypress": "13.17.0",
|
||||||
"eslint": "8.57.1",
|
"eslint": "8.57.1",
|
||||||
"eslint-plugin-import": "2.31.0",
|
"eslint-plugin-import": "2.31.0",
|
||||||
"eslint-plugin-vue": "9.30.0",
|
"eslint-plugin-vue": "9.32.0",
|
||||||
"fast-glob": "3.3.2",
|
"fast-glob": "3.3.2",
|
||||||
"happy-dom": "15.11.0",
|
"happy-dom": "15.11.7",
|
||||||
"intersection-observer": "0.12.2",
|
"intersection-observer": "0.12.2",
|
||||||
"micromatch": "4.0.8",
|
"micromatch": "4.0.8",
|
||||||
"msw": "2.6.2",
|
"msw": "2.7.0",
|
||||||
"msw-storybook-addon": "2.0.4",
|
"msw-storybook-addon": "2.0.4",
|
||||||
"nodemon": "3.1.7",
|
"nodemon": "3.1.9",
|
||||||
"prettier": "3.3.3",
|
"prettier": "3.4.2",
|
||||||
"react": "18.3.1",
|
"react": "19.0.0",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "19.0.0",
|
||||||
"start-server-and-test": "2.0.8",
|
"start-server-and-test": "2.0.9",
|
||||||
"storybook": "8.4.2",
|
"storybook": "8.4.7",
|
||||||
"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.4",
|
"vitest": "2.1.8",
|
||||||
"vitest-fetch-mock": "0.3.0",
|
"vitest-fetch-mock": "0.3.0",
|
||||||
"vue-component-type-helpers": "2.1.10",
|
"vue-component-type-helpers": "2.1.10",
|
||||||
"vue-eslint-parser": "9.4.3",
|
"vue-eslint-parser": "9.4.3",
|
||||||
|
|
|
@ -27,7 +27,11 @@ const props = defineProps<{
|
||||||
movedFrom?: string; // user id
|
movedFrom?: string; // user id
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
misskeyApi('users/show', { userId: props.movedTo ?? props.movedFrom }).then(u => user.value = u);
|
if (props.movedTo || props.movedFrom) {
|
||||||
|
misskeyApi('users/show', {
|
||||||
|
userId: props.movedTo ?? props.movedFrom
|
||||||
|
}).then(u => user.value = u);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
219
packages/frontend/src/components/MkAudioVisualizer.vue
Normal file
219
packages/frontend/src/components/MkAudioVisualizer.vue
Normal file
|
@ -0,0 +1,219 @@
|
||||||
|
<template>
|
||||||
|
<div ref="container" :class="$style.root">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { onMounted, onUnmounted, shallowRef, nextTick, ref } from 'vue';
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
|
import * as THREE from 'three';
|
||||||
|
import vertexShader from '../../node_modules/webgl-audiovisualizer/audiovisial-vertex.shader?raw';
|
||||||
|
import fragmentShader from '../../node_modules/webgl-audiovisualizer/audiovisial-fragment.shader?raw';
|
||||||
|
import circleMask from '../../node_modules/webgl-audiovisualizer/circlemask.png';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
user: Misskey.entities.UserLite;
|
||||||
|
audioEl: HTMLAudioElement | undefined;
|
||||||
|
analyser: AnalyserNode | undefined;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const container = shallowRef<HTMLDivElement>();
|
||||||
|
|
||||||
|
const isPlaying = ref(false);
|
||||||
|
const fftSize = 2048;
|
||||||
|
|
||||||
|
let prevTime = 0;
|
||||||
|
let angle1 = 0;
|
||||||
|
let angle2 = 0;
|
||||||
|
|
||||||
|
let scene, camera, renderer, width, height, uniforms, texture, maskTexture, dataArray1, dataArray2, dataArrayOrigin, bufferLength: number;
|
||||||
|
|
||||||
|
const init = () => {
|
||||||
|
const parent = container.value ?? { offsetWidth: 0 };
|
||||||
|
width = parent.offsetWidth;
|
||||||
|
height = Math.floor(width * 9 / 16);
|
||||||
|
|
||||||
|
scene = new THREE.Scene();
|
||||||
|
camera = new THREE.OrthographicCamera();
|
||||||
|
camera.left = width / -2;
|
||||||
|
camera.right = width / 2;
|
||||||
|
camera.top = height / 2;
|
||||||
|
camera.bottom = height / -2;
|
||||||
|
camera.updateProjectionMatrix();
|
||||||
|
|
||||||
|
renderer = new THREE.WebGLRenderer({ antialias: true });
|
||||||
|
renderer.setSize(width, height);
|
||||||
|
|
||||||
|
if (container.value) {
|
||||||
|
container.value.appendChild(renderer.domElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
const loader = new THREE.TextureLoader();
|
||||||
|
texture = loader.load(props.user.avatarUrl ?? '');
|
||||||
|
maskTexture = loader.load(circleMask);
|
||||||
|
uniforms = {
|
||||||
|
enableAudio: {
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
uTex: { value: texture },
|
||||||
|
uMask: { value: maskTexture },
|
||||||
|
time: {
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
resolution: {
|
||||||
|
value: new THREE.Vector2(width, height),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const material = new THREE.ShaderMaterial({
|
||||||
|
uniforms: uniforms,
|
||||||
|
vertexShader: vertexShader,
|
||||||
|
fragmentShader: fragmentShader,
|
||||||
|
});
|
||||||
|
|
||||||
|
const geometry = new THREE.PlaneGeometry(2, 2);
|
||||||
|
|
||||||
|
const mesh = new THREE.Mesh(geometry, material);
|
||||||
|
scene.add(mesh);
|
||||||
|
|
||||||
|
renderer.setAnimationLoop(animate);
|
||||||
|
};
|
||||||
|
|
||||||
|
const play = () => {
|
||||||
|
if (props.analyser) {
|
||||||
|
bufferLength = props.analyser.frequencyBinCount;
|
||||||
|
dataArrayOrigin = new Uint8Array(bufferLength);
|
||||||
|
dataArray1 = new Uint8Array(bufferLength);
|
||||||
|
dataArray2 = new Uint8Array(bufferLength);
|
||||||
|
uniforms = {
|
||||||
|
enableAudio: {
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
tAudioData1: { value: new THREE.DataTexture(dataArray1, fftSize / 2, 1, THREE.RedFormat) },
|
||||||
|
tAudioData2: { value: new THREE.DataTexture(dataArray2, fftSize / 2, 1, THREE.RedFormat) },
|
||||||
|
uTex: { value: texture },
|
||||||
|
uMask: { value: maskTexture },
|
||||||
|
time: {
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
resolution: {
|
||||||
|
value: new THREE.Vector2(width, height),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const material = new THREE.ShaderMaterial({
|
||||||
|
uniforms: uniforms,
|
||||||
|
vertexShader: vertexShader,
|
||||||
|
fragmentShader: fragmentShader,
|
||||||
|
});
|
||||||
|
|
||||||
|
const geometry = new THREE.PlaneGeometry(2, 2);
|
||||||
|
|
||||||
|
const mesh = new THREE.Mesh(geometry, material);
|
||||||
|
scene.add(mesh);
|
||||||
|
|
||||||
|
renderer.setAnimationLoop(animate);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const pauseAnimation = () => {
|
||||||
|
isPlaying.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const resumeAnimation = () => {
|
||||||
|
isPlaying.value = true;
|
||||||
|
renderer.setAnimationLoop(play);
|
||||||
|
};
|
||||||
|
|
||||||
|
const animate = (time) => {
|
||||||
|
if (angle1 >= bufferLength) {
|
||||||
|
angle1 = 0;
|
||||||
|
}
|
||||||
|
if (angle2 >= bufferLength) {
|
||||||
|
angle2 = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.analyser) {
|
||||||
|
if ((time - prevTime) > 10) {
|
||||||
|
for (let i = 0; i < bufferLength; i++) {
|
||||||
|
let n1 = (i + angle1) % bufferLength;
|
||||||
|
let n2 = (i + angle2) % bufferLength;
|
||||||
|
if (dataArrayOrigin[n1] > dataArray1[i]) {
|
||||||
|
dataArray1[i] = dataArray1[i] < 255 ? (dataArrayOrigin[i] + dataArray1[i]) / 2 : 255;
|
||||||
|
}
|
||||||
|
if (dataArrayOrigin[n2] > dataArray2[i]) {
|
||||||
|
dataArray2[i] = dataArray2[i] < 255 ? (dataArrayOrigin[i] + dataArray2[i]) / 2 : 255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((time - prevTime) > 20) {
|
||||||
|
for (let i = 0; i < bufferLength; i++) {
|
||||||
|
let n1 = (i + angle1) % bufferLength;
|
||||||
|
let n2 = (i + angle2) % bufferLength;
|
||||||
|
if (dataArrayOrigin[n1] < dataArray1[i]) {
|
||||||
|
dataArray1[i] = dataArray1[i] > 0 ? dataArray1[i] - 1 : 0;
|
||||||
|
}
|
||||||
|
if (dataArrayOrigin[n2] < dataArray2[i]) {
|
||||||
|
dataArray2[i] = dataArray2[i] > 0 ? dataArray2[i] - 1 : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uniforms.tAudioData1.value.needsUpdate = true;
|
||||||
|
uniforms.tAudioData2.value.needsUpdate = true;
|
||||||
|
prevTime = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPlaying.value) {
|
||||||
|
props.analyser.getByteTimeDomainData(dataArrayOrigin);
|
||||||
|
} else {
|
||||||
|
for (let i = 0; i < bufferLength; i++) {
|
||||||
|
dataArrayOrigin[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
angle1 += parseInt(String(bufferLength / 360 * 2)); //こうしないと型エラー出てうざい
|
||||||
|
angle2 += parseInt(String(bufferLength / 360 * 3));
|
||||||
|
}
|
||||||
|
uniforms.time.value = time;
|
||||||
|
renderer.render(scene, camera);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onResize = () => {
|
||||||
|
const parent = container.value ?? { offsetWidth: 0 };
|
||||||
|
width = parent.offsetWidth;
|
||||||
|
height = Math.floor(width * 9 / 16);
|
||||||
|
camera.left = width / -2;
|
||||||
|
camera.right = width / 2;
|
||||||
|
camera.top = height / 2;
|
||||||
|
camera.bottom = height / -2;
|
||||||
|
camera.updateProjectionMatrix();
|
||||||
|
renderer.setSize(width, height);
|
||||||
|
uniforms.resolution.value.set(width, height);
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
nextTick().then(() => {
|
||||||
|
init();
|
||||||
|
window.addEventListener('resize', onResize);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (renderer) {
|
||||||
|
renderer.dispose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
pauseAnimation,
|
||||||
|
resumeAnimation,
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
.root {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<MkA :to="`/gallery/${post.id}`" class="ttasepnz _panel" tabindex="-1" @pointerenter="enterHover" @pointerleave="leaveHover">
|
<MkA :to="`/gallery/${post.id}`" class="ttasepnz _panel" tabindex="-1">
|
||||||
<div class="thumbnail">
|
<div class="thumbnail">
|
||||||
<Transition>
|
<Transition>
|
||||||
<ImgWithBlurhash
|
<ImgWithBlurhash
|
||||||
|
@ -41,17 +41,8 @@ const props = defineProps<{
|
||||||
post: Misskey.entities.GalleryPost;
|
post: Misskey.entities.GalleryPost;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const hover = ref(false);
|
|
||||||
const safe = computed(() => defaultStore.state.nsfw === 'ignore' || defaultStore.state.nsfw === 'respect' && !props.post.isSensitive);
|
const safe = computed(() => defaultStore.state.nsfw === 'ignore' || defaultStore.state.nsfw === 'respect' && !props.post.isSensitive);
|
||||||
const show = computed(() => safe.value || hover.value);
|
const show = computed(() => safe.value);
|
||||||
|
|
||||||
function enterHover(): void {
|
|
||||||
hover.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function leaveHover(): void {
|
|
||||||
hover.value = false;
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
|
@ -35,40 +35,43 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</audio>
|
</audio>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else :class="$style.audioControls">
|
<div v-else>
|
||||||
<audio
|
<MkAudioVisualizer v-if="user" ref="audioVisualizer" :audioEl="audioEl" :analyser="analyserNode" :user="user" :profileImage="user.avatarUrl"/>
|
||||||
ref="audioEl"
|
<div :class="$style.audioControls">
|
||||||
preload="metadata"
|
<audio
|
||||||
>
|
ref="audioEl"
|
||||||
<source :src="audio.url">
|
preload="metadata"
|
||||||
</audio>
|
>
|
||||||
<div :class="[$style.controlsChild, $style.controlsLeft]">
|
<source :src="audio.url">
|
||||||
<button class="_button" :class="$style.controlButton" @click.prevent.stop="togglePlayPause">
|
</audio>
|
||||||
<i v-if="isPlaying" class="ti ti-player-pause-filled"></i>
|
<div :class="[$style.controlsChild, $style.controlsLeft]">
|
||||||
<i v-else class="ti ti-player-play-filled"></i>
|
<button class="_button" :class="$style.controlButton" @click.prevent.stop="togglePlayPause">
|
||||||
</button>
|
<i v-if="isPlaying" class="ti ti-player-pause-filled"></i>
|
||||||
</div>
|
<i v-else class="ti ti-player-play-filled"></i>
|
||||||
<div :class="[$style.controlsChild, $style.controlsRight]">
|
</button>
|
||||||
<button class="_button" :class="$style.controlButton" @click.prevent.stop="showMenu">
|
</div>
|
||||||
<i class="ti ti-settings"></i>
|
<div :class="[$style.controlsChild, $style.controlsRight]">
|
||||||
</button>
|
<button class="_button" :class="$style.controlButton" @click.prevent.stop="showMenu">
|
||||||
</div>
|
<i class="ti ti-settings"></i>
|
||||||
<div :class="[$style.controlsChild, $style.controlsTime]">{{ hms(elapsedTimeMs) }}</div>
|
</button>
|
||||||
<div :class="[$style.controlsChild, $style.controlsVolume]">
|
</div>
|
||||||
<button class="_button" :class="$style.controlButton" @click.prevent.stop="toggleMute">
|
<div :class="[$style.controlsChild, $style.controlsTime]">{{ hms(elapsedTimeMs) }}</div>
|
||||||
<i v-if="volume === 0" class="ti ti-volume-3"></i>
|
<div :class="[$style.controlsChild, $style.controlsVolume]">
|
||||||
<i v-else class="ti ti-volume"></i>
|
<button class="_button" :class="$style.controlButton" @click.prevent.stop="toggleMute">
|
||||||
</button>
|
<i v-if="volume === 0" class="ti ti-volume-3"></i>
|
||||||
|
<i v-else class="ti ti-volume"></i>
|
||||||
|
</button>
|
||||||
|
<MkMediaRange
|
||||||
|
v-model="volume"
|
||||||
|
:class="$style.volumeSeekbar"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<MkMediaRange
|
<MkMediaRange
|
||||||
v-model="volume"
|
v-model="rangePercent"
|
||||||
:class="$style.volumeSeekbar"
|
:class="$style.seekbarRoot"
|
||||||
|
:buffer="bufferedDataRatio"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<MkMediaRange
|
|
||||||
v-model="rangePercent"
|
|
||||||
:class="$style.seekbarRoot"
|
|
||||||
:buffer="bufferedDataRatio"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -82,12 +85,14 @@ import { i18n } from '@/i18n.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import bytes from '@/filters/bytes.js';
|
import bytes from '@/filters/bytes.js';
|
||||||
import { hms } from '@/filters/hms.js';
|
import { hms } from '@/filters/hms.js';
|
||||||
|
import MkAudioVisualizer from '@/components/MkAudioVisualizer.vue';
|
||||||
import MkMediaRange from '@/components/MkMediaRange.vue';
|
import MkMediaRange from '@/components/MkMediaRange.vue';
|
||||||
import { pleaseLogin } from '@/scripts/please-login.js';
|
import { pleaseLogin } from '@/scripts/please-login.js';
|
||||||
import { $i, iAmModerator } from '@/account.js';
|
import { $i, iAmModerator } from '@/account.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
audio: Misskey.entities.DriveFile;
|
audio: Misskey.entities.DriveFile;
|
||||||
|
user?: Misskey.entities.UserLite;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const keymap = {
|
const keymap = {
|
||||||
|
@ -126,6 +131,7 @@ function hasFocus() {
|
||||||
|
|
||||||
const playerEl = shallowRef<HTMLDivElement>();
|
const playerEl = shallowRef<HTMLDivElement>();
|
||||||
const audioEl = shallowRef<HTMLAudioElement>();
|
const audioEl = shallowRef<HTMLAudioElement>();
|
||||||
|
const audioVisualizer = ref<InstanceType<typeof MkAudioVisualizer>>();
|
||||||
|
|
||||||
// eslint-disable-next-line vue/no-setup-props-destructure
|
// eslint-disable-next-line vue/no-setup-props-destructure
|
||||||
const hide = ref((defaultStore.state.nsfw === 'force' || defaultStore.state.dataSaver.media) ? true : (props.audio.isSensitive && defaultStore.state.nsfw !== 'ignore'));
|
const hide = ref((defaultStore.state.nsfw === 'force' || defaultStore.state.dataSaver.media) ? true : (props.audio.isSensitive && defaultStore.state.nsfw !== 'ignore'));
|
||||||
|
@ -240,6 +246,11 @@ const isPlaying = ref(false);
|
||||||
const isActuallyPlaying = ref(false);
|
const isActuallyPlaying = ref(false);
|
||||||
const elapsedTimeMs = ref(0);
|
const elapsedTimeMs = ref(0);
|
||||||
const durationMs = ref(0);
|
const durationMs = ref(0);
|
||||||
|
const audioContext = ref<AudioContext | null>(null);
|
||||||
|
const sourceNode = ref<MediaElementAudioSourceNode | null>(null);
|
||||||
|
const gainNode = ref<GainNode | null>(null);
|
||||||
|
const analyserGainNode = ref<GainNode | null>(null);
|
||||||
|
const analyserNode = ref<AnalyserNode | null>(null);
|
||||||
const rangePercent = computed({
|
const rangePercent = computed({
|
||||||
get: () => {
|
get: () => {
|
||||||
return (elapsedTimeMs.value / durationMs.value) || 0;
|
return (elapsedTimeMs.value / durationMs.value) || 0;
|
||||||
|
@ -262,11 +273,33 @@ const bufferedDataRatio = computed(() => {
|
||||||
function togglePlayPause() {
|
function togglePlayPause() {
|
||||||
if (!isReady.value || !audioEl.value) return;
|
if (!isReady.value || !audioEl.value) return;
|
||||||
|
|
||||||
|
if (!sourceNode.value) {
|
||||||
|
audioContext.value = new (window.AudioContext || window.webkitAudioContext)();
|
||||||
|
sourceNode.value = audioContext.value.createMediaElementSource(audioEl.value);
|
||||||
|
|
||||||
|
analyserGainNode.value = audioContext.value.createGain();
|
||||||
|
gainNode.value = audioContext.value.createGain();
|
||||||
|
analyserNode.value = audioContext.value.createAnalyser();
|
||||||
|
|
||||||
|
sourceNode.value.connect(analyserGainNode.value);
|
||||||
|
analyserGainNode.value.connect(analyserNode.value);
|
||||||
|
analyserNode.value.connect(gainNode.value);
|
||||||
|
gainNode.value.connect(audioContext.value.destination);
|
||||||
|
|
||||||
|
analyserNode.value.fftSize = 2048;
|
||||||
|
|
||||||
|
analyserGainNode.value.gain.setValueAtTime(0.8, audioContext.value.currentTime);
|
||||||
|
|
||||||
|
gainNode.value.gain.setValueAtTime(volume.value, audioContext.value.currentTime);
|
||||||
|
}
|
||||||
|
|
||||||
if (isPlaying.value) {
|
if (isPlaying.value) {
|
||||||
audioEl.value.pause();
|
audioEl.value.pause();
|
||||||
|
audioVisualizer.value?.pauseAnimation();
|
||||||
isPlaying.value = false;
|
isPlaying.value = false;
|
||||||
} else {
|
} else {
|
||||||
audioEl.value.play();
|
audioEl.value.play();
|
||||||
|
audioVisualizer.value?.resumeAnimation();
|
||||||
isPlaying.value = true;
|
isPlaying.value = true;
|
||||||
oncePlayed.value = true;
|
oncePlayed.value = true;
|
||||||
}
|
}
|
||||||
|
@ -324,6 +357,7 @@ function init() {
|
||||||
oncePlayed.value = false;
|
oncePlayed.value = false;
|
||||||
isActuallyPlaying.value = false;
|
isActuallyPlaying.value = false;
|
||||||
isPlaying.value = false;
|
isPlaying.value = false;
|
||||||
|
audioVisualizer.value?.pauseAnimation();
|
||||||
});
|
});
|
||||||
|
|
||||||
durationMs.value = audioEl.value.duration * 1000;
|
durationMs.value = audioEl.value.duration * 1000;
|
||||||
|
@ -332,8 +366,7 @@ function init() {
|
||||||
durationMs.value = audioEl.value.duration * 1000;
|
durationMs.value = audioEl.value.duration * 1000;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
gainNode.value?.gain.setValueAtTime(volume.value, audioContext.value?.currentTime);
|
||||||
audioEl.value.volume = volume.value;
|
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
immediate: true,
|
immediate: true,
|
||||||
|
@ -341,7 +374,7 @@ function init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(volume, (to) => {
|
watch(volume, (to) => {
|
||||||
if (audioEl.value) audioEl.value.volume = to;
|
if (audioEl.value) gainNode.value?.gain.setValueAtTime(to, audioContext.value?.currentTime);
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(speed, (to) => {
|
watch(speed, (to) => {
|
||||||
|
|
|
@ -5,12 +5,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="$style.root">
|
<div :class="$style.root">
|
||||||
<MkMediaAudio v-if="media.type.startsWith('audio') && media.type !== 'audio/midi'" :audio="media"/>
|
<div v-if="media.isSensitive && hide" :class="$style.sensitive" @click="showHiddenContent" @dblclick="showHiddenContentDouble">
|
||||||
<div v-else-if="media.isSensitive && hide" :class="$style.sensitive" @click="showHiddenContent" @dblclick="showHiddenContentDouble">
|
|
||||||
<span style="font-size: 1.6em;"><i class="ti ti-alert-triangle"></i></span>
|
<span style="font-size: 1.6em;"><i class="ti ti-alert-triangle"></i></span>
|
||||||
<b>{{ i18n.ts.sensitive }}</b>
|
<b>{{ i18n.ts.sensitive }}</b>
|
||||||
<span>{{ i18n.ts.clickToShow }}</span>
|
<span>{{ i18n.ts.clickToShow }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<MkMediaAudio v-else-if="media.type.startsWith('audio') && media.type !== 'audio/midi'" :audio="media" :user="user"/>
|
||||||
<a
|
<a
|
||||||
v-else :class="$style.download"
|
v-else :class="$style.download"
|
||||||
:href="media.url"
|
:href="media.url"
|
||||||
|
@ -34,6 +34,7 @@ import { defaultStore } from '@/store.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
media: Misskey.entities.DriveFile;
|
media: Misskey.entities.DriveFile;
|
||||||
|
user?: Misskey.entities.UserLite;
|
||||||
}>(), {
|
}>(), {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<XBanner v-for="media in mediaList.filter(media => !previewable(media))" :key="media.id" :media="media"/>
|
<XBanner v-for="media in mediaList.filter(media => !previewable(media))" :key="media.id" :media="media" :user="user"/>
|
||||||
<div v-if="mediaList.filter(media => previewable(media)).length > 0" :class="$style.container">
|
<div v-if="mediaList.filter(media => previewable(media)).length > 0" :class="$style.container">
|
||||||
<div
|
<div
|
||||||
ref="gallery"
|
ref="gallery"
|
||||||
|
@ -42,6 +42,7 @@ import { defaultStore } from '@/store.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
mediaList: Misskey.entities.DriveFile[];
|
mediaList: Misskey.entities.DriveFile[];
|
||||||
|
user?: Misskey.entities.UserLite;
|
||||||
raw?: boolean;
|
raw?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { toUnicode } from 'punycode';
|
import { toUnicode } from 'punycode.js';
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import tinycolor from 'tinycolor2';
|
import tinycolor from 'tinycolor2';
|
||||||
import { host as localHost } from '@/config.js';
|
import { host as localHost } from '@/config.js';
|
||||||
|
|
|
@ -79,7 +79,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="appearNote.files && appearNote.files.length > 0">
|
<div v-if="appearNote.files && appearNote.files.length > 0">
|
||||||
<MkMediaList :mediaList="appearNote.files"/>
|
<MkMediaList :mediaList="appearNote.files" :user="appearNote.user"/>
|
||||||
</div>
|
</div>
|
||||||
<MkPoll v-if="appearNote.poll" :noteId="appearNote.id" :poll="appearNote.poll" :class="$style.poll"/>
|
<MkPoll v-if="appearNote.poll" :noteId="appearNote.id" :poll="appearNote.poll" :class="$style.poll"/>
|
||||||
<div v-if="isEnabledUrlPreview">
|
<div v-if="isEnabledUrlPreview">
|
||||||
|
@ -301,7 +301,7 @@ const keymap = {
|
||||||
'down|j|tab': focusAfter,
|
'down|j|tab': focusAfter,
|
||||||
'esc': blur,
|
'esc': blur,
|
||||||
'm|o': () => showMenu(true),
|
'm|o': () => showMenu(true),
|
||||||
's': () => showContent.value !== showContent.value,
|
's': () => { showContent.value = !showContent.value; focus(); },
|
||||||
};
|
};
|
||||||
|
|
||||||
provide('react', (reaction: string) => {
|
provide('react', (reaction: string) => {
|
||||||
|
|
|
@ -92,7 +92,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="appearNote.files && appearNote.files.length > 0">
|
<div v-if="appearNote.files && appearNote.files.length > 0">
|
||||||
<MkMediaList :mediaList="appearNote.files"/>
|
<MkMediaList :mediaList="appearNote.files" :user="appearNote.user"/>
|
||||||
</div>
|
</div>
|
||||||
<MkPoll v-if="appearNote.poll" ref="pollViewer" :noteId="appearNote.id" :poll="appearNote.poll" :class="$style.poll"/>
|
<MkPoll v-if="appearNote.poll" ref="pollViewer" :noteId="appearNote.id" :poll="appearNote.poll" :class="$style.poll"/>
|
||||||
<div v-if="isEnabledUrlPreview">
|
<div v-if="isEnabledUrlPreview">
|
||||||
|
@ -309,7 +309,7 @@ const keymap = {
|
||||||
'q': () => renote(true),
|
'q': () => renote(true),
|
||||||
'esc': blur,
|
'esc': blur,
|
||||||
'm|o': () => showMenu(true),
|
'm|o': () => showMenu(true),
|
||||||
's': () => showContent.value !== showContent.value,
|
's': () => { showContent.value = !showContent.value; focus(); },
|
||||||
};
|
};
|
||||||
|
|
||||||
provide('react', (reaction: string) => {
|
provide('react', (reaction: string) => {
|
||||||
|
|
|
@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
<article>
|
<article>
|
||||||
<header>
|
<header>
|
||||||
<h1 :title="page.title">{{ page.title }}</h1>
|
<h1 :title="page.title">{{ page.title || page.name }} <i v-if="page.visibility === 'private'" class="ti ti-lock"></i></h1>
|
||||||
</header>
|
</header>
|
||||||
<p v-if="page.summary" :title="page.summary">{{ page.summary.length > 85 ? page.summary.slice(0, 85) + '…' : page.summary }}</p>
|
<p v-if="page.summary" :title="page.summary">{{ page.summary.length > 85 ? page.summary.slice(0, 85) + '…' : page.summary }}</p>
|
||||||
<footer>
|
<footer>
|
||||||
|
|
|
@ -116,7 +116,7 @@ function get(): PollEditorModelValue {
|
||||||
};
|
};
|
||||||
|
|
||||||
const calcAfter = () => {
|
const calcAfter = () => {
|
||||||
let base = parseInt(after.value.toString());
|
let base = Number.parseInt(after.value.toString());
|
||||||
switch (unit.value) {
|
switch (unit.value) {
|
||||||
// @ts-expect-error fallthrough
|
// @ts-expect-error fallthrough
|
||||||
case 'day': base *= 24;
|
case 'day': base *= 24;
|
||||||
|
|
|
@ -105,7 +105,7 @@ import { inject, watch, nextTick, onMounted, onUnmounted, defineAsyncComponent,
|
||||||
import * as mfm from 'mfm-js';
|
import * as mfm from 'mfm-js';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import insertTextAtCursor from 'insert-text-at-cursor';
|
import insertTextAtCursor from 'insert-text-at-cursor';
|
||||||
import { toASCII } from 'punycode/';
|
import { toASCII } from 'punycode.js';
|
||||||
import MkNoteSimple from '@/components/MkNoteSimple.vue';
|
import MkNoteSimple from '@/components/MkNoteSimple.vue';
|
||||||
import MkNotePreview from '@/components/MkNotePreview.vue';
|
import MkNotePreview from '@/components/MkNotePreview.vue';
|
||||||
import XPostFormAttaches from '@/components/MkPostFormAttaches.vue';
|
import XPostFormAttaches from '@/components/MkPostFormAttaches.vue';
|
||||||
|
|
|
@ -54,7 +54,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, defineAsyncComponent, ref } from 'vue';
|
import { computed, defineAsyncComponent, ref } from 'vue';
|
||||||
import { toUnicode } from 'punycode/';
|
import { toUnicode } from 'punycode.js';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { supported as webAuthnSupported, get as webAuthnRequest, parseRequestOptionsFromJSON } from '@github/webauthn-json/browser-ponyfill';
|
import { supported as webAuthnSupported, get as webAuthnRequest, parseRequestOptionsFromJSON } from '@github/webauthn-json/browser-ponyfill';
|
||||||
import { showSuspendedDialog } from '@/scripts/show-suspended-dialog.js';
|
import { showSuspendedDialog } from '@/scripts/show-suspended-dialog.js';
|
||||||
|
|
|
@ -63,7 +63,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed, shallowRef } from 'vue';
|
import { ref, computed, shallowRef } from 'vue';
|
||||||
import { toUnicode } from 'punycode/';
|
import { toUnicode } from 'punycode.js';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import MkButton from './MkButton.vue';
|
import MkButton from './MkButton.vue';
|
||||||
import MkInput from './MkInput.vue';
|
import MkInput from './MkInput.vue';
|
||||||
|
|
|
@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
<details v-if="note.files && note.files.length > 0">
|
<details v-if="note.files && note.files.length > 0">
|
||||||
<summary>({{ i18n.tsx.withNFiles({ n: note.files.length }) }})</summary>
|
<summary>({{ i18n.tsx.withNFiles({ n: note.files.length }) }})</summary>
|
||||||
<MkMediaList :mediaList="note.files"/>
|
<MkMediaList :mediaList="note.files" :user="note.user"/>
|
||||||
</details>
|
</details>
|
||||||
<details v-if="note.poll">
|
<details v-if="note.poll">
|
||||||
<summary>{{ i18n.ts.poll }}</summary>
|
<summary>{{ i18n.ts.poll }}</summary>
|
||||||
|
|
|
@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { toUnicode } from 'punycode/';
|
import { toUnicode } from 'punycode.js';
|
||||||
import { host as hostRaw } from '@/config.js';
|
import { host as hostRaw } from '@/config.js';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { defineAsyncComponent, ref } from 'vue';
|
import { defineAsyncComponent, ref } from 'vue';
|
||||||
import { toUnicode as decodePunycode } from 'punycode/';
|
import { toUnicode as decodePunycode } from 'punycode.js';
|
||||||
import { url as local } from '@/config.js';
|
import { url as local } from '@/config.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { useTooltip } from '@/scripts/use-tooltip.js';
|
import { useTooltip } from '@/scripts/use-tooltip.js';
|
||||||
|
|
|
@ -11,8 +11,9 @@ import { openInstanceMenu, openToolsMenu } from '@/ui/_common_/common.js';
|
||||||
import { lookup } from '@/scripts/lookup.js';
|
import { lookup } from '@/scripts/lookup.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { ui } from '@/config.js';
|
import { ui, host } from '@/config.js';
|
||||||
import { unisonReload } from '@/scripts/unison-reload.js';
|
import { unisonReload } from '@/scripts/unison-reload.js';
|
||||||
|
import { instance } from '@/instance.js';
|
||||||
|
|
||||||
export const navbarItemDef = reactive({
|
export const navbarItemDef = reactive({
|
||||||
notifications: {
|
notifications: {
|
||||||
|
@ -177,6 +178,11 @@ export const navbarItemDef = reactive({
|
||||||
show: computed(() => $i != null),
|
show: computed(() => $i != null),
|
||||||
to: `/@${$i?.username}`,
|
to: `/@${$i?.username}`,
|
||||||
},
|
},
|
||||||
|
support: {
|
||||||
|
title: i18n.tsx.supportThisInstance({ name: instance.name ?? host }),
|
||||||
|
icon: 'ti ti-pig-money',
|
||||||
|
to: 'https://go.misskey.io/donate',
|
||||||
|
},
|
||||||
cacheClear: {
|
cacheClear: {
|
||||||
title: i18n.ts.clearCache,
|
title: i18n.ts.clearCache,
|
||||||
icon: 'ti ti-trash',
|
icon: 'ti ti-trash',
|
||||||
|
|
|
@ -709,6 +709,7 @@ definePageMetadata(() => ({
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
.ip {
|
.ip {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
word-break: break-all;
|
||||||
|
|
||||||
> :global(.date) {
|
> :global(.date) {
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
|
|
|
@ -10,14 +10,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div class="_root">
|
<div class="_root">
|
||||||
<Transition :name="defaultStore.state.animation ? 'fade' : ''" mode="out-in">
|
<Transition :name="defaultStore.state.animation ? 'fade' : ''" mode="out-in">
|
||||||
<div v-if="post" class="rkxwuolj">
|
<div v-if="post" class="rkxwuolj">
|
||||||
<div class="files">
|
|
||||||
<div v-for="file in post.files" :key="file.id" class="file">
|
|
||||||
<img :src="file.url"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<div class="title">{{ post.title }}</div>
|
<div class="title">{{ post.title }}</div>
|
||||||
<div class="description"><Mfm :text="post.description"/></div>
|
<div class="description"><Mfm :text="post.description"/></div>
|
||||||
|
<div class="files">
|
||||||
|
<div v-for="file in post.files" :key="file.id" class="file">
|
||||||
|
<img :src="file.url"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<i class="ti ti-clock"></i> <MkTime :time="post.createdAt" mode="detail"/>
|
<i class="ti ti-clock"></i> <MkTime :time="post.createdAt" mode="detail"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -59,18 +59,18 @@ async function onAccept(token: string) {
|
||||||
name: props.name,
|
name: props.name,
|
||||||
iconUrl: props.icon,
|
iconUrl: props.icon,
|
||||||
permission: _permissions.value,
|
permission: _permissions.value,
|
||||||
}, token).catch(() => {
|
}, token).then(() => {
|
||||||
|
if (props.callback && props.callback !== '') {
|
||||||
|
const cbUrl = new URL(props.callback);
|
||||||
|
if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:', 'vbscript:'].includes(cbUrl.protocol)) throw new Error('invalid url');
|
||||||
|
cbUrl.searchParams.set('session', props.session);
|
||||||
|
location.href = cbUrl.toString();
|
||||||
|
} else {
|
||||||
|
authRoot.value?.showUI('success');
|
||||||
|
}
|
||||||
|
}).catch(() => {
|
||||||
authRoot.value?.showUI('failed');
|
authRoot.value?.showUI('failed');
|
||||||
});
|
});
|
||||||
|
|
||||||
if (props.callback && props.callback !== '') {
|
|
||||||
const cbUrl = new URL(props.callback);
|
|
||||||
if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:', 'vbscript:'].includes(cbUrl.protocol)) throw new Error('invalid url');
|
|
||||||
cbUrl.searchParams.set('session', props.session);
|
|
||||||
location.href = cbUrl.toString();
|
|
||||||
} else {
|
|
||||||
authRoot.value?.showUI('success');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onDeny() {
|
function onDeny() {
|
||||||
|
|
|
@ -37,6 +37,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<option value="sans-serif">{{ i18n.ts._pages.fontSansSerif }}</option>
|
<option value="sans-serif">{{ i18n.ts._pages.fontSansSerif }}</option>
|
||||||
</MkSelect>
|
</MkSelect>
|
||||||
|
|
||||||
|
<MkSelect v-model="visibility">
|
||||||
|
<template #label>{{ i18n.ts._pages.visibility }}</template>
|
||||||
|
<option value="public">{{ i18n.ts._pages.public }}</option>
|
||||||
|
<option value="private">{{ i18n.ts._pages.private }}</option>
|
||||||
|
</MkSelect>
|
||||||
|
|
||||||
<MkSwitch v-model="hideTitleWhenPinned">{{ i18n.ts._pages.hideTitleWhenPinned }}</MkSwitch>
|
<MkSwitch v-model="hideTitleWhenPinned">{{ i18n.ts._pages.hideTitleWhenPinned }}</MkSwitch>
|
||||||
|
|
||||||
<div class="eyeCatch">
|
<div class="eyeCatch">
|
||||||
|
@ -96,6 +102,7 @@ const name = ref(Date.now().toString());
|
||||||
const eyeCatchingImage = ref<Misskey.entities.DriveFile | null>(null);
|
const eyeCatchingImage = ref<Misskey.entities.DriveFile | null>(null);
|
||||||
const eyeCatchingImageId = ref<string | null>(null);
|
const eyeCatchingImageId = ref<string | null>(null);
|
||||||
const font = ref('sans-serif');
|
const font = ref('sans-serif');
|
||||||
|
const visibility = ref('public');
|
||||||
const content = ref<Misskey.entities.Page['content']>([]);
|
const content = ref<Misskey.entities.Page['content']>([]);
|
||||||
const alignCenter = ref(false);
|
const alignCenter = ref(false);
|
||||||
const hideTitleWhenPinned = ref(false);
|
const hideTitleWhenPinned = ref(false);
|
||||||
|
@ -119,6 +126,7 @@ function getSaveOptions() {
|
||||||
name: name.value.trim(),
|
name: name.value.trim(),
|
||||||
summary: summary.value,
|
summary: summary.value,
|
||||||
font: font.value,
|
font: font.value,
|
||||||
|
visibility: visibility.value,
|
||||||
script: '',
|
script: '',
|
||||||
hideTitleWhenPinned: hideTitleWhenPinned.value,
|
hideTitleWhenPinned: hideTitleWhenPinned.value,
|
||||||
alignCenter: alignCenter.value,
|
alignCenter: alignCenter.value,
|
||||||
|
@ -256,6 +264,7 @@ async function init() {
|
||||||
currentName.value = page.value.name;
|
currentName.value = page.value.name;
|
||||||
summary.value = page.value.summary;
|
summary.value = page.value.summary;
|
||||||
font.value = page.value.font;
|
font.value = page.value.font;
|
||||||
|
visibility.value = page.value.visibility;
|
||||||
hideTitleWhenPinned.value = page.value.hideTitleWhenPinned;
|
hideTitleWhenPinned.value = page.value.hideTitleWhenPinned;
|
||||||
alignCenter.value = page.value.alignCenter;
|
alignCenter.value = page.value.alignCenter;
|
||||||
content.value = page.value.content;
|
content.value = page.value.content;
|
||||||
|
@ -286,8 +295,8 @@ const headerTabs = computed(() => [{
|
||||||
|
|
||||||
definePageMetadata(() => ({
|
definePageMetadata(() => ({
|
||||||
title: props.initPageId ? i18n.ts._pages.editPage
|
title: props.initPageId ? i18n.ts._pages.editPage
|
||||||
: props.initPageName && props.initUser ? i18n.ts._pages.readPage
|
: props.initPageName && props.initUser ? i18n.ts._pages.readPage
|
||||||
: i18n.ts._pages.newPage,
|
: i18n.ts._pages.newPage,
|
||||||
icon: 'ti ti-pencil',
|
icon: 'ti ti-pencil',
|
||||||
}));
|
}));
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -41,7 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.pageBannerTitle" class="_gaps_s">
|
<div :class="$style.pageBannerTitle" class="_gaps_s">
|
||||||
<h1>{{ page.title || page.name }}</h1>
|
<h1>{{ page.title || page.name }} <i v-if="page.visibility === 'private'" class="ti ti-lock"></i></h1>
|
||||||
<div :class="$style.pageBannerTitleSub">
|
<div :class="$style.pageBannerTitleSub">
|
||||||
<div v-if="page.user" :class="$style.pageBannerTitleUser">
|
<div v-if="page.user" :class="$style.pageBannerTitleUser">
|
||||||
<MkAvatar :user="page.user" :class="$style.avatar" indicator link preview/> <MkA :to="`/@${username}`"><MkUserName :user="page.user" :nowrap="false"/></MkA>
|
<MkAvatar :user="page.user" :class="$style.avatar" indicator link preview/> <MkA :to="`/@${username}`"><MkUserName :user="page.user" :nowrap="false"/></MkA>
|
||||||
|
@ -80,7 +80,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.pageLinks">
|
<div :class="$style.pageLinks">
|
||||||
<MkA v-if="!$i || $i.id !== page.userId" :to="`/@${username}/pages/${pageName}/view-source`" class="link">{{ i18n.ts._pages.viewSource }}</MkA>
|
<MkA v-if="!$i || $i.id !== page.userId" :to="`/@${username}/pages/${pageName}/view-source`" class="link">{{ i18n.ts._pages.viewSource }}</MkA>
|
||||||
<template v-if="$i && $i.id === page.userId">
|
<template v-if="($i && $i.id === page.userId) && page.visibility === 'public'">
|
||||||
<MkA :to="`/pages/edit/${page.id}`" class="link">{{ i18n.ts._pages.editThisPage }}</MkA>
|
<MkA :to="`/pages/edit/${page.id}`" class="link">{{ i18n.ts._pages.editThisPage }}</MkA>
|
||||||
<button v-if="$i.pinnedPageId === page.id" class="link _textButton" @click="pin(false)">{{ i18n.ts.unpin }}</button>
|
<button v-if="$i.pinnedPageId === page.id" class="link _textButton" @click="pin(false)">{{ i18n.ts.unpin }}</button>
|
||||||
<button v-else class="link _textButton" @click="pin(true)">{{ i18n.ts.pin }}</button>
|
<button v-else class="link _textButton" @click="pin(true)">{{ i18n.ts.pin }}</button>
|
||||||
|
|
|
@ -74,7 +74,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { watch, ref, computed } from 'vue';
|
import { watch, ref, computed } from 'vue';
|
||||||
import { toUnicode } from 'punycode/';
|
import { toUnicode } from 'punycode.js';
|
||||||
import tinycolor from 'tinycolor2';
|
import tinycolor from 'tinycolor2';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import JSON5 from 'json5';
|
import JSON5 from 'json5';
|
||||||
|
|
|
@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkA :to="notePage(media.note)">
|
<MkA :to="notePage(media.note)">
|
||||||
<XVideo v-if="media.file.type.startsWith('video')" :key="`video:${media.file.id}`" :class="$style.media" :video="media.file" :videoControls="false"/>
|
<XVideo v-if="media.file.type.startsWith('video')" :key="`video:${media.file.id}`" :class="$style.media" :video="media.file" :videoControls="false"/>
|
||||||
<XImage v-else-if="media.file.type.startsWith('image')" :key="`image:${media.file.id}`" :class="$style.media" class="image" :data-id="media.file.id" :image="media.file" :disableImageLink="true"/>
|
<XImage v-else-if="media.file.type.startsWith('image')" :key="`image:${media.file.id}`" :class="$style.media" class="image" :data-id="media.file.id" :image="media.file" :disableImageLink="true"/>
|
||||||
<XBanner v-else :media="media.file"/>
|
<XBanner v-else :media="media.file" :user="user"/>
|
||||||
</MkA>
|
</MkA>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkA v-if="note.renoteId" class="rp" :to="`/notes/${note.renoteId}`">RN: ...</MkA>
|
<MkA v-if="note.renoteId" class="rp" :to="`/notes/${note.renoteId}`">RN: ...</MkA>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="note.files.length > 0" :class="$style.richcontent">
|
<div v-if="note.files.length > 0" :class="$style.richcontent">
|
||||||
<MkMediaList :mediaList="note.files"/>
|
<MkMediaList :mediaList="note.files" :user="note.user"/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="note.poll">
|
<div v-if="note.poll">
|
||||||
<MkPoll :noteId="note.id" :poll="note.poll" :readOnly="true"/>
|
<MkPoll :noteId="note.id" :poll="note.poll" :readOnly="true"/>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
import { nextTick, Ref, ref, defineAsyncComponent } from 'vue';
|
import { nextTick, Ref, ref, defineAsyncComponent } from 'vue';
|
||||||
import getCaretCoordinates from 'textarea-caret';
|
import getCaretCoordinates from 'textarea-caret';
|
||||||
import { toASCII } from 'punycode/';
|
import { toASCII } from 'punycode.js';
|
||||||
import type { CompleteInfo } from '@/components/MkAutocomplete.vue';
|
import type { CompleteInfo } from '@/components/MkAutocomplete.vue';
|
||||||
import { popup } from '@/os.js';
|
import { popup } from '@/os.js';
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { toUnicode } from 'punycode';
|
import { toUnicode } from 'punycode.js';
|
||||||
import { defineAsyncComponent, ref, watch } from 'vue';
|
import { defineAsyncComponent, ref, watch } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
|
@ -145,6 +145,7 @@ export const defaultStore = markRaw(new Storage('base', {
|
||||||
'announcements',
|
'announcements',
|
||||||
'search',
|
'search',
|
||||||
'-',
|
'-',
|
||||||
|
'support',
|
||||||
'ui',
|
'ui',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -525,7 +526,7 @@ export const defaultStore = markRaw(new Storage('base', {
|
||||||
},
|
},
|
||||||
sound_note: {
|
sound_note: {
|
||||||
where: 'device',
|
where: 'device',
|
||||||
default: { type: 'syuilo/n-aec', volume: 1 } as SoundStore,
|
default: { type: null, volume: 1 } as SoundStore,
|
||||||
},
|
},
|
||||||
sound_noteMy: {
|
sound_noteMy: {
|
||||||
where: 'device',
|
where: 'device',
|
||||||
|
|
|
@ -305,8 +305,6 @@
|
||||||
"👸": ["girl", "woman", "female", "blond", "crown", "royal", "queen"],
|
"👸": ["girl", "woman", "female", "blond", "crown", "royal", "queen"],
|
||||||
"🤴": ["boy", "man", "male", "crown", "royal", "king"],
|
"🤴": ["boy", "man", "male", "crown", "royal", "king"],
|
||||||
"👰": ["couple", "marriage", "wedding", "woman", "bride"],
|
"👰": ["couple", "marriage", "wedding", "woman", "bride"],
|
||||||
"👰": ["couple", "marriage", "wedding", "woman", "bride"],
|
|
||||||
"🤵": ["couple", "marriage", "wedding", "groom"],
|
|
||||||
"🤵": ["couple", "marriage", "wedding", "groom"],
|
"🤵": ["couple", "marriage", "wedding", "groom"],
|
||||||
"🏃♀️": ["woman", "walking", "exercise", "race", "running", "female"],
|
"🏃♀️": ["woman", "walking", "exercise", "race", "running", "female"],
|
||||||
"🏃": ["man", "walking", "exercise", "race", "running"],
|
"🏃": ["man", "walking", "exercise", "race", "running"],
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"declaration": false,
|
"declaration": false,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"target": "ES2022",
|
"target": "es2022",
|
||||||
"module": "nodenext",
|
"module": "nodenext",
|
||||||
"moduleResolution": "nodenext",
|
"moduleResolution": "nodenext",
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"declaration": false,
|
"declaration": false,
|
||||||
"sourceMap": false,
|
"sourceMap": false,
|
||||||
"target": "ES2022",
|
"target": "es2022",
|
||||||
"module": "nodenext",
|
"module": "nodenext",
|
||||||
"moduleResolution": "nodenext",
|
"moduleResolution": "nodenext",
|
||||||
"removeComments": false,
|
"removeComments": false,
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { type UserConfig, defineConfig } from 'vite';
|
||||||
|
|
||||||
import locales from '../../locales/index.js';
|
import locales from '../../locales/index.js';
|
||||||
import meta from '../../package.json';
|
import meta from '../../package.json';
|
||||||
import packageInfo from './package.json' assert { type: 'json' };
|
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';
|
||||||
|
|
||||||
|
|
|
@ -25,14 +25,14 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@misskey-dev/eslint-plugin": "1.0.0",
|
"@misskey-dev/eslint-plugin": "1.0.0",
|
||||||
"@types/matter-js": "0.19.7",
|
"@types/matter-js": "0.19.8",
|
||||||
"@types/node": "22.9.0",
|
"@types/node": "22.10.2",
|
||||||
"@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",
|
||||||
"eslint": "8.57.1",
|
"eslint": "8.57.1",
|
||||||
"nodemon": "3.1.7",
|
"nodemon": "3.1.9",
|
||||||
"typescript": "5.6.3"
|
"typescript": "5.7.2"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"built"
|
"built"
|
||||||
|
|
|
@ -204,10 +204,10 @@ export class DropAndFusionGame extends EventEmitter<{
|
||||||
} else if (mono.shape === 'rectangle') {
|
} else if (mono.shape === 'rectangle') {
|
||||||
return Matter.Bodies.rectangle(x, y, mono.sizeX, mono.sizeY, options);
|
return Matter.Bodies.rectangle(x, y, mono.sizeX, mono.sizeY, options);
|
||||||
} else if (mono.shape === 'custom') {
|
} else if (mono.shape === 'custom') {
|
||||||
return Matter.Bodies.fromVertices(x, y, mono.vertices!.map(i => i.map(j => ({
|
return Matter.Bodies.fromVertices(x, y, mono.vertices?.map(i => i.map(j => ({
|
||||||
x: (j.x / mono.verticesSize!) * mono.sizeX,
|
x: (j.x / mono.verticesSize!) * mono.sizeX,
|
||||||
y: (j.y / mono.verticesSize!) * mono.sizeY,
|
y: (j.y / mono.verticesSize!) * mono.sizeY,
|
||||||
}))), options);
|
}))) ?? [], options);
|
||||||
} else {
|
} else {
|
||||||
throw new Error('unrecognized shape');
|
throw new Error('unrecognized shape');
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/tsconfig",
|
"$schema": "https://json.schemastore.org/tsconfig",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2022",
|
"target": "es2022",
|
||||||
"module": "nodenext",
|
"module": "nodenext",
|
||||||
"moduleResolution": "nodenext",
|
"moduleResolution": "nodenext",
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/swcrc",
|
"$schema": "https://swc.rs/schema.json",
|
||||||
"jsc": {
|
"jsc": {
|
||||||
"parser": {
|
"parser": {
|
||||||
"syntax": "typescript",
|
"syntax": "typescript",
|
||||||
|
@ -17,7 +17,8 @@
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["*"]
|
"@/*": ["*"]
|
||||||
},
|
},
|
||||||
"target": "es2022"
|
"target": "es2022",
|
||||||
|
"keepClassNames": true
|
||||||
},
|
},
|
||||||
"minify": false
|
"minify": false
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
"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.6.0",
|
||||||
"@types/node": "22.9.0",
|
"@types/node": "22.10.2",
|
||||||
"@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",
|
||||||
|
@ -17,7 +17,7 @@
|
||||||
"openapi-typescript": "6.7.6",
|
"openapi-typescript": "6.7.6",
|
||||||
"ts-case-convert": "2.1.0",
|
"ts-case-convert": "2.1.0",
|
||||||
"tsx": "4.19.2",
|
"tsx": "4.19.2",
|
||||||
"typescript": "5.6.3"
|
"typescript": "5.7.2"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"built"
|
"built"
|
||||||
|
|
|
@ -35,11 +35,11 @@
|
||||||
"url": "git+https://github.com/misskey-dev/misskey.js.git"
|
"url": "git+https://github.com/misskey-dev/misskey.js.git"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@microsoft/api-extractor": "7.47.11",
|
"@microsoft/api-extractor": "7.48.1",
|
||||||
"@misskey-dev/eslint-plugin": "1.0.0",
|
"@misskey-dev/eslint-plugin": "1.0.0",
|
||||||
"@swc/jest": "0.2.37",
|
"@swc/jest": "0.2.37",
|
||||||
"@types/jest": "29.5.14",
|
"@types/jest": "29.5.14",
|
||||||
"@types/node": "22.9.0",
|
"@types/node": "22.10.2",
|
||||||
"@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",
|
||||||
|
@ -48,9 +48,9 @@
|
||||||
"jest-websocket-mock": "2.5.0",
|
"jest-websocket-mock": "2.5.0",
|
||||||
"mock-socket": "9.3.1",
|
"mock-socket": "9.3.1",
|
||||||
"ncp": "2.0.0",
|
"ncp": "2.0.0",
|
||||||
"nodemon": "3.1.7",
|
"nodemon": "3.1.9",
|
||||||
"tsd": "0.31.2",
|
"tsd": "0.31.2",
|
||||||
"typescript": "5.6.3"
|
"typescript": "5.7.2"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"built",
|
"built",
|
||||||
|
@ -58,8 +58,8 @@
|
||||||
"built/dts"
|
"built/dts"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@swc/cli": "0.5.0",
|
"@swc/cli": "0.5.2",
|
||||||
"@swc/core": "1.9.1",
|
"@swc/core": "1.10.1",
|
||||||
"eventemitter3": "5.0.1",
|
"eventemitter3": "5.0.1",
|
||||||
"reconnecting-websocket": "4.4.0"
|
"reconnecting-websocket": "4.4.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -4695,6 +4695,8 @@ export type components = {
|
||||||
attachedFiles: components['schemas']['DriveFile'][];
|
attachedFiles: components['schemas']['DriveFile'][];
|
||||||
likedCount: number;
|
likedCount: number;
|
||||||
isLiked?: boolean;
|
isLiked?: boolean;
|
||||||
|
/** @enum {string} */
|
||||||
|
visibility: 'public' | 'private';
|
||||||
};
|
};
|
||||||
PageBlock: OneOf<[{
|
PageBlock: OneOf<[{
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -25630,6 +25632,8 @@ export type operations = {
|
||||||
alignCenter?: boolean;
|
alignCenter?: boolean;
|
||||||
/** @default false */
|
/** @default false */
|
||||||
hideTitleWhenPinned?: boolean;
|
hideTitleWhenPinned?: boolean;
|
||||||
|
/** @enum {string} */
|
||||||
|
visibility?: 'public' | 'private';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -25964,6 +25968,8 @@ export type operations = {
|
||||||
font?: 'serif' | 'sans-serif';
|
font?: 'serif' | 'sans-serif';
|
||||||
alignCenter?: boolean;
|
alignCenter?: boolean;
|
||||||
hideTitleWhenPinned?: boolean;
|
hideTitleWhenPinned?: boolean;
|
||||||
|
/** @enum {string} */
|
||||||
|
visibility?: 'public' | 'private';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/tsconfig",
|
"$schema": "https://json.schemastore.org/tsconfig",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2022",
|
"target": "es2022",
|
||||||
"module": "nodenext",
|
"module": "nodenext",
|
||||||
"moduleResolution": "nodenext",
|
"moduleResolution": "nodenext",
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
|
|
|
@ -25,12 +25,12 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@misskey-dev/eslint-plugin": "1.0.0",
|
"@misskey-dev/eslint-plugin": "1.0.0",
|
||||||
"@types/node": "22.9.0",
|
"@types/node": "22.10.2",
|
||||||
"@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",
|
||||||
"nodemon": "3.1.7",
|
"nodemon": "3.1.9",
|
||||||
"typescript": "5.6.3"
|
"typescript": "5.7.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"crc-32": "1.2.2",
|
"crc-32": "1.2.2",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/tsconfig",
|
"$schema": "https://json.schemastore.org/tsconfig",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2022",
|
"target": "es2022",
|
||||||
"module": "nodenext",
|
"module": "nodenext",
|
||||||
"moduleResolution": "nodenext",
|
"moduleResolution": "nodenext",
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import * as esbuild from 'esbuild';
|
import * as esbuild from 'esbuild';
|
||||||
import locales from '../../locales/index.js';
|
import locales from '../../locales/index.js';
|
||||||
import meta from '../../package.json' assert { type: "json" };
|
import meta from '../../package.json' with { type: "json" };
|
||||||
const watch = process.argv[2]?.includes('watch');
|
const watch = process.argv[2]?.includes('watch');
|
||||||
|
|
||||||
const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
||||||
|
|
|
@ -15,12 +15,12 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@misskey-dev/eslint-plugin": "1.0.0",
|
"@misskey-dev/eslint-plugin": "1.0.0",
|
||||||
"@types/serviceworker": "0.0.102",
|
"@types/serviceworker": "0.0.107",
|
||||||
"@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",
|
||||||
"nodemon": "3.1.7",
|
"nodemon": "3.1.9",
|
||||||
"typescript": "5.6.3"
|
"typescript": "5.7.2"
|
||||||
},
|
},
|
||||||
"type": "module"
|
"type": "module"
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"declaration": false,
|
"declaration": false,
|
||||||
"sourceMap": false,
|
"sourceMap": false,
|
||||||
"target": "ES2022",
|
"target": "es2022",
|
||||||
"module": "nodenext",
|
"module": "nodenext",
|
||||||
"moduleResolution": "nodenext",
|
"moduleResolution": "nodenext",
|
||||||
"removeComments": false,
|
"removeComments": false,
|
||||||
|
|
5226
pnpm-lock.yaml
generated
5226
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -11,7 +11,7 @@ import * as terser from 'terser';
|
||||||
|
|
||||||
import { build as buildLocales } from '../locales/index.js';
|
import { build as buildLocales } from '../locales/index.js';
|
||||||
import generateDTS from '../locales/generateDTS.js';
|
import generateDTS from '../locales/generateDTS.js';
|
||||||
import meta from '../package.json' assert { type: "json" };
|
import meta from '../package.json' with { type: "json" };
|
||||||
|
|
||||||
let locales = buildLocales();
|
let locales = buildLocales();
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue