diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2022-01-27 00:17:13 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2022-01-27 00:17:13 +0900 |
| commit | 5f5f68cdcd31653cef2ae6bd29ce8bfcf60113ff (patch) | |
| tree | 51e9e6179f6d1bda3013d1412f6e43f9f8f70e86 /packages | |
| parent | Merge branch 'develop' (diff) | |
| parent | 12.102.0 (diff) | |
| download | misskey-5f5f68cdcd31653cef2ae6bd29ce8bfcf60113ff.tar.gz misskey-5f5f68cdcd31653cef2ae6bd29ce8bfcf60113ff.tar.bz2 misskey-5f5f68cdcd31653cef2ae6bd29ce8bfcf60113ff.zip | |
Merge branch 'develop'
Diffstat (limited to 'packages')
927 files changed, 15563 insertions, 32168 deletions
diff --git a/packages/backend/migration/1637320813000-forwarded-report.js b/packages/backend/migration/1637320813000-forwarded-report.js new file mode 100644 index 0000000000..4056f7b5f4 --- /dev/null +++ b/packages/backend/migration/1637320813000-forwarded-report.js @@ -0,0 +1,13 @@ +const { QueryRunner } = require('typeorm'); + +module.exports = class forwardedReport1637320813000 { + name = 'forwardedReport1637320813000'; + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "forwarded" boolean NOT NULL DEFAULT false`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "forwarded"`); + } +}; diff --git a/packages/backend/migration/1642611822809-emoji-url.js b/packages/backend/migration/1642611822809-emoji-url.js new file mode 100644 index 0000000000..f229c403f4 --- /dev/null +++ b/packages/backend/migration/1642611822809-emoji-url.js @@ -0,0 +1,15 @@ +const { MigrationInterface, QueryRunner } = require("typeorm"); + +module.exports = class emojiUrl1642611822809 { + name = 'emojiUrl1642611822809' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "emoji" RENAME COLUMN "url" TO "originalUrl"`); + await queryRunner.query(`ALTER TABLE "emoji" ADD "publicUrl" character varying(512) NOT NULL DEFAULT ''`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "emoji" DROP COLUMN "publicUrl"`); + await queryRunner.query(`ALTER TABLE "emoji" RENAME COLUMN "originalUrl" TO "url"`); + } +} diff --git a/packages/backend/migration/1642613870898-drive-file-webpublic-type.js b/packages/backend/migration/1642613870898-drive-file-webpublic-type.js new file mode 100644 index 0000000000..e10c2ac2d2 --- /dev/null +++ b/packages/backend/migration/1642613870898-drive-file-webpublic-type.js @@ -0,0 +1,13 @@ +const { MigrationInterface, QueryRunner } = require("typeorm"); + +module.exports = class driveFileWebpublicType1642613870898 { + name = 'driveFileWebpublicType1642613870898' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "drive_file" ADD "webpublicType" character varying(128)`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "drive_file" DROP COLUMN "webpublicType"`); + } +} diff --git a/packages/backend/package.json b/packages/backend/package.json index ffd179dfa3..3d3a901f34 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -22,87 +22,78 @@ "@sinonjs/fake-timers": "7.1.2", "@syuilo/aiscript": "0.11.1", "@types/bcryptjs": "2.4.2", - "@types/bull": "3.15.5", + "@types/bull": "3.15.7", "@types/cbor": "6.0.0", "@types/dateformat": "3.0.1", - "@types/escape-regexp": "0.0.0", + "@types/escape-regexp": "0.0.1", "@types/glob": "7.2.0", "@types/is-url": "1.2.30", - "@types/js-yaml": "4.0.4", - "@types/jsdom": "16.2.13", + "@types/js-yaml": "4.0.5", + "@types/jsdom": "16.2.14", "@types/jsonld": "1.5.6", "@types/koa": "2.13.4", - "@types/koa-bodyparser": "4.3.3", + "@types/koa-bodyparser": "4.3.5", "@types/koa-cors": "0.0.2", "@types/koa-favicon": "2.0.21", "@types/koa-logger": "3.1.2", "@types/koa-mount": "4.0.1", "@types/koa-send": "4.1.3", "@types/koa-views": "7.0.0", - "@types/koa__cors": "3.0.3", + "@types/koa__cors": "3.1.1", "@types/koa__multer": "2.0.4", - "@types/koa__router": "8.0.8", + "@types/koa__router": "8.0.11", "@types/mocha": "8.2.3", - "@types/node": "16.11.7", - "@types/node-fetch": "2.5.12", + "@types/node": "17.0.10", + "@types/node-fetch": "3.0.3", "@types/nodemailer": "6.4.4", "@types/oauth": "0.9.1", "@types/parse5": "6.0.3", "@types/portscanner": "2.1.1", - "@types/pug": "2.0.5", + "@types/pug": "2.0.6", "@types/punycode": "2.1.0", - "@types/qrcode": "1.4.1", + "@types/qrcode": "1.4.2", "@types/random-seed": "0.3.3", - "@types/ratelimiter": "3.4.2", - "@types/redis": "2.8.32", + "@types/ratelimiter": "3.4.3", + "@types/redis": "4.0.11", "@types/rename": "1.0.4", "@types/request-stats": "3.0.0", - "@types/sanitize-html": "2.5.0", + "@types/sanitize-html": "2.6.2", "@types/seedrandom": "2.4.28", - "@types/sharp": "0.29.3", + "@types/sharp": "0.29.5", "@types/sinonjs__fake-timers": "6.0.4", - "@types/speakeasy": "2.0.6", + "@types/speakeasy": "2.0.7", "@types/throttle-debounce": "2.1.0", "@types/tinycolor2": "1.4.3", - "@types/tmp": "0.2.2", - "@types/uuid": "8.3.1", + "@types/tmp": "0.2.3", + "@types/uuid": "8.3.4", "@types/web-push": "3.3.2", "@types/webpack": "5.28.0", "@types/webpack-stream": "3.2.12", "@types/websocket": "1.0.4", - "@types/ws": "8.2.0", - "@typescript-eslint/eslint-plugin": "5.3.1", - "@typescript-eslint/parser": "5.1.0", + "@types/ws": "8.2.2", + "@typescript-eslint/eslint-plugin": "5.10.0", + "@typescript-eslint/parser": "5.10.0", "abort-controller": "3.0.0", "archiver": "5.3.0", "autobind-decorator": "2.4.0", - "autosize": "4.0.4", "autwh": "0.1.0", - "aws-sdk": "2.1013.0", + "aws-sdk": "2.1061.0", "bcryptjs": "2.4.3", "blurhash": "1.1.4", - "broadcast-channel": "4.5.0", - "bull": "4.1.0", + "broadcast-channel": "4.9.0", + "bull": "4.2.1", "cacheable-lookup": "6.0.4", "cafy": "15.2.1", "cbor": "8.1.0", "chalk": "4.1.2", - "chart.js": "3.6.0", - "chartjs-adapter-date-fns": "2.0.0", - "chartjs-plugin-zoom": "1.1.1", "cli-highlight": "2.1.11", - "compare-versions": "3.6.0", - "content-disposition": "0.5.3", + "content-disposition": "0.5.4", "crc-32": "1.2.0", - "css-loader": "6.5.1", - "cssnano": "5.0.10", - "date-fns": "2.25.0", "dateformat": "4.5.1", - "deep-email-validator": "0.1.18", + "deep-email-validator": "0.1.21", "escape-regexp": "0.0.1", - "eslint": "8.2.0", - "eslint-plugin-import": "2.25.3", - "eslint-plugin-vue": "8.0.3", + "eslint": "8.7.0", + "eslint-plugin-import": "2.25.4", "eventemitter3": "4.0.7", "feed": "4.2.2", "file-type": "16.5.3", @@ -110,11 +101,9 @@ "glob": "7.2.0", "got": "11.8.2", "hpagent": "0.1.2", - "http-signature": "1.3.5", - "idb-keyval": "5.1.3", - "insert-text-at-cursor": "0.3.0", + "http-signature": "1.3.6", "ip-cidr": "3.0.4", - "is-svg": "4.3.1", + "is-svg": "4.3.2", "js-yaml": "4.1.0", "jsdom": "16.7.0", "json5": "2.2.0", @@ -131,30 +120,29 @@ "koa-slow": "2.1.0", "koa-views": "7.0.2", "langmap": "0.0.16", - "mfm-js": "0.20.0", + "mfm-js": "0.21.0", "mime-types": "2.1.34", - "misskey-js": "0.0.8", + "misskey-js": "0.0.13", "mocha": "8.4.0", "ms": "3.0.0-canary.1", - "multer": "1.4.3", + "multer": "1.4.4", "nested-property": "4.0.0", "node-fetch": "2.6.1", - "nodemailer": "6.7.0", + "nodemailer": "6.7.2", "os-utils": "0.0.14", "parse5": "6.0.1", "pg": "8.7.1", "portscanner": "2.2.0", - "prismjs": "1.25.0", "private-ip": "2.3.3", - "probe-image-size": "7.2.1", + "probe-image-size": "7.2.2", "promise-limit": "2.7.0", "pug": "3.0.2", "punycode": "2.1.1", - "pureimage": "0.3.5", - "qrcode": "1.4.4", + "pureimage": "0.3.8", + "qrcode": "1.5.0", "random-seed": "0.3.0", "ratelimiter": "3.4.1", - "re2": "1.16.0", + "re2": "1.17.3", "redis": "3.1.2", "redis-lock": "0.1.4", "reflect-metadata": "0.1.13", @@ -163,9 +151,9 @@ "require-all": "3.0.0", "rndstr": "1.0.0", "s-age": "1.1.2", - "sanitize-html": "2.5.3", + "sanitize-html": "2.6.1", "seedrandom": "3.0.5", - "sharp": "0.29.2", + "sharp": "0.29.3", "speakeasy": "2.0.0", "strict-event-emitter-types": "2.0.0", "stringz": "2.1.0", @@ -179,20 +167,21 @@ "ts-loader": "9.2.6", "ts-node": "10.4.0", "tsc-alias": "1.4.1", - "tsconfig-paths": "3.11.0", + "tsconfig-paths": "3.12.0", "twemoji-parser": "13.1.0", - "typeorm": "0.2.39", - "typescript": "4.4.4", + "typeorm": "0.2.41", + "typescript": "4.5.5", "ulid": "2.3.0", + "unzipper": "0.10.11", "uuid": "8.3.2", "web-push": "3.4.5", "websocket": "1.0.34", - "ws": "8.2.3", + "ws": "8.4.2", "xev": "2.0.1" }, "devDependencies": { - "@redocly/openapi-core": "1.0.0-beta.54", - "@types/fluent-ffmpeg": "2.1.17", + "@redocly/openapi-core": "1.0.0-beta.79", + "@types/fluent-ffmpeg": "2.1.20", "cross-env": "7.0.3", "execa": "6.0.0" } diff --git a/packages/backend/src/const.ts b/packages/backend/src/const.ts index 43f59f1e4f..b00bd81655 100644 --- a/packages/backend/src/const.ts +++ b/packages/backend/src/const.ts @@ -1,2 +1,47 @@ export const USER_ONLINE_THRESHOLD = 1000 * 60 * 10; // 10min export const USER_ACTIVE_THRESHOLD = 1000 * 60 * 60 * 24 * 3; // 3days + +// ブラウザで直接表示することを許可するファイルの種類のリスト +// ここに含まれないものは application/octet-stream としてレスポンスされる +// SVGはXSSを生むので許可しない +export const FILE_TYPE_BROWSERSAFE = [ + // Images + 'image/png', + 'image/gif', + 'image/jpeg', + 'image/webp', + 'image/apng', + 'image/bmp', + 'image/tiff', + 'image/x-icon', + + // OggS + 'audio/opus', + 'video/ogg', + 'audio/ogg', + 'application/ogg', + + // ISO/IEC base media file format + 'video/quicktime', + 'video/mp4', + 'audio/mp4', + 'video/x-m4v', + 'audio/x-m4a', + 'video/3gpp', + 'video/3gpp2', + + 'video/mpeg', + 'audio/mpeg', + + 'video/webm', + 'audio/webm', + + 'audio/aac', + 'audio/x-flac', + 'audio/vnd.wave', +]; +/* +https://github.com/sindresorhus/file-type/blob/main/supported.js +https://github.com/sindresorhus/file-type/blob/main/core.js +https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers +*/ diff --git a/packages/backend/src/db/postgre.ts b/packages/backend/src/db/postgre.ts index b5f228d919..69336c2a46 100644 --- a/packages/backend/src/db/postgre.ts +++ b/packages/backend/src/db/postgre.ts @@ -40,8 +40,6 @@ import { Signin } from '@/models/entities/signin'; import { AuthSession } from '@/models/entities/auth-session'; import { FollowRequest } from '@/models/entities/follow-request'; import { Emoji } from '@/models/entities/emoji'; -import { ReversiGame } from '@/models/entities/games/reversi/game'; -import { ReversiMatching } from '@/models/entities/games/reversi/matching'; import { UserNotePining } from '@/models/entities/user-note-pining'; import { Poll } from '@/models/entities/poll'; import { UserKeypair } from '@/models/entities/user-keypair'; @@ -166,8 +164,6 @@ export const entities = [ AntennaNote, PromoNote, PromoRead, - ReversiGame, - ReversiMatching, Relay, MutedNote, Channel, @@ -224,7 +220,9 @@ export async function resetDb() { WHERE nspname NOT IN ('pg_catalog', 'information_schema') AND C.relkind = 'r' AND nspname !~ '^pg_toast';`); - await Promise.all(tables.map(t => t.table).map(x => conn.query(`DELETE FROM "${x}" CASCADE`))); + for (const table of tables) { + await conn.query(`DELETE FROM "${table.table}" CASCADE`); + } }; for (let i = 1; i <= 3; i++) { diff --git a/packages/backend/src/games/reversi/core.ts b/packages/backend/src/games/reversi/core.ts deleted file mode 100644 index 0cf7714543..0000000000 --- a/packages/backend/src/games/reversi/core.ts +++ /dev/null @@ -1,263 +0,0 @@ -import { count, concat } from '@/prelude/array'; - -// MISSKEY REVERSI ENGINE - -/** - * true ... 黒 - * false ... 白 - */ -export type Color = boolean; -const BLACK = true; -const WHITE = false; - -export type MapPixel = 'null' | 'empty'; - -export type Options = { - isLlotheo: boolean; - canPutEverywhere: boolean; - loopedBoard: boolean; -}; - -export type Undo = { - /** - * 色 - */ - color: Color; - - /** - * どこに打ったか - */ - pos: number; - - /** - * 反転した石の位置の配列 - */ - effects: number[]; - - /** - * ターン - */ - turn: Color | null; -}; - -/** - * リバーシエンジン - */ -export default class Reversi { - public map: MapPixel[]; - public mapWidth: number; - public mapHeight: number; - public board: (Color | null | undefined)[]; - public turn: Color | null = BLACK; - public opts: Options; - - public prevPos = -1; - public prevColor: Color | null = null; - - private logs: Undo[] = []; - - /** - * ゲームを初期化します - */ - constructor(map: string[], opts: Options) { - //#region binds - this.put = this.put.bind(this); - //#endregion - - //#region Options - this.opts = opts; - if (this.opts.isLlotheo == null) this.opts.isLlotheo = false; - if (this.opts.canPutEverywhere == null) this.opts.canPutEverywhere = false; - if (this.opts.loopedBoard == null) this.opts.loopedBoard = false; - //#endregion - - //#region Parse map data - this.mapWidth = map[0].length; - this.mapHeight = map.length; - const mapData = map.join(''); - - this.board = mapData.split('').map(d => d === '-' ? null : d === 'b' ? BLACK : d === 'w' ? WHITE : undefined); - - this.map = mapData.split('').map(d => d === '-' || d === 'b' || d === 'w' ? 'empty' : 'null'); - //#endregion - - // ゲームが始まった時点で片方の色の石しかないか、始まった時点で勝敗が決定するようなマップの場合がある - if (!this.canPutSomewhere(BLACK)) this.turn = this.canPutSomewhere(WHITE) ? WHITE : null; - } - - /** - * 黒石の数 - */ - public get blackCount() { - return count(BLACK, this.board); - } - - /** - * 白石の数 - */ - public get whiteCount() { - return count(WHITE, this.board); - } - - public transformPosToXy(pos: number): number[] { - const x = pos % this.mapWidth; - const y = Math.floor(pos / this.mapWidth); - return [x, y]; - } - - public transformXyToPos(x: number, y: number): number { - return x + (y * this.mapWidth); - } - - /** - * 指定のマスに石を打ちます - * @param color 石の色 - * @param pos 位置 - */ - public put(color: Color, pos: number) { - this.prevPos = pos; - this.prevColor = color; - - this.board[pos] = color; - - // 反転させられる石を取得 - const effects = this.effects(color, pos); - - // 反転させる - for (const pos of effects) { - this.board[pos] = color; - } - - const turn = this.turn; - - this.logs.push({ - color, - pos, - effects, - turn, - }); - - this.calcTurn(); - } - - private calcTurn() { - // ターン計算 - this.turn = - this.canPutSomewhere(!this.prevColor) ? !this.prevColor : - this.canPutSomewhere(this.prevColor!) ? this.prevColor : - null; - } - - public undo() { - const undo = this.logs.pop()!; - this.prevColor = undo.color; - this.prevPos = undo.pos; - this.board[undo.pos] = null; - for (const pos of undo.effects) { - const color = this.board[pos]; - this.board[pos] = !color; - } - this.turn = undo.turn; - } - - /** - * 指定した位置のマップデータのマスを取得します - * @param pos 位置 - */ - public mapDataGet(pos: number): MapPixel { - const [x, y] = this.transformPosToXy(pos); - return x < 0 || y < 0 || x >= this.mapWidth || y >= this.mapHeight ? 'null' : this.map[pos]; - } - - /** - * 打つことができる場所を取得します - */ - public puttablePlaces(color: Color): number[] { - return Array.from(this.board.keys()).filter(i => this.canPut(color, i)); - } - - /** - * 打つことができる場所があるかどうかを取得します - */ - public canPutSomewhere(color: Color): boolean { - return this.puttablePlaces(color).length > 0; - } - - /** - * 指定のマスに石を打つことができるかどうかを取得します - * @param color 自分の色 - * @param pos 位置 - */ - public canPut(color: Color, pos: number): boolean { - return ( - this.board[pos] !== null ? false : // 既に石が置いてある場所には打てない - this.opts.canPutEverywhere ? this.mapDataGet(pos) == 'empty' : // 挟んでなくても置けるモード - this.effects(color, pos).length !== 0); // 相手の石を1つでも反転させられるか - } - - /** - * 指定のマスに石を置いた時の、反転させられる石を取得します - * @param color 自分の色 - * @param initPos 位置 - */ - public effects(color: Color, initPos: number): number[] { - const enemyColor = !color; - - const diffVectors: [number, number][] = [ - [ 0, -1], // 上 - [ +1, -1], // 右上 - [ +1, 0], // 右 - [ +1, +1], // 右下 - [ 0, +1], // 下 - [ -1, +1], // 左下 - [ -1, 0], // 左 - [ -1, -1], // 左上 - ]; - - const effectsInLine = ([dx, dy]: [number, number]): number[] => { - const nextPos = (x: number, y: number): [number, number] => [x + dx, y + dy]; - - const found: number[] = []; // 挟めるかもしれない相手の石を入れておく配列 - let [x, y] = this.transformPosToXy(initPos); - while (true) { - [x, y] = nextPos(x, y); - - // 座標が指し示す位置がボード外に出たとき - if (this.opts.loopedBoard && this.transformXyToPos( - (x = ((x % this.mapWidth) + this.mapWidth) % this.mapWidth), - (y = ((y % this.mapHeight) + this.mapHeight) % this.mapHeight)) === initPos) { - // 盤面の境界でループし、自分が石を置く位置に戻ってきたとき、挟めるようにしている (ref: Test4のマップ) - return found; - } else if (x === -1 || y === -1 || x === this.mapWidth || y === this.mapHeight) { - return []; // 挟めないことが確定 (盤面外に到達) - } - - const pos = this.transformXyToPos(x, y); - if (this.mapDataGet(pos) === 'null') return []; // 挟めないことが確定 (配置不可能なマスに到達) - const stone = this.board[pos]; - if (stone === null) return []; // 挟めないことが確定 (石が置かれていないマスに到達) - if (stone === enemyColor) found.push(pos); // 挟めるかもしれない (相手の石を発見) - if (stone === color) return found; // 挟めることが確定 (対となる自分の石を発見) - } - }; - - return concat(diffVectors.map(effectsInLine)); - } - - /** - * ゲームが終了したか否か - */ - public get isEnded(): boolean { - return this.turn === null; - } - - /** - * ゲームの勝者 (null = 引き分け) - */ - public get winner(): Color | null { - return this.isEnded ? - this.blackCount == this.whiteCount ? null : - this.opts.isLlotheo === this.blackCount > this.whiteCount ? WHITE : BLACK : - undefined as never; - } -} diff --git a/packages/backend/src/games/reversi/maps.ts b/packages/backend/src/games/reversi/maps.ts deleted file mode 100644 index 8442c6d741..0000000000 --- a/packages/backend/src/games/reversi/maps.ts +++ /dev/null @@ -1,896 +0,0 @@ -/** - * 組み込みマップ定義 - * - * データ値: - * (スペース) ... マス無し - * - ... マス - * b ... 初期配置される黒石 - * w ... 初期配置される白石 - */ - -export type Map = { - name?: string; - category?: string; - author?: string; - data: string[]; -}; - -export const fourfour: Map = { - name: '4x4', - category: '4x4', - data: [ - '----', - '-wb-', - '-bw-', - '----', - ], -}; - -export const sixsix: Map = { - name: '6x6', - category: '6x6', - data: [ - '------', - '------', - '--wb--', - '--bw--', - '------', - '------', - ], -}; - -export const roundedSixsix: Map = { - name: '6x6 rounded', - category: '6x6', - author: 'syuilo', - data: [ - ' ---- ', - '------', - '--wb--', - '--bw--', - '------', - ' ---- ', - ], -}; - -export const roundedSixsix2: Map = { - name: '6x6 rounded 2', - category: '6x6', - author: 'syuilo', - data: [ - ' -- ', - ' ---- ', - '--wb--', - '--bw--', - ' ---- ', - ' -- ', - ], -}; - -export const eighteight: Map = { - name: '8x8', - category: '8x8', - data: [ - '--------', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - '--------', - ], -}; - -export const eighteightH1: Map = { - name: '8x8 handicap 1', - category: '8x8', - data: [ - 'b-------', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - '--------', - ], -}; - -export const eighteightH2: Map = { - name: '8x8 handicap 2', - category: '8x8', - data: [ - 'b-------', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - '-------b', - ], -}; - -export const eighteightH3: Map = { - name: '8x8 handicap 3', - category: '8x8', - data: [ - 'b------b', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - '-------b', - ], -}; - -export const eighteightH4: Map = { - name: '8x8 handicap 4', - category: '8x8', - data: [ - 'b------b', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - 'b------b', - ], -}; - -export const eighteightH28: Map = { - name: '8x8 handicap 28', - category: '8x8', - data: [ - 'bbbbbbbb', - 'b------b', - 'b------b', - 'b--wb--b', - 'b--bw--b', - 'b------b', - 'b------b', - 'bbbbbbbb', - ], -}; - -export const roundedEighteight: Map = { - name: '8x8 rounded', - category: '8x8', - author: 'syuilo', - data: [ - ' ------ ', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - ' ------ ', - ], -}; - -export const roundedEighteight2: Map = { - name: '8x8 rounded 2', - category: '8x8', - author: 'syuilo', - data: [ - ' ---- ', - ' ------ ', - '--------', - '---wb---', - '---bw---', - '--------', - ' ------ ', - ' ---- ', - ], -}; - -export const roundedEighteight3: Map = { - name: '8x8 rounded 3', - category: '8x8', - author: 'syuilo', - data: [ - ' -- ', - ' ---- ', - ' ------ ', - '---wb---', - '---bw---', - ' ------ ', - ' ---- ', - ' -- ', - ], -}; - -export const eighteightWithNotch: Map = { - name: '8x8 with notch', - category: '8x8', - author: 'syuilo', - data: [ - '--- ---', - '--------', - '--------', - ' --wb-- ', - ' --bw-- ', - '--------', - '--------', - '--- ---', - ], -}; - -export const eighteightWithSomeHoles: Map = { - name: '8x8 with some holes', - category: '8x8', - author: 'syuilo', - data: [ - '--- ----', - '----- --', - '-- -----', - '---wb---', - '---bw- -', - ' -------', - '--- ----', - '--------', - ], -}; - -export const circle: Map = { - name: 'Circle', - category: '8x8', - author: 'syuilo', - data: [ - ' -- ', - ' ------ ', - ' ------ ', - '---wb---', - '---bw---', - ' ------ ', - ' ------ ', - ' -- ', - ], -}; - -export const smile: Map = { - name: 'Smile', - category: '8x8', - author: 'syuilo', - data: [ - ' ------ ', - '--------', - '-- -- --', - '---wb---', - '-- bw --', - '--- ---', - '--------', - ' ------ ', - ], -}; - -export const window: Map = { - name: 'Window', - category: '8x8', - author: 'syuilo', - data: [ - '--------', - '- -- -', - '- -- -', - '---wb---', - '---bw---', - '- -- -', - '- -- -', - '--------', - ], -}; - -export const reserved: Map = { - name: 'Reserved', - category: '8x8', - author: 'Aya', - data: [ - 'w------b', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - 'b------w', - ], -}; - -export const x: Map = { - name: 'X', - category: '8x8', - author: 'Aya', - data: [ - 'w------b', - '-w----b-', - '--w--b--', - '---wb---', - '---bw---', - '--b--w--', - '-b----w-', - 'b------w', - ], -}; - -export const parallel: Map = { - name: 'Parallel', - category: '8x8', - author: 'Aya', - data: [ - '--------', - '--------', - '--------', - '---bb---', - '---ww---', - '--------', - '--------', - '--------', - ], -}; - -export const lackOfBlack: Map = { - name: 'Lack of Black', - category: '8x8', - data: [ - '--------', - '--------', - '--------', - '---w----', - '---bw---', - '--------', - '--------', - '--------', - ], -}; - -export const squareParty: Map = { - name: 'Square Party', - category: '8x8', - author: 'syuilo', - data: [ - '--------', - '-wwwbbb-', - '-w-wb-b-', - '-wwwbbb-', - '-bbbwww-', - '-b-bw-w-', - '-bbbwww-', - '--------', - ], -}; - -export const minesweeper: Map = { - name: 'Minesweeper', - category: '8x8', - author: 'syuilo', - data: [ - 'b-b--w-w', - '-w-wb-b-', - 'w-b--w-b', - '-b-wb-w-', - '-w-bw-b-', - 'b-w--b-w', - '-b-bw-w-', - 'w-w--b-b', - ], -}; - -export const tenthtenth: Map = { - name: '10x10', - category: '10x10', - data: [ - '----------', - '----------', - '----------', - '----------', - '----wb----', - '----bw----', - '----------', - '----------', - '----------', - '----------', - ], -}; - -export const hole: Map = { - name: 'The Hole', - category: '10x10', - author: 'syuilo', - data: [ - '----------', - '----------', - '--wb--wb--', - '--bw--bw--', - '---- ----', - '---- ----', - '--wb--wb--', - '--bw--bw--', - '----------', - '----------', - ], -}; - -export const grid: Map = { - name: 'Grid', - category: '10x10', - author: 'syuilo', - data: [ - '----------', - '- - -- - -', - '----------', - '- - -- - -', - '----wb----', - '----bw----', - '- - -- - -', - '----------', - '- - -- - -', - '----------', - ], -}; - -export const cross: Map = { - name: 'Cross', - category: '10x10', - author: 'Aya', - data: [ - ' ---- ', - ' ---- ', - ' ---- ', - '----------', - '----wb----', - '----bw----', - '----------', - ' ---- ', - ' ---- ', - ' ---- ', - ], -}; - -export const charX: Map = { - name: 'Char X', - category: '10x10', - author: 'syuilo', - data: [ - '--- ---', - '---- ----', - '----------', - ' -------- ', - ' --wb-- ', - ' --bw-- ', - ' -------- ', - '----------', - '---- ----', - '--- ---', - ], -}; - -export const charY: Map = { - name: 'Char Y', - category: '10x10', - author: 'syuilo', - data: [ - '--- ---', - '---- ----', - '----------', - ' -------- ', - ' --wb-- ', - ' --bw-- ', - ' ------ ', - ' ------ ', - ' ------ ', - ' ------ ', - ], -}; - -export const walls: Map = { - name: 'Walls', - category: '10x10', - author: 'Aya', - data: [ - ' bbbbbbbb ', - 'w--------w', - 'w--------w', - 'w--------w', - 'w---wb---w', - 'w---bw---w', - 'w--------w', - 'w--------w', - 'w--------w', - ' bbbbbbbb ', - ], -}; - -export const cpu: Map = { - name: 'CPU', - category: '10x10', - author: 'syuilo', - data: [ - ' b b b b ', - 'w--------w', - ' -------- ', - 'w--------w', - ' ---wb--- ', - ' ---bw--- ', - 'w--------w', - ' -------- ', - 'w--------w', - ' b b b b ', - ], -}; - -export const checker: Map = { - name: 'Checker', - category: '10x10', - author: 'Aya', - data: [ - '----------', - '----------', - '----------', - '---wbwb---', - '---bwbw---', - '---wbwb---', - '---bwbw---', - '----------', - '----------', - '----------', - ], -}; - -export const japaneseCurry: Map = { - name: 'Japanese curry', - category: '10x10', - author: 'syuilo', - data: [ - 'w-b-b-b-b-', - '-w-b-b-b-b', - 'w-w-b-b-b-', - '-w-w-b-b-b', - 'w-w-wwb-b-', - '-w-wbb-b-b', - 'w-w-w-b-b-', - '-w-w-w-b-b', - 'w-w-w-w-b-', - '-w-w-w-w-b', - ], -}; - -export const mosaic: Map = { - name: 'Mosaic', - category: '10x10', - author: 'syuilo', - data: [ - '- - - - - ', - ' - - - - -', - '- - - - - ', - ' - w w - -', - '- - b b - ', - ' - w w - -', - '- - b b - ', - ' - - - - -', - '- - - - - ', - ' - - - - -', - ], -}; - -export const arena: Map = { - name: 'Arena', - category: '10x10', - author: 'syuilo', - data: [ - '- - -- - -', - ' - - - - ', - '- ------ -', - ' -------- ', - '- --wb-- -', - '- --bw-- -', - ' -------- ', - '- ------ -', - ' - - - - ', - '- - -- - -', - ], -}; - -export const reactor: Map = { - name: 'Reactor', - category: '10x10', - author: 'syuilo', - data: [ - '-w------b-', - 'b- - - -w', - '- --wb-- -', - '---b w---', - '- b wb w -', - '- w bw b -', - '---w b---', - '- --bw-- -', - 'w- - - -b', - '-b------w-', - ], -}; - -export const sixeight: Map = { - name: '6x8', - category: 'Special', - data: [ - '------', - '------', - '------', - '--wb--', - '--bw--', - '------', - '------', - '------', - ], -}; - -export const spark: Map = { - name: 'Spark', - category: 'Special', - author: 'syuilo', - data: [ - ' - - ', - '----------', - ' -------- ', - ' -------- ', - ' ---wb--- ', - ' ---bw--- ', - ' -------- ', - ' -------- ', - '----------', - ' - - ', - ], -}; - -export const islands: Map = { - name: 'Islands', - category: 'Special', - author: 'syuilo', - data: [ - '-------- ', - '---wb--- ', - '---bw--- ', - '-------- ', - ' - - ', - ' - - ', - ' --------', - ' --------', - ' --------', - ' --------', - ], -}; - -export const galaxy: Map = { - name: 'Galaxy', - category: 'Special', - author: 'syuilo', - data: [ - ' ------ ', - ' --www--- ', - ' ------w--- ', - '---bbb--w---', - '--b---b-w-b-', - '-b--wwb-w-b-', - '-b-w-bww--b-', - '-b-w-b---b--', - '---w--bbb---', - ' ---w------ ', - ' ---www-- ', - ' ------ ', - ], -}; - -export const triangle: Map = { - name: 'Triangle', - category: 'Special', - author: 'syuilo', - data: [ - ' -- ', - ' -- ', - ' ---- ', - ' ---- ', - ' --wb-- ', - ' --bw-- ', - ' -------- ', - ' -------- ', - '----------', - '----------', - ], -}; - -export const iphonex: Map = { - name: 'iPhone X', - category: 'Special', - author: 'syuilo', - data: [ - ' -- -- ', - '--------', - '--------', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - '--------', - '--------', - ' ------ ', - ], -}; - -export const dealWithIt: Map = { - name: 'Deal with it!', - category: 'Special', - author: 'syuilo', - data: [ - '------------', - '--w-b-------', - ' --b-w------', - ' --w-b---- ', - ' ------- ', - ], -}; - -export const experiment: Map = { - name: 'Let\'s experiment', - category: 'Special', - author: 'syuilo', - data: [ - ' ------------ ', - '------wb------', - '------bw------', - '--------------', - ' - - ', - '------ ------', - 'bbbbbb wwwwww', - 'bbbbbb wwwwww', - 'bbbbbb wwwwww', - 'bbbbbb wwwwww', - 'wwwwww bbbbbb', - ], -}; - -export const bigBoard: Map = { - name: 'Big board', - category: 'Special', - data: [ - '----------------', - '----------------', - '----------------', - '----------------', - '----------------', - '----------------', - '----------------', - '-------wb-------', - '-------bw-------', - '----------------', - '----------------', - '----------------', - '----------------', - '----------------', - '----------------', - '----------------', - ], -}; - -export const twoBoard: Map = { - name: 'Two board', - category: 'Special', - author: 'Aya', - data: [ - '-------- --------', - '-------- --------', - '-------- --------', - '---wb--- ---wb---', - '---bw--- ---bw---', - '-------- --------', - '-------- --------', - '-------- --------', - ], -}; - -export const test1: Map = { - name: 'Test1', - category: 'Test', - data: [ - '--------', - '---wb---', - '---bw---', - '--------', - ], -}; - -export const test2: Map = { - name: 'Test2', - category: 'Test', - data: [ - '------', - '------', - '-b--w-', - '-w--b-', - '-w--b-', - ], -}; - -export const test3: Map = { - name: 'Test3', - category: 'Test', - data: [ - '-w-', - '--w', - 'w--', - '-w-', - '--w', - 'w--', - '-w-', - '--w', - 'w--', - '-w-', - '---', - 'b--', - ], -}; - -export const test4: Map = { - name: 'Test4', - category: 'Test', - data: [ - '-w--b-', - '-w--b-', - '------', - '-w--b-', - '-w--b-', - ], -}; - -// 検証用: この盤面で藍(lv3)が黒で始めると何故か(?)A1に打ってしまう -export const test6: Map = { - name: 'Test6', - category: 'Test', - data: [ - '--wwwww-', - 'wwwwwwww', - 'wbbbwbwb', - 'wbbbbwbb', - 'wbwbbwbb', - 'wwbwbbbb', - '--wbbbbb', - '-wwwww--', - ], -}; - -// 検証用: この盤面で藍(lv3)が黒で始めると何故か(?)G7に打ってしまう -export const test7: Map = { - name: 'Test7', - category: 'Test', - data: [ - 'b--w----', - 'b-wwww--', - 'bwbwwwbb', - 'wbwwwwb-', - 'wwwwwww-', - '-wwbbwwb', - '--wwww--', - '--wwww--', - ], -}; - -// 検証用: この盤面で藍(lv5)が黒で始めると何故か(?)A1に打ってしまう -export const test8: Map = { - name: 'Test8', - category: 'Test', - data: [ - '--------', - '-----w--', - 'w--www--', - 'wwwwww--', - 'bbbbwww-', - 'wwwwww--', - '--www---', - '--ww----', - ], -}; diff --git a/packages/backend/src/games/reversi/package.json b/packages/backend/src/games/reversi/package.json deleted file mode 100644 index a4415ad141..0000000000 --- a/packages/backend/src/games/reversi/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "misskey-reversi", - "version": "0.0.5", - "description": "Misskey reversi engine", - "keywords": [ - "misskey" - ], - "author": "syuilo <i@syuilo.com>", - "license": "MIT", - "repository": "https://github.com/misskey-dev/misskey.git", - "bugs": "https://github.com/misskey-dev/misskey/issues", - "main": "./built/core.js", - "types": "./built/core.d.ts", - "scripts": { - "build": "tsc" - }, - "dependencies": {} -} diff --git a/packages/backend/src/games/reversi/tsconfig.json b/packages/backend/src/games/reversi/tsconfig.json deleted file mode 100644 index 851fb6b7e4..0000000000 --- a/packages/backend/src/games/reversi/tsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "compilerOptions": { - "noEmitOnError": false, - "noImplicitAny": false, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "experimentalDecorators": true, - "declaration": true, - "sourceMap": false, - "target": "es2017", - "module": "commonjs", - "removeComments": false, - "noLib": false, - "outDir": "./built", - "rootDir": "./" - }, - "compileOnSave": false, - "include": [ - "./core.ts" - ] -} diff --git a/packages/backend/src/misc/gen-avatar.ts b/packages/backend/src/misc/gen-identicon.ts index 8838ec8d15..5cedd7afaf 100644 --- a/packages/backend/src/misc/gen-avatar.ts +++ b/packages/backend/src/misc/gen-identicon.ts @@ -1,5 +1,6 @@ /** - * Random avatar generator + * Identicon generator + * https://en.wikipedia.org/wiki/Identicon */ import * as p from 'pureimage'; @@ -34,9 +35,9 @@ const cellSize = actualSize / n; const sideN = Math.floor(n / 2); /** - * Generate buffer of random avatar by seed + * Generate buffer of an identicon by seed */ -export function genAvatar(seed: string, stream: WriteStream): Promise<void> { +export function genIdenticon(seed: string, stream: WriteStream): Promise<void> { const rand = gen.create(seed); const canvas = p.make(size, size); const ctx = canvas.getContext('2d'); diff --git a/packages/backend/src/misc/populate-emojis.ts b/packages/backend/src/misc/populate-emojis.ts index b021ec46eb..26c05e5fa6 100644 --- a/packages/backend/src/misc/populate-emojis.ts +++ b/packages/backend/src/misc/populate-emojis.ts @@ -62,7 +62,8 @@ export async function populateEmoji(emojiName: string, noteUserHost: string | nu if (emoji == null) return null; const isLocal = emoji.host == null; - const url = isLocal ? emoji.url : `${config.url}/proxy/image.png?${query({ url: emoji.url })}`; + const emojiUrl = emoji.publicUrl || emoji.originalUrl; // || emoji.originalUrl してるのは後方互換性のため + const url = isLocal ? emojiUrl : `${config.url}/proxy/image.png?${query({ url: emojiUrl })}`; return { name: emojiName, @@ -116,7 +117,7 @@ export async function prefetchEmojis(emojis: { name: string; host: string | null } const _emojis = emojisQuery.length > 0 ? await Emojis.find({ where: emojisQuery, - select: ['name', 'host', 'url'], + select: ['name', 'host', 'originalUrl', 'publicUrl'], }) : []; for (const emoji of _emojis) { cache.set(`${emoji.name} ${emoji.host}`, emoji); diff --git a/packages/backend/src/misc/schema.ts b/packages/backend/src/misc/schema.ts index 4131875ef7..2dae954af9 100644 --- a/packages/backend/src/misc/schema.ts +++ b/packages/backend/src/misc/schema.ts @@ -1,32 +1,44 @@ -import { SimpleObj, SimpleSchema } from './simple-schema'; -import { packedUserSchema } from '@/models/repositories/user'; -import { packedNoteSchema } from '@/models/repositories/note'; -import { packedUserListSchema } from '@/models/repositories/user-list'; -import { packedAppSchema } from '@/models/repositories/app'; -import { packedMessagingMessageSchema } from '@/models/repositories/messaging-message'; -import { packedNotificationSchema } from '@/models/repositories/notification'; -import { packedDriveFileSchema } from '@/models/repositories/drive-file'; -import { packedDriveFolderSchema } from '@/models/repositories/drive-folder'; -import { packedFollowingSchema } from '@/models/repositories/following'; -import { packedMutingSchema } from '@/models/repositories/muting'; -import { packedBlockingSchema } from '@/models/repositories/blocking'; -import { packedNoteReactionSchema } from '@/models/repositories/note-reaction'; -import { packedHashtagSchema } from '@/models/repositories/hashtag'; -import { packedPageSchema } from '@/models/repositories/page'; -import { packedUserGroupSchema } from '@/models/repositories/user-group'; -import { packedNoteFavoriteSchema } from '@/models/repositories/note-favorite'; -import { packedChannelSchema } from '@/models/repositories/channel'; -import { packedAntennaSchema } from '@/models/repositories/antenna'; -import { packedClipSchema } from '@/models/repositories/clip'; -import { packedFederationInstanceSchema } from '@/models/repositories/federation-instance'; -import { packedQueueCountSchema } from '@/models/repositories/queue'; -import { packedGalleryPostSchema } from '@/models/repositories/gallery-post'; -import { packedEmojiSchema } from '@/models/repositories/emoji'; -import { packedReversiGameSchema } from '@/models/repositories/games/reversi/game'; -import { packedReversiMatchingSchema } from '@/models/repositories/games/reversi/matching'; +import { + packedUserLiteSchema, + packedUserDetailedNotMeOnlySchema, + packedMeDetailedOnlySchema, + packedUserDetailedNotMeSchema, + packedMeDetailedSchema, + packedUserDetailedSchema, + packedUserSchema, +} from '@/models/schema/user'; +import { packedNoteSchema } from '@/models/schema/note'; +import { packedUserListSchema } from '@/models/schema/user-list'; +import { packedAppSchema } from '@/models/schema/app'; +import { packedMessagingMessageSchema } from '@/models/schema/messaging-message'; +import { packedNotificationSchema } from '@/models/schema/notification'; +import { packedDriveFileSchema } from '@/models/schema/drive-file'; +import { packedDriveFolderSchema } from '@/models/schema/drive-folder'; +import { packedFollowingSchema } from '@/models/schema/following'; +import { packedMutingSchema } from '@/models/schema/muting'; +import { packedBlockingSchema } from '@/models/schema/blocking'; +import { packedNoteReactionSchema } from '@/models/schema/note-reaction'; +import { packedHashtagSchema } from '@/models/schema/hashtag'; +import { packedPageSchema } from '@/models/schema/page'; +import { packedUserGroupSchema } from '@/models/schema/user-group'; +import { packedNoteFavoriteSchema } from '@/models/schema/note-favorite'; +import { packedChannelSchema } from '@/models/schema/channel'; +import { packedAntennaSchema } from '@/models/schema/antenna'; +import { packedClipSchema } from '@/models/schema/clip'; +import { packedFederationInstanceSchema } from '@/models/schema/federation-instance'; +import { packedQueueCountSchema } from '@/models/schema/queue'; +import { packedGalleryPostSchema } from '@/models/schema/gallery-post'; +import { packedEmojiSchema } from '@/models/schema/emoji'; export const refs = { + UserLite: packedUserLiteSchema, + UserDetailedNotMeOnly: packedUserDetailedNotMeOnlySchema, + MeDetailedOnly: packedMeDetailedOnlySchema, + UserDetailedNotMe: packedUserDetailedNotMeSchema, + MeDetailed: packedMeDetailedSchema, + UserDetailed: packedUserDetailedSchema, User: packedUserSchema, + UserList: packedUserListSchema, UserGroup: packedUserGroupSchema, App: packedAppSchema, @@ -49,16 +61,52 @@ export const refs = { FederationInstance: packedFederationInstanceSchema, GalleryPost: packedGalleryPostSchema, Emoji: packedEmojiSchema, - ReversiGame: packedReversiGameSchema, - ReversiMatching: packedReversiMatchingSchema, }; -export type Packed<x extends keyof typeof refs> = ObjType<(typeof refs[x])['properties']>; +// Packed = SchemaTypeDef<typeof refs[x]>; とすると展開されてマウスホバー時に型表示が使い物にならなくなる +// ObjType<r['properties']>を指定すると(なぜか)展開されずにPacked<'Hoge'>と表示される +type PackedDef<r extends { properties?: Obj; oneOf?: ReadonlyArray<MinimumSchema>; allOf?: ReadonlyArray<MinimumSchema> }> = + r['allOf'] extends ReadonlyArray<MinimumSchema> ? UnionToIntersection<UnionSchemaType<r['allOf']>> : + r['oneOf'] extends ReadonlyArray<MinimumSchema> ? UnionSchemaType<r['oneOf']> : + r['properties'] extends Obj ? ObjType<r['properties']> : + never; +export type Packed<x extends keyof typeof refs> = PackedDef<typeof refs[x]>; + +type TypeStringef = 'boolean' | 'number' | 'string' | 'array' | 'object' | 'any'; +type StringDefToType<T extends TypeStringef> = + T extends 'boolean' ? boolean : + T extends 'number' ? number : + T extends 'string' ? string | Date : + T extends 'array' ? ReadonlyArray<any> : + T extends 'object' ? Record<string, any> : + any; + +// https://swagger.io/specification/?sbsearch=optional#schema-object +type OfSchema = { + readonly anyOf?: ReadonlyArray<MinimumSchema>; + readonly oneOf?: ReadonlyArray<MinimumSchema>; + readonly allOf?: ReadonlyArray<MinimumSchema>; +} + +export interface MinimumSchema extends OfSchema { + readonly type?: TypeStringef; + readonly nullable?: boolean; + readonly optional?: boolean; + readonly items?: MinimumSchema; + readonly properties?: Obj; + readonly description?: string; + readonly example?: any; + readonly format?: string; + readonly ref?: keyof typeof refs; + readonly enum?: ReadonlyArray<string>; + readonly default?: (this['type'] extends TypeStringef ? StringDefToType<this['type']> : any) | null; + readonly maxLength?: number; + readonly minLength?: number; +} -export interface Schema extends SimpleSchema { - items?: Schema; - properties?: Obj; - ref?: keyof typeof refs; +export interface Schema extends MinimumSchema { + readonly nullable: boolean; + readonly optional: boolean; } type NonUndefinedPropertyNames<T extends Obj> = { @@ -69,22 +117,13 @@ type UndefinedPropertyNames<T extends Obj> = { [K in keyof T]: T[K]['optional'] extends true ? K : never }[keyof T]; -type OnlyRequired<T extends Obj> = Pick<T, NonUndefinedPropertyNames<T>>; -type OnlyOptional<T extends Obj> = Pick<T, UndefinedPropertyNames<T>>; - -export interface Obj extends SimpleObj { [key: string]: Schema; } +export interface Obj { [key: string]: Schema; } export type ObjType<s extends Obj> = - { [P in keyof OnlyOptional<s>]?: SchemaType<s[P]> } & - { [P in keyof OnlyRequired<s>]: SchemaType<s[P]> }; - -// https://qiita.com/hrsh7th@github/items/84e8968c3601009cdcf2 -type MyType<T extends Schema> = { - 0: any; - 1: SchemaType<T>; -}[T extends Schema ? 1 : 0]; + { -readonly [P in UndefinedPropertyNames<s>]?: SchemaType<s[P]> } & + { -readonly [P in NonUndefinedPropertyNames<s>]: SchemaType<s[P]> }; -type NullOrUndefined<p extends Schema, T> = +type NullOrUndefined<p extends MinimumSchema, T> = p['nullable'] extends true ? p['optional'] extends true ? (T | null | undefined) @@ -93,15 +132,41 @@ type NullOrUndefined<p extends Schema, T> = ? (T | undefined) : T; -export type SchemaType<p extends Schema> = - p['type'] extends 'number' ? NullOrUndefined<p, number> : - p['type'] extends 'string' ? NullOrUndefined<p, string> : - p['type'] extends 'boolean' ? NullOrUndefined<p, boolean> : - p['type'] extends 'array' ? NullOrUndefined<p, MyType<NonNullable<p['items']>>[]> : +// 共用体型を交差型にする型 https://stackoverflow.com/questions/54938141/typescript-convert-union-to-intersection +type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never; + +// https://github.com/misskey-dev/misskey/pull/8144#discussion_r785287552 +// 単純にSchemaTypeDef<X>で判定するだけではダメ +type UnionSchemaType<a extends readonly any[], X extends MinimumSchema = a[number]> = X extends any ? SchemaType<X> : never; +type ArrayUnion<T> = T extends any ? Array<T> : never; + +export type SchemaTypeDef<p extends MinimumSchema> = + p['type'] extends 'number' ? number : + p['type'] extends 'string' ? ( + p['enum'] extends readonly string[] ? + p['enum'][number] : + p['format'] extends 'date-time' ? string : // Dateにする?? + string + ) : + p['type'] extends 'boolean' ? boolean : p['type'] extends 'object' ? ( - p['ref'] extends keyof typeof refs - ? NullOrUndefined<p, Packed<p['ref']>> - : NullOrUndefined<p, ObjType<NonNullable<p['properties']>>> + p['ref'] extends keyof typeof refs ? Packed<p['ref']> : + p['properties'] extends NonNullable<Obj> ? ObjType<p['properties']> : + p['anyOf'] extends ReadonlyArray<MinimumSchema> ? UnionSchemaType<p['anyOf']> & Partial<UnionToIntersection<UnionSchemaType<p['anyOf']>>> : + p['allOf'] extends ReadonlyArray<MinimumSchema> ? UnionToIntersection<UnionSchemaType<p['allOf']>> : + any + ) : + p['type'] extends 'array' ? ( + p['items'] extends OfSchema ? ( + p['items']['anyOf'] extends ReadonlyArray<MinimumSchema> ? UnionSchemaType<NonNullable<p['items']['anyOf']>>[] : + p['items']['oneOf'] extends ReadonlyArray<MinimumSchema> ? ArrayUnion<UnionSchemaType<NonNullable<p['items']['oneOf']>>> : + p['items']['allOf'] extends ReadonlyArray<MinimumSchema> ? UnionToIntersection<UnionSchemaType<NonNullable<p['items']['allOf']>>>[] : + never + ) : + p['items'] extends NonNullable<MinimumSchema> ? SchemaTypeDef<p['items']>[] : + any[] ) : - p['type'] extends 'any' ? NullOrUndefined<p, any> : + p['oneOf'] extends ReadonlyArray<MinimumSchema> ? UnionSchemaType<p['oneOf']> : any; + +export type SchemaType<p extends MinimumSchema> = NullOrUndefined<p, SchemaTypeDef<p>>; diff --git a/packages/backend/src/misc/simple-schema.ts b/packages/backend/src/misc/simple-schema.ts deleted file mode 100644 index abbb348e24..0000000000 --- a/packages/backend/src/misc/simple-schema.ts +++ /dev/null @@ -1,15 +0,0 @@ -export interface SimpleSchema { - type: 'boolean' | 'number' | 'string' | 'array' | 'object' | 'any'; - nullable: boolean; - optional: boolean; - items?: SimpleSchema; - properties?: SimpleObj; - description?: string; - example?: any; - format?: string; - ref?: string; - enum?: string[]; - default?: boolean | null; -} - -export interface SimpleObj { [key: string]: SimpleSchema; } diff --git a/packages/backend/src/models/entities/abuse-user-report.ts b/packages/backend/src/models/entities/abuse-user-report.ts index 019d613f76..27c1e47fd8 100644 --- a/packages/backend/src/models/entities/abuse-user-report.ts +++ b/packages/backend/src/models/entities/abuse-user-report.ts @@ -51,6 +51,11 @@ export class AbuseUserReport { }) public resolved: boolean; + @Column('boolean', { + default: false + }) + public forwarded: boolean; + @Column('varchar', { length: 2048, }) diff --git a/packages/backend/src/models/entities/drive-file.ts b/packages/backend/src/models/entities/drive-file.ts index 0af52d7cc0..cec86880f5 100644 --- a/packages/backend/src/models/entities/drive-file.ts +++ b/packages/backend/src/models/entities/drive-file.ts @@ -101,6 +101,11 @@ export class DriveFile { }) public webpublicUrl: string | null; + @Column('varchar', { + length: 128, nullable: true, + }) + public webpublicType: string | null; + @Index({ unique: true }) @Column('varchar', { length: 256, nullable: true, diff --git a/packages/backend/src/models/entities/emoji.ts b/packages/backend/src/models/entities/emoji.ts index 1146908a88..2e9c11d21c 100644 --- a/packages/backend/src/models/entities/emoji.ts +++ b/packages/backend/src/models/entities/emoji.ts @@ -32,13 +32,19 @@ export class Emoji { @Column('varchar', { length: 512, }) - public url: string; + public originalUrl: string; + + @Column('varchar', { + length: 512, + }) + public publicUrl: string; @Column('varchar', { length: 512, nullable: true, }) public uri: string | null; + // publicUrlの方のtypeが入る @Column('varchar', { length: 64, nullable: true, }) diff --git a/packages/backend/src/models/entities/games/reversi/game.ts b/packages/backend/src/models/entities/games/reversi/game.ts deleted file mode 100644 index fe9b8a5ba5..0000000000 --- a/packages/backend/src/models/entities/games/reversi/game.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { User } from '../../user'; -import { id } from '../../../id'; - -@Entity() -export class ReversiGame { - @PrimaryColumn(id()) - public id: string; - - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the ReversiGame.', - }) - public createdAt: Date; - - @Column('timestamp with time zone', { - nullable: true, - comment: 'The started date of the ReversiGame.', - }) - public startedAt: Date | null; - - @Column(id()) - public user1Id: User['id']; - - @ManyToOne(type => User, { - onDelete: 'CASCADE', - }) - @JoinColumn() - public user1: User | null; - - @Column(id()) - public user2Id: User['id']; - - @ManyToOne(type => User, { - onDelete: 'CASCADE', - }) - @JoinColumn() - public user2: User | null; - - @Column('boolean', { - default: false, - }) - public user1Accepted: boolean; - - @Column('boolean', { - default: false, - }) - public user2Accepted: boolean; - - /** - * どちらのプレイヤーが先行(黒)か - * 1 ... user1 - * 2 ... user2 - */ - @Column('integer', { - nullable: true, - }) - public black: number | null; - - @Column('boolean', { - default: false, - }) - public isStarted: boolean; - - @Column('boolean', { - default: false, - }) - public isEnded: boolean; - - @Column({ - ...id(), - nullable: true, - }) - public winnerId: User['id'] | null; - - @Column({ - ...id(), - nullable: true, - }) - public surrendered: User['id'] | null; - - @Column('jsonb', { - default: [], - }) - public logs: { - at: Date; - color: boolean; - pos: number; - }[]; - - @Column('varchar', { - array: true, length: 64, - }) - public map: string[]; - - @Column('varchar', { - length: 32, - }) - public bw: string; - - @Column('boolean', { - default: false, - }) - public isLlotheo: boolean; - - @Column('boolean', { - default: false, - }) - public canPutEverywhere: boolean; - - @Column('boolean', { - default: false, - }) - public loopedBoard: boolean; - - @Column('jsonb', { - nullable: true, default: null, - }) - public form1: any | null; - - @Column('jsonb', { - nullable: true, default: null, - }) - public form2: any | null; - - /** - * ログのposを文字列としてすべて連結したもののCRC32値 - */ - @Column('varchar', { - length: 32, nullable: true, - }) - public crc32: string | null; -} diff --git a/packages/backend/src/models/entities/games/reversi/matching.ts b/packages/backend/src/models/entities/games/reversi/matching.ts deleted file mode 100644 index 70bb555e2f..0000000000 --- a/packages/backend/src/models/entities/games/reversi/matching.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; -import { User } from '../../user'; -import { id } from '../../../id'; - -@Entity() -export class ReversiMatching { - @PrimaryColumn(id()) - public id: string; - - @Index() - @Column('timestamp with time zone', { - comment: 'The created date of the ReversiMatching.', - }) - public createdAt: Date; - - @Index() - @Column(id()) - public parentId: User['id']; - - @ManyToOne(type => User, { - onDelete: 'CASCADE', - }) - @JoinColumn() - public parent: User | null; - - @Index() - @Column(id()) - public childId: User['id']; - - @ManyToOne(type => User, { - onDelete: 'CASCADE', - }) - @JoinColumn() - public child: User | null; -} diff --git a/packages/backend/src/models/entities/user-profile.ts b/packages/backend/src/models/entities/user-profile.ts index eb8cdadd19..d8317de8d3 100644 --- a/packages/backend/src/models/entities/user-profile.ts +++ b/packages/backend/src/models/entities/user-profile.ts @@ -124,6 +124,7 @@ export class UserProfile { }) public clientData: Record<string, any>; + // TODO: そのうち消す @Column('jsonb', { default: {}, comment: 'The room data of the User.', diff --git a/packages/backend/src/models/index.ts b/packages/backend/src/models/index.ts index 7154cca550..67da347395 100644 --- a/packages/backend/src/models/index.ts +++ b/packages/backend/src/models/index.ts @@ -18,7 +18,6 @@ import { AccessToken } from './entities/access-token'; import { UserNotePining } from './entities/user-note-pining'; import { SigninRepository } from './repositories/signin'; import { MessagingMessageRepository } from './repositories/messaging-message'; -import { ReversiGameRepository } from './repositories/games/reversi/game'; import { UserListRepository } from './repositories/user-list'; import { UserListJoining } from './entities/user-list-joining'; import { UserGroupRepository } from './repositories/user-group'; @@ -30,7 +29,6 @@ import { BlockingRepository } from './repositories/blocking'; import { NoteReactionRepository } from './repositories/note-reaction'; import { NotificationRepository } from './repositories/notification'; import { NoteFavoriteRepository } from './repositories/note-favorite'; -import { ReversiMatchingRepository } from './repositories/games/reversi/matching'; import { UserPublickey } from './entities/user-publickey'; import { UserKeypair } from './entities/user-keypair'; import { AppRepository } from './repositories/app'; @@ -107,8 +105,6 @@ export const AuthSessions = getCustomRepository(AuthSessionRepository); export const AccessTokens = getRepository(AccessToken); export const Signins = getCustomRepository(SigninRepository); export const MessagingMessages = getCustomRepository(MessagingMessageRepository); -export const ReversiGames = getCustomRepository(ReversiGameRepository); -export const ReversiMatchings = getCustomRepository(ReversiMatchingRepository); export const Pages = getCustomRepository(PageRepository); export const PageLikes = getCustomRepository(PageLikeRepository); export const GalleryPosts = getCustomRepository(GalleryPostRepository); diff --git a/packages/backend/src/models/repositories/abuse-user-report.ts b/packages/backend/src/models/repositories/abuse-user-report.ts index 5e267b3c2b..943b65eb64 100644 --- a/packages/backend/src/models/repositories/abuse-user-report.ts +++ b/packages/backend/src/models/repositories/abuse-user-report.ts @@ -27,6 +27,7 @@ export class AbuseUserReportRepository extends Repository<AbuseUserReport> { assignee: report.assigneeId ? Users.pack(report.assignee || report.assigneeId, null, { detail: true, }) : null, + forwarded: report.forwarded, }); } diff --git a/packages/backend/src/models/repositories/antenna.ts b/packages/backend/src/models/repositories/antenna.ts index 548f44f1b7..3bf0645a7f 100644 --- a/packages/backend/src/models/repositories/antenna.ts +++ b/packages/backend/src/models/repositories/antenna.ts @@ -31,94 +31,3 @@ export class AntennaRepository extends Repository<Antenna> { }; } } - -export const packedAntennaSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - name: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - keywords: { - type: 'array' as const, - optional: false as const, nullable: false as const, - items: { - type: 'array' as const, - optional: false as const, nullable: false as const, - items: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - }, - }, - excludeKeywords: { - type: 'array' as const, - optional: false as const, nullable: false as const, - items: { - type: 'array' as const, - optional: false as const, nullable: false as const, - items: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - }, - }, - src: { - type: 'string' as const, - optional: false as const, nullable: false as const, - enum: ['home', 'all', 'users', 'list', 'group'], - }, - userListId: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'id', - }, - userGroupId: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'id', - }, - users: { - type: 'array' as const, - optional: false as const, nullable: false as const, - items: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - }, - caseSensitive: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - default: false, - }, - notify: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - withReplies: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - default: false, - }, - withFile: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - hasUnreadNote: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - default: false, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/app.ts b/packages/backend/src/models/repositories/app.ts index bec0765ac2..6bac4d9598 100644 --- a/packages/backend/src/models/repositories/app.ts +++ b/packages/backend/src/models/repositories/app.ts @@ -38,38 +38,3 @@ export class AppRepository extends Repository<App> { }; } } - -export const packedAppSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - name: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - callbackUrl: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - permission: { - type: 'array' as const, - optional: false as const, nullable: false as const, - items: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - }, - secret: { - type: 'string' as const, - optional: true as const, nullable: false as const, - }, - isAuthorized: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/blocking.ts b/packages/backend/src/models/repositories/blocking.ts index a6895eabf4..c20b02f501 100644 --- a/packages/backend/src/models/repositories/blocking.ts +++ b/packages/backend/src/models/repositories/blocking.ts @@ -30,31 +30,3 @@ export class BlockingRepository extends Repository<Blocking> { return Promise.all(blockings.map(x => this.pack(x, me))); } } - -export const packedBlockingSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - blockeeId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - blockee: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User' as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/channel.ts b/packages/backend/src/models/repositories/channel.ts index 0a6b02f495..b3afb823ab 100644 --- a/packages/backend/src/models/repositories/channel.ts +++ b/packages/backend/src/models/repositories/channel.ts @@ -40,56 +40,3 @@ export class ChannelRepository extends Repository<Channel> { }; } } - -export const packedChannelSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - lastNotedAt: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'date-time', - }, - name: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - description: { - type: 'string' as const, - nullable: true as const, optional: false as const, - }, - bannerUrl: { - type: 'string' as const, - format: 'url', - nullable: true as const, optional: false as const, - }, - notesCount: { - type: 'number' as const, - nullable: false as const, optional: false as const, - }, - usersCount: { - type: 'number' as const, - nullable: false as const, optional: false as const, - }, - isFollowing: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, - }, - userId: { - type: 'string' as const, - nullable: true as const, optional: false as const, - format: 'id', - }, - }, -}; diff --git a/packages/backend/src/models/repositories/clip.ts b/packages/backend/src/models/repositories/clip.ts index 7892811d48..6f9ceeb50a 100644 --- a/packages/backend/src/models/repositories/clip.ts +++ b/packages/backend/src/models/repositories/clip.ts @@ -29,42 +29,3 @@ export class ClipRepository extends Repository<Clip> { } } -export const packedClipSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - userId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - user: { - type: 'object' as const, - ref: 'User' as const, - optional: false as const, nullable: false as const, - }, - name: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - description: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - isPublic: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/drive-file.ts b/packages/backend/src/models/repositories/drive-file.ts index 79b890aa6e..44db9a0a58 100644 --- a/packages/backend/src/models/repositories/drive-file.ts +++ b/packages/backend/src/models/repositories/drive-file.ts @@ -3,7 +3,7 @@ import { DriveFile } from '@/models/entities/drive-file'; import { Users, DriveFolders } from '../index'; import { User } from '@/models/entities/user'; import { toPuny } from '@/misc/convert-host'; -import { awaitAll } from '@/prelude/await-all'; +import { awaitAll, Promiseable } from '@/prelude/await-all'; import { Packed } from '@/misc/schema'; import config from '@/config/index'; import { query, appendQuery } from '@/prelude/url'; @@ -126,7 +126,7 @@ export class DriveFileRepository extends Repository<DriveFile> { const meta = await fetchMeta(); - return await awaitAll({ + return await awaitAll<Packed<'DriveFile'>>({ id: file.id, createdAt: file.createdAt.toISOString(), name: file.name, @@ -156,112 +156,3 @@ export class DriveFileRepository extends Repository<DriveFile> { return items.filter(x => x != null); } } - -export const packedDriveFileSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - name: { - type: 'string' as const, - optional: false as const, nullable: false as const, - example: 'lenna.jpg', - }, - type: { - type: 'string' as const, - optional: false as const, nullable: false as const, - example: 'image/jpeg', - }, - md5: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'md5', - example: '15eca7fba0480996e2245f5185bf39f2', - }, - size: { - type: 'number' as const, - optional: false as const, nullable: false as const, - example: 51469, - }, - isSensitive: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - blurhash: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - properties: { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - width: { - type: 'number' as const, - optional: true as const, nullable: false as const, - example: 1280, - }, - height: { - type: 'number' as const, - optional: true as const, nullable: false as const, - example: 720, - }, - orientation: { - type: 'number' as const, - optional: true as const, nullable: false as const, - example: 8, - }, - avgColor: { - type: 'string' as const, - optional: true as const, nullable: false as const, - example: 'rgb(40,65,87)', - }, - }, - }, - url: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'url', - }, - thumbnailUrl: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'url', - }, - comment: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - folderId: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - folder: { - type: 'object' as const, - optional: true as const, nullable: true as const, - ref: 'DriveFolder' as const, - }, - userId: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - user: { - type: 'object' as const, - optional: true as const, nullable: true as const, - ref: 'User' as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/drive-folder.ts b/packages/backend/src/models/repositories/drive-folder.ts index 4ee4a68e08..b2e6cee9b8 100644 --- a/packages/backend/src/models/repositories/drive-folder.ts +++ b/packages/backend/src/models/repositories/drive-folder.ts @@ -48,44 +48,3 @@ export class DriveFolderRepository extends Repository<DriveFolder> { }); } } - -export const packedDriveFolderSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - name: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - foldersCount: { - type: 'number' as const, - optional: true as const, nullable: false as const, - }, - filesCount: { - type: 'number' as const, - optional: true as const, nullable: false as const, - }, - parentId: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - parent: { - type: 'object' as const, - optional: true as const, nullable: true as const, - ref: 'DriveFolder' as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/emoji.ts b/packages/backend/src/models/repositories/emoji.ts index b7529595a9..b9dc6ed0ac 100644 --- a/packages/backend/src/models/repositories/emoji.ts +++ b/packages/backend/src/models/repositories/emoji.ts @@ -15,7 +15,8 @@ export class EmojiRepository extends Repository<Emoji> { name: emoji.name, category: emoji.category, host: emoji.host, - url: emoji.url, + // || emoji.originalUrl してるのは後方互換性のため + url: emoji.publicUrl || emoji.originalUrl, }; } @@ -25,41 +26,3 @@ export class EmojiRepository extends Repository<Emoji> { return Promise.all(emojis.map(x => this.pack(x))); } } - -export const packedEmojiSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - aliases: { - type: 'array' as const, - optional: false as const, nullable: false as const, - items: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - }, - name: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - category: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - host: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - url: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/federation-instance.ts b/packages/backend/src/models/repositories/federation-instance.ts index 90dbbaab1c..426fd5bfc3 100644 --- a/packages/backend/src/models/repositories/federation-instance.ts +++ b/packages/backend/src/models/repositories/federation-instance.ts @@ -1,106 +1,2 @@ import config from '@/config/index'; -export const packedFederationInstanceSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - caughtAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - host: { - type: 'string' as const, - optional: false as const, nullable: false as const, - example: 'misskey.example.com', - }, - usersCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - notesCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - followingCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - followersCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - driveUsage: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - driveFiles: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - latestRequestSentAt: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'date-time', - }, - lastCommunicatedAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - isNotResponding: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - isSuspended: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - softwareName: { - type: 'string' as const, - optional: false as const, nullable: true as const, - example: 'misskey', - }, - softwareVersion: { - type: 'string' as const, - optional: false as const, nullable: true as const, - example: config.version, - }, - openRegistrations: { - type: 'boolean' as const, - optional: false as const, nullable: true as const, - example: true, - }, - name: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - description: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - maintainerName: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - maintainerEmail: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - iconUrl: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'url', - }, - infoUpdatedAt: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'date-time', - }, - }, -}; diff --git a/packages/backend/src/models/repositories/following.ts b/packages/backend/src/models/repositories/following.ts index 1dfaaf908a..9d20f442df 100644 --- a/packages/backend/src/models/repositories/following.ts +++ b/packages/backend/src/models/repositories/following.ts @@ -84,41 +84,3 @@ export class FollowingRepository extends Repository<Following> { return Promise.all(followings.map(x => this.pack(x, me, opts))); } } - -export const packedFollowingSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - followeeId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - followee: { - type: 'object' as const, - optional: true as const, nullable: false as const, - ref: 'User' as const, - }, - followerId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - follower: { - type: 'object' as const, - optional: true as const, nullable: false as const, - ref: 'User' as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/gallery-post.ts b/packages/backend/src/models/repositories/gallery-post.ts index 6d37e3120e..e9233bb91e 100644 --- a/packages/backend/src/models/repositories/gallery-post.ts +++ b/packages/backend/src/models/repositories/gallery-post.ts @@ -38,74 +38,3 @@ export class GalleryPostRepository extends Repository<GalleryPost> { return Promise.all(posts.map(x => this.pack(x, me))); } } - -export const packedGalleryPostSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - updatedAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - title: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - description: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - userId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - user: { - type: 'object' as const, - ref: 'User' as const, - optional: false as const, nullable: false as const, - }, - fileIds: { - type: 'array' as const, - optional: true as const, nullable: false as const, - items: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - }, - files: { - type: 'array' as const, - optional: true as const, nullable: false as const, - items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'DriveFile' as const, - }, - }, - tags: { - type: 'array' as const, - optional: true as const, nullable: false as const, - items: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - }, - isSensitive: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/games/reversi/game.ts b/packages/backend/src/models/repositories/games/reversi/game.ts deleted file mode 100644 index a9e0496760..0000000000 --- a/packages/backend/src/models/repositories/games/reversi/game.ts +++ /dev/null @@ -1,191 +0,0 @@ -import { User } from '@/models/entities/user'; -import { EntityRepository, Repository } from 'typeorm'; -import { Users } from '../../../index'; -import { ReversiGame } from '@/models/entities/games/reversi/game'; -import { Packed } from '@/misc/schema'; - -@EntityRepository(ReversiGame) -export class ReversiGameRepository extends Repository<ReversiGame> { - public async pack( - src: ReversiGame['id'] | ReversiGame, - me?: { id: User['id'] } | null | undefined, - options?: { - detail?: boolean - } - ): Promise<Packed<'ReversiGame'>> { - const opts = Object.assign({ - detail: true, - }, options); - - const game = typeof src === 'object' ? src : await this.findOneOrFail(src); - - return { - id: game.id, - createdAt: game.createdAt.toISOString(), - startedAt: game.startedAt && game.startedAt.toISOString(), - isStarted: game.isStarted, - isEnded: game.isEnded, - form1: game.form1, - form2: game.form2, - user1Accepted: game.user1Accepted, - user2Accepted: game.user2Accepted, - user1Id: game.user1Id, - user2Id: game.user2Id, - user1: await Users.pack(game.user1Id, me), - user2: await Users.pack(game.user2Id, me), - winnerId: game.winnerId, - winner: game.winnerId ? await Users.pack(game.winnerId, me) : null, - surrendered: game.surrendered, - black: game.black, - bw: game.bw, - isLlotheo: game.isLlotheo, - canPutEverywhere: game.canPutEverywhere, - loopedBoard: game.loopedBoard, - ...(opts.detail ? { - logs: game.logs.map(log => ({ - at: log.at.toISOString(), - color: log.color, - pos: log.pos, - })), - map: game.map, - } : {}), - }; - } -} - -export const packedReversiGameSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - startedAt: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'date-time', - }, - isStarted: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - isEnded: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - form1: { - type: 'any' as const, - optional: false as const, nullable: true as const, - }, - form2: { - type: 'any' as const, - optional: false as const, nullable: true as const, - }, - user1Accepted: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - user2Accepted: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - user1Id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - user2Id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - user1: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User' as const, - }, - user2: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User' as const, - }, - winnerId: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - winner: { - type: 'object' as const, - optional: false as const, nullable: true as const, - ref: 'User' as const, - }, - surrendered: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - black: { - type: 'number' as const, - optional: false as const, nullable: true as const, - }, - bw: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - isLlotheo: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - canPutEverywhere: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - loopedBoard: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - logs: { - type: 'array' as const, - optional: true as const, nullable: false as const, - items: { - type: 'object' as const, - optional: true as const, nullable: false as const, - properties: { - at: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - color: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - pos: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - }, - }, - }, - map: { - type: 'array' as const, - optional: true as const, nullable: false as const, - items: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/games/reversi/matching.ts b/packages/backend/src/models/repositories/games/reversi/matching.ts deleted file mode 100644 index b55f598068..0000000000 --- a/packages/backend/src/models/repositories/games/reversi/matching.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { EntityRepository, Repository } from 'typeorm'; -import { ReversiMatching } from '@/models/entities/games/reversi/matching'; -import { Users } from '../../../index'; -import { awaitAll } from '@/prelude/await-all'; -import { User } from '@/models/entities/user'; -import { Packed } from '@/misc/schema'; - -@EntityRepository(ReversiMatching) -export class ReversiMatchingRepository extends Repository<ReversiMatching> { - public async pack( - src: ReversiMatching['id'] | ReversiMatching, - me: { id: User['id'] } - ): Promise<Packed<'ReversiMatching'>> { - const matching = typeof src === 'object' ? src : await this.findOneOrFail(src); - - return await awaitAll({ - id: matching.id, - createdAt: matching.createdAt.toISOString(), - parentId: matching.parentId, - parent: Users.pack(matching.parentId, me, { - detail: true, - }), - childId: matching.childId, - child: Users.pack(matching.childId, me, { - detail: true, - }), - }); - } -} - -export const packedReversiMatchingSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - parentId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - parent: { - type: 'object' as const, - optional: false as const, nullable: true as const, - ref: 'User' as const, - }, - childId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - child: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User' as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/hashtag.ts b/packages/backend/src/models/repositories/hashtag.ts index 6e513c7ebb..c4b8d50c4e 100644 --- a/packages/backend/src/models/repositories/hashtag.ts +++ b/packages/backend/src/models/repositories/hashtag.ts @@ -24,39 +24,3 @@ export class HashtagRepository extends Repository<Hashtag> { return Promise.all(hashtags.map(x => this.pack(x))); } } - -export const packedHashtagSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - tag: { - type: 'string' as const, - optional: false as const, nullable: false as const, - example: 'misskey', - }, - mentionedUsersCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - mentionedLocalUsersCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - mentionedRemoteUsersCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - attachedUsersCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - attachedLocalUsersCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - attachedRemoteUsersCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/messaging-message.ts b/packages/backend/src/models/repositories/messaging-message.ts index 1b2dd3a246..0a342430b9 100644 --- a/packages/backend/src/models/repositories/messaging-message.ts +++ b/packages/backend/src/models/repositories/messaging-message.ts @@ -42,78 +42,3 @@ export class MessagingMessageRepository extends Repository<MessagingMessage> { }; } } - -export const packedMessagingMessageSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - userId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - user: { - type: 'object' as const, - ref: 'User' as const, - optional: true as const, nullable: false as const, - }, - text: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - fileId: { - type: 'string' as const, - optional: true as const, nullable: true as const, - format: 'id', - }, - file: { - type: 'object' as const, - optional: true as const, nullable: true as const, - ref: 'DriveFile' as const, - }, - recipientId: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'id', - }, - recipient: { - type: 'object' as const, - optional: true as const, nullable: true as const, - ref: 'User' as const, - }, - groupId: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'id', - }, - group: { - type: 'object' as const, - optional: true as const, nullable: true as const, - ref: 'UserGroup' as const, - }, - isRead: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, - }, - reads: { - type: 'array' as const, - optional: true as const, nullable: false as const, - items: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/muting.ts b/packages/backend/src/models/repositories/muting.ts index b82d1f0daa..bdbe9b47da 100644 --- a/packages/backend/src/models/repositories/muting.ts +++ b/packages/backend/src/models/repositories/muting.ts @@ -30,31 +30,3 @@ export class MutingRepository extends Repository<Muting> { return Promise.all(mutings.map(x => this.pack(x, me))); } } - -export const packedMutingSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - muteeId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - mutee: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User' as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/note-favorite.ts b/packages/backend/src/models/repositories/note-favorite.ts index 47586a9116..c5de55c0c0 100644 --- a/packages/backend/src/models/repositories/note-favorite.ts +++ b/packages/backend/src/models/repositories/note-favorite.ts @@ -26,31 +26,3 @@ export class NoteFavoriteRepository extends Repository<NoteFavorite> { return Promise.all(favorites.map(x => this.pack(x, me))); } } - -export const packedNoteFavoriteSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - note: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'Note' as const, - }, - noteId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - }, -}; diff --git a/packages/backend/src/models/repositories/note-reaction.ts b/packages/backend/src/models/repositories/note-reaction.ts index dfb25cbea1..097574effa 100644 --- a/packages/backend/src/models/repositories/note-reaction.ts +++ b/packages/backend/src/models/repositories/note-reaction.ts @@ -31,30 +31,3 @@ export class NoteReactionRepository extends Repository<NoteReaction> { }; } } - -export const packedNoteReactionSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - user: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User' as const, - }, - type: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/note.ts b/packages/backend/src/models/repositories/note.ts index 96dfad70e9..9a7fef4977 100644 --- a/packages/backend/src/models/repositories/note.ts +++ b/packages/backend/src/models/repositories/note.ts @@ -218,7 +218,7 @@ export class NoteRepository extends Repository<Note> { const reactionEmojiNames = Object.keys(note.reactions).filter(x => x?.startsWith(':')).map(x => decodeReaction(x).reaction).map(x => x.replace(/:/g, '')); - const packed = await awaitAll({ + const packed: Packed<'Note'> = await awaitAll({ id: note.id, createdAt: note.createdAt.toISOString(), userId: note.userId, @@ -320,188 +320,3 @@ export class NoteRepository extends Repository<Note> { }))); } } - -export const packedNoteSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - text: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - cw: { - type: 'string' as const, - optional: true as const, nullable: true as const, - }, - userId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - user: { - type: 'object' as const, - ref: 'User' as const, - optional: false as const, nullable: false as const, - }, - replyId: { - type: 'string' as const, - optional: true as const, nullable: true as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - renoteId: { - type: 'string' as const, - optional: true as const, nullable: true as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - reply: { - type: 'object' as const, - optional: true as const, nullable: true as const, - ref: 'Note' as const, - }, - renote: { - type: 'object' as const, - optional: true as const, nullable: true as const, - ref: 'Note' as const, - }, - isHidden: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, - }, - visibility: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - mentions: { - type: 'array' as const, - optional: true as const, nullable: false as const, - items: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - }, - visibleUserIds: { - type: 'array' as const, - optional: true as const, nullable: false as const, - items: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - }, - fileIds: { - type: 'array' as const, - optional: true as const, nullable: false as const, - items: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - }, - files: { - type: 'array' as const, - optional: true as const, nullable: false as const, - items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'DriveFile' as const, - }, - }, - tags: { - type: 'array' as const, - optional: true as const, nullable: false as const, - items: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - }, - poll: { - type: 'object' as const, - optional: true as const, nullable: true as const, - }, - channelId: { - type: 'string' as const, - optional: true as const, nullable: true as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - channel: { - type: 'object' as const, - optional: true as const, nullable: true as const, - items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - name: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - }, - }, - }, - localOnly: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, - }, - emojis: { - type: 'array' as const, - optional: false as const, nullable: false as const, - items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - name: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - url: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - }, - }, - }, - reactions: { - type: 'object' as const, - optional: false as const, nullable: false as const, - }, - renoteCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - repliesCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - uri: { - type: 'string' as const, - optional: true as const, nullable: false as const, - }, - url: { - type: 'string' as const, - optional: true as const, nullable: false as const, - }, - - myReaction: { - type: 'object' as const, - optional: true as const, nullable: true as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/notification.ts b/packages/backend/src/models/repositories/notification.ts index 47d569ed21..5e42798898 100644 --- a/packages/backend/src/models/repositories/notification.ts +++ b/packages/backend/src/models/repositories/notification.ts @@ -107,69 +107,3 @@ export class NotificationRepository extends Repository<Notification> { }))); } } - -export const packedNotificationSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - isRead: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - type: { - type: 'string' as const, - optional: false as const, nullable: false as const, - enum: [...notificationTypes], - }, - user: { - type: 'object' as const, - ref: 'User' as const, - optional: true as const, nullable: true as const, - }, - userId: { - type: 'string' as const, - optional: true as const, nullable: true as const, - format: 'id', - }, - note: { - type: 'object' as const, - ref: 'Note' as const, - optional: true as const, nullable: true as const, - }, - reaction: { - type: 'string' as const, - optional: true as const, nullable: true as const, - }, - choice: { - type: 'number' as const, - optional: true as const, nullable: true as const, - }, - invitation: { - type: 'object' as const, - optional: true as const, nullable: true as const, - }, - body: { - type: 'string' as const, - optional: true as const, nullable: true as const, - }, - header: { - type: 'string' as const, - optional: true as const, nullable: true as const, - }, - icon: { - type: 'string' as const, - optional: true as const, nullable: true as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/page.ts b/packages/backend/src/models/repositories/page.ts index 46b453cad9..ec76c2e418 100644 --- a/packages/backend/src/models/repositories/page.ts +++ b/packages/backend/src/models/repositories/page.ts @@ -87,56 +87,3 @@ export class PageRepository extends Repository<Page> { return Promise.all(pages.map(x => this.pack(x, me))); } } - -export const packedPageSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - updatedAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - title: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - name: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - summary: { - type: 'string' as const, - optional: false as const, nullable: true as const, - }, - content: { - type: 'array' as const, - optional: false as const, nullable: false as const, - }, - variables: { - type: 'array' as const, - optional: false as const, nullable: false as const, - }, - userId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - user: { - type: 'object' as const, - ref: 'User' as const, - optional: false as const, nullable: false as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/queue.ts b/packages/backend/src/models/repositories/queue.ts deleted file mode 100644 index 521c634390..0000000000 --- a/packages/backend/src/models/repositories/queue.ts +++ /dev/null @@ -1,30 +0,0 @@ -export const packedQueueCountSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - waiting: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - active: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - completed: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - failed: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - delayed: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - paused: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/user-group.ts b/packages/backend/src/models/repositories/user-group.ts index 02a0348885..3ed37ca0ed 100644 --- a/packages/backend/src/models/repositories/user-group.ts +++ b/packages/backend/src/models/repositories/user-group.ts @@ -23,39 +23,3 @@ export class UserGroupRepository extends Repository<UserGroup> { }; } } - -export const packedUserGroupSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - name: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - ownerId: { - type: 'string' as const, - nullable: false as const, optional: false as const, - format: 'id', - }, - userIds: { - type: 'array' as const, - nullable: false as const, optional: true as const, - items: { - type: 'string' as const, - nullable: false as const, optional: false as const, - format: 'id', - }, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/user-list.ts b/packages/backend/src/models/repositories/user-list.ts index 792a17cb49..a2bffe8357 100644 --- a/packages/backend/src/models/repositories/user-list.ts +++ b/packages/backend/src/models/repositories/user-list.ts @@ -22,34 +22,3 @@ export class UserListRepository extends Repository<UserList> { }; } } - -export const packedUserListSchema = { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - name: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - userIds: { - type: 'array' as const, - nullable: false as const, optional: true as const, - items: { - type: 'string' as const, - nullable: false as const, optional: false as const, - format: 'id', - }, - }, - }, -}; diff --git a/packages/backend/src/models/repositories/user.ts b/packages/backend/src/models/repositories/user.ts index 3dc7c67ec2..2b8398832d 100644 --- a/packages/backend/src/models/repositories/user.ts +++ b/packages/backend/src/models/repositories/user.ts @@ -4,11 +4,19 @@ import { User, ILocalUser, IRemoteUser } from '@/models/entities/user'; import { Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles, UserSecurityKeys, UserGroupJoinings, Pages, Announcements, AnnouncementReads, Antennas, AntennaNotes, ChannelFollowings, Instances } from '../index'; import config from '@/config/index'; import { Packed } from '@/misc/schema'; -import { awaitAll } from '@/prelude/await-all'; +import { awaitAll, Promiseable } from '@/prelude/await-all'; import { populateEmojis } from '@/misc/populate-emojis'; import { getAntennas } from '@/misc/antenna-cache'; import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD } from '@/const'; +type IsUserDetailed<Detailed extends boolean> = Detailed extends true ? Packed<'UserDetailed'> : Packed<'UserLite'>; +type IsMeAndIsUserDetailed<ExpectsMe extends boolean | null, Detailed extends boolean> = + Detailed extends true ? + ExpectsMe extends true ? Packed<'MeDetailed'> : + ExpectsMe extends false ? Packed<'UserDetailedNotMe'> : + Packed<'UserDetailed'> : + Packed<'UserLite'>; + @EntityRepository(User) export class UserRepository extends Repository<User> { public async getRelation(me: User['id'], target: User['id']) { @@ -144,7 +152,7 @@ export class UserRepository extends Repository<User> { return count > 0; } - public getOnlineStatus(user: User): string { + public getOnlineStatus(user: User): 'unknown' | 'online' | 'active' | 'offline' { if (user.hideOnlineStatus) return 'unknown'; if (user.lastActiveDate == null) return 'unknown'; const elapsed = Date.now() - user.lastActiveDate.getTime(); @@ -159,18 +167,18 @@ export class UserRepository extends Repository<User> { if (user.avatarUrl) { return user.avatarUrl; } else { - return `${config.url}/random-avatar/${user.id}`; + return `${config.url}/identicon/${user.id}`; } } - public async pack( + public async pack<ExpectsMe extends boolean | null = null, D extends boolean = false>( src: User['id'] | User, me?: { id: User['id'] } | null | undefined, options?: { - detail?: boolean, + detail?: D, includeSecrets?: boolean, } - ): Promise<Packed<'User'>> { + ): Promise<IsMeAndIsUserDetailed<ExpectsMe, D>> { const opts = Object.assign({ detail: false, includeSecrets: false, @@ -178,8 +186,9 @@ export class UserRepository extends Repository<User> { const user = typeof src === 'object' ? src : await this.findOneOrFail(src); const meId = me ? me.id : null; + const isMe = meId === user.id; - const relation = meId && (meId !== user.id) && opts.detail ? await this.getRelation(meId, user.id) : null; + const relation = meId && !isMe && opts.detail ? await this.getRelation(meId, user.id) : null; const pins = opts.detail ? await UserNotePinings.createQueryBuilder('pin') .where('pin.userId = :userId', { userId: user.id }) .innerJoinAndSelect('pin.note', 'note') @@ -188,12 +197,12 @@ export class UserRepository extends Repository<User> { const profile = opts.detail ? await UserProfiles.findOneOrFail(user.id) : null; const followingCount = profile == null ? null : - (profile.ffVisibility === 'public') || (meId === user.id) ? user.followingCount : + (profile.ffVisibility === 'public') || isMe ? user.followingCount : (profile.ffVisibility === 'followers') && (relation && relation.isFollowing) ? user.followingCount : null; const followersCount = profile == null ? null : - (profile.ffVisibility === 'public') || (meId === user.id) ? user.followersCount : + (profile.ffVisibility === 'public') || isMe ? user.followersCount : (profile.ffVisibility === 'followers') && (relation && relation.isFollowing) ? user.followersCount : null; @@ -227,12 +236,11 @@ export class UserRepository extends Repository<User> { uri: user.uri, createdAt: user.createdAt.toISOString(), updatedAt: user.updatedAt ? user.updatedAt.toISOString() : null, - lastFetchedAt: user.lastFetchedAt?.toISOString(), + lastFetchedAt: user.lastFetchedAt ? user.lastFetchedAt.toISOString() : null, bannerUrl: user.bannerUrl, bannerBlurhash: user.bannerBlurhash, bannerColor: null, // 後方互換性のため isLocked: user.isLocked, - isModerator: user.isModerator || falsy, isSilenced: user.isSilenced || falsy, isSuspended: user.isSuspended || falsy, description: profile!.description, @@ -260,7 +268,7 @@ export class UserRepository extends Repository<User> { : false, } : {}), - ...(opts.detail && meId === user.id ? { + ...(opts.detail && isMe ? { avatarId: user.avatarId, bannerId: user.bannerId, injectFeaturedNote: profile!.injectFeaturedNote, @@ -315,19 +323,19 @@ export class UserRepository extends Repository<User> { isBlocked: relation.isBlocked, isMuted: relation.isMuted, } : {}), - }; + } as Promiseable<Packed<'User'>> as Promiseable<IsMeAndIsUserDetailed<ExpectsMe, D>>; return await awaitAll(packed); } - public packMany( + public packMany<D extends boolean = false>( users: (User['id'] | User)[], me?: { id: User['id'] } | null | undefined, options?: { - detail?: boolean, + detail?: D, includeSecrets?: boolean, } - ) { + ): Promise<IsUserDetailed<D>[]> { return Promise.all(users.map(u => this.pack(u, me, options))); } @@ -352,313 +360,3 @@ export class UserRepository extends Repository<User> { public validateBirthday = $.str.match(/^([0-9]{4})-([0-9]{2})-([0-9]{2})$/); //#endregion } - -export const packedUserSchema = { - type: 'object' as const, - nullable: false as const, optional: false as const, - properties: { - id: { - type: 'string' as const, - nullable: false as const, optional: false as const, - format: 'id', - example: 'xxxxxxxxxx', - }, - name: { - type: 'string' as const, - nullable: true as const, optional: false as const, - example: '藍', - }, - username: { - type: 'string' as const, - nullable: false as const, optional: false as const, - example: 'ai', - }, - host: { - type: 'string' as const, - nullable: true as const, optional: false as const, - example: 'misskey.example.com', - }, - avatarUrl: { - type: 'string' as const, - format: 'url', - nullable: true as const, optional: false as const, - }, - avatarBlurhash: { - type: 'any' as const, - nullable: true as const, optional: false as const, - }, - avatarColor: { - type: 'any' as const, - nullable: true as const, optional: false as const, - default: null, - }, - isAdmin: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - default: false, - }, - isModerator: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - default: false, - }, - isBot: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - isCat: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - emojis: { - type: 'array' as const, - nullable: false as const, optional: false as const, - items: { - type: 'object' as const, - nullable: false as const, optional: false as const, - properties: { - name: { - type: 'string' as const, - nullable: false as const, optional: false as const, - }, - url: { - type: 'string' as const, - nullable: false as const, optional: false as const, - format: 'url', - }, - }, - }, - }, - url: { - type: 'string' as const, - format: 'url', - nullable: true as const, optional: true as const, - }, - createdAt: { - type: 'string' as const, - nullable: false as const, optional: true as const, - format: 'date-time', - }, - updatedAt: { - type: 'string' as const, - nullable: true as const, optional: true as const, - format: 'date-time', - }, - bannerUrl: { - type: 'string' as const, - format: 'url', - nullable: true as const, optional: true as const, - }, - bannerBlurhash: { - type: 'any' as const, - nullable: true as const, optional: true as const, - }, - bannerColor: { - type: 'any' as const, - nullable: true as const, optional: true as const, - default: null, - }, - isLocked: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - isSuspended: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - example: false, - }, - description: { - type: 'string' as const, - nullable: true as const, optional: true as const, - example: 'Hi masters, I am Ai!', - }, - location: { - type: 'string' as const, - nullable: true as const, optional: true as const, - }, - birthday: { - type: 'string' as const, - nullable: true as const, optional: true as const, - example: '2018-03-12', - }, - fields: { - type: 'array' as const, - nullable: false as const, optional: true as const, - items: { - type: 'object' as const, - nullable: false as const, optional: false as const, - properties: { - name: { - type: 'string' as const, - nullable: false as const, optional: false as const, - }, - value: { - type: 'string' as const, - nullable: false as const, optional: false as const, - }, - }, - maxLength: 4, - }, - }, - followersCount: { - type: 'number' as const, - nullable: false as const, optional: true as const, - }, - followingCount: { - type: 'number' as const, - nullable: false as const, optional: true as const, - }, - notesCount: { - type: 'number' as const, - nullable: false as const, optional: true as const, - }, - pinnedNoteIds: { - type: 'array' as const, - nullable: false as const, optional: true as const, - items: { - type: 'string' as const, - nullable: false as const, optional: false as const, - format: 'id', - }, - }, - pinnedNotes: { - type: 'array' as const, - nullable: false as const, optional: true as const, - items: { - type: 'object' as const, - nullable: false as const, optional: false as const, - ref: 'Note' as const, - }, - }, - pinnedPageId: { - type: 'string' as const, - nullable: true as const, optional: true as const, - }, - pinnedPage: { - type: 'object' as const, - nullable: true as const, optional: true as const, - ref: 'Page' as const, - }, - twoFactorEnabled: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - default: false, - }, - usePasswordLessLogin: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - default: false, - }, - securityKeys: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - default: false, - }, - avatarId: { - type: 'string' as const, - nullable: true as const, optional: true as const, - format: 'id', - }, - bannerId: { - type: 'string' as const, - nullable: true as const, optional: true as const, - format: 'id', - }, - autoWatch: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - injectFeaturedNote: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - alwaysMarkNsfw: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - carefulBot: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - autoAcceptFollowed: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - hasUnreadSpecifiedNotes: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - hasUnreadMentions: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - hasUnreadAnnouncement: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - hasUnreadAntenna: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - hasUnreadChannel: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - hasUnreadMessagingMessage: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - hasUnreadNotification: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - hasPendingReceivedFollowRequest: { - type: 'boolean' as const, - nullable: false as const, optional: true as const, - }, - integrations: { - type: 'object' as const, - nullable: false as const, optional: true as const, - }, - mutedWords: { - type: 'array' as const, - nullable: false as const, optional: true as const, - }, - mutedInstances: { - type: 'array' as const, - nullable: false as const, optional: true as const, - }, - mutingNotificationTypes: { - type: 'array' as const, - nullable: false as const, optional: true as const, - }, - isFollowing: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, - }, - hasPendingFollowRequestFromYou: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, - }, - hasPendingFollowRequestToYou: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, - }, - isFollowed: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, - }, - isBlocking: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, - }, - isBlocked: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, - }, - isMuted: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, - }, - }, -}; diff --git a/packages/backend/src/models/schema/antenna.ts b/packages/backend/src/models/schema/antenna.ts new file mode 100644 index 0000000000..9cf522802c --- /dev/null +++ b/packages/backend/src/models/schema/antenna.ts @@ -0,0 +1,89 @@ +export const packedAntennaSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + name: { + type: 'string', + optional: false, nullable: false, + }, + keywords: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, + }, + excludeKeywords: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, + }, + src: { + type: 'string', + optional: false, nullable: false, + enum: ['home', 'all', 'users', 'list', 'group'], + }, + userListId: { + type: 'string', + optional: false, nullable: true, + format: 'id', + }, + userGroupId: { + type: 'string', + optional: false, nullable: true, + format: 'id', + }, + users: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, + caseSensitive: { + type: 'boolean', + optional: false, nullable: false, + default: false, + }, + notify: { + type: 'boolean', + optional: false, nullable: false, + }, + withReplies: { + type: 'boolean', + optional: false, nullable: false, + default: false, + }, + withFile: { + type: 'boolean', + optional: false, nullable: false, + }, + hasUnreadNote: { + type: 'boolean', + optional: false, nullable: false, + default: false, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/app.ts b/packages/backend/src/models/schema/app.ts new file mode 100644 index 0000000000..c80dc81c33 --- /dev/null +++ b/packages/backend/src/models/schema/app.ts @@ -0,0 +1,33 @@ +export const packedAppSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + }, + name: { + type: 'string', + optional: false, nullable: false, + }, + callbackUrl: { + type: 'string', + optional: false, nullable: true, + }, + permission: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, + secret: { + type: 'string', + optional: true, nullable: false, + }, + isAuthorized: { + type: 'boolean', + optional: true, nullable: false, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/blocking.ts b/packages/backend/src/models/schema/blocking.ts new file mode 100644 index 0000000000..5532322420 --- /dev/null +++ b/packages/backend/src/models/schema/blocking.ts @@ -0,0 +1,26 @@ +export const packedBlockingSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + blockeeId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + blockee: { + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailed', + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/channel.ts b/packages/backend/src/models/schema/channel.ts new file mode 100644 index 0000000000..7f4f2a48b8 --- /dev/null +++ b/packages/backend/src/models/schema/channel.ts @@ -0,0 +1,51 @@ +export const packedChannelSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + lastNotedAt: { + type: 'string', + optional: false, nullable: true, + format: 'date-time', + }, + name: { + type: 'string', + optional: false, nullable: false, + }, + description: { + type: 'string', + nullable: true, optional: false, + }, + bannerUrl: { + type: 'string', + format: 'url', + nullable: true, optional: false, + }, + notesCount: { + type: 'number', + nullable: false, optional: false, + }, + usersCount: { + type: 'number', + nullable: false, optional: false, + }, + isFollowing: { + type: 'boolean', + optional: true, nullable: false, + }, + userId: { + type: 'string', + nullable: true, optional: false, + format: 'id', + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/clip.ts b/packages/backend/src/models/schema/clip.ts new file mode 100644 index 0000000000..f0ee2ce0c4 --- /dev/null +++ b/packages/backend/src/models/schema/clip.ts @@ -0,0 +1,38 @@ +export const packedClipSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + userId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + user: { + type: 'object', + ref: 'UserLite', + optional: false, nullable: false, + }, + name: { + type: 'string', + optional: false, nullable: false, + }, + description: { + type: 'string', + optional: false, nullable: true, + }, + isPublic: { + type: 'boolean', + optional: false, nullable: false, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/drive-file.ts b/packages/backend/src/models/schema/drive-file.ts new file mode 100644 index 0000000000..4359076612 --- /dev/null +++ b/packages/backend/src/models/schema/drive-file.ts @@ -0,0 +1,107 @@ +export const packedDriveFileSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + name: { + type: 'string', + optional: false, nullable: false, + example: 'lenna.jpg', + }, + type: { + type: 'string', + optional: false, nullable: false, + example: 'image/jpeg', + }, + md5: { + type: 'string', + optional: false, nullable: false, + format: 'md5', + example: '15eca7fba0480996e2245f5185bf39f2', + }, + size: { + type: 'number', + optional: false, nullable: false, + example: 51469, + }, + isSensitive: { + type: 'boolean', + optional: false, nullable: false, + }, + blurhash: { + type: 'string', + optional: false, nullable: true, + }, + properties: { + type: 'object', + optional: false, nullable: false, + properties: { + width: { + type: 'number', + optional: true, nullable: false, + example: 1280, + }, + height: { + type: 'number', + optional: true, nullable: false, + example: 720, + }, + orientation: { + type: 'number', + optional: true, nullable: false, + example: 8, + }, + avgColor: { + type: 'string', + optional: true, nullable: false, + example: 'rgb(40,65,87)', + }, + }, + }, + url: { + type: 'string', + optional: false, nullable: true, + format: 'url', + }, + thumbnailUrl: { + type: 'string', + optional: false, nullable: true, + format: 'url', + }, + comment: { + type: 'string', + optional: false, nullable: true, + }, + folderId: { + type: 'string', + optional: false, nullable: true, + format: 'id', + example: 'xxxxxxxxxx', + }, + folder: { + type: 'object', + optional: true, nullable: true, + ref: 'DriveFolder', + }, + userId: { + type: 'string', + optional: false, nullable: true, + format: 'id', + example: 'xxxxxxxxxx', + }, + user: { + type: 'object', + optional: true, nullable: true, + ref: 'UserLite', + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/drive-folder.ts b/packages/backend/src/models/schema/drive-folder.ts new file mode 100644 index 0000000000..88cb8ab4a2 --- /dev/null +++ b/packages/backend/src/models/schema/drive-folder.ts @@ -0,0 +1,39 @@ +export const packedDriveFolderSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + name: { + type: 'string', + optional: false, nullable: false, + }, + foldersCount: { + type: 'number', + optional: true, nullable: false, + }, + filesCount: { + type: 'number', + optional: true, nullable: false, + }, + parentId: { + type: 'string', + optional: false, nullable: true, + format: 'id', + example: 'xxxxxxxxxx', + }, + parent: { + type: 'object', + optional: true, nullable: true, + ref: 'DriveFolder', + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/emoji.ts b/packages/backend/src/models/schema/emoji.ts new file mode 100644 index 0000000000..5f9af88db4 --- /dev/null +++ b/packages/backend/src/models/schema/emoji.ts @@ -0,0 +1,36 @@ +export const packedEmojiSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + aliases: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + }, + name: { + type: 'string', + optional: false, nullable: false, + }, + category: { + type: 'string', + optional: false, nullable: true, + }, + host: { + type: 'string', + optional: false, nullable: true, + }, + url: { + type: 'string', + optional: false, nullable: false, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/federation-instance.ts b/packages/backend/src/models/schema/federation-instance.ts new file mode 100644 index 0000000000..eef2f9e24f --- /dev/null +++ b/packages/backend/src/models/schema/federation-instance.ts @@ -0,0 +1,105 @@ +import config from "@/config"; + +export const packedFederationInstanceSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + caughtAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + host: { + type: 'string', + optional: false, nullable: false, + example: 'misskey.example.com', + }, + usersCount: { + type: 'number', + optional: false, nullable: false, + }, + notesCount: { + type: 'number', + optional: false, nullable: false, + }, + followingCount: { + type: 'number', + optional: false, nullable: false, + }, + followersCount: { + type: 'number', + optional: false, nullable: false, + }, + driveUsage: { + type: 'number', + optional: false, nullable: false, + }, + driveFiles: { + type: 'number', + optional: false, nullable: false, + }, + latestRequestSentAt: { + type: 'string', + optional: false, nullable: true, + format: 'date-time', + }, + lastCommunicatedAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + isNotResponding: { + type: 'boolean', + optional: false, nullable: false, + }, + isSuspended: { + type: 'boolean', + optional: false, nullable: false, + }, + softwareName: { + type: 'string', + optional: false, nullable: true, + example: 'misskey', + }, + softwareVersion: { + type: 'string', + optional: false, nullable: true, + example: config.version, + }, + openRegistrations: { + type: 'boolean', + optional: false, nullable: true, + example: true, + }, + name: { + type: 'string', + optional: false, nullable: true, + }, + description: { + type: 'string', + optional: false, nullable: true, + }, + maintainerName: { + type: 'string', + optional: false, nullable: true, + }, + maintainerEmail: { + type: 'string', + optional: false, nullable: true, + }, + iconUrl: { + type: 'string', + optional: false, nullable: true, + format: 'url', + }, + infoUpdatedAt: { + type: 'string', + optional: false, nullable: true, + format: 'date-time', + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/following.ts b/packages/backend/src/models/schema/following.ts new file mode 100644 index 0000000000..2bcffbfc4d --- /dev/null +++ b/packages/backend/src/models/schema/following.ts @@ -0,0 +1,36 @@ +export const packedFollowingSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + followeeId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + followee: { + type: 'object', + optional: true, nullable: false, + ref: 'UserDetailed', + }, + followerId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + follower: { + type: 'object', + optional: true, nullable: false, + ref: 'UserDetailed', + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/gallery-post.ts b/packages/backend/src/models/schema/gallery-post.ts new file mode 100644 index 0000000000..fc503d4a64 --- /dev/null +++ b/packages/backend/src/models/schema/gallery-post.ts @@ -0,0 +1,69 @@ +export const packedGalleryPostSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + updatedAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + title: { + type: 'string', + optional: false, nullable: false, + }, + description: { + type: 'string', + optional: false, nullable: true, + }, + userId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + user: { + type: 'object', + ref: 'UserLite', + optional: false, nullable: false, + }, + fileIds: { + type: 'array', + optional: true, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + }, + files: { + type: 'array', + optional: true, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + ref: 'DriveFile', + }, + }, + tags: { + type: 'array', + optional: true, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, + isSensitive: { + type: 'boolean', + optional: false, nullable: false, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/hashtag.ts b/packages/backend/src/models/schema/hashtag.ts new file mode 100644 index 0000000000..98f8827640 --- /dev/null +++ b/packages/backend/src/models/schema/hashtag.ts @@ -0,0 +1,34 @@ +export const packedHashtagSchema = { + type: 'object', + properties: { + tag: { + type: 'string', + optional: false, nullable: false, + example: 'misskey', + }, + mentionedUsersCount: { + type: 'number', + optional: false, nullable: false, + }, + mentionedLocalUsersCount: { + type: 'number', + optional: false, nullable: false, + }, + mentionedRemoteUsersCount: { + type: 'number', + optional: false, nullable: false, + }, + attachedUsersCount: { + type: 'number', + optional: false, nullable: false, + }, + attachedLocalUsersCount: { + type: 'number', + optional: false, nullable: false, + }, + attachedRemoteUsersCount: { + type: 'number', + optional: false, nullable: false, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/messaging-message.ts b/packages/backend/src/models/schema/messaging-message.ts new file mode 100644 index 0000000000..b1ffa45955 --- /dev/null +++ b/packages/backend/src/models/schema/messaging-message.ts @@ -0,0 +1,73 @@ +export const packedMessagingMessageSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + userId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + user: { + type: 'object', + ref: 'UserLite', + optional: true, nullable: false, + }, + text: { + type: 'string', + optional: false, nullable: true, + }, + fileId: { + type: 'string', + optional: true, nullable: true, + format: 'id', + }, + file: { + type: 'object', + optional: true, nullable: true, + ref: 'DriveFile', + }, + recipientId: { + type: 'string', + optional: false, nullable: true, + format: 'id', + }, + recipient: { + type: 'object', + optional: true, nullable: true, + ref: 'UserLite', + }, + groupId: { + type: 'string', + optional: false, nullable: true, + format: 'id', + }, + group: { + type: 'object', + optional: true, nullable: true, + ref: 'UserGroup', + }, + isRead: { + type: 'boolean', + optional: true, nullable: false, + }, + reads: { + type: 'array', + optional: true, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/muting.ts b/packages/backend/src/models/schema/muting.ts new file mode 100644 index 0000000000..d75a4fbfed --- /dev/null +++ b/packages/backend/src/models/schema/muting.ts @@ -0,0 +1,26 @@ +export const packedMutingSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + muteeId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + mutee: { + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailed', + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/note-favorite.ts b/packages/backend/src/models/schema/note-favorite.ts new file mode 100644 index 0000000000..d133f7367d --- /dev/null +++ b/packages/backend/src/models/schema/note-favorite.ts @@ -0,0 +1,26 @@ +export const packedNoteFavoriteSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + note: { + type: 'object', + optional: false, nullable: false, + ref: 'Note', + }, + noteId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/note-reaction.ts b/packages/backend/src/models/schema/note-reaction.ts new file mode 100644 index 0000000000..0d8fc5449b --- /dev/null +++ b/packages/backend/src/models/schema/note-reaction.ts @@ -0,0 +1,25 @@ +export const packedNoteReactionSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + user: { + type: 'object', + optional: false, nullable: false, + ref: 'UserLite', + }, + type: { + type: 'string', + optional: false, nullable: false, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/note.ts b/packages/backend/src/models/schema/note.ts new file mode 100644 index 0000000000..cdf4b9a544 --- /dev/null +++ b/packages/backend/src/models/schema/note.ts @@ -0,0 +1,183 @@ +export const packedNoteSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + text: { + type: 'string', + optional: false, nullable: true, + }, + cw: { + type: 'string', + optional: true, nullable: true, + }, + userId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + user: { + type: 'object', + ref: 'UserLite', + optional: false, nullable: false, + }, + replyId: { + type: 'string', + optional: true, nullable: true, + format: 'id', + example: 'xxxxxxxxxx', + }, + renoteId: { + type: 'string', + optional: true, nullable: true, + format: 'id', + example: 'xxxxxxxxxx', + }, + reply: { + type: 'object', + optional: true, nullable: true, + ref: 'Note', + }, + renote: { + type: 'object', + optional: true, nullable: true, + ref: 'Note', + }, + isHidden: { + type: 'boolean', + optional: true, nullable: false, + }, + visibility: { + type: 'string', + optional: false, nullable: false, + }, + mentions: { + type: 'array', + optional: true, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + }, + visibleUserIds: { + type: 'array', + optional: true, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + }, + fileIds: { + type: 'array', + optional: true, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + }, + files: { + type: 'array', + optional: true, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + ref: 'DriveFile', + }, + }, + tags: { + type: 'array', + optional: true, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, + poll: { + type: 'object', + optional: true, nullable: true, + }, + channelId: { + type: 'string', + optional: true, nullable: true, + format: 'id', + example: 'xxxxxxxxxx', + }, + channel: { + type: 'object', + optional: true, nullable: true, + items: { + type: 'object', + optional: false, nullable: false, + properties: { + id: { + type: 'string', + optional: false, nullable: false, + }, + name: { + type: 'string', + optional: false, nullable: true, + }, + }, + }, + }, + localOnly: { + type: 'boolean', + optional: true, nullable: false, + }, + emojis: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + properties: { + name: { + type: 'string', + optional: false, nullable: false, + }, + url: { + type: 'string', + optional: false, nullable: true, + }, + }, + }, + }, + reactions: { + type: 'object', + optional: false, nullable: false, + }, + renoteCount: { + type: 'number', + optional: false, nullable: false, + }, + repliesCount: { + type: 'number', + optional: false, nullable: false, + }, + uri: { + type: 'string', + optional: true, nullable: false, + }, + url: { + type: 'string', + optional: true, nullable: false, + }, + + myReaction: { + type: 'object', + optional: true, nullable: true, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/notification.ts b/packages/backend/src/models/schema/notification.ts new file mode 100644 index 0000000000..f3c293c480 --- /dev/null +++ b/packages/backend/src/models/schema/notification.ts @@ -0,0 +1,66 @@ +import { notificationTypes } from "@/types"; + +export const packedNotificationSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + isRead: { + type: 'boolean', + optional: false, nullable: false, + }, + type: { + type: 'string', + optional: false, nullable: false, + enum: [...notificationTypes], + }, + user: { + type: 'object', + ref: 'UserLite', + optional: true, nullable: true, + }, + userId: { + type: 'string', + optional: true, nullable: true, + format: 'id', + }, + note: { + type: 'object', + ref: 'Note', + optional: true, nullable: true, + }, + reaction: { + type: 'string', + optional: true, nullable: true, + }, + choice: { + type: 'number', + optional: true, nullable: true, + }, + invitation: { + type: 'object', + optional: true, nullable: true, + }, + body: { + type: 'string', + optional: true, nullable: true, + }, + header: { + type: 'string', + optional: true, nullable: true, + }, + icon: { + type: 'string', + optional: true, nullable: true, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/page.ts b/packages/backend/src/models/schema/page.ts new file mode 100644 index 0000000000..55ba3ce7f7 --- /dev/null +++ b/packages/backend/src/models/schema/page.ts @@ -0,0 +1,51 @@ +export const packedPageSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + updatedAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + title: { + type: 'string', + optional: false, nullable: false, + }, + name: { + type: 'string', + optional: false, nullable: false, + }, + summary: { + type: 'string', + optional: false, nullable: true, + }, + content: { + type: 'array', + optional: false, nullable: false, + }, + variables: { + type: 'array', + optional: false, nullable: false, + }, + userId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + user: { + type: 'object', + ref: 'UserLite', + optional: false, nullable: false, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/queue.ts b/packages/backend/src/models/schema/queue.ts new file mode 100644 index 0000000000..7ceeda26af --- /dev/null +++ b/packages/backend/src/models/schema/queue.ts @@ -0,0 +1,25 @@ +export const packedQueueCountSchema = { + type: 'object', + properties: { + waiting: { + type: 'number', + optional: false, nullable: false, + }, + active: { + type: 'number', + optional: false, nullable: false, + }, + completed: { + type: 'number', + optional: false, nullable: false, + }, + failed: { + type: 'number', + optional: false, nullable: false, + }, + delayed: { + type: 'number', + optional: false, nullable: false, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/user-group.ts b/packages/backend/src/models/schema/user-group.ts new file mode 100644 index 0000000000..a73bf82bb8 --- /dev/null +++ b/packages/backend/src/models/schema/user-group.ts @@ -0,0 +1,34 @@ +export const packedUserGroupSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + name: { + type: 'string', + optional: false, nullable: false, + }, + ownerId: { + type: 'string', + nullable: false, optional: false, + format: 'id', + }, + userIds: { + type: 'array', + nullable: false, optional: true, + items: { + type: 'string', + nullable: false, optional: false, + format: 'id', + }, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/user-list.ts b/packages/backend/src/models/schema/user-list.ts new file mode 100644 index 0000000000..3ba5dc4a8a --- /dev/null +++ b/packages/backend/src/models/schema/user-list.ts @@ -0,0 +1,29 @@ +export const packedUserListSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + name: { + type: 'string', + optional: false, nullable: false, + }, + userIds: { + type: 'array', + nullable: false, optional: true, + items: { + type: 'string', + nullable: false, optional: false, + format: 'id', + }, + }, + }, +} as const; diff --git a/packages/backend/src/models/schema/user.ts b/packages/backend/src/models/schema/user.ts new file mode 100644 index 0000000000..616bedc0dc --- /dev/null +++ b/packages/backend/src/models/schema/user.ts @@ -0,0 +1,467 @@ +export const packedUserLiteSchema = { + type: 'object', + properties: { + id: { + type: 'string', + nullable: false, optional: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + name: { + type: 'string', + nullable: true, optional: false, + example: '藍', + }, + username: { + type: 'string', + nullable: false, optional: false, + example: 'ai', + }, + host: { + type: 'string', + nullable: true, optional: false, + example: 'misskey.example.com', + }, + avatarUrl: { + type: 'string', + format: 'url', + nullable: true, optional: false, + }, + avatarBlurhash: { + type: 'any', + nullable: true, optional: false, + }, + avatarColor: { + type: 'any', + nullable: true, optional: false, + default: null, + }, + isAdmin: { + type: 'boolean', + nullable: false, optional: true, + default: false, + }, + isModerator: { + type: 'boolean', + nullable: false, optional: true, + default: false, + }, + isBot: { + type: 'boolean', + nullable: false, optional: true, + }, + isCat: { + type: 'boolean', + nullable: false, optional: true, + }, + emojis: { + type: 'array', + nullable: false, optional: false, + items: { + type: 'object', + nullable: false, optional: false, + properties: { + name: { + type: 'string', + nullable: false, optional: false, + }, + url: { + type: 'string', + nullable: false, optional: false, + format: 'url', + }, + }, + }, + }, + onlineStatus: { + type: 'string', + format: 'url', + nullable: true, optional: false, + enum: ['unknown', 'online', 'active', 'offline'], + }, + }, +} as const; + +export const packedUserDetailedNotMeOnlySchema = { + type: 'object', + properties: { + url: { + type: 'string', + format: 'url', + nullable: true, optional: false, + }, + uri: { + type: 'string', + format: 'uri', + nullable: true, optional: false, + }, + createdAt: { + type: 'string', + nullable: false, optional: false, + format: 'date-time', + }, + updatedAt: { + type: 'string', + nullable: true, optional: false, + format: 'date-time', + }, + lastFetchedAt: { + type: 'string', + nullable: true, optional: false, + format: 'date-time', + }, + bannerUrl: { + type: 'string', + format: 'url', + nullable: true, optional: false, + }, + bannerBlurhash: { + type: 'any', + nullable: true, optional: false, + }, + bannerColor: { + type: 'any', + nullable: true, optional: false, + default: null, + }, + isLocked: { + type: 'boolean', + nullable: false, optional: false, + }, + isSilenced: { + type: 'boolean', + nullable: false, optional: false, + }, + isSuspended: { + type: 'boolean', + nullable: false, optional: false, + example: false, + }, + description: { + type: 'string', + nullable: true, optional: false, + example: 'Hi masters, I am Ai!', + }, + location: { + type: 'string', + nullable: true, optional: false, + }, + birthday: { + type: 'string', + nullable: true, optional: false, + example: '2018-03-12', + }, + lang: { + type: 'string', + nullable: true, optional: false, + example: 'ja-JP', + }, + fields: { + type: 'array', + nullable: false, optional: false, + items: { + type: 'object', + nullable: false, optional: false, + properties: { + name: { + type: 'string', + nullable: false, optional: false, + }, + value: { + type: 'string', + nullable: false, optional: false, + }, + }, + maxLength: 4, + }, + }, + followersCount: { + type: 'number', + nullable: false, optional: false, + }, + followingCount: { + type: 'number', + nullable: false, optional: false, + }, + notesCount: { + type: 'number', + nullable: false, optional: false, + }, + pinnedNoteIds: { + type: 'array', + nullable: false, optional: false, + items: { + type: 'string', + nullable: false, optional: false, + format: 'id', + }, + }, + pinnedNotes: { + type: 'array', + nullable: false, optional: false, + items: { + type: 'object', + nullable: false, optional: false, + ref: 'Note', + }, + }, + pinnedPageId: { + type: 'string', + nullable: true, optional: false, + }, + pinnedPage: { + type: 'object', + nullable: true, optional: false, + ref: 'Page', + }, + publicReactions: { + type: 'boolean', + nullable: false, optional: false, + }, + twoFactorEnabled: { + type: 'boolean', + nullable: false, optional: false, + default: false, + }, + usePasswordLessLogin: { + type: 'boolean', + nullable: false, optional: false, + default: false, + }, + securityKeys: { + type: 'boolean', + nullable: false, optional: false, + default: false, + }, + //#region relations + isFollowing: { + type: 'boolean', + nullable: false, optional: true, + }, + isFollowed: { + type: 'boolean', + nullable: false, optional: true, + }, + hasPendingFollowRequestFromYou: { + type: 'boolean', + nullable: false, optional: true, + }, + hasPendingFollowRequestToYou: { + type: 'boolean', + nullable: false, optional: true, + }, + isBlocking: { + type: 'boolean', + nullable: false, optional: true, + }, + isBlocked: { + type: 'boolean', + nullable: false, optional: true, + }, + isMuted: { + type: 'boolean', + nullable: false, optional: true, + }, + //#endregion + }, +} as const; + +export const packedMeDetailedOnlySchema = { + type: 'object', + properties: { + avatarId: { + type: 'string', + nullable: true, optional: false, + format: 'id', + }, + bannerId: { + type: 'string', + nullable: true, optional: false, + format: 'id', + }, + injectFeaturedNote: { + type: 'boolean', + nullable: true, optional: false, + }, + receiveAnnouncementEmail: { + type: 'boolean', + nullable: true, optional: false, + }, + alwaysMarkNsfw: { + type: 'boolean', + nullable: true, optional: false, + }, + carefulBot: { + type: 'boolean', + nullable: true, optional: false, + }, + autoAcceptFollowed: { + type: 'boolean', + nullable: true, optional: false, + }, + noCrawle: { + type: 'boolean', + nullable: true, optional: false, + }, + isExplorable: { + type: 'boolean', + nullable: false, optional: false, + }, + isDeleted: { + type: 'boolean', + nullable: false, optional: false, + }, + hideOnlineStatus: { + type: 'boolean', + nullable: false, optional: false, + }, + hasUnreadSpecifiedNotes: { + type: 'boolean', + nullable: false, optional: false, + }, + hasUnreadMentions: { + type: 'boolean', + nullable: false, optional: false, + }, + hasUnreadAnnouncement: { + type: 'boolean', + nullable: false, optional: false, + }, + hasUnreadAntenna: { + type: 'boolean', + nullable: false, optional: false, + }, + hasUnreadChannel: { + type: 'boolean', + nullable: false, optional: false, + }, + hasUnreadMessagingMessage: { + type: 'boolean', + nullable: false, optional: false, + }, + hasUnreadNotification: { + type: 'boolean', + nullable: false, optional: false, + }, + hasPendingReceivedFollowRequest: { + type: 'boolean', + nullable: false, optional: false, + }, + integrations: { + type: 'object', + nullable: true, optional: false, + }, + mutedWords: { + type: 'array', + nullable: false, optional: false, + items: { + type: 'array', + nullable: false, optional: false, + items: { + type: 'string', + nullable: false, optional: false, + }, + }, + }, + mutedInstances: { + type: 'array', + nullable: true, optional: false, + items: { + type: 'string', + nullable: false, optional: false, + }, + }, + mutingNotificationTypes: { + type: 'array', + nullable: true, optional: false, + items: { + type: 'string', + nullable: false, optional: false, + }, + }, + emailNotificationTypes: { + type: 'array', + nullable: true, optional: false, + items: { + type: 'string', + nullable: false, optional: false, + }, + }, + //#region secrets + email: { + type: 'string', + nullable: true, optional: true, + }, + emailVerified: { + type: 'boolean', + nullable: true, optional: true, + }, + securityKeysList: { + type: 'array', + nullable: false, optional: true, + items: { + type: 'object', + nullable: false, optional: false, + }, + }, + //#endregion + }, +} as const; + +export const packedUserDetailedNotMeSchema = { + type: 'object', + allOf: [ + { + type: 'object', + ref: 'UserLite', + }, + { + type: 'object', + ref: 'UserDetailedNotMeOnly', + }, + ], +} as const; + +export const packedMeDetailedSchema = { + type: 'object', + allOf: [ + { + type: 'object', + ref: 'UserLite', + }, + { + type: 'object', + ref: 'UserDetailedNotMeOnly', + }, + { + type: 'object', + ref: 'MeDetailedOnly', + }, + ], +} as const; + +export const packedUserDetailedSchema = { + oneOf: [ + { + type: 'object', + ref: 'UserDetailedNotMe', + }, + { + type: 'object', + ref: 'MeDetailed', + }, + ], +} as const; + +export const packedUserSchema = { + oneOf: [ + { + type: 'object', + ref: 'UserLite', + }, + { + type: 'object', + ref: 'UserDetailed', + }, + ], +} as const; diff --git a/packages/backend/src/prelude/await-all.ts b/packages/backend/src/prelude/await-all.ts index 24795f3ae5..b955c3a5d8 100644 --- a/packages/backend/src/prelude/await-all.ts +++ b/packages/backend/src/prelude/await-all.ts @@ -1,13 +1,11 @@ -type Await<T> = T extends Promise<infer U> ? U : T; - -type AwaitAll<T> = { - [P in keyof T]: Await<T[P]>; +export type Promiseable<T> = { + [K in keyof T]: Promise<T[K]> | T[K]; }; -export async function awaitAll<T>(obj: T): Promise<AwaitAll<T>> { - const target = {} as any; - const keys = Object.keys(obj); - const values = Object.values(obj); +export async function awaitAll<T>(obj: Promiseable<T>): Promise<T> { + const target = {} as T; + const keys = Object.keys(obj) as unknown as (keyof T)[]; + const values = Object.values(obj) as any[]; const resolvedValues = await Promise.all(values.map(value => (!value || !value.constructor || value.constructor.name !== 'Object') diff --git a/packages/backend/src/queue/index.ts b/packages/backend/src/queue/index.ts index 2fbc1b1c01..f9994c3b59 100644 --- a/packages/backend/src/queue/index.ts +++ b/packages/backend/src/queue/index.ts @@ -213,6 +213,16 @@ export function createImportUserListsJob(user: ThinUser, fileId: DriveFile['id'] }); } +export function createImportCustomEmojisJob(user: ThinUser, fileId: DriveFile['id']) { + return dbQueue.add('importCustomEmojis', { + user: user, + fileId: fileId, + }, { + removeOnComplete: true, + removeOnFail: true, + }); +} + export function createDeleteAccountJob(user: ThinUser, opts: { soft?: boolean; } = {}) { return dbQueue.add('deleteAccount', { user: user, diff --git a/packages/backend/src/queue/processors/db/export-blocking.ts b/packages/backend/src/queue/processors/db/export-blocking.ts index 8c886d3b4e..01edaaeb63 100644 --- a/packages/backend/src/queue/processors/db/export-blocking.ts +++ b/packages/backend/src/queue/processors/db/export-blocking.ts @@ -3,7 +3,7 @@ import * as tmp from 'tmp'; import * as fs from 'fs'; import { queueLogger } from '../../logger'; -import addFile from '@/services/drive/add-file'; +import { addFile } from '@/services/drive/add-file'; import * as dateFormat from 'dateformat'; import { getFullApAccount } from '@/misc/convert-host'; import { Users, Blockings } from '@/models/index'; @@ -86,7 +86,7 @@ export async function exportBlocking(job: Bull.Job<DbUserJobData>, done: any): P logger.succ(`Exported to: ${path}`); const fileName = 'blocking-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv'; - const driveFile = await addFile(user, path, fileName, null, null, true); + const driveFile = await addFile({ user, path, name: fileName, force: true }); logger.succ(`Exported to: ${driveFile.id}`); cleanup(); diff --git a/packages/backend/src/queue/processors/db/export-custom-emojis.ts b/packages/backend/src/queue/processors/db/export-custom-emojis.ts index 3930b9d6d4..240a542fec 100644 --- a/packages/backend/src/queue/processors/db/export-custom-emojis.ts +++ b/packages/backend/src/queue/processors/db/export-custom-emojis.ts @@ -6,11 +6,12 @@ import { ulid } from 'ulid'; const mime = require('mime-types'); const archiver = require('archiver'); import { queueLogger } from '../../logger'; -import addFile from '@/services/drive/add-file'; +import { addFile } from '@/services/drive/add-file'; import * as dateFormat from 'dateformat'; import { Users, Emojis } from '@/models/index'; import { } from '@/queue/types'; import { downloadUrl } from '@/misc/download-url'; +import config from '@/config/index'; const logger = queueLogger.createSubLogger('export-custom-emojis'); @@ -52,7 +53,7 @@ export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promi }); }; - await writeMeta(`{"metaVersion":1,"emojis":[`); + await writeMeta(`{"metaVersion":2,"host":"${config.host}","exportedAt":"${new Date().toString()}","emojis":[`); const customEmojis = await Emojis.find({ where: { @@ -64,21 +65,25 @@ export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promi }); for (const emoji of customEmojis) { - const exportId = ulid().toLowerCase(); const ext = mime.extension(emoji.type); - const emojiPath = path + '/' + exportId + (ext ? '.' + ext : ''); + const fileName = emoji.name + (ext ? '.' + ext : ''); + const emojiPath = path + '/' + fileName; fs.writeFileSync(emojiPath, '', 'binary'); let downloaded = false; try { - await downloadUrl(emoji.url, emojiPath); + await downloadUrl(emoji.originalUrl, emojiPath); downloaded = true; } catch (e) { // TODO: 何度か再試行 logger.error(e); } + if (!downloaded) { + fs.unlinkSync(emojiPath); + } + const content = JSON.stringify({ - id: exportId, + fileName: fileName, downloaded: downloaded, emoji: emoji, }); @@ -106,7 +111,7 @@ export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promi logger.succ(`Exported to: ${archivePath}`); const fileName = 'custom-emojis-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.zip'; - const driveFile = await addFile(user, archivePath, fileName, null, null, true); + const driveFile = await addFile({ user, path: archivePath, name: fileName, force: true }); logger.succ(`Exported to: ${driveFile.id}`); cleanup(); diff --git a/packages/backend/src/queue/processors/db/export-following.ts b/packages/backend/src/queue/processors/db/export-following.ts index fbb9e25247..06572acec1 100644 --- a/packages/backend/src/queue/processors/db/export-following.ts +++ b/packages/backend/src/queue/processors/db/export-following.ts @@ -3,7 +3,7 @@ import * as tmp from 'tmp'; import * as fs from 'fs'; import { queueLogger } from '../../logger'; -import addFile from '@/services/drive/add-file'; +import { addFile } from '@/services/drive/add-file'; import * as dateFormat from 'dateformat'; import { getFullApAccount } from '@/misc/convert-host'; import { Users, Followings, Mutings } from '@/models/index'; @@ -87,7 +87,7 @@ export async function exportFollowing(job: Bull.Job<DbUserJobData>, done: () => logger.succ(`Exported to: ${path}`); const fileName = 'following-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv'; - const driveFile = await addFile(user, path, fileName, null, null, true); + const driveFile = await addFile({ user, path, name: fileName, force: true }); logger.succ(`Exported to: ${driveFile.id}`); cleanup(); diff --git a/packages/backend/src/queue/processors/db/export-mute.ts b/packages/backend/src/queue/processors/db/export-mute.ts index 0b1fd24fe0..4a856f8ef9 100644 --- a/packages/backend/src/queue/processors/db/export-mute.ts +++ b/packages/backend/src/queue/processors/db/export-mute.ts @@ -3,7 +3,7 @@ import * as tmp from 'tmp'; import * as fs from 'fs'; import { queueLogger } from '../../logger'; -import addFile from '@/services/drive/add-file'; +import { addFile } from '@/services/drive/add-file'; import * as dateFormat from 'dateformat'; import { getFullApAccount } from '@/misc/convert-host'; import { Users, Mutings } from '@/models/index'; @@ -86,7 +86,7 @@ export async function exportMute(job: Bull.Job<DbUserJobData>, done: any): Promi logger.succ(`Exported to: ${path}`); const fileName = 'mute-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv'; - const driveFile = await addFile(user, path, fileName, null, null, true); + const driveFile = await addFile({ user, path, name: fileName, force: true }); logger.succ(`Exported to: ${driveFile.id}`); cleanup(); diff --git a/packages/backend/src/queue/processors/db/export-notes.ts b/packages/backend/src/queue/processors/db/export-notes.ts index e64e763513..305abf44cf 100644 --- a/packages/backend/src/queue/processors/db/export-notes.ts +++ b/packages/backend/src/queue/processors/db/export-notes.ts @@ -3,7 +3,7 @@ import * as tmp from 'tmp'; import * as fs from 'fs'; import { queueLogger } from '../../logger'; -import addFile from '@/services/drive/add-file'; +import { addFile } from '@/services/drive/add-file'; import * as dateFormat from 'dateformat'; import { Users, Notes, Polls } from '@/models/index'; import { MoreThan } from 'typeorm'; @@ -95,7 +95,7 @@ export async function exportNotes(job: Bull.Job<DbUserJobData>, done: any): Prom logger.succ(`Exported to: ${path}`); const fileName = 'notes-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.json'; - const driveFile = await addFile(user, path, fileName, null, null, true); + const driveFile = await addFile({ user, path, name: fileName, force: true }); logger.succ(`Exported to: ${driveFile.id}`); cleanup(); diff --git a/packages/backend/src/queue/processors/db/export-user-lists.ts b/packages/backend/src/queue/processors/db/export-user-lists.ts index 44a8f9f671..f907cf9526 100644 --- a/packages/backend/src/queue/processors/db/export-user-lists.ts +++ b/packages/backend/src/queue/processors/db/export-user-lists.ts @@ -3,7 +3,7 @@ import * as tmp from 'tmp'; import * as fs from 'fs'; import { queueLogger } from '../../logger'; -import addFile from '@/services/drive/add-file'; +import { addFile } from '@/services/drive/add-file'; import * as dateFormat from 'dateformat'; import { getFullApAccount } from '@/misc/convert-host'; import { Users, UserLists, UserListJoinings } from '@/models/index'; @@ -63,7 +63,7 @@ export async function exportUserLists(job: Bull.Job<DbUserJobData>, done: any): logger.succ(`Exported to: ${path}`); const fileName = 'user-lists-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv'; - const driveFile = await addFile(user, path, fileName, null, null, true); + const driveFile = await addFile({ user, path, name: fileName, force: true }); logger.succ(`Exported to: ${driveFile.id}`); cleanup(); diff --git a/packages/backend/src/queue/processors/db/import-custom-emojis.ts b/packages/backend/src/queue/processors/db/import-custom-emojis.ts new file mode 100644 index 0000000000..04e93671ed --- /dev/null +++ b/packages/backend/src/queue/processors/db/import-custom-emojis.ts @@ -0,0 +1,85 @@ +import * as Bull from 'bull'; +import * as tmp from 'tmp'; +import * as fs from 'fs'; +const unzipper = require('unzipper'); +import { getConnection } from 'typeorm'; + +import { queueLogger } from '../../logger'; +import { downloadUrl } from '@/misc/download-url'; +import { DriveFiles, Emojis } from '@/models/index'; +import { DbUserImportJobData } from '@/queue/types'; +import { addFile } from '@/services/drive/add-file'; +import { genId } from '@/misc/gen-id'; + +const logger = queueLogger.createSubLogger('import-custom-emojis'); + +// TODO: 名前衝突時の動作を選べるようにする +export async function importCustomEmojis(job: Bull.Job<DbUserImportJobData>, done: any): Promise<void> { + logger.info(`Importing custom emojis ...`); + + const file = await DriveFiles.findOne({ + id: job.data.fileId, + }); + if (file == null) { + done(); + return; + } + + // Create temp dir + const [path, cleanup] = await new Promise<[string, () => void]>((res, rej) => { + tmp.dir((e, path, cleanup) => { + if (e) return rej(e); + res([path, cleanup]); + }); + }); + + logger.info(`Temp dir is ${path}`); + + const destPath = path + '/emojis.zip'; + + try { + fs.writeFileSync(destPath, '', 'binary'); + await downloadUrl(file.url, destPath); + } catch (e) { // TODO: 何度か再試行 + logger.error(e); + throw e; + } + + const outputPath = path + '/emojis'; + const unzipStream = fs.createReadStream(destPath); + const extractor = unzipper.Extract({ path: outputPath }); + extractor.on('close', async () => { + const metaRaw = fs.readFileSync(outputPath + '/meta.json', 'utf-8'); + const meta = JSON.parse(metaRaw); + + for (const record of meta.emojis) { + if (!record.downloaded) continue; + const emojiInfo = record.emoji; + const emojiPath = outputPath + '/' + record.fileName; + await Emojis.delete({ + name: emojiInfo.name, + }); + const driveFile = await addFile({ user: null, path: emojiPath, name: record.fileName, force: true }); + const emoji = await Emojis.insert({ + id: genId(), + updatedAt: new Date(), + name: emojiInfo.name, + category: emojiInfo.category, + host: null, + aliases: emojiInfo.aliases, + originalUrl: driveFile.url, + publicUrl: driveFile.webpublicUrl ?? driveFile.url, + type: driveFile.webpublicType ?? driveFile.type, + }).then(x => Emojis.findOneOrFail(x.identifiers[0])); + } + + await getConnection().queryResultCache!.remove(['meta_emojis']); + + cleanup(); + + logger.succ('Imported'); + done(); + }); + unzipStream.pipe(extractor); + logger.succ(`Unzipping to ${outputPath}`); +} diff --git a/packages/backend/src/queue/processors/db/import-user-lists.ts b/packages/backend/src/queue/processors/db/import-user-lists.ts index 8245010de0..e060e86dd8 100644 --- a/packages/backend/src/queue/processors/db/import-user-lists.ts +++ b/packages/backend/src/queue/processors/db/import-user-lists.ts @@ -46,13 +46,13 @@ export async function importUserLists(job: Bull.Job<DbUserImportJobData>, done: }); if (list == null) { - list = await UserLists.save({ + list = await UserLists.insert({ id: genId(), createdAt: new Date(), userId: user.id, name: listName, userIds: [], - }); + }).then(x => UserLists.findOneOrFail(x.identifiers[0])); } let target = isSelfHost(host!) ? await Users.findOne({ diff --git a/packages/backend/src/queue/processors/db/index.ts b/packages/backend/src/queue/processors/db/index.ts index 1542f401ef..5fffa378f5 100644 --- a/packages/backend/src/queue/processors/db/index.ts +++ b/packages/backend/src/queue/processors/db/index.ts @@ -12,6 +12,7 @@ import { importUserLists } from './import-user-lists'; import { deleteAccount } from './delete-account'; import { importMuting } from './import-muting'; import { importBlocking } from './import-blocking'; +import { importCustomEmojis } from './import-custom-emojis'; const jobs = { deleteDriveFiles, @@ -25,6 +26,7 @@ const jobs = { importMuting, importBlocking, importUserLists, + importCustomEmojis, deleteAccount, } as Record<string, Bull.ProcessCallbackFunction<DbJobData> | Bull.ProcessPromiseFunction<DbJobData>>; diff --git a/packages/backend/src/remote/activitypub/models/image.ts b/packages/backend/src/remote/activitypub/models/image.ts index 902eb36a17..6f60b7827d 100644 --- a/packages/backend/src/remote/activitypub/models/image.ts +++ b/packages/backend/src/remote/activitypub/models/image.ts @@ -1,4 +1,4 @@ -import uploadFromUrl from '@/services/drive/upload-from-url'; +import { uploadFromUrl } from '@/services/drive/upload-from-url'; import { IRemoteUser } from '@/models/entities/user'; import Resolver from '../resolver'; import { fetchMeta } from '@/misc/fetch-meta'; @@ -28,9 +28,15 @@ export async function createImage(actor: IRemoteUser, value: any): Promise<Drive logger.info(`Creating the Image: ${image.url}`); const instance = await fetchMeta(); - const cache = instance.cacheRemoteFiles; - let file = await uploadFromUrl(image.url, actor, null, image.url, image.sensitive, false, !cache, truncate(image.name, DB_MAX_IMAGE_COMMENT_LENGTH)); + let file = await uploadFromUrl({ + url: image.url, + user: actor, + uri: image.url, + sensitive: image.sensitive, + isLink: !instance.cacheRemoteFiles, + comment: truncate(image.name, DB_MAX_IMAGE_COMMENT_LENGTH) + }); if (file.isLink) { // URLが異なっている場合、同じ画像が以前に異なるURLで登録されていたということなので、 diff --git a/packages/backend/src/remote/activitypub/models/note.ts b/packages/backend/src/remote/activitypub/models/note.ts index cca054b52e..6847925a51 100644 --- a/packages/backend/src/remote/activitypub/models/note.ts +++ b/packages/backend/src/remote/activitypub/models/note.ts @@ -320,14 +320,15 @@ export async function extractEmojis(tags: IObject | IObject[], host: string): Pr if ((tag.updated != null && exists.updatedAt == null) || (tag.id != null && exists.uri == null) || (tag.updated != null && exists.updatedAt != null && new Date(tag.updated) > exists.updatedAt) - || (tag.icon!.url !== exists.url) + || (tag.icon!.url !== exists.originalUrl) ) { await Emojis.update({ host, name, }, { uri: tag.id, - url: tag.icon!.url, + originalUrl: tag.icon!.url, + publicUrl: tag.icon!.url, updatedAt: new Date(), }); @@ -342,14 +343,15 @@ export async function extractEmojis(tags: IObject | IObject[], host: string): Pr logger.info(`register emoji host=${host}, name=${name}`); - return await Emojis.save({ + return await Emojis.insert({ id: genId(), host, name, uri: tag.id, - url: tag.icon!.url, + originalUrl: tag.icon!.url, + publicUrl: tag.icon!.url, updatedAt: new Date(), aliases: [], - } as Partial<Emoji>); + } as Partial<Emoji>).then(x => Emojis.findOneOrFail(x.identifiers[0])); })); } diff --git a/packages/backend/src/remote/activitypub/renderer/emoji.ts b/packages/backend/src/remote/activitypub/renderer/emoji.ts index 9d08c8ba81..e7ae7d959a 100644 --- a/packages/backend/src/remote/activitypub/renderer/emoji.ts +++ b/packages/backend/src/remote/activitypub/renderer/emoji.ts @@ -9,6 +9,6 @@ export default (emoji: Emoji) => ({ icon: { type: 'Image', mediaType: emoji.type || 'image/png', - url: emoji.url, + url: emoji.publicUrl || emoji.originalUrl, // || emoji.originalUrl してるのは後方互換性のため }, }); diff --git a/packages/backend/src/remote/activitypub/renderer/flag.ts b/packages/backend/src/remote/activitypub/renderer/flag.ts new file mode 100644 index 0000000000..60ac496509 --- /dev/null +++ b/packages/backend/src/remote/activitypub/renderer/flag.ts @@ -0,0 +1,15 @@ +import config from '@/config/index'; +import { IObject, IActivity } from '@/remote/activitypub/type'; +import { ILocalUser, IRemoteUser } from '@/models/entities/user'; +import { getInstanceActor } from '@/services/instance-actor'; + +// to anonymise reporters, the reporting actor must be a system user +// object has to be a uri or array of uris +export const renderFlag = (user: ILocalUser, object: [string], content: string): IActivity => { + return { + type: 'Flag', + actor: `${config.url}/users/${user.id}`, + content, + object, + }; +}; diff --git a/packages/backend/src/remote/activitypub/renderer/index.ts b/packages/backend/src/remote/activitypub/renderer/index.ts index 911118e4c9..cffc9bfe04 100644 --- a/packages/backend/src/remote/activitypub/renderer/index.ts +++ b/packages/backend/src/remote/activitypub/renderer/index.ts @@ -32,7 +32,7 @@ export const renderActivity = (x: any): IActivity | null => { PropertyValue: 'schema:PropertyValue', value: 'schema:value', // Misskey - misskey: `${config.url}/ns#`, + misskey: 'https://misskey-hub.net/ns#', '_misskey_content': 'misskey:_misskey_content', '_misskey_quote': 'misskey:_misskey_quote', '_misskey_reaction': 'misskey:_misskey_reaction', diff --git a/packages/backend/src/remote/resolve-user.ts b/packages/backend/src/remote/resolve-user.ts index df6226cc50..747735ecaa 100644 --- a/packages/backend/src/remote/resolve-user.ts +++ b/packages/backend/src/remote/resolve-user.ts @@ -37,7 +37,7 @@ export async function resolveUser(username: string, host: string | null, option? }); } - const user = await Users.findOne({ usernameLower, host }, option) as IRemoteUser; + const user = await Users.findOne({ usernameLower, host }, option) as IRemoteUser | null; const acctLower = `${usernameLower}@${host}`; diff --git a/packages/backend/src/server/activitypub.ts b/packages/backend/src/server/activitypub.ts index da6a00e58e..bbbc231b8c 100644 --- a/packages/backend/src/server/activitypub.ts +++ b/packages/backend/src/server/activitypub.ts @@ -67,7 +67,7 @@ router.get('/notes/:note', async (ctx, next) => { const note = await Notes.findOne({ id: ctx.params.note, - visibility: In(['public', 'home']), + visibility: In(['public' as const, 'home' as const]), localOnly: false, }); @@ -96,7 +96,7 @@ router.get('/notes/:note/activity', async ctx => { const note = await Notes.findOne({ id: ctx.params.note, userHost: null, - visibility: In(['public', 'home']), + visibility: In(['public' as const, 'home' as const]), localOnly: false, }); diff --git a/packages/backend/src/server/api/authenticate.ts b/packages/backend/src/server/api/authenticate.ts index d33e9e3753..9e2f3eb743 100644 --- a/packages/backend/src/server/api/authenticate.ts +++ b/packages/backend/src/server/api/authenticate.ts @@ -10,7 +10,7 @@ export class AuthenticationError extends Error { } } -export default async (token: string): Promise<[User | null | undefined, App | null | undefined]> => { +export default async (token: string | null): Promise<[User | null | undefined, AccessToken | null | undefined]> => { if (token == null) { return [null, null]; } diff --git a/packages/backend/src/server/api/call.ts b/packages/backend/src/server/api/call.ts index 36aadb532b..399ee65bde 100644 --- a/packages/backend/src/server/api/call.ts +++ b/packages/backend/src/server/api/call.ts @@ -1,5 +1,5 @@ import { performance } from 'perf_hooks'; -import limiter from './limiter'; +import { limiter } from './limiter'; import { User } from '@/models/entities/user'; import endpoints from './endpoints'; import { ApiError } from './error'; diff --git a/packages/backend/src/server/api/common/signin.ts b/packages/backend/src/server/api/common/signin.ts index b713260ac6..df986fc457 100644 --- a/packages/backend/src/server/api/common/signin.ts +++ b/packages/backend/src/server/api/common/signin.ts @@ -29,14 +29,14 @@ export default function(ctx: Koa.Context, user: ILocalUser, redirect = false) { (async () => { // Append signin history - const record = await Signins.save({ + const record = await Signins.insert({ id: genId(), createdAt: new Date(), userId: user.id, ip: ctx.ip, headers: ctx.headers, success: true, - }); + }).then(x => Signins.findOneOrFail(x.identifiers[0])); // Publish signin event publishMainStream(user.id, 'signin', await Signins.pack(record)); diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index a61b3f564c..bb4e972b8e 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -3,7 +3,7 @@ import { dirname } from 'path'; import { Context } from 'cafy'; import * as path from 'path'; import * as glob from 'glob'; -import { SimpleSchema } from '@/misc/simple-schema'; +import { Schema } from '@/misc/schema'; //const _filename = fileURLToPath(import.meta.url); const _filename = __filename; @@ -18,87 +18,87 @@ export type Param = { }; export interface IEndpointMeta { - stability?: string; //'deprecated' | 'experimental' | 'stable'; + readonly stability?: 'deprecated' | 'experimental' | 'stable'; - tags?: string[]; + readonly tags?: ReadonlyArray<string>; - params?: { - [key: string]: Param; + readonly params?: { + readonly [key: string]: Param; }; - errors?: { - [key: string]: { - message: string; - code: string; - id: string; + readonly errors?: { + readonly [key: string]: { + readonly message: string; + readonly code: string; + readonly id: string; }; }; - res?: SimpleSchema; + readonly res?: Schema; /** * このエンドポイントにリクエストするのにユーザー情報が必須か否か * 省略した場合は false として解釈されます。 */ - requireCredential?: boolean; + readonly requireCredential?: boolean; /** * 管理者のみ使えるエンドポイントか否か */ - requireAdmin?: boolean; + readonly requireAdmin?: boolean; /** * 管理者またはモデレーターのみ使えるエンドポイントか否か */ - requireModerator?: boolean; + readonly requireModerator?: boolean; /** * エンドポイントのリミテーションに関するやつ * 省略した場合はリミテーションは無いものとして解釈されます。 * また、withCredential が false の場合はリミテーションを行うことはできません。 */ - limit?: { + readonly limit?: { /** * 複数のエンドポイントでリミットを共有したい場合に指定するキー */ - key?: string; + readonly key?: string; /** * リミットを適用する期間(ms) * このプロパティを設定する場合、max プロパティも設定する必要があります。 */ - duration?: number; + readonly duration?: number; /** * durationで指定した期間内にいくつまでリクエストできるのか * このプロパティを設定する場合、duration プロパティも設定する必要があります。 */ - max?: number; + readonly max?: number; /** * 最低でもどれくらいの間隔を開けてリクエストしなければならないか(ms) */ - minInterval?: number; + readonly minInterval?: number; }; /** * ファイルの添付を必要とするか否か * 省略した場合は false として解釈されます。 */ - requireFile?: boolean; + readonly requireFile?: boolean; /** * サードパーティアプリからはリクエストすることができないか否か * 省略した場合は false として解釈されます。 */ - secure?: boolean; + readonly secure?: boolean; /** * エンドポイントの種類 * パーミッションの実現に利用されます。 */ - kind?: string; + readonly kind?: string; } export interface IEndpoint { diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts index 774506bf0d..ed7b146d03 100644 --- a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts +++ b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -46,70 +46,76 @@ export const meta = { ]), default: 'combined', }, + + forwarded: { + validator: $.optional.bool, + default: false, + }, }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - nullable: false as const, optional: false as const, + type: 'string', + nullable: false, optional: false, format: 'id', example: 'xxxxxxxxxx', }, createdAt: { - type: 'string' as const, - nullable: false as const, optional: false as const, + type: 'string', + nullable: false, optional: false, format: 'date-time', }, comment: { - type: 'string' as const, - nullable: false as const, optional: false as const, + type: 'string', + nullable: false, optional: false, }, resolved: { - type: 'boolean' as const, - nullable: false as const, optional: false as const, + type: 'boolean', + nullable: false, optional: false, example: false, }, reporterId: { - type: 'string' as const, - nullable: false as const, optional: false as const, + type: 'string', + nullable: false, optional: false, format: 'id', }, targetUserId: { - type: 'string' as const, - nullable: false as const, optional: false as const, + type: 'string', + nullable: false, optional: false, format: 'id', }, assigneeId: { - type: 'string' as const, - nullable: true as const, optional: false as const, + type: 'string', + nullable: true, optional: false, format: 'id', }, reporter: { - type: 'object' as const, - nullable: false as const, optional: false as const, + type: 'object', + nullable: false, optional: false, ref: 'User', }, targetUser: { - type: 'object' as const, - nullable: false as const, optional: false as const, + type: 'object', + nullable: false, optional: false, ref: 'User', }, assignee: { - type: 'object' as const, - nullable: true as const, optional: true as const, + type: 'object', + nullable: true, optional: true, ref: 'User', }, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const query = makePaginationQuery(AbuseUserReports.createQueryBuilder('report'), ps.sinceId, ps.untilId); diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts index b1cdb29a56..20f1232959 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts @@ -16,18 +16,19 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'User', properties: { token: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, _me) => { const me = _me ? await Users.findOneOrFail(_me.id) : null; const noUsers = (await Users.count({ diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts b/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts index 3881e02bba..1701c1e3a7 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts @@ -9,7 +9,7 @@ import { ID } from '@/misc/cafy-id'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -17,8 +17,9 @@ export const meta = { validator: $.type(ID), }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const user = await Users.findOne(ps.userId); diff --git a/packages/backend/src/server/api/endpoints/admin/ad/create.ts b/packages/backend/src/server/api/endpoints/admin/ad/create.ts index e41d015646..00ad2012fe 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/create.ts @@ -6,7 +6,7 @@ import { genId } from '@/misc/gen-id'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -32,8 +32,9 @@ export const meta = { validator: $.str.min(1), }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { await Ads.insert({ id: genId(), diff --git a/packages/backend/src/server/api/endpoints/admin/ad/delete.ts b/packages/backend/src/server/api/endpoints/admin/ad/delete.ts index ab5cd96d0f..c0124e2484 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/delete.ts @@ -7,7 +7,7 @@ import { ApiError } from '../../../error'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -23,8 +23,9 @@ export const meta = { id: 'ccac9863-3a03-416e-b899-8a64041118b1', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const ad = await Ads.findOne(ps.id); diff --git a/packages/backend/src/server/api/endpoints/admin/ad/list.ts b/packages/backend/src/server/api/endpoints/admin/ad/list.ts index f22dd0e961..7a83637f3b 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/list.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../../common/make-pagination-query'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -24,8 +24,9 @@ export const meta = { validator: $.optional.type(ID), }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const query = makePaginationQuery(Ads.createQueryBuilder('ad'), ps.sinceId, ps.untilId) .andWhere('ad.expiresAt > :now', { now: new Date() }); diff --git a/packages/backend/src/server/api/endpoints/admin/ad/update.ts b/packages/backend/src/server/api/endpoints/admin/ad/update.ts index 1d91cfd162..c2b09ab9cf 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/update.ts @@ -7,7 +7,7 @@ import { ApiError } from '../../../error'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -44,8 +44,9 @@ export const meta = { id: 'b7aa1727-1354-47bc-a182-3a9c3973d300', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const ad = await Ads.findOne(ps.id); diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts index 886cacff73..24c4caa37d 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts @@ -6,7 +6,7 @@ import { genId } from '@/misc/gen-id'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -22,50 +22,51 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', example: 'xxxxxxxxxx', }, createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'date-time', }, updatedAt: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, format: 'date-time', }, title: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, text: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, imageUrl: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { - const announcement = await Announcements.save({ + const announcement = await Announcements.insert({ id: genId(), createdAt: new Date(), updatedAt: null, title: ps.title, text: ps.text, imageUrl: ps.imageUrl, - }); + }).then(x => Announcements.findOneOrFail(x.identifiers[0])); return announcement; }); diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts b/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts index ade9ef581e..5548f99006 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts @@ -7,7 +7,7 @@ import { ApiError } from '../../../error'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -23,8 +23,9 @@ export const meta = { id: 'ecad8040-a276-4e85-bda9-015a708d291e', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const announcement = await Announcements.findOne(ps.id); diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts index f25b5e8efc..e5cc53ccdd 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../../common/make-pagination-query'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -26,49 +26,50 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', example: 'xxxxxxxxxx', }, createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'date-time', }, updatedAt: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, format: 'date-time', }, text: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, title: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, imageUrl: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, reads: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const query = makePaginationQuery(Announcements.createQueryBuilder('announcement'), ps.sinceId, ps.untilId); diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts index 3fa8440ebd..f66293bb18 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts @@ -7,7 +7,7 @@ import { ApiError } from '../../../error'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -32,8 +32,9 @@ export const meta = { id: 'd3aae5a7-6372-4cb4-b61c-f511ffc2d7cc', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const announcement = await Announcements.findOne(ps.id); diff --git a/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts b/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts index 50979d53e8..249e63a0f8 100644 --- a/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts @@ -7,7 +7,7 @@ import { ID } from '@/misc/cafy-id'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -15,8 +15,9 @@ export const meta = { validator: $.type(ID), }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const files = await DriveFiles.find({ userId: ps.userId, diff --git a/packages/backend/src/server/api/endpoints/admin/delete-logs.ts b/packages/backend/src/server/api/endpoints/admin/delete-logs.ts deleted file mode 100644 index 9d37ceb434..0000000000 --- a/packages/backend/src/server/api/endpoints/admin/delete-logs.ts +++ /dev/null @@ -1,13 +0,0 @@ -import define from '../../define'; -import { Logs } from '@/models/index'; - -export const meta = { - tags: ['admin'], - - requireCredential: true as const, - requireModerator: true, -}; - -export default define(meta, async (ps) => { - await Logs.clear(); // TRUNCATE -}); diff --git a/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts b/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts index 76a6acff59..acabbfef5c 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts @@ -4,10 +4,11 @@ import { createCleanRemoteFilesJob } from '@/queue/index'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { createCleanRemoteFilesJob(); }); diff --git a/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts b/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts index ae0e396b4f..452e7069a8 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts @@ -6,10 +6,11 @@ import { DriveFiles } from '@/models/index'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const files = await DriveFiles.find({ userId: IsNull(), diff --git a/packages/backend/src/server/api/endpoints/admin/drive/files.ts b/packages/backend/src/server/api/endpoints/admin/drive/files.ts index ee07245db7..264f549867 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/files.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/files.ts @@ -7,7 +7,7 @@ import { ID } from '@/misc/cafy-id'; export const meta = { tags: ['admin'], - requireCredential: false as const, + requireCredential: false, requireModerator: true, params: { @@ -44,16 +44,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'DriveFile', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = makePaginationQuery(DriveFiles.createQueryBuilder('file'), ps.sinceId, ps.untilId); diff --git a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts index ab8e3d68e9..5d9a1f2703 100644 --- a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts +++ b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts @@ -7,7 +7,7 @@ import { DriveFiles } from '@/models/index'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -29,138 +29,139 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', example: 'xxxxxxxxxx', }, createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'date-time', }, userId: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, format: 'id', example: 'xxxxxxxxxx', }, userHost: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, md5: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'md5', example: '15eca7fba0480996e2245f5185bf39f2', }, name: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, example: 'lenna.jpg', }, type: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, example: 'image/jpeg', }, size: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, example: 51469, }, comment: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, blurhash: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, properties: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { width: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, example: 1280, }, height: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, example: 720, }, avgColor: { - type: 'string' as const, - optional: true as const, nullable: false as const, + type: 'string', + optional: true, nullable: false, example: 'rgb(40,65,87)', }, }, }, storedInternal: { - type: 'boolean' as const, - optional: false as const, nullable: true as const, + type: 'boolean', + optional: false, nullable: true, example: true, }, url: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, format: 'url', }, thumbnailUrl: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, format: 'url', }, webpublicUrl: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, format: 'url', }, accessKey: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, thumbnailAccessKey: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, webpublicAccessKey: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, uri: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, src: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, folderId: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, format: 'id', example: 'xxxxxxxxxx', }, isSensitive: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, isLink: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const file = ps.fileId ? await DriveFiles.findOne(ps.fileId) : await DriveFiles.findOne({ where: [{ diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts new file mode 100644 index 0000000000..f0fd73c276 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts @@ -0,0 +1,39 @@ +import $ from 'cafy'; +import define from '../../../define'; +import { ID } from '@/misc/cafy-id'; +import { Emojis } from '@/models/index'; +import { getConnection, In } from 'typeorm'; +import { ApiError } from '../../../error'; + +export const meta = { + tags: ['admin'], + + requireCredential: true, + requireModerator: true, + + params: { + ids: { + validator: $.arr($.type(ID)), + }, + + aliases: { + validator: $.arr($.str), + }, + }, +} as const; + +// eslint-disable-next-line import/no-default-export +export default define(meta, async (ps) => { + const emojis = await Emojis.find({ + id: In(ps.ids), + }); + + for (const emoji of emojis) { + await Emojis.update(emoji.id, { + updatedAt: new Date(), + aliases: [...new Set(emoji.aliases.concat(ps.aliases))], + }); + } + + await getConnection().queryResultCache!.remove(['meta_emojis']); +}); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts index 407d9920d9..1dfeae262f 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts @@ -12,7 +12,7 @@ import { publishBroadcastStream } from '@/services/stream'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -28,8 +28,9 @@ export const meta = { id: 'fc46b5a4-6b92-4c33-ac66-b806659bb5cf', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const file = await DriveFiles.findOne(ps.fileId); @@ -37,16 +38,17 @@ export default define(meta, async (ps, me) => { const name = file.name.split('.')[0].match(/^[a-z0-9_]+$/) ? file.name.split('.')[0] : `_${rndstr('a-z0-9', 8)}_`; - const emoji = await Emojis.save({ + const emoji = await Emojis.insert({ id: genId(), updatedAt: new Date(), name: name, category: null, host: null, aliases: [], - url: file.url, - type: file.type, - }); + originalUrl: file.url, + publicUrl: file.webpublicUrl ?? file.url, + type: file.webpublicType ?? file.type, + }).then(x => Emojis.findOneOrFail(x.identifiers[0])); await getConnection().queryResultCache!.remove(['meta_emojis']); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts index ae02df65e2..17cbf208aa 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts @@ -6,13 +6,13 @@ import { getConnection } from 'typeorm'; import { ApiError } from '../../../error'; import { DriveFile } from '@/models/entities/drive-file'; import { ID } from '@/misc/cafy-id'; -import uploadFromUrl from '@/services/drive/upload-from-url'; +import { uploadFromUrl } from '@/services/drive/upload-from-url'; import { publishBroadcastStream } from '@/services/stream'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -30,18 +30,19 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const emoji = await Emojis.findOne(ps.emojiId); @@ -53,7 +54,7 @@ export default define(meta, async (ps, me) => { try { // Create file - driveFile = await uploadFromUrl(emoji.url, null, null, null, false, true); + driveFile = await uploadFromUrl({ url: emoji.originalUrl, user: null, force: true }); } catch (e) { throw new ApiError(); } @@ -64,9 +65,9 @@ export default define(meta, async (ps, me) => { name: emoji.name, host: null, aliases: [], - url: driveFile.url, - type: driveFile.type, - fileId: driveFile.id, + originalUrl: driveFile.url, + publicUrl: driveFile.webpublicUrl ?? driveFile.url, + type: driveFile.webpublicType ?? driveFile.type, }).then(x => Emojis.findOneOrFail(x.identifiers[0])); await getConnection().queryResultCache!.remove(['meta_emojis']); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts new file mode 100644 index 0000000000..797a5de672 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts @@ -0,0 +1,37 @@ +import $ from 'cafy'; +import define from '../../../define'; +import { ID } from '@/misc/cafy-id'; +import { Emojis } from '@/models/index'; +import { getConnection, In } from 'typeorm'; +import { insertModerationLog } from '@/services/insert-moderation-log'; +import { ApiError } from '../../../error'; + +export const meta = { + tags: ['admin'], + + requireCredential: true, + requireModerator: true, + + params: { + ids: { + validator: $.arr($.type(ID)), + }, + }, +} as const; + +// eslint-disable-next-line import/no-default-export +export default define(meta, async (ps, me) => { + const emojis = await Emojis.find({ + id: In(ps.ids), + }); + + for (const emoji of emojis) { + await Emojis.delete(emoji.id); + + await getConnection().queryResultCache!.remove(['meta_emojis']); + + insertModerationLog(me, 'deleteEmoji', { + emoji: emoji, + }); + } +}); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/remove.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts index 78085470a9..1580439024 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/remove.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts @@ -9,7 +9,7 @@ import { ApiError } from '../../../error'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -25,8 +25,9 @@ export const meta = { id: 'be83669b-773a-44b7-b1f8-e5e5170ac3c2', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const emoji = await Emojis.findOne(ps.id); @@ -36,7 +37,7 @@ export default define(meta, async (ps, me) => { await getConnection().queryResultCache!.remove(['meta_emojis']); - insertModerationLog(me, 'removeEmoji', { + insertModerationLog(me, 'deleteEmoji', { emoji: emoji, }); }); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts b/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts new file mode 100644 index 0000000000..8856a38f24 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts @@ -0,0 +1,21 @@ +import $ from 'cafy'; +import define from '../../../define'; +import { createImportCustomEmojisJob } from '@/queue/index'; +import ms from 'ms'; +import { ID } from '@/misc/cafy-id'; + +export const meta = { + secure: true, + requireCredential: true, + requireModerator: true, + params: { + fileId: { + validator: $.type(ID), + }, + }, +} as const; + +// eslint-disable-next-line import/no-default-export +export default define(meta, async (ps, user) => { + createImportCustomEmojisJob(user, ps.fileId); +}); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts index 090ba2b64a..6e502547f5 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts @@ -8,7 +8,7 @@ import { ID } from '@/misc/cafy-id'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -37,46 +37,47 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, aliases: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, name: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, category: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, host: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, url: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const q = makePaginationQuery(Emojis.createQueryBuilder('emoji'), ps.sinceId, ps.untilId); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts index 89cb60acaf..76ef190f94 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts @@ -8,7 +8,7 @@ import { Emoji } from '@/models/entities/emoji'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -32,46 +32,47 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, aliases: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, name: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, category: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, host: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, url: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const q = makePaginationQuery(Emojis.createQueryBuilder('emoji'), ps.sinceId, ps.untilId) .andWhere(`emoji.host IS NULL`); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts new file mode 100644 index 0000000000..c49f84b7fb --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts @@ -0,0 +1,39 @@ +import $ from 'cafy'; +import define from '../../../define'; +import { ID } from '@/misc/cafy-id'; +import { Emojis } from '@/models/index'; +import { getConnection, In } from 'typeorm'; +import { ApiError } from '../../../error'; + +export const meta = { + tags: ['admin'], + + requireCredential: true, + requireModerator: true, + + params: { + ids: { + validator: $.arr($.type(ID)), + }, + + aliases: { + validator: $.arr($.str), + }, + }, +} as const; + +// eslint-disable-next-line import/no-default-export +export default define(meta, async (ps) => { + const emojis = await Emojis.find({ + id: In(ps.ids), + }); + + for (const emoji of emojis) { + await Emojis.update(emoji.id, { + updatedAt: new Date(), + aliases: emoji.aliases.filter(x => !ps.aliases.includes(x)), + }); + } + + await getConnection().queryResultCache!.remove(['meta_emojis']); +}); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts new file mode 100644 index 0000000000..06197820f0 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts @@ -0,0 +1,35 @@ +import $ from 'cafy'; +import define from '../../../define'; +import { ID } from '@/misc/cafy-id'; +import { Emojis } from '@/models/index'; +import { getConnection, In } from 'typeorm'; +import { ApiError } from '../../../error'; + +export const meta = { + tags: ['admin'], + + requireCredential: true, + requireModerator: true, + + params: { + ids: { + validator: $.arr($.type(ID)), + }, + + aliases: { + validator: $.arr($.str), + }, + }, +} as const; + +// eslint-disable-next-line import/no-default-export +export default define(meta, async (ps) => { + await Emojis.update({ + id: In(ps.ids), + }, { + updatedAt: new Date(), + aliases: ps.aliases, + }); + + await getConnection().queryResultCache!.remove(['meta_emojis']); +}); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts new file mode 100644 index 0000000000..f0645f111b --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts @@ -0,0 +1,35 @@ +import $ from 'cafy'; +import define from '../../../define'; +import { ID } from '@/misc/cafy-id'; +import { Emojis } from '@/models/index'; +import { getConnection, In } from 'typeorm'; +import { ApiError } from '../../../error'; + +export const meta = { + tags: ['admin'], + + requireCredential: true, + requireModerator: true, + + params: { + ids: { + validator: $.arr($.type(ID)), + }, + + category: { + validator: $.optional.nullable.str, + }, + }, +} as const; + +// eslint-disable-next-line import/no-default-export +export default define(meta, async (ps) => { + await Emojis.update({ + id: In(ps.ids), + }, { + updatedAt: new Date(), + category: ps.category, + }); + + await getConnection().queryResultCache!.remove(['meta_emojis']); +}); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts index c61d8cb320..54a2cf9517 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts @@ -8,7 +8,7 @@ import { ApiError } from '../../../error'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -36,8 +36,9 @@ export const meta = { id: '684dec9d-a8c2-4364-9aa8-456c49cb1dc8', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const emoji = await Emojis.findOne(ps.id); diff --git a/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts b/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts index d9e003d35d..db023c6f0b 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts @@ -6,7 +6,7 @@ import { DriveFiles } from '@/models/index'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -14,8 +14,9 @@ export const meta = { validator: $.str, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const files = await DriveFiles.find({ userHost: ps.host, diff --git a/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts b/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts index 9e7a8ec476..b68252ef2e 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts @@ -7,7 +7,7 @@ import { fetchInstanceMetadata } from '@/services/fetch-instance-metadata'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -15,8 +15,9 @@ export const meta = { validator: $.str, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const instance = await Instances.findOne({ host: toPuny(ps.host) }); diff --git a/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts b/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts index 8fc964fd2f..4de8ad1336 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts @@ -6,7 +6,7 @@ import { Followings, Users } from '@/models/index'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -14,8 +14,9 @@ export const meta = { validator: $.str, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const followings = await Followings.find({ followerHost: ps.host, diff --git a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts index 4ea1275f7c..6ac2f1f467 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts @@ -6,7 +6,7 @@ import { toPuny } from '@/misc/convert-host'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -18,8 +18,9 @@ export const meta = { validator: $.bool, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const instance = await Instances.findOne({ host: toPuny(ps.host) }); diff --git a/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts b/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts index f2b06d0ef2..9a2bccec77 100644 --- a/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts +++ b/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts @@ -2,15 +2,16 @@ import define from '../../define'; import { getConnection } from 'typeorm'; export const meta = { - requireCredential: true as const, + requireCredential: true, requireModerator: true, tags: ['admin'], params: { }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async () => { const stats = await getConnection().query(`SELECT * FROM pg_indexes;`) diff --git a/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts b/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts index 64f0292943..1c5f250676 100644 --- a/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts +++ b/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts @@ -2,7 +2,7 @@ import define from '../../define'; import { getConnection } from 'typeorm'; export const meta = { - requireCredential: true as const, + requireCredential: true, requireModerator: true, tags: ['admin'], @@ -11,8 +11,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, example: { migrations: { count: 66, @@ -20,8 +20,9 @@ export const meta = { }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async () => { const sizes = await getConnection().query(` diff --git a/packages/backend/src/server/api/endpoints/admin/invite.ts b/packages/backend/src/server/api/endpoints/admin/invite.ts index b9452c83d2..3428709c04 100644 --- a/packages/backend/src/server/api/endpoints/admin/invite.ts +++ b/packages/backend/src/server/api/endpoints/admin/invite.ts @@ -6,26 +6,27 @@ import { genId } from '@/misc/gen-id'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: {}, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { code: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, example: '2ERUA5VR', maxLength: 8, minLength: 8, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async () => { const code = rndstr({ length: 8, diff --git a/packages/backend/src/server/api/endpoints/admin/moderators/add.ts b/packages/backend/src/server/api/endpoints/admin/moderators/add.ts index 8e3419bf66..0308cf2761 100644 --- a/packages/backend/src/server/api/endpoints/admin/moderators/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/moderators/add.ts @@ -6,7 +6,7 @@ import { Users } from '@/models/index'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireAdmin: true, params: { @@ -14,8 +14,9 @@ export const meta = { validator: $.type(ID), }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const user = await Users.findOne(ps.userId as string); diff --git a/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts b/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts index 5a5a91e9ce..bdb976e9ec 100644 --- a/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts +++ b/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts @@ -6,7 +6,7 @@ import { Users } from '@/models/index'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireAdmin: true, params: { @@ -14,8 +14,9 @@ export const meta = { validator: $.type(ID), }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const user = await Users.findOne(ps.userId as string); diff --git a/packages/backend/src/server/api/endpoints/admin/promo/create.ts b/packages/backend/src/server/api/endpoints/admin/promo/create.ts index 245780c2d1..f2735ac9f8 100644 --- a/packages/backend/src/server/api/endpoints/admin/promo/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/promo/create.ts @@ -8,7 +8,7 @@ import { PromoNotes } from '@/models/index'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -34,8 +34,9 @@ export const meta = { id: 'ae427aa2-7a41-484f-a18c-2c1104051604', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const note = await getNote(ps.noteId).catch(e => { if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); diff --git a/packages/backend/src/server/api/endpoints/admin/queue/clear.ts b/packages/backend/src/server/api/endpoints/admin/queue/clear.ts index 3a7ae6b4b0..3c8e7a27a2 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/clear.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/clear.ts @@ -5,12 +5,13 @@ import { insertModerationLog } from '@/services/insert-moderation-log'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: {}, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { destroy(); diff --git a/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts b/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts index cb2d1d0d79..4760e2c310 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts @@ -5,25 +5,25 @@ import define from '../../../define'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { anyOf: [ { - type: 'string' as const, + type: 'string', }, { - type: 'number' as const, + type: 'number', }, ], }, @@ -33,8 +33,9 @@ export const meta = { 12, ]], }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const jobs = await deliverQueue.getJobs(['delayed']); diff --git a/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts b/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts index d524002faf..a95aabc506 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts @@ -5,25 +5,25 @@ import { inboxQueue } from '@/queue/queues'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { anyOf: [ { - type: 'string' as const, + type: 'string', }, { - type: 'number' as const, + type: 'number', }, ], }, @@ -33,8 +33,9 @@ export const meta = { 12, ]], }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const jobs = await inboxQueue.getJobs(['delayed']); diff --git a/packages/backend/src/server/api/endpoints/admin/queue/jobs.ts b/packages/backend/src/server/api/endpoints/admin/queue/jobs.ts index ece1d46f3f..df0b4a8f13 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/jobs.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/jobs.ts @@ -5,7 +5,7 @@ import define from '../../../define'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -24,38 +24,39 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, data: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, }, attempts: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, maxAttempts: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, timestamp: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const queue = ps.domain === 'deliver' ? deliverQueue : diff --git a/packages/backend/src/server/api/endpoints/admin/queue/stats.ts b/packages/backend/src/server/api/endpoints/admin/queue/stats.ts index 1fc42ea0b5..dab0be5dbc 100644 --- a/packages/backend/src/server/api/endpoints/admin/queue/stats.ts +++ b/packages/backend/src/server/api/endpoints/admin/queue/stats.ts @@ -4,31 +4,36 @@ import define from '../../../define'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: {}, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { deliver: { + optional: false, nullable: false, ref: 'QueueCount', }, inbox: { + optional: false, nullable: false, ref: 'QueueCount', }, db: { + optional: false, nullable: false, ref: 'QueueCount', }, objectStorage: { + optional: false, nullable: false, ref: 'QueueCount', }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const deliverJobCounts = await deliverQueue.getJobCounts(); const inboxJobCounts = await inboxQueue.getJobCounts(); diff --git a/packages/backend/src/server/api/endpoints/admin/relays/add.ts b/packages/backend/src/server/api/endpoints/admin/relays/add.ts index 80609aee94..65890a00f7 100644 --- a/packages/backend/src/server/api/endpoints/admin/relays/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/relays/add.ts @@ -7,8 +7,8 @@ import { ApiError } from '../../../error'; export const meta = { tags: ['admin'], - requireCredential: true as const, - requireModerator: true as const, + requireCredential: true, + requireModerator: true, params: { inbox: { @@ -25,22 +25,22 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, inbox: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'url', }, status: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, default: 'requesting', enum: [ 'requesting', @@ -50,8 +50,9 @@ export const meta = { }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { try { if (new URL(ps.inbox).protocol !== 'https:') throw 'https only'; diff --git a/packages/backend/src/server/api/endpoints/admin/relays/list.ts b/packages/backend/src/server/api/endpoints/admin/relays/list.ts index ef103fbb31..bdddf13374 100644 --- a/packages/backend/src/server/api/endpoints/admin/relays/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/relays/list.ts @@ -4,32 +4,32 @@ import { listRelay } from '@/services/relay'; export const meta = { tags: ['admin'], - requireCredential: true as const, - requireModerator: true as const, + requireCredential: true, + requireModerator: true, params: { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, inbox: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'url', }, status: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, default: 'requesting', enum: [ 'requesting', @@ -40,8 +40,9 @@ export const meta = { }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { return await listRelay(); }); diff --git a/packages/backend/src/server/api/endpoints/admin/relays/remove.ts b/packages/backend/src/server/api/endpoints/admin/relays/remove.ts index e7ddef30fc..4b04e620c1 100644 --- a/packages/backend/src/server/api/endpoints/admin/relays/remove.ts +++ b/packages/backend/src/server/api/endpoints/admin/relays/remove.ts @@ -5,16 +5,17 @@ import { removeRelay } from '@/services/relay'; export const meta = { tags: ['admin'], - requireCredential: true as const, - requireModerator: true as const, + requireCredential: true, + requireModerator: true, params: { inbox: { validator: $.str, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { return await removeRelay(ps.inbox); }); diff --git a/packages/backend/src/server/api/endpoints/admin/reset-password.ts b/packages/backend/src/server/api/endpoints/admin/reset-password.ts index bdd7ef27b8..b6cf1ee2d0 100644 --- a/packages/backend/src/server/api/endpoints/admin/reset-password.ts +++ b/packages/backend/src/server/api/endpoints/admin/reset-password.ts @@ -8,7 +8,7 @@ import { Users, UserProfiles } from '@/models/index'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -18,19 +18,20 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { password: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, minLength: 8, maxLength: 8, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const user = await Users.findOne(ps.userId as string); diff --git a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts index 94158ecfdb..b00457f092 100644 --- a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts +++ b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts @@ -1,21 +1,32 @@ import $ from 'cafy'; import { ID } from '@/misc/cafy-id'; import define from '../../define'; -import { AbuseUserReports } from '@/models/index'; +import { AbuseUserReports, Users } from '@/models/index'; +import { getInstanceActor } from '@/services/instance-actor'; +import { deliver } from '@/queue/index'; +import { renderActivity } from '@/remote/activitypub/renderer/index'; +import { renderFlag } from '@/remote/activitypub/renderer/flag'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { reportId: { validator: $.type(ID), }, + + forward: { + validator: $.optional.boolean, + required: false, + default: false, + }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const report = await AbuseUserReports.findOne(ps.reportId); @@ -23,8 +34,16 @@ export default define(meta, async (ps, me) => { throw new Error('report not found'); } + if (ps.forward && report.targetUserHost != null) { + const actor = await getInstanceActor(); + const targetUser = await Users.findOne(report.targetUserId); + + deliver(actor, renderActivity(renderFlag(actor, [targetUser.uri], report.comment)), targetUser.inbox); + } + await AbuseUserReports.update(report.id, { resolved: true, assigneeId: me.id, + forwarded: ps.forward && report.targetUserHost != null, }); }); diff --git a/packages/backend/src/server/api/endpoints/admin/resync-chart.ts b/packages/backend/src/server/api/endpoints/admin/resync-chart.ts index e01dfce1b6..d80d2b0426 100644 --- a/packages/backend/src/server/api/endpoints/admin/resync-chart.ts +++ b/packages/backend/src/server/api/endpoints/admin/resync-chart.ts @@ -5,10 +5,11 @@ import { insertModerationLog } from '@/services/insert-moderation-log'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { insertModerationLog(me, 'chartResync'); diff --git a/packages/backend/src/server/api/endpoints/admin/send-email.ts b/packages/backend/src/server/api/endpoints/admin/send-email.ts index 4797aa7a2b..c2972c35fa 100644 --- a/packages/backend/src/server/api/endpoints/admin/send-email.ts +++ b/packages/backend/src/server/api/endpoints/admin/send-email.ts @@ -5,7 +5,7 @@ import { sendEmail } from '@/services/send-email'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -19,8 +19,9 @@ export const meta = { validator: $.str, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { await sendEmail(ps.to, ps.subject, ps.text, ps.text); }); diff --git a/packages/backend/src/server/api/endpoints/admin/server-info.ts b/packages/backend/src/server/api/endpoints/admin/server-info.ts index 4ddc39deb0..cd282e364c 100644 --- a/packages/backend/src/server/api/endpoints/admin/server-info.ts +++ b/packages/backend/src/server/api/endpoints/admin/server-info.ts @@ -5,7 +5,7 @@ import define from '../../define'; import { redisClient } from '../../../../db/redis'; export const meta = { - requireCredential: true as const, + requireCredential: true, requireModerator: true, tags: ['admin', 'meta'], @@ -14,82 +14,83 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { machine: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, os: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, example: 'linux', }, node: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, psql: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, cpu: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { model: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, cores: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, }, }, mem: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { total: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, format: 'bytes', }, }, }, fs: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { total: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, format: 'bytes', }, used: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, format: 'bytes', }, }, }, net: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { interface: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, example: 'eth0', }, }, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async () => { const memStats = await si.mem(); const fsStats = await si.fsSize(); diff --git a/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts b/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts index 19d4be973f..84e2b84bb5 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -26,45 +26,46 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'date-time', }, type: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, info: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, }, userId: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, user: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailed', }, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const query = makePaginationQuery(ModerationLogs.createQueryBuilder('report'), ps.sinceId, ps.untilId); diff --git a/packages/backend/src/server/api/endpoints/admin/show-user.ts b/packages/backend/src/server/api/endpoints/admin/show-user.ts index 21cc9e5abc..c2a6a294b5 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts @@ -6,7 +6,7 @@ import { Users } from '@/models/index'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -16,149 +16,150 @@ export const meta = { }, res: { - type: 'object' as const, - nullable: false as const, optional: false as const, + type: 'object', + nullable: false, optional: false, properties: { id: { - type: 'string' as const, - nullable: false as const, optional: false as const, + type: 'string', + nullable: false, optional: false, format: 'id', }, createdAt: { - type: 'string' as const, - nullable: false as const, optional: false as const, + type: 'string', + nullable: false, optional: false, format: 'date-time', }, updatedAt: { - type: 'string' as const, - nullable: true as const, optional: false as const, + type: 'string', + nullable: true, optional: false, format: 'date-time', }, lastFetchedAt: { - type: 'string' as const, - nullable: true as const, optional: false as const, + type: 'string', + nullable: true, optional: false, }, username: { - type: 'string' as const, - nullable: false as const, optional: false as const, + type: 'string', + nullable: false, optional: false, }, name: { - type: 'string' as const, - nullable: false as const, optional: false as const, + type: 'string', + nullable: true, optional: false, }, folowersCount: { - type: 'number' as const, - nullable: false as const, optional: false as const, + type: 'number', + nullable: false, optional: true, }, followingCount: { - type: 'number' as const, - nullable: false as const, optional: false as const, + type: 'number', + nullable: false, optional: false, }, notesCount: { - type: 'number' as const, - nullable: false as const, optional: false as const, + type: 'number', + nullable: false, optional: false, }, avatarId: { - type: 'string' as const, - nullable: true as const, optional: false as const, + type: 'string', + nullable: true, optional: false, }, bannerId: { - type: 'string' as const, - nullable: true as const, optional: false as const, + type: 'string', + nullable: true, optional: false, }, tags: { - type: 'array' as const, - nullable: false as const, optional: false as const, + type: 'array', + nullable: false, optional: false, items: { - type: 'string' as const, - nullable: false as const, optional: false as const, + type: 'string', + nullable: false, optional: false, }, }, avatarUrl: { - type: 'string' as const, - nullable: true as const, optional: false as const, + type: 'string', + nullable: true, optional: false, format: 'url', }, bannerUrl: { - type: 'string' as const, - nullable: true as const, optional: false as const, + type: 'string', + nullable: true, optional: false, format: 'url', }, avatarBlurhash: { - type: 'any' as const, - nullable: true as const, optional: false as const, + type: 'any', + nullable: true, optional: false, default: null, }, bannerBlurhash: { - type: 'any' as const, - nullable: true as const, optional: false as const, + type: 'any', + nullable: true, optional: false, default: null, }, isSuspended: { - type: 'boolean' as const, - nullable: false as const, optional: false as const, + type: 'boolean', + nullable: false, optional: false, }, isSilenced: { - type: 'boolean' as const, - nullable: false as const, optional: false as const, + type: 'boolean', + nullable: false, optional: false, }, isLocked: { - type: 'boolean' as const, - nullable: false as const, optional: false as const, + type: 'boolean', + nullable: false, optional: false, }, isBot: { - type: 'boolean' as const, - nullable: false as const, optional: false as const, + type: 'boolean', + nullable: false, optional: false, }, isCat: { - type: 'boolean' as const, - nullable: false as const, optional: false as const, + type: 'boolean', + nullable: false, optional: false, }, isAdmin: { - type: 'boolean' as const, - nullable: false as const, optional: false as const, + type: 'boolean', + nullable: false, optional: false, }, isModerator: { - type: 'boolean' as const, - nullable: false as const, optional: false as const, + type: 'boolean', + nullable: false, optional: false, }, emojis: { - type: 'array' as const, - nullable: false as const, optional: false as const, + type: 'array', + nullable: false, optional: false, items: { - type: 'string' as const, - nullable: false as const, optional: false as const, + type: 'string', + nullable: false, optional: false, }, }, host: { - type: 'string' as const, - nullable: true as const, optional: false as const, + type: 'string', + nullable: true, optional: false, }, inbox: { - type: 'string' as const, - nullable: true as const, optional: false as const, + type: 'string', + nullable: true, optional: false, }, sharedInbox: { - type: 'string' as const, - nullable: true as const, optional: false as const, + type: 'string', + nullable: true, optional: false, }, featured: { - type: 'string' as const, - nullable: true as const, optional: false as const, + type: 'string', + nullable: true, optional: false, }, uri: { - type: 'string' as const, - nullable: true as const, optional: false as const, + type: 'string', + nullable: true, optional: false, }, token: { - type: 'string' as const, - nullable: false as const, optional: false as const, + type: 'string', + nullable: true, optional: false, default: '<MASKED>', }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const user = await Users.findOne(ps.userId as string); diff --git a/packages/backend/src/server/api/endpoints/admin/show-users.ts b/packages/backend/src/server/api/endpoints/admin/show-users.ts index 9c57917cb2..d3dde99b72 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-users.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-users.ts @@ -5,7 +5,7 @@ import { Users } from '@/models/index'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -64,16 +64,17 @@ export const meta = { }, res: { - type: 'array' as const, - nullable: false as const, optional: false as const, + type: 'array', + nullable: false, optional: false, items: { - type: 'object' as const, - nullable: false as const, optional: false as const, - ref: 'User', + type: 'object', + nullable: false, optional: false, + ref: 'UserDetailed', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = Users.createQueryBuilder('user'); diff --git a/packages/backend/src/server/api/endpoints/admin/silence-user.ts b/packages/backend/src/server/api/endpoints/admin/silence-user.ts index 9f5135b1a6..872bd2a6ac 100644 --- a/packages/backend/src/server/api/endpoints/admin/silence-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/silence-user.ts @@ -7,7 +7,7 @@ import { insertModerationLog } from '@/services/insert-moderation-log'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -15,8 +15,9 @@ export const meta = { validator: $.type(ID), }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const user = await Users.findOne(ps.userId as string); diff --git a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts index c062dcc93f..2bb1875fc0 100644 --- a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts @@ -11,7 +11,7 @@ import { publishUserEvent } from '@/services/stream'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -19,8 +19,9 @@ export const meta = { validator: $.type(ID), }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const user = await Users.findOne(ps.userId as string); diff --git a/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts b/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts index bc081c8487..a4c6ff2ade 100644 --- a/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts @@ -7,7 +7,7 @@ import { insertModerationLog } from '@/services/insert-moderation-log'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -15,8 +15,9 @@ export const meta = { validator: $.type(ID), }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const user = await Users.findOne(ps.userId as string); diff --git a/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts b/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts index 73a5bc739b..5ab56d51c7 100644 --- a/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts @@ -8,7 +8,7 @@ import { doPostUnsuspend } from '@/services/unsuspend-user'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -16,8 +16,9 @@ export const meta = { validator: $.type(ID), }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const user = await Users.findOne(ps.userId as string); diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index e67088e0aa..aa2d1222f7 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -9,7 +9,7 @@ import { ID } from '@/misc/cafy-id'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireAdmin: true, params: { @@ -297,8 +297,9 @@ export const meta = { validator: $.optional.bool, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const set = {} as Partial<Meta>; diff --git a/packages/backend/src/server/api/endpoints/admin/vacuum.ts b/packages/backend/src/server/api/endpoints/admin/vacuum.ts index d08dfdd9ea..4229ef0d29 100644 --- a/packages/backend/src/server/api/endpoints/admin/vacuum.ts +++ b/packages/backend/src/server/api/endpoints/admin/vacuum.ts @@ -6,7 +6,7 @@ import { insertModerationLog } from '@/services/insert-moderation-log'; export const meta = { tags: ['admin'], - requireCredential: true as const, + requireCredential: true, requireModerator: true, params: { @@ -17,8 +17,9 @@ export const meta = { validator: $.bool, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const params: string[] = []; diff --git a/packages/backend/src/server/api/endpoints/announcements.ts b/packages/backend/src/server/api/endpoints/announcements.ts index 32ef49d889..0bd29607d6 100644 --- a/packages/backend/src/server/api/endpoints/announcements.ts +++ b/packages/backend/src/server/api/endpoints/announcements.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../common/make-pagination-query'; export const meta = { tags: ['meta'], - requireCredential: false as const, + requireCredential: false, params: { limit: { @@ -30,49 +30,50 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', example: 'xxxxxxxxxx', }, createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'date-time', }, updatedAt: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, format: 'date-time', }, text: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, title: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, imageUrl: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, isRead: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: true, nullable: false, }, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = makePaginationQuery(Announcements.createQueryBuilder('announcement'), ps.sinceId, ps.untilId); diff --git a/packages/backend/src/server/api/endpoints/antennas/create.ts b/packages/backend/src/server/api/endpoints/antennas/create.ts index 83c7d3e0a5..2092d177ba 100644 --- a/packages/backend/src/server/api/endpoints/antennas/create.ts +++ b/packages/backend/src/server/api/endpoints/antennas/create.ts @@ -9,7 +9,7 @@ import { publishInternalEvent } from '@/services/stream'; export const meta = { tags: ['antennas'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -74,12 +74,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Antenna', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { let userList; let userGroupJoining; diff --git a/packages/backend/src/server/api/endpoints/antennas/delete.ts b/packages/backend/src/server/api/endpoints/antennas/delete.ts index 721932f311..b2793fc70d 100644 --- a/packages/backend/src/server/api/endpoints/antennas/delete.ts +++ b/packages/backend/src/server/api/endpoints/antennas/delete.ts @@ -8,7 +8,7 @@ import { publishInternalEvent } from '@/services/stream'; export const meta = { tags: ['antennas'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -25,8 +25,9 @@ export const meta = { id: 'b34dcf9d-348f-44bb-99d0-6c9314cfe2df', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const antenna = await Antennas.findOne({ id: ps.antennaId, diff --git a/packages/backend/src/server/api/endpoints/antennas/list.ts b/packages/backend/src/server/api/endpoints/antennas/list.ts index 4a9eed8563..bb58912612 100644 --- a/packages/backend/src/server/api/endpoints/antennas/list.ts +++ b/packages/backend/src/server/api/endpoints/antennas/list.ts @@ -4,21 +4,22 @@ import { Antennas } from '@/models/index'; export const meta = { tags: ['antennas', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:account', res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Antenna', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const antennas = await Antennas.find({ userId: me.id, diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts index 968924831c..eb7de901c5 100644 --- a/packages/backend/src/server/api/endpoints/antennas/notes.ts +++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts @@ -12,7 +12,7 @@ import { generateBlockedUserQuery } from '../../common/generate-block-query'; export const meta = { tags: ['antennas', 'account', 'notes'], - requireCredential: true as const, + requireCredential: true, kind: 'read:account', @@ -52,16 +52,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const antenna = await Antennas.findOne({ id: ps.antennaId, diff --git a/packages/backend/src/server/api/endpoints/antennas/show.ts b/packages/backend/src/server/api/endpoints/antennas/show.ts index 79e988497d..a37d37d31c 100644 --- a/packages/backend/src/server/api/endpoints/antennas/show.ts +++ b/packages/backend/src/server/api/endpoints/antennas/show.ts @@ -7,7 +7,7 @@ import { Antennas } from '@/models/index'; export const meta = { tags: ['antennas', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:account', @@ -26,12 +26,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Antenna', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { // Fetch the antenna const antenna = await Antennas.findOne({ diff --git a/packages/backend/src/server/api/endpoints/antennas/update.ts b/packages/backend/src/server/api/endpoints/antennas/update.ts index d5400a7926..900f725505 100644 --- a/packages/backend/src/server/api/endpoints/antennas/update.ts +++ b/packages/backend/src/server/api/endpoints/antennas/update.ts @@ -8,7 +8,7 @@ import { publishInternalEvent } from '@/services/stream'; export const meta = { tags: ['antennas'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -83,12 +83,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Antenna', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Fetch the antenna const antenna = await Antennas.findOne({ diff --git a/packages/backend/src/server/api/endpoints/ap/get.ts b/packages/backend/src/server/api/endpoints/ap/get.ts index 000ed2d2df..ff8c677b91 100644 --- a/packages/backend/src/server/api/endpoints/ap/get.ts +++ b/packages/backend/src/server/api/endpoints/ap/get.ts @@ -7,7 +7,7 @@ import ms from 'ms'; export const meta = { tags: ['federation'], - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1hour'), @@ -24,11 +24,12 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const resolver = new Resolver(); const object = await resolver.resolve(ps.uri); diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index 709349262a..7d17d8edce 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -12,11 +12,12 @@ import { User } from '@/models/entities/user'; import { fetchMeta } from '@/misc/fetch-meta'; import { isActor, isPost, getApId } from '@/remote/activitypub/type'; import ms from 'ms'; +import { SchemaType } from '@/misc/schema'; export const meta = { tags: ['federation'], - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1hour'), @@ -38,22 +39,43 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - type: { - type: 'string' as const, - optional: false as const, nullable: false as const, - enum: ['User', 'Note'], - }, - object: { - type: 'object' as const, - optional: false as const, nullable: false as const, + optional: false, nullable: false, + oneOf: [ + { + type: 'object', + properties: { + type: { + type: 'string', + optional: false, nullable: false, + enum: ['User'], + }, + object: { + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailedNotMe', + } + } }, - }, + { + type: 'object', + properties: { + type: { + type: 'string', + optional: false, nullable: false, + enum: ['Note'], + }, + object: { + type: 'object', + optional: false, nullable: false, + ref: 'Note', + } + } + } + ], }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const object = await fetchAny(ps.uri); if (object) { @@ -66,7 +88,7 @@ export default define(meta, async (ps) => { /*** * URIからUserかNoteを解決する */ -async function fetchAny(uri: string) { +async function fetchAny(uri: string): Promise<SchemaType<typeof meta['res']> | null> { // URIがこのサーバーを指しているなら、ローカルユーザーIDとしてDBからフェッチ if (uri.startsWith(config.url + '/')) { const parts = uri.split('/'); @@ -95,8 +117,8 @@ async function fetchAny(uri: string) { } // ブロックしてたら中断 - const meta = await fetchMeta(); - if (meta.blockedHosts.includes(extractDbHost(uri))) return null; + const fetchedMeta = await fetchMeta(); + if (fetchedMeta.blockedHosts.includes(extractDbHost(uri))) return null; // URI(AP Object id)としてDB検索 { @@ -171,7 +193,7 @@ async function fetchAny(uri: string) { return null; } -async function mergePack(user: User | null | undefined, note: Note | null | undefined) { +async function mergePack(user: User | null | undefined, note: Note | null | undefined): Promise<SchemaType<typeof meta.res> | null> { if (user != null) { return { type: 'User', diff --git a/packages/backend/src/server/api/endpoints/app/create.ts b/packages/backend/src/server/api/endpoints/app/create.ts index 78586cbcf2..fbe6690f1d 100644 --- a/packages/backend/src/server/api/endpoints/app/create.ts +++ b/packages/backend/src/server/api/endpoints/app/create.ts @@ -8,7 +8,7 @@ import { secureRndstr } from '@/misc/secure-rndstr'; export const meta = { tags: ['app'], - requireCredential: false as const, + requireCredential: false, params: { name: { @@ -31,12 +31,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'App', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Generate secret const secret = secureRndstr(32, true); @@ -45,7 +46,7 @@ export default define(meta, async (ps, user) => { const permission = unique(ps.permission.map(v => v.replace(/^(.+)(\/|-)(read|write)$/, '$3:$1'))); // Create account - const app = await Apps.save({ + const app = await Apps.insert({ id: genId(), createdAt: new Date(), userId: user ? user.id : null, @@ -54,7 +55,7 @@ export default define(meta, async (ps, user) => { permission, callbackUrl: ps.callbackUrl, secret: secret, - }); + }).then(x => Apps.findOneOrFail(x.identifiers[0])); return await Apps.pack(app, null, { detail: true, diff --git a/packages/backend/src/server/api/endpoints/app/show.ts b/packages/backend/src/server/api/endpoints/app/show.ts index c83d576b43..9f4777b383 100644 --- a/packages/backend/src/server/api/endpoints/app/show.ts +++ b/packages/backend/src/server/api/endpoints/app/show.ts @@ -13,12 +13,6 @@ export const meta = { }, }, - res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'App', - }, - errors: { noSuchApp: { message: 'No such app.', @@ -28,12 +22,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'App', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user, token) => { const isSecure = user != null && token == null; diff --git a/packages/backend/src/server/api/endpoints/auth/accept.ts b/packages/backend/src/server/api/endpoints/auth/accept.ts index 989ebf8f25..f028135ca5 100644 --- a/packages/backend/src/server/api/endpoints/auth/accept.ts +++ b/packages/backend/src/server/api/endpoints/auth/accept.ts @@ -9,7 +9,7 @@ import { secureRndstr } from '@/misc/secure-rndstr'; export const meta = { tags: ['auth'], - requireCredential: true as const, + requireCredential: true, secure: true, @@ -26,8 +26,9 @@ export const meta = { id: '9c72d8de-391a-43c1-9d06-08d29efde8df', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Fetch token const session = await AuthSessions diff --git a/packages/backend/src/server/api/endpoints/auth/session/generate.ts b/packages/backend/src/server/api/endpoints/auth/session/generate.ts index 39ba5a972a..98987eba5b 100644 --- a/packages/backend/src/server/api/endpoints/auth/session/generate.ts +++ b/packages/backend/src/server/api/endpoints/auth/session/generate.ts @@ -9,7 +9,7 @@ import { genId } from '@/misc/gen-id'; export const meta = { tags: ['auth'], - requireCredential: false as const, + requireCredential: false, params: { appSecret: { @@ -18,16 +18,16 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { token: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, url: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'url', }, }, @@ -40,8 +40,9 @@ export const meta = { id: '92f93e63-428e-4f2f-a5a4-39e1407fe998', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { // Lookup app const app = await Apps.findOne({ @@ -56,12 +57,12 @@ export default define(meta, async (ps) => { const token = uuid(); // Create session token document - const doc = await AuthSessions.save({ + const doc = await AuthSessions.insert({ id: genId(), createdAt: new Date(), appId: app.id, token: token, - }); + }).then(x => AuthSessions.findOneOrFail(x.identifiers[0])); return { token: doc.token, diff --git a/packages/backend/src/server/api/endpoints/auth/session/show.ts b/packages/backend/src/server/api/endpoints/auth/session/show.ts index e890dd95a3..ae0d016cea 100644 --- a/packages/backend/src/server/api/endpoints/auth/session/show.ts +++ b/packages/backend/src/server/api/endpoints/auth/session/show.ts @@ -6,7 +6,7 @@ import { AuthSessions } from '@/models/index'; export const meta = { tags: ['auth'], - requireCredential: false as const, + requireCredential: false, params: { token: { @@ -23,27 +23,28 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, app: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'App', }, token: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Lookup session const session = await AuthSessions.findOne({ diff --git a/packages/backend/src/server/api/endpoints/auth/session/userkey.ts b/packages/backend/src/server/api/endpoints/auth/session/userkey.ts index 241b13d4da..fe0211ebe3 100644 --- a/packages/backend/src/server/api/endpoints/auth/session/userkey.ts +++ b/packages/backend/src/server/api/endpoints/auth/session/userkey.ts @@ -6,7 +6,7 @@ import { Apps, AuthSessions, AccessTokens, Users } from '@/models/index'; export const meta = { tags: ['auth'], - requireCredential: false as const, + requireCredential: false, params: { appSecret: { @@ -19,18 +19,18 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { accessToken: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, user: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailedNotMe', }, }, }, @@ -54,8 +54,9 @@ export const meta = { id: '8c8a4145-02cc-4cca-8e66-29ba60445a8e', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { // Lookup app const app = await Apps.findOne({ diff --git a/packages/backend/src/server/api/endpoints/blocking/create.ts b/packages/backend/src/server/api/endpoints/blocking/create.ts index cb92589d19..6d555ff569 100644 --- a/packages/backend/src/server/api/endpoints/blocking/create.ts +++ b/packages/backend/src/server/api/endpoints/blocking/create.ts @@ -15,7 +15,7 @@ export const meta = { max: 100, }, - requireCredential: true as const, + requireCredential: true, kind: 'write:blocks', @@ -46,12 +46,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailedNotMe', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const blocker = await Users.findOneOrFail(user.id); diff --git a/packages/backend/src/server/api/endpoints/blocking/delete.ts b/packages/backend/src/server/api/endpoints/blocking/delete.ts index 7a99fe0f49..942cddaedf 100644 --- a/packages/backend/src/server/api/endpoints/blocking/delete.ts +++ b/packages/backend/src/server/api/endpoints/blocking/delete.ts @@ -15,7 +15,7 @@ export const meta = { max: 100, }, - requireCredential: true as const, + requireCredential: true, kind: 'write:blocks', @@ -46,12 +46,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailedNotMe', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const blocker = await Users.findOneOrFail(user.id); diff --git a/packages/backend/src/server/api/endpoints/blocking/list.ts b/packages/backend/src/server/api/endpoints/blocking/list.ts index 901403d144..9a4f662140 100644 --- a/packages/backend/src/server/api/endpoints/blocking/list.ts +++ b/packages/backend/src/server/api/endpoints/blocking/list.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:blocks', @@ -27,16 +27,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Blocking', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = makePaginationQuery(Blockings.createQueryBuilder('blocking'), ps.sinceId, ps.untilId) .andWhere(`blocking.blockerId = :meId`, { meId: me.id }); diff --git a/packages/backend/src/server/api/endpoints/channels/create.ts b/packages/backend/src/server/api/endpoints/channels/create.ts index 0176f86e00..68cdf1143e 100644 --- a/packages/backend/src/server/api/endpoints/channels/create.ts +++ b/packages/backend/src/server/api/endpoints/channels/create.ts @@ -9,7 +9,7 @@ import { ID } from '@/misc/cafy-id'; export const meta = { tags: ['channels'], - requireCredential: true as const, + requireCredential: true, kind: 'write:channels', @@ -28,8 +28,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Channel', }, @@ -40,8 +40,9 @@ export const meta = { id: 'cd1e9f3e-5a12-4ab4-96f6-5d0a2cc32050', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { let banner = null; if (ps.bannerId != null) { @@ -55,14 +56,14 @@ export default define(meta, async (ps, user) => { } } - const channel = await Channels.save({ + const channel = await Channels.insert({ id: genId(), createdAt: new Date(), userId: user.id, name: ps.name, description: ps.description || null, bannerId: banner ? banner.id : null, - } as Channel); + } as Channel).then(x => Channels.findOneOrFail(x.identifiers[0])); return await Channels.pack(channel, user); }); diff --git a/packages/backend/src/server/api/endpoints/channels/featured.ts b/packages/backend/src/server/api/endpoints/channels/featured.ts index 8eab2402be..ceadde907c 100644 --- a/packages/backend/src/server/api/endpoints/channels/featured.ts +++ b/packages/backend/src/server/api/endpoints/channels/featured.ts @@ -4,19 +4,20 @@ import { Channels } from '@/models/index'; export const meta = { tags: ['channels'], - requireCredential: false as const, + requireCredential: false, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Channel', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = Channels.createQueryBuilder('channel') .where('channel.lastNotedAt IS NOT NULL') diff --git a/packages/backend/src/server/api/endpoints/channels/follow.ts b/packages/backend/src/server/api/endpoints/channels/follow.ts index d30593acd9..bf580eea60 100644 --- a/packages/backend/src/server/api/endpoints/channels/follow.ts +++ b/packages/backend/src/server/api/endpoints/channels/follow.ts @@ -9,7 +9,7 @@ import { publishUserEvent } from '@/services/stream'; export const meta = { tags: ['channels'], - requireCredential: true as const, + requireCredential: true, kind: 'write:channels', @@ -26,8 +26,9 @@ export const meta = { id: 'c0031718-d573-4e85-928e-10039f1fbb68', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const channel = await Channels.findOne({ id: ps.channelId, diff --git a/packages/backend/src/server/api/endpoints/channels/followed.ts b/packages/backend/src/server/api/endpoints/channels/followed.ts index 28454c97fd..9e4c942af2 100644 --- a/packages/backend/src/server/api/endpoints/channels/followed.ts +++ b/packages/backend/src/server/api/endpoints/channels/followed.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['channels', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:channels', @@ -27,16 +27,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Channel', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = makePaginationQuery(ChannelFollowings.createQueryBuilder(), ps.sinceId, ps.untilId) .andWhere({ followerId: me.id }); diff --git a/packages/backend/src/server/api/endpoints/channels/owned.ts b/packages/backend/src/server/api/endpoints/channels/owned.ts index 44024b158e..5473636a85 100644 --- a/packages/backend/src/server/api/endpoints/channels/owned.ts +++ b/packages/backend/src/server/api/endpoints/channels/owned.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['channels', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:channels', @@ -27,16 +27,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Channel', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = makePaginationQuery(Channels.createQueryBuilder(), ps.sinceId, ps.untilId) .andWhere({ userId: me.id }); diff --git a/packages/backend/src/server/api/endpoints/channels/show.ts b/packages/backend/src/server/api/endpoints/channels/show.ts index e7ce4f22eb..598a87ec4e 100644 --- a/packages/backend/src/server/api/endpoints/channels/show.ts +++ b/packages/backend/src/server/api/endpoints/channels/show.ts @@ -7,7 +7,7 @@ import { Channels } from '@/models/index'; export const meta = { tags: ['channels'], - requireCredential: false as const, + requireCredential: false, params: { channelId: { @@ -16,8 +16,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Channel', }, @@ -28,8 +28,9 @@ export const meta = { id: '6f6c314b-7486-4897-8966-c04a66a02923', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const channel = await Channels.findOne({ id: ps.channelId, diff --git a/packages/backend/src/server/api/endpoints/channels/timeline.ts b/packages/backend/src/server/api/endpoints/channels/timeline.ts index 56c205cae5..927ce7c741 100644 --- a/packages/backend/src/server/api/endpoints/channels/timeline.ts +++ b/packages/backend/src/server/api/endpoints/channels/timeline.ts @@ -9,7 +9,7 @@ import { activeUsersChart } from '@/services/chart/index'; export const meta = { tags: ['notes', 'channels'], - requireCredential: false as const, + requireCredential: false, params: { channelId: { @@ -39,11 +39,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, @@ -55,8 +55,9 @@ export const meta = { id: '4d0eeeba-a02c-4c3c-9966-ef60d38d2e7f', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const channel = await Channels.findOne({ id: ps.channelId, diff --git a/packages/backend/src/server/api/endpoints/channels/unfollow.ts b/packages/backend/src/server/api/endpoints/channels/unfollow.ts index 8ce9a7e644..ada0cb29fd 100644 --- a/packages/backend/src/server/api/endpoints/channels/unfollow.ts +++ b/packages/backend/src/server/api/endpoints/channels/unfollow.ts @@ -8,7 +8,7 @@ import { publishUserEvent } from '@/services/stream'; export const meta = { tags: ['channels'], - requireCredential: true as const, + requireCredential: true, kind: 'write:channels', @@ -25,8 +25,9 @@ export const meta = { id: '19959ee9-0153-4c51-bbd9-a98c49dc59d6', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const channel = await Channels.findOne({ id: ps.channelId, diff --git a/packages/backend/src/server/api/endpoints/channels/update.ts b/packages/backend/src/server/api/endpoints/channels/update.ts index 0e6863ac76..1f7108a1cb 100644 --- a/packages/backend/src/server/api/endpoints/channels/update.ts +++ b/packages/backend/src/server/api/endpoints/channels/update.ts @@ -7,7 +7,7 @@ import { Channels, DriveFiles } from '@/models/index'; export const meta = { tags: ['channels'], - requireCredential: true as const, + requireCredential: true, kind: 'write:channels', @@ -30,8 +30,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Channel', }, @@ -54,8 +54,9 @@ export const meta = { id: 'e86c14a4-0da2-4032-8df3-e737a04c7f3b', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const channel = await Channels.findOne({ id: ps.channelId, diff --git a/packages/backend/src/server/api/endpoints/charts/active-users.ts b/packages/backend/src/server/api/endpoints/charts/active-users.ts index c4878f7d61..f7eadc7089 100644 --- a/packages/backend/src/server/api/endpoints/charts/active-users.ts +++ b/packages/backend/src/server/api/endpoints/charts/active-users.ts @@ -23,8 +23,9 @@ export const meta = { }, res: convertLog(activeUsersChart.schema), -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { return await activeUsersChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null); }); diff --git a/packages/backend/src/server/api/endpoints/charts/drive.ts b/packages/backend/src/server/api/endpoints/charts/drive.ts index 07bff82cf4..364279da95 100644 --- a/packages/backend/src/server/api/endpoints/charts/drive.ts +++ b/packages/backend/src/server/api/endpoints/charts/drive.ts @@ -23,8 +23,9 @@ export const meta = { }, res: convertLog(driveChart.schema), -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { return await driveChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null); }); diff --git a/packages/backend/src/server/api/endpoints/charts/federation.ts b/packages/backend/src/server/api/endpoints/charts/federation.ts index 9575f9a7b7..6feb82b6d9 100644 --- a/packages/backend/src/server/api/endpoints/charts/federation.ts +++ b/packages/backend/src/server/api/endpoints/charts/federation.ts @@ -23,8 +23,9 @@ export const meta = { }, res: convertLog(federationChart.schema), -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { return await federationChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null); }); diff --git a/packages/backend/src/server/api/endpoints/charts/hashtag.ts b/packages/backend/src/server/api/endpoints/charts/hashtag.ts index 53dc61e51e..99dc77998e 100644 --- a/packages/backend/src/server/api/endpoints/charts/hashtag.ts +++ b/packages/backend/src/server/api/endpoints/charts/hashtag.ts @@ -27,8 +27,9 @@ export const meta = { }, res: convertLog(hashtagChart.schema), -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { return await hashtagChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null, ps.tag); }); diff --git a/packages/backend/src/server/api/endpoints/charts/instance.ts b/packages/backend/src/server/api/endpoints/charts/instance.ts index 4e96304d8c..23e6fbf2b0 100644 --- a/packages/backend/src/server/api/endpoints/charts/instance.ts +++ b/packages/backend/src/server/api/endpoints/charts/instance.ts @@ -27,8 +27,9 @@ export const meta = { }, res: convertLog(instanceChart.schema), -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { return await instanceChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null, ps.host); }); diff --git a/packages/backend/src/server/api/endpoints/charts/network.ts b/packages/backend/src/server/api/endpoints/charts/network.ts index b524df93be..c5a39bbd76 100644 --- a/packages/backend/src/server/api/endpoints/charts/network.ts +++ b/packages/backend/src/server/api/endpoints/charts/network.ts @@ -23,8 +23,9 @@ export const meta = { }, res: convertLog(networkChart.schema), -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { return await networkChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null); }); diff --git a/packages/backend/src/server/api/endpoints/charts/notes.ts b/packages/backend/src/server/api/endpoints/charts/notes.ts index 676f302939..dcbd80c3e9 100644 --- a/packages/backend/src/server/api/endpoints/charts/notes.ts +++ b/packages/backend/src/server/api/endpoints/charts/notes.ts @@ -23,8 +23,9 @@ export const meta = { }, res: convertLog(notesChart.schema), -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { return await notesChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null); }); diff --git a/packages/backend/src/server/api/endpoints/charts/user/drive.ts b/packages/backend/src/server/api/endpoints/charts/user/drive.ts index 4fc12b3306..94787b4a57 100644 --- a/packages/backend/src/server/api/endpoints/charts/user/drive.ts +++ b/packages/backend/src/server/api/endpoints/charts/user/drive.ts @@ -28,8 +28,9 @@ export const meta = { }, res: convertLog(perUserDriveChart.schema), -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { return await perUserDriveChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null, ps.userId); }); diff --git a/packages/backend/src/server/api/endpoints/charts/user/following.ts b/packages/backend/src/server/api/endpoints/charts/user/following.ts index 9207760a3c..effe0c54b9 100644 --- a/packages/backend/src/server/api/endpoints/charts/user/following.ts +++ b/packages/backend/src/server/api/endpoints/charts/user/following.ts @@ -28,8 +28,9 @@ export const meta = { }, res: convertLog(perUserFollowingChart.schema), -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { return await perUserFollowingChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null, ps.userId); }); diff --git a/packages/backend/src/server/api/endpoints/charts/user/notes.ts b/packages/backend/src/server/api/endpoints/charts/user/notes.ts index f543920572..df68a5fe52 100644 --- a/packages/backend/src/server/api/endpoints/charts/user/notes.ts +++ b/packages/backend/src/server/api/endpoints/charts/user/notes.ts @@ -28,8 +28,9 @@ export const meta = { }, res: convertLog(perUserNotesChart.schema), -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { return await perUserNotesChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null, ps.userId); }); diff --git a/packages/backend/src/server/api/endpoints/charts/user/reactions.ts b/packages/backend/src/server/api/endpoints/charts/user/reactions.ts index 4ceb79f7f8..dcd067305f 100644 --- a/packages/backend/src/server/api/endpoints/charts/user/reactions.ts +++ b/packages/backend/src/server/api/endpoints/charts/user/reactions.ts @@ -28,8 +28,9 @@ export const meta = { }, res: convertLog(perUserReactionsChart.schema), -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { return await perUserReactionsChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null, ps.userId); }); diff --git a/packages/backend/src/server/api/endpoints/charts/users.ts b/packages/backend/src/server/api/endpoints/charts/users.ts index deac89b59d..d32e14ad61 100644 --- a/packages/backend/src/server/api/endpoints/charts/users.ts +++ b/packages/backend/src/server/api/endpoints/charts/users.ts @@ -23,8 +23,9 @@ export const meta = { }, res: convertLog(usersChart.schema), -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { return await usersChart.getChart(ps.span as any, ps.limit!, ps.offset ? new Date(ps.offset) : null); }); diff --git a/packages/backend/src/server/api/endpoints/clips/add-note.ts b/packages/backend/src/server/api/endpoints/clips/add-note.ts index 99312a71b9..4a740b6cfe 100644 --- a/packages/backend/src/server/api/endpoints/clips/add-note.ts +++ b/packages/backend/src/server/api/endpoints/clips/add-note.ts @@ -9,7 +9,7 @@ import { getNote } from '../../common/getters'; export const meta = { tags: ['account', 'notes', 'clips'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -42,8 +42,9 @@ export const meta = { id: '734806c4-542c-463a-9311-15c512803965', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const clip = await Clips.findOne({ id: ps.clipId, diff --git a/packages/backend/src/server/api/endpoints/clips/create.ts b/packages/backend/src/server/api/endpoints/clips/create.ts index cb4ff56abd..852e66c9e4 100644 --- a/packages/backend/src/server/api/endpoints/clips/create.ts +++ b/packages/backend/src/server/api/endpoints/clips/create.ts @@ -6,7 +6,7 @@ import { Clips } from '@/models/index'; export const meta = { tags: ['clips'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -25,12 +25,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Clip', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const clip = await Clips.insert({ id: genId(), diff --git a/packages/backend/src/server/api/endpoints/clips/delete.ts b/packages/backend/src/server/api/endpoints/clips/delete.ts index 9ec6bc7eac..85c64a115d 100644 --- a/packages/backend/src/server/api/endpoints/clips/delete.ts +++ b/packages/backend/src/server/api/endpoints/clips/delete.ts @@ -7,7 +7,7 @@ import { Clips } from '@/models/index'; export const meta = { tags: ['clips'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -24,8 +24,9 @@ export const meta = { id: '70ca08ba-6865-4630-b6fb-8494759aa754', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const clip = await Clips.findOne({ id: ps.clipId, diff --git a/packages/backend/src/server/api/endpoints/clips/list.ts b/packages/backend/src/server/api/endpoints/clips/list.ts index 3b32c02899..d88897d164 100644 --- a/packages/backend/src/server/api/endpoints/clips/list.ts +++ b/packages/backend/src/server/api/endpoints/clips/list.ts @@ -4,21 +4,22 @@ import { Clips } from '@/models/index'; export const meta = { tags: ['clips', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:account', res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Clip', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const clips = await Clips.find({ userId: me.id, diff --git a/packages/backend/src/server/api/endpoints/clips/notes.ts b/packages/backend/src/server/api/endpoints/clips/notes.ts index 90ddd66a1f..eeb20631c1 100644 --- a/packages/backend/src/server/api/endpoints/clips/notes.ts +++ b/packages/backend/src/server/api/endpoints/clips/notes.ts @@ -11,7 +11,7 @@ import { generateBlockedUserQuery } from '../../common/generate-block-query'; export const meta = { tags: ['account', 'notes', 'clips'], - requireCredential: false as const, + requireCredential: false, kind: 'read:account', @@ -43,16 +43,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const clip = await Clips.findOne({ id: ps.clipId, diff --git a/packages/backend/src/server/api/endpoints/clips/show.ts b/packages/backend/src/server/api/endpoints/clips/show.ts index 8e9409fadd..0a45672019 100644 --- a/packages/backend/src/server/api/endpoints/clips/show.ts +++ b/packages/backend/src/server/api/endpoints/clips/show.ts @@ -7,7 +7,7 @@ import { Clips } from '@/models/index'; export const meta = { tags: ['clips', 'account'], - requireCredential: false as const, + requireCredential: false, kind: 'read:account', @@ -26,12 +26,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Clip', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { // Fetch the clip const clip = await Clips.findOne({ diff --git a/packages/backend/src/server/api/endpoints/clips/update.ts b/packages/backend/src/server/api/endpoints/clips/update.ts index 9cf12499af..795483d5b2 100644 --- a/packages/backend/src/server/api/endpoints/clips/update.ts +++ b/packages/backend/src/server/api/endpoints/clips/update.ts @@ -7,7 +7,7 @@ import { Clips } from '@/models/index'; export const meta = { tags: ['clips'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -38,12 +38,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Clip', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Fetch the clip const clip = await Clips.findOne({ diff --git a/packages/backend/src/server/api/endpoints/drive.ts b/packages/backend/src/server/api/endpoints/drive.ts index b9741ba875..d9ab9883ca 100644 --- a/packages/backend/src/server/api/endpoints/drive.ts +++ b/packages/backend/src/server/api/endpoints/drive.ts @@ -5,26 +5,27 @@ import { DriveFiles } from '@/models/index'; export const meta = { tags: ['drive', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:drive', res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { capacity: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, usage: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const instance = await fetchMeta(true); diff --git a/packages/backend/src/server/api/endpoints/drive/files.ts b/packages/backend/src/server/api/endpoints/drive/files.ts index 00ebb51e30..a5c0a626a1 100644 --- a/packages/backend/src/server/api/endpoints/drive/files.ts +++ b/packages/backend/src/server/api/endpoints/drive/files.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, kind: 'read:drive', @@ -36,16 +36,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'DriveFile', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = makePaginationQuery(DriveFiles.createQueryBuilder('file'), ps.sinceId, ps.untilId) .andWhere('file.userId = :userId', { userId: user.id }); diff --git a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts index c8317c1cc8..835dde8058 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts @@ -7,7 +7,7 @@ import { DriveFiles, Notes } from '@/models/index'; export const meta = { tags: ['drive', 'notes'], - requireCredential: true as const, + requireCredential: true, kind: 'read:drive', @@ -18,11 +18,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, @@ -34,8 +34,9 @@ export const meta = { id: 'c118ece3-2e4b-4296-99d1-51756e32d232', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Fetch file const file = await DriveFiles.findOne({ diff --git a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts index a6db160d2f..a45d357ee8 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts @@ -5,7 +5,7 @@ import { DriveFiles } from '@/models/index'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, kind: 'read:drive', @@ -16,11 +16,12 @@ export const meta = { }, res: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const file = await DriveFiles.findOne({ md5: ps.md5, diff --git a/packages/backend/src/server/api/endpoints/drive/files/create.ts b/packages/backend/src/server/api/endpoints/drive/files/create.ts index 5523ae1967..dd65ab0611 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/create.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/create.ts @@ -1,16 +1,17 @@ import ms from 'ms'; import $ from 'cafy'; import { ID } from '@/misc/cafy-id'; -import create from '@/services/drive/add-file'; +import { addFile } from '@/services/drive/add-file'; import define from '../../../define'; import { apiLogger } from '../../../logger'; import { ApiError } from '../../../error'; import { DriveFiles } from '@/models/index'; +import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1hour'), @@ -32,6 +33,11 @@ export const meta = { default: null, }, + comment: { + validator: $.optional.nullable.str.max(DB_MAX_IMAGE_COMMENT_LENGTH), + default: null, + }, + isSensitive: { validator: $.optional.either($.bool, $.str), default: false, @@ -46,8 +52,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'DriveFile', }, @@ -58,8 +64,9 @@ export const meta = { id: 'f449b209-0c60-4e51-84d5-29486263bfd4', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user, _, file, cleanup) => { // Get 'name' parameter let name = ps.name || file.originalname; @@ -78,7 +85,7 @@ export default define(meta, async (ps, user, _, file, cleanup) => { try { // Create file - const driveFile = await create(user, file.path, name, null, ps.folderId, ps.force, false, null, null, ps.isSensitive); + const driveFile = await addFile({ user, path: file.path, name, comment: ps.comment, folderId: ps.folderId, force: ps.force, sensitive: ps.isSensitive }); return await DriveFiles.pack(driveFile, { self: true }); } catch (e) { apiLogger.error(e); diff --git a/packages/backend/src/server/api/endpoints/drive/files/delete.ts b/packages/backend/src/server/api/endpoints/drive/files/delete.ts index 3a8e4b11f4..308beb58a4 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/delete.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/delete.ts @@ -9,7 +9,7 @@ import { DriveFiles } from '@/models/index'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, kind: 'write:drive', @@ -32,8 +32,9 @@ export const meta = { id: '5eb8d909-2540-4970-90b8-dd6f86088121', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const file = await DriveFiles.findOne(ps.fileId); diff --git a/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts b/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts index 16717149ac..dc74dcb7e6 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts @@ -5,7 +5,7 @@ import { DriveFiles } from '@/models/index'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, kind: 'read:drive', @@ -16,16 +16,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'DriveFile', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const files = await DriveFiles.find({ md5: ps.md5, diff --git a/packages/backend/src/server/api/endpoints/drive/files/find.ts b/packages/backend/src/server/api/endpoints/drive/files/find.ts index 108e08593a..2244df13cd 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/find.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/find.ts @@ -4,7 +4,7 @@ import define from '../../../define'; import { DriveFiles } from '@/models/index'; export const meta = { - requireCredential: true as const, + requireCredential: true, tags: ['drive'], @@ -22,16 +22,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'DriveFile', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const files = await DriveFiles.find({ name: ps.name, diff --git a/packages/backend/src/server/api/endpoints/drive/files/show.ts b/packages/backend/src/server/api/endpoints/drive/files/show.ts index 96f8e5c03a..18b17c4653 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/show.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/show.ts @@ -8,7 +8,7 @@ import { DriveFiles } from '@/models/index'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, kind: 'read:drive', @@ -23,8 +23,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'DriveFile', }, @@ -47,8 +47,9 @@ export const meta = { id: '89674805-722c-440c-8d88-5641830dc3e4', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { let file: DriveFile | undefined; diff --git a/packages/backend/src/server/api/endpoints/drive/files/update.ts b/packages/backend/src/server/api/endpoints/drive/files/update.ts index 04b2db9cf4..b7ca80e83c 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/update.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/update.ts @@ -9,7 +9,7 @@ import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, kind: 'write:drive', @@ -60,12 +60,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'DriveFile', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const file = await DriveFiles.findOne(ps.fileId); diff --git a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts index 8a2fbc36b5..40da1a4fb4 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import { ID } from '@/misc/cafy-id'; import ms from 'ms'; -import uploadFromUrl from '@/services/drive/upload-from-url'; +import { uploadFromUrl } from '@/services/drive/upload-from-url'; import define from '../../../define'; import { DriveFiles } from '@/models/index'; import { publishMainStream } from '@/services/stream'; @@ -15,7 +15,7 @@ export const meta = { max: 60, }, - requireCredential: true as const, + requireCredential: true, kind: 'write:drive', @@ -50,10 +50,11 @@ export const meta = { default: false, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { - uploadFromUrl(ps.url, user, ps.folderId, null, ps.isSensitive, ps.force, false, ps.comment).then(file => { + uploadFromUrl({ url: ps.url, user, folderId: ps.folderId, sensitive: ps.isSensitive, force: ps.force, comment: ps.comment }).then(file => { DriveFiles.pack(file, { self: true }).then(packedFile => { publishMainStream(user.id, 'urlUploadFinished', { marker: ps.marker, diff --git a/packages/backend/src/server/api/endpoints/drive/folders.ts b/packages/backend/src/server/api/endpoints/drive/folders.ts index cd2d1743c8..8f8d1d2c0a 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, kind: 'read:drive', @@ -32,16 +32,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'DriveFolder', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = makePaginationQuery(DriveFolders.createQueryBuilder('folder'), ps.sinceId, ps.untilId) .andWhere('folder.userId = :userId', { userId: user.id }); diff --git a/packages/backend/src/server/api/endpoints/drive/folders/create.ts b/packages/backend/src/server/api/endpoints/drive/folders/create.ts index 9ae59d4b49..38ed17e0e5 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/create.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/create.ts @@ -9,7 +9,7 @@ import { genId } from '@/misc/gen-id'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, kind: 'write:drive', @@ -37,8 +37,9 @@ export const meta = { optional: false as const, nullable: false as const, ref: 'DriveFolder', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // If the parent folder is specified let parent = null; diff --git a/packages/backend/src/server/api/endpoints/drive/folders/delete.ts b/packages/backend/src/server/api/endpoints/drive/folders/delete.ts index bfd3361e76..13716fccea 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/delete.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/delete.ts @@ -8,7 +8,7 @@ import { DriveFolders, DriveFiles } from '@/models/index'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, kind: 'write:drive', @@ -31,8 +31,9 @@ export const meta = { id: 'b0fc8a17-963c-405d-bfbc-859a487295e1', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Get folder const folder = await DriveFolders.findOne({ diff --git a/packages/backend/src/server/api/endpoints/drive/folders/find.ts b/packages/backend/src/server/api/endpoints/drive/folders/find.ts index 872eabef98..911f51d78b 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/find.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/find.ts @@ -6,7 +6,7 @@ import { DriveFolders } from '@/models/index'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, kind: 'read:drive', @@ -22,16 +22,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'DriveFolder', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const folders = await DriveFolders.find({ name: ps.name, diff --git a/packages/backend/src/server/api/endpoints/drive/folders/show.ts b/packages/backend/src/server/api/endpoints/drive/folders/show.ts index 63b5bc9e77..58a6dd3c06 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/show.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/show.ts @@ -7,7 +7,7 @@ import { DriveFolders } from '@/models/index'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, kind: 'read:drive', @@ -18,8 +18,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'DriveFolder', }, @@ -30,8 +30,9 @@ export const meta = { id: 'd74ab9eb-bb09-4bba-bf24-fb58f761e1e9', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Get folder const folder = await DriveFolders.findOne({ diff --git a/packages/backend/src/server/api/endpoints/drive/folders/update.ts b/packages/backend/src/server/api/endpoints/drive/folders/update.ts index e547808866..5b0cccd1c6 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/update.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/update.ts @@ -8,7 +8,7 @@ import { DriveFolders } from '@/models/index'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, kind: 'write:drive', @@ -47,12 +47,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'DriveFolder', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Fetch folder const folder = await DriveFolders.findOne({ diff --git a/packages/backend/src/server/api/endpoints/drive/stream.ts b/packages/backend/src/server/api/endpoints/drive/stream.ts index e3031f75b2..9ba7804946 100644 --- a/packages/backend/src/server/api/endpoints/drive/stream.ts +++ b/packages/backend/src/server/api/endpoints/drive/stream.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['drive'], - requireCredential: true as const, + requireCredential: true, kind: 'read:drive', @@ -31,16 +31,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'DriveFile', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = makePaginationQuery(DriveFiles.createQueryBuilder('file'), ps.sinceId, ps.untilId) .andWhere('file.userId = :userId', { userId: user.id }); diff --git a/packages/backend/src/server/api/endpoints/email-address/available.ts b/packages/backend/src/server/api/endpoints/email-address/available.ts index f2254e1481..19f9b7ccdc 100644 --- a/packages/backend/src/server/api/endpoints/email-address/available.ts +++ b/packages/backend/src/server/api/endpoints/email-address/available.ts @@ -5,7 +5,7 @@ import { validateEmailForAccount } from '@/services/validate-email-for-account'; export const meta = { tags: ['users'], - requireCredential: false as const, + requireCredential: false, params: { emailAddress: { @@ -14,21 +14,22 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { available: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, reason: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { return await validateEmailForAccount(ps.emailAddress); }); diff --git a/packages/backend/src/server/api/endpoints/endpoint.ts b/packages/backend/src/server/api/endpoints/endpoint.ts index eacee689dc..42fd468838 100644 --- a/packages/backend/src/server/api/endpoints/endpoint.ts +++ b/packages/backend/src/server/api/endpoints/endpoint.ts @@ -3,7 +3,7 @@ import define from '../define'; import endpoints from '../endpoints'; export const meta = { - requireCredential: false as const, + requireCredential: false, tags: ['meta'], @@ -12,8 +12,9 @@ export const meta = { validator: $.str, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const ep = endpoints.find(x => x.name === ps.endpoint); if (ep == null) return null; diff --git a/packages/backend/src/server/api/endpoints/endpoints.ts b/packages/backend/src/server/api/endpoints/endpoints.ts index 6d4588383d..ebb78de337 100644 --- a/packages/backend/src/server/api/endpoints/endpoints.ts +++ b/packages/backend/src/server/api/endpoints/endpoints.ts @@ -2,7 +2,7 @@ import define from '../define'; import endpoints from '../endpoints'; export const meta = { - requireCredential: false as const, + requireCredential: false, tags: ['meta'], @@ -10,11 +10,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, example: [ 'admin/abuse-user-reports', @@ -23,8 +23,9 @@ export const meta = { '...', ], }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async () => { return endpoints.map(x => x.name); }); diff --git a/packages/backend/src/server/api/endpoints/export-custom-emojis.ts b/packages/backend/src/server/api/endpoints/export-custom-emojis.ts index 92738c8288..24c9f56aa6 100644 --- a/packages/backend/src/server/api/endpoints/export-custom-emojis.ts +++ b/packages/backend/src/server/api/endpoints/export-custom-emojis.ts @@ -5,13 +5,14 @@ import ms from 'ms'; export const meta = { secure: true, - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1hour'), max: 1, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { createExportCustomEmojisJob(user); }); diff --git a/packages/backend/src/server/api/endpoints/federation/dns.ts b/packages/backend/src/server/api/endpoints/federation/dns.ts deleted file mode 100644 index 19e3ef8055..0000000000 --- a/packages/backend/src/server/api/endpoints/federation/dns.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { promises as dns } from 'dns'; -import $ from 'cafy'; -import define from '../../define'; -import { Instances } from '@/models/index'; -import { toPuny } from '@/misc/convert-host'; - -const resolver = new dns.Resolver(); -resolver.setServers(['1.1.1.1']); - -export const meta = { - tags: ['federation'], - - requireCredential: false as const, - - params: { - host: { - validator: $.str, - }, - }, -}; - -export default define(meta, async (ps, me) => { - const instance = await Instances.findOneOrFail({ host: toPuny(ps.host) }); - - const [ - resolved4, - resolved6, - resolvedCname, - resolvedTxt, - ] = await Promise.all([ - resolver.resolve4(instance.host).catch(() => []), - resolver.resolve6(instance.host).catch(() => []), - resolver.resolveCname(instance.host).catch(() => []), - resolver.resolveTxt(instance.host).catch(() => []), - ]); - - return { - a: resolved4, - aaaa: resolved6, - cname: resolvedCname, - txt: resolvedTxt, - }; -}); diff --git a/packages/backend/src/server/api/endpoints/federation/followers.ts b/packages/backend/src/server/api/endpoints/federation/followers.ts index 9cb4082bbf..c0a85f166c 100644 --- a/packages/backend/src/server/api/endpoints/federation/followers.ts +++ b/packages/backend/src/server/api/endpoints/federation/followers.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['federation'], - requireCredential: false as const, + requireCredential: false, params: { host: { @@ -29,16 +29,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Following', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = makePaginationQuery(Followings.createQueryBuilder('following'), ps.sinceId, ps.untilId) .andWhere(`following.followeeHost = :host`, { host: ps.host }); diff --git a/packages/backend/src/server/api/endpoints/federation/following.ts b/packages/backend/src/server/api/endpoints/federation/following.ts index 4a42550f93..147f0aedb2 100644 --- a/packages/backend/src/server/api/endpoints/federation/following.ts +++ b/packages/backend/src/server/api/endpoints/federation/following.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['federation'], - requireCredential: false as const, + requireCredential: false, params: { host: { @@ -29,16 +29,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Following', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = makePaginationQuery(Followings.createQueryBuilder('following'), ps.sinceId, ps.untilId) .andWhere(`following.followerHost = :host`, { host: ps.host }); diff --git a/packages/backend/src/server/api/endpoints/federation/instances.ts b/packages/backend/src/server/api/endpoints/federation/instances.ts index 50d9ff3fd7..11df7ed6b6 100644 --- a/packages/backend/src/server/api/endpoints/federation/instances.ts +++ b/packages/backend/src/server/api/endpoints/federation/instances.ts @@ -7,7 +7,7 @@ import { fetchMeta } from '@/misc/fetch-meta'; export const meta = { tags: ['federation'], - requireCredential: false as const, + requireCredential: false, params: { host: { @@ -54,16 +54,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'FederationInstance', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = Instances.createQueryBuilder('instance'); diff --git a/packages/backend/src/server/api/endpoints/federation/show-instance.ts b/packages/backend/src/server/api/endpoints/federation/show-instance.ts index 9d5d9bc9f8..6f13b28cae 100644 --- a/packages/backend/src/server/api/endpoints/federation/show-instance.ts +++ b/packages/backend/src/server/api/endpoints/federation/show-instance.ts @@ -6,7 +6,7 @@ import { toPuny } from '@/misc/convert-host'; export const meta = { tags: ['federation'], - requireCredential: false as const, + requireCredential: false, params: { host: { @@ -15,12 +15,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: true, nullable: false, ref: 'FederationInstance', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const instance = await Instances .findOne({ host: toPuny(ps.host) }); diff --git a/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts b/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts index 2ba09b1361..092f805bc2 100644 --- a/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts +++ b/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts @@ -7,15 +7,16 @@ import { updatePerson } from '@/remote/activitypub/models/person'; export const meta = { tags: ['federation'], - requireCredential: true as const, + requireCredential: true, params: { userId: { validator: $.type(ID), }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const user = await getRemoteUser(ps.userId); await updatePerson(user.uri!); diff --git a/packages/backend/src/server/api/endpoints/federation/users.ts b/packages/backend/src/server/api/endpoints/federation/users.ts index 730dbd74c3..9a8f749936 100644 --- a/packages/backend/src/server/api/endpoints/federation/users.ts +++ b/packages/backend/src/server/api/endpoints/federation/users.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['federation'], - requireCredential: false as const, + requireCredential: false, params: { host: { @@ -29,16 +29,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailedNotMe', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = makePaginationQuery(Users.createQueryBuilder('user'), ps.sinceId, ps.untilId) .andWhere(`user.host = :host`, { host: ps.host }); diff --git a/packages/backend/src/server/api/endpoints/following/create.ts b/packages/backend/src/server/api/endpoints/following/create.ts index 096b1f6055..96aede4550 100644 --- a/packages/backend/src/server/api/endpoints/following/create.ts +++ b/packages/backend/src/server/api/endpoints/following/create.ts @@ -15,7 +15,7 @@ export const meta = { max: 100, }, - requireCredential: true as const, + requireCredential: true, kind: 'write:following', @@ -58,12 +58,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserLite', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const follower = user; diff --git a/packages/backend/src/server/api/endpoints/following/delete.ts b/packages/backend/src/server/api/endpoints/following/delete.ts index 5a0e44ad0b..4cd0c49452 100644 --- a/packages/backend/src/server/api/endpoints/following/delete.ts +++ b/packages/backend/src/server/api/endpoints/following/delete.ts @@ -15,7 +15,7 @@ export const meta = { max: 100, }, - requireCredential: true as const, + requireCredential: true, kind: 'write:following', @@ -46,12 +46,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserLite', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const follower = user; diff --git a/packages/backend/src/server/api/endpoints/following/invalidate.ts b/packages/backend/src/server/api/endpoints/following/invalidate.ts index 050199bfaa..92e887e00b 100644 --- a/packages/backend/src/server/api/endpoints/following/invalidate.ts +++ b/packages/backend/src/server/api/endpoints/following/invalidate.ts @@ -15,7 +15,7 @@ export const meta = { max: 100, }, - requireCredential: true as const, + requireCredential: true, kind: 'write:following', @@ -46,12 +46,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserLite', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const followee = user; diff --git a/packages/backend/src/server/api/endpoints/following/requests/accept.ts b/packages/backend/src/server/api/endpoints/following/requests/accept.ts index 9c07248568..7e7c056f55 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/accept.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/accept.ts @@ -8,7 +8,7 @@ import { getUser } from '../../../common/getters'; export const meta = { tags: ['following', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'write:following', @@ -30,8 +30,9 @@ export const meta = { id: 'bcde4f8b-0913-4614-8881-614e522fb041', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Fetch follower const follower = await getUser(ps.userId).catch(e => { diff --git a/packages/backend/src/server/api/endpoints/following/requests/cancel.ts b/packages/backend/src/server/api/endpoints/following/requests/cancel.ts index d65aa436a0..19ed02c152 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/cancel.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/cancel.ts @@ -9,7 +9,7 @@ import { Users } from '@/models/index'; export const meta = { tags: ['following', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'write:following', @@ -34,12 +34,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserLite', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Fetch followee const followee = await getUser(ps.userId).catch(e => { diff --git a/packages/backend/src/server/api/endpoints/following/requests/list.ts b/packages/backend/src/server/api/endpoints/following/requests/list.ts index 2dadd0d60c..ec0c76502c 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/list.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/list.ts @@ -4,37 +4,38 @@ import { FollowRequests } from '@/models/index'; export const meta = { tags: ['following', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:following', res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, follower: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserLite', }, followee: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserLite', }, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const reqs = await FollowRequests.find({ followeeId: user.id, diff --git a/packages/backend/src/server/api/endpoints/following/requests/reject.ts b/packages/backend/src/server/api/endpoints/following/requests/reject.ts index c385b32385..a5ce1e7c77 100644 --- a/packages/backend/src/server/api/endpoints/following/requests/reject.ts +++ b/packages/backend/src/server/api/endpoints/following/requests/reject.ts @@ -8,7 +8,7 @@ import { getUser } from '../../../common/getters'; export const meta = { tags: ['following', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'write:following', @@ -25,8 +25,9 @@ export const meta = { id: 'abc2ffa6-25b2-4380-ba99-321ff3a94555', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Fetch follower const follower = await getUser(ps.userId).catch(e => { diff --git a/packages/backend/src/server/api/endpoints/gallery/featured.ts b/packages/backend/src/server/api/endpoints/gallery/featured.ts index dc86cf40ba..ff7c16889f 100644 --- a/packages/backend/src/server/api/endpoints/gallery/featured.ts +++ b/packages/backend/src/server/api/endpoints/gallery/featured.ts @@ -4,19 +4,20 @@ import { GalleryPosts } from '@/models/index'; export const meta = { tags: ['gallery'], - requireCredential: false as const, + requireCredential: false, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'GalleryPost', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = GalleryPosts.createQueryBuilder('post') .andWhere('post.createdAt > :date', { date: new Date(Date.now() - (1000 * 60 * 60 * 24 * 3)) }) diff --git a/packages/backend/src/server/api/endpoints/gallery/popular.ts b/packages/backend/src/server/api/endpoints/gallery/popular.ts index ee3fe51ebf..2c3368a19d 100644 --- a/packages/backend/src/server/api/endpoints/gallery/popular.ts +++ b/packages/backend/src/server/api/endpoints/gallery/popular.ts @@ -4,19 +4,20 @@ import { GalleryPosts } from '@/models/index'; export const meta = { tags: ['gallery'], - requireCredential: false as const, + requireCredential: false, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'GalleryPost', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = GalleryPosts.createQueryBuilder('post') .andWhere('post.likedCount > 0') diff --git a/packages/backend/src/server/api/endpoints/gallery/posts.ts b/packages/backend/src/server/api/endpoints/gallery/posts.ts index 90bd2d959f..9d2601c7e9 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts.ts @@ -23,16 +23,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'GalleryPost', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = makePaginationQuery(GalleryPosts.createQueryBuilder('post'), ps.sinceId, ps.untilId) .innerJoinAndSelect('post.user', 'user'); diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts index dae6e27ddb..e9d5df1ab6 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts @@ -11,7 +11,7 @@ import { DriveFile } from '@/models/entities/drive-file'; export const meta = { tags: ['gallery'], - requireCredential: true as const, + requireCredential: true, kind: 'write:gallery', @@ -40,16 +40,17 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'GalleryPost', }, errors: { }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const files = (await Promise.all(ps.fileIds.map(fileId => DriveFiles.findOne({ diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts b/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts index e43c12a4c1..2a13b9ed58 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts @@ -7,7 +7,7 @@ import { ID } from '@/misc/cafy-id'; export const meta = { tags: ['gallery'], - requireCredential: true as const, + requireCredential: true, kind: 'write:gallery', @@ -24,8 +24,9 @@ export const meta = { id: 'ae52f367-4bd7-4ecd-afc6-5672fff427f5', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const post = await GalleryPosts.findOne({ id: ps.postId, diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts index d355d1e312..0fb408fa5f 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts @@ -8,7 +8,7 @@ import { genId } from '@/misc/gen-id'; export const meta = { tags: ['gallery'], - requireCredential: true as const, + requireCredential: true, kind: 'write:gallery-likes', @@ -37,8 +37,9 @@ export const meta = { id: '40e9ed56-a59c-473a-bf3f-f289c54fb5a7', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const post = await GalleryPosts.findOne(ps.postId); if (post == null) { diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/show.ts b/packages/backend/src/server/api/endpoints/gallery/posts/show.ts index 7e620b2c48..4325d2ad37 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/show.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/show.ts @@ -7,7 +7,7 @@ import { GalleryPosts } from '@/models/index'; export const meta = { tags: ['gallery'], - requireCredential: false as const, + requireCredential: false, params: { postId: { @@ -24,12 +24,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'GalleryPost', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const post = await GalleryPosts.findOne({ id: ps.postId, diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts b/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts index 323e7c4828..9cca09bddc 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts @@ -7,7 +7,7 @@ import { GalleryPosts, GalleryLikes } from '@/models/index'; export const meta = { tags: ['gallery'], - requireCredential: true as const, + requireCredential: true, kind: 'write:gallery-likes', @@ -30,8 +30,9 @@ export const meta = { id: 'e3e8e06e-be37-41f7-a5b4-87a8250288f0', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const post = await GalleryPosts.findOne(ps.postId); if (post == null) { diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts index 7cd694e804..c35e1bbf58 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts @@ -10,7 +10,7 @@ import { DriveFile } from '@/models/entities/drive-file'; export const meta = { tags: ['gallery'], - requireCredential: true as const, + requireCredential: true, kind: 'write:gallery', @@ -43,16 +43,17 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'GalleryPost', }, errors: { }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const files = (await Promise.all(ps.fileIds.map(fileId => DriveFiles.findOne({ diff --git a/packages/backend/src/server/api/endpoints/games/reversi/games.ts b/packages/backend/src/server/api/endpoints/games/reversi/games.ts deleted file mode 100644 index f77f11942c..0000000000 --- a/packages/backend/src/server/api/endpoints/games/reversi/games.ts +++ /dev/null @@ -1,156 +0,0 @@ -import $ from 'cafy'; -import { ID } from '@/misc/cafy-id'; -import define from '../../../define'; -import { ReversiGames } from '@/models/index'; -import { makePaginationQuery } from '../../../common/make-pagination-query'; -import { Brackets } from 'typeorm'; - -export const meta = { - tags: ['games'], - - params: { - limit: { - validator: $.optional.num.range(1, 100), - default: 10, - }, - - sinceId: { - validator: $.optional.type(ID), - }, - - untilId: { - validator: $.optional.type(ID), - }, - - my: { - validator: $.optional.bool, - default: false, - }, - }, - - res: { - type: 'array' as const, - optional: false as const, nullable: false as const, - items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - startedAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - isStarted: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - isEnded: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - form1: { - type: 'any' as const, - optional: false as const, nullable: true as const, - }, - form2: { - type: 'any' as const, - optional: false as const, nullable: true as const, - }, - user1Accepted: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - default: false, - }, - user2Accepted: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - default: false, - }, - user1Id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - user2Id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - user1: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', - }, - user2: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', - }, - winnerId: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'id', - }, - winner: { - type: 'object' as const, - optional: false as const, nullable: true as const, - ref: 'User', - }, - surrendered: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'id', - }, - black: { - type: 'number' as const, - optional: false as const, nullable: true as const, - }, - bw: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - isLlotheo: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - canPutEverywhere: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - loopedBoard: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - }, - }, - }, -}; - -export default define(meta, async (ps, user) => { - const query = makePaginationQuery(ReversiGames.createQueryBuilder('game'), ps.sinceId, ps.untilId) - .andWhere('game.isStarted = TRUE'); - - if (ps.my && user) { - query.andWhere(new Brackets(qb => { qb - .where('game.user1Id = :userId', { userId: user.id }) - .orWhere('game.user2Id = :userId', { userId: user.id }); - })); - } - - // Fetch games - const games = await query.take(ps.limit!).getMany(); - - return await Promise.all(games.map((g) => ReversiGames.pack(g, user, { - detail: false, - }))); -}); diff --git a/packages/backend/src/server/api/endpoints/games/reversi/games/show.ts b/packages/backend/src/server/api/endpoints/games/reversi/games/show.ts deleted file mode 100644 index 0476a4be9b..0000000000 --- a/packages/backend/src/server/api/endpoints/games/reversi/games/show.ts +++ /dev/null @@ -1,168 +0,0 @@ -import $ from 'cafy'; -import { ID } from '@/misc/cafy-id'; -import Reversi from '../../../../../../games/reversi/core'; -import define from '../../../../define'; -import { ApiError } from '../../../../error'; -import { ReversiGames } from '@/models/index'; - -export const meta = { - tags: ['games'], - - params: { - gameId: { - validator: $.type(ID), - }, - }, - - errors: { - noSuchGame: { - message: 'No such game.', - code: 'NO_SUCH_GAME', - id: 'f13a03db-fae1-46c9-87f3-43c8165419e1', - }, - }, - - res: { - type: 'array' as const, - optional: false as const, nullable: false as const, - items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - startedAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - isStarted: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - isEnded: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - form1: { - type: 'any' as const, - optional: false as const, nullable: true as const, - }, - form2: { - type: 'any' as const, - optional: false as const, nullable: true as const, - }, - user1Accepted: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - default: false, - }, - user2Accepted: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - default: false, - }, - user1Id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - user2Id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - user1: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', - }, - user2: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', - }, - winnerId: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'id', - }, - winner: { - type: 'object' as const, - optional: false as const, nullable: true as const, - ref: 'User', - }, - surrendered: { - type: 'string' as const, - optional: false as const, nullable: true as const, - format: 'id', - }, - black: { - type: 'number' as const, - optional: false as const, nullable: true as const, - }, - bw: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - isLlotheo: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - canPutEverywhere: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - loopedBoard: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, - }, - board: { - type: 'array' as const, - optional: false as const, nullable: false as const, - items: { - type: 'any' as const, - optional: false as const, nullable: false as const, - }, - }, - turn: { - type: 'any' as const, - optional: false as const, nullable: false as const, - }, - }, - }, - }, -}; - -export default define(meta, async (ps, user) => { - const game = await ReversiGames.findOne(ps.gameId); - - if (game == null) { - throw new ApiError(meta.errors.noSuchGame); - } - - const o = new Reversi(game.map, { - isLlotheo: game.isLlotheo, - canPutEverywhere: game.canPutEverywhere, - loopedBoard: game.loopedBoard, - }); - - for (const log of game.logs) { - o.put(log.color, log.pos); - } - - const packed = await ReversiGames.pack(game, user); - - return Object.assign({ - board: o.board, - turn: o.turn, - }, packed); -}); diff --git a/packages/backend/src/server/api/endpoints/games/reversi/games/surrender.ts b/packages/backend/src/server/api/endpoints/games/reversi/games/surrender.ts deleted file mode 100644 index 84b80c47a0..0000000000 --- a/packages/backend/src/server/api/endpoints/games/reversi/games/surrender.ts +++ /dev/null @@ -1,67 +0,0 @@ -import $ from 'cafy'; -import { ID } from '@/misc/cafy-id'; -import { publishReversiGameStream } from '@/services/stream'; -import define from '../../../../define'; -import { ApiError } from '../../../../error'; -import { ReversiGames } from '@/models/index'; - -export const meta = { - tags: ['games'], - - requireCredential: true as const, - - params: { - gameId: { - validator: $.type(ID), - }, - }, - - errors: { - noSuchGame: { - message: 'No such game.', - code: 'NO_SUCH_GAME', - id: 'ace0b11f-e0a6-4076-a30d-e8284c81b2df', - }, - - alreadyEnded: { - message: 'That game has already ended.', - code: 'ALREADY_ENDED', - id: '6c2ad4a6-cbf1-4a5b-b187-b772826cfc6d', - }, - - accessDenied: { - message: 'Access denied.', - code: 'ACCESS_DENIED', - id: '6e04164b-a992-4c93-8489-2123069973e1', - }, - }, -}; - -export default define(meta, async (ps, user) => { - const game = await ReversiGames.findOne(ps.gameId); - - if (game == null) { - throw new ApiError(meta.errors.noSuchGame); - } - - if (game.isEnded) { - throw new ApiError(meta.errors.alreadyEnded); - } - - if ((game.user1Id !== user.id) && (game.user2Id !== user.id)) { - throw new ApiError(meta.errors.accessDenied); - } - - const winnerId = game.user1Id === user.id ? game.user2Id : game.user1Id; - - await ReversiGames.update(game.id, { - surrendered: user.id, - isEnded: true, - winnerId: winnerId, - }); - - publishReversiGameStream(game.id, 'ended', { - winnerId: winnerId, - game: await ReversiGames.pack(game.id, user), - }); -}); diff --git a/packages/backend/src/server/api/endpoints/games/reversi/invitations.ts b/packages/backend/src/server/api/endpoints/games/reversi/invitations.ts deleted file mode 100644 index b859a2fc75..0000000000 --- a/packages/backend/src/server/api/endpoints/games/reversi/invitations.ts +++ /dev/null @@ -1,58 +0,0 @@ -import define from '../../../define'; -import { ReversiMatchings } from '@/models/index'; - -export const meta = { - tags: ['games'], - - requireCredential: true as const, - - res: { - type: 'array' as const, - optional: false as const, nullable: false as const, - items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - createdAt: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'date-time', - }, - parentId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - parent: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', - }, - childId: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'id', - }, - child: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', - }, - }, - }, - }, -}; - -export default define(meta, async (ps, user) => { - // Find session - const invitations = await ReversiMatchings.find({ - childId: user.id, - }); - - return await Promise.all(invitations.map((i) => ReversiMatchings.pack(i, user))); -}); diff --git a/packages/backend/src/server/api/endpoints/games/reversi/match.ts b/packages/backend/src/server/api/endpoints/games/reversi/match.ts deleted file mode 100644 index c66c5a4f75..0000000000 --- a/packages/backend/src/server/api/endpoints/games/reversi/match.ts +++ /dev/null @@ -1,108 +0,0 @@ -import $ from 'cafy'; -import { ID } from '@/misc/cafy-id'; -import { publishMainStream, publishReversiStream } from '@/services/stream'; -import { eighteight } from '../../../../../games/reversi/maps'; -import define from '../../../define'; -import { ApiError } from '../../../error'; -import { getUser } from '../../../common/getters'; -import { genId } from '@/misc/gen-id'; -import { ReversiMatchings, ReversiGames } from '@/models/index'; -import { ReversiGame } from '@/models/entities/games/reversi/game'; -import { ReversiMatching } from '@/models/entities/games/reversi/matching'; - -export const meta = { - tags: ['games'], - - requireCredential: true as const, - - params: { - userId: { - validator: $.type(ID), - }, - }, - - errors: { - noSuchUser: { - message: 'No such user.', - code: 'NO_SUCH_USER', - id: '0b4f0559-b484-4e31-9581-3f73cee89b28', - }, - - isYourself: { - message: 'Target user is yourself.', - code: 'TARGET_IS_YOURSELF', - id: '96fd7bd6-d2bc-426c-a865-d055dcd2828e', - }, - }, -}; - -export default define(meta, async (ps, user) => { - // Myself - if (ps.userId === user.id) { - throw new ApiError(meta.errors.isYourself); - } - - // Find session - const exist = await ReversiMatchings.findOne({ - parentId: ps.userId, - childId: user.id, - }); - - if (exist) { - // Destroy session - ReversiMatchings.delete(exist.id); - - // Create game - const game = await ReversiGames.save({ - id: genId(), - createdAt: new Date(), - user1Id: exist.parentId, - user2Id: user.id, - user1Accepted: false, - user2Accepted: false, - isStarted: false, - isEnded: false, - logs: [], - map: eighteight.data, - bw: 'random', - isLlotheo: false, - } as Partial<ReversiGame>); - - publishReversiStream(exist.parentId, 'matched', await ReversiGames.pack(game, { id: exist.parentId })); - - const other = await ReversiMatchings.count({ - childId: user.id, - }); - - if (other == 0) { - publishMainStream(user.id, 'reversiNoInvites'); - } - - return await ReversiGames.pack(game, user); - } else { - // Fetch child - const child = await getUser(ps.userId).catch(e => { - if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw e; - }); - - // 以前のセッションはすべて削除しておく - await ReversiMatchings.delete({ - parentId: user.id, - }); - - // セッションを作成 - const matching = await ReversiMatchings.save({ - id: genId(), - createdAt: new Date(), - parentId: user.id, - childId: child.id, - } as ReversiMatching); - - const packed = await ReversiMatchings.pack(matching, child); - publishReversiStream(child.id, 'invited', packed); - publishMainStream(child.id, 'reversiInvited', packed); - - return; - } -}); diff --git a/packages/backend/src/server/api/endpoints/games/reversi/match/cancel.ts b/packages/backend/src/server/api/endpoints/games/reversi/match/cancel.ts deleted file mode 100644 index 8076b7c5d8..0000000000 --- a/packages/backend/src/server/api/endpoints/games/reversi/match/cancel.ts +++ /dev/null @@ -1,14 +0,0 @@ -import define from '../../../../define'; -import { ReversiMatchings } from '@/models/index'; - -export const meta = { - tags: ['games'], - - requireCredential: true as const, -}; - -export default define(meta, async (ps, user) => { - await ReversiMatchings.delete({ - parentId: user.id, - }); -}); diff --git a/packages/backend/src/server/api/endpoints/get-online-users-count.ts b/packages/backend/src/server/api/endpoints/get-online-users-count.ts index 0616431abc..5b13d5a3b8 100644 --- a/packages/backend/src/server/api/endpoints/get-online-users-count.ts +++ b/packages/backend/src/server/api/endpoints/get-online-users-count.ts @@ -6,12 +6,13 @@ import define from '../define'; export const meta = { tags: ['meta'], - requireCredential: false as const, + requireCredential: false, params: { }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async () => { const count = await Users.count({ lastActiveDate: MoreThan(new Date(Date.now() - USER_ONLINE_THRESHOLD)), diff --git a/packages/backend/src/server/api/endpoints/hashtags/list.ts b/packages/backend/src/server/api/endpoints/hashtags/list.ts index 5f8bfee2df..9fa9b3edc6 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/list.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/list.ts @@ -5,7 +5,7 @@ import { Hashtags } from '@/models/index'; export const meta = { tags: ['hashtags'], - requireCredential: false as const, + requireCredential: false, params: { limit: { @@ -47,16 +47,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Hashtag', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = Hashtags.createQueryBuilder('tag'); diff --git a/packages/backend/src/server/api/endpoints/hashtags/search.ts b/packages/backend/src/server/api/endpoints/hashtags/search.ts index d7de1db557..0d646c64f5 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/search.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/search.ts @@ -5,7 +5,7 @@ import { Hashtags } from '@/models/index'; export const meta = { tags: ['hashtags'], - requireCredential: false as const, + requireCredential: false, params: { limit: { @@ -24,15 +24,16 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const hashtags = await Hashtags.createQueryBuilder('tag') .where('tag.name like :q', { q: ps.query.toLowerCase() + '%' }) diff --git a/packages/backend/src/server/api/endpoints/hashtags/show.ts b/packages/backend/src/server/api/endpoints/hashtags/show.ts index 9410aea389..242cef99d4 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/show.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/show.ts @@ -7,7 +7,7 @@ import { normalizeForSearch } from '@/misc/normalize-for-search'; export const meta = { tags: ['hashtags'], - requireCredential: false as const, + requireCredential: false, params: { tag: { @@ -16,8 +16,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Hashtag', }, @@ -28,8 +28,9 @@ export const meta = { id: '110ee688-193e-4a3a-9ecf-c167b2e6981e', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const hashtag = await Hashtags.findOne({ name: normalizeForSearch(ps.tag) }); if (hashtag == null) { diff --git a/packages/backend/src/server/api/endpoints/hashtags/trend.ts b/packages/backend/src/server/api/endpoints/hashtags/trend.ts index deb8417ad6..be964ad639 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/trend.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/trend.ts @@ -23,36 +23,37 @@ const max = 5; export const meta = { tags: ['hashtags'], - requireCredential: false as const, + requireCredential: false, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { tag: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, chart: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, }, usersCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async () => { const instance = await fetchMeta(true); const hiddenTags = instance.hiddenTags.map(t => normalizeForSearch(t)); diff --git a/packages/backend/src/server/api/endpoints/hashtags/users.ts b/packages/backend/src/server/api/endpoints/hashtags/users.ts index 69b17a19eb..2158dc4349 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/users.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/users.ts @@ -4,7 +4,7 @@ import { Users } from '@/models/index'; import { normalizeForSearch } from '@/misc/normalize-for-search'; export const meta = { - requireCredential: false as const, + requireCredential: false, tags: ['hashtags', 'users'], @@ -48,16 +48,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailed', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = Users.createQueryBuilder('user') .where(':tag = ANY(user.tags)', { tag: normalizeForSearch(ps.tag) }); diff --git a/packages/backend/src/server/api/endpoints/i.ts b/packages/backend/src/server/api/endpoints/i.ts index 2063a55a81..d69c118cfc 100644 --- a/packages/backend/src/server/api/endpoints/i.ts +++ b/packages/backend/src/server/api/endpoints/i.ts @@ -4,22 +4,23 @@ import { Users } from '@/models/index'; export const meta = { tags: ['account'], - requireCredential: true as const, + requireCredential: true, params: {}, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'MeDetailed', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user, token) => { const isSecure = token == null; // ここで渡ってきている user はキャッシュされていて古い可能性もあるので id だけ渡す - return await Users.pack(user.id, user, { + return await Users.pack<true, true>(user.id, user, { detail: true, includeSecrets: isSecure, }); diff --git a/packages/backend/src/server/api/endpoints/i/2fa/done.ts b/packages/backend/src/server/api/endpoints/i/2fa/done.ts index 3b772386f3..4853908693 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/done.ts @@ -4,7 +4,7 @@ import define from '../../../define'; import { UserProfiles } from '@/models/index'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -13,8 +13,9 @@ export const meta = { validator: $.str, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const token = ps.token.replace(/\s/g, ''); diff --git a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts index f0045fb997..26e9a60886 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts @@ -16,7 +16,7 @@ import { publishMainStream } from '@/services/stream'; const cborDecodeFirst = promisify(cbor.decodeFirst) as any; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -37,10 +37,11 @@ export const meta = { validator: $.str, }, }, -}; +} as const; const rpIdHashReal = hash(Buffer.from(config.hostname, 'utf-8')); +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const profile = await UserProfiles.findOneOrFail(user.id); @@ -129,7 +130,7 @@ export default define(meta, async (ps, user) => { const credentialIdString = credentialId.toString('hex'); - await UserSecurityKeys.save({ + await UserSecurityKeys.insert({ userId: user.id, id: credentialIdString, lastUsed: new Date(), diff --git a/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts b/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts index dc2b66286b..854848a434 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts @@ -3,7 +3,7 @@ import define from '../../../define'; import { UserProfiles } from '@/models/index'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -12,8 +12,9 @@ export const meta = { validator: $.boolean, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { await UserProfiles.update(user.id, { usePasswordLessLogin: ps.value, diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts index aa6c8fb1d5..057e54c69b 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts @@ -10,7 +10,7 @@ import { hash } from '../../../2fa'; const randomBytes = promisify(crypto.randomBytes); export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -19,8 +19,9 @@ export const meta = { validator: $.str, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const profile = await UserProfiles.findOneOrFail(user.id); @@ -44,7 +45,7 @@ export default define(meta, async (ps, user) => { const challengeId = genId(); - await AttestationChallenges.save({ + await AttestationChallenges.insert({ userId: user.id, id: challengeId, challenge: hash(Buffer.from(challenge, 'utf-8')).toString('hex'), diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register.ts b/packages/backend/src/server/api/endpoints/i/2fa/register.ts index 347dec0f43..c5cfb9dfad 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register.ts @@ -7,7 +7,7 @@ import define from '../../../define'; import { UserProfiles } from '@/models/index'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -16,8 +16,9 @@ export const meta = { validator: $.str, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const profile = await UserProfiles.findOneOrFail(user.id); diff --git a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts index 05d63452f1..03e1d0434d 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts @@ -5,7 +5,7 @@ import { UserProfiles, UserSecurityKeys, Users } from '@/models/index'; import { publishMainStream } from '@/services/stream'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -17,8 +17,9 @@ export const meta = { validator: $.str, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const profile = await UserProfiles.findOneOrFail(user.id); diff --git a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts index a0d8b5906b..a19ad6810d 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts @@ -4,7 +4,7 @@ import define from '../../../define'; import { UserProfiles } from '@/models/index'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -13,8 +13,9 @@ export const meta = { validator: $.str, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const profile = await UserProfiles.findOneOrFail(user.id); diff --git a/packages/backend/src/server/api/endpoints/i/apps.ts b/packages/backend/src/server/api/endpoints/i/apps.ts index 64986865b2..63999b0981 100644 --- a/packages/backend/src/server/api/endpoints/i/apps.ts +++ b/packages/backend/src/server/api/endpoints/i/apps.ts @@ -3,7 +3,7 @@ import define from '../../define'; import { AccessTokens } from '@/models/index'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -17,8 +17,9 @@ export const meta = { ]), }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = AccessTokens.createQueryBuilder('token') .where('token.userId = :userId', { userId: user.id }); diff --git a/packages/backend/src/server/api/endpoints/i/authorized-apps.ts b/packages/backend/src/server/api/endpoints/i/authorized-apps.ts index bfe20eb984..52122b851b 100644 --- a/packages/backend/src/server/api/endpoints/i/authorized-apps.ts +++ b/packages/backend/src/server/api/endpoints/i/authorized-apps.ts @@ -3,7 +3,7 @@ import define from '../../define'; import { AccessTokens, Apps } from '@/models/index'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -23,8 +23,9 @@ export const meta = { default: 'desc', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Get tokens const tokens = await AccessTokens.find({ diff --git a/packages/backend/src/server/api/endpoints/i/change-password.ts b/packages/backend/src/server/api/endpoints/i/change-password.ts index 416eb6229f..7b6c137737 100644 --- a/packages/backend/src/server/api/endpoints/i/change-password.ts +++ b/packages/backend/src/server/api/endpoints/i/change-password.ts @@ -4,7 +4,7 @@ import define from '../../define'; import { UserProfiles } from '@/models/index'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -17,8 +17,9 @@ export const meta = { validator: $.str, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const profile = await UserProfiles.findOneOrFail(user.id); diff --git a/packages/backend/src/server/api/endpoints/i/delete-account.ts b/packages/backend/src/server/api/endpoints/i/delete-account.ts index 13a8f79dfa..e1eee949fc 100644 --- a/packages/backend/src/server/api/endpoints/i/delete-account.ts +++ b/packages/backend/src/server/api/endpoints/i/delete-account.ts @@ -7,7 +7,7 @@ import { publishUserEvent } from '@/services/stream'; import { createDeleteAccountJob } from '@/queue'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -16,8 +16,9 @@ export const meta = { validator: $.str, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const profile = await UserProfiles.findOneOrFail(user.id); const userDetailed = await Users.findOneOrFail(user.id); diff --git a/packages/backend/src/server/api/endpoints/i/export-blocking.ts b/packages/backend/src/server/api/endpoints/i/export-blocking.ts index e276ecf384..44d8a1cb38 100644 --- a/packages/backend/src/server/api/endpoints/i/export-blocking.ts +++ b/packages/backend/src/server/api/endpoints/i/export-blocking.ts @@ -4,13 +4,14 @@ import ms from 'ms'; export const meta = { secure: true, - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1hour'), max: 1, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { createExportBlockingJob(user); }); diff --git a/packages/backend/src/server/api/endpoints/i/export-following.ts b/packages/backend/src/server/api/endpoints/i/export-following.ts index 15c09941e8..5d1617d57b 100644 --- a/packages/backend/src/server/api/endpoints/i/export-following.ts +++ b/packages/backend/src/server/api/endpoints/i/export-following.ts @@ -5,7 +5,7 @@ import ms from 'ms'; export const meta = { secure: true, - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1hour'), max: 1, @@ -20,8 +20,9 @@ export const meta = { default: false, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { createExportFollowingJob(user, ps.excludeMuting, ps.excludeInactive); }); diff --git a/packages/backend/src/server/api/endpoints/i/export-mute.ts b/packages/backend/src/server/api/endpoints/i/export-mute.ts index b176c7ee8d..27ce8f0b29 100644 --- a/packages/backend/src/server/api/endpoints/i/export-mute.ts +++ b/packages/backend/src/server/api/endpoints/i/export-mute.ts @@ -4,13 +4,14 @@ import ms from 'ms'; export const meta = { secure: true, - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1hour'), max: 1, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { createExportMuteJob(user); }); diff --git a/packages/backend/src/server/api/endpoints/i/export-notes.ts b/packages/backend/src/server/api/endpoints/i/export-notes.ts index 8cba04552e..25b1849e80 100644 --- a/packages/backend/src/server/api/endpoints/i/export-notes.ts +++ b/packages/backend/src/server/api/endpoints/i/export-notes.ts @@ -4,13 +4,14 @@ import ms from 'ms'; export const meta = { secure: true, - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1day'), max: 1, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { createExportNotesJob(user); }); diff --git a/packages/backend/src/server/api/endpoints/i/export-user-lists.ts b/packages/backend/src/server/api/endpoints/i/export-user-lists.ts index 44d43c0bea..d28b699c5a 100644 --- a/packages/backend/src/server/api/endpoints/i/export-user-lists.ts +++ b/packages/backend/src/server/api/endpoints/i/export-user-lists.ts @@ -4,13 +4,14 @@ import ms from 'ms'; export const meta = { secure: true, - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1min'), max: 1, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { createExportUserListsJob(user); }); diff --git a/packages/backend/src/server/api/endpoints/i/favorites.ts b/packages/backend/src/server/api/endpoints/i/favorites.ts index 49b0bcd46c..92c767876b 100644 --- a/packages/backend/src/server/api/endpoints/i/favorites.ts +++ b/packages/backend/src/server/api/endpoints/i/favorites.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['account', 'notes', 'favorites'], - requireCredential: true as const, + requireCredential: true, kind: 'read:favorites', @@ -27,16 +27,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'NoteFavorite', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = makePaginationQuery(NoteFavorites.createQueryBuilder('favorite'), ps.sinceId, ps.untilId) .andWhere(`favorite.userId = :meId`, { meId: user.id }) diff --git a/packages/backend/src/server/api/endpoints/i/gallery/likes.ts b/packages/backend/src/server/api/endpoints/i/gallery/likes.ts index 3ee7174f71..f1c5763593 100644 --- a/packages/backend/src/server/api/endpoints/i/gallery/likes.ts +++ b/packages/backend/src/server/api/endpoints/i/gallery/likes.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../../common/make-pagination-query'; export const meta = { tags: ['account', 'gallery'], - requireCredential: true as const, + requireCredential: true, kind: 'read:gallery-likes', @@ -27,23 +27,24 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, page: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'GalleryPost', }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = makePaginationQuery(GalleryLikes.createQueryBuilder('like'), ps.sinceId, ps.untilId) .andWhere(`like.userId = :meId`, { meId: user.id }) diff --git a/packages/backend/src/server/api/endpoints/i/gallery/posts.ts b/packages/backend/src/server/api/endpoints/i/gallery/posts.ts index c8aceb8bf3..d46d42f633 100644 --- a/packages/backend/src/server/api/endpoints/i/gallery/posts.ts +++ b/packages/backend/src/server/api/endpoints/i/gallery/posts.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../../common/make-pagination-query'; export const meta = { tags: ['account', 'gallery'], - requireCredential: true as const, + requireCredential: true, kind: 'read:gallery', @@ -27,16 +27,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'GalleryPost', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = makePaginationQuery(GalleryPosts.createQueryBuilder('post'), ps.sinceId, ps.untilId) .andWhere(`post.userId = :meId`, { meId: user.id }); diff --git a/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts b/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts index 3eddc2746c..4e1a4d3db9 100644 --- a/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts +++ b/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts @@ -4,7 +4,7 @@ import { MutedNotes } from '@/models/index'; export const meta = { tags: ['account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:account', @@ -12,17 +12,18 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { count: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { return { count: await MutedNotes.count({ diff --git a/packages/backend/src/server/api/endpoints/i/import-blocking.ts b/packages/backend/src/server/api/endpoints/i/import-blocking.ts index f0e3106c53..acc5797420 100644 --- a/packages/backend/src/server/api/endpoints/i/import-blocking.ts +++ b/packages/backend/src/server/api/endpoints/i/import-blocking.ts @@ -8,7 +8,7 @@ import { DriveFiles } from '@/models/index'; export const meta = { secure: true, - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1hour'), @@ -46,8 +46,9 @@ export const meta = { id: '6f3a4dcc-f060-a707-4950-806fbdbe60d6', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const file = await DriveFiles.findOne(ps.fileId); diff --git a/packages/backend/src/server/api/endpoints/i/import-following.ts b/packages/backend/src/server/api/endpoints/i/import-following.ts index 61e500599f..35006746fb 100644 --- a/packages/backend/src/server/api/endpoints/i/import-following.ts +++ b/packages/backend/src/server/api/endpoints/i/import-following.ts @@ -8,7 +8,7 @@ import { DriveFiles } from '@/models/index'; export const meta = { secure: true, - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1hour'), max: 1, @@ -45,8 +45,9 @@ export const meta = { id: '31a1b42c-06f7-42ae-8a38-a661c5c9f691', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const file = await DriveFiles.findOne(ps.fileId); diff --git a/packages/backend/src/server/api/endpoints/i/import-muting.ts b/packages/backend/src/server/api/endpoints/i/import-muting.ts index da26617d91..7bbb2e008e 100644 --- a/packages/backend/src/server/api/endpoints/i/import-muting.ts +++ b/packages/backend/src/server/api/endpoints/i/import-muting.ts @@ -8,7 +8,7 @@ import { DriveFiles } from '@/models/index'; export const meta = { secure: true, - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1hour'), @@ -46,8 +46,9 @@ export const meta = { id: 'd2f12af1-e7b4-feac-86a3-519548f2728e', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const file = await DriveFiles.findOne(ps.fileId); diff --git a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts index 1b850d314f..759d41b6cd 100644 --- a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts +++ b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts @@ -8,7 +8,7 @@ import { DriveFiles } from '@/models/index'; export const meta = { secure: true, - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1hour'), max: 1, @@ -45,8 +45,9 @@ export const meta = { id: '99efe367-ce6e-4d44-93f8-5fae7b040356', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const file = await DriveFiles.findOne(ps.fileId); diff --git a/packages/backend/src/server/api/endpoints/i/notifications.ts b/packages/backend/src/server/api/endpoints/i/notifications.ts index 9083aefacd..59efd32bb2 100644 --- a/packages/backend/src/server/api/endpoints/i/notifications.ts +++ b/packages/backend/src/server/api/endpoints/i/notifications.ts @@ -12,7 +12,7 @@ import { Brackets } from 'typeorm'; export const meta = { tags: ['account', 'notifications'], - requireCredential: true as const, + requireCredential: true, kind: 'read:notifications', @@ -55,16 +55,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Notification', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // includeTypes が空の場合はクエリしない if (ps.includeTypes && ps.includeTypes.length === 0) { diff --git a/packages/backend/src/server/api/endpoints/i/page-likes.ts b/packages/backend/src/server/api/endpoints/i/page-likes.ts index 92fc294850..59239c7446 100644 --- a/packages/backend/src/server/api/endpoints/i/page-likes.ts +++ b/packages/backend/src/server/api/endpoints/i/page-likes.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['account', 'pages'], - requireCredential: true as const, + requireCredential: true, kind: 'read:page-likes', @@ -27,23 +27,24 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, page: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Page', }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = makePaginationQuery(PageLikes.createQueryBuilder('like'), ps.sinceId, ps.untilId) .andWhere(`like.userId = :meId`, { meId: user.id }) diff --git a/packages/backend/src/server/api/endpoints/i/pages.ts b/packages/backend/src/server/api/endpoints/i/pages.ts index 5948712c30..bef775d063 100644 --- a/packages/backend/src/server/api/endpoints/i/pages.ts +++ b/packages/backend/src/server/api/endpoints/i/pages.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['account', 'pages'], - requireCredential: true as const, + requireCredential: true, kind: 'read:pages', @@ -27,16 +27,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Page', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = makePaginationQuery(Pages.createQueryBuilder('page'), ps.sinceId, ps.untilId) .andWhere(`page.userId = :meId`, { meId: user.id }); diff --git a/packages/backend/src/server/api/endpoints/i/pin.ts b/packages/backend/src/server/api/endpoints/i/pin.ts index 5fc49d6518..a940d1b99b 100644 --- a/packages/backend/src/server/api/endpoints/i/pin.ts +++ b/packages/backend/src/server/api/endpoints/i/pin.ts @@ -8,7 +8,7 @@ import { Users } from '@/models/index'; export const meta = { tags: ['account', 'notes'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -39,12 +39,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'MeDetailed', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { await addPinned(user, ps.noteId).catch(e => { if (e.id === '70c4e51f-5bea-449c-a030-53bee3cce202') throw new ApiError(meta.errors.noSuchNote); @@ -53,7 +54,7 @@ export default define(meta, async (ps, user) => { throw e; }); - return await Users.pack(user.id, user, { + return await Users.pack<true, true>(user.id, user, { detail: true, }); }); diff --git a/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts b/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts index a66d6bac7b..4e4fb3840f 100644 --- a/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts +++ b/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts @@ -5,14 +5,15 @@ import { MessagingMessages, UserGroupJoinings } from '@/models/index'; export const meta = { tags: ['account', 'messaging'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', params: { }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Update documents await MessagingMessages.update({ diff --git a/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts b/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts index 90f555763e..99f17ddfc9 100644 --- a/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts +++ b/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts @@ -5,14 +5,15 @@ import { NoteUnreads } from '@/models/index'; export const meta = { tags: ['account'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', params: { }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Remove documents await NoteUnreads.delete({ diff --git a/packages/backend/src/server/api/endpoints/i/read-announcement.ts b/packages/backend/src/server/api/endpoints/i/read-announcement.ts index d948f3efdf..e9bb66264b 100644 --- a/packages/backend/src/server/api/endpoints/i/read-announcement.ts +++ b/packages/backend/src/server/api/endpoints/i/read-announcement.ts @@ -9,7 +9,7 @@ import { publishMainStream } from '@/services/stream'; export const meta = { tags: ['account'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -26,8 +26,9 @@ export const meta = { id: '184663db-df88-4bc2-8b52-fb85f0681939', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Check if announcement exists const announcement = await Announcements.findOne(ps.announcementId); diff --git a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts index f7e910154d..a20719363b 100644 --- a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts +++ b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts @@ -6,7 +6,7 @@ import define from '../../define'; import { Users, UserProfiles } from '@/models/index'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -15,8 +15,9 @@ export const meta = { validator: $.str, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const profile = await UserProfiles.findOneOrFail(user.id); diff --git a/packages/backend/src/server/api/endpoints/i/registry/get-all.ts b/packages/backend/src/server/api/endpoints/i/registry/get-all.ts index 1599ccea6b..2941b441e2 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/get-all.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/get-all.ts @@ -3,7 +3,7 @@ import define from '../../../define'; import { RegistryItems } from '@/models/index'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -13,8 +13,9 @@ export const meta = { default: [], }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = RegistryItems.createQueryBuilder('item') .where('item.domain IS NULL') diff --git a/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts b/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts index 4edeae9e95..51371353c9 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts @@ -4,7 +4,7 @@ import { RegistryItems } from '@/models/index'; import { ApiError } from '../../../error'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -26,8 +26,9 @@ export const meta = { id: '97a1e8e7-c0f7-47d2-957a-92e61256e01a', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = RegistryItems.createQueryBuilder('item') .where('item.domain IS NULL') diff --git a/packages/backend/src/server/api/endpoints/i/registry/get.ts b/packages/backend/src/server/api/endpoints/i/registry/get.ts index aa0695281a..ac617defb0 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/get.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/get.ts @@ -4,7 +4,7 @@ import { RegistryItems } from '@/models/index'; import { ApiError } from '../../../error'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -26,8 +26,9 @@ export const meta = { id: 'ac3ed68a-62f0-422b-a7bc-d5e09e8f6a6a', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = RegistryItems.createQueryBuilder('item') .where('item.domain IS NULL') diff --git a/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts b/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts index 9cac503538..0445922188 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts @@ -3,7 +3,7 @@ import define from '../../../define'; import { RegistryItems } from '@/models/index'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -13,8 +13,9 @@ export const meta = { default: [], }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = RegistryItems.createQueryBuilder('item') .where('item.domain IS NULL') diff --git a/packages/backend/src/server/api/endpoints/i/registry/keys.ts b/packages/backend/src/server/api/endpoints/i/registry/keys.ts index 215ccbd5b5..a3c9d0e5ee 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/keys.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/keys.ts @@ -3,7 +3,7 @@ import define from '../../../define'; import { RegistryItems } from '@/models/index'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -13,8 +13,9 @@ export const meta = { default: [], }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = RegistryItems.createQueryBuilder('item') .select('item.key') diff --git a/packages/backend/src/server/api/endpoints/i/registry/remove.ts b/packages/backend/src/server/api/endpoints/i/registry/remove.ts index 17ce5851c0..08185f224b 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/remove.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/remove.ts @@ -4,7 +4,7 @@ import { RegistryItems } from '@/models/index'; import { ApiError } from '../../../error'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -26,8 +26,9 @@ export const meta = { id: '1fac4e8a-a6cd-4e39-a4a5-3a7e11f1b019', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = RegistryItems.createQueryBuilder('item') .where('item.domain IS NULL') diff --git a/packages/backend/src/server/api/endpoints/i/registry/scopes.ts b/packages/backend/src/server/api/endpoints/i/registry/scopes.ts index 45aeb59771..9de68ac6e8 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/scopes.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/scopes.ts @@ -2,14 +2,15 @@ import define from '../../../define'; import { RegistryItems } from '@/models/index'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, params: { }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = RegistryItems.createQueryBuilder('item') .select('item.scope') diff --git a/packages/backend/src/server/api/endpoints/i/registry/set.ts b/packages/backend/src/server/api/endpoints/i/registry/set.ts index 7c282064c3..27884046b4 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/set.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/set.ts @@ -5,7 +5,7 @@ import { RegistryItems } from '@/models/index'; import { genId } from '@/misc/gen-id'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -23,8 +23,9 @@ export const meta = { default: [], }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = RegistryItems.createQueryBuilder('item') .where('item.domain IS NULL') diff --git a/packages/backend/src/server/api/endpoints/i/revoke-token.ts b/packages/backend/src/server/api/endpoints/i/revoke-token.ts index 1b6b18aa80..51721c5b58 100644 --- a/packages/backend/src/server/api/endpoints/i/revoke-token.ts +++ b/packages/backend/src/server/api/endpoints/i/revoke-token.ts @@ -5,7 +5,7 @@ import { ID } from '@/misc/cafy-id'; import { publishUserEvent } from '@/services/stream'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -14,8 +14,9 @@ export const meta = { validator: $.type(ID), }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const token = await AccessTokens.findOne(ps.tokenId); diff --git a/packages/backend/src/server/api/endpoints/i/signin-history.ts b/packages/backend/src/server/api/endpoints/i/signin-history.ts index 6f2f8fc8ca..796e2ec309 100644 --- a/packages/backend/src/server/api/endpoints/i/signin-history.ts +++ b/packages/backend/src/server/api/endpoints/i/signin-history.ts @@ -5,7 +5,7 @@ import { Signins } from '@/models/index'; import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -23,8 +23,9 @@ export const meta = { validator: $.optional.type(ID), }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = makePaginationQuery(Signins.createQueryBuilder('signin'), ps.sinceId, ps.untilId) .andWhere(`signin.userId = :meId`, { meId: user.id }); diff --git a/packages/backend/src/server/api/endpoints/i/unpin.ts b/packages/backend/src/server/api/endpoints/i/unpin.ts index c1b753bfaf..9c82b74960 100644 --- a/packages/backend/src/server/api/endpoints/i/unpin.ts +++ b/packages/backend/src/server/api/endpoints/i/unpin.ts @@ -8,7 +8,7 @@ import { Users } from '@/models/index'; export const meta = { tags: ['account', 'notes'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -27,19 +27,20 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'MeDetailed', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { await removePinned(user, ps.noteId).catch(e => { if (e.id === 'b302d4cf-c050-400a-bbb3-be208681f40c') throw new ApiError(meta.errors.noSuchNote); throw e; }); - return await Users.pack(user.id, user, { + return await Users.pack<true, true>(user.id, user, { detail: true, }); }); diff --git a/packages/backend/src/server/api/endpoints/i/update-email.ts b/packages/backend/src/server/api/endpoints/i/update-email.ts index d99fa2474d..b4479aa50d 100644 --- a/packages/backend/src/server/api/endpoints/i/update-email.ts +++ b/packages/backend/src/server/api/endpoints/i/update-email.ts @@ -11,7 +11,7 @@ import { ApiError } from '../../error'; import { validateEmailForAccount } from '@/services/validate-email-for-account'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, @@ -43,8 +43,9 @@ export const meta = { id: 'a2defefb-f220-8849-0af6-17f816099323', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const profile = await UserProfiles.findOneOrFail(user.id); diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index 5a62b39377..6b7e53aa1f 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -19,7 +19,7 @@ import { normalizeForSearch } from '@/misc/normalize-for-search'; export const meta = { tags: ['account'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -162,12 +162,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'MeDetailed', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, _user, token) => { const user = await Users.findOneOrFail(_user.id); const isSecure = token == null; @@ -278,7 +279,7 @@ export default define(meta, async (ps, _user, token) => { if (Object.keys(updates).length > 0) await Users.update(user.id, updates); if (Object.keys(profileUpdates).length > 0) await UserProfiles.update(user.id, profileUpdates); - const iObj = await Users.pack(user.id, user, { + const iObj = await Users.pack<true, true>(user.id, user, { detail: true, includeSecrets: isSecure, }); diff --git a/packages/backend/src/server/api/endpoints/i/user-group-invites.ts b/packages/backend/src/server/api/endpoints/i/user-group-invites.ts index 6949e486ab..76a3131e6d 100644 --- a/packages/backend/src/server/api/endpoints/i/user-group-invites.ts +++ b/packages/backend/src/server/api/endpoints/i/user-group-invites.ts @@ -7,7 +7,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['account', 'groups'], - requireCredential: true as const, + requireCredential: true, kind: 'read:user-groups', @@ -27,27 +27,28 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, group: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'UserGroup', }, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = makePaginationQuery(UserGroupInvitations.createQueryBuilder('invitation'), ps.sinceId, ps.untilId) .andWhere(`invitation.userId = :meId`, { meId: user.id }) diff --git a/packages/backend/src/server/api/endpoints/messaging/history.ts b/packages/backend/src/server/api/endpoints/messaging/history.ts index 4ca3d6ebed..5ac49cf96b 100644 --- a/packages/backend/src/server/api/endpoints/messaging/history.ts +++ b/packages/backend/src/server/api/endpoints/messaging/history.ts @@ -7,7 +7,7 @@ import { Brackets } from 'typeorm'; export const meta = { tags: ['messaging'], - requireCredential: true as const, + requireCredential: true, kind: 'read:messaging', @@ -24,16 +24,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'MessagingMessage', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const mute = await Mutings.find({ muterId: user.id, diff --git a/packages/backend/src/server/api/endpoints/messaging/messages.ts b/packages/backend/src/server/api/endpoints/messaging/messages.ts index 79e7764245..7dbddd80e2 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages.ts @@ -11,7 +11,7 @@ import { readUserMessagingMessage, readGroupMessagingMessage, deliverReadActivit export const meta = { tags: ['messaging'], - requireCredential: true as const, + requireCredential: true, kind: 'read:messaging', @@ -44,11 +44,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'MessagingMessage', }, }, @@ -72,8 +72,9 @@ export const meta = { id: 'a053a8dd-a491-4718-8f87-50775aad9284', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { if (ps.userId != null) { // Fetch recipient (user) diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/create.ts b/packages/backend/src/server/api/endpoints/messaging/messages/create.ts index 02b22ead80..5ec16f5e5a 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages/create.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages/create.ts @@ -11,7 +11,7 @@ import { createMessage } from '@/services/messages/create'; export const meta = { tags: ['messaging'], - requireCredential: true as const, + requireCredential: true, kind: 'write:messaging', @@ -34,8 +34,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'MessagingMessage', }, @@ -82,8 +82,9 @@ export const meta = { id: 'c15a5199-7422-4968-941a-2a462c478f7d', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { let recipientUser: User | undefined; let recipientGroup: UserGroup | undefined; diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts b/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts index dd1c2e8dee..2975419cef 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts @@ -9,7 +9,7 @@ import { deleteMessage } from '@/services/messages/delete'; export const meta = { tags: ['messaging'], - requireCredential: true as const, + requireCredential: true, kind: 'write:messaging', @@ -32,8 +32,9 @@ export const meta = { id: '54b5b326-7925-42cf-8019-130fda8b56af', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const message = await MessagingMessages.findOne({ id: ps.messageId, diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/read.ts b/packages/backend/src/server/api/endpoints/messaging/messages/read.ts index 96d68b2605..42c3f49f6f 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages/read.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages/read.ts @@ -8,7 +8,7 @@ import { readUserMessagingMessage, readGroupMessagingMessage } from '../../../co export const meta = { tags: ['messaging'], - requireCredential: true as const, + requireCredential: true, kind: 'write:messaging', @@ -25,8 +25,9 @@ export const meta = { id: '86d56a2f-a9c3-4afb-b13c-3e9bfef9aa14', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const message = await MessagingMessages.findOne(ps.messageId); diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index bced077c12..693a7a04ec 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -9,7 +9,7 @@ import { MoreThan } from 'typeorm'; export const meta = { tags: ['meta'], - requireCredential: false as const, + requireCredential: false, params: { detail: { @@ -19,435 +19,436 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { maintainerName: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, maintainerEmail: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, version: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, example: config.version, }, name: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, uri: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'url', example: 'https://misskey.example.com', }, description: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, langs: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, tosUrl: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, repositoryUrl: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, default: 'https://github.com/misskey-dev/misskey', }, feedbackUrl: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, default: 'https://github.com/misskey-dev/misskey/issues/new', }, secure: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, default: false, }, disableRegistration: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, disableLocalTimeline: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, disableGlobalTimeline: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, driveCapacityPerLocalUserMb: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, driveCapacityPerRemoteUserMb: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, cacheRemoteFiles: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, proxyRemoteFiles: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, emailRequiredForSignup: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, enableHcaptcha: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, hcaptchaSiteKey: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, enableRecaptcha: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, recaptchaSiteKey: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, swPublickey: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, mascotImageUrl: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, default: '/assets/ai.png', }, bannerUrl: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, errorImageUrl: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, default: 'https://xn--931a.moe/aiart/yubitun.png', }, iconUrl: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, maxNoteTextLength: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, default: 500, }, emojis: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, aliases: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, category: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, host: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, url: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'url', }, }, }, }, ads: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { place: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, url: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'url', }, imageUrl: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'url', }, }, }, }, requireSetup: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, example: false, }, enableEmail: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, enableTwitterIntegration: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, enableGithubIntegration: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, enableDiscordIntegration: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, enableServiceWorker: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, translatorAvailable: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, proxyAccountName: { - type: 'string' as const, - optional: false as const, nullable: true as const, + type: 'string', + optional: false, nullable: true, }, features: { - type: 'object' as const, - optional: true as const, nullable: false as const, + type: 'object', + optional: true, nullable: false, properties: { registration: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, localTimeLine: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, globalTimeLine: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, elasticsearch: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, hcaptcha: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, recaptcha: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, objectStorage: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, twitter: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, github: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, discord: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, serviceWorker: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, miauth: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, + type: 'boolean', + optional: true, nullable: false, default: true, }, }, }, userStarForReactionFallback: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, + type: 'boolean', + optional: true, nullable: false, }, pinnedUsers: { - type: 'array' as const, - optional: true as const, nullable: false as const, + type: 'array', + optional: true, nullable: false, items: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, hiddenTags: { - type: 'array' as const, - optional: true as const, nullable: false as const, + type: 'array', + optional: true, nullable: false, items: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, blockedHosts: { - type: 'array' as const, - optional: true as const, nullable: false as const, + type: 'array', + optional: true, nullable: false, items: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, hcaptchaSecretKey: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, recaptchaSecretKey: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, proxyAccountId: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, format: 'id', }, twitterConsumerKey: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, twitterConsumerSecret: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, githubClientId: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, githubClientSecret: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, discordClientId: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, discordClientSecret: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, summaryProxy: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, email: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, smtpSecure: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, + type: 'boolean', + optional: true, nullable: false, }, smtpHost: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, smtpPort: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, smtpUser: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, smtpPass: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, swPrivateKey: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, useObjectStorage: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, + type: 'boolean', + optional: true, nullable: false, }, objectStorageBaseUrl: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, objectStorageBucket: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, objectStoragePrefix: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, objectStorageEndpoint: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, objectStorageRegion: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, objectStoragePort: { - type: 'number' as const, - optional: true as const, nullable: true as const, + type: 'number', + optional: true, nullable: true, }, objectStorageAccessKey: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, objectStorageSecretKey: { - type: 'string' as const, - optional: true as const, nullable: true as const, + type: 'string', + optional: true, nullable: true, }, objectStorageUseSSL: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, + type: 'boolean', + optional: true, nullable: false, }, objectStorageUseProxy: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, + type: 'boolean', + optional: true, nullable: false, }, objectStorageSetPublicRead: { - type: 'boolean' as const, - optional: true as const, nullable: false as const, + type: 'boolean', + optional: true, nullable: false, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const instance = await fetchMeta(true); diff --git a/packages/backend/src/server/api/endpoints/miauth/gen-token.ts b/packages/backend/src/server/api/endpoints/miauth/gen-token.ts index 29f109f369..158c8877e9 100644 --- a/packages/backend/src/server/api/endpoints/miauth/gen-token.ts +++ b/packages/backend/src/server/api/endpoints/miauth/gen-token.ts @@ -7,7 +7,7 @@ import { secureRndstr } from '@/misc/secure-rndstr'; export const meta = { tags: ['auth'], - requireCredential: true as const, + requireCredential: true, secure: true, @@ -34,17 +34,18 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { token: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Generate access token const accessToken = secureRndstr(32, true); diff --git a/packages/backend/src/server/api/endpoints/mute/create.ts b/packages/backend/src/server/api/endpoints/mute/create.ts index 703611f67f..6ba5a453c3 100644 --- a/packages/backend/src/server/api/endpoints/mute/create.ts +++ b/packages/backend/src/server/api/endpoints/mute/create.ts @@ -11,7 +11,7 @@ import { publishUserEvent } from '@/services/stream'; export const meta = { tags: ['account'], - requireCredential: true as const, + requireCredential: true, kind: 'write:mutes', @@ -40,8 +40,9 @@ export const meta = { id: '7e7359cb-160c-4956-b08f-4d1c653cd007', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const muter = user; diff --git a/packages/backend/src/server/api/endpoints/mute/delete.ts b/packages/backend/src/server/api/endpoints/mute/delete.ts index aa8c33d046..21948dc3d1 100644 --- a/packages/backend/src/server/api/endpoints/mute/delete.ts +++ b/packages/backend/src/server/api/endpoints/mute/delete.ts @@ -9,7 +9,7 @@ import { publishUserEvent } from '@/services/stream'; export const meta = { tags: ['account'], - requireCredential: true as const, + requireCredential: true, kind: 'write:mutes', @@ -38,8 +38,9 @@ export const meta = { id: '5467d020-daa9-4553-81e1-135c0c35a96d', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const muter = user; diff --git a/packages/backend/src/server/api/endpoints/mute/list.ts b/packages/backend/src/server/api/endpoints/mute/list.ts index 48b6ddb060..4c6a81b63c 100644 --- a/packages/backend/src/server/api/endpoints/mute/list.ts +++ b/packages/backend/src/server/api/endpoints/mute/list.ts @@ -7,7 +7,7 @@ import { Mutings } from '@/models/index'; export const meta = { tags: ['account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:mutes', @@ -27,16 +27,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Muting', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = makePaginationQuery(Mutings.createQueryBuilder('muting'), ps.sinceId, ps.untilId) .andWhere(`muting.muterId = :meId`, { meId: me.id }); diff --git a/packages/backend/src/server/api/endpoints/my/apps.ts b/packages/backend/src/server/api/endpoints/my/apps.ts index 1164f5f6f3..42bd5c5f75 100644 --- a/packages/backend/src/server/api/endpoints/my/apps.ts +++ b/packages/backend/src/server/api/endpoints/my/apps.ts @@ -5,7 +5,7 @@ import { Apps } from '@/models/index'; export const meta = { tags: ['account', 'app'], - requireCredential: true as const, + requireCredential: true, params: { limit: { @@ -20,55 +20,56 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, name: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, callbackUrl: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, permission: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, secret: { - type: 'string' as const, - optional: true as const, nullable: false as const, + type: 'string', + optional: true, nullable: false, }, isAuthorized: { - type: 'object' as const, - optional: true as const, nullable: false as const, + type: 'object', + optional: true, nullable: false, properties: { appId: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, userId: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, }, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = { userId: user.id, diff --git a/packages/backend/src/server/api/endpoints/notes.ts b/packages/backend/src/server/api/endpoints/notes.ts index 37d1b03dce..9edc6cb11c 100644 --- a/packages/backend/src/server/api/endpoints/notes.ts +++ b/packages/backend/src/server/api/endpoints/notes.ts @@ -43,16 +43,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) .andWhere(`note.visibility = 'public'`) diff --git a/packages/backend/src/server/api/endpoints/notes/children.ts b/packages/backend/src/server/api/endpoints/notes/children.ts index acd9d6f7e4..088ef65e96 100644 --- a/packages/backend/src/server/api/endpoints/notes/children.ts +++ b/packages/backend/src/server/api/endpoints/notes/children.ts @@ -12,7 +12,7 @@ import { generateMutedInstanceQuery } from '../../common/generate-muted-instance export const meta = { tags: ['notes'], - requireCredential: false as const, + requireCredential: false, params: { noteId: { @@ -34,16 +34,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) .andWhere(new Brackets(qb => { qb diff --git a/packages/backend/src/server/api/endpoints/notes/clips.ts b/packages/backend/src/server/api/endpoints/notes/clips.ts index deb14da16c..b89c6db4a8 100644 --- a/packages/backend/src/server/api/endpoints/notes/clips.ts +++ b/packages/backend/src/server/api/endpoints/notes/clips.ts @@ -9,7 +9,7 @@ import { In } from 'typeorm'; export const meta = { tags: ['clips', 'notes'], - requireCredential: false as const, + requireCredential: false, params: { noteId: { @@ -18,12 +18,12 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'Note', + type: 'object', + optional: false, nullable: false, + ref: 'Clip', }, }, @@ -34,8 +34,9 @@ export const meta = { id: '47db1a1c-b0af-458d-8fb4-986e4efafe1e', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const note = await getNote(ps.noteId).catch(e => { if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); diff --git a/packages/backend/src/server/api/endpoints/notes/conversation.ts b/packages/backend/src/server/api/endpoints/notes/conversation.ts index 8fdbb7fdeb..4bd89c32e7 100644 --- a/packages/backend/src/server/api/endpoints/notes/conversation.ts +++ b/packages/backend/src/server/api/endpoints/notes/conversation.ts @@ -9,7 +9,7 @@ import { Notes } from '@/models/index'; export const meta = { tags: ['notes'], - requireCredential: false as const, + requireCredential: false, params: { noteId: { @@ -28,11 +28,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, @@ -44,8 +44,9 @@ export const meta = { id: 'e1035875-9551-45ec-afa8-1ded1fcb53c8', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const note = await getNote(ps.noteId).catch(e => { if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts index 9567374c63..4efa76b248 100644 --- a/packages/backend/src/server/api/endpoints/notes/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/create.ts @@ -25,7 +25,7 @@ setInterval(() => { export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, limit: { duration: ms('1hour'), @@ -48,7 +48,7 @@ export const meta = { validator: $.optional.nullable.str.pipe(text => text.trim() != '' && length(text.trim()) <= maxNoteTextLength - && Array.from(text.trim()).length <= DB_MAX_NOTE_TEXT_LENGTH // DB limit + && Array.from(text.trim()).length <= DB_MAX_NOTE_TEXT_LENGTH, // DB limit ), default: null, }, @@ -78,11 +78,11 @@ export const meta = { }, fileIds: { - validator: $.optional.arr($.type(ID)).unique().range(1, 4), + validator: $.optional.arr($.type(ID)).unique().range(1, 16), }, mediaIds: { - validator: $.optional.arr($.type(ID)).unique().range(1, 4), + validator: $.optional.arr($.type(ID)).unique().range(1, 16), deprecated: true, }, @@ -113,12 +113,12 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { createdNote: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, @@ -173,8 +173,9 @@ export const meta = { id: 'b390d7e1-8a5e-46ed-b625-06271cafd3d3', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { let visibleUsers: User[] = []; if (ps.visibleUserIds) { diff --git a/packages/backend/src/server/api/endpoints/notes/delete.ts b/packages/backend/src/server/api/endpoints/notes/delete.ts index 532213c725..9e080d9e99 100644 --- a/packages/backend/src/server/api/endpoints/notes/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/delete.ts @@ -10,7 +10,7 @@ import { Users } from '@/models/index'; export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, kind: 'write:notes', @@ -39,8 +39,9 @@ export const meta = { id: 'fe8d7103-0ea8-4ec3-814d-f8b401dc69e9', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const note = await getNote(ps.noteId).catch(e => { if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts index 14191eefde..78da6a3b00 100644 --- a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts @@ -9,7 +9,7 @@ import { genId } from '@/misc/gen-id'; export const meta = { tags: ['notes', 'favorites'], - requireCredential: true as const, + requireCredential: true, kind: 'write:favorites', @@ -32,8 +32,9 @@ export const meta = { id: 'a402c12b-34dd-41d2-97d8-4d2ffd96a1a6', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Get favoritee const note = await getNote(ps.noteId).catch(e => { diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts index f8d3b63026..3f3d50f0d5 100644 --- a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts @@ -8,7 +8,7 @@ import { NoteFavorites } from '@/models/index'; export const meta = { tags: ['notes', 'favorites'], - requireCredential: true as const, + requireCredential: true, kind: 'write:favorites', @@ -31,8 +31,9 @@ export const meta = { id: 'b625fc69-635e-45e9-86f4-dbefbef35af5', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Get favoritee const note = await getNote(ps.noteId).catch(e => { diff --git a/packages/backend/src/server/api/endpoints/notes/featured.ts b/packages/backend/src/server/api/endpoints/notes/featured.ts index 2a14c52abc..5a47fb9e08 100644 --- a/packages/backend/src/server/api/endpoints/notes/featured.ts +++ b/packages/backend/src/server/api/endpoints/notes/featured.ts @@ -7,7 +7,7 @@ import { generateBlockedUserQuery } from '../../common/generate-block-query'; export const meta = { tags: ['notes'], - requireCredential: false as const, + requireCredential: false, params: { limit: { @@ -22,16 +22,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const max = 30; const day = 1000 * 60 * 60 * 24 * 3; // 3日前まで diff --git a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts index c3be042bfb..cac8b7d8a9 100644 --- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts @@ -43,11 +43,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, @@ -59,8 +59,9 @@ export const meta = { id: '0332fc13-6ab2-4427-ae80-a9fadffd1a6b', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const m = await fetchMeta(); if (m.disableGlobalTimeline) { diff --git a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts index 4a0b9d49d7..9683df4611 100644 --- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -18,7 +18,7 @@ import { generateBlockedUserQuery } from '../../common/generate-block-query'; export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, params: { limit: { @@ -63,11 +63,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, @@ -79,8 +79,9 @@ export const meta = { id: '620763f4-f621-4533-ab33-0577a1a3c342', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const m = await fetchMeta(); if (m.disableLocalTimeline && !user.isAdmin && !user.isModerator) { diff --git a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts index 113268982b..7776644124 100644 --- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts @@ -54,11 +54,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, @@ -70,8 +70,9 @@ export const meta = { id: '45a6eb02-7695-4393-b023-dd3be9aaaefd', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const m = await fetchMeta(); if (m.disableLocalTimeline) { diff --git a/packages/backend/src/server/api/endpoints/notes/mentions.ts b/packages/backend/src/server/api/endpoints/notes/mentions.ts index 916209ca71..81b3844365 100644 --- a/packages/backend/src/server/api/endpoints/notes/mentions.ts +++ b/packages/backend/src/server/api/endpoints/notes/mentions.ts @@ -13,7 +13,7 @@ import { generateMutedNoteThreadQuery } from '../../common/generate-muted-note-t export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, params: { following: { @@ -40,16 +40,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const followingQuery = Followings.createQueryBuilder('following') .select('following.followeeId') diff --git a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts index 9f133c071e..79b558e65e 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts @@ -6,7 +6,7 @@ import { Brackets, In } from 'typeorm'; export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, params: { limit: { @@ -21,16 +21,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = Polls.createQueryBuilder('poll') .where('poll.userHost IS NULL') diff --git a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts index 479034389a..77387cacb2 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts @@ -17,7 +17,7 @@ import { genId } from '@/misc/gen-id'; export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, kind: 'write:votes', @@ -68,8 +68,9 @@ export const meta = { id: '85a5377e-b1e9-4617-b0b9-5bea73331e49', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const createdAt = new Date(); diff --git a/packages/backend/src/server/api/endpoints/notes/reactions.ts b/packages/backend/src/server/api/endpoints/notes/reactions.ts index dca6deb06f..5205a78171 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions.ts @@ -10,7 +10,7 @@ import { NoteReaction } from '@/models/entities/note-reaction'; export const meta = { tags: ['notes', 'reactions'], - requireCredential: false as const, + requireCredential: false, params: { noteId: { @@ -41,11 +41,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'NoteReaction', }, }, @@ -57,8 +57,9 @@ export const meta = { id: '263fff3d-d0e1-4af4-bea7-8408059b451a', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const note = await getNote(ps.noteId).catch(e => { if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); diff --git a/packages/backend/src/server/api/endpoints/notes/reactions/create.ts b/packages/backend/src/server/api/endpoints/notes/reactions/create.ts index 879b32cd07..1b42781ceb 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions/create.ts @@ -8,7 +8,7 @@ import { ApiError } from '../../../error'; export const meta = { tags: ['reactions', 'notes'], - requireCredential: true as const, + requireCredential: true, kind: 'write:reactions', @@ -41,8 +41,9 @@ export const meta = { id: '20ef5475-9f38-4e4c-bd33-de6d979498ec', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const note = await getNote(ps.noteId).catch(e => { if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); diff --git a/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts b/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts index eb9281f7a0..1d686b5971 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts @@ -9,7 +9,7 @@ import { ApiError } from '../../../error'; export const meta = { tags: ['reactions', 'notes'], - requireCredential: true as const, + requireCredential: true, kind: 'write:reactions', @@ -38,8 +38,9 @@ export const meta = { id: '92f4426d-4196-4125-aa5b-02943e2ec8fc', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const note = await getNote(ps.noteId).catch(e => { if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); diff --git a/packages/backend/src/server/api/endpoints/notes/renotes.ts b/packages/backend/src/server/api/endpoints/notes/renotes.ts index d53d725165..f71d23146a 100644 --- a/packages/backend/src/server/api/endpoints/notes/renotes.ts +++ b/packages/backend/src/server/api/endpoints/notes/renotes.ts @@ -12,7 +12,7 @@ import { generateBlockedUserQuery } from '../../common/generate-block-query'; export const meta = { tags: ['notes'], - requireCredential: false as const, + requireCredential: false, params: { noteId: { @@ -34,11 +34,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, @@ -50,8 +50,9 @@ export const meta = { id: '12908022-2e21-46cd-ba6a-3edaf6093f46', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const note = await getNote(ps.noteId).catch(e => { if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); diff --git a/packages/backend/src/server/api/endpoints/notes/replies.ts b/packages/backend/src/server/api/endpoints/notes/replies.ts index e39878f4f2..62c56534e1 100644 --- a/packages/backend/src/server/api/endpoints/notes/replies.ts +++ b/packages/backend/src/server/api/endpoints/notes/replies.ts @@ -10,7 +10,7 @@ import { generateBlockedUserQuery } from '../../common/generate-block-query'; export const meta = { tags: ['notes'], - requireCredential: false as const, + requireCredential: false, params: { noteId: { @@ -32,16 +32,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) .andWhere('note.replyId = :replyId', { replyId: ps.noteId }) diff --git a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts index 2275f7c1ae..87eaffe2f1 100644 --- a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts +++ b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts @@ -56,16 +56,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) .innerJoinAndSelect('note.user', 'user') diff --git a/packages/backend/src/server/api/endpoints/notes/search.ts b/packages/backend/src/server/api/endpoints/notes/search.ts index b49ee87199..e75212b14b 100644 --- a/packages/backend/src/server/api/endpoints/notes/search.ts +++ b/packages/backend/src/server/api/endpoints/notes/search.ts @@ -13,7 +13,7 @@ import { generateBlockedUserQuery } from '../../common/generate-block-query'; export const meta = { tags: ['notes'], - requireCredential: false as const, + requireCredential: false, params: { query: { @@ -50,19 +50,20 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, errors: { }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { if (es == null) { const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId); diff --git a/packages/backend/src/server/api/endpoints/notes/show.ts b/packages/backend/src/server/api/endpoints/notes/show.ts index 1f7f84cbe4..feb94be1a1 100644 --- a/packages/backend/src/server/api/endpoints/notes/show.ts +++ b/packages/backend/src/server/api/endpoints/notes/show.ts @@ -8,7 +8,7 @@ import { Notes } from '@/models/index'; export const meta = { tags: ['notes'], - requireCredential: false as const, + requireCredential: false, params: { noteId: { @@ -17,8 +17,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, @@ -29,8 +29,9 @@ export const meta = { id: '24fcbfc6-2e37-42b6-8388-c29b3861a08d', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const note = await getNote(ps.noteId).catch(e => { if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); diff --git a/packages/backend/src/server/api/endpoints/notes/state.ts b/packages/backend/src/server/api/endpoints/notes/state.ts index 9673b5a77c..c3e9090bbf 100644 --- a/packages/backend/src/server/api/endpoints/notes/state.ts +++ b/packages/backend/src/server/api/endpoints/notes/state.ts @@ -6,7 +6,7 @@ import { NoteFavorites, Notes, NoteThreadMutings, NoteWatchings } from '@/models export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, params: { noteId: { @@ -15,25 +15,26 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { isFavorited: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, isWatching: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, isMutedThread: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const note = await Notes.findOneOrFail(ps.noteId); diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts index dd2f887f01..a8b50d90f6 100644 --- a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts @@ -10,7 +10,7 @@ import readNote from '@/services/note/read'; export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -27,8 +27,9 @@ export const meta = { id: '5ff67ada-ed3b-2e71-8e87-a1a421e177d2', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const note = await getNote(ps.noteId).catch(e => { if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts index d34c99f901..f76b526ce1 100644 --- a/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts @@ -8,7 +8,7 @@ import { NoteThreadMutings } from '@/models'; export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -25,8 +25,9 @@ export const meta = { id: 'bddd57ac-ceb3-b29d-4334-86ea5fae481a', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const note = await getNote(ps.noteId).catch(e => { if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts index 211b8d4f40..8be2861aec 100644 --- a/packages/backend/src/server/api/endpoints/notes/timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts @@ -16,7 +16,7 @@ import { generateBlockedUserQuery } from '../../common/generate-block-query'; export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, params: { limit: { @@ -61,16 +61,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const hasFollowing = (await Followings.count({ where: { diff --git a/packages/backend/src/server/api/endpoints/notes/translate.ts b/packages/backend/src/server/api/endpoints/notes/translate.ts index 647ae4efe7..ed069cb75a 100644 --- a/packages/backend/src/server/api/endpoints/notes/translate.ts +++ b/packages/backend/src/server/api/endpoints/notes/translate.ts @@ -13,7 +13,7 @@ import { Notes } from '@/models'; export const meta = { tags: ['notes'], - requireCredential: false as const, + requireCredential: false, params: { noteId: { @@ -25,8 +25,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, }, errors: { @@ -36,8 +36,9 @@ export const meta = { id: 'bea9b03f-36e0-49c5-a4db-627a029f8971', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const note = await getNote(ps.noteId).catch(e => { if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); diff --git a/packages/backend/src/server/api/endpoints/notes/unrenote.ts b/packages/backend/src/server/api/endpoints/notes/unrenote.ts index 3661db4d86..8db543d328 100644 --- a/packages/backend/src/server/api/endpoints/notes/unrenote.ts +++ b/packages/backend/src/server/api/endpoints/notes/unrenote.ts @@ -10,7 +10,7 @@ import { Notes, Users } from '@/models/index'; export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, kind: 'write:notes', @@ -33,8 +33,9 @@ export const meta = { id: 'efd4a259-2442-496b-8dd7-b255aa1a160f', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const note = await getNote(ps.noteId).catch(e => { if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); diff --git a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts index d614ddf453..89de73fb9d 100644 --- a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts @@ -11,7 +11,7 @@ import { Brackets } from 'typeorm'; export const meta = { tags: ['notes', 'lists'], - requireCredential: true as const, + requireCredential: true, params: { listId: { @@ -60,11 +60,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, @@ -76,8 +76,9 @@ export const meta = { id: '8fb1fbd5-e476-4c37-9fb0-43d55b63a2ff', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const list = await UserLists.findOne({ id: ps.listId, diff --git a/packages/backend/src/server/api/endpoints/notes/watching/create.ts b/packages/backend/src/server/api/endpoints/notes/watching/create.ts index 7f724953df..6433c6bc2a 100644 --- a/packages/backend/src/server/api/endpoints/notes/watching/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/watching/create.ts @@ -8,7 +8,7 @@ import { ApiError } from '../../../error'; export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -25,8 +25,9 @@ export const meta = { id: 'ea0e37a6-90a3-4f58-ba6b-c328ca206fc7', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const note = await getNote(ps.noteId).catch(e => { if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); diff --git a/packages/backend/src/server/api/endpoints/notes/watching/delete.ts b/packages/backend/src/server/api/endpoints/notes/watching/delete.ts index 76a368c51d..3e9faa2b23 100644 --- a/packages/backend/src/server/api/endpoints/notes/watching/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/watching/delete.ts @@ -8,7 +8,7 @@ import { ApiError } from '../../../error'; export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -25,8 +25,9 @@ export const meta = { id: '09b3695c-f72c-4731-a428-7cff825fc82e', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const note = await getNote(ps.noteId).catch(e => { if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); diff --git a/packages/backend/src/server/api/endpoints/notifications/create.ts b/packages/backend/src/server/api/endpoints/notifications/create.ts index e285eae460..bd8a7ba1b7 100644 --- a/packages/backend/src/server/api/endpoints/notifications/create.ts +++ b/packages/backend/src/server/api/endpoints/notifications/create.ts @@ -5,7 +5,7 @@ import { createNotification } from '@/services/create-notification'; export const meta = { tags: ['notifications'], - requireCredential: true as const, + requireCredential: true, kind: 'write:notifications', @@ -25,8 +25,9 @@ export const meta = { errors: { }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user, token) => { createNotification(user.id, 'app', { appAccessTokenId: token ? token.id : null, diff --git a/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts b/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts index 963af6cb43..4cec38a95d 100644 --- a/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts +++ b/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts @@ -5,11 +5,12 @@ import { Notifications } from '@/models/index'; export const meta = { tags: ['notifications', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'write:notifications', -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Update documents await Notifications.update({ diff --git a/packages/backend/src/server/api/endpoints/notifications/read.ts b/packages/backend/src/server/api/endpoints/notifications/read.ts index 9ff0bbeb83..7e23bc234d 100644 --- a/packages/backend/src/server/api/endpoints/notifications/read.ts +++ b/packages/backend/src/server/api/endpoints/notifications/read.ts @@ -9,7 +9,7 @@ import { ApiError } from '../../error'; export const meta = { tags: ['notifications', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'write:notifications', @@ -26,8 +26,9 @@ export const meta = { id: 'efa929d5-05b5-47d1-beec-e6a4dbed011e', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const notification = await Notifications.findOne({ notifieeId: user.id, diff --git a/packages/backend/src/server/api/endpoints/page-push.ts b/packages/backend/src/server/api/endpoints/page-push.ts index 421eed5ea1..61c0160f83 100644 --- a/packages/backend/src/server/api/endpoints/page-push.ts +++ b/packages/backend/src/server/api/endpoints/page-push.ts @@ -6,7 +6,7 @@ import { Users, Pages } from '@/models/index'; import { ApiError } from '../error'; export const meta = { - requireCredential: true as const, + requireCredential: true, secure: true, params: { @@ -30,8 +30,9 @@ export const meta = { id: '4a13ad31-6729-46b4-b9af-e86b265c2e74', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const page = await Pages.findOne(ps.pageId); if (page == null) { diff --git a/packages/backend/src/server/api/endpoints/pages/create.ts b/packages/backend/src/server/api/endpoints/pages/create.ts index 441ba54265..7ee50fbdfa 100644 --- a/packages/backend/src/server/api/endpoints/pages/create.ts +++ b/packages/backend/src/server/api/endpoints/pages/create.ts @@ -10,7 +10,7 @@ import { ApiError } from '../../error'; export const meta = { tags: ['pages'], - requireCredential: true as const, + requireCredential: true, kind: 'write:pages', @@ -65,8 +65,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Page', }, @@ -82,8 +82,9 @@ export const meta = { id: '4650348e-301c-499a-83c9-6aa988c66bc1', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { let eyeCatchingImage = null; if (ps.eyeCatchingImageId != null) { @@ -106,7 +107,7 @@ export default define(meta, async (ps, user) => { } }); - const page = await Pages.save(new Page({ + const page = await Pages.insert(new Page({ id: genId(), createdAt: new Date(), updatedAt: new Date(), @@ -122,7 +123,7 @@ export default define(meta, async (ps, user) => { alignCenter: ps.alignCenter, hideTitleWhenPinned: ps.hideTitleWhenPinned, font: ps.font, - })); + })).then(x => Pages.findOneOrFail(x.identifiers[0])); return await Pages.pack(page); }); diff --git a/packages/backend/src/server/api/endpoints/pages/delete.ts b/packages/backend/src/server/api/endpoints/pages/delete.ts index 7a45237697..aeda823e52 100644 --- a/packages/backend/src/server/api/endpoints/pages/delete.ts +++ b/packages/backend/src/server/api/endpoints/pages/delete.ts @@ -7,7 +7,7 @@ import { ID } from '@/misc/cafy-id'; export const meta = { tags: ['pages'], - requireCredential: true as const, + requireCredential: true, kind: 'write:pages', @@ -30,8 +30,9 @@ export const meta = { id: '8b741b3e-2c22-44b3-a15f-29949aa1601e', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const page = await Pages.findOne(ps.pageId); if (page == null) { diff --git a/packages/backend/src/server/api/endpoints/pages/featured.ts b/packages/backend/src/server/api/endpoints/pages/featured.ts index 1dcfb8dd83..7f0d58b350 100644 --- a/packages/backend/src/server/api/endpoints/pages/featured.ts +++ b/packages/backend/src/server/api/endpoints/pages/featured.ts @@ -4,19 +4,20 @@ import { Pages } from '@/models/index'; export const meta = { tags: ['pages'], - requireCredential: false as const, + requireCredential: false, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Page', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = Pages.createQueryBuilder('page') .where('page.visibility = \'public\'') diff --git a/packages/backend/src/server/api/endpoints/pages/like.ts b/packages/backend/src/server/api/endpoints/pages/like.ts index f48359ab2d..c479f637a9 100644 --- a/packages/backend/src/server/api/endpoints/pages/like.ts +++ b/packages/backend/src/server/api/endpoints/pages/like.ts @@ -8,7 +8,7 @@ import { genId } from '@/misc/gen-id'; export const meta = { tags: ['pages'], - requireCredential: true as const, + requireCredential: true, kind: 'write:page-likes', @@ -37,8 +37,9 @@ export const meta = { id: 'cc98a8a2-0dc3-4123-b198-62c71df18ed3', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const page = await Pages.findOne(ps.pageId); if (page == null) { diff --git a/packages/backend/src/server/api/endpoints/pages/show.ts b/packages/backend/src/server/api/endpoints/pages/show.ts index d94c7457da..5cda5386d5 100644 --- a/packages/backend/src/server/api/endpoints/pages/show.ts +++ b/packages/backend/src/server/api/endpoints/pages/show.ts @@ -8,7 +8,7 @@ import { Page } from '@/models/entities/page'; export const meta = { tags: ['pages'], - requireCredential: false as const, + requireCredential: false, params: { pageId: { @@ -25,8 +25,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Page', }, @@ -37,8 +37,9 @@ export const meta = { id: '222120c0-3ead-4528-811b-b96f233388d7', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { let page: Page | undefined; diff --git a/packages/backend/src/server/api/endpoints/pages/unlike.ts b/packages/backend/src/server/api/endpoints/pages/unlike.ts index 5a2b68e425..cca5e5b5a9 100644 --- a/packages/backend/src/server/api/endpoints/pages/unlike.ts +++ b/packages/backend/src/server/api/endpoints/pages/unlike.ts @@ -7,7 +7,7 @@ import { Pages, PageLikes } from '@/models/index'; export const meta = { tags: ['pages'], - requireCredential: true as const, + requireCredential: true, kind: 'write:page-likes', @@ -30,8 +30,9 @@ export const meta = { id: 'f5e586b0-ce93-4050-b0e3-7f31af5259ee', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const page = await Pages.findOne(ps.pageId); if (page == null) { diff --git a/packages/backend/src/server/api/endpoints/pages/update.ts b/packages/backend/src/server/api/endpoints/pages/update.ts index f980d9207b..991085ee09 100644 --- a/packages/backend/src/server/api/endpoints/pages/update.ts +++ b/packages/backend/src/server/api/endpoints/pages/update.ts @@ -9,7 +9,7 @@ import { Not } from 'typeorm'; export const meta = { tags: ['pages'], - requireCredential: true as const, + requireCredential: true, kind: 'write:pages', @@ -88,8 +88,9 @@ export const meta = { id: '2298a392-d4a1-44c5-9ebb-ac1aeaa5a9ab', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const page = await Pages.findOne(ps.pageId); if (page == null) { diff --git a/packages/backend/src/server/api/endpoints/ping.ts b/packages/backend/src/server/api/endpoints/ping.ts index c8f67981f5..3eab70ae2e 100644 --- a/packages/backend/src/server/api/endpoints/ping.ts +++ b/packages/backend/src/server/api/endpoints/ping.ts @@ -1,7 +1,7 @@ import define from '../define'; export const meta = { - requireCredential: false as const, + requireCredential: false, tags: ['meta'], @@ -9,17 +9,18 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { pong: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async () => { return { pong: Date.now(), diff --git a/packages/backend/src/server/api/endpoints/pinned-users.ts b/packages/backend/src/server/api/endpoints/pinned-users.ts index 9d8d3963b4..ff0e22555f 100644 --- a/packages/backend/src/server/api/endpoints/pinned-users.ts +++ b/packages/backend/src/server/api/endpoints/pinned-users.ts @@ -7,22 +7,23 @@ import { User } from '@/models/entities/user'; export const meta = { tags: ['users'], - requireCredential: false as const, + requireCredential: false, params: { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailed', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const meta = await fetchMeta(); diff --git a/packages/backend/src/server/api/endpoints/promo/read.ts b/packages/backend/src/server/api/endpoints/promo/read.ts index ac3cd9cd0b..8d8c60d755 100644 --- a/packages/backend/src/server/api/endpoints/promo/read.ts +++ b/packages/backend/src/server/api/endpoints/promo/read.ts @@ -9,7 +9,7 @@ import { genId } from '@/misc/gen-id'; export const meta = { tags: ['notes'], - requireCredential: true as const, + requireCredential: true, params: { noteId: { @@ -24,8 +24,9 @@ export const meta = { id: 'd785b897-fcd3-4fe9-8fc3-b85c26e6c932', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const note = await getNote(ps.noteId).catch(e => { if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); diff --git a/packages/backend/src/server/api/endpoints/request-reset-password.ts b/packages/backend/src/server/api/endpoints/request-reset-password.ts index 6caf572222..af1aeb4311 100644 --- a/packages/backend/src/server/api/endpoints/request-reset-password.ts +++ b/packages/backend/src/server/api/endpoints/request-reset-password.ts @@ -11,7 +11,7 @@ import { genId } from '@/misc/gen-id'; import { IsNull } from 'typeorm'; export const meta = { - requireCredential: false as const, + requireCredential: false, limit: { duration: ms('1hour'), @@ -31,8 +31,9 @@ export const meta = { errors: { }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { const user = await Users.findOne({ usernameLower: ps.username.toLowerCase(), diff --git a/packages/backend/src/server/api/endpoints/reset-db.ts b/packages/backend/src/server/api/endpoints/reset-db.ts index f6fd5735d9..e99dc9db15 100644 --- a/packages/backend/src/server/api/endpoints/reset-db.ts +++ b/packages/backend/src/server/api/endpoints/reset-db.ts @@ -4,7 +4,7 @@ import { ApiError } from '../error'; import { resetDb } from '@/db/postgre'; export const meta = { - requireCredential: false as const, + requireCredential: false, params: { }, @@ -12,8 +12,9 @@ export const meta = { errors: { }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { if (process.env.NODE_ENV !== 'test') throw 'NODE_ENV is not a test'; diff --git a/packages/backend/src/server/api/endpoints/reset-password.ts b/packages/backend/src/server/api/endpoints/reset-password.ts index 706f0a32c0..a7366584b1 100644 --- a/packages/backend/src/server/api/endpoints/reset-password.ts +++ b/packages/backend/src/server/api/endpoints/reset-password.ts @@ -6,7 +6,7 @@ import { Users, UserProfiles, PasswordResetRequests } from '@/models/index'; import { ApiError } from '../error'; export const meta = { - requireCredential: false as const, + requireCredential: false, params: { token: { @@ -21,8 +21,9 @@ export const meta = { errors: { }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const req = await PasswordResetRequests.findOneOrFail({ token: ps.token, diff --git a/packages/backend/src/server/api/endpoints/room/show.ts b/packages/backend/src/server/api/endpoints/room/show.ts deleted file mode 100644 index ec53982ebb..0000000000 --- a/packages/backend/src/server/api/endpoints/room/show.ts +++ /dev/null @@ -1,159 +0,0 @@ -import $ from 'cafy'; -import define from '../../define'; -import { ApiError } from '../../error'; -import { Users, UserProfiles } from '@/models/index'; -import { ID } from '@/misc/cafy-id'; -import { toPunyNullable } from '@/misc/convert-host'; - -export const meta = { - tags: ['room'], - - requireCredential: false as const, - - params: { - userId: { - validator: $.optional.type(ID), - }, - - username: { - validator: $.optional.str, - }, - - host: { - validator: $.optional.nullable.str, - }, - }, - - errors: { - noSuchUser: { - message: 'No such user.', - code: 'NO_SUCH_USER', - id: '7ad3fa3e-5e12-42f0-b23a-f3d13f10ee4b', - }, - }, - - res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - roomType: { - type: 'string' as const, - optional: false as const, nullable: false as const, - enum: ['default', 'washitsu'], - }, - furnitures: { - type: 'array' as const, - optional: false as const, nullable: false as const, - items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - id: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - type: { - type: 'string' as const, - optional: false as const, nullable: false as const, - }, - props: { - type: 'object' as const, - optional: true as const, nullable: false as const, - }, - position: { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - x: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - y: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - z: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - }, - }, - rotation: { - type: 'object' as const, - optional: false as const, nullable: false as const, - properties: { - x: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - y: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - z: { - type: 'number' as const, - optional: false as const, nullable: false as const, - }, - }, - }, - }, - }, - }, - carpetColor: { - type: 'string' as const, - optional: false as const, nullable: false as const, - format: 'hex', - example: '#85CAF0', - }, - }, - }, -}; - -export default define(meta, async (ps, me) => { - const user = await Users.findOne(ps.userId != null - ? { id: ps.userId } - : { usernameLower: ps.username!.toLowerCase(), host: toPunyNullable(ps.host) }); - - if (user == null) { - throw new ApiError(meta.errors.noSuchUser); - } - - const profile = await UserProfiles.findOneOrFail(user.id); - - if (profile.room.furnitures == null) { - await UserProfiles.update(user.id, { - room: { - furnitures: [], - ...profile.room, - }, - }); - - profile.room.furnitures = []; - } - - if (profile.room.roomType == null) { - const initialType = 'default'; - await UserProfiles.update(user.id, { - room: { - roomType: initialType as any, - ...profile.room, - }, - }); - - profile.room.roomType = initialType; - } - - if (profile.room.carpetColor == null) { - const initialColor = '#85CAF0'; - await UserProfiles.update(user.id, { - room: { - carpetColor: initialColor as any, - ...profile.room, - }, - }); - - profile.room.carpetColor = initialColor; - } - - return profile.room; -}); diff --git a/packages/backend/src/server/api/endpoints/room/update.ts b/packages/backend/src/server/api/endpoints/room/update.ts deleted file mode 100644 index f9fc2b278e..0000000000 --- a/packages/backend/src/server/api/endpoints/room/update.ts +++ /dev/null @@ -1,51 +0,0 @@ -import $ from 'cafy'; -import { publishMainStream } from '@/services/stream'; -import define from '../../define'; -import { Users, UserProfiles } from '@/models/index'; - -export const meta = { - tags: ['room'], - - requireCredential: true as const, - - params: { - room: { - validator: $.obj({ - furnitures: $.arr($.obj({ - id: $.str, - type: $.str, - position: $.obj({ - x: $.num, - y: $.num, - z: $.num, - }), - rotation: $.obj({ - x: $.num, - y: $.num, - z: $.num, - }), - props: $.optional.nullable.obj(), - })), - roomType: $.str, - carpetColor: $.str, - }), - }, - }, -}; - -export default define(meta, async (ps, user) => { - await UserProfiles.update(user.id, { - room: ps.room as any, - }); - - const iObj = await Users.pack(user.id, user, { - detail: true, - includeSecrets: true, - }); - - // Publish meUpdated event - publishMainStream(user.id, 'meUpdated', iObj); - - // TODO: レスポンスがおかしいと思う by YuzuRyo61 - return iObj; -}); diff --git a/packages/backend/src/server/api/endpoints/server-info.ts b/packages/backend/src/server/api/endpoints/server-info.ts index be502cf24a..1ad2c54ab5 100644 --- a/packages/backend/src/server/api/endpoints/server-info.ts +++ b/packages/backend/src/server/api/endpoints/server-info.ts @@ -3,7 +3,7 @@ import * as si from 'systeminformation'; import define from '../define'; export const meta = { - requireCredential: false as const, + requireCredential: false, desc: { }, @@ -12,8 +12,9 @@ export const meta = { params: { }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async () => { const memStats = await si.mem(); const fsStats = await si.fsSize(); diff --git a/packages/backend/src/server/api/endpoints/stats.ts b/packages/backend/src/server/api/endpoints/stats.ts index f47b0d0a2b..9879ef2adf 100644 --- a/packages/backend/src/server/api/endpoints/stats.ts +++ b/packages/backend/src/server/api/endpoints/stats.ts @@ -3,7 +3,7 @@ import { NoteReactions, Notes, Users } from '@/models/index'; import { federationChart, driveChart } from '@/services/chart/index'; export const meta = { - requireCredential: false as const, + requireCredential: false, tags: ['meta'], @@ -11,41 +11,42 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { notesCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, originalNotesCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, usersCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, originalUsersCount: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, instances: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, driveUsageLocal: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, driveUsageRemote: { - type: 'number' as const, - optional: false as const, nullable: false as const, + type: 'number', + optional: false, nullable: false, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async () => { const [ notesCount, diff --git a/packages/backend/src/server/api/endpoints/sw/register.ts b/packages/backend/src/server/api/endpoints/sw/register.ts index 9734746770..ae3e9ce77a 100644 --- a/packages/backend/src/server/api/endpoints/sw/register.ts +++ b/packages/backend/src/server/api/endpoints/sw/register.ts @@ -7,7 +7,7 @@ import { SwSubscriptions } from '@/models/index'; export const meta = { tags: ['account'], - requireCredential: true as const, + requireCredential: true, params: { endpoint: { @@ -24,22 +24,23 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { state: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, enum: ['already-subscribed', 'subscribed'], }, key: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // if already subscribed const exist = await SwSubscriptions.findOne({ diff --git a/packages/backend/src/server/api/endpoints/sw/unregister.ts b/packages/backend/src/server/api/endpoints/sw/unregister.ts index 24ee861f16..6f569e9417 100644 --- a/packages/backend/src/server/api/endpoints/sw/unregister.ts +++ b/packages/backend/src/server/api/endpoints/sw/unregister.ts @@ -5,15 +5,16 @@ import { SwSubscriptions } from '../../../../models'; export const meta = { tags: ['account'], - requireCredential: true as const, + requireCredential: true, params: { endpoint: { validator: $.str, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { await SwSubscriptions.delete({ userId: user.id, diff --git a/packages/backend/src/server/api/endpoints/username/available.ts b/packages/backend/src/server/api/endpoints/username/available.ts index f1b46a2b65..74120fc406 100644 --- a/packages/backend/src/server/api/endpoints/username/available.ts +++ b/packages/backend/src/server/api/endpoints/username/available.ts @@ -5,7 +5,7 @@ import { Users, UsedUsernames } from '@/models/index'; export const meta = { tags: ['users'], - requireCredential: false as const, + requireCredential: false, params: { username: { @@ -14,17 +14,18 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { available: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps) => { // Get exist const exist = await Users.count({ diff --git a/packages/backend/src/server/api/endpoints/users.ts b/packages/backend/src/server/api/endpoints/users.ts index 601578de27..6b11ec0f01 100644 --- a/packages/backend/src/server/api/endpoints/users.ts +++ b/packages/backend/src/server/api/endpoints/users.ts @@ -7,7 +7,7 @@ import { generateBlockQueryForUsers } from '../common/generate-block-query'; export const meta = { tags: ['users'], - requireCredential: false as const, + requireCredential: false, params: { limit: { @@ -53,16 +53,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailed', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = Users.createQueryBuilder('user'); query.where('user.isExplorable = TRUE'); diff --git a/packages/backend/src/server/api/endpoints/users/clips.ts b/packages/backend/src/server/api/endpoints/users/clips.ts index f5964c54db..d4152fbf50 100644 --- a/packages/backend/src/server/api/endpoints/users/clips.ts +++ b/packages/backend/src/server/api/endpoints/users/clips.ts @@ -25,8 +25,9 @@ export const meta = { validator: $.optional.type(ID), }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = makePaginationQuery(Clips.createQueryBuilder('clip'), ps.sinceId, ps.untilId) .andWhere(`clip.userId = :userId`, { userId: ps.userId }) diff --git a/packages/backend/src/server/api/endpoints/users/followers.ts b/packages/backend/src/server/api/endpoints/users/followers.ts index 535b10412e..6214ab40ba 100644 --- a/packages/backend/src/server/api/endpoints/users/followers.ts +++ b/packages/backend/src/server/api/endpoints/users/followers.ts @@ -9,7 +9,7 @@ import { toPunyNullable } from '@/misc/convert-host'; export const meta = { tags: ['users'], - requireCredential: false as const, + requireCredential: false, params: { userId: { @@ -39,11 +39,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Following', }, }, @@ -61,8 +61,9 @@ export const meta = { id: '3c6a84db-d619-26af-ca14-06232a21df8a', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const user = await Users.findOne(ps.userId != null ? { id: ps.userId } diff --git a/packages/backend/src/server/api/endpoints/users/following.ts b/packages/backend/src/server/api/endpoints/users/following.ts index 58c72bb957..76112eab25 100644 --- a/packages/backend/src/server/api/endpoints/users/following.ts +++ b/packages/backend/src/server/api/endpoints/users/following.ts @@ -9,7 +9,7 @@ import { toPunyNullable } from '@/misc/convert-host'; export const meta = { tags: ['users'], - requireCredential: false as const, + requireCredential: false, params: { userId: { @@ -39,11 +39,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Following', }, }, @@ -61,8 +61,9 @@ export const meta = { id: 'f6cdb0df-c19f-ec5c-7dbb-0ba84a1f92ba', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const user = await Users.findOne(ps.userId != null ? { id: ps.userId } diff --git a/packages/backend/src/server/api/endpoints/users/gallery/posts.ts b/packages/backend/src/server/api/endpoints/users/gallery/posts.ts index 6ef884deda..c5f08b4c94 100644 --- a/packages/backend/src/server/api/endpoints/users/gallery/posts.ts +++ b/packages/backend/src/server/api/endpoints/users/gallery/posts.ts @@ -25,8 +25,9 @@ export const meta = { validator: $.optional.type(ID), }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = makePaginationQuery(GalleryPosts.createQueryBuilder('post'), ps.sinceId, ps.untilId) .andWhere(`post.userId = :userId`, { userId: ps.userId }); diff --git a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts index a88de7ac83..d886d3355a 100644 --- a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts +++ b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts @@ -10,7 +10,7 @@ import { Notes, Users } from '@/models/index'; export const meta = { tags: ['users'], - requireCredential: false as const, + requireCredential: false, params: { userId: { @@ -24,12 +24,22 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + properties: { + user: { + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailed', + }, + weight: { + type: 'number', + optional: false, nullable: false, + }, + }, }, }, @@ -40,8 +50,9 @@ export const meta = { id: 'e6965129-7b2a-40a4-bae2-cd84cd434822', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { // Lookup user const user = await getUser(ps.userId).catch(e => { diff --git a/packages/backend/src/server/api/endpoints/users/groups/create.ts b/packages/backend/src/server/api/endpoints/users/groups/create.ts index 12ee11ba55..25e29de01c 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/create.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/create.ts @@ -8,7 +8,7 @@ import { UserGroupJoining } from '@/models/entities/user-group-joining'; export const meta = { tags: ['groups'], - requireCredential: true as const, + requireCredential: true, kind: 'write:user-groups', @@ -19,12 +19,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'UserGroup', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const userGroup = await UserGroups.insert({ id: genId(), diff --git a/packages/backend/src/server/api/endpoints/users/groups/delete.ts b/packages/backend/src/server/api/endpoints/users/groups/delete.ts index dbc77dd8fe..f30ab78ca0 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/delete.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/delete.ts @@ -7,7 +7,7 @@ import { UserGroups } from '@/models/index'; export const meta = { tags: ['groups'], - requireCredential: true as const, + requireCredential: true, kind: 'write:user-groups', @@ -24,8 +24,9 @@ export const meta = { id: '63dbd64c-cd77-413f-8e08-61781e210b38', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const userGroup = await UserGroups.findOne({ id: ps.groupId, diff --git a/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts b/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts index fef94c306f..7061db538b 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts @@ -9,7 +9,7 @@ import { UserGroupJoining } from '@/models/entities/user-group-joining'; export const meta = { tags: ['groups', 'users'], - requireCredential: true as const, + requireCredential: true, kind: 'write:user-groups', @@ -26,8 +26,9 @@ export const meta = { id: '98c11eca-c890-4f42-9806-c8c8303ebb5e', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Fetch the invitation const invitation = await UserGroupInvitations.findOne({ diff --git a/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts b/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts index 33a202f029..f5ca3dec8b 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts @@ -7,7 +7,7 @@ import { UserGroupInvitations } from '@/models/index'; export const meta = { tags: ['groups', 'users'], - requireCredential: true as const, + requireCredential: true, kind: 'write:user-groups', @@ -24,8 +24,9 @@ export const meta = { id: 'ad7471d4-2cd9-44b4-ac68-e7136b4ce656', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Fetch the invitation const invitation = await UserGroupInvitations.findOne({ diff --git a/packages/backend/src/server/api/endpoints/users/groups/invite.ts b/packages/backend/src/server/api/endpoints/users/groups/invite.ts index 4dee18fcb0..3b7a4edb81 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invite.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invite.ts @@ -11,7 +11,7 @@ import { createNotification } from '@/services/create-notification'; export const meta = { tags: ['groups', 'users'], - requireCredential: true as const, + requireCredential: true, kind: 'write:user-groups', @@ -50,8 +50,9 @@ export const meta = { id: 'ee0f58b4-b529-4d13-b761-b9a3e69f97e6', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { // Fetch the group const userGroup = await UserGroups.findOne({ diff --git a/packages/backend/src/server/api/endpoints/users/groups/joined.ts b/packages/backend/src/server/api/endpoints/users/groups/joined.ts index 1bd065ca00..ab48b1910d 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/joined.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/joined.ts @@ -5,21 +5,22 @@ import { Not, In } from 'typeorm'; export const meta = { tags: ['groups', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:user-groups', res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'UserGroup', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const ownedGroups = await UserGroups.find({ userId: me.id, diff --git a/packages/backend/src/server/api/endpoints/users/groups/leave.ts b/packages/backend/src/server/api/endpoints/users/groups/leave.ts index 9a41175d63..d2fcdab301 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/leave.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/leave.ts @@ -7,7 +7,7 @@ import { UserGroups, UserGroupJoinings } from '@/models/index'; export const meta = { tags: ['groups', 'users'], - requireCredential: true as const, + requireCredential: true, kind: 'write:user-groups', @@ -30,8 +30,9 @@ export const meta = { id: 'b6d6e0c2-ef8a-9bb8-653d-79f4a3107c69', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { // Fetch the group const userGroup = await UserGroups.findOne({ diff --git a/packages/backend/src/server/api/endpoints/users/groups/owned.ts b/packages/backend/src/server/api/endpoints/users/groups/owned.ts index 69e4c85717..6193a71019 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/owned.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/owned.ts @@ -4,21 +4,22 @@ import { UserGroups } from '@/models/index'; export const meta = { tags: ['groups', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:user-groups', res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'UserGroup', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const userGroups = await UserGroups.find({ userId: me.id, diff --git a/packages/backend/src/server/api/endpoints/users/groups/pull.ts b/packages/backend/src/server/api/endpoints/users/groups/pull.ts index 70c1457dcd..785bea140d 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/pull.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/pull.ts @@ -8,7 +8,7 @@ import { UserGroups, UserGroupJoinings } from '@/models/index'; export const meta = { tags: ['groups', 'users'], - requireCredential: true as const, + requireCredential: true, kind: 'write:user-groups', @@ -41,8 +41,9 @@ export const meta = { id: '1546eed5-4414-4dea-81c1-b0aec4f6d2af', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { // Fetch the group const userGroup = await UserGroups.findOne({ diff --git a/packages/backend/src/server/api/endpoints/users/groups/show.ts b/packages/backend/src/server/api/endpoints/users/groups/show.ts index 0bb06f8df4..eb26eac2a8 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/show.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/show.ts @@ -7,7 +7,7 @@ import { UserGroups, UserGroupJoinings } from '@/models/index'; export const meta = { tags: ['groups', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:user-groups', @@ -18,8 +18,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'UserGroup', }, @@ -30,8 +30,9 @@ export const meta = { id: 'ea04751e-9b7e-487b-a509-330fb6bd6b9b', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { // Fetch the group const userGroup = await UserGroups.findOne({ diff --git a/packages/backend/src/server/api/endpoints/users/groups/transfer.ts b/packages/backend/src/server/api/endpoints/users/groups/transfer.ts index 54cf8197e7..4b1c8fbbdb 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/transfer.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/transfer.ts @@ -8,7 +8,7 @@ import { UserGroups, UserGroupJoinings } from '@/models/index'; export const meta = { tags: ['groups', 'users'], - requireCredential: true as const, + requireCredential: true, kind: 'write:user-groups', @@ -23,8 +23,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'UserGroup', }, @@ -47,8 +47,9 @@ export const meta = { id: 'd31bebee-196d-42c2-9a3e-9474d4be6cc4', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { // Fetch the group const userGroup = await UserGroups.findOne({ diff --git a/packages/backend/src/server/api/endpoints/users/groups/update.ts b/packages/backend/src/server/api/endpoints/users/groups/update.ts index d16f1ac42b..6caf903555 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/update.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/update.ts @@ -7,7 +7,7 @@ import { UserGroups } from '@/models/index'; export const meta = { tags: ['groups'], - requireCredential: true as const, + requireCredential: true, kind: 'write:user-groups', @@ -22,8 +22,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'UserGroup', }, @@ -34,8 +34,9 @@ export const meta = { id: '9081cda3-7a9e-4fac-a6ce-908d70f282f6', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { // Fetch the group const userGroup = await UserGroups.findOne({ diff --git a/packages/backend/src/server/api/endpoints/users/lists/create.ts b/packages/backend/src/server/api/endpoints/users/lists/create.ts index 8372139f84..945b511628 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/create.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/create.ts @@ -7,7 +7,7 @@ import { UserList } from '@/models/entities/user-list'; export const meta = { tags: ['lists'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -18,12 +18,13 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'UserList', }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const userList = await UserLists.insert({ id: genId(), diff --git a/packages/backend/src/server/api/endpoints/users/lists/delete.ts b/packages/backend/src/server/api/endpoints/users/lists/delete.ts index fac4e90dbf..3183d2a09c 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/delete.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/delete.ts @@ -7,7 +7,7 @@ import { UserLists } from '@/models/index'; export const meta = { tags: ['lists'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -24,8 +24,9 @@ export const meta = { id: '78436795-db79-42f5-b1e2-55ea2cf19166', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const userList = await UserLists.findOne({ id: ps.listId, diff --git a/packages/backend/src/server/api/endpoints/users/lists/list.ts b/packages/backend/src/server/api/endpoints/users/lists/list.ts index 222c930d0e..ae66b0aacc 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/list.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/list.ts @@ -4,21 +4,22 @@ import { UserLists } from '@/models/index'; export const meta = { tags: ['lists', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:account', res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'UserList', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const userLists = await UserLists.find({ userId: me.id, diff --git a/packages/backend/src/server/api/endpoints/users/lists/pull.ts b/packages/backend/src/server/api/endpoints/users/lists/pull.ts index 86daa9b2e1..4c74aefa8a 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/pull.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/pull.ts @@ -9,7 +9,7 @@ import { UserLists, UserListJoinings, Users } from '@/models/index'; export const meta = { tags: ['lists', 'users'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -36,8 +36,9 @@ export const meta = { id: '588e7f72-c744-4a61-b180-d354e912bda2', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { // Fetch the list const userList = await UserLists.findOne({ diff --git a/packages/backend/src/server/api/endpoints/users/lists/push.ts b/packages/backend/src/server/api/endpoints/users/lists/push.ts index 77ecb4a223..8b50c475b0 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/push.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/push.ts @@ -9,7 +9,7 @@ import { UserLists, UserListJoinings, Blockings } from '@/models/index'; export const meta = { tags: ['lists', 'users'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -48,8 +48,9 @@ export const meta = { id: '990232c5-3f9d-4d83-9f3f-ef27b6332a4b', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { // Fetch the list const userList = await UserLists.findOne({ diff --git a/packages/backend/src/server/api/endpoints/users/lists/show.ts b/packages/backend/src/server/api/endpoints/users/lists/show.ts index 9c985bb091..06555c1a88 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/show.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/show.ts @@ -7,7 +7,7 @@ import { UserLists } from '@/models/index'; export const meta = { tags: ['lists', 'account'], - requireCredential: true as const, + requireCredential: true, kind: 'read:account', @@ -18,8 +18,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'UserList', }, @@ -30,8 +30,9 @@ export const meta = { id: '7bc05c21-1d7a-41ae-88f1-66820f4dc686', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { // Fetch the list const userList = await UserLists.findOne({ diff --git a/packages/backend/src/server/api/endpoints/users/lists/update.ts b/packages/backend/src/server/api/endpoints/users/lists/update.ts index 8a0f96a5d9..02b0d5fe18 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/update.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/update.ts @@ -7,7 +7,7 @@ import { UserLists } from '@/models/index'; export const meta = { tags: ['lists'], - requireCredential: true as const, + requireCredential: true, kind: 'write:account', @@ -22,8 +22,8 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'UserList', }, @@ -34,8 +34,9 @@ export const meta = { id: '796666fe-3dff-4d39-becb-8a5932c1d5b7', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { // Fetch the list const userList = await UserLists.findOne({ diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts index da8e858119..99158fb0ae 100644 --- a/packages/backend/src/server/api/endpoints/users/notes.ts +++ b/packages/backend/src/server/api/endpoints/users/notes.ts @@ -66,11 +66,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'Note', }, }, @@ -82,8 +82,9 @@ export const meta = { id: '27e494ba-2ac2-48e8-893b-10d4d8c2387b', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { // Lookup user const user = await getUser(ps.userId).catch(e => { diff --git a/packages/backend/src/server/api/endpoints/users/pages.ts b/packages/backend/src/server/api/endpoints/users/pages.ts index 4763303a70..6e003dd1af 100644 --- a/packages/backend/src/server/api/endpoints/users/pages.ts +++ b/packages/backend/src/server/api/endpoints/users/pages.ts @@ -25,8 +25,9 @@ export const meta = { validator: $.optional.type(ID), }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, user) => { const query = makePaginationQuery(Pages.createQueryBuilder('page'), ps.sinceId, ps.untilId) .andWhere(`page.userId = :userId`, { userId: ps.userId }) diff --git a/packages/backend/src/server/api/endpoints/users/reactions.ts b/packages/backend/src/server/api/endpoints/users/reactions.ts index 626487176b..312d4dbf23 100644 --- a/packages/backend/src/server/api/endpoints/users/reactions.ts +++ b/packages/backend/src/server/api/endpoints/users/reactions.ts @@ -9,7 +9,7 @@ import { ApiError } from '../../error'; export const meta = { tags: ['users', 'reactions'], - requireCredential: false as const, + requireCredential: false, params: { userId: { @@ -39,11 +39,11 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'NoteReaction', }, }, @@ -55,8 +55,9 @@ export const meta = { id: '673a7dd2-6924-1093-e0c0-e68456ceae5c', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const profile = await UserProfiles.findOneOrFail(ps.userId); diff --git a/packages/backend/src/server/api/endpoints/users/recommendation.ts b/packages/backend/src/server/api/endpoints/users/recommendation.ts index 71c564c980..9ea39eb2dd 100644 --- a/packages/backend/src/server/api/endpoints/users/recommendation.ts +++ b/packages/backend/src/server/api/endpoints/users/recommendation.ts @@ -8,7 +8,7 @@ import { generateBlockedUserQuery, generateBlockQueryForUsers } from '../../comm export const meta = { tags: ['users'], - requireCredential: true as const, + requireCredential: true, kind: 'read:account', @@ -25,16 +25,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + type: 'object', + optional: false, nullable: false, + ref: 'UserDetailed', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const query = Users.createQueryBuilder('user') .where('user.isLocked = FALSE') diff --git a/packages/backend/src/server/api/endpoints/users/relation.ts b/packages/backend/src/server/api/endpoints/users/relation.ts index af1984fa2b..7e319ca105 100644 --- a/packages/backend/src/server/api/endpoints/users/relation.ts +++ b/packages/backend/src/server/api/endpoints/users/relation.ts @@ -6,7 +6,7 @@ import { Users } from '@/models/index'; export const meta = { tags: ['users'], - requireCredential: true as const, + requireCredential: true, params: { userId: { @@ -15,93 +15,93 @@ export const meta = { }, res: { + optional: false, nullable: false, oneOf: [ { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, isFollowing: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, hasPendingFollowRequestFromYou: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, hasPendingFollowRequestToYou: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, isFollowed: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, isBlocking: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, isBlocked: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, isMuted: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, }, }, { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, properties: { id: { - type: 'string' as const, - optional: false as const, nullable: false as const, + type: 'string', + optional: false, nullable: false, format: 'id', }, isFollowing: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, hasPendingFollowRequestFromYou: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, hasPendingFollowRequestToYou: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, isFollowed: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, isBlocking: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, isBlocked: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, isMuted: { - type: 'boolean' as const, - optional: false as const, nullable: false as const, + type: 'boolean', + optional: false, nullable: false, }, }, }, }, ], }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const ids = Array.isArray(ps.userId) ? ps.userId : [ps.userId]; diff --git a/packages/backend/src/server/api/endpoints/users/report-abuse.ts b/packages/backend/src/server/api/endpoints/users/report-abuse.ts index a1d8376651..ed2aa7bb26 100644 --- a/packages/backend/src/server/api/endpoints/users/report-abuse.ts +++ b/packages/backend/src/server/api/endpoints/users/report-abuse.ts @@ -13,7 +13,7 @@ import { fetchMeta } from '@/misc/fetch-meta'; export const meta = { tags: ['users'], - requireCredential: true as const, + requireCredential: true, params: { userId: { @@ -44,7 +44,7 @@ export const meta = { id: '35e166f5-05fb-4f87-a2d5-adb42676d48f', }, }, -}; +} as const; // eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { @@ -62,7 +62,7 @@ export default define(meta, async (ps, me) => { throw new ApiError(meta.errors.cannotReportAdmin); } - const report = await AbuseUserReports.save({ + const report = await AbuseUserReports.insert({ id: genId(), createdAt: new Date(), targetUserId: user.id, @@ -70,7 +70,7 @@ export default define(meta, async (ps, me) => { reporterId: me.id, reporterHost: null, comment: ps.comment, - }); + }).then(x => AbuseUserReports.findOneOrFail(x.identifiers[0])); // Publish event to moderators setTimeout(async () => { diff --git a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts index 58a8d929f7..d67625e624 100644 --- a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts +++ b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts @@ -8,7 +8,7 @@ import { User } from '@/models/entities/user'; export const meta = { tags: ['users'], - requireCredential: false as const, + requireCredential: false, params: { username: { @@ -31,16 +31,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'User', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const activeThreshold = new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)); // 30日 @@ -111,6 +112,6 @@ export default define(meta, async (ps, me) => { .getMany(); } - return await Users.packMany(users, me, { detail: ps.detail }); + return await Users.packMany(users, me, { detail: !!ps.detail }); } }); diff --git a/packages/backend/src/server/api/endpoints/users/search.ts b/packages/backend/src/server/api/endpoints/users/search.ts index f87088688c..26f818afcc 100644 --- a/packages/backend/src/server/api/endpoints/users/search.ts +++ b/packages/backend/src/server/api/endpoints/users/search.ts @@ -7,7 +7,7 @@ import { Brackets } from 'typeorm'; export const meta = { tags: ['users'], - requireCredential: false as const, + requireCredential: false, params: { query: { @@ -36,16 +36,17 @@ export const meta = { }, res: { - type: 'array' as const, - optional: false as const, nullable: false as const, + type: 'array', + optional: false, nullable: false, items: { - type: 'object' as const, - optional: false as const, nullable: false as const, + type: 'object', + optional: false, nullable: false, ref: 'User', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const activeThreshold = new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)); // 30日 diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts index eacb2aee16..92910e9ed8 100644 --- a/packages/backend/src/server/api/endpoints/users/show.ts +++ b/packages/backend/src/server/api/endpoints/users/show.ts @@ -11,7 +11,7 @@ import { User } from '@/models/entities/user'; export const meta = { tags: ['users'], - requireCredential: false as const, + requireCredential: false, params: { userId: { @@ -32,9 +32,20 @@ export const meta = { }, res: { - type: 'object' as const, - optional: false as const, nullable: false as const, - ref: 'User', + optional: false, nullable: false, + oneOf: [ + { + type: 'object', + ref: 'UserDetailed', + }, + { + type: 'array', + items: { + type: 'object', + ref: 'UserDetailed', + } + }, + ] }, errors: { @@ -42,7 +53,7 @@ export const meta = { message: 'Failed to resolve remote user.', code: 'FAILED_TO_RESOLVE_REMOTE_USER', id: 'ef7b9be4-9cba-4e6f-ab41-90ed171c7d3c', - kind: 'server' as const, + kind: 'server', }, noSuchUser: { @@ -51,8 +62,9 @@ export const meta = { id: '4362f8dc-731f-4ad8-a694-be5a88922a24', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { let user; diff --git a/packages/backend/src/server/api/endpoints/users/stats.ts b/packages/backend/src/server/api/endpoints/users/stats.ts index b8564218ac..381e433479 100644 --- a/packages/backend/src/server/api/endpoints/users/stats.ts +++ b/packages/backend/src/server/api/endpoints/users/stats.ts @@ -2,12 +2,12 @@ import $ from 'cafy'; import define from '../../define'; import { ApiError } from '../../error'; import { ID } from '@/misc/cafy-id'; -import { DriveFiles, Followings, NoteFavorites, NoteReactions, Notes, PageLikes, PollVotes, ReversiGames, Users } from '@/models/index'; +import { DriveFiles, Followings, NoteFavorites, NoteReactions, Notes, PageLikes, PollVotes, Users } from '@/models/index'; export const meta = { tags: ['users'], - requireCredential: false as const, + requireCredential: false, params: { userId: { @@ -22,8 +22,9 @@ export const meta = { id: '9e638e45-3b25-4ef7-8f95-07e8498f1819', }, }, -}; +} as const; +// eslint-disable-next-line import/no-default-export export default define(meta, async (ps, me) => { const user = await Users.findOne(ps.userId); if (user == null) { @@ -49,7 +50,6 @@ export default define(meta, async (ps, me) => { pageLikedCount, driveFilesCount, driveUsage, - reversiCount, ] = await Promise.all([ Notes.createQueryBuilder('note') .where('note.userId = :userId', { userId: user.id }) @@ -112,10 +112,6 @@ export default define(meta, async (ps, me) => { .where('file.userId = :userId', { userId: user.id }) .getCount(), DriveFiles.calcDriveUsageOf(user), - ReversiGames.createQueryBuilder('game') - .where('game.user1Id = :userId', { userId: user.id }) - .orWhere('game.user2Id = :userId', { userId: user.id }) - .getCount(), ]); return { @@ -139,6 +135,5 @@ export default define(meta, async (ps, me) => { pageLikedCount, driveFilesCount, driveUsage, - reversiCount, }; }); diff --git a/packages/backend/src/server/api/limiter.ts b/packages/backend/src/server/api/limiter.ts index 5f617771e0..4721f6263a 100644 --- a/packages/backend/src/server/api/limiter.ts +++ b/packages/backend/src/server/api/limiter.ts @@ -7,8 +7,8 @@ import Logger from '@/services/logger'; const logger = new Logger('limiter'); -export default (endpoint: IEndpoint, user: User) => new Promise<void>((ok, reject) => { - const limitation = endpoint.meta.limit!; +export const limiter = (endpoint: IEndpoint & { meta: { limit: NonNullable<IEndpoint['meta']['limit']> } }, user: User) => new Promise<void>((ok, reject) => { + const limitation = endpoint.meta.limit; const key = Object.prototype.hasOwnProperty.call(limitation, 'key') ? limitation.key @@ -30,7 +30,7 @@ export default (endpoint: IEndpoint, user: User) => new Promise<void>((ok, rejec } // Short-term limit - function min() { + function min(): void { const minIntervalLimiter = new Limiter({ id: `${user.id}:${key}:min`, duration: limitation.minInterval, @@ -58,7 +58,7 @@ export default (endpoint: IEndpoint, user: User) => new Promise<void>((ok, rejec } // Long term limit - function max() { + function max(): void { const limiter = new Limiter({ id: `${user.id}:${key}`, duration: limitation.duration, diff --git a/packages/backend/src/server/api/openapi/gen-spec.ts b/packages/backend/src/server/api/openapi/gen-spec.ts index 1c521f212f..1efef8d26d 100644 --- a/packages/backend/src/server/api/openapi/gen-spec.ts +++ b/packages/backend/src/server/api/openapi/gen-spec.ts @@ -118,7 +118,7 @@ export function genOpenapiSpec(lang = 'ja-JP') { description: desc, externalDocs: { description: 'Source code', - url: `https://github.com/misskey-dev/misskey/blob/develop/src/server/api/endpoints/${endpoint.name}.ts`, + url: `https://github.com/misskey-dev/misskey/blob/develop/packages/backend/src/server/api/endpoints/${endpoint.name}.ts`, }, ...(endpoint.meta.tags ? { tags: [endpoint.meta.tags[0]], diff --git a/packages/backend/src/server/api/openapi/schemas.ts b/packages/backend/src/server/api/openapi/schemas.ts index 723b3e884a..eb42667fd5 100644 --- a/packages/backend/src/server/api/openapi/schemas.ts +++ b/packages/backend/src/server/api/openapi/schemas.ts @@ -1,6 +1,6 @@ -import { refs, Schema } from '@/misc/schema'; +import { refs, MinimumSchema } from '@/misc/schema'; -export function convertSchemaToOpenApiSchema(schema: Schema) { +export function convertSchemaToOpenApiSchema(schema: MinimumSchema) { const res: any = schema; if (schema.type === 'object' && schema.properties) { @@ -15,6 +15,10 @@ export function convertSchemaToOpenApiSchema(schema: Schema) { res.items = convertSchemaToOpenApiSchema(schema.items); } + if (schema.anyOf) res.anyOf = schema.anyOf.map(convertSchemaToOpenApiSchema); + if (schema.oneOf) res.oneOf = schema.oneOf.map(convertSchemaToOpenApiSchema); + if (schema.allOf) res.allOf = schema.allOf.map(convertSchemaToOpenApiSchema); + if (schema.ref) { res.$ref = `#/components/schemas/${schema.ref}`; } diff --git a/packages/backend/src/server/api/stream/channels/games/reversi-game.ts b/packages/backend/src/server/api/stream/channels/games/reversi-game.ts deleted file mode 100644 index 314db48b5e..0000000000 --- a/packages/backend/src/server/api/stream/channels/games/reversi-game.ts +++ /dev/null @@ -1,372 +0,0 @@ -import autobind from 'autobind-decorator'; -import * as CRC32 from 'crc-32'; -import { publishReversiGameStream } from '@/services/stream'; -import Reversi from '../../../../../games/reversi/core'; -import * as maps from '../../../../../games/reversi/maps'; -import Channel from '../../channel'; -import { ReversiGame } from '@/models/entities/games/reversi/game'; -import { ReversiGames, Users } from '@/models/index'; -import { User } from '@/models/entities/user'; - -export default class extends Channel { - public readonly chName = 'gamesReversiGame'; - public static shouldShare = false; - public static requireCredential = false; - - private gameId: ReversiGame['id'] | null = null; - private watchers: Record<User['id'], Date> = {}; - private emitWatchersIntervalId: ReturnType<typeof setInterval>; - - @autobind - public async init(params: any) { - this.gameId = params.gameId; - - // Subscribe game stream - this.subscriber.on(`reversiGameStream:${this.gameId}`, this.onEvent); - this.emitWatchersIntervalId = setInterval(this.emitWatchers, 5000); - - const game = await ReversiGames.findOne(this.gameId!); - if (game == null) throw new Error('game not found'); - - // 観戦者イベント - this.watch(game); - } - - @autobind - private onEvent(data: any) { - if (data.type === 'watching') { - const id = data.body; - this.watchers[id] = new Date(); - } else { - this.send(data); - } - } - - @autobind - private async emitWatchers() { - const now = new Date(); - - // Remove not watching users - for (const [userId, date] of Object.entries(this.watchers)) { - if (now.getTime() - date.getTime() > 5000) delete this.watchers[userId]; - } - - const users = await Users.packMany(Object.keys(this.watchers), null, { detail: false }); - - this.send({ - type: 'watchers', - body: users, - }); - } - - @autobind - public dispose() { - // Unsubscribe events - this.subscriber.off(`reversiGameStream:${this.gameId}`, this.onEvent); - clearInterval(this.emitWatchersIntervalId); - } - - @autobind - public onMessage(type: string, body: any) { - switch (type) { - case 'accept': this.accept(true); break; - case 'cancelAccept': this.accept(false); break; - case 'updateSettings': this.updateSettings(body.key, body.value); break; - case 'initForm': this.initForm(body); break; - case 'updateForm': this.updateForm(body.id, body.value); break; - case 'message': this.message(body); break; - case 'set': this.set(body.pos); break; - case 'check': this.check(body.crc32); break; - } - } - - @autobind - private async updateSettings(key: string, value: any) { - if (this.user == null) return; - - const game = await ReversiGames.findOne(this.gameId!); - if (game == null) throw new Error('game not found'); - - if (game.isStarted) return; - if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return; - if ((game.user1Id === this.user.id) && game.user1Accepted) return; - if ((game.user2Id === this.user.id) && game.user2Accepted) return; - - if (!['map', 'bw', 'isLlotheo', 'canPutEverywhere', 'loopedBoard'].includes(key)) return; - - await ReversiGames.update(this.gameId!, { - [key]: value, - }); - - publishReversiGameStream(this.gameId!, 'updateSettings', { - key: key, - value: value, - }); - } - - @autobind - private async initForm(form: any) { - if (this.user == null) return; - - const game = await ReversiGames.findOne(this.gameId!); - if (game == null) throw new Error('game not found'); - - if (game.isStarted) return; - if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return; - - const set = game.user1Id === this.user.id ? { - form1: form, - } : { - form2: form, - }; - - await ReversiGames.update(this.gameId!, set); - - publishReversiGameStream(this.gameId!, 'initForm', { - userId: this.user.id, - form, - }); - } - - @autobind - private async updateForm(id: string, value: any) { - if (this.user == null) return; - - const game = await ReversiGames.findOne(this.gameId!); - if (game == null) throw new Error('game not found'); - - if (game.isStarted) return; - if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return; - - const form = game.user1Id === this.user.id ? game.form2 : game.form1; - - const item = form.find((i: any) => i.id == id); - - if (item == null) return; - - item.value = value; - - const set = game.user1Id === this.user.id ? { - form2: form, - } : { - form1: form, - }; - - await ReversiGames.update(this.gameId!, set); - - publishReversiGameStream(this.gameId!, 'updateForm', { - userId: this.user.id, - id, - value, - }); - } - - @autobind - private async message(message: any) { - if (this.user == null) return; - - message.id = Math.random(); - publishReversiGameStream(this.gameId!, 'message', { - userId: this.user.id, - message, - }); - } - - @autobind - private async accept(accept: boolean) { - if (this.user == null) return; - - const game = await ReversiGames.findOne(this.gameId!); - if (game == null) throw new Error('game not found'); - - if (game.isStarted) return; - - let bothAccepted = false; - - if (game.user1Id === this.user.id) { - await ReversiGames.update(this.gameId!, { - user1Accepted: accept, - }); - - publishReversiGameStream(this.gameId!, 'changeAccepts', { - user1: accept, - user2: game.user2Accepted, - }); - - if (accept && game.user2Accepted) bothAccepted = true; - } else if (game.user2Id === this.user.id) { - await ReversiGames.update(this.gameId!, { - user2Accepted: accept, - }); - - publishReversiGameStream(this.gameId!, 'changeAccepts', { - user1: game.user1Accepted, - user2: accept, - }); - - if (accept && game.user1Accepted) bothAccepted = true; - } else { - return; - } - - if (bothAccepted) { - // 3秒後、まだacceptされていたらゲーム開始 - setTimeout(async () => { - const freshGame = await ReversiGames.findOne(this.gameId!); - if (freshGame == null || freshGame.isStarted || freshGame.isEnded) return; - if (!freshGame.user1Accepted || !freshGame.user2Accepted) return; - - let bw: number; - if (freshGame.bw == 'random') { - bw = Math.random() > 0.5 ? 1 : 2; - } else { - bw = parseInt(freshGame.bw, 10); - } - - function getRandomMap() { - const mapCount = Object.entries(maps).length; - const rnd = Math.floor(Math.random() * mapCount); - return Object.values(maps)[rnd].data; - } - - const map = freshGame.map != null ? freshGame.map : getRandomMap(); - - await ReversiGames.update(this.gameId!, { - startedAt: new Date(), - isStarted: true, - black: bw, - map: map, - }); - - //#region 盤面に最初から石がないなどして始まった瞬間に勝敗が決定する場合があるのでその処理 - const o = new Reversi(map, { - isLlotheo: freshGame.isLlotheo, - canPutEverywhere: freshGame.canPutEverywhere, - loopedBoard: freshGame.loopedBoard, - }); - - if (o.isEnded) { - let winner; - if (o.winner === true) { - winner = freshGame.black == 1 ? freshGame.user1Id : freshGame.user2Id; - } else if (o.winner === false) { - winner = freshGame.black == 1 ? freshGame.user2Id : freshGame.user1Id; - } else { - winner = null; - } - - await ReversiGames.update(this.gameId!, { - isEnded: true, - winnerId: winner, - }); - - publishReversiGameStream(this.gameId!, 'ended', { - winnerId: winner, - game: await ReversiGames.pack(this.gameId!, this.user), - }); - } - //#endregion - - publishReversiGameStream(this.gameId!, 'started', - await ReversiGames.pack(this.gameId!, this.user)); - }, 3000); - } - } - - // 石を打つ - @autobind - private async set(pos: number) { - if (this.user == null) return; - - const game = await ReversiGames.findOne(this.gameId!); - if (game == null) throw new Error('game not found'); - - if (!game.isStarted) return; - if (game.isEnded) return; - if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return; - - const myColor = - ((game.user1Id === this.user.id) && game.black == 1) || ((game.user2Id === this.user.id) && game.black == 2) - ? true - : false; - - const o = new Reversi(game.map, { - isLlotheo: game.isLlotheo, - canPutEverywhere: game.canPutEverywhere, - loopedBoard: game.loopedBoard, - }); - - // 盤面の状態を再生 - for (const log of game.logs) { - o.put(log.color, log.pos); - } - - if (o.turn !== myColor) return; - - if (!o.canPut(myColor, pos)) return; - o.put(myColor, pos); - - let winner; - if (o.isEnded) { - if (o.winner === true) { - winner = game.black == 1 ? game.user1Id : game.user2Id; - } else if (o.winner === false) { - winner = game.black == 1 ? game.user2Id : game.user1Id; - } else { - winner = null; - } - } - - const log = { - at: new Date(), - color: myColor, - pos, - }; - - const crc32 = CRC32.str(game.logs.map(x => x.pos.toString()).join('') + pos.toString()).toString(); - - game.logs.push(log); - - await ReversiGames.update(this.gameId!, { - crc32, - isEnded: o.isEnded, - winnerId: winner, - logs: game.logs, - }); - - publishReversiGameStream(this.gameId!, 'set', Object.assign(log, { - next: o.turn, - })); - - if (o.isEnded) { - publishReversiGameStream(this.gameId!, 'ended', { - winnerId: winner, - game: await ReversiGames.pack(this.gameId!, this.user), - }); - } - } - - @autobind - private async check(crc32: string | number) { - const game = await ReversiGames.findOne(this.gameId!); - if (game == null) throw new Error('game not found'); - - if (!game.isStarted) return; - - if (crc32.toString() !== game.crc32) { - this.send('rescue', await ReversiGames.pack(game, this.user)); - } - - // ついでに観戦者イベントを発行 - this.watch(game); - } - - @autobind - private watch(game: ReversiGame) { - if (this.user != null) { - if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) { - publishReversiGameStream(this.gameId!, 'watching', this.user.id); - } - } - } -} diff --git a/packages/backend/src/server/api/stream/channels/games/reversi.ts b/packages/backend/src/server/api/stream/channels/games/reversi.ts deleted file mode 100644 index 121560ff87..0000000000 --- a/packages/backend/src/server/api/stream/channels/games/reversi.ts +++ /dev/null @@ -1,34 +0,0 @@ -import autobind from 'autobind-decorator'; -import { publishMainStream } from '@/services/stream'; -import Channel from '../../channel'; -import { ReversiMatchings } from '@/models/index'; - -export default class extends Channel { - public readonly chName = 'gamesReversi'; - public static shouldShare = true; - public static requireCredential = true; - - @autobind - public async init(params: any) { - // Subscribe reversi stream - this.subscriber.on(`reversiStream:${this.user!.id}`, data => { - this.send(data); - }); - } - - @autobind - public async onMessage(type: string, body: any) { - switch (type) { - case 'ping': { - if (body.id == null) return; - const matching = await ReversiMatchings.findOne({ - parentId: this.user!.id, - childId: body.id, - }); - if (matching == null) return; - publishMainStream(matching.childId, 'reversiInvited', await ReversiMatchings.pack(matching, { id: matching.childId })); - break; - } - } - } -} diff --git a/packages/backend/src/server/api/stream/channels/index.ts b/packages/backend/src/server/api/stream/channels/index.ts index 89d93f2da3..f3826c4cf7 100644 --- a/packages/backend/src/server/api/stream/channels/index.ts +++ b/packages/backend/src/server/api/stream/channels/index.ts @@ -13,8 +13,6 @@ import drive from './drive'; import hashtag from './hashtag'; import channel from './channel'; import admin from './admin'; -import gamesReversi from './games/reversi'; -import gamesReversiGame from './games/reversi-game'; export default { main, @@ -32,6 +30,4 @@ export default { hashtag, channel, admin, - gamesReversi, - gamesReversiGame, }; diff --git a/packages/backend/src/server/api/stream/types.ts b/packages/backend/src/server/api/stream/types.ts index f4302f64a0..e70c26f5e5 100644 --- a/packages/backend/src/server/api/stream/types.ts +++ b/packages/backend/src/server/api/stream/types.ts @@ -11,7 +11,6 @@ import { Emoji } from '@/models/entities/emoji'; import { UserList } from '@/models/entities/user-list'; import { MessagingMessage } from '@/models/entities/messaging-message'; import { UserGroup } from '@/models/entities/user-group'; -import { ReversiGame } from '@/models/entities/games/reversi/game'; import { AbuseUserReport } from '@/models/entities/abuse-user-report'; import { Signin } from '@/models/entities/signin'; import { Page } from '@/models/entities/page'; @@ -37,7 +36,7 @@ export interface UserStreamTypes { updateUserProfile: UserProfile; mute: User; unmute: User; - follow: Packed<'User'>; + follow: Packed<'UserDetailedNotMe'>; unfollow: Packed<'User'>; userAdded: Packed<'User'>; } @@ -47,7 +46,7 @@ export interface MainStreamTypes { mention: Packed<'Note'>; reply: Packed<'Note'>; renote: Packed<'Note'>; - follow: Packed<'User'>; + follow: Packed<'UserDetailedNotMe'>; followed: Packed<'User'>; unfollow: Packed<'User'>; meUpdated: Packed<'User'>; @@ -77,8 +76,6 @@ export interface MainStreamTypes { readAllChannels: undefined; unreadChannel: Note['id']; myTokenRegenerated: undefined; - reversiNoInvites: undefined; - reversiInvited: Packed<'ReversiMatching'>; signin: Signin; registryUpdated: { scope?: string[]; @@ -158,47 +155,6 @@ export interface MessagingIndexStreamTypes { message: Packed<'MessagingMessage'>; } -export interface ReversiStreamTypes { - matched: Packed<'ReversiGame'>; - invited: Packed<'ReversiMatching'>; -} - -export interface ReversiGameStreamTypes { - started: Packed<'ReversiGame'>; - ended: { - winnerId?: User['id'] | null, - game: Packed<'ReversiGame'>; - }; - updateSettings: { - key: string; - value: FIXME; - }; - initForm: { - userId: User['id']; - form: FIXME; - }; - updateForm: { - userId: User['id']; - id: string; - value: FIXME; - }; - message: { - userId: User['id']; - message: FIXME; - }; - changeAccepts: { - user1: boolean; - user2: boolean; - }; - set: { - at: Date; - color: boolean; - pos: number; - next: boolean; - }; - watching: User['id']; -} - export interface AdminStreamTypes { newAbuseUserReport: { id: AbuseUserReport['id']; @@ -268,14 +224,6 @@ export type StreamMessages = { name: `messagingIndexStream:${User['id']}`; payload: EventUnionFromDictionary<MessagingIndexStreamTypes>; }; - reversi: { - name: `reversiStream:${User['id']}`; - payload: EventUnionFromDictionary<ReversiStreamTypes>; - }; - reversiGame: { - name: `reversiGameStream:${ReversiGame['id']}`; - payload: EventUnionFromDictionary<ReversiGameStreamTypes>; - }; admin: { name: `adminStream:${User['id']}`; payload: EventUnionFromDictionary<AdminStreamTypes>; diff --git a/packages/backend/src/server/file/send-drive-file.ts b/packages/backend/src/server/file/send-drive-file.ts index 8bb5655b4f..f3c6c518fa 100644 --- a/packages/backend/src/server/file/send-drive-file.ts +++ b/packages/backend/src/server/file/send-drive-file.ts @@ -11,9 +11,10 @@ import { DriveFiles } from '@/models/index'; import { InternalStorage } from '@/services/drive/internal-storage'; import { downloadUrl } from '@/misc/download-url'; import { detectType } from '@/misc/get-file-info'; -import { convertToJpeg, convertToPngOrJpeg } from '@/services/drive/image-processor'; +import { convertToJpeg, convertToPng, convertToPngOrJpeg } from '@/services/drive/image-processor'; import { GenerateVideoThumbnail } from '@/services/drive/generate-video-thumbnail'; import { StatusError } from '@/misc/fetch'; +import { FILE_TYPE_BROWSERSAFE } from '@/const'; //const _filename = fileURLToPath(import.meta.url); const _filename = __filename; @@ -27,6 +28,7 @@ const commonReadableHandlerGenerator = (ctx: Koa.Context) => (e: Error): void => ctx.set('Cache-Control', 'max-age=300'); }; +// eslint-disable-next-line import/no-default-export export default async function(ctx: Koa.Context) { const key = ctx.params.key; @@ -65,13 +67,19 @@ export default async function(ctx: Koa.Context) { if (isThumbnail) { if (['image/jpeg', 'image/webp'].includes(mime)) { return await convertToJpeg(path, 498, 280); - } else if (['image/png'].includes(mime)) { + } else if (['image/png', 'image/svg+xml'].includes(mime)) { return await convertToPngOrJpeg(path, 498, 280); } else if (mime.startsWith('video/')) { return await GenerateVideoThumbnail(path); } } + if (isWebpublic) { + if (['image/svg+xml'].includes(mime)) { + return await convertToPng(path, 2048, 2048); + } + } + return { data: fs.readFileSync(path), ext, @@ -81,7 +89,7 @@ export default async function(ctx: Koa.Context) { const image = await convertFile(); ctx.body = image.data; - ctx.set('Content-Type', image.type); + ctx.set('Content-Type', FILE_TYPE_BROWSERSAFE.includes(image.type) ? image.type : 'application/octet-stream'); ctx.set('Cache-Control', 'max-age=31536000, immutable'); } catch (e) { serverLogger.error(`${e}`); @@ -112,14 +120,14 @@ export default async function(ctx: Koa.Context) { }).toString(); ctx.body = InternalStorage.read(key); - ctx.set('Content-Type', mime); + ctx.set('Content-Type', FILE_TYPE_BROWSERSAFE.includes(mime) ? mime : 'application/octet-stream'); ctx.set('Cache-Control', 'max-age=31536000, immutable'); ctx.set('Content-Disposition', contentDisposition('inline', filename)); } else { const readable = InternalStorage.read(file.accessKey!); readable.on('error', commonReadableHandlerGenerator(ctx)); ctx.body = readable; - ctx.set('Content-Type', file.type); + ctx.set('Content-Type', FILE_TYPE_BROWSERSAFE.includes(file.type) ? file.type : 'application/octet-stream'); ctx.set('Cache-Control', 'max-age=31536000, immutable'); ctx.set('Content-Disposition', contentDisposition('inline', file.name)); } diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts index 85fe21accb..764306c7d8 100644 --- a/packages/backend/src/server/index.ts +++ b/packages/backend/src/server/index.ts @@ -23,7 +23,7 @@ import Logger from '@/services/logger'; import { envOption } from '../env'; import { UserProfiles, Users } from '@/models/index'; import { networkChart } from '@/services/chart/index'; -import { genAvatar } from '@/misc/gen-avatar'; +import { genIdenticon } from '@/misc/gen-identicon'; import { createTemp } from '@/misc/create-temp'; import { publishMainStream } from '@/services/stream'; import * as Acct from 'misskey-js/built/acct'; @@ -84,9 +84,9 @@ router.get('/avatar/@:acct', async ctx => { } }); -router.get('/random-avatar/:x', async ctx => { +router.get('/identicon/:x', async ctx => { const [temp] = await createTemp(); - await genAvatar(ctx.params.x, fs.createWriteStream(temp)); + await genIdenticon(ctx.params.x, fs.createWriteStream(temp)); ctx.set('Content-Type', 'image/png'); ctx.body = fs.createReadStream(temp); }); diff --git a/packages/backend/src/server/nodeinfo.ts b/packages/backend/src/server/nodeinfo.ts index 097c6c664d..44f32bf882 100644 --- a/packages/backend/src/server/nodeinfo.ts +++ b/packages/backend/src/server/nodeinfo.ts @@ -1,9 +1,8 @@ import * as Router from '@koa/router'; import config from '@/config/index'; import { fetchMeta } from '@/misc/fetch-meta'; -import { Users } from '@/models/index'; -// import User from '../models/user'; -// import Note from '../models/note'; +import { Users, Notes } from '@/models/index'; +import { Not, IsNull, MoreThan } from 'typeorm'; const router = new Router(); @@ -19,20 +18,21 @@ export const links = [/* (awaiting release) { }]; const nodeinfo2 = async () => { + const now = Date.now(); const [ meta, - // total, - // activeHalfyear, - // activeMonth, - // localPosts, - // localComments + total, + activeHalfyear, + activeMonth, + localPosts, + localComments, ] = await Promise.all([ fetchMeta(true), - // User.count({ host: null }), - // User.count({ host: null, updatedAt: { $gt: new Date(Date.now() - 15552000000) } }), - // User.count({ host: null, updatedAt: { $gt: new Date(Date.now() - 2592000000) } }), - // Note.count({ '_user.host': null, replyId: null }), - // Note.count({ '_user.host': null, replyId: { $ne: null } }) + Users.count({ where: { host: null } }), + Users.count({ where: { host: null, updatedAt: MoreThan(new Date(now - 15552000000)) } }), + Users.count({ where: { host: null, updatedAt: MoreThan(new Date(now - 2592000000)) } }), + Notes.count({ where: { userHost: null, replyId: null } }), + Notes.count({ where: { userHost: null, replyId: Not(IsNull()) } }), ]); const proxyAccount = meta.proxyAccountId ? await Users.pack(meta.proxyAccountId).catch(() => null) : null; @@ -50,9 +50,9 @@ const nodeinfo2 = async () => { }, openRegistrations: !meta.disableRegistration, usage: { - users: {}, // { total, activeHalfyear, activeMonth }, - // localPosts, - // localComments + users: { total, activeHalfyear, activeMonth }, + localPosts, + localComments, }, metadata: { nodeName: meta.name, diff --git a/packages/backend/src/server/proxy/proxy-media.ts b/packages/backend/src/server/proxy/proxy-media.ts index 9e13c0877f..c234b70c55 100644 --- a/packages/backend/src/server/proxy/proxy-media.ts +++ b/packages/backend/src/server/proxy/proxy-media.ts @@ -6,6 +6,7 @@ import { createTemp } from '@/misc/create-temp'; import { downloadUrl } from '@/misc/download-url'; import { detectType } from '@/misc/get-file-info'; import { StatusError } from '@/misc/fetch'; +import { FILE_TYPE_BROWSERSAFE } from '@/const'; export async function proxyMedia(ctx: Koa.Context) { const url = 'url' in ctx.query ? ctx.query.url : 'https://' + ctx.params.url; @@ -18,14 +19,16 @@ export async function proxyMedia(ctx: Koa.Context) { const { mime, ext } = await detectType(path); - if (!mime.startsWith('image/')) throw 403; - let image: IImage; - if ('static' in ctx.query && ['image/png', 'image/gif', 'image/apng', 'image/vnd.mozilla.apng', 'image/webp'].includes(mime)) { + if ('static' in ctx.query && ['image/png', 'image/gif', 'image/apng', 'image/vnd.mozilla.apng', 'image/webp', 'image/svg+xml'].includes(mime)) { image = await convertToPng(path, 498, 280); - } else if ('preview' in ctx.query && ['image/jpeg', 'image/png', 'image/gif', 'image/apng', 'image/vnd.mozilla.apng'].includes(mime)) { + } else if ('preview' in ctx.query && ['image/jpeg', 'image/png', 'image/gif', 'image/apng', 'image/vnd.mozilla.apng', 'image/svg+xml'].includes(mime)) { image = await convertToJpeg(path, 200, 200); + } else if (['image/svg+xml'].includes(mime)) { + image = await convertToPng(path, 2048, 2048); + } else if (!mime.startsWith('image/') || !FILE_TYPE_BROWSERSAFE.includes(mime)) { + throw new StatusError('Rejected type', 403, 'Rejected type'); } else { image = { data: fs.readFileSync(path), diff --git a/packages/backend/src/server/web/index.ts b/packages/backend/src/server/web/index.ts index 5623f7db83..e95a115aec 100644 --- a/packages/backend/src/server/web/index.ts +++ b/packages/backend/src/server/web/index.ts @@ -390,9 +390,6 @@ router.get('/cli', async ctx => { const override = (source: string, target: string, depth: number = 0) => [, ...target.split('/').filter(x => x), ...source.split('/').filter(x => x).splice(depth)].join('/'); -router.get('/othello', async ctx => ctx.redirect(override(ctx.URL.pathname, 'games/reversi', 1))); -router.get('/reversi', async ctx => ctx.redirect(override(ctx.URL.pathname, 'games'))); - router.get('/flush', async ctx => { await ctx.render('flush'); }); diff --git a/packages/backend/src/services/chart/core.ts b/packages/backend/src/services/chart/core.ts index f97fa521d3..e406449f4f 100644 --- a/packages/backend/src/services/chart/core.ts +++ b/packages/backend/src/services/chart/core.ts @@ -7,7 +7,7 @@ import * as nestedProperty from 'nested-property'; import autobind from 'autobind-decorator'; import Logger from '../logger'; -import { SimpleSchema } from '@/misc/simple-schema'; +import { Schema } from '@/misc/schema'; import { EntitySchema, getRepository, Repository, LessThan, Between } from 'typeorm'; import { dateUTC, isTimeSame, isTimeBefore, subtractTime, addTime } from '@/prelude/time'; import { getChartInsertLock } from '@/misc/app-lock'; @@ -57,7 +57,7 @@ export default abstract class Chart<T extends Record<string, any>> { diff: DeepPartial<T>; group: string | null; }[] = []; - public schema: SimpleSchema; + public schema: Schema; protected repositoryForHour: Repository<Log>; protected repositoryForDay: Repository<Log>; @@ -71,7 +71,7 @@ export default abstract class Chart<T extends Record<string, any>> { protected abstract fetchActual(group: string | null): Promise<DeepPartial<T>>; @autobind - private static convertSchemaToFlatColumnDefinitions(schema: SimpleSchema) { + private static convertSchemaToFlatColumnDefinitions(schema: Schema) { const columns = {} as Record<string, unknown>; const flatColumns = (x: Obj, path?: string) => { for (const [k, v] of Object.entries(x)) { @@ -183,7 +183,7 @@ export default abstract class Chart<T extends Record<string, any>> { } @autobind - public static schemaToEntity(name: string, schema: SimpleSchema, grouped = false): { + public static schemaToEntity(name: string, schema: Schema, grouped = false): { hour: EntitySchema, day: EntitySchema, } { @@ -233,7 +233,7 @@ export default abstract class Chart<T extends Record<string, any>> { }; } - constructor(name: string, schema: SimpleSchema, grouped = false) { + constructor(name: string, schema: Schema, grouped = false) { this.name = name; this.schema = schema; @@ -573,8 +573,8 @@ export default abstract class Chart<T extends Record<string, any>> { } } -export function convertLog(logSchema: SimpleSchema): SimpleSchema { - const v: SimpleSchema = JSON.parse(JSON.stringify(logSchema)); // copy +export function convertLog(logSchema: Schema): Schema { + const v: Schema = JSON.parse(JSON.stringify(logSchema)); // copy if (v.type === 'number') { v.type = 'array'; v.items = { diff --git a/packages/backend/src/services/create-notification.ts b/packages/backend/src/services/create-notification.ts index fc43ab29d7..1c1c1fcdff 100644 --- a/packages/backend/src/services/create-notification.ts +++ b/packages/backend/src/services/create-notification.ts @@ -20,7 +20,7 @@ export async function createNotification( const isMuted = profile?.mutingNotificationTypes.includes(type); // Create notification - const notification = await Notifications.save({ + const notification = await Notifications.insert({ id: genId(), createdAt: new Date(), notifieeId: notifieeId, @@ -28,7 +28,8 @@ export async function createNotification( // 相手がこの通知をミュートしているようなら、既読を予めつけておく isRead: isMuted, ...data, - } as Partial<Notification>); + } as Partial<Notification>) + .then(x => Notifications.findOneOrFail(x.identifiers[0])); const packed = await Notifications.pack(notification, {}); diff --git a/packages/backend/src/services/drive/add-file.ts b/packages/backend/src/services/drive/add-file.ts index ee4d51a96d..a89e068f45 100644 --- a/packages/backend/src/services/drive/add-file.ts +++ b/packages/backend/src/services/drive/add-file.ts @@ -20,6 +20,7 @@ import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error'; import * as S3 from 'aws-sdk/clients/s3'; import { getS3 } from './s3'; import * as sharp from 'sharp'; +import { FILE_TYPE_BROWSERSAFE } from '@/const'; const logger = driveLogger.createSubLogger('register', 'yellow'); @@ -49,6 +50,12 @@ async function save(file: DriveFile, path: string, name: string, type: string, h if (type === 'image/vnd.mozilla.apng') ext = '.apng'; } + // 拡張子からContent-Typeを設定してそうな挙動を示すオブジェクトストレージ (upcloud?) も存在するので、 + // 許可されているファイル形式でしか拡張子をつけない + if (!FILE_TYPE_BROWSERSAFE.includes(type)) { + ext = ''; + } + const baseUrl = meta.objectStorageBaseUrl || `${ meta.objectStorageUseSSL ? 'https' : 'http' }://${ meta.objectStorageEndpoint }${ meta.objectStoragePort ? `:${meta.objectStoragePort}` : '' }/${ meta.objectStorageBucket }`; @@ -94,13 +101,14 @@ async function save(file: DriveFile, path: string, name: string, type: string, h file.accessKey = key; file.thumbnailAccessKey = thumbnailKey; file.webpublicAccessKey = webpublicKey; + file.webpublicType = alts.webpublic?.type ?? null; file.name = name; file.type = type; file.md5 = hash; file.size = size; file.storedInternal = false; - return await DriveFiles.save(file); + return await DriveFiles.insert(file).then(x => DriveFiles.findOneOrFail(x.identifiers[0])); } else { // use internal storage const accessKey = uuid(); const thumbnailAccessKey = 'thumbnail-' + uuid(); @@ -128,12 +136,13 @@ async function save(file: DriveFile, path: string, name: string, type: string, h file.accessKey = accessKey; file.thumbnailAccessKey = thumbnailAccessKey; file.webpublicAccessKey = webpublicAccessKey; + file.webpublicType = alts.webpublic?.type ?? null; file.name = name; file.type = type; file.md5 = hash; file.size = size; - return await DriveFiles.save(file); + return await DriveFiles.insert(file).then(x => DriveFiles.findOneOrFail(x.identifiers[0])); } } @@ -160,7 +169,7 @@ export async function generateAlts(path: string, type: string, generateWeb: bool } } - if (!['image/jpeg', 'image/png', 'image/webp'].includes(type)) { + if (!['image/jpeg', 'image/png', 'image/webp', 'image/svg+xml'].includes(type)) { logger.debug(`web image and thumbnail not created (not an required file)`); return { webpublic: null, @@ -201,7 +210,7 @@ export async function generateAlts(path: string, type: string, generateWeb: bool webpublic = await convertSharpToJpeg(img, 2048, 2048); } else if (['image/webp'].includes(type)) { webpublic = await convertSharpToWebp(img, 2048, 2048); - } else if (['image/png'].includes(type)) { + } else if (['image/png', 'image/svg+xml'].includes(type)) { webpublic = await convertSharpToPng(img, 2048, 2048); } else { logger.debug(`web image not created (not an required image)`); @@ -220,7 +229,7 @@ export async function generateAlts(path: string, type: string, generateWeb: bool try { if (['image/jpeg', 'image/webp'].includes(type)) { thumbnail = await convertSharpToJpeg(img, 498, 280); - } else if (['image/png'].includes(type)) { + } else if (['image/png', 'image/svg+xml'].includes(type)) { thumbnail = await convertSharpToPngOrJpeg(img, 498, 280); } else { logger.debug(`thumbnail not created (not an required file)`); @@ -241,6 +250,7 @@ export async function generateAlts(path: string, type: string, generateWeb: bool */ async function upload(key: string, stream: fs.ReadStream | Buffer, type: string, filename?: string) { if (type === 'image/apng') type = 'image/png'; + if (!FILE_TYPE_BROWSERSAFE.includes(type)) type = 'application/octet-stream'; const meta = await fetchMeta(); @@ -287,33 +297,45 @@ async function deleteOldFile(user: IRemoteUser) { } } +type AddFileArgs = { + /** User who wish to add file */ + user: { id: User['id']; host: User['host'] } | null; + /** File path */ + path: string; + /** Name */ + name?: string | null; + /** Comment */ + comment?: string | null; + /** Folder ID */ + folderId?: any; + /** If set to true, forcibly upload the file even if there is a file with the same hash. */ + force?: boolean; + /** Do not save file to local */ + isLink?: boolean; + /** URL of source (URLからアップロードされた場合(ローカル/リモート)の元URL) */ + url?: string | null; + /** URL of source (リモートインスタンスのURLからアップロードされた場合の元URL) */ + uri?: string | null; + /** Mark file as sensitive */ + sensitive?: boolean | null; +}; + /** * Add file to drive * - * @param user User who wish to add file - * @param path File path - * @param name Name - * @param comment Comment - * @param folderId Folder ID - * @param force If set to true, forcibly upload the file even if there is a file with the same hash. - * @param isLink Do not save file to local - * @param url URL of source (URLからアップロードされた場合(ローカル/リモート)の元URL) - * @param uri URL of source (リモートインスタンスのURLからアップロードされた場合の元URL) - * @param sensitive Mark file as sensitive - * @return Created drive file */ -export default async function( - user: { id: User['id']; host: User['host'] } | null, - path: string, - name: string | null = null, - comment: string | null = null, - folderId: any = null, - force: boolean = false, - isLink: boolean = false, - url: string | null = null, - uri: string | null = null, - sensitive: boolean | null = null -): Promise<DriveFile> { +export async function addFile({ + user, + path, + name = null, + comment = null, + folderId = null, + force = false, + isLink = false, + url = null, + uri = null, + sensitive = null +}: AddFileArgs): Promise<DriveFile> { const info = await getFileInfo(path); logger.info(`${JSON.stringify(info)}`); @@ -428,7 +450,7 @@ export default async function( file.type = info.type.mime; file.storedInternal = false; - file = await DriveFiles.save(file); + file = await DriveFiles.insert(file).then(x => DriveFiles.findOneOrFail(x.identifiers[0])); } catch (e) { // duplicate key error (when already registered) if (isDuplicateKeyValueError(e)) { diff --git a/packages/backend/src/services/drive/upload-from-url.ts b/packages/backend/src/services/drive/upload-from-url.ts index 28f42bc344..7c5fa5ce3f 100644 --- a/packages/backend/src/services/drive/upload-from-url.ts +++ b/packages/backend/src/services/drive/upload-from-url.ts @@ -1,5 +1,5 @@ import { URL } from 'url'; -import create from './add-file'; +import { addFile } from './add-file'; import { User } from '@/models/entities/user'; import { driveLogger } from './logger'; import { createTemp } from '@/misc/create-temp'; @@ -10,16 +10,27 @@ import { DriveFiles } from '@/models/index'; const logger = driveLogger.createSubLogger('downloader'); -export default async ( - url: string, - user: { id: User['id']; host: User['host'] } | null, - folderId: DriveFolder['id'] | null = null, - uri: string | null = null, +type Args = { + url: string; + user: { id: User['id']; host: User['host'] } | null; + folderId?: DriveFolder['id'] | null; + uri?: string | null; + sensitive?: boolean; + force?: boolean; + isLink?: boolean; + comment?: string | null; +}; + +export async function uploadFromUrl({ + url, + user, + folderId = null, + uri = null, sensitive = false, force = false, - link = false, + isLink = false, comment = null -): Promise<DriveFile> => { +}: Args): Promise<DriveFile> { let name = new URL(url).pathname.split('/').pop() || null; if (name == null || !DriveFiles.validateFileName(name)) { name = null; @@ -41,7 +52,7 @@ export default async ( let error; try { - driveFile = await create(user, path, name, comment, folderId, force, link, url, uri, sensitive); + driveFile = await addFile({ user, path, name, comment, folderId, force, isLink, url, uri, sensitive }); logger.succ(`Got: ${driveFile.id}`); } catch (e) { error = e; @@ -59,4 +70,4 @@ export default async ( } else { return driveFile!; } -}; +} diff --git a/packages/backend/src/services/following/create.ts b/packages/backend/src/services/following/create.ts index 45bd226588..bc5ac275b5 100644 --- a/packages/backend/src/services/following/create.ts +++ b/packages/backend/src/services/following/create.ts @@ -14,6 +14,7 @@ import { instanceChart, perUserFollowingChart } from '@/services/chart/index'; import { genId } from '@/misc/gen-id'; import { createNotification } from '../create-notification'; import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error'; +import { Packed } from '@/misc/schema'; const logger = new Logger('following/create'); @@ -89,8 +90,8 @@ export async function insertFollowingDoc(followee: { id: User['id']; host: User[ Users.pack(followee.id, follower, { detail: true, }).then(packed => { - publishUserEvent(follower.id, 'follow', packed); - publishMainStream(follower.id, 'follow', packed); + publishUserEvent(follower.id, 'follow', packed as Packed<"UserDetailedNotMe">); + publishMainStream(follower.id, 'follow', packed as Packed<"UserDetailedNotMe">); }); } diff --git a/packages/backend/src/services/following/requests/create.ts b/packages/backend/src/services/following/requests/create.ts index cc1abcce19..e45023015d 100644 --- a/packages/backend/src/services/following/requests/create.ts +++ b/packages/backend/src/services/following/requests/create.ts @@ -25,7 +25,7 @@ export default async function(follower: { id: User['id']; host: User['host']; ur if (blocking != null) throw new Error('blocking'); if (blocked != null) throw new Error('blocked'); - const followRequest = await FollowRequests.save({ + const followRequest = await FollowRequests.insert({ id: genId(), createdAt: new Date(), followerId: follower.id, @@ -39,7 +39,7 @@ export default async function(follower: { id: User['id']; host: User['host']; ur followeeHost: followee.host, followeeInbox: Users.isRemoteUser(followee) ? followee.inbox : undefined, followeeSharedInbox: Users.isRemoteUser(followee) ? followee.sharedInbox : undefined, - }); + }).then(x => FollowRequests.findOneOrFail(x.identifiers[0])); // Publish receiveRequest event if (Users.isLocalUser(followee)) { diff --git a/packages/backend/src/services/note/reaction/create.ts b/packages/backend/src/services/note/reaction/create.ts index e0baa6a096..47f46419dd 100644 --- a/packages/backend/src/services/note/reaction/create.ts +++ b/packages/backend/src/services/note/reaction/create.ts @@ -81,19 +81,15 @@ export default async (user: { id: User['id']; host: User['host']; }, note: Note, name: decodedReaction.name, host: decodedReaction.host, }, - select: ['name', 'host', 'url'], + select: ['name', 'host', 'originalUrl', 'publicUrl'], }); - if (emoji) { - emoji = { - name: emoji.host ? `${emoji.name}@${emoji.host}` : `${emoji.name}@.`, - url: emoji.url, - } as any; - } - publishNoteStream(note.id, 'reacted', { reaction: decodedReaction.reaction, - emoji: emoji, + emoji: emoji != null ? { + name: emoji.host ? `${emoji.name}@${emoji.host}` : `${emoji.name}@.`, + url: emoji.publicUrl || emoji.originalUrl, // || emoji.originalUrl してるのは後方互換性のため + } : null, userId: user.id, }); diff --git a/packages/backend/src/services/register-or-fetch-instance-doc.ts b/packages/backend/src/services/register-or-fetch-instance-doc.ts index a548ab0497..18b42ed15b 100644 --- a/packages/backend/src/services/register-or-fetch-instance-doc.ts +++ b/packages/backend/src/services/register-or-fetch-instance-doc.ts @@ -16,12 +16,12 @@ export async function registerOrFetchInstanceDoc(host: string): Promise<Instance const index = await Instances.findOne({ host }); if (index == null) { - const i = await Instances.save({ + const i = await Instances.insert({ id: genId(), host, caughtAt: new Date(), lastCommunicatedAt: new Date(), - }); + }).then(x => Instances.findOneOrFail(x.identifiers[0])); federationChart.update(true); diff --git a/packages/backend/src/services/relay.ts b/packages/backend/src/services/relay.ts index 422da88846..33a5ef7f9b 100644 --- a/packages/backend/src/services/relay.ts +++ b/packages/backend/src/services/relay.ts @@ -22,11 +22,11 @@ export async function getRelayActor(): Promise<ILocalUser> { } export async function addRelay(inbox: string) { - const relay = await Relays.save({ + const relay = await Relays.insert({ id: genId(), inbox, status: 'requesting', - }); + }).then(x => Relays.findOneOrFail(x.identifiers[0])); const relayActor = await getRelayActor(); const follow = await renderFollowRelay(relay, relayActor); diff --git a/packages/backend/src/services/stream.ts b/packages/backend/src/services/stream.ts index 7d11ff3f45..c0cefe9af4 100644 --- a/packages/backend/src/services/stream.ts +++ b/packages/backend/src/services/stream.ts @@ -2,7 +2,6 @@ import { redisClient } from '../db/redis'; import { User } from '@/models/entities/user'; import { Note } from '@/models/entities/note'; import { UserList } from '@/models/entities/user-list'; -import { ReversiGame } from '@/models/entities/games/reversi/game'; import { UserGroup } from '@/models/entities/user-group'; import config from '@/config/index'; import { Antenna } from '@/models/entities/antenna'; @@ -20,8 +19,6 @@ import { MessagingIndexStreamTypes, MessagingStreamTypes, NoteStreamTypes, - ReversiGameStreamTypes, - ReversiStreamTypes, UserListStreamTypes, UserStreamTypes, } from '@/server/api/stream/types'; @@ -90,14 +87,6 @@ class Publisher { this.publish(`messagingIndexStream:${userId}`, type, typeof value === 'undefined' ? null : value); }; - public publishReversiStream = <K extends keyof ReversiStreamTypes>(userId: User['id'], type: K, value?: ReversiStreamTypes[K]): void => { - this.publish(`reversiStream:${userId}`, type, typeof value === 'undefined' ? null : value); - }; - - public publishReversiGameStream = <K extends keyof ReversiGameStreamTypes>(gameId: ReversiGame['id'], type: K, value?: ReversiGameStreamTypes[K]): void => { - this.publish(`reversiGameStream:${gameId}`, type, typeof value === 'undefined' ? null : value); - }; - public publishNotesStream = (note: Packed<'Note'>): void => { this.publish('notesStream', null, note); }; @@ -124,6 +113,4 @@ export const publishAntennaStream = publisher.publishAntennaStream; export const publishMessagingStream = publisher.publishMessagingStream; export const publishGroupMessagingStream = publisher.publishGroupMessagingStream; export const publishMessagingIndexStream = publisher.publishMessagingIndexStream; -export const publishReversiStream = publisher.publishReversiStream; -export const publishReversiGameStream = publisher.publishReversiGameStream; export const publishAdminStream = publisher.publishAdminStream; diff --git a/packages/backend/src/tools/add-emoji.ts b/packages/backend/src/tools/add-emoji.ts deleted file mode 100644 index a3f4b54c7e..0000000000 --- a/packages/backend/src/tools/add-emoji.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { initDb } from '@/db/postgre'; -import { genId } from '@/misc/gen-id'; - -async function main(name: string, url: string, alias?: string): Promise<any> { - await initDb(); - const { Emojis } = await import('@/models/index'); - - const aliases = alias != null ? [ alias ] : []; - - await Emojis.save({ - id: genId(), - host: null, - name, - url, - aliases, - updatedAt: new Date(), - }); -} - -const args = process.argv.slice(2); -const name = args[0]; -const url = args[1]; - -if (!name) throw new Error('require name'); -if (!url) throw new Error('require url'); - -main(name, url).then(() => { - console.log('success'); - process.exit(0); -}).catch(e => { - console.warn(e); - process.exit(1); -}); diff --git a/packages/backend/test/docker-compose.yml b/packages/backend/test/docker-compose.yml index c045e7c6c4..5f95bec4c0 100644 --- a/packages/backend/test/docker-compose.yml +++ b/packages/backend/test/docker-compose.yml @@ -2,12 +2,12 @@ version: "3" services: redistest: - image: redis:4.0-alpine + image: redis:6 ports: - "127.0.0.1:56312:6379" dbtest: - image: postgres:12.2-alpine + image: postgres:13 ports: - "127.0.0.1:54312:5432" environment: diff --git a/packages/backend/yarn.lock b/packages/backend/yarn.lock index 0652d1df3f..99e2e2306e 100644 --- a/packages/backend/yarn.lock +++ b/packages/backend/yarn.lock @@ -82,14 +82,14 @@ pump "^3.0.0" secure-json-parse "^2.1.0" -"@eslint/eslintrc@^1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.0.4.tgz#dfe0ff7ba270848d10c5add0715e04964c034b31" - integrity sha512-h8Vx6MdxwWI2WM8/zREHMoqdgLNXEL4QX3MWSVMdyNJGvXVOs+6lp+m2hc3FnuMHDc4poxFNI20vCk0OmI4G0Q== +"@eslint/eslintrc@^1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.0.5.tgz#33f1b838dbf1f923bfa517e008362b78ddbbf318" + integrity sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ== dependencies: ajv "^6.12.4" debug "^4.3.2" - espree "^9.0.0" + espree "^9.2.0" globals "^13.9.0" ignore "^4.0.6" import-fresh "^3.2.1" @@ -97,19 +97,24 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" -"@humanwhocodes/config-array@^0.6.0": - version "0.6.0" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.6.0.tgz#b5621fdb3b32309d2d16575456cbc277fa8f021a" - integrity sha512-JQlEKbcgEUjBFhLIF4iqM7u/9lwgHRBcpHrmUNCALK0Q3amXN6lxdoXLnF0sm11E9VqTmBALR87IlUg1bZ8A9A== +"@gar/promisify@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.2.tgz#30aa825f11d438671d585bd44e7fd564535fc210" + integrity sha512-82cpyJyKRoQoRi+14ibCeGPu0CwypgtBAdBhq1WfvagpCZNKqwXbKwXllYSMG91DhmG4jt9gN8eP6lGOtozuaw== + +"@humanwhocodes/config-array@^0.9.2": + version "0.9.2" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.9.2.tgz#68be55c737023009dfc5fe245d51181bb6476914" + integrity sha512-UXOuFCGcwciWckOpmfKDq/GyhlTf9pN/BzG//x8p8zTOFEcGuA68ANXheFS0AGvy3qgZqLBUkMs7hqzqCKOVwA== dependencies: - "@humanwhocodes/object-schema" "^1.2.0" + "@humanwhocodes/object-schema" "^1.2.1" debug "^4.1.1" minimatch "^3.0.4" -"@humanwhocodes/object-schema@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz#87de7af9c231826fdd68ac7258f77c429e0e5fcf" - integrity sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w== +"@humanwhocodes/object-schema@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== "@koa/cors@3.1.0": version "3.1.0" @@ -134,6 +139,36 @@ methods "^1.1.2" path-to-regexp "^6.1.0" +"@node-redis/bloom@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@node-redis/bloom/-/bloom-1.0.1.tgz#144474a0b7dc4a4b91badea2cfa9538ce0a1854e" + integrity sha512-mXEBvEIgF4tUzdIN89LiYsbi6//EdpFA7L8M+DHCvePXg+bfHWi+ct5VI6nHUFQE5+ohm/9wmgihCH3HSkeKsw== + +"@node-redis/client@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@node-redis/client/-/client-1.0.2.tgz#7f09fb739675728fbc6e73536f7cd1be99bf7b8f" + integrity sha512-C+gkx68pmTnxfV+y4pzasvCH3s4UGHNOAUNhdJxGI27aMdnXNDZct7ffDHBL7bAZSGv9FSwCP5PeYvEIEKGbiA== + dependencies: + cluster-key-slot "1.1.0" + generic-pool "3.8.2" + redis-parser "3.0.0" + yallist "4.0.0" + +"@node-redis/json@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@node-redis/json/-/json-1.0.2.tgz#8ad2d0f026698dc1a4238cc3d1eb099a3bee5ab8" + integrity sha512-qVRgn8WfG46QQ08CghSbY4VhHFgaTY71WjpwRBGEuqGPfWwfRcIf3OqSpR7Q/45X+v3xd8mvYjywqh0wqJ8T+g== + +"@node-redis/search@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@node-redis/search/-/search-1.0.2.tgz#8cfc91006ea787df801d41410283e1f59027f818" + integrity sha512-gWhEeji+kTAvzZeguUNJdMSZNH2c5dv3Bci8Nn2f7VGuf6IvvwuZDSBOuOlirLVgayVuWzAG7EhwaZWK1VDnWQ== + +"@node-redis/time-series@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@node-redis/time-series/-/time-series-1.0.1.tgz#703149f8fa4f6fff377c61a0873911e7c1ba5cc3" + integrity sha512-+nTn6EewVj3GlUXPuD3dgheWqo219jTxlo6R+pg24OeVvFHx9aFGGiyOgj3vBPhWUdRZ0xMcujXV5ki4fbLyMw== + "@nodelib/fs.scandir@2.1.3": version "2.1.3" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" @@ -155,6 +190,14 @@ "@nodelib/fs.scandir" "2.1.3" fastq "^1.6.0" +"@npmcli/fs@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-1.1.0.tgz#bec1d1b89c170d40e1b73ad6c943b0b75e7d2951" + integrity sha512-VhP1qZLXcrXRIaPoqb4YA55JQxLNF3jNR4T55IdOJa3+IFJKNYHtPvtXx8slmeMavj37vCzCfrqQM1vWLsYKLA== + dependencies: + "@gar/promisify" "^1.0.1" + semver "^7.3.5" + "@npmcli/move-file@^1.0.1": version "1.1.2" resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674" @@ -163,29 +206,30 @@ mkdirp "^1.0.4" rimraf "^3.0.2" -"@redocly/ajv@^8.6.2": - version "8.6.2" - resolved "https://registry.yarnpkg.com/@redocly/ajv/-/ajv-8.6.2.tgz#8c4e485e72f7864f91fae40093bed548ec2619b2" - integrity sha512-tU8fQs0D76ZKhJ2cWtnfQthWqiZgGBx0gH0+5D8JvaBEBaqA8foPPBt3Nonwr3ygyv5xrw2IzKWgIY86BlGs+w== +"@redocly/ajv@^8.6.4": + version "8.6.4" + resolved "https://registry.yarnpkg.com/@redocly/ajv/-/ajv-8.6.4.tgz#94053e7a9d4146d1a4feacd3813892873f229a85" + integrity sha512-y9qNj0//tZtWB2jfXNK3BX18BSBp9zNR7KE7lMysVHwbZtY392OJCjm6Rb/h4UHH2r1AqjNEHFD6bRn+DqU9Mw== dependencies: fast-deep-equal "^3.1.1" json-schema-traverse "^1.0.0" require-from-string "^2.0.2" uri-js "^4.2.2" -"@redocly/openapi-core@1.0.0-beta.54": - version "1.0.0-beta.54" - resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.0.0-beta.54.tgz#42575a849c4dd54b9d0c6413fb8ca547e087cd11" - integrity sha512-uYs0N1Trjkh7u8IMIuCU2VxCXhMyGWSZUkP/WNdTR1OgBUtvNdF9C32zoQV+hyCIH4gVu42ROHkjisy333ZX+w== +"@redocly/openapi-core@1.0.0-beta.79": + version "1.0.0-beta.79" + resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.0.0-beta.79.tgz#7512b3507ab99dc78226f9069669c5302abb0969" + integrity sha512-do79vGt3iiHsaVG9LKY8dH+d1E7TLHr+3T+CQ1lqagtWVjYOxqGaoxAT8tRD7R1W0z8BmS4e2poNON6c1sxP5g== dependencies: - "@redocly/ajv" "^8.6.2" + "@redocly/ajv" "^8.6.4" "@types/node" "^14.11.8" colorette "^1.2.0" js-levenshtein "^1.1.6" - js-yaml "^3.14.1" + js-yaml "^4.1.0" lodash.isequal "^4.5.0" minimatch "^3.0.4" node-fetch "^2.6.1" + pluralize "^8.0.0" yaml-ast-parser "0.0.43" "@sindresorhus/is@^3.0.0": @@ -245,11 +289,6 @@ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== -"@trysound/sax@0.2.0": - version "0.2.0" - resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" - integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== - "@tsconfig/node10@^1.0.7": version "1.0.7" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.7.tgz#1eb1de36c73478a2479cc661ef5af1c16d86d606" @@ -295,12 +334,13 @@ "@types/connect" "*" "@types/node" "*" -"@types/bull@3.15.5": - version "3.15.5" - resolved "https://registry.yarnpkg.com/@types/bull/-/bull-3.15.5.tgz#a4459c127c5b10fb847531579a2cd5db35751366" - integrity sha512-XgJQWJ03jyKMfdoL8IAIoHIo7JkkL74kcxuujTONkSJswm0giIJ9kuVgDNHS0OvD+OiPNcFmbBl0H3scj2+A8A== +"@types/bull@3.15.7": + version "3.15.7" + resolved "https://registry.yarnpkg.com/@types/bull/-/bull-3.15.7.tgz#a9d7fb332cc02dc021d0eb234b9604b356e9e6de" + integrity sha512-7NC7XN5NoS0A+leJ/dR69ZfKaegOlCZaii/xGgKnCyh1UYisRncibImb7VMwrc3OdJcbDJt6+4om70TeNl3J7g== dependencies: "@types/ioredis" "*" + "@types/redis" "^2.8.0" "@types/cacheable-request@^6.0.1": version "6.0.1" @@ -356,10 +396,10 @@ resolved "https://registry.yarnpkg.com/@types/disposable-email-domains/-/disposable-email-domains-1.0.2.tgz#0280f6b38fa7f14e54b056a434135ecd254483b1" integrity sha512-SDKwyYTjk3y5aZBxxc38yRecpJPjsqn57STz1bNxYYlv4k11bBe7QB8w4llXDTmQXKT1mFvgGmJv+8Zdu3YmJw== -"@types/escape-regexp@0.0.0": - version "0.0.0" - resolved "https://registry.yarnpkg.com/@types/escape-regexp/-/escape-regexp-0.0.0.tgz#bff0225f9ef30d0dbdbe0e2a24283ee5342990c3" - integrity sha512-HTansGo4tJ7K7W9I9LBdQqnHtPB/Y7tlS+EMrkboaAQLsRPhRpHaqAHe01K1HVXM5e1u1IplRd8EBh+pJrp7Dg== +"@types/escape-regexp@0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@types/escape-regexp/-/escape-regexp-0.0.1.tgz#f1a977ccdf2ef059e9862bd3af5e92cbbe723e0e" + integrity sha512-ogj/ZTIdeFkiuxDwawYuZSIgC6suFGgBeZPr6Xs5lHEcvIXTjXGtH+/n8f1XhZhespaUwJ5LIGRICPji972FLw== "@types/eslint-scope@^3.7.0": version "3.7.0" @@ -400,10 +440,10 @@ "@types/qs" "*" "@types/serve-static" "*" -"@types/fluent-ffmpeg@2.1.17": - version "2.1.17" - resolved "https://registry.yarnpkg.com/@types/fluent-ffmpeg/-/fluent-ffmpeg-2.1.17.tgz#6958dda400fe1b33c21f3683db76905cb210d053" - integrity sha512-/bdvjKw/mtBHlJ2370d04nt4CsWqU5MrwS/NtO96V01jxitJ4+iq8OFNcqc5CegeV3TQOK3uueK02kvRK+zjUg== +"@types/fluent-ffmpeg@2.1.20": + version "2.1.20" + resolved "https://registry.yarnpkg.com/@types/fluent-ffmpeg/-/fluent-ffmpeg-2.1.20.tgz#3b5f42fc8263761d58284fa46ee6759a64ce54ac" + integrity sha512-B+OvhCdJ3LgEq2PhvWNOiB/EfwnXLElfMCgc4Z1K5zXgSfo9I6uGKwR/lqmNPFQuebNnes7re3gqkV77SyypLg== dependencies: "@types/node" "*" @@ -442,15 +482,15 @@ resolved "https://registry.yarnpkg.com/@types/is-url/-/is-url-1.2.30.tgz#85567e8bee4fee69202bc3448f9fb34b0d56c50a" integrity sha512-AnlNFwjzC8XLda5VjRl4ItSd8qp8pSNowvsut0WwQyBWHpOxjxRJm8iO6uETWqEyLdYdb9/1j+Qd9gQ4l5I4fw== -"@types/js-yaml@4.0.4": - version "4.0.4" - resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.4.tgz#cc38781257612581a1a0eb25f1709d2b06812fce" - integrity sha512-AuHubXUmg0AzkXH0Mx6sIxeY/1C110mm/EkE/gB1sTRz3h2dao2W/63q42SlVST+lICxz5Oki2hzYA6+KnnieQ== +"@types/js-yaml@4.0.5": + version "4.0.5" + resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.5.tgz#738dd390a6ecc5442f35e7f03fa1431353f7e138" + integrity sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA== -"@types/jsdom@16.2.13": - version "16.2.13" - resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-16.2.13.tgz#126c8b7441b159d6234610a48de77b6066f1823f" - integrity sha512-8JQCjdeAidptSsOcRWk2iTm9wCcwn9l+kRG6k5bzUacrnm1ezV4forq0kWjUih/tumAeoG+OspOvQEbbRucBTw== +"@types/jsdom@16.2.14": + version "16.2.14" + resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-16.2.14.tgz#26fe9da6a8870715b154bb84cd3b2e53433d8720" + integrity sha512-6BAy1xXEmMuHeAJ4Fv4yXKwBDTGTOseExKE3OaHiNycdHdZw59KfYzrt0DkDluvwmik1HRt6QS7bImxUmpSy+w== dependencies: "@types/node" "*" "@types/parse5" "*" @@ -493,10 +533,10 @@ dependencies: "@types/node" "*" -"@types/koa-bodyparser@4.3.3": - version "4.3.3" - resolved "https://registry.yarnpkg.com/@types/koa-bodyparser/-/koa-bodyparser-4.3.3.tgz#9c7d4295576bc863d550002f732f1c57dd88cc58" - integrity sha512-/ileIpXsy1fFEzgZhZ07eZH8rAVL7jwuk/kaoVEfauO6s80g2LIDIJKEyDbuAL9S/BWflKzEC0PHD6aXkmaSbw== +"@types/koa-bodyparser@4.3.5": + version "4.3.5" + resolved "https://registry.yarnpkg.com/@types/koa-bodyparser/-/koa-bodyparser-4.3.5.tgz#0c5fa44d7150202ffc16b89bd730ce1b6c7bc250" + integrity sha512-NRqqoTtt7cfdDk/KNo+EwCIKRuzPAu/wsaZ7tgIvSIBtNfxuZHYueaLoWdxX3ZftWavQv07NE46TcpyoZGqpgQ== dependencies: "@types/koa" "*" @@ -577,10 +617,10 @@ "@types/koa-compose" "*" "@types/node" "*" -"@types/koa__cors@3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/koa__cors/-/koa__cors-3.0.3.tgz#49d75813b443ba3d4da28ea6cf6244b7e99a3b23" - integrity sha512-74Xb4hJOPGKlrQ4PRBk1A/p0gfLpgbnpT0o67OMVbwyeMXvlBN+ZCRztAAmkKZs+8hKbgMutUlZVbA52Hr/0IA== +"@types/koa__cors@3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@types/koa__cors/-/koa__cors-3.1.1.tgz#198b5abbc425a672ae57c311b420bc270e65bdef" + integrity sha512-O7MBkCocnLrpEvkMrYAp17arUDS+KuS5bXMG/Z4aPSbrO7vrYB6YrqcsTD3Dp2OnAL3j4WME2k/x2kOcyzwNUw== dependencies: "@types/koa" "*" @@ -591,10 +631,10 @@ dependencies: "@types/koa" "*" -"@types/koa__router@8.0.8": - version "8.0.8" - resolved "https://registry.yarnpkg.com/@types/koa__router/-/koa__router-8.0.8.tgz#b1e0e9a512498777d3366bbdf0e853df27ec831c" - integrity sha512-9pGCaDtzCsj4HJ8HmGuqzk8+s57sPj4njWd08GG5o92n5Xp9io2snc40CPpXFhoKcZ8OKhuu6ht4gNou9e1C2w== +"@types/koa__router@8.0.11": + version "8.0.11" + resolved "https://registry.yarnpkg.com/@types/koa__router/-/koa__router-8.0.11.tgz#d7b37e6db934fc072ea1baa2ab92bc8ac4564f3e" + integrity sha512-WXgKWpBsbS14kzmzD9LeFapOIa678h7zvUHxDwXwSx4ETKXhXLVUAToX6jZ/U7EihM7qwyD9W/BZvB0MRu7MTQ== dependencies: "@types/koa" "*" @@ -613,23 +653,22 @@ resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-8.2.3.tgz#bbeb55fbc73f28ea6de601fbfa4613f58d785323" integrity sha512-ekGvFhFgrc2zYQoX4JeZPmVzZxw6Dtllga7iGHzfbYIYkAMUx/sAFP2GdFpLff+vdHXu5fl7WX9AT+TtqYcsyw== -"@types/node-fetch@2.5.12": - version "2.5.12" - resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.12.tgz#8a6f779b1d4e60b7a57fb6fd48d84fb545b9cc66" - integrity sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw== +"@types/node-fetch@3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-3.0.3.tgz#9d969c9a748e841554a40ee435d26e53fa3ee899" + integrity sha512-HhggYPH5N+AQe/OmN6fmhKmRRt2XuNJow+R3pQwJxOOF9GuwM7O2mheyGeIrs5MOIeNjDEdgdoyHBOrFeJBR3g== dependencies: - "@types/node" "*" - form-data "^3.0.0" + node-fetch "*" "@types/node@*": version "16.6.2" resolved "https://registry.yarnpkg.com/@types/node/-/node-16.6.2.tgz#331b7b9f8621c638284787c5559423822fdffc50" integrity sha512-LSw8TZt12ZudbpHc6EkIyDM3nHVWKYrAvGy6EAJfNfjusbwnThqjqxUKKRwuV3iWYeW/LYMzNgaq3MaLffQ2xA== -"@types/node@16.11.7": - version "16.11.7" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.7.tgz#36820945061326978c42a01e56b61cd223dfdc42" - integrity sha512-QB5D2sqfSjCmTuWcBWyJ+/44bcjO7VbjSbOE0ucoVbAsSNQc4Lt6QkgkVXkTDwkL4z/beecZNDvVX15D4P8Jbw== +"@types/node@17.0.10": + version "17.0.10" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.10.tgz#616f16e9d3a2a3d618136b1be244315d95bd7cab" + integrity sha512-S/3xB4KzyFxYGCppyDt68yzBU9ysL88lSdIah4D6cptdcltc4NCPCAMc0+PCpg/lLIyC7IPvj2Z52OJWeIUkog== "@types/node@^14.11.8": version "14.17.9" @@ -665,20 +704,20 @@ resolved "https://registry.yarnpkg.com/@types/portscanner/-/portscanner-2.1.1.tgz#89d5094e16f3d941f20f3889dfa5d3a164b3dd3b" integrity sha512-1NsVIbgBKvrqxwtMN0V6CLji1ERwKSI/RWz0J3y++CzSwYNGBStCfpIFgxV3ZwxsDR5PoZqoUWhwraDm+Ztn0Q== -"@types/pug@2.0.5": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@types/pug/-/pug-2.0.5.tgz#69bc700934dd473c7ab97270bd2dbacefe562231" - integrity sha512-LOnASQoeNZMkzexRuyqcBBDZ6rS+rQxUMkmj5A0PkhhiSZivLIuz6Hxyr1mkGoEZEkk66faROmpMi4fFkrKsBA== +"@types/pug@2.0.6": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/pug/-/pug-2.0.6.tgz#f830323c88172e66826d0bde413498b61054b5a6" + integrity sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg== "@types/punycode@2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@types/punycode/-/punycode-2.1.0.tgz#89e4f3d09b3f92e87a80505af19be7e0c31d4e83" integrity sha512-PG5aLpW6PJOeV2fHRslP4IOMWn+G+Uq8CfnyJ+PDS8ndCbU+soO+fB3NKCKo0p/Jh2Y4aPaiQZsrOXFdzpcA6g== -"@types/qrcode@1.4.1": - version "1.4.1" - resolved "https://registry.yarnpkg.com/@types/qrcode/-/qrcode-1.4.1.tgz#0689f400c3a95d2db040c99c99834faa09ee9dc1" - integrity sha512-vxMyr7JM7tYPxu8vUE83NiosWX5DZieCyYeJRoOIg0pAkyofCBzknJ2ycUZkPGDFis2RS8GN/BeJLnRnAPxeCA== +"@types/qrcode@1.4.2": + version "1.4.2" + resolved "https://registry.yarnpkg.com/@types/qrcode/-/qrcode-1.4.2.tgz#7d7142d6fa9921f195db342ed08b539181546c74" + integrity sha512-7uNT9L4WQTNJejHTSTdaJhfBSCN73xtXaHFyBJ8TSwiLhe4PRuTue7Iph0s2nG9R/ifUaSnGhLUOZavlBEqDWQ== dependencies: "@types/node" "*" @@ -697,10 +736,12 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c" integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA== -"@types/ratelimiter@3.4.2": - version "3.4.2" - resolved "https://registry.yarnpkg.com/@types/ratelimiter/-/ratelimiter-3.4.2.tgz#adf1a6d0cbe72d42207efc510a9170602e23456c" - integrity sha512-iz+yyY+ViphaM8ZwrX1mUQzelIeC59zyaaLKTJ0YVOOCkCpIYpaysiIM4z5Xv9HdXYqIb80S+DhH7J22A0rW2w== +"@types/ratelimiter@3.4.3": + version "3.4.3" + resolved "https://registry.yarnpkg.com/@types/ratelimiter/-/ratelimiter-3.4.3.tgz#2159c234b9d75bcc2be39379f05c6af0a5e4a3b7" + integrity sha512-B/IRdHGcttRsDeDJ4+VFjzRA1mzqTxsYlg2X8GLQtTgRUMhQQc+bL8zFmuHhZkK4oA+Ldb4K1NogspNDxevWBA== + dependencies: + "@types/redis" "^2.8.0" "@types/readable-stream@^2.3.9": version "2.3.9" @@ -710,7 +751,14 @@ "@types/node" "*" safe-buffer "*" -"@types/redis@2.8.32": +"@types/redis@4.0.11": + version "4.0.11" + resolved "https://registry.yarnpkg.com/@types/redis/-/redis-4.0.11.tgz#0bb4c11ac9900a21ad40d2a6768ec6aaf651c0e1" + integrity sha512-bI+gth8La8Wg/QCR1+V1fhrL9+LZUSWfcqpOj2Kc80ZQ4ffbdL173vQd5wovmoV9i071FU9oP2g6etLuEwb6Rg== + dependencies: + redis "*" + +"@types/redis@^2.8.0": version "2.8.32" resolved "https://registry.yarnpkg.com/@types/redis/-/redis-2.8.32.tgz#1d3430219afbee10f8cfa389dad2571a05ecfb11" integrity sha512-7jkMKxcGq9p242exlbsVzuJb57KqHRhNl4dHoQu2Y5v9bCAbtIXXH0R3HleSQW4CTOqpHIYUW3t6tpUj4BVQ+w== @@ -736,10 +784,10 @@ dependencies: "@types/node" "*" -"@types/sanitize-html@2.5.0": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@types/sanitize-html/-/sanitize-html-2.5.0.tgz#bfef58fbcf2674b20ffcc23c3506faa68c3a13e3" - integrity sha512-PeFIEZsO9m1+ACJlXUaimgrR+5DEDiIXhz7Hso307jmq5Yz0lb5kDp8LiTr5dMMMliC/jNNx/qds7Zoxa4zexw== +"@types/sanitize-html@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@types/sanitize-html/-/sanitize-html-2.6.2.tgz#9c47960841b9def1e4c9dfebaaab010a3f6e97b9" + integrity sha512-7Lu2zMQnmHHQGKXVvCOhSziQMpa+R2hMHFefzbYoYMHeaXR0uXqNeOc3JeQQQ8/6Xa2Br/P1IQTLzV09xxAiUQ== dependencies: htmlparser2 "^6.0.0" @@ -756,10 +804,10 @@ "@types/express-serve-static-core" "*" "@types/mime" "*" -"@types/sharp@0.29.3": - version "0.29.3" - resolved "https://registry.yarnpkg.com/@types/sharp/-/sharp-0.29.3.tgz#54ceb81b68bb99a0e62db2b52f4fb0bea84878ac" - integrity sha512-83Xp05eK2hvfNnmKLr2Fz0C2A0jrr2TnSLqKRbkLTYuAu+Erj6mKQLoEMGafE73Om8p3q3ryZxtHFM/7hy4Adg== +"@types/sharp@0.29.5": + version "0.29.5" + resolved "https://registry.yarnpkg.com/@types/sharp/-/sharp-0.29.5.tgz#9c7032d30d138ad16dde6326beaff2af757b91b3" + integrity sha512-3TC+S3H5RwnJmLYMHrcdfNjz/CaApKmujjY9b6PU/pE6n0qfooi99YqXGWoW8frU9EWYj/XTI35Pzxa+ThAZ5Q== dependencies: "@types/node" "*" @@ -773,10 +821,10 @@ resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" integrity sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA== -"@types/speakeasy@2.0.6": - version "2.0.6" - resolved "https://registry.yarnpkg.com/@types/speakeasy/-/speakeasy-2.0.6.tgz#12540f7b64d08180393ae2c5c8c280866a85da61" - integrity sha512-2wIXZp5yJUddhsSZarYCZIakCvzwQgTVdtT29DYVdFzc0cHttanaQx9THRhtjY4kDqVaF2jhyFOEofozOioFdQ== +"@types/speakeasy@2.0.7": + version "2.0.7" + resolved "https://registry.yarnpkg.com/@types/speakeasy/-/speakeasy-2.0.7.tgz#cb087c501b3eef744a1ae620c19812dd1c3b2f3f" + integrity sha512-JEcOhN2SQCoX86ZfiZEe8px84sVJtivBXMZfOVyARTYEj0hrwwbj1nF0FwEL3nJSoEV6uTbcdLllMKBgAYHWCQ== dependencies: "@types/node" "*" @@ -795,10 +843,10 @@ resolved "https://registry.yarnpkg.com/@types/tinycolor2/-/tinycolor2-1.4.3.tgz#ed4a0901f954b126e6a914b4839c77462d56e706" integrity sha512-Kf1w9NE5HEgGxCRyIcRXR/ZYtDv0V8FVPtYHwLxl0O+maGX0erE77pQlD0gpP+/KByMZ87mOA79SjifhSB3PjQ== -"@types/tmp@0.2.2": - version "0.2.2" - resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.2.2.tgz#424537a3b91828cb26aaf697f21ae3cd1b69f7e7" - integrity sha512-MhSa0yylXtVMsyT8qFpHA1DLHj4DvQGH5ntxrhHSh8PxUVNi35Wk+P5hVgqbO2qZqOotqr9jaoPRL+iRjWYm/A== +"@types/tmp@0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.2.3.tgz#908bfb113419fd6a42273674c00994d40902c165" + integrity sha512-dDZH/tXzwjutnuk4UacGgFRwV+JSLaXL1ikvidfJprkb7L9Nx1njcRHHmi3Dsvt7pgqqTEeucQuOrWHPFgzVHA== "@types/tough-cookie@*": version "4.0.0" @@ -812,10 +860,10 @@ dependencies: source-map "^0.6.1" -"@types/uuid@8.3.1": - version "8.3.1" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.1.tgz#1a32969cf8f0364b3d8c8af9cc3555b7805df14f" - integrity sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg== +"@types/uuid@8.3.4": + version "8.3.4" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" + integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== "@types/web-push@3.3.2": version "3.3.2" @@ -869,10 +917,10 @@ dependencies: "@types/node" "*" -"@types/ws@8.2.0": - version "8.2.0" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.2.0.tgz#75faefbe2328f3b833cb8dc640658328990d04f3" - integrity sha512-cyeefcUCgJlEk+hk2h3N+MqKKsPViQgF5boi9TTHSK+PoR9KWBb/C5ccPcDyAqgsbAYHTwulch725DV84+pSpg== +"@types/ws@8.2.2": + version "8.2.2" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.2.2.tgz#7c5be4decb19500ae6b3d563043cd407bf366c21" + integrity sha512-NOn5eIcgWLOo6qW8AcuLZ7G8PycXu0xTxxkS6Q18VWFxgPUSOwV0pBj2a/4viNZVu25i7RIB7GttdkAIUUXOOg== dependencies: "@types/node" "*" @@ -881,13 +929,14 @@ resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.2.tgz#808c9fa7e4517274ed555fa158f2de4b4f468e71" integrity sha512-HrCIVMLjE1MOozVoD86622S7aunluLb2PJdPfb3nYiEtohm8mIB/vyv0Fd37AdeMFrTUQXEunw78YloMA3Qilg== -"@typescript-eslint/eslint-plugin@5.3.1": - version "5.3.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.3.1.tgz#d8ff412f10f54f6364e7fd7c1e70eb6767f434c3" - integrity sha512-cFImaoIr5Ojj358xI/SDhjog57OK2NqlpxwdcgyxDA3bJlZcJq5CPzUXtpD7CxI2Hm6ATU7w5fQnnkVnmwpHqw== +"@typescript-eslint/eslint-plugin@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.10.0.tgz#e90afea96dff8620892ad216b0e4ccdf8ee32d3a" + integrity sha512-XXVKnMsq2fuu9K2KsIxPUGqb6xAImz8MEChClbXmE3VbveFtBUU5bzM6IPVWqzyADIgdkS2Ws/6Xo7W2TeZWjQ== dependencies: - "@typescript-eslint/experimental-utils" "5.3.1" - "@typescript-eslint/scope-manager" "5.3.1" + "@typescript-eslint/scope-manager" "5.10.0" + "@typescript-eslint/type-utils" "5.10.0" + "@typescript-eslint/utils" "5.10.0" debug "^4.3.2" functional-red-black-tree "^1.0.1" ignore "^5.1.8" @@ -895,94 +944,69 @@ semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/experimental-utils@5.3.1": - version "5.3.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.3.1.tgz#bbd8f9b67b4d5fdcb9d2f90297d8fcda22561e05" - integrity sha512-RgFn5asjZ5daUhbK5Sp0peq0SSMytqcrkNfU4pnDma2D8P3ElZ6JbYjY8IMSFfZAJ0f3x3tnO3vXHweYg0g59w== - dependencies: - "@types/json-schema" "^7.0.9" - "@typescript-eslint/scope-manager" "5.3.1" - "@typescript-eslint/types" "5.3.1" - "@typescript-eslint/typescript-estree" "5.3.1" - eslint-scope "^5.1.1" - eslint-utils "^3.0.0" - -"@typescript-eslint/parser@5.1.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.1.0.tgz#6c7f837d210d2bc0a811e7ea742af414f4e00908" - integrity sha512-vx1P+mhCtYw3+bRHmbalq/VKP2Y3gnzNgxGxfEWc6OFpuEL7iQdAeq11Ke3Rhy8NjgB+AHsIWEwni3e+Y7djKA== +"@typescript-eslint/parser@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.10.0.tgz#8f59e036f5f1cffc178cacbd5ccdd02aeb96c91c" + integrity sha512-pJB2CCeHWtwOAeIxv8CHVGJhI5FNyJAIpx5Pt72YkK3QfEzt6qAlXZuyaBmyfOdM62qU0rbxJzNToPTVeJGrQw== dependencies: - "@typescript-eslint/scope-manager" "5.1.0" - "@typescript-eslint/types" "5.1.0" - "@typescript-eslint/typescript-estree" "5.1.0" + "@typescript-eslint/scope-manager" "5.10.0" + "@typescript-eslint/types" "5.10.0" + "@typescript-eslint/typescript-estree" "5.10.0" debug "^4.3.2" -"@typescript-eslint/scope-manager@5.1.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.1.0.tgz#6f1f26ad66a8f71bbb33b635e74fec43f76b44df" - integrity sha512-yYlyVjvn5lvwCL37i4hPsa1s0ORsjkauhTqbb8MnpvUs7xykmcjGqwlNZ2Q5QpoqkJ1odlM2bqHqJwa28qV6Tw== +"@typescript-eslint/scope-manager@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.10.0.tgz#bb5d872e8b9e36203908595507fbc4d3105329cb" + integrity sha512-tgNgUgb4MhqK6DoKn3RBhyZ9aJga7EQrw+2/OiDk5hKf3pTVZWyqBi7ukP+Z0iEEDMF5FDa64LqODzlfE4O/Dg== dependencies: - "@typescript-eslint/types" "5.1.0" - "@typescript-eslint/visitor-keys" "5.1.0" + "@typescript-eslint/types" "5.10.0" + "@typescript-eslint/visitor-keys" "5.10.0" -"@typescript-eslint/scope-manager@5.3.1": - version "5.3.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.3.1.tgz#3cfbfbcf5488fb2a9a6fbbe97963ee1e8d419269" - integrity sha512-XksFVBgAq0Y9H40BDbuPOTUIp7dn4u8oOuhcgGq7EoDP50eqcafkMVGrypyVGvDYHzjhdUCUwuwVUK4JhkMAMg== +"@typescript-eslint/type-utils@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.10.0.tgz#8524b9479c19c478347a7df216827e749e4a51e5" + integrity sha512-TzlyTmufJO5V886N+hTJBGIfnjQDQ32rJYxPaeiyWKdjsv2Ld5l8cbS7pxim4DeNs62fKzRSt8Q14Evs4JnZyQ== dependencies: - "@typescript-eslint/types" "5.3.1" - "@typescript-eslint/visitor-keys" "5.3.1" - -"@typescript-eslint/types@5.1.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.1.0.tgz#a8a75ddfc611660de6be17d3ad950302385607a9" - integrity sha512-sEwNINVxcB4ZgC6Fe6rUyMlvsB2jvVdgxjZEjQUQVlaSPMNamDOwO6/TB98kFt4sYYfNhdhTPBEQqNQZjMMswA== - -"@typescript-eslint/types@5.3.1": - version "5.3.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.3.1.tgz#afaa715b69ebfcfde3af8b0403bf27527912f9b7" - integrity sha512-bG7HeBLolxKHtdHG54Uac750eXuQQPpdJfCYuw4ZI3bZ7+GgKClMWM8jExBtp7NSP4m8PmLRM8+lhzkYnSmSxQ== - -"@typescript-eslint/typescript-estree@5.1.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.1.0.tgz#132aea34372df09decda961cb42457433aa6e83d" - integrity sha512-SSz+l9YrIIsW4s0ZqaEfnjl156XQ4VRmJsbA0ZE1XkXrD3cRpzuZSVCyqeCMR3EBjF27IisWakbBDGhGNIOvfQ== - dependencies: - "@typescript-eslint/types" "5.1.0" - "@typescript-eslint/visitor-keys" "5.1.0" + "@typescript-eslint/utils" "5.10.0" debug "^4.3.2" - globby "^11.0.4" - is-glob "^4.0.3" - semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/typescript-estree@5.3.1": - version "5.3.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.3.1.tgz#50cc4bfb93dc31bc75e08ae52e29fcb786d606ec" - integrity sha512-PwFbh/PKDVo/Wct6N3w+E4rLZxUDgsoII/GrWM2A62ETOzJd4M6s0Mu7w4CWsZraTbaC5UQI+dLeyOIFF1PquQ== +"@typescript-eslint/types@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.10.0.tgz#beb3cb345076f5b088afe996d57bcd1dfddaa75c" + integrity sha512-wUljCgkqHsMZbw60IbOqT/puLfyqqD5PquGiBo1u1IS3PLxdi3RDGlyf032IJyh+eQoGhz9kzhtZa+VC4eWTlQ== + +"@typescript-eslint/typescript-estree@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.10.0.tgz#4be24a3dea0f930bb1397c46187d0efdd955a224" + integrity sha512-x+7e5IqfwLwsxTdliHRtlIYkgdtYXzE0CkFeV6ytAqq431ZyxCFzNMNR5sr3WOlIG/ihVZr9K/y71VHTF/DUQA== dependencies: - "@typescript-eslint/types" "5.3.1" - "@typescript-eslint/visitor-keys" "5.3.1" + "@typescript-eslint/types" "5.10.0" + "@typescript-eslint/visitor-keys" "5.10.0" debug "^4.3.2" globby "^11.0.4" is-glob "^4.0.3" semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/visitor-keys@5.1.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.1.0.tgz#e01a01b27eb173092705ae983aa1451bd1842630" - integrity sha512-uqNXepKBg81JVwjuqAxYrXa1Ql/YDzM+8g/pS+TCPxba0wZttl8m5DkrasbfnmJGHs4lQ2jTbcZ5azGhI7kK+w== +"@typescript-eslint/utils@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.10.0.tgz#c3d152a85da77c400e37281355561c72fb1b5a65" + integrity sha512-IGYwlt1CVcFoE2ueW4/ioEwybR60RAdGeiJX/iDAw0t5w0wK3S7QncDwpmsM70nKgGTuVchEWB8lwZwHqPAWRg== dependencies: - "@typescript-eslint/types" "5.1.0" - eslint-visitor-keys "^3.0.0" + "@types/json-schema" "^7.0.9" + "@typescript-eslint/scope-manager" "5.10.0" + "@typescript-eslint/types" "5.10.0" + "@typescript-eslint/typescript-estree" "5.10.0" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" -"@typescript-eslint/visitor-keys@5.3.1": - version "5.3.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.3.1.tgz#c2860ff22939352db4f3806f34b21d8ad00588ba" - integrity sha512-3cHUzUuVTuNHx0Gjjt5pEHa87+lzyqOiHXy/Gz+SJOCW1mpw9xQHIIEwnKn+Thph1mgWyZ90nboOcSuZr/jTTQ== +"@typescript-eslint/visitor-keys@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.10.0.tgz#770215497ad67cd15a572b52089991d5dfe06281" + integrity sha512-GMxj0K1uyrFLPKASLmZzCuSddmjZVbVj3Ouy5QVuIGKZopxvOr24JsS7gruz6C3GExE01mublZ3mIBOaon9zuQ== dependencies: - "@typescript-eslint/types" "5.3.1" + "@typescript-eslint/types" "5.10.0" eslint-visitor-keys "^3.0.0" "@ungap/promise-all-settled@1.1.2": @@ -1189,12 +1213,12 @@ acorn@^8.4.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.4.1.tgz#56c36251fc7cabc7096adc18f05afe814321a28c" integrity sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA== -acorn@^8.5.0: - version "8.5.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" - integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== +acorn@^8.7.0: + version "8.7.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" + integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== -agent-base@6: +agent-base@6, agent-base@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== @@ -1233,12 +1257,7 @@ ajv@^6.10.0, ajv@^6.12.4, ajv@^6.12.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -alphanum-sort@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" - integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM= - -ansi-colors@4.1.1, ansi-colors@^4.1.1: +ansi-colors@4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== @@ -1253,11 +1272,6 @@ ansi-regex@^3.0.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= -ansi-regex@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" - integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== - ansi-regex@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" @@ -1268,7 +1282,7 @@ ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== -ansi-styles@^3.2.0, ansi-styles@^3.2.1: +ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== @@ -1311,6 +1325,11 @@ aproba@^1.0.3: resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== +"aproba@^1.0.3 || ^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" + integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== + archiver-utils@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-2.1.0.tgz#e8a460e94b693c3e3da182a098ca6285ba9249e2" @@ -1340,6 +1359,14 @@ archiver@5.3.0: tar-stream "^2.2.0" zip-stream "^4.1.0" +are-we-there-yet@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c" + integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + are-we-there-yet@~1.1.2: version "1.1.5" resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" @@ -1353,13 +1380,6 @@ arg@^4.1.0: resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - argparse@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" @@ -1449,11 +1469,6 @@ autobind-decorator@2.4.0, autobind-decorator@^2.4.0: resolved "https://registry.yarnpkg.com/autobind-decorator/-/autobind-decorator-2.4.0.tgz#ea9e1c98708cf3b5b356f7cf9f10f265ff18239c" integrity sha512-OGYhWUO72V6DafbF8PM8rm3EPbfuyMZcJhtm5/n26IDwO18pohE4eNazLoCGhPiXOCD0gEGmrbU3849QvM8bbw== -autosize@4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/autosize/-/autosize-4.0.4.tgz#924f13853a466b633b9309330833936d8bccce03" - integrity sha512-5yxLQ22O0fCRGoxGfeLSNt3J8LB1v+umtpMnPW6XjkTWXKoN0AmXAIhelJcDtFT/Y/wYWmfE+oqU10Q0b8FhaQ== - autwh@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/autwh/-/autwh-0.1.0.tgz#24a5300923309d105133401a2568f9c8ab7d7e03" @@ -1461,27 +1476,27 @@ autwh@0.1.0: dependencies: oauth "0.9.15" -aws-sdk@2.1013.0: - version "2.1013.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1013.0.tgz#85babc473b0bc90cc1160eb48baf616ddb86e346" - integrity sha512-TXxkp/meAdofpC15goFpNuur7fvh/mcMRfHJoP1jYzTtD0wcoB4FK16GLcny0uDYgkQgZuiO9QYv3Rq5bhGCqQ== +aws-sdk@2.1061.0: + version "2.1061.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1061.0.tgz#79c75e6856e5a59e0857d0d066a8ff5ff5e0d752" + integrity sha512-T29yV+EPo4Fis9hAArxAXS/u6utKnlBq3DEu85LTSIA8i6e6Xg7e9u7Rveo8DmrlVrf7EGCNThaeF9WERHnwLg== dependencies: buffer "4.9.2" events "1.1.1" ieee754 "1.1.13" - jmespath "0.15.0" + jmespath "0.16.0" querystring "0.2.0" sax "1.2.1" url "0.10.3" uuid "3.3.2" xml2js "0.4.19" -axios@^0.19.2: - version "0.19.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" - integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA== +axios@^0.24.0: + version "0.24.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.24.0.tgz#804e6fa1e4b9c5288501dd9dff56a7a0940d20d6" + integrity sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA== dependencies: - follow-redirects "1.5.10" + follow-redirects "^1.14.4" babel-walk@3.0.0-canary-5: version "3.0.0-canary-5" @@ -1527,6 +1542,11 @@ big-integer@^1.6.16: resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e" integrity sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w== +big-integer@^1.6.17: + version "1.6.51" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" + integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== + big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" @@ -1537,6 +1557,14 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== +binary@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79" + integrity sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk= + dependencies: + buffers "~0.1.1" + chainsaw "~0.1.0" + bl@^4.0.1, bl@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/bl/-/bl-4.0.3.tgz#12d6287adc29080e22a705e5764b2a9522cdc489" @@ -1551,6 +1579,11 @@ bluebird@^3.7.2: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== +bluebird@~3.4.1: + version "3.4.7" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3" + integrity sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM= + blurhash@1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/blurhash/-/blurhash-1.1.4.tgz#a7010ceb3019cd2c9809b17c910ebf6175d29244" @@ -1561,7 +1594,7 @@ bn.js@^4.0.0: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== -boolbase@^1.0.0, boolbase@~1.0.0: +boolbase@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= @@ -1581,16 +1614,17 @@ braces@^3.0.1, braces@~3.0.2: dependencies: fill-range "^7.0.1" -broadcast-channel@4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-4.5.0.tgz#d4717c493e219908fcb7f2f9078fe0baf95b77c1" - integrity sha512-jp+VPlQ1HyR0CM3uIYUrdpXupBvhTMFRkjR6mEmt5W4HaGDPFEzrO2Jqvi2PZ6zCC4zwLeco7CC5EUJPrVH8Tw== +broadcast-channel@4.9.0: + version "4.9.0" + resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-4.9.0.tgz#8af337d4ea19aeb6b819ec2eb3dda942b28c724c" + integrity sha512-xWzFb3wrOZGJF2kOSs2D3KvHXdLDMVb+WypEIoNvwblcHgUBydVy65pDJ9RS4WN9Kyvs0UVQuCCzfKme0G6Qjw== dependencies: "@babel/runtime" "^7.16.0" detect-node "^2.1.0" microseconds "0.2.0" nano-time "1.0.0" oblivious-set "1.0.0" + p-queue "6.6.2" rimraf "3.0.2" unload "2.3.1" @@ -1604,7 +1638,7 @@ browser-stdout@1.3.1: resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== -browserslist@^4.0.0, browserslist@^4.14.5: +browserslist@^4.14.5: version "4.16.3" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.3.tgz#340aa46940d7db878748567c5dea24a48ddf3717" integrity sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw== @@ -1615,48 +1649,6 @@ browserslist@^4.0.0, browserslist@^4.14.5: escalade "^3.1.1" node-releases "^1.1.70" -browserslist@^4.16.0: - version "4.16.4" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.4.tgz#7ebf913487f40caf4637b892b268069951c35d58" - integrity sha512-d7rCxYV8I9kj41RH8UKYnvDYCRENUlHRgyXy/Rhr/1BaeLGfiCptEdFE8MIrvGfWbBFNjVYx76SQWvNX1j+/cQ== - dependencies: - caniuse-lite "^1.0.30001208" - colorette "^1.2.2" - electron-to-chromium "^1.3.712" - escalade "^3.1.1" - node-releases "^1.1.71" - -browserslist@^4.16.6: - version "4.16.6" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.6.tgz#d7901277a5a88e554ed305b183ec9b0c08f66fa2" - integrity sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ== - dependencies: - caniuse-lite "^1.0.30001219" - colorette "^1.2.2" - electron-to-chromium "^1.3.723" - escalade "^3.1.1" - node-releases "^1.1.71" - -bs-logger@0.x: - version "0.2.6" - resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" - integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== - dependencies: - fast-json-stable-stringify "2.x" - -buffer-alloc-unsafe@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" - integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg== - -buffer-alloc@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec" - integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow== - dependencies: - buffer-alloc-unsafe "^1.1.0" - buffer-fill "^1.0.0" - buffer-crc32@^0.2.1, buffer-crc32@^0.2.13: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" @@ -1667,21 +1659,16 @@ buffer-equal-constant-time@1.0.1: resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= -buffer-fill@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" - integrity sha1-+PeLdniYiO858gXNY39o5wISKyw= - -buffer-from@1.x: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -buffer-from@^1.0.0, buffer-from@^1.1.1: +buffer-from@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== +buffer-indexof-polyfill@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz#d2732135c5999c64b277fcf9b1abe3498254729c" + integrity sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A== + buffer-writer@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-2.0.0.tgz#ce7eb81a38f7829db09c873f2fbb792c0c98ec04" @@ -1696,7 +1683,7 @@ buffer@4.9.2: ieee754 "^1.1.4" isarray "^1.0.0" -buffer@^5.4.3, buffer@^5.5.0: +buffer@^5.5.0: version "5.6.0" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== @@ -1712,6 +1699,11 @@ buffer@^6.0.3: base64-js "^1.3.1" ieee754 "^1.2.1" +buffers@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb" + integrity sha1-skV5w77U1tOWru5tmorn9Ugqt7s= + bufferutil@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.1.tgz#3a177e8e5819a1243fe16b63a199951a7ad8d4a7" @@ -1719,10 +1711,10 @@ bufferutil@^4.0.1: dependencies: node-gyp-build "~3.7.0" -bull@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/bull/-/bull-4.1.0.tgz#ff8f628694e7dbbdf89b6a6860a1f33e990527fd" - integrity sha512-rQcLuAmzZIv1dHJO/yKrWu497xcTxMpYeTEBfpStrJFZ1UZpBSGHSx+defbtFVeGEeY8Pn0aMGRvRtldUBVUyQ== +bull@4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/bull/-/bull-4.2.1.tgz#c5a7e1496c7903274ce90192e4e5cb18f6c866c0" + integrity sha512-YkCQZMOub++siHw3SbYYXZ5xGEn6Tt3BPoCVq/irPNCxUqUYzta8yDlXyyAsfMKMVj0M7PcnynUabfMf9PFpOA== dependencies: cron-parser "^2.13.0" debuglog "^1.0.0" @@ -1730,9 +1722,7 @@ bull@4.1.0: ioredis "^4.27.0" lodash "^4.17.21" p-timeout "^3.2.0" - promise.prototype.finally "^3.1.2" semver "^7.3.2" - util.promisify "^1.0.1" uuid "^8.3.0" busboy@^0.2.11: @@ -1748,11 +1738,12 @@ bytes@3.1.0, bytes@^3.1.0: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== -cacache@^15.0.5: - version "15.1.0" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.1.0.tgz#164c2f857ee606e4cc793c63018fefd0ea5eba7b" - integrity sha512-mfx0C+mCfWjD1PnwQ9yaOrwG1ou9FkKnx0SvzUHWdFt7r7GaRtzT+9M8HAvLu62zIHtnpQ/1m93nWNDCckJGXQ== +cacache@^15.2.0: + version "15.3.0" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.3.0.tgz#dc85380fb2f556fe3dda4c719bfa0ec875a7f1eb" + integrity sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ== dependencies: + "@npmcli/fs" "^1.0.0" "@npmcli/move-file" "^1.0.1" chownr "^2.0.0" fs-minipass "^2.0.0" @@ -1830,36 +1821,11 @@ camelcase@^6.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== -caniuse-api@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" - integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== - dependencies: - browserslist "^4.0.0" - caniuse-lite "^1.0.0" - lodash.memoize "^4.1.2" - lodash.uniq "^4.5.0" - -caniuse-lite@^1.0.0: - version "1.0.30001048" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001048.tgz#4bb4f1bc2eb304e5e1154da80b93dee3f1cf447e" - integrity sha512-g1iSHKVxornw0K8LG9LLdf+Fxnv7T1Z+mMsf0/YYLclQX4Cd522Ap0Lrw6NFqHgezit78dtyWxzlV2Xfc7vgRg== - caniuse-lite@^1.0.30001181: version "1.0.30001191" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001191.tgz#bacb432b6701f690c8c5f7c680166b9a9f0843d9" integrity sha512-xJJqzyd+7GCJXkcoBiQ1GuxEiOBCLQ0aVW9HMekifZsAVGdj5eJ4mFB9fEhSHipq9IOk/QXFJUiIr9lZT+EsGw== -caniuse-lite@^1.0.30001208: - version "1.0.30001208" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001208.tgz#a999014a35cebd4f98c405930a057a0d75352eb9" - integrity sha512-OE5UE4+nBOro8Dyvv0lfx+SRtfVIOM9uhKqFmJeUbGriqhhStgp1A0OyBpgy3OUF8AhYCT+PVwPC1gMl2ZcQMA== - -caniuse-lite@^1.0.30001219: - version "1.0.30001230" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001230.tgz#8135c57459854b2240b57a4a6786044bdc5a9f71" - integrity sha512-5yBd5nWCBS+jWKTcHOzXwo5xzcj4ePE/yjtkZyUV1BTUmrBaA9MRGC+e7mxnqXSA90CmCA8L3eKLaSUkt099IQ== - canonicalize@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/canonicalize/-/canonicalize-1.0.1.tgz#657b4f3fa38a6ecb97a9e5b7b26d7a19cc6e0da9" @@ -1880,6 +1846,13 @@ cbor@8.1.0: dependencies: nofilter "^3.1.0" +chainsaw@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/chainsaw/-/chainsaw-0.1.0.tgz#5eab50b28afe58074d0d58291388828b5e5fbc98" + integrity sha1-XqtQsor+WAdNDVgpE4iCi15fvJg= + dependencies: + traverse ">=0.3.0 <0.4" + chalk@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.0.0.tgz#6e98081ed2d17faab615eb52ac66ec1fe6209e72" @@ -1925,23 +1898,6 @@ character-parser@^2.2.0: dependencies: is-regex "^1.0.3" -chart.js@3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.6.0.tgz#a87fce8431d4e7c5523d721f487f53aada1e42fe" - integrity sha512-iOzzDKePL+bj+ccIsVAgWQehCXv8xOKGbaU2fO/myivH736zcx535PGJzQGanvcSGVOqX6yuLZsN3ygcQ35UgQ== - -chartjs-adapter-date-fns@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/chartjs-adapter-date-fns/-/chartjs-adapter-date-fns-2.0.0.tgz#5e53b2f660b993698f936f509c86dddf9ed44c6b" - integrity sha512-rmZINGLe+9IiiEB0kb57vH3UugAtYw33anRiw5kS2Tu87agpetDDoouquycWc9pRsKtQo5j+vLsYHyr8etAvFw== - -chartjs-plugin-zoom@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/chartjs-plugin-zoom/-/chartjs-plugin-zoom-1.1.1.tgz#8a28923a17fcb5eb57a0dc94c5113bf402677647" - integrity sha512-1q54WOzK7FtAjkbemQeqvmFUV0btNYIQny2HbQ6Awq9wUtCz7Zmj6vIgp3C1DYMQwN0nqgpC3vnApqiwI7cSdQ== - dependencies: - hammerjs "^2.0.8" - cheerio@0.22.0: version "0.22.0" resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-0.22.0.tgz#a9baa860a3f9b595a6b81b1a86873121ed3a269e" @@ -2013,14 +1969,14 @@ cli-highlight@2.1.11, cli-highlight@^2.1.11: parse5-htmlparser2-tree-adapter "^6.0.0" yargs "^16.0.0" -cliui@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" - integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== +cliui@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" + integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== dependencies: - string-width "^3.1.0" - strip-ansi "^5.2.0" - wrap-ansi "^5.1.0" + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^6.2.0" cliui@^7.0.2: version "7.0.4" @@ -2038,7 +1994,7 @@ clone-response@^1.0.2: dependencies: mimic-response "^1.0.0" -cluster-key-slot@^1.1.0: +cluster-key-slot@1.1.0, cluster-key-slot@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz#30474b2a981fb12172695833052bc0d01336d10d" integrity sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw== @@ -2105,6 +2061,11 @@ color-string@^1.6.0: color-name "^1.0.0" simple-swizzle "^0.2.2" +color-support@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" + integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== + color@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/color/-/color-4.0.1.tgz#21df44cd10245a91b1ccf5ba031609b0e10e7d67" @@ -2113,12 +2074,7 @@ color@^4.0.1: color-convert "^2.0.1" color-string "^1.6.0" -colord@^2.9.1: - version "2.9.1" - resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.1.tgz#c961ea0efeb57c9f0f4834458f26cb9cc4a3f90e" - integrity sha512-4LBMSt09vR0uLnPVkOUBnmxgoaeN4ewRbx801wY/bXcltXfpR/G46OdWn96XpYmCWuYvO46aBZP4NgX8HpNAcw== - -colorette@^1.2.0, colorette@^1.2.1, colorette@^1.2.2: +colorette@^1.2.0, colorette@^1.2.1: version "1.2.2" resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== @@ -2135,21 +2091,11 @@ commander@^2.19.0, commander@^2.20.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@^7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" - integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== - commander@^8.2.0: version "8.3.0" resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== -compare-versions@3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62" - integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA== - compress-commons@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-4.1.1.tgz#df2a09a7ed17447642bad10a85cc9a19e5c42a7d" @@ -2192,7 +2138,7 @@ config-chain@^1.1.12: ini "^1.3.4" proto-list "~1.2.1" -console-control-strings@^1.0.0, console-control-strings@~1.1.0: +console-control-strings@^1.0.0, console-control-strings@^1.1.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= @@ -2212,7 +2158,14 @@ constantinople@^4.0.1: "@babel/parser" "^7.6.0" "@babel/types" "^7.6.1" -content-disposition@0.5.3, content-disposition@~0.5.2: +content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +content-disposition@~0.5.2: version "0.5.3" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== @@ -2287,43 +2240,6 @@ cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" -css-color-names@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-1.0.1.tgz#6ff7ee81a823ad46e020fa2fd6ab40a887e2ba67" - integrity sha512-/loXYOch1qU1biStIFsHH8SxTmOseh1IJqFvy8IujXOm1h+QjUdDhkzOrR5HG8K8mlxREj0yfi8ewCHx0eMxzA== - -css-declaration-sorter@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.0.3.tgz#9dfd8ea0df4cc7846827876fafb52314890c21a9" - integrity sha512-52P95mvW1SMzuRZegvpluT6yEv0FqQusydKQPZsNN5Q7hh8EwQvN8E2nwuJ16BBvNN6LcoIZXu/Bk58DAhrrxw== - dependencies: - timsort "^0.3.0" - -css-loader@6.5.1: - version "6.5.1" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.5.1.tgz#0c43d4fbe0d97f699c91e9818cb585759091d1b1" - integrity sha512-gEy2w9AnJNnD9Kuo4XAP9VflW/ujKoS9c/syO+uWMlm5igc7LysKzPXaDoR2vroROkSwsTS2tGr1yGGEbZOYZQ== - dependencies: - icss-utils "^5.1.0" - postcss "^8.2.15" - postcss-modules-extract-imports "^3.0.0" - postcss-modules-local-by-default "^4.0.0" - postcss-modules-scope "^3.0.0" - postcss-modules-values "^4.0.0" - postcss-value-parser "^4.1.0" - semver "^7.3.5" - -css-select@^4.1.3: - version "4.1.3" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.1.3.tgz#a70440f70317f2669118ad74ff105e65849c7067" - integrity sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA== - dependencies: - boolbase "^1.0.0" - css-what "^5.0.0" - domhandler "^4.2.0" - domutils "^2.6.0" - nth-check "^2.0.0" - css-select@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" @@ -2334,86 +2250,11 @@ css-select@~1.2.0: domutils "1.5.1" nth-check "~1.0.1" -css-tree@^1.1.2, css-tree@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" - integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== - dependencies: - mdn-data "2.0.14" - source-map "^0.6.1" - css-what@2.1: version "2.1.3" resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg== -css-what@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.1.0.tgz#3f7b707aadf633baf62c2ceb8579b545bb40f7fe" - integrity sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw== - -cssesc@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" - integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== - -cssnano-preset-default@^5.1.6: - version "5.1.6" - resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.1.6.tgz#1bdb83be6a6b1fee6dc5e9ec2e61286bcadcc7a6" - integrity sha512-X2nDeNGBXc0486oHjT2vSj+TdeyVsxRvJUxaOH50hOM6vSDLkKd0+59YXpSZRInJ4sNtBOykS4KsPfhdrU/35w== - dependencies: - css-declaration-sorter "^6.0.3" - cssnano-utils "^2.0.1" - postcss-calc "^8.0.0" - postcss-colormin "^5.2.1" - postcss-convert-values "^5.0.2" - postcss-discard-comments "^5.0.1" - postcss-discard-duplicates "^5.0.1" - postcss-discard-empty "^5.0.1" - postcss-discard-overridden "^5.0.1" - postcss-merge-longhand "^5.0.3" - postcss-merge-rules "^5.0.2" - postcss-minify-font-values "^5.0.1" - postcss-minify-gradients "^5.0.3" - postcss-minify-params "^5.0.1" - postcss-minify-selectors "^5.1.0" - postcss-normalize-charset "^5.0.1" - postcss-normalize-display-values "^5.0.1" - postcss-normalize-positions "^5.0.1" - postcss-normalize-repeat-style "^5.0.1" - postcss-normalize-string "^5.0.1" - postcss-normalize-timing-functions "^5.0.1" - postcss-normalize-unicode "^5.0.1" - postcss-normalize-url "^5.0.2" - postcss-normalize-whitespace "^5.0.1" - postcss-ordered-values "^5.0.2" - postcss-reduce-initial "^5.0.1" - postcss-reduce-transforms "^5.0.1" - postcss-svgo "^5.0.3" - postcss-unique-selectors "^5.0.1" - -cssnano-utils@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-2.0.1.tgz#8660aa2b37ed869d2e2f22918196a9a8b6498ce2" - integrity sha512-i8vLRZTnEH9ubIyfdZCAdIdgnHAUeQeByEeQ2I7oTilvP9oHO6RScpeq3GsFUVqeB8uZgOQ9pw8utofNn32hhQ== - -cssnano@5.0.10: - version "5.0.10" - resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.0.10.tgz#92207eb7c9c6dc08d318050726f9fad0adf7220b" - integrity sha512-YfNhVJJ04imffOpbPbXP2zjIoByf0m8E2c/s/HnvSvjXgzXMfgopVjAEGvxYOjkOpWuRQDg/OZFjO7WW94Ri8w== - dependencies: - cssnano-preset-default "^5.1.6" - is-resolvable "^1.1.0" - lilconfig "^2.0.3" - yaml "^1.10.2" - -csso@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" - integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== - dependencies: - css-tree "^1.1.2" - cssom@^0.4.4: version "0.4.4" resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" @@ -2451,6 +2292,11 @@ data-uri-to-buffer@^3.0.1: resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz#594b8973938c5bc2c33046535785341abc4f3636" integrity sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og== +data-uri-to-buffer@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz#b5db46aea50f6176428ac05b73be39a57701a64b" + integrity sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA== + data-urls@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" @@ -2460,11 +2306,6 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" -date-fns@2.25.0: - version "2.25.0" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.25.0.tgz#8c5c8f1d958be3809a9a03f4b742eba894fc5680" - integrity sha512-ovYRFnTrbGPD4nqaEqescPEv1mNwvt+UTqI3Ay9SzNtey9NZnYu6E2qCcBBgJ6/2VF1zGGygpyTDITqpQQ5e+w== - dateformat@4.5.1: version "4.5.1" resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-4.5.1.tgz#c20e7a9ca77d147906b6dc2261a8be0a5bd2173c" @@ -2491,13 +2332,6 @@ debug@4.3.3: dependencies: ms "2.1.2" -debug@=3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== - dependencies: - ms "2.0.0" - debug@^3.2.6: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" @@ -2539,13 +2373,6 @@ decimal.js@^10.2.1: resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.1.tgz#238ae7b0f0c793d3e3cea410108b35a2c01426a3" integrity sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw== -decompress-response@^4.2.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986" - integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw== - dependencies: - mimic-response "^2.0.0" - decompress-response@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" @@ -2553,17 +2380,15 @@ decompress-response@^6.0.0: dependencies: mimic-response "^3.1.0" -deep-email-validator@0.1.18: - version "0.1.18" - resolved "https://registry.yarnpkg.com/deep-email-validator/-/deep-email-validator-0.1.18.tgz#a072a93f28e11863cc6b9ca3ae964e0e45b3ece8" - integrity sha512-eo2WEUidQvppg6Qdek8iwOqmXvaxRJ2D2VJKbIOwUgLZNFveDDdJMBsFc+yq0S+lILEUcmzrJRrCWbyoe7QUzQ== +deep-email-validator@0.1.21: + version "0.1.21" + resolved "https://registry.yarnpkg.com/deep-email-validator/-/deep-email-validator-0.1.21.tgz#5d0120fe1aeae83ab7cb39378a40a381b681219f" + integrity sha512-DBAmMzbr+MAubXQ+TS9tZuPwLcdKscb8YzKZiwoLqF3NmaeEgXvSSHhZ0EXOFeKFE2FNWC4mNXCyiQ/JdFXUwg== dependencies: "@types/disposable-email-domains" "^1.0.1" - axios "^0.19.2" - disposable-email-domains "^1.0.53" - lodash "^4.17.15" + axios "^0.24.0" + disposable-email-domains "^1.0.59" mailcheck "^1.1.1" - ts-jest "^25.2.1" deep-equal@~1.0.1: version "1.0.1" @@ -2590,7 +2415,7 @@ defer-to-connect@^2.0.0: resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.0.tgz#83d6b199db041593ac84d781b5222308ccf4c2c1" integrity sha512-bYL2d05vOSf1JEZNx5vSAtPuBMkX8K9EUutg7zlKvTqKXHt7RhWJFbmd7qakVuf13i+IkGmp6FwSsONOf6VYIg== -define-properties@^1.1.2, define-properties@^1.1.3: +define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== @@ -2677,10 +2502,10 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" -disposable-email-domains@^1.0.53: - version "1.0.58" - resolved "https://registry.yarnpkg.com/disposable-email-domains/-/disposable-email-domains-1.0.58.tgz#ac9c879c02c4f0898bfb6c0c80b959c0b0b7bc51" - integrity sha512-frnNCPqTjk6t/sosPoco6EIFHbP9SazHQkeltJNfZeUyNgewaVf+kFjEfVkVDVd436Vln43YElJPb8JozhBs7Q== +disposable-email-domains@^1.0.59: + version "1.0.59" + resolved "https://registry.yarnpkg.com/disposable-email-domains/-/disposable-email-domains-1.0.59.tgz#8b3670667dcef9d0d21b224de283d56d468913c2" + integrity sha512-45NbOP1Oboaddf0pD5mGnT+1msEifY6VUcR9Msq4zBHk2EeGv9PxiwuoynIfdGID1BSFR3U3egPfMbERkqXxUQ== doctrine@^2.1.0: version "2.1.0" @@ -2785,7 +2610,7 @@ domutils@^1.5.1: dom-serializer "0" domelementtype "1" -domutils@^2.5.2, domutils@^2.6.0: +domutils@^2.5.2: version "2.8.0" resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== @@ -2799,6 +2624,13 @@ dotenv@^8.2.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== +duplexer2@~0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" + integrity sha1-ixLauHjA1p4+eJEFFmKjL8a93ME= + dependencies: + readable-stream "^2.0.2" + ecc-jsbn@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" @@ -2834,21 +2666,6 @@ electron-to-chromium@^1.3.649: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.672.tgz#3a6e335016dab4bc584d5292adc4f98f54541f6a" integrity sha512-gFQe7HBb0lbOMqK2GAS5/1F+B0IMdYiAgB9OT/w1F4M7lgJK2aNOMNOM622aEax+nS1cTMytkiT0uMOkbtFmHw== -electron-to-chromium@^1.3.712: - version "1.3.717" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.717.tgz#78d4c857070755fb58ab64bcc173db1d51cbc25f" - integrity sha512-OfzVPIqD1MkJ7fX+yTl2nKyOE4FReeVfMCzzxQS+Kp43hZYwHwThlGP+EGIZRXJsxCM7dqo8Y65NOX/HP12iXQ== - -electron-to-chromium@^1.3.723: - version "1.3.742" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.742.tgz#7223215acbbd3a5284962ebcb6df85d88b95f200" - integrity sha512-ihL14knI9FikJmH2XUIDdZFWJxvr14rPSdOhJ7PpS27xbz8qmaRwCwyg/bmFwjWKmWK9QyamiCZVCvXm5CH//Q== - -emoji-regex@^7.0.1: - version "7.0.3" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" - integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== - emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -2859,6 +2676,11 @@ emojis-list@^3.0.0: resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== +encode-utf8@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/encode-utf8/-/encode-utf8-1.0.3.tgz#f30fdd31da07fb596f281beb2f6b027851994cda" + integrity sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw== + encodeurl@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" @@ -2894,13 +2716,6 @@ enhanced-resolve@^5.7.0: graceful-fs "^4.2.4" tapable "^2.2.0" -enquirer@^2.3.5: - version "2.3.6" - resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" - integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== - dependencies: - ansi-colors "^4.1.1" - entities@^1.1.1, entities@~1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" @@ -2921,23 +2736,6 @@ err-code@^2.0.2: resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== -es-abstract@^1.17.0-next.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.5: - version "1.17.5" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.5.tgz#d8c9d1d66c8981fb9200e2251d799eee92774ae9" - integrity sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg== - dependencies: - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.1.5" - is-regex "^1.0.5" - object-inspect "^1.7.0" - object-keys "^1.1.1" - object.assign "^4.1.0" - string.prototype.trimleft "^2.1.1" - string.prototype.trimright "^2.1.1" - es-abstract@^1.19.0, es-abstract@^1.19.1: version "1.19.1" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.19.1.tgz#d4885796876916959de78edaa0df456627115ec3" @@ -3049,43 +2847,32 @@ eslint-import-resolver-node@^0.3.6: debug "^3.2.7" resolve "^1.20.0" -eslint-module-utils@^2.7.1: - version "2.7.1" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.1.tgz#b435001c9f8dd4ab7f6d0efcae4b9696d4c24b7c" - integrity sha512-fjoetBXQZq2tSTWZ9yWVl2KuFrTZZH3V+9iD1V1RfpDgxzJR+mPd/KZmMiA8gbPqdBzpNiEHOuT7IYEWxrH0zQ== +eslint-module-utils@^2.7.2: + version "2.7.2" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.2.tgz#1d0aa455dcf41052339b63cada8ab5fd57577129" + integrity sha512-zquepFnWCY2ISMFwD/DqzaM++H+7PDzOpUvotJWm/y1BAFt5R4oeULgdrTejKqLkz7MA/tgstsUMNYc7wNdTrg== dependencies: debug "^3.2.7" find-up "^2.1.0" - pkg-dir "^2.0.0" -eslint-plugin-import@2.25.3: - version "2.25.3" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.25.3.tgz#a554b5f66e08fb4f6dc99221866e57cfff824766" - integrity sha512-RzAVbby+72IB3iOEL8clzPLzL3wpDrlwjsTBAQXgyp5SeTqqY+0bFubwuo+y/HLhNZcXV4XqTBO4LGsfyHIDXg== +eslint-plugin-import@2.25.4: + version "2.25.4" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz#322f3f916a4e9e991ac7af32032c25ce313209f1" + integrity sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA== dependencies: array-includes "^3.1.4" array.prototype.flat "^1.2.5" debug "^2.6.9" doctrine "^2.1.0" eslint-import-resolver-node "^0.3.6" - eslint-module-utils "^2.7.1" + eslint-module-utils "^2.7.2" has "^1.0.3" is-core-module "^2.8.0" is-glob "^4.0.3" minimatch "^3.0.4" object.values "^1.1.5" resolve "^1.20.0" - tsconfig-paths "^3.11.0" - -eslint-plugin-vue@8.0.3: - version "8.0.3" - resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-8.0.3.tgz#791cc4543940319e612ea61a1d779e8c87cf749a" - integrity sha512-Rlhhy5ltzde0sRwSkqHuNePTXLMMaJ5+qsQubM4RYloYsQ8cXlnJT5MDaCzSirkGADipOHtmQXIbbPFAzUrADg== - dependencies: - eslint-utils "^3.0.0" - natural-compare "^1.4.0" - semver "^7.3.5" - vue-eslint-parser "^8.0.1" + tsconfig-paths "^3.12.0" eslint-scope@^5.1.1: version "5.1.1" @@ -3095,10 +2882,10 @@ eslint-scope@^5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" -eslint-scope@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-6.0.0.tgz#9cf45b13c5ac8f3d4c50f46a5121f61b3e318978" - integrity sha512-uRDL9MWmQCkaFus8RF5K9/L/2fn+80yoW3jkD53l4shjCh26fCtvJGasxjUqP5OT87SYTxCVA3BwTUzuELx9kA== +eslint-scope@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.0.tgz#c1f6ea30ac583031f203d65c73e723b01298f153" + integrity sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg== dependencies: esrecurse "^4.3.0" estraverse "^5.2.0" @@ -3120,24 +2907,28 @@ eslint-visitor-keys@^3.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.0.0.tgz#e32e99c6cdc2eb063f204eda5db67bfe58bb4186" integrity sha512-mJOZa35trBTb3IyRmo8xmKBZlxf+N7OnUl4+ZhJHs/r+0770Wh/LEACE2pqMGMe27G/4y8P2bYGk4J70IC5k1Q== -eslint@8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.2.0.tgz#44d3fb506d0f866a506d97a0fc0e90ee6d06a815" - integrity sha512-erw7XmM+CLxTOickrimJ1SiF55jiNlVSp2qqm0NuBWPtHYQCegD5ZMaW0c3i5ytPqL+SSLaCxdvQXFPLJn+ABw== +eslint-visitor-keys@^3.1.0, eslint-visitor-keys@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.2.0.tgz#6fbb166a6798ee5991358bc2daa1ba76cc1254a1" + integrity sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ== + +eslint@8.7.0: + version "8.7.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.7.0.tgz#22e036842ee5b7cf87b03fe237731675b4d3633c" + integrity sha512-ifHYzkBGrzS2iDU7KjhCAVMGCvF6M3Xfs8X8b37cgrUlDt6bWRTpRh6T/gtSXv1HJ/BUGgmjvNvOEGu85Iif7w== dependencies: - "@eslint/eslintrc" "^1.0.4" - "@humanwhocodes/config-array" "^0.6.0" + "@eslint/eslintrc" "^1.0.5" + "@humanwhocodes/config-array" "^0.9.2" ajv "^6.10.0" chalk "^4.0.0" cross-spawn "^7.0.2" debug "^4.3.2" doctrine "^3.0.0" - enquirer "^2.3.5" escape-string-regexp "^4.0.0" - eslint-scope "^6.0.0" + eslint-scope "^7.1.0" eslint-utils "^3.0.0" - eslint-visitor-keys "^3.0.0" - espree "^9.0.0" + eslint-visitor-keys "^3.2.0" + espree "^9.3.0" esquery "^1.4.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" @@ -3145,7 +2936,7 @@ eslint@8.2.0: functional-red-black-tree "^1.0.1" glob-parent "^6.0.1" globals "^13.6.0" - ignore "^4.0.6" + ignore "^5.2.0" import-fresh "^3.0.0" imurmurhash "^0.1.4" is-glob "^4.0.0" @@ -3156,9 +2947,7 @@ eslint@8.2.0: minimatch "^3.0.4" natural-compare "^1.4.0" optionator "^0.9.1" - progress "^2.0.0" regexpp "^3.2.0" - semver "^7.2.1" strip-ansi "^6.0.1" strip-json-comments "^3.1.0" text-table "^0.2.0" @@ -3169,16 +2958,16 @@ esm@^3.2.22: resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10" integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA== -espree@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.0.0.tgz#e90a2965698228502e771c7a58489b1a9d107090" - integrity sha512-r5EQJcYZ2oaGbeR0jR0fFVijGOcwai07/690YRXLINuhmVeRY4UKSAsQPe/0BNuDgwP7Ophoc1PRsr2E3tkbdQ== +espree@^9.2.0, espree@^9.3.0: + version "9.3.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.0.tgz#c1240d79183b72aaee6ccfa5a90bc9111df085a8" + integrity sha512-d/5nCsb0JcqsSEeQzFZ8DH1RmxPcglRWh24EFTlUEmCKoehXGdpsx0RkHDubqUI8LSAIKMQp4r9SzQ3n+sm4HQ== dependencies: - acorn "^8.5.0" + acorn "^8.7.0" acorn-jsx "^5.3.1" - eslint-visitor-keys "^3.0.0" + eslint-visitor-keys "^3.1.0" -esprima@^4.0.0, esprima@^4.0.1: +esprima@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== @@ -3222,7 +3011,7 @@ event-target-shim@^5.0.0: resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== -eventemitter3@4.0.7, eventemitter3@^4.0.7: +eventemitter3@4.0.7, eventemitter3@^4.0.4, eventemitter3@^4.0.7: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== @@ -3315,7 +3104,7 @@ fast-glob@^3.1.1: micromatch "^4.0.2" picomatch "^2.2.1" -fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: +fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== @@ -3349,6 +3138,14 @@ fetch-blob@^2.1.1: resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-2.1.2.tgz#a7805db1361bd44c1ef62bb57fb5fe8ea173ef3c" integrity sha512-YKqtUDwqLyfyMnmbw8XD6Q8j9i/HggKtPEI+pZ1+8bvheBu78biSmNaXWusx1TauGqtUUGx/cBb1mKdq2rLYow== +fetch-blob@^3.1.2, fetch-blob@^3.1.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.1.4.tgz#e8c6567f80ad7fc22fd302e7dcb72bafde9c1717" + integrity sha512-Eq5Xv5+VlSrYWEqKrusxY1C3Hm/hjeAsCGVG3ft7pZahlUAChpGZT/Ms1WmSLnEAisEXszjzu/s+ce6HZB2VHA== + dependencies: + node-domexception "^1.0.0" + web-streams-polyfill "^3.0.3" + file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -3395,12 +3192,13 @@ find-up@^2.1.0: dependencies: locate-path "^2.0.0" -find-up@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" - integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== +find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== dependencies: - locate-path "^3.0.0" + locate-path "^5.0.0" + path-exists "^4.0.0" findup-sync@^4.0.0: version "4.0.0" @@ -3438,12 +3236,10 @@ fluent-ffmpeg@2.1.2: async ">=0.2.9" which "^1.1.1" -follow-redirects@1.5.10: - version "1.5.10" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" - integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== - dependencies: - debug "=3.1.0" +follow-redirects@^1.14.4: + version "1.14.7" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.7.tgz#2004c02eb9436eee9a21446a6477debf17e81685" + integrity sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ== form-data@^3.0.0: version "3.0.0" @@ -3454,6 +3250,13 @@ form-data@^3.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +formdata-polyfill@^4.0.10: + version "4.0.10" + resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423" + integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g== + dependencies: + fetch-blob "^3.1.2" + fresh@~0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" @@ -3490,6 +3293,16 @@ fsevents@~2.1.2: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== +fstream@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" + integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg== + dependencies: + graceful-fs "^4.1.2" + inherits "~2.0.0" + mkdirp ">=0.5 0" + rimraf "2" + function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -3500,6 +3313,21 @@ functional-red-black-tree@^1.0.1: resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= +gauge@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-4.0.0.tgz#afba07aa0374a93c6219603b1fb83eaa2264d8f8" + integrity sha512-F8sU45yQpjQjxKkm1UOAhf0U/O0aFt//Fl7hsrNVto+patMHjs7dPI9mFOGUKbhrgKm0S3EjW3scMFuQmWSROw== + dependencies: + ansi-regex "^5.0.1" + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.2" + console-control-strings "^1.0.0" + has-unicode "^2.0.1" + signal-exit "^3.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.2" + gauge@~2.7.3: version "2.7.4" resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" @@ -3514,6 +3342,11 @@ gauge@~2.7.3: strip-ansi "^3.0.1" wide-align "^1.1.0" +generic-pool@3.8.2: + version "3.8.2" + resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-3.8.2.tgz#aab4f280adb522fdfbdc5e5b64d718d3683f04e9" + integrity sha512-nGToKy6p3PAbYQ7p1UlWl6vSPwfwU6TMSWK7TTu+WUY4ZjyZQGniGGt2oNVvyNSpyZYSB43zMXVLcBm08MTMkg== + get-caller-file@^2.0.1, get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" @@ -3700,7 +3533,7 @@ graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.4: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== -graceful-fs@^4.2.0: +graceful-fs@^4.2.0, graceful-fs@^4.2.2: version "4.2.8" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== @@ -3715,11 +3548,6 @@ growl@1.10.5: resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== -hammerjs@^2.0.8: - version "2.0.8" - resolved "https://registry.yarnpkg.com/hammerjs/-/hammerjs-2.0.8.tgz#04ef77862cff2bb79d30f7692095930222bf60f1" - integrity sha1-BO93hiz/K7edMPdpIJWTAiK/YPE= - has-bigints@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" @@ -3735,7 +3563,7 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-symbols@^1.0.0, has-symbols@^1.0.1: +has-symbols@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== @@ -3752,7 +3580,7 @@ has-tostringtag@^1.0.0: dependencies: has-symbols "^1.0.2" -has-unicode@^2.0.0: +has-unicode@^2.0.0, has-unicode@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= @@ -3875,13 +3703,13 @@ http-proxy-agent@^4.0.1: agent-base "6" debug "4" -http-signature@1.3.5: - version "1.3.5" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.3.5.tgz#9f19496ffbf3227298d7b5f156e0e1a948678683" - integrity sha512-NwoTQYSJoFt34jSBbwzDHDofoA61NGXzu6wXh95o1Ry62EnmKjXb/nR/RknLeZ3G/uGwrlKNY2z7uPt+Cdl7Tw== +http-signature@1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.3.6.tgz#cb6fbfdf86d1c974f343be94e87f7fc128662cf9" + integrity sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw== dependencies: assert-plus "^1.0.0" - jsprim "^1.2.2" + jsprim "^2.0.2" sshpk "^1.14.1" http2-wrapper@^1.0.0-beta.5.0: @@ -3946,18 +3774,6 @@ iconv-lite@^0.6.2: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" -icss-utils@^5.0.0, icss-utils@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" - integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== - -idb-keyval@5.1.3: - version "5.1.3" - resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-5.1.3.tgz#6ef5dff371897c23f144322dc6374eadd6a345d9" - integrity sha512-N9HbCK/FaXSRVI+k6Xq4QgWxbcZRUv+SfG1y7HJ28JdV8yEJu6k+C/YLea7npGckX2DQJeEVuMc4bKOBeU/2LQ== - dependencies: - safari-14-idb-fix "^1.0.4" - ieee754@1.1.13, ieee754@^1.1.4: version "1.1.13" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" @@ -3983,6 +3799,11 @@ ignore@^5.1.8: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.9.tgz#9ec1a5cbe8e1446ec60d4420060d43aa6e7382fb" integrity sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ== +ignore@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" + integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== + import-fresh@^3.0.0, import-fresh@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" @@ -4001,11 +3822,6 @@ indent-string@^4.0.0: resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== -indexes-of@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" - integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= - infer-owner@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" @@ -4024,7 +3840,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -4039,15 +3855,10 @@ ini@^1.3.4, ini@~1.3.0: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84" integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ== -insert-text-at-cursor@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/insert-text-at-cursor/-/insert-text-at-cursor-0.3.0.tgz#1819607680ec1570618347c4cd475e791faa25da" - integrity sha512-/nPtyeX9xPUvxZf+r0518B7uqNKlP+LqNJqSiXFEaa2T71rWIwTVXGH7hB9xO/EVdwa5/pWlFCPwShOW81XIxQ== - -install-artifact-from-github@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/install-artifact-from-github/-/install-artifact-from-github-1.2.0.tgz#adcbd123c16a4337ec44ea76d0ebf253cc16b074" - integrity sha512-3OxCPcY55XlVM3kkfIpeCgmoSKnMsz2A3Dbhsq0RXpIknKQmrX1YiznCeW9cD2ItFmDxziA3w6Eg8d80AoL3oA== +install-artifact-from-github@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/install-artifact-from-github/-/install-artifact-from-github-1.3.0.tgz#cab6ff821976b8a35b0c079da19a727c90381a40" + integrity sha512-iT8v1GwOAX0pPXifF/5ihnMhHOCo3OeK7z3TQa4CtSNCIg8k0UxqBEk9jRwz8OP68hHXvJ2gxRa89KYHtBkqGA== internal-slot@^1.0.3: version "1.0.3" @@ -4105,11 +3916,6 @@ ipaddr.js@^2.0.1: resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.0.1.tgz#eca256a7a877e917aeb368b0a7497ddf42ef81c0" integrity sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng== -is-absolute-url@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698" - integrity sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q== - is-arrayish@^0.3.1: version "0.3.2" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" @@ -4142,7 +3948,7 @@ is-buffer@^1.1.5: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-callable@^1.1.4, is-callable@^1.1.5: +is-callable@^1.1.4: version "1.1.5" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q== @@ -4288,7 +4094,7 @@ is-promise@^2.0.0: resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== -is-regex@^1.0.3, is-regex@^1.0.5: +is-regex@^1.0.3: version "1.0.5" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae" integrity sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ== @@ -4303,11 +4109,6 @@ is-regex@^1.1.4: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-resolvable@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" - integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== - is-shared-array-buffer@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz#97b0c85fbdacb59c9c446fe653b82cf2b5b7cfe6" @@ -4325,10 +4126,10 @@ is-string@^1.0.5, is-string@^1.0.7: dependencies: has-tostringtag "^1.0.0" -is-svg@4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-4.3.1.tgz#8c63ec8c67c8c7f0a8de0a71c8c7d58eccf4406b" - integrity sha512-h2CGs+yPUyvkgTJQS9cJzo9lYK06WgRiXUqBBHtglSzVKAuH4/oWsqk7LGfbSa1hGk9QcZ0SyQtVggvBA8LZXA== +is-svg@4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-4.3.2.tgz#a119e9932e1af53f6be1969d1790d6cc5fd947d3" + integrity sha512-mM90duy00JGMyjqIVHu9gNTjywdZV+8qNasX8cm/EEYZ53PHDgajvbBwNVvty5dwSAxLUD3p3bdo+7sR/UMrpw== dependencies: fast-xml-parser "^3.19.0" @@ -4378,11 +4179,6 @@ isarray@^1.0.0, isarray@~1.0.0: resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= -isarray@^2.0.1: - version "2.0.5" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" - integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== - isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -4397,10 +4193,10 @@ jest-worker@^26.6.2: merge-stream "^2.0.0" supports-color "^7.0.0" -jmespath@0.15.0: - version "0.15.0" - resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217" - integrity sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc= +jmespath@0.16.0: + version "0.16.0" + resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.16.0.tgz#b15b0a85dfd4d930d43e69ed605943c802785076" + integrity sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw== jpeg-js@^0.4.1: version "0.4.1" @@ -4442,14 +4238,6 @@ js-yaml@4.1.0, js-yaml@^4.0.0, js-yaml@^4.1.0: dependencies: argparse "^2.0.1" -js-yaml@^3.14.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - jsbn@1.1.0, jsbn@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040" @@ -4518,10 +4306,10 @@ json-schema-traverse@^1.0.0: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== -json-schema@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" - integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= +json-schema@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" + integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" @@ -4542,7 +4330,7 @@ json5-loader@4.0.1: loader-utils "^2.0.0" schema-utils "^3.0.0" -json5@2.2.0, json5@2.x: +json5@2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== @@ -4589,14 +4377,14 @@ jsonld@5.2.0: lru-cache "^6.0.0" rdf-canonize "^3.0.0" -jsprim@^1.2.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" - integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= +jsprim@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-2.0.2.tgz#77ca23dbcd4135cd364800d22ff82c2185803d4d" + integrity sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ== dependencies: assert-plus "1.0.0" extsprintf "1.3.0" - json-schema "0.2.3" + json-schema "0.4.0" verror "1.10.0" jsrsasign@8.0.20: @@ -4817,10 +4605,10 @@ levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" -lilconfig@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.3.tgz#68f3005e921dafbd2a2afb48379986aa6d2579fd" - integrity sha512-EHKqr/+ZvdKCifpNrJCKxBTgk5XupZA3y/aCPY9mxfgBzmgh93Mt/WqjjQ38oMxXuvDokaKiM3lAgvSH2sjtHg== +listenercount@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/listenercount/-/listenercount-1.0.1.tgz#84c8a72ab59c4725321480c975e6508342e70937" + integrity sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc= loader-runner@^4.2.0: version "4.2.0" @@ -4844,13 +4632,12 @@ locate-path@^2.0.0: p-locate "^2.0.0" path-exists "^3.0.0" -locate-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" - integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== dependencies: - p-locate "^3.0.0" - path-exists "^3.0.0" + p-locate "^4.1.0" locate-path@^6.0.0: version "6.0.0" @@ -4919,11 +4706,6 @@ lodash.map@^4.4.0: resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3" integrity sha1-dx7Hg540c9nEzeKLGTlMNWL09tM= -lodash.memoize@4.x, lodash.memoize@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= - lodash.merge@^4.4.0, lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" @@ -4959,12 +4741,7 @@ lodash.union@^4.6.0: resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" integrity sha1-SLtQiECfFvGCFmZkHETdGqrjzYg= -lodash.uniq@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" - integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= - -lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.7.0: +lodash@^4.17.14, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -5001,18 +4778,18 @@ mailcheck@^1.1.1: resolved "https://registry.yarnpkg.com/mailcheck/-/mailcheck-1.1.1.tgz#d87cf6ba0b64ba512199dbf93f1489f479591e34" integrity sha1-2Hz2ugtkulEhmdv5PxSJ9HlZHjQ= -make-error@1.x, make-error@^1.1.1: +make-error@^1.1.1: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== -make-fetch-happen@^8.0.14: - version "8.0.14" - resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-8.0.14.tgz#aaba73ae0ab5586ad8eaa68bd83332669393e222" - integrity sha512-EsS89h6l4vbfJEtBZnENTOFk8mCRpY5ru36Xe5bcX1KYIli2mkSHqoFsp5O1wMDvTJJzxe/4THpCTtygjeeGWQ== +make-fetch-happen@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz#53085a09e7971433e6765f7971bf63f4e05cb968" + integrity sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg== dependencies: agentkeepalive "^4.1.3" - cacache "^15.0.5" + cacache "^15.2.0" http-cache-semantics "^4.1.0" http-proxy-agent "^4.0.1" https-proxy-agent "^5.0.0" @@ -5023,15 +4800,11 @@ make-fetch-happen@^8.0.14: minipass-fetch "^1.3.2" minipass-flush "^1.0.5" minipass-pipeline "^1.2.4" + negotiator "^0.6.2" promise-retry "^2.0.1" - socks-proxy-agent "^5.0.0" + socks-proxy-agent "^6.0.0" ssri "^8.0.0" -mdn-data@2.0.14: - version "2.0.14" - resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" - integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== - media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -5057,21 +4830,13 @@ methods@^1.1.2: resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= -mfm-js@0.20.0: - version "0.20.0" - resolved "https://registry.yarnpkg.com/mfm-js/-/mfm-js-0.20.0.tgz#3afdcd7959461fd825aa8af9b9e8a57cdbddc290" - integrity sha512-1+3tV3nWUKQNh/ztX3wXu5iLBtdsg6q3wUhl+XyOhc2H3sQdG+sih/w2c0nR9TIawjN+Z1/pvgGzxMJHfmKQmA== +mfm-js@0.21.0: + version "0.21.0" + resolved "https://registry.yarnpkg.com/mfm-js/-/mfm-js-0.21.0.tgz#954cc6e7071700b0b1872c78a90bada10be7f772" + integrity sha512-nyQXaipa7rmAw9ER9uYigMvGcdCwhSv93abZBwccnSnPOc1W3S/WW0+sN28g3YSmlHDCA0i2q9aAFc9EgOi5KA== dependencies: twemoji-parser "13.1.x" -micromatch@4.x: - version "4.0.4" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" - integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== - dependencies: - braces "^3.0.1" - picomatch "^2.2.3" - micromatch@^4.0.0, micromatch@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" @@ -5119,11 +4884,6 @@ mimic-response@^1.0.0: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== -mimic-response@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43" - integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA== - mimic-response@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" @@ -5207,10 +4967,10 @@ minizlib@^2.0.0, minizlib@^2.1.1: minipass "^3.0.0" yallist "^4.0.0" -misskey-js@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/misskey-js/-/misskey-js-0.0.8.tgz#b47a1ec54bff96b23cc2a6b307a3895f99a94424" - integrity sha512-Q1L6FaroVz8kpW7T4xQyJmJKSwjOYPbNY3TspOUWmbIBDf2JP0HVeKEFLI9dvdSL0kSkdQNz3MSVLjlHiyPqLQ== +misskey-js@0.0.13: + version "0.0.13" + resolved "https://registry.yarnpkg.com/misskey-js/-/misskey-js-0.0.13.tgz#03a4e469186e28752d599dc4093519eb64647970" + integrity sha512-kBdJdfe281gtykzzsrN3IAxWUQIimzPiJGyKWf863ggWJlWYVPmP9hTFlX2z8oPOaypgVBPEPHyw/jNUdc2DbQ== dependencies: autobind-decorator "^2.4.0" eventemitter3 "^4.0.7" @@ -5226,7 +4986,7 @@ mkdirp-classic@^0.5.3: resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== -mkdirp@0.x, mkdirp@^0.5.4: +"mkdirp@>=0.5 0", mkdirp@^0.5.4: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -5301,10 +5061,10 @@ ms@3.0.0-canary.1: resolved "https://registry.yarnpkg.com/ms/-/ms-3.0.0-canary.1.tgz#c7b34fbce381492fd0b345d1cf56e14d67b77b80" integrity sha512-kh8ARjh8rMN7Du2igDRO9QJnqCb2xYTJxyQYK7vJJS4TvLLmsbyhiKpSW+t+y26gyOyMd0riphX0GeWKU3ky5g== -multer@1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/multer/-/multer-1.4.3.tgz#4db352d6992e028ac0eacf7be45c6efd0264297b" - integrity sha512-np0YLKncuZoTzufbkM6wEKp68EhWJXcU6fq6QqrSwkckd2LlMgd1UqhUJLj6NS/5sZ8dE8LYDWslsltJznnXlg== +multer@1.4.4: + version "1.4.4" + resolved "https://registry.yarnpkg.com/multer/-/multer-1.4.4.tgz#e2bc6cac0df57a8832b858d7418ccaa8ebaf7d8c" + integrity sha512-2wY2+xD4udX612aMqMcB8Ws2Voq6NIUPEtD1be6m411T4uDH/VtL9i//xvcyFlTVfRdaBsk7hV5tgrGQqhuBiw== dependencies: append-field "^1.0.0" busboy "^0.2.11" @@ -5329,10 +5089,10 @@ mz@^2.4.0, mz@^2.7.0: object-assign "^4.0.1" thenify-all "^1.0.0" -nan@^2.14.2: - version "2.14.2" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" - integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== +nan@^2.15.0: + version "2.15.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" + integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== nano-time@1.0.0: version "1.0.0" @@ -5346,11 +5106,6 @@ nanoid@3.1.20: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788" integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw== -nanoid@^3.1.23: - version "3.1.23" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.23.tgz#f744086ce7c2bc47ee0a8472574d5c78e4183a81" - integrity sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw== - nanoid@^3.1.30: version "3.1.30" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.30.tgz#63f93cc548d2a113dc5dfbc63bfa09e2b9b64362" @@ -5375,7 +5130,7 @@ needle@^2.5.2: iconv-lite "^0.4.4" sax "^1.2.4" -negotiator@0.6.2: +negotiator@0.6.2, negotiator@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== @@ -5405,18 +5160,32 @@ next-tick@~1.0.0: resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= -node-abi@^2.21.0: - version "2.21.0" - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.21.0.tgz#c2dc9ebad6f4f53d6ea9b531e7b8faad81041d48" - integrity sha512-smhrivuPqEM3H5LmnY3KU6HfYv0u4QklgAxfFyRNujKUzbUcYZ+Jc2EhukB9SRcD2VpqhxM7n/MIcp1Ua1/JMg== +node-abi@^3.3.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.5.0.tgz#26e8b7b251c3260a5ac5ba5aef3b4345a0229248" + integrity sha512-LtHvNIBgOy5mO8mPEUtkCW/YCRWYEKshIvqhe1GHHyXEHEB5mgICyYnAcl4qan3uFeRROErKGzatFHPf6kDxWw== dependencies: - semver "^5.4.1" + semver "^7.3.5" node-addon-api@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.2.0.tgz#117cbb5a959dff0992e1c586ae0393573e4d2a87" integrity sha512-eazsqzwG2lskuzBqCGPi7Ac2UgOoMz8JVOXVhTvvPDYhthvNpefx8jWD8Np7Gv+2Sz0FlPWZk0nJV0z598Wn8Q== +node-domexception@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" + integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== + +node-fetch@*: + version "3.2.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.0.tgz#59390db4e489184fa35d4b74caf5510e8dfbaf3b" + integrity sha512-8xeimMwMItMw8hRrOl3C9/xzU49HV/yE6ORew/l+dxWimO5A4Ra8ld2rerlJvc/O7et5Z1zrWsPX43v1QBjCxw== + dependencies: + data-uri-to-buffer "^4.0.0" + fetch-blob "^3.1.4" + formdata-polyfill "^4.0.10" + node-fetch@2.6.1, node-fetch@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" @@ -5435,31 +5204,31 @@ node-gyp-build@~3.7.0: resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-3.7.0.tgz#daa77a4f547b9aed3e2aac779eaf151afd60ec8d" integrity sha512-L/Eg02Epx6Si2NXmedx+Okg+4UHqmaf3TNcxd50SF9NQGcJaON3AtU++kax69XV7YWz4tUspqZSAsVofhFKG2w== -node-gyp@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-8.0.0.tgz#225af2b06b8419ae81f924bf25ae4c167f6378a5" - integrity sha512-Jod6NxyWtcwrpAQe0O/aXOpC5QfncotgtG73dg65z6VW/C6g/G4jiajXQUBIJ8pk/VfM6mBYE9BN/HvudTunUQ== +node-gyp@^8.4.1: + version "8.4.1" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-8.4.1.tgz#3d49308fc31f768180957d6b5746845fbd429937" + integrity sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w== dependencies: env-paths "^2.2.0" glob "^7.1.4" graceful-fs "^4.2.6" - make-fetch-happen "^8.0.14" + make-fetch-happen "^9.1.0" nopt "^5.0.0" - npmlog "^4.1.2" + npmlog "^6.0.0" rimraf "^3.0.2" semver "^7.3.5" - tar "^6.1.0" + tar "^6.1.2" which "^2.0.2" -node-releases@^1.1.70, node-releases@^1.1.71: +node-releases@^1.1.70: version "1.1.71" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.71.tgz#cb1334b179896b1c89ecfdd4b725fb7bbdfc7dbb" integrity sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg== -nodemailer@6.7.0: - version "6.7.0" - resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.7.0.tgz#86614722c4e0c33d1b5b02aecb90d6d629932b0d" - integrity sha512-AtiTVUFHLiiDnMQ43zi0YgkzHOEWUkhDgPlBXrsDzJiJvB29Alo4OKxHQ0ugF3gRqRQIneCLtZU3yiUo7pItZw== +nodemailer@6.7.2: + version "6.7.2" + resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.7.2.tgz#44b2ad5f7ed71b7067f7a21c4fedabaec62b85e0" + integrity sha512-Dz7zVwlef4k5R71fdmxwR8Q39fiboGbu3xgswkzGwczUfjp873rVxt1O46+Fh0j1ORnAC6L9+heI8uUpO6DT7Q== nofilter@^2.0.3: version "2.0.3" @@ -5498,11 +5267,6 @@ normalize-url@^4.1.0: resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== -normalize-url@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" - integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== - npm-run-path@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.0.1.tgz#748dd68ed7de377bb1f7132c7dafe657be5ab400" @@ -5510,7 +5274,7 @@ npm-run-path@^5.0.1: dependencies: path-key "^4.0.0" -npmlog@^4.0.1, npmlog@^4.1.2: +npmlog@^4.0.1: version "4.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== @@ -5520,12 +5284,15 @@ npmlog@^4.0.1, npmlog@^4.1.2: gauge "~2.7.3" set-blocking "~2.0.0" -nth-check@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.0.tgz#1bb4f6dac70072fc313e8c9cd1417b5074c0a125" - integrity sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q== +npmlog@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-6.0.0.tgz#ba9ef39413c3d936ea91553db7be49c34ad0520c" + integrity sha512-03ppFRGlsyUaQFbGC2C8QWJN/C/K7PsfyD9aQdhVKAQIH4sQBc8WASqFBP7O+Ut4d2oo5LoeoboB3cGdBZSp6Q== dependencies: - boolbase "^1.0.0" + are-we-there-yet "^2.0.0" + console-control-strings "^1.1.0" + gauge "^4.0.0" + set-blocking "^2.0.0" nth-check@~1.0.1: version "1.0.2" @@ -5559,26 +5326,11 @@ object-inspect@^1.11.0, object-inspect@^1.9.0: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1" integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg== -object-inspect@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" - integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== - -object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: +object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== -object.assign@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" - integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== - dependencies: - define-properties "^1.1.2" - function-bind "^1.1.1" - has-symbols "^1.0.0" - object-keys "^1.0.11" - object.assign@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" @@ -5589,14 +5341,6 @@ object.assign@^4.1.2: has-symbols "^1.0.1" object-keys "^1.1.1" -object.getownpropertydescriptors@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" - integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - object.values@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.5.tgz#959f63e3ce9ef108720333082131e4a459b716ac" @@ -5706,7 +5450,7 @@ p-limit@^1.1.0: dependencies: p-try "^1.0.0" -p-limit@^2.0.0: +p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== @@ -5734,12 +5478,12 @@ p-locate@^2.0.0: dependencies: p-limit "^1.1.0" -p-locate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" - integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== dependencies: - p-limit "^2.0.0" + p-limit "^2.2.0" p-locate@^5.0.0: version "5.0.0" @@ -5760,6 +5504,14 @@ p-map@^4.0.0: dependencies: aggregate-error "^3.0.0" +p-queue@6.6.2: + version "6.6.2" + resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426" + integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ== + dependencies: + eventemitter3 "^4.0.4" + p-timeout "^3.2.0" + p-timeout@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" @@ -5932,28 +5684,26 @@ picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.0.7, picomatch@^2.2.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== -picomatch@^2.2.3: - version "2.3.0" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" - integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== - pify@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== -pkg-dir@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" - integrity sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s= - dependencies: - find-up "^2.1.0" +pluralize@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" + integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== -pngjs@^3.3.0, pngjs@^3.3.1: +pngjs@^3.3.1: version "3.4.0" resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f" integrity sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w== +pngjs@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb" + integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw== + portscanner@2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/portscanner/-/portscanner-2.2.0.tgz#6059189b3efa0965c9d96a56b958eb9508411cf1" @@ -5962,274 +5712,6 @@ portscanner@2.2.0: async "^2.6.0" is-number-like "^1.0.3" -postcss-calc@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.0.0.tgz#a05b87aacd132740a5db09462a3612453e5df90a" - integrity sha512-5NglwDrcbiy8XXfPM11F3HeC6hoT9W7GUH/Zi5U/p7u3Irv4rHhdDcIZwG0llHXV4ftsBjpfWMXAnXNl4lnt8g== - dependencies: - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.0.2" - -postcss-colormin@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.2.1.tgz#6e444a806fd3c578827dbad022762df19334414d" - integrity sha512-VVwMrEYLcHYePUYV99Ymuoi7WhKrMGy/V9/kTS0DkCoJYmmjdOMneyhzYUxcNgteKDVbrewOkSM7Wje/MFwxzA== - dependencies: - browserslist "^4.16.6" - caniuse-api "^3.0.0" - colord "^2.9.1" - postcss-value-parser "^4.1.0" - -postcss-convert-values@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-5.0.2.tgz#879b849dc3677c7d6bc94b6a2c1a3f0808798059" - integrity sha512-KQ04E2yadmfa1LqXm7UIDwW1ftxU/QWZmz6NKnHnUvJ3LEYbbcX6i329f/ig+WnEByHegulocXrECaZGLpL8Zg== - dependencies: - postcss-value-parser "^4.1.0" - -postcss-discard-comments@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-5.0.1.tgz#9eae4b747cf760d31f2447c27f0619d5718901fe" - integrity sha512-lgZBPTDvWrbAYY1v5GYEv8fEO/WhKOu/hmZqmCYfrpD6eyDWWzAOsl2rF29lpvziKO02Gc5GJQtlpkTmakwOWg== - -postcss-discard-duplicates@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-5.0.1.tgz#68f7cc6458fe6bab2e46c9f55ae52869f680e66d" - integrity sha512-svx747PWHKOGpAXXQkCc4k/DsWo+6bc5LsVrAsw+OU+Ibi7klFZCyX54gjYzX4TH+f2uzXjRviLARxkMurA2bA== - -postcss-discard-empty@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-5.0.1.tgz#ee136c39e27d5d2ed4da0ee5ed02bc8a9f8bf6d8" - integrity sha512-vfU8CxAQ6YpMxV2SvMcMIyF2LX1ZzWpy0lqHDsOdaKKLQVQGVP1pzhrI9JlsO65s66uQTfkQBKBD/A5gp9STFw== - -postcss-discard-overridden@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.0.1.tgz#454b41f707300b98109a75005ca4ab0ff2743ac6" - integrity sha512-Y28H7y93L2BpJhrdUR2SR2fnSsT+3TVx1NmVQLbcnZWwIUpJ7mfcTC6Za9M2PG6w8j7UQRfzxqn8jU2VwFxo3Q== - -postcss-merge-longhand@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.0.3.tgz#42194a5ffbaa5513edbf606ef79c44958564658b" - integrity sha512-kmB+1TjMTj/bPw6MCDUiqSA5e/x4fvLffiAdthra3a0m2/IjTrWsTmD3FdSskzUjEwkj5ZHBDEbv5dOcqD7CMQ== - dependencies: - css-color-names "^1.0.1" - postcss-value-parser "^4.1.0" - stylehacks "^5.0.1" - -postcss-merge-rules@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.0.2.tgz#d6e4d65018badbdb7dcc789c4f39b941305d410a" - integrity sha512-5K+Md7S3GwBewfB4rjDeol6V/RZ8S+v4B66Zk2gChRqLTCC8yjnHQ601omj9TKftS19OPGqZ/XzoqpzNQQLwbg== - dependencies: - browserslist "^4.16.6" - caniuse-api "^3.0.0" - cssnano-utils "^2.0.1" - postcss-selector-parser "^6.0.5" - vendors "^1.0.3" - -postcss-minify-font-values@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.0.1.tgz#a90cefbfdaa075bd3dbaa1b33588bb4dc268addf" - integrity sha512-7JS4qIsnqaxk+FXY1E8dHBDmraYFWmuL6cgt0T1SWGRO5bzJf8sUoelwa4P88LEWJZweHevAiDKxHlofuvtIoA== - dependencies: - postcss-value-parser "^4.1.0" - -postcss-minify-gradients@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.0.3.tgz#f970a11cc71e08e9095e78ec3a6b34b91c19550e" - integrity sha512-Z91Ol22nB6XJW+5oe31+YxRsYooxOdFKcbOqY/V8Fxse1Y3vqlNRpi1cxCqoACZTQEhl+xvt4hsbWiV5R+XI9Q== - dependencies: - colord "^2.9.1" - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" - -postcss-minify-params@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.0.1.tgz#371153ba164b9d8562842fdcd929c98abd9e5b6c" - integrity sha512-4RUC4k2A/Q9mGco1Z8ODc7h+A0z7L7X2ypO1B6V8057eVK6mZ6xwz6QN64nHuHLbqbclkX1wyzRnIrdZehTEHw== - dependencies: - alphanum-sort "^1.0.2" - browserslist "^4.16.0" - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" - uniqs "^2.0.0" - -postcss-minify-selectors@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.1.0.tgz#4385c845d3979ff160291774523ffa54eafd5a54" - integrity sha512-NzGBXDa7aPsAcijXZeagnJBKBPMYLaJJzB8CQh6ncvyl2sIndLVWfbcDi0SBjRWk5VqEjXvf8tYwzoKf4Z07og== - dependencies: - alphanum-sort "^1.0.2" - postcss-selector-parser "^6.0.5" - -postcss-modules-extract-imports@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" - integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== - -postcss-modules-local-by-default@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c" - integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ== - dependencies: - icss-utils "^5.0.0" - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.1.0" - -postcss-modules-scope@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06" - integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg== - dependencies: - postcss-selector-parser "^6.0.4" - -postcss-modules-values@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c" - integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ== - dependencies: - icss-utils "^5.0.0" - -postcss-normalize-charset@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-5.0.1.tgz#121559d1bebc55ac8d24af37f67bd4da9efd91d0" - integrity sha512-6J40l6LNYnBdPSk+BHZ8SF+HAkS4q2twe5jnocgd+xWpz/mx/5Sa32m3W1AA8uE8XaXN+eg8trIlfu8V9x61eg== - -postcss-normalize-display-values@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.0.1.tgz#62650b965981a955dffee83363453db82f6ad1fd" - integrity sha512-uupdvWk88kLDXi5HEyI9IaAJTE3/Djbcrqq8YgjvAVuzgVuqIk3SuJWUisT2gaJbZm1H9g5k2w1xXilM3x8DjQ== - dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" - -postcss-normalize-positions@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.0.1.tgz#868f6af1795fdfa86fbbe960dceb47e5f9492fe5" - integrity sha512-rvzWAJai5xej9yWqlCb1OWLd9JjW2Ex2BCPzUJrbaXmtKtgfL8dBMOOMTX6TnvQMtjk3ei1Lswcs78qKO1Skrg== - dependencies: - postcss-value-parser "^4.1.0" - -postcss-normalize-repeat-style@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.0.1.tgz#cbc0de1383b57f5bb61ddd6a84653b5e8665b2b5" - integrity sha512-syZ2itq0HTQjj4QtXZOeefomckiV5TaUO6ReIEabCh3wgDs4Mr01pkif0MeVwKyU/LHEkPJnpwFKRxqWA/7O3w== - dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" - -postcss-normalize-string@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.0.1.tgz#d9eafaa4df78c7a3b973ae346ef0e47c554985b0" - integrity sha512-Ic8GaQ3jPMVl1OEn2U//2pm93AXUcF3wz+OriskdZ1AOuYV25OdgS7w9Xu2LO5cGyhHCgn8dMXh9bO7vi3i9pA== - dependencies: - postcss-value-parser "^4.1.0" - -postcss-normalize-timing-functions@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.0.1.tgz#8ee41103b9130429c6cbba736932b75c5e2cb08c" - integrity sha512-cPcBdVN5OsWCNEo5hiXfLUnXfTGtSFiBU9SK8k7ii8UD7OLuznzgNRYkLZow11BkQiiqMcgPyh4ZqXEEUrtQ1Q== - dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" - -postcss-normalize-unicode@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.0.1.tgz#82d672d648a411814aa5bf3ae565379ccd9f5e37" - integrity sha512-kAtYD6V3pK0beqrU90gpCQB7g6AOfP/2KIPCVBKJM2EheVsBQmx/Iof+9zR9NFKLAx4Pr9mDhogB27pmn354nA== - dependencies: - browserslist "^4.16.0" - postcss-value-parser "^4.1.0" - -postcss-normalize-url@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-5.0.2.tgz#ddcdfb7cede1270740cf3e4dfc6008bd96abc763" - integrity sha512-k4jLTPUxREQ5bpajFQZpx8bCF2UrlqOTzP9kEqcEnOfwsRshWs2+oAFIHfDQB8GO2PaUaSE0NlTAYtbluZTlHQ== - dependencies: - is-absolute-url "^3.0.3" - normalize-url "^6.0.1" - postcss-value-parser "^4.1.0" - -postcss-normalize-whitespace@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.0.1.tgz#b0b40b5bcac83585ff07ead2daf2dcfbeeef8e9a" - integrity sha512-iPklmI5SBnRvwceb/XH568yyzK0qRVuAG+a1HFUsFRf11lEJTiQQa03a4RSCQvLKdcpX7XsI1Gen9LuLoqwiqA== - dependencies: - postcss-value-parser "^4.1.0" - -postcss-ordered-values@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.0.2.tgz#1f351426977be00e0f765b3164ad753dac8ed044" - integrity sha512-8AFYDSOYWebJYLyJi3fyjl6CqMEG/UVworjiyK1r573I56kb3e879sCJLGvR3merj+fAdPpVplXKQZv+ey6CgQ== - dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" - -postcss-reduce-initial@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-5.0.1.tgz#9d6369865b0f6f6f6b165a0ef5dc1a4856c7e946" - integrity sha512-zlCZPKLLTMAqA3ZWH57HlbCjkD55LX9dsRyxlls+wfuRfqCi5mSlZVan0heX5cHr154Dq9AfbH70LyhrSAezJw== - dependencies: - browserslist "^4.16.0" - caniuse-api "^3.0.0" - -postcss-reduce-transforms@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.0.1.tgz#93c12f6a159474aa711d5269923e2383cedcf640" - integrity sha512-a//FjoPeFkRuAguPscTVmRQUODP+f3ke2HqFNgGPwdYnpeC29RZdCBvGRGTsKpMURb/I3p6jdKoBQ2zI+9Q7kA== - dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" - -postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4: - version "6.0.4" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz#56075a1380a04604c38b063ea7767a129af5c2b3" - integrity sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw== - dependencies: - cssesc "^3.0.0" - indexes-of "^1.0.1" - uniq "^1.0.1" - util-deprecate "^1.0.2" - -postcss-selector-parser@^6.0.5: - version "6.0.6" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz#2c5bba8174ac2f6981ab631a42ab0ee54af332ea" - integrity sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg== - dependencies: - cssesc "^3.0.0" - util-deprecate "^1.0.2" - -postcss-svgo@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-5.0.3.tgz#d945185756e5dfaae07f9edb0d3cae7ff79f9b30" - integrity sha512-41XZUA1wNDAZrQ3XgWREL/M2zSw8LJPvb5ZWivljBsUQAGoEKMYm6okHsTjJxKYI4M75RQEH4KYlEM52VwdXVA== - dependencies: - postcss-value-parser "^4.1.0" - svgo "^2.7.0" - -postcss-unique-selectors@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-5.0.1.tgz#3be5c1d7363352eff838bd62b0b07a0abad43bfc" - integrity sha512-gwi1NhHV4FMmPn+qwBNuot1sG1t2OmacLQ/AX29lzyggnjd+MnVD5uqQmpXO3J17KGL2WAxQruj1qTd3H0gG/w== - dependencies: - alphanum-sort "^1.0.2" - postcss-selector-parser "^6.0.5" - uniqs "^2.0.0" - -postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" - integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== - -postcss@^8.2.15: - version "8.3.0" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.0.tgz#b1a713f6172ca427e3f05ef1303de8b65683325f" - integrity sha512-+ogXpdAjWGa+fdYY5BQ96V/6tAo+TdSSIMP5huJBIygdWwKtVoB5JWZ7yUd4xZ8r+8Kvvx4nyg/PQ071H4UtcQ== - dependencies: - colorette "^1.2.2" - nanoid "^3.1.23" - source-map-js "^0.6.2" - postcss@^8.3.11: version "8.3.11" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.11.tgz#c3beca7ea811cd5e1c4a3ec6d2e7599ef1f8f858" @@ -6261,10 +5743,10 @@ postgres-interval@^1.1.0: dependencies: xtend "^4.0.0" -prebuild-install@^6.1.4: - version "6.1.4" - resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-6.1.4.tgz#ae3c0142ad611d58570b89af4986088a4937e00f" - integrity sha512-Z4vpywnK1lBg+zdPCVCsKq0xO66eEV9rWo2zrROGGiRS4JtueBOdlB1FnY8lcy7JsUud/Q3ijUxyWN26Ika0vQ== +prebuild-install@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.0.0.tgz#3c5ce3902f1cb9d6de5ae94ca53575e4af0c1574" + integrity sha512-IvSenf33K7JcgddNz2D5w521EgO+4aMMjFt73Uk9FRzQ7P+QZPKrp7qPsDydsSwjGt3T5xRNnM1bj1zMTD5fTA== dependencies: detect-libc "^1.0.3" expand-template "^2.0.3" @@ -6272,11 +5754,11 @@ prebuild-install@^6.1.4: minimist "^1.2.3" mkdirp-classic "^0.5.3" napi-build-utils "^1.0.1" - node-abi "^2.21.0" + node-abi "^3.3.0" npmlog "^4.0.1" pump "^3.0.0" rc "^1.2.7" - simple-get "^3.0.3" + simple-get "^4.0.0" tar-fs "^2.0.0" tunnel-agent "^0.6.0" @@ -6304,11 +5786,6 @@ printj@~1.1.0: resolved "https://registry.yarnpkg.com/printj/-/printj-1.1.2.tgz#d90deb2975a8b9f600fb3a1c94e3f4c53c78a222" integrity sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ== -prismjs@1.25.0: - version "1.25.0" - resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.25.0.tgz#6f822df1bdad965734b310b315a23315cf999756" - integrity sha512-WCjJHl1KEWbnkQom1+SzftbtXMKQoezOCYs5rECqMN+jP+apI7ftoflyqigqzopSO3hMhTEb0mFClA8lkolgEg== - private-ip@2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/private-ip/-/private-ip-2.3.3.tgz#1e80ff8443e5ac78f555631aec3ea6ff027fa6aa" @@ -6319,10 +5796,10 @@ private-ip@2.3.3: is-ip "^3.1.0" netmask "^2.0.2" -probe-image-size@7.2.1: - version "7.2.1" - resolved "https://registry.yarnpkg.com/probe-image-size/-/probe-image-size-7.2.1.tgz#df0c924e67e247bc94f8fcb0fad7f0081061fc44" - integrity sha512-d+6L3NvQBCNt4peRDoEfA7r9bPm6/qy18FnLKwg4NWBC5JrJm0pMLRg1kF4XNsPe1bUdt3WIMonPJzQWN2HXjQ== +probe-image-size@7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/probe-image-size/-/probe-image-size-7.2.2.tgz#e5851b9be7864f21e3bac5e6e4fac9da9055b412" + integrity sha512-QUm+w1S9WTsT5GZB830u0BHExrUmF0J4fyRm5kbLUMEP3fl9UVYXc3xOBVqZNnH9tnvVEJO8vDk3PMtsLqjxug== dependencies: lodash.merge "^4.6.2" needle "^2.5.2" @@ -6333,11 +5810,6 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== -progress@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== - promise-inflight@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" @@ -6356,15 +5828,6 @@ promise-retry@^2.0.1: err-code "^2.0.2" retry "^0.12.0" -promise.prototype.finally@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/promise.prototype.finally/-/promise.prototype.finally-3.1.2.tgz#b8af89160c9c673cefe3b4c4435b53cfd0287067" - integrity sha512-A2HuJWl2opDH0EafgdjwEw7HysI8ff/n4lW4QEVBCUXFk9QeGecBWv0Deph0UmLe3tTNYegz8MOjsVuE6SMoJA== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0-next.0" - function-bind "^1.1.1" - promise@^7.0.1: version "7.3.1" resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" @@ -6508,10 +5971,10 @@ punycode@2.1.1, punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -pureimage@0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/pureimage/-/pureimage-0.3.5.tgz#cd5e91f7b6409fcf4880297aaa3e7fc0afc24d5e" - integrity sha512-+CFUEpoX6GemlKlHihI7Ii4IqKqF5KZjd682sAxwzbc4t4zU4Gwhxd4W3UMZW94nJzf0n4nA9zJrwTR4jZB4TA== +pureimage@0.3.8: + version "0.3.8" + resolved "https://registry.yarnpkg.com/pureimage/-/pureimage-0.3.8.tgz#b9c2a127f3182ab94fb4520e83f4fbcbdd9b38f1" + integrity sha512-+CuR0HM0VmBfKKQTM56myBonDZAhZkS6ymJ8W5oYYDXG7y7X34B/dEH3UesbJI497Vc2OkA+g8T1/Xj/FTyQ8A== dependencies: jpeg-js "^0.4.1" opentype.js "^0.4.3" @@ -6522,18 +5985,15 @@ q@1.4.1: resolved "https://registry.yarnpkg.com/q/-/q-1.4.1.tgz#55705bcd93c5f3673530c2c2cbc0c2b3addc286e" integrity sha1-VXBbzZPF82c1MMLCy8DCs63cKG4= -qrcode@1.4.4: - version "1.4.4" - resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.4.4.tgz#f0c43568a7e7510a55efc3b88d9602f71963ea83" - integrity sha512-oLzEC5+NKFou9P0bMj5+v6Z40evexeE29Z9cummZXZ9QXyMr3lphkURzxjXgPJC5azpxcshoDWV1xE46z+/c3Q== +qrcode@1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.5.0.tgz#95abb8a91fdafd86f8190f2836abbfc500c72d1b" + integrity sha512-9MgRpgVc+/+47dFvQeD6U2s0Z92EsKzcHogtum4QB+UNd025WOJSHvn/hjk9xmzj7Stj95CyUAs31mrjxliEsQ== dependencies: - buffer "^5.4.3" - buffer-alloc "^1.2.0" - buffer-from "^1.1.1" dijkstrajs "^1.0.1" - isarray "^2.0.1" - pngjs "^3.3.0" - yargs "^13.2.4" + encode-utf8 "^1.0.3" + pngjs "^5.0.0" + yargs "^15.3.1" qs@^6.4.0, qs@^6.5.2: version "6.9.3" @@ -6601,14 +6061,14 @@ rdf-canonize@^3.0.0: dependencies: setimmediate "^1.0.5" -re2@1.16.0: - version "1.16.0" - resolved "https://registry.yarnpkg.com/re2/-/re2-1.16.0.tgz#f311eb4865b1296123800ea8e013cec8dab25590" - integrity sha512-eizTZL2ZO0ZseLqfD4t3Qd0M3b3Nr0MBWpX81EbPMIud/1d/CSfUIx2GQK8fWiAeHoSekO5EOeFib2udTZLwYw== +re2@1.17.3: + version "1.17.3" + resolved "https://registry.yarnpkg.com/re2/-/re2-1.17.3.tgz#8cceb48f52c45b860b1f67cee8a44726f7d05e9a" + integrity sha512-Dp5iWVR8W3C7Nm9DziMY4BleMPRb/pe6kvfbzLv80dVYaXRc9jRnwwNqU0oE/taRm0qYR1+Qrtzk9rPjS9ecaQ== dependencies: - install-artifact-from-github "^1.2.0" - nan "^2.14.2" - node-gyp "^8.0.0" + install-artifact-from-github "^1.3.0" + nan "^2.15.0" + node-gyp "^8.4.1" readable-stream@1.1.x: version "1.1.14" @@ -6620,7 +6080,7 @@ readable-stream@1.1.x: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@^2.0.0, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.2.2: +readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.2.2, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -6684,13 +6144,24 @@ redis-lock@0.1.4: resolved "https://registry.yarnpkg.com/redis-lock/-/redis-lock-0.1.4.tgz#e83590bee22b5f01cdb65bfbd88d988045356272" integrity sha512-7/+zu86XVQfJVx1nHTzux5reglDiyUCDwmW7TSlvVezfhH2YLc/Rc8NE0ejQG+8/0lwKzm29/u/4+ogKeLosiA== -redis-parser@^3.0.0: +redis-parser@3.0.0, redis-parser@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" integrity sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ= dependencies: redis-errors "^1.0.0" +redis@*: + version "4.0.2" + resolved "https://registry.yarnpkg.com/redis/-/redis-4.0.2.tgz#096cf716842731a24f34c7c3a996c143e2b133bb" + integrity sha512-Ip1DJ/lwuvtJz9AZ6pl1Bv33fWzk5d3iQpGzsXpi04ErkT4fq0pfGOm4k/p9DHmPGieEIOWvJ9xmIeQMooLybg== + dependencies: + "@node-redis/bloom" "^1.0.0" + "@node-redis/client" "^1.0.2" + "@node-redis/json" "^1.0.2" + "@node-redis/search" "^1.0.2" + "@node-redis/time-series" "^1.0.1" + redis@3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/redis/-/redis-3.1.2.tgz#766851117e80653d23e0ed536254677ab647638c" @@ -6802,6 +6273,13 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== +rimraf@2: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" @@ -6827,12 +6305,7 @@ s-age@1.1.2: resolved "https://registry.yarnpkg.com/s-age/-/s-age-1.1.2.tgz#c0cf15233ccc93f41de92ea42c36d957977d1ea2" integrity sha512-aSN2TlF39WLoZA/6cgYSJZhKt63kJ4EaadejPWjWY9/h4rksIqvfWY3gfd+3uAegSM1IXsA9aWeEhJtkxkFQtA== -safari-14-idb-fix@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/safari-14-idb-fix/-/safari-14-idb-fix-1.0.4.tgz#5c68ba63e2a8ae0d89a0aa1e13fe89e3aef7da19" - integrity sha512-4+Y2baQdgJpzu84d0QjySl70Kyygzf0pepVg8NVg4NnQEPpfC91fAn0baNvtStlCjUUxxiu0BOMiafa98fRRuA== - -safe-buffer@*: +safe-buffer@*, safe-buffer@5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -6852,10 +6325,10 @@ safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sanitize-html@2.5.3: - version "2.5.3" - resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.5.3.tgz#91aa3dc760b072cdf92f9c6973747569b1ba1cd8" - integrity sha512-DGATXd1fs/Rm287/i5FBKVYSBBUL0iAaztOA1/RFhEs4yqo39/X52i/q/CwsfCUG5cilmXSBmnQmyWfnKhBlOg== +sanitize-html@2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.6.1.tgz#5d37c08e189c61c0631560a889b10d9d155d000e" + integrity sha512-DzjSz3H5qDntD7s1TcWCSoRPmNR8UmA+y+xZQOvWgjATe2Br9ZW73+vD3Pj6Snrg0RuEuJdXgrKvnYuiuixRkA== dependencies: deepmerge "^4.2.2" escape-string-regexp "^4.0.0" @@ -6905,17 +6378,12 @@ seedrandom@3.0.5: resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7" integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg== -semver@6.x: - version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== - -semver@^5.4.1, semver@^5.6.0: +semver@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@^7.2.1, semver@^7.3.2, semver@^7.3.4: +semver@^7.3.2, semver@^7.3.4: version "7.3.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== @@ -6941,7 +6409,7 @@ set-blocking@^2.0.0, set-blocking@~2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= -setimmediate@^1.0.5: +setimmediate@^1.0.5, setimmediate@~1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= @@ -6964,17 +6432,17 @@ sha.js@^2.4.11: inherits "^2.0.1" safe-buffer "^5.0.1" -sharp@0.29.2: - version "0.29.2" - resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.29.2.tgz#e8c003cd9cb321585b32dbda6eed3baa7d6f2308" - integrity sha512-XWRdiYLIJ3tDUejRyG24KERnJzMfIoyiJBntd2S6/uj3NEeNgRFRLgiBlvPxMa8aml14dKKD98yHinSNKp1xzQ== +sharp@0.29.3: + version "0.29.3" + resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.29.3.tgz#0da183d626094c974516a48fab9b3e4ba92eb5c2" + integrity sha512-fKWUuOw77E4nhpyzCCJR1ayrttHoFHBT2U/kR/qEMRhvPEcluG4BKj324+SCO1e84+knXHwhJ1HHJGnUt4ElGA== dependencies: color "^4.0.1" detect-libc "^1.0.3" node-addon-api "^4.2.0" - prebuild-install "^6.1.4" + prebuild-install "^7.0.0" semver "^7.3.5" - simple-get "^3.1.0" + simple-get "^4.0.0" tar-fs "^2.1.1" tunnel-agent "^0.6.0" @@ -7019,12 +6487,12 @@ simple-concat@^1.0.0: resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.0.tgz#7344cbb8b6e26fb27d66b2fc86f9f6d5997521c6" integrity sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY= -simple-get@^3.0.3, simple-get@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.0.tgz#b45be062435e50d159540b576202ceec40b9c6b3" - integrity sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA== +simple-get@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.0.tgz#73fa628278d21de83dadd5512d2cc1f4872bd675" + integrity sha512-ZalZGexYr3TA0SwySsr5HlgOOinS4Jsa8YB2GJ6lUNAazyAu4KG/VmzMTwAt2YVXzzVj8QmefmAonZIK2BSGcQ== dependencies: - decompress-response "^4.2.0" + decompress-response "^6.0.0" once "^1.3.1" simple-concat "^1.0.0" @@ -7045,16 +6513,16 @@ smart-buffer@^4.1.0: resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.1.0.tgz#91605c25d91652f4661ea69ccf45f1b331ca21ba" integrity sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw== -socks-proxy-agent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-5.0.0.tgz#7c0f364e7b1cf4a7a437e71253bed72e9004be60" - integrity sha512-lEpa1zsWCChxiynk+lCycKuC502RxDWLKJZoIhnxrWNjLSDGYRFflHA1/228VkRcnv9TIb8w98derGbpKxJRgA== +socks-proxy-agent@^6.0.0: + version "6.1.1" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz#e664e8f1aaf4e1fb3df945f09e3d94f911137f87" + integrity sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew== dependencies: - agent-base "6" - debug "4" - socks "^2.3.3" + agent-base "^6.0.2" + debug "^4.3.1" + socks "^2.6.1" -socks@^2.3.3: +socks@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/socks/-/socks-2.6.1.tgz#989e6534a07cf337deb1b1c94aaa44296520d30e" integrity sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA== @@ -7109,11 +6577,6 @@ sprintf-js@1.1.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug== -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= - sshpk@^1.14.1: version "1.16.1" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" @@ -7136,11 +6599,6 @@ ssri@^8.0.0, ssri@^8.0.1: dependencies: minipass "^3.1.1" -stable@^0.1.8: - version "0.1.8" - resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" - integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== - standard-as-callback@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45" @@ -7185,14 +6643,14 @@ string-width@^1.0.1: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" -string-width@^3.0.0, string-width@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" - integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== dependencies: - emoji-regex "^7.0.1" - is-fullwidth-code-point "^2.0.0" - strip-ansi "^5.1.0" + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" string-width@^4.1.0, string-width@^4.2.0: version "4.2.0" @@ -7203,14 +6661,6 @@ string-width@^4.1.0, string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" -string.prototype.trimend@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" - integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - string.prototype.trimend@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" @@ -7219,32 +6669,6 @@ string.prototype.trimend@^1.0.4: call-bind "^1.0.2" define-properties "^1.1.3" -string.prototype.trimleft@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz#4408aa2e5d6ddd0c9a80739b087fbc067c03b3cc" - integrity sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - string.prototype.trimstart "^1.0.0" - -string.prototype.trimright@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz#c76f1cef30f21bbad8afeb8db1511496cfb0f2a3" - integrity sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - string.prototype.trimend "^1.0.0" - -string.prototype.trimstart@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" - integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - string.prototype.trimstart@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" @@ -7293,13 +6717,6 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" -strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" - integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== - dependencies: - ansi-regex "^4.1.0" - strip-ansi@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" @@ -7347,14 +6764,6 @@ style-loader@3.3.1: resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.1.tgz#057dfa6b3d4d7c7064462830f9113ed417d38575" integrity sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ== -stylehacks@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-5.0.1.tgz#323ec554198520986806388c7fdaebc38d2c06fb" - integrity sha512-Es0rVnHIqbWzveU1b24kbw92HsebBepxfcqe5iix7t9j0PQqhs0IxXVXv0pY2Bxa08CgMkzD6OWql7kbGOuEdA== - dependencies: - browserslist "^4.16.0" - postcss-selector-parser "^6.0.4" - summaly@2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/summaly/-/summaly-2.5.0.tgz#ec5af6e84857efcb6c844d896e83569e64a923ea" @@ -7392,19 +6801,6 @@ supports-color@^7.0.0, supports-color@^7.1.0: dependencies: has-flag "^4.0.0" -svgo@^2.7.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24" - integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg== - dependencies: - "@trysound/sax" "0.2.0" - commander "^7.2.0" - css-select "^4.1.3" - css-tree "^1.1.3" - csso "^4.2.0" - picocolors "^1.0.0" - stable "^0.1.8" - symbol-tree@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" @@ -7481,10 +6877,10 @@ tar@^6.0.2: mkdirp "^1.0.3" yallist "^4.0.0" -tar@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.0.tgz#d1724e9bcc04b977b18d5c573b333a2207229a83" - integrity sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA== +tar@^6.1.2: + version "6.1.11" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" + integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" @@ -7543,11 +6939,6 @@ through@2: resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= -timsort@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" - integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= - tinycolor2@1.4.2: version "1.4.2" resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.2.tgz#3f6a4d1071ad07676d7fa472e1fac40a719d8803" @@ -7611,21 +7002,10 @@ trace-redirect@1.0.6: resolved "https://registry.yarnpkg.com/trace-redirect/-/trace-redirect-1.0.6.tgz#ac629b5bf8247d30dde5a35fe9811b811075b504" integrity sha512-UUfa1DjjU5flcjMdaFIiIEGDTyu2y/IiMjOX4uGXa7meKBS4vD4f2Uy/tken9Qkd4Jsm4sRsfZcIIPqrRVF3Mg== -ts-jest@^25.2.1: - version "25.5.1" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-25.5.1.tgz#2913afd08f28385d54f2f4e828be4d261f4337c7" - integrity sha512-kHEUlZMK8fn8vkxDjwbHlxXRB9dHYpyzqKIGDNxbzs+Rz+ssNDSDNusEK8Fk/sDd4xE6iKoQLfFkFVaskmTJyw== - dependencies: - bs-logger "0.x" - buffer-from "1.x" - fast-json-stable-stringify "2.x" - json5 "2.x" - lodash.memoize "4.x" - make-error "1.x" - micromatch "4.x" - mkdirp "0.x" - semver "6.x" - yargs-parser "18.x" +"traverse@>=0.3.0 <0.4": + version "0.3.9" + resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" + integrity sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk= ts-loader@9.2.6: version "9.2.6" @@ -7667,10 +7047,10 @@ tsc-alias@1.4.1: mylas "^2.1.4" normalize-path "^3.0.0" -tsconfig-paths@3.11.0, tsconfig-paths@^3.11.0: - version "3.11.0" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz#954c1fe973da6339c78e06b03ce2e48810b65f36" - integrity sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA== +tsconfig-paths@3.12.0, tsconfig-paths@^3.12.0: + version "3.12.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz#19769aca6ee8f6a1a341e38c8fa45dd9fb18899b" + integrity sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg== dependencies: "@types/json5" "^0.0.29" json5 "^1.0.1" @@ -7770,10 +7150,10 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typeorm@0.2.39: - version "0.2.39" - resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.2.39.tgz#4d22fc68d114b2ca88a8d7b064f31af15e836ade" - integrity sha512-yQdvDpmmmn8wp1We25V76KIBPYR/lDbymNbGC++Uq8mSRhpHIPnlg26VAT4CF6Ypqx72zn8eqr+/72uSo7HdJQ== +typeorm@0.2.41: + version "0.2.41" + resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.2.41.tgz#88758101ac158dc0a0a903d70eaacea2974281cc" + integrity sha512-/d8CLJJxKPgsnrZWiMyPI0rz2MFZnBQrnQ5XP3Vu3mswv2WPexb58QM6BEtmRmlTMYN5KFWUz8SKluze+wS9xw== dependencies: "@sqltools/formatter" "^1.2.2" app-root-path "^3.0.0" @@ -7792,10 +7172,10 @@ typeorm@0.2.39: yargs "^17.0.1" zen-observable-ts "^1.0.0" -typescript@4.4.4: - version "4.4.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.4.tgz#2cd01a1a1f160704d3101fd5a58ff0f9fcb8030c" - integrity sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA== +typescript@4.5.5: + version "4.5.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3" + integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA== ulid@2.3.0: version "2.3.0" @@ -7812,16 +7192,6 @@ unbox-primitive@^1.0.1: has-symbols "^1.0.2" which-boxed-primitive "^1.0.2" -uniq@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" - integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8= - -uniqs@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" - integrity sha1-/+3ks2slKQaW5uFl1KWe25mOawI= - unique-filename@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" @@ -7854,6 +7224,22 @@ unpipe@1.0.0: resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= +unzipper@0.10.11: + version "0.10.11" + resolved "https://registry.yarnpkg.com/unzipper/-/unzipper-0.10.11.tgz#0b4991446472cbdb92ee7403909f26c2419c782e" + integrity sha512-+BrAq2oFqWod5IESRjL3S8baohbevGcVA+teAIOYWM3pDVdseogqbzhhvvmiyQrUNKFUnDMtELW3X8ykbyDCJw== + dependencies: + big-integer "^1.6.17" + binary "~0.3.0" + bluebird "~3.4.1" + buffer-indexof-polyfill "~1.0.0" + duplexer2 "~0.1.4" + fstream "^1.0.12" + graceful-fs "^4.2.2" + listenercount "~1.0.1" + readable-stream "~2.3.6" + setimmediate "~1.0.4" + uri-js@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" @@ -7881,21 +7267,11 @@ utf-8-validate@^5.0.2: dependencies: node-gyp-build "~3.7.0" -util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: +util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= -util.promisify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee" - integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.2" - has-symbols "^1.0.1" - object.getownpropertydescriptors "^2.1.0" - uuid@3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" @@ -7921,11 +7297,6 @@ vary@^1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= -vendors@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e" - integrity sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w== - verror@1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" @@ -7940,19 +7311,6 @@ void-elements@^3.1.0: resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" integrity sha1-YU9/v42AHwu18GYfWy9XhXUOTwk= -vue-eslint-parser@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-8.0.1.tgz#25e08b20a414551531f3e19f999902e1ecf45f13" - integrity sha512-lhWjDXJhe3UZw2uu3ztX51SJAPGPey1Tff2RK3TyZURwbuI4vximQLzz4nQfCv8CZq4xx7uIiogHMMoSJPr33A== - dependencies: - debug "^4.3.2" - eslint-scope "^6.0.0" - eslint-visitor-keys "^3.0.0" - espree "^9.0.0" - esquery "^1.4.0" - lodash "^4.17.21" - semver "^7.3.5" - w3c-hr-time@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" @@ -7987,6 +7345,11 @@ web-push@3.4.5: minimist "^1.2.5" urlsafe-base64 "^1.0.0" +web-streams-polyfill@^3.0.3: + version "3.2.0" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz#a6b74026b38e4885869fb5c589e90b95ccfc7965" + integrity sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA== + webidl-conversions@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" @@ -8113,6 +7476,13 @@ wide-align@1.1.3, wide-align@^1.1.0: dependencies: string-width "^1.0.2 || 2" +wide-align@^1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" + integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== + dependencies: + string-width "^1.0.2 || 2 || 3 || 4" + with@^7.0.0: version "7.0.2" resolved "https://registry.yarnpkg.com/with/-/with-7.0.2.tgz#ccee3ad542d25538a7a7a80aad212b9828495bac" @@ -8133,14 +7503,14 @@ workerpool@6.1.0: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.1.0.tgz#a8e038b4c94569596852de7a8ea4228eefdeb37b" integrity sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg== -wrap-ansi@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" - integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== dependencies: - ansi-styles "^3.2.0" - string-width "^3.0.0" - strip-ansi "^5.0.0" + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" wrap-ansi@^7.0.0: version "7.0.0" @@ -8156,10 +7526,10 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -ws@8.2.3: - version "8.2.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.2.3.tgz#63a56456db1b04367d0b721a0b80cae6d8becbba" - integrity sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA== +ws@8.4.2: + version "8.4.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.4.2.tgz#18e749868d8439f2268368829042894b6907aa0b" + integrity sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA== ws@^7.4.6: version "7.5.3" @@ -8234,43 +7604,30 @@ yaeti@^0.0.6: resolved "https://registry.yarnpkg.com/yaeti/-/yaeti-0.0.6.tgz#f26f484d72684cf42bedfb76970aa1608fbf9577" integrity sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc= +yallist@4.0.0, yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + yallist@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== - yaml-ast-parser@0.0.43: version "0.0.43" resolved "https://registry.yarnpkg.com/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz#e8a23e6fb4c38076ab92995c5dca33f3d3d7c9bb" integrity sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A== -yaml@^1.10.2: - version "1.10.2" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" - integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== - -yargs-parser@18.x: - version "18.1.3" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" - integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - yargs-parser@20.2.4, yargs-parser@^20.2.2: version "20.2.4" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== -yargs-parser@^13.1.2: - version "13.1.2" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" - integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== +yargs-parser@^18.1.2: + version "18.1.3" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" + integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== dependencies: camelcase "^5.0.0" decamelize "^1.2.0" @@ -8298,21 +7655,22 @@ yargs@16.2.0, yargs@^16.0.0: y18n "^5.0.5" yargs-parser "^20.2.2" -yargs@^13.2.4: - version "13.3.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" - integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== +yargs@^15.3.1: + version "15.4.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" + integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== dependencies: - cliui "^5.0.0" - find-up "^3.0.0" + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" get-caller-file "^2.0.1" require-directory "^2.1.1" require-main-filename "^2.0.0" set-blocking "^2.0.0" - string-width "^3.0.0" + string-width "^4.2.0" which-module "^2.0.0" y18n "^4.0.0" - yargs-parser "^13.1.2" + yargs-parser "^18.1.2" yargs@^17.0.1: version "17.1.1" diff --git a/packages/client/.eslintrc.js b/packages/client/.eslintrc.js index 8e4ff6e455..d414f86ed3 100644 --- a/packages/client/.eslintrc.js +++ b/packages/client/.eslintrc.js @@ -14,6 +14,10 @@ module.exports = { "plugin:vue/vue3-recommended" ], rules: { + // window の禁止理由: グローバルスコープと衝突し、予期せぬ結果を招くため + // data の禁止理由: 抽象的すぎるため + // e の禁止理由: error や event など、複数のキーワードの頭文字であり分かりにくいため + "id-denylist": ["error", "window", "data", "e"], "vue/attributes-order": ["error", { "alphabetical": false }], @@ -43,6 +47,7 @@ module.exports = { "vue/no-unused-components": "warn", "vue/valid-v-for": "warn", "vue/return-in-computed-property": "warn", + "vue/no-setup-props-destructure": "warn", "vue/max-attributes-per-line": "off", "vue/html-self-closing": "off", "vue/singleline-html-element-content-newline": "off", diff --git a/packages/client/@types/vue.d.ts b/packages/client/@types/vue.d.ts index 8cb6130629..f6b66228f6 100644 --- a/packages/client/@types/vue.d.ts +++ b/packages/client/@types/vue.d.ts @@ -1,3 +1,5 @@ +/// <reference types="vue/macros-global" /> + declare module '*.vue' { import type { DefineComponent } from 'vue'; const component: DefineComponent<{}, {}, any>; diff --git a/packages/client/assets/room/furnitures/banknote/banknote.blend b/packages/client/assets/room/furnitures/banknote/banknote.blend Binary files differdeleted file mode 100644 index 60b1968a29..0000000000 --- a/packages/client/assets/room/furnitures/banknote/banknote.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/banknote/banknote.glb b/packages/client/assets/room/furnitures/banknote/banknote.glb Binary files differdeleted file mode 100644 index f4ef0b91e7..0000000000 --- a/packages/client/assets/room/furnitures/banknote/banknote.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/banknote/tex.png b/packages/client/assets/room/furnitures/banknote/tex.png Binary files differdeleted file mode 100644 index 9106dc1457..0000000000 --- a/packages/client/assets/room/furnitures/banknote/tex.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/bed/bed.blend b/packages/client/assets/room/furnitures/bed/bed.blend Binary files differdeleted file mode 100644 index 731df76d0c..0000000000 --- a/packages/client/assets/room/furnitures/bed/bed.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/bed/bed.glb b/packages/client/assets/room/furnitures/bed/bed.glb Binary files differdeleted file mode 100644 index f35ecb9ef4..0000000000 --- a/packages/client/assets/room/furnitures/bed/bed.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/bin/bin.blend b/packages/client/assets/room/furnitures/bin/bin.blend Binary files differdeleted file mode 100644 index 8d459a0869..0000000000 --- a/packages/client/assets/room/furnitures/bin/bin.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/bin/bin.glb b/packages/client/assets/room/furnitures/bin/bin.glb Binary files differdeleted file mode 100644 index b45f203802..0000000000 --- a/packages/client/assets/room/furnitures/bin/bin.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/book/book.blend b/packages/client/assets/room/furnitures/book/book.blend Binary files differdeleted file mode 100644 index 0d4899d4ae..0000000000 --- a/packages/client/assets/room/furnitures/book/book.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/book/book.glb b/packages/client/assets/room/furnitures/book/book.glb Binary files differdeleted file mode 100644 index 546893da06..0000000000 --- a/packages/client/assets/room/furnitures/book/book.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/book2/barcode.png b/packages/client/assets/room/furnitures/book2/barcode.png Binary files differdeleted file mode 100644 index 37cfe5add3..0000000000 --- a/packages/client/assets/room/furnitures/book2/barcode.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/book2/book2.blend b/packages/client/assets/room/furnitures/book2/book2.blend Binary files differdeleted file mode 100644 index e0fdb48101..0000000000 --- a/packages/client/assets/room/furnitures/book2/book2.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/book2/book2.glb b/packages/client/assets/room/furnitures/book2/book2.glb Binary files differdeleted file mode 100644 index 2b26402f8c..0000000000 --- a/packages/client/assets/room/furnitures/book2/book2.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/book2/texture.afdesign b/packages/client/assets/room/furnitures/book2/texture.afdesign Binary files differdeleted file mode 100644 index b63771607a..0000000000 --- a/packages/client/assets/room/furnitures/book2/texture.afdesign +++ /dev/null diff --git a/packages/client/assets/room/furnitures/book2/texture.png b/packages/client/assets/room/furnitures/book2/texture.png Binary files differdeleted file mode 100644 index 5aa84f0340..0000000000 --- a/packages/client/assets/room/furnitures/book2/texture.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/book2/uv.png b/packages/client/assets/room/furnitures/book2/uv.png Binary files differdeleted file mode 100644 index 61c4fb0400..0000000000 --- a/packages/client/assets/room/furnitures/book2/uv.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/cardboard-box/cardboard-box.blend b/packages/client/assets/room/furnitures/cardboard-box/cardboard-box.blend Binary files differdeleted file mode 100644 index 3a528de32a..0000000000 --- a/packages/client/assets/room/furnitures/cardboard-box/cardboard-box.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/cardboard-box/cardboard-box.glb b/packages/client/assets/room/furnitures/cardboard-box/cardboard-box.glb Binary files differdeleted file mode 100644 index bed372e94f..0000000000 --- a/packages/client/assets/room/furnitures/cardboard-box/cardboard-box.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/cardboard-box2/cardboard-box2.blend b/packages/client/assets/room/furnitures/cardboard-box2/cardboard-box2.blend Binary files differdeleted file mode 100644 index 5f146267ac..0000000000 --- a/packages/client/assets/room/furnitures/cardboard-box2/cardboard-box2.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/cardboard-box2/cardboard-box2.glb b/packages/client/assets/room/furnitures/cardboard-box2/cardboard-box2.glb Binary files differdeleted file mode 100644 index 85fcb5c0b6..0000000000 --- a/packages/client/assets/room/furnitures/cardboard-box2/cardboard-box2.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/cardboard-box2/texture.png b/packages/client/assets/room/furnitures/cardboard-box2/texture.png Binary files differdeleted file mode 100644 index e498d8f65b..0000000000 --- a/packages/client/assets/room/furnitures/cardboard-box2/texture.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/cardboard-box2/uv.png b/packages/client/assets/room/furnitures/cardboard-box2/uv.png Binary files differdeleted file mode 100644 index d547843ee0..0000000000 --- a/packages/client/assets/room/furnitures/cardboard-box2/uv.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/cardboard-box3/cardboard-box3.blend b/packages/client/assets/room/furnitures/cardboard-box3/cardboard-box3.blend Binary files differdeleted file mode 100644 index 00681a3cfd..0000000000 --- a/packages/client/assets/room/furnitures/cardboard-box3/cardboard-box3.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/cardboard-box3/cardboard-box3.glb b/packages/client/assets/room/furnitures/cardboard-box3/cardboard-box3.glb Binary files differdeleted file mode 100644 index 1ef0427689..0000000000 --- a/packages/client/assets/room/furnitures/cardboard-box3/cardboard-box3.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/cardboard-box3/texture.png b/packages/client/assets/room/furnitures/cardboard-box3/texture.png Binary files differdeleted file mode 100644 index 56c914cb9d..0000000000 --- a/packages/client/assets/room/furnitures/cardboard-box3/texture.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/cardboard-box3/texture.xcf b/packages/client/assets/room/furnitures/cardboard-box3/texture.xcf Binary files differdeleted file mode 100644 index 7ffb3e3439..0000000000 --- a/packages/client/assets/room/furnitures/cardboard-box3/texture.xcf +++ /dev/null diff --git a/packages/client/assets/room/furnitures/cardboard-box3/uv.png b/packages/client/assets/room/furnitures/cardboard-box3/uv.png Binary files differdeleted file mode 100644 index 797ac509db..0000000000 --- a/packages/client/assets/room/furnitures/cardboard-box3/uv.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/carpet-stripe/carpet-stripe.blend b/packages/client/assets/room/furnitures/carpet-stripe/carpet-stripe.blend Binary files differdeleted file mode 100644 index 750343d4f0..0000000000 --- a/packages/client/assets/room/furnitures/carpet-stripe/carpet-stripe.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/carpet-stripe/carpet-stripe.glb b/packages/client/assets/room/furnitures/carpet-stripe/carpet-stripe.glb Binary files differdeleted file mode 100644 index 3066a69e35..0000000000 --- a/packages/client/assets/room/furnitures/carpet-stripe/carpet-stripe.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/chair/chair.blend b/packages/client/assets/room/furnitures/chair/chair.blend Binary files differdeleted file mode 100644 index 79c29a8401..0000000000 --- a/packages/client/assets/room/furnitures/chair/chair.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/chair/chair.glb b/packages/client/assets/room/furnitures/chair/chair.glb Binary files differdeleted file mode 100644 index 08ee1a0bb0..0000000000 --- a/packages/client/assets/room/furnitures/chair/chair.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/chair2/chair2.blend b/packages/client/assets/room/furnitures/chair2/chair2.blend Binary files differdeleted file mode 100644 index c6a1acd96f..0000000000 --- a/packages/client/assets/room/furnitures/chair2/chair2.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/chair2/chair2.glb b/packages/client/assets/room/furnitures/chair2/chair2.glb Binary files differdeleted file mode 100644 index 5ea2f3518b..0000000000 --- a/packages/client/assets/room/furnitures/chair2/chair2.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/color-box/color-box.blend b/packages/client/assets/room/furnitures/color-box/color-box.blend Binary files differdeleted file mode 100644 index f96a4ff766..0000000000 --- a/packages/client/assets/room/furnitures/color-box/color-box.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/color-box/color-box.glb b/packages/client/assets/room/furnitures/color-box/color-box.glb Binary files differdeleted file mode 100644 index 43f2abcae8..0000000000 --- a/packages/client/assets/room/furnitures/color-box/color-box.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/corkboard/corkboard.blend b/packages/client/assets/room/furnitures/corkboard/corkboard.blend Binary files differdeleted file mode 100644 index 9a7e1878cd..0000000000 --- a/packages/client/assets/room/furnitures/corkboard/corkboard.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/corkboard/corkboard.glb b/packages/client/assets/room/furnitures/corkboard/corkboard.glb Binary files differdeleted file mode 100644 index fee108fb91..0000000000 --- a/packages/client/assets/room/furnitures/corkboard/corkboard.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/cube/cube.blend b/packages/client/assets/room/furnitures/cube/cube.blend Binary files differdeleted file mode 100644 index 1af5bf40a9..0000000000 --- a/packages/client/assets/room/furnitures/cube/cube.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/cube/cube.glb b/packages/client/assets/room/furnitures/cube/cube.glb Binary files differdeleted file mode 100644 index 4ac8b6036d..0000000000 --- a/packages/client/assets/room/furnitures/cube/cube.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/cup-noodle/cup-noodle.blend b/packages/client/assets/room/furnitures/cup-noodle/cup-noodle.blend Binary files differdeleted file mode 100644 index 37ca8868c7..0000000000 --- a/packages/client/assets/room/furnitures/cup-noodle/cup-noodle.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/cup-noodle/cup-noodle.glb b/packages/client/assets/room/furnitures/cup-noodle/cup-noodle.glb Binary files differdeleted file mode 100644 index 58efb1b3b4..0000000000 --- a/packages/client/assets/room/furnitures/cup-noodle/cup-noodle.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/cup-noodle/noodle.png b/packages/client/assets/room/furnitures/cup-noodle/noodle.png Binary files differdeleted file mode 100644 index 1d74e0bbe7..0000000000 --- a/packages/client/assets/room/furnitures/cup-noodle/noodle.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/desk/desk.blend b/packages/client/assets/room/furnitures/desk/desk.blend Binary files differdeleted file mode 100644 index c88d01f0b2..0000000000 --- a/packages/client/assets/room/furnitures/desk/desk.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/desk/desk.glb b/packages/client/assets/room/furnitures/desk/desk.glb Binary files differdeleted file mode 100644 index 4a58513095..0000000000 --- a/packages/client/assets/room/furnitures/desk/desk.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/doll-ai/doll-ai.blend b/packages/client/assets/room/furnitures/doll-ai/doll-ai.blend Binary files differdeleted file mode 100644 index a912231ecb..0000000000 --- a/packages/client/assets/room/furnitures/doll-ai/doll-ai.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/doll-ai/doll-ai.glb b/packages/client/assets/room/furnitures/doll-ai/doll-ai.glb Binary files differdeleted file mode 100644 index ec55a7bd7b..0000000000 --- a/packages/client/assets/room/furnitures/doll-ai/doll-ai.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/doll-ai/doll_ai_tex.png b/packages/client/assets/room/furnitures/doll-ai/doll_ai_tex.png Binary files differdeleted file mode 100644 index 370ca5f75b..0000000000 --- a/packages/client/assets/room/furnitures/doll-ai/doll_ai_tex.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/energy-drink/energy-drink.blend b/packages/client/assets/room/furnitures/energy-drink/energy-drink.blend Binary files differdeleted file mode 100644 index 65fc41273e..0000000000 --- a/packages/client/assets/room/furnitures/energy-drink/energy-drink.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/energy-drink/energy-drink.glb b/packages/client/assets/room/furnitures/energy-drink/energy-drink.glb Binary files differdeleted file mode 100644 index 7fb1c27836..0000000000 --- a/packages/client/assets/room/furnitures/energy-drink/energy-drink.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/energy-drink/texture.afdesign b/packages/client/assets/room/furnitures/energy-drink/texture.afdesign Binary files differdeleted file mode 100644 index 8c117a49b1..0000000000 --- a/packages/client/assets/room/furnitures/energy-drink/texture.afdesign +++ /dev/null diff --git a/packages/client/assets/room/furnitures/energy-drink/texture.png b/packages/client/assets/room/furnitures/energy-drink/texture.png Binary files differdeleted file mode 100644 index 484ca0f96f..0000000000 --- a/packages/client/assets/room/furnitures/energy-drink/texture.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/energy-drink/uv.png b/packages/client/assets/room/furnitures/energy-drink/uv.png Binary files differdeleted file mode 100644 index 2a3f20c999..0000000000 --- a/packages/client/assets/room/furnitures/energy-drink/uv.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/eraser/cover.png b/packages/client/assets/room/furnitures/eraser/cover.png Binary files differdeleted file mode 100644 index 932a3fc62e..0000000000 --- a/packages/client/assets/room/furnitures/eraser/cover.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/eraser/cover.psd b/packages/client/assets/room/furnitures/eraser/cover.psd Binary files differdeleted file mode 100644 index c393337833..0000000000 --- a/packages/client/assets/room/furnitures/eraser/cover.psd +++ /dev/null diff --git a/packages/client/assets/room/furnitures/eraser/eraser-uv.png b/packages/client/assets/room/furnitures/eraser/eraser-uv.png Binary files differdeleted file mode 100644 index 89e4ea4c45..0000000000 --- a/packages/client/assets/room/furnitures/eraser/eraser-uv.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/eraser/eraser.blend b/packages/client/assets/room/furnitures/eraser/eraser.blend Binary files differdeleted file mode 100644 index 103c54fbae..0000000000 --- a/packages/client/assets/room/furnitures/eraser/eraser.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/eraser/eraser.glb b/packages/client/assets/room/furnitures/eraser/eraser.glb Binary files differdeleted file mode 100644 index 016b60df20..0000000000 --- a/packages/client/assets/room/furnitures/eraser/eraser.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/facial-tissue/facial-tissue-uv.png b/packages/client/assets/room/furnitures/facial-tissue/facial-tissue-uv.png Binary files differdeleted file mode 100644 index e3865ad15e..0000000000 --- a/packages/client/assets/room/furnitures/facial-tissue/facial-tissue-uv.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/facial-tissue/facial-tissue.blend b/packages/client/assets/room/furnitures/facial-tissue/facial-tissue.blend Binary files differdeleted file mode 100644 index d59f87c1ee..0000000000 --- a/packages/client/assets/room/furnitures/facial-tissue/facial-tissue.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/facial-tissue/facial-tissue.glb b/packages/client/assets/room/furnitures/facial-tissue/facial-tissue.glb Binary files differdeleted file mode 100644 index 48b36ef347..0000000000 --- a/packages/client/assets/room/furnitures/facial-tissue/facial-tissue.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/facial-tissue/facial-tissue.png b/packages/client/assets/room/furnitures/facial-tissue/facial-tissue.png Binary files differdeleted file mode 100644 index 7cee4b1859..0000000000 --- a/packages/client/assets/room/furnitures/facial-tissue/facial-tissue.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/facial-tissue/facial-tissue.psd b/packages/client/assets/room/furnitures/facial-tissue/facial-tissue.psd Binary files differdeleted file mode 100644 index cd59fc007b..0000000000 --- a/packages/client/assets/room/furnitures/facial-tissue/facial-tissue.psd +++ /dev/null diff --git a/packages/client/assets/room/furnitures/fan/fan.blend b/packages/client/assets/room/furnitures/fan/fan.blend Binary files differdeleted file mode 100644 index 8c8106e5fe..0000000000 --- a/packages/client/assets/room/furnitures/fan/fan.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/fan/fan.glb b/packages/client/assets/room/furnitures/fan/fan.glb Binary files differdeleted file mode 100644 index d9367f3534..0000000000 --- a/packages/client/assets/room/furnitures/fan/fan.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/holo-display/holo-display.blend b/packages/client/assets/room/furnitures/holo-display/holo-display.blend Binary files differdeleted file mode 100644 index 56d2e1f819..0000000000 --- a/packages/client/assets/room/furnitures/holo-display/holo-display.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/holo-display/holo-display.glb b/packages/client/assets/room/furnitures/holo-display/holo-display.glb Binary files differdeleted file mode 100644 index 4d042a59b3..0000000000 --- a/packages/client/assets/room/furnitures/holo-display/holo-display.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/holo-display/ray-uv.png b/packages/client/assets/room/furnitures/holo-display/ray-uv.png Binary files differdeleted file mode 100644 index aa7e817e0f..0000000000 --- a/packages/client/assets/room/furnitures/holo-display/ray-uv.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/holo-display/ray.png b/packages/client/assets/room/furnitures/holo-display/ray.png Binary files differdeleted file mode 100644 index 6a5d24e143..0000000000 --- a/packages/client/assets/room/furnitures/holo-display/ray.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/keyboard/keyboard.blend b/packages/client/assets/room/furnitures/keyboard/keyboard.blend Binary files differdeleted file mode 100644 index ab33d134b3..0000000000 --- a/packages/client/assets/room/furnitures/keyboard/keyboard.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/keyboard/keyboard.glb b/packages/client/assets/room/furnitures/keyboard/keyboard.glb Binary files differdeleted file mode 100644 index 15dc69f47a..0000000000 --- a/packages/client/assets/room/furnitures/keyboard/keyboard.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/low-table/low-table.blend b/packages/client/assets/room/furnitures/low-table/low-table.blend Binary files differdeleted file mode 100644 index e1592174d9..0000000000 --- a/packages/client/assets/room/furnitures/low-table/low-table.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/low-table/low-table.glb b/packages/client/assets/room/furnitures/low-table/low-table.glb Binary files differdeleted file mode 100644 index c69bf35d7b..0000000000 --- a/packages/client/assets/room/furnitures/low-table/low-table.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/mat/mat.blend b/packages/client/assets/room/furnitures/mat/mat.blend Binary files differdeleted file mode 100644 index a1e1a68c55..0000000000 --- a/packages/client/assets/room/furnitures/mat/mat.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/mat/mat.glb b/packages/client/assets/room/furnitures/mat/mat.glb Binary files differdeleted file mode 100644 index 87ccd44e1a..0000000000 --- a/packages/client/assets/room/furnitures/mat/mat.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/milk/milk-uv.png b/packages/client/assets/room/furnitures/milk/milk-uv.png Binary files differdeleted file mode 100644 index 258fd54638..0000000000 --- a/packages/client/assets/room/furnitures/milk/milk-uv.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/milk/milk.blend b/packages/client/assets/room/furnitures/milk/milk.blend Binary files differdeleted file mode 100644 index 2df508d5b9..0000000000 --- a/packages/client/assets/room/furnitures/milk/milk.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/milk/milk.glb b/packages/client/assets/room/furnitures/milk/milk.glb Binary files differdeleted file mode 100644 index b335fe3d02..0000000000 --- a/packages/client/assets/room/furnitures/milk/milk.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/milk/milk.png b/packages/client/assets/room/furnitures/milk/milk.png Binary files differdeleted file mode 100644 index 35181c8c8c..0000000000 --- a/packages/client/assets/room/furnitures/milk/milk.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/milk/milk.psd b/packages/client/assets/room/furnitures/milk/milk.psd Binary files differdeleted file mode 100644 index f31e439277..0000000000 --- a/packages/client/assets/room/furnitures/milk/milk.psd +++ /dev/null diff --git a/packages/client/assets/room/furnitures/monitor/monitor.blend b/packages/client/assets/room/furnitures/monitor/monitor.blend Binary files differdeleted file mode 100644 index 6c042ccdd8..0000000000 --- a/packages/client/assets/room/furnitures/monitor/monitor.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/monitor/monitor.glb b/packages/client/assets/room/furnitures/monitor/monitor.glb Binary files differdeleted file mode 100644 index fc33286a15..0000000000 --- a/packages/client/assets/room/furnitures/monitor/monitor.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/monitor/monitor.psd b/packages/client/assets/room/furnitures/monitor/monitor.psd Binary files differdeleted file mode 100644 index 57afff9cd9..0000000000 --- a/packages/client/assets/room/furnitures/monitor/monitor.psd +++ /dev/null diff --git a/packages/client/assets/room/furnitures/monitor/screen-uv.png b/packages/client/assets/room/furnitures/monitor/screen-uv.png Binary files differdeleted file mode 100644 index 35f74de8aa..0000000000 --- a/packages/client/assets/room/furnitures/monitor/screen-uv.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/monitor/screen.jpg b/packages/client/assets/room/furnitures/monitor/screen.jpg Binary files differdeleted file mode 100644 index 4004a1ede9..0000000000 --- a/packages/client/assets/room/furnitures/monitor/screen.jpg +++ /dev/null diff --git a/packages/client/assets/room/furnitures/moon/moon.blend b/packages/client/assets/room/furnitures/moon/moon.blend Binary files differdeleted file mode 100644 index 4ff3deab8e..0000000000 --- a/packages/client/assets/room/furnitures/moon/moon.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/moon/moon.glb b/packages/client/assets/room/furnitures/moon/moon.glb Binary files differdeleted file mode 100644 index 07fa7e4c02..0000000000 --- a/packages/client/assets/room/furnitures/moon/moon.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/moon/moon.jpg b/packages/client/assets/room/furnitures/moon/moon.jpg Binary files differdeleted file mode 100644 index 8988ac64b9..0000000000 --- a/packages/client/assets/room/furnitures/moon/moon.jpg +++ /dev/null diff --git a/packages/client/assets/room/furnitures/mousepad/mousepad.blend b/packages/client/assets/room/furnitures/mousepad/mousepad.blend Binary files differdeleted file mode 100644 index 14bd139c94..0000000000 --- a/packages/client/assets/room/furnitures/mousepad/mousepad.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/mousepad/mousepad.glb b/packages/client/assets/room/furnitures/mousepad/mousepad.glb Binary files differdeleted file mode 100644 index 681ada49cd..0000000000 --- a/packages/client/assets/room/furnitures/mousepad/mousepad.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/pc/motherboard-uv.png b/packages/client/assets/room/furnitures/pc/motherboard-uv.png Binary files differdeleted file mode 100644 index 355009fe7c..0000000000 --- a/packages/client/assets/room/furnitures/pc/motherboard-uv.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/pc/motherboard-uv.psd b/packages/client/assets/room/furnitures/pc/motherboard-uv.psd Binary files differdeleted file mode 100644 index 971f33f79e..0000000000 --- a/packages/client/assets/room/furnitures/pc/motherboard-uv.psd +++ /dev/null diff --git a/packages/client/assets/room/furnitures/pc/motherboard.jpg b/packages/client/assets/room/furnitures/pc/motherboard.jpg Binary files differdeleted file mode 100644 index d894e4efcf..0000000000 --- a/packages/client/assets/room/furnitures/pc/motherboard.jpg +++ /dev/null diff --git a/packages/client/assets/room/furnitures/pc/pc.blend b/packages/client/assets/room/furnitures/pc/pc.blend Binary files differdeleted file mode 100644 index 13dfec6ccc..0000000000 --- a/packages/client/assets/room/furnitures/pc/pc.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/pc/pc.glb b/packages/client/assets/room/furnitures/pc/pc.glb Binary files differdeleted file mode 100644 index 44a48b18ae..0000000000 --- a/packages/client/assets/room/furnitures/pc/pc.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/pencil/pencil.blend b/packages/client/assets/room/furnitures/pencil/pencil.blend Binary files differdeleted file mode 100644 index 0fc6bdd776..0000000000 --- a/packages/client/assets/room/furnitures/pencil/pencil.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/pencil/pencil.glb b/packages/client/assets/room/furnitures/pencil/pencil.glb Binary files differdeleted file mode 100644 index a938b5cdcc..0000000000 --- a/packages/client/assets/room/furnitures/pencil/pencil.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/photoframe/photo-uv.png b/packages/client/assets/room/furnitures/photoframe/photo-uv.png Binary files differdeleted file mode 100644 index 9b94906413..0000000000 --- a/packages/client/assets/room/furnitures/photoframe/photo-uv.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/photoframe/photo.jpg b/packages/client/assets/room/furnitures/photoframe/photo.jpg Binary files differdeleted file mode 100644 index af14f0f36a..0000000000 --- a/packages/client/assets/room/furnitures/photoframe/photo.jpg +++ /dev/null diff --git a/packages/client/assets/room/furnitures/photoframe/photoframe.blend b/packages/client/assets/room/furnitures/photoframe/photoframe.blend Binary files differdeleted file mode 100644 index 4224cde45b..0000000000 --- a/packages/client/assets/room/furnitures/photoframe/photoframe.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/photoframe/photoframe.glb b/packages/client/assets/room/furnitures/photoframe/photoframe.glb Binary files differdeleted file mode 100644 index 4255a77de6..0000000000 --- a/packages/client/assets/room/furnitures/photoframe/photoframe.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/piano/piano.blend b/packages/client/assets/room/furnitures/piano/piano.blend Binary files differdeleted file mode 100644 index 7653cdf672..0000000000 --- a/packages/client/assets/room/furnitures/piano/piano.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/piano/piano.glb b/packages/client/assets/room/furnitures/piano/piano.glb Binary files differdeleted file mode 100644 index 7242e78ceb..0000000000 --- a/packages/client/assets/room/furnitures/piano/piano.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/pinguin/pinguin.blend b/packages/client/assets/room/furnitures/pinguin/pinguin.blend Binary files differdeleted file mode 100644 index 514c713e4c..0000000000 --- a/packages/client/assets/room/furnitures/pinguin/pinguin.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/pinguin/pinguin.glb b/packages/client/assets/room/furnitures/pinguin/pinguin.glb Binary files differdeleted file mode 100644 index 6df34c06e9..0000000000 --- a/packages/client/assets/room/furnitures/pinguin/pinguin.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/plant/plant-soil-uv.png b/packages/client/assets/room/furnitures/plant/plant-soil-uv.png Binary files differdeleted file mode 100644 index d4971a896c..0000000000 --- a/packages/client/assets/room/furnitures/plant/plant-soil-uv.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/plant/plant-soil.png b/packages/client/assets/room/furnitures/plant/plant-soil.png Binary files differdeleted file mode 100644 index e79ccd240e..0000000000 --- a/packages/client/assets/room/furnitures/plant/plant-soil.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/plant/plant-soil.psd b/packages/client/assets/room/furnitures/plant/plant-soil.psd Binary files differdeleted file mode 100644 index 1457b7ea5b..0000000000 --- a/packages/client/assets/room/furnitures/plant/plant-soil.psd +++ /dev/null diff --git a/packages/client/assets/room/furnitures/plant/plant.blend b/packages/client/assets/room/furnitures/plant/plant.blend Binary files differdeleted file mode 100644 index aa38c7b54e..0000000000 --- a/packages/client/assets/room/furnitures/plant/plant.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/plant/plant.glb b/packages/client/assets/room/furnitures/plant/plant.glb Binary files differdeleted file mode 100644 index 38422b4a9b..0000000000 --- a/packages/client/assets/room/furnitures/plant/plant.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/plant2/plant2.blend b/packages/client/assets/room/furnitures/plant2/plant2.blend Binary files differdeleted file mode 100644 index 6592c5d98d..0000000000 --- a/packages/client/assets/room/furnitures/plant2/plant2.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/plant2/plant2.glb b/packages/client/assets/room/furnitures/plant2/plant2.glb Binary files differdeleted file mode 100644 index 223e6f5834..0000000000 --- a/packages/client/assets/room/furnitures/plant2/plant2.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/plant2/soil.png b/packages/client/assets/room/furnitures/plant2/soil.png Binary files differdeleted file mode 100644 index e79ccd240e..0000000000 --- a/packages/client/assets/room/furnitures/plant2/soil.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/poster-h/poster-h.blend b/packages/client/assets/room/furnitures/poster-h/poster-h.blend Binary files differdeleted file mode 100644 index 40f944f3c1..0000000000 --- a/packages/client/assets/room/furnitures/poster-h/poster-h.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/poster-h/poster-h.glb b/packages/client/assets/room/furnitures/poster-h/poster-h.glb Binary files differdeleted file mode 100644 index c6032c1009..0000000000 --- a/packages/client/assets/room/furnitures/poster-h/poster-h.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/poster-h/uv.png b/packages/client/assets/room/furnitures/poster-h/uv.png Binary files differdeleted file mode 100644 index f854231e0b..0000000000 --- a/packages/client/assets/room/furnitures/poster-h/uv.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/poster-v/poster-v.blend b/packages/client/assets/room/furnitures/poster-v/poster-v.blend Binary files differdeleted file mode 100644 index 07fe971634..0000000000 --- a/packages/client/assets/room/furnitures/poster-v/poster-v.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/poster-v/poster-v.glb b/packages/client/assets/room/furnitures/poster-v/poster-v.glb Binary files differdeleted file mode 100644 index 6e3782f193..0000000000 --- a/packages/client/assets/room/furnitures/poster-v/poster-v.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/poster-v/uv.png b/packages/client/assets/room/furnitures/poster-v/uv.png Binary files differdeleted file mode 100644 index 7bb2bf809e..0000000000 --- a/packages/client/assets/room/furnitures/poster-v/uv.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/pudding/pudding.blend b/packages/client/assets/room/furnitures/pudding/pudding.blend Binary files differdeleted file mode 100644 index bba40ce161..0000000000 --- a/packages/client/assets/room/furnitures/pudding/pudding.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/pudding/pudding.glb b/packages/client/assets/room/furnitures/pudding/pudding.glb Binary files differdeleted file mode 100644 index 06c9ed80cc..0000000000 --- a/packages/client/assets/room/furnitures/pudding/pudding.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/rubik-cube/rubik-cube.blend b/packages/client/assets/room/furnitures/rubik-cube/rubik-cube.blend Binary files differdeleted file mode 100644 index 6c09067e78..0000000000 --- a/packages/client/assets/room/furnitures/rubik-cube/rubik-cube.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/rubik-cube/rubik-cube.glb b/packages/client/assets/room/furnitures/rubik-cube/rubik-cube.glb Binary files differdeleted file mode 100644 index d640df9b06..0000000000 --- a/packages/client/assets/room/furnitures/rubik-cube/rubik-cube.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/server/rack-uv.png b/packages/client/assets/room/furnitures/server/rack-uv.png Binary files differdeleted file mode 100644 index 65bdb0ffd9..0000000000 --- a/packages/client/assets/room/furnitures/server/rack-uv.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/server/rack.png b/packages/client/assets/room/furnitures/server/rack.png Binary files differdeleted file mode 100644 index b851295cfa..0000000000 --- a/packages/client/assets/room/furnitures/server/rack.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/server/server.blend b/packages/client/assets/room/furnitures/server/server.blend Binary files differdeleted file mode 100644 index 6675dfbdc2..0000000000 --- a/packages/client/assets/room/furnitures/server/server.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/server/server.glb b/packages/client/assets/room/furnitures/server/server.glb Binary files differdeleted file mode 100644 index a8b530a2d2..0000000000 --- a/packages/client/assets/room/furnitures/server/server.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/server/server.png b/packages/client/assets/room/furnitures/server/server.png Binary files differdeleted file mode 100644 index 8e9a0d716c..0000000000 --- a/packages/client/assets/room/furnitures/server/server.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/server/uv.png b/packages/client/assets/room/furnitures/server/uv.png Binary files differdeleted file mode 100644 index ca2e747d16..0000000000 --- a/packages/client/assets/room/furnitures/server/uv.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/sofa/sofa.blend b/packages/client/assets/room/furnitures/sofa/sofa.blend Binary files differdeleted file mode 100644 index fb5aa51a2c..0000000000 --- a/packages/client/assets/room/furnitures/sofa/sofa.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/sofa/sofa.glb b/packages/client/assets/room/furnitures/sofa/sofa.glb Binary files differdeleted file mode 100644 index 6ce77d94ac..0000000000 --- a/packages/client/assets/room/furnitures/sofa/sofa.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/spiral/spiral.blend b/packages/client/assets/room/furnitures/spiral/spiral.blend Binary files differdeleted file mode 100644 index 9d3be77bce..0000000000 --- a/packages/client/assets/room/furnitures/spiral/spiral.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/spiral/spiral.glb b/packages/client/assets/room/furnitures/spiral/spiral.glb Binary files differdeleted file mode 100644 index ee8e3c23b1..0000000000 --- a/packages/client/assets/room/furnitures/spiral/spiral.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/tv/screen-uv.png b/packages/client/assets/room/furnitures/tv/screen-uv.png Binary files differdeleted file mode 100644 index 4bb74f031f..0000000000 --- a/packages/client/assets/room/furnitures/tv/screen-uv.png +++ /dev/null diff --git a/packages/client/assets/room/furnitures/tv/tv.blend b/packages/client/assets/room/furnitures/tv/tv.blend Binary files differdeleted file mode 100644 index 490e298e7b..0000000000 --- a/packages/client/assets/room/furnitures/tv/tv.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/tv/tv.glb b/packages/client/assets/room/furnitures/tv/tv.glb Binary files differdeleted file mode 100644 index b9bd23896b..0000000000 --- a/packages/client/assets/room/furnitures/tv/tv.glb +++ /dev/null diff --git a/packages/client/assets/room/furnitures/wall-clock/wall-clock.blend b/packages/client/assets/room/furnitures/wall-clock/wall-clock.blend Binary files differdeleted file mode 100644 index 0a61c8f01e..0000000000 --- a/packages/client/assets/room/furnitures/wall-clock/wall-clock.blend +++ /dev/null diff --git a/packages/client/assets/room/furnitures/wall-clock/wall-clock.glb b/packages/client/assets/room/furnitures/wall-clock/wall-clock.glb Binary files differdeleted file mode 100644 index b9f0093a8d..0000000000 --- a/packages/client/assets/room/furnitures/wall-clock/wall-clock.glb +++ /dev/null diff --git a/packages/client/assets/room/rooms/default/default.blend b/packages/client/assets/room/rooms/default/default.blend Binary files differdeleted file mode 100644 index 661154724a..0000000000 --- a/packages/client/assets/room/rooms/default/default.blend +++ /dev/null diff --git a/packages/client/assets/room/rooms/default/default.glb b/packages/client/assets/room/rooms/default/default.glb Binary files differdeleted file mode 100644 index 3d378deee2..0000000000 --- a/packages/client/assets/room/rooms/default/default.glb +++ /dev/null diff --git a/packages/client/assets/room/rooms/washitsu/husuma-uv.png b/packages/client/assets/room/rooms/washitsu/husuma-uv.png Binary files differdeleted file mode 100644 index ae2fca3911..0000000000 --- a/packages/client/assets/room/rooms/washitsu/husuma-uv.png +++ /dev/null diff --git a/packages/client/assets/room/rooms/washitsu/husuma.png b/packages/client/assets/room/rooms/washitsu/husuma.png Binary files differdeleted file mode 100644 index 084cbed67c..0000000000 --- a/packages/client/assets/room/rooms/washitsu/husuma.png +++ /dev/null diff --git a/packages/client/assets/room/rooms/washitsu/tatami-single1600.png b/packages/client/assets/room/rooms/washitsu/tatami-single1600.png Binary files differdeleted file mode 100644 index c0e684d743..0000000000 --- a/packages/client/assets/room/rooms/washitsu/tatami-single1600.png +++ /dev/null diff --git a/packages/client/assets/room/rooms/washitsu/tatami-uv.png b/packages/client/assets/room/rooms/washitsu/tatami-uv.png Binary files differdeleted file mode 100644 index 5b16c66091..0000000000 --- a/packages/client/assets/room/rooms/washitsu/tatami-uv.png +++ /dev/null diff --git a/packages/client/assets/room/rooms/washitsu/tatami.afdesign b/packages/client/assets/room/rooms/washitsu/tatami.afdesign Binary files differdeleted file mode 100644 index 9300a26950..0000000000 --- a/packages/client/assets/room/rooms/washitsu/tatami.afdesign +++ /dev/null diff --git a/packages/client/assets/room/rooms/washitsu/tatami.png b/packages/client/assets/room/rooms/washitsu/tatami.png Binary files differdeleted file mode 100644 index 8894d040ae..0000000000 --- a/packages/client/assets/room/rooms/washitsu/tatami.png +++ /dev/null diff --git a/packages/client/assets/room/rooms/washitsu/washitsu.blend b/packages/client/assets/room/rooms/washitsu/washitsu.blend Binary files differdeleted file mode 100644 index 84dc11374d..0000000000 --- a/packages/client/assets/room/rooms/washitsu/washitsu.blend +++ /dev/null diff --git a/packages/client/assets/room/rooms/washitsu/washitsu.glb b/packages/client/assets/room/rooms/washitsu/washitsu.glb Binary files differdeleted file mode 100644 index 5b4767bc73..0000000000 --- a/packages/client/assets/room/rooms/washitsu/washitsu.glb +++ /dev/null diff --git a/packages/client/package.json b/packages/client/package.json index 198bcbcef6..71dd89bea4 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -11,11 +11,9 @@ }, "dependencies": { "@discordapp/twemoji": "13.1.0", - "@sentry/browser": "5.29.2", - "@sentry/tracing": "5.29.2", "@syuilo/aiscript": "0.11.1", "@types/dateformat": "3.0.1", - "@types/escape-regexp": "0.0.0", + "@types/escape-regexp": "0.0.1", "@types/glob": "7.2.0", "@types/gulp": "4.0.9", "@types/gulp-rename": "2.0.1", @@ -23,7 +21,6 @@ "@types/katex": "0.11.1", "@types/matter-js": "0.17.6", "@types/mocha": "8.2.3", - "@types/node": "16.11.12", "@types/oauth": "0.9.1", "@types/parse5": "6.0.3", "@types/punycode": "2.1.0", @@ -34,55 +31,54 @@ "@types/throttle-debounce": "2.1.0", "@types/tinycolor2": "1.4.3", "@types/tmp": "0.2.3", - "@types/uuid": "8.3.3", + "@types/uuid": "8.3.4", "@types/web-push": "3.3.2", "@types/webpack": "5.28.0", "@types/webpack-stream": "3.2.12", "@types/websocket": "1.0.4", "@types/ws": "8.2.2", - "@typescript-eslint/parser": "5.8.1", - "@vue/compiler-sfc": "3.2.26", + "@typescript-eslint/parser": "5.10.0", + "@vue/compiler-sfc": "3.2.29", "abort-controller": "3.0.0", "autobind-decorator": "2.4.0", - "autosize": "4.0.4", + "autosize": "5.0.1", "autwh": "0.1.0", "blurhash": "1.1.4", "broadcast-channel": "4.9.0", "chart.js": "3.7.0", "chartjs-adapter-date-fns": "2.0.0", "chartjs-plugin-zoom": "1.2.0", - "compare-versions": "3.6.0", + "compare-versions": "4.1.3", "content-disposition": "0.5.4", "crc-32": "1.2.0", "css-loader": "6.5.1", - "cssnano": "5.0.14", + "cssnano": "5.0.15", "date-fns": "2.28.0", - "dateformat": "4.5.1", "escape-regexp": "0.0.1", - "eslint": "8.5.0", - "eslint-plugin-vue": "8.2.0", + "eslint": "8.7.0", + "eslint-plugin-vue": "8.3.0", "eventemitter3": "4.0.7", "feed": "4.2.2", "glob": "7.2.0", - "idb-keyval": "5.1.3", + "idb-keyval": "6.1.0", "insert-text-at-cursor": "0.3.0", "ip-cidr": "3.0.4", "json5": "2.2.0", "json5-loader": "4.0.1", - "katex": "0.15.1", + "katex": "0.15.2", "langmap": "0.0.16", - "matter-js": "0.17.1", - "mfm-js": "0.20.0", - "misskey-js": "0.0.10", + "matter-js": "0.18.0", + "mfm-js": "0.21.0", + "misskey-js": "0.0.13", "mocha": "8.4.0", "ms": "2.1.3", "nested-property": "4.0.0", "parse5": "6.0.1", - "photoswipe": "git://github.com/dimsemenov/photoswipe#v5-beta", + "photoswipe": "git+https://github.com/dimsemenov/photoswipe#v5-beta", "portscanner": "2.2.0", "postcss": "8.4.5", "postcss-loader": "6.2.1", - "prismjs": "1.25.0", + "prismjs": "1.26.0", "private-ip": "2.3.3", "promise-limit": "2.7.0", "pug": "3.0.2", @@ -94,7 +90,7 @@ "request-stats": "3.0.0", "rndstr": "1.0.0", "s-age": "1.1.2", - "sass": "1.45.1", + "sass": "1.49.0", "sass-loader": "12.4.0", "seedrandom": "3.0.5", "strict-event-emitter-types": "2.0.0", @@ -102,39 +98,39 @@ "style-loader": "3.3.1", "syuilo-password-strength": "0.0.1", "textarea-caret": "3.1.0", - "three": "0.117.1", + "three": "0.136.0", "throttle-debounce": "3.0.1", "tinycolor2": "1.4.2", "tmp": "0.2.1", "ts-loader": "9.2.6", "ts-node": "10.4.0", - "tsc-alias": "1.4.2", + "tsc-alias": "1.5.0", "tsconfig-paths": "3.12.0", "twemoji-parser": "13.1.0", - "typescript": "4.5.4", + "typescript": "4.5.5", "uuid": "8.3.2", "v-debounce": "0.1.2", "vanilla-tilt": "1.7.2", - "vue": "3.2.26", - "vue-loader": "16.8.3", + "vue": "3.2.29", + "vue-loader": "17.0.0", "vue-prism-editor": "2.0.0-alpha.2", "vue-router": "4.0.5", "vue-style-loader": "4.1.3", "vue-svg-loader": "0.17.0-beta.2", "vuedraggable": "4.0.1", "web-push": "3.4.5", - "webpack": "5.65.0", + "webpack": "5.66.0", "webpack-cli": "4.9.1", "websocket": "1.0.34", - "ws": "8.4.0" + "ws": "8.4.2" }, "devDependencies": { - "@redocly/openapi-core": "1.0.0-beta.54", - "@types/fluent-ffmpeg": "2.1.17", - "@typescript-eslint/eslint-plugin": "5.8.1", + "@redocly/openapi-core": "1.0.0-beta.79", + "@types/fluent-ffmpeg": "2.1.20", + "@typescript-eslint/eslint-plugin": "5.10.0", "cross-env": "7.0.3", - "cypress": "9.2.0", - "eslint-plugin-import": "2.25.3", + "cypress": "9.3.1", + "eslint-plugin-import": "2.25.4", "start-server-and-test": "1.14.0" } } diff --git a/packages/client/src/account.ts b/packages/client/src/account.ts index 4c83b78c91..5a935e1dc7 100644 --- a/packages/client/src/account.ts +++ b/packages/client/src/account.ts @@ -16,6 +16,8 @@ const data = localStorage.getItem('account'); // TODO: 外部からはreadonlyに export const $i = data ? reactive(JSON.parse(data) as Account) : null; +export const iAmModerator = $i != null && ($i.isAdmin || $i.isModerator); + export async function signout() { waiting(); localStorage.removeItem('account'); @@ -127,7 +129,12 @@ export async function login(token: Account['token'], redirect?: string) { unisonReload(); } -export async function openAccountMenu(ev: MouseEvent) { +export async function openAccountMenu(opts: { + includeCurrentAccount?: boolean; + withExtraOperation: boolean; + active?: misskey.entities.UserDetailed['id']; + onChoose?: (account: misskey.entities.UserDetailed) => void; +}, ev: MouseEvent) { function showSigninDialog() { popup(import('@/components/signin-dialog.vue'), {}, { done: res => { @@ -146,7 +153,7 @@ export async function openAccountMenu(ev: MouseEvent) { }, 'closed'); } - async function switchAccount(account: any) { + async function switchAccount(account: misskey.entities.UserDetailed) { const storedAccounts = await getAccounts(); const token = storedAccounts.find(x => x.id === account.id).token; switchAccountWithToken(token); @@ -159,48 +166,58 @@ export async function openAccountMenu(ev: MouseEvent) { const storedAccounts = await getAccounts().then(accounts => accounts.filter(x => x.id !== $i.id)); const accountsPromise = api('users/show', { userIds: storedAccounts.map(x => x.id) }); + function createItem(account: misskey.entities.UserDetailed) { + return { + type: 'user', + user: account, + active: opts.active != null ? opts.active === account.id : false, + action: () => { + if (opts.onChoose) { + opts.onChoose(account); + } else { + switchAccount(account); + } + }, + }; + } + const accountItemPromises = storedAccounts.map(a => new Promise(res => { accountsPromise.then(accounts => { const account = accounts.find(x => x.id === a.id); if (account == null) return res(null); - res({ - type: 'user', - user: account, - action: () => { switchAccount(account); } - }); + res(createItem(account)); }); })); - popupMenu([...[{ - type: 'link', - text: i18n.locale.profile, - to: `/@${ $i.username }`, - avatar: $i, - }, null, ...accountItemPromises, { - icon: 'fas fa-plus', - text: i18n.locale.addAccount, - action: () => { - popupMenu([{ - text: i18n.locale.existingAccount, - action: () => { showSigninDialog(); }, - }, { - text: i18n.locale.createAccount, - action: () => { createAccount(); }, - }], ev.currentTarget || ev.target); - }, - }, { - type: 'link', - icon: 'fas fa-users', - text: i18n.locale.manageAccounts, - to: `/settings/accounts`, - }]], ev.currentTarget || ev.target, { - align: 'left' - }); -} - -// このファイルに書きたくないけどここに書かないと何故かVeturが認識しない -declare module '@vue/runtime-core' { - interface ComponentCustomProperties { - $i: typeof $i; + if (opts.withExtraOperation) { + popupMenu([...[{ + type: 'link', + text: i18n.locale.profile, + to: `/@${ $i.username }`, + avatar: $i, + }, null, ...(opts.includeCurrentAccount ? [createItem($i)] : []), ...accountItemPromises, { + icon: 'fas fa-plus', + text: i18n.locale.addAccount, + action: () => { + popupMenu([{ + text: i18n.locale.existingAccount, + action: () => { showSigninDialog(); }, + }, { + text: i18n.locale.createAccount, + action: () => { createAccount(); }, + }], ev.currentTarget || ev.target); + }, + }, { + type: 'link', + icon: 'fas fa-users', + text: i18n.locale.manageAccounts, + to: `/settings/accounts`, + }]], ev.currentTarget || ev.target, { + align: 'left' + }); + } else { + popupMenu([...(opts.includeCurrentAccount ? [createItem($i)] : []), ...accountItemPromises], ev.currentTarget || ev.target, { + align: 'left' + }); } } diff --git a/packages/client/src/components/note.sub.vue b/packages/client/src/components/MkNoteSub.vue index de4218e535..30c27e6235 100644 --- a/packages/client/src/components/note.sub.vue +++ b/packages/client/src/components/MkNoteSub.vue @@ -10,13 +10,13 @@ <XCwButton v-model="showContent" :note="note"/> </p> <div v-show="note.cw == null || showContent" class="content"> - <XSubNote-content class="text" :note="note"/> + <MkNoteSubNoteContent class="text" :note="note"/> </div> </div> </div> </div> <template v-if="depth < 5"> - <XSub v-for="reply in replies" :key="reply.id" :note="reply" class="reply" :detail="true" :depth="depth + 1"/> + <MkNoteSub v-for="reply in replies" :key="reply.id" :note="reply" class="reply" :detail="true" :depth="depth + 1"/> </template> <div v-else class="more"> <MkA class="text _link" :to="notePage(note)">{{ $ts.continueThread }} <i class="fas fa-angle-double-right"></i></MkA> @@ -24,63 +24,36 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; +import * as misskey from 'misskey-js'; import { notePage } from '@/filters/note'; import XNoteHeader from './note-header.vue'; -import XSubNoteContent from './sub-note-content.vue'; +import MkNoteSubNoteContent from './sub-note-content.vue'; import XCwButton from './cw-button.vue'; import * as os from '@/os'; -export default defineComponent({ - name: 'XSub', +const props = withDefaults(defineProps<{ + note: misskey.entities.Note; + detail?: boolean; - components: { - XNoteHeader, - XSubNoteContent, - XCwButton, - }, - - props: { - note: { - type: Object, - required: true - }, - detail: { - type: Boolean, - required: false, - default: false - }, - // how many notes are in between this one and the note being viewed in detail - depth: { - type: Number, - required: false, - default: 1 - }, - }, - - data() { - return { - showContent: false, - replies: [], - }; - }, + // how many notes are in between this one and the note being viewed in detail + depth?: number; +}>(), { + depth: 1, +}); - created() { - if (this.detail) { - os.api('notes/children', { - noteId: this.note.id, - limit: 5 - }).then(replies => { - this.replies = replies; - }); - } - }, +let showContent = $ref(false); +let replies: misskey.entities.Note[] = $ref([]); - methods: { - notePage, - } -}); +if (props.detail) { + os.api('notes/children', { + noteId: props.note.id, + limit: 5 + }).then(res => { + replies = res; + }); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/abuse-report-window.vue b/packages/client/src/components/abuse-report-window.vue index 6b07639f6d..cd04f62bca 100644 --- a/packages/client/src/components/abuse-report-window.vue +++ b/packages/client/src/components/abuse-report-window.vue @@ -1,8 +1,8 @@ <template> -<XWindow ref="window" :initial-width="400" :initial-height="500" :can-resize="true" @closed="$emit('closed')"> +<XWindow ref="window" :initial-width="400" :initial-height="500" :can-resize="true" @closed="emit('closed')"> <template #header> <i class="fas fa-exclamation-circle" style="margin-right: 0.5em;"></i> - <I18n :src="$ts.reportAbuseOf" tag="span"> + <I18n :src="i18n.locale.reportAbuseOf" tag="span"> <template #name> <b><MkAcct :user="user"/></b> </template> @@ -11,65 +11,51 @@ <div class="dpvffvvy _monolithic_"> <div class="_section"> <MkTextarea v-model="comment"> - <template #label>{{ $ts.details }}</template> - <template #caption>{{ $ts.fillAbuseReportDescription }}</template> + <template #label>{{ i18n.locale.details }}</template> + <template #caption>{{ i18n.locale.fillAbuseReportDescription }}</template> </MkTextarea> </div> <div class="_section"> - <MkButton primary full :disabled="comment.length === 0" @click="send">{{ $ts.send }}</MkButton> + <MkButton primary full :disabled="comment.length === 0" @click="send">{{ i18n.locale.send }}</MkButton> </div> </div> </XWindow> </template> -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; +<script setup lang="ts"> +import { ref } from 'vue'; +import * as Misskey from 'misskey-js'; import XWindow from '@/components/ui/window.vue'; import MkTextarea from '@/components/form/textarea.vue'; import MkButton from '@/components/ui/button.vue'; import * as os from '@/os'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XWindow, - MkTextarea, - MkButton, - }, +const props = defineProps<{ + user: Misskey.entities.User; + initialComment?: string; +}>(); - props: { - user: { - type: Object, - required: true, - }, - initialComment: { - type: String, - required: false, - }, - }, +const emit = defineEmits<{ + (e: 'closed'): void; +}>(); - emits: ['closed'], +const window = ref<InstanceType<typeof XWindow>>(); +const comment = ref(props.initialComment || ''); - data() { - return { - comment: this.initialComment || '', - }; - }, - - methods: { - send() { - os.apiWithDialog('users/report-abuse', { - userId: this.user.id, - comment: this.comment, - }, undefined, res => { - os.alert({ - type: 'success', - text: this.$ts.abuseReported - }); - this.$refs.window.close(); - }); - } - }, -}); +function send() { + os.apiWithDialog('users/report-abuse', { + userId: props.user.id, + comment: comment.value, + }, undefined).then(res => { + os.alert({ + type: 'success', + text: i18n.locale.abuseReported + }); + window.value?.close(); + emit('closed'); + }); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/abuse-report.vue b/packages/client/src/components/abuse-report.vue new file mode 100644 index 0000000000..b67cef209b --- /dev/null +++ b/packages/client/src/components/abuse-report.vue @@ -0,0 +1,102 @@ +<template> +<div class="bcekxzvu _card _gap"> + <div class="_content target"> + <MkAvatar class="avatar" :user="report.targetUser" :show-indicator="true"/> + <MkA class="info" :to="userPage(report.targetUser)" v-user-preview="report.targetUserId"> + <MkUserName class="name" :user="report.targetUser"/> + <MkAcct class="acct" :user="report.targetUser" style="display: block;"/> + </MkA> + </div> + <div class="_content"> + <div> + <Mfm :text="report.comment"/> + </div> + <hr/> + <div>{{ $ts.reporter }}: <MkAcct :user="report.reporter"/></div> + <div v-if="report.assignee"> + {{ $ts.moderator }}: + <MkAcct :user="report.assignee"/> + </div> + <div><MkTime :time="report.createdAt"/></div> + </div> + <div class="_footer"> + <MkSwitch v-model="forward" :disabled="report.targetUser.host == null || report.resolved"> + {{ $ts.forwardReport }} + <template #caption>{{ $ts.forwardReportIsAnonymous }}</template> + </MkSwitch> + <MkButton v-if="!report.resolved" primary @click="resolve">{{ $ts.abuseMarkAsResolved }}</MkButton> + </div> +</div> +</template> + +<script lang="ts"> +import { defineComponent } from 'vue'; + +import MkButton from '@/components/ui/button.vue'; +import MkSwitch from '@/components/form/switch.vue'; +import { acct, userPage } from '@/filters/user'; +import * as os from '@/os'; + +export default defineComponent({ + components: { + MkButton, + MkSwitch, + }, + + emits: ['resolved'], + + props: { + report: { + type: Object, + required: true, + } + } + + data() { + return { + forward: this.report.forwarded, + }; + } + + methods: { + acct, + userPage, + + resolve() { + os.apiWithDialog('admin/resolve-abuse-user-report', { + forward: this.forward, + reportId: this.report.id, + }).then(() => { + this.$emit('resolved', this.report.id); + }); + } + } +}); +</script> + +<style lang="scss" scoped> +.bcekxzvu { + > .target { + display: flex; + width: 100%; + box-sizing: border-box; + text-align: left; + align-items: center; + + > .avatar { + width: 42px; + height: 42px; + } + + > .info { + margin-left: 0.3em; + padding: 0 8px; + flex: 1; + + > .name { + font-weight: bold; + } + } + } +} +</style> diff --git a/packages/client/src/components/analog-clock.vue b/packages/client/src/components/analog-clock.vue index 450488b198..59b8e97304 100644 --- a/packages/client/src/components/analog-clock.vue +++ b/packages/client/src/components/analog-clock.vue @@ -40,106 +40,64 @@ </svg> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { ref, computed, onMounted, onBeforeUnmount } from 'vue'; import * as tinycolor from 'tinycolor2'; -export default defineComponent({ - props: { - thickness: { - type: Number, - default: 0.1 - } - }, - - data() { - return { - now: new Date(), - enabled: true, - - graduationsPadding: 0.5, - handsPadding: 1, - handsTailLength: 0.7, - hHandLengthRatio: 0.75, - mHandLengthRatio: 1, - sHandLengthRatio: 1, - - computedStyle: getComputedStyle(document.documentElement) - }; - }, - - computed: { - dark(): boolean { - return tinycolor(this.computedStyle.getPropertyValue('--bg')).isDark(); - }, - - majorGraduationColor(): string { - return this.dark ? 'rgba(255, 255, 255, 0.3)' : 'rgba(0, 0, 0, 0.3)'; - }, - minorGraduationColor(): string { - return this.dark ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; - }, +withDefaults(defineProps<{ + thickness: number; +}>(), { + thickness: 0.1, +}); - sHandColor(): string { - return this.dark ? 'rgba(255, 255, 255, 0.5)' : 'rgba(0, 0, 0, 0.3)'; - }, - mHandColor(): string { - return tinycolor(this.computedStyle.getPropertyValue('--fg')).toHexString(); - }, - hHandColor(): string { - return tinycolor(this.computedStyle.getPropertyValue('--accent')).toHexString(); - }, +const now = ref(new Date()); +const enabled = ref(true); +const graduationsPadding = ref(0.5); +const handsPadding = ref(1); +const handsTailLength = ref(0.7); +const hHandLengthRatio = ref(0.75); +const mHandLengthRatio = ref(1); +const sHandLengthRatio = ref(1); +const computedStyle = getComputedStyle(document.documentElement); - s(): number { - return this.now.getSeconds(); - }, - m(): number { - return this.now.getMinutes(); - }, - h(): number { - return this.now.getHours(); - }, +const dark = computed(() => tinycolor(computedStyle.getPropertyValue('--bg')).isDark()); +const majorGraduationColor = computed(() => dark.value ? 'rgba(255, 255, 255, 0.3)' : 'rgba(0, 0, 0, 0.3)'); +const minorGraduationColor = computed(() => dark.value ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'); +const sHandColor = computed(() => dark.value ? 'rgba(255, 255, 255, 0.5)' : 'rgba(0, 0, 0, 0.3)'); +const mHandColor = computed(() => tinycolor(computedStyle.getPropertyValue('--fg')).toHexString()); +const hHandColor = computed(() => tinycolor(computedStyle.getPropertyValue('--accent')).toHexString()); +const s = computed(() => now.value.getSeconds()); +const m = computed(() => now.value.getMinutes()); +const h = computed(() => now.value.getHours()); +const hAngle = computed(() => Math.PI * (h.value % 12 + (m.value + s.value / 60) / 60) / 6); +const mAngle = computed(() => Math.PI * (m.value + s.value / 60) / 30); +const sAngle = computed(() => Math.PI * s.value / 30); +const graduations = computed(() => { + const angles: number[] = []; + for (let i = 0; i < 60; i++) { + const angle = Math.PI * i / 30; + angles.push(angle); + } - hAngle(): number { - return Math.PI * (this.h % 12 + (this.m + this.s / 60) / 60) / 6; - }, - mAngle(): number { - return Math.PI * (this.m + this.s / 60) / 30; - }, - sAngle(): number { - return Math.PI * this.s / 30; - }, + return angles; +}); - graduations(): any { - const angles = []; - for (let i = 0; i < 60; i++) { - const angle = Math.PI * i / 30; - angles.push(angle); - } +function tick() { + now.value = new Date(); +} - return angles; +onMounted(() => { + const update = () => { + if (enabled.value) { + tick(); + window.setTimeout(update, 1000); } - }, - - mounted() { - const update = () => { - if (this.enabled) { - this.tick(); - setTimeout(update, 1000); - } - }; - update(); - }, - - beforeUnmount() { - this.enabled = false; - }, + }; + update(); +}); - methods: { - tick() { - this.now = new Date(); - } - } +onBeforeUnmount(() => { + enabled.value = false; }); </script> diff --git a/packages/client/src/components/autocomplete.vue b/packages/client/src/components/autocomplete.vue index 30be2ac741..7ba83b7cb1 100644 --- a/packages/client/src/components/autocomplete.vue +++ b/packages/client/src/components/autocomplete.vue @@ -1,5 +1,5 @@ <template> -<div class="swhvrteh _popup _shadow" :style="{ zIndex }" @contextmenu.prevent="() => {}"> +<div ref="rootEl" class="swhvrteh _popup _shadow" :style="{ zIndex }" @contextmenu.prevent="() => {}"> <ol v-if="type === 'user'" ref="suggests" class="users"> <li v-for="user in users" tabindex="-1" class="user" @click="complete(type, user)" @keydown="onKeydown"> <img class="avatar" :src="user.avatarUrl"/> @@ -8,7 +8,7 @@ </span> <span class="username">@{{ acct(user) }}</span> </li> - <li tabindex="-1" class="choose" @click="chooseUser()" @keydown="onKeydown">{{ $ts.selectUser }}</li> + <li tabindex="-1" class="choose" @click="chooseUser()" @keydown="onKeydown">{{ i18n.locale.selectUser }}</li> </ol> <ol v-else-if="hashtags.length > 0" ref="suggests" class="hashtags"> <li v-for="hashtag in hashtags" tabindex="-1" @click="complete(type, hashtag)" @keydown="onKeydown"> @@ -17,8 +17,8 @@ </ol> <ol v-else-if="emojis.length > 0" ref="suggests" class="emojis"> <li v-for="emoji in emojis" tabindex="-1" @click="complete(type, emoji.emoji)" @keydown="onKeydown"> - <span v-if="emoji.isCustomEmoji" class="emoji"><img :src="$store.state.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url" :alt="emoji.emoji"/></span> - <span v-else-if="!$store.state.useOsNativeEmojis" class="emoji"><img :src="emoji.url" :alt="emoji.emoji"/></span> + <span v-if="emoji.isCustomEmoji" class="emoji"><img :src="defaultStore.state.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url" :alt="emoji.emoji"/></span> + <span v-else-if="!defaultStore.state.useOsNativeEmojis" class="emoji"><img :src="emoji.url" :alt="emoji.emoji"/></span> <span v-else class="emoji">{{ emoji.emoji }}</span> <span class="name" v-html="emoji.name.replace(q, `<b>${q}</b>`)"></span> <span v-if="emoji.aliasOf" class="alias">({{ emoji.aliasOf }})</span> @@ -33,15 +33,17 @@ </template> <script lang="ts"> -import { defineComponent, markRaw } from 'vue'; -import { emojilist } from '@/scripts/emojilist'; +import { markRaw, ref, onUpdated, onMounted, onBeforeUnmount, nextTick, watch } from 'vue'; import contains from '@/scripts/contains'; -import { twemojiSvgBase } from '@/scripts/twemoji-base'; import { getStaticImageUrl } from '@/scripts/get-static-image-url'; import { acct } from '@/filters/user'; import * as os from '@/os'; -import { instance } from '@/instance'; import { MFM_TAGS } from '@/scripts/mfm-tags'; +import { defaultStore } from '@/store'; +import { emojilist } from '@/scripts/emojilist'; +import { instance } from '@/instance'; +import { twemojiSvgBase } from '@/scripts/twemoji-base'; +import { i18n } from '@/i18n'; type EmojiDef = { emoji: string; @@ -54,16 +56,14 @@ type EmojiDef = { const lib = emojilist.filter(x => x.category !== 'flags'); const char2file = (char: string) => { - let codes = Array.from(char).map(x => x.codePointAt(0).toString(16)); + let codes = Array.from(char).map(x => x.codePointAt(0)?.toString(16)); if (!codes.includes('200d')) codes = codes.filter(x => x != 'fe0f'); - codes = codes.filter(x => x && x.length); - return codes.join('-'); + return codes.filter(x => x && x.length).join('-'); }; const emjdb: EmojiDef[] = lib.map(x => ({ emoji: x.char, name: x.name, - aliasOf: null, url: `${twemojiSvgBase}/${char2file(x.char)}.svg` })); @@ -112,291 +112,270 @@ emojiDefinitions.sort((a, b) => a.name.length - b.name.length); const emojiDb = markRaw(emojiDefinitions.concat(emjdb)); //#endregion -export default defineComponent({ - props: { - type: { - type: String, - required: true, - }, +export default { + emojiDb, + emojiDefinitions, + emojilist, + customEmojis, +}; +</script> - q: { - type: String, - required: false, - }, +<script lang="ts" setup> +const props = defineProps<{ + type: string; + q: string | null; + textarea: HTMLTextAreaElement; + close: () => void; + x: number; + y: number; +}>(); - textarea: { - type: HTMLTextAreaElement, - required: true, - }, +const emit = defineEmits<{ + (e: 'done', v: { type: string; value: any }): void; + (e: 'closed'): void; +}>(); - close: { - type: Function, - required: true, - }, +const suggests = ref<Element>(); +const rootEl = ref<HTMLDivElement>(); - x: { - type: Number, - required: true, - }, +const fetching = ref(true); +const users = ref<any[]>([]); +const hashtags = ref<any[]>([]); +const emojis = ref<(EmojiDef)[]>([]); +const items = ref<Element[] | HTMLCollection>([]); +const mfmTags = ref<string[]>([]); +const select = ref(-1); +const zIndex = os.claimZIndex('high'); - y: { - type: Number, - required: true, - }, - }, +function complete(type: string, value: any) { + emit('done', { type, value }); + emit('closed'); + if (type === 'emoji') { + let recents = defaultStore.state.recentlyUsedEmojis; + recents = recents.filter((e: any) => e !== value); + recents.unshift(value); + defaultStore.set('recentlyUsedEmojis', recents.splice(0, 32)); + } +} - emits: ['done', 'closed'], +function setPosition() { + if (!rootEl.value) return; + if (props.x + rootEl.value.offsetWidth > window.innerWidth) { + rootEl.value.style.left = (window.innerWidth - rootEl.value.offsetWidth) + 'px'; + } else { + rootEl.value.style.left = `${props.x}px`; + } + if (props.y + rootEl.value.offsetHeight > window.innerHeight) { + rootEl.value.style.top = (props.y - rootEl.value.offsetHeight) + 'px'; + rootEl.value.style.marginTop = '0'; + } else { + rootEl.value.style.top = props.y + 'px'; + rootEl.value.style.marginTop = 'calc(1em + 8px)'; + } +} - data() { - return { - getStaticImageUrl, - fetching: true, - users: [], - hashtags: [], - emojis: [], - items: [], - mfmTags: [], - select: -1, - zIndex: os.claimZIndex('high'), +function exec() { + select.value = -1; + if (suggests.value) { + for (const el of Array.from(items.value)) { + el.removeAttribute('data-selected'); } - }, - - updated() { - this.setPosition(); - this.items = (this.$refs.suggests as Element | undefined)?.children || []; - }, - - mounted() { - this.setPosition(); - - this.textarea.addEventListener('keydown', this.onKeydown); - - for (const el of Array.from(document.querySelectorAll('body *'))) { - el.addEventListener('mousedown', this.onMousedown); + } + if (props.type === 'user') { + if (!props.q) { + users.value = []; + fetching.value = false; + return; } - this.$nextTick(() => { - this.exec(); + const cacheKey = `autocomplete:user:${props.q}`; + const cache = sessionStorage.getItem(cacheKey); - this.$watch('q', () => { - this.$nextTick(() => { - this.exec(); - }); + if (cache) { + const users = JSON.parse(cache); + users.value = users; + fetching.value = false; + } else { + os.api('users/search-by-username-and-host', { + username: props.q, + limit: 10, + detail: false + }).then(searchedUsers => { + users.value = searchedUsers as any[]; + fetching.value = false; + // キャッシュ + sessionStorage.setItem(cacheKey, JSON.stringify(searchedUsers)); }); - }); - }, - - beforeUnmount() { - this.textarea.removeEventListener('keydown', this.onKeydown); - - for (const el of Array.from(document.querySelectorAll('body *'))) { - el.removeEventListener('mousedown', this.onMousedown); } - }, - - methods: { - complete(type, value) { - this.$emit('done', { type, value }); - this.$emit('closed'); - - if (type === 'emoji') { - let recents = this.$store.state.recentlyUsedEmojis; - recents = recents.filter((e: any) => e !== value); - recents.unshift(value); - this.$store.set('recentlyUsedEmojis', recents.splice(0, 32)); - } - }, - - setPosition() { - if (this.x + this.$el.offsetWidth > window.innerWidth) { - this.$el.style.left = (window.innerWidth - this.$el.offsetWidth) + 'px'; + } else if (props.type === 'hashtag') { + if (!props.q || props.q == '') { + hashtags.value = JSON.parse(localStorage.getItem('hashtags') || '[]'); + fetching.value = false; + } else { + const cacheKey = `autocomplete:hashtag:${props.q}`; + const cache = sessionStorage.getItem(cacheKey); + if (cache) { + const hashtags = JSON.parse(cache); + hashtags.value = hashtags; + fetching.value = false; } else { - this.$el.style.left = this.x + 'px'; + os.api('hashtags/search', { + query: props.q, + limit: 30 + }).then(searchedHashtags => { + hashtags.value = searchedHashtags as any[]; + fetching.value = false; + // キャッシュ + sessionStorage.setItem(cacheKey, JSON.stringify(searchedHashtags)); + }); } + } + } else if (props.type === 'emoji') { + if (!props.q || props.q == '') { + // 最近使った絵文字をサジェスト + emojis.value = defaultStore.state.recentlyUsedEmojis.map(emoji => emojiDb.find(e => e.emoji == emoji)).filter(x => x) as EmojiDef[]; + return; + } - if (this.y + this.$el.offsetHeight > window.innerHeight) { - this.$el.style.top = (this.y - this.$el.offsetHeight) + 'px'; - this.$el.style.marginTop = '0'; - } else { - this.$el.style.top = this.y + 'px'; - this.$el.style.marginTop = 'calc(1em + 8px)'; - } - }, + const matched: EmojiDef[] = []; + const max = 30; - exec() { - this.select = -1; - if (this.$refs.suggests) { - for (const el of Array.from(this.items)) { - el.removeAttribute('data-selected'); - } - } + emojiDb.some(x => { + if (x.name.startsWith(props.q || '') && !x.aliasOf && !matched.some(y => y.emoji == x.emoji)) matched.push(x); + return matched.length == max; + }); - if (this.type === 'user') { - if (this.q == null) { - this.users = []; - this.fetching = false; - return; - } + if (matched.length < max) { + emojiDb.some(x => { + if (x.name.startsWith(props.q || '') && !matched.some(y => y.emoji == x.emoji)) matched.push(x); + return matched.length == max; + }); + } - const cacheKey = `autocomplete:user:${this.q}`; - const cache = sessionStorage.getItem(cacheKey); - if (cache) { - const users = JSON.parse(cache); - this.users = users; - this.fetching = false; - } else { - os.api('users/search-by-username-and-host', { - username: this.q, - limit: 10, - detail: false - }).then(users => { - this.users = users; - this.fetching = false; + if (matched.length < max) { + emojiDb.some(x => { + if (x.name.includes(props.q || '') && !matched.some(y => y.emoji == x.emoji)) matched.push(x); + return matched.length == max; + }); + } - // キャッシュ - sessionStorage.setItem(cacheKey, JSON.stringify(users)); - }); - } - } else if (this.type === 'hashtag') { - if (this.q == null || this.q == '') { - this.hashtags = JSON.parse(localStorage.getItem('hashtags') || '[]'); - this.fetching = false; - } else { - const cacheKey = `autocomplete:hashtag:${this.q}`; - const cache = sessionStorage.getItem(cacheKey); - if (cache) { - const hashtags = JSON.parse(cache); - this.hashtags = hashtags; - this.fetching = false; - } else { - os.api('hashtags/search', { - query: this.q, - limit: 30 - }).then(hashtags => { - this.hashtags = hashtags; - this.fetching = false; + emojis.value = matched; + } else if (props.type === 'mfmTag') { + if (!props.q || props.q == '') { + mfmTags.value = MFM_TAGS; + return; + } - // キャッシュ - sessionStorage.setItem(cacheKey, JSON.stringify(hashtags)); - }); - } - } - } else if (this.type === 'emoji') { - if (this.q == null || this.q == '') { - // 最近使った絵文字をサジェスト - this.emojis = this.$store.state.recentlyUsedEmojis.map(emoji => emojiDb.find(e => e.emoji == emoji)).filter(x => x != null); - return; - } + mfmTags.value = MFM_TAGS.filter(tag => tag.startsWith(props.q || '')); + } +} + +function onMousedown(e: Event) { + if (!contains(rootEl.value, e.target) && (rootEl.value != e.target)) props.close(); +} - const matched = []; - const max = 30; +function onKeydown(e: KeyboardEvent) { + const cancel = () => { + e.preventDefault(); + e.stopPropagation(); + }; - emojiDb.some(x => { - if (x.name.startsWith(this.q) && !x.aliasOf && !matched.some(y => y.emoji == x.emoji)) matched.push(x); - return matched.length == max; - }); - if (matched.length < max) { - emojiDb.some(x => { - if (x.name.startsWith(this.q) && !matched.some(y => y.emoji == x.emoji)) matched.push(x); - return matched.length == max; - }); - } - if (matched.length < max) { - emojiDb.some(x => { - if (x.name.includes(this.q) && !matched.some(y => y.emoji == x.emoji)) matched.push(x); - return matched.length == max; - }); - } + switch (e.key) { + case 'Enter': + if (select.value !== -1) { + cancel(); + (items.value[select.value] as any).click(); + } else { + props.close(); + } + break; - this.emojis = matched; - } else if (this.type === 'mfmTag') { - if (this.q == null || this.q == '') { - this.mfmTags = MFM_TAGS; - return; - } + case 'Escape': + cancel(); + props.close(); + break; - this.mfmTags = MFM_TAGS.filter(tag => tag.startsWith(this.q)); + case 'ArrowUp': + if (select.value !== -1) { + cancel(); + selectPrev(); + } else { + props.close(); } - }, + break; + + case 'Tab': + case 'ArrowDown': + cancel(); + selectNext(); + break; - onMousedown(e) { - if (!contains(this.$el, e.target) && (this.$el != e.target)) this.close(); - }, + default: + e.stopPropagation(); + props.textarea.focus(); + } +} - onKeydown(e) { - const cancel = () => { - e.preventDefault(); - e.stopPropagation(); - }; +function selectNext() { + if (++select.value >= items.value.length) select.value = 0; + if (items.value.length === 0) select.value = -1; + applySelect(); +} - switch (e.which) { - case 10: // [ENTER] - case 13: // [ENTER] - if (this.select !== -1) { - cancel(); - (this.items[this.select] as any).click(); - } else { - this.close(); - } - break; +function selectPrev() { + if (--select.value < 0) select.value = items.value.length - 1; + applySelect(); +} - case 27: // [ESC] - cancel(); - this.close(); - break; +function applySelect() { + for (const el of Array.from(items.value)) { + el.removeAttribute('data-selected'); + } - case 38: // [↑] - if (this.select !== -1) { - cancel(); - this.selectPrev(); - } else { - this.close(); - } - break; + if (select.value !== -1) { + items.value[select.value].setAttribute('data-selected', 'true'); + (items.value[select.value] as any).focus(); + } +} - case 9: // [TAB] - case 40: // [↓] - cancel(); - this.selectNext(); - break; +function chooseUser() { + props.close(); + os.selectUser().then(user => { + complete('user', user); + props.textarea.focus(); + }); +} - default: - e.stopPropagation(); - this.textarea.focus(); - } - }, +onUpdated(() => { + setPosition(); + items.value = suggests.value?.children || []; +}); - selectNext() { - if (++this.select >= this.items.length) this.select = 0; - if (this.items.length === 0) this.select = -1; - this.applySelect(); - }, +onMounted(() => { + setPosition(); - selectPrev() { - if (--this.select < 0) this.select = this.items.length - 1; - this.applySelect(); - }, + props.textarea.addEventListener('keydown', onKeydown); - applySelect() { - for (const el of Array.from(this.items)) { - el.removeAttribute('data-selected'); - } + for (const el of Array.from(document.querySelectorAll('body *'))) { + el.addEventListener('mousedown', onMousedown); + } - if (this.select !== -1) { - this.items[this.select].setAttribute('data-selected', 'true'); - (this.items[this.select] as any).focus(); - } - }, + nextTick(() => { + exec(); - chooseUser() { - this.close(); - os.selectUser().then(user => { - this.complete('user', user); - this.textarea.focus(); + watch(() => props.q, () => { + nextTick(() => { + exec(); }); - }, + }); + }); +}); + +onBeforeUnmount(() => { + props.textarea.removeEventListener('keydown', onKeydown); - acct + for (const el of Array.from(document.querySelectorAll('body *'))) { + el.removeEventListener('mousedown', onMousedown); } }); </script> diff --git a/packages/client/src/components/avatars.vue b/packages/client/src/components/avatars.vue index e843d26daa..958e5db0a1 100644 --- a/packages/client/src/components/avatars.vue +++ b/packages/client/src/components/avatars.vue @@ -1,30 +1,24 @@ <template> <div> - <div v-for="user in us" :key="user.id" style="display:inline-block;width:32px;height:32px;margin-right:8px;"> + <div v-for="user in users" :key="user.id" style="display:inline-block;width:32px;height:32px;margin-right:8px;"> <MkAvatar :user="user" style="width:32px;height:32px;" :show-indicator="true"/> </div> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted, ref } from 'vue'; import * as os from '@/os'; -export default defineComponent({ - props: { - userIds: { - required: true - }, - }, - data() { - return { - us: [] - }; - }, - async created() { - this.us = await os.api('users/show', { - userIds: this.userIds - }); - } +const props = defineProps<{ + userIds: string[]; +}>(); + +const users = ref([]); + +onMounted(async () => { + users.value = await os.api('users/show', { + userIds: props.userIds + }); }); </script> diff --git a/packages/client/src/components/captcha.vue b/packages/client/src/components/captcha.vue index baa922506e..307fc312bc 100644 --- a/packages/client/src/components/captcha.vue +++ b/packages/client/src/components/captcha.vue @@ -1,12 +1,14 @@ <template> <div> - <span v-if="!available">{{ $ts.waiting }}<MkEllipsis/></span> - <div ref="captcha"></div> + <span v-if="!available">{{ i18n.locale.waiting }}<MkEllipsis/></span> + <div ref="captchaEl"></div> </div> </template> -<script lang="ts"> -import { defineComponent, PropType } from 'vue'; +<script lang="ts" setup> +import { ref, computed, onMounted, onBeforeUnmount, watch } from 'vue'; +import { defaultStore } from '@/store'; +import { i18n } from '@/i18n'; type Captcha = { render(container: string | Node, options: { @@ -14,7 +16,7 @@ type Captcha = { }): string; remove(id: string): void; execute(id: string): void; - reset(id: string): void; + reset(id?: string): void; getResponse(id: string): string; }; @@ -29,95 +31,85 @@ declare global { } } -export default defineComponent({ - props: { - provider: { - type: String as PropType<CaptchaProvider>, - required: true, - }, - sitekey: { - type: String, - required: true, - }, - modelValue: { - type: String, - }, - }, +const props = defineProps<{ + provider: CaptchaProvider; + sitekey: string; + modelValue?: string | null; +}>(); - data() { - return { - available: false, - }; - }, +const emit = defineEmits<{ + (ev: 'update:modelValue', v: string | null): void; +}>(); - computed: { - variable(): string { - switch (this.provider) { - case 'hcaptcha': return 'hcaptcha'; - case 'recaptcha': return 'grecaptcha'; - } - }, - loaded(): boolean { - return !!window[this.variable]; - }, - src(): string { - const endpoint = ({ - hcaptcha: 'https://hcaptcha.com/1', - recaptcha: 'https://www.recaptcha.net/recaptcha', - } as Record<CaptchaProvider, string>)[this.provider]; +const available = ref(false); - return `${typeof endpoint === 'string' ? endpoint : 'about:invalid'}/api.js?render=explicit`; - }, - captcha(): Captcha { - return window[this.variable] || {} as unknown as Captcha; - }, - }, +const captchaEl = ref<HTMLDivElement | undefined>(); - created() { - if (this.loaded) { - this.available = true; - } else { - (document.getElementById(this.provider) || document.head.appendChild(Object.assign(document.createElement('script'), { - async: true, - id: this.provider, - src: this.src, - }))) - .addEventListener('load', () => this.available = true); - } - }, +const variable = computed(() => { + switch (props.provider) { + case 'hcaptcha': return 'hcaptcha'; + case 'recaptcha': return 'grecaptcha'; + } +}); + +const loaded = computed(() => !!window[variable.value]); + +const src = computed(() => { + switch (props.provider) { + case 'hcaptcha': return 'https://js.hcaptcha.com/1/api.js?render=explicit&recaptchacompat=off'; + case 'recaptcha': return 'https://www.recaptcha.net/recaptcha/api.js?render=explicit'; + } +}); + +const captcha = computed<Captcha>(() => window[variable.value] || {} as unknown as Captcha); + +if (loaded.value) { + available.value = true; +} else { + (document.getElementById(props.provider) || document.head.appendChild(Object.assign(document.createElement('script'), { + async: true, + id: props.provider, + src: src.value, + }))) + .addEventListener('load', () => available.value = true); +} + +function reset() { + if (captcha.value?.reset) captcha.value.reset(); +} + +function requestRender() { + if (captcha.value.render && captchaEl.value instanceof Element) { + captcha.value.render(captchaEl.value, { + sitekey: props.sitekey, + theme: defaultStore.state.darkMode ? 'dark' : 'light', + callback: callback, + 'expired-callback': callback, + 'error-callback': callback, + }); + } else { + window.setTimeout(requestRender, 1); + } +} + +function callback(response?: string) { + emit('update:modelValue', typeof response == 'string' ? response : null); +} - mounted() { - if (this.available) { - this.requestRender(); - } else { - this.$watch('available', this.requestRender); - } - }, +onMounted(() => { + if (available.value) { + requestRender(); + } else { + watch(available, requestRender); + } +}); - beforeUnmount() { - this.reset(); - }, +onBeforeUnmount(() => { + reset(); +}); - methods: { - reset() { - if (this.captcha?.reset) this.captcha.reset(); - }, - requestRender() { - if (this.captcha.render && this.$refs.captcha instanceof Element) { - this.captcha.render(this.$refs.captcha, { - sitekey: this.sitekey, - theme: this.$store.state.darkMode ? 'dark' : 'light', - callback: this.callback, - 'expired-callback': this.callback, - 'error-callback': this.callback, - }); - } else { - setTimeout(this.requestRender.bind(this), 1); - } - }, - callback(response?: string) { - this.$emit('update:modelValue', typeof response == 'string' ? response : null); - }, - }, +defineExpose({ + reset, }); + </script> diff --git a/packages/client/src/components/channel-follow-button.vue b/packages/client/src/components/channel-follow-button.vue index abde7c8148..0ad5384cd5 100644 --- a/packages/client/src/components/channel-follow-button.vue +++ b/packages/client/src/components/channel-follow-button.vue @@ -6,66 +6,54 @@ > <template v-if="!wait"> <template v-if="isFollowing"> - <span v-if="full">{{ $ts.unfollow }}</span><i class="fas fa-minus"></i> + <span v-if="full">{{ i18n.locale.unfollow }}</span><i class="fas fa-minus"></i> </template> <template v-else> - <span v-if="full">{{ $ts.follow }}</span><i class="fas fa-plus"></i> + <span v-if="full">{{ i18n.locale.follow }}</span><i class="fas fa-plus"></i> </template> </template> <template v-else> - <span v-if="full">{{ $ts.processing }}</span><i class="fas fa-spinner fa-pulse fa-fw"></i> + <span v-if="full">{{ i18n.locale.processing }}</span><i class="fas fa-spinner fa-pulse fa-fw"></i> </template> </button> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { ref } from 'vue'; import * as os from '@/os'; +import { i18n } from '@/i18n'; -export default defineComponent({ - props: { - channel: { - type: Object, - required: true - }, - full: { - type: Boolean, - required: false, - default: false, - }, - }, +const props = withDefaults(defineProps<{ + channel: Record<string, any>; + full?: boolean; +}>(), { + full: false, +}); - data() { - return { - isFollowing: this.channel.isFollowing, - wait: false, - }; - }, +const isFollowing = ref<boolean>(props.channel.isFollowing); +const wait = ref(false); - methods: { - async onClick() { - this.wait = true; +async function onClick() { + wait.value = true; - try { - if (this.isFollowing) { - await os.api('channels/unfollow', { - channelId: this.channel.id - }); - this.isFollowing = false; - } else { - await os.api('channels/follow', { - channelId: this.channel.id - }); - this.isFollowing = true; - } - } catch (e) { - console.error(e); - } finally { - this.wait = false; - } + try { + if (isFollowing.value) { + await os.api('channels/unfollow', { + channelId: props.channel.id + }); + isFollowing.value = false; + } else { + await os.api('channels/follow', { + channelId: props.channel.id + }); + isFollowing.value = true; } + } catch (e) { + console.error(e); + } finally { + wait.value = false; } -}); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/channel-preview.vue b/packages/client/src/components/channel-preview.vue index f2b6de97fd..8d135a192f 100644 --- a/packages/client/src/components/channel-preview.vue +++ b/packages/client/src/components/channel-preview.vue @@ -6,7 +6,7 @@ <div class="status"> <div> <i class="fas fa-users fa-fw"></i> - <I18n :src="$ts._channel.usersCount" tag="span" style="margin-left: 4px;"> + <I18n :src="i18n.locale._channel.usersCount" tag="span" style="margin-left: 4px;"> <template #n> <b>{{ channel.usersCount }}</b> </template> @@ -14,7 +14,7 @@ </div> <div> <i class="fas fa-pencil-alt fa-fw"></i> - <I18n :src="$ts._channel.notesCount" tag="span" style="margin-left: 4px;"> + <I18n :src="i18n.locale._channel.notesCount" tag="span" style="margin-left: 4px;"> <template #n> <b>{{ channel.notesCount }}</b> </template> @@ -27,37 +27,26 @@ </article> <footer> <span v-if="channel.lastNotedAt"> - {{ $ts.updatedAt }}: <MkTime :time="channel.lastNotedAt"/> + {{ i18n.locale.updatedAt }}: <MkTime :time="channel.lastNotedAt"/> </span> </footer> </MkA> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; +import { i18n } from '@/i18n'; -export default defineComponent({ - props: { - channel: { - type: Object, - required: true - }, - }, +const props = defineProps<{ + channel: Record<string, any>; +}>(); - data() { - return { - }; - }, - - computed: { - bannerStyle() { - if (this.channel.bannerUrl) { - return { backgroundImage: `url(${this.channel.bannerUrl})` }; - } else { - return { backgroundColor: '#4c5e6d' }; - } - } - }, +const bannerStyle = computed(() => { + if (props.channel.bannerUrl) { + return { backgroundImage: `url(${props.channel.bannerUrl})` }; + } else { + return { backgroundColor: '#4c5e6d' }; + } }); </script> diff --git a/packages/client/src/components/chart.vue b/packages/client/src/components/chart.vue index c4d0eb85dd..1959271f5d 100644 --- a/packages/client/src/components/chart.vue +++ b/packages/client/src/components/chart.vue @@ -170,10 +170,10 @@ export default defineComponent({ aspectRatio: props.aspectRatio || 2.5, layout: { padding: { - left: 16, - right: 16, - top: 16, - bottom: 8, + left: 0, + right: 0, + top: 0, + bottom: 0, }, }, scales: { diff --git a/packages/client/src/components/code-core.vue b/packages/client/src/components/code-core.vue index b58484c2ac..45a38afe04 100644 --- a/packages/client/src/components/code-core.vue +++ b/packages/client/src/components/code-core.vue @@ -3,33 +3,17 @@ <pre v-else :class="`language-${prismLang}`"><code :class="`language-${prismLang}`" v-html="html"></code></pre> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; import 'prismjs'; import 'prismjs/themes/prism-okaidia.css'; -export default defineComponent({ - props: { - code: { - type: String, - required: true - }, - lang: { - type: String, - required: false - }, - inline: { - type: Boolean, - required: false - } - }, - computed: { - prismLang() { - return Prism.languages[this.lang] ? this.lang : 'js'; - }, - html() { - return Prism.highlight(this.code, Prism.languages[this.prismLang], this.prismLang); - } - } -}); +const props = defineProps<{ + code: string; + lang?: string; + inline?: boolean; +}>(); + +const prismLang = computed(() => Prism.languages[props.lang] ? props.lang : 'js'); +const html = computed(() => Prism.highlight(props.code, Prism.languages[prismLang.value], prismLang.value)); </script> diff --git a/packages/client/src/components/code.vue b/packages/client/src/components/code.vue index f5d6c5673a..d6478fd2f8 100644 --- a/packages/client/src/components/code.vue +++ b/packages/client/src/components/code.vue @@ -2,26 +2,14 @@ <XCode :code="code" :lang="lang" :inline="inline"/> </template> -<script lang="ts"> -import { defineComponent, defineAsyncComponent } from 'vue'; +<script lang="ts" setup> +import { defineAsyncComponent } from 'vue'; -export default defineComponent({ - components: { - XCode: defineAsyncComponent(() => import('./code-core.vue')) - }, - props: { - code: { - type: String, - required: true - }, - lang: { - type: String, - required: false - }, - inline: { - type: Boolean, - required: false - } - } -}); +defineProps<{ + code: string; + lang?: string; + inline?: boolean; +}>(); + +const XCode = defineAsyncComponent(() => import('./code-core.vue')); </script> diff --git a/packages/client/src/components/cw-button.vue b/packages/client/src/components/cw-button.vue index 4bec7b511e..ccfd11462a 100644 --- a/packages/client/src/components/cw-button.vue +++ b/packages/client/src/components/cw-button.vue @@ -1,45 +1,37 @@ <template> <button class="nrvgflfu _button" @click="toggle"> - <b>{{ modelValue ? $ts._cw.hide : $ts._cw.show }}</b> + <b>{{ modelValue ? i18n.locale._cw.hide : i18n.locale._cw.show }}</b> <span v-if="!modelValue">{{ label }}</span> </button> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; import { length } from 'stringz'; +import * as misskey from 'misskey-js'; import { concat } from '@/scripts/array'; +import { i18n } from '@/i18n'; -export default defineComponent({ - props: { - modelValue: { - type: Boolean, - required: true - }, - note: { - type: Object, - required: true - } - }, - - computed: { - label(): string { - return concat([ - this.note.text ? [this.$t('_cw.chars', { count: length(this.note.text) })] : [], - this.note.files && this.note.files.length !== 0 ? [this.$t('_cw.files', { count: this.note.files.length }) ] : [], - this.note.poll != null ? [this.$ts.poll] : [] - ] as string[][]).join(' / '); - } - }, +const props = defineProps<{ + modelValue: boolean; + note: misskey.entities.Note; +}>(); - methods: { - length, +const emit = defineEmits<{ + (e: 'update:modelValue', v: boolean): void; +}>(); - toggle() { - this.$emit('update:modelValue', !this.modelValue); - } - } +const label = computed(() => { + return concat([ + props.note.text ? [i18n.t('_cw.chars', { count: length(props.note.text) })] : [], + props.note.files && props.note.files.length !== 0 ? [i18n.t('_cw.files', { count: props.note.files.length }) ] : [], + props.note.poll != null ? [i18n.locale.poll] : [] + ] as string[][]).join(' / '); }); + +const toggle = () => { + emit('update:modelValue', !props.modelValue); +}; </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/date-separated-list.vue b/packages/client/src/components/date-separated-list.vue index aa84c6f60d..c85a0a6ffc 100644 --- a/packages/client/src/components/date-separated-list.vue +++ b/packages/client/src/components/date-separated-list.vue @@ -1,6 +1,8 @@ <script lang="ts"> import { defineComponent, h, PropType, TransitionGroup } from 'vue'; import MkAd from '@/components/global/ad.vue'; +import { i18n } from '@/i18n'; +import { defaultStore } from '@/store'; export default defineComponent({ props: { @@ -30,29 +32,29 @@ export default defineComponent({ }, }, - methods: { - getDateText(time: string) { + setup(props, { slots, expose }) { + function getDateText(time: string) { const date = new Date(time).getDate(); const month = new Date(time).getMonth() + 1; - return this.$t('monthAndDay', { + return i18n.t('monthAndDay', { month: month.toString(), day: date.toString() }); } - }, - render() { - if (this.items.length === 0) return; + if (props.items.length === 0) return; + + const renderChildren = () => props.items.map((item, i) => { + if (!slots || !slots.default) return; - const renderChildren = () => this.items.map((item, i) => { - const el = this.$slots.default({ + const el = slots.default({ item: item })[0]; if (el.key == null && item.id) el.key = item.id; if ( - i != this.items.length - 1 && - new Date(item.createdAt).getDate() != new Date(this.items[i + 1].createdAt).getDate() + i != props.items.length - 1 && + new Date(item.createdAt).getDate() != new Date(props.items[i + 1].createdAt).getDate() ) { const separator = h('div', { class: 'separator', @@ -64,10 +66,10 @@ export default defineComponent({ h('i', { class: 'fas fa-angle-up icon', }), - this.getDateText(item.createdAt) + getDateText(item.createdAt) ]), h('span', [ - this.getDateText(this.items[i + 1].createdAt), + getDateText(props.items[i + 1].createdAt), h('i', { class: 'fas fa-angle-down icon', }) @@ -76,7 +78,7 @@ export default defineComponent({ return [el, separator]; } else { - if (this.ad && item._shouldInsertAd_) { + if (props.ad && item._shouldInsertAd_) { return [h(MkAd, { class: 'a', // advertiseの意(ブロッカー対策) key: item.id + ':ad', @@ -88,18 +90,19 @@ export default defineComponent({ } }); - return h(this.$store.state.animation ? TransitionGroup : 'div', this.$store.state.animation ? { - class: 'sqadhkmv' + (this.noGap ? ' noGap' : ''), - name: 'list', - tag: 'div', - 'data-direction': this.direction, - 'data-reversed': this.reversed ? 'true' : 'false', - } : { - class: 'sqadhkmv' + (this.noGap ? ' noGap' : ''), - }, { - default: renderChildren - }); - }, + return () => h( + defaultStore.state.animation ? TransitionGroup : 'div', + defaultStore.state.animation ? { + class: 'sqadhkmv' + (props.noGap ? ' noGap' : ''), + name: 'list', + tag: 'div', + 'data-direction': props.direction, + 'data-reversed': props.reversed ? 'true' : 'false', + } : { + class: 'sqadhkmv' + (props.noGap ? ' noGap' : ''), + }, + { default: renderChildren }); + } }); </script> diff --git a/packages/client/src/components/debobigego/base.vue b/packages/client/src/components/debobigego/base.vue deleted file mode 100644 index 9ed59abc69..0000000000 --- a/packages/client/src/components/debobigego/base.vue +++ /dev/null @@ -1,65 +0,0 @@ -<template> -<div v-size="{ max: [400] }" class="rbusrurv" :class="{ wide: forceWide }"> - <slot></slot> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; - -export default defineComponent({ - props: { - forceWide: { - type: Boolean, - required: false, - default: false, - } - } -}); -</script> - -<style lang="scss" scoped> -.rbusrurv { - // 他のCSSからも参照されるので消さないように - --debobigegoXPadding: 32px; - --debobigegoYPadding: 32px; - - --debobigegoContentHMargin: 16px; - - font-size: 95%; - line-height: 1.3em; - background: var(--bg); - padding: var(--debobigegoYPadding) var(--debobigegoXPadding); - max-width: 750px; - margin: 0 auto; - - &:not(.wide).max-width_400px { - --debobigegoXPadding: 0px; - - > ::v-deep(*) { - ._debobigegoPanel { - border: solid 0.5px var(--divider); - border-radius: 0; - border-left: none; - border-right: none; - } - - ._debobigego_group { - > *:not(._debobigegoNoConcat) { - &:not(:last-child):not(._debobigegoNoConcatPrev) { - &._debobigegoPanel, ._debobigegoPanel { - border-bottom: solid 0.5px var(--divider); - } - } - - &:not(:first-child):not(._debobigegoNoConcatNext) { - &._debobigegoPanel, ._debobigegoPanel { - border-top: none; - } - } - } - } - } - } -} -</style> diff --git a/packages/client/src/components/debobigego/button.vue b/packages/client/src/components/debobigego/button.vue deleted file mode 100644 index b883e817a4..0000000000 --- a/packages/client/src/components/debobigego/button.vue +++ /dev/null @@ -1,81 +0,0 @@ -<template> -<div class="yzpgjkxe _debobigegoItem"> - <div class="_debobigegoLabel"><slot name="label"></slot></div> - <button class="main _button _debobigegoPanel _debobigegoClickable" :class="{ center, primary, danger }"> - <slot></slot> - <div class="suffix"> - <slot name="suffix"></slot> - <div class="icon"> - <slot name="suffixIcon"></slot> - </div> - </div> - </button> - <div class="_debobigegoCaption"><slot name="desc"></slot></div> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import './debobigego.scss'; - -export default defineComponent({ - props: { - primary: { - type: Boolean, - required: false, - default: false, - }, - danger: { - type: Boolean, - required: false, - default: false, - }, - disabled: { - type: Boolean, - required: false, - default: false, - }, - center: { - type: Boolean, - required: false, - default: true, - } - }, -}); -</script> - -<style lang="scss" scoped> -.yzpgjkxe { - > .main { - display: flex; - width: 100%; - box-sizing: border-box; - padding: 14px 16px; - text-align: left; - align-items: center; - - &.center { - display: block; - text-align: center; - } - - &.primary { - color: var(--accent); - } - - &.danger { - color: #ff2a2a; - } - - > .suffix { - display: inline-flex; - margin-left: auto; - opacity: 0.7; - - > .icon { - margin-left: 1em; - } - } - } -} -</style> diff --git a/packages/client/src/components/debobigego/debobigego.scss b/packages/client/src/components/debobigego/debobigego.scss deleted file mode 100644 index 833b656b66..0000000000 --- a/packages/client/src/components/debobigego/debobigego.scss +++ /dev/null @@ -1,52 +0,0 @@ -._debobigegoPanel { - background: var(--panel); - border-radius: var(--radius); - transition: background 0.2s ease; - - &._debobigegoClickable { - &:hover { - //background: var(--panelHighlight); - } - - &:active { - background: var(--panelHighlight); - transition: background 0s; - } - } -} - -._debobigegoLabel, -._debobigegoCaption { - font-size: 80%; - color: var(--fgTransparentWeak); - - &:empty { - display: none; - } -} - -._debobigegoLabel { - position: sticky; - top: var(--stickyTop, 0px); - z-index: 2; - margin: -8px calc(var(--debobigegoXPadding) * -1) 0 calc(var(--debobigegoXPadding) * -1); - padding: 8px calc(var(--debobigegoContentHMargin) + var(--debobigegoXPadding)) 8px calc(var(--debobigegoContentHMargin) + var(--debobigegoXPadding)); - background: var(--X17); - -webkit-backdrop-filter: var(--blur, blur(10px)); - backdrop-filter: var(--blur, blur(10px)); -} - -._themeChanging_ ._debobigegoLabel { - transition: none !important; - background: transparent; -} - -._debobigegoCaption { - padding: 8px var(--debobigegoContentHMargin) 0 var(--debobigegoContentHMargin); -} - -._debobigegoItem { - & + ._debobigegoItem { - margin-top: 24px; - } -} diff --git a/packages/client/src/components/debobigego/group.vue b/packages/client/src/components/debobigego/group.vue deleted file mode 100644 index 871d3c8dba..0000000000 --- a/packages/client/src/components/debobigego/group.vue +++ /dev/null @@ -1,78 +0,0 @@ -<template> -<div v-size="{ max: [500] }" v-sticky-container class="vrtktovg _debobigegoItem _debobigegoNoConcat"> - <div class="_debobigegoLabel"><slot name="label"></slot></div> - <div ref="child" class="main _debobigego_group"> - <slot></slot> - </div> - <div class="_debobigegoCaption"><slot name="caption"></slot></div> -</div> -</template> - -<script lang="ts"> -import { defineComponent, onMounted, ref } from 'vue'; - -export default defineComponent({ - setup(props, context) { - const child = ref<HTMLElement | null>(null); - - const scanChild = () => { - if (child.value == null) return; - const els = Array.from(child.value.children); - for (let i = 0; i < els.length; i++) { - const el = els[i]; - if (el.classList.contains('_debobigegoNoConcat')) { - if (els[i - 1]) els[i - 1].classList.add('_debobigegoNoConcatPrev'); - if (els[i + 1]) els[i + 1].classList.add('_debobigegoNoConcatNext'); - } - } - }; - - onMounted(() => { - scanChild(); - - const observer = new MutationObserver(records => { - scanChild(); - }); - - observer.observe(child.value, { - childList: true, - subtree: false, - attributes: false, - characterData: false, - }); - }); - - return { - child - }; - } -}); -</script> - -<style lang="scss" scoped> -.vrtktovg { - > .main { - > ::v-deep(*):not(._debobigegoNoConcat) { - &:not(._debobigegoNoConcatNext) { - margin: 0; - } - - &:not(:last-child):not(._debobigegoNoConcatPrev) { - &._debobigegoPanel, ._debobigegoPanel { - border-bottom: solid 0.5px var(--divider); - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - } - } - - &:not(:first-child):not(._debobigegoNoConcatNext) { - &._debobigegoPanel, ._debobigegoPanel { - border-top: none; - border-top-left-radius: 0; - border-top-right-radius: 0; - } - } - } - } -} -</style> diff --git a/packages/client/src/components/debobigego/info.vue b/packages/client/src/components/debobigego/info.vue deleted file mode 100644 index 41afb03304..0000000000 --- a/packages/client/src/components/debobigego/info.vue +++ /dev/null @@ -1,47 +0,0 @@ -<template> -<div class="fzenkabp _debobigegoItem"> - <div class="_debobigegoPanel" :class="{ warn }"> - <i v-if="warn" class="fas fa-exclamation-triangle"></i> - <i v-else class="fas fa-info-circle"></i> - <slot></slot> - </div> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; - -export default defineComponent({ - props: { - warn: { - type: Boolean, - required: false, - default: false - }, - }, - data() { - return { - }; - } -}); -</script> - -<style lang="scss" scoped> -.fzenkabp { - > div { - padding: 14px 16px; - font-size: 90%; - background: var(--infoBg); - color: var(--infoFg); - - &.warn { - background: var(--infoWarnBg); - color: var(--infoWarnFg); - } - - > i { - margin-right: 4px; - } - } -} -</style> diff --git a/packages/client/src/components/debobigego/input.vue b/packages/client/src/components/debobigego/input.vue deleted file mode 100644 index 6228a33fe4..0000000000 --- a/packages/client/src/components/debobigego/input.vue +++ /dev/null @@ -1,292 +0,0 @@ -<template> -<FormGroup class="_debobigegoItem"> - <template #label><slot></slot></template> - <div class="ztzhwixg _debobigegoItem" :class="{ inline, disabled }"> - <div ref="icon" class="icon"><slot name="icon"></slot></div> - <div class="input _debobigegoPanel"> - <div ref="prefixEl" class="prefix"><slot name="prefix"></slot></div> - <input ref="inputEl" - v-model="v" - :type="type" - :disabled="disabled" - :required="required" - :readonly="readonly" - :placeholder="placeholder" - :pattern="pattern" - :autocomplete="autocomplete" - :spellcheck="spellcheck" - :step="step" - :list="id" - @focus="focused = true" - @blur="focused = false" - @keydown="onKeydown($event)" - @input="onInput" - > - <datalist v-if="datalist" :id="id"> - <option v-for="data in datalist" :value="data"/> - </datalist> - <div ref="suffixEl" class="suffix"><slot name="suffix"></slot></div> - </div> - </div> - <template #caption><slot name="desc"></slot></template> - - <FormButton v-if="manualSave && changed" primary @click="updated"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> -</FormGroup> -</template> - -<script lang="ts"> -import { defineComponent, onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs } from 'vue'; -import './debobigego.scss'; -import FormButton from './button.vue'; -import FormGroup from './group.vue'; - -export default defineComponent({ - components: { - FormGroup, - FormButton, - }, - props: { - modelValue: { - required: false - }, - type: { - type: String, - required: false - }, - required: { - type: Boolean, - required: false - }, - readonly: { - type: Boolean, - required: false - }, - disabled: { - type: Boolean, - required: false - }, - pattern: { - type: String, - required: false - }, - placeholder: { - type: String, - required: false - }, - autofocus: { - type: Boolean, - required: false, - default: false - }, - autocomplete: { - required: false - }, - spellcheck: { - required: false - }, - step: { - required: false - }, - datalist: { - type: Array, - required: false, - }, - inline: { - type: Boolean, - required: false, - default: false - }, - manualSave: { - type: Boolean, - required: false, - default: false - }, - }, - emits: ['change', 'keydown', 'enter', 'update:modelValue'], - setup(props, context) { - const { modelValue, type, autofocus } = toRefs(props); - const v = ref(modelValue.value); - const id = Math.random().toString(); // TODO: uuid? - const focused = ref(false); - const changed = ref(false); - const invalid = ref(false); - const filled = computed(() => v.value !== '' && v.value != null); - const inputEl = ref(null); - const prefixEl = ref(null); - const suffixEl = ref(null); - - const focus = () => inputEl.value.focus(); - const onInput = (ev) => { - changed.value = true; - context.emit('change', ev); - }; - const onKeydown = (ev: KeyboardEvent) => { - context.emit('keydown', ev); - - if (ev.code === 'Enter') { - context.emit('enter'); - } - }; - - const updated = () => { - changed.value = false; - if (type?.value === 'number') { - context.emit('update:modelValue', parseFloat(v.value)); - } else { - context.emit('update:modelValue', v.value); - } - }; - - watch(modelValue.value, newValue => { - v.value = newValue; - }); - - watch(v, newValue => { - if (!props.manualSave) { - updated(); - } - - invalid.value = inputEl.value.validity.badInput; - }); - - onMounted(() => { - nextTick(() => { - if (autofocus.value) { - focus(); - } - - // このコンポーネントが作成された時、非表示状態である場合がある - // 非表示状態だと要素の幅などは0になってしまうので、定期的に計算する - const clock = setInterval(() => { - if (prefixEl.value) { - if (prefixEl.value.offsetWidth) { - inputEl.value.style.paddingLeft = prefixEl.value.offsetWidth + 'px'; - } - } - if (suffixEl.value) { - if (suffixEl.value.offsetWidth) { - inputEl.value.style.paddingRight = suffixEl.value.offsetWidth + 'px'; - } - } - }, 100); - - onUnmounted(() => { - clearInterval(clock); - }); - }); - }); - - return { - id, - v, - focused, - invalid, - changed, - filled, - inputEl, - prefixEl, - suffixEl, - focus, - onInput, - onKeydown, - updated, - }; - }, -}); -</script> - -<style lang="scss" scoped> -.ztzhwixg { - position: relative; - - > .icon { - position: absolute; - top: 0; - left: 0; - width: 24px; - text-align: center; - line-height: 32px; - - &:not(:empty) + .input { - margin-left: 28px; - } - } - - > .input { - $height: 48px; - position: relative; - - > input { - display: block; - height: $height; - width: 100%; - margin: 0; - padding: 0 16px; - font: inherit; - font-weight: normal; - font-size: 1em; - line-height: $height; - color: var(--inputText); - background: transparent; - border: none; - border-radius: 0; - outline: none; - box-shadow: none; - box-sizing: border-box; - - &[type='file'] { - display: none; - } - } - - > .prefix, - > .suffix { - display: block; - position: absolute; - z-index: 1; - top: 0; - padding: 0 16px; - font-size: 1em; - line-height: $height; - color: var(--inputLabel); - pointer-events: none; - - &:empty { - display: none; - } - - > * { - display: inline-block; - min-width: 16px; - max-width: 150px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - } - - > .prefix { - left: 0; - padding-right: 8px; - } - - > .suffix { - right: 0; - padding-left: 8px; - } - } - - &.inline { - display: inline-block; - margin: 0; - } - - &.disabled { - opacity: 0.7; - - &, * { - cursor: not-allowed !important; - } - } -} -</style> diff --git a/packages/client/src/components/debobigego/key-value-view.vue b/packages/client/src/components/debobigego/key-value-view.vue deleted file mode 100644 index 0e034a2d54..0000000000 --- a/packages/client/src/components/debobigego/key-value-view.vue +++ /dev/null @@ -1,38 +0,0 @@ -<template> -<div class="_debobigegoItem"> - <div class="_debobigegoPanel anocepby"> - <span class="key"><slot name="key"></slot></span> - <span class="value"><slot name="value"></slot></span> - </div> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import './debobigego.scss'; - -export default defineComponent({ - -}); -</script> - -<style lang="scss" scoped> -.anocepby { - display: flex; - align-items: center; - padding: 14px var(--debobigegoContentHMargin); - - > .key { - margin-right: 12px; - white-space: nowrap; - } - - > .value { - margin-left: auto; - opacity: 0.7; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - } -} -</style> diff --git a/packages/client/src/components/debobigego/link.vue b/packages/client/src/components/debobigego/link.vue deleted file mode 100644 index de463465d4..0000000000 --- a/packages/client/src/components/debobigego/link.vue +++ /dev/null @@ -1,103 +0,0 @@ -<template> -<div class="qmfkfnzi _debobigegoItem"> - <a v-if="external" class="main _button _debobigegoPanel _debobigegoClickable" :href="to" target="_blank"> - <span class="icon"><slot name="icon"></slot></span> - <span class="text"><slot></slot></span> - <span class="right"> - <span class="text"><slot name="suffix"></slot></span> - <i class="fas fa-external-link-alt icon"></i> - </span> - </a> - <MkA v-else class="main _button _debobigegoPanel _debobigegoClickable" :class="{ active }" :to="to" :behavior="behavior"> - <span class="icon"><slot name="icon"></slot></span> - <span class="text"><slot></slot></span> - <span class="right"> - <span class="text"><slot name="suffix"></slot></span> - <i class="fas fa-chevron-right icon"></i> - </span> - </MkA> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import './debobigego.scss'; - -export default defineComponent({ - props: { - to: { - type: String, - required: true - }, - active: { - type: Boolean, - required: false - }, - external: { - type: Boolean, - required: false - }, - behavior: { - type: String, - required: false, - }, - }, - data() { - return { - }; - } -}); -</script> - -<style lang="scss" scoped> -.qmfkfnzi { - > .main { - display: flex; - align-items: center; - width: 100%; - box-sizing: border-box; - padding: 14px 16px 14px 14px; - - &:hover { - text-decoration: none; - } - - &.active { - color: var(--accent); - background: var(--panelHighlight); - } - - > .icon { - width: 32px; - margin-right: 2px; - flex-shrink: 0; - text-align: center; - opacity: 0.8; - - &:empty { - display: none; - - & + .text { - padding-left: 4px; - } - } - } - - > .text { - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - padding-right: 12px; - } - - > .right { - margin-left: auto; - opacity: 0.7; - - > .text:not(:empty) { - margin-right: 0.75em; - } - } - } -} -</style> diff --git a/packages/client/src/components/debobigego/object-view.vue b/packages/client/src/components/debobigego/object-view.vue deleted file mode 100644 index 68be08560b..0000000000 --- a/packages/client/src/components/debobigego/object-view.vue +++ /dev/null @@ -1,102 +0,0 @@ -<template> -<FormGroup class="_debobigegoItem"> - <template #label><slot></slot></template> - <div class="drooglns _debobigegoItem" :class="{ tall }"> - <div class="input _debobigegoPanel"> - <textarea v-model="v" - class="_monospace" - readonly - :spellcheck="false" - ></textarea> - </div> - </div> - <template #caption><slot name="desc"></slot></template> -</FormGroup> -</template> - -<script lang="ts"> -import { defineComponent, ref, toRefs, watch } from 'vue'; -import * as JSON5 from 'json5'; -import './debobigego.scss'; -import FormGroup from './group.vue'; - -export default defineComponent({ - components: { - FormGroup, - }, - props: { - value: { - required: false - }, - tall: { - type: Boolean, - required: false, - default: false - }, - pre: { - type: Boolean, - required: false, - default: false - }, - manualSave: { - type: Boolean, - required: false, - default: false - }, - }, - setup(props, context) { - const { value } = toRefs(props); - const v = ref(''); - - watch(() => value, newValue => { - v.value = JSON5.stringify(newValue.value, null, '\t'); - }, { - immediate: true - }); - - return { - v, - }; - } -}); -</script> - -<style lang="scss" scoped> -.drooglns { - position: relative; - - > .input { - position: relative; - - > textarea { - display: block; - width: 100%; - min-width: 100%; - max-width: 100%; - min-height: 130px; - margin: 0; - padding: 16px var(--debobigegoContentHMargin); - box-sizing: border-box; - font: inherit; - font-weight: normal; - font-size: 1em; - background: transparent; - border: none; - border-radius: 0; - outline: none; - box-shadow: none; - color: var(--fg); - tab-size: 2; - white-space: pre; - } - } - - &.tall { - > .input { - > textarea { - min-height: 200px; - } - } - } -} -</style> diff --git a/packages/client/src/components/debobigego/pagination.vue b/packages/client/src/components/debobigego/pagination.vue deleted file mode 100644 index 16779caa42..0000000000 --- a/packages/client/src/components/debobigego/pagination.vue +++ /dev/null @@ -1,42 +0,0 @@ -<template> -<FormGroup class="uljviswt _debobigegoItem"> - <template #label><slot name="label"></slot></template> - <slot :items="items"></slot> - <div v-if="empty" key="_empty_" class="empty"> - <slot name="empty"></slot> - </div> - <FormButton v-show="more" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMore"> - <template v-if="!moreFetching">{{ $ts.loadMore }}</template> - <template v-if="moreFetching"><MkLoading inline/></template> - </FormButton> -</FormGroup> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import FormButton from './button.vue'; -import FormGroup from './group.vue'; -import paging from '@/scripts/paging'; - -export default defineComponent({ - components: { - FormButton, - FormGroup, - }, - - mixins: [ - paging({}), - ], - - props: { - pagination: { - required: true - }, - }, -}); -</script> - -<style lang="scss" scoped> -.uljviswt { -} -</style> diff --git a/packages/client/src/components/debobigego/radios.vue b/packages/client/src/components/debobigego/radios.vue deleted file mode 100644 index b4c5841337..0000000000 --- a/packages/client/src/components/debobigego/radios.vue +++ /dev/null @@ -1,112 +0,0 @@ -<script lang="ts"> -import { defineComponent, h } from 'vue'; -import MkRadio from '@/components/form/radio.vue'; -import './debobigego.scss'; - -export default defineComponent({ - components: { - MkRadio - }, - props: { - modelValue: { - required: false - }, - }, - data() { - return { - value: this.modelValue, - } - }, - watch: { - modelValue() { - this.value = this.modelValue; - }, - value() { - this.$emit('update:modelValue', this.value); - } - }, - render() { - const label = this.$slots.desc(); - let options = this.$slots.default(); - - // なぜかFragmentになることがあるため - if (options.length === 1 && options[0].props == null) options = options[0].children; - - return h('div', { - class: 'cnklmpwm _debobigegoItem' - }, [ - h('div', { - class: '_debobigegoLabel', - }, label), - ...options.map(option => h('button', { - class: '_button _debobigegoPanel _debobigegoClickable', - key: option.key, - onClick: () => this.value = option.props.value, - }, [h('span', { - class: ['check', { checked: this.value === option.props.value }], - }), option.children])) - ]); - } -}); -</script> - -<style lang="scss"> -.cnklmpwm { - > button { - display: block; - width: 100%; - box-sizing: border-box; - padding: 14px 18px; - text-align: left; - - &:not(:first-of-type) { - border-top: none !important; - border-top-left-radius: 0; - border-top-right-radius: 0; - } - - &:not(:last-of-type) { - border-bottom: solid 0.5px var(--divider); - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - } - - > .check { - display: inline-block; - vertical-align: bottom; - position: relative; - width: 16px; - height: 16px; - margin-right: 8px; - background: none; - border: 2px solid var(--inputBorder); - border-radius: 100%; - transition: inherit; - - &:after { - content: ""; - display: block; - position: absolute; - top: 3px; - right: 3px; - bottom: 3px; - left: 3px; - border-radius: 100%; - opacity: 0; - transform: scale(0); - transition: .4s cubic-bezier(.25,.8,.25,1); - } - - &.checked { - border-color: var(--accent); - - &:after { - background-color: var(--accent); - transform: scale(1); - opacity: 1; - } - } - } - } -} -</style> diff --git a/packages/client/src/components/debobigego/range.vue b/packages/client/src/components/debobigego/range.vue deleted file mode 100644 index dc71f25d83..0000000000 --- a/packages/client/src/components/debobigego/range.vue +++ /dev/null @@ -1,122 +0,0 @@ -<template> -<div class="ifitouly _debobigegoItem" :class="{ focused, disabled }"> - <div class="_debobigegoLabel"><slot name="label"></slot></div> - <div class="_debobigegoPanel main"> - <input - ref="input" - v-model="v" - type="range" - :disabled="disabled" - :min="min" - :max="max" - :step="step" - @focus="focused = true" - @blur="focused = false" - @input="$emit('update:value', $event.target.value)" - /> - </div> - <div class="_debobigegoCaption"><slot name="caption"></slot></div> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; - -export default defineComponent({ - props: { - value: { - type: Number, - required: false, - default: 0 - }, - disabled: { - type: Boolean, - required: false, - default: false - }, - min: { - type: Number, - required: false, - default: 0 - }, - max: { - type: Number, - required: false, - default: 100 - }, - step: { - type: Number, - required: false, - default: 1 - }, - }, - data() { - return { - v: this.value, - focused: false - }; - }, - watch: { - value(v) { - this.v = parseFloat(v); - } - }, -}); -</script> - -<style lang="scss" scoped> -.ifitouly { - position: relative; - - > .main { - padding: 22px 16px; - - > input { - display: block; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - background: var(--X10); - height: 4px; - width: 100%; - box-sizing: border-box; - margin: 0; - outline: 0; - border: 0; - border-radius: 7px; - - &.disabled { - opacity: 0.6; - cursor: not-allowed; - } - - &::-webkit-slider-thumb { - -webkit-appearance: none; - appearance: none; - cursor: pointer; - width: 20px; - height: 20px; - display: block; - border-radius: 50%; - border: none; - background: var(--accent); - box-shadow: 0 0 6px rgba(0, 0, 0, 0.3); - box-sizing: content-box; - } - - &::-moz-range-thumb { - -moz-appearance: none; - appearance: none; - cursor: pointer; - width: 20px; - height: 20px; - display: block; - border-radius: 50%; - border: none; - background: var(--accent); - box-shadow: 0 0 6px rgba(0, 0, 0, 0.3); - } - } - } -} -</style> diff --git a/packages/client/src/components/debobigego/select.vue b/packages/client/src/components/debobigego/select.vue deleted file mode 100644 index 081bbfe302..0000000000 --- a/packages/client/src/components/debobigego/select.vue +++ /dev/null @@ -1,145 +0,0 @@ -<template> -<div class="yrtfrpux _debobigegoItem" :class="{ disabled, inline }"> - <div class="_debobigegoLabel"><slot name="label"></slot></div> - <div ref="icon" class="icon"><slot name="icon"></slot></div> - <div class="input _debobigegoPanel _debobigegoClickable" @click="focus"> - <div ref="prefix" class="prefix"><slot name="prefix"></slot></div> - <select ref="input" - v-model="v" - :required="required" - :disabled="disabled" - @focus="focused = true" - @blur="focused = false" - > - <slot></slot> - </select> - <div class="suffix"> - <i class="fas fa-chevron-down"></i> - </div> - </div> - <div class="_debobigegoCaption"><slot name="caption"></slot></div> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import './debobigego.scss'; - -export default defineComponent({ - props: { - modelValue: { - required: false - }, - required: { - type: Boolean, - required: false - }, - disabled: { - type: Boolean, - required: false - }, - inline: { - type: Boolean, - required: false, - default: false - }, - }, - data() { - return { - }; - }, - computed: { - v: { - get() { - return this.modelValue; - }, - set(v) { - this.$emit('update:modelValue', v); - } - }, - }, - methods: { - focus() { - this.$refs.input.focus(); - } - } -}); -</script> - -<style lang="scss" scoped> -.yrtfrpux { - position: relative; - - > .icon { - position: absolute; - top: 0; - left: 0; - width: 24px; - text-align: center; - line-height: 32px; - - &:not(:empty) + .input { - margin-left: 28px; - } - } - - > .input { - display: flex; - position: relative; - - > select { - display: block; - flex: 1; - width: 100%; - padding: 0 16px; - font: inherit; - font-weight: normal; - font-size: 1em; - height: 48px; - background: none; - border: none; - border-radius: 0; - outline: none; - box-shadow: none; - appearance: none; - -webkit-appearance: none; - color: var(--fg); - - option, - optgroup { - color: var(--fg); - background: var(--bg); - } - } - - > .prefix, - > .suffix { - display: block; - align-self: center; - justify-self: center; - font-size: 1em; - line-height: 32px; - color: var(--inputLabel); - pointer-events: none; - - &:empty { - display: none; - } - - > * { - display: block; - min-width: 16px; - } - } - - > .prefix { - padding-right: 4px; - } - - > .suffix { - padding: 0 16px 0 0; - opacity: 0.7; - } - } -} -</style> diff --git a/packages/client/src/components/debobigego/suspense.vue b/packages/client/src/components/debobigego/suspense.vue deleted file mode 100644 index acb0b64424..0000000000 --- a/packages/client/src/components/debobigego/suspense.vue +++ /dev/null @@ -1,101 +0,0 @@ -<template> -<transition name="fade" mode="out-in"> - <div v-if="pending" class="_debobigegoItem"> - <div class="_debobigegoPanel"> - <MkLoading/> - </div> - </div> - <div v-else-if="resolved" class="_debobigegoItem"> - <slot :result="result"></slot> - </div> - <div v-else class="_debobigegoItem"> - <div class="_debobigegoPanel eiurkvay"> - <div><i class="fas fa-exclamation-triangle"></i> {{ $ts.somethingHappened }}</div> - <MkButton inline class="retry" @click="retry"><i class="fas fa-redo-alt"></i> {{ $ts.retry }}</MkButton> - </div> - </div> -</transition> -</template> - -<script lang="ts"> -import { defineComponent, PropType, ref, watch } from 'vue'; -import './debobigego.scss'; -import MkButton from '@/components/ui/button.vue'; - -export default defineComponent({ - components: { - MkButton - }, - - props: { - p: { - type: Function as PropType<() => Promise<any>>, - required: true, - } - }, - - setup(props, context) { - const pending = ref(true); - const resolved = ref(false); - const rejected = ref(false); - const result = ref(null); - - const process = () => { - if (props.p == null) { - return; - } - const promise = props.p(); - pending.value = true; - resolved.value = false; - rejected.value = false; - promise.then((_result) => { - pending.value = false; - resolved.value = true; - result.value = _result; - }); - promise.catch(() => { - pending.value = false; - rejected.value = true; - }); - }; - - watch(() => props.p, () => { - process(); - }, { - immediate: true - }); - - const retry = () => { - process(); - }; - - return { - pending, - resolved, - rejected, - result, - retry, - }; - } -}); -</script> - -<style lang="scss" scoped> -.fade-enter-active, -.fade-leave-active { - transition: opacity 0.125s ease; -} -.fade-enter-from, -.fade-leave-to { - opacity: 0; -} - -.eiurkvay { - padding: 16px; - text-align: center; - - > .retry { - margin-top: 16px; - } -} -</style> diff --git a/packages/client/src/components/debobigego/switch.vue b/packages/client/src/components/debobigego/switch.vue deleted file mode 100644 index 239140f730..0000000000 --- a/packages/client/src/components/debobigego/switch.vue +++ /dev/null @@ -1,132 +0,0 @@ -<template> -<div class="ijnpvmgr _debobigegoItem"> - <div class="main _debobigegoPanel _debobigegoClickable" - :class="{ disabled, checked }" - :aria-checked="checked" - :aria-disabled="disabled" - @click.prevent="toggle" - > - <input - ref="input" - type="checkbox" - :disabled="disabled" - @keydown.enter="toggle" - > - <span v-tooltip="checked ? $ts.itsOn : $ts.itsOff" class="button"> - <span class="handle"></span> - </span> - <span class="label"> - <span><slot></slot></span> - </span> - </div> - <div class="_debobigegoCaption"><slot name="desc"></slot></div> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import './debobigego.scss'; - -export default defineComponent({ - props: { - modelValue: { - type: Boolean, - default: false - }, - disabled: { - type: Boolean, - default: false - } - }, - computed: { - checked(): boolean { - return this.modelValue; - } - }, - methods: { - toggle() { - if (this.disabled) return; - this.$emit('update:modelValue', !this.checked); - } - } -}); -</script> - -<style lang="scss" scoped> -.ijnpvmgr { - > .main { - position: relative; - display: flex; - padding: 14px 16px; - cursor: pointer; - - > * { - user-select: none; - } - - > input { - position: absolute; - width: 0; - height: 0; - opacity: 0; - margin: 0; - } - - > .button { - position: relative; - display: inline-block; - flex-shrink: 0; - margin: 0; - width: 34px; - height: 22px; - background: var(--switchBg); - outline: none; - border-radius: 999px; - transition: all 0.3s; - cursor: pointer; - - > .handle { - position: absolute; - top: 0; - left: 3px; - bottom: 0; - margin: auto 0; - border-radius: 100%; - transition: background-color 0.3s, transform 0.3s; - width: 16px; - height: 16px; - background-color: #fff; - pointer-events: none; - } - } - - > .label { - margin-left: 12px; - display: block; - transition: inherit; - color: var(--fg); - - > span { - display: block; - line-height: 20px; - transition: inherit; - } - } - - &.disabled { - opacity: 0.6; - cursor: not-allowed; - } - - &.checked { - > .button { - background-color: var(--accent); - - > .handle { - transform: translateX(12px); - } - } - } - } -} -</style> diff --git a/packages/client/src/components/debobigego/textarea.vue b/packages/client/src/components/debobigego/textarea.vue deleted file mode 100644 index ca5b35c49e..0000000000 --- a/packages/client/src/components/debobigego/textarea.vue +++ /dev/null @@ -1,161 +0,0 @@ -<template> -<FormGroup class="_debobigegoItem"> - <template #label><slot></slot></template> - <div class="rivhosbp _debobigegoItem" :class="{ tall, pre }"> - <div class="input _debobigegoPanel"> - <textarea ref="input" v-model="v" - :class="{ code, _monospace: code }" - :required="required" - :readonly="readonly" - :pattern="pattern" - :autocomplete="autocomplete" - :spellcheck="!code" - @input="onInput" - @focus="focused = true" - @blur="focused = false" - ></textarea> - </div> - </div> - <template #caption><slot name="desc"></slot></template> - - <FormButton v-if="manualSave && changed" primary @click="updated"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> -</FormGroup> -</template> - -<script lang="ts"> -import { defineComponent, ref, toRefs, watch } from 'vue'; -import './debobigego.scss'; -import FormButton from './button.vue'; -import FormGroup from './group.vue'; - -export default defineComponent({ - components: { - FormGroup, - FormButton, - }, - props: { - modelValue: { - required: false - }, - required: { - type: Boolean, - required: false - }, - readonly: { - type: Boolean, - required: false - }, - pattern: { - type: String, - required: false - }, - autocomplete: { - type: String, - required: false - }, - code: { - type: Boolean, - required: false - }, - tall: { - type: Boolean, - required: false, - default: false - }, - pre: { - type: Boolean, - required: false, - default: false - }, - manualSave: { - type: Boolean, - required: false, - default: false - }, - }, - setup(props, context) { - const { modelValue } = toRefs(props); - const v = ref(modelValue.value); - const changed = ref(false); - const inputEl = ref(null); - const focus = () => inputEl.value.focus(); - const onInput = (ev) => { - changed.value = true; - context.emit('change', ev); - }; - - const updated = () => { - changed.value = false; - context.emit('update:modelValue', v.value); - }; - - watch(modelValue.value, newValue => { - v.value = newValue; - }); - - watch(v, newValue => { - if (!props.manualSave) { - updated(); - } - }); - - return { - v, - updated, - changed, - focus, - onInput, - }; - } -}); -</script> - -<style lang="scss" scoped> -.rivhosbp { - position: relative; - - > .input { - position: relative; - - > textarea { - display: block; - width: 100%; - min-width: 100%; - max-width: 100%; - min-height: 130px; - margin: 0; - padding: 16px; - box-sizing: border-box; - font: inherit; - font-weight: normal; - font-size: 1em; - background: transparent; - border: none; - border-radius: 0; - outline: none; - box-shadow: none; - color: var(--fg); - - &.code { - tab-size: 2; - } - } - } - - &.tall { - > .input { - > textarea { - min-height: 200px; - } - } - } - - &.pre { - > .input { - > textarea { - white-space: pre; - } - } - } -} -</style> diff --git a/packages/client/src/components/debobigego/tuple.vue b/packages/client/src/components/debobigego/tuple.vue deleted file mode 100644 index 1d2a6cb55e..0000000000 --- a/packages/client/src/components/debobigego/tuple.vue +++ /dev/null @@ -1,36 +0,0 @@ -<template> -<div v-size="{ max: [500] }" class="wthhikgt _debobigegoItem"> - <slot></slot> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; - -export default defineComponent({ -}); -</script> - -<style lang="scss" scoped> -.wthhikgt { - position: relative; - display: flex; - - > ::v-deep(*) { - flex: 1; - margin: 0; - - &:not(:last-child) { - margin-right: 16px; - } - } - - &.max-width_500px { - display: block; - - > ::v-deep(*) { - margin: inherit; - } - } -} -</style> diff --git a/packages/client/src/components/dialog.vue b/packages/client/src/components/dialog.vue index c2fa1b02b8..b6b649cde9 100644 --- a/packages/client/src/components/dialog.vue +++ b/packages/client/src/components/dialog.vue @@ -1,5 +1,5 @@ <template> -<MkModal ref="modal" :prefer-type="'dialog'" :z-priority="'high'" @click="done(true)" @closed="$emit('closed')"> +<MkModal ref="modal" :prefer-type="'dialog'" :z-priority="'high'" @click="done(true)" @closed="emit('closed')"> <div class="mk-dialog"> <div v-if="icon" class="icon"> <i :class="icon"></i> @@ -14,7 +14,7 @@ </div> <header v-if="title"><Mfm :text="title"/></header> <div v-if="text" class="body"><Mfm :text="text"/></div> - <MkInput v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder" @keydown="onInputKeydown"> + <MkInput v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder || undefined" @keydown="onInputKeydown"> <template v-if="input.type === 'password'" #prefix><i class="fas fa-lock"></i></template> </MkInput> <MkSelect v-if="select" v-model="selectedValue" autofocus> @@ -28,8 +28,8 @@ </template> </MkSelect> <div v-if="(showOkButton || showCancelButton) && !actions" class="buttons"> - <MkButton v-if="showOkButton" inline primary :autofocus="!input && !select" @click="ok">{{ (showCancelButton || input || select) ? $ts.ok : $ts.gotIt }}</MkButton> - <MkButton v-if="showCancelButton || input || select" inline @click="cancel">{{ $ts.cancel }}</MkButton> + <MkButton v-if="showOkButton" inline primary :autofocus="!input && !select" @click="ok">{{ (showCancelButton || input || select) ? i18n.locale.ok : i18n.locale.gotIt }}</MkButton> + <MkButton v-if="showCancelButton || input || select" inline @click="cancel">{{ i18n.locale.cancel }}</MkButton> </div> <div v-if="actions" class="buttons"> <MkButton v-for="action in actions" :key="action.text" inline :primary="action.primary" @click="() => { action.callback(); close(); }">{{ action.text }}</MkButton> @@ -38,118 +38,108 @@ </MkModal> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onBeforeUnmount, onMounted, ref } from 'vue'; import MkModal from '@/components/ui/modal.vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkSelect from '@/components/form/select.vue'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkModal, - MkButton, - MkInput, - MkSelect, - }, +type Input = { + type: HTMLInputElement['type']; + placeholder?: string | null; + default: any | null; +}; - props: { - type: { - type: String, - required: false, - default: 'info' - }, - title: { - type: String, - required: false - }, - text: { - type: String, - required: false - }, - input: { - required: false - }, - select: { - required: false - }, - icon: { - required: false - }, - actions: { - required: false - }, - showOkButton: { - type: Boolean, - default: true - }, - showCancelButton: { - type: Boolean, - default: false - }, - cancelableByBgClick: { - type: Boolean, - default: true - }, - }, +type Select = { + items: { + value: string; + text: string; + }[]; + groupedItems: { + label: string; + items: { + value: string; + text: string; + }[]; + }[]; + default: string | null; +}; - emits: ['done', 'closed'], +const props = withDefaults(defineProps<{ + type?: 'success' | 'error' | 'warning' | 'info' | 'question' | 'waiting'; + title: string; + text?: string; + input?: Input; + select?: Select; + icon?: string; + actions?: { + text: string; + primary?: boolean, + callback: (...args: any[]) => void; + }[]; + showOkButton?: boolean; + showCancelButton?: boolean; + cancelableByBgClick?: boolean; +}>(), { + type: 'info', + showOkButton: true, + showCancelButton: false, + cancelableByBgClick: true, +}); - data() { - return { - inputValue: this.input && this.input.default ? this.input.default : null, - selectedValue: this.select ? this.select.default ? this.select.default : this.select.items ? this.select.items[0].value : this.select.groupedItems[0].items[0].value : null, - }; - }, +const emit = defineEmits<{ + (e: 'done', v: { canceled: boolean; result: any }): void; + (e: 'closed'): void; +}>(); - mounted() { - document.addEventListener('keydown', this.onKeydown); - }, +const modal = ref<InstanceType<typeof MkModal>>(); - beforeUnmount() { - document.removeEventListener('keydown', this.onKeydown); - }, +const inputValue = ref(props.input?.default || null); +const selectedValue = ref(props.select?.default || null); - methods: { - done(canceled, result?) { - this.$emit('done', { canceled, result }); - this.$refs.modal.close(); - }, +function done(canceled: boolean, result?) { + emit('done', { canceled, result }); + modal.value?.close(); +} - async ok() { - if (!this.showOkButton) return; +async function ok() { + if (!props.showOkButton) return; - const result = - this.input ? this.inputValue : - this.select ? this.selectedValue : - true; - this.done(false, result); - }, + const result = + props.input ? inputValue.value : + props.select ? selectedValue.value : + true; + done(false, result); +} - cancel() { - this.done(true); - }, +function cancel() { + done(true); +} +/* +function onBgClick() { + if (props.cancelableByBgClick) cancel(); +} +*/ +function onKeydown(e: KeyboardEvent) { + if (e.key === 'Escape') cancel(); +} - onBgClick() { - if (this.cancelableByBgClick) { - this.cancel(); - } - }, +function onInputKeydown(e: KeyboardEvent) { + if (e.key === 'Enter') { + e.preventDefault(); + e.stopPropagation(); + ok(); + } +} - onKeydown(e) { - if (e.which === 27) { // ESC - this.cancel(); - } - }, +onMounted(() => { + document.addEventListener('keydown', onKeydown); +}); - onInputKeydown(e) { - if (e.which === 13) { // Enter - e.preventDefault(); - e.stopPropagation(); - this.ok(); - } - } - } +onBeforeUnmount(() => { + document.removeEventListener('keydown', onKeydown); }); </script> diff --git a/packages/client/src/components/drive-file-thumbnail.vue b/packages/client/src/components/drive-file-thumbnail.vue index e94b6b8bcb..81b80e7e8e 100644 --- a/packages/client/src/components/drive-file-thumbnail.vue +++ b/packages/client/src/components/drive-file-thumbnail.vue @@ -14,71 +14,42 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; +import * as Misskey from 'misskey-js'; import ImgWithBlurhash from '@/components/img-with-blurhash.vue'; -import { ColdDeviceStorage } from '@/store'; -export default defineComponent({ - components: { - ImgWithBlurhash - }, - props: { - file: { - type: Object, - required: true - }, - fit: { - type: String, - required: false, - default: 'cover' - }, - }, - data() { - return { - isContextmenuShowing: false, - isDragging: false, +const props = defineProps<{ + file: Misskey.entities.DriveFile; + fit: string; +}>(); - }; - }, - computed: { - is(): 'image' | 'video' | 'midi' | 'audio' | 'csv' | 'pdf' | 'textfile' | 'archive' | 'unknown' { - if (this.file.type.startsWith('image/')) return 'image'; - if (this.file.type.startsWith('video/')) return 'video'; - if (this.file.type === 'audio/midi') return 'midi'; - if (this.file.type.startsWith('audio/')) return 'audio'; - if (this.file.type.endsWith('/csv')) return 'csv'; - if (this.file.type.endsWith('/pdf')) return 'pdf'; - if (this.file.type.startsWith('text/')) return 'textfile'; - if ([ - "application/zip", - "application/x-cpio", - "application/x-bzip", - "application/x-bzip2", - "application/java-archive", - "application/x-rar-compressed", - "application/x-tar", - "application/gzip", - "application/x-7z-compressed" - ].some(e => e === this.file.type)) return 'archive'; - return 'unknown'; - }, - isThumbnailAvailable(): boolean { - return this.file.thumbnailUrl - ? (this.is === 'image' || this.is === 'video') - : false; - }, - }, - mounted() { - const audioTag = this.$refs.volumectrl as HTMLAudioElement; - if (audioTag) audioTag.volume = ColdDeviceStorage.get('mediaVolume'); - }, - methods: { - volumechange() { - const audioTag = this.$refs.volumectrl as HTMLAudioElement; - ColdDeviceStorage.set('mediaVolume', audioTag.volume); - } - } +const is = computed(() => { + if (props.file.type.startsWith('image/')) return 'image'; + if (props.file.type.startsWith('video/')) return 'video'; + if (props.file.type === 'audio/midi') return 'midi'; + if (props.file.type.startsWith('audio/')) return 'audio'; + if (props.file.type.endsWith('/csv')) return 'csv'; + if (props.file.type.endsWith('/pdf')) return 'pdf'; + if (props.file.type.startsWith('text/')) return 'textfile'; + if ([ + "application/zip", + "application/x-cpio", + "application/x-bzip", + "application/x-bzip2", + "application/java-archive", + "application/x-rar-compressed", + "application/x-tar", + "application/gzip", + "application/x-7z-compressed" + ].some(e => e === props.file.type)) return 'archive'; + return 'unknown'; +}); + +const isThumbnailAvailable = computed(() => { + return props.file.thumbnailUrl + ? (is.value === 'image' as const || is.value === 'video') + : false; }); </script> diff --git a/packages/client/src/components/drive-select-dialog.vue b/packages/client/src/components/drive-select-dialog.vue index 75537dfe3e..6d84511277 100644 --- a/packages/client/src/components/drive-select-dialog.vue +++ b/packages/client/src/components/drive-select-dialog.vue @@ -7,64 +7,51 @@ @click="cancel()" @close="cancel()" @ok="ok()" - @closed="$emit('closed')" + @closed="emit('closed')" > <template #header> - {{ multiple ? ((type === 'file') ? $ts.selectFiles : $ts.selectFolders) : ((type === 'file') ? $ts.selectFile : $ts.selectFolder) }} + {{ multiple ? ((type === 'file') ? i18n.locale.selectFiles : i18n.locale.selectFolders) : ((type === 'file') ? i18n.locale.selectFile : i18n.locale.selectFolder) }} <span v-if="selected.length > 0" style="margin-left: 8px; opacity: 0.5;">({{ number(selected.length) }})</span> </template> <XDrive :multiple="multiple" :select="type" @changeSelection="onChangeSelection" @selected="ok()"/> </XModalWindow> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { ref } from 'vue'; +import * as Misskey from 'misskey-js'; import XDrive from './drive.vue'; import XModalWindow from '@/components/ui/modal-window.vue'; import number from '@/filters/number'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XDrive, - XModalWindow, - }, - - props: { - type: { - type: String, - required: false, - default: 'file' - }, - multiple: { - type: Boolean, - default: false - } - }, +withDefaults(defineProps<{ + type?: 'file' | 'folder'; + multiple: boolean; +}>(), { + type: 'file', +}); - emits: ['done', 'closed'], +const emit = defineEmits<{ + (e: 'done', r?: Misskey.entities.DriveFile[]): void; + (e: 'closed'): void; +}>(); - data() { - return { - selected: [] - }; - }, +const dialog = ref<InstanceType<typeof XModalWindow>>(); - methods: { - ok() { - this.$emit('done', this.selected); - this.$refs.dialog.close(); - }, +const selected = ref<Misskey.entities.DriveFile[]>([]); - cancel() { - this.$emit('done'); - this.$refs.dialog.close(); - }, +function ok() { + emit('done', selected.value); + dialog.value?.close(); +} - onChangeSelection(xs) { - this.selected = xs; - }, +function cancel() { + emit('done'); + dialog.value?.close(); +} - number - } -}); +function onChangeSelection(files: Misskey.entities.DriveFile[]) { + selected.value = files; +} </script> diff --git a/packages/client/src/components/drive-window.vue b/packages/client/src/components/drive-window.vue index 43f07ebe76..8b60bf7794 100644 --- a/packages/client/src/components/drive-window.vue +++ b/packages/client/src/components/drive-window.vue @@ -3,42 +3,27 @@ :initial-width="800" :initial-height="500" :can-resize="true" - @closed="$emit('closed')" + @closed="emit('closed')" > <template #header> - {{ $ts.drive }} + {{ i18n.locale.drive }} </template> <XDrive :initial-folder="initialFolder"/> </XWindow> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; +import * as Misskey from 'misskey-js'; import XDrive from './drive.vue'; import XWindow from '@/components/ui/window.vue'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XDrive, - XWindow, - }, +defineProps<{ + initialFolder?: Misskey.entities.DriveFolder; +}>(); - props: { - initialFolder: { - type: Object, - required: false - }, - }, - - emits: ['closed'], - - data() { - return { - }; - }, - - methods: { - - } -}); +const emit = defineEmits<{ + (e: 'closed'): void; +}>(); </script> diff --git a/packages/client/src/components/drive.file.vue b/packages/client/src/components/drive.file.vue index 511647229e..fd6a813838 100644 --- a/packages/client/src/components/drive.file.vue +++ b/packages/client/src/components/drive.file.vue @@ -8,17 +8,17 @@ @dragstart="onDragstart" @dragend="onDragend" > - <div v-if="$i.avatarId == file.id" class="label"> + <div v-if="$i?.avatarId == file.id" class="label"> <img src="/client-assets/label.svg"/> - <p>{{ $ts.avatar }}</p> + <p>{{ i18n.locale.avatar }}</p> </div> - <div v-if="$i.bannerId == file.id" class="label"> + <div v-if="$i?.bannerId == file.id" class="label"> <img src="/client-assets/label.svg"/> - <p>{{ $ts.banner }}</p> + <p>{{ i18n.locale.banner }}</p> </div> <div v-if="file.isSensitive" class="label red"> <img src="/client-assets/label-red.svg"/> - <p>{{ $ts.nsfw }}</p> + <p>{{ i18n.locale.nsfw }}</p> </div> <MkDriveFileThumbnail class="thumbnail" :file="file" fit="contain"/> @@ -30,179 +30,155 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed, ref } from 'vue'; +import * as Misskey from 'misskey-js'; import copyToClipboard from '@/scripts/copy-to-clipboard'; import MkDriveFileThumbnail from './drive-file-thumbnail.vue'; import bytes from '@/filters/bytes'; import * as os from '@/os'; +import { i18n } from '@/i18n'; +import { $i } from '@/account'; -export default defineComponent({ - components: { - MkDriveFileThumbnail - }, +const props = withDefaults(defineProps<{ + file: Misskey.entities.DriveFile; + isSelected?: boolean; + selectMode?: boolean; +}>(), { + isSelected: false, + selectMode: false, +}); - props: { - file: { - type: Object, - required: true, - }, - isSelected: { - type: Boolean, - required: false, - default: false, - }, - selectMode: { - type: Boolean, - required: false, - default: false, - } - }, +const emit = defineEmits<{ + (e: 'chosen', r: Misskey.entities.DriveFile): void; + (e: 'dragstart'): void; + (e: 'dragend'): void; +}>(); - emits: ['chosen'], +const isDragging = ref(false); - data() { - return { - isDragging: false - }; - }, +const title = computed(() => `${props.file.name}\n${props.file.type} ${bytes(props.file.size)}`); - computed: { - // TODO: parentへの参照を無くす - browser(): any { - return this.$parent; - }, - title(): string { - return `${this.file.name}\n${this.file.type} ${bytes(this.file.size)}`; - } - }, - - methods: { - getMenu() { - return [{ - text: this.$ts.rename, - icon: 'fas fa-i-cursor', - action: this.rename - }, { - text: this.file.isSensitive ? this.$ts.unmarkAsSensitive : this.$ts.markAsSensitive, - icon: this.file.isSensitive ? 'fas fa-eye' : 'fas fa-eye-slash', - action: this.toggleSensitive - }, { - text: this.$ts.describeFile, - icon: 'fas fa-i-cursor', - action: this.describe - }, null, { - text: this.$ts.copyUrl, - icon: 'fas fa-link', - action: this.copyUrl - }, { - type: 'a', - href: this.file.url, - target: '_blank', - text: this.$ts.download, - icon: 'fas fa-download', - download: this.file.name - }, null, { - text: this.$ts.delete, - icon: 'fas fa-trash-alt', - danger: true, - action: this.deleteFile - }]; - }, +function getMenu() { + return [{ + text: i18n.locale.rename, + icon: 'fas fa-i-cursor', + action: rename + }, { + text: props.file.isSensitive ? i18n.locale.unmarkAsSensitive : i18n.locale.markAsSensitive, + icon: props.file.isSensitive ? 'fas fa-eye' : 'fas fa-eye-slash', + action: toggleSensitive + }, { + text: i18n.locale.describeFile, + icon: 'fas fa-i-cursor', + action: describe + }, null, { + text: i18n.locale.copyUrl, + icon: 'fas fa-link', + action: copyUrl + }, { + type: 'a', + href: props.file.url, + target: '_blank', + text: i18n.locale.download, + icon: 'fas fa-download', + download: props.file.name + }, null, { + text: i18n.locale.delete, + icon: 'fas fa-trash-alt', + danger: true, + action: deleteFile + }]; +} - onClick(ev) { - if (this.selectMode) { - this.$emit('chosen', this.file); - } else { - os.popupMenu(this.getMenu(), ev.currentTarget || ev.target); - } - }, +function onClick(ev: MouseEvent) { + if (props.selectMode) { + emit('chosen', props.file); + } else { + os.popupMenu(getMenu(), (ev.currentTarget || ev.target || undefined) as HTMLElement | undefined); + } +} - onContextmenu(e) { - os.contextMenu(this.getMenu(), e); - }, +function onContextmenu(e: MouseEvent) { + os.contextMenu(getMenu(), e); +} - onDragstart(e) { - e.dataTransfer.effectAllowed = 'move'; - e.dataTransfer.setData(_DATA_TRANSFER_DRIVE_FILE_, JSON.stringify(this.file)); - this.isDragging = true; +function onDragstart(e: DragEvent) { + if (e.dataTransfer) { + e.dataTransfer.effectAllowed = 'move'; + e.dataTransfer.setData(_DATA_TRANSFER_DRIVE_FILE_, JSON.stringify(props.file)); + } + isDragging.value = true; - // 親ブラウザに対して、ドラッグが開始されたフラグを立てる - // (=あなたの子供が、ドラッグを開始しましたよ) - this.browser.isDragSource = true; - }, + emit('dragstart'); +} - onDragend(e) { - this.isDragging = false; - this.browser.isDragSource = false; - }, +function onDragend() { + isDragging.value = false; + emit('dragend'); +} - rename() { - os.inputText({ - title: this.$ts.renameFile, - placeholder: this.$ts.inputNewFileName, - default: this.file.name, - allowEmpty: false - }).then(({ canceled, result: name }) => { - if (canceled) return; - os.api('drive/files/update', { - fileId: this.file.id, - name: name - }); - }); - }, +function rename() { + os.inputText({ + title: i18n.locale.renameFile, + placeholder: i18n.locale.inputNewFileName, + default: props.file.name, + }).then(({ canceled, result: name }) => { + if (canceled) return; + os.api('drive/files/update', { + fileId: props.file.id, + name: name + }); + }); +} - describe() { - os.popup(import('@/components/media-caption.vue'), { - title: this.$ts.describeFile, - input: { - placeholder: this.$ts.inputNewDescription, - default: this.file.comment !== null ? this.file.comment : '', - }, - image: this.file - }, { - done: result => { - if (!result || result.canceled) return; - let comment = result.result; - os.api('drive/files/update', { - fileId: this.file.id, - comment: comment.length == 0 ? null : comment - }); - } - }, 'closed'); +function describe() { + os.popup(import('@/components/media-caption.vue'), { + title: i18n.locale.describeFile, + input: { + placeholder: i18n.locale.inputNewDescription, + default: props.file.comment !== null ? props.file.comment : '', }, - - toggleSensitive() { + image: props.file + }, { + done: result => { + if (!result || result.canceled) return; + let comment = result.result; os.api('drive/files/update', { - fileId: this.file.id, - isSensitive: !this.file.isSensitive + fileId: props.file.id, + comment: comment.length == 0 ? null : comment }); - }, - - copyUrl() { - copyToClipboard(this.file.url); - os.success(); - }, - - addApp() { - alert('not implemented yet'); - }, + } + }, 'closed'); +} - async deleteFile() { - const { canceled } = await os.confirm({ - type: 'warning', - text: this.$t('driveFileDeleteConfirm', { name: this.file.name }), - }); - if (canceled) return; +function toggleSensitive() { + os.api('drive/files/update', { + fileId: props.file.id, + isSensitive: !props.file.isSensitive + }); +} - os.api('drive/files/delete', { - fileId: this.file.id - }); - }, +function copyUrl() { + copyToClipboard(props.file.url); + os.success(); +} +/* +function addApp() { + alert('not implemented yet'); +} +*/ +async function deleteFile() { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.t('driveFileDeleteConfirm', { name: props.file.name }), + }); - bytes - } -}); + if (canceled) return; + os.api('drive/files/delete', { + fileId: props.file.id + }); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/drive.folder.vue b/packages/client/src/components/drive.folder.vue index aaba736cf8..20a6343cfe 100644 --- a/packages/client/src/components/drive.folder.vue +++ b/packages/client/src/components/drive.folder.vue @@ -19,243 +19,233 @@ <template v-if="!hover"><i class="fas fa-folder fa-fw"></i></template> {{ folder.name }} </p> - <p v-if="$store.state.uploadFolder == folder.id" class="upload"> - {{ $ts.uploadFolder }} + <p v-if="defaultStore.state.uploadFolder == folder.id" class="upload"> + {{ i18n.locale.uploadFolder }} </p> <button v-if="selectMode" class="checkbox _button" :class="{ checked: isSelected }" @click.prevent.stop="checkboxClicked"></button> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed, ref } from 'vue'; +import * as Misskey from 'misskey-js'; import * as os from '@/os'; +import { i18n } from '@/i18n'; +import { defaultStore } from '@/store'; -export default defineComponent({ - props: { - folder: { - type: Object, - required: true, - }, - isSelected: { - type: Boolean, - required: false, - default: false, - }, - selectMode: { - type: Boolean, - required: false, - default: false, - } - }, +const props = withDefaults(defineProps<{ + folder: Misskey.entities.DriveFolder; + isSelected?: boolean; + selectMode?: boolean; +}>(), { + isSelected: false, + selectMode: false, +}); - emits: ['chosen'], +const emit = defineEmits<{ + (ev: 'chosen', v: Misskey.entities.DriveFolder): void; + (ev: 'move', v: Misskey.entities.DriveFolder): void; + (ev: 'upload', file: File, folder: Misskey.entities.DriveFolder); + (ev: 'removeFile', v: Misskey.entities.DriveFile['id']): void; + (ev: 'removeFolder', v: Misskey.entities.DriveFolder['id']): void; + (ev: 'dragstart'): void; + (ev: 'dragend'): void; +}>(); - data() { - return { - hover: false, - draghover: false, - isDragging: false, - }; - }, +const hover = ref(false); +const draghover = ref(false); +const isDragging = ref(false); - computed: { - browser(): any { - return this.$parent; - }, - title(): string { - return this.folder.name; - } - }, +const title = computed(() => props.folder.name); - methods: { - checkboxClicked(e) { - this.$emit('chosen', this.folder); - }, +function checkboxClicked() { + emit('chosen', props.folder); +} - onClick() { - this.browser.move(this.folder); - }, +function onClick() { + emit('move', props.folder); +} - onMouseover() { - this.hover = true; - }, +function onMouseover() { + hover.value = true; +} - onMouseout() { - this.hover = false - }, +function onMouseout() { + hover.value = false +} - onDragover(e) { - // 自分自身がドラッグされている場合 - if (this.isDragging) { - // 自分自身にはドロップさせない - e.dataTransfer.dropEffect = 'none'; - return; - } +function onDragover(ev: DragEvent) { + if (!ev.dataTransfer) return; - const isFile = e.dataTransfer.items[0].kind == 'file'; - const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; - const isDriveFolder = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FOLDER_; + // 自分自身がドラッグされている場合 + if (isDragging.value) { + // 自分自身にはドロップさせない + ev.dataTransfer.dropEffect = 'none'; + return; + } - if (isFile || isDriveFile || isDriveFolder) { - e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; - } else { - e.dataTransfer.dropEffect = 'none'; - } - }, + const isFile = ev.dataTransfer.items[0].kind == 'file'; + const isDriveFile = ev.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; + const isDriveFolder = ev.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FOLDER_; - onDragenter() { - if (!this.isDragging) this.draghover = true; - }, + if (isFile || isDriveFile || isDriveFolder) { + ev.dataTransfer.dropEffect = ev.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; + } else { + ev.dataTransfer.dropEffect = 'none'; + } +} - onDragleave() { - this.draghover = false; - }, +function onDragenter() { + if (!isDragging.value) draghover.value = true; +} - onDrop(e) { - this.draghover = false; +function onDragleave() { + draghover.value = false; +} - // ファイルだったら - if (e.dataTransfer.files.length > 0) { - for (const file of Array.from(e.dataTransfer.files)) { - this.browser.upload(file, this.folder); - } - return; - } +function onDrop(ev: DragEvent) { + draghover.value = false; - //#region ドライブのファイル - const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); - if (driveFile != null && driveFile != '') { - const file = JSON.parse(driveFile); - this.browser.removeFile(file.id); - os.api('drive/files/update', { - fileId: file.id, - folderId: this.folder.id - }); - } - //#endregion + if (!ev.dataTransfer) return; + + // ファイルだったら + if (ev.dataTransfer.files.length > 0) { + for (const file of Array.from(ev.dataTransfer.files)) { + emit('upload', file, props.folder); + } + return; + } - //#region ドライブのフォルダ - const driveFolder = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_); - if (driveFolder != null && driveFolder != '') { - const folder = JSON.parse(driveFolder); + //#region ドライブのファイル + const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); + if (driveFile != null && driveFile != '') { + const file = JSON.parse(driveFile); + emit('removeFile', file.id); + os.api('drive/files/update', { + fileId: file.id, + folderId: props.folder.id + }); + } + //#endregion - // 移動先が自分自身ならreject - if (folder.id == this.folder.id) return; + //#region ドライブのフォルダ + const driveFolder = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_); + if (driveFolder != null && driveFolder != '') { + const folder = JSON.parse(driveFolder); - this.browser.removeFolder(folder.id); - os.api('drive/folders/update', { - folderId: folder.id, - parentId: this.folder.id - }).then(() => { - // noop - }).catch(err => { - switch (err) { - case 'detected-circular-definition': - os.alert({ - title: this.$ts.unableToProcess, - text: this.$ts.circularReferenceFolder - }); - break; - default: - os.alert({ - type: 'error', - text: this.$ts.somethingHappened - }); - } - }); + // 移動先が自分自身ならreject + if (folder.id == props.folder.id) return; + + emit('removeFolder', folder.id); + os.api('drive/folders/update', { + folderId: folder.id, + parentId: props.folder.id + }).then(() => { + // noop + }).catch(err => { + switch (err) { + case 'detected-circular-definition': + os.alert({ + title: i18n.locale.unableToProcess, + text: i18n.locale.circularReferenceFolder + }); + break; + default: + os.alert({ + type: 'error', + text: i18n.locale.somethingHappened + }); } - //#endregion - }, + }); + } + //#endregion +} - onDragstart(e) { - e.dataTransfer.effectAllowed = 'move'; - e.dataTransfer.setData(_DATA_TRANSFER_DRIVE_FOLDER_, JSON.stringify(this.folder)); - this.isDragging = true; +function onDragstart(ev: DragEvent) { + if (!ev.dataTransfer) return; - // 親ブラウザに対して、ドラッグが開始されたフラグを立てる - // (=あなたの子供が、ドラッグを開始しましたよ) - this.browser.isDragSource = true; - }, + ev.dataTransfer.effectAllowed = 'move'; + ev.dataTransfer.setData(_DATA_TRANSFER_DRIVE_FOLDER_, JSON.stringify(props.folder)); + isDragging.value = true; - onDragend() { - this.isDragging = false; - this.browser.isDragSource = false; - }, + // 親ブラウザに対して、ドラッグが開始されたフラグを立てる + // (=あなたの子供が、ドラッグを開始しましたよ) + emit('dragstart'); +} - go() { - this.browser.move(this.folder.id); - }, +function onDragend() { + isDragging.value = false; + emit('dragend'); +} - newWindow() { - this.browser.newWindow(this.folder); - }, +function go() { + emit('move', props.folder.id); +} - rename() { - os.inputText({ - title: this.$ts.renameFolder, - placeholder: this.$ts.inputNewFolderName, - default: this.folder.name - }).then(({ canceled, result: name }) => { - if (canceled) return; - os.api('drive/folders/update', { - folderId: this.folder.id, - name: name - }); - }); - }, +function rename() { + os.inputText({ + title: i18n.locale.renameFolder, + placeholder: i18n.locale.inputNewFolderName, + default: props.folder.name + }).then(({ canceled, result: name }) => { + if (canceled) return; + os.api('drive/folders/update', { + folderId: props.folder.id, + name: name + }); + }); +} - deleteFolder() { - os.api('drive/folders/delete', { - folderId: this.folder.id - }).then(() => { - if (this.$store.state.uploadFolder === this.folder.id) { - this.$store.set('uploadFolder', null); - } - }).catch(err => { - switch(err.id) { - case 'b0fc8a17-963c-405d-bfbc-859a487295e1': - os.alert({ - type: 'error', - title: this.$ts.unableToDelete, - text: this.$ts.hasChildFilesOrFolders - }); - break; - default: - os.alert({ - type: 'error', - text: this.$ts.unableToDelete - }); - } - }); - }, +function deleteFolder() { + os.api('drive/folders/delete', { + folderId: props.folder.id + }).then(() => { + if (defaultStore.state.uploadFolder === props.folder.id) { + defaultStore.set('uploadFolder', null); + } + }).catch(err => { + switch(err.id) { + case 'b0fc8a17-963c-405d-bfbc-859a487295e1': + os.alert({ + type: 'error', + title: i18n.locale.unableToDelete, + text: i18n.locale.hasChildFilesOrFolders + }); + break; + default: + os.alert({ + type: 'error', + text: i18n.locale.unableToDelete + }); + } + }); +} - setAsUploadFolder() { - this.$store.set('uploadFolder', this.folder.id); - }, +function setAsUploadFolder() { + defaultStore.set('uploadFolder', props.folder.id); +} - onContextmenu(e) { - os.contextMenu([{ - text: this.$ts.openInWindow, - icon: 'fas fa-window-restore', - action: () => { - os.popup(import('./drive-window.vue'), { - initialFolder: this.folder - }, { - }, 'closed'); - } - }, null, { - text: this.$ts.rename, - icon: 'fas fa-i-cursor', - action: this.rename - }, null, { - text: this.$ts.delete, - icon: 'fas fa-trash-alt', - danger: true, - action: this.deleteFolder - }], e); - }, - } -}); +function onContextmenu(ev: MouseEvent) { + os.contextMenu([{ + text: i18n.locale.openInWindow, + icon: 'fas fa-window-restore', + action: () => { + os.popup(import('./drive-window.vue'), { + initialFolder: props.folder + }, { + }, 'closed'); + } + }, null, { + text: i18n.locale.rename, + icon: 'fas fa-i-cursor', + action: rename, + }, null, { + text: i18n.locale.delete, + icon: 'fas fa-trash-alt', + danger: true, + action: deleteFolder, + }], ev); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/drive.nav-folder.vue b/packages/client/src/components/drive.nav-folder.vue index 4f0e6ce0e9..7c35c5d3da 100644 --- a/packages/client/src/components/drive.nav-folder.vue +++ b/packages/client/src/components/drive.nav-folder.vue @@ -8,114 +8,111 @@ @drop.stop="onDrop" > <i v-if="folder == null" class="fas fa-cloud"></i> - <span>{{ folder == null ? $ts.drive : folder.name }}</span> + <span>{{ folder == null ? i18n.locale.drive : folder.name }}</span> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { ref } from 'vue'; +import * as Misskey from 'misskey-js'; import * as os from '@/os'; +import { i18n } from '@/i18n'; -export default defineComponent({ - props: { - folder: { - type: Object, - required: false, - } - }, +const props = defineProps<{ + folder?: Misskey.entities.DriveFolder; + parentFolder: Misskey.entities.DriveFolder | null; +}>(); - data() { - return { - hover: false, - draghover: false, - }; - }, +const emit = defineEmits<{ + (e: 'move', v?: Misskey.entities.DriveFolder): void; + (e: 'upload', file: File, folder?: Misskey.entities.DriveFolder | null): void; + (e: 'removeFile', v: Misskey.entities.DriveFile['id']): void; + (e: 'removeFolder', v: Misskey.entities.DriveFolder['id']): void; +}>(); - computed: { - browser(): any { - return this.$parent; - } - }, +const hover = ref(false); +const draghover = ref(false); - methods: { - onClick() { - this.browser.move(this.folder); - }, +function onClick() { + emit('move', props.folder); +} - onMouseover() { - this.hover = true; - }, +function onMouseover() { + hover.value = true; +} - onMouseout() { - this.hover = false; - }, +function onMouseout() { + hover.value = false; +} - onDragover(e) { - // このフォルダがルートかつカレントディレクトリならドロップ禁止 - if (this.folder == null && this.browser.folder == null) { - e.dataTransfer.dropEffect = 'none'; - } +function onDragover(e: DragEvent) { + if (!e.dataTransfer) return; - const isFile = e.dataTransfer.items[0].kind == 'file'; - const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; - const isDriveFolder = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FOLDER_; + // このフォルダがルートかつカレントディレクトリならドロップ禁止 + if (props.folder == null && props.parentFolder == null) { + e.dataTransfer.dropEffect = 'none'; + } - if (isFile || isDriveFile || isDriveFolder) { - e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; - } else { - e.dataTransfer.dropEffect = 'none'; - } + const isFile = e.dataTransfer.items[0].kind == 'file'; + const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; + const isDriveFolder = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FOLDER_; - return false; - }, + if (isFile || isDriveFile || isDriveFolder) { + e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; + } else { + e.dataTransfer.dropEffect = 'none'; + } - onDragenter() { - if (this.folder || this.browser.folder) this.draghover = true; - }, + return false; +} - onDragleave() { - if (this.folder || this.browser.folder) this.draghover = false; - }, +function onDragenter() { + if (props.folder || props.parentFolder) draghover.value = true; +} - onDrop(e) { - this.draghover = false; +function onDragleave() { + if (props.folder || props.parentFolder) draghover.value = false; +} - // ファイルだったら - if (e.dataTransfer.files.length > 0) { - for (const file of Array.from(e.dataTransfer.files)) { - this.browser.upload(file, this.folder); - } - return; - } +function onDrop(e: DragEvent) { + draghover.value = false; - //#region ドライブのファイル - const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); - if (driveFile != null && driveFile != '') { - const file = JSON.parse(driveFile); - this.browser.removeFile(file.id); - os.api('drive/files/update', { - fileId: file.id, - folderId: this.folder ? this.folder.id : null - }); - } - //#endregion + if (!e.dataTransfer) return; - //#region ドライブのフォルダ - const driveFolder = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_); - if (driveFolder != null && driveFolder != '') { - const folder = JSON.parse(driveFolder); - // 移動先が自分自身ならreject - if (this.folder && folder.id == this.folder.id) return; - this.browser.removeFolder(folder.id); - os.api('drive/folders/update', { - folderId: folder.id, - parentId: this.folder ? this.folder.id : null - }); - } - //#endregion + // ファイルだったら + if (e.dataTransfer.files.length > 0) { + for (const file of Array.from(e.dataTransfer.files)) { + emit('upload', file, props.folder); } + return; + } + + //#region ドライブのファイル + const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); + if (driveFile != null && driveFile != '') { + const file = JSON.parse(driveFile); + emit('removeFile', file.id); + os.api('drive/files/update', { + fileId: file.id, + folderId: props.folder ? props.folder.id : null + }); } -}); + //#endregion + + //#region ドライブのフォルダ + const driveFolder = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_); + if (driveFolder != null && driveFolder != '') { + const folder = JSON.parse(driveFolder); + // 移動先が自分自身ならreject + if (props.folder && folder.id == props.folder.id) return; + emit('removeFolder', folder.id); + os.api('drive/folders/update', { + folderId: folder.id, + parentId: props.folder ? props.folder.id : null + }); + } + //#endregion +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/drive.vue b/packages/client/src/components/drive.vue index 46bcd42558..e27b0a5fbb 100644 --- a/packages/client/src/components/drive.vue +++ b/packages/client/src/components/drive.vue @@ -2,10 +2,24 @@ <div class="yfudmmck"> <nav> <div class="path" @contextmenu.prevent.stop="() => {}"> - <XNavFolder :class="{ current: folder == null }"/> + <XNavFolder + :class="{ current: folder == null }" + :parent-folder="folder" + @move="move" + @upload="upload" + @removeFile="removeFile" + @removeFolder="removeFolder" + /> <template v-for="f in hierarchyFolders"> <span class="separator"><i class="fas fa-angle-right"></i></span> - <XNavFolder :folder="f"/> + <XNavFolder + :folder="f" + :parent-folder="folder" + @move="move" + @upload="upload" + @removeFile="removeFile" + @removeFolder="removeFolder" + /> </template> <span v-if="folder != null" class="separator"><i class="fas fa-angle-right"></i></span> <span v-if="folder != null" class="folder current">{{ folder.name }}</span> @@ -22,616 +36,601 @@ > <div ref="contents" class="contents"> <div v-show="folders.length > 0" ref="foldersContainer" class="folders"> - <XFolder v-for="(f, i) in folders" :key="f.id" v-anim="i" class="folder" :folder="f" :select-mode="select === 'folder'" :is-selected="selectedFolders.some(x => x.id === f.id)" @chosen="chooseFolder"/> + <XFolder + v-for="(f, i) in folders" + :key="f.id" + v-anim="i" + class="folder" + :folder="f" + :select-mode="select === 'folder'" + :is-selected="selectedFolders.some(x => x.id === f.id)" + @chosen="chooseFolder" + @move="move" + @upload="upload" + @removeFile="removeFile" + @removeFolder="removeFolder" + @dragstart="isDragSource = true" + @dragend="isDragSource = false" + /> <!-- SEE: https://stackoverflow.com/questions/18744164/flex-box-align-last-row-to-grid --> <div v-for="(n, i) in 16" :key="i" class="padding"></div> - <MkButton v-if="moreFolders" ref="moreFolders">{{ $ts.loadMore }}</MkButton> + <MkButton v-if="moreFolders" ref="moreFolders">{{ i18n.locale.loadMore }}</MkButton> </div> <div v-show="files.length > 0" ref="filesContainer" class="files"> - <XFile v-for="(file, i) in files" :key="file.id" v-anim="i" class="file" :file="file" :select-mode="select === 'file'" :is-selected="selectedFiles.some(x => x.id === file.id)" @chosen="chooseFile"/> + <XFile + v-for="(file, i) in files" + :key="file.id" + v-anim="i" + class="file" + :file="file" + :select-mode="select === 'file'" + :is-selected="selectedFiles.some(x => x.id === file.id)" + @chosen="chooseFile" + @dragstart="isDragSource = true" + @dragend="isDragSource = false" + /> <!-- SEE: https://stackoverflow.com/questions/18744164/flex-box-align-last-row-to-grid --> <div v-for="(n, i) in 16" :key="i" class="padding"></div> - <MkButton v-show="moreFiles" ref="loadMoreFiles" @click="fetchMoreFiles">{{ $ts.loadMore }}</MkButton> + <MkButton v-show="moreFiles" ref="loadMoreFiles" @click="fetchMoreFiles">{{ i18n.locale.loadMore }}</MkButton> </div> <div v-if="files.length == 0 && folders.length == 0 && !fetching" class="empty"> - <p v-if="draghover">{{ $t('empty-draghover') }}</p> - <p v-if="!draghover && folder == null"><strong>{{ $ts.emptyDrive }}</strong><br/>{{ $t('empty-drive-description') }}</p> - <p v-if="!draghover && folder != null">{{ $ts.emptyFolder }}</p> + <p v-if="draghover">{{ i18n.t('empty-draghover') }}</p> + <p v-if="!draghover && folder == null"><strong>{{ i18n.locale.emptyDrive }}</strong><br/>{{ i18n.t('empty-drive-description') }}</p> + <p v-if="!draghover && folder != null">{{ i18n.locale.emptyFolder }}</p> </div> </div> <MkLoading v-if="fetching"/> </div> <div v-if="draghover" class="dropzone"></div> - <input ref="fileInput" type="file" accept="*/*" multiple="multiple" tabindex="-1" @change="onChangeFileInput"/> + <input ref="fileInput" type="file" accept="*/*" multiple tabindex="-1" @change="onChangeFileInput"/> </div> </template> -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; +<script lang="ts" setup> +import { markRaw, nextTick, onActivated, onBeforeUnmount, onMounted, ref, watch } from 'vue'; +import * as Misskey from 'misskey-js'; import XNavFolder from './drive.nav-folder.vue'; import XFolder from './drive.folder.vue'; import XFile from './drive.file.vue'; import MkButton from './ui/button.vue'; import * as os from '@/os'; +import { stream } from '@/stream'; +import { defaultStore } from '@/store'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XNavFolder, - XFolder, - XFile, - MkButton, - }, - - props: { - initialFolder: { - type: Object, - required: false - }, - type: { - type: String, - required: false, - default: undefined - }, - multiple: { - type: Boolean, - required: false, - default: false - }, - select: { - type: String, - required: false, - default: null - } - }, - - emits: ['selected', 'change-selection', 'move-root', 'cd', 'open-folder'], - - data() { - return { - /** - * 現在の階層(フォルダ) - * * null でルートを表す - */ - folder: null, +const props = withDefaults(defineProps<{ + initialFolder?: Misskey.entities.DriveFolder; + type?: string; + multiple?: boolean; + select?: 'file' | 'folder' | null; +}>(), { + multiple: false, + select: null, +}); - files: [], - folders: [], - moreFiles: false, - moreFolders: false, - hierarchyFolders: [], - selectedFiles: [], - selectedFolders: [], - uploadings: os.uploads, - connection: null, +const emit = defineEmits<{ + (e: 'selected', v: Misskey.entities.DriveFile | Misskey.entities.DriveFolder): void; + (e: 'change-selection', v: Misskey.entities.DriveFile[] | Misskey.entities.DriveFolder[]): void; + (e: 'move-root'): void; + (e: 'cd', v: Misskey.entities.DriveFolder | null): void; + (e: 'open-folder', v: Misskey.entities.DriveFolder): void; +}>(); - /** - * ドロップされようとしているか - */ - draghover: false, +const loadMoreFiles = ref<InstanceType<typeof MkButton>>(); +const fileInput = ref<HTMLInputElement>(); - /** - * 自信の所有するアイテムがドラッグをスタートさせたか - * (自分自身の階層にドロップできないようにするためのフラグ) - */ - isDragSource: false, +const folder = ref<Misskey.entities.DriveFolder | null>(null); +const files = ref<Misskey.entities.DriveFile[]>([]); +const folders = ref<Misskey.entities.DriveFolder[]>([]); +const moreFiles = ref(false); +const moreFolders = ref(false); +const hierarchyFolders = ref<Misskey.entities.DriveFolder[]>([]); +const selectedFiles = ref<Misskey.entities.DriveFile[]>([]); +const selectedFolders = ref<Misskey.entities.DriveFolder[]>([]); +const uploadings = os.uploads; +const connection = stream.useChannel('drive'); - fetching: true, +// ドロップされようとしているか +const draghover = ref(false); - ilFilesObserver: new IntersectionObserver( - (entries) => entries.some((entry) => entry.isIntersecting) - && !this.fetching && this.moreFiles && - this.fetchMoreFiles() - ), - moreFilesElement: null as Element, +// 自身の所有するアイテムがドラッグをスタートさせたか +// (自分自身の階層にドロップできないようにするためのフラグ) +const isDragSource = ref(false); - }; - }, +const fetching = ref(true); - watch: { - folder() { - this.$emit('cd', this.folder); - } - }, +const ilFilesObserver = new IntersectionObserver( + (entries) => entries.some((entry) => entry.isIntersecting) && !fetching.value && moreFiles.value && fetchMoreFiles() +) - mounted() { - if (this.$store.state.enableInfiniteScroll && this.$refs.loadMoreFiles) { - this.$nextTick(() => { - this.ilFilesObserver.observe((this.$refs.loadMoreFiles as Vue).$el) - }); - } +watch(folder, () => emit('cd', folder.value)); - this.connection = markRaw(os.stream.useChannel('drive')); - - this.connection.on('fileCreated', this.onStreamDriveFileCreated); - this.connection.on('fileUpdated', this.onStreamDriveFileUpdated); - this.connection.on('fileDeleted', this.onStreamDriveFileDeleted); - this.connection.on('folderCreated', this.onStreamDriveFolderCreated); - this.connection.on('folderUpdated', this.onStreamDriveFolderUpdated); - this.connection.on('folderDeleted', this.onStreamDriveFolderDeleted); +function onStreamDriveFileCreated(file: Misskey.entities.DriveFile) { + addFile(file, true); +} - if (this.initialFolder) { - this.move(this.initialFolder); - } else { - this.fetch(); - } - }, +function onStreamDriveFileUpdated(file: Misskey.entities.DriveFile) { + const current = folder.value ? folder.value.id : null; + if (current != file.folderId) { + removeFile(file); + } else { + addFile(file, true); + } +} - activated() { - if (this.$store.state.enableInfiniteScroll) { - this.$nextTick(() => { - this.ilFilesObserver.observe((this.$refs.loadMoreFiles as Vue).$el) - }); - } - }, +function onStreamDriveFileDeleted(fileId: string) { + removeFile(fileId); +} - beforeUnmount() { - this.connection.dispose(); - this.ilFilesObserver.disconnect(); - }, +function onStreamDriveFolderCreated(createdFolder: Misskey.entities.DriveFolder) { + addFolder(createdFolder, true); +} - methods: { - onStreamDriveFileCreated(file) { - this.addFile(file, true); - }, +function onStreamDriveFolderUpdated(updatedFolder: Misskey.entities.DriveFolder) { + const current = folder.value ? folder.value.id : null; + if (current != updatedFolder.parentId) { + removeFolder(updatedFolder); + } else { + addFolder(updatedFolder, true); + } +} - onStreamDriveFileUpdated(file) { - const current = this.folder ? this.folder.id : null; - if (current != file.folderId) { - this.removeFile(file); - } else { - this.addFile(file, true); - } - }, +function onStreamDriveFolderDeleted(folderId: string) { + removeFolder(folderId); +} - onStreamDriveFileDeleted(fileId) { - this.removeFile(fileId); - }, +function onDragover(e: DragEvent): any { + if (!e.dataTransfer) return; - onStreamDriveFolderCreated(folder) { - this.addFolder(folder, true); - }, + // ドラッグ元が自分自身の所有するアイテムだったら + if (isDragSource.value) { + // 自分自身にはドロップさせない + e.dataTransfer.dropEffect = 'none'; + return; + } - onStreamDriveFolderUpdated(folder) { - const current = this.folder ? this.folder.id : null; - if (current != folder.parentId) { - this.removeFolder(folder); - } else { - this.addFolder(folder, true); - } - }, + const isFile = e.dataTransfer.items[0].kind == 'file'; + const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; + const isDriveFolder = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FOLDER_; + if (isFile || isDriveFile || isDriveFolder) { + e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; + } else { + e.dataTransfer.dropEffect = 'none'; + } - onStreamDriveFolderDeleted(folderId) { - this.removeFolder(folderId); - }, + return false; +} - onDragover(e): any { - // ドラッグ元が自分自身の所有するアイテムだったら - if (this.isDragSource) { - // 自分自身にはドロップさせない - e.dataTransfer.dropEffect = 'none'; - return; - } +function onDragenter() { + if (!isDragSource.value) draghover.value = true; +} - const isFile = e.dataTransfer.items[0].kind == 'file'; - const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; - const isDriveFolder = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FOLDER_; +function onDragleave() { + draghover.value = false; +} - if (isFile || isDriveFile || isDriveFolder) { - e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; - } else { - e.dataTransfer.dropEffect = 'none'; - } +function onDrop(e: DragEvent): any { + draghover.value = false; - return false; - }, + if (!e.dataTransfer) return; - onDragenter(e) { - if (!this.isDragSource) this.draghover = true; - }, + // ドロップされてきたものがファイルだったら + if (e.dataTransfer.files.length > 0) { + for (const file of Array.from(e.dataTransfer.files)) { + upload(file, folder.value); + } + return; + } - onDragleave(e) { - this.draghover = false; - }, + //#region ドライブのファイル + const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); + if (driveFile != null && driveFile != '') { + const file = JSON.parse(driveFile); + if (files.value.some(f => f.id == file.id)) return; + removeFile(file.id); + os.api('drive/files/update', { + fileId: file.id, + folderId: folder.value ? folder.value.id : null + }); + } + //#endregion - onDrop(e): any { - this.draghover = false; + //#region ドライブのフォルダ + const driveFolder = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_); + if (driveFolder != null && driveFolder != '') { + const droppedFolder = JSON.parse(driveFolder); - // ドロップされてきたものがファイルだったら - if (e.dataTransfer.files.length > 0) { - for (const file of Array.from(e.dataTransfer.files)) { - this.upload(file, this.folder); - } - return; + // 移動先が自分自身ならreject + if (folder.value && droppedFolder.id == folder.value.id) return false; + if (folders.value.some(f => f.id == droppedFolder.id)) return false; + removeFolder(droppedFolder.id); + os.api('drive/folders/update', { + folderId: droppedFolder.id, + parentId: folder.value ? folder.value.id : null + }).then(() => { + // noop + }).catch(err => { + switch (err) { + case 'detected-circular-definition': + os.alert({ + title: i18n.locale.unableToProcess, + text: i18n.locale.circularReferenceFolder + }); + break; + default: + os.alert({ + type: 'error', + text: i18n.locale.somethingHappened + }); } + }); + } + //#endregion +} - //#region ドライブのファイル - const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); - if (driveFile != null && driveFile != '') { - const file = JSON.parse(driveFile); - if (this.files.some(f => f.id == file.id)) return; - this.removeFile(file.id); - os.api('drive/files/update', { - fileId: file.id, - folderId: this.folder ? this.folder.id : null - }); - } - //#endregion +function selectLocalFile() { + fileInput.value?.click(); +} - //#region ドライブのフォルダ - const driveFolder = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_); - if (driveFolder != null && driveFolder != '') { - const folder = JSON.parse(driveFolder); +function urlUpload() { + os.inputText({ + title: i18n.locale.uploadFromUrl, + type: 'url', + placeholder: i18n.locale.uploadFromUrlDescription + }).then(({ canceled, result: url }) => { + if (canceled || !url) return; + os.api('drive/files/upload-from-url', { + url: url, + folderId: folder.value ? folder.value.id : undefined + }); - // 移動先が自分自身ならreject - if (this.folder && folder.id == this.folder.id) return false; - if (this.folders.some(f => f.id == folder.id)) return false; - this.removeFolder(folder.id); - os.api('drive/folders/update', { - folderId: folder.id, - parentId: this.folder ? this.folder.id : null - }).then(() => { - // noop - }).catch(err => { - switch (err) { - case 'detected-circular-definition': - os.alert({ - title: this.$ts.unableToProcess, - text: this.$ts.circularReferenceFolder - }); - break; - default: - os.alert({ - type: 'error', - text: this.$ts.somethingHappened - }); - } - }); - } - //#endregion - }, + os.alert({ + title: i18n.locale.uploadFromUrlRequested, + text: i18n.locale.uploadFromUrlMayTakeTime + }); + }); +} - selectLocalFile() { - (this.$refs.fileInput as any).click(); - }, +function createFolder() { + os.inputText({ + title: i18n.locale.createFolder, + placeholder: i18n.locale.folderName + }).then(({ canceled, result: name }) => { + if (canceled) return; + os.api('drive/folders/create', { + name: name, + parentId: folder.value ? folder.value.id : undefined + }).then(createdFolder => { + addFolder(createdFolder, true); + }); + }); +} - urlUpload() { - os.inputText({ - title: this.$ts.uploadFromUrl, - type: 'url', - placeholder: this.$ts.uploadFromUrlDescription - }).then(({ canceled, result: url }) => { - if (canceled) return; - os.api('drive/files/upload-from-url', { - url: url, - folderId: this.folder ? this.folder.id : undefined - }); +function renameFolder(folderToRename: Misskey.entities.DriveFolder) { + os.inputText({ + title: i18n.locale.renameFolder, + placeholder: i18n.locale.inputNewFolderName, + default: folderToRename.name + }).then(({ canceled, result: name }) => { + if (canceled) return; + os.api('drive/folders/update', { + folderId: folderToRename.id, + name: name + }).then(updatedFolder => { + // FIXME: 画面を更新するために自分自身に移動 + move(updatedFolder); + }); + }); +} +function deleteFolder(folderToDelete: Misskey.entities.DriveFolder) { + os.api('drive/folders/delete', { + folderId: folderToDelete.id + }).then(() => { + // 削除時に親フォルダに移動 + move(folderToDelete.parentId); + }).catch(err => { + switch(err.id) { + case 'b0fc8a17-963c-405d-bfbc-859a487295e1': os.alert({ - title: this.$ts.uploadFromUrlRequested, - text: this.$ts.uploadFromUrlMayTakeTime + type: 'error', + title: i18n.locale.unableToDelete, + text: i18n.locale.hasChildFilesOrFolders }); - }); - }, - - createFolder() { - os.inputText({ - title: this.$ts.createFolder, - placeholder: this.$ts.folderName - }).then(({ canceled, result: name }) => { - if (canceled) return; - os.api('drive/folders/create', { - name: name, - parentId: this.folder ? this.folder.id : undefined - }).then(folder => { - this.addFolder(folder, true); + break; + default: + os.alert({ + type: 'error', + text: i18n.locale.unableToDelete }); - }); - }, + } + }); +} - renameFolder(folder) { - os.inputText({ - title: this.$ts.renameFolder, - placeholder: this.$ts.inputNewFolderName, - default: folder.name - }).then(({ canceled, result: name }) => { - if (canceled) return; - os.api('drive/folders/update', { - folderId: folder.id, - name: name - }).then(folder => { - // FIXME: 画面を更新するために自分自身に移動 - this.move(folder); - }); - }); - }, +function onChangeFileInput() { + if (!fileInput.value?.files) return; + for (const file of Array.from(fileInput.value.files)) { + upload(file, folder.value); + } +} - deleteFolder(folder) { - os.api('drive/folders/delete', { - folderId: folder.id - }).then(() => { - // 削除時に親フォルダに移動 - this.move(folder.parentId); - }).catch(err => { - switch(err.id) { - case 'b0fc8a17-963c-405d-bfbc-859a487295e1': - os.alert({ - type: 'error', - title: this.$ts.unableToDelete, - text: this.$ts.hasChildFilesOrFolders - }); - break; - default: - os.alert({ - type: 'error', - text: this.$ts.unableToDelete - }); - } - }); - }, +function upload(file: File, folderToUpload?: Misskey.entities.DriveFolder | null) { + os.upload(file, (folderToUpload && typeof folderToUpload == 'object') ? folderToUpload.id : null).then(res => { + addFile(res, true); + }); +} - onChangeFileInput() { - for (const file of Array.from((this.$refs.fileInput as any).files)) { - this.upload(file, this.folder); - } - }, +function chooseFile(file: Misskey.entities.DriveFile) { + const isAlreadySelected = selectedFiles.value.some(f => f.id == file.id); + if (props.multiple) { + if (isAlreadySelected) { + selectedFiles.value = selectedFiles.value.filter(f => f.id != file.id); + } else { + selectedFiles.value.push(file); + } + emit('change-selection', selectedFiles.value); + } else { + if (isAlreadySelected) { + emit('selected', file); + } else { + selectedFiles.value = [file]; + emit('change-selection', [file]); + } + } +} - upload(file, folder) { - if (folder && typeof folder == 'object') folder = folder.id; - os.upload(file, folder).then(res => { - this.addFile(res, true); - }); - }, +function chooseFolder(folderToChoose: Misskey.entities.DriveFolder) { + const isAlreadySelected = selectedFolders.value.some(f => f.id == folderToChoose.id); + if (props.multiple) { + if (isAlreadySelected) { + selectedFolders.value = selectedFolders.value.filter(f => f.id != folderToChoose.id); + } else { + selectedFolders.value.push(folderToChoose); + } + emit('change-selection', selectedFolders.value); + } else { + if (isAlreadySelected) { + emit('selected', folderToChoose); + } else { + selectedFolders.value = [folderToChoose]; + emit('change-selection', [folderToChoose]); + } + } +} - chooseFile(file) { - const isAlreadySelected = this.selectedFiles.some(f => f.id == file.id); - if (this.multiple) { - if (isAlreadySelected) { - this.selectedFiles = this.selectedFiles.filter(f => f.id != file.id); - } else { - this.selectedFiles.push(file); - } - this.$emit('change-selection', this.selectedFiles); - } else { - if (isAlreadySelected) { - this.$emit('selected', file); - } else { - this.selectedFiles = [file]; - this.$emit('change-selection', [file]); - } - } - }, +function move(target?: Misskey.entities.DriveFolder) { + if (!target) { + goRoot(); + return; + } else if (typeof target == 'object') { + target = target.id; + } - chooseFolder(folder) { - const isAlreadySelected = this.selectedFolders.some(f => f.id == folder.id); - if (this.multiple) { - if (isAlreadySelected) { - this.selectedFolders = this.selectedFolders.filter(f => f.id != folder.id); - } else { - this.selectedFolders.push(folder); - } - this.$emit('change-selection', this.selectedFolders); - } else { - if (isAlreadySelected) { - this.$emit('selected', folder); - } else { - this.selectedFolders = [folder]; - this.$emit('change-selection', [folder]); - } - } - }, + fetching.value = true; - move(target) { - if (target == null) { - this.goRoot(); - return; - } else if (typeof target == 'object') { - target = target.id; - } + os.api('drive/folders/show', { + folderId: target + }).then(folderToMove => { + folder.value = folderToMove; + hierarchyFolders.value = []; - this.fetching = true; + const dive = folderToDive => { + hierarchyFolders.value.unshift(folderToDive); + if (folderToDive.parent) dive(folderToDive.parent); + }; + + if (folderToMove.parent) dive(folderToMove.parent); - os.api('drive/folders/show', { - folderId: target - }).then(folder => { - this.folder = folder; - this.hierarchyFolders = []; + emit('open-folder', folderToMove); + fetch(); + }); +} - const dive = folder => { - this.hierarchyFolders.unshift(folder); - if (folder.parent) dive(folder.parent); - }; +function addFolder(folderToAdd: Misskey.entities.DriveFolder, unshift = false) { + const current = folder.value ? folder.value.id : null; + if (current != folderToAdd.parentId) return; - if (folder.parent) dive(folder.parent); + if (folders.value.some(f => f.id == folderToAdd.id)) { + const exist = folders.value.map(f => f.id).indexOf(folderToAdd.id); + folders.value[exist] = folderToAdd; + return; + } - this.$emit('open-folder', folder); - this.fetch(); - }); - }, + if (unshift) { + folders.value.unshift(folderToAdd); + } else { + folders.value.push(folderToAdd); + } +} - addFolder(folder, unshift = false) { - const current = this.folder ? this.folder.id : null; - if (current != folder.parentId) return; +function addFile(fileToAdd: Misskey.entities.DriveFile, unshift = false) { + const current = folder.value ? folder.value.id : null; + if (current != fileToAdd.folderId) return; - if (this.folders.some(f => f.id == folder.id)) { - const exist = this.folders.map(f => f.id).indexOf(folder.id); - this.folders[exist] = folder; - return; - } + if (files.value.some(f => f.id == fileToAdd.id)) { + const exist = files.value.map(f => f.id).indexOf(fileToAdd.id); + files.value[exist] = fileToAdd; + return; + } - if (unshift) { - this.folders.unshift(folder); - } else { - this.folders.push(folder); - } - }, + if (unshift) { + files.value.unshift(fileToAdd); + } else { + files.value.push(fileToAdd); + } +} - addFile(file, unshift = false) { - const current = this.folder ? this.folder.id : null; - if (current != file.folderId) return; +function removeFolder(folderToRemove: Misskey.entities.DriveFolder | string) { + const folderIdToRemove = typeof folderToRemove === 'object' ? folderToRemove.id : folderToRemove; + folders.value = folders.value.filter(f => f.id != folderIdToRemove); +} - if (this.files.some(f => f.id == file.id)) { - const exist = this.files.map(f => f.id).indexOf(file.id); - this.files[exist] = file; - return; - } +function removeFile(file: Misskey.entities.DriveFile | string) { + const fileId = typeof file === 'object' ? file.id : file; + files.value = files.value.filter(f => f.id != fileId); +} - if (unshift) { - this.files.unshift(file); - } else { - this.files.push(file); - } - }, +function appendFile(file: Misskey.entities.DriveFile) { + addFile(file); +} - removeFolder(folder) { - if (typeof folder == 'object') folder = folder.id; - this.folders = this.folders.filter(f => f.id != folder); - }, +function appendFolder(folderToAppend: Misskey.entities.DriveFolder) { + addFolder(folderToAppend); +} +/* +function prependFile(file: Misskey.entities.DriveFile) { + addFile(file, true); +} - removeFile(file) { - if (typeof file == 'object') file = file.id; - this.files = this.files.filter(f => f.id != file); - }, +function prependFolder(folderToPrepend: Misskey.entities.DriveFolder) { + addFolder(folderToPrepend, true); +} +*/ +function goRoot() { + // 既にrootにいるなら何もしない + if (folder.value == null) return; - appendFile(file) { - this.addFile(file); - }, + folder.value = null; + hierarchyFolders.value = []; + emit('move-root'); + fetch(); +} - appendFolder(folder) { - this.addFolder(folder); - }, +async function fetch() { + folders.value = []; + files.value = []; + moreFolders.value = false; + moreFiles.value = false; + fetching.value = true; - prependFile(file) { - this.addFile(file, true); - }, + const foldersMax = 30; + const filesMax = 30; - prependFolder(folder) { - this.addFolder(folder, true); - }, + const foldersPromise = os.api('drive/folders', { + folderId: folder.value ? folder.value.id : null, + limit: foldersMax + 1 + }).then(fetchedFolders => { + if (fetchedFolders.length == foldersMax + 1) { + moreFolders.value = true; + fetchedFolders.pop(); + } + return fetchedFolders; + }); - goRoot() { - // 既にrootにいるなら何もしない - if (this.folder == null) return; + const filesPromise = os.api('drive/files', { + folderId: folder.value ? folder.value.id : null, + type: props.type, + limit: filesMax + 1 + }).then(fetchedFiles => { + if (fetchedFiles.length == filesMax + 1) { + moreFiles.value = true; + fetchedFiles.pop(); + } + return fetchedFiles; + }); - this.folder = null; - this.hierarchyFolders = []; - this.$emit('move-root'); - this.fetch(); - }, + const [fetchedFolders, fetchedFiles] = await Promise.all([foldersPromise, filesPromise]); - fetch() { - this.folders = []; - this.files = []; - this.moreFolders = false; - this.moreFiles = false; - this.fetching = true; + for (const x of fetchedFolders) appendFolder(x); + for (const x of fetchedFiles) appendFile(x); - let fetchedFolders = null; - let fetchedFiles = null; + fetching.value = false; +} - const foldersMax = 30; - const filesMax = 30; +function fetchMoreFiles() { + fetching.value = true; - // フォルダ一覧取得 - os.api('drive/folders', { - folderId: this.folder ? this.folder.id : null, - limit: foldersMax + 1 - }).then(folders => { - if (folders.length == foldersMax + 1) { - this.moreFolders = true; - folders.pop(); - } - fetchedFolders = folders; - complete(); - }); + const max = 30; - // ファイル一覧取得 - os.api('drive/files', { - folderId: this.folder ? this.folder.id : null, - type: this.type, - limit: filesMax + 1 - }).then(files => { - if (files.length == filesMax + 1) { - this.moreFiles = true; - files.pop(); - } - fetchedFiles = files; - complete(); - }); + // ファイル一覧取得 + os.api('drive/files', { + folderId: folder.value ? folder.value.id : null, + type: props.type, + untilId: files.value[files.value.length - 1].id, + limit: max + 1 + }).then(files => { + if (files.length == max + 1) { + moreFiles.value = true; + files.pop(); + } else { + moreFiles.value = false; + } + for (const x of files) appendFile(x); + fetching.value = false; + }); +} - let flag = false; - const complete = () => { - if (flag) { - for (const x of fetchedFolders) this.appendFolder(x); - for (const x of fetchedFiles) this.appendFile(x); - this.fetching = false; - } else { - flag = true; - } - }; - }, +function getMenu() { + return [{ + text: i18n.locale.addFile, + type: 'label' + }, { + text: i18n.locale.upload, + icon: 'fas fa-upload', + action: () => { selectLocalFile(); } + }, { + text: i18n.locale.fromUrl, + icon: 'fas fa-link', + action: () => { urlUpload(); } + }, null, { + text: folder.value ? folder.value.name : i18n.locale.drive, + type: 'label' + }, folder.value ? { + text: i18n.locale.renameFolder, + icon: 'fas fa-i-cursor', + action: () => { renameFolder(folder.value); } + } : undefined, folder.value ? { + text: i18n.locale.deleteFolder, + icon: 'fas fa-trash-alt', + action: () => { deleteFolder(folder.value as Misskey.entities.DriveFolder); } + } : undefined, { + text: i18n.locale.createFolder, + icon: 'fas fa-folder-plus', + action: () => { createFolder(); } + }]; +} - fetchMoreFiles() { - this.fetching = true; +function showMenu(ev: MouseEvent) { + os.popupMenu(getMenu(), (ev.currentTarget || ev.target || undefined) as HTMLElement | undefined); +} - const max = 30; +function onContextmenu(ev: MouseEvent) { + os.contextMenu(getMenu(), ev); +} - // ファイル一覧取得 - os.api('drive/files', { - folderId: this.folder ? this.folder.id : null, - type: this.type, - untilId: this.files[this.files.length - 1].id, - limit: max + 1 - }).then(files => { - if (files.length == max + 1) { - this.moreFiles = true; - files.pop(); - } else { - this.moreFiles = false; - } - for (const x of files) this.appendFile(x); - this.fetching = false; - }); - }, +onMounted(() => { + if (defaultStore.state.enableInfiniteScroll && loadMoreFiles.value) { + nextTick(() => { + ilFilesObserver.observe(loadMoreFiles.value?.$el) + }); + } - getMenu() { - return [{ - text: this.$ts.addFile, - type: 'label' - }, { - text: this.$ts.upload, - icon: 'fas fa-upload', - action: () => { this.selectLocalFile(); } - }, { - text: this.$ts.fromUrl, - icon: 'fas fa-link', - action: () => { this.urlUpload(); } - }, null, { - text: this.folder ? this.folder.name : this.$ts.drive, - type: 'label' - }, this.folder ? { - text: this.$ts.renameFolder, - icon: 'fas fa-i-cursor', - action: () => { this.renameFolder(this.folder); } - } : undefined, this.folder ? { - text: this.$ts.deleteFolder, - icon: 'fas fa-trash-alt', - action: () => { this.deleteFolder(this.folder); } - } : undefined, { - text: this.$ts.createFolder, - icon: 'fas fa-folder-plus', - action: () => { this.createFolder(); } - }]; - }, + connection.on('fileCreated', onStreamDriveFileCreated); + connection.on('fileUpdated', onStreamDriveFileUpdated); + connection.on('fileDeleted', onStreamDriveFileDeleted); + connection.on('folderCreated', onStreamDriveFolderCreated); + connection.on('folderUpdated', onStreamDriveFolderUpdated); + connection.on('folderDeleted', onStreamDriveFolderDeleted); - showMenu(ev) { - os.popupMenu(this.getMenu(), ev.currentTarget || ev.target); - }, + if (props.initialFolder) { + move(props.initialFolder); + } else { + fetch(); + } +}); - onContextmenu(ev) { - os.contextMenu(this.getMenu(), ev); - }, +onActivated(() => { + if (defaultStore.state.enableInfiniteScroll) { + nextTick(() => { + ilFilesObserver.observe(loadMoreFiles.value?.$el) + }); } }); + +onBeforeUnmount(() => { + connection.dispose(); + ilFilesObserver.disconnect(); +}); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/emoji-picker-dialog.vue b/packages/client/src/components/emoji-picker-dialog.vue index 51c634dd8e..f06a24636c 100644 --- a/packages/client/src/components/emoji-picker-dialog.vue +++ b/packages/client/src/components/emoji-picker-dialog.vue @@ -1,58 +1,65 @@ <template> -<MkModal ref="modal" v-slot="{ type, maxHeight }" :z-priority="'middle'" :prefer-type="asReactionPicker && $store.state.reactionPickerUseDrawerForMobile === false ? 'popup' : 'auto'" :transparent-bg="true" :manual-showing="manualShowing" :src="src" @click="$refs.modal.close()" @opening="opening" @close="$emit('close')" @closed="$emit('closed')"> - <MkEmojiPicker ref="picker" class="ryghynhb _popup _shadow" :class="{ drawer: type === 'drawer' }" :show-pinned="showPinned" :as-reaction-picker="asReactionPicker" :as-drawer="type === 'drawer'" :max-height="maxHeight" @chosen="chosen"/> +<MkModal + ref="modal" + v-slot="{ type, maxHeight }" + :z-priority="'middle'" + :prefer-type="asReactionPicker && defaultStore.state.reactionPickerUseDrawerForMobile === false ? 'popup' : 'auto'" + :transparent-bg="true" + :manual-showing="manualShowing" + :src="src" + @click="modal?.close()" + @opening="opening" + @close="emit('close')" + @closed="emit('closed')" +> + <MkEmojiPicker + ref="picker" + class="ryghynhb _popup _shadow" + :class="{ drawer: type === 'drawer' }" + :show-pinned="showPinned" + :as-reaction-picker="asReactionPicker" + :as-drawer="type === 'drawer'" + :max-height="maxHeight" + @chosen="chosen" + /> </MkModal> </template> -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; +<script lang="ts" setup> +import { ref } from 'vue'; import MkModal from '@/components/ui/modal.vue'; import MkEmojiPicker from '@/components/emoji-picker.vue'; +import { defaultStore } from '@/store'; -export default defineComponent({ - components: { - MkModal, - MkEmojiPicker, - }, - - props: { - manualShowing: { - type: Boolean, - required: false, - default: null, - }, - src: { - required: false - }, - showPinned: { - required: false, - default: true - }, - asReactionPicker: { - required: false - }, - }, - - emits: ['done', 'close', 'closed'], +withDefaults(defineProps<{ + manualShowing?: boolean; + src?: HTMLElement; + showPinned?: boolean; + asReactionPicker?: boolean; +}>(), { + manualShowing: false, + showPinned: true, + asReactionPicker: false, +}); - data() { - return { +const emit = defineEmits<{ + (e: 'done', v: any): void; + (e: 'close'): void; + (e: 'closed'): void; +}>(); - }; - }, +const modal = ref<InstanceType<typeof MkModal>>(); +const picker = ref<InstanceType<typeof MkEmojiPicker>>(); - methods: { - chosen(emoji: any) { - this.$emit('done', emoji); - this.$refs.modal.close(); - }, +function chosen(emoji: any) { + emit('done', emoji); + modal.value?.close(); +} - opening() { - this.$refs.picker.reset(); - this.$refs.picker.focus(); - } - } -}); +function opening() { + picker.value?.reset(); + picker.value?.focus(); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/emoji-picker-window.vue b/packages/client/src/components/emoji-picker-window.vue index 0ffa0c1187..4d27fb48ba 100644 --- a/packages/client/src/components/emoji-picker-window.vue +++ b/packages/client/src/components/emoji-picker-window.vue @@ -5,50 +5,33 @@ :can-resize="false" :mini="true" :front="true" - @closed="$emit('closed')" + @closed="emit('closed')" > <MkEmojiPicker :show-pinned="showPinned" :as-reaction-picker="asReactionPicker" @chosen="chosen"/> </MkWindow> </template> -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MkWindow from '@/components/ui/window.vue'; import MkEmojiPicker from '@/components/emoji-picker.vue'; -export default defineComponent({ - components: { - MkWindow, - MkEmojiPicker, - }, - - props: { - src: { - required: false - }, - showPinned: { - required: false, - default: true - }, - asReactionPicker: { - required: false - }, - }, - - emits: ['chosen', 'closed'], - - data() { - return { +withDefaults(defineProps<{ + src?: HTMLElement; + showPinned?: boolean; + asReactionPicker?: boolean; +}>(), { + showPinned: true, +}); - }; - }, +const emit = defineEmits<{ + (e: 'chosen', v: any): void; + (e: 'closed'): void; +}>(); - methods: { - chosen(emoji: any) { - this.$emit('chosen', emoji); - }, - } -}); +function chosen(emoji: any) { + emit('chosen', emoji); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/emoji-picker.section.vue b/packages/client/src/components/emoji-picker.section.vue index 08c4f6813d..1026e894d1 100644 --- a/packages/client/src/components/emoji-picker.section.vue +++ b/packages/client/src/components/emoji-picker.section.vue @@ -7,7 +7,7 @@ <button v-for="emoji in emojis" :key="emoji" class="_button" - @click="chosen(emoji, $event)" + @click="emit('chosen', emoji, $event)" > <MkEmoji :emoji="emoji" :normal="true"/> </button> @@ -15,35 +15,19 @@ </section> </template> -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; -import { getStaticImageUrl } from '@/scripts/get-static-image-url'; +<script lang="ts" setup> +import { ref } from 'vue'; -export default defineComponent({ - props: { - emojis: { - required: true, - }, - initialShown: { - required: false - } - }, +const props = defineProps<{ + emojis: string[]; + initialShown?: boolean; +}>(); - emits: ['chosen'], +const emit = defineEmits<{ + (e: 'chosen', v: string, ev: MouseEvent): void; +}>(); - data() { - return { - getStaticImageUrl, - shown: this.initialShown, - }; - }, - - methods: { - chosen(emoji: any, ev) { - this.$parent.chosen(emoji, ev); - }, - } -}); +const shown = ref(!!props.initialShown); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/emoji-picker.vue b/packages/client/src/components/emoji-picker.vue index a8eed1ca21..96670fa58c 100644 --- a/packages/client/src/components/emoji-picker.vue +++ b/packages/client/src/components/emoji-picker.vue @@ -1,18 +1,18 @@ <template> -<div class="omfetrab" :class="['w' + width, 'h' + height, { big, asDrawer }]" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : null }"> - <input ref="search" v-model.trim="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="$ts.search" @paste.stop="paste" @keyup.enter="done()"> +<div class="omfetrab" :class="['w' + width, 'h' + height, { big, asDrawer }]" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : undefined }"> + <input ref="search" v-model.trim="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="i18n.locale.search" @paste.stop="paste" @keyup.enter="done()"> <div ref="emojis" class="emojis"> <section class="result"> <div v-if="searchResultCustom.length > 0"> <button v-for="emoji in searchResultCustom" - :key="emoji" + :key="emoji.id" class="_button" :title="emoji.name" tabindex="0" @click="chosen(emoji, $event)" > - <MkEmoji v-if="emoji.char != null" :emoji="emoji.char"/> - <img v-else :src="$store.state.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/> + <!--<MkEmoji v-if="emoji.char != null" :emoji="emoji.char"/>--> + <img :src="disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/> </button> </div> <div v-if="searchResultUnicode.length > 0"> @@ -43,9 +43,9 @@ </section> <section> - <header class="_acrylic"><i class="far fa-clock fa-fw"></i> {{ $ts.recentUsed }}</header> + <header class="_acrylic"><i class="far fa-clock fa-fw"></i> {{ i18n.locale.recentUsed }}</header> <div> - <button v-for="emoji in $store.state.recentlyUsedEmojis" + <button v-for="emoji in recentlyUsedEmojis" :key="emoji" class="_button" @click="chosen(emoji, $event)" @@ -56,12 +56,12 @@ </section> </div> <div> - <header class="_acrylic">{{ $ts.customEmojis }}</header> - <XSection v-for="category in customEmojiCategories" :key="'custom:' + category" :initial-shown="false" :emojis="customEmojis.filter(e => e.category === category).map(e => ':' + e.name + ':')">{{ category || $ts.other }}</XSection> + <header class="_acrylic">{{ i18n.locale.customEmojis }}</header> + <XSection v-for="category in customEmojiCategories" :key="'custom:' + category" :initial-shown="false" :emojis="customEmojis.filter(e => e.category === category).map(e => ':' + e.name + ':')" @chosen="chosen">{{ category || i18n.locale.other }}</XSection> </div> <div> - <header class="_acrylic">{{ $ts.emoji }}</header> - <XSection v-for="category in categories" :emojis="emojilist.filter(e => e.category === category).map(e => e.char)">{{ category }}</XSection> + <header class="_acrylic">{{ i18n.locale.emoji }}</header> + <XSection v-for="category in categories" :emojis="emojilist.filter(e => e.category === category).map(e => e.char)" @chosen="chosen">{{ category }}</XSection> </div> </div> <div class="tabs"> @@ -73,277 +73,272 @@ </div> </template> -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; -import { emojilist } from '@/scripts/emojilist'; +<script lang="ts" setup> +import { ref, computed, watch, onMounted } from 'vue'; +import * as Misskey from 'misskey-js'; +import { emojilist, UnicodeEmojiDef, unicodeEmojiCategories as categories } from '@/scripts/emojilist'; import { getStaticImageUrl } from '@/scripts/get-static-image-url'; import Ripple from '@/components/ripple.vue'; import * as os from '@/os'; import { isTouchUsing } from '@/scripts/touch'; import { isMobile } from '@/scripts/is-mobile'; -import { emojiCategories } from '@/instance'; +import { emojiCategories, instance } from '@/instance'; import XSection from './emoji-picker.section.vue'; +import { i18n } from '@/i18n'; +import { defaultStore } from '@/store'; -export default defineComponent({ - components: { - XSection - }, - - props: { - showPinned: { - required: false, - default: true, - }, - asReactionPicker: { - required: false, - }, - maxHeight: { - type: Number, - required: false, - }, - asDrawer: { - type: Boolean, - required: false - }, - }, +const props = withDefaults(defineProps<{ + showPinned?: boolean; + asReactionPicker?: boolean; + maxHeight?: number; + asDrawer?: boolean; +}>(), { + showPinned: true, +}); - emits: ['chosen'], +const emit = defineEmits<{ + (e: 'chosen', v: string): void; +}>(); - data() { - return { - emojilist: markRaw(emojilist), - getStaticImageUrl, - pinned: this.$store.reactiveState.reactions, - width: this.asReactionPicker ? this.$store.state.reactionPickerWidth : 3, - height: this.asReactionPicker ? this.$store.state.reactionPickerHeight : 2, - big: this.asReactionPicker ? isTouchUsing : false, - customEmojiCategories: emojiCategories, - customEmojis: this.$instance.emojis, - q: null, - searchResultCustom: [], - searchResultUnicode: [], - tab: 'index', - categories: ['face', 'people', 'animals_and_nature', 'food_and_drink', 'activity', 'travel_and_places', 'objects', 'symbols', 'flags'], - }; - }, +const search = ref<HTMLInputElement>(); +const emojis = ref<HTMLDivElement>(); - watch: { - q() { - this.$refs.emojis.scrollTop = 0; +const { + reactions: pinned, + reactionPickerWidth, + reactionPickerHeight, + disableShowingAnimatedImages, + recentlyUsedEmojis, +} = defaultStore.reactiveState; - if (this.q == null || this.q === '') { - this.searchResultCustom = []; - this.searchResultUnicode = []; - return; - } +const width = computed(() => props.asReactionPicker ? reactionPickerWidth.value : 3); +const height = computed(() => props.asReactionPicker ? reactionPickerHeight.value : 2); +const big = props.asReactionPicker ? isTouchUsing : false; +const customEmojiCategories = emojiCategories; +const customEmojis = instance.emojis; +const q = ref<string | null>(null); +const searchResultCustom = ref<Misskey.entities.CustomEmoji[]>([]); +const searchResultUnicode = ref<UnicodeEmojiDef[]>([]); +const tab = ref<'index' | 'custom' | 'unicode' | 'tags'>('index'); - const q = this.q.replace(/:/g, ''); +watch(q, () => { + if (emojis.value) emojis.value.scrollTop = 0; - const searchCustom = () => { - const max = 8; - const emojis = this.customEmojis; - const matches = new Set(); + if (q.value == null || q.value === '') { + searchResultCustom.value = []; + searchResultUnicode.value = []; + return; + } - const exactMatch = emojis.find(e => e.name === q); - if (exactMatch) matches.add(exactMatch); + const newQ = q.value.replace(/:/g, ''); - if (q.includes(' ')) { // AND検索 - const keywords = q.split(' '); + const searchCustom = () => { + const max = 8; + const emojis = customEmojis; + const matches = new Set<Misskey.entities.CustomEmoji>(); - // 名前にキーワードが含まれている - for (const emoji of emojis) { - if (keywords.every(keyword => emoji.name.includes(keyword))) { - matches.add(emoji); - if (matches.size >= max) break; - } - } - if (matches.size >= max) return matches; + const exactMatch = emojis.find(e => e.name === newQ); + if (exactMatch) matches.add(exactMatch); - // 名前またはエイリアスにキーワードが含まれている - for (const emoji of emojis) { - if (keywords.every(keyword => emoji.name.includes(keyword) || emoji.aliases.some(alias => alias.includes(keyword)))) { - matches.add(emoji); - if (matches.size >= max) break; - } - } - } else { - for (const emoji of emojis) { - if (emoji.name.startsWith(q)) { - matches.add(emoji); - if (matches.size >= max) break; - } - } - if (matches.size >= max) return matches; + if (newQ.includes(' ')) { // AND検索 + const keywords = newQ.split(' '); - for (const emoji of emojis) { - if (emoji.aliases.some(alias => alias.startsWith(q))) { - matches.add(emoji); - if (matches.size >= max) break; - } - } - if (matches.size >= max) return matches; + // 名前にキーワードが含まれている + for (const emoji of emojis) { + if (keywords.every(keyword => emoji.name.includes(keyword))) { + matches.add(emoji); + if (matches.size >= max) break; + } + } + if (matches.size >= max) return matches; - for (const emoji of emojis) { - if (emoji.name.includes(q)) { - matches.add(emoji); - if (matches.size >= max) break; - } - } - if (matches.size >= max) return matches; + // 名前またはエイリアスにキーワードが含まれている + for (const emoji of emojis) { + if (keywords.every(keyword => emoji.name.includes(keyword) || emoji.aliases.some(alias => alias.includes(keyword)))) { + matches.add(emoji); + if (matches.size >= max) break; + } + } + } else { + for (const emoji of emojis) { + if (emoji.name.startsWith(newQ)) { + matches.add(emoji); + if (matches.size >= max) break; + } + } + if (matches.size >= max) return matches; - for (const emoji of emojis) { - if (emoji.aliases.some(alias => alias.includes(q))) { - matches.add(emoji); - if (matches.size >= max) break; - } - } + for (const emoji of emojis) { + if (emoji.aliases.some(alias => alias.startsWith(newQ))) { + matches.add(emoji); + if (matches.size >= max) break; } + } + if (matches.size >= max) return matches; - return matches; - }; + for (const emoji of emojis) { + if (emoji.name.includes(newQ)) { + matches.add(emoji); + if (matches.size >= max) break; + } + } + if (matches.size >= max) return matches; - const searchUnicode = () => { - const max = 8; - const emojis = this.emojilist; - const matches = new Set(); + for (const emoji of emojis) { + if (emoji.aliases.some(alias => alias.includes(newQ))) { + matches.add(emoji); + if (matches.size >= max) break; + } + } + } - const exactMatch = emojis.find(e => e.name === q); - if (exactMatch) matches.add(exactMatch); + return matches; + }; - if (q.includes(' ')) { // AND検索 - const keywords = q.split(' '); + const searchUnicode = () => { + const max = 8; + const emojis = emojilist; + const matches = new Set<UnicodeEmojiDef>(); - // 名前にキーワードが含まれている - for (const emoji of emojis) { - if (keywords.every(keyword => emoji.name.includes(keyword))) { - matches.add(emoji); - if (matches.size >= max) break; - } - } - if (matches.size >= max) return matches; + const exactMatch = emojis.find(e => e.name === newQ); + if (exactMatch) matches.add(exactMatch); - // 名前またはエイリアスにキーワードが含まれている - for (const emoji of emojis) { - if (keywords.every(keyword => emoji.name.includes(keyword) || emoji.keywords.some(alias => alias.includes(keyword)))) { - matches.add(emoji); - if (matches.size >= max) break; - } - } - } else { - for (const emoji of emojis) { - if (emoji.name.startsWith(q)) { - matches.add(emoji); - if (matches.size >= max) break; - } - } - if (matches.size >= max) return matches; + if (newQ.includes(' ')) { // AND検索 + const keywords = newQ.split(' '); - for (const emoji of emojis) { - if (emoji.keywords.some(keyword => keyword.startsWith(q))) { - matches.add(emoji); - if (matches.size >= max) break; - } - } - if (matches.size >= max) return matches; + // 名前にキーワードが含まれている + for (const emoji of emojis) { + if (keywords.every(keyword => emoji.name.includes(keyword))) { + matches.add(emoji); + if (matches.size >= max) break; + } + } + if (matches.size >= max) return matches; - for (const emoji of emojis) { - if (emoji.name.includes(q)) { - matches.add(emoji); - if (matches.size >= max) break; - } - } - if (matches.size >= max) return matches; + // 名前またはエイリアスにキーワードが含まれている + for (const emoji of emojis) { + if (keywords.every(keyword => emoji.name.includes(keyword) || emoji.keywords.some(alias => alias.includes(keyword)))) { + matches.add(emoji); + if (matches.size >= max) break; + } + } + } else { + for (const emoji of emojis) { + if (emoji.name.startsWith(newQ)) { + matches.add(emoji); + if (matches.size >= max) break; + } + } + if (matches.size >= max) return matches; - for (const emoji of emojis) { - if (emoji.keywords.some(keyword => keyword.includes(q))) { - matches.add(emoji); - if (matches.size >= max) break; - } - } + for (const emoji of emojis) { + if (emoji.keywords.some(keyword => keyword.startsWith(newQ))) { + matches.add(emoji); + if (matches.size >= max) break; } + } + if (matches.size >= max) return matches; - return matches; - }; + for (const emoji of emojis) { + if (emoji.name.includes(newQ)) { + matches.add(emoji); + if (matches.size >= max) break; + } + } + if (matches.size >= max) return matches; - this.searchResultCustom = Array.from(searchCustom()); - this.searchResultUnicode = Array.from(searchUnicode()); + for (const emoji of emojis) { + if (emoji.keywords.some(keyword => keyword.includes(newQ))) { + matches.add(emoji); + if (matches.size >= max) break; + } + } } - }, - mounted() { - this.focus(); - }, + return matches; + }; - methods: { - focus() { - if (!isMobile && !isTouchUsing) { - this.$refs.search.focus({ - preventScroll: true - }); - } - }, + searchResultCustom.value = Array.from(searchCustom()); + searchResultUnicode.value = Array.from(searchUnicode()); +}); - reset() { - this.$refs.emojis.scrollTop = 0; - this.q = ''; - }, +function focus() { + if (!isMobile && !isTouchUsing) { + search.value?.focus({ + preventScroll: true + }); + } +} - getKey(emoji: any) { - return typeof emoji === 'string' ? emoji : (emoji.char || `:${emoji.name}:`); - }, +function reset() { + if (emojis.value) emojis.value.scrollTop = 0; + q.value = ''; +} - chosen(emoji: any, ev) { - if (ev) { - const el = ev.currentTarget || ev.target; - const rect = el.getBoundingClientRect(); - const x = rect.left + (el.offsetWidth / 2); - const y = rect.top + (el.offsetHeight / 2); - os.popup(Ripple, { x, y }, {}, 'end'); - } +function getKey(emoji: string | Misskey.entities.CustomEmoji | UnicodeEmojiDef): string { + return typeof emoji === 'string' ? emoji : (emoji.char || `:${emoji.name}:`); +} - const key = this.getKey(emoji); - this.$emit('chosen', key); +function chosen(emoji: any, ev?: MouseEvent) { + const el = ev && (ev.currentTarget || ev.target) as HTMLElement | null | undefined; + if (el) { + const rect = el.getBoundingClientRect(); + const x = rect.left + (el.offsetWidth / 2); + const y = rect.top + (el.offsetHeight / 2); + os.popup(Ripple, { x, y }, {}, 'end'); + } - // 最近使った絵文字更新 - if (!this.pinned.includes(key)) { - let recents = this.$store.state.recentlyUsedEmojis; - recents = recents.filter((e: any) => e !== key); - recents.unshift(key); - this.$store.set('recentlyUsedEmojis', recents.splice(0, 32)); - } - }, + const key = getKey(emoji); + emit('chosen', key); - paste(event) { - const paste = (event.clipboardData || window.clipboardData).getData('text'); - if (this.done(paste)) { - event.preventDefault(); - } - }, + // 最近使った絵文字更新 + if (!pinned.value.includes(key)) { + let recents = defaultStore.state.recentlyUsedEmojis; + recents = recents.filter((e: any) => e !== key); + recents.unshift(key); + defaultStore.set('recentlyUsedEmojis', recents.splice(0, 32)); + } +} - done(query) { - if (query == null) query = this.q; - if (query == null) return; - const q = query.replace(/:/g, ''); - const exactMatchCustom = this.customEmojis.find(e => e.name === q); - if (exactMatchCustom) { - this.chosen(exactMatchCustom); - return true; - } - const exactMatchUnicode = this.emojilist.find(e => e.char === q || e.name === q); - if (exactMatchUnicode) { - this.chosen(exactMatchUnicode); - return true; - } - if (this.searchResultCustom.length > 0) { - this.chosen(this.searchResultCustom[0]); - return true; - } - if (this.searchResultUnicode.length > 0) { - this.chosen(this.searchResultUnicode[0]); - return true; - } - }, +function paste(event: ClipboardEvent) { + const paste = (event.clipboardData || window.clipboardData).getData('text'); + if (done(paste)) { + event.preventDefault(); } +} + +function done(query?: any): boolean | void { + if (query == null) query = q.value; + if (query == null || typeof query !== 'string') return; + + const q2 = query.replace(/:/g, ''); + const exactMatchCustom = customEmojis.find(e => e.name === q2); + if (exactMatchCustom) { + chosen(exactMatchCustom); + return true; + } + const exactMatchUnicode = emojilist.find(e => e.char === q2 || e.name === q2); + if (exactMatchUnicode) { + chosen(exactMatchUnicode); + return true; + } + if (searchResultCustom.value.length > 0) { + chosen(searchResultCustom.value[0]); + return true; + } + if (searchResultUnicode.value.length > 0) { + chosen(searchResultUnicode.value[0]); + return true; + } +} + +onMounted(() => { + focus(); +}); + +defineExpose({ + focus, + reset, }); </script> diff --git a/packages/client/src/components/featured-photos.vue b/packages/client/src/components/featured-photos.vue index af5892c98e..e58b5d2849 100644 --- a/packages/client/src/components/featured-photos.vue +++ b/packages/client/src/components/featured-photos.vue @@ -2,25 +2,15 @@ <div v-if="meta" class="xfbouadm" :style="{ backgroundImage: `url(${ meta.backgroundImageUrl })` }"></div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { ref } from 'vue'; +import * as Misskey from 'misskey-js'; import * as os from '@/os'; -export default defineComponent({ - components: { - }, +const meta = ref<Misskey.entities.DetailedInstanceMetadata>(); - data() { - return { - meta: null, - }; - }, - - created() { - os.api('meta', { detail: true }).then(meta => { - this.meta = meta; - }); - }, +os.api('meta', { detail: true }).then(gotMeta => { + meta.value = gotMeta; }); </script> diff --git a/packages/client/src/components/file-type-icon.vue b/packages/client/src/components/file-type-icon.vue index be1af5e501..11d28188cc 100644 --- a/packages/client/src/components/file-type-icon.vue +++ b/packages/client/src/components/file-type-icon.vue @@ -4,25 +4,12 @@ </span> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as os from '@/os'; +<script lang="ts" setup> +import { computed } from 'vue'; -export default defineComponent({ - props: { - type: { - type: String, - required: true, - } - }, - data() { - return { - }; - }, - computed: { - kind(): string { - return this.type.split('/')[0]; - } - } -}); +const props = defineProps<{ + type: string; +}>(); + +const kind = computed(() => props.type.split('/')[0]); </script> diff --git a/packages/client/src/components/follow-button.vue b/packages/client/src/components/follow-button.vue index 7136261914..345edb6441 100644 --- a/packages/client/src/components/follow-button.vue +++ b/packages/client/src/components/follow-button.vue @@ -6,128 +6,110 @@ > <template v-if="!wait"> <template v-if="hasPendingFollowRequestFromYou && user.isLocked"> - <span v-if="full">{{ $ts.followRequestPending }}</span><i class="fas fa-hourglass-half"></i> + <span v-if="full">{{ i18n.locale.followRequestPending }}</span><i class="fas fa-hourglass-half"></i> </template> <template v-else-if="hasPendingFollowRequestFromYou && !user.isLocked"> <!-- つまりリモートフォローの場合。 --> - <span v-if="full">{{ $ts.processing }}</span><i class="fas fa-spinner fa-pulse"></i> + <span v-if="full">{{ i18n.locale.processing }}</span><i class="fas fa-spinner fa-pulse"></i> </template> <template v-else-if="isFollowing"> - <span v-if="full">{{ $ts.unfollow }}</span><i class="fas fa-minus"></i> + <span v-if="full">{{ i18n.locale.unfollow }}</span><i class="fas fa-minus"></i> </template> <template v-else-if="!isFollowing && user.isLocked"> - <span v-if="full">{{ $ts.followRequest }}</span><i class="fas fa-plus"></i> + <span v-if="full">{{ i18n.locale.followRequest }}</span><i class="fas fa-plus"></i> </template> <template v-else-if="!isFollowing && !user.isLocked"> - <span v-if="full">{{ $ts.follow }}</span><i class="fas fa-plus"></i> + <span v-if="full">{{ i18n.locale.follow }}</span><i class="fas fa-plus"></i> </template> </template> <template v-else> - <span v-if="full">{{ $ts.processing }}</span><i class="fas fa-spinner fa-pulse fa-fw"></i> + <span v-if="full">{{ i18n.locale.processing }}</span><i class="fas fa-spinner fa-pulse fa-fw"></i> </template> </button> </template> -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; +<script lang="ts" setup> +import { onBeforeUnmount, onMounted, ref } from 'vue'; +import * as Misskey from 'misskey-js'; import * as os from '@/os'; +import { stream } from '@/stream'; +import { i18n } from '@/i18n'; -export default defineComponent({ - props: { - user: { - type: Object, - required: true - }, - full: { - type: Boolean, - required: false, - default: false, - }, - large: { - type: Boolean, - required: false, - default: false, - }, - }, - - data() { - return { - isFollowing: this.user.isFollowing, - hasPendingFollowRequestFromYou: this.user.hasPendingFollowRequestFromYou, - wait: false, - connection: null, - }; - }, - - created() { - // 渡されたユーザー情報が不完全な場合 - if (this.user.isFollowing == null) { - os.api('users/show', { - userId: this.user.id - }).then(u => { - this.isFollowing = u.isFollowing; - this.hasPendingFollowRequestFromYou = u.hasPendingFollowRequestFromYou; - }); - } - }, - - mounted() { - this.connection = markRaw(os.stream.useChannel('main')); +const props = withDefaults(defineProps<{ + user: Misskey.entities.UserDetailed, + full?: boolean, + large?: boolean, +}>(), { + full: false, + large: false, +}); - this.connection.on('follow', this.onFollowChange); - this.connection.on('unfollow', this.onFollowChange); - }, +const isFollowing = ref(props.user.isFollowing); +const hasPendingFollowRequestFromYou = ref(props.user.hasPendingFollowRequestFromYou); +const wait = ref(false); +const connection = stream.useChannel('main'); - beforeUnmount() { - this.connection.dispose(); - }, +if (props.user.isFollowing == null) { + os.api('users/show', { + userId: props.user.id + }).then(u => { + isFollowing.value = u.isFollowing; + hasPendingFollowRequestFromYou.value = u.hasPendingFollowRequestFromYou; + }); +} - methods: { - onFollowChange(user) { - if (user.id == this.user.id) { - this.isFollowing = user.isFollowing; - this.hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou; - } - }, +function onFollowChange(user: Misskey.entities.UserDetailed) { + if (user.id == props.user.id) { + isFollowing.value = user.isFollowing; + hasPendingFollowRequestFromYou.value = user.hasPendingFollowRequestFromYou; + } +} - async onClick() { - this.wait = true; +async function onClick() { + wait.value = true; - try { - if (this.isFollowing) { - const { canceled } = await os.confirm({ - type: 'warning', - text: this.$t('unfollowConfirm', { name: this.user.name || this.user.username }), - }); + try { + if (isFollowing.value) { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.t('unfollowConfirm', { name: props.user.name || props.user.username }), + }); - if (canceled) return; + if (canceled) return; - await os.api('following/delete', { - userId: this.user.id - }); - } else { - if (this.hasPendingFollowRequestFromYou) { - await os.api('following/requests/cancel', { - userId: this.user.id - }); - } else if (this.user.isLocked) { - await os.api('following/create', { - userId: this.user.id - }); - this.hasPendingFollowRequestFromYou = true; - } else { - await os.api('following/create', { - userId: this.user.id - }); - this.hasPendingFollowRequestFromYou = true; - } - } - } catch (e) { - console.error(e); - } finally { - this.wait = false; + await os.api('following/delete', { + userId: props.user.id + }); + } else { + if (hasPendingFollowRequestFromYou.value) { + await os.api('following/requests/cancel', { + userId: props.user.id + }); + } else if (props.user.isLocked) { + await os.api('following/create', { + userId: props.user.id + }); + hasPendingFollowRequestFromYou.value = true; + } else { + await os.api('following/create', { + userId: props.user.id + }); + hasPendingFollowRequestFromYou.value = true; } } + } catch (e) { + console.error(e); + } finally { + wait.value = false; } +} + +onMounted(() => { + connection.on('follow', onFollowChange); + connection.on('unfollow', onFollowChange); +}); + +onBeforeUnmount(() => { + connection.dispose(); }); </script> diff --git a/packages/client/src/components/forgot-password.vue b/packages/client/src/components/forgot-password.vue index b03a6133b4..c74e1ac75e 100644 --- a/packages/client/src/components/forgot-password.vue +++ b/packages/client/src/components/forgot-password.vue @@ -2,72 +2,64 @@ <XModalWindow ref="dialog" :width="370" :height="400" - @close="$refs.dialog.close()" - @closed="$emit('closed')" + @close="dialog.close()" + @closed="emit('closed')" > - <template #header>{{ $ts.forgotPassword }}</template> + <template #header>{{ i18n.locale.forgotPassword }}</template> - <form v-if="$instance.enableEmail" class="bafeceda" @submit.prevent="onSubmit"> + <form v-if="instance.enableEmail" class="bafeceda" @submit.prevent="onSubmit"> <div class="main _formRoot"> <MkInput v-model="username" class="_formBlock" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required> - <template #label>{{ $ts.username }}</template> + <template #label>{{ i18n.locale.username }}</template> <template #prefix>@</template> </MkInput> <MkInput v-model="email" class="_formBlock" type="email" spellcheck="false" required> - <template #label>{{ $ts.emailAddress }}</template> - <template #caption>{{ $ts._forgotPassword.enterEmail }}</template> + <template #label>{{ i18n.locale.emailAddress }}</template> + <template #caption>{{ i18n.locale._forgotPassword.enterEmail }}</template> </MkInput> - <MkButton class="_formBlock" type="submit" :disabled="processing" primary style="margin: 0 auto;">{{ $ts.send }}</MkButton> + <MkButton class="_formBlock" type="submit" :disabled="processing" primary style="margin: 0 auto;">{{ i18n.locale.send }}</MkButton> </div> <div class="sub"> - <MkA to="/about" class="_link">{{ $ts._forgotPassword.ifNoEmail }}</MkA> + <MkA to="/about" class="_link">{{ i18n.locale._forgotPassword.ifNoEmail }}</MkA> </div> </form> - <div v-else> - {{ $ts._forgotPassword.contactAdmin }} + <div v-else class="bafecedb"> + {{ i18n.locale._forgotPassword.contactAdmin }} </div> </XModalWindow> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import XModalWindow from '@/components/ui/modal-window.vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import * as os from '@/os'; +import { instance } from '@/instance'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XModalWindow, - MkButton, - MkInput, - }, +const emit = defineEmits<{ + (e: 'done'): void; + (e: 'closed'): void; +}>(); - emits: ['done', 'closed'], +let dialog: InstanceType<typeof XModalWindow> = $ref(); - data() { - return { - username: '', - email: '', - processing: false, - }; - }, +let username = $ref(''); +let email = $ref(''); +let processing = $ref(false); - methods: { - async onSubmit() { - this.processing = true; - await os.apiWithDialog('request-reset-password', { - username: this.username, - email: this.email, - }); - - this.$emit('done'); - this.$refs.dialog.close(); - } - } -}); +async function onSubmit() { + processing = true; + await os.apiWithDialog('request-reset-password', { + username, + email, + }); + emit('done'); + dialog.close(); +} </script> <style lang="scss" scoped> @@ -81,4 +73,8 @@ export default defineComponent({ padding: 24px; } } + +.bafecedb { + padding: 24px; +} </style> diff --git a/packages/client/src/components/form/folder.vue b/packages/client/src/components/form/folder.vue new file mode 100644 index 0000000000..571afe50c0 --- /dev/null +++ b/packages/client/src/components/form/folder.vue @@ -0,0 +1,107 @@ +<template> +<div class="dwzlatin" :class="{ opened }"> + <div class="header _button" @click="toggle"> + <span class="icon"><slot name="icon"></slot></span> + <span class="text"><slot name="label"></slot></span> + <span class="right"> + <span class="text"><slot name="suffix"></slot></span> + <i v-if="opened" class="fas fa-angle-up icon"></i> + <i v-else class="fas fa-angle-down icon"></i> + </span> + </div> + <keep-alive> + <div v-if="openedAtLeastOnce" v-show="opened" class="body"> + <MkSpacer :margin-min="14" :margin-max="22"> + <slot></slot> + </MkSpacer> + </div> + </keep-alive> +</div> +</template> + +<script lang="ts" setup> +const props = withDefaults(defineProps<{ + defaultOpen: boolean; +}>(), { + defaultOpen: false, +}) + +let opened = $ref(props.defaultOpen); +let openedAtLeastOnce = $ref(props.defaultOpen); + +const toggle = () => { + opened = !opened; + if (opened) { + openedAtLeastOnce = true; + } +}; +</script> + +<style lang="scss" scoped> +.dwzlatin { + display: block; + + > .header { + display: flex; + align-items: center; + width: 100%; + box-sizing: border-box; + padding: 12px 14px 12px 14px; + background: var(--buttonBg); + border-radius: 6px; + + &:hover { + text-decoration: none; + background: var(--buttonHoverBg); + } + + &.active { + color: var(--accent); + background: var(--buttonHoverBg); + } + + > .icon { + margin-right: 0.75em; + flex-shrink: 0; + text-align: center; + opacity: 0.8; + + &:empty { + display: none; + + & + .text { + padding-left: 4px; + } + } + } + + > .text { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + padding-right: 12px; + } + + > .right { + margin-left: auto; + opacity: 0.7; + white-space: nowrap; + + > .text:not(:empty) { + margin-right: 0.75em; + } + } + } + + > .body { + background: var(--panel); + border-radius: 0 0 6px 6px; + } + + &.opened { + > .header { + border-radius: 6px 6px 0 0; + } + } +} +</style> diff --git a/packages/client/src/components/form/group.vue b/packages/client/src/components/form/group.vue index 2fc203f1b9..1e8376ca44 100644 --- a/packages/client/src/components/form/group.vue +++ b/packages/client/src/components/form/group.vue @@ -1,5 +1,5 @@ <template> -<div v-sticky-container v-panel class="adfeebaf _formBlock"> +<div v-sticky-container class="adfeebaf _formBlock"> <div class="label"><slot name="label"></slot></div> <div class="main _formRoot"> <slot></slot> @@ -17,6 +17,7 @@ export default defineComponent({ <style lang="scss" scoped> .adfeebaf { padding: 24px 24px; + border: solid 1px var(--divider); border-radius: var(--radius); > .label { diff --git a/packages/client/src/components/form/input.vue b/packages/client/src/components/form/input.vue index 3533f4f27b..7165671af3 100644 --- a/packages/client/src/components/form/input.vue +++ b/packages/client/src/components/form/input.vue @@ -167,7 +167,7 @@ export default defineComponent({ // このコンポーネントが作成された時、非表示状態である場合がある // 非表示状態だと要素の幅などは0になってしまうので、定期的に計算する - const clock = setInterval(() => { + const clock = window.setInterval(() => { if (prefixEl.value) { if (prefixEl.value.offsetWidth) { inputEl.value.style.paddingLeft = prefixEl.value.offsetWidth + 'px'; @@ -181,7 +181,7 @@ export default defineComponent({ }, 100); onUnmounted(() => { - clearInterval(clock); + window.clearInterval(clock); }); }); }); diff --git a/packages/client/src/components/form/pagination.vue b/packages/client/src/components/form/pagination.vue deleted file mode 100644 index 3d3b40a783..0000000000 --- a/packages/client/src/components/form/pagination.vue +++ /dev/null @@ -1,44 +0,0 @@ -<template> -<FormSlot> - <template #label><slot name="label"></slot></template> - <div class="abcaccfa"> - <slot :items="items"></slot> - <div v-if="empty" key="_empty_" class="empty"> - <slot name="empty"></slot> - </div> - <MkButton v-show="more" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore"> - <template v-if="!moreFetching">{{ $ts.loadMore }}</template> - <template v-if="moreFetching"><MkLoading inline/></template> - </MkButton> - </div> -</FormSlot> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import MkButton from '@/components/ui/button.vue'; -import FormSlot from './slot.vue'; -import paging from '@/scripts/paging'; - -export default defineComponent({ - components: { - MkButton, - FormSlot, - }, - - mixins: [ - paging({}), - ], - - props: { - pagination: { - required: true - }, - }, -}); -</script> - -<style lang="scss" scoped> -.abcaccfa { -} -</style> diff --git a/packages/client/src/components/form/radio.vue b/packages/client/src/components/form/radio.vue index f0b8c71376..2becbec6f3 100644 --- a/packages/client/src/components/form/radio.vue +++ b/packages/client/src/components/form/radio.vue @@ -1,6 +1,6 @@ <template> <div - v-panel + v-adaptive-border class="novjtctn" :class="{ disabled, checked }" :aria-checked="checked" @@ -53,7 +53,10 @@ export default defineComponent({ display: inline-block; text-align: left; cursor: pointer; - padding: 11px 14px; + padding: 10px 12px; + background-color: var(--panel); + background-clip: padding-box !important; + border: solid 1px var(--panel); border-radius: 6px; transition: all 0.3s; @@ -69,9 +72,13 @@ export default defineComponent({ } } + &:hover { + border-color: var(--inputBorderHover) !important; + } + &.checked { - background: var(--accentedBg) !important; - border-color: var(--accent); + background-color: var(--accentedBg) !important; + border-color: var(--accentedBg) !important; color: var(--accent); &, * { @@ -89,11 +96,6 @@ export default defineComponent({ } } - &:hover { - border-color: var(--inputBorderHover); - color: var(--accent); - } - > input { position: absolute; width: 0; diff --git a/packages/client/src/components/form/section.vue b/packages/client/src/components/form/section.vue index bc2ab966b8..c6e34ef1cc 100644 --- a/packages/client/src/components/form/section.vue +++ b/packages/client/src/components/form/section.vue @@ -1,5 +1,5 @@ <template> -<div v-size="{ max: [500] }" v-sticky-container class="vrtktovh _formBlock"> +<div class="vrtktovh _formBlock"> <div class="label"><slot name="label"></slot></div> <div class="main _formRoot"> <slot></slot> @@ -7,20 +7,13 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; - -export default defineComponent({ - -}); +<script lang="ts" setup> </script> <style lang="scss" scoped> .vrtktovh { - margin: 0; border-top: solid 0.5px var(--divider); border-bottom: solid 0.5px var(--divider); - padding: 24px 0; & + .vrtktovh { border-top: none; @@ -36,7 +29,7 @@ export default defineComponent({ > .label { font-weight: bold; - padding: 0 0 16px 0; + margin: 1.5em 0 16px 0; &:empty { display: none; @@ -44,6 +37,7 @@ export default defineComponent({ } > .main { + margin: 1.5em 0; } } </style> diff --git a/packages/client/src/components/form/select.vue b/packages/client/src/components/form/select.vue index afc53ca9c8..87196027a8 100644 --- a/packages/client/src/components/form/select.vue +++ b/packages/client/src/components/form/select.vue @@ -117,7 +117,7 @@ export default defineComponent({ // このコンポーネントが作成された時、非表示状態である場合がある // 非表示状態だと要素の幅などは0になってしまうので、定期的に計算する - const clock = setInterval(() => { + const clock = window.setInterval(() => { if (prefixEl.value) { if (prefixEl.value.offsetWidth) { inputEl.value.style.paddingLeft = prefixEl.value.offsetWidth + 'px'; @@ -131,7 +131,7 @@ export default defineComponent({ }, 100); onUnmounted(() => { - clearInterval(clock); + window.clearInterval(clock); }); }); }); diff --git a/packages/client/src/components/form/split.vue b/packages/client/src/components/form/split.vue new file mode 100644 index 0000000000..676b293967 --- /dev/null +++ b/packages/client/src/components/form/split.vue @@ -0,0 +1,27 @@ +<template> +<div class="terlnhxf _formBlock"> + <slot></slot> +</div> +</template> + +<script lang="ts" setup> +const props = withDefaults(defineProps<{ + minWidth: number; +}>(), { + minWidth: 210, +}); + +const minWidth = props.minWidth + 'px'; +</script> + +<style lang="scss" scoped> +.terlnhxf { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(v-bind('minWidth'), 1fr)); + grid-gap: 12px; + + > ::v-deep(*) { + margin: 0 !important; + } +} +</style> diff --git a/packages/client/src/components/form/suspense.vue b/packages/client/src/components/form/suspense.vue index 4d5debe604..2ad55dacae 100644 --- a/packages/client/src/components/form/suspense.vue +++ b/packages/client/src/components/form/suspense.vue @@ -1,5 +1,5 @@ <template> -<transition name="fade" mode="out-in"> +<transition :name="$store.state.animation ? 'fade' : ''" mode="out-in"> <div v-if="pending"> <MkLoading/> </div> diff --git a/packages/client/src/components/form/switch.vue b/packages/client/src/components/form/switch.vue index aa9b09215e..f8a07b4caa 100644 --- a/packages/client/src/components/form/switch.vue +++ b/packages/client/src/components/form/switch.vue @@ -13,7 +13,8 @@ <i class="check fas fa-check"></i> </span> <span class="label"> - <span @click="toggle"><slot></slot></span> + <!-- TODO: 無名slotの方は廃止 --> + <span @click="toggle"><slot name="label"></slot><slot></slot></span> <p class="caption"><slot name="caption"></slot></p> </span> </div> @@ -110,7 +111,7 @@ export default defineComponent({ } > .label { - margin-left: 16px; + margin-left: 12px; margin-top: 2px; display: block; transition: inherit; diff --git a/packages/client/src/components/global/a.vue b/packages/client/src/components/global/a.vue index 77ee7525a4..cf7385ca22 100644 --- a/packages/client/src/components/global/a.vue +++ b/packages/client/src/components/global/a.vue @@ -4,130 +4,114 @@ </a> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { inject } from 'vue'; import * as os from '@/os'; import copyToClipboard from '@/scripts/copy-to-clipboard'; import { router } from '@/router'; import { url } from '@/config'; -import { popout } from '@/scripts/popout'; -import { ColdDeviceStorage } from '@/store'; +import { popout as popout_ } from '@/scripts/popout'; +import { i18n } from '@/i18n'; +import { defaultStore } from '@/store'; -export default defineComponent({ - inject: { - navHook: { - default: null - }, - sideViewHook: { - default: null - } - }, +const props = withDefaults(defineProps<{ + to: string; + activeClass?: null | string; + behavior?: null | 'window' | 'browser' | 'modalWindow'; +}>(), { + activeClass: null, + behavior: null, +}); - props: { - to: { - type: String, - required: true, - }, - activeClass: { - type: String, - required: false, - }, - behavior: { - type: String, - required: false, - }, - }, +const navHook = inject('navHook', null); +const sideViewHook = inject('sideViewHook', null); - computed: { - active() { - if (this.activeClass == null) return false; - const resolved = router.resolve(this.to); - if (resolved.path == this.$route.path) return true; - if (resolved.name == null) return false; - if (this.$route.name == null) return false; - return resolved.name == this.$route.name; - } - }, +const active = $computed(() => { + if (props.activeClass == null) return false; + const resolved = router.resolve(props.to); + if (resolved.path === router.currentRoute.value.path) return true; + if (resolved.name == null) return false; + if (router.currentRoute.value.name == null) return false; + return resolved.name === router.currentRoute.value.name; +}); - methods: { - onContextmenu(e) { - if (window.getSelection().toString() !== '') return; - os.contextMenu([{ - type: 'label', - text: this.to, - }, { - icon: 'fas fa-window-maximize', - text: this.$ts.openInWindow, - action: () => { - os.pageWindow(this.to); - } - }, this.sideViewHook ? { - icon: 'fas fa-columns', - text: this.$ts.openInSideView, - action: () => { - this.sideViewHook(this.to); - } - } : undefined, { - icon: 'fas fa-expand-alt', - text: this.$ts.showInPage, - action: () => { - this.$router.push(this.to); - } - }, null, { - icon: 'fas fa-external-link-alt', - text: this.$ts.openInNewTab, - action: () => { - window.open(this.to, '_blank'); - } - }, { - icon: 'fas fa-link', - text: this.$ts.copyLink, - action: () => { - copyToClipboard(`${url}${this.to}`); - } - }], e); - }, +function onContextmenu(ev) { + const selection = window.getSelection(); + if (selection && selection.toString() !== '') return; + os.contextMenu([{ + type: 'label', + text: props.to, + }, { + icon: 'fas fa-window-maximize', + text: i18n.locale.openInWindow, + action: () => { + os.pageWindow(props.to); + } + }, sideViewHook ? { + icon: 'fas fa-columns', + text: i18n.locale.openInSideView, + action: () => { + sideViewHook(props.to); + } + } : undefined, { + icon: 'fas fa-expand-alt', + text: i18n.locale.showInPage, + action: () => { + router.push(props.to); + } + }, null, { + icon: 'fas fa-external-link-alt', + text: i18n.locale.openInNewTab, + action: () => { + window.open(props.to, '_blank'); + } + }, { + icon: 'fas fa-link', + text: i18n.locale.copyLink, + action: () => { + copyToClipboard(`${url}${props.to}`); + } + }], ev); +} - window() { - os.pageWindow(this.to); - }, +function openWindow() { + os.pageWindow(props.to); +} - modalWindow() { - os.modalPageWindow(this.to); - }, +function modalWindow() { + os.modalPageWindow(props.to); +} - popout() { - popout(this.to); - }, +function popout() { + popout_(props.to); +} - nav() { - if (this.behavior === 'browser') { - location.href = this.to; - return; - } +function nav() { + if (props.behavior === 'browser') { + location.href = props.to; + return; + } - if (this.behavior) { - if (this.behavior === 'window') { - return this.window(); - } else if (this.behavior === 'modalWindow') { - return this.modalWindow(); - } - } + if (props.behavior) { + if (props.behavior === 'window') { + return openWindow(); + } else if (props.behavior === 'modalWindow') { + return modalWindow(); + } + } - if (this.navHook) { - this.navHook(this.to); - } else { - if (this.$store.state.defaultSideView && this.sideViewHook && this.to !== '/') { - return this.sideViewHook(this.to); - } + if (navHook) { + navHook(props.to); + } else { + if (defaultStore.state.defaultSideView && sideViewHook && props.to !== '/') { + return sideViewHook(props.to); + } - if (this.$router.currentRoute.value.path === this.to) { - window.scroll({ top: 0, behavior: 'smooth' }); - } else { - this.$router.push(this.to); - } - } + if (router.currentRoute.value.path === props.to) { + window.scroll({ top: 0, behavior: 'smooth' }); + } else { + router.push(props.to); } } -}); +} </script> diff --git a/packages/client/src/components/global/acct.vue b/packages/client/src/components/global/acct.vue index 018826153c..c3e806b5fb 100644 --- a/packages/client/src/components/global/acct.vue +++ b/packages/client/src/components/global/acct.vue @@ -5,28 +5,17 @@ </span> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import * as misskey from 'misskey-js'; import { toUnicode } from 'punycode/'; -import { host } from '@/config'; +import { host as hostRaw } from '@/config'; -export default defineComponent({ - props: { - user: { - type: Object, - required: true - }, - detail: { - type: Boolean, - default: false - }, - }, - data() { - return { - host: toUnicode(host), - }; - } -}); +defineProps<{ + user: misskey.entities.UserDetailed; + detail?: boolean; +}>(); + +const host = toUnicode(hostRaw); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/global/ad.vue b/packages/client/src/components/global/ad.vue index 49046b00a7..180dabb2a2 100644 --- a/packages/client/src/components/global/ad.vue +++ b/packages/client/src/components/global/ad.vue @@ -20,7 +20,7 @@ <script lang="ts"> import { defineComponent, ref } from 'vue'; -import { Instance, instance } from '@/instance'; +import { instance } from '@/instance'; import { host } from '@/config'; import MkButton from '@/components/ui/button.vue'; import { defaultStore } from '@/store'; @@ -48,9 +48,9 @@ export default defineComponent({ showMenu.value = !showMenu.value; }; - const choseAd = (): Instance['ads'][number] | null => { + const choseAd = (): (typeof instance)['ads'][number] | null => { if (props.specify) { - return props.specify as Instance['ads'][number]; + return props.specify as (typeof instance)['ads'][number]; } const allAds = instance.ads.map(ad => defaultStore.state.mutedAds.includes(ad.id) ? { diff --git a/packages/client/src/components/global/avatar.vue b/packages/client/src/components/global/avatar.vue index 300e5e079f..27cfb6e4d4 100644 --- a/packages/client/src/components/global/avatar.vue +++ b/packages/client/src/components/global/avatar.vue @@ -1,74 +1,54 @@ <template> -<span v-if="disableLink" v-user-preview="disablePreview ? undefined : user.id" class="eiwwqkts _noSelect" :class="{ cat, square: $store.state.squareAvatars }" :title="acct(user)" @click="onClick"> +<span v-if="disableLink" v-user-preview="disablePreview ? undefined : user.id" class="eiwwqkts _noSelect" :class="{ cat: user.isCat, square: $store.state.squareAvatars }" :style="{ color }" :title="acct(user)" @click="onClick"> <img class="inner" :src="url" decoding="async"/> <MkUserOnlineIndicator v-if="showIndicator" class="indicator" :user="user"/> </span> -<MkA v-else v-user-preview="disablePreview ? undefined : user.id" class="eiwwqkts _noSelect" :class="{ cat, square: $store.state.squareAvatars }" :to="userPage(user)" :title="acct(user)" :target="target"> +<MkA v-else v-user-preview="disablePreview ? undefined : user.id" class="eiwwqkts _noSelect" :class="{ cat: user.isCat, square: $store.state.squareAvatars }" :style="{ color }" :to="userPage(user)" :title="acct(user)" :target="target"> <img class="inner" :src="url" decoding="async"/> <MkUserOnlineIndicator v-if="showIndicator" class="indicator" :user="user"/> </MkA> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted, watch } from 'vue'; +import * as misskey from 'misskey-js'; import { getStaticImageUrl } from '@/scripts/get-static-image-url'; import { extractAvgColorFromBlurhash } from '@/scripts/extract-avg-color-from-blurhash'; import { acct, userPage } from '@/filters/user'; import MkUserOnlineIndicator from '@/components/user-online-indicator.vue'; +import { defaultStore } from '@/store'; -export default defineComponent({ - components: { - MkUserOnlineIndicator - }, - props: { - user: { - type: Object, - required: true - }, - target: { - required: false, - default: null - }, - disableLink: { - required: false, - default: false - }, - disablePreview: { - required: false, - default: false - }, - showIndicator: { - required: false, - default: false - } - }, - emits: ['click'], - computed: { - cat(): boolean { - return this.user.isCat; - }, - url(): string { - return this.$store.state.disableShowingAnimatedImages - ? getStaticImageUrl(this.user.avatarUrl) - : this.user.avatarUrl; - }, - }, - watch: { - 'user.avatarBlurhash'() { - if (this.$el == null) return; - this.$el.style.color = extractAvgColorFromBlurhash(this.user.avatarBlurhash); - } - }, - mounted() { - this.$el.style.color = extractAvgColorFromBlurhash(this.user.avatarBlurhash); - }, - methods: { - onClick(e) { - this.$emit('click', e); - }, - acct, - userPage - } +const props = withDefaults(defineProps<{ + user: misskey.entities.User; + target?: string | null; + disableLink?: boolean; + disablePreview?: boolean; + showIndicator?: boolean; +}>(), { + target: null, + disableLink: false, + disablePreview: false, + showIndicator: false, +}); + +const emit = defineEmits<{ + (e: 'click', ev: MouseEvent): void; +}>(); + +const url = $computed(() => defaultStore.state.disableShowingAnimatedImages + ? getStaticImageUrl(props.user.avatarUrl) + : props.user.avatarUrl); + +function onClick(ev: MouseEvent) { + emit('click', ev); +} + +let color = $ref(); + +watch(() => props.user.avatarBlurhash, () => { + color = extractAvgColorFromBlurhash(props.user.avatarBlurhash); +}, { + immediate: true, }); </script> diff --git a/packages/client/src/components/global/error.vue b/packages/client/src/components/global/error.vue index d759186167..98b96fb414 100644 --- a/packages/client/src/components/global/error.vue +++ b/packages/client/src/components/global/error.vue @@ -8,19 +8,8 @@ </transition> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> import MkButton from '@/components/ui/button.vue'; - -export default defineComponent({ - components: { - MkButton, - }, - data() { - return { - }; - }, -}); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/global/loading.vue b/packages/client/src/components/global/loading.vue index 7bde53c12e..43ea1395ed 100644 --- a/packages/client/src/components/global/loading.vue +++ b/packages/client/src/components/global/loading.vue @@ -4,27 +4,17 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; -export default defineComponent({ - props: { - inline: { - type: Boolean, - required: false, - default: false - }, - colored: { - type: Boolean, - required: false, - default: true - }, - mini: { - type: Boolean, - required: false, - default: false - }, - } +const props = withDefaults(defineProps<{ + inline?: boolean; + colored?: boolean; + mini?: boolean; +}>(), { + inline: false, + colored: true, + mini: false, }); </script> diff --git a/packages/client/src/components/global/misskey-flavored-markdown.vue b/packages/client/src/components/global/misskey-flavored-markdown.vue index ab20404909..243d8614ba 100644 --- a/packages/client/src/components/global/misskey-flavored-markdown.vue +++ b/packages/client/src/components/global/misskey-flavored-markdown.vue @@ -1,15 +1,23 @@ <template> -<mfm-core v-bind="$attrs" class="havbbuyv" :class="{ nowrap: $attrs['nowrap'] }"/> +<MfmCore :text="text" :plain="plain" :nowrap="nowrap" :author="author" :customEmojis="customEmojis" :isNote="isNote" class="havbbuyv" :class="{ nowrap }"/> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MfmCore from '@/components/mfm'; -export default defineComponent({ - components: { - MfmCore - } +const props = withDefaults(defineProps<{ + text: string; + plain?: boolean; + nowrap?: boolean; + author?: any; + customEmojis?: any; + isNote?: boolean; +}>(), { + plain: false, + nowrap: false, + author: null, + isNote: true, }); </script> diff --git a/packages/client/src/components/global/spacer.vue b/packages/client/src/components/global/spacer.vue index e2f1d1aec7..8a1d7a4e8a 100644 --- a/packages/client/src/components/global/spacer.vue +++ b/packages/client/src/components/global/spacer.vue @@ -40,7 +40,7 @@ export default defineComponent({ return; } - if (rect.width > props.contentMax || rect.width > 500) { + if (rect.width > props.contentMax || (rect.width > 360 && window.innerWidth > 400)) { margin.value = props.marginMax; } else { margin.value = props.marginMin; diff --git a/packages/client/src/components/global/sticky-container.vue b/packages/client/src/components/global/sticky-container.vue index 859b2c1d73..89d397f082 100644 --- a/packages/client/src/components/global/sticky-container.vue +++ b/packages/client/src/components/global/sticky-container.vue @@ -45,7 +45,7 @@ export default defineComponent({ calc(); const observer = new MutationObserver(() => { - setTimeout(() => { + window.setTimeout(() => { calc(); }, 100); }); diff --git a/packages/client/src/components/global/time.vue b/packages/client/src/components/global/time.vue index 6a330a2307..d2788264c5 100644 --- a/packages/client/src/components/global/time.vue +++ b/packages/client/src/components/global/time.vue @@ -1,73 +1,57 @@ <template> <time :title="absolute"> - <template v-if="mode == 'relative'">{{ relative }}</template> - <template v-else-if="mode == 'absolute'">{{ absolute }}</template> - <template v-else-if="mode == 'detail'">{{ absolute }} ({{ relative }})</template> + <template v-if="mode === 'relative'">{{ relative }}</template> + <template v-else-if="mode === 'absolute'">{{ absolute }}</template> + <template v-else-if="mode === 'detail'">{{ absolute }} ({{ relative }})</template> </time> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onUnmounted } from 'vue'; +import { i18n } from '@/i18n'; -export default defineComponent({ - props: { - time: { - type: [Date, String], - required: true - }, - mode: { - type: String, - default: 'relative' - } - }, - data() { - return { - tickId: null, - now: new Date() - }; - }, - computed: { - _time(): Date { - return typeof this.time == 'string' ? new Date(this.time) : this.time; - }, - absolute(): string { - return this._time.toLocaleString(); - }, - relative(): string { - const time = this._time; - const ago = (this.now.getTime() - time.getTime()) / 1000/*ms*/; - return ( - ago >= 31536000 ? this.$t('_ago.yearsAgo', { n: (~~(ago / 31536000)).toString() }) : - ago >= 2592000 ? this.$t('_ago.monthsAgo', { n: (~~(ago / 2592000)).toString() }) : - ago >= 604800 ? this.$t('_ago.weeksAgo', { n: (~~(ago / 604800)).toString() }) : - ago >= 86400 ? this.$t('_ago.daysAgo', { n: (~~(ago / 86400)).toString() }) : - ago >= 3600 ? this.$t('_ago.hoursAgo', { n: (~~(ago / 3600)).toString() }) : - ago >= 60 ? this.$t('_ago.minutesAgo', { n: (~~(ago / 60)).toString() }) : - ago >= 10 ? this.$t('_ago.secondsAgo', { n: (~~(ago % 60)).toString() }) : - ago >= -1 ? this.$ts._ago.justNow : - ago < -1 ? this.$ts._ago.future : - this.$ts._ago.unknown); - } - }, - created() { - if (this.mode == 'relative' || this.mode == 'detail') { - this.tickId = window.requestAnimationFrame(this.tick); - } - }, - unmounted() { - if (this.mode === 'relative' || this.mode === 'detail') { - window.clearTimeout(this.tickId); - } - }, - methods: { - tick() { - // TODO: パフォーマンス向上のため、このコンポーネントが画面内に表示されている場合のみ更新する - this.now = new Date(); +const props = withDefaults(defineProps<{ + time: Date | string; + mode?: 'relative' | 'absolute' | 'detail'; +}>(), { + mode: 'relative', +}); + +const _time = typeof props.time == 'string' ? new Date(props.time) : props.time; +const absolute = _time.toLocaleString(); - this.tickId = setTimeout(() => { - window.requestAnimationFrame(this.tick); - }, 10000); - } - } +let now = $ref(new Date()); +const relative = $computed(() => { + const ago = (now.getTime() - _time.getTime()) / 1000/*ms*/; + return ( + ago >= 31536000 ? i18n.t('_ago.yearsAgo', { n: (~~(ago / 31536000)).toString() }) : + ago >= 2592000 ? i18n.t('_ago.monthsAgo', { n: (~~(ago / 2592000)).toString() }) : + ago >= 604800 ? i18n.t('_ago.weeksAgo', { n: (~~(ago / 604800)).toString() }) : + ago >= 86400 ? i18n.t('_ago.daysAgo', { n: (~~(ago / 86400)).toString() }) : + ago >= 3600 ? i18n.t('_ago.hoursAgo', { n: (~~(ago / 3600)).toString() }) : + ago >= 60 ? i18n.t('_ago.minutesAgo', { n: (~~(ago / 60)).toString() }) : + ago >= 10 ? i18n.t('_ago.secondsAgo', { n: (~~(ago % 60)).toString() }) : + ago >= -1 ? i18n.locale._ago.justNow : + ago < -1 ? i18n.locale._ago.future : + i18n.locale._ago.unknown); }); + +function tick() { + // TODO: パフォーマンス向上のため、このコンポーネントが画面内に表示されている場合のみ更新する + now = new Date(); + + tickId = window.setTimeout(() => { + window.requestAnimationFrame(tick); + }, 10000); +} + +let tickId: number; + +if (props.mode === 'relative' || props.mode === 'detail') { + tickId = window.requestAnimationFrame(tick); + + onUnmounted(() => { + window.clearTimeout(tickId); + }); +} </script> diff --git a/packages/client/src/components/global/user-name.vue b/packages/client/src/components/global/user-name.vue index bc93a8ea30..090de3df30 100644 --- a/packages/client/src/components/global/user-name.vue +++ b/packages/client/src/components/global/user-name.vue @@ -2,19 +2,14 @@ <Mfm :text="user.name || user.username" :plain="true" :nowrap="nowrap" :custom-emojis="user.emojis"/> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; +import * as misskey from 'misskey-js'; -export default defineComponent({ - props: { - user: { - type: Object, - required: true - }, - nowrap: { - type: Boolean, - default: true - }, - } +const props = withDefaults(defineProps<{ + user: misskey.entities.User; + nowrap?: boolean; +}>(), { + nowrap: true, }); </script> diff --git a/packages/client/src/components/google.vue b/packages/client/src/components/google.vue index a39168b80f..210ca72bfe 100644 --- a/packages/client/src/components/google.vue +++ b/packages/client/src/components/google.vue @@ -5,31 +5,18 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as os from '@/os'; +<script lang="ts" setup> +import { ref } from 'vue'; -export default defineComponent({ - props: { - q: { - type: String, - required: true, - } - }, - data() { - return { - query: null, - }; - }, - mounted() { - this.query = this.q; - }, - methods: { - search() { - window.open(`https://www.google.com/search?q=${this.query}`, '_blank'); - } - } -}); +const props = defineProps<{ + q: string; +}>(); + +const query = ref(props.q); + +const search = () => { + window.open(`https://www.google.com/search?q=${query.value}`, '_blank'); +}; </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/image-viewer.vue b/packages/client/src/components/image-viewer.vue index 8584b91a61..c39076df16 100644 --- a/packages/client/src/components/image-viewer.vue +++ b/packages/client/src/components/image-viewer.vue @@ -1,8 +1,8 @@ <template> -<MkModal ref="modal" :z-priority="'middle'" @click="$refs.modal.close()" @closed="$emit('closed')"> +<MkModal ref="modal" :z-priority="'middle'" @click="modal.close()" @closed="emit('closed')"> <div class="xubzgfga"> <header>{{ image.name }}</header> - <img :src="image.url" :alt="image.comment" :title="image.comment" @click="$refs.modal.close()"/> + <img :src="image.url" :alt="image.comment" :title="image.comment" @click="modal.close()"/> <footer> <span>{{ image.type }}</span> <span>{{ bytes(image.size) }}</span> @@ -12,31 +12,23 @@ </MkModal> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; +import * as misskey from 'misskey-js'; import bytes from '@/filters/bytes'; import number from '@/filters/number'; import MkModal from '@/components/ui/modal.vue'; -export default defineComponent({ - components: { - MkModal, - }, - - props: { - image: { - type: Object, - required: true - }, - }, +const props = withDefaults(defineProps<{ + image: misskey.entities.DriveFile; +}>(), { +}); - emits: ['closed'], +const emit = defineEmits<{ + (e: 'closed'): void; +}>(); - methods: { - bytes, - number, - } -}); +const modal = $ref<InstanceType<typeof MkModal>>(); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/img-with-blurhash.vue b/packages/client/src/components/img-with-blurhash.vue index a000c699b6..06ad764403 100644 --- a/packages/client/src/components/img-with-blurhash.vue +++ b/packages/client/src/components/img-with-blurhash.vue @@ -5,67 +5,43 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted } from 'vue'; import { decode } from 'blurhash'; -export default defineComponent({ - props: { - src: { - type: String, - required: false, - default: null - }, - hash: { - type: String, - required: true - }, - alt: { - type: String, - required: false, - default: '', - }, - title: { - type: String, - required: false, - default: null, - }, - size: { - type: Number, - required: false, - default: 64 - }, - cover: { - type: Boolean, - required: false, - default: true, - } - }, +const props = withDefaults(defineProps<{ + src?: string | null; + hash: string; + alt?: string; + title?: string | null; + size?: number; + cover?: boolean; +}>(), { + src: null, + alt: '', + title: null, + size: 64, + cover: true, +}); - data() { - return { - loaded: false, - }; - }, +const canvas = $ref<HTMLCanvasElement>(); +let loaded = $ref(false); - mounted() { - this.draw(); - }, +function draw() { + if (props.hash == null) return; + const pixels = decode(props.hash, props.size, props.size); + const ctx = canvas.getContext('2d'); + const imageData = ctx!.createImageData(props.size, props.size); + imageData.data.set(pixels); + ctx!.putImageData(imageData, 0, 0); +} - methods: { - draw() { - if (this.hash == null) return; - const pixels = decode(this.hash, this.size, this.size); - const ctx = (this.$refs.canvas as HTMLCanvasElement).getContext('2d'); - const imageData = ctx!.createImageData(this.size, this.size); - imageData.data.set(pixels); - ctx!.putImageData(imageData, 0, 0); - }, +function onLoad() { + loaded = true; +} - onLoad() { - this.loaded = true; - } - } +onMounted(() => { + draw(); }); </script> diff --git a/packages/client/src/components/instance-stats.vue b/packages/client/src/components/instance-stats.vue index bc62998a4a..409c3a49ca 100644 --- a/packages/client/src/components/instance-stats.vue +++ b/packages/client/src/components/instance-stats.vue @@ -1,5 +1,5 @@ <template> -<div class="zbcjwnqg" style="margin-top: -8px;"> +<div class="zbcjwnqg"> <div class="selects" style="display: flex;"> <MkSelect v-model="chartSrc" style="margin: 0; flex: 1;"> <optgroup :label="$ts.federation"> @@ -29,16 +29,16 @@ <option value="day">{{ $ts.perDay }}</option> </MkSelect> </div> - <MkChart :src="chartSrc" :span="chartSpan" :limit="chartLimit" :detailed="detailed"></MkChart> + <div class="chart"> + <MkChart :src="chartSrc" :span="chartSpan" :limit="chartLimit" :detailed="detailed"></MkChart> + </div> </div> </template> <script lang="ts"> -import { defineComponent, onMounted, ref, watch } from 'vue'; +import { defineComponent, ref } from 'vue'; import MkSelect from '@/components/form/select.vue'; import MkChart from '@/components/chart.vue'; -import * as os from '@/os'; -import { defaultStore } from '@/store'; export default defineComponent({ components: { @@ -74,7 +74,10 @@ export default defineComponent({ <style lang="scss" scoped> .zbcjwnqg { > .selects { - padding: 8px 16px 0 16px; + } + + > .chart { + padding: 8px 0 0 0; } } </style> diff --git a/packages/client/src/components/instance-ticker.vue b/packages/client/src/components/instance-ticker.vue index 1ce5a1c2c1..77fd8bb344 100644 --- a/packages/client/src/components/instance-ticker.vue +++ b/packages/client/src/components/instance-ticker.vue @@ -1,41 +1,22 @@ <template> <div class="hpaizdrt" :style="bg"> - <img v-if="info.faviconUrl" class="icon" :src="info.faviconUrl"/> - <span class="name">{{ info.name }}</span> + <img v-if="instance.faviconUrl" class="icon" :src="instance.faviconUrl"/> + <span class="name">{{ instance.name }}</span> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import { instanceName } from '@/config'; +<script lang="ts" setup> +import { } from 'vue'; -export default defineComponent({ - props: { - instance: { - type: Object, - required: false - }, - }, +const props = defineProps<{ + instance: any; // TODO +}>(); - data() { - return { - info: this.instance || { - faviconUrl: '/favicon.ico', - name: instanceName, - themeColor: (document.querySelector('meta[name="theme-color-orig"]') as HTMLMetaElement)?.content - } - } - }, +const themeColor = props.instance.themeColor || '#777777'; - computed: { - bg(): any { - const themeColor = this.info.themeColor || '#777777'; - return { - background: `linear-gradient(90deg, ${themeColor}, ${themeColor + '00'})` - }; - } - } -}); +const bg = { + background: `linear-gradient(90deg, ${themeColor}, ${themeColor + '00'})` +}; </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/key-value.vue b/packages/client/src/components/key-value.vue index 6a9a948ce9..da98abd77c 100644 --- a/packages/client/src/components/key-value.vue +++ b/packages/client/src/components/key-value.vue @@ -1,5 +1,5 @@ <template> -<div class="alqyeyti"> +<div class="alqyeyti" :class="{ oneline }"> <div class="key"> <slot name="key"></slot> </div> @@ -22,6 +22,11 @@ export default defineComponent({ required: false, default: null, }, + oneline: { + type: Boolean, + required: false, + default: false, + }, }, setup(props) { @@ -39,10 +44,30 @@ export default defineComponent({ <style lang="scss" scoped> .alqyeyti { + > .key, > .value { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + > .key { font-size: 0.85em; padding: 0 0 0.25em 0; opacity: 0.75; } + + &.oneline { + display: flex; + + > .key { + width: 30%; + font-size: 1em; + padding: 0 8px 0 0; + } + + > .value { + width: 70%; + } + } } </style> diff --git a/packages/client/src/components/link.vue b/packages/client/src/components/link.vue index 8b8cde6510..317c931cec 100644 --- a/packages/client/src/components/link.vue +++ b/packages/client/src/components/link.vue @@ -1,82 +1,36 @@ <template> -<component :is="self ? 'MkA' : 'a'" class="xlcxczvw _link" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target" +<component :is="self ? 'MkA' : 'a'" ref="el" class="xlcxczvw _link" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target" :title="url" - @mouseover="onMouseover" - @mouseleave="onMouseleave" > <slot></slot> <i v-if="target === '_blank'" class="fas fa-external-link-square-alt icon"></i> </component> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import { url as local } from '@/config'; -import { isTouchUsing } from '@/scripts/touch'; +import { useTooltip } from '@/scripts/use-tooltip'; import * as os from '@/os'; -export default defineComponent({ - props: { - url: { - type: String, - required: true, - }, - rel: { - type: String, - required: false, - } - }, - data() { - const self = this.url.startsWith(local); - return { - local, - self: self, - attr: self ? 'to' : 'href', - target: self ? null : '_blank', - showTimer: null, - hideTimer: null, - checkTimer: null, - close: null, - }; - }, - methods: { - async showPreview() { - if (!document.body.contains(this.$el)) return; - if (this.close) return; +const props = withDefaults(defineProps<{ + url: string; + rel?: null | string; +}>(), { +}); - const { dispose } = await os.popup(import('@/components/url-preview-popup.vue'), { - url: this.url, - source: this.$el - }); +const self = props.url.startsWith(local); +const attr = self ? 'to' : 'href'; +const target = self ? null : '_blank'; - this.close = () => { - dispose(); - }; +const el = $ref(); - this.checkTimer = setInterval(() => { - if (!document.body.contains(this.$el)) this.closePreview(); - }, 1000); - }, - closePreview() { - if (this.close) { - clearInterval(this.checkTimer); - this.close(); - this.close = null; - } - }, - onMouseover() { - if (isTouchUsing) return; - clearTimeout(this.showTimer); - clearTimeout(this.hideTimer); - this.showTimer = setTimeout(this.showPreview, 500); - }, - onMouseleave() { - if (isTouchUsing) return; - clearTimeout(this.showTimer); - clearTimeout(this.hideTimer); - this.hideTimer = setTimeout(this.closePreview, 500); - } - } +useTooltip($$(el), (showing) => { + os.popup(import('@/components/url-preview-popup.vue'), { + showing, + url: props.url, + source: el, + }, {}, 'closed'); }); </script> diff --git a/packages/client/src/components/media-banner.vue b/packages/client/src/components/media-banner.vue index 9dbfe3d0c6..5093f11e97 100644 --- a/packages/client/src/components/media-banner.vue +++ b/packages/client/src/components/media-banner.vue @@ -6,7 +6,7 @@ <span>{{ $ts.clickToShow }}</span> </div> <div v-else-if="media.type.startsWith('audio') && media.type !== 'audio/midi'" class="audio"> - <audio ref="audio" + <audio ref="audioEl" class="audio" :src="media.url" :title="media.name" @@ -25,34 +25,26 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as os from '@/os'; +<script lang="ts" setup> +import { onMounted } from 'vue'; +import * as misskey from 'misskey-js'; import { ColdDeviceStorage } from '@/store'; -export default defineComponent({ - props: { - media: { - type: Object, - required: true - } - }, - data() { - return { - hide: true, - }; - }, - mounted() { - const audioTag = this.$refs.audio as HTMLAudioElement; - if (audioTag) audioTag.volume = ColdDeviceStorage.get('mediaVolume'); - }, - methods: { - volumechange() { - const audioTag = this.$refs.audio as HTMLAudioElement; - ColdDeviceStorage.set('mediaVolume', audioTag.volume); - }, - }, -}) +const props = withDefaults(defineProps<{ + media: misskey.entities.DriveFile; +}>(), { +}); + +const audioEl = $ref<HTMLAudioElement | null>(); +let hide = $ref(true); + +function volumechange() { + if (audioEl) ColdDeviceStorage.set('mediaVolume', audioEl.volume); +} + +onMounted(() => { + if (audioEl) audioEl.volume = ColdDeviceStorage.get('mediaVolume'); +}); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/media-list.vue b/packages/client/src/components/media-list.vue index 2970d06c97..efcbb12922 100644 --- a/packages/client/src/components/media-list.vue +++ b/packages/client/src/components/media-list.vue @@ -3,7 +3,7 @@ <XBanner v-for="media in mediaList.filter(media => !previewable(media))" :key="media.id" :media="media"/> <div v-if="mediaList.filter(media => previewable(media)).length > 0" class="gird-container"> <div ref="gallery" :data-count="mediaList.filter(media => previewable(media)).length"> - <template v-for="media in mediaList"> + <template v-for="media in mediaList.filter(media => previewable(media))"> <XVideo v-if="media.type.startsWith('video')" :key="media.id" :video="media"/> <XImage v-else-if="media.type.startsWith('image')" :key="media.id" class="image" :data-id="media.id" :image="media" :raw="raw"/> </template> @@ -22,6 +22,7 @@ import XBanner from './media-banner.vue'; import XImage from './media-image.vue'; import XVideo from './media-video.vue'; import * as os from '@/os'; +import { FILE_TYPE_BROWSERSAFE } from '@/const'; import { defaultStore } from '@/store'; export default defineComponent({ @@ -44,18 +45,23 @@ export default defineComponent({ onMounted(() => { const lightbox = new PhotoSwipeLightbox({ - dataSource: props.mediaList.filter(media => media.type.startsWith('image')).map(media => { - const item = { - src: media.url, - w: media.properties.width, - h: media.properties.height, - alt: media.name, - }; - if (media.properties.orientation != null && media.properties.orientation >= 5) { - [item.w, item.h] = [item.h, item.w]; - } - return item; - }), + dataSource: props.mediaList + .filter(media => { + if (media.type === 'image/svg+xml') return true; // svgのwebpublicはpngなのでtrue + return media.type.startsWith('image') && FILE_TYPE_BROWSERSAFE.includes(media.type); + }) + .map(media => { + const item = { + src: media.url, + w: media.properties.width, + h: media.properties.height, + alt: media.name, + }; + if (media.properties.orientation != null && media.properties.orientation >= 5) { + [item.w, item.h] = [item.h, item.w]; + } + return item; + }), gallery: gallery.value, children: '.image', thumbSelector: '.image', @@ -99,7 +105,9 @@ export default defineComponent({ }); const previewable = (file: misskey.entities.DriveFile): boolean => { - return file.type.startsWith('video') || file.type.startsWith('image'); + if (file.type === 'image/svg+xml') return true; // svgのwebpublic/thumbnailはpngなのでtrue + // FILE_TYPE_BROWSERSAFEに適合しないものはブラウザで表示するのに不適切 + return (file.type.startsWith('video') || file.type.startsWith('image')) && FILE_TYPE_BROWSERSAFE.includes(file.type); }; return { diff --git a/packages/client/src/components/media-video.vue b/packages/client/src/components/media-video.vue index a0dc57b657..680eb27e64 100644 --- a/packages/client/src/components/media-video.vue +++ b/packages/client/src/components/media-video.vue @@ -22,26 +22,16 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as os from '@/os'; +<script lang="ts" setup> +import { ref } from 'vue'; +import * as misskey from 'misskey-js'; +import { defaultStore } from '@/store'; -export default defineComponent({ - props: { - video: { - type: Object, - required: true - } - }, - data() { - return { - hide: true, - }; - }, - created() { - this.hide = (this.$store.state.nsfw === 'force') ? true : this.video.isSensitive && (this.$store.state.nsfw !== 'ignore'); - }, -}); +const props = defineProps<{ + video: misskey.entities.DriveFile; +}>(); + +const hide = ref((defaultStore.state.nsfw === 'force') ? true : props.video.isSensitive && (defaultStore.state.nsfw !== 'ignore')); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/mini-chart.vue b/packages/client/src/components/mini-chart.vue index 2eb9ae8cbe..8c74eae876 100644 --- a/packages/client/src/components/mini-chart.vue +++ b/packages/client/src/components/mini-chart.vue @@ -63,10 +63,10 @@ export default defineComponent({ this.draw(); // Vueが何故かWatchを発動させない場合があるので - this.clock = setInterval(this.draw, 1000); + this.clock = window.setInterval(this.draw, 1000); }, beforeUnmount() { - clearInterval(this.clock); + window.clearInterval(this.clock); }, methods: { draw() { diff --git a/packages/client/src/components/modal-page-window.vue b/packages/client/src/components/modal-page-window.vue index 3de1980820..2e17d5d030 100644 --- a/packages/client/src/components/modal-page-window.vue +++ b/packages/client/src/components/modal-page-window.vue @@ -153,8 +153,8 @@ export default defineComponent({ this.$refs.window.close(); }, - onContextmenu(e) { - os.contextMenu(this.contextmenu, e); + onContextmenu(ev: MouseEvent) { + os.contextMenu(this.contextmenu, ev); } }, }); diff --git a/packages/client/src/components/note-detailed.vue b/packages/client/src/components/note-detailed.vue index 55a02f1e73..a3b30f726e 100644 --- a/packages/client/src/components/note-detailed.vue +++ b/packages/client/src/components/note-detailed.vue @@ -4,12 +4,13 @@ v-show="!isDeleted" v-hotkey="keymap" v-size="{ max: [500, 450, 350, 300] }" + ref="el" class="lxwezrsl _block" :tabindex="!isDeleted ? '-1' : null" :class="{ renote: isRenote }" > - <XSub v-for="note in conversation" :key="note.id" class="reply-to-more" :note="note"/> - <XSub v-if="appearNote.reply" :note="appearNote.reply" class="reply-to"/> + <MkNoteSub v-for="note in conversation" :key="note.id" class="reply-to-more" :note="note"/> + <MkNoteSub v-if="appearNote.reply" :note="appearNote.reply" class="reply-to"/> <div v-if="isRenote" class="renote"> <MkAvatar class="avatar" :user="note.user"/> <i class="fas fa-retweet"></i> @@ -107,7 +108,7 @@ </footer> </div> </article> - <XSub v-for="note in replies" :key="note.id" :note="note" class="reply" :detail="true"/> + <MkNoteSub v-for="note in replies" :key="note.id" :note="note" class="reply" :detail="true"/> </div> <div v-else class="_panel muted" @click="muted = false"> <I18n :src="$ts.userSaysSomething" tag="small"> @@ -120,764 +121,171 @@ </div> </template> -<script lang="ts"> -import { defineAsyncComponent, defineComponent, markRaw } from 'vue'; +<script lang="ts" setup> +import { computed, inject, onMounted, onUnmounted, reactive, ref } from 'vue'; import * as mfm from 'mfm-js'; -import { sum } from '@/scripts/array'; -import XSub from './note.sub.vue'; -import XNoteHeader from './note-header.vue'; +import * as misskey from 'misskey-js'; +import MkNoteSub from './MkNoteSub.vue'; import XNoteSimple from './note-simple.vue'; import XReactionsViewer from './reactions-viewer.vue'; import XMediaList from './media-list.vue'; import XCwButton from './cw-button.vue'; import XPoll from './poll.vue'; import XRenoteButton from './renote-button.vue'; +import MkUrlPreview from '@/components/url-preview.vue'; +import MkInstanceTicker from '@/components/instance-ticker.vue'; import { pleaseLogin } from '@/scripts/please-login'; -import { focusPrev, focusNext } from '@/scripts/focus'; -import { url } from '@/config'; -import copyToClipboard from '@/scripts/copy-to-clipboard'; import { checkWordMute } from '@/scripts/check-word-mute'; import { userPage } from '@/filters/user'; import { notePage } from '@/filters/note'; import * as os from '@/os'; -import { noteActions, noteViewInterruptors } from '@/store'; +import { defaultStore, noteViewInterruptors } from '@/store'; import { reactionPicker } from '@/scripts/reaction-picker'; import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm'; +import { $i } from '@/account'; +import { i18n } from '@/i18n'; +import { getNoteMenu } from '@/scripts/get-note-menu'; +import { useNoteCapture } from '@/scripts/use-note-capture'; -// TODO: note.vueとほぼ同じなので共通化したい -export default defineComponent({ - components: { - XSub, - XNoteHeader, - XNoteSimple, - XReactionsViewer, - XMediaList, - XCwButton, - XPoll, - XRenoteButton, - MkUrlPreview: defineAsyncComponent(() => import('@/components/url-preview.vue')), - MkInstanceTicker: defineAsyncComponent(() => import('@/components/instance-ticker.vue')), - }, +const props = defineProps<{ + note: misskey.entities.Note; + pinned?: boolean; +}>(); - inject: { - inChannel: { - default: null - }, - }, +const inChannel = inject('inChannel', null); - props: { - note: { - type: Object, - required: true - }, - }, +const isRenote = ( + props.note.renote != null && + props.note.text == null && + props.note.fileIds.length === 0 && + props.note.poll == null +); - emits: ['update:note'], +const el = ref<HTMLElement>(); +const menuButton = ref<HTMLElement>(); +const renoteButton = ref<InstanceType<typeof XRenoteButton>>(); +const renoteTime = ref<HTMLElement>(); +const reactButton = ref<HTMLElement>(); +let appearNote = $ref(isRenote ? props.note.renote as misskey.entities.Note : props.note); +const isMyRenote = $i && ($i.id === props.note.userId); +const showContent = ref(false); +const isDeleted = ref(false); +const muted = ref(checkWordMute(appearNote, $i, defaultStore.state.mutedWords)); +const translation = ref(null); +const translating = ref(false); +const urls = appearNote.text ? extractUrlFromMfm(mfm.parse(appearNote.text)) : null; +const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.user.instance); +const conversation = ref<misskey.entities.Note[]>([]); +const replies = ref<misskey.entities.Note[]>([]); - data() { - return { - connection: null, - conversation: [], - replies: [], - showContent: false, - isDeleted: false, - muted: false, - translation: null, - translating: false, - notePage, - }; - }, +const keymap = { + 'r': () => reply(true), + 'e|a|plus': () => react(true), + 'q': () => renoteButton.value.renote(true), + 'esc': blur, + 'm|o': () => menu(true), + 's': () => showContent.value != showContent.value, +}; - computed: { - rs() { - return this.$store.state.reactions; - }, - keymap(): any { - return { - 'r': () => this.reply(true), - 'e|a|plus': () => this.react(true), - 'q': () => this.$refs.renoteButton.renote(true), - 'f|b': this.favorite, - 'delete|ctrl+d': this.del, - 'ctrl+q': this.renoteDirectly, - 'up|k|shift+tab': this.focusBefore, - 'down|j|tab': this.focusAfter, - 'esc': this.blur, - 'm|o': () => this.menu(true), - 's': this.toggleShowContent, - '1': () => this.reactDirectly(this.rs[0]), - '2': () => this.reactDirectly(this.rs[1]), - '3': () => this.reactDirectly(this.rs[2]), - '4': () => this.reactDirectly(this.rs[3]), - '5': () => this.reactDirectly(this.rs[4]), - '6': () => this.reactDirectly(this.rs[5]), - '7': () => this.reactDirectly(this.rs[6]), - '8': () => this.reactDirectly(this.rs[7]), - '9': () => this.reactDirectly(this.rs[8]), - '0': () => this.reactDirectly(this.rs[9]), - }; - }, - - isRenote(): boolean { - return (this.note.renote && - this.note.text == null && - this.note.fileIds.length == 0 && - this.note.poll == null); - }, - - appearNote(): any { - return this.isRenote ? this.note.renote : this.note; - }, - - isMyNote(): boolean { - return this.$i && (this.$i.id === this.appearNote.userId); - }, - - isMyRenote(): boolean { - return this.$i && (this.$i.id === this.note.userId); - }, - - reactionsCount(): number { - return this.appearNote.reactions - ? sum(Object.values(this.appearNote.reactions)) - : 0; - }, - - urls(): string[] { - if (this.appearNote.text) { - return extractUrlFromMfm(mfm.parse(this.appearNote.text)); - } else { - return null; - } - }, - - showTicker() { - if (this.$store.state.instanceTicker === 'always') return true; - if (this.$store.state.instanceTicker === 'remote' && this.appearNote.user.instance) return true; - return false; - } - }, - - async created() { - if (this.$i) { - this.connection = os.stream; - } - - this.muted = await checkWordMute(this.appearNote, this.$i, this.$store.state.mutedWords); +useNoteCapture({ + appearNote: $$(appearNote), + rootEl: el, +}); - // plugin - if (noteViewInterruptors.length > 0) { - let result = this.note; - for (const interruptor of noteViewInterruptors) { - result = await interruptor.handler(JSON.parse(JSON.stringify(result))); - } - this.$emit('update:note', Object.freeze(result)); - } +function reply(viaKeyboard = false): void { + pleaseLogin(); + os.post({ + reply: appearNote, + animation: !viaKeyboard, + }, () => { + focus(); + }); +} - os.api('notes/children', { - noteId: this.appearNote.id, - limit: 30 - }).then(replies => { - this.replies = replies; +function react(viaKeyboard = false): void { + pleaseLogin(); + blur(); + reactionPicker.show(reactButton.value, reaction => { + os.api('notes/reactions/create', { + noteId: appearNote.id, + reaction: reaction }); + }, () => { + focus(); + }); +} - if (this.appearNote.replyId) { - os.api('notes/conversation', { - noteId: this.appearNote.replyId - }).then(conversation => { - this.conversation = conversation.reverse(); - }); - } - }, - - mounted() { - this.capture(true); - - if (this.$i) { - this.connection.on('_connected_', this.onStreamConnected); - } - }, - - beforeUnmount() { - this.decapture(true); +function undoReact(note): void { + const oldReaction = note.myReaction; + if (!oldReaction) return; + os.api('notes/reactions/delete', { + noteId: note.id + }); +} - if (this.$i) { - this.connection.off('_connected_', this.onStreamConnected); +function onContextmenu(ev: MouseEvent): void { + const isLink = (el: HTMLElement) => { + if (el.tagName === 'A') return true; + if (el.parentElement) { + return isLink(el.parentElement); } - }, - - methods: { - updateAppearNote(v) { - this.$emit('update:note', Object.freeze(this.isRenote ? { - ...this.note, - renote: { - ...this.note.renote, - ...v - } - } : { - ...this.note, - ...v - })); - }, - - readPromo() { - os.api('promo/read', { - noteId: this.appearNote.id - }); - this.isDeleted = true; - }, - - capture(withHandler = false) { - if (this.$i) { - // TODO: このノートがストリーミング経由で流れてきた場合のみ sr する - this.connection.send(document.body.contains(this.$el) ? 'sr' : 's', { id: this.appearNote.id }); - if (withHandler) this.connection.on('noteUpdated', this.onStreamNoteUpdated); - } - }, - - decapture(withHandler = false) { - if (this.$i) { - this.connection.send('un', { - id: this.appearNote.id - }); - if (withHandler) this.connection.off('noteUpdated', this.onStreamNoteUpdated); - } - }, - - onStreamConnected() { - this.capture(); - }, - - onStreamNoteUpdated(data) { - const { type, id, body } = data; - - if (id !== this.appearNote.id) return; + }; + if (isLink(ev.target)) return; + if (window.getSelection().toString() !== '') return; - switch (type) { - case 'reacted': { - const reaction = body.reaction; - - // DeepではなくShallowコピーであることに注意。n.reactions[reaction] = hogeとかしないように(親からもらったデータをミューテートすることになるので) - let n = { - ...this.appearNote, - }; - - if (body.emoji) { - const emojis = this.appearNote.emojis || []; - if (!emojis.includes(body.emoji)) { - n.emojis = [...emojis, body.emoji]; - } - } - - // TODO: reactionsプロパティがない場合ってあったっけ? なければ || {} は消せる - const currentCount = (this.appearNote.reactions || {})[reaction] || 0; - - // Increment the count - n.reactions = { - ...this.appearNote.reactions, - [reaction]: currentCount + 1 - }; - - if (body.userId === this.$i.id) { - n.myReaction = reaction; - } - - this.updateAppearNote(n); - break; - } - - case 'unreacted': { - const reaction = body.reaction; - - // DeepではなくShallowコピーであることに注意。n.reactions[reaction] = hogeとかしないように(親からもらったデータをミューテートすることになるので) - let n = { - ...this.appearNote, - }; - - // TODO: reactionsプロパティがない場合ってあったっけ? なければ || {} は消せる - const currentCount = (this.appearNote.reactions || {})[reaction] || 0; - - // Decrement the count - n.reactions = { - ...this.appearNote.reactions, - [reaction]: Math.max(0, currentCount - 1) - }; - - if (body.userId === this.$i.id) { - n.myReaction = null; - } - - this.updateAppearNote(n); - break; - } - - case 'pollVoted': { - const choice = body.choice; - - // DeepではなくShallowコピーであることに注意。n.reactions[reaction] = hogeとかしないように(親からもらったデータをミューテートすることになるので) - let n = { - ...this.appearNote, - }; - - const choices = [...this.appearNote.poll.choices]; - choices[choice] = { - ...choices[choice], - votes: choices[choice].votes + 1, - ...(body.userId === this.$i.id ? { - isVoted: true - } : {}) - }; - - n.poll = { - ...this.appearNote.poll, - choices: choices - }; - - this.updateAppearNote(n); - break; - } - - case 'deleted': { - this.isDeleted = true; - break; - } - } - }, - - reply(viaKeyboard = false) { - pleaseLogin(); - os.post({ - reply: this.appearNote, - animation: !viaKeyboard, - }, () => { - this.focus(); - }); - }, - - renoteDirectly() { - os.apiWithDialog('notes/create', { - renoteId: this.appearNote.id - }, undefined, (res: any) => { - os.alert({ - type: 'success', - text: this.$ts.renoted, - }); - }, (e: Error) => { - if (e.id === 'b5c90186-4ab0-49c8-9bba-a1f76c282ba4') { - os.alert({ - type: 'error', - text: this.$ts.cantRenote, - }); - } else if (e.id === 'fd4cc33e-2a37-48dd-99cc-9b806eb2031a') { - os.alert({ - type: 'error', - text: this.$ts.cantReRenote, - }); - } - }); - }, - - react(viaKeyboard = false) { - pleaseLogin(); - this.blur(); - reactionPicker.show(this.$refs.reactButton, reaction => { - os.api('notes/reactions/create', { - noteId: this.appearNote.id, - reaction: reaction - }); - }, () => { - this.focus(); - }); - }, - - reactDirectly(reaction) { - os.api('notes/reactions/create', { - noteId: this.appearNote.id, - reaction: reaction - }); - }, - - undoReact(note) { - const oldReaction = note.myReaction; - if (!oldReaction) return; - os.api('notes/reactions/delete', { - noteId: note.id - }); - }, - - favorite() { - pleaseLogin(); - os.apiWithDialog('notes/favorites/create', { - noteId: this.appearNote.id - }, undefined, (res: any) => { - os.alert({ - type: 'success', - text: this.$ts.favorited, - }); - }, (e: Error) => { - if (e.id === 'a402c12b-34dd-41d2-97d8-4d2ffd96a1a6') { - os.alert({ - type: 'error', - text: this.$ts.alreadyFavorited, - }); - } else if (e.id === '6dd26674-e060-4816-909a-45ba3f4da458') { - os.alert({ - type: 'error', - text: this.$ts.cantFavorite, - }); - } - }); - }, - - del() { - os.confirm({ - type: 'warning', - text: this.$ts.noteDeleteConfirm, - }).then(({ canceled }) => { - if (canceled) return; - - os.api('notes/delete', { - noteId: this.appearNote.id - }); - }); - }, - - delEdit() { - os.confirm({ - type: 'warning', - text: this.$ts.deleteAndEditConfirm, - }).then(({ canceled }) => { - if (canceled) return; - - os.api('notes/delete', { - noteId: this.appearNote.id - }); - - os.post({ initialNote: this.appearNote, renote: this.appearNote.renote, reply: this.appearNote.reply, channel: this.appearNote.channel }); - }); - }, - - toggleFavorite(favorite: boolean) { - os.apiWithDialog(favorite ? 'notes/favorites/create' : 'notes/favorites/delete', { - noteId: this.appearNote.id - }); - }, - - toggleWatch(watch: boolean) { - os.apiWithDialog(watch ? 'notes/watching/create' : 'notes/watching/delete', { - noteId: this.appearNote.id - }); - }, - - toggleThreadMute(mute: boolean) { - os.apiWithDialog(mute ? 'notes/thread-muting/create' : 'notes/thread-muting/delete', { - noteId: this.appearNote.id - }); - }, - - getMenu() { - let menu; - if (this.$i) { - const statePromise = os.api('notes/state', { - noteId: this.appearNote.id - }); - - menu = [{ - icon: 'fas fa-copy', - text: this.$ts.copyContent, - action: this.copyContent - }, { - icon: 'fas fa-link', - text: this.$ts.copyLink, - action: this.copyLink - }, (this.appearNote.url || this.appearNote.uri) ? { - icon: 'fas fa-external-link-square-alt', - text: this.$ts.showOnRemote, - action: () => { - window.open(this.appearNote.url || this.appearNote.uri, '_blank'); - } - } : undefined, - { - icon: 'fas fa-share-alt', - text: this.$ts.share, - action: this.share - }, - this.$instance.translatorAvailable ? { - icon: 'fas fa-language', - text: this.$ts.translate, - action: this.translate - } : undefined, - null, - statePromise.then(state => state.isFavorited ? { - icon: 'fas fa-star', - text: this.$ts.unfavorite, - action: () => this.toggleFavorite(false) - } : { - icon: 'fas fa-star', - text: this.$ts.favorite, - action: () => this.toggleFavorite(true) - }), - { - icon: 'fas fa-paperclip', - text: this.$ts.clip, - action: () => this.clip() - }, - (this.appearNote.userId != this.$i.id) ? statePromise.then(state => state.isWatching ? { - icon: 'fas fa-eye-slash', - text: this.$ts.unwatch, - action: () => this.toggleWatch(false) - } : { - icon: 'fas fa-eye', - text: this.$ts.watch, - action: () => this.toggleWatch(true) - }) : undefined, - statePromise.then(state => state.isMutedThread ? { - icon: 'fas fa-comment-slash', - text: this.$ts.unmuteThread, - action: () => this.toggleThreadMute(false) - } : { - icon: 'fas fa-comment-slash', - text: this.$ts.muteThread, - action: () => this.toggleThreadMute(true) - }), - this.appearNote.userId == this.$i.id ? (this.$i.pinnedNoteIds || []).includes(this.appearNote.id) ? { - icon: 'fas fa-thumbtack', - text: this.$ts.unpin, - action: () => this.togglePin(false) - } : { - icon: 'fas fa-thumbtack', - text: this.$ts.pin, - action: () => this.togglePin(true) - } : undefined, - /*...(this.$i.isModerator || this.$i.isAdmin ? [ - null, - { - icon: 'fas fa-bullhorn', - text: this.$ts.promote, - action: this.promote - }] - : [] - ),*/ - ...(this.appearNote.userId != this.$i.id ? [ - null, - { - icon: 'fas fa-exclamation-circle', - text: this.$ts.reportAbuse, - action: () => { - const u = `${url}/notes/${this.appearNote.id}`; - os.popup(import('@/components/abuse-report-window.vue'), { - user: this.appearNote.user, - initialComment: `Note: ${u}\n-----\n` - }, {}, 'closed'); - } - }] - : [] - ), - ...(this.appearNote.userId == this.$i.id || this.$i.isModerator || this.$i.isAdmin ? [ - null, - this.appearNote.userId == this.$i.id ? { - icon: 'fas fa-edit', - text: this.$ts.deleteAndEdit, - action: this.delEdit - } : undefined, - { - icon: 'fas fa-trash-alt', - text: this.$ts.delete, - danger: true, - action: this.del - }] - : [] - )] - .filter(x => x !== undefined); - } else { - menu = [{ - icon: 'fas fa-copy', - text: this.$ts.copyContent, - action: this.copyContent - }, { - icon: 'fas fa-link', - text: this.$ts.copyLink, - action: this.copyLink - }, (this.appearNote.url || this.appearNote.uri) ? { - icon: 'fas fa-external-link-square-alt', - text: this.$ts.showOnRemote, - action: () => { - window.open(this.appearNote.url || this.appearNote.uri, '_blank'); - } - } : undefined] - .filter(x => x !== undefined); - } - - if (noteActions.length > 0) { - menu = menu.concat([null, ...noteActions.map(action => ({ - icon: 'fas fa-plug', - text: action.title, - action: () => { - action.handler(this.appearNote); - } - }))]); - } - - return menu; - }, - - onContextmenu(e) { - const isLink = (el: HTMLElement) => { - if (el.tagName === 'A') return true; - if (el.parentElement) { - return isLink(el.parentElement); - } - }; - if (isLink(e.target)) return; - if (window.getSelection().toString() !== '') return; - - if (this.$store.state.useReactionPickerForContextMenu) { - e.preventDefault(); - this.react(); - } else { - os.contextMenu(this.getMenu(), e).then(this.focus); - } - }, - - menu(viaKeyboard = false) { - os.popupMenu(this.getMenu(), this.$refs.menuButton, { - viaKeyboard - }).then(this.focus); - }, - - showRenoteMenu(viaKeyboard = false) { - if (!this.isMyRenote) return; - os.popupMenu([{ - text: this.$ts.unrenote, - icon: 'fas fa-trash-alt', - danger: true, - action: () => { - os.api('notes/delete', { - noteId: this.note.id - }); - this.isDeleted = true; - } - }], this.$refs.renoteTime, { - viaKeyboard: viaKeyboard - }); - }, - - toggleShowContent() { - this.showContent = !this.showContent; - }, - - copyContent() { - copyToClipboard(this.appearNote.text); - os.success(); - }, - - copyLink() { - copyToClipboard(`${url}/notes/${this.appearNote.id}`); - os.success(); - }, - - togglePin(pin: boolean) { - os.apiWithDialog(pin ? 'i/pin' : 'i/unpin', { - noteId: this.appearNote.id - }, undefined, null, e => { - if (e.id === '72dab508-c64d-498f-8740-a8eec1ba385a') { - os.alert({ - type: 'error', - text: this.$ts.pinLimitExceeded - }); - } - }); - }, - - async clip() { - const clips = await os.api('clips/list'); - os.popupMenu([{ - icon: 'fas fa-plus', - text: this.$ts.createNew, - action: async () => { - const { canceled, result } = await os.form(this.$ts.createNewClip, { - name: { - type: 'string', - label: this.$ts.name - }, - description: { - type: 'string', - required: false, - multiline: true, - label: this.$ts.description - }, - isPublic: { - type: 'boolean', - label: this.$ts.public, - default: false - } - }); - if (canceled) return; - - const clip = await os.apiWithDialog('clips/create', result); - - os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: this.appearNote.id }); - } - }, null, ...clips.map(clip => ({ - text: clip.name, - action: () => { - os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: this.appearNote.id }); - } - }))], this.$refs.menuButton, { - }).then(this.focus); - }, - - async promote() { - const { canceled, result: days } = await os.inputNumber({ - title: this.$ts.numberOfDays, - }); - - if (canceled) return; - - os.apiWithDialog('admin/promo/create', { - noteId: this.appearNote.id, - expiresAt: Date.now() + (86400000 * days) - }); - }, + if (defaultStore.state.useReactionPickerForContextMenu) { + ev.preventDefault(); + react(); + } else { + os.contextMenu(getNoteMenu({ note: props.note, translating, translation, menuButton }), ev).then(focus); + } +} - share() { - navigator.share({ - title: this.$t('noteOf', { user: this.appearNote.user.name }), - text: this.appearNote.text, - url: `${url}/notes/${this.appearNote.id}` - }); - }, +function menu(viaKeyboard = false): void { + os.popupMenu(getNoteMenu({ note: props.note, translating, translation, menuButton }), menuButton.value, { + viaKeyboard + }).then(focus); +} - async translate() { - if (this.translation != null) return; - this.translating = true; - const res = await os.api('notes/translate', { - noteId: this.appearNote.id, - targetLang: localStorage.getItem('lang') || navigator.language, +function showRenoteMenu(viaKeyboard = false): void { + if (!isMyRenote) return; + os.popupMenu([{ + text: i18n.locale.unrenote, + icon: 'fas fa-trash-alt', + danger: true, + action: () => { + os.api('notes/delete', { + noteId: props.note.id }); - this.translating = false; - this.translation = res; - }, - - focus() { - this.$el.focus(); - }, - - blur() { - this.$el.blur(); - }, + isDeleted.value = true; + } + }], renoteTime.value, { + viaKeyboard: viaKeyboard + }); +} - focusBefore() { - focusPrev(this.$el); - }, +function focus() { + el.value.focus(); +} - focusAfter() { - focusNext(this.$el); - }, +function blur() { + el.value.blur(); +} - userPage - } +os.api('notes/children', { + noteId: appearNote.id, + limit: 30 +}).then(res => { + replies.value = res; }); + +if (appearNote.replyId) { + os.api('notes/conversation', { + noteId: appearNote.replyId + }).then(res => { + conversation.value = res.reverse(); + }); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/note-header.vue b/packages/client/src/components/note-header.vue index 26e725c6b8..56a3a37e75 100644 --- a/packages/client/src/components/note-header.vue +++ b/packages/client/src/components/note-header.vue @@ -19,30 +19,16 @@ </header> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; +import * as misskey from 'misskey-js'; import { notePage } from '@/filters/note'; import { userPage } from '@/filters/user'; -import * as os from '@/os'; -export default defineComponent({ - props: { - note: { - type: Object, - required: true - }, - }, - - data() { - return { - }; - }, - - methods: { - notePage, - userPage - } -}); +defineProps<{ + note: misskey.entities.Note; + pinned?: boolean; +}>(); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/note-preview.vue b/packages/client/src/components/note-preview.vue index bdcb8d5eed..a78b499654 100644 --- a/packages/client/src/components/note-preview.vue +++ b/packages/client/src/components/note-preview.vue @@ -14,20 +14,12 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; -export default defineComponent({ - components: { - }, - - props: { - text: { - type: String, - required: true - } - }, -}); +const props = defineProps<{ + text: string; +}>(); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/note-simple.vue b/packages/client/src/components/note-simple.vue index 135f06602d..c6907787b5 100644 --- a/packages/client/src/components/note-simple.vue +++ b/packages/client/src/components/note-simple.vue @@ -9,40 +9,26 @@ <XCwButton v-model="showContent" :note="note"/> </p> <div v-show="note.cw == null || showContent" class="content"> - <XSubNote-content class="text" :note="note"/> + <MkNoteSubNoteContent class="text" :note="note"/> </div> </div> </div> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; +import * as misskey from 'misskey-js'; import XNoteHeader from './note-header.vue'; -import XSubNoteContent from './sub-note-content.vue'; +import MkNoteSubNoteContent from './sub-note-content.vue'; import XCwButton from './cw-button.vue'; -import * as os from '@/os'; -export default defineComponent({ - components: { - XNoteHeader, - XSubNoteContent, - XCwButton, - }, +const props = defineProps<{ + note: misskey.entities.Note; + pinned?: boolean; +}>(); - props: { - note: { - type: Object, - required: true - } - }, - - data() { - return { - showContent: false - }; - } -}); +const showContent = $ref(false); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/note.vue b/packages/client/src/components/note.vue index c4040388a9..fc89c2777b 100644 --- a/packages/client/src/components/note.vue +++ b/packages/client/src/components/note.vue @@ -2,20 +2,21 @@ <div v-if="!muted" v-show="!isDeleted" + ref="el" v-hotkey="keymap" v-size="{ max: [500, 450, 350, 300] }" class="tkcbzcuz" :tabindex="!isDeleted ? '-1' : null" :class="{ renote: isRenote }" > - <XSub v-if="appearNote.reply" :note="appearNote.reply" class="reply-to"/> - <div v-if="pinned" class="info"><i class="fas fa-thumbtack"></i> {{ $ts.pinnedNote }}</div> - <div v-if="appearNote._prId_" class="info"><i class="fas fa-bullhorn"></i> {{ $ts.promotion }}<button class="_textButton hide" @click="readPromo()">{{ $ts.hideThisNote }} <i class="fas fa-times"></i></button></div> - <div v-if="appearNote._featuredId_" class="info"><i class="fas fa-bolt"></i> {{ $ts.featured }}</div> + <MkNoteSub v-if="appearNote.reply" :note="appearNote.reply" class="reply-to"/> + <div v-if="pinned" class="info"><i class="fas fa-thumbtack"></i> {{ i18n.locale.pinnedNote }}</div> + <div v-if="appearNote._prId_" class="info"><i class="fas fa-bullhorn"></i> {{ i18n.locale.promotion }}<button class="_textButton hide" @click="readPromo()">{{ i18n.locale.hideThisNote }} <i class="fas fa-times"></i></button></div> + <div v-if="appearNote._featuredId_" class="info"><i class="fas fa-bolt"></i> {{ i18n.locale.featured }}</div> <div v-if="isRenote" class="renote"> <MkAvatar class="avatar" :user="note.user"/> <i class="fas fa-retweet"></i> - <I18n :src="$ts.renotedBy" tag="span"> + <I18n :src="i18n.locale.renotedBy" tag="span"> <template #user> <MkA v-user-preview="note.userId" class="name" :to="userPage(note.user)"> <MkUserName :user="note.user"/> @@ -47,7 +48,7 @@ </p> <div v-show="appearNote.cw == null || showContent" class="content" :class="{ collapsed }"> <div class="text"> - <span v-if="appearNote.isHidden" style="opacity: 0.5">({{ $ts.private }})</span> + <span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.locale.private }})</span> <MkA v-if="appearNote.replyId" class="reply" :to="`/notes/${appearNote.replyId}`"><i class="fas fa-reply"></i></MkA> <Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/> <a v-if="appearNote.renote != null" class="rp">RN:</a> @@ -66,7 +67,7 @@ <MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" class="url-preview"/> <div v-if="appearNote.renote" class="renote"><XNoteSimple :note="appearNote.renote"/></div> <button v-if="collapsed" class="fade _button" @click="collapsed = false"> - <span>{{ $ts.showMore }}</span> + <span>{{ i18n.locale.showMore }}</span> </button> </div> <MkA v-if="appearNote.channel && !inChannel" class="channel" :to="`/channels/${appearNote.channel.id}`"><i class="fas fa-satellite-dish"></i> {{ appearNote.channel.name }}</MkA> @@ -93,7 +94,7 @@ </article> </div> <div v-else class="muted" @click="muted = false"> - <I18n :src="$ts.userSaysSomething" tag="small"> + <I18n :src="i18n.locale.userSaysSomething" tag="small"> <template #name> <MkA v-user-preview="appearNote.userId" class="name" :to="userPage(appearNote.user)"> <MkUserName :user="appearNote.user"/> @@ -103,11 +104,11 @@ </div> </template> -<script lang="ts"> -import { defineAsyncComponent, defineComponent, markRaw } from 'vue'; +<script lang="ts" setup> +import { computed, inject, onMounted, onUnmounted, reactive, ref } from 'vue'; import * as mfm from 'mfm-js'; -import { sum } from '@/scripts/array'; -import XSub from './note.sub.vue'; +import * as misskey from 'misskey-js'; +import MkNoteSub from './MkNoteSub.vue'; import XNoteHeader from './note-header.vue'; import XNoteSimple from './note-simple.vue'; import XReactionsViewer from './reactions-viewer.vue'; @@ -115,744 +116,164 @@ import XMediaList from './media-list.vue'; import XCwButton from './cw-button.vue'; import XPoll from './poll.vue'; import XRenoteButton from './renote-button.vue'; +import MkUrlPreview from '@/components/url-preview.vue'; +import MkInstanceTicker from '@/components/instance-ticker.vue'; import { pleaseLogin } from '@/scripts/please-login'; import { focusPrev, focusNext } from '@/scripts/focus'; -import { url } from '@/config'; -import copyToClipboard from '@/scripts/copy-to-clipboard'; import { checkWordMute } from '@/scripts/check-word-mute'; import { userPage } from '@/filters/user'; import * as os from '@/os'; -import { noteActions, noteViewInterruptors } from '@/store'; +import { defaultStore, noteViewInterruptors } from '@/store'; import { reactionPicker } from '@/scripts/reaction-picker'; import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm'; +import { $i } from '@/account'; +import { i18n } from '@/i18n'; +import { getNoteMenu } from '@/scripts/get-note-menu'; +import { useNoteCapture } from '@/scripts/use-note-capture'; -export default defineComponent({ - components: { - XSub, - XNoteHeader, - XNoteSimple, - XReactionsViewer, - XMediaList, - XCwButton, - XPoll, - XRenoteButton, - MkUrlPreview: defineAsyncComponent(() => import('@/components/url-preview.vue')), - MkInstanceTicker: defineAsyncComponent(() => import('@/components/instance-ticker.vue')), - }, +const props = defineProps<{ + note: misskey.entities.Note; + pinned?: boolean; +}>(); - inject: { - inChannel: { - default: null - }, - }, +const inChannel = inject('inChannel', null); - props: { - note: { - type: Object, - required: true - }, - pinned: { - type: Boolean, - required: false, - default: false - }, - }, +const isRenote = ( + props.note.renote != null && + props.note.text == null && + props.note.fileIds.length === 0 && + props.note.poll == null +); - emits: ['update:note'], +const el = ref<HTMLElement>(); +const menuButton = ref<HTMLElement>(); +const renoteButton = ref<InstanceType<typeof XRenoteButton>>(); +const renoteTime = ref<HTMLElement>(); +const reactButton = ref<HTMLElement>(); +let appearNote = $ref(isRenote ? props.note.renote as misskey.entities.Note : props.note); +const isMyRenote = $i && ($i.id === props.note.userId); +const showContent = ref(false); +const collapsed = ref(appearNote.cw == null && appearNote.text != null && ( + (appearNote.text.split('\n').length > 9) || + (appearNote.text.length > 500) +)); +const isDeleted = ref(false); +const muted = ref(checkWordMute(appearNote, $i, defaultStore.state.mutedWords)); +const translation = ref(null); +const translating = ref(false); +const urls = appearNote.text ? extractUrlFromMfm(mfm.parse(appearNote.text)) : null; +const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.user.instance); - data() { - return { - connection: null, - replies: [], - showContent: false, - collapsed: false, - isDeleted: false, - muted: false, - translation: null, - translating: false, - }; - }, +const keymap = { + 'r': () => reply(true), + 'e|a|plus': () => react(true), + 'q': () => renoteButton.value.renote(true), + 'up|k|shift+tab': focusBefore, + 'down|j|tab': focusAfter, + 'esc': blur, + 'm|o': () => menu(true), + 's': () => showContent.value != showContent.value, +}; - computed: { - rs() { - return this.$store.state.reactions; - }, - keymap(): any { - return { - 'r': () => this.reply(true), - 'e|a|plus': () => this.react(true), - 'q': () => this.$refs.renoteButton.renote(true), - 'f|b': this.favorite, - 'delete|ctrl+d': this.del, - 'ctrl+q': this.renoteDirectly, - 'up|k|shift+tab': this.focusBefore, - 'down|j|tab': this.focusAfter, - 'esc': this.blur, - 'm|o': () => this.menu(true), - 's': this.toggleShowContent, - '1': () => this.reactDirectly(this.rs[0]), - '2': () => this.reactDirectly(this.rs[1]), - '3': () => this.reactDirectly(this.rs[2]), - '4': () => this.reactDirectly(this.rs[3]), - '5': () => this.reactDirectly(this.rs[4]), - '6': () => this.reactDirectly(this.rs[5]), - '7': () => this.reactDirectly(this.rs[6]), - '8': () => this.reactDirectly(this.rs[7]), - '9': () => this.reactDirectly(this.rs[8]), - '0': () => this.reactDirectly(this.rs[9]), - }; - }, - - isRenote(): boolean { - return (this.note.renote && - this.note.text == null && - this.note.fileIds.length == 0 && - this.note.poll == null); - }, - - appearNote(): any { - return this.isRenote ? this.note.renote : this.note; - }, - - isMyNote(): boolean { - return this.$i && (this.$i.id === this.appearNote.userId); - }, - - isMyRenote(): boolean { - return this.$i && (this.$i.id === this.note.userId); - }, - - reactionsCount(): number { - return this.appearNote.reactions - ? sum(Object.values(this.appearNote.reactions)) - : 0; - }, - - urls(): string[] { - if (this.appearNote.text) { - return extractUrlFromMfm(mfm.parse(this.appearNote.text)); - } else { - return null; - } - }, - - showTicker() { - if (this.$store.state.instanceTicker === 'always') return true; - if (this.$store.state.instanceTicker === 'remote' && this.appearNote.user.instance) return true; - return false; - } - }, - - async created() { - if (this.$i) { - this.connection = os.stream; - } - - this.collapsed = this.appearNote.cw == null && this.appearNote.text && ( - (this.appearNote.text.split('\n').length > 9) || - (this.appearNote.text.length > 500) - ); - this.muted = await checkWordMute(this.appearNote, this.$i, this.$store.state.mutedWords); - - // plugin - if (noteViewInterruptors.length > 0) { - let result = this.note; - for (const interruptor of noteViewInterruptors) { - result = await interruptor.handler(JSON.parse(JSON.stringify(result))); - } - this.$emit('update:note', Object.freeze(result)); - } - }, +useNoteCapture({ + appearNote: $$(appearNote), + rootEl: el, +}); - mounted() { - this.capture(true); +function reply(viaKeyboard = false): void { + pleaseLogin(); + os.post({ + reply: appearNote, + animation: !viaKeyboard, + }, () => { + focus(); + }); +} - if (this.$i) { - this.connection.on('_connected_', this.onStreamConnected); - } - }, +function react(viaKeyboard = false): void { + pleaseLogin(); + blur(); + reactionPicker.show(reactButton.value, reaction => { + os.api('notes/reactions/create', { + noteId: appearNote.id, + reaction: reaction + }); + }, () => { + focus(); + }); +} - beforeUnmount() { - this.decapture(true); +function undoReact(note): void { + const oldReaction = note.myReaction; + if (!oldReaction) return; + os.api('notes/reactions/delete', { + noteId: note.id + }); +} - if (this.$i) { - this.connection.off('_connected_', this.onStreamConnected); +function onContextmenu(ev: MouseEvent): void { + const isLink = (el: HTMLElement) => { + if (el.tagName === 'A') return true; + if (el.parentElement) { + return isLink(el.parentElement); } - }, - - methods: { - updateAppearNote(v) { - this.$emit('update:note', Object.freeze(this.isRenote ? { - ...this.note, - renote: { - ...this.note.renote, - ...v - } - } : { - ...this.note, - ...v - })); - }, - - readPromo() { - os.api('promo/read', { - noteId: this.appearNote.id - }); - this.isDeleted = true; - }, - - capture(withHandler = false) { - if (this.$i) { - // TODO: このノートがストリーミング経由で流れてきた場合のみ sr する - this.connection.send(document.body.contains(this.$el) ? 'sr' : 's', { id: this.appearNote.id }); - if (withHandler) this.connection.on('noteUpdated', this.onStreamNoteUpdated); - } - }, + }; + if (isLink(ev.target)) return; + if (window.getSelection().toString() !== '') return; - decapture(withHandler = false) { - if (this.$i) { - this.connection.send('un', { - id: this.appearNote.id - }); - if (withHandler) this.connection.off('noteUpdated', this.onStreamNoteUpdated); - } - }, - - onStreamConnected() { - this.capture(); - }, - - onStreamNoteUpdated(data) { - const { type, id, body } = data; - - if (id !== this.appearNote.id) return; - - switch (type) { - case 'reacted': { - const reaction = body.reaction; - - // DeepではなくShallowコピーであることに注意。n.reactions[reaction] = hogeとかしないように(親からもらったデータをミューテートすることになるので) - let n = { - ...this.appearNote, - }; - - if (body.emoji) { - const emojis = this.appearNote.emojis || []; - if (!emojis.includes(body.emoji)) { - n.emojis = [...emojis, body.emoji]; - } - } - - // TODO: reactionsプロパティがない場合ってあったっけ? なければ || {} は消せる - const currentCount = (this.appearNote.reactions || {})[reaction] || 0; - - // Increment the count - n.reactions = { - ...this.appearNote.reactions, - [reaction]: currentCount + 1 - }; - - if (body.userId === this.$i.id) { - n.myReaction = reaction; - } - - this.updateAppearNote(n); - break; - } - - case 'unreacted': { - const reaction = body.reaction; - - // DeepではなくShallowコピーであることに注意。n.reactions[reaction] = hogeとかしないように(親からもらったデータをミューテートすることになるので) - let n = { - ...this.appearNote, - }; - - // TODO: reactionsプロパティがない場合ってあったっけ? なければ || {} は消せる - const currentCount = (this.appearNote.reactions || {})[reaction] || 0; - - // Decrement the count - n.reactions = { - ...this.appearNote.reactions, - [reaction]: Math.max(0, currentCount - 1) - }; - - if (body.userId === this.$i.id) { - n.myReaction = null; - } - - this.updateAppearNote(n); - break; - } - - case 'pollVoted': { - const choice = body.choice; - - // DeepではなくShallowコピーであることに注意。n.reactions[reaction] = hogeとかしないように(親からもらったデータをミューテートすることになるので) - let n = { - ...this.appearNote, - }; - - const choices = [...this.appearNote.poll.choices]; - choices[choice] = { - ...choices[choice], - votes: choices[choice].votes + 1, - ...(body.userId === this.$i.id ? { - isVoted: true - } : {}) - }; - - n.poll = { - ...this.appearNote.poll, - choices: choices - }; - - this.updateAppearNote(n); - break; - } - - case 'deleted': { - this.isDeleted = true; - break; - } - } - }, - - reply(viaKeyboard = false) { - pleaseLogin(); - os.post({ - reply: this.appearNote, - animation: !viaKeyboard, - }, () => { - this.focus(); - }); - }, - - renoteDirectly() { - os.apiWithDialog('notes/create', { - renoteId: this.appearNote.id - }, undefined, (res: any) => { - os.alert({ - type: 'success', - text: this.$ts.renoted, - }); - }, (e: Error) => { - if (e.id === 'b5c90186-4ab0-49c8-9bba-a1f76c282ba4') { - os.alert({ - type: 'error', - text: this.$ts.cantRenote, - }); - } else if (e.id === 'fd4cc33e-2a37-48dd-99cc-9b806eb2031a') { - os.alert({ - type: 'error', - text: this.$ts.cantReRenote, - }); - } - }); - }, - - react(viaKeyboard = false) { - pleaseLogin(); - this.blur(); - reactionPicker.show(this.$refs.reactButton, reaction => { - os.api('notes/reactions/create', { - noteId: this.appearNote.id, - reaction: reaction - }); - }, () => { - this.focus(); - }); - }, - - reactDirectly(reaction) { - os.api('notes/reactions/create', { - noteId: this.appearNote.id, - reaction: reaction - }); - }, - - undoReact(note) { - const oldReaction = note.myReaction; - if (!oldReaction) return; - os.api('notes/reactions/delete', { - noteId: note.id - }); - }, - - favorite() { - pleaseLogin(); - os.apiWithDialog('notes/favorites/create', { - noteId: this.appearNote.id - }, undefined, (res: any) => { - os.alert({ - type: 'success', - text: this.$ts.favorited, - }); - }, (e: Error) => { - if (e.id === 'a402c12b-34dd-41d2-97d8-4d2ffd96a1a6') { - os.alert({ - type: 'error', - text: this.$ts.alreadyFavorited, - }); - } else if (e.id === '6dd26674-e060-4816-909a-45ba3f4da458') { - os.alert({ - type: 'error', - text: this.$ts.cantFavorite, - }); - } - }); - }, - - del() { - os.confirm({ - type: 'warning', - text: this.$ts.noteDeleteConfirm, - }).then(({ canceled }) => { - if (canceled) return; - - os.api('notes/delete', { - noteId: this.appearNote.id - }); - }); - }, - - delEdit() { - os.confirm({ - type: 'warning', - text: this.$ts.deleteAndEditConfirm, - }).then(({ canceled }) => { - if (canceled) return; - - os.api('notes/delete', { - noteId: this.appearNote.id - }); - - os.post({ initialNote: this.appearNote, renote: this.appearNote.renote, reply: this.appearNote.reply, channel: this.appearNote.channel }); - }); - }, - - toggleFavorite(favorite: boolean) { - os.apiWithDialog(favorite ? 'notes/favorites/create' : 'notes/favorites/delete', { - noteId: this.appearNote.id - }); - }, - - toggleWatch(watch: boolean) { - os.apiWithDialog(watch ? 'notes/watching/create' : 'notes/watching/delete', { - noteId: this.appearNote.id - }); - }, - - toggleThreadMute(mute: boolean) { - os.apiWithDialog(mute ? 'notes/thread-muting/create' : 'notes/thread-muting/delete', { - noteId: this.appearNote.id - }); - }, - - getMenu() { - let menu; - if (this.$i) { - const statePromise = os.api('notes/state', { - noteId: this.appearNote.id - }); - - menu = [{ - icon: 'fas fa-copy', - text: this.$ts.copyContent, - action: this.copyContent - }, { - icon: 'fas fa-link', - text: this.$ts.copyLink, - action: this.copyLink - }, (this.appearNote.url || this.appearNote.uri) ? { - icon: 'fas fa-external-link-square-alt', - text: this.$ts.showOnRemote, - action: () => { - window.open(this.appearNote.url || this.appearNote.uri, '_blank'); - } - } : undefined, - { - icon: 'fas fa-share-alt', - text: this.$ts.share, - action: this.share - }, - this.$instance.translatorAvailable ? { - icon: 'fas fa-language', - text: this.$ts.translate, - action: this.translate - } : undefined, - null, - statePromise.then(state => state.isFavorited ? { - icon: 'fas fa-star', - text: this.$ts.unfavorite, - action: () => this.toggleFavorite(false) - } : { - icon: 'fas fa-star', - text: this.$ts.favorite, - action: () => this.toggleFavorite(true) - }), - { - icon: 'fas fa-paperclip', - text: this.$ts.clip, - action: () => this.clip() - }, - (this.appearNote.userId != this.$i.id) ? statePromise.then(state => state.isWatching ? { - icon: 'fas fa-eye-slash', - text: this.$ts.unwatch, - action: () => this.toggleWatch(false) - } : { - icon: 'fas fa-eye', - text: this.$ts.watch, - action: () => this.toggleWatch(true) - }) : undefined, - statePromise.then(state => state.isMutedThread ? { - icon: 'fas fa-comment-slash', - text: this.$ts.unmuteThread, - action: () => this.toggleThreadMute(false) - } : { - icon: 'fas fa-comment-slash', - text: this.$ts.muteThread, - action: () => this.toggleThreadMute(true) - }), - this.appearNote.userId == this.$i.id ? (this.$i.pinnedNoteIds || []).includes(this.appearNote.id) ? { - icon: 'fas fa-thumbtack', - text: this.$ts.unpin, - action: () => this.togglePin(false) - } : { - icon: 'fas fa-thumbtack', - text: this.$ts.pin, - action: () => this.togglePin(true) - } : undefined, - /* - ...(this.$i.isModerator || this.$i.isAdmin ? [ - null, - { - icon: 'fas fa-bullhorn', - text: this.$ts.promote, - action: this.promote - }] - : [] - ),*/ - ...(this.appearNote.userId != this.$i.id ? [ - null, - { - icon: 'fas fa-exclamation-circle', - text: this.$ts.reportAbuse, - action: () => { - const u = `${url}/notes/${this.appearNote.id}`; - os.popup(import('@/components/abuse-report-window.vue'), { - user: this.appearNote.user, - initialComment: `Note: ${u}\n-----\n` - }, {}, 'closed'); - } - }] - : [] - ), - ...(this.appearNote.userId == this.$i.id || this.$i.isModerator || this.$i.isAdmin ? [ - null, - this.appearNote.userId == this.$i.id ? { - icon: 'fas fa-edit', - text: this.$ts.deleteAndEdit, - action: this.delEdit - } : undefined, - { - icon: 'fas fa-trash-alt', - text: this.$ts.delete, - danger: true, - action: this.del - }] - : [] - )] - .filter(x => x !== undefined); - } else { - menu = [{ - icon: 'fas fa-copy', - text: this.$ts.copyContent, - action: this.copyContent - }, { - icon: 'fas fa-link', - text: this.$ts.copyLink, - action: this.copyLink - }, (this.appearNote.url || this.appearNote.uri) ? { - icon: 'fas fa-external-link-square-alt', - text: this.$ts.showOnRemote, - action: () => { - window.open(this.appearNote.url || this.appearNote.uri, '_blank'); - } - } : undefined] - .filter(x => x !== undefined); - } - - if (noteActions.length > 0) { - menu = menu.concat([null, ...noteActions.map(action => ({ - icon: 'fas fa-plug', - text: action.title, - action: () => { - action.handler(this.appearNote); - } - }))]); - } - - return menu; - }, - - onContextmenu(e) { - const isLink = (el: HTMLElement) => { - if (el.tagName === 'A') return true; - if (el.parentElement) { - return isLink(el.parentElement); - } - }; - if (isLink(e.target)) return; - if (window.getSelection().toString() !== '') return; - - if (this.$store.state.useReactionPickerForContextMenu) { - e.preventDefault(); - this.react(); - } else { - os.contextMenu(this.getMenu(), e).then(this.focus); - } - }, - - menu(viaKeyboard = false) { - os.popupMenu(this.getMenu(), this.$refs.menuButton, { - viaKeyboard - }).then(this.focus); - }, - - showRenoteMenu(viaKeyboard = false) { - if (!this.isMyRenote) return; - os.popupMenu([{ - text: this.$ts.unrenote, - icon: 'fas fa-trash-alt', - danger: true, - action: () => { - os.api('notes/delete', { - noteId: this.note.id - }); - this.isDeleted = true; - } - }], this.$refs.renoteTime, { - viaKeyboard: viaKeyboard - }); - }, - - toggleShowContent() { - this.showContent = !this.showContent; - }, - - copyContent() { - copyToClipboard(this.appearNote.text); - os.success(); - }, - - copyLink() { - copyToClipboard(`${url}/notes/${this.appearNote.id}`); - os.success(); - }, - - togglePin(pin: boolean) { - os.apiWithDialog(pin ? 'i/pin' : 'i/unpin', { - noteId: this.appearNote.id - }, undefined, null, e => { - if (e.id === '72dab508-c64d-498f-8740-a8eec1ba385a') { - os.alert({ - type: 'error', - text: this.$ts.pinLimitExceeded - }); - } - }); - }, - - async clip() { - const clips = await os.api('clips/list'); - os.popupMenu([{ - icon: 'fas fa-plus', - text: this.$ts.createNew, - action: async () => { - const { canceled, result } = await os.form(this.$ts.createNewClip, { - name: { - type: 'string', - label: this.$ts.name - }, - description: { - type: 'string', - required: false, - multiline: true, - label: this.$ts.description - }, - isPublic: { - type: 'boolean', - label: this.$ts.public, - default: false - } - }); - if (canceled) return; - - const clip = await os.apiWithDialog('clips/create', result); - - os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: this.appearNote.id }); - } - }, null, ...clips.map(clip => ({ - text: clip.name, - action: () => { - os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: this.appearNote.id }); - } - }))], this.$refs.menuButton, { - }).then(this.focus); - }, - - async promote() { - const { canceled, result: days } = await os.inputNumber({ - title: this.$ts.numberOfDays, - }); - - if (canceled) return; - - os.apiWithDialog('admin/promo/create', { - noteId: this.appearNote.id, - expiresAt: Date.now() + (86400000 * days) - }); - }, + if (defaultStore.state.useReactionPickerForContextMenu) { + ev.preventDefault(); + react(); + } else { + os.contextMenu(getNoteMenu({ note: props.note, translating, translation, menuButton }), ev).then(focus); + } +} - share() { - navigator.share({ - title: this.$t('noteOf', { user: this.appearNote.user.name }), - text: this.appearNote.text, - url: `${url}/notes/${this.appearNote.id}` - }); - }, +function menu(viaKeyboard = false): void { + os.popupMenu(getNoteMenu({ note: props.note, translating, translation, menuButton }), menuButton.value, { + viaKeyboard + }).then(focus); +} - async translate() { - if (this.translation != null) return; - this.translating = true; - const res = await os.api('notes/translate', { - noteId: this.appearNote.id, - targetLang: localStorage.getItem('lang') || navigator.language, +function showRenoteMenu(viaKeyboard = false): void { + if (!isMyRenote) return; + os.popupMenu([{ + text: i18n.locale.unrenote, + icon: 'fas fa-trash-alt', + danger: true, + action: () => { + os.api('notes/delete', { + noteId: props.note.id }); - this.translating = false; - this.translation = res; - }, + isDeleted.value = true; + } + }], renoteTime.value, { + viaKeyboard: viaKeyboard + }); +} - focus() { - this.$el.focus(); - }, +function focus() { + el.value.focus(); +} - blur() { - this.$el.blur(); - }, +function blur() { + el.value.blur(); +} - focusBefore() { - focusPrev(this.$el); - }, +function focusBefore() { + focusPrev(el.value); +} - focusAfter() { - focusNext(this.$el); - }, +function focusAfter() { + focusNext(el.value); +} - userPage - } -}); +function readPromo() { + os.api('promo/read', { + noteId: appearNote.id + }); + isDeleted.value = true; +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/notes.vue b/packages/client/src/components/notes.vue index 4136f72b1b..41bec5a579 100644 --- a/packages/client/src/components/notes.vue +++ b/packages/client/src/components/notes.vue @@ -1,114 +1,42 @@ <template> -<transition name="fade" mode="out-in"> - <MkLoading v-if="fetching"/> - - <MkError v-else-if="error" @retry="init()"/> - - <div v-else-if="empty" class="_fullinfo"> - <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> - <div>{{ $ts.noNotes }}</div> - </div> - - <div v-else class="giivymft" :class="{ noGap }"> - <div v-show="more && reversed" style="margin-bottom: var(--margin);"> - <MkButton style="margin: 0 auto;" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMoreFeature"> - <template v-if="!moreFetching">{{ $ts.loadMore }}</template> - <template v-if="moreFetching"><MkLoading inline/></template> - </MkButton> +<MkPagination ref="pagingComponent" :pagination="pagination"> + <template #empty> + <div class="_fullinfo"> + <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> + <div>{{ $ts.noNotes }}</div> </div> + </template> - <XList ref="notes" v-slot="{ item: note }" :items="notes" :direction="reversed ? 'up' : 'down'" :reversed="reversed" :no-gap="noGap" :ad="true" class="notes"> - <XNote :key="note._featuredId_ || note._prId_ || note.id" class="qtqtichx" :note="note" @update:note="updated(note, $event)"/> - </XList> - - <div v-show="more && !reversed" style="margin-top: var(--margin);"> - <MkButton v-appear="$store.state.enableInfiniteScroll ? fetchMore : null" style="margin: 0 auto;" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore"> - <template v-if="!moreFetching">{{ $ts.loadMore }}</template> - <template v-if="moreFetching"><MkLoading inline/></template> - </MkButton> + <template #default="{ items: notes }"> + <div class="giivymft" :class="{ noGap }"> + <XList ref="notes" v-slot="{ item: note }" :items="notes" :direction="pagination.reversed ? 'up' : 'down'" :reversed="pagination.reversed" :no-gap="noGap" :ad="true" class="notes"> + <XNote :key="note._featuredId_ || note._prId_ || note.id" class="qtqtichx" :note="note"/> + </XList> </div> - </div> -</transition> + </template> +</MkPagination> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import paging from '@/scripts/paging'; -import XNote from './note.vue'; -import XList from './date-separated-list.vue'; -import MkButton from '@/components/ui/button.vue'; - -export default defineComponent({ - components: { - XNote, XList, MkButton, - }, - - mixins: [ - paging({ - before: (self) => { - self.$emit('before'); - }, - - after: (self, e) => { - self.$emit('after', e); - } - }), - ], - - props: { - pagination: { - required: true - }, - prop: { - type: String, - required: false - }, - noGap: { - type: Boolean, - required: false, - default: false - }, - }, +<script lang="ts" setup> +import { ref } from 'vue'; +import XNote from '@/components/note.vue'; +import XList from '@/components/date-separated-list.vue'; +import MkPagination from '@/components/ui/pagination.vue'; +import { Paging } from '@/components/ui/pagination.vue'; - emits: ['before', 'after'], +const props = defineProps<{ + pagination: Paging; + noGap?: boolean; +}>(); - computed: { - notes(): any[] { - return this.prop ? this.items.map(item => item[this.prop]) : this.items; - }, +const pagingComponent = ref<InstanceType<typeof MkPagination>>(); - reversed(): boolean { - return this.pagination.reversed; - } - }, - - methods: { - updated(oldValue, newValue) { - const i = this.notes.findIndex(n => n === oldValue); - if (this.prop) { - this.items[i][this.prop] = newValue; - } else { - this.items[i] = newValue; - } - }, - - focus() { - this.$refs.notes.focus(); - } - } +defineExpose({ + pagingComponent, }); </script> <style lang="scss" scoped> -.fade-enter-active, -.fade-leave-active { - transition: opacity 0.125s ease; -} -.fade-enter-from, -.fade-leave-to { - opacity: 0; -} - .giivymft { &.noGap { > .notes { diff --git a/packages/client/src/components/notification-toast.vue b/packages/client/src/components/notification-toast.vue index 5449409ccc..b2ab1029ad 100644 --- a/packages/client/src/components/notification-toast.vue +++ b/packages/client/src/components/notification-toast.vue @@ -1,6 +1,6 @@ <template> <div class="mk-notification-toast" :style="{ zIndex }"> - <transition name="notification-toast" appear @after-leave="$emit('closed')"> + <transition :name="$store.state.animation ? 'notification-toast' : ''" appear @after-leave="$emit('closed')"> <XNotification v-if="showing" :notification="notification" class="notification _acrylic"/> </transition> </div> @@ -29,7 +29,7 @@ export default defineComponent({ }; }, mounted() { - setTimeout(() => { + window.setTimeout(() => { this.showing = false; }, 6000); } diff --git a/packages/client/src/components/notification.vue b/packages/client/src/components/notification.vue index 37a88edc64..5659c899be 100644 --- a/packages/client/src/components/notification.vue +++ b/packages/client/src/components/notification.vue @@ -74,6 +74,7 @@ import { notePage } from '@/filters/note'; import { userPage } from '@/filters/user'; import { i18n } from '@/i18n'; import * as os from '@/os'; +import { stream } from '@/stream'; import { useTooltip } from '@/scripts/use-tooltip'; export default defineComponent({ @@ -106,7 +107,7 @@ export default defineComponent({ if (!props.notification.isRead) { const readObserver = new IntersectionObserver((entries, observer) => { if (!entries.some(entry => entry.isIntersecting)) return; - os.stream.send('readNotification', { + stream.send('readNotification', { id: props.notification.id }); observer.disconnect(); @@ -114,7 +115,7 @@ export default defineComponent({ readObserver.observe(elRef.value); - const connection = os.stream.useChannel('main'); + const connection = stream.useChannel('main'); connection.on('readAllNotifications', () => readObserver.disconnect()); onUnmounted(() => { diff --git a/packages/client/src/components/notifications.vue b/packages/client/src/components/notifications.vue index f3e5ee32f7..5a77b5487e 100644 --- a/packages/client/src/components/notifications.vue +++ b/packages/client/src/components/notifications.vue @@ -1,158 +1,77 @@ <template> -<transition name="fade" mode="out-in"> - <MkLoading v-if="fetching"/> +<MkPagination ref="pagingComponent" :pagination="pagination"> + <template #empty> + <div class="_fullinfo"> + <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> + <div>{{ $ts.noNotifications }}</div> + </div> + </template> - <MkError v-else-if="error" @retry="init()"/> - - <p v-else-if="empty" class="mfcuwfyp">{{ $ts.noNotifications }}</p> - - <div v-else> - <XList v-slot="{ item: notification }" class="elsfgstc" :items="items" :no-gap="true"> - <XNote v-if="['reply', 'quote', 'mention'].includes(notification.type)" :key="notification.id" :note="notification.note" @update:note="noteUpdated(notification.note, $event)"/> + <template #default="{ items: notifications }"> + <XList v-slot="{ item: notification }" class="elsfgstc" :items="notifications" :no-gap="true"> + <XNote v-if="['reply', 'quote', 'mention'].includes(notification.type)" :key="notification.id" :note="notification.note"/> <XNotification v-else :key="notification.id" :notification="notification" :with-time="true" :full="true" class="_panel notification"/> </XList> - - <MkButton v-show="more" v-appear="$store.state.enableInfiniteScroll ? fetchMore : null" primary style="margin: var(--margin) auto;" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore"> - <template v-if="!moreFetching">{{ $ts.loadMore }}</template> - <template v-if="moreFetching"><MkLoading inline/></template> - </MkButton> - </div> -</transition> + </template> +</MkPagination> </template> -<script lang="ts"> -import { defineComponent, PropType, markRaw } from 'vue'; -import paging from '@/scripts/paging'; -import XNotification from './notification.vue'; -import XList from './date-separated-list.vue'; -import XNote from './note.vue'; +<script lang="ts" setup> +import { defineComponent, PropType, markRaw, onUnmounted, onMounted, computed, ref } from 'vue'; import { notificationTypes } from 'misskey-js'; +import MkPagination from '@/components/ui/pagination.vue'; +import { Paging } from '@/components/ui/pagination.vue'; +import XNotification from '@/components/notification.vue'; +import XList from '@/components/date-separated-list.vue'; +import XNote from '@/components/note.vue'; import * as os from '@/os'; -import MkButton from '@/components/ui/button.vue'; - -export default defineComponent({ - components: { - XNotification, - XList, - XNote, - MkButton, - }, - - mixins: [ - paging({}), - ], - - props: { - includeTypes: { - type: Array as PropType<typeof notificationTypes[number][]>, - required: false, - default: null, - }, - unreadOnly: { - type: Boolean, - required: false, - default: false, - }, - }, - - data() { - return { - connection: null, - pagination: { - endpoint: 'i/notifications', - limit: 10, - params: () => ({ - includeTypes: this.allIncludeTypes || undefined, - unreadOnly: this.unreadOnly, - }) - }, - }; - }, - - computed: { - allIncludeTypes() { - return this.includeTypes ?? notificationTypes.filter(x => !this.$i.mutingNotificationTypes.includes(x)); - } - }, +import { stream } from '@/stream'; +import { $i } from '@/account'; - watch: { - includeTypes: { - handler() { - this.reload(); - }, - deep: true - }, - unreadOnly: { - handler() { - this.reload(); - }, - }, - // TODO: vue/vuexのバグか仕様かは不明なものの、プロフィール更新するなどして $i が更新されると、 - // mutingNotificationTypes に変化が無くてもこのハンドラーが呼び出され無駄なリロードが発生するのを直す - '$i.mutingNotificationTypes': { - handler() { - if (this.includeTypes === null) { - this.reload(); - } - }, - deep: true - } - }, +const props = defineProps<{ + includeTypes?: PropType<typeof notificationTypes[number][]>; + unreadOnly?: boolean; +}>(); - mounted() { - this.connection = markRaw(os.stream.useChannel('main')); - this.connection.on('notification', this.onNotification); - }, +const pagingComponent = ref<InstanceType<typeof MkPagination>>(); - beforeUnmount() { - this.connection.dispose(); - }, +const allIncludeTypes = computed(() => props.includeTypes ?? notificationTypes.filter(x => !$i.mutingNotificationTypes.includes(x))); - methods: { - onNotification(notification) { - const isMuted = !this.allIncludeTypes.includes(notification.type); - if (isMuted || document.visibilityState === 'visible') { - os.stream.send('readNotification', { - id: notification.id - }); - } +const pagination: Paging = { + endpoint: 'i/notifications' as const, + limit: 10, + params: computed(() => ({ + includeTypes: allIncludeTypes.value || undefined, + unreadOnly: props.unreadOnly, + })), +}; - if (!isMuted) { - this.prepend({ - ...notification, - isRead: document.visibilityState === 'visible' - }); - } - }, +const onNotification = (notification) => { + const isMuted = !allIncludeTypes.value.includes(notification.type); + if (isMuted || document.visibilityState === 'visible') { + stream.send('readNotification', { + id: notification.id + }); + } - noteUpdated(oldValue, newValue) { - const i = this.items.findIndex(n => n.note === oldValue); - this.items[i] = { - ...this.items[i], - note: newValue - }; - }, + if (!isMuted) { + pagingComponent.value.prepend({ + ...notification, + isRead: document.visibilityState === 'visible' + }); } +}; + +onMounted(() => { + const connection = stream.useChannel('main'); + connection.on('notification', onNotification); + onUnmounted(() => { + connection.dispose(); + }); }); </script> <style lang="scss" scoped> -.fade-enter-active, -.fade-leave-active { - transition: opacity 0.125s ease; -} -.fade-enter-from, -.fade-leave-to { - opacity: 0; -} - -.mfcuwfyp { - margin: 0; - padding: 16px; - text-align: center; - color: var(--fg); -} - .elsfgstc { background: var(--panel); } diff --git a/packages/client/src/components/object-view.value.vue b/packages/client/src/components/object-view.value.vue new file mode 100644 index 0000000000..6f388636dd --- /dev/null +++ b/packages/client/src/components/object-view.value.vue @@ -0,0 +1,108 @@ +<template> +<div class="igpposuu _monospace"> + <div v-if="value === null" class="null">null</div> + <div v-else-if="typeof value === 'boolean'" class="boolean">{{ value ? 'true' : 'false' }}</div> + <div v-else-if="typeof value === 'string'" class="string">"{{ value }}"</div> + <div v-else-if="typeof value === 'number'" class="number">{{ number(value) }}</div> + <div v-else-if="Array.isArray(value)" class="array"> + <button @click="collapsed_ = !collapsed_">[ {{ collapsed_ ? '+' : '-' }} ]</button> + <template v-if="!collapsed_"> + <div v-for="i in value.length" class="element"> + {{ i }}: <XValue :value="value[i - 1]" collapsed/> + </div> + </template> + </div> + <div v-else-if="typeof value === 'object'" class="object"> + <button @click="collapsed_ = !collapsed_">{ {{ collapsed_ ? '+' : '-' }} }</button> + <template v-if="!collapsed_"> + <div v-for="k in Object.keys(value)" class="kv"> + <div class="k">{{ k }}:</div> + <div class="v"><XValue :value="value[k]" collapsed/></div> + </div> + </template> + </div> +</div> +</template> + +<script lang="ts"> +import { computed, defineComponent, ref } from 'vue'; +import number from '@/filters/number'; + +export default defineComponent({ + name: 'XValue', + + props: { + value: { + type: Object, + required: true, + }, + collapsed: { + type: Boolean, + required: false, + default: false, + }, + }, + + setup(props) { + const collapsed_ = ref(props.collapsed); + + return { + number, + collapsed_, + }; + } +}); +</script> + +<style lang="scss" scoped> +.igpposuu { + display: inline; + + > .null { + display: inline; + opacity: 0.7; + } + + > .boolean { + display: inline; + color: var(--codeBoolean); + } + + > .string { + display: inline; + color: var(--codeString); + } + + > .number { + display: inline; + color: var(--codeNumber); + } + + > .array { + display: inline; + + > .element { + display: block; + padding-left: 16px; + } + } + + > .object { + display: inline; + + > .kv { + display: block; + padding-left: 16px; + + > .k { + display: inline; + margin-right: 8px; + } + + > .v { + display: inline; + } + } + } +} +</style> diff --git a/packages/client/src/components/object-view.vue b/packages/client/src/components/object-view.vue new file mode 100644 index 0000000000..e9db96de8c --- /dev/null +++ b/packages/client/src/components/object-view.vue @@ -0,0 +1,33 @@ +<template> +<div class="zhyxdalp"> + <XValue :value="value" :collapsed="false"/> +</div> +</template> + +<script lang="ts"> +import { computed, defineComponent } from 'vue'; +import XValue from './object-view.value.vue'; + +export default defineComponent({ + components: { + XValue + }, + + props: { + value: { + type: Object, + required: true, + }, + }, + + setup(props) { + + } +}); +</script> + +<style lang="scss" scoped> +.zhyxdalp { + +} +</style> diff --git a/packages/client/src/components/poll-editor.vue b/packages/client/src/components/poll-editor.vue index fad0cf1593..6f3f23a2d3 100644 --- a/packages/client/src/components/poll-editor.vue +++ b/packages/client/src/components/poll-editor.vue @@ -3,7 +3,7 @@ <p v-if="choices.length < 2" class="caution"> <i class="fas fa-exclamation-triangle"></i>{{ $ts._poll.noOnlyOneChoice }} </p> - <ul ref="choices"> + <ul> <li v-for="(choice, i) in choices" :key="i"> <MkInput class="input" :model-value="choice" :placeholder="$t('_poll.choiceN', { n: i + 1 })" @update:modelValue="onInput(i, $event)"> </MkInput> @@ -14,8 +14,8 @@ </ul> <MkButton v-if="choices.length < 10" class="add" @click="add">{{ $ts.add }}</MkButton> <MkButton v-else class="add" disabled>{{ $ts._poll.noMore }}</MkButton> + <MkSwitch v-model="multiple">{{ $ts._poll.canMultipleVote }}</MkSwitch> <section> - <MkSwitch v-model="multiple">{{ $ts._poll.canMultipleVote }}</MkSwitch> <div> <MkSelect v-model="expiration"> <template #label>{{ $ts._poll.expiration }}</template> @@ -31,7 +31,7 @@ <template #label>{{ $ts._poll.deadlineTime }}</template> </MkInput> </section> - <section v-if="expiration === 'after'"> + <section v-else-if="expiration === 'after'"> <MkInput v-model="after" type="number" class="input"> <template #label>{{ $ts._poll.duration }}</template> </MkInput> @@ -47,8 +47,8 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { ref, watch } from 'vue'; import { addTime } from '@/scripts/time'; import { formatDateTimeString } from '@/scripts/format-time-string'; import MkInput from './form/input.vue'; @@ -56,131 +56,91 @@ import MkSelect from './form/select.vue'; import MkSwitch from './form/switch.vue'; import MkButton from './ui/button.vue'; -export default defineComponent({ - components: { - MkInput, - MkSelect, - MkSwitch, - MkButton, - }, +const props = defineProps<{ + modelValue: { + expiresAt: string; + expiredAfter: number; + choices: string[]; + multiple: boolean; + }; +}>(); +const emit = defineEmits<{ + (ev: 'update:modelValue', v: { + expiresAt: string; + expiredAfter: number; + choices: string[]; + multiple: boolean; + }): void; +}>(); - props: { - poll: { - type: Object, - required: true - } - }, - - emits: ['updated'], - - data() { - return { - choices: this.poll.choices, - multiple: this.poll.multiple, - expiration: 'infinite', - atDate: formatDateTimeString(addTime(new Date(), 1, 'day'), 'yyyy-MM-dd'), - atTime: '00:00', - after: 0, - unit: 'second', - }; - }, +const choices = ref(props.modelValue.choices); +const multiple = ref(props.modelValue.multiple); +const expiration = ref('infinite'); +const atDate = ref(formatDateTimeString(addTime(new Date(), 1, 'day'), 'yyyy-MM-dd')); +const atTime = ref('00:00'); +const after = ref(0); +const unit = ref('second'); - watch: { - choices: { - handler() { - this.$emit('updated', this.get()); - }, - deep: true - }, - multiple: { - handler() { - this.$emit('updated', this.get()); - }, - }, - expiration: { - handler() { - this.$emit('updated', this.get()); - }, - }, - atDate: { - handler() { - this.$emit('updated', this.get()); - }, - }, - after: { - handler() { - this.$emit('updated', this.get()); - }, - }, - unit: { - handler() { - this.$emit('updated', this.get()); - }, - }, - }, +if (props.modelValue.expiresAt) { + expiration.value = 'at'; + atDate.value = atTime.value = props.modelValue.expiresAt; +} else if (typeof props.modelValue.expiredAfter === 'number') { + expiration.value = 'after'; + after.value = props.modelValue.expiredAfter / 1000; +} else { + expiration.value = 'infinite'; +} - created() { - const poll = this.poll; - if (poll.expiresAt) { - this.expiration = 'at'; - this.atDate = this.atTime = poll.expiresAt; - } else if (typeof poll.expiredAfter === 'number') { - this.expiration = 'after'; - this.after = poll.expiredAfter / 1000; - } else { - this.expiration = 'infinite'; - } - }, +function onInput(i, value) { + choices.value[i] = value; +} - methods: { - onInput(i, e) { - this.choices[i] = e; - }, +function add() { + choices.value.push(''); + // TODO + // nextTick(() => { + // (this.$refs.choices as any).childNodes[this.choices.length - 1].childNodes[0].focus(); + // }); +} - add() { - this.choices.push(''); - this.$nextTick(() => { - // TODO - //(this.$refs.choices as any).childNodes[this.choices.length - 1].childNodes[0].focus(); - }); - }, +function remove(i) { + choices.value = choices.value.filter((_, _i) => _i != i); +} - remove(i) { - this.choices = this.choices.filter((_, _i) => _i != i); - }, +function get() { + const calcAt = () => { + return new Date(`${atDate.value} ${atTime.value}`).getTime(); + }; - get() { - const at = () => { - return new Date(`${this.atDate} ${this.atTime}`).getTime(); - }; + const calcAfter = () => { + let base = parseInt(after.value); + switch (unit.value) { + case 'day': base *= 24; + case 'hour': base *= 60; + case 'minute': base *= 60; + case 'second': return base *= 1000; + default: return null; + } + }; - const after = () => { - let base = parseInt(this.after); - switch (this.unit) { - case 'day': base *= 24; - case 'hour': base *= 60; - case 'minute': base *= 60; - case 'second': return base *= 1000; - default: return null; - } - }; + return { + choices: choices.value, + multiple: multiple.value, + ...( + expiration.value === 'at' ? { expiresAt: calcAt() } : + expiration.value === 'after' ? { expiredAfter: calcAfter() } : {} + ) + }; +} - return { - choices: this.choices, - multiple: this.multiple, - ...( - this.expiration === 'at' ? { expiresAt: at() } : - this.expiration === 'after' ? { expiredAfter: after() } : {} - ) - }; - }, - } +watch([choices, multiple, expiration, atDate, atTime, after, unit], () => emit('update:modelValue', get()), { + deep: true, }); </script> <style lang="scss" scoped> .zmdxowus { - padding: 8px; + padding: 8px 16px; > .caution { margin: 0 0 8px 0; @@ -216,7 +176,7 @@ export default defineComponent({ } > .add { - margin: 8px 0 0 0; + margin: 8px 0; z-index: 1; } @@ -225,21 +185,27 @@ export default defineComponent({ > div { margin: 0 8px; + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 12px; &:last-child { flex: 1 0 auto; + > div { + flex-grow: 1; + } + > section { - align-items: center; + // MAGIC: Prevent div above from growing unless wrapped to its own line + flex-grow: 9999; + align-items: end; display: flex; - margin: -32px 0 0; - - > &:first-child { - margin-right: 16px; - } + gap: 4px; > .input { - flex: 1 0 auto; + flex: 1 1 auto; } } } diff --git a/packages/client/src/components/post-form-attaches.vue b/packages/client/src/components/post-form-attaches.vue index 0782ce22e5..0c8181b481 100644 --- a/packages/client/src/components/post-form-attaches.vue +++ b/packages/client/src/components/post-form-attaches.vue @@ -10,7 +10,7 @@ </div> </template> </XDraggable> - <p class="remain">{{ 4 - files.length }}/4</p> + <p class="remain">{{ 16 - files.length }}/16</p> </div> </template> @@ -41,7 +41,6 @@ export default defineComponent({ data() { return { menu: null as Promise<null> | null, - }; }, @@ -99,10 +98,12 @@ export default defineComponent({ }, { done: result => { if (!result || result.canceled) return; - let comment = result.result; + let comment = result.result.length == 0 ? null : result.result; os.api('drive/files/update', { fileId: file.id, - comment: comment.length == 0 ? null : comment + comment: comment, + }).then(() => { + file.comment = comment; }); } }, 'closed'); diff --git a/packages/client/src/components/post-form.vue b/packages/client/src/components/post-form.vue index 4265c575e2..ed78c5a3fb 100644 --- a/packages/client/src/components/post-form.vue +++ b/packages/client/src/components/post-form.vue @@ -8,25 +8,28 @@ > <header> <button v-if="!fixed" class="cancel _button" @click="cancel"><i class="fas fa-times"></i></button> + <button v-click-anime v-tooltip="i18n.locale.switchAccount" class="account _button" @click="openAccountMenu"> + <MkAvatar :user="postAccount ?? $i" class="avatar"/> + </button> <div> - <span class="text-count" :class="{ over: textLength > max }">{{ max - textLength }}</span> + <span class="text-count" :class="{ over: textLength > maxTextLength }">{{ maxTextLength - textLength }}</span> <span v-if="localOnly" class="local-only"><i class="fas fa-biohazard"></i></span> - <button ref="visibilityButton" v-tooltip="$ts.visibility" class="_button visibility" :disabled="channel != null" @click="setVisibility"> + <button ref="visibilityButton" v-tooltip="i18n.locale.visibility" class="_button visibility" :disabled="channel != null" @click="setVisibility"> <span v-if="visibility === 'public'"><i class="fas fa-globe"></i></span> <span v-if="visibility === 'home'"><i class="fas fa-home"></i></span> <span v-if="visibility === 'followers'"><i class="fas fa-unlock"></i></span> <span v-if="visibility === 'specified'"><i class="fas fa-envelope"></i></span> </button> - <button v-tooltip="$ts.previewNoteText" class="_button preview" :class="{ active: showPreview }" @click="showPreview = !showPreview"><i class="fas fa-file-code"></i></button> + <button v-tooltip="i18n.locale.previewNoteText" class="_button preview" :class="{ active: showPreview }" @click="showPreview = !showPreview"><i class="fas fa-file-code"></i></button> <button class="submit _buttonGradate" :disabled="!canPost" data-cy-open-post-form-submit @click="post">{{ submitText }}<i :class="reply ? 'fas fa-reply' : renote ? 'fas fa-quote-right' : 'fas fa-paper-plane'"></i></button> </div> </header> <div class="form" :class="{ fixed }"> <XNoteSimple v-if="reply" class="preview" :note="reply"/> <XNoteSimple v-if="renote" class="preview" :note="renote"/> - <div v-if="quoteId" class="with-quote"><i class="fas fa-quote-left"></i> {{ $ts.quoteAttached }}<button @click="quoteId = null"><i class="fas fa-times"></i></button></div> + <div v-if="quoteId" class="with-quote"><i class="fas fa-quote-left"></i> {{ i18n.locale.quoteAttached }}<button @click="quoteId = null"><i class="fas fa-times"></i></button></div> <div v-if="visibility === 'specified'" class="to-specified"> - <span style="margin-right: 8px;">{{ $ts.recipient }}</span> + <span style="margin-right: 8px;">{{ i18n.locale.recipient }}</span> <div class="visibleUsers"> <span v-for="u in visibleUsers" :key="u.id"> <MkAcct :user="u"/> @@ -35,21 +38,21 @@ <button class="_buttonPrimary" @click="addVisibleUser"><i class="fas fa-plus fa-fw"></i></button> </div> </div> - <MkInfo v-if="hasNotSpecifiedMentions" warn class="hasNotSpecifiedMentions">{{ $ts.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ $ts.add }}</button></MkInfo> - <input v-show="useCw" ref="cw" v-model="cw" class="cw" :placeholder="$ts.annotation" @keydown="onKeydown"> - <textarea ref="text" v-model="text" class="text" :class="{ withCw: useCw }" :disabled="posting" :placeholder="placeholder" data-cy-post-form-text @keydown="onKeydown" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd"/> - <input v-show="withHashtags" ref="hashtags" v-model="hashtags" class="hashtags" :placeholder="$ts.hashtags" list="hashtags"> + <MkInfo v-if="hasNotSpecifiedMentions" warn class="hasNotSpecifiedMentions">{{ i18n.locale.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ i18n.locale.add }}</button></MkInfo> + <input v-show="useCw" ref="cwInputEl" v-model="cw" class="cw" :placeholder="i18n.locale.annotation" @keydown="onKeydown"> + <textarea ref="textareaEl" v-model="text" class="text" :class="{ withCw: useCw }" :disabled="posting" :placeholder="placeholder" data-cy-post-form-text @keydown="onKeydown" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd"/> + <input v-show="withHashtags" ref="hashtagsInputEl" v-model="hashtags" class="hashtags" :placeholder="i18n.locale.hashtags" list="hashtags"> <XPostFormAttaches class="attaches" :files="files" @updated="updateFiles" @detach="detachFile" @changeSensitive="updateFileSensitive" @changeName="updateFileName"/> - <XPollEditor v-if="poll" :poll="poll" @destroyed="poll = null" @updated="onPollUpdate"/> + <XPollEditor v-if="poll" v-model="poll" @destroyed="poll = null"/> <XNotePreview v-if="showPreview" class="preview" :text="text"/> <footer> - <button v-tooltip="$ts.attachFile" class="_button" @click="chooseFileFrom"><i class="fas fa-photo-video"></i></button> - <button v-tooltip="$ts.poll" class="_button" :class="{ active: poll }" @click="togglePoll"><i class="fas fa-poll-h"></i></button> - <button v-tooltip="$ts.useCw" class="_button" :class="{ active: useCw }" @click="useCw = !useCw"><i class="fas fa-eye-slash"></i></button> - <button v-tooltip="$ts.mention" class="_button" @click="insertMention"><i class="fas fa-at"></i></button> - <button v-tooltip="$ts.hashtags" class="_button" :class="{ active: withHashtags }" @click="withHashtags = !withHashtags"><i class="fas fa-hashtag"></i></button> - <button v-tooltip="$ts.emoji" class="_button" @click="insertEmoji"><i class="fas fa-laugh-squint"></i></button> - <button v-if="postFormActions.length > 0" v-tooltip="$ts.plugin" class="_button" @click="showActions"><i class="fas fa-plug"></i></button> + <button v-tooltip="i18n.locale.attachFile" class="_button" @click="chooseFileFrom"><i class="fas fa-photo-video"></i></button> + <button v-tooltip="i18n.locale.poll" class="_button" :class="{ active: poll }" @click="togglePoll"><i class="fas fa-poll-h"></i></button> + <button v-tooltip="i18n.locale.useCw" class="_button" :class="{ active: useCw }" @click="useCw = !useCw"><i class="fas fa-eye-slash"></i></button> + <button v-tooltip="i18n.locale.mention" class="_button" @click="insertMention"><i class="fas fa-at"></i></button> + <button v-tooltip="i18n.locale.hashtags" class="_button" :class="{ active: withHashtags }" @click="withHashtags = !withHashtags"><i class="fas fa-hashtag"></i></button> + <button v-tooltip="i18n.locale.emoji" class="_button" @click="insertEmoji"><i class="fas fa-laugh-squint"></i></button> + <button v-if="postFormActions.length > 0" v-tooltip="i18n.locale.plugin" class="_button" @click="showActions"><i class="fas fa-plug"></i></button> </footer> <datalist id="hashtags"> <option v-for="hashtag in recentHashtags" :key="hashtag" :value="hashtag"/> @@ -58,667 +61,623 @@ </div> </template> -<script lang="ts"> -import { defineComponent, defineAsyncComponent } from 'vue'; +<script lang="ts" setup> +import { inject, watch, nextTick, onMounted } from 'vue'; +import * as mfm from 'mfm-js'; +import * as misskey from 'misskey-js'; import insertTextAtCursor from 'insert-text-at-cursor'; import { length } from 'stringz'; import { toASCII } from 'punycode/'; import XNoteSimple from './note-simple.vue'; import XNotePreview from './note-preview.vue'; -import * as mfm from 'mfm-js'; +import XPostFormAttaches from './post-form-attaches.vue'; +import XPollEditor from './poll-editor.vue'; import { host, url } from '@/config'; import { erase, unique } from '@/scripts/array'; import { extractMentions } from '@/scripts/extract-mentions'; import * as Acct from 'misskey-js/built/acct'; import { formatTimeString } from '@/scripts/format-time-string'; import { Autocomplete } from '@/scripts/autocomplete'; -import { noteVisibilities } from 'misskey-js'; import * as os from '@/os'; +import { stream } from '@/stream'; import { selectFiles } from '@/scripts/select-file'; import { defaultStore, notePostInterruptors, postFormActions } from '@/store'; import { throttle } from 'throttle-debounce'; import MkInfo from '@/components/ui/info.vue'; -import { defaultStore } from '@/store'; +import { i18n } from '@/i18n'; +import { instance } from '@/instance'; +import { $i, getAccounts, openAccountMenu as openAccountMenu_ } from '@/account'; -export default defineComponent({ - components: { - XNoteSimple, - XNotePreview, - XPostFormAttaches: defineAsyncComponent(() => import('./post-form-attaches.vue')), - XPollEditor: defineAsyncComponent(() => import('./poll-editor.vue')), - MkInfo, - }, +const modal = inject('modal'); - inject: ['modal'], +const props = withDefaults(defineProps<{ + reply?: misskey.entities.Note; + renote?: misskey.entities.Note; + channel?: any; // TODO + mention?: misskey.entities.User; + specified?: misskey.entities.User; + initialText?: string; + initialVisibility?: typeof misskey.noteVisibilities; + initialFiles?: misskey.entities.DriveFile[]; + initialLocalOnly?: boolean; + initialVisibleUsers?: misskey.entities.User[]; + initialNote?: misskey.entities.Note; + share?: boolean; + fixed?: boolean; + autofocus?: boolean; +}>(), { + initialVisibleUsers: [], + autofocus: true, +}); - props: { - reply: { - type: Object, - required: false - }, - renote: { - type: Object, - required: false - }, - channel: { - type: Object, - required: false - }, - mention: { - type: Object, - required: false - }, - specified: { - type: Object, - required: false - }, - initialText: { - type: String, - required: false - }, - initialVisibility: { - type: String, - required: false - }, - initialFiles: { - type: Array, - required: false - }, - initialLocalOnly: { - type: Boolean, - required: false - }, - initialVisibleUsers: { - type: Array, - required: false, - default: () => [] - }, - initialNote: { - type: Object, - required: false - }, - share: { - type: Boolean, - required: false, - default: false - }, - fixed: { - type: Boolean, - required: false, - default: false - }, - autofocus: { - type: Boolean, - required: false, - default: true - }, - }, +const emit = defineEmits<{ + (ev: 'posted'): void; + (ev: 'cancel'): void; + (ev: 'esc'): void; +}>(); - emits: ['posted', 'cancel', 'esc'], +const textareaEl = $ref<HTMLTextAreaElement | null>(null); +const cwInputEl = $ref<HTMLInputElement | null>(null); +const hashtagsInputEl = $ref<HTMLInputElement | null>(null); +const visibilityButton = $ref<HTMLElement | null>(null); - data() { - return { - posting: false, - text: '', - files: [], - poll: null, - useCw: false, - showPreview: false, - cw: null, - localOnly: this.$store.state.rememberNoteVisibility ? this.$store.state.localOnly : this.$store.state.defaultNoteLocalOnly, - visibility: (this.$store.state.rememberNoteVisibility ? this.$store.state.visibility : this.$store.state.defaultNoteVisibility) as typeof noteVisibilities[number], - visibleUsers: [], - autocomplete: null, - draghover: false, - quoteId: null, - hasNotSpecifiedMentions: false, - recentHashtags: JSON.parse(localStorage.getItem('hashtags') || '[]'), - imeText: '', - typing: throttle(3000, () => { - if (this.channel) { - os.stream.send('typingOnChannel', { channel: this.channel.id }); - } - }), - postFormActions, - }; - }, +let posting = $ref(false); +let text = $ref(props.initialText ?? ''); +let files = $ref(props.initialFiles ?? []); +let poll = $ref<{ + choices: string[]; + multiple: boolean; + expiresAt: string | null; + expiredAfter: string | null; +} | null>(null); +let useCw = $ref(false); +let showPreview = $ref(false); +let cw = $ref<string | null>(null); +let localOnly = $ref<boolean>(props.initialLocalOnly ?? defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly); +let visibility = $ref(props.initialVisibility ?? (defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility) as typeof misskey.noteVisibilities[number]); +let visibleUsers = $ref(props.initialVisibleUsers ?? []); +let autocomplete = $ref(null); +let draghover = $ref(false); +let quoteId = $ref(null); +let hasNotSpecifiedMentions = $ref(false); +let recentHashtags = $ref(JSON.parse(localStorage.getItem('hashtags') || '[]')); +let imeText = $ref(''); - computed: { - draftKey(): string { - let key = this.channel ? `channel:${this.channel.id}` : ''; +const typing = throttle(3000, () => { + if (props.channel) { + stream.send('typingOnChannel', { channel: props.channel.id }); + } +}); - if (this.renote) { - key += `renote:${this.renote.id}`; - } else if (this.reply) { - key += `reply:${this.reply.id}`; - } else { - key += 'note'; - } +const draftKey = $computed((): string => { + let key = props.channel ? `channel:${props.channel.id}` : ''; - return key; - }, + if (props.renote) { + key += `renote:${props.renote.id}`; + } else if (props.reply) { + key += `reply:${props.reply.id}`; + } else { + key += 'note'; + } - placeholder(): string { - if (this.renote) { - return this.$ts._postForm.quotePlaceholder; - } else if (this.reply) { - return this.$ts._postForm.replyPlaceholder; - } else if (this.channel) { - return this.$ts._postForm.channelPlaceholder; - } else { - const xs = [ - this.$ts._postForm._placeholders.a, - this.$ts._postForm._placeholders.b, - this.$ts._postForm._placeholders.c, - this.$ts._postForm._placeholders.d, - this.$ts._postForm._placeholders.e, - this.$ts._postForm._placeholders.f - ]; - return xs[Math.floor(Math.random() * xs.length)]; - } - }, + return key; +}); - submitText(): string { - return this.renote - ? this.$ts.quote - : this.reply - ? this.$ts.reply - : this.$ts.note; - }, +const placeholder = $computed((): string => { + if (props.renote) { + return i18n.locale._postForm.quotePlaceholder; + } else if (props.reply) { + return i18n.locale._postForm.replyPlaceholder; + } else if (props.channel) { + return i18n.locale._postForm.channelPlaceholder; + } else { + const xs = [ + i18n.locale._postForm._placeholders.a, + i18n.locale._postForm._placeholders.b, + i18n.locale._postForm._placeholders.c, + i18n.locale._postForm._placeholders.d, + i18n.locale._postForm._placeholders.e, + i18n.locale._postForm._placeholders.f + ]; + return xs[Math.floor(Math.random() * xs.length)]; + } +}); - textLength(): number { - return length((this.text + this.imeText).trim()); - }, +const submitText = $computed((): string => { + return props.renote + ? i18n.locale.quote + : props.reply + ? i18n.locale.reply + : i18n.locale.note; +}); - canPost(): boolean { - return !this.posting && - (1 <= this.textLength || 1 <= this.files.length || !!this.poll || !!this.renote) && - (this.textLength <= this.max) && - (!this.poll || this.poll.choices.length >= 2); - }, +const textLength = $computed((): number => { + return length((text + imeText).trim()); +}); - max(): number { - return this.$instance ? this.$instance.maxNoteTextLength : 1000; - }, +const maxTextLength = $computed((): number => { + return instance ? instance.maxNoteTextLength : 1000; +}); - withHashtags: defaultStore.makeGetterSetter('postFormWithHashtags'), - hashtags: defaultStore.makeGetterSetter('postFormHashtags'), - }, +const canPost = $computed((): boolean => { + return !posting && + (1 <= textLength || 1 <= files.length || !!poll || !!props.renote) && + (textLength <= maxTextLength) && + (!poll || poll.choices.length >= 2); +}); - watch: { - text() { - this.checkMissingMention(); - }, - visibleUsers: { - handler() { - this.checkMissingMention(); - }, - deep: true - } - }, +const withHashtags = $computed(defaultStore.makeGetterSetter('postFormWithHashtags')); +const hashtags = $computed(defaultStore.makeGetterSetter('postFormHashtags')); - mounted() { - if (this.initialText) { - this.text = this.initialText; - } +watch($$(text), () => { + checkMissingMention(); +}); - if (this.initialVisibility) { - this.visibility = this.initialVisibility; - } +watch($$(visibleUsers), () => { + checkMissingMention(); +}, { + deep: true, +}); - if (this.initialFiles) { - this.files = this.initialFiles; - } +if (props.mention) { + text = props.mention.host ? `@${props.mention.username}@${toASCII(props.mention.host)}` : `@${props.mention.username}`; + text += ' '; +} - if (typeof this.initialLocalOnly === 'boolean') { - this.localOnly = this.initialLocalOnly; - } +if (props.reply && (props.reply.user.username != $i.username || (props.reply.user.host != null && props.reply.user.host != host))) { + text = `@${props.reply.user.username}${props.reply.user.host != null ? '@' + toASCII(props.reply.user.host) : ''} `; +} - if (this.initialVisibleUsers) { - this.visibleUsers = this.initialVisibleUsers; - } +if (props.reply && props.reply.text != null) { + const ast = mfm.parse(props.reply.text); + const otherHost = props.reply.user.host; - if (this.mention) { - this.text = this.mention.host ? `@${this.mention.username}@${toASCII(this.mention.host)}` : `@${this.mention.username}`; - this.text += ' '; - } + for (const x of extractMentions(ast)) { + const mention = x.host ? + `@${x.username}@${toASCII(x.host)}` : + (otherHost == null || otherHost == host) ? + `@${x.username}` : + `@${x.username}@${toASCII(otherHost)}`; - if (this.reply && (this.reply.user.username != this.$i.username || (this.reply.user.host != null && this.reply.user.host != host))) { - this.text = `@${this.reply.user.username}${this.reply.user.host != null ? '@' + toASCII(this.reply.user.host) : ''} `; - } + // 自分は除外 + if ($i.username == x.username && x.host == null) continue; + if ($i.username == x.username && x.host == host) continue; - if (this.reply && this.reply.text != null) { - const ast = mfm.parse(this.reply.text); - const otherHost = this.reply.user.host; + // 重複は除外 + if (text.indexOf(`${mention} `) != -1) continue; - for (const x of extractMentions(ast)) { - const mention = x.host ? - `@${x.username}@${toASCII(x.host)}` : - (otherHost == null || otherHost == host) ? - `@${x.username}` : - `@${x.username}@${toASCII(otherHost)}`; + text += `${mention} `; + } +} - // 自分は除外 - if (this.$i.username == x.username && x.host == null) continue; - if (this.$i.username == x.username && x.host == host) continue; +if (props.channel) { + visibility = 'public'; + localOnly = true; // TODO: チャンネルが連合するようになった折には消す +} - // 重複は除外 - if (this.text.indexOf(`${mention} `) != -1) continue; +// 公開以外へのリプライ時は元の公開範囲を引き継ぐ +if (props.reply && ['home', 'followers', 'specified'].includes(props.reply.visibility)) { + visibility = props.reply.visibility; + if (props.reply.visibility === 'specified') { + os.api('users/show', { + userIds: props.reply.visibleUserIds.filter(uid => uid !== $i.id && uid !== props.reply.userId) + }).then(users => { + visibleUsers.push(...users); + }); - this.text += `${mention} `; - } + if (props.reply.userId !== $i.id) { + os.api('users/show', { userId: props.reply.userId }).then(user => { + visibleUsers.push(user); + }); } + } +} - if (this.channel) { - this.visibility = 'public'; - this.localOnly = true; // TODO: チャンネルが連合するようになった折には消す - } +if (props.specified) { + visibility = 'specified'; + visibleUsers.push(props.specified); +} - // 公開以外へのリプライ時は元の公開範囲を引き継ぐ - if (this.reply && ['home', 'followers', 'specified'].includes(this.reply.visibility)) { - this.visibility = this.reply.visibility; - if (this.reply.visibility === 'specified') { - os.api('users/show', { - userIds: this.reply.visibleUserIds.filter(uid => uid !== this.$i.id && uid !== this.reply.userId) - }).then(users => { - this.visibleUsers.push(...users); - }); +// keep cw when reply +if (defaultStore.state.keepCw && props.reply && props.reply.cw) { + useCw = true; + cw = props.reply.cw; +} - if (this.reply.userId !== this.$i.id) { - os.api('users/show', { userId: this.reply.userId }).then(user => { - this.visibleUsers.push(user); - }); - } - } - } +function watchForDraft() { + watch($$(text), () => saveDraft()); + watch($$(useCw), () => saveDraft()); + watch($$(cw), () => saveDraft()); + watch($$(poll), () => saveDraft()); + watch($$(files), () => saveDraft(), { deep: true }); + watch($$(visibility), () => saveDraft()); + watch($$(localOnly), () => saveDraft()); +} - if (this.specified) { - this.visibility = 'specified'; - this.visibleUsers.push(this.specified); - } +function checkMissingMention() { + if (visibility === 'specified') { + const ast = mfm.parse(text); - // keep cw when reply - if (this.$store.state.keepCw && this.reply && this.reply.cw) { - this.useCw = true; - this.cw = this.reply.cw; + for (const x of extractMentions(ast)) { + if (!visibleUsers.some(u => (u.username === x.username) && (u.host == x.host))) { + hasNotSpecifiedMentions = true; + return; + } } + hasNotSpecifiedMentions = false; + } +} - if (this.autofocus) { - this.focus(); +function addMissingMention() { + const ast = mfm.parse(text); - this.$nextTick(() => { - this.focus(); + for (const x of extractMentions(ast)) { + if (!visibleUsers.some(u => (u.username === x.username) && (u.host == x.host))) { + os.api('users/show', { username: x.username, host: x.host }).then(user => { + visibleUsers.push(user); }); } + } +} - // TODO: detach when unmount - new Autocomplete(this.$refs.text, this, { model: 'text' }); - new Autocomplete(this.$refs.cw, this, { model: 'cw' }); - new Autocomplete(this.$refs.hashtags, this, { model: 'hashtags' }); +function togglePoll() { + if (poll) { + poll = null; + } else { + poll = { + choices: ['', ''], + multiple: false, + expiresAt: null, + expiredAfter: null, + }; + } +} - this.$nextTick(() => { - // 書きかけの投稿を復元 - if (!this.share && !this.mention && !this.specified) { - const draft = JSON.parse(localStorage.getItem('drafts') || '{}')[this.draftKey]; - if (draft) { - this.text = draft.data.text; - this.useCw = draft.data.useCw; - this.cw = draft.data.cw; - this.visibility = draft.data.visibility; - this.localOnly = draft.data.localOnly; - this.files = (draft.data.files || []).filter(e => e); - if (draft.data.poll) { - this.poll = draft.data.poll; - } - } - } +function addTag(tag: string) { + insertTextAtCursor(textareaEl, ` #${tag} `); +} - // 削除して編集 - if (this.initialNote) { - const init = this.initialNote; - this.text = init.text ? init.text : ''; - this.files = init.files; - this.cw = init.cw; - this.useCw = init.cw != null; - if (init.poll) { - this.poll = { - choices: init.poll.choices.map(x => x.text), - multiple: init.poll.multiple, - expiresAt: init.poll.expiresAt, - expiredAfter: init.poll.expiredAfter, - }; - } - this.visibility = init.visibility; - this.localOnly = init.localOnly; - this.quoteId = init.renote ? init.renote.id : null; - } +function focus() { + textareaEl.focus(); +} - this.$nextTick(() => this.watch()); - }); - }, +function chooseFileFrom(ev) { + selectFiles(ev.currentTarget || ev.target, i18n.locale.attachFile).then(files_ => { + for (const file of files_) { + files.push(file); + } + }); +} - methods: { - watch() { - this.$watch('text', () => this.saveDraft()); - this.$watch('useCw', () => this.saveDraft()); - this.$watch('cw', () => this.saveDraft()); - this.$watch('poll', () => this.saveDraft()); - this.$watch('files', () => this.saveDraft(), { deep: true }); - this.$watch('visibility', () => this.saveDraft()); - this.$watch('localOnly', () => this.saveDraft()); - }, +function detachFile(id) { + files = files.filter(x => x.id != id); +} - checkMissingMention() { - if (this.visibility === 'specified') { - const ast = mfm.parse(this.text); +function updateFiles(_files) { + files = _files; +} - for (const x of extractMentions(ast)) { - if (!this.visibleUsers.some(u => (u.username === x.username) && (u.host == x.host))) { - this.hasNotSpecifiedMentions = true; - return; - } - } - this.hasNotSpecifiedMentions = false; - } - }, +function updateFileSensitive(file, sensitive) { + files[files.findIndex(x => x.id === file.id)].isSensitive = sensitive; +} - addMissingMention() { - const ast = mfm.parse(this.text); +function updateFileName(file, name) { + files[files.findIndex(x => x.id === file.id)].name = name; +} - for (const x of extractMentions(ast)) { - if (!this.visibleUsers.some(u => (u.username === x.username) && (u.host == x.host))) { - os.api('users/show', { username: x.username, host: x.host }).then(user => { - this.visibleUsers.push(user); - }); - } - } - }, +function upload(file: File, name?: string) { + os.upload(file, defaultStore.state.uploadFolder, name).then(res => { + files.push(res); + }); +} - togglePoll() { - if (this.poll) { - this.poll = null; - } else { - this.poll = { - choices: ['', ''], - multiple: false, - expiresAt: null, - expiredAfter: null, - }; +function setVisibility() { + if (props.channel) { + // TODO: information dialog + return; + } + + os.popup(import('./visibility-picker.vue'), { + currentVisibility: visibility, + currentLocalOnly: localOnly, + src: visibilityButton, + }, { + changeVisibility: v => { + visibility = v; + if (defaultStore.state.rememberNoteVisibility) { + defaultStore.set('visibility', visibility); } }, + changeLocalOnly: v => { + localOnly = v; + if (defaultStore.state.rememberNoteVisibility) { + defaultStore.set('localOnly', localOnly); + } + } + }, 'closed'); +} - addTag(tag: string) { - insertTextAtCursor(this.$refs.text, ` #${tag} `); - }, +function addVisibleUser() { + os.selectUser().then(user => { + visibleUsers.push(user); + }); +} - focus() { - (this.$refs.text as any).focus(); - }, +function removeVisibleUser(user) { + visibleUsers = erase(user, visibleUsers); +} - chooseFileFrom(ev) { - selectFiles(ev.currentTarget || ev.target, this.$ts.attachFile).then(files => { - for (const file of files) { - this.files.push(file); - } - }); - }, +function clear() { + text = ''; + files = []; + poll = null; + quoteId = null; +} - detachFile(id) { - this.files = this.files.filter(x => x.id != id); - }, +function onKeydown(e: KeyboardEvent) { + if ((e.which === 10 || e.which === 13) && (e.ctrlKey || e.metaKey) && canPost) post(); + if (e.which === 27) emit('esc'); + typing(); +} - updateFiles(files) { - this.files = files; - }, +function onCompositionUpdate(e: CompositionEvent) { + imeText = e.data; + typing(); +} - updateFileSensitive(file, sensitive) { - this.files[this.files.findIndex(x => x.id === file.id)].isSensitive = sensitive; - }, +function onCompositionEnd(e: CompositionEvent) { + imeText = ''; +} - updateFileName(file, name) { - this.files[this.files.findIndex(x => x.id === file.id)].name = name; - }, +async function onPaste(e: ClipboardEvent) { + for (const { item, i } of Array.from(e.clipboardData.items).map((item, i) => ({item, i}))) { + if (item.kind == 'file') { + const file = item.getAsFile(); + const lio = file.name.lastIndexOf('.'); + const ext = lio >= 0 ? file.name.slice(lio) : ''; + const formatted = `${formatTimeString(new Date(file.lastModified), defaultStore.state.pastedFileName).replace(/{{number}}/g, `${i + 1}`)}${ext}`; + upload(file, formatted); + } + } - upload(file: File, name?: string) { - os.upload(file, this.$store.state.uploadFolder, name).then(res => { - this.files.push(res); - }); - }, + const paste = e.clipboardData.getData('text'); - onPollUpdate(poll) { - this.poll = poll; - this.saveDraft(); - }, + if (!props.renote && !quoteId && paste.startsWith(url + '/notes/')) { + e.preventDefault(); - setVisibility() { - if (this.channel) { - // TODO: information dialog + os.confirm({ + type: 'info', + text: i18n.locale.quoteQuestion, + }).then(({ canceled }) => { + if (canceled) { + insertTextAtCursor(textareaEl, paste); return; } - os.popup(import('./visibility-picker.vue'), { - currentVisibility: this.visibility, - currentLocalOnly: this.localOnly, - src: this.$refs.visibilityButton - }, { - changeVisibility: visibility => { - this.visibility = visibility; - if (this.$store.state.rememberNoteVisibility) { - this.$store.set('visibility', visibility); - } - }, - changeLocalOnly: localOnly => { - this.localOnly = localOnly; - if (this.$store.state.rememberNoteVisibility) { - this.$store.set('localOnly', localOnly); - } - } - }, 'closed'); - }, - - addVisibleUser() { - os.selectUser().then(user => { - this.visibleUsers.push(user); - }); - }, - - removeVisibleUser(user) { - this.visibleUsers = erase(user, this.visibleUsers); - }, + quoteId = paste.substr(url.length).match(/^\/notes\/(.+?)\/?$/)[1]; + }); + } +} - clear() { - this.text = ''; - this.files = []; - this.poll = null; - this.quoteId = null; - }, +function onDragover(e) { + if (!e.dataTransfer.items[0]) return; + const isFile = e.dataTransfer.items[0].kind == 'file'; + const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; + if (isFile || isDriveFile) { + e.preventDefault(); + draghover = true; + e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; + } +} - onKeydown(e: KeyboardEvent) { - if ((e.which === 10 || e.which === 13) && (e.ctrlKey || e.metaKey) && this.canPost) this.post(); - if (e.which === 27) this.$emit('esc'); - this.typing(); - }, +function onDragenter(e) { + draghover = true; +} - onCompositionUpdate(e: CompositionEvent) { - this.imeText = e.data; - this.typing(); - }, +function onDragleave(e) { + draghover = false; +} - onCompositionEnd(e: CompositionEvent) { - this.imeText = ''; - }, +function onDrop(e): void { + draghover = false; - async onPaste(e: ClipboardEvent) { - for (const { item, i } of Array.from(e.clipboardData.items).map((item, i) => ({item, i}))) { - if (item.kind == 'file') { - const file = item.getAsFile(); - const lio = file.name.lastIndexOf('.'); - const ext = lio >= 0 ? file.name.slice(lio) : ''; - const formatted = `${formatTimeString(new Date(file.lastModified), this.$store.state.pastedFileName).replace(/{{number}}/g, `${i + 1}`)}${ext}`; - this.upload(file, formatted); - } - } + // ファイルだったら + if (e.dataTransfer.files.length > 0) { + e.preventDefault(); + for (const x of Array.from(e.dataTransfer.files)) upload(x); + return; + } - const paste = e.clipboardData.getData('text'); + //#region ドライブのファイル + const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); + if (driveFile != null && driveFile != '') { + const file = JSON.parse(driveFile); + files.push(file); + e.preventDefault(); + } + //#endregion +} - if (!this.renote && !this.quoteId && paste.startsWith(url + '/notes/')) { - e.preventDefault(); +function saveDraft() { + const data = JSON.parse(localStorage.getItem('drafts') || '{}'); - os.confirm({ - type: 'info', - text: this.$ts.quoteQuestion, - }).then(({ canceled }) => { - if (canceled) { - insertTextAtCursor(this.$refs.text, paste); - return; - } + data[draftKey] = { + updatedAt: new Date(), + data: { + text: text, + useCw: useCw, + cw: cw, + visibility: visibility, + localOnly: localOnly, + files: files, + poll: poll + } + }; - this.quoteId = paste.substr(url.length).match(/^\/notes\/(.+?)\/?$/)[1]; - }); - } - }, + localStorage.setItem('drafts', JSON.stringify(data)); +} - onDragover(e) { - if (!e.dataTransfer.items[0]) return; - const isFile = e.dataTransfer.items[0].kind == 'file'; - const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; - if (isFile || isDriveFile) { - e.preventDefault(); - this.draghover = true; - e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; - } - }, +function deleteDraft() { + const data = JSON.parse(localStorage.getItem('drafts') || '{}'); - onDragenter(e) { - this.draghover = true; - }, + delete data[draftKey]; - onDragleave(e) { - this.draghover = false; - }, + localStorage.setItem('drafts', JSON.stringify(data)); +} - onDrop(e): void { - this.draghover = false; +async function post() { + let data = { + text: text == '' ? undefined : text, + fileIds: files.length > 0 ? files.map(f => f.id) : undefined, + replyId: props.reply ? props.reply.id : undefined, + renoteId: props.renote ? props.renote.id : quoteId ? quoteId : undefined, + channelId: props.channel ? props.channel.id : undefined, + poll: poll, + cw: useCw ? cw || '' : undefined, + localOnly: localOnly, + visibility: visibility, + visibleUserIds: visibility == 'specified' ? visibleUsers.map(u => u.id) : undefined, + }; - // ファイルだったら - if (e.dataTransfer.files.length > 0) { - e.preventDefault(); - for (const x of Array.from(e.dataTransfer.files)) this.upload(x); - return; - } + if (withHashtags && hashtags && hashtags.trim() !== '') { + const hashtags = hashtags.trim().split(' ').map(x => x.startsWith('#') ? x : '#' + x).join(' '); + data.text = data.text ? `${data.text} ${hashtags}` : hashtags; + } - //#region ドライブのファイル - const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); - if (driveFile != null && driveFile != '') { - const file = JSON.parse(driveFile); - this.files.push(file); - e.preventDefault(); - } - //#endregion - }, + // plugin + if (notePostInterruptors.length > 0) { + for (const interruptor of notePostInterruptors) { + data = await interruptor.handler(JSON.parse(JSON.stringify(data))); + } + } - saveDraft() { - const data = JSON.parse(localStorage.getItem('drafts') || '{}'); + let token = undefined; - data[this.draftKey] = { - updatedAt: new Date(), - data: { - text: this.text, - useCw: this.useCw, - cw: this.cw, - visibility: this.visibility, - localOnly: this.localOnly, - files: this.files, - poll: this.poll - } - }; + if (postAccount) { + const storedAccounts = await getAccounts(); + token = storedAccounts.find(x => x.id === postAccount.id)?.token; + } - localStorage.setItem('drafts', JSON.stringify(data)); - }, + posting = true; + os.api('notes/create', data, token).then(() => { + clear(); + nextTick(() => { + deleteDraft(); + emit('posted'); + if (data.text && data.text != '') { + const hashtags = mfm.parse(data.text).filter(x => x.type === 'hashtag').map(x => x.props.hashtag); + const history = JSON.parse(localStorage.getItem('hashtags') || '[]') as string[]; + localStorage.setItem('hashtags', JSON.stringify(unique(hashtags.concat(history)))); + } + posting = false; + postAccount = null; + }); + }).catch(err => { + posting = false; + os.alert({ + type: 'error', + text: err.message + '\n' + (err as any).id, + }); + }); +} - deleteDraft() { - const data = JSON.parse(localStorage.getItem('drafts') || '{}'); +function cancel() { + emit('cancel'); +} - delete data[this.draftKey]; +function insertMention() { + os.selectUser().then(user => { + insertTextAtCursor(textareaEl, '@' + Acct.toString(user) + ' '); + }); +} - localStorage.setItem('drafts', JSON.stringify(data)); - }, +async function insertEmoji(ev: MouseEvent) { + os.openEmojiPicker(ev.currentTarget || ev.target, {}, textareaEl); +} - async post() { - let data = { - text: this.text == '' ? undefined : this.text, - fileIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined, - replyId: this.reply ? this.reply.id : undefined, - renoteId: this.renote ? this.renote.id : this.quoteId ? this.quoteId : undefined, - channelId: this.channel ? this.channel.id : undefined, - poll: this.poll, - cw: this.useCw ? this.cw || '' : undefined, - localOnly: this.localOnly, - visibility: this.visibility, - visibleUserIds: this.visibility == 'specified' ? this.visibleUsers.map(u => u.id) : undefined, - }; +function showActions(ev) { + os.popupMenu(postFormActions.map(action => ({ + text: action.title, + action: () => { + action.handler({ + text: text + }, (key, value) => { + if (key === 'text') { text = value; } + }); + } + })), ev.currentTarget || ev.target); +} - if (this.withHashtags && this.hashtags && this.hashtags.trim() !== '') { - const hashtags = this.hashtags.trim().split(' ').map(x => x.startsWith('#') ? x : '#' + x).join(' '); - data.text = data.text ? `${data.text} ${hashtags}` : hashtags; - } +let postAccount = $ref<misskey.entities.UserDetailed | null>(null); - // plugin - if (notePostInterruptors.length > 0) { - for (const interruptor of notePostInterruptors) { - data = await interruptor.handler(JSON.parse(JSON.stringify(data))); - } +function openAccountMenu(ev: MouseEvent) { + openAccountMenu_({ + withExtraOperation: false, + includeCurrentAccount: true, + active: postAccount != null ? postAccount.id : $i.id, + onChoose: (account) => { + if (account.id === $i.id) { + postAccount = null; + } else { + postAccount = account; } - - this.posting = true; - os.api('notes/create', data).then(() => { - this.clear(); - this.$nextTick(() => { - this.deleteDraft(); - this.$emit('posted'); - if (data.text && data.text != '') { - const hashtags = mfm.parse(data.text).filter(x => x.type === 'hashtag').map(x => x.props.hashtag); - const history = JSON.parse(localStorage.getItem('hashtags') || '[]') as string[]; - localStorage.setItem('hashtags', JSON.stringify(unique(hashtags.concat(history)))); - } - this.posting = false; - }); - }).catch(err => { - this.posting = false; - os.alert({ - type: 'error', - text: err.message + '\n' + (err as any).id, - }); - }); }, + }, ev); +} - cancel() { - this.$emit('cancel'); - }, +onMounted(() => { + if (props.autofocus) { + focus(); - insertMention() { - os.selectUser().then(user => { - insertTextAtCursor(this.$refs.text, '@' + Acct.toString(user) + ' '); - }); - }, + nextTick(() => { + focus(); + }); + } - async insertEmoji(ev) { - os.openEmojiPicker(ev.currentTarget || ev.target, {}, this.$refs.text); - }, + // TODO: detach when unmount + new Autocomplete(textareaEl, $$(text)); + new Autocomplete(cwInputEl, $$(cw)); + new Autocomplete(hashtagsInputEl, $$(hashtags)); - showActions(ev) { - os.popupMenu(postFormActions.map(action => ({ - text: action.title, - action: () => { - action.handler({ - text: this.text - }, (key, value) => { - if (key === 'text') { this.text = value; } - }); + nextTick(() => { + // 書きかけの投稿を復元 + if (!props.share && !props.mention && !props.specified) { + const draft = JSON.parse(localStorage.getItem('drafts') || '{}')[draftKey]; + if (draft) { + text = draft.data.text; + useCw = draft.data.useCw; + cw = draft.data.cw; + visibility = draft.data.visibility; + localOnly = draft.data.localOnly; + files = (draft.data.files || []).filter(e => e); + if (draft.data.poll) { + poll = draft.data.poll; } - })), ev.currentTarget || ev.target); + } } - } + + // 削除して編集 + if (props.initialNote) { + const init = props.initialNote; + text = init.text ? init.text : ''; + files = init.files; + cw = init.cw; + useCw = init.cw != null; + if (init.poll) { + poll = { + choices: init.poll.choices.map(x => x.text), + multiple: init.poll.multiple, + expiresAt: init.poll.expiresAt, + expiredAfter: init.poll.expiredAfter, + }; + } + visibility = init.visibility; + localOnly = init.localOnly; + quoteId = init.renote ? init.renote.id : null; + } + + nextTick(() => watchForDraft()); + }); }); </script> @@ -742,6 +701,19 @@ export default defineComponent({ line-height: 66px; } + > .account { + height: 100%; + aspect-ratio: 1/1; + display: inline-flex; + vertical-align: bottom; + + > .avatar { + width: 28px; + height: 28px; + margin: auto; + } + } + > div { position: absolute; top: 0; diff --git a/packages/client/src/components/reaction-icon.vue b/packages/client/src/components/reaction-icon.vue index c0ec955e32..5638c9a816 100644 --- a/packages/client/src/components/reaction-icon.vue +++ b/packages/client/src/components/reaction-icon.vue @@ -1,25 +1,13 @@ <template> -<MkEmoji :emoji="reaction" :custom-emojis="customEmojis" :is-reaction="true" :normal="true" :no-style="noStyle"/> +<MkEmoji :emoji="reaction" :custom-emojis="customEmojis || []" :is-reaction="true" :normal="true" :no-style="noStyle"/> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; -export default defineComponent({ - props: { - reaction: { - type: String, - required: true - }, - customEmojis: { - required: false, - default: () => [] - }, - noStyle: { - type: Boolean, - required: false, - default: false - }, - }, -}); +const props = defineProps<{ + reaction: string; + customEmojis?: any[]; // TODO + noStyle?: boolean; +}>(); </script> diff --git a/packages/client/src/components/reaction-tooltip.vue b/packages/client/src/components/reaction-tooltip.vue index dda8e7c6d7..1b2a024e21 100644 --- a/packages/client/src/components/reaction-tooltip.vue +++ b/packages/client/src/components/reaction-tooltip.vue @@ -1,5 +1,5 @@ <template> -<MkTooltip ref="tooltip" :source="source" :max-width="340" @closed="$emit('closed')"> +<MkTooltip ref="tooltip" :source="source" :max-width="340" @closed="emit('closed')"> <div class="beeadbfb"> <XReactionIcon :reaction="reaction" :custom-emojis="emojis" class="icon" :no-style="true"/> <div class="name">{{ reaction.replace('@.', '') }}</div> @@ -7,31 +7,20 @@ </MkTooltip> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MkTooltip from './ui/tooltip.vue'; import XReactionIcon from './reaction-icon.vue'; -export default defineComponent({ - components: { - MkTooltip, - XReactionIcon, - }, - props: { - reaction: { - type: String, - required: true, - }, - emojis: { - type: Array, - required: true, - }, - source: { - required: true, - } - }, - emits: ['closed'], -}) +const props = defineProps<{ + reaction: string; + emojis: any[]; // TODO + source: any; // TODO +}>(); + +const emit = defineEmits<{ + (e: 'closed'): void; +}>(); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/reactions-viewer.details.vue b/packages/client/src/components/reactions-viewer.details.vue index d6374517a2..8cec8dfa2f 100644 --- a/packages/client/src/components/reactions-viewer.details.vue +++ b/packages/client/src/components/reactions-viewer.details.vue @@ -1,5 +1,5 @@ <template> -<MkTooltip ref="tooltip" :source="source" :max-width="340" @closed="$emit('closed')"> +<MkTooltip ref="tooltip" :source="source" :max-width="340" @closed="emit('closed')"> <div class="bqxuuuey"> <div class="reaction"> <XReactionIcon :reaction="reaction" :custom-emojis="emojis" class="icon" :no-style="true"/> @@ -16,39 +16,22 @@ </MkTooltip> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MkTooltip from './ui/tooltip.vue'; import XReactionIcon from './reaction-icon.vue'; -export default defineComponent({ - components: { - MkTooltip, - XReactionIcon - }, - props: { - reaction: { - type: String, - required: true, - }, - users: { - type: Array, - required: true, - }, - count: { - type: Number, - required: true, - }, - emojis: { - type: Array, - required: true, - }, - source: { - required: true, - } - }, - emits: ['closed'], -}) +const props = defineProps<{ + reaction: string; + users: any[]; // TODO + count: number; + emojis: any[]; // TODO + source: any; // TODO +}>(); + +const emit = defineEmits<{ + (e: 'closed'): void; +}>(); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/reactions-viewer.vue b/packages/client/src/components/reactions-viewer.vue index 59fcbb7129..a9bf51f65f 100644 --- a/packages/client/src/components/reactions-viewer.vue +++ b/packages/client/src/components/reactions-viewer.vue @@ -4,31 +4,19 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; +import * as misskey from 'misskey-js'; +import { $i } from '@/account'; import XReaction from './reactions-viewer.reaction.vue'; -export default defineComponent({ - components: { - XReaction - }, - props: { - note: { - type: Object, - required: true - }, - }, - data() { - return { - initialReactions: new Set(Object.keys(this.note.reactions)) - }; - }, - computed: { - isMe(): boolean { - return this.$i && this.$i.id === this.note.userId; - }, - }, -}); +const props = defineProps<{ + note: misskey.entities.Note; +}>(); + +const initialReactions = new Set(Object.keys(props.note.reactions)); + +const isMe = computed(() => $i && $i.id === props.note.userId); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/remote-caution.vue b/packages/client/src/components/remote-caution.vue index c496ea8f48..aa623f0fb0 100644 --- a/packages/client/src/components/remote-caution.vue +++ b/packages/client/src/components/remote-caution.vue @@ -2,22 +2,10 @@ <div class="jmgmzlwq _block"><i class="fas fa-exclamation-triangle" style="margin-right: 8px;"></i>{{ $ts.remoteUserCaution }}<a :href="href" rel="nofollow noopener" target="_blank">{{ $ts.showOnRemote }}</a></div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as os from '@/os'; - -export default defineComponent({ - props: { - href: { - type: String, - required: true - }, - }, - data() { - return { - }; - } -}); +<script lang="ts" setup> +defineProps<{ + href: string; +}>(); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/renote.details.vue b/packages/client/src/components/renote.details.vue index e3ef15c753..cdbc71bdce 100644 --- a/packages/client/src/components/renote.details.vue +++ b/packages/client/src/components/renote.details.vue @@ -1,5 +1,5 @@ <template> -<MkTooltip ref="tooltip" :source="source" :max-width="250" @closed="$emit('closed')"> +<MkTooltip ref="tooltip" :source="source" :max-width="250" @closed="emit('closed')"> <div class="beaffaef"> <div v-for="u in users" :key="u.id" class="user"> <MkAvatar class="avatar" :user="u"/> @@ -10,29 +10,19 @@ </MkTooltip> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MkTooltip from './ui/tooltip.vue'; -export default defineComponent({ - components: { - MkTooltip, - }, - props: { - users: { - type: Array, - required: true, - }, - count: { - type: Number, - required: true, - }, - source: { - required: true, - } - }, - emits: ['closed'], -}) +const props = defineProps<{ + users: any[]; // TODO + count: number; + source: any; // TODO +}>(); + +const emit = defineEmits<{ + (e: 'closed'): void; +}>(); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/ripple.vue b/packages/client/src/components/ripple.vue index 272eacbc6e..401e78e304 100644 --- a/packages/client/src/components/ripple.vue +++ b/packages/client/src/components/ripple.vue @@ -94,7 +94,7 @@ export default defineComponent({ } onMounted(() => { - setTimeout(() => { + window.setTimeout(() => { context.emit('end'); }, 1100); }); diff --git a/packages/client/src/components/signin-dialog.vue b/packages/client/src/components/signin-dialog.vue index 2edd10f539..5c2048e7b0 100644 --- a/packages/client/src/components/signin-dialog.vue +++ b/packages/client/src/components/signin-dialog.vue @@ -2,8 +2,8 @@ <XModalWindow ref="dialog" :width="370" :height="400" - @close="$refs.dialog.close()" - @closed="$emit('closed')" + @close="dialog.close()" + @closed="emit('closed')" > <template #header>{{ $ts.login }}</template> @@ -11,32 +11,26 @@ </XModalWindow> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import XModalWindow from '@/components/ui/modal-window.vue'; import MkSignin from './signin.vue'; -export default defineComponent({ - components: { - MkSignin, - XModalWindow, - }, +const props = withDefaults(defineProps<{ + autoSet?: boolean; +}>(), { + autoSet: false, +}); - props: { - autoSet: { - type: Boolean, - required: false, - default: false, - } - }, +const emit = defineEmits<{ + (e: 'done'): void; + (e: 'closed'): void; +}>(); - emits: ['done', 'closed'], +const dialog = $ref<InstanceType<typeof XModalWindow>>(); - methods: { - onLogin(res) { - this.$emit('done', res); - this.$refs.dialog.close(); - } - } -}); +function onLogin(res) { + emit('done', res); + dialog.close(); +} </script> diff --git a/packages/client/src/components/signup-dialog.vue b/packages/client/src/components/signup-dialog.vue index 30fe3bf7d3..bda2495ba7 100644 --- a/packages/client/src/components/signup-dialog.vue +++ b/packages/client/src/components/signup-dialog.vue @@ -2,7 +2,7 @@ <XModalWindow ref="dialog" :width="366" :height="500" - @close="$refs.dialog.close()" + @close="dialog.close()" @closed="$emit('closed')" > <template #header>{{ $ts.signup }}</template> @@ -15,36 +15,30 @@ </XModalWindow> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import XModalWindow from '@/components/ui/modal-window.vue'; import XSignup from './signup.vue'; -export default defineComponent({ - components: { - XSignup, - XModalWindow, - }, +const props = withDefaults(defineProps<{ + autoSet?: boolean; +}>(), { + autoSet: false, +}); - props: { - autoSet: { - type: Boolean, - required: false, - default: false, - } - }, +const emit = defineEmits<{ + (e: 'done'): void; + (e: 'closed'): void; +}>(); - emits: ['done', 'closed'], +const dialog = $ref<InstanceType<typeof XModalWindow>>(); - methods: { - onSignup(res) { - this.$emit('done', res); - this.$refs.dialog.close(); - }, +function onSignup(res) { + emit('done', res); + dialog.close(); +} - onSignupEmailPending() { - this.$refs.dialog.close(); - } - } -}); +function onSignupEmailPending() { + dialog.close(); +} </script> diff --git a/packages/client/src/components/sub-note-content.vue b/packages/client/src/components/sub-note-content.vue index efa202ce2f..d6a37d07be 100644 --- a/packages/client/src/components/sub-note-content.vue +++ b/packages/client/src/components/sub-note-content.vue @@ -21,35 +21,21 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import XPoll from './poll.vue'; import XMediaList from './media-list.vue'; -import * as os from '@/os'; +import * as misskey from 'misskey-js'; -export default defineComponent({ - components: { - XPoll, - XMediaList, - }, - props: { - note: { - type: Object, - required: true - } - }, - data() { - return { - collapsed: false, - }; - }, - created() { - this.collapsed = this.note.cw == null && this.note.text && ( - (this.note.text.split('\n').length > 9) || - (this.note.text.length > 500) - ); - } -}); +const props = defineProps<{ + note: misskey.entities.Note; +}>(); + +const collapsed = $ref( + props.note.cw == null && props.note.text != null && ( + (props.note.text.split('\n').length > 9) || + (props.note.text.length > 500) + )); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/taskmanager.api-window.vue b/packages/client/src/components/taskmanager.api-window.vue deleted file mode 100644 index 6ec4da3a59..0000000000 --- a/packages/client/src/components/taskmanager.api-window.vue +++ /dev/null @@ -1,72 +0,0 @@ -<template> -<XWindow ref="window" - :initial-width="370" - :initial-height="450" - :can-resize="true" - @close="$refs.window.close()" - @closed="$emit('closed')" -> - <template #header>Req Viewer</template> - - <div class="rlkneywz"> - <MkTab v-model="tab" style="border-bottom: solid 0.5px var(--divider);"> - <option value="req">Request</option> - <option value="res">Response</option> - </MkTab> - - <code v-if="tab === 'req'" class="_monospace">{{ reqStr }}</code> - <code v-if="tab === 'res'" class="_monospace">{{ resStr }}</code> - </div> -</XWindow> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as JSON5 from 'json5'; -import XWindow from '@/components/ui/window.vue'; -import MkTab from '@/components/tab.vue'; - -export default defineComponent({ - components: { - XWindow, - MkTab, - }, - - props: { - req: { - required: true, - } - }, - - emits: ['closed'], - - data() { - return { - tab: 'req', - reqStr: JSON5.stringify(this.req.req, null, '\t'), - resStr: JSON5.stringify(this.req.res, null, '\t'), - } - }, - - methods: { - } -}); -</script> - -<style lang="scss" scoped> -.rlkneywz { - display: flex; - flex-direction: column; - height: 100%; - - > code { - display: block; - flex: 1; - padding: 8px; - overflow: auto; - font-size: 0.9em; - tab-size: 2; - white-space: pre; - } -} -</style> diff --git a/packages/client/src/components/taskmanager.vue b/packages/client/src/components/taskmanager.vue deleted file mode 100644 index 6901d88c2c..0000000000 --- a/packages/client/src/components/taskmanager.vue +++ /dev/null @@ -1,233 +0,0 @@ -<template> -<XWindow ref="window" :initial-width="650" :initial-height="420" :can-resize="true" @closed="$emit('closed')"> - <template #header> - <i class="fas fa-terminal" style="margin-right: 0.5em;"></i>Task Manager - </template> - <div class="qljqmnzj _monospace"> - <MkTab v-model="tab" style="border-bottom: solid 0.5px var(--divider);"> - <option value="windows">Windows</option> - <option value="stream">Stream</option> - <option value="streamPool">Stream (Pool)</option> - <option value="api">API</option> - </MkTab> - - <div class="content"> - <div v-if="tab === 'windows'" v-follow class="windows"> - <div class="header"> - <div>#ID</div> - <div>Component</div> - <div>Action</div> - </div> - <div v-for="p in popups"> - <div>#{{ p.id }}</div> - <div>{{ p.component.name ? p.component.name : '<anonymous>' }}</div> - <div><button class="_textButton" @click="killPopup(p)">Kill</button></div> - </div> - </div> - <div v-if="tab === 'stream'" v-follow class="stream"> - <div class="header"> - <div>#ID</div> - <div>Ch</div> - <div>Handle</div> - <div>In</div> - <div>Out</div> - </div> - <div v-for="c in connections"> - <div>#{{ c.id }}</div> - <div>{{ c.channel }}</div> - <div v-if="c.users !== null">(shared)<span v-if="c.name">{{ ' ' + c.name }}</span></div> - <div v-else>{{ c.name ? c.name : '<anonymous>' }}</div> - <div>{{ c.in }}</div> - <div>{{ c.out }}</div> - </div> - </div> - <div v-if="tab === 'streamPool'" v-follow class="streamPool"> - <div class="header"> - <div>#ID</div> - <div>Ch</div> - <div>Users</div> - </div> - <div v-for="p in pools"> - <div>#{{ p.id }}</div> - <div>{{ p.channel }}</div> - <div>{{ p.users }}</div> - </div> - </div> - <div v-if="tab === 'api'" v-follow class="api"> - <div class="header"> - <div>#ID</div> - <div>Endpoint</div> - <div>State</div> - </div> - <div v-for="req in apiRequests" @click="showReq(req)"> - <div>#{{ req.id }}</div> - <div>{{ req.endpoint }}</div> - <div class="state" :class="req.state">{{ req.state }}</div> - </div> - </div> - </div> - - <footer> - <div><span class="label">Windows</span>{{ popups.length }}</div> - <div><span class="label">Stream</span>{{ connections.length }}</div> - <div><span class="label">Stream (Pool)</span>{{ pools.length }}</div> - </footer> - </div> -</XWindow> -</template> - -<script lang="ts"> -import { defineComponent, markRaw, onBeforeUnmount, ref, shallowRef } from 'vue'; -import XWindow from '@/components/ui/window.vue'; -import MkTab from '@/components/tab.vue'; -import MkButton from '@/components/ui/button.vue'; -import follow from '@/directives/follow-append'; -import * as os from '@/os'; - -export default defineComponent({ - components: { - XWindow, - MkTab, - MkButton, - }, - - directives: { - follow - }, - - props: { - }, - - emits: ['closed'], - - setup() { - const connections = shallowRef([]); - const pools = shallowRef([]); - const refreshStreamInfo = () => { - console.log(os.stream.sharedConnectionPools, os.stream.sharedConnections, os.stream.nonSharedConnections); - const conn = os.stream.sharedConnections.map(c => ({ - id: c.id, name: c.name, channel: c.channel, users: c.pool.users, in: c.inCount, out: c.outCount, - })).concat(os.stream.nonSharedConnections.map(c => ({ - id: c.id, name: c.name, channel: c.channel, users: null, in: c.inCount, out: c.outCount, - }))); - conn.sort((a, b) => (a.id > b.id) ? 1 : -1); - connections.value = conn; - pools.value = os.stream.sharedConnectionPools; - }; - const interval = setInterval(refreshStreamInfo, 1000); - onBeforeUnmount(() => { - clearInterval(interval); - }); - - const killPopup = p => { - os.popups.value = os.popups.value.filter(x => x !== p); - }; - - const showReq = req => { - os.popup(import('./taskmanager.api-window.vue'), { - req: req - }, { - }, 'closed'); - }; - - return { - tab: ref('stream'), - popups: os.popups, - apiRequests: os.apiRequests, - connections, - pools, - killPopup, - showReq, - }; - }, -}); -</script> - -<style lang="scss" scoped> -.qljqmnzj { - display: flex; - flex-direction: column; - height: 100%; - - > .content { - flex: 1; - overflow: auto; - - > div { - display: table; - width: 100%; - padding: 16px; - box-sizing: border-box; - - > div { - display: table-row; - - &:nth-child(even) { - //background: rgba(0, 0, 0, 0.1); - } - - &.header { - opacity: 0.7; - } - - > div { - display: table-cell; - white-space: nowrap; - - &:not(:last-child) { - padding-right: 8px; - } - } - } - - &.api { - > div { - &:not(.header) { - cursor: pointer; - - &:hover { - color: var(--accent); - } - } - - > .state { - &.pending { - color: var(--warn); - } - - &.success { - color: var(--success); - } - - &.failed { - color: var(--error); - } - } - } - } - } - } - - > footer { - display: flex; - width: 100%; - padding: 8px 16px; - box-sizing: border-box; - border-top: solid 0.5px var(--divider); - font-size: 0.9em; - - > div { - flex: 1; - - > .label { - opacity: 0.7; - margin-right: 0.5em; - - &:after { - content: ":"; - } - } - } - } -} -</style> diff --git a/packages/client/src/components/timeline.vue b/packages/client/src/components/timeline.vue index f8a800872f..59956b9526 100644 --- a/packages/client/src/components/timeline.vue +++ b/packages/client/src/components/timeline.vue @@ -1,183 +1,143 @@ <template> -<XNotes ref="tl" :no-gap="!$store.state.showGapBetweenNotesInTimeline" :pagination="pagination" @before="$emit('before')" @after="e => $emit('after', e)" @queue="$emit('queue', $event)"/> +<XNotes ref="tlComponent" :no-gap="!$store.state.showGapBetweenNotesInTimeline" :pagination="pagination" @queue="emit('queue', $event)"/> </template> -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; +<script lang="ts" setup> +import { ref, computed, provide, onUnmounted } from 'vue'; import XNotes from './notes.vue'; import * as os from '@/os'; +import { stream } from '@/stream'; import * as sound from '@/scripts/sound'; +import { $i } from '@/account'; -export default defineComponent({ - components: { - XNotes - }, +const props = defineProps<{ + src: string; + list?: string; + antenna?: string; + channel?: string; + sound?: boolean; +}>(); - provide() { - return { - inChannel: this.src === 'channel' - }; - }, +const emit = defineEmits<{ + (e: 'note'): void; + (e: 'queue', count: number): void; +}>(); - props: { - src: { - type: String, - required: true - }, - list: { - type: String, - required: false - }, - antenna: { - type: String, - required: false - }, - channel: { - type: String, - required: false - }, - sound: { - type: Boolean, - required: false, - default: false, - } - }, - - emits: ['note', 'queue', 'before', 'after'], +provide('inChannel', computed(() => props.src === 'channel')); - data() { - return { - connection: null, - connection2: null, - pagination: null, - baseQuery: { - includeMyRenotes: this.$store.state.showMyRenotes, - includeRenotedMyNotes: this.$store.state.showRenotedMyNotes, - includeLocalRenotes: this.$store.state.showLocalRenotes - }, - query: {}, - date: null - }; - }, +const tlComponent: InstanceType<typeof XNotes> = $ref(); - created() { - const prepend = note => { - (this.$refs.tl as any).prepend(note); +const prepend = note => { + tlComponent.pagingComponent?.prepend(note); - this.$emit('note'); + emit('note'); - if (this.sound) { - sound.play(note.userId === this.$i.id ? 'noteMy' : 'note'); - } - }; + if (props.sound) { + sound.play($i && (note.userId === $i.id) ? 'noteMy' : 'note'); + } +}; - const onUserAdded = () => { - (this.$refs.tl as any).reload(); - }; +const onUserAdded = () => { + tlComponent.pagingComponent?.reload(); +}; - const onUserRemoved = () => { - (this.$refs.tl as any).reload(); - }; +const onUserRemoved = () => { + tlComponent.pagingComponent?.reload(); +}; - const onChangeFollowing = () => { - if (!this.$refs.tl.backed) { - this.$refs.tl.reload(); - } - }; +const onChangeFollowing = () => { + if (!tlComponent.pagingComponent?.backed) { + tlComponent.pagingComponent?.reload(); + } +}; - let endpoint; +let endpoint; +let query; +let connection; +let connection2; - if (this.src == 'antenna') { - endpoint = 'antennas/notes'; - this.query = { - antennaId: this.antenna - }; - this.connection = markRaw(os.stream.useChannel('antenna', { - antennaId: this.antenna - })); - this.connection.on('note', prepend); - } else if (this.src == 'home') { - endpoint = 'notes/timeline'; - this.connection = markRaw(os.stream.useChannel('homeTimeline')); - this.connection.on('note', prepend); +if (props.src === 'antenna') { + endpoint = 'antennas/notes'; + query = { + antennaId: props.antenna + }; + connection = stream.useChannel('antenna', { + antennaId: props.antenna + }); + connection.on('note', prepend); +} else if (props.src === 'home') { + endpoint = 'notes/timeline'; + connection = stream.useChannel('homeTimeline'); + connection.on('note', prepend); - this.connection2 = markRaw(os.stream.useChannel('main')); - this.connection2.on('follow', onChangeFollowing); - this.connection2.on('unfollow', onChangeFollowing); - } else if (this.src == 'local') { - endpoint = 'notes/local-timeline'; - this.connection = markRaw(os.stream.useChannel('localTimeline')); - this.connection.on('note', prepend); - } else if (this.src == 'social') { - endpoint = 'notes/hybrid-timeline'; - this.connection = markRaw(os.stream.useChannel('hybridTimeline')); - this.connection.on('note', prepend); - } else if (this.src == 'global') { - endpoint = 'notes/global-timeline'; - this.connection = markRaw(os.stream.useChannel('globalTimeline')); - this.connection.on('note', prepend); - } else if (this.src == 'mentions') { - endpoint = 'notes/mentions'; - this.connection = markRaw(os.stream.useChannel('main')); - this.connection.on('mention', prepend); - } else if (this.src == 'directs') { - endpoint = 'notes/mentions'; - this.query = { - visibility: 'specified' - }; - const onNote = note => { - if (note.visibility == 'specified') { - prepend(note); - } - }; - this.connection = markRaw(os.stream.useChannel('main')); - this.connection.on('mention', onNote); - } else if (this.src == 'list') { - endpoint = 'notes/user-list-timeline'; - this.query = { - listId: this.list - }; - this.connection = markRaw(os.stream.useChannel('userList', { - listId: this.list - })); - this.connection.on('note', prepend); - this.connection.on('userAdded', onUserAdded); - this.connection.on('userRemoved', onUserRemoved); - } else if (this.src == 'channel') { - endpoint = 'channels/timeline'; - this.query = { - channelId: this.channel - }; - this.connection = markRaw(os.stream.useChannel('channel', { - channelId: this.channel - })); - this.connection.on('note', prepend); + connection2 = stream.useChannel('main'); + connection2.on('follow', onChangeFollowing); + connection2.on('unfollow', onChangeFollowing); +} else if (props.src === 'local') { + endpoint = 'notes/local-timeline'; + connection = stream.useChannel('localTimeline'); + connection.on('note', prepend); +} else if (props.src === 'social') { + endpoint = 'notes/hybrid-timeline'; + connection = stream.useChannel('hybridTimeline'); + connection.on('note', prepend); +} else if (props.src === 'global') { + endpoint = 'notes/global-timeline'; + connection = stream.useChannel('globalTimeline'); + connection.on('note', prepend); +} else if (props.src === 'mentions') { + endpoint = 'notes/mentions'; + connection = stream.useChannel('main'); + connection.on('mention', prepend); +} else if (props.src === 'directs') { + endpoint = 'notes/mentions'; + query = { + visibility: 'specified' + }; + const onNote = note => { + if (note.visibility == 'specified') { + prepend(note); } + }; + connection = stream.useChannel('main'); + connection.on('mention', onNote); +} else if (props.src === 'list') { + endpoint = 'notes/user-list-timeline'; + query = { + listId: props.list + }; + connection = stream.useChannel('userList', { + listId: props.list + }); + connection.on('note', prepend); + connection.on('userAdded', onUserAdded); + connection.on('userRemoved', onUserRemoved); +} else if (props.src === 'channel') { + endpoint = 'channels/timeline'; + query = { + channelId: props.channel + }; + connection = stream.useChannel('channel', { + channelId: props.channel + }); + connection.on('note', prepend); +} - this.pagination = { - endpoint: endpoint, - limit: 10, - params: init => ({ - untilDate: this.date?.getTime(), - ...this.baseQuery, ...this.query - }) - }; - }, - - beforeUnmount() { - this.connection.dispose(); - if (this.connection2) this.connection2.dispose(); - }, +const pagination = { + endpoint: endpoint, + limit: 10, + params: query, +}; - methods: { - focus() { - this.$refs.tl.focus(); - }, - - timetravel(date?: Date) { - this.date = date; - this.$refs.tl.reload(); - } - } +onUnmounted(() => { + connection.dispose(); + if (connection2) connection2.dispose(); }); + +/* TODO +const timetravel = (date?: Date) => { + this.date = date; + this.$refs.tl.reload(); +}; +*/ </script> diff --git a/packages/client/src/components/toast.vue b/packages/client/src/components/toast.vue index 914704c527..c114379716 100644 --- a/packages/client/src/components/toast.vue +++ b/packages/client/src/components/toast.vue @@ -1,6 +1,6 @@ <template> <div class="mk-toast"> - <transition name="toast" appear @after-leave="$emit('closed')"> + <transition :name="$store.state.animation ? 'toast' : ''" appear @after-leave="emit('closed')"> <div v-if="showing" class="body _acrylic" :style="{ zIndex }"> <div class="message"> {{ message }} @@ -10,29 +10,25 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted, ref } from 'vue'; import * as os from '@/os'; -export default defineComponent({ - props: { - message: { - type: String, - required: true, - }, - }, - emits: ['closed'], - data() { - return { - showing: true, - zIndex: os.claimZIndex('high'), - }; - }, - mounted() { - setTimeout(() => { - this.showing = false; - }, 4000); - } +defineProps<{ + message: string; +}>(); + +const emit = defineEmits<{ + (e: 'closed'): void; +}>(); + +const showing = ref(true); +const zIndex = os.claimZIndex('high'); + +onMounted(() => { + window.setTimeout(() => { + showing.value = false; + }, 4000); }); </script> diff --git a/packages/client/src/components/ui/button.vue b/packages/client/src/components/ui/button.vue index 804a2e2720..c7b6c8ba96 100644 --- a/packages/client/src/components/ui/button.vue +++ b/packages/client/src/components/ui/button.vue @@ -117,14 +117,14 @@ export default defineComponent({ const scale = calcCircleScale(e.target.clientWidth, e.target.clientHeight, circleCenterX, circleCenterY); - setTimeout(() => { + window.setTimeout(() => { ripple.style.transform = 'scale(' + (scale / 2) + ')'; }, 1); - setTimeout(() => { + window.setTimeout(() => { ripple.style.transition = 'all 1s ease'; ripple.style.opacity = '0'; }, 1000); - setTimeout(() => { + window.setTimeout(() => { if (this.$refs.ripples) this.$refs.ripples.removeChild(ripple); }, 2000); } diff --git a/packages/client/src/components/ui/container.vue b/packages/client/src/components/ui/container.vue index fcd9f32290..7c595d8116 100644 --- a/packages/client/src/components/ui/container.vue +++ b/packages/client/src/components/ui/container.vue @@ -10,7 +10,7 @@ </button> </div> </header> - <transition name="container-toggle" + <transition :name="$store.state.animation ? 'container-toggle' : ''" @enter="enter" @after-enter="afterEnter" @leave="leave" diff --git a/packages/client/src/components/ui/folder.vue b/packages/client/src/components/ui/folder.vue index 9795b1d81a..fe1602b2bb 100644 --- a/packages/client/src/components/ui/folder.vue +++ b/packages/client/src/components/ui/folder.vue @@ -8,7 +8,7 @@ <template v-else><i class="fas fa-angle-down"></i></template> </button> </header> - <transition name="folder-toggle" + <transition :name="$store.state.animation ? 'folder-toggle' : ''" @enter="enter" @after-enter="afterEnter" @leave="leave" diff --git a/packages/client/src/components/ui/menu.vue b/packages/client/src/components/ui/menu.vue index 6f3f277b11..41165c8d33 100644 --- a/packages/client/src/components/ui/menu.vue +++ b/packages/client/src/components/ui/menu.vue @@ -24,7 +24,7 @@ <span>{{ item.text }}</span> <span v-if="item.indicate" class="indicator"><i class="fas fa-circle"></i></span> </a> - <button v-else-if="item.type === 'user'" :tabindex="i" class="_button item" @click="clicked(item.action, $event)"> + <button v-else-if="item.type === 'user'" :tabindex="i" class="_button item" :class="{ active: item.active }" :disabled="item.active" @click="clicked(item.action, $event)"> <MkAvatar :user="item.user" class="avatar"/><MkUserName :user="item.user"/> <span v-if="item.indicate" class="indicator"><i class="fas fa-circle"></i></span> </button> diff --git a/packages/client/src/components/ui/modal.vue b/packages/client/src/components/ui/modal.vue index 3e2e59b27c..c691c8c6d0 100644 --- a/packages/client/src/components/ui/modal.vue +++ b/packages/client/src/components/ui/modal.vue @@ -211,7 +211,7 @@ export default defineComponent({ contentClicking = true; window.addEventListener('mouseup', e => { // click イベントより先に mouseup イベントが発生するかもしれないのでちょっと待つ - setTimeout(() => { + window.setTimeout(() => { contentClicking = false; }, 100); }, { passive: true, once: true }); diff --git a/packages/client/src/components/ui/pagination.vue b/packages/client/src/components/ui/pagination.vue index 64af4a54f7..13f3215671 100644 --- a/packages/client/src/components/ui/pagination.vue +++ b/packages/client/src/components/ui/pagination.vue @@ -1,5 +1,5 @@ <template> -<transition name="fade" mode="out-in"> +<transition :name="$store.state.animation ? 'fade' : ''" mode="out-in"> <MkLoading v-if="fetching"/> <MkError v-else-if="error" @retry="init()"/> @@ -13,43 +13,269 @@ </slot> </div> - <div v-else class="cxiknjgy"> + <div v-else ref="rootEl"> <slot :items="items"></slot> - <div v-show="more" key="_more_" class="more _gap"> - <MkButton v-appear="($store.state.enableInfiniteScroll && !disableAutoLoad) ? fetchMore : null" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMore"> - <template v-if="!moreFetching">{{ $ts.loadMore }}</template> - <template v-if="moreFetching"><MkLoading inline/></template> + <div v-show="more" key="_more_" class="cxiknjgy _gap"> + <MkButton v-if="!moreFetching" v-appear="($store.state.enableInfiniteScroll && !disableAutoLoad) ? fetchMore : null" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMore"> + {{ $ts.loadMore }} </MkButton> + <MkLoading v-else class="loading"/> </div> </div> </transition> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import MkButton from './button.vue'; -import paging from '@/scripts/paging'; +<script lang="ts" setup> +import { computed, ComputedRef, isRef, markRaw, onActivated, onDeactivated, Ref, ref, watch } from 'vue'; +import * as misskey from 'misskey-js'; +import * as os from '@/os'; +import { onScrollTop, isTopVisible, getScrollPosition, getScrollContainer } from '@/scripts/scroll'; +import MkButton from '@/components/ui/button.vue'; -export default defineComponent({ - components: { - MkButton - }, +const SECOND_FETCH_LIMIT = 30; - mixins: [ - paging({}), - ], +export type Paging<E extends keyof misskey.Endpoints = keyof misskey.Endpoints> = { + endpoint: E; + limit: number; + params?: misskey.Endpoints[E]['req'] | ComputedRef<misskey.Endpoints[E]['req']>; - props: { - pagination: { - required: true - }, + /** + * 検索APIのような、ページング不可なエンドポイントを利用する場合 + * (そのようなAPIをこの関数で使うのは若干矛盾してるけど) + */ + noPaging?: boolean; - disableAutoLoad: { - type: Boolean, - required: false, - default: false, + /** + * items 配列の中身を逆順にする(新しい方が最後) + */ + reversed?: boolean; + + offsetMode?: boolean; +}; + +const props = withDefaults(defineProps<{ + pagination: Paging; + disableAutoLoad?: boolean; + displayLimit?: number; +}>(), { + displayLimit: 30, +}); + +const emit = defineEmits<{ + (e: 'queue', count: number): void; +}>(); + +type Item = { id: string; [another: string]: unknown; }; + +const rootEl = ref<HTMLElement>(); +const items = ref<Item[]>([]); +const queue = ref<Item[]>([]); +const offset = ref(0); +const fetching = ref(true); +const moreFetching = ref(false); +const more = ref(false); +const backed = ref(false); // 遡り中か否か +const isBackTop = ref(false); +const empty = computed(() => items.value.length === 0); +const error = ref(false); + +const init = async (): Promise<void> => { + queue.value = []; + fetching.value = true; + const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {}; + await os.api(props.pagination.endpoint, { + ...params, + limit: props.pagination.noPaging ? (props.pagination.limit || 10) : (props.pagination.limit || 10) + 1, + }).then(res => { + for (let i = 0; i < res.length; i++) { + const item = res[i]; + if (props.pagination.reversed) { + if (i === res.length - 2) item._shouldInsertAd_ = true; + } else { + if (i === 3) item._shouldInsertAd_ = true; + } + } + if (!props.pagination.noPaging && (res.length > (props.pagination.limit || 10))) { + res.pop(); + items.value = props.pagination.reversed ? [...res].reverse() : res; + more.value = true; + } else { + items.value = props.pagination.reversed ? [...res].reverse() : res; + more.value = false; + } + offset.value = res.length; + error.value = false; + fetching.value = false; + }, e => { + error.value = true; + fetching.value = false; + }); +}; + +const reload = (): void => { + items.value = []; + init(); +}; + +const fetchMore = async (): Promise<void> => { + if (!more.value || fetching.value || moreFetching.value || items.value.length === 0) return; + moreFetching.value = true; + backed.value = true; + const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {}; + await os.api(props.pagination.endpoint, { + ...params, + limit: SECOND_FETCH_LIMIT + 1, + ...(props.pagination.offsetMode ? { + offset: offset.value, + } : { + untilId: props.pagination.reversed ? items.value[0].id : items.value[items.value.length - 1].id, + }), + }).then(res => { + for (let i = 0; i < res.length; i++) { + const item = res[i]; + if (props.pagination.reversed) { + if (i === res.length - 9) item._shouldInsertAd_ = true; + } else { + if (i === 10) item._shouldInsertAd_ = true; + } + } + if (res.length > SECOND_FETCH_LIMIT) { + res.pop(); + items.value = props.pagination.reversed ? [...res].reverse().concat(items.value) : items.value.concat(res); + more.value = true; + } else { + items.value = props.pagination.reversed ? [...res].reverse().concat(items.value) : items.value.concat(res); + more.value = false; + } + offset.value += res.length; + moreFetching.value = false; + }, e => { + moreFetching.value = false; + }); +}; + +const fetchMoreAhead = async (): Promise<void> => { + if (!more.value || fetching.value || moreFetching.value || items.value.length === 0) return; + moreFetching.value = true; + const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {}; + await os.api(props.pagination.endpoint, { + ...params, + limit: SECOND_FETCH_LIMIT + 1, + ...(props.pagination.offsetMode ? { + offset: offset.value, + } : { + sinceId: props.pagination.reversed ? items.value[0].id : items.value[items.value.length - 1].id, + }), + }).then(res => { + if (res.length > SECOND_FETCH_LIMIT) { + res.pop(); + items.value = props.pagination.reversed ? [...res].reverse().concat(items.value) : items.value.concat(res); + more.value = true; + } else { + items.value = props.pagination.reversed ? [...res].reverse().concat(items.value) : items.value.concat(res); + more.value = false; + } + offset.value += res.length; + moreFetching.value = false; + }, e => { + moreFetching.value = false; + }); +}; + +const prepend = (item: Item): void => { + if (props.pagination.reversed) { + if (rootEl.value) { + const container = getScrollContainer(rootEl.value); + if (container == null) return; // TODO? + + const pos = getScrollPosition(rootEl.value); + const viewHeight = container.clientHeight; + const height = container.scrollHeight; + const isBottom = (pos + viewHeight > height - 32); + if (isBottom) { + // オーバーフローしたら古いアイテムは捨てる + if (items.value.length >= props.displayLimit) { + // このやり方だとVue 3.2以降アニメーションが動かなくなる + //items.value = items.value.slice(-props.displayLimit); + while (items.value.length >= props.displayLimit) { + items.value.shift(); + } + more.value = true; + } + } } - }, + items.value.push(item); + // TODO + } else { + // 初回表示時はunshiftだけでOK + if (!rootEl.value) { + items.value.unshift(item); + return; + } + + const isTop = isBackTop.value || (document.body.contains(rootEl.value) && isTopVisible(rootEl.value)); + + if (isTop) { + // Prepend the item + items.value.unshift(item); + + // オーバーフローしたら古いアイテムは捨てる + if (items.value.length >= props.displayLimit) { + // このやり方だとVue 3.2以降アニメーションが動かなくなる + //this.items = items.value.slice(0, props.displayLimit); + while (items.value.length >= props.displayLimit) { + items.value.pop(); + } + more.value = true; + } + } else { + queue.value.push(item); + onScrollTop(rootEl.value, () => { + for (const item of queue.value) { + prepend(item); + } + queue.value = []; + }); + } + } +}; + +const append = (item: Item): void => { + items.value.push(item); +}; + +const updateItem = (id: Item['id'], replacer: (old: Item) => Item): void => { + const i = items.value.findIndex(item => item.id === id); + items.value[i] = replacer(items.value[i]); +}; + +if (props.pagination.params && isRef(props.pagination.params)) { + watch(props.pagination.params, init, { deep: true }); +} + +watch(queue, (a, b) => { + if (a.length === 0 && b.length === 0) return; + emit('queue', queue.value.length); +}, { deep: true }); + +init(); + +onActivated(() => { + isBackTop.value = false; +}); + +onDeactivated(() => { + isBackTop.value = window.scrollY === 0; +}); + +defineExpose({ + items, + backed, + reload, + fetchMoreAhead, + prepend, + append, + updateItem, }); </script> @@ -64,11 +290,9 @@ export default defineComponent({ } .cxiknjgy { - > .more > .button { + > .button { margin-left: auto; margin-right: auto; - height: 48px; - min-width: 150px; } } </style> diff --git a/packages/client/src/components/ui/tooltip.vue b/packages/client/src/components/ui/tooltip.vue index 2e48ab623e..394b068352 100644 --- a/packages/client/src/components/ui/tooltip.vue +++ b/packages/client/src/components/ui/tooltip.vue @@ -1,5 +1,5 @@ <template> -<transition name="tooltip" appear @after-leave="$emit('closed')"> +<transition :name="$store.state.animation ? 'tooltip' : ''" appear @after-leave="$emit('closed')"> <div v-show="showing" ref="el" class="buebdbiu _acrylic _shadow" :style="{ zIndex, maxWidth: maxWidth + 'px' }"> <slot>{{ text }}</slot> </div> diff --git a/packages/client/src/components/ui/window.vue b/packages/client/src/components/ui/window.vue index bd33289ccc..fa32ecfdef 100644 --- a/packages/client/src/components/ui/window.vue +++ b/packages/client/src/components/ui/window.vue @@ -147,9 +147,9 @@ export default defineComponent({ } }, - onContextmenu(e) { + onContextmenu(ev: MouseEvent) { if (this.contextmenu) { - os.contextMenu(this.contextmenu, e); + os.contextMenu(this.contextmenu, ev); } }, diff --git a/packages/client/src/components/url-preview-popup.vue b/packages/client/src/components/url-preview-popup.vue index c345bafcf9..5f3717ab91 100644 --- a/packages/client/src/components/url-preview-popup.vue +++ b/packages/client/src/components/url-preview-popup.vue @@ -1,6 +1,6 @@ <template> <div class="fgmtyycl" :style="{ zIndex, top: top + 'px', left: left + 'px' }"> - <transition name="zoom" @after-leave="$emit('closed')"> + <transition :name="$store.state.animation ? 'zoom' : ''" @after-leave="$emit('closed')"> <MkUrlPreview v-if="showing" class="_popup _shadow" :url="url"/> </transition> </div> diff --git a/packages/client/src/components/url-preview.vue b/packages/client/src/components/url-preview.vue index fe88985a62..6c57957617 100644 --- a/packages/client/src/components/url-preview.vue +++ b/packages/client/src/components/url-preview.vue @@ -4,10 +4,10 @@ <iframe :src="player.url + (player.url.match(/\?/) ? '&autoplay=1&auto_play=1' : '?autoplay=1&auto_play=1')" :width="player.width || '100%'" :heigth="player.height || 250" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen /> </div> <div v-else-if="tweetId && tweetExpanded" ref="twitter" class="twitter"> - <iframe ref="tweet" scrolling="no" frameborder="no" :style="{ position: 'relative', left: `${tweetLeft}px`, width: `${tweetLeft < 0 ? 'auto' : '100%'}`, height: `${tweetHeight}px` }" :src="`https://platform.twitter.com/embed/index.html?embedId=${embedId}&hideCard=false&hideThread=false&lang=en&theme=${$store.state.darkMode ? 'dark' : 'light'}&id=${tweetId}`"></iframe> + <iframe ref="tweet" scrolling="no" frameborder="no" :style="{ position: 'relative', width: '100%', height: `${tweetHeight}px` }" :src="`https://platform.twitter.com/embed/index.html?embedId=${embedId}&hideCard=false&hideThread=false&lang=en&theme=${$store.state.darkMode ? 'dark' : 'light'}&id=${tweetId}`"></iframe> </div> <div v-else v-size="{ max: [400, 350] }" class="mk-url-preview"> - <transition name="zoom" mode="out-in"> + <transition :name="$store.state.animation ? 'zoom' : ''" mode="out-in"> <component :is="self ? 'MkA' : 'a'" v-if="!fetching" :class="{ compact }" :[attr]="self ? url.substr(local.length) : url" rel="nofollow noopener" :target="target" :title="url"> <div v-if="thumbnail" class="thumbnail" :style="`background-image: url('${thumbnail}')`"> <button v-if="!playerEnabled && player.url" class="_button" :title="$ts.enablePlayer" @click.prevent="playerEnabled = true"><i class="fas fa-play-circle"></i></button> @@ -32,110 +32,80 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted, onUnmounted } from 'vue'; import { url as local, lang } from '@/config'; -import * as os from '@/os'; -export default defineComponent({ - props: { - url: { - type: String, - require: true - }, - - detail: { - type: Boolean, - required: false, - default: false - }, - - compact: { - type: Boolean, - required: false, - default: false - }, - }, - - data() { - const self = this.url.startsWith(local); - return { - local, - fetching: true, - title: null, - description: null, - thumbnail: null, - icon: null, - sitename: null, - player: { - url: null, - width: null, - height: null - }, - tweetId: null, - tweetExpanded: this.detail, - embedId: `embed${Math.random().toString().replace(/\D/,'')}`, - tweetHeight: 150, - tweetLeft: 0, - playerEnabled: false, - self: self, - attr: self ? 'to' : 'href', - target: self ? null : '_blank', - }; - }, +const props = withDefaults(defineProps<{ + url: string; + detail?: boolean; + compact?: boolean; +}>(), { + detail: false, + compact: false, +}); - created() { - const requestUrl = new URL(this.url); +const self = props.url.startsWith(local); +const attr = self ? 'to' : 'href'; +const target = self ? null : '_blank'; +let fetching = $ref(true); +let title = $ref<string | null>(null); +let description = $ref<string | null>(null); +let thumbnail = $ref<string | null>(null); +let icon = $ref<string | null>(null); +let sitename = $ref<string | null>(null); +let player = $ref({ + url: null, + width: null, + height: null +}); +let playerEnabled = $ref(false); +let tweetId = $ref<string | null>(null); +let tweetExpanded = $ref(props.detail); +const embedId = `embed${Math.random().toString().replace(/\D/,'')}`; +let tweetHeight = $ref(150); - if (requestUrl.hostname == 'twitter.com') { - const m = requestUrl.pathname.match(/^\/.+\/status(?:es)?\/(\d+)/); - if (m) this.tweetId = m[1]; - } +const requestUrl = new URL(props.url); - if (requestUrl.hostname === 'music.youtube.com' && requestUrl.pathname.match('^/(?:watch|channel)')) { - requestUrl.hostname = 'www.youtube.com'; - } +if (requestUrl.hostname == 'twitter.com') { + const m = requestUrl.pathname.match(/^\/.+\/status(?:es)?\/(\d+)/); + if (m) tweetId = m[1]; +} - const requestLang = (lang || 'ja-JP').replace('ja-KS', 'ja-JP'); +if (requestUrl.hostname === 'music.youtube.com' && requestUrl.pathname.match('^/(?:watch|channel)')) { + requestUrl.hostname = 'www.youtube.com'; +} - requestUrl.hash = ''; +const requestLang = (lang || 'ja-JP').replace('ja-KS', 'ja-JP'); - fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${requestLang}`).then(res => { - res.json().then(info => { - if (info.url == null) return; - this.title = info.title; - this.description = info.description; - this.thumbnail = info.thumbnail; - this.icon = info.icon; - this.sitename = info.sitename; - this.fetching = false; - this.player = info.player; - }) - }); +requestUrl.hash = ''; - (window as any).addEventListener('message', this.adjustTweetHeight); - }, +fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${requestLang}`).then(res => { + res.json().then(info => { + if (info.url == null) return; + title = info.title; + description = info.description; + thumbnail = info.thumbnail; + icon = info.icon; + sitename = info.sitename; + fetching = false; + player = info.player; + }) +}); - mounted() { - // 300pxないと絶対右にはみ出るので左に移動してしまう - const areaWidth = (this.$el as any)?.clientWidth; - if (areaWidth && areaWidth < 300) this.tweetLeft = areaWidth - 241; - }, +function adjustTweetHeight(message: any) { + if (message.origin !== 'https://platform.twitter.com') return; + const embed = message.data?.['twttr.embed']; + if (embed?.method !== 'twttr.private.resize') return; + if (embed?.id !== embedId) return; + const height = embed?.params[0]?.height; + if (height) tweetHeight = height; +} - beforeUnmount() { - (window as any).removeEventListener('message', this.adjustTweetHeight); - }, +(window as any).addEventListener('message', adjustTweetHeight); - methods: { - adjustTweetHeight(message: any) { - if (message.origin !== 'https://platform.twitter.com') return; - const embed = message.data?.['twttr.embed']; - if (embed?.method !== 'twttr.private.resize') return; - if (embed?.id !== this.embedId) return; - const height = embed?.params[0]?.height; - if (height) this.tweetHeight = height; - }, - }, +onUnmounted(() => { + (window as any).removeEventListener('message', adjustTweetHeight); }); </script> diff --git a/packages/client/src/components/user-info.vue b/packages/client/src/components/user-info.vue index 779a71358d..6a25d412fc 100644 --- a/packages/client/src/components/user-info.vue +++ b/packages/client/src/components/user-info.vue @@ -27,32 +27,14 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import * as misskey from 'misskey-js'; import MkFollowButton from './follow-button.vue'; import { userPage } from '@/filters/user'; -export default defineComponent({ - components: { - MkFollowButton - }, - - props: { - user: { - type: Object, - required: true - }, - }, - - data() { - return { - }; - }, - - methods: { - userPage, - } -}); +defineProps<{ + user: misskey.entities.UserDetailed; +}>(); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/user-list.vue b/packages/client/src/components/user-list.vue index 2148dab608..3e273721c7 100644 --- a/packages/client/src/components/user-list.vue +++ b/packages/client/src/components/user-list.vue @@ -1,91 +1,39 @@ <template> -<MkError v-if="error" @retry="init()"/> +<MkPagination ref="pagingComponent" :pagination="pagination"> + <template #empty> + <div class="_fullinfo"> + <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> + <div>{{ $ts.noUsers }}</div> + </div> + </template> -<div v-else class="efvhhmdq _isolated"> - <div v-if="empty" class="no-users"> - <p>{{ $ts.noUsers }}</p> - </div> - <div class="users"> - <MkUserInfo v-for="user in users" :key="user.id" class="user" :user="user"/> - </div> - <button v-show="more" v-appear="$store.state.enableInfiniteScroll ? fetchMore : null" class="more" :class="{ fetching: moreFetching }" :disabled="moreFetching" @click="fetchMore"> - <template v-if="moreFetching"><i class="fas fa-spinner fa-pulse fa-fw"></i></template>{{ moreFetching ? $ts.loading : $ts.loadMore }} - </button> -</div> + <template #default="{ items: users }"> + <div class="efvhhmdq"> + <MkUserInfo v-for="user in users" :key="user.id" class="user" :user="user"/> + </div> + </template> +</MkPagination> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import paging from '@/scripts/paging'; -import MkUserInfo from './user-info.vue'; +<script lang="ts" setup> +import { ref } from 'vue'; +import MkUserInfo from '@/components/user-info.vue'; +import MkPagination from '@/components/ui/pagination.vue'; +import { Paging } from '@/components/ui/pagination.vue'; import { userPage } from '@/filters/user'; -export default defineComponent({ - components: { - MkUserInfo, - }, +const props = defineProps<{ + pagination: Paging; + noGap?: boolean; +}>(); - mixins: [ - paging({}), - ], - - props: { - pagination: { - required: true - }, - extract: { - required: false - }, - expanded: { - type: Boolean, - default: true - }, - }, - - computed: { - users() { - return this.extract ? this.extract(this.items) : this.items; - } - }, - - methods: { - userPage - } -}); +const pagingComponent = ref<InstanceType<typeof MkPagination>>(); </script> <style lang="scss" scoped> .efvhhmdq { - > .no-users { - text-align: center; - } - - > .users { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); - grid-gap: var(--margin); - } - - > .more { - display: block; - width: 100%; - padding: 16px; - - &:hover { - background: rgba(#000, 0.025); - } - - &:active { - background: rgba(#000, 0.05); - } - - &.fetching { - cursor: wait; - } - - > i { - margin-right: 4px; - } - } + display: grid; + grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); + grid-gap: var(--margin); } </style> diff --git a/packages/client/src/components/user-online-indicator.vue b/packages/client/src/components/user-online-indicator.vue index 93e9dea57b..a87b0aeff5 100644 --- a/packages/client/src/components/user-online-indicator.vue +++ b/packages/client/src/components/user-online-indicator.vue @@ -2,26 +2,21 @@ <div v-tooltip="text" class="fzgwjkgc" :class="user.onlineStatus"></div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; +import * as misskey from 'misskey-js'; +import { i18n } from '@/i18n'; -export default defineComponent({ - props: { - user: { - type: Object, - required: true - }, - }, +const props = defineProps<{ + user: misskey.entities.User; +}>(); - computed: { - text(): string { - switch (this.user.onlineStatus) { - case 'online': return this.$ts.online; - case 'active': return this.$ts.active; - case 'offline': return this.$ts.offline; - case 'unknown': return this.$ts.unknown; - } - } +const text = $computed(() => { + switch (props.user.onlineStatus) { + case 'online': return i18n.locale.online; + case 'active': return i18n.locale.active; + case 'offline': return i18n.locale.offline; + case 'unknown': return i18n.locale.unknown; } }); </script> diff --git a/packages/client/src/components/user-preview.vue b/packages/client/src/components/user-preview.vue index f85a32fbe7..51c5330564 100644 --- a/packages/client/src/components/user-preview.vue +++ b/packages/client/src/components/user-preview.vue @@ -1,5 +1,5 @@ <template> -<transition name="popup" appear @after-leave="$emit('closed')"> +<transition :name="$store.state.animation ? 'popup' : ''" appear @after-leave="$emit('closed')"> <div v-if="showing" class="fxxzrfni _popup _shadow" :style="{ zIndex, top: top + 'px', left: left + 'px' }" @mouseover="() => { $emit('mouseover'); }" @mouseleave="() => { $emit('mouseleave'); }"> <div v-if="fetched" class="info"> <div class="banner" :style="user.bannerUrl ? `background-image: url(${user.bannerUrl})` : ''"></div> diff --git a/packages/client/src/components/user-select-dialog.vue b/packages/client/src/components/user-select-dialog.vue index ba2975478b..dbef34d547 100644 --- a/packages/client/src/components/user-select-dialog.vue +++ b/packages/client/src/components/user-select-dialog.vue @@ -1,5 +1,5 @@ <template> -<XModalWindow ref="dialog" +<XModalWindow ref="dialogEl" :with-ok-button="true" :ok-button-disabled="selected == null" @click="cancel()" @@ -8,20 +8,20 @@ @closed="$emit('closed')" > <template #header>{{ $ts.selectUser }}</template> - <div class="tbhwbxda _monolithic_"> - <div class="_section"> - <div class="_inputSplit"> - <MkInput ref="username" v-model="username" class="input" @update:modelValue="search"> + <div class="tbhwbxda"> + <div class="form"> + <FormSplit :min-width="170"> + <MkInput ref="usernameEl" v-model="username" @update:modelValue="search"> <template #label>{{ $ts.username }}</template> <template #prefix>@</template> </MkInput> - <MkInput v-model="host" class="input" @update:modelValue="search"> + <MkInput v-model="host" @update:modelValue="search"> <template #label>{{ $ts.host }}</template> <template #prefix>@</template> </MkInput> - </div> + </FormSplit> </div> - <div v-if="username != '' || host != ''" class="_section result" :class="{ hit: users.length > 0 }"> + <div v-if="username != '' || host != ''" class="result" :class="{ hit: users.length > 0 }"> <div v-if="users.length > 0" class="users"> <div v-for="user in users" :key="user.id" class="user" :class="{ selected: selected && selected.id === user.id }" @click="selected = user" @dblclick="ok()"> <MkAvatar :user="user" class="avatar" :show-indicator="true"/> @@ -35,7 +35,7 @@ <span>{{ $ts.noUsers }}</span> </div> </div> - <div v-if="username == '' && host == ''" class="_section recent"> + <div v-if="username == '' && host == ''" class="recent"> <div class="users"> <div v-for="user in recentUsers" :key="user.id" class="user" :class="{ selected: selected && selected.id === user.id }" @click="selected = user" @dblclick="ok()"> <MkAvatar :user="user" class="avatar" :show-indicator="true"/> @@ -50,87 +50,89 @@ </XModalWindow> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import MkInput from './form/input.vue'; +<script lang="ts" setup> +import { nextTick, onMounted } from 'vue'; +import * as misskey from 'misskey-js'; +import MkInput from '@/components/form/input.vue'; +import FormSplit from '@/components/form/split.vue'; import XModalWindow from '@/components/ui/modal-window.vue'; import * as os from '@/os'; +import { defaultStore } from '@/store'; -export default defineComponent({ - components: { - MkInput, - XModalWindow, - }, - - props: { - }, +const emit = defineEmits<{ + (e: 'ok', selected: misskey.entities.UserDetailed): void; + (e: 'cancel'): void; + (e: 'closed'): void; +}>(); - emits: ['ok', 'cancel', 'closed'], +let username = $ref(''); +let host = $ref(''); +let users: misskey.entities.UserDetailed[] = $ref([]); +let recentUsers: misskey.entities.UserDetailed[] = $ref([]); +let selected: misskey.entities.UserDetailed | null = $ref(null); +let usernameEl: HTMLElement = $ref(); +let dialogEl = $ref(); - data() { - return { - username: '', - host: '', - recentUsers: [], - users: [], - selected: null, - }; - }, +const focus = () => { + if (usernameEl) { + usernameEl.focus(); + } +}; - async mounted() { - this.focus(); +const search = () => { + if (username === '' && host === '') { + users = []; + return; + } + os.api('users/search-by-username-and-host', { + username: username, + host: host, + limit: 10, + detail: false + }).then(_users => { + users = _users; + }); +}; - this.$nextTick(() => { - this.focus(); - }); +const ok = () => { + if (selected == null) return; + emit('ok', selected); + dialogEl.close(); - this.recentUsers = await os.api('users/show', { - userIds: this.$store.state.recentlyUsedUsers - }); - }, + // 最近使ったユーザー更新 + let recents = defaultStore.state.recentlyUsedUsers; + recents = recents.filter(x => x !== selected.id); + recents.unshift(selected.id); + defaultStore.set('recentlyUsedUsers', recents.splice(0, 16)); +}; - methods: { - search() { - if (this.username == '' && this.host == '') { - this.users = []; - return; - } - os.api('users/search-by-username-and-host', { - username: this.username, - host: this.host, - limit: 10, - detail: false - }).then(users => { - this.users = users; - }); - }, +const cancel = () => { + emit('cancel'); + dialogEl.close(); +}; - focus() { - this.$refs.username.focus(); - }, +onMounted(() => { + focus(); - ok() { - this.$emit('ok', this.selected); - this.$refs.dialog.close(); + nextTick(() => { + focus(); + }); - // 最近使ったユーザー更新 - let recents = this.$store.state.recentlyUsedUsers; - recents = recents.filter(x => x !== this.selected.id); - recents.unshift(this.selected.id); - this.$store.set('recentlyUsedUsers', recents.splice(0, 16)); - }, - - cancel() { - this.$emit('cancel'); - this.$refs.dialog.close(); - }, - } + os.api('users/show', { + userIds: defaultStore.state.recentlyUsedUsers, + }).then(users => { + recentUsers = users; + }); }); </script> <style lang="scss" scoped> .tbhwbxda { - > ._section { + > .form { + padding: 0 var(--root-margin); + } + + > .result, > .recent { display: flex; flex-direction: column; overflow: auto; diff --git a/packages/client/src/components/visibility-picker.vue b/packages/client/src/components/visibility-picker.vue index 4200f4354e..4b20063a51 100644 --- a/packages/client/src/components/visibility-picker.vue +++ b/packages/client/src/components/visibility-picker.vue @@ -1,28 +1,28 @@ <template> -<MkModal ref="modal" :z-priority="'high'" :src="src" @click="$refs.modal.close()" @closed="$emit('closed')"> +<MkModal ref="modal" :z-priority="'high'" :src="src" @click="modal.close()" @closed="emit('closed')"> <div class="gqyayizv _popup"> - <button key="public" class="_button" :class="{ active: v == 'public' }" data-index="1" @click="choose('public')"> + <button key="public" class="_button" :class="{ active: v === 'public' }" data-index="1" @click="choose('public')"> <div><i class="fas fa-globe"></i></div> <div> <span>{{ $ts._visibility.public }}</span> <span>{{ $ts._visibility.publicDescription }}</span> </div> </button> - <button key="home" class="_button" :class="{ active: v == 'home' }" data-index="2" @click="choose('home')"> + <button key="home" class="_button" :class="{ active: v === 'home' }" data-index="2" @click="choose('home')"> <div><i class="fas fa-home"></i></div> <div> <span>{{ $ts._visibility.home }}</span> <span>{{ $ts._visibility.homeDescription }}</span> </div> </button> - <button key="followers" class="_button" :class="{ active: v == 'followers' }" data-index="3" @click="choose('followers')"> + <button key="followers" class="_button" :class="{ active: v === 'followers' }" data-index="3" @click="choose('followers')"> <div><i class="fas fa-unlock"></i></div> <div> <span>{{ $ts._visibility.followers }}</span> <span>{{ $ts._visibility.followersDescription }}</span> </div> </button> - <button key="specified" :disabled="localOnly" class="_button" :class="{ active: v == 'specified' }" data-index="4" @click="choose('specified')"> + <button key="specified" :disabled="localOnly" class="_button" :class="{ active: v === 'specified' }" data-index="4" @click="choose('specified')"> <div><i class="fas fa-envelope"></i></div> <div> <span>{{ $ts._visibility.specified }}</span> @@ -42,49 +42,40 @@ </MkModal> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { nextTick, watch } from 'vue'; +import * as misskey from 'misskey-js'; import MkModal from '@/components/ui/modal.vue'; -export default defineComponent({ - components: { - MkModal, - }, - props: { - currentVisibility: { - type: String, - required: true - }, - currentLocalOnly: { - type: Boolean, - required: true - }, - src: { - required: false - }, - }, - emits: ['change-visibility', 'change-local-only', 'closed'], - data() { - return { - v: this.currentVisibility, - localOnly: this.currentLocalOnly, - } - }, - watch: { - localOnly() { - this.$emit('change-local-only', this.localOnly); - } - }, - methods: { - choose(visibility) { - this.v = visibility; - this.$emit('change-visibility', visibility); - this.$nextTick(() => { - this.$refs.modal.close(); - }); - }, - } +const modal = $ref<InstanceType<typeof MkModal>>(); + +const props = withDefaults(defineProps<{ + currentVisibility: typeof misskey.noteVisibilities[number]; + currentLocalOnly: boolean; + src?: HTMLElement; +}>(), { +}); + +const emit = defineEmits<{ + (e: 'changeVisibility', v: typeof misskey.noteVisibilities[number]): void; + (e: 'changeLocalOnly', v: boolean): void; + (e: 'closed'): void; +}>(); + +let v = $ref(props.currentVisibility); +let localOnly = $ref(props.currentLocalOnly); + +watch($$(localOnly), () => { + emit('changeLocalOnly', localOnly); }); + +function choose(visibility: typeof misskey.noteVisibilities[number]): void { + v = visibility; + emit('changeVisibility', visibility); + nextTick(() => { + modal.close(); + }); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/waiting-dialog.vue b/packages/client/src/components/waiting-dialog.vue index 10aedbd8f6..7dfcc55695 100644 --- a/packages/client/src/components/waiting-dialog.vue +++ b/packages/client/src/components/waiting-dialog.vue @@ -1,5 +1,5 @@ <template> -<MkModal ref="modal" :prefer-type="'dialog'" :z-priority="'high'" @click="success ? done() : () => {}" @closed="$emit('closed')"> +<MkModal ref="modal" :prefer-type="'dialog'" :z-priority="'high'" @click="success ? done() : () => {}" @closed="emit('closed')"> <div class="iuyakobc" :class="{ iconOnly: (text == null) || success }"> <i v-if="success" class="fas fa-check icon success"></i> <i v-else class="fas fa-spinner fa-pulse icon waiting"></i> @@ -8,49 +8,30 @@ </MkModal> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { watch, ref } from 'vue'; import MkModal from '@/components/ui/modal.vue'; -export default defineComponent({ - components: { - MkModal, - }, +const modal = ref<InstanceType<typeof MkModal>>(); - props: { - success: { - type: Boolean, - required: true, - }, - showing: { - type: Boolean, - required: true, - }, - text: { - type: String, - required: false, - }, - }, +const props = defineProps<{ + success: boolean; + showing: boolean; + text?: string; +}>(); - emits: ['done', 'closed'], +const emit = defineEmits<{ + (e: 'done'); + (e: 'closed'); +}>(); - data() { - return { - }; - }, - - watch: { - showing() { - if (!this.showing) this.done(); - } - }, +function done() { + emit('done'); + modal.value.close(); +} - methods: { - done() { - this.$emit('done'); - this.$refs.modal.close(); - }, - } +watch(() => props.showing, () => { + if (!props.showing) done(); }); </script> diff --git a/packages/client/src/components/widgets.vue b/packages/client/src/components/widgets.vue index 12f7129253..ccde5fbe55 100644 --- a/packages/client/src/components/widgets.vue +++ b/packages/client/src/components/widgets.vue @@ -10,7 +10,7 @@ <MkButton inline @click="$emit('exit')">{{ $ts.close }}</MkButton> </header> <XDraggable - v-model="_widgets" + v-model="widgets_" item-key="id" animation="150" > @@ -18,7 +18,7 @@ <div class="customize-container"> <button class="config _button" @click.prevent.stop="configWidget(element.id)"><i class="fas fa-cog"></i></button> <button class="remove _button" @click.prevent.stop="removeWidget(element)"><i class="fas fa-times"></i></button> - <component :is="`mkw-${element.name}`" :widget="element" :setting-callback="setting => settings[element.id] = setting" @updateProps="updateWidget(element.id, $event)"/> + <component :ref="el => widgetRefs[element.id] = el" :is="`mkw-${element.name}`" :widget="element" @updateProps="updateWidget(element.id, $event)"/> </div> </template> </XDraggable> @@ -28,7 +28,7 @@ </template> <script lang="ts"> -import { defineComponent, defineAsyncComponent } from 'vue'; +import { defineComponent, defineAsyncComponent, reactive, ref, computed } from 'vue'; import { v4 as uuid } from 'uuid'; import MkSelect from '@/components/form/select.vue'; import MkButton from '@/components/ui/button.vue'; @@ -54,50 +54,47 @@ export default defineComponent({ emits: ['updateWidgets', 'addWidget', 'removeWidget', 'updateWidget', 'exit'], - data() { - return { - widgetAdderSelected: null, - widgetDefs, - settings: {}, + setup(props, context) { + const widgetRefs = reactive({}); + const configWidget = (id: string) => { + widgetRefs[id].configure(); }; - }, - - computed: { - _widgets: { - get() { - return this.widgets; - }, - set(value) { - this.$emit('updateWidgets', value); - } - } - }, - - methods: { - configWidget(id) { - this.settings[id](); - }, + const widgetAdderSelected = ref(null); + const addWidget = () => { + if (widgetAdderSelected.value == null) return; - addWidget() { - if (this.widgetAdderSelected == null) return; - - this.$emit('addWidget', { - name: this.widgetAdderSelected, + context.emit('addWidget', { + name: widgetAdderSelected.value, id: uuid(), - data: {} + data: {}, }); - this.widgetAdderSelected = null; - }, - - removeWidget(widget) { - this.$emit('removeWidget', widget); - }, + widgetAdderSelected.value = null; + }; + const removeWidget = (widget) => { + context.emit('removeWidget', widget); + }; + const updateWidget = (id, data) => { + context.emit('updateWidget', { id, data }); + }; + const widgets_ = computed({ + get: () => props.widgets, + set: (value) => { + context.emit('updateWidgets', value); + }, + }); - updateWidget(id, data) { - this.$emit('updateWidget', { id, data }); - }, - } + return { + widgetRefs, + configWidget, + widgetAdderSelected, + widgetDefs, + addWidget, + removeWidget, + updateWidget, + widgets_, + }; + }, }); </script> diff --git a/packages/client/src/const.ts b/packages/client/src/const.ts new file mode 100644 index 0000000000..505cf2748e --- /dev/null +++ b/packages/client/src/const.ts @@ -0,0 +1,44 @@ +// ブラウザで直接表示することを許可するファイルの種類のリスト +// ここに含まれないものは application/octet-stream としてレスポンスされる +// SVGはXSSを生むので許可しない +export const FILE_TYPE_BROWSERSAFE = [ + // Images + 'image/png', + 'image/gif', + 'image/jpeg', + 'image/webp', + 'image/apng', + 'image/bmp', + 'image/tiff', + 'image/x-icon', + + // OggS + 'audio/opus', + 'video/ogg', + 'audio/ogg', + 'application/ogg', + + // ISO/IEC base media file format + 'video/quicktime', + 'video/mp4', + 'audio/mp4', + 'video/x-m4v', + 'audio/x-m4a', + 'video/3gpp', + 'video/3gpp2', + + 'video/mpeg', + 'audio/mpeg', + + 'video/webm', + 'audio/webm', + + 'audio/aac', + 'audio/x-flac', + 'audio/vnd.wave', +]; +/* +https://github.com/sindresorhus/file-type/blob/main/supported.js +https://github.com/sindresorhus/file-type/blob/main/core.js +https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers +*/ diff --git a/packages/client/src/directives/anim.ts b/packages/client/src/directives/anim.ts index 1ceef984d8..04e1c6a404 100644 --- a/packages/client/src/directives/anim.ts +++ b/packages/client/src/directives/anim.ts @@ -10,7 +10,7 @@ export default { }, mounted(src, binding, vn) { - setTimeout(() => { + window.setTimeout(() => { src.style.opacity = '1'; src.style.transform = 'none'; }, 1); diff --git a/packages/client/src/directives/tooltip.ts b/packages/client/src/directives/tooltip.ts index e14ee81dff..fffde14874 100644 --- a/packages/client/src/directives/tooltip.ts +++ b/packages/client/src/directives/tooltip.ts @@ -21,7 +21,7 @@ export default { self.close = () => { if (self._close) { - clearInterval(self.checkTimer); + window.clearInterval(self.checkTimer); self._close(); self._close = null; } @@ -61,19 +61,19 @@ export default { }); el.addEventListener(start, () => { - clearTimeout(self.showTimer); - clearTimeout(self.hideTimer); - self.showTimer = setTimeout(self.show, delay); + window.clearTimeout(self.showTimer); + window.clearTimeout(self.hideTimer); + self.showTimer = window.setTimeout(self.show, delay); }, { passive: true }); el.addEventListener(end, () => { - clearTimeout(self.showTimer); - clearTimeout(self.hideTimer); - self.hideTimer = setTimeout(self.close, delay); + window.clearTimeout(self.showTimer); + window.clearTimeout(self.hideTimer); + self.hideTimer = window.setTimeout(self.close, delay); }, { passive: true }); el.addEventListener('click', () => { - clearTimeout(self.showTimer); + window.clearTimeout(self.showTimer); self.close(); }); }, @@ -85,6 +85,6 @@ export default { unmounted(el, binding, vn) { const self = el._tooltipDirective_; - clearInterval(self.checkTimer); + window.clearInterval(self.checkTimer); }, } as Directive; diff --git a/packages/client/src/directives/user-preview.ts b/packages/client/src/directives/user-preview.ts index 68d9e2816c..cdd2afa194 100644 --- a/packages/client/src/directives/user-preview.ts +++ b/packages/client/src/directives/user-preview.ts @@ -30,11 +30,11 @@ export class UserPreview { source: this.el }, { mouseover: () => { - clearTimeout(this.hideTimer); + window.clearTimeout(this.hideTimer); }, mouseleave: () => { - clearTimeout(this.showTimer); - this.hideTimer = setTimeout(this.close, 500); + window.clearTimeout(this.showTimer); + this.hideTimer = window.setTimeout(this.close, 500); }, }, 'closed'); @@ -44,10 +44,10 @@ export class UserPreview { } }; - this.checkTimer = setInterval(() => { + this.checkTimer = window.setInterval(() => { if (!document.body.contains(this.el)) { - clearTimeout(this.showTimer); - clearTimeout(this.hideTimer); + window.clearTimeout(this.showTimer); + window.clearTimeout(this.hideTimer); this.close(); } }, 1000); @@ -56,7 +56,7 @@ export class UserPreview { @autobind private close() { if (this.promise) { - clearInterval(this.checkTimer); + window.clearInterval(this.checkTimer); this.promise.cancel(); this.promise = null; } @@ -64,21 +64,21 @@ export class UserPreview { @autobind private onMouseover() { - clearTimeout(this.showTimer); - clearTimeout(this.hideTimer); - this.showTimer = setTimeout(this.show, 500); + window.clearTimeout(this.showTimer); + window.clearTimeout(this.hideTimer); + this.showTimer = window.setTimeout(this.show, 500); } @autobind private onMouseleave() { - clearTimeout(this.showTimer); - clearTimeout(this.hideTimer); - this.hideTimer = setTimeout(this.close, 500); + window.clearTimeout(this.showTimer); + window.clearTimeout(this.hideTimer); + this.hideTimer = window.setTimeout(this.close, 500); } @autobind private onClick() { - clearTimeout(this.showTimer); + window.clearTimeout(this.showTimer); this.close(); } @@ -94,7 +94,7 @@ export class UserPreview { this.el.removeEventListener('mouseover', this.onMouseover); this.el.removeEventListener('mouseleave', this.onMouseleave); this.el.removeEventListener('click', this.onClick); - clearInterval(this.checkTimer); + window.clearInterval(this.checkTimer); } } diff --git a/packages/client/src/init.ts b/packages/client/src/init.ts index 82a1e169ce..af70aec70a 100644 --- a/packages/client/src/init.ts +++ b/packages/client/src/init.ts @@ -13,10 +13,8 @@ if (localStorage.getItem('accounts') != null) { } //#endregion -import * as Sentry from '@sentry/browser'; -import { Integrations } from '@sentry/tracing'; import { computed, createApp, watch, markRaw, version as vueVersion } from 'vue'; -import compareVersions from 'compare-versions'; +import * as compareVersions from 'compare-versions'; import widgets from '@/widgets'; import directives from '@/directives'; @@ -26,7 +24,8 @@ import { router } from '@/router'; import { applyTheme } from '@/scripts/theme'; import { isDeviceDarkmode } from '@/scripts/is-device-darkmode'; import { i18n } from '@/i18n'; -import { stream, confirm, alert, post, popup, toast } from '@/os'; +import { confirm, alert, post, popup, toast } from '@/os'; +import { stream } from '@/stream'; import * as sound from '@/scripts/sound'; import { $i, refreshAccount, login, updateAccount, signout } from '@/account'; import { defaultStore, ColdDeviceStorage } from '@/store'; @@ -73,18 +72,6 @@ if (_DEV_) { }); } -if (defaultStore.state.reportError && !_DEV_) { - Sentry.init({ - dsn: 'https://fd273254a07a4b61857607a9ea05d629@o501808.ingest.sentry.io/5583438', - tracesSampleRate: 1.0, - }); - - Sentry.setTag('misskey_version', version); - Sentry.setTag('ui', ui); - Sentry.setTag('lang', lang); - Sentry.setTag('host', host); -} - // タッチデバイスでCSSの:hoverを機能させる document.addEventListener('touchend', () => {}, { passive: true }); @@ -185,7 +172,6 @@ const app = createApp(await ( !$i ? import('@/ui/visitor.vue') : ui === 'deck' ? import('@/ui/deck.vue') : ui === 'desktop' ? import('@/ui/desktop.vue') : - ui === 'chat' ? import('@/ui/chat/index.vue') : ui === 'classic' ? import('@/ui/classic.vue') : import('@/ui/universal.vue') ).then(x => x.default)); diff --git a/packages/client/src/menu.ts b/packages/client/src/menu.ts index bd155ba16d..184779f21f 100644 --- a/packages/client/src/menu.ts +++ b/packages/client/src/menu.ts @@ -163,22 +163,11 @@ export const menuDef = reactive({ icon: 'fas fa-laugh', to: '/emojis', }, - games: { - title: 'games', - icon: 'fas fa-gamepad', - to: '/games/reversi', - }, scratchpad: { title: 'scratchpad', icon: 'fas fa-terminal', to: '/scratchpad', }, - rooms: { - title: 'rooms', - icon: 'fas fa-door-closed', - show: computed(() => $i != null), - to: computed(() => `/@${$i.username}/room`), - }, ui: { title: 'switchUi', icon: 'fas fa-columns', @@ -204,13 +193,6 @@ export const menuDef = reactive({ localStorage.setItem('ui', 'classic'); unisonReload(); } - }, { - text: 'Chat (β)', - active: ui === 'chat', - action: () => { - localStorage.setItem('ui', 'chat'); - unisonReload(); - } }, /*{ text: i18n.locale.desktop + ' (β)', active: ui === 'desktop', diff --git a/packages/client/src/os.ts b/packages/client/src/os.ts index 4ed69e0ec0..c16ea717ad 100644 --- a/packages/client/src/os.ts +++ b/packages/client/src/os.ts @@ -4,19 +4,13 @@ import { Component, defineAsyncComponent, markRaw, reactive, Ref, ref } from 'vu import { EventEmitter } from 'eventemitter3'; import insertTextAtCursor from 'insert-text-at-cursor'; import * as Misskey from 'misskey-js'; -import * as Sentry from '@sentry/browser'; -import { apiUrl, debug, url } from '@/config'; +import { apiUrl, url } from '@/config'; import MkPostFormDialog from '@/components/post-form-dialog.vue'; import MkWaitingDialog from '@/components/waiting-dialog.vue'; import { resolve } from '@/router'; import { $i } from '@/account'; -import { defaultStore } from '@/store'; - -export const stream = markRaw(new Misskey.Stream(url, $i)); export const pendingApiRequestsCount = ref(0); -let apiRequestsCount = 0; // for debug -export const apiRequests = ref([]); // for debug const apiClient = new Misskey.api.APIClient({ origin: url, @@ -29,18 +23,6 @@ export const api = ((endpoint: string, data: Record<string, any> = {}, token?: s pendingApiRequestsCount.value--; }; - const log = debug ? reactive({ - id: ++apiRequestsCount, - endpoint, - req: markRaw(data), - res: null, - state: 'pending', - }) : null; - if (debug) { - apiRequests.value.push(log); - if (apiRequests.value.length > 128) apiRequests.value.shift(); - } - const promise = new Promise((resolve, reject) => { // Append a credential if ($i) (data as any).i = $i.token; @@ -57,34 +39,10 @@ export const api = ((endpoint: string, data: Record<string, any> = {}, token?: s if (res.status === 200) { resolve(body); - if (debug) { - log!.res = markRaw(JSON.parse(JSON.stringify(body))); - log!.state = 'success'; - } } else if (res.status === 204) { resolve(); - if (debug) { - log!.state = 'success'; - } } else { reject(body.error); - if (debug) { - log!.res = markRaw(body.error); - log!.state = 'failed'; - } - - if (defaultStore.state.reportError && !_DEV_) { - Sentry.withScope((scope) => { - scope.setTag('api_endpoint', endpoint); - scope.setContext('api params', data); - scope.setContext('api error info', body.info); - scope.setTag('api_error_id', body.id); - scope.setTag('api_error_code', body.code); - scope.setTag('api_error_kind', body.kind); - scope.setLevel(Sentry.Severity.Error); - Sentry.captureMessage('API error'); - }); - } } }).catch(reject); }); @@ -125,7 +83,7 @@ export function promiseDialog<T extends Promise<any>>( onSuccess(res); } else { success.value = true; - setTimeout(() => { + window.setTimeout(() => { showing.value = false; }, 1000); } @@ -181,7 +139,7 @@ export async function popup(component: Component | typeof import('*.vue') | Prom const id = ++popupIdCount; const dispose = () => { // このsetTimeoutが無いと挙動がおかしくなる(autocompleteが閉じなくなる)。Vueのバグ? - setTimeout(() => { + window.setTimeout(() => { popups.value = popups.value.filter(popup => popup.id !== id); }, 0); }; @@ -371,7 +329,7 @@ export function select(props: { export function success() { return new Promise((resolve, reject) => { const showing = ref(true); - setTimeout(() => { + window.setTimeout(() => { showing.value = false; }, 1000); popup(import('@/components/waiting-dialog.vue'), { @@ -583,7 +541,7 @@ export const uploads = ref<{ img: string; }[]>([]); -export function upload(file: File, folder?: any, name?: string) { +export function upload(file: File, folder?: any, name?: string): Promise<Misskey.entities.DriveFile> { if (folder && typeof folder == 'object') folder = folder.id; return new Promise((resolve, reject) => { @@ -612,7 +570,7 @@ export function upload(file: File, folder?: any, name?: string) { const xhr = new XMLHttpRequest(); xhr.open('POST', apiUrl + '/drive/files/create', true); xhr.onload = (ev) => { - if (ev.target == null || ev.target.response == null) { + if (xhr.status !== 200 || ev.target == null || ev.target.response == null) { // TODO: 消すのではなくて再送できるようにしたい uploads.value = uploads.value.filter(x => x.id != id); diff --git a/packages/client/src/pages/_error_.vue b/packages/client/src/pages/_error_.vue index 2f8f08b5cf..7540995707 100644 --- a/packages/client/src/pages/_error_.vue +++ b/packages/client/src/pages/_error_.vue @@ -1,68 +1,61 @@ <template> -<MkLoading v-if="!loaded" /> +<MkLoading v-if="!loaded"/> <transition :name="$store.state.animation ? 'zoom' : ''" appear> <div v-show="loaded" class="mjndxjch"> <img src="https://xn--931a.moe/assets/error.jpg" class="_ghost"/> - <p><b><i class="fas fa-exclamation-triangle"></i> {{ $ts.pageLoadError }}</b></p> - <p v-if="version === meta.version">{{ $ts.pageLoadErrorDescription }}</p> - <p v-else-if="serverIsDead">{{ $ts.serverIsDead }}</p> + <p><b><i class="fas fa-exclamation-triangle"></i> {{ i18n.locale.pageLoadError }}</b></p> + <p v-if="meta && (version === meta.version)">{{ i18n.locale.pageLoadErrorDescription }}</p> + <p v-else-if="serverIsDead">{{ i18n.locale.serverIsDead }}</p> <template v-else> - <p>{{ $ts.newVersionOfClientAvailable }}</p> - <p>{{ $ts.youShouldUpgradeClient }}</p> - <MkButton class="button primary" @click="reload">{{ $ts.reload }}</MkButton> + <p>{{ i18n.locale.newVersionOfClientAvailable }}</p> + <p>{{ i18n.locale.youShouldUpgradeClient }}</p> + <MkButton class="button primary" @click="reload">{{ i18n.locale.reload }}</MkButton> </template> - <p><MkA to="/docs/general/troubleshooting" class="_link">{{ $ts.troubleshooting }}</MkA></p> + <p><MkA to="/docs/general/troubleshooting" class="_link">{{ i18n.locale.troubleshooting }}</MkA></p> <p v-if="error" class="error">ERROR: {{ error }}</p> </div> </transition> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; +import * as misskey from 'misskey-js'; import MkButton from '@/components/ui/button.vue'; import * as symbols from '@/symbols'; import { version } from '@/config'; import * as os from '@/os'; import { unisonReload } from '@/scripts/unison-reload'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, - }, - props: { - error: { - required: false, - } - }, - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.error, - icon: 'fas fa-exclamation-triangle' - }, - loaded: false, - serverIsDead: false, - meta: {} as any, - version, - }; - }, - created() { - os.api('meta', { - detail: false - }).then(meta => { - this.loaded = true; - this.serverIsDead = false; - this.meta = meta; - localStorage.setItem('v', meta.version); - }, () => { - this.loaded = true; - this.serverIsDead = true; - }); - }, - methods: { - reload() { - unisonReload(); - }, +const props = withDefaults(defineProps<{ + error?: Error; +}>(), { +}); + +let loaded = $ref(false); +let serverIsDead = $ref(false); +let meta = $ref<misskey.entities.LiteInstanceMetadata | null>(null); + +os.api('meta', { + detail: false, +}).then(res => { + loaded = true; + serverIsDead = false; + meta = res; + localStorage.setItem('v', res.version); +}, () => { + loaded = true; + serverIsDead = true; +}); + +function reload() { + unisonReload(); +} + +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.error, + icon: 'fas fa-exclamation-triangle', }, }); </script> diff --git a/packages/client/src/pages/_loading_.vue b/packages/client/src/pages/_loading_.vue index 05c6af1cd7..1dd2e46e10 100644 --- a/packages/client/src/pages/_loading_.vue +++ b/packages/client/src/pages/_loading_.vue @@ -2,9 +2,5 @@ <MkLoading/> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as os from '@/os'; - -export default defineComponent({}); +<script lang="ts" setup> </script> diff --git a/packages/client/src/pages/about-misskey.vue b/packages/client/src/pages/about-misskey.vue index 855a21e493..8119f33051 100644 --- a/packages/client/src/pages/about-misskey.vue +++ b/packages/client/src/pages/about-misskey.vue @@ -3,36 +3,39 @@ <MkSpacer :content-max="600" :margin-min="20"> <div class="_formRoot znqjceqz"> <div id="debug"></div> - <div ref="about" v-panel class="_formBlock about" :class="{ playing: easterEggEngine != null }"> + <div ref="containerEl" v-panel class="_formBlock about" :class="{ playing: easterEggEngine != null }"> <img src="/client-assets/about-icon.png" alt="" class="icon" draggable="false" @load="iconLoaded" @click="gravity"/> <div class="misskey">Misskey</div> <div class="version">v{{ version }}</div> <span v-for="emoji in easterEggEmojis" :key="emoji.id" class="emoji" :data-physics-x="emoji.left" :data-physics-y="emoji.top" :class="{ _physics_circle_: !emoji.emoji.startsWith(':') }"><MkEmoji class="emoji" :emoji="emoji.emoji" :custom-emojis="$instance.emojis" :is-reaction="false" :normal="true" :no-style="true"/></span> </div> <div class="_formBlock" style="text-align: center;"> - {{ $ts._aboutMisskey.about }}<br><a href="https://misskey-hub.net/docs/misskey.html" target="_blank" class="_link">{{ $ts.learnMore }}</a> + {{ i18n.locale._aboutMisskey.about }}<br><a href="https://misskey-hub.net/docs/misskey.html" target="_blank" class="_link">{{ i18n.locale.learnMore }}</a> + </div> + <div class="_formBlock" style="text-align: center;"> + <MkButton primary rounded inline @click="iLoveMisskey">I <Mfm text="$[jelly ❤]"/> #Misskey</MkButton> </div> <FormSection> <div class="_formLinks"> <FormLink to="https://github.com/misskey-dev/misskey" external> <template #icon><i class="fas fa-code"></i></template> - {{ $ts._aboutMisskey.source }} + {{ i18n.locale._aboutMisskey.source }} <template #suffix>GitHub</template> </FormLink> <FormLink to="https://crowdin.com/project/misskey" external> <template #icon><i class="fas fa-language"></i></template> - {{ $ts._aboutMisskey.translation }} + {{ i18n.locale._aboutMisskey.translation }} <template #suffix>Crowdin</template> </FormLink> <FormLink to="https://www.patreon.com/syuilo" external> <template #icon><i class="fas fa-hand-holding-medical"></i></template> - {{ $ts._aboutMisskey.donate }} + {{ i18n.locale._aboutMisskey.donate }} <template #suffix>Patreon</template> </FormLink> </div> </FormSection> <FormSection> - <template #label>{{ $ts._aboutMisskey.contributors }}</template> + <template #label>{{ i18n.locale._aboutMisskey.contributors }}</template> <div class="_formLinks"> <FormLink to="https://github.com/syuilo" external>@syuilo</FormLink> <FormLink to="https://github.com/AyaMorisawa" external>@AyaMorisawa</FormLink> @@ -44,27 +47,30 @@ <FormLink to="https://github.com/u1-liquid" external>@u1-liquid</FormLink> <FormLink to="https://github.com/marihachi" external>@marihachi</FormLink> </div> - <template #caption><MkLink url="https://github.com/misskey-dev/misskey/graphs/contributors">{{ $ts._aboutMisskey.allContributors }}</MkLink></template> + <template #caption><MkLink url="https://github.com/misskey-dev/misskey/graphs/contributors">{{ i18n.locale._aboutMisskey.allContributors }}</MkLink></template> </FormSection> <FormSection> - <template #label><Mfm text="$[jelly ❤]"/> {{ $ts._aboutMisskey.patrons }}</template> + <template #label><Mfm text="$[jelly ❤]"/> {{ i18n.locale._aboutMisskey.patrons }}</template> <div v-for="patron in patrons" :key="patron">{{ patron }}</div> - <template #caption>{{ $ts._aboutMisskey.morePatrons }}</template> + <template #caption>{{ i18n.locale._aboutMisskey.morePatrons }}</template> </FormSection> </div> </MkSpacer> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { nextTick, onBeforeUnmount } from 'vue'; import { version } from '@/config'; import FormLink from '@/components/form/link.vue'; import FormSection from '@/components/form/section.vue'; -import MkKeyValue from '@/components/key-value.vue'; +import MkButton from '@/components/ui/button.vue'; import MkLink from '@/components/link.vue'; import { physics } from '@/scripts/physics'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; +import { defaultStore } from '@/store'; +import * as os from '@/os'; const patrons = [ 'まっちゃとーにゅ', @@ -145,59 +151,53 @@ const patrons = [ '蝉暮せせせ', ]; -export default defineComponent({ - components: { - FormSection, - FormLink, - MkKeyValue, - MkLink, - }, +let easterEggReady = false; +let easterEggEmojis = $ref([]); +let easterEggEngine = $ref(null); +const containerEl = $ref<HTMLElement>(); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.aboutMisskey, - icon: null - }, - version, - patrons, - easterEggReady: false, - easterEggEmojis: [], - easterEggEngine: null, - } - }, +function iconLoaded() { + const emojis = defaultStore.state.reactions; + const containerWidth = containerEl.offsetWidth; + for (let i = 0; i < 32; i++) { + easterEggEmojis.push({ + id: i.toString(), + top: -(128 + (Math.random() * 256)), + left: (Math.random() * containerWidth), + emoji: emojis[Math.floor(Math.random() * emojis.length)], + }); + } - beforeUnmount() { - if (this.easterEggEngine) { - this.easterEggEngine.stop(); - } - }, + nextTick(() => { + easterEggReady = true; + }); +} - methods: { - iconLoaded() { - const emojis = this.$store.state.reactions; - const containerWidth = this.$refs.about.offsetWidth; - for (let i = 0; i < 32; i++) { - this.easterEggEmojis.push({ - id: i.toString(), - top: -(128 + (Math.random() * 256)), - left: (Math.random() * containerWidth), - emoji: emojis[Math.floor(Math.random() * emojis.length)], - }); - } +function gravity() { + if (!easterEggReady) return; + easterEggReady = false; + easterEggEngine = physics(containerEl); +} - this.$nextTick(() => { - this.easterEggReady = true; - }); - }, +function iLoveMisskey() { + os.post({ + initialText: 'I $[jelly ❤] #Misskey', + }); +} - gravity() { - if (!this.easterEggReady) return; - this.easterEggReady = false; - this.easterEggEngine = physics(this.$refs.about); - } +onBeforeUnmount(() => { + if (easterEggEngine) { + easterEggEngine.stop(); } }); + +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.aboutMisskey, + icon: null, + bg: 'var(--bg)', + }, +}); </script> <style lang="scss" scoped> diff --git a/packages/client/src/pages/about.vue b/packages/client/src/pages/about.vue index 04f68b7201..a5984c548d 100644 --- a/packages/client/src/pages/about.vue +++ b/packages/client/src/pages/about.vue @@ -24,7 +24,7 @@ </FormSection> <FormSection> - <div class="_inputSplit _formBlock"> + <FormSplit> <MkKeyValue class="_formBlock"> <template #key>{{ $ts.administrator }}</template> <template #value>{{ $instance.maintainerName }}</template> @@ -33,14 +33,14 @@ <template #key>{{ $ts.contact }}</template> <template #value>{{ $instance.maintainerEmail }}</template> </MkKeyValue> - </div> + </FormSplit> <FormLink v-if="$instance.tosUrl" :to="$instance.tosUrl" class="_formBlock" external>{{ $ts.tos }}</FormLink> </FormSection> <FormSuspense :p="initStats"> <FormSection> <template #label>{{ $ts.statistics }}</template> - <div class="_inputSplit"> + <FormSplit> <MkKeyValue class="_formBlock"> <template #key>{{ $ts.users }}</template> <template #value>{{ number(stats.originalUsersCount) }}</template> @@ -49,7 +49,7 @@ <template #key>{{ $ts.notes }}</template> <template #value>{{ number(stats.originalNotesCount) }}</template> </MkKeyValue> - </div> + </FormSplit> </FormSection> </FormSuspense> @@ -67,46 +67,33 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { ref } from 'vue'; import { version, instanceName } from '@/config'; import FormLink from '@/components/form/link.vue'; import FormSection from '@/components/form/section.vue'; import FormSuspense from '@/components/form/suspense.vue'; +import FormSplit from '@/components/form/split.vue'; import MkKeyValue from '@/components/key-value.vue'; import * as os from '@/os'; import number from '@/filters/number'; import * as symbols from '@/symbols'; import { host } from '@/config'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkKeyValue, - FormSection, - FormLink, - FormSuspense, - }, +const stats = ref(null); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.instanceInfo, - icon: 'fas fa-info-circle' - }, - host, - version, - instanceName, - stats: null, - initStats: () => os.api('stats', { - }).then((stats) => { - this.stats = stats; - }) - } - }, +const initStats = () => os.api('stats', { +}).then((res) => { + stats.value = res; +}); - methods: { - number - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.instanceInfo, + icon: 'fas fa-info-circle', + bg: 'var(--bg)', + }, }); </script> diff --git a/packages/client/src/pages/admin/abuses.vue b/packages/client/src/pages/admin/abuses.vue index 8df20097b3..92f93797ce 100644 --- a/packages/client/src/pages/admin/abuses.vue +++ b/packages/client/src/pages/admin/abuses.vue @@ -34,27 +34,7 @@ --> <MkPagination v-slot="{items}" ref="reports" :pagination="pagination" style="margin-top: var(--margin);"> - <div v-for="report in items" :key="report.id" class="bcekxzvu _card _gap"> - <div class="_content target"> - <MkAvatar class="avatar" :user="report.targetUser" :show-indicator="true"/> - <div class="info"> - <MkUserName class="name" :user="report.targetUser"/> - <div class="acct">@{{ acct(report.targetUser) }}</div> - </div> - </div> - <div class="_content"> - <div> - <Mfm :text="report.comment"/> - </div> - <hr> - <div>Reporter: <MkAcct :user="report.reporter"/></div> - <div><MkTime :time="report.createdAt"/></div> - </div> - <div class="_footer"> - <div v-if="report.assignee">Assignee: <MkAcct :user="report.assignee"/></div> - <MkButton v-if="!report.resolved" primary @click="resolve(report)">{{ $ts.abuseMarkAsResolved }}</MkButton> - </div> - </div> + <XAbuseReport v-for="report in items" :key="report.id" :report="report" @resolved="resolved"/> </MkPagination> </div> </div> @@ -62,22 +42,21 @@ </template> <script lang="ts"> -import { defineComponent } from 'vue'; +import { computed, defineComponent } from 'vue'; -import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkSelect from '@/components/form/select.vue'; import MkPagination from '@/components/ui/pagination.vue'; -import { acct } from '@/filters/user'; +import XAbuseReport from '@/components/abuse-report.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; export default defineComponent({ components: { - MkButton, MkInput, MkSelect, MkPagination, + XAbuseReport, }, emits: ['info'], @@ -95,44 +74,20 @@ export default defineComponent({ reporterOrigin: 'combined', targetUserOrigin: 'combined', pagination: { - endpoint: 'admin/abuse-user-reports', + endpoint: 'admin/abuse-user-reports' as const, limit: 10, - params: () => ({ + params: computed(() => ({ state: this.state, reporterOrigin: this.reporterOrigin, targetUserOrigin: this.targetUserOrigin, - }), + })), }, } }, - watch: { - state() { - this.$refs.reports.reload(); - }, - - reporterOrigin() { - this.$refs.reports.reload(); - }, - - targetUserOrigin() { - this.$refs.reports.reload(); - }, - }, - - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { - acct, - - resolve(report) { - os.apiWithDialog('admin/resolve-abuse-user-report', { - reportId: report.id, - }).then(() => { - this.$refs.reports.removeItem(item => item.id === report.id); - }); + resolved(reportId) { + this.$refs.reports.removeItem(item => item.id === reportId); }, } }); @@ -142,29 +97,4 @@ export default defineComponent({ .lcixvhis { margin: var(--margin); } - -.bcekxzvu { - > .target { - display: flex; - width: 100%; - box-sizing: border-box; - text-align: left; - align-items: center; - - > .avatar { - width: 42px; - height: 42px; - } - - > .info { - margin-left: 0.3em; - padding: 0 8px; - flex: 1; - - > .name { - font-weight: bold; - } - } - } -} </style> diff --git a/packages/client/src/pages/admin/ads.vue b/packages/client/src/pages/admin/ads.vue index d12ed8563e..8f164caa99 100644 --- a/packages/client/src/pages/admin/ads.vue +++ b/packages/client/src/pages/admin/ads.vue @@ -23,14 +23,14 @@ <MkRadio v-model="ad.priority" value="low">{{ $ts.low }}</MkRadio> </div> --> - <div class="_inputSplit"> + <FormSplit> <MkInput v-model="ad.ratio" type="number"> <template #label>{{ $ts.ratio }}</template> </MkInput> <MkInput v-model="ad.expiresAt" type="date"> <template #label>{{ $ts.expiration }}</template> </MkInput> - </div> + </FormSplit> <MkTextarea v-model="ad.memo" class="_formBlock"> <template #label>{{ $ts.memo }}</template> </MkTextarea> @@ -49,6 +49,7 @@ import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkTextarea from '@/components/form/textarea.vue'; import FormRadios from '@/components/form/radios.vue'; +import FormSplit from '@/components/form/split.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; @@ -58,6 +59,7 @@ export default defineComponent({ MkInput, MkTextarea, FormRadios, + FormSplit, }, emits: ['info'], @@ -85,10 +87,6 @@ export default defineComponent({ }); }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { add() { this.ads.unshift({ diff --git a/packages/client/src/pages/admin/announcements.vue b/packages/client/src/pages/admin/announcements.vue index 3614cb1441..a0d720bb29 100644 --- a/packages/client/src/pages/admin/announcements.vue +++ b/packages/client/src/pages/admin/announcements.vue @@ -61,10 +61,6 @@ export default defineComponent({ }); }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { add() { this.announcements.unshift({ diff --git a/packages/client/src/pages/admin/bot-protection.vue b/packages/client/src/pages/admin/bot-protection.vue index 5a97083841..82ab155317 100644 --- a/packages/client/src/pages/admin/bot-protection.vue +++ b/packages/client/src/pages/admin/bot-protection.vue @@ -1,70 +1,55 @@ <template> -<FormBase> +<div> <FormSuspense :p="init"> - <FormRadios v-model="provider"> - <template #desc><i class="fas fa-shield-alt"></i> {{ $ts.botProtection }}</template> - <option :value="null">{{ $ts.none }} ({{ $ts.notRecommended }})</option> - <option value="hcaptcha">hCaptcha</option> - <option value="recaptcha">reCAPTCHA</option> - </FormRadios> + <div class="_formRoot"> + <FormRadios v-model="provider" class="_formBlock"> + <option :value="null">{{ $ts.none }} ({{ $ts.notRecommended }})</option> + <option value="hcaptcha">hCaptcha</option> + <option value="recaptcha">reCAPTCHA</option> + </FormRadios> - <template v-if="provider === 'hcaptcha'"> - <div v-sticky-container class="_debobigegoItem _debobigegoNoConcat"> - <div class="_debobigegoLabel">hCaptcha</div> - <div class="main"> - <FormInput v-model="hcaptchaSiteKey"> - <template #prefix><i class="fas fa-key"></i></template> - <span>{{ $ts.hcaptchaSiteKey }}</span> - </FormInput> - <FormInput v-model="hcaptchaSecretKey"> - <template #prefix><i class="fas fa-key"></i></template> - <span>{{ $ts.hcaptchaSecretKey }}</span> - </FormInput> - </div> - </div> - <div v-sticky-container class="_debobigegoItem _debobigegoNoConcat"> - <div class="_debobigegoLabel">{{ $ts.preview }}</div> - <div class="_debobigegoPanel" style="padding: var(--debobigegoContentHMargin);"> + <template v-if="provider === 'hcaptcha'"> + <FormInput v-model="hcaptchaSiteKey" class="_formBlock"> + <template #prefix><i class="fas fa-key"></i></template> + <template #label>{{ $ts.hcaptchaSiteKey }}</template> + </FormInput> + <FormInput v-model="hcaptchaSecretKey" class="_formBlock"> + <template #prefix><i class="fas fa-key"></i></template> + <template #label>{{ $ts.hcaptchaSecretKey }}</template> + </FormInput> + <FormSlot class="_formBlock"> + <template #label>{{ $ts.preview }}</template> <MkCaptcha provider="hcaptcha" :sitekey="hcaptchaSiteKey || '10000000-ffff-ffff-ffff-000000000001'"/> - </div> - </div> - </template> - <template v-else-if="provider === 'recaptcha'"> - <div v-sticky-container class="_debobigegoItem _debobigegoNoConcat"> - <div class="_debobigegoLabel">reCAPTCHA</div> - <div class="main"> - <FormInput v-model="recaptchaSiteKey"> - <template #prefix><i class="fas fa-key"></i></template> - <span>{{ $ts.recaptchaSiteKey }}</span> - </FormInput> - <FormInput v-model="recaptchaSecretKey"> - <template #prefix><i class="fas fa-key"></i></template> - <span>{{ $ts.recaptchaSecretKey }}</span> - </FormInput> - </div> - </div> - <div v-if="recaptchaSiteKey" v-sticky-container class="_debobigegoItem _debobigegoNoConcat"> - <div class="_debobigegoLabel">{{ $ts.preview }}</div> - <div class="_debobigegoPanel" style="padding: var(--debobigegoContentHMargin);"> + </FormSlot> + </template> + <template v-else-if="provider === 'recaptcha'"> + <FormInput v-model="recaptchaSiteKey" class="_formBlock"> + <template #prefix><i class="fas fa-key"></i></template> + <template #label>{{ $ts.recaptchaSiteKey }}</template> + </FormInput> + <FormInput v-model="recaptchaSecretKey" class="_formBlock"> + <template #prefix><i class="fas fa-key"></i></template> + <template #label>{{ $ts.recaptchaSecretKey }}</template> + </FormInput> + <FormSlot v-if="recaptchaSiteKey" class="_formBlock"> + <template #label>{{ $ts.preview }}</template> <MkCaptcha provider="recaptcha" :sitekey="recaptchaSiteKey"/> - </div> - </div> - </template> + </FormSlot> + </template> - <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + </div> </FormSuspense> -</FormBase> +</div> </template> <script lang="ts"> import { defineAsyncComponent, defineComponent } from 'vue'; -import FormRadios from '@/components/debobigego/radios.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormInfo from '@/components/debobigego/info.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import FormRadios from '@/components/form/radios.vue'; +import FormInput from '@/components/form/input.vue'; +import FormButton from '@/components/ui/button.vue'; +import FormSuspense from '@/components/form/suspense.vue'; +import FormSlot from '@/components/form/slot.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; @@ -73,11 +58,9 @@ export default defineComponent({ components: { FormRadios, FormInput, - FormBase, - FormGroup, FormButton, - FormInfo, FormSuspense, + FormSlot, MkCaptcha: defineAsyncComponent(() => import('@/components/captcha.vue')), }, @@ -99,10 +82,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); diff --git a/packages/client/src/pages/admin/database.vue b/packages/client/src/pages/admin/database.vue index b09f1ad867..3a835eeafa 100644 --- a/packages/client/src/pages/admin/database.vue +++ b/packages/client/src/pages/admin/database.vue @@ -1,28 +1,18 @@ <template> -<FormBase> +<MkSpacer :content-max="800" :margin-min="16" :margin-max="32"> <FormSuspense v-slot="{ result: database }" :p="databasePromiseFactory"> - <FormGroup v-for="table in database" :key="table[0]"> - <template #label>{{ table[0] }}</template> - <FormKeyValueView> - <template #key>Size</template> - <template #value>{{ bytes(table[1].size) }}</template> - </FormKeyValueView> - <FormKeyValueView> - <template #key>Records</template> - <template #value>{{ number(table[1].count) }}</template> - </FormKeyValueView> - </FormGroup> + <MkKeyValue v-for="table in database" :key="table[0]" oneline style="margin: 1em 0;"> + <template #key>{{ table[0] }}</template> + <template #value>{{ bytes(table[1].size) }} ({{ number(table[1].count) }} recs)</template> + </MkKeyValue> </FormSuspense> -</FormBase> +</MkSpacer> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; -import FormKeyValueView from '@/components/debobigego/key-value-view.vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; +import FormSuspense from '@/components/form/suspense.vue'; +import MkKeyValue from '@/components/key-value.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import bytes from '@/filters/bytes'; @@ -31,10 +21,7 @@ import number from '@/filters/number'; export default defineComponent({ components: { FormSuspense, - FormKeyValueView, - FormBase, - FormGroup, - FormLink, + MkKeyValue, }, emits: ['info'], @@ -50,10 +37,6 @@ export default defineComponent({ } }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { bytes, number, } diff --git a/packages/client/src/pages/admin/email-settings.vue b/packages/client/src/pages/admin/email-settings.vue index 873a853918..6491a453ab 100644 --- a/packages/client/src/pages/admin/email-settings.vue +++ b/packages/client/src/pages/admin/email-settings.vue @@ -1,50 +1,55 @@ <template> -<FormBase> +<MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> <FormSuspense :p="init"> - <FormSwitch v-model="enableEmail">{{ $ts.enableEmail }}<template #desc>{{ $ts.emailConfigInfo }}</template></FormSwitch> + <div class="_formRoot"> + <FormSwitch v-model="enableEmail" class="_formBlock"> + <template #label>{{ $ts.enableEmail }}</template> + <template #caption>{{ $ts.emailConfigInfo }}</template> + </FormSwitch> - <template v-if="enableEmail"> - <FormInput v-model="email" type="email"> - <span>{{ $ts.emailAddress }}</span> - </FormInput> + <template v-if="enableEmail"> + <FormInput v-model="email" type="email" class="_formBlock"> + <template #label>{{ $ts.emailAddress }}</template> + </FormInput> - <div v-sticky-container class="_debobigegoItem _debobigegoNoConcat"> - <div class="_debobigegoLabel">{{ $ts.smtpConfig }}</div> - <div class="main"> - <FormInput v-model="smtpHost"> - <span>{{ $ts.smtpHost }}</span> - </FormInput> - <FormInput v-model="smtpPort" type="number"> - <span>{{ $ts.smtpPort }}</span> - </FormInput> - <FormInput v-model="smtpUser"> - <span>{{ $ts.smtpUser }}</span> - </FormInput> - <FormInput v-model="smtpPass" type="password"> - <span>{{ $ts.smtpPass }}</span> - </FormInput> - <FormInfo>{{ $ts.emptyToDisableSmtpAuth }}</FormInfo> - <FormSwitch v-model="smtpSecure">{{ $ts.smtpSecure }}<template #desc>{{ $ts.smtpSecureInfo }}</template></FormSwitch> - </div> - </div> - - <FormButton @click="testEmail">{{ $ts.testEmail }}</FormButton> - </template> - - <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + <FormSection> + <template #label>{{ $ts.smtpConfig }}</template> + <FormSplit :min-width="280"> + <FormInput v-model="smtpHost" class="_formBlock"> + <template #label>{{ $ts.smtpHost }}</template> + </FormInput> + <FormInput v-model="smtpPort" type="number" class="_formBlock"> + <template #label>{{ $ts.smtpPort }}</template> + </FormInput> + </FormSplit> + <FormSplit :min-width="280"> + <FormInput v-model="smtpUser" class="_formBlock"> + <template #label>{{ $ts.smtpUser }}</template> + </FormInput> + <FormInput v-model="smtpPass" type="password" class="_formBlock"> + <template #label>{{ $ts.smtpPass }}</template> + </FormInput> + </FormSplit> + <FormInfo class="_formBlock">{{ $ts.emptyToDisableSmtpAuth }}</FormInfo> + <FormSwitch v-model="smtpSecure" class="_formBlock"> + <template #label>{{ $ts.smtpSecure }}</template> + <template #caption>{{ $ts.smtpSecureInfo }}</template> + </FormSwitch> + </FormSection> + </template> + </div> </FormSuspense> -</FormBase> +</MkSpacer> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@/components/debobigego/switch.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormInfo from '@/components/debobigego/info.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import FormSwitch from '@/components/form/switch.vue'; +import FormInput from '@/components/form/input.vue'; +import FormInfo from '@/components/ui/info.vue'; +import FormSuspense from '@/components/form/suspense.vue'; +import FormSplit from '@/components/form/split.vue'; +import FormSection from '@/components/form/section.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; @@ -53,9 +58,8 @@ export default defineComponent({ components: { FormSwitch, FormInput, - FormBase, - FormGroup, - FormButton, + FormSplit, + FormSection, FormInfo, FormSuspense, }, @@ -68,6 +72,16 @@ export default defineComponent({ title: this.$ts.emailServer, icon: 'fas fa-envelope', bg: 'var(--bg)', + actions: [{ + asFullButton: true, + text: this.$ts.testEmail, + handler: this.testEmail, + }, { + asFullButton: true, + icon: 'fas fa-check', + text: this.$ts.save, + handler: this.save, + }], }, enableEmail: false, email: null, @@ -79,10 +93,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); diff --git a/packages/client/src/pages/admin/emoji-edit-dialog.vue b/packages/client/src/pages/admin/emoji-edit-dialog.vue index a45d92fa16..2e3903426e 100644 --- a/packages/client/src/pages/admin/emoji-edit-dialog.vue +++ b/packages/client/src/pages/admin/emoji-edit-dialog.vue @@ -95,7 +95,7 @@ export default defineComponent({ }); if (canceled) return; - os.api('admin/emoji/remove', { + os.api('admin/emoji/delete', { id: this.emoji.id }).then(() => { this.$emit('done', { diff --git a/packages/client/src/pages/admin/emojis.vue b/packages/client/src/pages/admin/emojis.vue index 49277325a0..5b1dfe565a 100644 --- a/packages/client/src/pages/admin/emojis.vue +++ b/packages/client/src/pages/admin/emojis.vue @@ -6,11 +6,22 @@ <template #prefix><i class="fas fa-search"></i></template> <template #label>{{ $ts.search }}</template> </MkInput> - <MkPagination ref="emojis" :pagination="pagination"> + <MkSwitch v-model="selectMode" style="margin: 8px 0;"> + <template #label>Select mode</template> + </MkSwitch> + <div v-if="selectMode" style="display: flex; gap: var(--margin); flex-wrap: wrap;"> + <MkButton inline @click="selectAll">Select all</MkButton> + <MkButton inline @click="setCategoryBulk">Set category</MkButton> + <MkButton inline @click="addTagBulk">Add tag</MkButton> + <MkButton inline @click="removeTagBulk">Remove tag</MkButton> + <MkButton inline @click="setTagBulk">Set tag</MkButton> + <MkButton inline danger @click="delBulk">Delete</MkButton> + </div> + <MkPagination ref="emojisPaginationComponent" :pagination="pagination"> <template #empty><span>{{ $ts.noCustomEmojis }}</span></template> <template v-slot="{items}"> <div class="ldhfsamy"> - <button v-for="emoji in items" :key="emoji.id" class="emoji _panel _button" @click="edit(emoji)"> + <button v-for="emoji in items" :key="emoji.id" class="emoji _panel _button" :class="{ selected: selectedEmojis.includes(emoji.id) }" @click="selectMode ? toggleSelect(emoji) : edit(emoji)"> <img :src="emoji.url" class="img" :alt="emoji.name"/> <div class="body"> <div class="name _monospace">{{ emoji.name }}</div> @@ -23,7 +34,7 @@ </div> <div v-else-if="tab === 'remote'" class="remote"> - <div class="_inputSplit"> + <FormSplit> <MkInput v-model="queryRemote" :debounce="true" type="search"> <template #prefix><i class="fas fa-search"></i></template> <template #label>{{ $ts.search }}</template> @@ -31,8 +42,8 @@ <MkInput v-model="host" :debounce="true"> <template #label>{{ $ts.host }}</template> </MkInput> - </div> - <MkPagination ref="remoteEmojis" :pagination="remotePagination"> + </FormSplit> + <MkPagination :pagination="remotePagination"> <template #empty><span>{{ $ts.noCustomEmojis }}</span></template> <template v-slot="{items}"> <div class="ldhfsamy"> @@ -51,146 +62,233 @@ </MkSpacer> </template> -<script lang="ts"> -import { computed, defineComponent, toRef } from 'vue'; +<script lang="ts" setup> +import { computed, defineComponent, ref, toRef } from 'vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkPagination from '@/components/ui/pagination.vue'; import MkTab from '@/components/tab.vue'; -import { selectFiles } from '@/scripts/select-file'; +import MkSwitch from '@/components/form/switch.vue'; +import FormSplit from '@/components/form/split.vue'; +import { selectFile, selectFiles } from '@/scripts/select-file'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkTab, - MkButton, - MkInput, - MkPagination, - }, +const emojisPaginationComponent = ref<InstanceType<typeof MkPagination>>(); - emits: ['info'], +const tab = ref('local'); +const query = ref(null); +const queryRemote = ref(null); +const host = ref(null); +const selectMode = ref(false); +const selectedEmojis = ref<string[]>([]); - data() { - return { - [symbols.PAGE_INFO]: computed(() => ({ - title: this.$ts.customEmojis, - icon: 'fas fa-laugh', - bg: 'var(--bg)', - actions: [{ - asFullButton: true, - icon: 'fas fa-plus', - text: this.$ts.addEmoji, - handler: this.add, - }, { - icon: 'fas fa-ellipsis-h', - handler: this.menu, - }], - tabs: [{ - active: this.tab === 'local', - title: this.$ts.local, - onClick: () => { this.tab = 'local'; }, - }, { - active: this.tab === 'remote', - title: this.$ts.remote, - onClick: () => { this.tab = 'remote'; }, - },] - })), - tab: 'local', - query: null, - queryRemote: null, - host: '', - pagination: { - endpoint: 'admin/emoji/list', - limit: 30, - params: computed(() => ({ - query: (this.query && this.query !== '') ? this.query : null - })) - }, - remotePagination: { - endpoint: 'admin/emoji/list-remote', - limit: 30, - params: computed(() => ({ - query: (this.queryRemote && this.queryRemote !== '') ? this.queryRemote : null, - host: (this.host && this.host !== '') ? this.host : null - })) - }, - } - }, +const pagination = { + endpoint: 'admin/emoji/list' as const, + limit: 30, + params: computed(() => ({ + query: (query.value && query.value !== '') ? query.value : null, + })), +}; - async mounted() { - this.$emit('info', toRef(this, symbols.PAGE_INFO)); - }, +const remotePagination = { + endpoint: 'admin/emoji/list-remote' as const, + limit: 30, + params: computed(() => ({ + query: (queryRemote.value && queryRemote.value !== '') ? queryRemote.value : null, + host: (host.value && host.value !== '') ? host.value : null, + })), +}; - methods: { - async add(e) { - const files = await selectFiles(e.currentTarget || e.target, null); +const selectAll = () => { + if (selectedEmojis.value.length > 0) { + selectedEmojis.value = []; + } else { + selectedEmojis.value = emojisPaginationComponent.value.items.map(item => item.id); + } +}; - const promise = Promise.all(files.map(file => os.api('admin/emoji/add', { - fileId: file.id, - }))); - promise.then(() => { - this.$refs.emojis.reload(); - }); - os.promiseDialog(promise); - }, +const toggleSelect = (emoji) => { + if (selectedEmojis.value.includes(emoji.id)) { + selectedEmojis.value = selectedEmojis.value.filter(x => x !== emoji.id); + } else { + selectedEmojis.value.push(emoji.id); + } +}; - edit(emoji) { - os.popup(import('./emoji-edit-dialog.vue'), { - emoji: emoji - }, { - done: result => { - if (result.updated) { - this.$refs.emojis.replaceItem(item => item.id === emoji.id, { - ...emoji, - ...result.updated - }); - } else if (result.deleted) { - this.$refs.emojis.removeItem(item => item.id === emoji.id); - } - }, - }, 'closed'); - }, +const add = async (ev: MouseEvent) => { + const files = await selectFiles(ev.currentTarget || ev.target, null); - im(emoji) { - os.apiWithDialog('admin/emoji/copy', { - emojiId: emoji.id, - }); - }, + const promise = Promise.all(files.map(file => os.api('admin/emoji/add', { + fileId: file.id, + }))); + promise.then(() => { + emojisPaginationComponent.value.reload(); + }); + os.promiseDialog(promise); +}; - remoteMenu(emoji, ev) { - os.popupMenu([{ - type: 'label', - text: ':' + emoji.name + ':', - }, { - text: this.$ts.import, - icon: 'fas fa-plus', - action: () => { this.im(emoji) } - }], ev.currentTarget || ev.target); +const edit = (emoji) => { + os.popup(import('./emoji-edit-dialog.vue'), { + emoji: emoji + }, { + done: result => { + if (result.updated) { + emojisPaginationComponent.value.replaceItem(item => item.id === emoji.id, { + ...emoji, + ...result.updated + }); + } else if (result.deleted) { + emojisPaginationComponent.value.removeItem(item => item.id === emoji.id); + } }, + }, 'closed'); +}; - menu(ev) { - os.popupMenu([{ - icon: 'fas fa-download', - text: this.$ts.export, - action: async () => { - os.api('export-custom-emojis', { - }) - .then(() => { - os.alert({ - type: 'info', - text: this.$ts.exportRequested, - }); - }).catch((e) => { - os.alert({ - type: 'error', - text: e.message, - }); - }); - } - }], ev.currentTarget || ev.target); +const im = (emoji) => { + os.apiWithDialog('admin/emoji/copy', { + emojiId: emoji.id, + }); +}; + +const remoteMenu = (emoji, ev: MouseEvent) => { + os.popupMenu([{ + type: 'label', + text: ':' + emoji.name + ':', + }, { + text: i18n.locale.import, + icon: 'fas fa-plus', + action: () => { im(emoji) } + }], ev.currentTarget || ev.target); +}; + +const menu = (ev: MouseEvent) => { + os.popupMenu([{ + icon: 'fas fa-download', + text: i18n.locale.export, + action: async () => { + os.api('export-custom-emojis', { + }) + .then(() => { + os.alert({ + type: 'info', + text: i18n.locale.exportRequested, + }); + }).catch((e) => { + os.alert({ + type: 'error', + text: e.message, + }); + }); } - } + }, { + icon: 'fas fa-upload', + text: i18n.locale.import, + action: async () => { + const file = await selectFile(ev.currentTarget || ev.target); + os.api('admin/emoji/import-zip', { + fileId: file.id, + }) + .then(() => { + os.alert({ + type: 'info', + text: i18n.locale.importRequested, + }); + }).catch((e) => { + os.alert({ + type: 'error', + text: e.message, + }); + }); + } + }], ev.currentTarget || ev.target); +}; + +const setCategoryBulk = async () => { + const { canceled, result } = await os.inputText({ + title: 'Category', + }); + if (canceled) return; + await os.apiWithDialog('admin/emoji/set-category-bulk', { + ids: selectedEmojis.value, + category: result, + }); + emojisPaginationComponent.value.reload(); +}; + +const addTagBulk = async () => { + const { canceled, result } = await os.inputText({ + title: 'Tag', + }); + if (canceled) return; + await os.apiWithDialog('admin/emoji/add-aliases-bulk', { + ids: selectedEmojis.value, + aliases: result.split(' '), + }); + emojisPaginationComponent.value.reload(); +}; + +const removeTagBulk = async () => { + const { canceled, result } = await os.inputText({ + title: 'Tag', + }); + if (canceled) return; + await os.apiWithDialog('admin/emoji/remove-aliases-bulk', { + ids: selectedEmojis.value, + aliases: result.split(' '), + }); + emojisPaginationComponent.value.reload(); +}; + +const setTagBulk = async () => { + const { canceled, result } = await os.inputText({ + title: 'Tag', + }); + if (canceled) return; + await os.apiWithDialog('admin/emoji/set-aliases-bulk', { + ids: selectedEmojis.value, + aliases: result.split(' '), + }); + emojisPaginationComponent.value.reload(); +}; + +const delBulk = async () => { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.locale.deleteConfirm, + }); + if (canceled) return; + await os.apiWithDialog('admin/emoji/delete-bulk', { + ids: selectedEmojis.value, + }); + emojisPaginationComponent.value.reload(); +}; + +defineExpose({ + [symbols.PAGE_INFO]: computed(() => ({ + title: i18n.locale.customEmojis, + icon: 'fas fa-laugh', + bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-plus', + text: i18n.locale.addEmoji, + handler: add, + }, { + icon: 'fas fa-ellipsis-h', + handler: menu, + }], + tabs: [{ + active: tab.value === 'local', + title: i18n.locale.local, + onClick: () => { tab.value = 'local'; }, + }, { + active: tab.value === 'remote', + title: i18n.locale.remote, + onClick: () => { tab.value = 'remote'; }, + },] + })), }); </script> @@ -210,11 +308,16 @@ export default defineComponent({ > .emoji { display: flex; align-items: center; - padding: 12px; + padding: 11px; text-align: left; + border: solid 1px var(--panel); &:hover { - color: var(--accent); + border-color: var(--inputBorderHover); + } + + &.selected { + border-color: var(--accent); } > .img { diff --git a/packages/client/src/pages/admin/files-settings.vue b/packages/client/src/pages/admin/files-settings.vue deleted file mode 100644 index df25bd0fb2..0000000000 --- a/packages/client/src/pages/admin/files-settings.vue +++ /dev/null @@ -1,93 +0,0 @@ -<template> -<FormBase> - <FormSuspense :p="init"> - <FormSwitch v-model="cacheRemoteFiles"> - {{ $ts.cacheRemoteFiles }} - <template #desc>{{ $ts.cacheRemoteFilesDescription }}</template> - </FormSwitch> - - <FormSwitch v-model="proxyRemoteFiles"> - {{ $ts.proxyRemoteFiles }} - <template #desc>{{ $ts.proxyRemoteFilesDescription }}</template> - </FormSwitch> - - <FormInput v-model="localDriveCapacityMb" type="number"> - <span>{{ $ts.driveCapacityPerLocalAccount }}</span> - <template #suffix>MB</template> - <template #desc>{{ $ts.inMb }}</template> - </FormInput> - - <FormInput v-model="remoteDriveCapacityMb" type="number" :disabled="!cacheRemoteFiles"> - <span>{{ $ts.driveCapacityPerRemoteAccount }}</span> - <template #suffix>MB</template> - <template #desc>{{ $ts.inMb }}</template> - </FormInput> - - <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> - </FormSuspense> -</FormBase> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import FormSwitch from '@/components/debobigego/switch.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; -import * as os from '@/os'; -import * as symbols from '@/symbols'; -import { fetchInstance } from '@/instance'; - -export default defineComponent({ - components: { - FormSwitch, - FormInput, - FormBase, - FormGroup, - FormButton, - FormSuspense, - }, - - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.files, - icon: 'fas fa-cloud', - bg: 'var(--bg)', - }, - cacheRemoteFiles: false, - proxyRemoteFiles: false, - localDriveCapacityMb: 0, - remoteDriveCapacityMb: 0, - } - }, - - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - - methods: { - async init() { - const meta = await os.api('meta', { detail: true }); - this.cacheRemoteFiles = meta.cacheRemoteFiles; - this.proxyRemoteFiles = meta.proxyRemoteFiles; - this.localDriveCapacityMb = meta.driveCapacityPerLocalUserMb; - this.remoteDriveCapacityMb = meta.driveCapacityPerRemoteUserMb; - }, - save() { - os.apiWithDialog('admin/update-meta', { - cacheRemoteFiles: this.cacheRemoteFiles, - proxyRemoteFiles: this.proxyRemoteFiles, - localDriveCapacityMb: parseInt(this.localDriveCapacityMb, 10), - remoteDriveCapacityMb: parseInt(this.remoteDriveCapacityMb, 10), - }).then(() => { - fetchInstance(); - }); - } - } -}); -</script> diff --git a/packages/client/src/pages/admin/files.vue b/packages/client/src/pages/admin/files.vue index 032e394a66..87dd12f489 100644 --- a/packages/client/src/pages/admin/files.vue +++ b/packages/client/src/pages/admin/files.vue @@ -19,7 +19,7 @@ <option value="local">{{ $ts.local }}</option> <option value="remote">{{ $ts.remote }}</option> </MkSelect> - <MkInput v-model="searchHost" :debounce="true" type="search" style="margin: 0; flex: 1;" :disabled="pagination.params().origin === 'local'"> + <MkInput v-model="searchHost" :debounce="true" type="search" style="margin: 0; flex: 1;" :disabled="pagination.params.origin === 'local'"> <template #label>{{ $ts.host }}</template> </MkInput> </div> @@ -55,7 +55,7 @@ </template> <script lang="ts"> -import { defineComponent } from 'vue'; +import { computed, defineComponent } from 'vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkSelect from '@/components/form/select.vue'; @@ -95,33 +95,17 @@ export default defineComponent({ type: null, searchHost: '', pagination: { - endpoint: 'admin/drive/files', + endpoint: 'admin/drive/files' as const, limit: 10, - params: () => ({ + params: computed(() => ({ type: (this.type && this.type !== '') ? this.type : null, origin: this.origin, - hostname: (this.hostname && this.hostname !== '') ? this.hostname : null, - }), + hostname: (this.searchHost && this.searchHost !== '') ? this.searchHost : null, + })), }, } }, - watch: { - type() { - this.$refs.files.reload(); - }, - origin() { - this.$refs.files.reload(); - }, - searchHost() { - this.$refs.files.reload(); - }, - }, - - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { clear() { os.confirm({ diff --git a/packages/client/src/pages/admin/index.vue b/packages/client/src/pages/admin/index.vue index e363d1bd03..350e7defc6 100644 --- a/packages/client/src/pages/admin/index.vue +++ b/packages/client/src/pages/admin/index.vue @@ -3,14 +3,14 @@ <div v-if="!narrow || page == null" class="nav"> <MkHeader :info="header"></MkHeader> - <MkSpacer :content-max="700"> + <MkSpacer :content-max="700" :margin-min="16"> <div class="lxpfedzu"> <div class="banner"> <img :src="$instance.iconUrl || '/favicon.ico'" alt="" class="icon"/> </div> <MkInfo v-if="noMaintainerInformation" warn class="info">{{ $ts.noMaintainerInformationWarning }} <MkA to="/admin/settings" class="_link">{{ $ts.configure }}</MkA></MkInfo> - <MkInfo v-if="noBotProtection" warn class="info">{{ $ts.noBotProtectionWarning }} <MkA to="/admin/bot-protection" class="_link">{{ $ts.configure }}</MkA></MkInfo> + <MkInfo v-if="noBotProtection" warn class="info">{{ $ts.noBotProtectionWarning }} <MkA to="/admin/security" class="_link">{{ $ts.configure }}</MkA></MkInfo> <MkSuperMenu :def="menuDef" :grid="page == null"></MkSuperMenu> </div> @@ -19,7 +19,7 @@ <div class="main"> <MkStickyContainer> <template #header><MkHeader v-if="childInfo && !childInfo.hideHeader" :info="childInfo"/></template> - <component :is="component" :key="page" v-bind="pageProps" @info="onInfo"/> + <component :is="component" :ref="el => pageChanged(el)" :key="page" v-bind="pageProps"/> </MkStickyContainer> </div> </div> @@ -29,9 +29,6 @@ import { computed, defineAsyncComponent, defineComponent, isRef, nextTick, onMounted, reactive, ref, watch } from 'vue'; import { i18n } from '@/i18n'; import MkSuperMenu from '@/components/ui/super-menu.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormButton from '@/components/debobigego/button.vue'; import MkInfo from '@/components/ui/info.vue'; import { scroll } from '@/scripts/scroll'; import { instance } from '@/instance'; @@ -41,10 +38,7 @@ import { lookupUser } from '@/scripts/lookup-user'; export default defineComponent({ components: { - FormBase, MkSuperMenu, - FormGroup, - FormButton, MkInfo, }, @@ -72,7 +66,9 @@ export default defineComponent({ const narrow = ref(false); const view = ref(null); const el = ref(null); - const onInfo = (viewInfo) => { + const pageChanged = (page) => { + if (page == null) return; + const viewInfo = page[symbols.PAGE_INFO]; if (isRef(viewInfo)) { watch(viewInfo, () => { childInfo.value = viewInfo.value; @@ -163,11 +159,6 @@ export default defineComponent({ to: '/admin/settings', active: page.value === 'settings', }, { - icon: 'fas fa-cloud', - text: i18n.locale.files, - to: '/admin/files-settings', - active: page.value === 'files-settings', - }, { icon: 'fas fa-envelope', text: i18n.locale.emailServer, to: '/admin/email-settings', @@ -183,11 +174,6 @@ export default defineComponent({ to: '/admin/security', active: page.value === 'security', }, { - icon: 'fas fa-bolt', - text: 'ServiceWorker', - to: '/admin/service-worker', - active: page.value === 'service-worker', - }, { icon: 'fas fa-globe', text: i18n.locale.relays, to: '/admin/relays', @@ -236,17 +222,11 @@ export default defineComponent({ case 'database': return defineAsyncComponent(() => import('./database.vue')); case 'abuses': return defineAsyncComponent(() => import('./abuses.vue')); case 'settings': return defineAsyncComponent(() => import('./settings.vue')); - case 'files-settings': return defineAsyncComponent(() => import('./files-settings.vue')); case 'email-settings': return defineAsyncComponent(() => import('./email-settings.vue')); case 'object-storage': return defineAsyncComponent(() => import('./object-storage.vue')); case 'security': return defineAsyncComponent(() => import('./security.vue')); - case 'bot-protection': return defineAsyncComponent(() => import('./bot-protection.vue')); - case 'service-worker': return defineAsyncComponent(() => import('./service-worker.vue')); case 'relays': return defineAsyncComponent(() => import('./relays.vue')); case 'integrations': return defineAsyncComponent(() => import('./integrations.vue')); - case 'integrations/twitter': return defineAsyncComponent(() => import('./integrations-twitter.vue')); - case 'integrations/github': return defineAsyncComponent(() => import('./integrations-github.vue')); - case 'integrations/discord': return defineAsyncComponent(() => import('./integrations-discord.vue')); case 'instance-block': return defineAsyncComponent(() => import('./instance-block.vue')); case 'proxy-account': return defineAsyncComponent(() => import('./proxy-account.vue')); case 'other-settings': return defineAsyncComponent(() => import('./other-settings.vue')); @@ -333,7 +313,7 @@ export default defineComponent({ narrow, view, el, - onInfo, + pageChanged, childInfo, pageProps, component, diff --git a/packages/client/src/pages/admin/instance-block.vue b/packages/client/src/pages/admin/instance-block.vue index 2e899de687..6cadc7df39 100644 --- a/packages/client/src/pages/admin/instance-block.vue +++ b/packages/client/src/pages/admin/instance-block.vue @@ -1,39 +1,29 @@ <template> -<FormBase> +<MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> <FormSuspense :p="init"> - <FormTextarea v-model="blockedHosts"> + <FormTextarea v-model="blockedHosts" class="_formBlock"> <span>{{ $ts.blockedInstances }}</span> - <template #desc>{{ $ts.blockedInstancesDescription }}</template> + <template #caption>{{ $ts.blockedInstancesDescription }}</template> </FormTextarea> - <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + <FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> </FormSuspense> -</FormBase> +</MkSpacer> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@/components/debobigego/switch.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormTextarea from '@/components/debobigego/textarea.vue'; -import FormInfo from '@/components/debobigego/info.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import FormButton from '@/components/ui/button.vue'; +import FormTextarea from '@/components/form/textarea.vue'; +import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; export default defineComponent({ components: { - FormSwitch, - FormInput, - FormBase, - FormGroup, FormButton, FormTextarea, - FormInfo, FormSuspense, }, @@ -50,10 +40,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); diff --git a/packages/client/src/pages/admin/instance.vue b/packages/client/src/pages/admin/instance.vue deleted file mode 100644 index 51fcb8675a..0000000000 --- a/packages/client/src/pages/admin/instance.vue +++ /dev/null @@ -1,291 +0,0 @@ -<template> -<XModalWindow ref="dialog" - :width="520" - :height="500" - @close="$refs.dialog.close()" - @closed="$emit('closed')" -> - <template #header>{{ instance.host }}</template> - <div class="mk-instance-info"> - <div class="_table section"> - <div class="_row"> - <div class="_cell"> - <div class="_label">{{ $ts.software }}</div> - <div class="_data">{{ instance.softwareName || '?' }}</div> - </div> - <div class="_cell"> - <div class="_label">{{ $ts.version }}</div> - <div class="_data">{{ instance.softwareVersion || '?' }}</div> - </div> - </div> - </div> - <div class="_table data section"> - <div class="_row"> - <div class="_cell"> - <div class="_label">{{ $ts.registeredAt }}</div> - <div class="_data">{{ new Date(instance.caughtAt).toLocaleString() }} (<MkTime :time="instance.caughtAt"/>)</div> - </div> - </div> - <div class="_row"> - <div class="_cell"> - <div class="_label">{{ $ts.following }}</div> - <button class="_data _textButton" @click="showFollowing()">{{ number(instance.followingCount) }}</button> - </div> - <div class="_cell"> - <div class="_label">{{ $ts.followers }}</div> - <button class="_data _textButton" @click="showFollowers()">{{ number(instance.followersCount) }}</button> - </div> - </div> - <div class="_row"> - <div class="_cell"> - <div class="_label">{{ $ts.users }}</div> - <button class="_data _textButton" @click="showUsers()">{{ number(instance.usersCount) }}</button> - </div> - <div class="_cell"> - <div class="_label">{{ $ts.notes }}</div> - <div class="_data">{{ number(instance.notesCount) }}</div> - </div> - </div> - <div class="_row"> - <div class="_cell"> - <div class="_label">{{ $ts.files }}</div> - <div class="_data">{{ number(instance.driveFiles) }}</div> - </div> - <div class="_cell"> - <div class="_label">{{ $ts.storageUsage }}</div> - <div class="_data">{{ bytes(instance.driveUsage) }}</div> - </div> - </div> - <div class="_row"> - <div class="_cell"> - <div class="_label">{{ $ts.latestRequestSentAt }}</div> - <div class="_data"><MkTime v-if="instance.latestRequestSentAt" :time="instance.latestRequestSentAt"/><span v-else>N/A</span></div> - </div> - <div class="_cell"> - <div class="_label">{{ $ts.latestStatus }}</div> - <div class="_data">{{ instance.latestStatus ? instance.latestStatus : 'N/A' }}</div> - </div> - </div> - <div class="_row"> - <div class="_cell"> - <div class="_label">{{ $ts.latestRequestReceivedAt }}</div> - <div class="_data"><MkTime v-if="instance.latestRequestReceivedAt" :time="instance.latestRequestReceivedAt"/><span v-else>N/A</span></div> - </div> - </div> - </div> - <div class="chart"> - <div class="header"> - <span class="label">{{ $ts.charts }}</span> - <div class="selects"> - <MkSelect v-model="chartSrc" style="margin: 0; flex: 1;"> - <option value="instance-requests">{{ $ts._instanceCharts.requests }}</option> - <option value="instance-users">{{ $ts._instanceCharts.users }}</option> - <option value="instance-users-total">{{ $ts._instanceCharts.usersTotal }}</option> - <option value="instance-notes">{{ $ts._instanceCharts.notes }}</option> - <option value="instance-notes-total">{{ $ts._instanceCharts.notesTotal }}</option> - <option value="instance-ff">{{ $ts._instanceCharts.ff }}</option> - <option value="instance-ff-total">{{ $ts._instanceCharts.ffTotal }}</option> - <option value="instance-drive-usage">{{ $ts._instanceCharts.cacheSize }}</option> - <option value="instance-drive-usage-total">{{ $ts._instanceCharts.cacheSizeTotal }}</option> - <option value="instance-drive-files">{{ $ts._instanceCharts.files }}</option> - <option value="instance-drive-files-total">{{ $ts._instanceCharts.filesTotal }}</option> - </MkSelect> - <MkSelect v-model="chartSpan" style="margin: 0;"> - <option value="hour">{{ $ts.perHour }}</option> - <option value="day">{{ $ts.perDay }}</option> - </MkSelect> - </div> - </div> - <div class="chart"> - <MkChart :src="chartSrc" :span="chartSpan" :limit="90" :detailed="true"></MkChart> - </div> - </div> - <div class="operations section"> - <span class="label">{{ $ts.operations }}</span> - <MkSwitch v-model="isSuspended" class="switch">{{ $ts.stopActivityDelivery }}</MkSwitch> - <MkSwitch :model-value="isBlocked" class="switch" @update:modelValue="changeBlock">{{ $ts.blockThisInstance }}</MkSwitch> - <details> - <summary>{{ $ts.deleteAllFiles }}</summary> - <MkButton style="margin: 0.5em 0 0.5em 0;" @click="deleteAllFiles()"><i class="fas fa-trash-alt"></i> {{ $ts.deleteAllFiles }}</MkButton> - </details> - <details> - <summary>{{ $ts.removeAllFollowing }}</summary> - <MkButton style="margin: 0.5em 0 0.5em 0;" @click="removeAllFollowing()"><i class="fas fa-minus-circle"></i> {{ $ts.removeAllFollowing }}</MkButton> - <MkInfo warn>{{ $t('removeAllFollowingDescription', { host: instance.host }) }}</MkInfo> - </details> - </div> - <details class="metadata section"> - <summary class="label">{{ $ts.metadata }}</summary> - <pre><code>{{ JSON.stringify(instance, null, 2) }}</code></pre> - </details> - </div> -</XModalWindow> -</template> - -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; -import XModalWindow from '@/components/ui/modal-window.vue'; -import MkSelect from '@/components/form/select.vue'; -import MkButton from '@/components/ui/button.vue'; -import MkSwitch from '@/components/form/switch.vue'; -import MkInfo from '@/components/ui/info.vue'; -import MkChart from '@/components/chart.vue'; -import bytes from '@/filters/bytes'; -import number from '@/filters/number'; -import * as os from '@/os'; - -export default defineComponent({ - components: { - XModalWindow, - MkSelect, - MkButton, - MkSwitch, - MkInfo, - MkChart, - }, - - props: { - instance: { - type: Object, - required: true - } - }, - - emits: ['closed'], - - data() { - return { - isSuspended: this.instance.isSuspended, - chartSrc: 'requests', - chartSpan: 'hour', - }; - }, - - computed: { - meta() { - return this.$instance; - }, - - isBlocked() { - return this.meta && this.meta.blockedHosts && this.meta.blockedHosts.includes(this.instance.host); - } - }, - - watch: { - isSuspended() { - os.api('admin/federation/update-instance', { - host: this.instance.host, - isSuspended: this.isSuspended - }); - }, - }, - - methods: { - changeBlock(e) { - os.api('admin/update-meta', { - blockedHosts: this.isBlocked ? this.meta.blockedHosts.concat([this.instance.host]) : this.meta.blockedHosts.filter(x => x !== this.instance.host) - }); - }, - - removeAllFollowing() { - os.apiWithDialog('admin/federation/remove-all-following', { - host: this.instance.host - }); - }, - - deleteAllFiles() { - os.apiWithDialog('admin/federation/delete-all-files', { - host: this.instance.host - }); - }, - - showFollowing() { - // TODO: ページ遷移 - }, - - showFollowers() { - // TODO: ページ遷移 - }, - - showUsers() { - // TODO: ページ遷移 - }, - - bytes, - - number - } -}); -</script> - -<style lang="scss" scoped> -.mk-instance-info { - overflow: auto; - - > .section { - padding: 16px 32px; - - @media (max-width: 500px) { - padding: 8px 16px; - } - - &:not(:first-child) { - border-top: solid 0.5px var(--divider); - } - } - - > .chart { - border-top: solid 0.5px var(--divider); - padding: 16px 0 12px 0; - - > .header { - padding: 0 32px; - - @media (max-width: 500px) { - padding: 0 16px; - } - - > .label { - font-size: 80%; - opacity: 0.7; - } - - > .selects { - display: flex; - } - } - - > .chart { - padding: 0 16px; - - @media (max-width: 500px) { - padding: 0; - } - } - } - - > .operations { - > .label { - font-size: 80%; - opacity: 0.7; - } - - > .switch { - margin: 16px 0; - } - } - - > .metadata { - > .label { - font-size: 80%; - opacity: 0.7; - } - - > pre > code { - display: block; - max-height: 200px; - overflow: auto; - } - } -} -</style> diff --git a/packages/client/src/pages/admin/integrations-discord.vue b/packages/client/src/pages/admin/integrations.discord.vue index 383031f3d1..8fc340150a 100644 --- a/packages/client/src/pages/admin/integrations-discord.vue +++ b/packages/client/src/pages/admin/integrations.discord.vue @@ -1,37 +1,36 @@ <template> -<FormBase> - <FormSuspense :p="init"> - <FormSwitch v-model="enableDiscordIntegration"> - {{ $ts.enable }} +<FormSuspense :p="init"> + <div class="_formRoot"> + <FormSwitch v-model="enableDiscordIntegration" class="_formBlock"> + <template #label>{{ $ts.enable }}</template> </FormSwitch> <template v-if="enableDiscordIntegration"> - <FormInfo>Callback URL: {{ `${uri}/api/dc/cb` }}</FormInfo> + <FormInfo class="_formBlock">Callback URL: {{ `${uri}/api/dc/cb` }}</FormInfo> - <FormInput v-model="discordClientId"> + <FormInput v-model="discordClientId" class="_formBlock"> <template #prefix><i class="fas fa-key"></i></template> - Client ID + <template #label>Client ID</template> </FormInput> - <FormInput v-model="discordClientSecret"> + <FormInput v-model="discordClientSecret" class="_formBlock"> <template #prefix><i class="fas fa-key"></i></template> - Client Secret + <template #label>Client Secret</template> </FormInput> </template> - <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> - </FormSuspense> -</FormBase> + <FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + </div> +</FormSuspense> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@/components/debobigego/switch.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormInfo from '@/components/debobigego/info.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import FormSwitch from '@/components/form/switch.vue'; +import FormInput from '@/components/form/input.vue'; +import FormButton from '@/components/ui/button.vue'; +import FormInfo from '@/components/ui/info.vue'; +import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; @@ -40,7 +39,6 @@ export default defineComponent({ components: { FormSwitch, FormInput, - FormBase, FormInfo, FormButton, FormSuspense, @@ -60,10 +58,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); diff --git a/packages/client/src/pages/admin/integrations-github.vue b/packages/client/src/pages/admin/integrations.github.vue index ecb2fd67fa..d9db9c00f1 100644 --- a/packages/client/src/pages/admin/integrations-github.vue +++ b/packages/client/src/pages/admin/integrations.github.vue @@ -1,37 +1,36 @@ <template> -<FormBase> - <FormSuspense :p="init"> - <FormSwitch v-model="enableGithubIntegration"> - {{ $ts.enable }} +<FormSuspense :p="init"> + <div class="_formRoot"> + <FormSwitch v-model="enableGithubIntegration" class="_formBlock"> + <template #label>{{ $ts.enable }}</template> </FormSwitch> <template v-if="enableGithubIntegration"> - <FormInfo>Callback URL: {{ `${uri}/api/gh/cb` }}</FormInfo> + <FormInfo class="_formBlock">Callback URL: {{ `${uri}/api/gh/cb` }}</FormInfo> - <FormInput v-model="githubClientId"> + <FormInput v-model="githubClientId" class="_formBlock"> <template #prefix><i class="fas fa-key"></i></template> - Client ID + <template #label>Client ID</template> </FormInput> - <FormInput v-model="githubClientSecret"> + <FormInput v-model="githubClientSecret" class="_formBlock"> <template #prefix><i class="fas fa-key"></i></template> - Client Secret + <template #label>Client Secret</template> </FormInput> </template> - <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> - </FormSuspense> -</FormBase> + <FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + </div> +</FormSuspense> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@/components/debobigego/switch.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormInfo from '@/components/debobigego/info.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import FormSwitch from '@/components/form/switch.vue'; +import FormInput from '@/components/form/input.vue'; +import FormButton from '@/components/ui/button.vue'; +import FormInfo from '@/components/ui/info.vue'; +import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; @@ -40,7 +39,6 @@ export default defineComponent({ components: { FormSwitch, FormInput, - FormBase, FormInfo, FormButton, FormSuspense, @@ -60,10 +58,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); diff --git a/packages/client/src/pages/admin/integrations-twitter.vue b/packages/client/src/pages/admin/integrations.twitter.vue index 1404102c57..1f8074535a 100644 --- a/packages/client/src/pages/admin/integrations-twitter.vue +++ b/packages/client/src/pages/admin/integrations.twitter.vue @@ -1,37 +1,36 @@ <template> -<FormBase> - <FormSuspense :p="init"> - <FormSwitch v-model="enableTwitterIntegration"> - {{ $ts.enable }} +<FormSuspense :p="init"> + <div class="_formRoot"> + <FormSwitch v-model="enableTwitterIntegration" class="_formBlock"> + <template #label>{{ $ts.enable }}</template> </FormSwitch> <template v-if="enableTwitterIntegration"> - <FormInfo>Callback URL: {{ `${uri}/api/tw/cb` }}</FormInfo> + <FormInfo class="_formBlock">Callback URL: {{ `${uri}/api/tw/cb` }}</FormInfo> - <FormInput v-model="twitterConsumerKey"> + <FormInput v-model="twitterConsumerKey" class="_formBlock"> <template #prefix><i class="fas fa-key"></i></template> - Consumer Key + <template #label>Consumer Key</template> </FormInput> - <FormInput v-model="twitterConsumerSecret"> + <FormInput v-model="twitterConsumerSecret" class="_formBlock"> <template #prefix><i class="fas fa-key"></i></template> - Consumer Secret + <template #label>Consumer Secret</template> </FormInput> </template> - <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> - </FormSuspense> -</FormBase> + <FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + </div> +</FormSuspense> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@/components/debobigego/switch.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormInfo from '@/components/debobigego/info.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import FormSwitch from '@/components/form/switch.vue'; +import FormInput from '@/components/form/input.vue'; +import FormButton from '@/components/ui/button.vue'; +import FormInfo from '@/components/ui/info.vue'; +import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; @@ -40,7 +39,6 @@ export default defineComponent({ components: { FormSwitch, FormInput, - FormBase, FormInfo, FormButton, FormSuspense, @@ -60,10 +58,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); diff --git a/packages/client/src/pages/admin/integrations.vue b/packages/client/src/pages/admin/integrations.vue index c21eebc1c6..91d03fef31 100644 --- a/packages/client/src/pages/admin/integrations.vue +++ b/packages/client/src/pages/admin/integrations.vue @@ -1,46 +1,48 @@ <template> -<FormBase> +<MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> <FormSuspense :p="init"> - <FormLink to="/admin/integrations/twitter"> - <i class="fab fa-twitter"></i> Twitter + <FormFolder class="_formBlock"> + <template #icon><i class="fab fa-twitter"></i></template> + <template #label>Twitter</template> <template #suffix>{{ enableTwitterIntegration ? $ts.enabled : $ts.disabled }}</template> - </FormLink> - <FormLink to="/admin/integrations/github"> - <i class="fab fa-github"></i> GitHub + <XTwitter/> + </FormFolder> + <FormFolder to="/admin/integrations/github" class="_formBlock"> + <template #icon><i class="fab fa-github"></i></template> + <template #label>GitHub</template> <template #suffix>{{ enableGithubIntegration ? $ts.enabled : $ts.disabled }}</template> - </FormLink> - <FormLink to="/admin/integrations/discord"> - <i class="fab fa-discord"></i> Discord + <XGithub/> + </FormFolder> + <FormFolder to="/admin/integrations/discord" class="_formBlock"> + <template #icon><i class="fab fa-discord"></i></template> + <template #label>Discord</template> <template #suffix>{{ enableDiscordIntegration ? $ts.enabled : $ts.disabled }}</template> - </FormLink> + <XDiscord/> + </FormFolder> </FormSuspense> -</FormBase> +</MkSpacer> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormTextarea from '@/components/debobigego/textarea.vue'; -import FormInfo from '@/components/debobigego/info.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import FormFolder from '@/components/form/folder.vue'; +import FormSecion from '@/components/form/section.vue'; +import FormSuspense from '@/components/form/suspense.vue'; +import XTwitter from './integrations.twitter.vue'; +import XGithub from './integrations.github.vue'; +import XDiscord from './integrations.discord.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; export default defineComponent({ components: { - FormLink, - FormInput, - FormBase, - FormGroup, - FormButton, - FormTextarea, - FormInfo, + FormFolder, + FormSecion, FormSuspense, + XTwitter, + XGithub, + XDiscord, }, emits: ['info'], @@ -58,10 +60,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); diff --git a/packages/client/src/pages/admin/metrics.vue b/packages/client/src/pages/admin/metrics.vue index 05b64b235c..1de297fd93 100644 --- a/packages/client/src/pages/admin/metrics.vue +++ b/packages/client/src/pages/admin/metrics.vue @@ -76,7 +76,6 @@ import MkwFederation from '../../widgets/federation.vue'; import { version, url } from '@/config'; import bytes from '@/filters/bytes'; import number from '@/filters/number'; -import MkInstanceInfo from './instance.vue'; Chart.register( ArcElement, @@ -101,6 +100,7 @@ const alpha = (hex, a) => { return `rgba(${r}, ${g}, ${b}, ${a})`; }; import * as os from '@/os'; +import { stream } from '@/stream'; export default defineComponent({ components: { @@ -119,7 +119,7 @@ export default defineComponent({ stats: null, serverInfo: null, connection: null, - queueConnection: markRaw(os.stream.useChannel('queueStats')), + queueConnection: markRaw(stream.useChannel('queueStats')), memUsage: 0, chartCpuMem: null, chartNet: null, @@ -150,7 +150,7 @@ export default defineComponent({ os.api('admin/server-info', {}).then(res => { this.serverInfo = res; - this.connection = markRaw(os.stream.useChannel('serverStats')); + this.connection = markRaw(stream.useChannel('serverStats')); this.connection.on('stats', this.onStats); this.connection.on('statsLog', this.onStatsLog); this.connection.send('requestLog', { diff --git a/packages/client/src/pages/admin/object-storage.vue b/packages/client/src/pages/admin/object-storage.vue index 8984686b5e..6c5be220f8 100644 --- a/packages/client/src/pages/admin/object-storage.vue +++ b/packages/client/src/pages/admin/object-storage.vue @@ -1,76 +1,78 @@ <template> -<FormBase> +<MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> <FormSuspense :p="init"> - <FormSwitch v-model="useObjectStorage">{{ $ts.useObjectStorage }}</FormSwitch> + <div class="_formRoot"> + <FormSwitch v-model="useObjectStorage" class="_formBlock">{{ $ts.useObjectStorage }}</FormSwitch> - <template v-if="useObjectStorage"> - <FormInput v-model="objectStorageBaseUrl"> - <span>{{ $ts.objectStorageBaseUrl }}</span> - <template #desc>{{ $ts.objectStorageBaseUrlDesc }}</template> - </FormInput> + <template v-if="useObjectStorage"> + <FormInput v-model="objectStorageBaseUrl" class="_formBlock"> + <template #label>{{ $ts.objectStorageBaseUrl }}</template> + <template #caption>{{ $ts.objectStorageBaseUrlDesc }}</template> + </FormInput> - <FormInput v-model="objectStorageBucket"> - <span>{{ $ts.objectStorageBucket }}</span> - <template #desc>{{ $ts.objectStorageBucketDesc }}</template> - </FormInput> + <FormInput v-model="objectStorageBucket" class="_formBlock"> + <template #label>{{ $ts.objectStorageBucket }}</template> + <template #caption>{{ $ts.objectStorageBucketDesc }}</template> + </FormInput> - <FormInput v-model="objectStoragePrefix"> - <span>{{ $ts.objectStoragePrefix }}</span> - <template #desc>{{ $ts.objectStoragePrefixDesc }}</template> - </FormInput> + <FormInput v-model="objectStoragePrefix" class="_formBlock"> + <template #label>{{ $ts.objectStoragePrefix }}</template> + <template #caption>{{ $ts.objectStoragePrefixDesc }}</template> + </FormInput> - <FormInput v-model="objectStorageEndpoint"> - <span>{{ $ts.objectStorageEndpoint }}</span> - <template #desc>{{ $ts.objectStorageEndpointDesc }}</template> - </FormInput> + <FormInput v-model="objectStorageEndpoint" class="_formBlock"> + <template #label>{{ $ts.objectStorageEndpoint }}</template> + <template #caption>{{ $ts.objectStorageEndpointDesc }}</template> + </FormInput> - <FormInput v-model="objectStorageRegion"> - <span>{{ $ts.objectStorageRegion }}</span> - <template #desc>{{ $ts.objectStorageRegionDesc }}</template> - </FormInput> + <FormInput v-model="objectStorageRegion" class="_formBlock"> + <template #label>{{ $ts.objectStorageRegion }}</template> + <template #caption>{{ $ts.objectStorageRegionDesc }}</template> + </FormInput> - <FormInput v-model="objectStorageAccessKey"> - <template #prefix><i class="fas fa-key"></i></template> - <span>Access key</span> - </FormInput> + <FormSplit :min-width="280"> + <FormInput v-model="objectStorageAccessKey" class="_formBlock"> + <template #prefix><i class="fas fa-key"></i></template> + <template #label>Access key</template> + </FormInput> - <FormInput v-model="objectStorageSecretKey"> - <template #prefix><i class="fas fa-key"></i></template> - <span>Secret key</span> - </FormInput> + <FormInput v-model="objectStorageSecretKey" class="_formBlock"> + <template #prefix><i class="fas fa-key"></i></template> + <template #label>Secret key</template> + </FormInput> + </FormSplit> - <FormSwitch v-model="objectStorageUseSSL"> - {{ $ts.objectStorageUseSSL }} - <template #desc>{{ $ts.objectStorageUseSSLDesc }}</template> - </FormSwitch> + <FormSwitch v-model="objectStorageUseSSL" class="_formBlock"> + <template #label>{{ $ts.objectStorageUseSSL }}</template> + <template #caption>{{ $ts.objectStorageUseSSLDesc }}</template> + </FormSwitch> - <FormSwitch v-model="objectStorageUseProxy"> - {{ $ts.objectStorageUseProxy }} - <template #desc>{{ $ts.objectStorageUseProxyDesc }}</template> - </FormSwitch> + <FormSwitch v-model="objectStorageUseProxy" class="_formBlock"> + <template #label>{{ $ts.objectStorageUseProxy }}</template> + <template #caption>{{ $ts.objectStorageUseProxyDesc }}</template> + </FormSwitch> - <FormSwitch v-model="objectStorageSetPublicRead"> - {{ $ts.objectStorageSetPublicRead }} - </FormSwitch> + <FormSwitch v-model="objectStorageSetPublicRead" class="_formBlock"> + <template #label>{{ $ts.objectStorageSetPublicRead }}</template> + </FormSwitch> - <FormSwitch v-model="objectStorageS3ForcePathStyle"> - s3ForcePathStyle - </FormSwitch> - </template> - - <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + <FormSwitch v-model="objectStorageS3ForcePathStyle" class="_formBlock"> + <template #label>s3ForcePathStyle</template> + </FormSwitch> + </template> + </div> </FormSuspense> -</FormBase> +</MkSpacer> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@/components/debobigego/switch.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import FormSwitch from '@/components/form/switch.vue'; +import FormInput from '@/components/form/input.vue'; +import FormGroup from '@/components/form/group.vue'; +import FormSuspense from '@/components/form/suspense.vue'; +import FormSplit from '@/components/form/split.vue'; +import FormSection from '@/components/form/section.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; @@ -79,10 +81,10 @@ export default defineComponent({ components: { FormSwitch, FormInput, - FormBase, FormGroup, - FormButton, FormSuspense, + FormSplit, + FormSection, }, emits: ['info'], @@ -93,6 +95,12 @@ export default defineComponent({ title: this.$ts.objectStorage, icon: 'fas fa-cloud', bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-check', + text: this.$ts.save, + handler: this.save, + }], }, useObjectStorage: false, objectStorageBaseUrl: null, @@ -110,10 +118,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); diff --git a/packages/client/src/pages/admin/other-settings.vue b/packages/client/src/pages/admin/other-settings.vue index eb214a21c8..6b588e88aa 100644 --- a/packages/client/src/pages/admin/other-settings.vue +++ b/packages/client/src/pages/admin/other-settings.vue @@ -1,34 +1,17 @@ <template> -<FormBase> +<MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> <FormSuspense :p="init"> - <FormGroup> - <FormInput v-model="summalyProxy"> - <template #prefix><i class="fas fa-link"></i></template> - Summaly Proxy URL - </FormInput> - </FormGroup> - <FormGroup> - <FormInput v-model="deeplAuthKey"> - <template #prefix><i class="fas fa-key"></i></template> - DeepL Auth Key - </FormInput> - <FormSwitch v-model="deeplIsPro"> - Pro account - </FormSwitch> - </FormGroup> - <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + none </FormSuspense> -</FormBase> +</MkSpacer> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@/components/debobigego/switch.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import FormSwitch from '@/components/form/switch.vue'; +import FormInput from '@/components/form/input.vue'; +import FormSection from '@/components/form/section.vue'; +import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; @@ -37,9 +20,7 @@ export default defineComponent({ components: { FormSwitch, FormInput, - FormBase, - FormGroup, - FormButton, + FormSection, FormSuspense, }, @@ -51,29 +32,22 @@ export default defineComponent({ title: this.$ts.other, icon: 'fas fa-cogs', bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-check', + text: this.$ts.save, + handler: this.save, + }], }, - summalyProxy: '', - deeplAuthKey: '', - deeplIsPro: false, } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); - this.summalyProxy = meta.summalyProxy; - this.deeplAuthKey = meta.deeplAuthKey; - this.deeplIsPro = meta.deeplIsPro; }, save() { os.apiWithDialog('admin/update-meta', { - summalyProxy: this.summalyProxy, - deeplAuthKey: this.deeplAuthKey, - deeplIsPro: this.deeplIsPro, }).then(() => { fetchInstance(); }); diff --git a/packages/client/src/pages/admin/overview.vue b/packages/client/src/pages/admin/overview.vue index da5fc0ba6d..b8ae8ad9e1 100644 --- a/packages/client/src/pages/admin/overview.vue +++ b/packages/client/src/pages/admin/overview.vue @@ -19,7 +19,7 @@ <MkContainer :foldable="true" class="charts"> <template #header><i class="fas fa-chart-bar"></i>{{ $ts.charts }}</template> - <div style="padding-top: 12px;"> + <div style="padding: 12px;"> <MkInstanceStats :chart-limit="500" :detailed="true"/> </div> </MkContainer> @@ -67,7 +67,6 @@ <script lang="ts"> import { computed, defineComponent, markRaw, version as vueVersion } from 'vue'; -import FormKeyValueView from '@/components/debobigego/key-value-view.vue'; import MkInstanceStats from '@/components/instance-stats.vue'; import MkButton from '@/components/ui/button.vue'; import MkSelect from '@/components/form/select.vue'; @@ -78,15 +77,14 @@ import MkQueueChart from '@/components/queue-chart.vue'; import { version, url } from '@/config'; import bytes from '@/filters/bytes'; import number from '@/filters/number'; -import MkInstanceInfo from './instance.vue'; import XMetrics from './metrics.vue'; import * as os from '@/os'; +import { stream } from '@/stream'; import * as symbols from '@/symbols'; export default defineComponent({ components: { MkNumberDiff, - FormKeyValueView, MkInstanceStats, MkContainer, MkFolder, @@ -113,13 +111,11 @@ export default defineComponent({ notesComparedToThePrevDay: null, fetchJobs: () => os.api('admin/queue/deliver-delayed', {}), fetchModLogs: () => os.api('admin/show-moderation-logs', {}), - queueStatsConnection: markRaw(os.stream.useChannel('queueStats')), + queueStatsConnection: markRaw(stream.useChannel('queueStats')), } }, async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - os.api('meta', { detail: true }).then(meta => { this.meta = meta; }); @@ -160,9 +156,7 @@ export default defineComponent({ host: q }); } - os.popup(MkInstanceInfo, { - instance: instance - }, {}, 'closed'); + // TODO }, bytes, diff --git a/packages/client/src/pages/admin/proxy-account.vue b/packages/client/src/pages/admin/proxy-account.vue index 14ef92a747..5c4fbffa0c 100644 --- a/packages/client/src/pages/admin/proxy-account.vue +++ b/packages/client/src/pages/admin/proxy-account.vue @@ -1,42 +1,32 @@ <template> -<FormBase> +<MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> <FormSuspense :p="init"> - <FormGroup> - <FormKeyValueView> - <template #key>{{ $ts.proxyAccount }}</template> - <template #value>{{ proxyAccount ? `@${proxyAccount.username}` : $ts.none }}</template> - </FormKeyValueView> - <template #caption>{{ $ts.proxyAccountDescription }}</template> - </FormGroup> + <MkInfo class="_formBlock">{{ $ts.proxyAccountDescription }}</MkInfo> + <MkKeyValue class="_formBlock"> + <template #key>{{ $ts.proxyAccount }}</template> + <template #value>{{ proxyAccount ? `@${proxyAccount.username}` : $ts.none }}</template> + </MkKeyValue> - <FormButton primary @click="chooseProxyAccount">{{ $ts.selectAccount }}</FormButton> + <FormButton primary class="_formBlock" @click="chooseProxyAccount">{{ $ts.selectAccount }}</FormButton> </FormSuspense> -</FormBase> +</MkSpacer> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormKeyValueView from '@/components/debobigego/key-value-view.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormTextarea from '@/components/debobigego/textarea.vue'; -import FormInfo from '@/components/debobigego/info.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import MkKeyValue from '@/components/key-value.vue'; +import FormButton from '@/components/ui/button.vue'; +import MkInfo from '@/components/ui/info.vue'; +import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; export default defineComponent({ components: { - FormKeyValueView, - FormInput, - FormBase, - FormGroup, + MkKeyValue, FormButton, - FormTextarea, - FormInfo, + MkInfo, FormSuspense, }, @@ -54,10 +44,6 @@ export default defineComponent({ } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); diff --git a/packages/client/src/pages/admin/queue.vue b/packages/client/src/pages/admin/queue.vue index 37a87089cb..522210d933 100644 --- a/packages/client/src/pages/admin/queue.vue +++ b/packages/client/src/pages/admin/queue.vue @@ -1,28 +1,25 @@ <template> -<FormBase> +<MkSpacer :content-max="800"> <XQueue :connection="connection" domain="inbox"> <template #title>In</template> </XQueue> <XQueue :connection="connection" domain="deliver"> <template #title>Out</template> </XQueue> - <FormButton danger @click="clear()"><i class="fas fa-trash-alt"></i> {{ $ts.clearQueue }}</FormButton> -</FormBase> + <MkButton danger @click="clear()"><i class="fas fa-trash-alt"></i> {{ $ts.clearQueue }}</MkButton> +</MkSpacer> </template> <script lang="ts"> import { defineComponent, markRaw } from 'vue'; import MkButton from '@/components/ui/button.vue'; import XQueue from './queue.chart.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormButton from '@/components/debobigego/button.vue'; import * as os from '@/os'; +import { stream } from '@/stream'; import * as symbols from '@/symbols'; export default defineComponent({ components: { - FormBase, - FormButton, MkButton, XQueue, }, @@ -36,13 +33,11 @@ export default defineComponent({ icon: 'fas fa-clipboard-list', bg: 'var(--bg)', }, - connection: markRaw(os.stream.useChannel('queueStats')), + connection: markRaw(stream.useChannel('queueStats')), } }, mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - this.$nextTick(() => { this.connection.send('requestLog', { id: Math.random().toString().substr(2, 8), diff --git a/packages/client/src/pages/admin/relays.vue b/packages/client/src/pages/admin/relays.vue index 3e2f1c6f26..bb840db0a2 100644 --- a/packages/client/src/pages/admin/relays.vue +++ b/packages/client/src/pages/admin/relays.vue @@ -1,32 +1,27 @@ <template> -<FormBase class="relaycxt"> - <FormButton primary @click="addRelay"><i class="fas fa-plus"></i> {{ $ts.addRelay }}</FormButton> - - <div v-for="relay in relays" :key="relay.inbox" class="_debobigegoItem"> - <div class="_debobigegoPanel" style="padding: 16px;"> - <div>{{ relay.inbox }}</div> - <div>{{ $t(`_relayStatus.${relay.status}`) }}</div> - <MkButton class="button" inline danger @click="remove(relay.inbox)"><i class="fas fa-trash-alt"></i> {{ $ts.remove }}</MkButton> +<MkSpacer :content-max="800"> + <div v-for="relay in relays" :key="relay.inbox" class="relaycxt _panel _block" style="padding: 16px;"> + <div>{{ relay.inbox }}</div> + <div class="status"> + <i v-if="relay.status === 'accepted'" class="fas fa-check icon accepted"></i> + <i v-else-if="relay.status === 'rejected'" class="fas fa-ban icon rejected"></i> + <i v-else class="fas fa-clock icon requesting"></i> + <span>{{ $t(`_relayStatus.${relay.status}`) }}</span> </div> + <MkButton class="button" inline danger @click="remove(relay.inbox)"><i class="fas fa-trash-alt"></i> {{ $ts.remove }}</MkButton> </div> -</FormBase> +</MkSpacer> </template> <script lang="ts"> import { defineComponent } from 'vue'; import MkButton from '@/components/ui/button.vue'; -import MkInput from '@/components/form/input.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormButton from '@/components/debobigego/button.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; export default defineComponent({ components: { - FormBase, - FormButton, MkButton, - MkInput, }, emits: ['info'], @@ -37,6 +32,12 @@ export default defineComponent({ title: this.$ts.relays, icon: 'fas fa-globe', bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-plus', + text: this.$ts.addRelay, + handler: this.addRelay, + }], }, relays: [], inbox: '', @@ -47,10 +48,6 @@ export default defineComponent({ this.refresh(); }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async addRelay() { const { canceled, result: inbox } = await os.inputText({ @@ -94,5 +91,22 @@ export default defineComponent({ </script> <style lang="scss" scoped> +.relaycxt { + > .status { + margin: 8px 0; + + > .icon { + width: 1em; + margin-right: 0.75em; + &.accepted { + color: var(--success); + } + + &.rejected { + color: var(--error); + } + } + } +} </style> diff --git a/packages/client/src/pages/admin/security.vue b/packages/client/src/pages/admin/security.vue index adfb2e786c..d069891647 100644 --- a/packages/client/src/pages/admin/security.vue +++ b/packages/client/src/pages/admin/security.vue @@ -1,44 +1,58 @@ <template> -<FormBase> +<MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> <FormSuspense :p="init"> - <FormLink to="/admin/bot-protection"> - <i class="fas fa-shield-alt"></i> {{ $ts.botProtection }} - <template v-if="enableHcaptcha" #suffix>hCaptcha</template> - <template v-else-if="enableRecaptcha" #suffix>reCAPTCHA</template> - <template v-else #suffix>{{ $ts.none }} ({{ $ts.notRecommended }})</template> - </FormLink> + <div class="_formRoot"> + <FormFolder class="_formBlock"> + <template #icon><i class="fas fa-shield-alt"></i></template> + <template #label>{{ $ts.botProtection }}</template> + <template v-if="enableHcaptcha" #suffix>hCaptcha</template> + <template v-else-if="enableRecaptcha" #suffix>reCAPTCHA</template> + <template v-else #suffix>{{ $ts.none }} ({{ $ts.notRecommended }})</template> - <FormSwitch v-model="enableRegistration">{{ $ts.enableRegistration }}</FormSwitch> + <XBotProtection/> + </FormFolder> - <FormSwitch v-model="emailRequiredForSignup">{{ $ts.emailRequiredForSignup }}</FormSwitch> + <FormFolder class="_formBlock"> + <template #label>Summaly Proxy</template> - <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + <div class="_formRoot"> + <FormInput v-model="summalyProxy" class="_formBlock"> + <template #prefix><i class="fas fa-link"></i></template> + <template #label>Summaly Proxy URL</template> + </FormInput> + + <FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + </div> + </FormFolder> + </div> </FormSuspense> -</FormBase> +</MkSpacer> </template> <script lang="ts"> import { defineAsyncComponent, defineComponent } from 'vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormSwitch from '@/components/debobigego/switch.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormInfo from '@/components/debobigego/info.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import FormFolder from '@/components/form/folder.vue'; +import FormSwitch from '@/components/form/switch.vue'; +import FormInfo from '@/components/ui/info.vue'; +import FormSuspense from '@/components/form/suspense.vue'; +import FormSection from '@/components/form/section.vue'; +import FormInput from '@/components/form/input.vue'; +import FormButton from '@/components/ui/button.vue'; +import XBotProtection from './bot-protection.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; export default defineComponent({ components: { - FormLink, + FormFolder, FormSwitch, - FormBase, - FormGroup, - FormButton, FormInfo, + FormSection, FormSuspense, + FormButton, + FormInput, + XBotProtection, }, emits: ['info'], @@ -50,30 +64,23 @@ export default defineComponent({ icon: 'fas fa-lock', bg: 'var(--bg)', }, + summalyProxy: '', enableHcaptcha: false, enableRecaptcha: false, - enableRegistration: false, - emailRequiredForSignup: false, } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); + this.summalyProxy = meta.summalyProxy; this.enableHcaptcha = meta.enableHcaptcha; this.enableRecaptcha = meta.enableRecaptcha; - this.enableRegistration = !meta.disableRegistration; - this.emailRequiredForSignup = meta.emailRequiredForSignup; }, - + save() { os.apiWithDialog('admin/update-meta', { - disableRegistration: !this.enableRegistration, - emailRequiredForSignup: this.emailRequiredForSignup, + summalyProxy: this.summalyProxy, }).then(() => { fetchInstance(); }); diff --git a/packages/client/src/pages/admin/service-worker.vue b/packages/client/src/pages/admin/service-worker.vue deleted file mode 100644 index f34cb03e4e..0000000000 --- a/packages/client/src/pages/admin/service-worker.vue +++ /dev/null @@ -1,85 +0,0 @@ -<template> -<FormBase> - <FormSuspense :p="init"> - <FormSwitch v-model="enableServiceWorker"> - {{ $ts.enableServiceworker }} - <template #desc>{{ $ts.serviceworkerInfo }}</template> - </FormSwitch> - - <template v-if="enableServiceWorker"> - <FormInput v-model="swPublicKey"> - <template #prefix><i class="fas fa-key"></i></template> - Public key - </FormInput> - - <FormInput v-model="swPrivateKey"> - <template #prefix><i class="fas fa-key"></i></template> - Private key - </FormInput> - </template> - - <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> - </FormSuspense> -</FormBase> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import FormSwitch from '@/components/debobigego/switch.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; -import * as os from '@/os'; -import * as symbols from '@/symbols'; -import { fetchInstance } from '@/instance'; - -export default defineComponent({ - components: { - FormSwitch, - FormInput, - FormBase, - FormGroup, - FormButton, - FormSuspense, - }, - - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: 'ServiceWorker', - icon: 'fas fa-bolt', - bg: 'var(--bg)', - }, - enableServiceWorker: false, - swPublicKey: null, - swPrivateKey: null, - } - }, - - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - - methods: { - async init() { - const meta = await os.api('meta', { detail: true }); - this.enableServiceWorker = meta.enableServiceWorker; - this.swPublicKey = meta.swPublickey; - this.swPrivateKey = meta.swPrivateKey; - }, - save() { - os.apiWithDialog('admin/update-meta', { - enableServiceWorker: this.enableServiceWorker, - swPublicKey: this.swPublicKey, - swPrivateKey: this.swPrivateKey, - }).then(() => { - fetchInstance(); - }); - } - } -}); -</script> diff --git a/packages/client/src/pages/admin/settings.vue b/packages/client/src/pages/admin/settings.vue index d88445abdb..a4bac93834 100644 --- a/packages/client/src/pages/admin/settings.vue +++ b/packages/client/src/pages/admin/settings.vue @@ -1,72 +1,146 @@ <template> -<FormBase> +<MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> <FormSuspense :p="init"> - <FormInput v-model="name"> - <span>{{ $ts.instanceName }}</span> - </FormInput> + <div class="_formRoot"> + <FormInput v-model="name" class="_formBlock"> + <template #label>{{ $ts.instanceName }}</template> + </FormInput> - <FormTextarea v-model="description"> - <span>{{ $ts.instanceDescription }}</span> - </FormTextarea> + <FormTextarea v-model="description" class="_formBlock"> + <template #label>{{ $ts.instanceDescription }}</template> + </FormTextarea> - <FormInput v-model="iconUrl"> - <template #prefix><i class="fas fa-link"></i></template> - <span>{{ $ts.iconUrl }}</span> - </FormInput> + <FormInput v-model="iconUrl" class="_formBlock"> + <template #prefix><i class="fas fa-link"></i></template> + <template #label>{{ $ts.iconUrl }}</template> + </FormInput> - <FormInput v-model="bannerUrl"> - <template #prefix><i class="fas fa-link"></i></template> - <span>{{ $ts.bannerUrl }}</span> - </FormInput> + <FormInput v-model="bannerUrl" class="_formBlock"> + <template #prefix><i class="fas fa-link"></i></template> + <template #label>{{ $ts.bannerUrl }}</template> + </FormInput> - <FormInput v-model="backgroundImageUrl"> - <template #prefix><i class="fas fa-link"></i></template> - <span>{{ $ts.backgroundImageUrl }}</span> - </FormInput> + <FormInput v-model="backgroundImageUrl" class="_formBlock"> + <template #prefix><i class="fas fa-link"></i></template> + <template #label>{{ $ts.backgroundImageUrl }}</template> + </FormInput> - <FormInput v-model="tosUrl"> - <template #prefix><i class="fas fa-link"></i></template> - <span>{{ $ts.tosUrl }}</span> - </FormInput> + <FormInput v-model="tosUrl" class="_formBlock"> + <template #prefix><i class="fas fa-link"></i></template> + <template #label>{{ $ts.tosUrl }}</template> + </FormInput> - <FormInput v-model="maintainerName"> - <span>{{ $ts.maintainerName }}</span> - </FormInput> + <FormSplit :min-width="300"> + <FormInput v-model="maintainerName" class="_formBlock"> + <template #label>{{ $ts.maintainerName }}</template> + </FormInput> - <FormInput v-model="maintainerEmail" type="email"> - <template #prefix><i class="fas fa-envelope"></i></template> - <span>{{ $ts.maintainerEmail }}</span> - </FormInput> + <FormInput v-model="maintainerEmail" type="email" class="_formBlock"> + <template #prefix><i class="fas fa-envelope"></i></template> + <template #label>{{ $ts.maintainerEmail }}</template> + </FormInput> + </FormSplit> - <FormTextarea v-model="pinnedUsers"> - <span>{{ $ts.pinnedUsers }}</span> - <template #desc>{{ $ts.pinnedUsersDescription }}</template> - </FormTextarea> + <FormTextarea v-model="pinnedUsers" class="_formBlock"> + <template #label>{{ $ts.pinnedUsers }}</template> + <template #caption>{{ $ts.pinnedUsersDescription }}</template> + </FormTextarea> - <FormInput v-model="maxNoteTextLength" type="number"> - <template #prefix><i class="fas fa-pencil-alt"></i></template> - <span>{{ $ts.maxNoteTextLength }}</span> - </FormInput> + <FormInput v-model="maxNoteTextLength" type="number" class="_formBlock"> + <template #prefix><i class="fas fa-pencil-alt"></i></template> + <template #label>{{ $ts.maxNoteTextLength }}</template> + </FormInput> - <FormSwitch v-model="enableLocalTimeline">{{ $ts.enableLocalTimeline }}</FormSwitch> - <FormSwitch v-model="enableGlobalTimeline">{{ $ts.enableGlobalTimeline }}</FormSwitch> - <FormInfo>{{ $ts.disablingTimelinesInfo }}</FormInfo> + <FormSection> + <FormSwitch v-model="enableRegistration" class="_formBlock"> + <template #label>{{ $ts.enableRegistration }}</template> + </FormSwitch> - <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + <FormSwitch v-model="emailRequiredForSignup" class="_formBlock"> + <template #label>{{ $ts.emailRequiredForSignup }}</template> + </FormSwitch> + </FormSection> + + <FormSection> + <FormSwitch v-model="enableLocalTimeline" class="_formBlock">{{ $ts.enableLocalTimeline }}</FormSwitch> + <FormSwitch v-model="enableGlobalTimeline" class="_formBlock">{{ $ts.enableGlobalTimeline }}</FormSwitch> + <FormInfo class="_formBlock">{{ $ts.disablingTimelinesInfo }}</FormInfo> + </FormSection> + + <FormSection> + <template #label>{{ $ts.files }}</template> + + <FormSwitch v-model="cacheRemoteFiles" class="_formBlock"> + <template #label>{{ $ts.cacheRemoteFiles }}</template> + <template #caption>{{ $ts.cacheRemoteFilesDescription }}</template> + </FormSwitch> + + <FormSwitch v-model="proxyRemoteFiles" class="_formBlock"> + <template #label>{{ $ts.proxyRemoteFiles }}</template> + <template #caption>{{ $ts.proxyRemoteFilesDescription }}</template> + </FormSwitch> + + <FormSplit :min-width="280"> + <FormInput v-model="localDriveCapacityMb" type="number" class="_formBlock"> + <template #label>{{ $ts.driveCapacityPerLocalAccount }}</template> + <template #suffix>MB</template> + <template #caption>{{ $ts.inMb }}</template> + </FormInput> + + <FormInput v-model="remoteDriveCapacityMb" type="number" :disabled="!cacheRemoteFiles" class="_formBlock"> + <template #label>{{ $ts.driveCapacityPerRemoteAccount }}</template> + <template #suffix>MB</template> + <template #caption>{{ $ts.inMb }}</template> + </FormInput> + </FormSplit> + </FormSection> + + <FormSection> + <template #label>ServiceWorker</template> + + <FormSwitch v-model="enableServiceWorker" class="_formBlock"> + <template #label>{{ $ts.enableServiceworker }}</template> + <template #caption>{{ $ts.serviceworkerInfo }}</template> + </FormSwitch> + + <template v-if="enableServiceWorker"> + <FormInput v-model="swPublicKey" class="_formBlock"> + <template #prefix><i class="fas fa-key"></i></template> + <template #label>Public key</template> + </FormInput> + + <FormInput v-model="swPrivateKey" class="_formBlock"> + <template #prefix><i class="fas fa-key"></i></template> + <template #label>Private key</template> + </FormInput> + </template> + </FormSection> + + <FormSection> + <template #label>DeepL Translation</template> + + <FormInput v-model="deeplAuthKey" class="_formBlock"> + <template #prefix><i class="fas fa-key"></i></template> + <template #label>DeepL Auth Key</template> + </FormInput> + <FormSwitch v-model="deeplIsPro" class="_formBlock"> + <template #label>Pro account</template> + </FormSwitch> + </FormSection> + </div> </FormSuspense> -</FormBase> +</MkSpacer> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@/components/debobigego/switch.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormTextarea from '@/components/debobigego/textarea.vue'; -import FormInfo from '@/components/debobigego/info.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import FormSwitch from '@/components/form/switch.vue'; +import FormInput from '@/components/form/input.vue'; +import FormTextarea from '@/components/form/textarea.vue'; +import FormInfo from '@/components/ui/info.vue'; +import FormSection from '@/components/form/section.vue'; +import FormSplit from '@/components/form/split.vue'; +import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; @@ -75,12 +149,11 @@ export default defineComponent({ components: { FormSwitch, FormInput, - FormBase, - FormGroup, - FormButton, + FormSuspense, FormTextarea, FormInfo, - FormSuspense, + FormSection, + FormSplit, }, emits: ['info'], @@ -91,6 +164,12 @@ export default defineComponent({ title: this.$ts.general, icon: 'fas fa-cog', bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-check', + text: this.$ts.save, + handler: this.save, + }], }, name: null, description: null, @@ -104,13 +183,20 @@ export default defineComponent({ enableLocalTimeline: false, enableGlobalTimeline: false, pinnedUsers: '', + cacheRemoteFiles: false, + proxyRemoteFiles: false, + localDriveCapacityMb: 0, + remoteDriveCapacityMb: 0, + enableRegistration: false, + emailRequiredForSignup: false, + enableServiceWorker: false, + swPublicKey: null, + swPrivateKey: null, + deeplAuthKey: '', + deeplIsPro: false, } }, - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async init() { const meta = await os.api('meta', { detail: true }); @@ -126,6 +212,17 @@ export default defineComponent({ this.enableLocalTimeline = !meta.disableLocalTimeline; this.enableGlobalTimeline = !meta.disableGlobalTimeline; this.pinnedUsers = meta.pinnedUsers.join('\n'); + this.cacheRemoteFiles = meta.cacheRemoteFiles; + this.proxyRemoteFiles = meta.proxyRemoteFiles; + this.localDriveCapacityMb = meta.driveCapacityPerLocalUserMb; + this.remoteDriveCapacityMb = meta.driveCapacityPerRemoteUserMb; + this.enableRegistration = !meta.disableRegistration; + this.emailRequiredForSignup = meta.emailRequiredForSignup; + this.enableServiceWorker = meta.enableServiceWorker; + this.swPublicKey = meta.swPublickey; + this.swPrivateKey = meta.swPrivateKey; + this.deeplAuthKey = meta.deeplAuthKey; + this.deeplIsPro = meta.deeplIsPro; }, save() { @@ -142,6 +239,17 @@ export default defineComponent({ disableLocalTimeline: !this.enableLocalTimeline, disableGlobalTimeline: !this.enableGlobalTimeline, pinnedUsers: this.pinnedUsers.split('\n'), + cacheRemoteFiles: this.cacheRemoteFiles, + proxyRemoteFiles: this.proxyRemoteFiles, + localDriveCapacityMb: parseInt(this.localDriveCapacityMb, 10), + remoteDriveCapacityMb: parseInt(this.remoteDriveCapacityMb, 10), + disableRegistration: !this.enableRegistration, + emailRequiredForSignup: this.emailRequiredForSignup, + enableServiceWorker: this.enableServiceWorker, + swPublicKey: this.swPublicKey, + swPrivateKey: this.swPrivateKey, + deeplAuthKey: this.deeplAuthKey, + deeplIsPro: this.deeplIsPro, }).then(() => { fetchInstance(); }); diff --git a/packages/client/src/pages/admin/users.vue b/packages/client/src/pages/admin/users.vue index e7a3437167..03e155ddcf 100644 --- a/packages/client/src/pages/admin/users.vue +++ b/packages/client/src/pages/admin/users.vue @@ -30,7 +30,7 @@ <template #prefix>@</template> <template #label>{{ $ts.username }}</template> </MkInput> - <MkInput v-model="searchHost" style="flex: 1;" type="text" spellcheck="false" :disabled="pagination.params().origin === 'local'" @update:modelValue="$refs.users.reload()"> + <MkInput v-model="searchHost" style="flex: 1;" type="text" spellcheck="false" :disabled="pagination.params.origin === 'local'" @update:modelValue="$refs.users.reload()"> <template #prefix>@</template> <template #label>{{ $ts.host }}</template> </MkInput> @@ -62,7 +62,7 @@ </template> <script lang="ts"> -import { defineComponent } from 'vue'; +import { computed, defineComponent } from 'vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkSelect from '@/components/form/select.vue'; @@ -110,36 +110,20 @@ export default defineComponent({ searchUsername: '', searchHost: '', pagination: { - endpoint: 'admin/show-users', + endpoint: 'admin/show-users' as const, limit: 10, - params: () => ({ + params: computed(() => ({ sort: this.sort, state: this.state, origin: this.origin, username: this.searchUsername, hostname: this.searchHost, - }), + })), offsetMode: true }, } }, - watch: { - sort() { - this.$refs.users.reload(); - }, - state() { - this.$refs.users.reload(); - }, - origin() { - this.$refs.users.reload(); - }, - }, - - async mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { lookupUser, diff --git a/packages/client/src/pages/advanced-theme-editor.vue b/packages/client/src/pages/advanced-theme-editor.vue deleted file mode 100644 index 9c2423131c..0000000000 --- a/packages/client/src/pages/advanced-theme-editor.vue +++ /dev/null @@ -1,349 +0,0 @@ -<template> -<div class="t9makv94"> - <section class="_section"> - <div class="_content"> - <details> - <summary>{{ $ts.import }}</summary> - <MkTextarea v-model="themeToImport"> - {{ $ts._theme.importInfo }} - </MkTextarea> - <MkButton :disabled="!themeToImport.trim()" @click="importTheme">{{ $ts.import }}</MkButton> - </details> - </div> - </section> - <section class="_section"> - <div class="_content _card _gap"> - <div class="_content"> - <MkInput v-model="name" required><span>{{ $ts.name }}</span></MkInput> - <MkInput v-model="author" required><span>{{ $ts.author }}</span></MkInput> - <MkTextarea v-model="description"><span>{{ $ts.description }}</span></MkTextarea> - <div class="_inputs"> - <div v-text="$ts._theme.base" /> - <MkRadio v-model="baseTheme" value="light">{{ $ts.light }}</MkRadio> - <MkRadio v-model="baseTheme" value="dark">{{ $ts.dark }}</MkRadio> - </div> - </div> - </div> - <div class="_content _card _gap"> - <div class="list-view _content"> - <div v-for="([ k, v ], i) in theme" :key="k" class="item"> - <div class="_inputs"> - <div> - {{ k.startsWith('$') ? `${k} (${$ts._theme.constant})` : $t('_theme.keys.' + k) }} - <button v-if="k.startsWith('$')" class="_button _link" @click="del(i)" v-text="$ts.delete" /> - </div> - <div> - <div class="type" @click="chooseType($event, i)"> - {{ getTypeOf(v) }} <i class="fas fa-chevron-down"></i> - </div> - <!-- default --> - <div v-if="v === null" class="default-value" v-text="baseProps[k]" /> - <!-- color --> - <div v-else-if="typeof v === 'string'" class="color"> - <input type="color" :value="v" @input="colorChanged($event.target.value, i)"/> - <MkInput class="select" :value="v" @update:modelValue="colorChanged($event, i)"/> - </div> - <!-- ref const --> - <MkInput v-else-if="v.type === 'refConst'" v-model="v.key"> - <template #prefix>$</template> - <span>{{ $ts.name }}</span> - </MkInput> - <!-- ref props --> - <MkSelect v-else-if="v.type === 'refProp'" v-model="v.key" class="select"> - <option v-for="key in themeProps" :key="key" :value="key">{{ $t('_theme.keys.' + key) }}</option> - </MkSelect> - <!-- func --> - <template v-else-if="v.type === 'func'"> - <MkSelect v-model="v.name" class="select"> - <template #label>{{ $ts._theme.funcKind }}</template> - <option v-for="n in ['alpha', 'darken', 'lighten']" :key="n" :value="n">{{ $t('_theme.' + n) }}</option> - </MkSelect> - <MkInput v-model="v.arg" type="number"><span>{{ $ts._theme.argument }}</span></MkInput> - <MkSelect v-model="v.value" class="select"> - <template #label>{{ $ts._theme.basedProp }}</template> - <option v-for="key in themeProps" :key="key" :value="key">{{ $t('_theme.keys.' + key) }}</option> - </MkSelect> - </template> - <!-- CSS --> - <MkInput v-else-if="v.type === 'css'" v-model="v.value"> - <span>CSS</span> - </MkInput> - </div> - </div> - </div> - <MkButton primary @click="addConst">{{ $ts._theme.addConstant }}</MkButton> - </div> - </div> - </section> - <section class="_section"> - <details class="_content"> - <summary>{{ $ts.sample }}</summary> - <MkSample/> - </details> - </section> - <section class="_section"> - <div class="_content"> - <MkButton inline @click="preview">{{ $ts.preview }}</MkButton> - <MkButton inline primary :disabled="!name || !author" @click="save">{{ $ts.save }}</MkButton> - </div> - </section> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as JSON5 from 'json5'; -import { toUnicode } from 'punycode/'; - -import MkRadio from '@/components/form/radio.vue'; -import MkButton from '@/components/ui/button.vue'; -import MkInput from '@/components/form/input.vue'; -import MkTextarea from '@/components/form/textarea.vue'; -import MkSelect from '@/components/form/select.vue'; -import MkSample from '@/components/sample.vue'; - -import { convertToMisskeyTheme, ThemeValue, convertToViewModel, ThemeViewModel } from '@/scripts/theme-editor'; -import { Theme, applyTheme, lightTheme, darkTheme, themeProps, validateTheme } from '@/scripts/theme'; -import { host } from '@/config'; -import * as os from '@/os'; -import { ColdDeviceStorage } from '@/store'; -import { addTheme } from '@/theme-store'; -import * as symbols from '@/symbols'; - -export default defineComponent({ - components: { - MkRadio, - MkButton, - MkInput, - MkTextarea, - MkSelect, - MkSample, - }, - - async beforeRouteLeave(to, from, next) { - if (this.changed && !(await this.confirm())) { - next(false); - } else { - next(); - } - }, - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.themeEditor, - icon: 'fas fa-palette', - }, - theme: [] as ThemeViewModel, - name: '', - description: '', - baseTheme: 'light' as 'dark' | 'light', - author: `@${this.$i.username}@${toUnicode(host)}`, - themeToImport: '', - changed: false, - lightTheme, darkTheme, themeProps, - } - }, - - computed: { - baseProps() { - return this.baseTheme === 'light' ? this.lightTheme.props : this.darkTheme.props; - }, - }, - - beforeUnmount() { - window.removeEventListener('beforeunload', this.beforeunload); - }, - - mounted() { - this.init(); - window.addEventListener('beforeunload', this.beforeunload); - const changed = () => this.changed = true; - this.$watch('name', changed); - this.$watch('description', changed); - this.$watch('baseTheme', changed); - this.$watch('author', changed); - this.$watch('theme', changed); - }, - - methods: { - beforeunload(e: BeforeUnloadEvent) { - if (this.changed) { - e.preventDefault(); - e.returnValue = ''; - } - }, - - async confirm(): Promise<boolean> { - const { canceled } = await os.confirm({ - type: 'warning', - text: this.$ts.leaveConfirm, - }); - return !canceled; - }, - - init() { - const t: ThemeViewModel = []; - for (const key of themeProps) { - t.push([ key, null ]); - } - this.theme = t; - }, - - async del(i: number) { - const { canceled } = await os.confirm({ - type: 'warning', - text: this.$t('_theme.deleteConstantConfirm', { const: this.theme[i][0] }), - }); - if (canceled) return; - Vue.delete(this.theme, i); - }, - - async addConst() { - const { canceled, result } = await os.inputText({ - title: this.$ts._theme.inputConstantName, - }); - if (canceled) return; - this.theme.push([ '$' + result, '#000000']); - }, - - save() { - const theme = convertToMisskeyTheme(this.theme, this.name, this.description, this.author, this.baseTheme); - addTheme(theme); - os.alert({ - type: 'success', - text: this.$t('_theme.installed', { name: theme.name }) - }); - this.changed = false; - }, - - preview() { - const theme = convertToMisskeyTheme(this.theme, this.name, this.description, this.author, this.baseTheme); - try { - applyTheme(theme, false); - } catch (e) { - os.alert({ - type: 'error', - text: e.message - }); - } - }, - - async importTheme() { - if (this.changed && (!await this.confirm())) return; - - try { - const theme = JSON5.parse(this.themeToImport) as Theme; - if (!validateTheme(theme)) throw new Error(this.$ts._theme.invalid); - - this.name = theme.name; - this.description = theme.desc || ''; - this.author = theme.author; - this.baseTheme = theme.base || 'light'; - this.theme = convertToViewModel(theme); - this.themeToImport = ''; - } catch (e) { - os.alert({ - type: 'error', - text: e.message - }); - } - }, - - colorChanged(color: string, i: number) { - this.theme[i] = [this.theme[i][0], color]; - }, - - getTypeOf(v: ThemeValue) { - return v === null - ? this.$ts._theme.defaultValue - : typeof v === 'string' - ? this.$ts._theme.color - : this.$t('_theme.' + v.type); - }, - - async chooseType(e: MouseEvent, i: number) { - const newValue = await this.showTypeMenu(e); - this.theme[i] = [ this.theme[i][0], newValue ]; - }, - - showTypeMenu(e: MouseEvent) { - return new Promise<ThemeValue>((resolve) => { - os.popupMenu([{ - text: this.$ts._theme.defaultValue, - action: () => resolve(null), - }, { - text: this.$ts._theme.color, - action: () => resolve('#000000'), - }, { - text: this.$ts._theme.func, - action: () => resolve({ - type: 'func', name: 'alpha', arg: 1, value: 'accent' - }), - }, { - text: this.$ts._theme.refProp, - action: () => resolve({ - type: 'refProp', key: 'accent', - }), - }, { - text: this.$ts._theme.refConst, - action: () => resolve({ - type: 'refConst', key: '', - }), - }, { - text: 'CSS', - action: () => resolve({ - type: 'css', value: '', - }), - }], e.currentTarget || e.target); - }); - } - } -}); -</script> - -<style lang="scss" scoped> -.t9makv94 { - > ._section { - > ._content { - > .list-view { - > .item { - min-height: 48px; - word-break: break-all; - - &:not(:last-child) { - margin-bottom: 8px; - } - - .select { - margin: 24px 0; - } - - .type { - cursor: pointer; - } - - .default-value { - opacity: 0.6; - pointer-events: none; - user-select: none; - } - - .color { - > input { - display: inline-block; - width: 1.5em; - height: 1.5em; - } - - > div { - margin-left: 8px; - display: inline-block; - } - } - } - } - } - } -} -</style> diff --git a/packages/client/src/pages/announcements.vue b/packages/client/src/pages/announcements.vue index ca94640dda..53727823a4 100644 --- a/packages/client/src/pages/announcements.vue +++ b/packages/client/src/pages/announcements.vue @@ -36,7 +36,7 @@ export default defineComponent({ bg: 'var(--bg)', }, pagination: { - endpoint: 'announcements', + endpoint: 'announcements' as const, limit: 10, }, }; diff --git a/packages/client/src/pages/channel.vue b/packages/client/src/pages/channel.vue index 67ab2d8981..c9a8f36844 100644 --- a/packages/client/src/pages/channel.vue +++ b/packages/client/src/pages/channel.vue @@ -67,11 +67,11 @@ export default defineComponent({ channel: null, showBanner: true, pagination: { - endpoint: 'channels/timeline', + endpoint: 'channels/timeline' as const, limit: 10, - params: () => ({ + params: computed(() => ({ channelId: this.channelId, - }) + })) }, }; }, diff --git a/packages/client/src/pages/channels.vue b/packages/client/src/pages/channels.vue index 48877ab3ec..4e538a6da3 100644 --- a/packages/client/src/pages/channels.vue +++ b/packages/client/src/pages/channels.vue @@ -60,15 +60,15 @@ export default defineComponent({ })), tab: 'featured', featuredPagination: { - endpoint: 'channels/featured', + endpoint: 'channels/featured' as const, noPaging: true, }, followingPagination: { - endpoint: 'channels/followed', + endpoint: 'channels/followed' as const, limit: 5, }, ownedPagination: { - endpoint: 'channels/owned', + endpoint: 'channels/owned' as const, limit: 5, }, }; diff --git a/packages/client/src/pages/clip.vue b/packages/client/src/pages/clip.vue index 077a6ac8b5..6b49221d32 100644 --- a/packages/client/src/pages/clip.vue +++ b/packages/client/src/pages/clip.vue @@ -50,11 +50,11 @@ export default defineComponent({ } : null), clip: null, pagination: { - endpoint: 'clips/notes', + endpoint: 'clips/notes' as const, limit: 10, - params: () => ({ + params: computed(() => ({ clipId: this.clipId, - }) + })) }, }; }, diff --git a/packages/client/src/pages/drive.vue b/packages/client/src/pages/drive.vue index f30000367f..1e17bea0cc 100644 --- a/packages/client/src/pages/drive.vue +++ b/packages/client/src/pages/drive.vue @@ -4,27 +4,21 @@ </div> </template> -<script lang="ts"> -import { computed, defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; import XDrive from '@/components/drive.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XDrive - }, +let folder = $ref(null); - data() { - return { - [symbols.PAGE_INFO]: { - title: computed(() => this.folder ? this.folder.name : this.$ts.drive), - icon: 'fas fa-cloud', - bg: 'var(--bg)', - hideHeader: true, - }, - folder: null, - }; - }, +defineExpose({ + [symbols.PAGE_INFO]: computed(() => ({ + title: folder ? folder.name : i18n.locale.drive, + icon: 'fas fa-cloud', + bg: 'var(--bg)', + hideHeader: true, + })), }); </script> diff --git a/packages/client/src/pages/emojis.emoji.vue b/packages/client/src/pages/emojis.emoji.vue index 5dab72daea..83539ce7a3 100644 --- a/packages/client/src/pages/emojis.emoji.vue +++ b/packages/client/src/pages/emojis.emoji.vue @@ -8,35 +8,29 @@ </button> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import * as os from '@/os'; import copyToClipboard from '@/scripts/copy-to-clipboard'; +import { i18n } from '@/i18n'; -export default defineComponent({ - props: { - emoji: { - type: Object, - required: true, - } - }, +const props = defineProps<{ + emoji: Record<string, unknown>; // TODO +}>(); - methods: { - menu(ev) { - os.popupMenu([{ - type: 'label', - text: ':' + this.emoji.name + ':', - }, { - text: this.$ts.copy, - icon: 'fas fa-copy', - action: () => { - copyToClipboard(`:${this.emoji.name}:`); - os.success(); - } - }], ev.currentTarget || ev.target); +function menu(ev) { + os.popupMenu([{ + type: 'label', + text: ':' + props.emoji.name + ':', + }, { + text: i18n.locale.copy, + icon: 'fas fa-copy', + action: () => { + copyToClipboard(`:${props.emoji.name}:`); + os.success(); } - } -}); + }], ev.currentTarget || ev.target); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/pages/emojis.vue b/packages/client/src/pages/emojis.vue index 2adb5345e2..6577f5abd9 100644 --- a/packages/client/src/pages/emojis.vue +++ b/packages/client/src/pages/emojis.vue @@ -4,55 +4,47 @@ </div> </template> -<script lang="ts"> -import { defineComponent, computed } from 'vue'; +<script lang="ts" setup> +import { ref, computed } from 'vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import XCategory from './emojis.category.vue'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XCategory, - }, +const tab = ref('category'); - data() { - return { - [symbols.PAGE_INFO]: computed(() => ({ - title: this.$ts.customEmojis, - icon: 'fas fa-laugh', - bg: 'var(--bg)', - actions: [{ - icon: 'fas fa-ellipsis-h', - handler: this.menu - }], - })), - tab: 'category', +function menu(ev) { + os.popupMenu([{ + icon: 'fas fa-download', + text: i18n.locale.export, + action: async () => { + os.api('export-custom-emojis', { + }) + .then(() => { + os.alert({ + type: 'info', + text: i18n.locale.exportRequested, + }); + }).catch((e) => { + os.alert({ + type: 'error', + text: e.message, + }); + }); } - }, + }], ev.currentTarget || ev.target); +} - methods: { - menu(ev) { - os.popupMenu([{ - icon: 'fas fa-download', - text: this.$ts.export, - action: async () => { - os.api('export-custom-emojis', { - }) - .then(() => { - os.alert({ - type: 'info', - text: this.$ts.exportRequested, - }); - }).catch((e) => { - os.alert({ - type: 'error', - text: e.message, - }); - }); - } - }], ev.currentTarget || ev.target); - } - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.customEmojis, + icon: 'fas fa-laugh', + bg: 'var(--bg)', + actions: [{ + icon: 'fas fa-ellipsis-h', + handler: menu, + }], + }, }); </script> diff --git a/packages/client/src/pages/explore.vue b/packages/client/src/pages/explore.vue index a3c3b771f2..04cc3662a7 100644 --- a/packages/client/src/pages/explore.vue +++ b/packages/client/src/pages/explore.vue @@ -156,7 +156,7 @@ export default defineComponent({ sort: '+createdAt', } }, searchPagination: { - endpoint: 'users/search', + endpoint: 'users/search' as const, limit: 10, params: computed(() => (this.searchQuery && this.searchQuery !== '') ? { query: this.searchQuery, @@ -178,7 +178,7 @@ export default defineComponent({ }, tagUsers(): any { return { - endpoint: 'hashtags/users', + endpoint: 'hashtags/users' as const, limit: 30, params: { tag: this.tag, diff --git a/packages/client/src/pages/favorites.vue b/packages/client/src/pages/favorites.vue index faab864744..8965b30d60 100644 --- a/packages/client/src/pages/favorites.vue +++ b/packages/client/src/pages/favorites.vue @@ -1,49 +1,49 @@ <template> -<div class="jmelgwjh"> - <div class="body"> - <XNotes class="notes" :pagination="pagination" :detail="true" :prop="'note'"/> - </div> -</div> +<MkSpacer :content-max="800"> + <MkPagination ref="pagingComponent" :pagination="pagination"> + <template #empty> + <div class="_fullinfo"> + <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> + <div>{{ $ts.noNotes }}</div> + </div> + </template> + + <template #default="{ items }"> + <XList v-slot="{ item }" :items="items" :direction="'down'" :no-gap="false" :ad="false"> + <XNote :key="item.id" :note="item.note" :class="$style.note"/> + </XList> + </template> + </MkPagination> +</MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import XNotes from '@/components/notes.vue'; -import * as os from '@/os'; +<script lang="ts" setup> +import { ref } from 'vue'; +import MkPagination from '@/components/ui/pagination.vue'; +import XNote from '@/components/note.vue'; +import XList from '@/components/date-separated-list.vue'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XNotes - }, +const pagination = { + endpoint: 'i/favorites' as const, + limit: 10, +}; - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.favorites, - icon: 'fas fa-star', - bg: 'var(--bg)', - }, - pagination: { - endpoint: 'i/favorites', - limit: 10, - params: () => ({ - }) - }, - }; +const pagingComponent = ref<InstanceType<typeof MkPagination>>(); + +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.favorites, + icon: 'fas fa-star', + bg: 'var(--bg)', }, }); </script> -<style lang="scss" scoped> -.jmelgwjh { - background: var(--bg); - - > .body { - box-sizing: border-box; - max-width: 800px; - margin: 0 auto; - padding: 16px; - } +<style lang="scss" module> +.note { + background: var(--panel); + border-radius: var(--radius); } </style> diff --git a/packages/client/src/pages/featured.vue b/packages/client/src/pages/featured.vue index 0844c0952f..725c70f0f7 100644 --- a/packages/client/src/pages/featured.vue +++ b/packages/client/src/pages/featured.vue @@ -4,29 +4,22 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> import XNotes from '@/components/notes.vue'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XNotes - }, +const pagination = { + endpoint: 'notes/featured' as const, + limit: 10, + offsetMode: true, +}; - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.featured, - icon: 'fas fa-fire-alt', - bg: 'var(--bg)', - }, - pagination: { - endpoint: 'notes/featured', - limit: 10, - offsetMode: true, - }, - }; +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.featured, + icon: 'fas fa-fire-alt', + bg: 'var(--bg)', }, }); </script> diff --git a/packages/client/src/pages/federation.vue b/packages/client/src/pages/federation.vue index 4e5f428ff9..6a4a28b6b4 100644 --- a/packages/client/src/pages/federation.vue +++ b/packages/client/src/pages/federation.vue @@ -6,7 +6,7 @@ <template #prefix><i class="fas fa-search"></i></template> <template #label>{{ $ts.host }}</template> </MkInput> - <div class="_inputSplit" style="margin-top: var(--margin);"> + <FormSplit style="margin-top: var(--margin);"> <MkSelect v-model="state"> <template #label>{{ $ts.state }}</template> <option value="all">{{ $ts.all }}</option> @@ -38,7 +38,7 @@ <option value="+driveFiles">{{ $ts.driveFilesCount }} ({{ $ts.descendingOrder }})</option> <option value="-driveFiles">{{ $ts.driveFilesCount }} ({{ $ts.ascendingOrder }})</option> </MkSelect> - </div> + </FormSplit> </div> <MkPagination v-slot="{items}" ref="instances" :key="host + state" :pagination="pagination"> @@ -95,75 +95,50 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkSelect from '@/components/form/select.vue'; import MkPagination from '@/components/ui/pagination.vue'; +import FormSplit from '@/components/form/split.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, - MkInput, - MkSelect, - MkPagination, - }, - - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.federation, - icon: 'fas fa-globe', - bg: 'var(--bg)', - }, - host: '', - state: 'federating', - sort: '+pubSub', - pagination: { - endpoint: 'federation/instances', - limit: 10, - offsetMode: true, - params: () => ({ - sort: this.sort, - host: this.host != '' ? this.host : null, - ...( - this.state === 'federating' ? { federating: true } : - this.state === 'subscribing' ? { subscribing: true } : - this.state === 'publishing' ? { publishing: true } : - this.state === 'suspended' ? { suspended: true } : - this.state === 'blocked' ? { blocked: true } : - this.state === 'notResponding' ? { notResponding: true } : - {}) - }) - }, - } - }, +let host = $ref(''); +let state = $ref('federating'); +let sort = $ref('+pubSub'); +const pagination = { + endpoint: 'federation/instances' as const, + limit: 10, + offsetMode: true, + params: computed(() => ({ + sort: sort, + host: host != '' ? host : null, + ...( + state === 'federating' ? { federating: true } : + state === 'subscribing' ? { subscribing: true } : + state === 'publishing' ? { publishing: true } : + state === 'suspended' ? { suspended: true } : + state === 'blocked' ? { blocked: true } : + state === 'notResponding' ? { notResponding: true } : + {}) + })) +}; - watch: { - host() { - this.$refs.instances.reload(); - }, - state() { - this.$refs.instances.reload(); - } - }, +function getStatus(instance) { + if (instance.isSuspended) return 'suspended'; + if (instance.isNotResponding) return 'error'; + return 'alive'; +}; - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.federation, + icon: 'fas fa-globe', + bg: 'var(--bg)', }, - - methods: { - getStatus(instance) { - if (instance.isSuspended) return 'suspended'; - if (instance.isNotResponding) return 'error'; - return 'alive'; - }, - } }); </script> diff --git a/packages/client/src/pages/follow-requests.vue b/packages/client/src/pages/follow-requests.vue index 54d695091d..764daa0d3e 100644 --- a/packages/client/src/pages/follow-requests.vue +++ b/packages/client/src/pages/follow-requests.vue @@ -1,6 +1,6 @@ <template> <div> - <MkPagination ref="list" :pagination="pagination" class="mk-follow-requests"> + <MkPagination ref="paginationComponent" :pagination="pagination"> <template #empty> <div class="_fullinfo"> <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> @@ -8,19 +8,21 @@ </div> </template> <template v-slot="{items}"> - <div v-for="req in items" :key="req.id" class="user _panel"> - <MkAvatar class="avatar" :user="req.follower" :show-indicator="true"/> - <div class="body"> - <div class="name"> - <MkA v-user-preview="req.follower.id" class="name" :to="userPage(req.follower)"><MkUserName :user="req.follower"/></MkA> - <p class="acct">@{{ acct(req.follower) }}</p> - </div> - <div v-if="req.follower.description" class="description" :title="req.follower.description"> - <Mfm :text="req.follower.description" :is-note="false" :author="req.follower" :i="$i" :custom-emojis="req.follower.emojis" :plain="true" :nowrap="true"/> - </div> - <div class="actions"> - <button class="_button" @click="accept(req.follower)"><i class="fas fa-check"></i></button> - <button class="_button" @click="reject(req.follower)"><i class="fas fa-times"></i></button> + <div class="mk-follow-requests"> + <div v-for="req in items" :key="req.id" class="user _panel"> + <MkAvatar class="avatar" :user="req.follower" :show-indicator="true"/> + <div class="body"> + <div class="name"> + <MkA v-user-preview="req.follower.id" class="name" :to="userPage(req.follower)"><MkUserName :user="req.follower"/></MkA> + <p class="acct">@{{ acct(req.follower) }}</p> + </div> + <div v-if="req.follower.description" class="description" :title="req.follower.description"> + <Mfm :text="req.follower.description" :is-note="false" :author="req.follower" :i="$i" :custom-emojis="req.follower.emojis" :plain="true" :nowrap="true"/> + </div> + <div class="actions"> + <button class="_button" @click="accept(req.follower)"><i class="fas fa-check"></i></button> + <button class="_button" @click="reject(req.follower)"><i class="fas fa-times"></i></button> + </div> </div> </div> </div> @@ -29,45 +31,39 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { ref, computed } from 'vue'; import MkPagination from '@/components/ui/pagination.vue'; import { userPage, acct } from '@/filters/user'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkPagination - }, +const paginationComponent = ref<InstanceType<typeof MkPagination>>(); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.followRequests, - icon: 'fas fa-user-clock', - }, - pagination: { - endpoint: 'following/requests/list', - limit: 10, - }, - }; - }, +const pagination = { + endpoint: 'following/requests/list' as const, + limit: 10, +}; - methods: { - accept(user) { - os.api('following/requests/accept', { userId: user.id }).then(() => { - this.$refs.list.reload(); - }); - }, - reject(user) { - os.api('following/requests/reject', { userId: user.id }).then(() => { - this.$refs.list.reload(); - }); - }, - userPage, - acct - } +function accept(user) { + os.api('following/requests/accept', { userId: user.id }).then(() => { + paginationComponent.value.reload(); + }); +} + +function reject(user) { + os.api('following/requests/reject', { userId: user.id }).then(() => { + paginationComponent.value.reload(); + }); +} + +defineExpose({ + [symbols.PAGE_INFO]: computed(() => ({ + title: i18n.locale.followRequests, + icon: 'fas fa-user-clock', + bg: 'var(--bg)', + })), }); </script> diff --git a/packages/client/src/pages/gallery/edit.vue b/packages/client/src/pages/gallery/edit.vue index caca6aed4b..e3fa1a0fcd 100644 --- a/packages/client/src/pages/gallery/edit.vue +++ b/packages/client/src/pages/gallery/edit.vue @@ -1,16 +1,16 @@ <template> -<FormBase> +<div> <FormSuspense :p="init"> <FormInput v-model="title"> - <span>{{ $ts.title }}</span> + <template #label>{{ $ts.title }}</template> </FormInput> <FormTextarea v-model="description" :max="500"> - <span>{{ $ts.description }}</span> + <template #label>{{ $ts.description }}</template> </FormTextarea> <FormGroup> - <div v-for="file in files" :key="file.id" class="_debobigegoItem _debobigegoPanel wqugxsfx" :style="{ backgroundImage: file ? `url(${ file.thumbnailUrl })` : null }"> + <div v-for="file in files" :key="file.id" class="_formGroup wqugxsfx" :style="{ backgroundImage: file ? `url(${ file.thumbnailUrl })` : null }"> <div class="name">{{ file.name }}</div> <button v-tooltip="$ts.remove" class="remove _button" @click="remove(file)"><i class="fas fa-times"></i></button> </div> @@ -24,19 +24,17 @@ <FormButton v-if="postId" danger @click="del"><i class="fas fa-trash-alt"></i> {{ $ts.delete }}</FormButton> </FormSuspense> -</FormBase> +</div> </template> <script lang="ts"> import { computed, defineComponent } from 'vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormTextarea from '@/components/debobigego/textarea.vue'; -import FormSwitch from '@/components/debobigego/switch.vue'; -import FormTuple from '@/components/debobigego/tuple.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import FormButton from '@/components/ui/button.vue'; +import FormInput from '@/components/form/input.vue'; +import FormTextarea from '@/components/form/textarea.vue'; +import FormSwitch from '@/components/form/switch.vue'; +import FormGroup from '@/components/form/group.vue'; +import FormSuspense from '@/components/form/suspense.vue'; import { selectFiles } from '@/scripts/select-file'; import * as os from '@/os'; import * as symbols from '@/symbols'; @@ -47,7 +45,6 @@ export default defineComponent({ FormInput, FormTextarea, FormSwitch, - FormBase, FormGroup, FormSuspense, }, diff --git a/packages/client/src/pages/gallery/index.vue b/packages/client/src/pages/gallery/index.vue index cd0d2a40e4..a19d69d5c2 100644 --- a/packages/client/src/pages/gallery/index.vue +++ b/packages/client/src/pages/gallery/index.vue @@ -81,19 +81,19 @@ export default defineComponent({ }, tab: 'explore', recentPostsPagination: { - endpoint: 'gallery/posts', + endpoint: 'gallery/posts' as const, limit: 6, }, popularPostsPagination: { - endpoint: 'gallery/featured', + endpoint: 'gallery/featured' as const, limit: 5, }, myPostsPagination: { - endpoint: 'i/gallery/posts', + endpoint: 'i/gallery/posts' as const, limit: 5, }, likedPostsPagination: { - endpoint: 'i/gallery/likes', + endpoint: 'i/gallery/likes' as const, limit: 5, }, tags: [], @@ -106,7 +106,7 @@ export default defineComponent({ }, tagUsers(): any { return { - endpoint: 'hashtags/users', + endpoint: 'hashtags/users' as const, limit: 30, params: { tag: this.tag, diff --git a/packages/client/src/pages/gallery/post.vue b/packages/client/src/pages/gallery/post.vue index 096947e6f8..1755c23286 100644 --- a/packages/client/src/pages/gallery/post.vue +++ b/packages/client/src/pages/gallery/post.vue @@ -1,6 +1,6 @@ <template> <div class="_root"> - <transition name="fade" mode="out-in"> + <transition :name="$store.state.animation ? 'fade' : ''" mode="out-in"> <div v-if="post" class="rkxwuolj"> <div class="files"> <div v-for="file in post.files" :key="file.id" class="file"> @@ -93,11 +93,11 @@ export default defineComponent({ }] } : null), otherPostsPagination: { - endpoint: 'users/gallery/posts', + endpoint: 'users/gallery/posts' as const, limit: 6, - params: () => ({ + params: computed(() => ({ userId: this.post.user.id - }) + })), }, post: null, error: null, diff --git a/packages/client/src/pages/instance-info.vue b/packages/client/src/pages/instance-info.vue index 85096d991a..fa36db0659 100644 --- a/packages/client/src/pages/instance-info.vue +++ b/packages/client/src/pages/instance-info.vue @@ -1,70 +1,71 @@ <template> -<FormBase> - <FormGroup v-if="instance"> - <template #label>{{ instance.host }}</template> - <FormGroup> - <div class="_debobigegoItem"> - <div class="_debobigegoPanel fnfelxur"> - <img :src="instance.iconUrl || instance.faviconUrl" alt="" class="icon"/> - </div> - </div> - <FormKeyValueView> - <template #key>Name</template> - <template #value><span class="_monospace">{{ instance.name || `(${$ts.unknown})` }}</span></template> - </FormKeyValueView> - </FormGroup> - - <FormButton v-if="$i.isAdmin || $i.isModerator" primary @click="info">{{ $ts.settings }}</FormButton> +<MkSpacer :content-max="600" :margin-min="16" :margin-max="32"> + <div v-if="instance" class="_formRoot"> + <div class="fnfelxur"> + <img :src="instance.iconUrl || instance.faviconUrl" alt="" class="icon"/> + </div> + <MkKeyValue :copy="host" oneline style="margin: 1em 0;"> + <template #key>Host</template> + <template #value><span class="_monospace"><MkLink :url="`https://${host}`">{{ host }}</MkLink></span></template> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> + <template #key>Name</template> + <template #value>{{ instance.name || `(${$ts.unknown})` }}</template> + </MkKeyValue> + <MkKeyValue> + <template #key>{{ $ts.description }}</template> + <template #value>{{ instance.description }}</template> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> + <template #key>{{ $ts.software }}</template> + <template #value><span class="_monospace">{{ instance.softwareName || `(${$ts.unknown})` }} / {{ instance.softwareVersion || `(${$ts.unknown})` }}</span></template> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> + <template #key>{{ $ts.administrator }}</template> + <template #value>{{ instance.maintainerName || `(${$ts.unknown})` }} ({{ instance.maintainerEmail || `(${$ts.unknown})` }})</template> + </MkKeyValue> - <FormTextarea readonly :value="instance.description"> - <span>{{ $ts.description }}</span> - </FormTextarea> + <FormSection v-if="iAmModerator"> + <template #label>Moderation</template> + <FormSwitch v-model="suspended" class="_formBlock" @update:modelValue="toggleSuspend">{{ $ts.stopActivityDelivery }}</FormSwitch> + <FormSwitch v-model="isBlocked" class="_formBlock" @update:modelValue="toggleBlock">{{ $ts.blockThisInstance }}</FormSwitch> + </FormSection> - <FormGroup> - <FormKeyValueView> - <template #key>{{ $ts.software }}</template> - <template #value><span class="_monospace">{{ instance.softwareName || `(${$ts.unknown})` }}</span></template> - </FormKeyValueView> - <FormKeyValueView> - <template #key>{{ $ts.version }}</template> - <template #value><span class="_monospace">{{ instance.softwareVersion || `(${$ts.unknown})` }}</span></template> - </FormKeyValueView> - </FormGroup> - <FormGroup> - <FormKeyValueView> - <template #key>{{ $ts.administrator }}</template> - <template #value><span class="_monospace">{{ instance.maintainerName || `(${$ts.unknown})` }}</span></template> - </FormKeyValueView> - <FormKeyValueView> - <template #key>{{ $ts.contact }}</template> - <template #value><span class="_monospace">{{ instance.maintainerEmail || `(${$ts.unknown})` }}</span></template> - </FormKeyValueView> - </FormGroup> - <FormGroup> - <FormKeyValueView> + <FormSection> + <MkKeyValue oneline style="margin: 1em 0;"> + <template #key>{{ $ts.registeredAt }}</template> + <template #value><MkTime mode="detail" :time="instance.caughtAt"/></template> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> + <template #key>{{ $ts.updatedAt }}</template> + <template #value><MkTime mode="detail" :time="instance.infoUpdatedAt"/></template> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.latestRequestSentAt }}</template> <template #value><MkTime v-if="instance.latestRequestSentAt" :time="instance.latestRequestSentAt"/><span v-else>N/A</span></template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.latestStatus }}</template> <template #value>{{ instance.latestStatus ? instance.latestStatus : 'N/A' }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.latestRequestReceivedAt }}</template> <template #value><MkTime v-if="instance.latestRequestReceivedAt" :time="instance.latestRequestReceivedAt"/><span v-else>N/A</span></template> - </FormKeyValueView> - </FormGroup> - <FormGroup> - <FormKeyValueView> + </MkKeyValue> + </FormSection> + + <FormSection> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>Open Registrations</template> <template #value>{{ instance.openRegistrations ? $ts.yes : $ts.no }}</template> - </FormKeyValueView> - </FormGroup> - <div class="_debobigegoItem"> - <div class="_debobigegoLabel">{{ $ts.statistics }}</div> - <div class="_debobigegoPanel cmhjzshl"> + </MkKeyValue> + </FormSection> + + <FormSection> + <template #label>{{ $ts.statistics }}</template> + <div class="cmhjzshl"> <div class="selects"> - <MkSelect v-model="chartSrc" style="margin: 0; flex: 1;"> + <MkSelect v-model="chartSrc" style="margin: 0 10px 0 0; flex: 1;"> <option value="instance-requests">{{ $ts._instanceCharts.requests }}</option> <option value="instance-users">{{ $ts._instanceCharts.users }}</option> <option value="instance-users-total">{{ $ts._instanceCharts.usersTotal }}</option> @@ -83,147 +84,100 @@ </MkSelect> </div> <div class="chart"> - <MkChart :src="chartSrc" :span="chartSpan" :limit="90" :detailed="true"></MkChart> + <MkChart :src="chartSrc" :span="chartSpan" :limit="90" :args="{ host: host }" :detailed="true"></MkChart> </div> </div> - </div> - <FormGroup> - <FormKeyValueView> - <template #key>{{ $ts.registeredAt }}</template> - <template #value><MkTime mode="detail" :time="instance.caughtAt"/></template> - </FormKeyValueView> - <FormKeyValueView> - <template #key>{{ $ts.updatedAt }}</template> - <template #value><MkTime mode="detail" :time="instance.infoUpdatedAt"/></template> - </FormKeyValueView> - </FormGroup> - <FormObjectView tall :value="instance"> - <span>Raw</span> - </FormObjectView> - <FormGroup> + </FormSection> + + <MkObjectView tall :value="instance"> + </MkObjectView> + + <FormSection> <template #label>Well-known resources</template> - <FormLink :to="`https://${host}/.well-known/host-meta`" external>host-meta</FormLink> - <FormLink :to="`https://${host}/.well-known/host-meta.json`" external>host-meta.json</FormLink> - <FormLink :to="`https://${host}/.well-known/nodeinfo`" external>nodeinfo</FormLink> - <FormLink :to="`https://${host}/robots.txt`" external>robots.txt</FormLink> - <FormLink :to="`https://${host}/manifest.json`" external>manifest.json</FormLink> - </FormGroup> - <FormSuspense v-slot="{ result: dns }" :p="dnsPromiseFactory"> - <FormGroup> - <template #label>DNS</template> - <FormKeyValueView v-for="record in dns.a" :key="record"> - <template #key>A</template> - <template #value><span class="_monospace">{{ record }}</span></template> - </FormKeyValueView> - <FormKeyValueView v-for="record in dns.aaaa" :key="record"> - <template #key>AAAA</template> - <template #value><span class="_monospace">{{ record }}</span></template> - </FormKeyValueView> - <FormKeyValueView v-for="record in dns.cname" :key="record"> - <template #key>CNAME</template> - <template #value><span class="_monospace">{{ record }}</span></template> - </FormKeyValueView> - <FormKeyValueView v-for="record in dns.txt"> - <template #key>TXT</template> - <template #value><span class="_monospace">{{ record[0] }}</span></template> - </FormKeyValueView> - </FormGroup> - </FormSuspense> - </FormGroup> -</FormBase> + <FormLink :to="`https://${host}/.well-known/host-meta`" external style="margin-bottom: 8px;">host-meta</FormLink> + <FormLink :to="`https://${host}/.well-known/host-meta.json`" external style="margin-bottom: 8px;">host-meta.json</FormLink> + <FormLink :to="`https://${host}/.well-known/nodeinfo`" external style="margin-bottom: 8px;">nodeinfo</FormLink> + <FormLink :to="`https://${host}/robots.txt`" external style="margin-bottom: 8px;">robots.txt</FormLink> + <FormLink :to="`https://${host}/manifest.json`" external style="margin-bottom: 8px;">manifest.json</FormLink> + </FormSection> + </div> +</MkSpacer> </template> -<script lang="ts"> -import { defineAsyncComponent, defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; +import * as misskey from 'misskey-js'; import MkChart from '@/components/chart.vue'; -import FormObjectView from '@/components/debobigego/object-view.vue'; -import FormTextarea from '@/components/debobigego/textarea.vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormKeyValueView from '@/components/debobigego/key-value-view.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import MkObjectView from '@/components/object-view.vue'; +import FormLink from '@/components/form/link.vue'; +import MkLink from '@/components/link.vue'; +import FormSection from '@/components/form/section.vue'; +import MkKeyValue from '@/components/key-value.vue'; import MkSelect from '@/components/form/select.vue'; +import FormSwitch from '@/components/form/switch.vue'; import * as os from '@/os'; import number from '@/filters/number'; import bytes from '@/filters/bytes'; import * as symbols from '@/symbols'; -import MkInstanceInfo from '@/pages/admin/instance.vue'; +import { iAmModerator } from '@/account'; -export default defineComponent({ - components: { - FormBase, - FormTextarea, - FormObjectView, - FormButton, - FormLink, - FormGroup, - FormKeyValueView, - FormSuspense, - MkSelect, - MkChart, - }, +const props = defineProps<{ + host: string; +}>(); - props: { - host: { - type: String, - required: true - } - }, +let meta = $ref<misskey.entities.DetailedInstanceMetadata | null>(null); +let instance = $ref<misskey.entities.Instance | null>(null); +let suspended = $ref(false); +let isBlocked = $ref(false); +let chartSrc = $ref('instance-requests'); +let chartSpan = $ref('hour'); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.instanceInfo, - icon: 'fas fa-info-circle', - actions: [{ - text: `https://${this.host}`, - icon: 'fas fa-external-link-alt', - handler: () => { - window.open(`https://${this.host}`, '_blank'); - } - }], - }, - instance: null, - dnsPromiseFactory: () => os.api('federation/dns', { - host: this.host - }), - chartSrc: 'instance-requests', - chartSpan: 'hour', - } - }, +async function fetch() { + meta = await os.api('meta', { detail: true }); + instance = await os.api('federation/show-instance', { + host: props.host, + }); + suspended = instance.isSuspended; + isBlocked = meta.blockedHosts.includes(instance.host); +} - mounted() { - this.fetch(); - }, +async function toggleBlock(ev) { + if (meta == null) return; + await os.api('admin/update-meta', { + blockedHosts: isBlocked ? meta.blockedHosts.concat([instance.host]) : meta.blockedHosts.filter(x => x !== instance.host) + }); +} - methods: { - number, - bytes, +async function toggleSuspend(v) { + await os.api('admin/federation/update-instance', { + host: instance.host, + isSuspended: suspended, + }); +} - async fetch() { - this.instance = await os.api('federation/show-instance', { - host: this.host - }); - }, +fetch(); - info() { - os.popup(MkInstanceInfo, { - instance: this.instance - }, {}, 'closed'); - } - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: props.host, + icon: 'fas fa-info-circle', + bg: 'var(--bg)', + actions: [{ + text: `https://${props.host}`, + icon: 'fas fa-external-link-alt', + handler: () => { + window.open(`https://${props.host}`, '_blank'); + } + }], + }, }); </script> <style lang="scss" scoped> .fnfelxur { - padding: 16px; - > .icon { display: block; - margin: auto; + margin: 0; height: 64px; border-radius: 8px; } @@ -232,7 +186,7 @@ export default defineComponent({ .cmhjzshl { > .selects { display: flex; - padding: 16px; + margin: 0 0 16px 0; } } </style> diff --git a/packages/client/src/pages/mentions.vue b/packages/client/src/pages/mentions.vue index 691d3bd9aa..bda56fc729 100644 --- a/packages/client/src/pages/mentions.vue +++ b/packages/client/src/pages/mentions.vue @@ -4,28 +4,21 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> import XNotes from '@/components/notes.vue'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XNotes - }, +const pagination = { + endpoint: 'notes/mentions' as const, + limit: 10, +}; - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.mentions, - icon: 'fas fa-at', - bg: 'var(--bg)', - }, - pagination: { - endpoint: 'notes/mentions', - limit: 10, - }, - }; +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.mentions, + icon: 'fas fa-at', + bg: 'var(--bg)', }, }); </script> diff --git a/packages/client/src/pages/messages.vue b/packages/client/src/pages/messages.vue index 9085af9489..8efdc55586 100644 --- a/packages/client/src/pages/messages.vue +++ b/packages/client/src/pages/messages.vue @@ -4,31 +4,24 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> import XNotes from '@/components/notes.vue'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XNotes - }, +const pagination = { + endpoint: 'notes/mentions' as const, + limit: 10, + params: () => ({ + visibility: 'specified' + }), +}; - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.directNotes, - icon: 'fas fa-envelope', - bg: 'var(--bg)', - }, - pagination: { - endpoint: 'notes/mentions', - limit: 10, - params: () => ({ - visibility: 'specified' - }) - }, - }; +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.directNotes, + icon: 'fas fa-envelope', + bg: 'var(--bg)', }, }); </script> diff --git a/packages/client/src/pages/messaging/index.vue b/packages/client/src/pages/messaging/index.vue index 01f9d4518f..554ebc4b6b 100644 --- a/packages/client/src/pages/messaging/index.vue +++ b/packages/client/src/pages/messaging/index.vue @@ -44,6 +44,7 @@ import * as Acct from 'misskey-js/built/acct'; import MkButton from '@/components/ui/button.vue'; import { acct } from '@/filters/user'; import * as os from '@/os'; +import { stream } from '@/stream'; import * as symbols from '@/symbols'; export default defineComponent({ @@ -66,7 +67,7 @@ export default defineComponent({ }, mounted() { - this.connection = markRaw(os.stream.useChannel('messagingIndex')); + this.connection = markRaw(stream.useChannel('messagingIndex')); this.connection.on('message', this.onMessage); this.connection.on('read', this.onRead); diff --git a/packages/client/src/pages/messaging/messaging-room.form.vue b/packages/client/src/pages/messaging/messaging-room.form.vue index 8d92c430f1..0fc7c8a5df 100644 --- a/packages/client/src/pages/messaging/messaging-room.form.vue +++ b/packages/client/src/pages/messaging/messaging-room.form.vue @@ -7,7 +7,7 @@ ref="text" v-model="text" :placeholder="$ts.inputMessageHere" - @keypress="onKeypress" + @keydown="onKeydown" @compositionupdate="onCompositionUpdate" @paste="onPaste" ></textarea> @@ -28,6 +28,7 @@ import * as autosize from 'autosize'; import { formatTimeString } from '@/scripts/format-time-string'; import { selectFile } from '@/scripts/select-file'; import * as os from '@/os'; +import { stream } from '@/stream'; import { Autocomplete } from '@/scripts/autocomplete'; import { throttle } from 'throttle-debounce'; @@ -48,7 +49,7 @@ export default defineComponent({ file: null, sending: false, typing: throttle(3000, () => { - os.stream.send('typingOnMessaging', this.user ? { partner: this.user.id } : { group: this.group.id }); + stream.send('typingOnMessaging', this.user ? { partner: this.user.id } : { group: this.group.id }); }), }; }, @@ -140,7 +141,7 @@ export default defineComponent({ //#endregion }, - onKeypress(e) { + onKeydown(e) { this.typing(); if ((e.which == 10 || e.which == 13) && (e.ctrlKey || e.metaKey) && this.canSend) { this.send(); diff --git a/packages/client/src/pages/messaging/messaging-room.vue b/packages/client/src/pages/messaging/messaging-room.vue index ffc7f7bc0d..a715dad6de 100644 --- a/packages/client/src/pages/messaging/messaging-room.vue +++ b/packages/client/src/pages/messaging/messaging-room.vue @@ -24,7 +24,7 @@ </I18n> <MkEllipsis/> </div> - <transition name="fade"> + <transition :name="$store.state.animation ? 'fade' : ''"> <div v-show="showIndicator" class="new-message"> <button class="_buttonPrimary" @click="onIndicatorClick"><i class="fas fa-arrow-circle-down"></i>{{ $ts.newMessageExists }}</button> </div> @@ -43,6 +43,7 @@ import XForm from './messaging-room.form.vue'; import * as Acct from 'misskey-js/built/acct'; import { isBottom, onScrollBottom, scroll } from '@/scripts/scroll'; import * as os from '@/os'; +import { stream } from '@/stream'; import { popout } from '@/scripts/popout'; import * as sound from '@/scripts/sound'; import * as symbols from '@/symbols'; @@ -141,7 +142,7 @@ const Component = defineComponent({ this.group = group; } - this.connection = markRaw(os.stream.useChannel('messaging', { + this.connection = markRaw(stream.useChannel('messaging', { otherparty: this.user ? this.user.id : undefined, group: this.group ? this.group.id : undefined, })); @@ -161,7 +162,7 @@ const Component = defineComponent({ // もっと見るの交差検知を発火させないためにfetchは // スクロールが終わるまでfalseにしておく // scrollendのようなイベントはないのでsetTimeoutで - setTimeout(() => this.fetching = false, 300); + window.setTimeout(() => this.fetching = false, 300); }); }, @@ -299,9 +300,9 @@ const Component = defineComponent({ this.showIndicator = false; }); - if (this.timer) clearTimeout(this.timer); + if (this.timer) window.clearTimeout(this.timer); - this.timer = setTimeout(() => { + this.timer = window.setTimeout(() => { this.showIndicator = false; }, 4000); }, diff --git a/packages/client/src/pages/my-antennas/create.vue b/packages/client/src/pages/my-antennas/create.vue index 173807475a..427c9935c3 100644 --- a/packages/client/src/pages/my-antennas/create.vue +++ b/packages/client/src/pages/my-antennas/create.vue @@ -4,45 +4,37 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import MkButton from '@/components/ui/button.vue'; +<script lang="ts" setup> +import { } from 'vue'; import XAntenna from './editor.vue'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; +import { router } from '@/router'; -export default defineComponent({ - components: { - MkButton, - XAntenna, - }, +let draft = $ref({ + name: '', + src: 'all', + userListId: null, + userGroupId: null, + users: [], + keywords: [], + excludeKeywords: [], + withReplies: false, + caseSensitive: false, + withFile: false, + notify: false +}); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.manageAntennas, - icon: 'fas fa-satellite', - }, - draft: { - name: '', - src: 'all', - userListId: null, - userGroupId: null, - users: [], - keywords: [], - excludeKeywords: [], - withReplies: false, - caseSensitive: false, - withFile: false, - notify: false - }, - }; - }, +function onAntennaCreated() { + router.push('/my/antennas'); +} - methods: { - onAntennaCreated() { - this.$router.push('/my/antennas'); - }, - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.manageAntennas, + icon: 'fas fa-satellite', + bg: 'var(--bg)', + }, }); </script> diff --git a/packages/client/src/pages/my-antennas/index.vue b/packages/client/src/pages/my-antennas/index.vue index d185e796c3..7138d269a9 100644 --- a/packages/client/src/pages/my-antennas/index.vue +++ b/packages/client/src/pages/my-antennas/index.vue @@ -38,7 +38,7 @@ export default defineComponent({ } }, pagination: { - endpoint: 'antennas/list', + endpoint: 'antennas/list' as const, limit: 10, }, }; diff --git a/packages/client/src/pages/my-clips/index.vue b/packages/client/src/pages/my-clips/index.vue index a5bbc3fd2d..97b563f6f8 100644 --- a/packages/client/src/pages/my-clips/index.vue +++ b/packages/client/src/pages/my-clips/index.vue @@ -3,7 +3,7 @@ <div class="qtcaoidl"> <MkButton primary class="add" @click="create"><i class="fas fa-plus"></i> {{ $ts.add }}</MkButton> - <MkPagination v-slot="{items}" ref="list" :pagination="pagination" class="list"> + <MkPagination v-slot="{items}" ref="pagingComponent" :pagination="pagination" class="list"> <MkA v-for="item in items" :key="item.id" :to="`/clips/${item.id}`" class="item _panel _gap"> <b>{{ item.name }}</b> <div v-if="item.description" class="description">{{ item.description }}</div> @@ -13,71 +13,64 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MkPagination from '@/components/ui/pagination.vue'; import MkButton from '@/components/ui/button.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import i18n from '@/components/global/i18n'; -export default defineComponent({ - components: { - MkPagination, - MkButton, - }, - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.clip, - icon: 'fas fa-paperclip', - bg: 'var(--bg)', - action: { - icon: 'fas fa-plus', - handler: this.create - } - }, - pagination: { - endpoint: 'clips/list', - limit: 10, - }, - draft: null, - }; - }, +const pagination = { + endpoint: 'clips/list' as const, + limit: 10, +}; - methods: { - async create() { - const { canceled, result } = await os.form(this.$ts.createNewClip, { - name: { - type: 'string', - label: this.$ts.name - }, - description: { - type: 'string', - required: false, - multiline: true, - label: this.$ts.description - }, - isPublic: { - type: 'boolean', - label: this.$ts.public, - default: false - } - }); - if (canceled) return; +const pagingComponent = $ref<InstanceType<typeof MkPagination>>(); - os.apiWithDialog('clips/create', result); +async function create() { + const { canceled, result } = await os.form(i18n.locale.createNewClip, { + name: { + type: 'string', + label: i18n.locale.name, }, - - onClipCreated() { - this.$refs.list.reload(); - this.draft = null; + description: { + type: 'string', + required: false, + multiline: true, + label: i18n.locale.description, }, + isPublic: { + type: 'boolean', + label: i18n.locale.public, + default: false, + }, + }); + if (canceled) return; + + os.apiWithDialog('clips/create', result); + + pagingComponent.reload(); +} + +function onClipCreated() { + pagingComponent.reload(); +} - onClipDeleted() { - this.$refs.list.reload(); +function onClipDeleted() { + pagingComponent.reload(); +} + +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.clip, + icon: 'fas fa-paperclip', + bg: 'var(--bg)', + action: { + icon: 'fas fa-plus', + handler: create }, - } + }, }); </script> diff --git a/packages/client/src/pages/my-groups/group.vue b/packages/client/src/pages/my-groups/group.vue index c307f037a6..92c0483af9 100644 --- a/packages/client/src/pages/my-groups/group.vue +++ b/packages/client/src/pages/my-groups/group.vue @@ -1,6 +1,6 @@ <template> <div class="mk-group-page"> - <transition name="zoom" mode="out-in"> + <transition :name="$store.state.animation ? 'zoom' : ''" mode="out-in"> <div v-if="group" class="_section"> <div class="_content" style="display: flex; gap: var(--margin); flex-wrap: wrap;"> <MkButton inline @click="invite()">{{ $ts.invite }}</MkButton> @@ -11,7 +11,7 @@ </div> </transition> - <transition name="zoom" mode="out-in"> + <transition :name="$store.state.animation ? 'zoom' : ''" mode="out-in"> <div v-if="group" class="_section members _gap"> <div class="_title">{{ $ts.members }}</div> <div class="_content"> diff --git a/packages/client/src/pages/my-groups/index.vue b/packages/client/src/pages/my-groups/index.vue index db5ccde466..4b2b2963a8 100644 --- a/packages/client/src/pages/my-groups/index.vue +++ b/packages/client/src/pages/my-groups/index.vue @@ -87,15 +87,15 @@ export default defineComponent({ })), tab: 'owned', ownedPagination: { - endpoint: 'users/groups/owned', + endpoint: 'users/groups/owned' as const, limit: 10, }, joinedPagination: { - endpoint: 'users/groups/joined', + endpoint: 'users/groups/joined' as const, limit: 10, }, invitationPagination: { - endpoint: 'i/user-group-invites', + endpoint: 'i/user-group-invites' as const, limit: 10, }, }; diff --git a/packages/client/src/pages/my-lists/index.vue b/packages/client/src/pages/my-lists/index.vue index 94a869b9ff..e6fcba1b34 100644 --- a/packages/client/src/pages/my-lists/index.vue +++ b/packages/client/src/pages/my-lists/index.vue @@ -3,7 +3,7 @@ <div class="qkcjvfiv"> <MkButton primary class="add" @click="create"><i class="fas fa-plus"></i> {{ $ts.createList }}</MkButton> - <MkPagination v-slot="{items}" ref="list" :pagination="pagination" class="lists _content"> + <MkPagination v-slot="{items}" ref="pagingComponent" :pagination="pagination" class="lists _content"> <MkA v-for="list in items" :key="list.id" class="list _panel" :to="`/my/lists/${ list.id }`"> <div class="name">{{ list.name }}</div> <MkAvatars :user-ids="list.userIds"/> @@ -13,50 +13,41 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MkPagination from '@/components/ui/pagination.vue'; import MkButton from '@/components/ui/button.vue'; import MkAvatars from '@/components/avatars.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkPagination, - MkButton, - MkAvatars, - }, +const pagingComponent = $ref<InstanceType<typeof MkPagination>>(); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.manageLists, - icon: 'fas fa-list-ul', - bg: 'var(--bg)', - action: { - icon: 'fas fa-plus', - handler: this.create - }, - }, - pagination: { - endpoint: 'users/lists/list', - limit: 10, - }, - }; - }, +const pagination = { + endpoint: 'users/lists/list' as const, + limit: 10, +}; + +async function create() { + const { canceled, result: name } = await os.inputText({ + title: i18n.locale.enterListName, + }); + if (canceled) return; + await os.apiWithDialog('users/lists/create', { name: name }); + pagingComponent.reload(); +} - methods: { - async create() { - const { canceled, result: name } = await os.inputText({ - title: this.$ts.enterListName, - }); - if (canceled) return; - await os.api('users/lists/create', { name: name }); - this.$refs.list.reload(); - os.success(); +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.manageLists, + icon: 'fas fa-list-ul', + bg: 'var(--bg)', + action: { + icon: 'fas fa-plus', + handler: create, }, - } + }, }); </script> diff --git a/packages/client/src/pages/my-lists/list.vue b/packages/client/src/pages/my-lists/list.vue index a25522f933..bc24f58431 100644 --- a/packages/client/src/pages/my-lists/list.vue +++ b/packages/client/src/pages/my-lists/list.vue @@ -1,7 +1,7 @@ <template> <MkSpacer :content-max="700"> <div class="mk-list-page"> - <transition name="zoom" mode="out-in"> + <transition :name="$store.state.animation ? 'zoom' : ''" mode="out-in"> <div v-if="list" class="_section"> <div class="_content"> <MkButton inline @click="addUser()">{{ $ts.addUser }}</MkButton> @@ -11,7 +11,7 @@ </div> </transition> - <transition name="zoom" mode="out-in"> + <transition :name="$store.state.animation ? 'zoom' : ''" mode="out-in"> <div v-if="list" class="_section members _gap"> <div class="_title">{{ $ts.members }}</div> <div class="_content"> diff --git a/packages/client/src/pages/not-found.vue b/packages/client/src/pages/not-found.vue index 92d3f399f7..914fdb9297 100644 --- a/packages/client/src/pages/not-found.vue +++ b/packages/client/src/pages/not-found.vue @@ -7,19 +7,15 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as os from '@/os'; +<script lang="ts" setup> import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.notFound, - icon: 'fas fa-exclamation-triangle' - }, - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.notFound, + icon: 'fas fa-exclamation-triangle', + bg: 'var(--bg)', }, }); </script> diff --git a/packages/client/src/pages/note.vue b/packages/client/src/pages/note.vue index d40082381c..efeea345dc 100644 --- a/packages/client/src/pages/note.vue +++ b/packages/client/src/pages/note.vue @@ -1,7 +1,7 @@ <template> <MkSpacer :content-max="800"> <div class="fcuexfpr"> - <transition name="fade" mode="out-in"> + <transition :name="$store.state.animation ? 'fade' : ''" mode="out-in"> <div v-if="note" class="note"> <div v-if="showNext" class="_gap"> <XNotes class="_content" :pagination="next" :no-gap="true"/> @@ -82,21 +82,21 @@ export default defineComponent({ showNext: false, error: null, prev: { - endpoint: 'users/notes', + endpoint: 'users/notes' as const, limit: 10, - params: init => ({ + params: computed(() => ({ userId: this.note.userId, untilId: this.note.id, - }) + })), }, next: { reversed: true, - endpoint: 'users/notes', + endpoint: 'users/notes' as const, limit: 10, - params: init => ({ + params: computed(() => ({ userId: this.note.userId, sinceId: this.note.id, - }) + })), }, }; }, diff --git a/packages/client/src/pages/notifications.vue b/packages/client/src/pages/notifications.vue index 695c54a535..090e80f99a 100644 --- a/packages/client/src/pages/notifications.vue +++ b/packages/client/src/pages/notifications.vue @@ -6,70 +6,62 @@ </MkSpacer> </template> -<script lang="ts"> -import { computed, defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; import XNotifications from '@/components/notifications.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { notificationTypes } from 'misskey-js'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XNotifications - }, +let tab = $ref('all'); +let includeTypes = $ref<string[] | null>(null); - data() { - return { - [symbols.PAGE_INFO]: computed(() => ({ - title: this.$ts.notifications, - icon: 'fas fa-bell', - bg: 'var(--bg)', - actions: [{ - text: this.$ts.filter, - icon: 'fas fa-filter', - highlighted: this.includeTypes != null, - handler: this.setFilter, - }, { - text: this.$ts.markAllAsRead, - icon: 'fas fa-check', - handler: () => { - os.apiWithDialog('notifications/mark-all-as-read'); - }, - }], - tabs: [{ - active: this.tab === 'all', - title: this.$ts.all, - onClick: () => { this.tab = 'all'; }, - }, { - active: this.tab === 'unread', - title: this.$ts.unread, - onClick: () => { this.tab = 'unread'; }, - },] - })), - tab: 'all', - includeTypes: null, - }; - }, - - methods: { - setFilter(ev) { - const typeItems = notificationTypes.map(t => ({ - text: this.$t(`_notification._types.${t}`), - active: this.includeTypes && this.includeTypes.includes(t), - action: () => { - this.includeTypes = [t]; - } - })); - const items = this.includeTypes != null ? [{ - icon: 'fas fa-times', - text: this.$ts.clear, - action: () => { - this.includeTypes = null; - } - }, null, ...typeItems] : typeItems; - os.popupMenu(items, ev.currentTarget || ev.target); +function setFilter(ev) { + const typeItems = notificationTypes.map(t => ({ + text: i18n.t(`_notification._types.${t}`), + active: includeTypes && includeTypes.includes(t), + action: () => { + includeTypes = [t]; + } + })); + const items = includeTypes != null ? [{ + icon: 'fas fa-times', + text: i18n.locale.clear, + action: () => { + includeTypes = null; } - } + }, null, ...typeItems] : typeItems; + os.popupMenu(items, ev.currentTarget || ev.target); +} + +defineExpose({ + [symbols.PAGE_INFO]: computed(() => ({ + title: i18n.locale.notifications, + icon: 'fas fa-bell', + bg: 'var(--bg)', + actions: [{ + text: i18n.locale.filter, + icon: 'fas fa-filter', + highlighted: includeTypes != null, + handler: setFilter, + }, { + text: i18n.locale.markAllAsRead, + icon: 'fas fa-check', + handler: () => { + os.apiWithDialog('notifications/mark-all-as-read'); + }, + }], + tabs: [{ + active: tab === 'all', + title: i18n.locale.all, + onClick: () => { tab = 'all'; }, + }, { + active: tab === 'unread', + title: i18n.locale.unread, + onClick: () => { tab = 'unread'; }, + },] + })), }); </script> diff --git a/packages/client/src/pages/page.vue b/packages/client/src/pages/page.vue index 3a4803c3a3..b2c039a269 100644 --- a/packages/client/src/pages/page.vue +++ b/packages/client/src/pages/page.vue @@ -1,6 +1,6 @@ <template> <MkSpacer :content-max="700"> - <transition name="fade" mode="out-in"> + <transition :name="$store.state.animation ? 'fade' : ''" mode="out-in"> <div v-if="page" :key="page.id" v-size="{ max: [450] }" class="xcukqgmh"> <div class="_block main"> <!-- @@ -106,11 +106,11 @@ export default defineComponent({ page: null, error: null, otherPostsPagination: { - endpoint: 'users/pages', + endpoint: 'users/pages' as const, limit: 6, - params: () => ({ + params: computed(() => ({ userId: this.page.user.id - }) + })), }, }; }, diff --git a/packages/client/src/pages/pages.vue b/packages/client/src/pages/pages.vue index f1dd64f119..dcccf7f7c4 100644 --- a/packages/client/src/pages/pages.vue +++ b/packages/client/src/pages/pages.vue @@ -62,15 +62,15 @@ export default defineComponent({ })), tab: 'featured', featuredPagesPagination: { - endpoint: 'pages/featured', + endpoint: 'pages/featured' as const, noPaging: true, }, myPagesPagination: { - endpoint: 'i/pages', + endpoint: 'i/pages' as const, limit: 5, }, likedPagesPagination: { - endpoint: 'i/page-likes', + endpoint: 'i/page-likes' as const, limit: 5, }, }; diff --git a/packages/client/src/pages/preview.vue b/packages/client/src/pages/preview.vue index 9d1ebb74ed..8eb4549516 100644 --- a/packages/client/src/pages/preview.vue +++ b/packages/client/src/pages/preview.vue @@ -4,24 +4,18 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; import MkSample from '@/components/sample.vue'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkSample, - }, - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.preview, - icon: 'fas fa-eye', - }, - } - }, +defineExpose({ + [symbols.PAGE_INFO]: computed(() => ({ + title: i18n.locale.preview, + icon: 'fas fa-eye', + bg: 'var(--bg)', + })), }); </script> diff --git a/packages/client/src/pages/reset-password.vue b/packages/client/src/pages/reset-password.vue index f9a2500840..8ef73858f6 100644 --- a/packages/client/src/pages/reset-password.vue +++ b/packages/client/src/pages/reset-password.vue @@ -1,67 +1,53 @@ <template> -<FormBase v-if="token"> - <FormInput v-model="password" type="password"> - <template #prefix><i class="fas fa-lock"></i></template> - <span>{{ $ts.newPassword }}</span> - </FormInput> - - <FormButton primary @click="save">{{ $ts.save }}</FormButton> -</FormBase> +<MkSpacer v-if="token" :content-max="700" :margin-min="16" :margin-max="32"> + <div class="_formRoot"> + <FormInput v-model="password" type="password" class="_formBlock"> + <template #prefix><i class="fas fa-lock"></i></template> + <template #label>{{ i18n.locale.newPassword }}</template> + </FormInput> + + <FormButton primary class="_formBlock" @click="save">{{ i18n.locale.save }}</FormButton> + </div> +</MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormButton from '@/components/debobigego/button.vue'; +<script lang="ts" setup> +import { onMounted } from 'vue'; +import FormInput from '@/components/form/input.vue'; +import FormButton from '@/components/ui/button.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; +import { router } from '@/router'; -export default defineComponent({ - components: { - FormBase, - FormGroup, - FormLink, - FormInput, - FormButton, - }, - - props: { - token: { - type: String, - required: false - } - }, +const props = defineProps<{ + token?: string; +}>(); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.resetPassword, - icon: 'fas fa-lock' - }, - password: '', - } - }, +let password = $ref(''); - mounted() { - if (this.token == null) { - os.popup(import('@/components/forgot-password.vue'), {}, {}, 'closed'); - this.$router.push('/'); - } - }, +async function save() { + await os.apiWithDialog('reset-password', { + token: props.token, + password: password, + }); + router.push('/'); +} - methods: { - async save() { - await os.apiWithDialog('reset-password', { - token: this.token, - password: this.password, - }); - this.$router.push('/'); - } +onMounted(() => { + if (props.token == null) { + os.popup(import('@/components/forgot-password.vue'), {}, {}, 'closed'); + router.push('/'); } }); + +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.resetPassword, + icon: 'fas fa-lock', + bg: 'var(--bg)', + }, +}); </script> <style lang="scss" scoped> diff --git a/packages/client/src/pages/reversi/game.board.vue b/packages/client/src/pages/reversi/game.board.vue deleted file mode 100644 index eb6fef2799..0000000000 --- a/packages/client/src/pages/reversi/game.board.vue +++ /dev/null @@ -1,528 +0,0 @@ -<template> -<div class="xqnhankfuuilcwvhgsopeqncafzsquya"> - <header><b><MkA :to="userPage(blackUser)"><MkUserName :user="blackUser"/></MkA></b>({{ $ts._reversi.black }}) vs <b><MkA :to="userPage(whiteUser)"><MkUserName :user="whiteUser"/></MkA></b>({{ $ts._reversi.white }})</header> - - <div style="overflow: hidden; line-height: 28px;"> - <p v-if="!iAmPlayer && !game.isEnded" class="turn"> - <Mfm :key="'turn:' + turnUser().name" :text="$t('_reversi.turnOf', { name: turnUser().name })" :plain="true" :custom-emojis="turnUser().emojis"/> - <MkEllipsis/> - </p> - <p v-if="logPos != logs.length" class="turn"> - <Mfm :key="'past-turn-of:' + turnUser().name" :text="$t('_reversi.pastTurnOf', { name: turnUser().name })" :plain="true" :custom-emojis="turnUser().emojis"/> - </p> - <p v-if="iAmPlayer && !game.isEnded && !isMyTurn()" class="turn1">{{ $ts._reversi.opponentTurn }}<MkEllipsis/></p> - <p v-if="iAmPlayer && !game.isEnded && isMyTurn()" class="turn2" style="animation: tada 1s linear infinite both;">{{ $ts._reversi.myTurn }}</p> - <p v-if="game.isEnded && logPos == logs.length" class="result"> - <template v-if="game.winner"> - <Mfm :key="'won'" :text="$t('_reversi.won', { name: game.winner.name })" :plain="true" :custom-emojis="game.winner.emojis"/> - <span v-if="game.surrendered != null"> ({{ $ts._reversi.surrendered }})</span> - </template> - <template v-else>{{ $ts._reversi.drawn }}</template> - </p> - </div> - - <div class="board"> - <div v-if="$store.state.gamesReversiShowBoardLabels" class="labels-x"> - <span v-for="i in game.map[0].length">{{ String.fromCharCode(64 + i) }}</span> - </div> - <div class="flex"> - <div v-if="$store.state.gamesReversiShowBoardLabels" class="labels-y"> - <div v-for="i in game.map.length">{{ i }}</div> - </div> - <div class="cells" :style="cellsStyle"> - <div v-for="(stone, i) in o.board" - :class="{ empty: stone == null, none: o.map[i] == 'null', isEnded: game.isEnded, myTurn: !game.isEnded && isMyTurn(), can: turnUser() ? o.canPut(turnUser().id == blackUser.id, i) : null, prev: o.prevPos == i }" - :title="`${String.fromCharCode(65 + o.transformPosToXy(i)[0])}${o.transformPosToXy(i)[1] + 1}`" - @click="set(i)" - > - <template v-if="$store.state.gamesReversiUseAvatarStones || true"> - <img v-if="stone === true" :src="blackUser.avatarUrl" alt="black"> - <img v-if="stone === false" :src="whiteUser.avatarUrl" alt="white"> - </template> - <template v-else> - <i v-if="stone === true" class="fas fa-circle"></i> - <i v-if="stone === false" class="far fa-circle"></i> - </template> - </div> - </div> - <div v-if="$store.state.gamesReversiShowBoardLabels" class="labels-y"> - <div v-for="i in game.map.length">{{ i }}</div> - </div> - </div> - <div v-if="$store.state.gamesReversiShowBoardLabels" class="labels-x"> - <span v-for="i in game.map[0].length">{{ String.fromCharCode(64 + i) }}</span> - </div> - </div> - - <p class="status"><b>{{ $t('_reversi.turnCount', { count: logPos }) }}</b> {{ $ts._reversi.black }}:{{ o.blackCount }} {{ $ts._reversi.white }}:{{ o.whiteCount }} {{ $ts._reversi.total }}:{{ o.blackCount + o.whiteCount }}</p> - - <div v-if="!game.isEnded && iAmPlayer" class="actions"> - <MkButton inline @click="surrender">{{ $ts._reversi.surrender }}</MkButton> - </div> - - <div v-if="game.isEnded" class="player"> - <span>{{ logPos }} / {{ logs.length }}</span> - <div v-if="!autoplaying" class="buttons"> - <MkButton inline :disabled="logPos == 0" @click="logPos = 0"><i class="fas fa-angle-double-left"></i></MkButton> - <MkButton inline :disabled="logPos == 0" @click="logPos--"><i class="fas fa-angle-left"></i></MkButton> - <MkButton inline :disabled="logPos == logs.length" @click="logPos++"><i class="fas fa-angle-right"></i></MkButton> - <MkButton inline :disabled="logPos == logs.length" @click="logPos = logs.length"><i class="fas fa-angle-double-right"></i></MkButton> - </div> - <MkButton :disabled="autoplaying" style="margin: var(--margin) auto 0 auto;" @click="autoplay()"><i class="fas fa-play"></i></MkButton> - </div> - - <div class="info"> - <p v-if="game.isLlotheo">{{ $ts._reversi.isLlotheo }}</p> - <p v-if="game.loopedBoard">{{ $ts._reversi.loopedMap }}</p> - <p v-if="game.canPutEverywhere">{{ $ts._reversi.canPutEverywhere }}</p> - </div> - - <div class="watchers"> - <MkAvatar v-for="user in watchers" :key="user.id" :user="user" class="avatar"/> - </div> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as CRC32 from 'crc-32'; -import Reversi, { Color } from '@/scripts/games/reversi/core'; -import { url } from '@/config'; -import MkButton from '@/components/ui/button.vue'; -import { userPage } from '@/filters/user'; -import * as os from '@/os'; -import * as sound from '@/scripts/sound'; - -export default defineComponent({ - components: { - MkButton - }, - - props: { - initGame: { - type: Object, - require: true - }, - connection: { - type: Object, - require: true - }, - }, - - data() { - return { - game: JSON.parse(JSON.stringify(this.initGame)), - o: null as Reversi, - logs: [], - logPos: 0, - watchers: [], - pollingClock: null, - }; - }, - - computed: { - iAmPlayer(): boolean { - if (!this.$i) return false; - return this.game.user1Id == this.$i.id || this.game.user2Id == this.$i.id; - }, - - myColor(): Color { - if (!this.iAmPlayer) return null; - if (this.game.user1Id == this.$i.id && this.game.black == 1) return true; - if (this.game.user2Id == this.$i.id && this.game.black == 2) return true; - return false; - }, - - opColor(): Color { - if (!this.iAmPlayer) return null; - return this.myColor === true ? false : true; - }, - - blackUser(): any { - return this.game.black == 1 ? this.game.user1 : this.game.user2; - }, - - whiteUser(): any { - return this.game.black == 1 ? this.game.user2 : this.game.user1; - }, - - cellsStyle(): any { - return { - 'grid-template-rows': `repeat(${this.game.map.length}, 1fr)`, - 'grid-template-columns': `repeat(${this.game.map[0].length}, 1fr)` - }; - } - }, - - watch: { - logPos(v) { - if (!this.game.isEnded) return; - const o = new Reversi(this.game.map, { - isLlotheo: this.game.isLlotheo, - canPutEverywhere: this.game.canPutEverywhere, - loopedBoard: this.game.loopedBoard - }); - for (const log of this.logs.slice(0, v)) { - o.put(log.color, log.pos); - } - this.o = o; - //this.$forceUpdate(); - } - }, - - created() { - this.o = new Reversi(this.game.map, { - isLlotheo: this.game.isLlotheo, - canPutEverywhere: this.game.canPutEverywhere, - loopedBoard: this.game.loopedBoard - }); - - for (const log of this.game.logs) { - this.o.put(log.color, log.pos); - } - - this.logs = this.game.logs; - this.logPos = this.logs.length; - - // 通信を取りこぼしてもいいように定期的にポーリングさせる - if (this.game.isStarted && !this.game.isEnded) { - this.pollingClock = setInterval(() => { - if (this.game.isEnded) return; - const crc32 = CRC32.str(this.logs.map(x => x.pos.toString()).join('')); - this.connection.send('check', { - crc32: crc32 - }); - }, 3000); - } - }, - - mounted() { - this.connection.on('set', this.onSet); - this.connection.on('rescue', this.onRescue); - this.connection.on('ended', this.onEnded); - this.connection.on('watchers', this.onWatchers); - }, - - beforeUnmount() { - this.connection.off('set', this.onSet); - this.connection.off('rescue', this.onRescue); - this.connection.off('ended', this.onEnded); - this.connection.off('watchers', this.onWatchers); - - clearInterval(this.pollingClock); - }, - - methods: { - userPage, - - // this.o がリアクティブになった折にはcomputedにできる - turnUser(): any { - if (this.o.turn === true) { - return this.game.black == 1 ? this.game.user1 : this.game.user2; - } else if (this.o.turn === false) { - return this.game.black == 1 ? this.game.user2 : this.game.user1; - } else { - return null; - } - }, - - // this.o がリアクティブになった折にはcomputedにできる - isMyTurn(): boolean { - if (!this.iAmPlayer) return false; - if (this.turnUser() == null) return false; - return this.turnUser().id == this.$i.id; - }, - - set(pos) { - if (this.game.isEnded) return; - if (!this.iAmPlayer) return; - if (!this.isMyTurn()) return; - if (!this.o.canPut(this.myColor, pos)) return; - - this.o.put(this.myColor, pos); - - // サウンドを再生する - sound.play(this.myColor ? 'reversiPutBlack' : 'reversiPutWhite'); - - this.connection.send('set', { - pos: pos - }); - - this.checkEnd(); - - this.$forceUpdate(); - }, - - onSet(x) { - this.logs.push(x); - this.logPos++; - this.o.put(x.color, x.pos); - this.checkEnd(); - this.$forceUpdate(); - - // サウンドを再生する - if (x.color !== this.myColor) { - sound.play(x.color ? 'reversiPutBlack' : 'reversiPutWhite'); - } - }, - - onEnded(x) { - this.game = JSON.parse(JSON.stringify(x.game)); - }, - - checkEnd() { - this.game.isEnded = this.o.isEnded; - if (this.game.isEnded) { - if (this.o.winner === true) { - this.game.winnerId = this.game.black == 1 ? this.game.user1Id : this.game.user2Id; - this.game.winner = this.game.black == 1 ? this.game.user1 : this.game.user2; - } else if (this.o.winner === false) { - this.game.winnerId = this.game.black == 1 ? this.game.user2Id : this.game.user1Id; - this.game.winner = this.game.black == 1 ? this.game.user2 : this.game.user1; - } else { - this.game.winnerId = null; - this.game.winner = null; - } - } - }, - - // 正しいゲーム情報が送られてきたとき - onRescue(game) { - this.game = JSON.parse(JSON.stringify(game)); - - this.o = new Reversi(this.game.map, { - isLlotheo: this.game.isLlotheo, - canPutEverywhere: this.game.canPutEverywhere, - loopedBoard: this.game.loopedBoard - }); - - for (const log of this.game.logs) { - this.o.put(log.color, log.pos, true); - } - - this.logs = this.game.logs; - this.logPos = this.logs.length; - - this.checkEnd(); - this.$forceUpdate(); - }, - - onWatchers(users) { - this.watchers = users; - }, - - surrender() { - os.api('games/reversi/games/surrender', { - gameId: this.game.id - }); - }, - - autoplay() { - this.autoplaying = true; - this.logPos = 0; - - setTimeout(() => { - this.logPos = 1; - - let i = 1; - let previousLog = this.game.logs[0]; - const tick = () => { - const log = this.game.logs[i]; - const time = new Date(log.at).getTime() - new Date(previousLog.at).getTime() - setTimeout(() => { - i++; - this.logPos++; - previousLog = log; - - if (i < this.game.logs.length) { - tick(); - } else { - this.autoplaying = false; - } - }, time); - }; - - tick(); - }, 1000); - } - } -}); -</script> - -<style lang="scss" scoped> - -@use "sass:math"; - -.xqnhankfuuilcwvhgsopeqncafzsquya { - text-align: center; - - > .go-index { - position: absolute; - top: 0; - left: 0; - z-index: 1; - width: 42px; - height :42px; - } - - > header { - padding: 8px; - border-bottom: dashed 1px var(--divider); - } - - > .board { - width: calc(100% - 16px); - max-width: 500px; - margin: 0 auto; - - $label-size: 16px; - $gap: 4px; - - > .labels-x { - height: $label-size; - padding: 0 $label-size; - display: flex; - - > * { - flex: 1; - display: flex; - align-items: center; - justify-content: center; - font-size: 0.8em; - - &:first-child { - margin-left: -(math.div($gap, 2)); - } - - &:last-child { - margin-right: -(math.div($gap, 2)); - } - } - } - - > .flex { - display: flex; - - > .labels-y { - width: $label-size; - display: flex; - flex-direction: column; - - > * { - flex: 1; - display: flex; - align-items: center; - justify-content: center; - font-size: 12px; - - &:first-child { - margin-top: -(math.div($gap, 2)); - } - - &:last-child { - margin-bottom: -(math.div($gap, 2)); - } - } - } - - > .cells { - flex: 1; - display: grid; - grid-gap: $gap; - - > div { - background: transparent; - border-radius: 6px; - overflow: hidden; - - * { - pointer-events: none; - user-select: none; - } - - &.empty { - border: solid 2px var(--divider); - } - - &.empty.can { - border-color: var(--accent); - } - - &.empty.myTurn { - border-color: var(--divider); - - &.can { - border-color: var(--accent); - cursor: pointer; - - &:hover { - background: var(--accent); - } - } - } - - &.prev { - box-shadow: 0 0 0 4px var(--accent); - } - - &.isEnded { - border-color: var(--divider); - } - - &.none { - border-color: transparent !important; - } - - > svg, > img { - display: block; - width: 100%; - height: 100%; - } - } - } - } - } - - > .status { - margin: 0; - padding: 16px 0; - } - - > .actions { - padding-bottom: 16px; - } - - > .player { - padding: 0 16px 32px 16px; - margin: 0 auto; - max-width: 500px; - - > span { - display: inline-block; - margin: 0 8px; - min-width: 70px; - } - - > .buttons { - display: flex; - - > * { - flex: 1; - } - } - } - - > .watchers { - padding: 0 0 16px 0; - - &:empty { - display: none; - } - - > .avatar { - width: 32px; - height: 32px; - } - } -} -</style> diff --git a/packages/client/src/pages/reversi/game.setting.vue b/packages/client/src/pages/reversi/game.setting.vue deleted file mode 100644 index 28bc598cfd..0000000000 --- a/packages/client/src/pages/reversi/game.setting.vue +++ /dev/null @@ -1,390 +0,0 @@ -<template> -<div class="urbixznjwwuukfsckrwzwsqzsxornqij"> - <header><b><MkUserName :user="game.user1"/></b> vs <b><MkUserName :user="game.user2"/></b></header> - - <div> - <p>{{ $ts._reversi.gameSettings }}</p> - - <div class="card map _panel"> - <header> - <select v-model="mapName" :placeholder="$ts._reversi.chooseBoard" @change="onMapChange"> - <option v-if="mapName == '-Custom-'" label="-Custom-" :value="mapName"/> - <option :label="$ts.random" :value="null"/> - <optgroup v-for="c in mapCategories" :key="c" :label="c"> - <option v-for="m in Object.values(maps).filter(m => m.category == c)" :key="m.name" :label="m.name" :value="m.name">{{ m.name }}</option> - </optgroup> - </select> - </header> - - <div> - <div v-if="game.map == null" class="random"><i class="fas fa-dice"></i></div> - <div v-else class="board" :style="{ 'grid-template-rows': `repeat(${ game.map.length }, 1fr)`, 'grid-template-columns': `repeat(${ game.map[0].length }, 1fr)` }"> - <div v-for="(x, i) in game.map.join('')" :class="{ none: x == ' ' }" @click="onPixelClick(i, x)"> - <i v-if="x === 'b'" class="fas fa-circle"></i> - <i v-if="x === 'w'" class="far fa-circle"></i> - </div> - </div> - </div> - </div> - - <div class="card _panel"> - <header> - <span>{{ $ts._reversi.blackOrWhite }}</span> - </header> - - <div> - <MkRadio v-model="game.bw" value="random" @update:modelValue="updateSettings('bw')">{{ $ts.random }}</MkRadio> - <MkRadio v-model="game.bw" :value="'1'" @update:modelValue="updateSettings('bw')"> - <I18n :src="$ts._reversi.blackIs" tag="span"> - <template #name> - <b><MkUserName :user="game.user1"/></b> - </template> - </I18n> - </MkRadio> - <MkRadio v-model="game.bw" :value="'2'" @update:modelValue="updateSettings('bw')"> - <I18n :src="$ts._reversi.blackIs" tag="span"> - <template #name> - <b><MkUserName :user="game.user2"/></b> - </template> - </I18n> - </MkRadio> - </div> - </div> - - <div class="card _panel"> - <header> - <span>{{ $ts._reversi.rules }}</span> - </header> - - <div> - <MkSwitch v-model="game.isLlotheo" @update:modelValue="updateSettings('isLlotheo')">{{ $ts._reversi.isLlotheo }}</MkSwitch> - <MkSwitch v-model="game.loopedBoard" @update:modelValue="updateSettings('loopedBoard')">{{ $ts._reversi.loopedMap }}</MkSwitch> - <MkSwitch v-model="game.canPutEverywhere" @update:modelValue="updateSettings('canPutEverywhere')">{{ $ts._reversi.canPutEverywhere }}</MkSwitch> - </div> - </div> - - <div v-if="form" class="card form _panel"> - <header> - <span>{{ $ts._reversi.botSettings }}</span> - </header> - - <div> - <template v-for="item in form"> - <MkSwitch v-if="item.type == 'switch'" :key="item.id" v-model="item.value" @change="onChangeForm(item)">{{ item.label || item.desc || '' }}</MkSwitch> - - <div v-if="item.type == 'radio'" :key="item.id" class="card"> - <header> - <span>{{ item.label }}</span> - </header> - - <div> - <MkRadio v-for="(r, i) in item.items" :key="item.id + ':' + i" v-model="item.value" :value="r.value" @update:modelValue="onChangeForm(item)">{{ r.label }}</MkRadio> - </div> - </div> - - <div v-if="item.type == 'slider'" :key="item.id" class="card"> - <header> - <span>{{ item.label }}</span> - </header> - - <div> - <input v-model="item.value" type="range" :min="item.min" :max="item.max" :step="item.step || 1" @change="onChangeForm(item)"/> - </div> - </div> - - <div v-if="item.type == 'textbox'" :key="item.id" class="card"> - <header> - <span>{{ item.label }}</span> - </header> - - <div> - <input v-model="item.value" @change="onChangeForm(item)"/> - </div> - </div> - </template> - </div> - </div> - </div> - - <footer class="_acrylic"> - <p class="status"> - <template v-if="isAccepted && isOpAccepted">{{ $ts._reversi.thisGameIsStartedSoon }}<MkEllipsis/></template> - <template v-if="isAccepted && !isOpAccepted">{{ $ts._reversi.waitingForOther }}<MkEllipsis/></template> - <template v-if="!isAccepted && isOpAccepted">{{ $ts._reversi.waitingForMe }}</template> - <template v-if="!isAccepted && !isOpAccepted">{{ $ts._reversi.waitingBoth }}<MkEllipsis/></template> - </p> - - <div class="actions"> - <MkButton inline @click="exit">{{ $ts.cancel }}</MkButton> - <MkButton v-if="!isAccepted" inline primary @click="accept">{{ $ts._reversi.ready }}</MkButton> - <MkButton v-if="isAccepted" inline primary @click="cancel">{{ $ts._reversi.cancelReady }}</MkButton> - </div> - </footer> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as maps from '@/scripts/games/reversi/maps'; -import MkButton from '@/components/ui/button.vue'; -import MkSwitch from '@/components/form/switch.vue'; -import MkRadio from '@/components/form/radio.vue'; - -export default defineComponent({ - components: { - MkButton, - MkSwitch, - MkRadio, - }, - - props: { - initGame: { - type: Object, - require: true - }, - connection: { - type: Object, - require: true - }, - }, - - data() { - return { - game: this.initGame, - o: null, - isLlotheo: false, - mapName: maps.eighteight.name, - maps: maps, - form: null, - messages: [], - }; - }, - - computed: { - mapCategories(): string[] { - const categories = Object.values(maps).map(x => x.category); - return categories.filter((item, pos) => categories.indexOf(item) == pos); - }, - isAccepted(): boolean { - if (this.game.user1Id == this.$i.id && this.game.user1Accepted) return true; - if (this.game.user2Id == this.$i.id && this.game.user2Accepted) return true; - return false; - }, - isOpAccepted(): boolean { - if (this.game.user1Id != this.$i.id && this.game.user1Accepted) return true; - if (this.game.user2Id != this.$i.id && this.game.user2Accepted) return true; - return false; - } - }, - - created() { - this.connection.on('changeAccepts', this.onChangeAccepts); - this.connection.on('updateSettings', this.onUpdateSettings); - this.connection.on('initForm', this.onInitForm); - this.connection.on('message', this.onMessage); - - if (this.game.user1Id != this.$i.id && this.game.form1) this.form = this.game.form1; - if (this.game.user2Id != this.$i.id && this.game.form2) this.form = this.game.form2; - }, - - beforeUnmount() { - this.connection.off('changeAccepts', this.onChangeAccepts); - this.connection.off('updateSettings', this.onUpdateSettings); - this.connection.off('initForm', this.onInitForm); - this.connection.off('message', this.onMessage); - }, - - methods: { - exit() { - - }, - - accept() { - this.connection.send('accept', {}); - }, - - cancel() { - this.connection.send('cancelAccept', {}); - }, - - onChangeAccepts(accepts) { - this.game.user1Accepted = accepts.user1; - this.game.user2Accepted = accepts.user2; - }, - - updateSettings(key: string) { - this.connection.send('updateSettings', { - key: key, - value: this.game[key] - }); - }, - - onUpdateSettings({ key, value }) { - this.game[key] = value; - if (this.game.map == null) { - this.mapName = null; - } else { - const found = Object.values(maps).find(x => x.data.join('') == this.game.map.join('')); - this.mapName = found ? found.name : '-Custom-'; - } - }, - - onInitForm(x) { - if (x.userId == this.$i.id) return; - this.form = x.form; - }, - - onMessage(x) { - if (x.userId == this.$i.id) return; - this.messages.unshift(x.message); - }, - - onChangeForm(item) { - this.connection.send('updateForm', { - id: item.id, - value: item.value - }); - }, - - onMapChange() { - if (this.mapName == null) { - this.game.map = null; - } else { - this.game.map = Object.values(maps).find(x => x.name == this.mapName).data; - } - this.updateSettings('map'); - }, - - onPixelClick(pos, pixel) { - const x = pos % this.game.map[0].length; - const y = Math.floor(pos / this.game.map[0].length); - const newPixel = - pixel == ' ' ? '-' : - pixel == '-' ? 'b' : - pixel == 'b' ? 'w' : - ' '; - const line = this.game.map[y].split(''); - line[x] = newPixel; - this.game.map[y] = line.join(''); - this.updateSettings('map'); - } - } -}); -</script> - -<style lang="scss" scoped> -.urbixznjwwuukfsckrwzwsqzsxornqij { - text-align: center; - background: var(--bg); - - > header { - padding: 8px; - border-bottom: dashed 1px #c4cdd4; - } - - > div { - padding: 0 16px; - - > .card { - margin: 0 auto 16px auto; - - &.map { - > header { - > select { - width: 100%; - padding: 12px 14px; - background: var(--face); - border: 1px solid var(--inputBorder); - border-radius: 4px; - color: var(--fg); - cursor: pointer; - transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1); - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - - &:focus-visible, - &:active { - border-color: var(--accent); - } - } - } - - > div { - > .random { - padding: 32px 0; - font-size: 64px; - color: var(--fg); - opacity: 0.7; - } - - > .board { - display: grid; - grid-gap: 4px; - width: 300px; - height: 300px; - margin: 0 auto; - color: var(--fg); - - > div { - background: transparent; - border: solid 2px var(--divider); - border-radius: 6px; - overflow: hidden; - cursor: pointer; - - * { - pointer-events: none; - user-select: none; - width: 100%; - height: 100%; - } - - &.none { - border-color: transparent; - } - } - } - } - } - - &.form { - > div { - > .card + .card { - margin-top: 16px; - } - - input[type='range'] { - width: 100%; - } - } - } - } - - .card { - max-width: 400px; - - > header { - padding: 18px 20px; - border-bottom: 1px solid var(--divider); - } - - > div { - padding: 20px; - color: var(--fg); - } - } - } - - > footer { - position: sticky; - bottom: 0; - padding: 16px; - border-top: solid 1px var(--divider); - - > .status { - margin: 0 0 16px 0; - } - } -} -</style> diff --git a/packages/client/src/pages/reversi/game.vue b/packages/client/src/pages/reversi/game.vue deleted file mode 100644 index b1ed632904..0000000000 --- a/packages/client/src/pages/reversi/game.vue +++ /dev/null @@ -1,76 +0,0 @@ -<template> -<div v-if="game == null"><MkLoading/></div> -<GameSetting v-else-if="!game.isStarted" :init-game="game" :connection="connection"/> -<GameBoard v-else :init-game="game" :connection="connection"/> -</template> - -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; -import GameSetting from './game.setting.vue'; -import GameBoard from './game.board.vue'; -import * as os from '@/os'; -import * as symbols from '@/symbols'; - -export default defineComponent({ - components: { - GameSetting, - GameBoard, - }, - - props: { - gameId: { - type: String, - required: true - }, - }, - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts._reversi.reversi, - icon: 'fas fa-gamepad' - }, - game: null, - connection: null, - }; - }, - - watch: { - gameId() { - this.fetch(); - } - }, - - mounted() { - this.fetch(); - }, - - beforeUnmount() { - if (this.connection) { - this.connection.dispose(); - } - }, - - methods: { - fetch() { - os.api('games/reversi/games/show', { - gameId: this.gameId - }).then(game => { - this.game = game; - - if (this.connection) { - this.connection.dispose(); - } - this.connection = markRaw(os.stream.useChannel('gamesReversiGame', { - gameId: this.game.id - })); - this.connection.on('started', this.onStarted); - }); - }, - - onStarted(game) { - Object.assign(this.game, game); - }, - } -}); -</script> diff --git a/packages/client/src/pages/reversi/index.vue b/packages/client/src/pages/reversi/index.vue deleted file mode 100644 index 0b118531fc..0000000000 --- a/packages/client/src/pages/reversi/index.vue +++ /dev/null @@ -1,279 +0,0 @@ -<template> -<div v-if="!matching" class="bgvwxkhb"> - <h1>Misskey {{ $ts._reversi.reversi }}</h1> - - <div class="play"> - <MkButton primary round style="margin: var(--margin) auto 0 auto;" @click="match">{{ $ts.invite }}</MkButton> - </div> - - <div class="_section"> - <MkFolder v-if="invitations.length > 0"> - <template #header>{{ $ts.invitations }}</template> - <div class="nfcacttm"> - <button v-for="invitation in invitations" class="invitation _panel _button" tabindex="-1" @click="accept(invitation)"> - <MkAvatar class="avatar" :user="invitation.parent" :show-indicator="true"/> - <span class="name"><b><MkUserName :user="invitation.parent"/></b></span> - <span class="username">@{{ invitation.parent.username }}</span> - <MkTime :time="invitation.createdAt" class="time"/> - </button> - </div> - </MkFolder> - - <MkFolder v-if="myGames.length > 0"> - <template #header>{{ $ts._reversi.myGames }}</template> - <div class="knextgwz"> - <MkA v-for="g in myGames" :key="g.id" class="game _panel" tabindex="-1" :to="`/games/reversi/${g.id}`"> - <div class="players"> - <MkAvatar class="avatar" :user="g.user1"/><b><MkUserName :user="g.user1"/></b> vs <b><MkUserName :user="g.user2"/></b><MkAvatar class="avatar" :user="g.user2"/> - </div> - <footer><span class="state" :class="{ playing: !g.isEnded }">{{ g.isEnded ? $ts._reversi.ended : $ts._reversi.playing }}</span><MkTime class="time" :time="g.createdAt"/></footer> - </MkA> - </div> - </MkFolder> - - <MkFolder v-if="games.length > 0"> - <template #header>{{ $ts._reversi.allGames }}</template> - <div class="knextgwz"> - <MkA v-for="g in games" :key="g.id" class="game _panel" tabindex="-1" :to="`/games/reversi/${g.id}`"> - <div class="players"> - <MkAvatar class="avatar" :user="g.user1"/><b><MkUserName :user="g.user1"/></b> vs <b><MkUserName :user="g.user2"/></b><MkAvatar class="avatar" :user="g.user2"/> - </div> - <footer><span class="state" :class="{ playing: !g.isEnded }">{{ g.isEnded ? $ts._reversi.ended : $ts._reversi.playing }}</span><MkTime class="time" :time="g.createdAt"/></footer> - </MkA> - </div> - </MkFolder> - </div> -</div> -<div v-else class="sazhgisb"> - <h1> - <I18n :src="$ts.waitingFor" tag="span"> - <template #x> - <b><MkUserName :user="matching"/></b> - </template> - </I18n> - <MkEllipsis/> - </h1> - <div class="cancel"> - <MkButton inline round @click="cancel">{{ $ts.cancel }}</MkButton> - </div> -</div> -</template> - -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; -import * as os from '@/os'; -import MkButton from '@/components/ui/button.vue'; -import MkFolder from '@/components/ui/folder.vue'; -import * as symbols from '@/symbols'; - -export default defineComponent({ - components: { - MkButton, MkFolder, - }, - - inject: ['navHook'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts._reversi.reversi, - icon: 'fas fa-gamepad' - }, - games: [], - gamesFetching: true, - gamesMoreFetching: false, - myGames: [], - matching: null, - invitations: [], - connection: null, - pingClock: null, - }; - }, - - mounted() { - if (this.$i) { - this.connection = markRaw(os.stream.useChannel('gamesReversi')); - - this.connection.on('invited', this.onInvited); - - this.connection.on('matched', this.onMatched); - - this.pingClock = setInterval(() => { - if (this.matching) { - this.connection.send('ping', { - id: this.matching.id - }); - } - }, 3000); - - os.api('games/reversi/games', { - my: true - }).then(games => { - this.myGames = games; - }); - - os.api('games/reversi/invitations').then(invitations => { - this.invitations = this.invitations.concat(invitations); - }); - } - - os.api('games/reversi/games').then(games => { - this.games = games; - this.gamesFetching = false; - }); - }, - - beforeUnmount() { - if (this.connection) { - this.connection.dispose(); - clearInterval(this.pingClock); - } - }, - - methods: { - go(game) { - const url = '/games/reversi/' + game.id; - if (this.navHook) { - this.navHook(url); - } else { - this.$router.push(url); - } - }, - - async match() { - const user = await os.selectUser({ local: true }); - if (user == null) return; - os.api('games/reversi/match', { - userId: user.id - }).then(res => { - if (res == null) { - this.matching = user; - } else { - this.go(res); - } - }); - }, - - cancel() { - this.matching = null; - os.api('games/reversi/match/cancel'); - }, - - accept(invitation) { - os.api('games/reversi/match', { - userId: invitation.parent.id - }).then(game => { - if (game) { - this.go(game); - } - }); - }, - - onMatched(game) { - this.go(game); - }, - - onInvited(invite) { - this.invitations.unshift(invite); - } - } -}); -</script> - -<style lang="scss" scoped> -.bgvwxkhb { - > h1 { - margin: 0; - padding: 24px; - text-align: center; - font-size: 1.5em; - background: linear-gradient(0deg, #43c583, #438881); - color: #fff; - } - - > .play { - text-align: center; - } -} - -.sazhgisb { - text-align: center; -} - -.nfcacttm { - > .invitation { - display: flex; - box-sizing: border-box; - width: 100%; - padding: 16px; - line-height: 32px; - text-align: left; - - > .avatar { - width: 32px; - height: 32px; - margin-right: 8px; - } - - > .name { - margin-right: 8px; - } - - > .username { - margin-right: 8px; - opacity: 0.7; - } - - > .time { - margin-left: auto; - opacity: 0.7; - } - } -} - -.knextgwz { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); - grid-gap: var(--margin); - - > .game { - > .players { - text-align: center; - padding: 16px; - line-height: 32px; - - > .avatar { - width: 32px; - height: 32px; - - &:first-child { - margin-right: 8px; - } - - &:last-child { - margin-left: 8px; - } - } - } - - > footer { - display: flex; - align-items: baseline; - border-top: solid 0.5px var(--divider); - padding: 6px 8px; - font-size: 0.9em; - - > .state { - &.playing { - color: var(--accent); - } - } - - > .time { - margin-left: auto; - opacity: 0.7; - } - } - } -} -</style> diff --git a/packages/client/src/pages/room/preview.vue b/packages/client/src/pages/room/preview.vue deleted file mode 100644 index b0e600d4fb..0000000000 --- a/packages/client/src/pages/room/preview.vue +++ /dev/null @@ -1,107 +0,0 @@ -<template> -<canvas width="224" height="128"></canvas> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as THREE from 'three'; -import * as os from '@/os'; - -export default defineComponent({ - data() { - return { - selected: null, - objectHeight: 0, - orbitRadius: 5 - }; - }, - - mounted() { - const canvas = this.$el; - - const width = canvas.width; - const height = canvas.height; - - const scene = new THREE.Scene(); - - const renderer = new THREE.WebGLRenderer({ - canvas: canvas, - antialias: true, - alpha: false - }); - renderer.setPixelRatio(window.devicePixelRatio); - renderer.setSize(width, height); - renderer.setClearColor(0x000000); - renderer.autoClear = false; - renderer.shadowMap.enabled = true; - renderer.shadowMap.cullFace = THREE.CullFaceBack; - - const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 100); - camera.zoom = 10; - camera.position.x = 0; - camera.position.y = 2; - camera.position.z = 0; - camera.updateProjectionMatrix(); - scene.add(camera); - - const ambientLight = new THREE.AmbientLight(0xffffff, 1); - ambientLight.castShadow = false; - scene.add(ambientLight); - - const light = new THREE.PointLight(0xffffff, 1, 100); - light.position.set(3, 3, 3); - scene.add(light); - - const grid = new THREE.GridHelper(5, 16, 0x444444, 0x222222); - scene.add(grid); - - const render = () => { - const timer = Date.now() * 0.0004; - requestAnimationFrame(render); - - camera.position.y = Math.sin(Math.PI / 6) * this.orbitRadius; // Math.PI / 6 => 30deg - camera.position.z = Math.cos(timer) * this.orbitRadius; - camera.position.x = Math.sin(timer) * this.orbitRadius; - camera.lookAt(new THREE.Vector3(0, this.objectHeight / 2, 0)); - renderer.render(scene, camera); - }; - - this.selected = selected => { - const obj = selected.clone(); - - // Remove current object - const current = scene.getObjectByName('obj'); - if (current != null) { - scene.remove(current); - } - - // Add new object - obj.name = 'obj'; - obj.position.x = 0; - obj.position.y = 0; - obj.position.z = 0; - obj.rotation.x = 0; - obj.rotation.y = 0; - obj.rotation.z = 0; - obj.traverse(child => { - if (child instanceof THREE.Mesh) { - child.material = child.material.clone(); - return child.material.emissive.setHex(0x000000); - } - }); - const objectBoundingBox = new THREE.Box3().setFromObject(obj); - this.objectHeight = objectBoundingBox.max.y - objectBoundingBox.min.y; - - const objectWidth = objectBoundingBox.max.x - objectBoundingBox.min.x; - const objectDepth = objectBoundingBox.max.z - objectBoundingBox.min.z; - - const horizontal = Math.hypot(objectWidth, objectDepth) / camera.aspect; - this.orbitRadius = Math.max(horizontal, this.objectHeight) * camera.zoom * 0.625 / Math.tan(camera.fov * 0.5 * (Math.PI / 180)); - - scene.add(obj); - }; - - render(); - }, -}); -</script> diff --git a/packages/client/src/pages/room/room.vue b/packages/client/src/pages/room/room.vue deleted file mode 100644 index eb85d39dc4..0000000000 --- a/packages/client/src/pages/room/room.vue +++ /dev/null @@ -1,279 +0,0 @@ -<template> -<div class="hveuntkp"> - <div v-if="objectSelected" class="controller _section"> - <div class="_content"> - <p class="name">{{ selectedFurnitureName }}</p> - <XPreview ref="preview"/> - <template v-if="selectedFurnitureInfo.props"> - <div v-for="k in Object.keys(selectedFurnitureInfo.props)" :key="k"> - <p>{{ k }}</p> - <template v-if="selectedFurnitureInfo.props[k] === 'image'"> - <MkButton @click="chooseImage(k, $event)">{{ $ts._rooms.chooseImage }}</MkButton> - </template> - <template v-else-if="selectedFurnitureInfo.props[k] === 'color'"> - <input type="color" :value="selectedFurnitureProps ? selectedFurnitureProps[k] : null" @change="updateColor(k, $event)"/> - </template> - </div> - </template> - </div> - <div class="_content"> - <MkButton inline :primary="isTranslateMode" @click="translate()"><i class="fas fa-arrows-alt"></i> {{ $ts._rooms.translate }}</MkButton> - <MkButton inline :primary="isRotateMode" @click="rotate()"><i class="fas fa-undo"></i> {{ $ts._rooms.rotate }}</MkButton> - <MkButton v-if="isTranslateMode || isRotateMode" inline @click="exit()"><i class="fas fa-ban"></i> {{ $ts._rooms.exit }}</MkButton> - </div> - <div class="_content"> - <MkButton @click="remove()"><i class="fas fa-trash-alt"></i> {{ $ts._rooms.remove }}</MkButton> - </div> - </div> - - <div v-if="isMyRoom" class="menu _section"> - <div class="_content"> - <MkButton @click="add()"><i class="fas fa-box-open"></i> {{ $ts._rooms.addFurniture }}</MkButton> - </div> - <div class="_content"> - <MkSelect :model-value="roomType" @update:modelValue="updateRoomType($event)"> - <template #label>{{ $ts._rooms.roomType }}</template> - <option value="default">{{ $ts._rooms._roomType.default }}</option> - <option value="washitsu">{{ $ts._rooms._roomType.washitsu }}</option> - </MkSelect> - <label v-if="roomType === 'default'"> - <span>{{ $ts._rooms.carpetColor }}</span> - <input type="color" :value="carpetColor" @change="updateCarpetColor($event)"/> - </label> - </div> - <div class="_content"> - <MkButton inline :disabled="!changed" primary @click="save()"><i class="fas fa-save"></i> {{ $ts.save }}</MkButton> - <MkButton inline @click="clear()"><i class="fas fa-broom"></i> {{ $ts._rooms.clear }}</MkButton> - </div> - </div> -</div> -</template> - -<script lang="ts"> -import { computed, defineComponent } from 'vue'; -import { Room } from '@/scripts/room/room'; -import * as Acct from 'misskey-js/built/acct'; -import XPreview from './preview.vue'; -const storeItems = require('@/scripts/room/furnitures.json5'); -import { query as urlQuery } from '@/scripts/url'; -import MkButton from '@/components/ui/button.vue'; -import MkSelect from '@/components/form/select.vue'; -import { selectFile } from '@/scripts/select-file'; -import * as os from '@/os'; -import { ColdDeviceStorage } from '@/store'; -import * as symbols from '@/symbols'; - -let room: Room; - -export default defineComponent({ - components: { - XPreview, - MkButton, - MkSelect, - }, - - beforeRouteLeave(to, from, next) { - if (this.changed) { - os.confirm({ - type: 'warning', - text: this.$ts.leaveConfirm, - }).then(({ canceled }) => { - if (canceled) { - next(false); - } else { - next(); - } - }); - } else { - next(); - } - }, - - props: { - acct: { - type: String, - required: true - }, - }, - - data() { - return { - [symbols.PAGE_INFO]: computed(() => this.user ? { - title: this.$ts.room, - avatar: this.user, - } : null), - user: null, - objectSelected: false, - selectedFurnitureName: null, - selectedFurnitureInfo: null, - selectedFurnitureProps: null, - roomType: null, - carpetColor: null, - isTranslateMode: false, - isRotateMode: false, - isMyRoom: false, - changed: false, - }; - }, - - async mounted() { - window.addEventListener('beforeunload', this.beforeunload); - - this.user = await os.api('users/show', { - ...Acct.parse(this.acct) - }); - - this.isMyRoom = this.$i && (this.$i.id === this.user.id); - - const roomInfo = await os.api('room/show', { - userId: this.user.id - }); - - this.roomType = roomInfo.roomType; - this.carpetColor = roomInfo.carpetColor; - - room = new Room(this.user, this.isMyRoom, roomInfo, this.$el, { - graphicsQuality: ColdDeviceStorage.get('roomGraphicsQuality'), - onChangeSelect: obj => { - this.objectSelected = obj != null; - if (obj) { - const f = room.findFurnitureById(obj.name); - this.selectedFurnitureName = this.$t('_rooms._furnitures.' + f.type); - this.selectedFurnitureInfo = storeItems.find(x => x.id === f.type); - this.selectedFurnitureProps = f.props - ? JSON.parse(JSON.stringify(f.props)) // Disable reactivity - : null; - this.$nextTick(() => { - this.$refs.preview.selected(obj); - }); - } - }, - useOrthographicCamera: ColdDeviceStorage.get('roomUseOrthographicCamera'), - }); - }, - - beforeUnmount() { - room.destroy(); - window.removeEventListener('beforeunload', this.beforeunload); - }, - - methods: { - beforeunload(e: BeforeUnloadEvent) { - if (this.changed) { - e.preventDefault(); - e.returnValue = ''; - } - }, - - async add() { - const { canceled, result: id } = await os.select({ - title: this.$ts._rooms.addFurniture, - items: storeItems.map(item => ({ - value: item.id, text: this.$t('_rooms._furnitures.' + item.id) - })) - }); - if (canceled) return; - room.addFurniture(id); - this.changed = true; - }, - - remove() { - this.isTranslateMode = false; - this.isRotateMode = false; - room.removeFurniture(); - this.changed = true; - }, - - save() { - os.api('room/update', { - room: room.getRoomInfo() - }).then(() => { - this.changed = false; - os.success(); - }).catch((e: any) => { - os.alert({ - type: 'error', - text: e.message - }); - }); - }, - - clear() { - os.confirm({ - type: 'warning', - text: this.$ts._rooms.clearConfirm, - }).then(({ canceled }) => { - if (canceled) return; - room.removeAllFurnitures(); - this.changed = true; - }); - }, - - chooseImage(key, e) { - selectFile(e.currentTarget || e.target, null).then(file => { - room.updateProp(key, `/proxy/?${urlQuery({ url: file.thumbnailUrl })}`); - this.$refs.preview.selected(room.getSelectedObject()); - this.changed = true; - }); - }, - - updateColor(key, ev) { - room.updateProp(key, ev.target.value); - this.$refs.preview.selected(room.getSelectedObject()); - this.changed = true; - }, - - updateCarpetColor(ev) { - room.updateCarpetColor(ev.target.value); - this.carpetColor = ev.target.value; - this.changed = true; - }, - - updateRoomType(type) { - room.changeRoomType(type); - this.roomType = type; - this.changed = true; - }, - - translate() { - if (this.isTranslateMode) { - this.exit(); - } else { - this.isRotateMode = false; - this.isTranslateMode = true; - room.enterTransformMode('translate'); - } - this.changed = true; - }, - - rotate() { - if (this.isRotateMode) { - this.exit(); - } else { - this.isTranslateMode = false; - this.isRotateMode = true; - room.enterTransformMode('rotate'); - } - this.changed = true; - }, - - exit() { - this.isTranslateMode = false; - this.isRotateMode = false; - room.exitTransformMode(); - this.changed = true; - } - } -}); -</script> - -<style lang="scss" scoped> -.hveuntkp { - position: relative; - min-height: 500px; - - > ::v-deep(canvas) { - display: block; - } -} -</style> diff --git a/packages/client/src/pages/search.vue b/packages/client/src/pages/search.vue index 85d19bb255..ce2b7035da 100644 --- a/packages/client/src/pages/search.vue +++ b/packages/client/src/pages/search.vue @@ -6,37 +6,31 @@ </div> </template> -<script lang="ts"> -import { computed, defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; import XNotes from '@/components/notes.vue'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XNotes - }, +const props = defineProps<{ + query: string; + channel?: string; +}>(); - data() { - return { - [symbols.PAGE_INFO]: { - title: computed(() => this.$t('searchWith', { q: this.$route.query.q })), - icon: 'fas fa-search', - }, - pagination: { - endpoint: 'notes/search', - limit: 10, - params: () => ({ - query: this.$route.query.q, - channelId: this.$route.query.channel, - }) - }, - }; - }, +const pagination = { + endpoint: 'notes/search' as const, + limit: 10, + params: computed(() => ({ + query: props.query, + channelId: props.channel, + })) +}; - watch: { - $route() { - (this.$refs.notes as any).reload(); - } - }, +defineExpose({ + [symbols.PAGE_INFO]: computed(() => ({ + title: i18n.t('searchWith', { q: props.query }), + icon: 'fas fa-search', + bg: 'var(--bg)', + })), }); </script> diff --git a/packages/client/src/pages/settings/2fa.vue b/packages/client/src/pages/settings/2fa.vue index cffd10a0ee..10599d99ff 100644 --- a/packages/client/src/pages/settings/2fa.vue +++ b/packages/client/src/pages/settings/2fa.vue @@ -71,9 +71,6 @@ import MkButton from '@/components/ui/button.vue'; import MkInfo from '@/components/ui/info.vue'; import MkInput from '@/components/form/input.vue'; import MkSwitch from '@/components/form/switch.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormButton from '@/components/debobigego/button.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; diff --git a/packages/client/src/pages/settings/account-info.vue b/packages/client/src/pages/settings/account-info.vue index f3d5e2f2c3..c98ad056f6 100644 --- a/packages/client/src/pages/settings/account-info.vue +++ b/packages/client/src/pages/settings/account-info.vue @@ -1,144 +1,135 @@ <template> -<FormBase> - <FormKeyValueView> +<div class="_formRoot"> + <MkKeyValue> <template #key>ID</template> <template #value><span class="_monospace">{{ $i.id }}</span></template> - </FormKeyValueView> + </MkKeyValue> - <FormGroup> - <FormKeyValueView> + <FormSection> + <MkKeyValue> <template #key>{{ $ts.registeredDate }}</template> <template #value><MkTime :time="$i.createdAt" mode="detail"/></template> - </FormKeyValueView> - </FormGroup> + </MkKeyValue> + </FormSection> - <FormGroup v-if="stats"> + <FormSection v-if="stats"> <template #label>{{ $ts.statistics }}</template> - <FormKeyValueView> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.notesCount }}</template> <template #value>{{ number(stats.notesCount) }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.repliesCount }}</template> <template #value>{{ number(stats.repliesCount) }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.renotesCount }}</template> <template #value>{{ number(stats.renotesCount) }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.repliedCount }}</template> <template #value>{{ number(stats.repliedCount) }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.renotedCount }}</template> <template #value>{{ number(stats.renotedCount) }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.pollVotesCount }}</template> <template #value>{{ number(stats.pollVotesCount) }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.pollVotedCount }}</template> <template #value>{{ number(stats.pollVotedCount) }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.sentReactionsCount }}</template> <template #value>{{ number(stats.sentReactionsCount) }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.receivedReactionsCount }}</template> <template #value>{{ number(stats.receivedReactionsCount) }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.noteFavoritesCount }}</template> <template #value>{{ number(stats.noteFavoritesCount) }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.followingCount }}</template> <template #value>{{ number(stats.followingCount) }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.followingCount }} ({{ $ts.local }})</template> <template #value>{{ number(stats.localFollowingCount) }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.followingCount }} ({{ $ts.remote }})</template> <template #value>{{ number(stats.remoteFollowingCount) }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.followersCount }}</template> <template #value>{{ number(stats.followersCount) }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.followersCount }} ({{ $ts.local }})</template> <template #value>{{ number(stats.localFollowersCount) }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.followersCount }} ({{ $ts.remote }})</template> <template #value>{{ number(stats.remoteFollowersCount) }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.pageLikesCount }}</template> <template #value>{{ number(stats.pageLikesCount) }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.pageLikedCount }}</template> <template #value>{{ number(stats.pageLikedCount) }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.driveFilesCount }}</template> <template #value>{{ number(stats.driveFilesCount) }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>{{ $ts.driveUsage }}</template> <template #value>{{ bytes(stats.driveUsage) }}</template> - </FormKeyValueView> - <FormKeyValueView> - <template #key>{{ $ts.reversiCount }}</template> - <template #value>{{ number(stats.reversiCount) }}</template> - </FormKeyValueView> - </FormGroup> + </MkKeyValue> + </FormSection> - <FormGroup> + <FormSection> <template #label>{{ $ts.other }}</template> - <FormKeyValueView> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>emailVerified</template> <template #value>{{ $i.emailVerified ? $ts.yes : $ts.no }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>twoFactorEnabled</template> <template #value>{{ $i.twoFactorEnabled ? $ts.yes : $ts.no }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>securityKeys</template> <template #value>{{ $i.securityKeys ? $ts.yes : $ts.no }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>usePasswordLessLogin</template> <template #value>{{ $i.usePasswordLessLogin ? $ts.yes : $ts.no }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>isModerator</template> <template #value>{{ $i.isModerator ? $ts.yes : $ts.no }}</template> - </FormKeyValueView> - <FormKeyValueView> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> <template #key>isAdmin</template> <template #value>{{ $i.isAdmin ? $ts.yes : $ts.no }}</template> - </FormKeyValueView> - </FormGroup> -</FormBase> + </MkKeyValue> + </FormSection> +</div> </template> <script lang="ts"> import { defineAsyncComponent, defineComponent } from 'vue'; -import FormSwitch from '@/components/form/switch.vue'; -import FormSelect from '@/components/form/select.vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormKeyValueView from '@/components/debobigego/key-value-view.vue'; +import FormSection from '@/components/form/section.vue'; +import MkKeyValue from '@/components/key-value.vue'; import * as os from '@/os'; import number from '@/filters/number'; import bytes from '@/filters/bytes'; @@ -146,13 +137,8 @@ import * as symbols from '@/symbols'; export default defineComponent({ components: { - FormBase, - FormSelect, - FormSwitch, - FormButton, - FormLink, - FormGroup, - FormKeyValueView, + FormSection, + MkKeyValue, }, emits: ['info'], @@ -168,8 +154,6 @@ export default defineComponent({ }, mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - os.api('users/stats', { userId: this.$i.id }).then(stats => { diff --git a/packages/client/src/pages/settings/accounts.vue b/packages/client/src/pages/settings/accounts.vue index 2d1e0eff4e..c795ede8ac 100644 --- a/packages/client/src/pages/settings/accounts.vue +++ b/packages/client/src/pages/settings/accounts.vue @@ -1,41 +1,35 @@ <template> -<FormBase> +<div class="_formRoot"> <FormSuspense :p="init"> <FormButton primary @click="addAccount"><i class="fas fa-plus"></i> {{ $ts.addAccount }}</FormButton> - <div v-for="account in accounts" :key="account.id" class="_debobigegoItem _button" @click="menu(account, $event)"> - <div class="_debobigegoPanel lcjjdxlm"> - <div class="avatar"> - <MkAvatar :user="account" class="avatar"/> + <div v-for="account in accounts" :key="account.id" class="_panel _button lcjjdxlm" @click="menu(account, $event)"> + <div class="avatar"> + <MkAvatar :user="account" class="avatar"/> + </div> + <div class="body"> + <div class="name"> + <MkUserName :user="account"/> </div> - <div class="body"> - <div class="name"> - <MkUserName :user="account"/> - </div> - <div class="acct"> - <MkAcct :user="account"/> - </div> + <div class="acct"> + <MkAcct :user="account"/> </div> </div> </div> </FormSuspense> -</FormBase> +</div> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormButton from '@/components/debobigego/button.vue'; +import FormSuspense from '@/components/form/suspense.vue'; +import FormButton from '@/components/ui/button.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { getAccounts, addAccount, login } from '@/account'; export default defineComponent({ components: { - FormBase, FormSuspense, FormButton, }, @@ -59,10 +53,6 @@ export default defineComponent({ }; }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { menu(account, ev) { os.popupMenu([{ diff --git a/packages/client/src/pages/settings/api.vue b/packages/client/src/pages/settings/api.vue index 30a4902a15..20ff2a8d96 100644 --- a/packages/client/src/pages/settings/api.vue +++ b/packages/client/src/pages/settings/api.vue @@ -1,25 +1,20 @@ <template> -<FormBase> - <FormButton primary @click="generateToken">{{ $ts.generateAccessToken }}</FormButton> - <FormLink to="/settings/apps">{{ $ts.manageAccessTokens }}</FormLink> - <FormLink to="/api-console" :behavior="isDesktop ? 'window' : null">API console</FormLink> -</FormBase> +<div class="_formRoot"> + <FormButton primary class="_formBlock" @click="generateToken">{{ $ts.generateAccessToken }}</FormButton> + <FormLink to="/settings/apps" class="_formBlock">{{ $ts.manageAccessTokens }}</FormLink> + <FormLink to="/api-console" :behavior="isDesktop ? 'window' : null" class="_formBlock">API console</FormLink> +</div> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@/components/form/switch.vue'; -import FormSelect from '@/components/form/select.vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormButton from '@/components/debobigego/button.vue'; +import FormLink from '@/components/form/link.vue'; +import FormButton from '@/components/ui/button.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; export default defineComponent({ components: { - FormBase, FormButton, FormLink, }, @@ -37,10 +32,6 @@ export default defineComponent({ }; }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { generateToken() { os.popup(import('@/components/token-generate-window.vue'), {}, { diff --git a/packages/client/src/pages/settings/apps.vue b/packages/client/src/pages/settings/apps.vue index b5fe4e0aed..9c0fa8a54d 100644 --- a/packages/client/src/pages/settings/apps.vue +++ b/packages/client/src/pages/settings/apps.vue @@ -1,5 +1,5 @@ <template> -<FormBase> +<div class="_formRoot"> <FormPagination ref="list" :pagination="pagination"> <template #empty> <div class="_fullinfo"> @@ -8,7 +8,7 @@ </div> </template> <template v-slot="{items}"> - <div v-for="token in items" :key="token.id" class="_debobigegoPanel bfomjevm"> + <div v-for="token in items" :key="token.id" class="_panel bfomjevm"> <img v-if="token.iconUrl" class="icon" :src="token.iconUrl" alt=""/> <div class="body"> <div class="name">{{ token.name }}</div> @@ -34,23 +34,17 @@ </div> </template> </FormPagination> -</FormBase> +</div> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormPagination from '@/components/debobigego/pagination.vue'; -import FormSelect from '@/components/form/select.vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormButton from '@/components/debobigego/button.vue'; +import FormPagination from '@/components/ui/pagination.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; export default defineComponent({ components: { - FormBase, FormPagination, }, @@ -64,7 +58,7 @@ export default defineComponent({ bg: 'var(--bg)', }, pagination: { - endpoint: 'i/apps', + endpoint: 'i/apps' as const, limit: 100, params: { sort: '+lastUsedAt' @@ -73,10 +67,6 @@ export default defineComponent({ }; }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { revoke(token) { os.api('i/revoke-token', { tokenId: token.id }).then(() => { diff --git a/packages/client/src/pages/settings/custom-css.vue b/packages/client/src/pages/settings/custom-css.vue index 155956923c..556ee30c1d 100644 --- a/packages/client/src/pages/settings/custom-css.vue +++ b/packages/client/src/pages/settings/custom-css.vue @@ -1,25 +1,18 @@ <template> -<FormBase> - <FormInfo warn>{{ $ts.customCssWarn }}</FormInfo> +<div class="_formRoot"> + <FormInfo warn class="_formBlock">{{ $ts.customCssWarn }}</FormInfo> - <FormTextarea v-model="localCustomCss" manual-save tall class="_monospace" style="tab-size: 2;"> - <span>{{ $ts.local }}</span> + <FormTextarea v-model="localCustomCss" manual-save tall class="_monospace _formBlock" style="tab-size: 2;"> + <template #label>CSS</template> </FormTextarea> -</FormBase> +</div> </template> <script lang="ts"> import { defineComponent } from 'vue'; import FormTextarea from '@/components/form/textarea.vue'; -import FormSelect from '@/components/form/select.vue'; -import FormRadios from '@/components/form/radios.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormInfo from '@/components/debobigego/info.vue'; +import FormInfo from '@/components/ui/info.vue'; import * as os from '@/os'; -import { ColdDeviceStorage } from '@/store'; import { unisonReload } from '@/scripts/unison-reload'; import * as symbols from '@/symbols'; import { defaultStore } from '@/store'; @@ -27,12 +20,6 @@ import { defaultStore } from '@/store'; export default defineComponent({ components: { FormTextarea, - FormSelect, - FormRadios, - FormBase, - FormGroup, - FormLink, - FormButton, FormInfo, }, @@ -50,8 +37,6 @@ export default defineComponent({ }, mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - this.$watch('localCustomCss', this.apply); }, diff --git a/packages/client/src/pages/settings/deck.vue b/packages/client/src/pages/settings/deck.vue index bc82b0ca84..46b90d3d1a 100644 --- a/packages/client/src/pages/settings/deck.vue +++ b/packages/client/src/pages/settings/deck.vue @@ -1,42 +1,41 @@ <template> -<FormBase> +<div class="_formRoot"> <FormGroup> <template #label>{{ $ts.defaultNavigationBehaviour }}</template> <FormSwitch v-model="navWindow">{{ $ts.openInWindow }}</FormSwitch> </FormGroup> - <FormSwitch v-model="alwaysShowMainColumn">{{ $ts._deck.alwaysShowMainColumn }}</FormSwitch> + <FormSwitch v-model="alwaysShowMainColumn" class="_formBlock">{{ $ts._deck.alwaysShowMainColumn }}</FormSwitch> - <FormRadios v-model="columnAlign"> - <template #desc>{{ $ts._deck.columnAlign }}</template> + <FormRadios v-model="columnAlign" class="_formBlock"> + <template #label>{{ $ts._deck.columnAlign }}</template> <option value="left">{{ $ts.left }}</option> <option value="center">{{ $ts.center }}</option> </FormRadios> - <FormRadios v-model="columnHeaderHeight"> - <template #desc>{{ $ts._deck.columnHeaderHeight }}</template> + <FormRadios v-model="columnHeaderHeight" class="_formBlock"> + <template #label>{{ $ts._deck.columnHeaderHeight }}</template> <option :value="42">{{ $ts.narrow }}</option> <option :value="45">{{ $ts.medium }}</option> <option :value="48">{{ $ts.wide }}</option> </FormRadios> - <FormInput v-model="columnMargin" type="number"> - <span>{{ $ts._deck.columnMargin }}</span> + <FormInput v-model="columnMargin" type="number" class="_formBlock"> + <template #label>{{ $ts._deck.columnMargin }}</template> <template #suffix>px</template> </FormInput> - <FormLink @click="setProfile">{{ $ts._deck.profile }}<template #suffix>{{ profile }}</template></FormLink> -</FormBase> + <FormLink class="_formBlock" @click="setProfile">{{ $ts._deck.profile }}<template #suffix>{{ profile }}</template></FormLink> +</div> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormSwitch from '@/components/debobigego/switch.vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormRadios from '@/components/debobigego/radios.vue'; -import FormInput from '@/components/debobigego/input.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; +import FormSwitch from '@/components/form/switch.vue'; +import FormLink from '@/components/form/link.vue'; +import FormRadios from '@/components/form/radios.vue'; +import FormInput from '@/components/form/input.vue'; +import FormGroup from '@/components/form/group.vue'; import { deckStore } from '@/ui/deck/deck-store'; import * as os from '@/os'; import { unisonReload } from '@/scripts/unison-reload'; @@ -48,7 +47,6 @@ export default defineComponent({ FormLink, FormInput, FormRadios, - FormBase, FormGroup, }, @@ -85,10 +83,6 @@ export default defineComponent({ } }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async setProfile() { const { canceled, result: name } = await os.inputText({ diff --git a/packages/client/src/pages/settings/delete-account.vue b/packages/client/src/pages/settings/delete-account.vue index 6ce8d6509c..7edc81a309 100644 --- a/packages/client/src/pages/settings/delete-account.vue +++ b/packages/client/src/pages/settings/delete-account.vue @@ -1,28 +1,23 @@ <template> -<FormBase> - <FormInfo warn>{{ $ts._accountDelete.mayTakeTime }}</FormInfo> - <FormInfo>{{ $ts._accountDelete.sendEmail }}</FormInfo> - <FormButton v-if="!$i.isDeleted" danger @click="deleteAccount">{{ $ts._accountDelete.requestAccountDelete }}</FormButton> +<div class="_formRoot"> + <FormInfo warn class="_formBlock">{{ $ts._accountDelete.mayTakeTime }}</FormInfo> + <FormInfo class="_formBlock">{{ $ts._accountDelete.sendEmail }}</FormInfo> + <FormButton v-if="!$i.isDeleted" danger class="_formBlock" @click="deleteAccount">{{ $ts._accountDelete.requestAccountDelete }}</FormButton> <FormButton v-else disabled>{{ $ts._accountDelete.inProgress }}</FormButton> -</FormBase> +</div> </template> <script lang="ts"> import { defineAsyncComponent, defineComponent } from 'vue'; -import FormInfo from '@/components/debobigego/info.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormButton from '@/components/debobigego/button.vue'; +import FormInfo from '@/components/ui/info.vue'; +import FormButton from '@/components/ui/button.vue'; import * as os from '@/os'; -import { debug } from '@/config'; import { signout } from '@/account'; import * as symbols from '@/symbols'; export default defineComponent({ components: { - FormBase, FormButton, - FormGroup, FormInfo, }, @@ -35,14 +30,9 @@ export default defineComponent({ icon: 'fas fa-exclamation-triangle', bg: 'var(--bg)', }, - debug, } }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async deleteAccount() { { diff --git a/packages/client/src/pages/settings/drive.vue b/packages/client/src/pages/settings/drive.vue index 9ab99c6efe..f1016ebd84 100644 --- a/packages/client/src/pages/settings/drive.vue +++ b/packages/client/src/pages/settings/drive.vue @@ -5,7 +5,7 @@ <div class="_formBlock uawsfosz"> <div class="meter"><div :style="meterStyle"></div></div> </div> - <div class="_inputSplit _formBlock"> + <FormSplit> <MkKeyValue class="_formBlock"> <template #key>{{ $ts.capacity }}</template> <template #value>{{ bytes(capacity, 1) }}</template> @@ -14,7 +14,7 @@ <template #key>{{ $ts.inUse }}</template> <template #value>{{ bytes(usage, 1) }}</template> </MkKeyValue> - </div> + </FormSplit> </FormSection> <FormSection> @@ -38,6 +38,7 @@ import * as tinycolor from 'tinycolor2'; import FormLink from '@/components/form/link.vue'; import FormSection from '@/components/form/section.vue'; import MkKeyValue from '@/components/key-value.vue'; +import FormSplit from '@/components/form/split.vue'; import * as os from '@/os'; import bytes from '@/filters/bytes'; import * as symbols from '@/symbols'; @@ -49,6 +50,7 @@ export default defineComponent({ FormLink, FormSection, MkKeyValue, + FormSplit, }, emits: ['info'], @@ -97,10 +99,6 @@ export default defineComponent({ } }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { chooseUploadFolder() { os.selectDriveFolder(false).then(async folder => { diff --git a/packages/client/src/pages/settings/email.vue b/packages/client/src/pages/settings/email.vue index b04295cce0..54557f8773 100644 --- a/packages/client/src/pages/settings/email.vue +++ b/packages/client/src/pages/settings/email.vue @@ -41,8 +41,6 @@ <script lang="ts"> import { defineComponent, onMounted, ref, watch } from 'vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormLink from '@/components/debobigego/link.vue'; import FormSection from '@/components/form/section.vue'; import FormInput from '@/components/form/input.vue'; import FormSwitch from '@/components/form/switch.vue'; @@ -54,8 +52,6 @@ import { i18n } from '@/i18n'; export default defineComponent({ components: { FormSection, - FormLink, - FormButton, FormSwitch, FormInput, }, @@ -115,8 +111,6 @@ export default defineComponent({ }); onMounted(() => { - context.emit('info', INFO); - watch(emailAddress, () => { saveEmailAddress(); }); diff --git a/packages/client/src/pages/settings/experimental-features.vue b/packages/client/src/pages/settings/experimental-features.vue deleted file mode 100644 index 5a7bcb3b41..0000000000 --- a/packages/client/src/pages/settings/experimental-features.vue +++ /dev/null @@ -1,52 +0,0 @@ -<template> -<FormBase> - <FormButton @click="error()">error test</FormButton> -</FormBase> -</template> - -<script lang="ts"> -import { defineAsyncComponent, defineComponent } from 'vue'; -import FormSwitch from '@/components/form/switch.vue'; -import FormSelect from '@/components/form/select.vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormKeyValueView from '@/components/debobigego/key-value-view.vue'; -import * as os from '@/os'; -import * as symbols from '@/symbols'; - -export default defineComponent({ - components: { - FormBase, - FormSelect, - FormSwitch, - FormButton, - FormLink, - FormGroup, - FormKeyValueView, - }, - - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.experimentalFeatures, - icon: 'fas fa-flask' - }, - stats: null - } - }, - - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - - methods: { - error() { - throw new Error('Test error'); - } - } -}); -</script> diff --git a/packages/client/src/pages/settings/general.vue b/packages/client/src/pages/settings/general.vue index 734bc78442..2e159e56a9 100644 --- a/packages/client/src/pages/settings/general.vue +++ b/packages/client/src/pages/settings/general.vue @@ -195,10 +195,6 @@ export default defineComponent({ }, }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async reloadAsk() { const { canceled } = await os.confirm({ diff --git a/packages/client/src/pages/settings/import-export.vue b/packages/client/src/pages/settings/import-export.vue index a1dd6a1539..21031c559e 100644 --- a/packages/client/src/pages/settings/import-export.vue +++ b/packages/client/src/pages/settings/import-export.vue @@ -133,10 +133,6 @@ export default defineComponent({ os.api('i/import-blocking', { fileId: file.id }).then(onImportSuccess).catch(onError); }; - onMounted(() => { - context.emit('info', INFO); - }); - return { [symbols.PAGE_INFO]: INFO, excludeMutingUsers, diff --git a/packages/client/src/pages/settings/index.vue b/packages/client/src/pages/settings/index.vue index 8ffff86705..66c8b147bb 100644 --- a/packages/client/src/pages/settings/index.vue +++ b/packages/client/src/pages/settings/index.vue @@ -14,7 +14,7 @@ </div> <div class="main"> <div class="bkzroven"> - <component :is="component" :key="page" v-bind="pageProps" @info="onInfo"/> + <component :is="component" :ref="el => pageChanged(el)" :key="page" v-bind="pageProps"/> </div> </div> </div> @@ -215,19 +215,9 @@ export default defineComponent({ case 'deck': return defineAsyncComponent(() => import('./deck.vue')); case 'plugin': return defineAsyncComponent(() => import('./plugin.vue')); case 'plugin/install': return defineAsyncComponent(() => import('./plugin.install.vue')); - case 'plugin/manage': return defineAsyncComponent(() => import('./plugin.manage.vue')); case 'import-export': return defineAsyncComponent(() => import('./import-export.vue')); case 'account-info': return defineAsyncComponent(() => import('./account-info.vue')); - case 'update': return defineAsyncComponent(() => import('./update.vue')); - case 'registry': return defineAsyncComponent(() => import('./registry.vue')); case 'delete-account': return defineAsyncComponent(() => import('./delete-account.vue')); - case 'experimental-features': return defineAsyncComponent(() => import('./experimental-features.vue')); - } - if (page.value.startsWith('registry/keys/system/')) { - return defineAsyncComponent(() => import('./registry.keys.vue')); - } - if (page.value.startsWith('registry/value/system/')) { - return defineAsyncComponent(() => import('./registry.value.vue')); } return null; }); @@ -235,17 +225,6 @@ export default defineComponent({ watch(component, () => { pageProps.value = {}; - if (page.value) { - if (page.value.startsWith('registry/keys/system/')) { - pageProps.value.scope = page.value.replace('registry/keys/system/', '').split('/'); - } - if (page.value.startsWith('registry/value/system/')) { - const path = page.value.replace('registry/value/system/', '').split('/'); - pageProps.value.xKey = path.pop(); - pageProps.value.scope = path; - } - } - nextTick(() => { scroll(el.value, { top: 0 }); }); @@ -271,8 +250,9 @@ export default defineComponent({ const emailNotConfigured = computed(() => instance.enableEmail && ($i.email == null || !$i.emailVerified)); - const onInfo = (info) => { - childInfo.value = info; + const pageChanged = (page) => { + if (page == null) return; + childInfo.value = page[symbols.PAGE_INFO]; }; return { @@ -285,7 +265,7 @@ export default defineComponent({ pageProps, component, emailNotConfigured, - onInfo, + pageChanged, childInfo, }; }, diff --git a/packages/client/src/pages/settings/instance-mute.vue b/packages/client/src/pages/settings/instance-mute.vue index 584a21e4bd..f84a209b60 100644 --- a/packages/client/src/pages/settings/instance-mute.vue +++ b/packages/client/src/pages/settings/instance-mute.vue @@ -47,11 +47,6 @@ export default defineComponent({ }, }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - - async created() { this.instanceMutes = this.$i.mutedInstances.join('\n'); }, diff --git a/packages/client/src/pages/settings/integration.vue b/packages/client/src/pages/settings/integration.vue index 3d8aaf8a6f..ca36c91665 100644 --- a/packages/client/src/pages/settings/integration.vue +++ b/packages/client/src/pages/settings/integration.vue @@ -1,45 +1,39 @@ <template> -<FormBase> - <div v-if="enableTwitterIntegration" class="_debobigegoItem"> - <div class="_debobigegoLabel"><i class="fab fa-twitter"></i> Twitter</div> - <div class="_debobigegoPanel" style="padding: 16px;"> - <p v-if="integrations.twitter">{{ $ts.connectedTo }}: <a :href="`https://twitter.com/${integrations.twitter.screenName}`" rel="nofollow noopener" target="_blank">@{{ integrations.twitter.screenName }}</a></p> - <MkButton v-if="integrations.twitter" danger @click="disconnectTwitter">{{ $ts.disconnectService }}</MkButton> - <MkButton v-else primary @click="connectTwitter">{{ $ts.connectService }}</MkButton> - </div> - </div> +<div class="_formRoot"> + <FormSection v-if="enableTwitterIntegration"> + <template #label><i class="fab fa-twitter"></i> Twitter</template> + <p v-if="integrations.twitter">{{ $ts.connectedTo }}: <a :href="`https://twitter.com/${integrations.twitter.screenName}`" rel="nofollow noopener" target="_blank">@{{ integrations.twitter.screenName }}</a></p> + <MkButton v-if="integrations.twitter" danger @click="disconnectTwitter">{{ $ts.disconnectService }}</MkButton> + <MkButton v-else primary @click="connectTwitter">{{ $ts.connectService }}</MkButton> + </FormSection> - <div v-if="enableDiscordIntegration" class="_debobigegoItem"> - <div class="_debobigegoLabel"><i class="fab fa-discord"></i> Discord</div> - <div class="_debobigegoPanel" style="padding: 16px;"> - <p v-if="integrations.discord">{{ $ts.connectedTo }}: <a :href="`https://discord.com/users/${integrations.discord.id}`" rel="nofollow noopener" target="_blank">@{{ integrations.discord.username }}#{{ integrations.discord.discriminator }}</a></p> - <MkButton v-if="integrations.discord" danger @click="disconnectDiscord">{{ $ts.disconnectService }}</MkButton> - <MkButton v-else primary @click="connectDiscord">{{ $ts.connectService }}</MkButton> - </div> - </div> + <FormSection v-if="enableDiscordIntegration"> + <template #label><i class="fab fa-discord"></i> Discord</template> + <p v-if="integrations.discord">{{ $ts.connectedTo }}: <a :href="`https://discord.com/users/${integrations.discord.id}`" rel="nofollow noopener" target="_blank">@{{ integrations.discord.username }}#{{ integrations.discord.discriminator }}</a></p> + <MkButton v-if="integrations.discord" danger @click="disconnectDiscord">{{ $ts.disconnectService }}</MkButton> + <MkButton v-else primary @click="connectDiscord">{{ $ts.connectService }}</MkButton> + </FormSection> - <div v-if="enableGithubIntegration" class="_debobigegoItem"> - <div class="_debobigegoLabel"><i class="fab fa-github"></i> GitHub</div> - <div class="_debobigegoPanel" style="padding: 16px;"> - <p v-if="integrations.github">{{ $ts.connectedTo }}: <a :href="`https://github.com/${integrations.github.login}`" rel="nofollow noopener" target="_blank">@{{ integrations.github.login }}</a></p> - <MkButton v-if="integrations.github" danger @click="disconnectGithub">{{ $ts.disconnectService }}</MkButton> - <MkButton v-else primary @click="connectGithub">{{ $ts.connectService }}</MkButton> - </div> - </div> -</FormBase> + <FormSection v-if="enableGithubIntegration"> + <template #label><i class="fab fa-github"></i> GitHub</template> + <p v-if="integrations.github">{{ $ts.connectedTo }}: <a :href="`https://github.com/${integrations.github.login}`" rel="nofollow noopener" target="_blank">@{{ integrations.github.login }}</a></p> + <MkButton v-if="integrations.github" danger @click="disconnectGithub">{{ $ts.disconnectService }}</MkButton> + <MkButton v-else primary @click="connectGithub">{{ $ts.connectService }}</MkButton> + </FormSection> +</div> </template> <script lang="ts"> import { defineComponent } from 'vue'; import { apiUrl } from '@/config'; -import FormBase from '@/components/debobigego/base.vue'; +import FormSection from '@/components/form/section.vue'; import MkButton from '@/components/ui/button.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; export default defineComponent({ components: { - FormBase, + FormSection, MkButton }, @@ -79,8 +73,6 @@ export default defineComponent({ }, mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - document.cookie = `igi=${this.$i.token}; path=/;` + ` max-age=31536000;` + (document.location.protocol.startsWith('https') ? ' secure' : ''); diff --git a/packages/client/src/pages/settings/menu.vue b/packages/client/src/pages/settings/menu.vue index 19d26be89a..6e38cd5dfe 100644 --- a/packages/client/src/pages/settings/menu.vue +++ b/packages/client/src/pages/settings/menu.vue @@ -21,7 +21,6 @@ import { defineComponent } from 'vue'; import FormTextarea from '@/components/form/textarea.vue'; import FormRadios from '@/components/form/radios.vue'; -import FormBase from '@/components/debobigego/base.vue'; import FormButton from '@/components/ui/button.vue'; import * as os from '@/os'; import { menuDef } from '@/menu'; @@ -31,7 +30,6 @@ import { unisonReload } from '@/scripts/unison-reload'; export default defineComponent({ components: { - FormBase, FormButton, FormTextarea, FormRadios, @@ -69,10 +67,6 @@ export default defineComponent({ }, }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async addItem() { const menu = Object.keys(this.menuDef).filter(k => !this.$store.state.menu.includes(k)); diff --git a/packages/client/src/pages/settings/mute-block.vue b/packages/client/src/pages/settings/mute-block.vue index 4f42d5e429..f4f9ebf8dd 100644 --- a/packages/client/src/pages/settings/mute-block.vue +++ b/packages/client/src/pages/settings/mute-block.vue @@ -1,5 +1,5 @@ <template> -<FormBase> +<div class="_formRoot"> <MkTab v-model="tab" style="margin-bottom: var(--margin);"> <option value="mute">{{ $ts.mutedUsers }}</option> <option value="block">{{ $ts.blockedUsers }}</option> @@ -8,11 +8,9 @@ <MkPagination :pagination="mutingPagination" class="muting"> <template #empty><FormInfo>{{ $ts.noUsers }}</FormInfo></template> <template v-slot="{items}"> - <FormGroup> - <FormLink v-for="mute in items" :key="mute.id" :to="userPage(mute.mutee)"> - <MkAcct :user="mute.mutee"/> - </FormLink> - </FormGroup> + <FormLink v-for="mute in items" :key="mute.id" :to="userPage(mute.mutee)"> + <MkAcct :user="mute.mutee"/> + </FormLink> </template> </MkPagination> </div> @@ -20,66 +18,43 @@ <MkPagination :pagination="blockingPagination" class="blocking"> <template #empty><FormInfo>{{ $ts.noUsers }}</FormInfo></template> <template v-slot="{items}"> - <FormGroup> - <FormLink v-for="block in items" :key="block.id" :to="userPage(block.blockee)"> - <MkAcct :user="block.blockee"/> - </FormLink> - </FormGroup> + <FormLink v-for="block in items" :key="block.id" :to="userPage(block.blockee)"> + <MkAcct :user="block.blockee"/> + </FormLink> </template> </MkPagination> </div> -</FormBase> +</div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MkPagination from '@/components/ui/pagination.vue'; import MkTab from '@/components/tab.vue'; -import FormInfo from '@/components/debobigego/info.vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; +import FormInfo from '@/components/ui/info.vue'; +import FormLink from '@/components/form/link.vue'; import { userPage } from '@/filters/user'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkPagination, - MkTab, - FormInfo, - FormBase, - FormGroup, - FormLink, - }, +let tab = $ref('mute'); - emits: ['info'], +const mutingPagination = { + endpoint: 'mute/list' as const, + limit: 10, +}; - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.muteAndBlock, - icon: 'fas fa-ban', - bg: 'var(--bg)', - }, - tab: 'mute', - mutingPagination: { - endpoint: 'mute/list', - limit: 10, - }, - blockingPagination: { - endpoint: 'blocking/list', - limit: 10, - }, - } - }, +const blockingPagination = { + endpoint: 'blocking/list' as const, + limit: 10, +}; - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.muteAndBlock, + icon: 'fas fa-ban', + bg: 'var(--bg)', }, - - methods: { - userPage - } }); </script> diff --git a/packages/client/src/pages/settings/notifications.vue b/packages/client/src/pages/settings/notifications.vue index d3ada0d7ef..12171530bb 100644 --- a/packages/client/src/pages/settings/notifications.vue +++ b/packages/client/src/pages/settings/notifications.vue @@ -13,7 +13,6 @@ import { defineComponent } from 'vue'; import FormButton from '@/components/ui/button.vue'; import FormLink from '@/components/form/link.vue'; -import FormBase from '@/components/debobigego/base.vue'; import FormSection from '@/components/form/section.vue'; import { notificationTypes } from 'misskey-js'; import * as os from '@/os'; @@ -21,7 +20,6 @@ import * as symbols from '@/symbols'; export default defineComponent({ components: { - FormBase, FormLink, FormButton, FormSection, @@ -39,10 +37,6 @@ export default defineComponent({ } }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { readAllUnreadNotes() { os.api('i/read-all-unread-notes'); diff --git a/packages/client/src/pages/settings/other.vue b/packages/client/src/pages/settings/other.vue index 0d9e60e21d..6e48cb58a6 100644 --- a/packages/client/src/pages/settings/other.vue +++ b/packages/client/src/pages/settings/other.vue @@ -1,30 +1,12 @@ <template> <div class="_formRoot"> - <FormLink to="/settings/update" class="_formBlock">Misskey Update</FormLink> - - <FormSwitch :value="$i.injectFeaturedNote" @update:modelValue="onChangeInjectFeaturedNote" class="_formBlock"> + <FormSwitch :value="$i.injectFeaturedNote" class="_formBlock" @update:modelValue="onChangeInjectFeaturedNote"> {{ $ts.showFeaturedNotesInTimeline }} </FormSwitch> - <FormSwitch v-model="reportError" class="_formBlock">{{ $ts.sendErrorReports }}<template #desc>{{ $ts.sendErrorReportsDescription }}</template></FormSwitch> + <FormSwitch v-model="reportError" class="_formBlock">{{ $ts.sendErrorReports }}<template #caption>{{ $ts.sendErrorReportsDescription }}</template></FormSwitch> <FormLink to="/settings/account-info" class="_formBlock">{{ $ts.accountInfo }}</FormLink> - <FormLink to="/settings/experimental-features" class="_formBlock">{{ $ts.experimentalFeatures }}</FormLink> - - <FormSection> - <template #label>{{ $ts.developer }}</template> - <FormSwitch v-model="debug" @update:modelValue="changeDebug" class="_formBlock"> - DEBUG MODE - </FormSwitch> - <template v-if="debug"> - <FormButton @click="taskmanager">Task Manager</FormButton> - </template> - </FormSection> - - <FormLink to="/settings/registry" class="_formBlock"><template #icon><i class="fas fa-cogs"></i></template>{{ $ts.registry }}</FormLink> - - <FormLink to="/bios" behavior="browser" class="_formBlock"><template #icon><i class="fas fa-door-open"></i></template>BIOS</FormLink> - <FormLink to="/cli" behavior="browser" class="_formBlock"><template #icon><i class="fas fa-door-open"></i></template>CLI</FormLink> <FormLink to="/settings/delete-account" class="_formBlock"><template #icon><i class="fas fa-exclamation-triangle"></i></template>{{ $ts.closeAccount }}</FormLink> </div> @@ -33,10 +15,8 @@ <script lang="ts"> import { defineAsyncComponent, defineComponent } from 'vue'; import FormSwitch from '@/components/form/switch.vue'; -import FormSelect from '@/components/form/select.vue'; import FormSection from '@/components/form/section.vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormButton from '@/components/debobigego/button.vue'; +import FormLink from '@/components/form/link.vue'; import * as os from '@/os'; import { debug } from '@/config'; import { defaultStore } from '@/store'; @@ -45,10 +25,8 @@ import * as symbols from '@/symbols'; export default defineComponent({ components: { - FormSelect, FormSection, FormSwitch, - FormButton, FormLink, }, @@ -69,10 +47,6 @@ export default defineComponent({ reportError: defaultStore.makeGetterSetter('reportError'), }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { changeDebug(v) { console.log(v); @@ -85,11 +59,6 @@ export default defineComponent({ injectFeaturedNote: v }); }, - - taskmanager() { - os.popup(import('@/components/taskmanager.vue'), { - }, {}, 'closed'); - }, } }); </script> diff --git a/packages/client/src/pages/settings/plugin.install.vue b/packages/client/src/pages/settings/plugin.install.vue index af93ef2930..d35d20d17a 100644 --- a/packages/client/src/pages/settings/plugin.install.vue +++ b/packages/client/src/pages/settings/plugin.install.vue @@ -1,15 +1,15 @@ <template> -<FormBase> - <FormInfo warn>{{ $ts._plugin.installWarn }}</FormInfo> +<div class="_formRoot"> + <FormInfo warn class="_formBlock">{{ $ts._plugin.installWarn }}</FormInfo> - <FormGroup> - <FormTextarea v-model="code" tall> - <span>{{ $ts.code }}</span> - </FormTextarea> - </FormGroup> + <FormTextarea v-model="code" tall class="_formBlock"> + <template #label>{{ $ts.code }}</template> + </FormTextarea> - <FormButton :disabled="code == null" primary inline @click="install"><i class="fas fa-check"></i> {{ $ts.install }}</FormButton> -</FormBase> + <div class="_formBlock"> + <FormButton :disabled="code == null" primary inline @click="install"><i class="fas fa-check"></i> {{ $ts.install }}</FormButton> + </div> +</div> </template> <script lang="ts"> @@ -18,13 +18,8 @@ import { AiScript, parse } from '@syuilo/aiscript'; import { serialize } from '@syuilo/aiscript/built/serializer'; import { v4 as uuid } from 'uuid'; import FormTextarea from '@/components/form/textarea.vue'; -import FormSelect from '@/components/form/select.vue'; -import FormRadios from '@/components/form/radios.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormInfo from '@/components/debobigego/info.vue'; +import FormButton from '@/components/ui/button.vue'; +import FormInfo from '@/components/ui/info.vue'; import * as os from '@/os'; import { ColdDeviceStorage } from '@/store'; import { unisonReload } from '@/scripts/unison-reload'; @@ -33,11 +28,6 @@ import * as symbols from '@/symbols'; export default defineComponent({ components: { FormTextarea, - FormSelect, - FormRadios, - FormBase, - FormGroup, - FormLink, FormButton, FormInfo, }, @@ -55,10 +45,6 @@ export default defineComponent({ } }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { installPlugin({ id, meta, ast, token }) { ColdDeviceStorage.set('plugins', ColdDeviceStorage.get('plugins').concat({ diff --git a/packages/client/src/pages/settings/plugin.manage.vue b/packages/client/src/pages/settings/plugin.manage.vue deleted file mode 100644 index 8b9021dc3d..0000000000 --- a/packages/client/src/pages/settings/plugin.manage.vue +++ /dev/null @@ -1,116 +0,0 @@ -<template> -<FormBase> - <FormGroup v-for="plugin in plugins" :key="plugin.id"> - <template #label><span style="display: flex;"><b>{{ plugin.name }}</b><span style="margin-left: auto;">v{{ plugin.version }}</span></span></template> - - <FormSwitch :modelValue="plugin.active" @update:modelValue="changeActive(plugin, $event)">{{ $ts.makeActive }}</FormSwitch> - <div class="_debobigegoItem"> - <div class="_debobigegoPanel" style="padding: 16px;"> - <div class="_keyValue"> - <div>{{ $ts.author }}:</div> - <div>{{ plugin.author }}</div> - </div> - <div class="_keyValue"> - <div>{{ $ts.description }}:</div> - <div>{{ plugin.description }}</div> - </div> - <div class="_keyValue"> - <div>{{ $ts.permission }}:</div> - <div>{{ plugin.permissions }}</div> - </div> - </div> - </div> - <div class="_debobigegoItem"> - <div class="_debobigegoPanel" style="padding: 16px;"> - <MkButton v-if="plugin.config" inline @click="config(plugin)"><i class="fas fa-cog"></i> {{ $ts.settings }}</MkButton> - <MkButton inline danger @click="uninstall(plugin)"><i class="fas fa-trash-alt"></i> {{ $ts.uninstall }}</MkButton> - </div> - </div> - </FormGroup> -</FormBase> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import MkButton from '@/components/ui/button.vue'; -import MkTextarea from '@/components/form/textarea.vue'; -import MkSelect from '@/components/form/select.vue'; -import FormSwitch from '@/components/form/switch.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import * as os from '@/os'; -import { ColdDeviceStorage } from '@/store'; -import * as symbols from '@/symbols'; -import { unisonReload } from '@/scripts/unison-reload'; - -export default defineComponent({ - components: { - MkButton, - MkTextarea, - MkSelect, - FormSwitch, - FormBase, - FormGroup, - }, - - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts._plugin.manage, - icon: 'fas fa-plug', - bg: 'var(--bg)', - }, - plugins: ColdDeviceStorage.get('plugins'), - } - }, - - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - - methods: { - uninstall(plugin) { - ColdDeviceStorage.set('plugins', this.plugins.filter(x => x.id !== plugin.id)); - os.success(); - this.$nextTick(() => { - unisonReload(); - }); - }, - - // TODO: この処理をstore側にactionとして移動し、設定画面を開くAiScriptAPIを実装できるようにする - async config(plugin) { - const config = plugin.config; - for (const key in plugin.configData) { - config[key].default = plugin.configData[key]; - } - - const { canceled, result } = await os.form(plugin.name, config); - if (canceled) return; - - const plugins = ColdDeviceStorage.get('plugins'); - plugins.find(p => p.id === plugin.id).configData = result; - ColdDeviceStorage.set('plugins', plugins); - - this.$nextTick(() => { - location.reload(); - }); - }, - - changeActive(plugin, active) { - const plugins = ColdDeviceStorage.get('plugins'); - plugins.find(p => p.id === plugin.id).active = active; - ColdDeviceStorage.set('plugins', plugins); - - this.$nextTick(() => { - location.reload(); - }); - } - }, -}); -</script> - -<style lang="scss" scoped> - -</style> diff --git a/packages/client/src/pages/settings/plugin.vue b/packages/client/src/pages/settings/plugin.vue index 50e53f459f..7a3ab9d152 100644 --- a/packages/client/src/pages/settings/plugin.vue +++ b/packages/client/src/pages/settings/plugin.vue @@ -1,23 +1,54 @@ <template> -<FormBase> +<div class="_formRoot"> <FormLink to="/settings/plugin/install"><template #icon><i class="fas fa-download"></i></template>{{ $ts._plugin.install }}</FormLink> - <FormLink to="/settings/plugin/manage"><template #icon><i class="fas fa-folder-open"></i></template>{{ $ts._plugin.manage }}<template #suffix>{{ plugins }}</template></FormLink> -</FormBase> + + <FormSection> + <template #label>{{ $ts.manage }}</template> + <div v-for="plugin in plugins" :key="plugin.id" class="_formBlock _panel" style="padding: 20px;"> + <span style="display: flex;"><b>{{ plugin.name }}</b><span style="margin-left: auto;">v{{ plugin.version }}</span></span> + + <FormSwitch class="_formBlock" :modelValue="plugin.active" @update:modelValue="changeActive(plugin, $event)">{{ $ts.makeActive }}</FormSwitch> + + <MkKeyValue class="_formBlock"> + <template #key>{{ $ts.author }}</template> + <template #value>{{ plugin.author }}</template> + </MkKeyValue> + <MkKeyValue class="_formBlock"> + <template #key>{{ $ts.description }}</template> + <template #value>{{ plugin.description }}</template> + </MkKeyValue> + <MkKeyValue class="_formBlock"> + <template #key>{{ $ts.permission }}</template> + <template #value>{{ plugin.permission }}</template> + </MkKeyValue> + + <div style="display: flex; gap: var(--margin); flex-wrap: wrap;"> + <MkButton v-if="plugin.config" inline @click="config(plugin)"><i class="fas fa-cog"></i> {{ $ts.settings }}</MkButton> + <MkButton inline danger @click="uninstall(plugin)"><i class="fas fa-trash-alt"></i> {{ $ts.uninstall }}</MkButton> + </div> + </div> + </FormSection> +</div> </template> <script lang="ts"> import { defineComponent } from 'vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormLink from '@/components/debobigego/link.vue'; +import FormLink from '@/components/form/link.vue'; +import FormSwitch from '@/components/form/switch.vue'; +import FormSection from '@/components/form/section.vue'; +import MkButton from '@/components/ui/button.vue'; +import MkKeyValue from '@/components/key-value.vue'; import * as os from '@/os'; import { ColdDeviceStorage } from '@/store'; import * as symbols from '@/symbols'; export default defineComponent({ components: { - FormBase, FormLink, + FormSwitch, + FormSection, + MkButton, + MkKeyValue, }, emits: ['info'], @@ -29,12 +60,47 @@ export default defineComponent({ icon: 'fas fa-plug', bg: 'var(--bg)', }, - plugins: ColdDeviceStorage.get('plugins').length, + plugins: ColdDeviceStorage.get('plugins'), } }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); + methods: { + uninstall(plugin) { + ColdDeviceStorage.set('plugins', this.plugins.filter(x => x.id !== plugin.id)); + os.success(); + this.$nextTick(() => { + unisonReload(); + }); + }, + + // TODO: この処理をstore側にactionとして移動し、設定画面を開くAiScriptAPIを実装できるようにする + async config(plugin) { + const config = plugin.config; + for (const key in plugin.configData) { + config[key].default = plugin.configData[key]; + } + + const { canceled, result } = await os.form(plugin.name, config); + if (canceled) return; + + const plugins = ColdDeviceStorage.get('plugins'); + plugins.find(p => p.id === plugin.id).configData = result; + ColdDeviceStorage.set('plugins', plugins); + + this.$nextTick(() => { + location.reload(); + }); + }, + + changeActive(plugin, active) { + const plugins = ColdDeviceStorage.get('plugins'); + plugins.find(p => p.id === plugin.id).active = active; + ColdDeviceStorage.set('plugins', plugins); + + this.$nextTick(() => { + location.reload(); + }); + } }, }); </script> diff --git a/packages/client/src/pages/settings/privacy.vue b/packages/client/src/pages/settings/privacy.vue index 78a0ea8b8d..dd13ba4bd0 100644 --- a/packages/client/src/pages/settings/privacy.vue +++ b/packages/client/src/pages/settings/privacy.vue @@ -47,8 +47,8 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import FormSwitch from '@/components/form/switch.vue'; import FormSelect from '@/components/form/select.vue'; import FormSection from '@/components/form/section.vue'; @@ -56,67 +56,39 @@ import FormGroup from '@/components/form/group.vue'; import * as os from '@/os'; import { defaultStore } from '@/store'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; +import { $i } from '@/account'; -export default defineComponent({ - components: { - FormSelect, - FormSection, - FormGroup, - FormSwitch, - }, - - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.privacy, - icon: 'fas fa-lock-open', - bg: 'var(--bg)', - }, - isLocked: false, - autoAcceptFollowed: false, - noCrawle: false, - isExplorable: false, - hideOnlineStatus: false, - publicReactions: false, - ffVisibility: 'public', - } - }, +let isLocked = $ref($i.isLocked); +let autoAcceptFollowed = $ref($i.autoAcceptFollowed); +let noCrawle = $ref($i.noCrawle); +let isExplorable = $ref($i.isExplorable); +let hideOnlineStatus = $ref($i.hideOnlineStatus); +let publicReactions = $ref($i.publicReactions); +let ffVisibility = $ref($i.ffVisibility); - computed: { - defaultNoteVisibility: defaultStore.makeGetterSetter('defaultNoteVisibility'), - defaultNoteLocalOnly: defaultStore.makeGetterSetter('defaultNoteLocalOnly'), - rememberNoteVisibility: defaultStore.makeGetterSetter('rememberNoteVisibility'), - keepCw: defaultStore.makeGetterSetter('keepCw'), - }, +let defaultNoteVisibility = $computed(defaultStore.makeGetterSetter('defaultNoteVisibility')); +let defaultNoteLocalOnly = $computed(defaultStore.makeGetterSetter('defaultNoteLocalOnly')); +let rememberNoteVisibility = $computed(defaultStore.makeGetterSetter('rememberNoteVisibility')); +let keepCw = $computed(defaultStore.makeGetterSetter('keepCw')); - created() { - this.isLocked = this.$i.isLocked; - this.autoAcceptFollowed = this.$i.autoAcceptFollowed; - this.noCrawle = this.$i.noCrawle; - this.isExplorable = this.$i.isExplorable; - this.hideOnlineStatus = this.$i.hideOnlineStatus; - this.publicReactions = this.$i.publicReactions; - this.ffVisibility = this.$i.ffVisibility; - }, +function save() { + os.api('i/update', { + isLocked: !!isLocked, + autoAcceptFollowed: !!autoAcceptFollowed, + noCrawle: !!noCrawle, + isExplorable: !!isExplorable, + hideOnlineStatus: !!hideOnlineStatus, + publicReactions: !!publicReactions, + ffVisibility: ffVisibility, + }); +} - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.privacy, + icon: 'fas fa-lock-open', + bg: 'var(--bg)', }, - - methods: { - save() { - os.api('i/update', { - isLocked: !!this.isLocked, - autoAcceptFollowed: !!this.autoAcceptFollowed, - noCrawle: !!this.noCrawle, - isExplorable: !!this.isExplorable, - hideOnlineStatus: !!this.hideOnlineStatus, - publicReactions: !!this.publicReactions, - ffVisibility: this.ffVisibility, - }); - } - } }); </script> diff --git a/packages/client/src/pages/settings/profile.vue b/packages/client/src/pages/settings/profile.vue index 2eaf9a9f83..f875146a2c 100644 --- a/packages/client/src/pages/settings/profile.vue +++ b/packages/client/src/pages/settings/profile.vue @@ -3,50 +3,50 @@ <div class="llvierxe" :style="{ backgroundImage: $i.bannerUrl ? `url(${ $i.bannerUrl })` : null }"> <div class="avatar _acrylic"> <MkAvatar class="avatar" :user="$i" :disable-link="true" @click="changeAvatar"/> - <MkButton primary class="avatarEdit" @click="changeAvatar">{{ $ts._profile.changeAvatar }}</MkButton> + <MkButton primary class="avatarEdit" @click="changeAvatar">{{ i18n.locale._profile.changeAvatar }}</MkButton> </div> - <MkButton primary class="bannerEdit" @click="changeBanner">{{ $ts._profile.changeBanner }}</MkButton> + <MkButton primary class="bannerEdit" @click="changeBanner">{{ i18n.locale._profile.changeBanner }}</MkButton> </div> - <FormInput v-model="name" :max="30" manual-save class="_formBlock"> - <template #label>{{ $ts._profile.name }}</template> + <FormInput v-model="profile.name" :max="30" manual-save class="_formBlock"> + <template #label>{{ i18n.locale._profile.name }}</template> </FormInput> - <FormTextarea v-model="description" :max="500" tall manual-save class="_formBlock"> - <template #label>{{ $ts._profile.description }}</template> - <template #caption>{{ $ts._profile.youCanIncludeHashtags }}</template> + <FormTextarea v-model="profile.description" :max="500" tall manual-save class="_formBlock"> + <template #label>{{ i18n.locale._profile.description }}</template> + <template #caption>{{ i18n.locale._profile.youCanIncludeHashtags }}</template> </FormTextarea> - <FormInput v-model="location" manual-save class="_formBlock"> - <template #label>{{ $ts.location }}</template> + <FormInput v-model="profile.location" manual-save class="_formBlock"> + <template #label>{{ i18n.locale.location }}</template> <template #prefix><i class="fas fa-map-marker-alt"></i></template> </FormInput> - <FormInput v-model="birthday" type="date" manual-save class="_formBlock"> - <template #label>{{ $ts.birthday }}</template> + <FormInput v-model="profile.birthday" type="date" manual-save class="_formBlock"> + <template #label>{{ i18n.locale.birthday }}</template> <template #prefix><i class="fas fa-birthday-cake"></i></template> </FormInput> - <FormSelect v-model="lang" class="_formBlock"> - <template #label>{{ $ts.language }}</template> + <FormSelect v-model="profile.lang" class="_formBlock"> + <template #label>{{ i18n.locale.language }}</template> <option v-for="x in langs" :key="x[0]" :value="x[0]">{{ x[1] }}</option> </FormSelect> <FormSlot> - <MkButton @click="editMetadata">{{ $ts._profile.metadataEdit }}</MkButton> - <template #caption>{{ $ts._profile.metadataDescription }}</template> + <MkButton @click="editMetadata">{{ i18n.locale._profile.metadataEdit }}</MkButton> + <template #caption>{{ i18n.locale._profile.metadataDescription }}</template> </FormSlot> - <FormSwitch v-model="isCat" class="_formBlock">{{ $ts.flagAsCat }}<template #caption>{{ $ts.flagAsCatDescription }}</template></FormSwitch> + <FormSwitch v-model="profile.isCat" class="_formBlock">{{ i18n.locale.flagAsCat }}<template #caption>{{ i18n.locale.flagAsCatDescription }}</template></FormSwitch> - <FormSwitch v-model="isBot" class="_formBlock">{{ $ts.flagAsBot }}<template #caption>{{ $ts.flagAsBotDescription }}</template></FormSwitch> + <FormSwitch v-model="profile.isBot" class="_formBlock">{{ i18n.locale.flagAsBot }}<template #caption>{{ i18n.locale.flagAsBotDescription }}</template></FormSwitch> - <FormSwitch v-model="alwaysMarkNsfw" class="_formBlock">{{ $ts.alwaysMarkSensitive }}</FormSwitch> + <FormSwitch v-model="profile.alwaysMarkNsfw" class="_formBlock">{{ i18n.locale.alwaysMarkSensitive }}</FormSwitch> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineComponent, reactive, watch } from 'vue'; import MkButton from '@/components/ui/button.vue'; import FormInput from '@/components/form/input.vue'; import FormTextarea from '@/components/form/textarea.vue'; @@ -57,198 +57,149 @@ import { host, langs } from '@/config'; import { selectFile } from '@/scripts/select-file'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; +import { $i } from '@/account'; -export default defineComponent({ - components: { - MkButton, - FormInput, - FormTextarea, - FormSwitch, - FormSelect, - FormSlot, - }, - - emits: ['info'], +const profile = reactive({ + name: $i.name, + description: $i.description, + location: $i.location, + birthday: $i.birthday, + lang: $i.lang, + isBot: $i.isBot, + isCat: $i.isCat, + alwaysMarkNsfw: $i.alwaysMarkNsfw, +}); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.profile, - icon: 'fas fa-user', - bg: 'var(--bg)', - }, - host, - langs, - name: null, - description: null, - birthday: null, - lang: null, - location: null, - fieldName0: null, - fieldValue0: null, - fieldName1: null, - fieldValue1: null, - fieldName2: null, - fieldValue2: null, - fieldName3: null, - fieldValue3: null, - avatarId: null, - bannerId: null, - isBot: false, - isCat: false, - alwaysMarkNsfw: false, - saving: false, - } - }, +const additionalFields = reactive({ + fieldName0: $i.fields[0] ? $i.fields[0].name : null, + fieldValue0: $i.fields[0] ? $i.fields[0].value : null, + fieldName1: $i.fields[1] ? $i.fields[1].name : null, + fieldValue1: $i.fields[1] ? $i.fields[1].value : null, + fieldName2: $i.fields[2] ? $i.fields[2].name : null, + fieldValue2: $i.fields[2] ? $i.fields[2].value : null, + fieldName3: $i.fields[3] ? $i.fields[3].name : null, + fieldValue3: $i.fields[3] ? $i.fields[3].value : null, +}); - created() { - this.name = this.$i.name; - this.description = this.$i.description; - this.location = this.$i.location; - this.birthday = this.$i.birthday; - this.lang = this.$i.lang; - this.avatarId = this.$i.avatarId; - this.bannerId = this.$i.bannerId; - this.isBot = this.$i.isBot; - this.isCat = this.$i.isCat; - this.alwaysMarkNsfw = this.$i.alwaysMarkNsfw; +watch(() => profile, () => { + save(); +}, { + deep: true, +}); - this.fieldName0 = this.$i.fields[0] ? this.$i.fields[0].name : null; - this.fieldValue0 = this.$i.fields[0] ? this.$i.fields[0].value : null; - this.fieldName1 = this.$i.fields[1] ? this.$i.fields[1].name : null; - this.fieldValue1 = this.$i.fields[1] ? this.$i.fields[1].value : null; - this.fieldName2 = this.$i.fields[2] ? this.$i.fields[2].name : null; - this.fieldValue2 = this.$i.fields[2] ? this.$i.fields[2].value : null; - this.fieldName3 = this.$i.fields[3] ? this.$i.fields[3].name : null; - this.fieldValue3 = this.$i.fields[3] ? this.$i.fields[3].value : null; +function save() { + os.apiWithDialog('i/update', { + name: profile.name || null, + description: profile.description || null, + location: profile.location || null, + birthday: profile.birthday || null, + lang: profile.lang || null, + isBot: !!profile.isBot, + isCat: !!profile.isCat, + alwaysMarkNsfw: !!profile.alwaysMarkNsfw, + }); +} - this.$watch('name', this.save); - this.$watch('description', this.save); - this.$watch('location', this.save); - this.$watch('birthday', this.save); - this.$watch('lang', this.save); - this.$watch('isBot', this.save); - this.$watch('isCat', this.save); - this.$watch('alwaysMarkNsfw', this.save); - }, +function changeAvatar(ev) { + selectFile(ev.currentTarget || ev.target, i18n.locale.avatar).then(async (file) => { + const i = await os.apiWithDialog('i/update', { + avatarId: file.id, + }); + $i.avatarId = i.avatarId; + $i.avatarUrl = i.avatarUrl; + }); +} - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, +function changeBanner(ev) { + selectFile(ev.currentTarget || ev.target, i18n.locale.banner).then(async (file) => { + const i = await os.apiWithDialog('i/update', { + bannerId: file.id, + }); + $i.bannerId = i.bannerId; + $i.bannerUrl = i.bannerUrl; + }); +} - methods: { - changeAvatar(e) { - selectFile(e.currentTarget || e.target, this.$ts.avatar).then(file => { - os.api('i/update', { - avatarId: file.id, - }); - }); +async function editMetadata() { + const { canceled, result } = await os.form(i18n.locale._profile.metadata, { + fieldName0: { + type: 'string', + label: i18n.locale._profile.metadataLabel + ' 1', + default: additionalFields.fieldName0, }, - - changeBanner(e) { - selectFile(e.currentTarget || e.target, this.$ts.banner).then(file => { - os.api('i/update', { - bannerId: file.id, - }); - }); + fieldValue0: { + type: 'string', + label: i18n.locale._profile.metadataContent + ' 1', + default: additionalFields.fieldValue0, }, + fieldName1: { + type: 'string', + label: i18n.locale._profile.metadataLabel + ' 2', + default: additionalFields.fieldName1, + }, + fieldValue1: { + type: 'string', + label: i18n.locale._profile.metadataContent + ' 2', + default: additionalFields.fieldValue1, + }, + fieldName2: { + type: 'string', + label: i18n.locale._profile.metadataLabel + ' 3', + default: additionalFields.fieldName2, + }, + fieldValue2: { + type: 'string', + label: i18n.locale._profile.metadataContent + ' 3', + default: additionalFields.fieldValue2, + }, + fieldName3: { + type: 'string', + label: i18n.locale._profile.metadataLabel + ' 4', + default: additionalFields.fieldName3, + }, + fieldValue3: { + type: 'string', + label: i18n.locale._profile.metadataContent + ' 4', + default: additionalFields.fieldValue3, + }, + }); + if (canceled) return; - async editMetadata() { - const { canceled, result } = await os.form(this.$ts._profile.metadata, { - fieldName0: { - type: 'string', - label: this.$ts._profile.metadataLabel + ' 1', - default: this.fieldName0, - }, - fieldValue0: { - type: 'string', - label: this.$ts._profile.metadataContent + ' 1', - default: this.fieldValue0, - }, - fieldName1: { - type: 'string', - label: this.$ts._profile.metadataLabel + ' 2', - default: this.fieldName1, - }, - fieldValue1: { - type: 'string', - label: this.$ts._profile.metadataContent + ' 2', - default: this.fieldValue1, - }, - fieldName2: { - type: 'string', - label: this.$ts._profile.metadataLabel + ' 3', - default: this.fieldName2, - }, - fieldValue2: { - type: 'string', - label: this.$ts._profile.metadataContent + ' 3', - default: this.fieldValue2, - }, - fieldName3: { - type: 'string', - label: this.$ts._profile.metadataLabel + ' 4', - default: this.fieldName3, - }, - fieldValue3: { - type: 'string', - label: this.$ts._profile.metadataContent + ' 4', - default: this.fieldValue3, - }, - }); - if (canceled) return; - - this.fieldName0 = result.fieldName0; - this.fieldValue0 = result.fieldValue0; - this.fieldName1 = result.fieldName1; - this.fieldValue1 = result.fieldValue1; - this.fieldName2 = result.fieldName2; - this.fieldValue2 = result.fieldValue2; - this.fieldName3 = result.fieldName3; - this.fieldValue3 = result.fieldValue3; - - const fields = [ - { name: this.fieldName0, value: this.fieldValue0 }, - { name: this.fieldName1, value: this.fieldValue1 }, - { name: this.fieldName2, value: this.fieldValue2 }, - { name: this.fieldName3, value: this.fieldValue3 }, - ]; + additionalFields.fieldName0 = result.fieldName0; + additionalFields.fieldValue0 = result.fieldValue0; + additionalFields.fieldName1 = result.fieldName1; + additionalFields.fieldValue1 = result.fieldValue1; + additionalFields.fieldName2 = result.fieldName2; + additionalFields.fieldValue2 = result.fieldValue2; + additionalFields.fieldName3 = result.fieldName3; + additionalFields.fieldValue3 = result.fieldValue3; - os.api('i/update', { - fields, - }).then(i => { - os.success(); - }).catch(err => { - os.alert({ - type: 'error', - text: err.id - }); - }); - }, + const fields = [ + { name: additionalFields.fieldName0, value: additionalFields.fieldValue0 }, + { name: additionalFields.fieldName1, value: additionalFields.fieldValue1 }, + { name: additionalFields.fieldName2, value: additionalFields.fieldValue2 }, + { name: additionalFields.fieldName3, value: additionalFields.fieldValue3 }, + ]; - save() { - this.saving = true; + os.api('i/update', { + fields, + }).then(i => { + os.success(); + }).catch(err => { + os.alert({ + type: 'error', + text: err.id + }); + }); +} - os.apiWithDialog('i/update', { - name: this.name || null, - description: this.description || null, - location: this.location || null, - birthday: this.birthday || null, - lang: this.lang || null, - isBot: !!this.isBot, - isCat: !!this.isCat, - alwaysMarkNsfw: !!this.alwaysMarkNsfw, - }).then(i => { - this.saving = false; - this.$i.avatarId = i.avatarId; - this.$i.avatarUrl = i.avatarUrl; - this.$i.bannerId = i.bannerId; - this.$i.bannerUrl = i.bannerUrl; - }).catch(err => { - this.saving = false; - }); - }, - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.profile, + icon: 'fas fa-user', + bg: 'var(--bg)', + }, }); </script> diff --git a/packages/client/src/pages/settings/reaction.vue b/packages/client/src/pages/settings/reaction.vue index 0d4db46936..e5b1189947 100644 --- a/packages/client/src/pages/settings/reaction.vue +++ b/packages/client/src/pages/settings/reaction.vue @@ -100,10 +100,6 @@ export default defineComponent({ } }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { save() { this.$store.set('reactions', this.reactions); diff --git a/packages/client/src/pages/settings/registry.keys.vue b/packages/client/src/pages/settings/registry.keys.vue deleted file mode 100644 index 89953ebea1..0000000000 --- a/packages/client/src/pages/settings/registry.keys.vue +++ /dev/null @@ -1,114 +0,0 @@ -<template> -<FormBase> - <FormGroup> - <FormKeyValueView> - <template #key>{{ $ts._registry.domain }}</template> - <template #value>{{ $ts.system }}</template> - </FormKeyValueView> - <FormKeyValueView> - <template #key>{{ $ts._registry.scope }}</template> - <template #value>{{ scope.join('/') }}</template> - </FormKeyValueView> - </FormGroup> - - <FormGroup v-if="keys"> - <template #label>{{ $ts._registry.keys }}</template> - <FormLink v-for="key in keys" :to="`/settings/registry/value/system/${scope.join('/')}/${key[0]}`" class="_monospace">{{ key[0] }}<template #suffix>{{ key[1].toUpperCase() }}</template></FormLink> - </FormGroup> - - <FormButton primary @click="createKey">{{ $ts._registry.createKey }}</FormButton> -</FormBase> -</template> - -<script lang="ts"> -import { defineAsyncComponent, defineComponent } from 'vue'; -import * as JSON5 from 'json5'; -import FormSwitch from '@/components/form/switch.vue'; -import FormSelect from '@/components/form/select.vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormKeyValueView from '@/components/debobigego/key-value-view.vue'; -import * as os from '@/os'; -import * as symbols from '@/symbols'; - -export default defineComponent({ - components: { - FormBase, - FormSelect, - FormSwitch, - FormButton, - FormLink, - FormGroup, - FormKeyValueView, - }, - - props: { - scope: { - required: true - } - }, - - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.registry, - icon: 'fas fa-cogs', - bg: 'var(--bg)', - }, - keys: null, - } - }, - - watch: { - scope() { - this.fetch(); - } - }, - - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - this.fetch(); - }, - - methods: { - fetch() { - os.api('i/registry/keys-with-type', { - scope: this.scope - }).then(keys => { - this.keys = Object.entries(keys).sort((a, b) => a[0].localeCompare(b[0])); - }); - }, - - async createKey() { - const { canceled, result } = await os.form(this.$ts._registry.createKey, { - key: { - type: 'string', - label: this.$ts._registry.key, - }, - value: { - type: 'string', - multiline: true, - label: this.$ts.value, - }, - scope: { - type: 'string', - label: this.$ts._registry.scope, - default: this.scope.join('/') - } - }); - if (canceled) return; - os.apiWithDialog('i/registry/set', { - scope: result.scope.split('/'), - key: result.key, - value: JSON5.parse(result.value), - }).then(() => { - this.fetch(); - }); - } - } -}); -</script> diff --git a/packages/client/src/pages/settings/registry.value.vue b/packages/client/src/pages/settings/registry.value.vue deleted file mode 100644 index 6acd3f6048..0000000000 --- a/packages/client/src/pages/settings/registry.value.vue +++ /dev/null @@ -1,147 +0,0 @@ -<template> -<FormBase> - <FormInfo warn>{{ $ts.editTheseSettingsMayBreakAccount }}</FormInfo> - - <template v-if="value"> - <FormGroup> - <FormKeyValueView> - <template #key>{{ $ts._registry.domain }}</template> - <template #value>{{ $ts.system }}</template> - </FormKeyValueView> - <FormKeyValueView> - <template #key>{{ $ts._registry.scope }}</template> - <template #value>{{ scope.join('/') }}</template> - </FormKeyValueView> - <FormKeyValueView> - <template #key>{{ $ts._registry.key }}</template> - <template #value>{{ xKey }}</template> - </FormKeyValueView> - </FormGroup> - - <FormGroup> - <FormTextarea v-model="valueForEditor" tall class="_monospace" style="tab-size: 2;"> - <span>{{ $ts.value }} (JSON)</span> - </FormTextarea> - <FormButton primary @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> - </FormGroup> - - <FormKeyValueView> - <template #key>{{ $ts.updatedAt }}</template> - <template #value><MkTime :time="value.updatedAt" mode="detail"/></template> - </FormKeyValueView> - - <FormButton danger @click="del"><i class="fas fa-trash"></i> {{ $ts.delete }}</FormButton> - </template> -</FormBase> -</template> - -<script lang="ts"> -import { defineAsyncComponent, defineComponent } from 'vue'; -import * as JSON5 from 'json5'; -import FormInfo from '@/components/debobigego/info.vue'; -import FormSwitch from '@/components/form/switch.vue'; -import FormSelect from '@/components/form/select.vue'; -import FormTextarea from '@/components/form/textarea.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormKeyValueView from '@/components/debobigego/key-value-view.vue'; -import * as os from '@/os'; -import * as symbols from '@/symbols'; - -export default defineComponent({ - components: { - FormInfo, - FormBase, - FormSelect, - FormSwitch, - FormButton, - FormTextarea, - FormGroup, - FormKeyValueView, - }, - - props: { - scope: { - required: true - }, - xKey: { - required: true - }, - }, - - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.registry, - icon: 'fas fa-cogs', - bg: 'var(--bg)', - }, - value: null, - valueForEditor: null, - } - }, - - watch: { - key() { - this.fetch(); - }, - }, - - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - this.fetch(); - }, - - methods: { - fetch() { - os.api('i/registry/get-detail', { - scope: this.scope, - key: this.xKey - }).then(value => { - this.value = value; - this.valueForEditor = JSON5.stringify(this.value.value, null, '\t'); - }); - }, - - save() { - try { - JSON5.parse(this.valueForEditor); - } catch (e) { - os.alert({ - type: 'error', - text: this.$ts.invalidValue - }); - return; - } - - os.confirm({ - type: 'warning', - text: this.$ts.saveConfirm, - }).then(({ canceled }) => { - if (canceled) return; - os.apiWithDialog('i/registry/set', { - scope: this.scope, - key: this.xKey, - value: JSON5.parse(this.valueForEditor) - }); - }); - }, - - del() { - os.confirm({ - type: 'warning', - text: this.$ts.deleteConfirm, - }).then(({ canceled }) => { - if (canceled) return; - os.apiWithDialog('i/registry/remove', { - scope: this.scope, - key: this.xKey - }); - }); - } - } -}); -</script> diff --git a/packages/client/src/pages/settings/registry.vue b/packages/client/src/pages/settings/registry.vue deleted file mode 100644 index 6faff5d2a4..0000000000 --- a/packages/client/src/pages/settings/registry.vue +++ /dev/null @@ -1,90 +0,0 @@ -<template> -<FormBase> - <FormGroup v-if="scopes"> - <template #label>{{ $ts.system }}</template> - <FormLink v-for="scope in scopes" :to="`/settings/registry/keys/system/${scope.join('/')}`" class="_monospace">{{ scope.join('/') }}</FormLink> - </FormGroup> - <FormButton primary @click="createKey">{{ $ts._registry.createKey }}</FormButton> -</FormBase> -</template> - -<script lang="ts"> -import { defineAsyncComponent, defineComponent } from 'vue'; -import * as JSON5 from 'json5'; -import FormSwitch from '@/components/form/switch.vue'; -import FormSelect from '@/components/form/select.vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormKeyValueView from '@/components/debobigego/key-value-view.vue'; -import * as os from '@/os'; -import * as symbols from '@/symbols'; - -export default defineComponent({ - components: { - FormBase, - FormSelect, - FormSwitch, - FormButton, - FormLink, - FormGroup, - FormKeyValueView, - }, - - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.registry, - icon: 'fas fa-cogs', - bg: 'var(--bg)', - }, - scopes: null, - } - }, - - created() { - this.fetch(); - }, - - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - - methods: { - fetch() { - os.api('i/registry/scopes').then(scopes => { - this.scopes = scopes.slice().sort((a, b) => a.join('/').localeCompare(b.join('/'))); - }); - }, - - async createKey() { - const { canceled, result } = await os.form(this.$ts._registry.createKey, { - key: { - type: 'string', - label: this.$ts._registry.key, - }, - value: { - type: 'string', - multiline: true, - label: this.$ts.value, - }, - scope: { - type: 'string', - label: this.$ts._registry.scope, - } - }); - if (canceled) return; - os.apiWithDialog('i/registry/set', { - scope: result.scope.split('/'), - key: result.key, - value: JSON5.parse(result.value), - }).then(() => { - this.fetch(); - }); - } - } -}); -</script> diff --git a/packages/client/src/pages/settings/security.vue b/packages/client/src/pages/settings/security.vue index 069f9d964d..6fb3f1c413 100644 --- a/packages/client/src/pages/settings/security.vue +++ b/packages/client/src/pages/settings/security.vue @@ -12,7 +12,7 @@ <FormSection> <template #label>{{ $ts.signinHistory }}</template> - <FormPagination :pagination="pagination"> + <MkPagination :pagination="pagination"> <template v-slot="{items}"> <div> <div v-for="item in items" :key="item.id" v-panel class="timnmucd"> @@ -25,7 +25,7 @@ </div> </div> </template> - </FormPagination> + </MkPagination> </FormSection> <FormSection> @@ -40,10 +40,9 @@ <script lang="ts"> import { defineComponent } from 'vue'; import FormSection from '@/components/form/section.vue'; -import FormLink from '@/components/debobigego/link.vue'; import FormSlot from '@/components/form/slot.vue'; import FormButton from '@/components/ui/button.vue'; -import FormPagination from '@/components/form/pagination.vue'; +import MkPagination from '@/components/ui/pagination.vue'; import X2fa from './2fa.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; @@ -51,9 +50,8 @@ import * as symbols from '@/symbols'; export default defineComponent({ components: { FormSection, - FormLink, FormButton, - FormPagination, + MkPagination, FormSlot, X2fa, }, @@ -68,16 +66,12 @@ export default defineComponent({ bg: 'var(--bg)', }, pagination: { - endpoint: 'i/signin-history', + endpoint: 'i/signin-history' as const, limit: 5, }, } }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async change() { const { canceled: canceled1, result: currentPassword } = await os.inputText({ diff --git a/packages/client/src/pages/settings/sounds.vue b/packages/client/src/pages/settings/sounds.vue index 0977dd8322..490a1b5514 100644 --- a/packages/client/src/pages/settings/sounds.vue +++ b/packages/client/src/pages/settings/sounds.vue @@ -94,12 +94,6 @@ export default defineComponent({ this.sounds.chatBg = ColdDeviceStorage.get('sound_chatBg'); this.sounds.antenna = ColdDeviceStorage.get('sound_antenna'); this.sounds.channel = ColdDeviceStorage.get('sound_channel'); - this.sounds.reversiPutBlack = ColdDeviceStorage.get('sound_reversiPutBlack'); - this.sounds.reversiPutWhite = ColdDeviceStorage.get('sound_reversiPutWhite'); - }, - - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); }, methods: { diff --git a/packages/client/src/pages/settings/theme.install.vue b/packages/client/src/pages/settings/theme.install.vue index c3e531afb2..e2a3f042b9 100644 --- a/packages/client/src/pages/settings/theme.install.vue +++ b/packages/client/src/pages/settings/theme.install.vue @@ -1,105 +1,79 @@ <template> -<FormBase> - <FormGroup> - <FormTextarea v-model="installThemeCode"> - <span>{{ $ts._theme.code }}</span> - </FormTextarea> - <FormButton :disabled="installThemeCode == null" inline @click="() => preview(installThemeCode)"><i class="fas fa-eye"></i> {{ $ts.preview }}</FormButton> - </FormGroup> +<div class="_formRoot"> + <FormTextarea v-model="installThemeCode" class="_formBlock"> + <template #label>{{ i18n.locale._theme.code }}</template> + </FormTextarea> - <FormButton :disabled="installThemeCode == null" primary inline @click="() => install(installThemeCode)"><i class="fas fa-check"></i> {{ $ts.install }}</FormButton> -</FormBase> + <div class="_formBlock" style="display: flex; gap: var(--margin); flex-wrap: wrap;"> + <FormButton :disabled="installThemeCode == null" inline @click="() => preview(installThemeCode)"><i class="fas fa-eye"></i> {{ i18n.locale.preview }}</FormButton> + <FormButton :disabled="installThemeCode == null" primary inline @click="() => install(installThemeCode)"><i class="fas fa-check"></i> {{ i18n.locale.install }}</FormButton> + </div> +</div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import * as JSON5 from 'json5'; import FormTextarea from '@/components/form/textarea.vue'; -import FormSelect from '@/components/form/select.vue'; -import FormRadios from '@/components/form/radios.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormButton from '@/components/debobigego/button.vue'; +import FormButton from '@/components/ui/button.vue'; import { applyTheme, validateTheme } from '@/scripts/theme'; import * as os from '@/os'; -import { ColdDeviceStorage } from '@/store'; import { addTheme, getThemes } from '@/theme-store'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormTextarea, - FormSelect, - FormRadios, - FormBase, - FormGroup, - FormLink, - FormButton, - }, - - emits: ['info'], +let installThemeCode = $ref(null); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts._theme.install, - icon: 'fas fa-download', - bg: 'var(--bg)', - }, - installThemeCode: null, - } - }, - - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, +function parseThemeCode(code: string) { + let theme; - methods: { - parseThemeCode(code) { - let theme; + try { + theme = JSON5.parse(code); + } catch (e) { + os.alert({ + type: 'error', + text: i18n.locale._theme.invalid + }); + return false; + } + if (!validateTheme(theme)) { + os.alert({ + type: 'error', + text: i18n.locale._theme.invalid + }); + return false; + } + if (getThemes().some(t => t.id === theme.id)) { + os.alert({ + type: 'info', + text: i18n.locale._theme.alreadyInstalled + }); + return false; + } - try { - theme = JSON5.parse(code); - } catch (e) { - os.alert({ - type: 'error', - text: this.$ts._theme.invalid - }); - return false; - } - if (!validateTheme(theme)) { - os.alert({ - type: 'error', - text: this.$ts._theme.invalid - }); - return false; - } - if (getThemes().some(t => t.id === theme.id)) { - os.alert({ - type: 'info', - text: this.$ts._theme.alreadyInstalled - }); - return false; - } + return theme; +} - return theme; - }, +function preview(code: string): void { + const theme = parseThemeCode(code); + if (theme) applyTheme(theme, false); +} - preview(code) { - const theme = this.parseThemeCode(code); - if (theme) applyTheme(theme, false); - }, +async function install(code: string): Promise<void> { + const theme = parseThemeCode(code); + if (!theme) return; + await addTheme(theme); + os.alert({ + type: 'success', + text: i18n.t('_theme.installed', { name: theme.name }) + }); +} - async install(code) { - const theme = this.parseThemeCode(code); - if (!theme) return; - await addTheme(theme); - os.alert({ - type: 'success', - text: this.$t('_theme.installed', { name: theme.name }) - }); - }, - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale._theme.install, + icon: 'fas fa-download', + bg: 'var(--bg)', + }, }); </script> diff --git a/packages/client/src/pages/settings/theme.manage.vue b/packages/client/src/pages/settings/theme.manage.vue index c605b1eb64..a1e849b540 100644 --- a/packages/client/src/pages/settings/theme.manage.vue +++ b/packages/client/src/pages/settings/theme.manage.vue @@ -30,9 +30,6 @@ import { defineComponent } from 'vue'; import * as JSON5 from 'json5'; import FormTextarea from '@/components/form/textarea.vue'; import FormSelect from '@/components/form/select.vue'; -import FormRadios from '@/components/form/radios.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; import FormInput from '@/components/form/input.vue'; import FormButton from '@/components/ui/button.vue'; import { Theme, builtinThemes } from '@/scripts/theme'; @@ -46,9 +43,6 @@ export default defineComponent({ components: { FormTextarea, FormSelect, - FormRadios, - FormBase, - FormGroup, FormInput, FormButton, }, @@ -84,10 +78,6 @@ export default defineComponent({ }, }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { copyThemeCode() { copyToClipboard(this.selectedThemeCode); diff --git a/packages/client/src/pages/settings/theme.vue b/packages/client/src/pages/settings/theme.vue index 6c88b65699..658e36ec05 100644 --- a/packages/client/src/pages/settings/theme.vue +++ b/packages/client/src/pages/settings/theme.vue @@ -163,10 +163,6 @@ export default defineComponent({ location.reload(); }); - onMounted(() => { - emit('info', INFO); - }); - onActivated(() => { fetchThemes().then(() => { installedThemes.value = getThemes(); diff --git a/packages/client/src/pages/settings/update.vue b/packages/client/src/pages/settings/update.vue deleted file mode 100644 index e0d8f6d15c..0000000000 --- a/packages/client/src/pages/settings/update.vue +++ /dev/null @@ -1,95 +0,0 @@ -<template> -<FormBase> - <template v-if="meta"> - <FormInfo v-if="version === meta.version">{{ $ts.youAreRunningUpToDateClient }}</FormInfo> - <FormInfo v-else warn>{{ $ts.newVersionOfClientAvailable }}</FormInfo> - </template> - <FormGroup> - <template #label>{{ instanceName }}</template> - <FormKeyValueView> - <template #key>{{ $ts.currentVersion }}</template> - <template #value>{{ version }}</template> - </FormKeyValueView> - <FormKeyValueView> - <template #key>{{ $ts.latestVersion }}</template> - <template v-if="meta" #value>{{ meta.version }}</template> - <template v-else #value><MkEllipsis/></template> - </FormKeyValueView> - </FormGroup> - <FormGroup> - <template #label>Misskey</template> - <FormKeyValueView> - <template #key>{{ $ts.latestVersion }}</template> - <template v-if="releases" #value>{{ releases[0].tag_name }}</template> - <template v-else #value><MkEllipsis/></template> - </FormKeyValueView> - <template v-if="releases" #caption><MkTime :time="releases[0].published_at" mode="detail"/></template> - </FormGroup> -</FormBase> -</template> - -<script lang="ts"> -import { defineAsyncComponent, defineComponent } from 'vue'; -import FormSwitch from '@/components/form/switch.vue'; -import FormSelect from '@/components/form/select.vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormKeyValueView from '@/components/debobigego/key-value-view.vue'; -import FormInfo from '@/components/debobigego/info.vue'; -import * as os from '@/os'; -import { version, instanceName } from '@/config'; -import * as symbols from '@/symbols'; - -export default defineComponent({ - components: { - FormBase, - FormSelect, - FormSwitch, - FormButton, - FormLink, - FormGroup, - FormKeyValueView, - FormInfo, - }, - - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: 'Misskey Update', - icon: 'fas fa-sync-alt', - bg: 'var(--bg)', - }, - version, - instanceName, - releases: null, - meta: null - } - }, - - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - - os.api('meta', { - detail: false - }).then(meta => { - this.meta = meta; - localStorage.setItem('v', meta.version); - }); - - fetch('https://api.github.com/repos/misskey-dev/misskey/releases', { - method: 'GET', - }) - .then(res => res.json()) - .then(res => { - this.releases = res; - }); - }, - - methods: { - } -}); -</script> diff --git a/packages/client/src/pages/settings/word-mute.vue b/packages/client/src/pages/settings/word-mute.vue index 068f88740a..19980dea14 100644 --- a/packages/client/src/pages/settings/word-mute.vue +++ b/packages/client/src/pages/settings/word-mute.vue @@ -31,7 +31,6 @@ <script lang="ts"> import { defineComponent } from 'vue'; import FormTextarea from '@/components/form/textarea.vue'; -import FormBase from '@/components/debobigego/base.vue'; import MkKeyValue from '@/components/key-value.vue'; import MkButton from '@/components/ui/button.vue'; import MkInfo from '@/components/ui/info.vue'; @@ -42,7 +41,6 @@ import * as symbols from '@/symbols'; export default defineComponent({ components: { - FormBase, MkButton, FormTextarea, MkKeyValue, @@ -89,10 +87,6 @@ export default defineComponent({ this.hardWordMutedNotesCount = (await os.api('i/get-word-muted-notes-count', {})).count; }, - mounted() { - this.$emit('info', this[symbols.PAGE_INFO]); - }, - methods: { async save() { this.$store.set('mutedWords', this.softMutedWords.trim().split('\n').map(x => x.trim().split(' '))); diff --git a/packages/client/src/pages/share.vue b/packages/client/src/pages/share.vue index bdd8500ee4..5df6256fb2 100644 --- a/packages/client/src/pages/share.vue +++ b/packages/client/src/pages/share.vue @@ -169,7 +169,7 @@ export default defineComponent({ window.close(); // 閉じなければ100ms後タイムラインに - setTimeout(() => { + window.setTimeout(() => { this.$router.push('/'); }, 100); } diff --git a/packages/client/src/pages/signup-complete.vue b/packages/client/src/pages/signup-complete.vue index 89375e05d2..a10af1a4cc 100644 --- a/packages/client/src/pages/signup-complete.vue +++ b/packages/client/src/pages/signup-complete.vue @@ -1,50 +1,36 @@ <template> <div> - {{ $ts.processing }} + {{ i18n.locale.processing }} </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted } from 'vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { login } from '@/account'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { +const props = defineProps<{ + code: string; +}>(); - }, - - props: { - code: { - type: String, - required: true - } - }, - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.signup, - icon: 'fas fa-user' - }, - } - }, +onMounted(async () => { + await os.alert({ + type: 'info', + text: i18n.t('clickToFinishEmailVerification', { ok: i18n.locale.gotIt }), + }); + const res = await os.apiWithDialog('signup-pending', { + code: props.code, + }); + login(res.i, '/'); +}); - async mounted() { - await os.alert({ - type: 'info', - text: this.$t('clickToFinishEmailVerification', { ok: this.$ts.gotIt }), - }); - const res = await os.apiWithDialog('signup-pending', { - code: this.code, - }); - login(res.i, '/'); +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.signup, + icon: 'fas fa-user', }, - - methods: { - - } }); </script> diff --git a/packages/client/src/pages/tag.vue b/packages/client/src/pages/tag.vue index a0c8367849..045f1ef259 100644 --- a/packages/client/src/pages/tag.vue +++ b/packages/client/src/pages/tag.vue @@ -1,46 +1,31 @@ <template> <div class="_section"> - <XNotes ref="notes" class="_content" :pagination="pagination"/> + <XNotes class="_content" :pagination="pagination"/> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; import XNotes from '@/components/notes.vue'; import * as symbols from '@/symbols'; -export default defineComponent({ - components: { - XNotes - }, +const props = defineProps<{ + tag: string; +}>(); - props: { - tag: { - type: String, - required: true - } - }, +const pagination = { + endpoint: 'notes/search-by-tag' as const, + limit: 10, + params: computed(() => ({ + tag: props.tag, + })), +}; - data() { - return { - [symbols.PAGE_INFO]: { - title: this.tag, - icon: 'fas fa-hashtag' - }, - pagination: { - endpoint: 'notes/search-by-tag', - limit: 10, - params: () => ({ - tag: this.tag, - }) - }, - }; - }, - - watch: { - tag() { - (this.$refs.notes as any).reload(); - } - }, +defineExpose({ + [symbols.PAGE_INFO]: computed(() => ({ + title: props.tag, + icon: 'fas fa-hashtag', + bg: 'var(--bg)', + })), }); </script> diff --git a/packages/client/src/pages/test.vue b/packages/client/src/pages/test.vue deleted file mode 100644 index d05e00d374..0000000000 --- a/packages/client/src/pages/test.vue +++ /dev/null @@ -1,260 +0,0 @@ -<template> -<div class="_section"> - <div class="_content"> - <div class="_card _gap"> - <div class="_title">Dialog</div> - <div class="_content"> - <MkInput v-model="dialogTitle"> - <template #label>Title</template> - </MkInput> - <MkInput v-model="dialogBody"> - <template #label>Body</template> - </MkInput> - <MkRadio v-model="dialogType" value="info">Info</MkRadio> - <MkRadio v-model="dialogType" value="success">Success</MkRadio> - <MkRadio v-model="dialogType" value="warning">Warn</MkRadio> - <MkRadio v-model="dialogType" value="error">Error</MkRadio> - <MkSwitch v-model="dialogCancel"> - <span>With cancel button</span> - </MkSwitch> - <MkSwitch v-model="dialogCancelByBgClick"> - <span>Can cancel by modal bg click</span> - </MkSwitch> - <MkSwitch v-model="dialogInput"> - <span>With input field</span> - </MkSwitch> - <MkButton @click="showDialog()">Show</MkButton> - </div> - <div class="_content"> - <code>Result: {{ dialogResult }}</code> - </div> - </div> - - <div class="_card _gap"> - <div class="_title">Form</div> - <div class="_content"> - <MkInput v-model="formTitle"> - <template #label>Title</template> - </MkInput> - <MkTextarea v-model="formForm"> - <template #label>Form</template> - </MkTextarea> - <MkButton @click="form()">Show</MkButton> - </div> - <div class="_content"> - <code>Result: {{ formResult }}</code> - </div> - </div> - - <div class="_card _gap"> - <div class="_title">MFM</div> - <div class="_content"> - <MkTextarea v-model="mfm"> - <template #label>MFM</template> - </MkTextarea> - </div> - <div class="_content"> - <Mfm :text="mfm"/> - </div> - </div> - - <div class="_card _gap"> - <div class="_title">selectDriveFile</div> - <div class="_content"> - <MkSwitch v-model="selectDriveFileMultiple"> - <span>Multiple</span> - </MkSwitch> - <MkButton @click="selectDriveFile()">selectDriveFile</MkButton> - </div> - <div class="_content"> - <code>Result: {{ JSON.stringify(selectDriveFileResult) }}</code> - </div> - </div> - - <div class="_card _gap"> - <div class="_title">selectDriveFolder</div> - <div class="_content"> - <MkSwitch v-model="selectDriveFolderMultiple"> - <span>Multiple</span> - </MkSwitch> - <MkButton @click="selectDriveFolder()">selectDriveFolder</MkButton> - </div> - <div class="_content"> - <code>Result: {{ JSON.stringify(selectDriveFolderResult) }}</code> - </div> - </div> - - <div class="_card _gap"> - <div class="_title">selectUser</div> - <div class="_content"> - <MkButton @click="selectUser()">selectUser</MkButton> - </div> - <div class="_content"> - <code>Result: {{ user }}</code> - </div> - </div> - - <div class="_card _gap"> - <div class="_title">Notification</div> - <div class="_content"> - <MkInput v-model="notificationIconUrl"> - <template #label>Icon URL</template> - </MkInput> - <MkInput v-model="notificationHeader"> - <template #label>Header</template> - </MkInput> - <MkTextarea v-model="notificationBody"> - <template #label>Body</template> - </MkTextarea> - <MkButton @click="createNotification()">createNotification</MkButton> - </div> - </div> - - <div class="_card _gap"> - <div class="_title">Waiting dialog</div> - <div class="_content"> - <MkButton inline @click="openWaitingDialog()">icon only</MkButton> - <MkButton inline @click="openWaitingDialog('Doing')">with text</MkButton> - </div> - </div> - - <div class="_card _gap"> - <div class="_title">Messaging window</div> - <div class="_content"> - <MkButton @click="messagingWindowOpen()">open</MkButton> - </div> - </div> - - <MkButton @click="resetTutorial()">Reset tutorial</MkButton> - </div> -</div> -</template> - -<script lang="ts"> -import { defineComponent, defineAsyncComponent } from 'vue'; -import MkButton from '@/components/ui/button.vue'; -import MkInput from '@/components/form/input.vue'; -import MkSwitch from '@/components/form/switch.vue'; -import MkTextarea from '@/components/form/textarea.vue'; -import MkRadio from '@/components/form/radio.vue'; -import * as os from '@/os'; -import * as symbols from '@/symbols'; - -export default defineComponent({ - components: { - MkButton, - MkInput, - MkSwitch, - MkTextarea, - MkRadio, - }, - - data() { - return { - [symbols.PAGE_INFO]: { - title: 'TEST', - icon: 'fas fa-exclamation-triangle' - }, - dialogTitle: 'Hello', - dialogBody: 'World!', - dialogType: 'info', - dialogCancel: false, - dialogCancelByBgClick: true, - dialogInput: false, - dialogResult: null, - formTitle: 'Test form', - formForm: JSON.stringify({ - foo: { - type: 'boolean', - default: true, - label: 'This is a boolean property' - }, - bar: { - type: 'number', - default: 300, - label: 'This is a number property' - }, - baz: { - type: 'string', - default: 'Misskey makes you happy.', - label: 'This is a string property' - }, - qux: { - type: 'string', - multiline: true, - default: 'Misskey makes\nyou happy.', - label: 'Multiline string' - }, - }, null, '\t'), - formResult: null, - mfm: '', - selectDriveFileMultiple: false, - selectDriveFolderMultiple: false, - selectDriveFileResult: null, - selectDriveFolderResult: null, - user: null, - notificationIconUrl: null, - notificationHeader: '', - notificationBody: '', - } - }, - - methods: { - async showDialog() { - this.dialogResult = null; - /* - this.dialogResult = await os.dialog({ - type: this.dialogType, - title: this.dialogTitle, - text: this.dialogBody, - showCancelButton: this.dialogCancel, - cancelableByBgClick: this.dialogCancelByBgClick, - input: this.dialogInput ? {} : null - });*/ - }, - - async form() { - this.formResult = null; - this.formResult = await os.form(this.formTitle, JSON.parse(this.formForm)); - }, - - async selectDriveFile() { - this.selectDriveFileResult = null; - this.selectDriveFileResult = await os.selectDriveFile(this.selectDriveFileMultiple); - }, - - async selectDriveFolder() { - this.selectDriveFolderResult = null; - this.selectDriveFolderResult = await os.selectDriveFolder(this.selectDriveFolderMultiple); - }, - - async selectUser() { - this.user = null; - this.user = await os.selectUser(); - }, - - async createNotification() { - os.api('notifications/create', { - header: this.notificationHeader, - body: this.notificationBody, - icon: this.notificationIconUrl, - }); - }, - - messagingWindowOpen() { - os.pageWindow('/my/messaging'); - }, - - openWaitingDialog(text?) { - const promise = new Promise((resolve, reject) => { - setTimeout(resolve, 2000); - }); - os.promiseDialog(promise, null, null, text); - }, - - resetTutorial() { - this.$store.set('tutorial', 0); - }, - } -}); -</script> diff --git a/packages/client/src/pages/theme-editor.vue b/packages/client/src/pages/theme-editor.vue index f023653425..80b8c7806c 100644 --- a/packages/client/src/pages/theme-editor.vue +++ b/packages/client/src/pages/theme-editor.vue @@ -1,300 +1,274 @@ <template> -<FormBase class="cwepdizn"> - <div class="_debobigegoItem colorPicker"> - <div class="_debobigegoLabel">{{ $ts.backgroundColor }}</div> - <div class="_debobigegoPanel colors"> - <div class="row"> - <button v-for="color in bgColors.filter(x => x.kind === 'light')" :key="color.color" class="color _button" :class="{ active: theme.props.bg === color.color }" @click="setBgColor(color)"> - <div class="preview" :style="{ background: color.forPreview }"></div> - </button> +<MkSpacer :content-max="800" :margin-min="16" :margin-max="32"> + <div class="cwepdizn _formRoot"> + <FormFolder :default-open="true" class="_formBlock"> + <template #label>{{ i18n.locale.backgroundColor }}</template> + <div class="cwepdizn-colors"> + <div class="row"> + <button v-for="color in bgColors.filter(x => x.kind === 'light')" :key="color.color" class="color _button" :class="{ active: theme.props.bg === color.color }" @click="setBgColor(color)"> + <div class="preview" :style="{ background: color.forPreview }"></div> + </button> + </div> + <div class="row"> + <button v-for="color in bgColors.filter(x => x.kind === 'dark')" :key="color.color" class="color _button" :class="{ active: theme.props.bg === color.color }" @click="setBgColor(color)"> + <div class="preview" :style="{ background: color.forPreview }"></div> + </button> + </div> </div> - <div class="row"> - <button v-for="color in bgColors.filter(x => x.kind === 'dark')" :key="color.color" class="color _button" :class="{ active: theme.props.bg === color.color }" @click="setBgColor(color)"> - <div class="preview" :style="{ background: color.forPreview }"></div> - </button> - </div> - </div> - </div> - <div class="_debobigegoItem colorPicker"> - <div class="_debobigegoLabel">{{ $ts.accentColor }}</div> - <div class="_debobigegoPanel colors"> - <div class="row"> - <button v-for="color in accentColors" :key="color" class="color rounded _button" :class="{ active: theme.props.accent === color }" @click="setAccentColor(color)"> - <div class="preview" :style="{ background: color }"></div> - </button> + </FormFolder> + + <FormFolder :default-open="true" class="_formBlock"> + <template #label>{{ i18n.locale.accentColor }}</template> + <div class="cwepdizn-colors"> + <div class="row"> + <button v-for="color in accentColors" :key="color" class="color rounded _button" :class="{ active: theme.props.accent === color }" @click="setAccentColor(color)"> + <div class="preview" :style="{ background: color }"></div> + </button> + </div> </div> - </div> - </div> - <div class="_debobigegoItem colorPicker"> - <div class="_debobigegoLabel">{{ $ts.textColor }}</div> - <div class="_debobigegoPanel colors"> - <div class="row"> - <button v-for="color in fgColors" :key="color" class="color char _button" :class="{ active: (theme.props.fg === color.forLight) || (theme.props.fg === color.forDark) }" @click="setFgColor(color)"> - <div class="preview" :style="{ color: color.forPreview ? color.forPreview : theme.base === 'light' ? '#5f5f5f' : '#dadada' }">A</div> - </button> + </FormFolder> + + <FormFolder :default-open="true" class="_formBlock"> + <template #label>{{ i18n.locale.textColor }}</template> + <div class="cwepdizn-colors"> + <div class="row"> + <button v-for="color in fgColors" :key="color" class="color char _button" :class="{ active: (theme.props.fg === color.forLight) || (theme.props.fg === color.forDark) }" @click="setFgColor(color)"> + <div class="preview" :style="{ color: color.forPreview ? color.forPreview : theme.base === 'light' ? '#5f5f5f' : '#dadada' }">A</div> + </button> + </div> </div> - </div> - </div> + </FormFolder> - <FormGroup v-if="codeEnabled"> - <FormTextarea v-model="themeCode" tall> - <span>{{ $ts._theme.code }}</span> - </FormTextarea> - <FormButton primary @click="applyThemeCode">{{ $ts.apply }}</FormButton> - </FormGroup> - <FormButton v-else @click="codeEnabled = true"><i class="fas fa-code"></i> {{ $ts.editCode }}</FormButton> + <FormFolder :default-open="false" class="_formBlock"> + <template #icon><i class="fas fa-code"></i></template> + <template #label>{{ i18n.locale.editCode }}</template> - <FormGroup v-if="descriptionEnabled"> - <FormTextarea v-model="description"> - <span>{{ $ts._theme.description }}</span> - </FormTextarea> - </FormGroup> - <FormButton v-else @click="descriptionEnabled = true">{{ $ts.addDescription }}</FormButton> + <div class="_formRoot"> + <FormTextarea v-model="themeCode" tall class="_formBlock"> + <template #label>{{ i18n.locale._theme.code }}</template> + </FormTextarea> + <FormButton primary class="_formBlock" @click="applyThemeCode">{{ i18n.locale.apply }}</FormButton> + </div> + </FormFolder> - <FormGroup> - <FormButton @click="showPreview"><i class="fas fa-eye"></i> {{ $ts.preview }}</FormButton> - <FormButton primary @click="saveAs"><i class="fas fa-save"></i> {{ $ts.saveAs }}</FormButton> - </FormGroup> -</FormBase> + <FormFolder :default-open="false" class="_formBlock"> + <template #label>{{ i18n.locale.addDescription }}</template> + + <div class="_formRoot"> + <FormTextarea v-model="description"> + <template #label>{{ i18n.locale._theme.description }}</template> + </FormTextarea> + </div> + </FormFolder> + </div> +</MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { watch } from 'vue'; import { toUnicode } from 'punycode/'; import * as tinycolor from 'tinycolor2'; import { v4 as uuid} from 'uuid'; import * as JSON5 from 'json5'; -import FormBase from '@/components/debobigego/base.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormTextarea from '@/components/debobigego/textarea.vue'; -import FormGroup from '@/components/debobigego/group.vue'; +import FormButton from '@/components/ui/button.vue'; +import FormTextarea from '@/components/form/textarea.vue'; +import FormFolder from '@/components/form/folder.vue'; -import { Theme, applyTheme, validateTheme, darkTheme, lightTheme } from '@/scripts/theme'; +import { Theme, applyTheme, darkTheme, lightTheme } from '@/scripts/theme'; import { host } from '@/config'; import * as os from '@/os'; -import { ColdDeviceStorage } from '@/store'; +import { ColdDeviceStorage, defaultStore } from '@/store'; import { addTheme } from '@/theme-store'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; +import { useLeaveGuard } from '@/scripts/use-leave-guard'; -export default defineComponent({ - components: { - FormBase, - FormButton, - FormTextarea, - FormGroup, - }, +const bgColors = [ + { color: '#f5f5f5', kind: 'light', forPreview: '#f5f5f5' }, + { color: '#f0eee9', kind: 'light', forPreview: '#f3e2b9' }, + { color: '#e9eff0', kind: 'light', forPreview: '#bfe3e8' }, + { color: '#f0e9ee', kind: 'light', forPreview: '#f1d1e8' }, + { color: '#dce2e0', kind: 'light', forPreview: '#a4dccc' }, + { color: '#e2e0dc', kind: 'light', forPreview: '#d8c7a5' }, + { color: '#d5dbe0', kind: 'light', forPreview: '#b0cae0' }, + { color: '#dad5d5', kind: 'light', forPreview: '#d6afaf' }, + { color: '#2b2b2b', kind: 'dark', forPreview: '#444444' }, + { color: '#362e29', kind: 'dark', forPreview: '#735c4d' }, + { color: '#303629', kind: 'dark', forPreview: '#506d2f' }, + { color: '#293436', kind: 'dark', forPreview: '#258192' }, + { color: '#2e2936', kind: 'dark', forPreview: '#504069' }, + { color: '#252722', kind: 'dark', forPreview: '#3c462f' }, + { color: '#212525', kind: 'dark', forPreview: '#303e3e' }, + { color: '#191919', kind: 'dark', forPreview: '#272727' }, +] as const; +const accentColors = ['#e36749', '#f29924', '#98c934', '#34c9a9', '#34a1c9', '#606df7', '#8d34c9', '#e84d83']; +const fgColors = [ + { color: 'none', forLight: '#5f5f5f', forDark: '#dadada', forPreview: null }, + { color: 'red', forLight: '#7f6666', forDark: '#e4d1d1', forPreview: '#ca4343' }, + { color: 'yellow', forLight: '#736955', forDark: '#e0d5c0', forPreview: '#d49923' }, + { color: 'green', forLight: '#586d5b', forDark: '#d1e4d4', forPreview: '#4cbd5c' }, + { color: 'cyan', forLight: '#5d7475', forDark: '#d1e3e4', forPreview: '#2abdc3' }, + { color: 'blue', forLight: '#676880', forDark: '#d1d2e4', forPreview: '#7275d8' }, + { color: 'pink', forLight: '#84667d', forDark: '#e4d1e0', forPreview: '#b12390' }, +]; - async beforeRouteLeave(to, from) { - if (this.changed && !(await this.leaveConfirm())) { - return false; - } - }, - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.themeEditor, - icon: 'fas fa-palette', - }, - theme: { - base: 'light', - props: lightTheme.props - } as Theme, - codeEnabled: false, - descriptionEnabled: false, - description: null, - themeCode: null, - bgColors: [ - { color: '#f5f5f5', kind: 'light', forPreview: '#f5f5f5' }, - { color: '#f0eee9', kind: 'light', forPreview: '#f3e2b9' }, - { color: '#e9eff0', kind: 'light', forPreview: '#bfe3e8' }, - { color: '#f0e9ee', kind: 'light', forPreview: '#f1d1e8' }, - { color: '#dce2e0', kind: 'light', forPreview: '#a4dccc' }, - { color: '#e2e0dc', kind: 'light', forPreview: '#d8c7a5' }, - { color: '#d5dbe0', kind: 'light', forPreview: '#b0cae0' }, - { color: '#dad5d5', kind: 'light', forPreview: '#d6afaf' }, - { color: '#2b2b2b', kind: 'dark', forPreview: '#444444' }, - { color: '#362e29', kind: 'dark', forPreview: '#735c4d' }, - { color: '#303629', kind: 'dark', forPreview: '#506d2f' }, - { color: '#293436', kind: 'dark', forPreview: '#258192' }, - { color: '#2e2936', kind: 'dark', forPreview: '#504069' }, - { color: '#252722', kind: 'dark', forPreview: '#3c462f' }, - { color: '#212525', kind: 'dark', forPreview: '#303e3e' }, - { color: '#191919', kind: 'dark', forPreview: '#272727' }, - ], - accentColors: ['#e36749', '#f29924', '#98c934', '#34c9a9', '#34a1c9', '#606df7', '#8d34c9', '#e84d83'], - fgColors: [ - { color: 'none', forLight: '#5f5f5f', forDark: '#dadada', forPreview: null }, - { color: 'red', forLight: '#7f6666', forDark: '#e4d1d1', forPreview: '#ca4343' }, - { color: 'yellow', forLight: '#736955', forDark: '#e0d5c0', forPreview: '#d49923' }, - { color: 'green', forLight: '#586d5b', forDark: '#d1e4d4', forPreview: '#4cbd5c' }, - { color: 'cyan', forLight: '#5d7475', forDark: '#d1e3e4', forPreview: '#2abdc3' }, - { color: 'blue', forLight: '#676880', forDark: '#d1d2e4', forPreview: '#7275d8' }, - { color: 'pink', forLight: '#84667d', forDark: '#e4d1e0', forPreview: '#b12390' }, - ], - changed: false, - } - }, - - created() { - this.$watch('theme', this.apply, { deep: true }); - window.addEventListener('beforeunload', this.beforeunload); - }, +const theme = $ref<Partial<Theme>>({ + base: 'light', + props: lightTheme.props, +}); +let description = $ref<string | null>(null); +let themeCode = $ref<string | null>(null); +let changed = $ref(false); - beforeUnmount() { - window.removeEventListener('beforeunload', this.beforeunload); - }, +useLeaveGuard($$(changed)); - methods: { - beforeunload(e: BeforeUnloadEvent) { - if (this.changed) { - e.preventDefault(); - e.returnValue = ''; - } - }, +function showPreview() { + os.pageWindow('preview'); +} - async leaveConfirm(): Promise<boolean> { - const { canceled } = await os.confirm({ - type: 'warning', - text: this.$ts.leaveConfirm, - }); - return !canceled; - }, +function setBgColor(color: typeof bgColors[number]) { + if (theme.base != color.kind) { + const base = color.kind === 'dark' ? darkTheme : lightTheme; + for (const prop of Object.keys(base.props)) { + if (prop === 'accent') continue; + if (prop === 'fg') continue; + theme.props[prop] = base.props[prop]; + } + } + theme.base = color.kind; + theme.props.bg = color.color; - showPreview() { - os.pageWindow('preview'); - }, + if (theme.props.fg) { + const matchedFgColor = fgColors.find(x => [tinycolor(x.forLight).toRgbString(), tinycolor(x.forDark).toRgbString()].includes(tinycolor(theme.props.fg).toRgbString())); + if (matchedFgColor) setFgColor(matchedFgColor); + } +} - setBgColor(color) { - if (this.theme.base != color.kind) { - const base = color.kind === 'dark' ? darkTheme : lightTheme; - for (const prop of Object.keys(base.props)) { - if (prop === 'accent') continue; - if (prop === 'fg') continue; - this.theme.props[prop] = base.props[prop]; - } - } - this.theme.base = color.kind; - this.theme.props.bg = color.color; +function setAccentColor(color) { + theme.props.accent = color; +} - if (this.theme.props.fg) { - const matchedFgColor = this.fgColors.find(x => [tinycolor(x.forLight).toRgbString(), tinycolor(x.forDark).toRgbString()].includes(tinycolor(this.theme.props.fg).toRgbString())); - if (matchedFgColor) this.setFgColor(matchedFgColor); - } - }, +function setFgColor(color) { + theme.props.fg = theme.base === 'light' ? color.forLight : color.forDark; +} - setAccentColor(color) { - this.theme.props.accent = color; - }, +function apply() { + themeCode = JSON5.stringify(theme, null, '\t'); + applyTheme(theme, false); + changed = true; +} - setFgColor(color) { - this.theme.props.fg = this.theme.base === 'light' ? color.forLight : color.forDark; - }, +function applyThemeCode() { + let parsed; - apply() { - this.themeCode = JSON5.stringify(this.theme, null, '\t'); - applyTheme(this.theme, false); - this.changed = true; - }, + try { + parsed = JSON5.parse(themeCode); + } catch (err) { + os.alert({ + type: 'error', + text: i18n.locale._theme.invalid, + }); + return; + } - applyThemeCode() { - let parsed; + theme = parsed; +} - try { - parsed = JSON5.parse(this.themeCode); - } catch (e) { - os.alert({ - type: 'error', - text: this.$ts._theme.invalid - }); - return; - } +async function saveAs() { + const { canceled, result: name } = await os.inputText({ + title: i18n.locale.name, + allowEmpty: false, + }); + if (canceled) return; - this.theme = parsed; - }, + theme.id = uuid(); + theme.name = name; + theme.author = `@${$i.username}@${toUnicode(host)}`; + if (description) theme.desc = description; + addTheme(theme); + applyTheme(theme); + if (defaultStore.state.darkMode) { + ColdDeviceStorage.set('darkTheme', theme); + } else { + ColdDeviceStorage.set('lightTheme', theme); + } + changed = false; + os.alert({ + type: 'success', + text: i18n.t('_theme.installed', { name: theme.name }), + }); +} - async saveAs() { - const { canceled, result: name } = await os.inputText({ - title: this.$ts.name, - allowEmpty: false - }); - if (canceled) return; +watch($$(theme), apply, { deep: true }); - this.theme.id = uuid(); - this.theme.name = name; - this.theme.author = `@${this.$i.username}@${toUnicode(host)}`; - if (this.description) this.theme.desc = this.description; - addTheme(this.theme); - applyTheme(this.theme); - if (this.$store.state.darkMode) { - ColdDeviceStorage.set('darkTheme', this.theme); - } else { - ColdDeviceStorage.set('lightTheme', this.theme); - } - this.changed = false; - os.alert({ - type: 'success', - text: this.$t('_theme.installed', { name: this.theme.name }) - }); - } - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.locale.themeEditor, + icon: 'fas fa-palette', + bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-eye', + text: i18n.locale.preview, + handler: showPreview, + }, { + asFullButton: true, + icon: 'fas fa-check', + text: i18n.locale.saveAs, + handler: saveAs, + }], + }, }); </script> <style lang="scss" scoped> .cwepdizn { - max-width: 800px; - margin: 0 auto; + ::v-deep(.cwepdizn-colors) { + text-align: center; - > .colorPicker { - > .colors { - padding: 32px; - text-align: center; + > .row { + > .color { + display: inline-block; + position: relative; + width: 64px; + height: 64px; + border-radius: 8px; - > .row { - > .color { - display: inline-block; - position: relative; - width: 64px; - height: 64px; - border-radius: 8px; + > .preview { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + margin: auto; + width: 42px; + height: 42px; + border-radius: 4px; + box-shadow: 0 2px 4px rgb(0 0 0 / 30%); + transition: transform 0.15s ease; + } + &:hover { > .preview { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - margin: auto; - width: 42px; - height: 42px; - border-radius: 4px; - box-shadow: 0 2px 4px rgb(0 0 0 / 30%); - transition: transform 0.15s ease; + transform: scale(1.1); } + } - &:hover { - > .preview { - transform: scale(1.1); - } - } + &.active { + box-shadow: 0 0 0 2px var(--divider) inset; + } - &.active { - box-shadow: 0 0 0 2px var(--divider) inset; - } + &.rounded { + border-radius: 999px; - &.rounded { + > .preview { border-radius: 999px; - - > .preview { - border-radius: 999px; - } } + } - &.char { - line-height: 42px; - } + &.char { + line-height: 42px; } } } diff --git a/packages/client/src/pages/timeline.tutorial.vue b/packages/client/src/pages/timeline.tutorial.vue index 3775796940..432d28c60b 100644 --- a/packages/client/src/pages/timeline.tutorial.vue +++ b/packages/client/src/pages/timeline.tutorial.vue @@ -65,26 +65,14 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; import MkButton from '@/components/ui/button.vue'; +import { defaultStore } from '@/store'; -export default defineComponent({ - components: { - MkButton, - }, - - data() { - return { - } - }, - - computed: { - tutorial: { - get() { return this.$store.reactiveState.tutorial.value || 0; }, - set(value) { this.$store.set('tutorial', value); } - }, - }, +const tutorial = computed({ + get() { return defaultStore.reactiveState.tutorial.value || 0; }, + set(value) { defaultStore.set('tutorial', value); } }); </script> diff --git a/packages/client/src/pages/timeline.vue b/packages/client/src/pages/timeline.vue index 216b3c34ea..aabb953aec 100644 --- a/packages/client/src/pages/timeline.vue +++ b/packages/client/src/pages/timeline.vue @@ -1,6 +1,6 @@ <template> <MkSpacer :content-max="800"> - <div v-hotkey.global="keymap" class="cmuxhskf"> + <div ref="rootEl" v-hotkey.global="keymap" class="cmuxhskf"> <XTutorial v-if="$store.reactiveState.tutorial.value != -1" class="tutorial _block"/> <XPostForm v-if="$store.reactiveState.showFixedPostForm.value" class="post-form _block" fixed/> @@ -18,162 +18,144 @@ </template> <script lang="ts"> -import { defineComponent, defineAsyncComponent, computed } from 'vue'; +export default { + name: 'MkTimelinePage', +} +</script> + +<script lang="ts" setup> +import { defineAsyncComponent, computed, watch } from 'vue'; import XTimeline from '@/components/timeline.vue'; import XPostForm from '@/components/post-form.vue'; import { scroll } from '@/scripts/scroll'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { defaultStore } from '@/store'; +import { i18n } from '@/i18n'; +import { instance } from '@/instance'; +import { $i } from '@/account'; -export default defineComponent({ - name: 'timeline', - - components: { - XTimeline, - XTutorial: defineAsyncComponent(() => import('./timeline.tutorial.vue')), - XPostForm, - }, +const XTutorial = defineAsyncComponent(() => import('./timeline.tutorial.vue')); - data() { - return { - src: 'home', - queue: 0, - [symbols.PAGE_INFO]: computed(() => ({ - title: this.$ts.timeline, - icon: this.src === 'local' ? 'fas fa-comments' : this.src === 'social' ? 'fas fa-share-alt' : this.src === 'global' ? 'fas fa-globe' : 'fas fa-home', - bg: 'var(--bg)', - actions: [{ - icon: 'fas fa-list-ul', - text: this.$ts.lists, - handler: this.chooseList - }, { - icon: 'fas fa-satellite', - text: this.$ts.antennas, - handler: this.chooseAntenna - }, { - icon: 'fas fa-satellite-dish', - text: this.$ts.channel, - handler: this.chooseChannel - }, { - icon: 'fas fa-calendar-alt', - text: this.$ts.jumpToSpecifiedDate, - handler: this.timetravel - }], - tabs: [{ - active: this.src === 'home', - title: this.$ts._timelines.home, - icon: 'fas fa-home', - iconOnly: true, - onClick: () => { this.src = 'home'; this.saveSrc(); }, - }, ...(this.isLocalTimelineAvailable ? [{ - active: this.src === 'local', - title: this.$ts._timelines.local, - icon: 'fas fa-comments', - iconOnly: true, - onClick: () => { this.src = 'local'; this.saveSrc(); }, - }, { - active: this.src === 'social', - title: this.$ts._timelines.social, - icon: 'fas fa-share-alt', - iconOnly: true, - onClick: () => { this.src = 'social'; this.saveSrc(); }, - }] : []), ...(this.isGlobalTimelineAvailable ? [{ - active: this.src === 'global', - title: this.$ts._timelines.global, - icon: 'fas fa-globe', - iconOnly: true, - onClick: () => { this.src = 'global'; this.saveSrc(); }, - }] : [])], - })), - }; - }, +const isLocalTimelineAvailable = !instance.disableLocalTimeline || ($i != null && ($i.isModerator || $i.isAdmin)); +const isGlobalTimelineAvailable = !instance.disableGlobalTimeline || ($i != null && ($i.isModerator || $i.isAdmin)); +const keymap = { + 't': focus, +}; - computed: { - keymap(): any { - return { - 't': this.focus - }; - }, +const tlComponent = $ref<InstanceType<typeof XTimeline>>(); +const rootEl = $ref<HTMLElement>(); - isLocalTimelineAvailable(): boolean { - return !this.$instance.disableLocalTimeline || this.$i.isModerator || this.$i.isAdmin; - }, +let src = $ref<'home' | 'local' | 'social' | 'global'>(defaultStore.state.tl.src); +let queue = $ref(0); - isGlobalTimelineAvailable(): boolean { - return !this.$instance.disableGlobalTimeline || this.$i.isModerator || this.$i.isAdmin; - }, - }, - - watch: { - src() { - this.showNav = false; - }, - }, - - created() { - this.src = this.$store.state.tl.src; - }, +function queueUpdated(q: number): void { + queue = q; +} - methods: { - queueUpdated(q) { - this.queue = q; - }, +function top(): void { + scroll(rootEl, { top: 0 }); +} - top() { - scroll(this.$el, { top: 0 }); - }, +async function chooseList(ev: MouseEvent): Promise<void> { + const lists = await os.api('users/lists/list'); + const items = lists.map(list => ({ + type: 'link', + text: list.name, + to: `/timeline/list/${list.id}`, + })); + os.popupMenu(items, ev.currentTarget || ev.target); +} - async chooseList(ev) { - const lists = await os.api('users/lists/list'); - const items = lists.map(list => ({ - type: 'link', - text: list.name, - to: `/timeline/list/${list.id}` - })); - os.popupMenu(items, ev.currentTarget || ev.target); - }, +async function chooseAntenna(ev: MouseEvent): Promise<void> { + const antennas = await os.api('antennas/list'); + const items = antennas.map(antenna => ({ + type: 'link', + text: antenna.name, + indicate: antenna.hasUnreadNote, + to: `/timeline/antenna/${antenna.id}`, + })); + os.popupMenu(items, ev.currentTarget || ev.target); +} - async chooseAntenna(ev) { - const antennas = await os.api('antennas/list'); - const items = antennas.map(antenna => ({ - type: 'link', - text: antenna.name, - indicate: antenna.hasUnreadNote, - to: `/timeline/antenna/${antenna.id}` - })); - os.popupMenu(items, ev.currentTarget || ev.target); - }, +async function chooseChannel(ev: MouseEvent): Promise<void> { + const channels = await os.api('channels/followed'); + const items = channels.map(channel => ({ + type: 'link', + text: channel.name, + indicate: channel.hasUnreadNote, + to: `/channels/${channel.id}`, + })); + os.popupMenu(items, ev.currentTarget || ev.target); +} - async chooseChannel(ev) { - const channels = await os.api('channels/followed'); - const items = channels.map(channel => ({ - type: 'link', - text: channel.name, - indicate: channel.hasUnreadNote, - to: `/channels/${channel.id}` - })); - os.popupMenu(items, ev.currentTarget || ev.target); - }, +function saveSrc(): void { + defaultStore.set('tl', { + src: src, + }); +} - saveSrc() { - this.$store.set('tl', { - src: this.src, - }); - }, +async function timetravel(): Promise<void> { + const { canceled, result: date } = await os.inputDate({ + title: i18n.locale.date, + }); + if (canceled) return; - async timetravel() { - const { canceled, result: date } = await os.inputDate({ - title: this.$ts.date, - }); - if (canceled) return; + tlComponent.timetravel(date); +} - this.$refs.tl.timetravel(date); - }, +function focus(): void { + tlComponent.focus(); +} - focus() { - (this.$refs.tl as any).focus(); - } - } +defineExpose({ + [symbols.PAGE_INFO]: computed(() => ({ + title: i18n.locale.timeline, + icon: src === 'local' ? 'fas fa-comments' : src === 'social' ? 'fas fa-share-alt' : src === 'global' ? 'fas fa-globe' : 'fas fa-home', + bg: 'var(--bg)', + actions: [{ + icon: 'fas fa-list-ul', + text: i18n.locale.lists, + handler: chooseList, + }, { + icon: 'fas fa-satellite', + text: i18n.locale.antennas, + handler: chooseAntenna, + }, { + icon: 'fas fa-satellite-dish', + text: i18n.locale.channel, + handler: chooseChannel, + }, { + icon: 'fas fa-calendar-alt', + text: i18n.locale.jumpToSpecifiedDate, + handler: timetravel, + }], + tabs: [{ + active: src === 'home', + title: i18n.locale._timelines.home, + icon: 'fas fa-home', + iconOnly: true, + onClick: () => { src = 'home'; saveSrc(); }, + }, ...(isLocalTimelineAvailable ? [{ + active: src === 'local', + title: i18n.locale._timelines.local, + icon: 'fas fa-comments', + iconOnly: true, + onClick: () => { src = 'local'; saveSrc(); }, + }, { + active: src === 'social', + title: i18n.locale._timelines.social, + icon: 'fas fa-share-alt', + iconOnly: true, + onClick: () => { src = 'social'; saveSrc(); }, + }] : []), ...(isGlobalTimelineAvailable ? [{ + active: src === 'global', + title: i18n.locale._timelines.global, + icon: 'fas fa-globe', + iconOnly: true, + onClick: () => { src = 'global'; saveSrc(); }, + }] : [])], + })), }); </script> diff --git a/packages/client/src/pages/user-ap-info.vue b/packages/client/src/pages/user-ap-info.vue deleted file mode 100644 index 0027381f53..0000000000 --- a/packages/client/src/pages/user-ap-info.vue +++ /dev/null @@ -1,124 +0,0 @@ -<template> -<FormBase> - <FormSuspense v-slot="{ result: ap }" :p="apPromiseFactory"> - <FormGroup> - <template #label>ActivityPub</template> - <FormKeyValueView> - <template #key>Type</template> - <template #value><span class="_monospace">{{ ap.type }}</span></template> - </FormKeyValueView> - <FormKeyValueView> - <template #key>URI</template> - <template #value><span class="_monospace">{{ ap.id }}</span></template> - </FormKeyValueView> - <FormKeyValueView> - <template #key>URL</template> - <template #value><span class="_monospace">{{ ap.url }}</span></template> - </FormKeyValueView> - <FormGroup> - <FormKeyValueView> - <template #key>Inbox</template> - <template #value><span class="_monospace">{{ ap.inbox }}</span></template> - </FormKeyValueView> - <FormKeyValueView> - <template #key>Shared Inbox</template> - <template #value><span class="_monospace">{{ ap.sharedInbox || ap.endpoints.sharedInbox }}</span></template> - </FormKeyValueView> - <FormKeyValueView> - <template #key>Outbox</template> - <template #value><span class="_monospace">{{ ap.outbox }}</span></template> - </FormKeyValueView> - </FormGroup> - <FormTextarea readonly tall code pre :value="ap.publicKey.publicKeyPem"> - <span>Public Key</span> - </FormTextarea> - <FormKeyValueView> - <template #key>Discoverable</template> - <template #value>{{ ap.discoverable ? $ts.yes : $ts.no }}</template> - </FormKeyValueView> - <FormKeyValueView> - <template #key>ManuallyApprovesFollowers</template> - <template #value>{{ ap.manuallyApprovesFollowers ? $ts.yes : $ts.no }}</template> - </FormKeyValueView> - <FormObjectView tall :value="ap"> - <span>Raw</span> - </FormObjectView> - <FormGroup> - <FormLink :to="`https://${user.host}/.well-known/webfinger?resource=acct:${user.username}`" external>WebFinger</FormLink> - </FormGroup> - <FormLink v-if="user.host" :to="`/instance-info/${user.host}`">{{ $ts.instanceInfo }}<template #suffix>{{ user.host }}</template></FormLink> - <FormKeyValueView v-else> - <template #key>{{ $ts.instanceInfo }}</template> - <template #value>(Local user)</template> - </FormKeyValueView> - </FormGroup> - </FormSuspense> -</FormBase> -</template> - -<script lang="ts"> -import { defineAsyncComponent, defineComponent } from 'vue'; -import FormObjectView from '@/components/debobigego/object-view.vue'; -import FormTextarea from '@/components/debobigego/textarea.vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormKeyValueView from '@/components/debobigego/key-value-view.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; -import * as os from '@/os'; -import number from '@/filters/number'; -import bytes from '@/filters/bytes'; -import * as symbols from '@/symbols'; -import { url } from '@/config'; - -export default defineComponent({ - components: { - FormBase, - FormTextarea, - FormObjectView, - FormButton, - FormLink, - FormGroup, - FormKeyValueView, - FormSuspense, - }, - - props: { - userId: { - type: String, - required: true - } - }, - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.userInfo, - icon: 'fas fa-info-circle' - }, - user: null, - apPromiseFactory: null, - } - }, - - mounted() { - this.fetch(); - }, - - methods: { - number, - bytes, - - async fetch() { - this.user = await os.api('users/show', { - userId: this.userId - }); - - this.apPromiseFactory = () => os.api('ap/get', { - uri: this.user.uri || `${url}/users/${this.user.id}` - }); - } - } -}); -</script> diff --git a/packages/client/src/pages/user-info.vue b/packages/client/src/pages/user-info.vue index 0fd208a64a..4bdc82f601 100644 --- a/packages/client/src/pages/user-info.vue +++ b/packages/client/src/pages/user-info.vue @@ -1,70 +1,75 @@ <template> -<FormBase> +<MkSpacer :content-max="500" :margin-min="16" :margin-max="32"> <FormSuspense :p="init"> - <div class="_debobigegoItem aeakzknw"> - <MkAvatar class="avatar" :user="user" :show-indicator="true"/> - </div> - - <FormLink :to="userPage(user)">Profile</FormLink> + <div class="_formRoot"> + <div class="_formBlock aeakzknw"> + <MkAvatar class="avatar" :user="user" :show-indicator="true"/> + </div> - <FormGroup> - <FormKeyValueView> - <template #key>Acct</template> - <template #value><span class="_monospace">{{ acct(user) }}</span></template> - </FormKeyValueView> + <FormLink :to="userPage(user)">Profile</FormLink> - <FormKeyValueView> - <template #key>ID</template> - <template #value><span class="_monospace">{{ user.id }}</span></template> - </FormKeyValueView> - </FormGroup> + <div class="_formBlock"> + <MkKeyValue :copy="acct(user)" oneline style="margin: 1em 0;"> + <template #key>Acct</template> + <template #value><span class="_monospace">{{ acct(user) }}</span></template> + </MkKeyValue> - <FormGroup v-if="iAmModerator"> - <FormSwitch v-if="user.host == null && $i.isAdmin && (moderator || !user.isAdmin)" v-model="moderator" @update:modelValue="toggleModerator">{{ $ts.moderator }}</FormSwitch> - <FormSwitch v-model="silenced" @update:modelValue="toggleSilence">{{ $ts.silence }}</FormSwitch> - <FormSwitch v-model="suspended" @update:modelValue="toggleSuspend">{{ $ts.suspend }}</FormSwitch> - </FormGroup> + <MkKeyValue :copy="user.id" oneline style="margin: 1em 0;"> + <template #key>ID</template> + <template #value><span class="_monospace">{{ user.id }}</span></template> + </MkKeyValue> + </div> - <FormGroup> - <FormButton v-if="user.host != null" @click="updateRemoteUser"><i class="fas fa-sync"></i> {{ $ts.updateRemoteUser }}</FormButton> - <FormButton v-if="user.host == null && iAmModerator" @click="resetPassword"><i class="fas fa-key"></i> {{ $ts.resetPassword }}</FormButton> - </FormGroup> + <FormSection v-if="iAmModerator"> + <template #label>Moderation</template> + <FormSwitch v-if="user.host == null && $i.isAdmin && (moderator || !user.isAdmin)" v-model="moderator" class="_formBlock" @update:modelValue="toggleModerator">{{ $ts.moderator }}</FormSwitch> + <FormSwitch v-model="silenced" class="_formBlock" @update:modelValue="toggleSilence">{{ $ts.silence }}</FormSwitch> + <FormSwitch v-model="suspended" class="_formBlock" @update:modelValue="toggleSuspend">{{ $ts.suspend }}</FormSwitch> + <FormButton v-if="user.host == null && iAmModerator" class="_formBlock" @click="resetPassword"><i class="fas fa-key"></i> {{ $ts.resetPassword }}</FormButton> + </FormSection> - <FormGroup> - <FormLink :to="`/user-ap-info/${user.id}`">ActivityPub</FormLink> + <FormSection> + <template #label>ActivityPub</template> - <FormLink v-if="user.host" :to="`/instance-info/${user.host}`">{{ $ts.instanceInfo }}<template #suffix>{{ user.host }}</template></FormLink> - <FormKeyValueView v-else> - <template #key>{{ $ts.instanceInfo }}</template> - <template #value>(Local user)</template> - </FormKeyValueView> - </FormGroup> + <div class="_formBlock"> + <MkKeyValue v-if="user.host" oneline style="margin: 1em 0;"> + <template #key>{{ $ts.instanceInfo }}</template> + <template #value><MkA :to="`/instance-info/${user.host}`" class="_link">{{ user.host }} <i class="fas fa-angle-right"></i></MkA></template> + </MkKeyValue> + <MkKeyValue v-else oneline style="margin: 1em 0;"> + <template #key>{{ $ts.instanceInfo }}</template> + <template #value>(Local user)</template> + </MkKeyValue> + <MkKeyValue oneline style="margin: 1em 0;"> + <template #key>{{ $ts.updatedAt }}</template> + <template #value><MkTime v-if="user.lastFetchedAt" mode="detail" :time="user.lastFetchedAt"/><span v-else>N/A</span></template> + </MkKeyValue> + <MkKeyValue v-if="ap" oneline style="margin: 1em 0;"> + <template #key>Type</template> + <template #value><span class="_monospace">{{ ap.type }}</span></template> + </MkKeyValue> + </div> - <FormGroup> - <FormKeyValueView> - <template #key>{{ $ts.updatedAt }}</template> - <template #value><MkTime v-if="user.lastFetchedAt" mode="detail" :time="user.lastFetchedAt"/><span v-else>N/A</span></template> - </FormKeyValueView> - </FormGroup> + <FormButton v-if="user.host != null" class="_formBlock" @click="updateRemoteUser"><i class="fas fa-sync"></i> {{ $ts.updateRemoteUser }}</FormButton> + </FormSection> - <FormObjectView tall :value="user"> - <span>Raw</span> - </FormObjectView> + <MkObjectView tall :value="user"> + </MkObjectView> + </div> </FormSuspense> -</FormBase> +</MkSpacer> </template> <script lang="ts"> import { computed, defineAsyncComponent, defineComponent } from 'vue'; -import FormObjectView from '@/components/debobigego/object-view.vue'; -import FormTextarea from '@/components/debobigego/textarea.vue'; -import FormSwitch from '@/components/debobigego/switch.vue'; -import FormLink from '@/components/debobigego/link.vue'; -import FormBase from '@/components/debobigego/base.vue'; -import FormGroup from '@/components/debobigego/group.vue'; -import FormButton from '@/components/debobigego/button.vue'; -import FormKeyValueView from '@/components/debobigego/key-value-view.vue'; -import FormSuspense from '@/components/debobigego/suspense.vue'; +import MkObjectView from '@/components/object-view.vue'; +import FormTextarea from '@/components/form/textarea.vue'; +import FormSwitch from '@/components/form/switch.vue'; +import FormLink from '@/components/form/link.vue'; +import FormSection from '@/components/form/section.vue'; +import FormButton from '@/components/ui/button.vue'; +import MkKeyValue from '@/components/key-value.vue'; +import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; import number from '@/filters/number'; import bytes from '@/filters/bytes'; @@ -74,14 +79,13 @@ import { userPage, acct } from '@/filters/user'; export default defineComponent({ components: { - FormBase, + FormSection, FormTextarea, FormSwitch, - FormObjectView, + MkObjectView, FormButton, FormLink, - FormGroup, - FormKeyValueView, + MkKeyValue, FormSuspense, }, @@ -97,6 +101,7 @@ export default defineComponent({ [symbols.PAGE_INFO]: computed(() => ({ title: this.user ? acct(this.user) : this.$ts.userInfo, icon: 'fas fa-info-circle', + bg: 'var(--bg)', actions: this.user ? [this.user.url ? { text: this.user.url, icon: 'fas fa-external-link-alt', @@ -108,6 +113,7 @@ export default defineComponent({ init: null, user: null, info: null, + ap: null, moderator: false, silenced: false, suspended: false, @@ -126,6 +132,13 @@ export default defineComponent({ this.init = this.createFetcher(); }, immediate: true + }, + user() { + os.api('ap/get', { + uri: this.user.uri || `${url}/users/${this.user.id}` + }).then(res => { + this.ap = res; + }); } }, @@ -234,7 +247,6 @@ export default defineComponent({ .aeakzknw { > .avatar { display: block; - margin: 0 auto; width: 64px; height: 64px; } diff --git a/packages/client/src/pages/user/clips.vue b/packages/client/src/pages/user/clips.vue index aad5317ce0..870e6f7174 100644 --- a/packages/client/src/pages/user/clips.vue +++ b/packages/client/src/pages/user/clips.vue @@ -28,7 +28,7 @@ export default defineComponent({ data() { return { pagination: { - endpoint: 'users/clips', + endpoint: 'users/clips' as const, limit: 20, params: { userId: this.user.id, diff --git a/packages/client/src/pages/user/follow-list.vue b/packages/client/src/pages/user/follow-list.vue index 9fb8943fb8..98a1fc0f86 100644 --- a/packages/client/src/pages/user/follow-list.vue +++ b/packages/client/src/pages/user/follow-list.vue @@ -1,6 +1,6 @@ <template> <div> - <MkPagination v-slot="{items}" ref="list" :pagination="pagination" class="mk-following-or-followers"> + <MkPagination v-slot="{items}" ref="list" :pagination="type === 'following' ? followingPagination : followersPagination" class="mk-following-or-followers"> <div class="users _isolated"> <MkUserInfo v-for="user in items.map(x => type === 'following' ? x.followee : x.follower)" :key="user.id" class="user" :user="user"/> </div> @@ -8,50 +8,32 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; +import * as misskey from 'misskey-js'; import MkUserInfo from '@/components/user-info.vue'; import MkPagination from '@/components/ui/pagination.vue'; -export default defineComponent({ - components: { - MkPagination, - MkUserInfo, - }, +const props = defineProps<{ + user: misskey.entities.User; + type: 'following' | 'followers'; +}>(); - props: { - user: { - type: Object, - required: true - }, - type: { - type: String, - required: true - }, - }, +const followingPagination = { + endpoint: 'users/following' as const, + limit: 20, + params: computed(() => ({ + userId: props.user.id, + })), +}; - data() { - return { - pagination: { - endpoint: () => this.type === 'following' ? 'users/following' : 'users/followers', - limit: 20, - params: { - userId: this.user.id, - } - }, - }; - }, - - watch: { - type() { - this.$refs.list.reload(); - }, - - user() { - this.$refs.list.reload(); - } - } -}); +const followersPagination = { + endpoint: 'users/followers' as const, + limit: 20, + params: computed(() => ({ + userId: props.user.id, + })), +}; </script> <style lang="scss" scoped> diff --git a/packages/client/src/pages/user/gallery.vue b/packages/client/src/pages/user/gallery.vue index 860aa9f44f..07dda4a292 100644 --- a/packages/client/src/pages/user/gallery.vue +++ b/packages/client/src/pages/user/gallery.vue @@ -9,7 +9,7 @@ </template> <script lang="ts"> -import { defineComponent } from 'vue'; +import { computed, defineComponent } from 'vue'; import MkGalleryPostPreview from '@/components/gallery-post-preview.vue'; import MkPagination from '@/components/ui/pagination.vue'; @@ -29,20 +29,14 @@ export default defineComponent({ data() { return { pagination: { - endpoint: 'users/gallery/posts', + endpoint: 'users/gallery/posts' as const, limit: 6, - params: () => ({ + params: computed(() => ({ userId: this.user.id - }) + })), }, }; }, - - watch: { - user() { - this.$refs.list.reload(); - } - } }); </script> diff --git a/packages/client/src/pages/user/index.activity.vue b/packages/client/src/pages/user/index.activity.vue index e51d6c6090..43a4f476f1 100644 --- a/packages/client/src/pages/user/index.activity.vue +++ b/packages/client/src/pages/user/index.activity.vue @@ -8,27 +8,16 @@ </MkContainer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as os from '@/os'; +<script lang="ts" setup> +import { } from 'vue'; +import * as misskey from 'misskey-js'; import MkContainer from '@/components/ui/container.vue'; import MkChart from '@/components/chart.vue'; -export default defineComponent({ - components: { - MkContainer, - MkChart, - }, - props: { - user: { - type: Object, - required: true - }, - limit: { - type: Number, - required: false, - default: 40 - } - }, +const props = withDefaults(defineProps<{ + user: misskey.entities.User; + limit?: number; +}>(), { + limit: 40, }); </script> diff --git a/packages/client/src/pages/user/index.timeline.vue b/packages/client/src/pages/user/index.timeline.vue index 2ffa496979..a1329a7411 100644 --- a/packages/client/src/pages/user/index.timeline.vue +++ b/packages/client/src/pages/user/index.timeline.vue @@ -1,60 +1,36 @@ <template> <div v-sticky-container class="yrzkoczt"> - <MkTab v-model="with_" class="tab"> + <MkTab v-model="include" class="tab"> <option :value="null">{{ $ts.notes }}</option> <option value="replies">{{ $ts.notesAndReplies }}</option> <option value="files">{{ $ts.withFiles }}</option> </MkTab> - <XNotes ref="timeline" :no-gap="true" :pagination="pagination" @before="$emit('before')" @after="e => $emit('after', e)"/> + <XNotes :no-gap="true" :pagination="pagination"/> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { ref, computed } from 'vue'; +import * as misskey from 'misskey-js'; import XNotes from '@/components/notes.vue'; import MkTab from '@/components/tab.vue'; import * as os from '@/os'; -export default defineComponent({ - components: { - XNotes, - MkTab, - }, +const props = defineProps<{ + user: misskey.entities.UserDetailed; +}>(); - props: { - user: { - type: Object, - required: true, - }, - }, +const include = ref<string | null>(null); - data() { - return { - date: null, - with_: null, - pagination: { - endpoint: 'users/notes', - limit: 10, - params: init => ({ - userId: this.user.id, - includeReplies: this.with_ === 'replies', - withFiles: this.with_ === 'files', - untilDate: init ? undefined : (this.date ? this.date.getTime() : undefined), - }) - } - }; - }, - - watch: { - user() { - this.$refs.timeline.reload(); - }, - - with_() { - this.$refs.timeline.reload(); - }, - }, -}); +const pagination = { + endpoint: 'users/notes' as const, + limit: 10, + params: computed(() => ({ + userId: props.user.id, + includeReplies: include.value === 'replies', + withFiles: include.value === 'files', + })), +}; </script> <style lang="scss" scoped> diff --git a/packages/client/src/pages/user/index.vue b/packages/client/src/pages/user/index.vue index 0b96368587..599e24d81c 100644 --- a/packages/client/src/pages/user/index.vue +++ b/packages/client/src/pages/user/index.vue @@ -1,196 +1,125 @@ <template> <div> -<transition name="fade" mode="out-in"> - <div v-if="user && narrow === false" class="ftskorzw wide"> - <MkRemoteCaution v-if="user.host != null" :href="user.url"/> + <transition name="fade" mode="out-in"> + <MkSpacer v-if="user" :content-max="narrow ? 800 : 1100"> + <div v-size="{ max: [500] }" class="ftskorzw" :class="{ wide: !narrow }"> + <div class="main"> + <!-- TODO --> + <!-- <div class="punished" v-if="user.isSuspended"><i class="fas fa-exclamation-triangle" style="margin-right: 8px;"></i> {{ $ts.userSuspended }}</div> --> + <!-- <div class="punished" v-if="user.isSilenced"><i class="fas fa-exclamation-triangle" style="margin-right: 8px;"></i> {{ $ts.userSilenced }}</div> --> - <div class="banner-container" :style="style"> - <div ref="banner" class="banner" :style="style"></div> - </div> - <div class="contents"> - <div class="side _forceContainerFull_"> - <MkAvatar class="avatar" :user="user" :disable-preview="true" :show-indicator="true"/> - <div class="name"> - <MkUserName :user="user" :nowrap="false" class="name"/> - <MkAcct :user="user" :detail="true" class="acct"/> - </div> - <div v-if="$i && $i.id != user.id && user.isFollowed" class="followed"><span>{{ $ts.followsYou }}</span></div> - <div class="status"> - <MkA :to="userPage(user)" :class="{ active: page === 'index' }"> - <b>{{ number(user.notesCount) }}</b> - <span>{{ $ts.notes }}</span> - </MkA> - <MkA :to="userPage(user, 'following')" :class="{ active: page === 'following' }"> - <b>{{ number(user.followingCount) }}</b> - <span>{{ $ts.following }}</span> - </MkA> - <MkA :to="userPage(user, 'followers')" :class="{ active: page === 'followers' }"> - <b>{{ number(user.followersCount) }}</b> - <span>{{ $ts.followers }}</span> - </MkA> - </div> - <div class="description"> - <Mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$i" :custom-emojis="user.emojis"/> - <p v-else class="empty">{{ $ts.noAccountDescription }}</p> - </div> - <div class="fields system"> - <dl v-if="user.location" class="field"> - <dt class="name"><i class="fas fa-map-marker fa-fw"></i> {{ $ts.location }}</dt> - <dd class="value">{{ user.location }}</dd> - </dl> - <dl v-if="user.birthday" class="field"> - <dt class="name"><i class="fas fa-birthday-cake fa-fw"></i> {{ $ts.birthday }}</dt> - <dd class="value">{{ user.birthday.replace('-', '/').replace('-', '/') }} ({{ $t('yearsOld', { age }) }})</dd> - </dl> - <dl class="field"> - <dt class="name"><i class="fas fa-calendar-alt fa-fw"></i> {{ $ts.registeredDate }}</dt> - <dd class="value">{{ new Date(user.createdAt).toLocaleString() }} (<MkTime :time="user.createdAt"/>)</dd> - </dl> - </div> - <div v-if="user.fields.length > 0" class="fields"> - <dl v-for="(field, i) in user.fields" :key="i" class="field"> - <dt class="name"> - <Mfm :text="field.name" :plain="true" :custom-emojis="user.emojis" :colored="false"/> - </dt> - <dd class="value"> - <Mfm :text="field.value" :author="user" :i="$i" :custom-emojis="user.emojis" :colored="false"/> - </dd> - </dl> - </div> - <XActivity :key="user.id" :user="user" class="_gap"/> - <XPhotos :key="user.id" :user="user" class="_gap"/> - </div> - <div class="main"> - <div class="actions"> - <button class="menu _button" @click="menu"><i class="fas fa-ellipsis-h"></i></button> - <MkFollowButton v-if="!$i || $i.id != user.id" :user="user" :inline="true" :transparent="false" :full="true" large class="koudoku"/> - </div> - <template v-if="page === 'index'"> - <div v-if="user.pinnedNotes.length > 0" class="_gap"> - <XNote v-for="note in user.pinnedNotes" :key="note.id" class="note _gap" :note="note" :pinned="true" @update:note="pinnedNoteUpdated(note, $event)"/> - </div> - <div class="_gap"> - <XUserTimeline :user="user"/> - </div> - </template> - <XFollowList v-else-if="page === 'following'" type="following" :user="user" class="_gap"/> - <XFollowList v-else-if="page === 'followers'" type="followers" :user="user" class="_gap"/> - <XClips v-else-if="page === 'clips'" :user="user" class="_gap"/> - <XPages v-else-if="page === 'pages'" :user="user" class="_gap"/> - </div> - </div> - </div> - <MkSpacer v-else-if="user && narrow === true" :content-max="800"> - <div v-size="{ max: [500] }" class="ftskorzw narrow"> - <!-- TODO --> - <!-- <div class="punished" v-if="user.isSuspended"><i class="fas fa-exclamation-triangle" style="margin-right: 8px;"></i> {{ $ts.userSuspended }}</div> --> - <!-- <div class="punished" v-if="user.isSilenced"><i class="fas fa-exclamation-triangle" style="margin-right: 8px;"></i> {{ $ts.userSilenced }}</div> --> + <div class="profile"> + <MkRemoteCaution v-if="user.host != null" :href="user.url" class="warn"/> - <div class="profile"> - <MkRemoteCaution v-if="user.host != null" :href="user.url" class="warn"/> - - <div :key="user.id" class="_block main"> - <div class="banner-container" :style="style"> - <div ref="banner" class="banner" :style="style"></div> - <div class="fade"></div> - <div class="title"> - <MkUserName class="name" :user="user" :nowrap="true"/> - <div class="bottom"> - <span class="username"><MkAcct :user="user" :detail="true" /></span> - <span v-if="user.isAdmin" :title="$ts.isAdmin" style="color: var(--badge);"><i class="fas fa-bookmark"></i></span> - <span v-if="!user.isAdmin && user.isModerator" :title="$ts.isModerator" style="color: var(--badge);"><i class="far fa-bookmark"></i></span> - <span v-if="user.isLocked" :title="$ts.isLocked"><i class="fas fa-lock"></i></span> - <span v-if="user.isBot" :title="$ts.isBot"><i class="fas fa-robot"></i></span> + <div :key="user.id" class="_block main"> + <div class="banner-container" :style="style"> + <div ref="banner" class="banner" :style="style"></div> + <div class="fade"></div> + <div class="title"> + <MkUserName class="name" :user="user" :nowrap="true"/> + <div class="bottom"> + <span class="username"><MkAcct :user="user" :detail="true" /></span> + <span v-if="user.isAdmin" :title="$ts.isAdmin" style="color: var(--badge);"><i class="fas fa-bookmark"></i></span> + <span v-if="!user.isAdmin && user.isModerator" :title="$ts.isModerator" style="color: var(--badge);"><i class="far fa-bookmark"></i></span> + <span v-if="user.isLocked" :title="$ts.isLocked"><i class="fas fa-lock"></i></span> + <span v-if="user.isBot" :title="$ts.isBot"><i class="fas fa-robot"></i></span> + </div> + </div> + <span v-if="$i && $i.id != user.id && user.isFollowed" class="followed">{{ $ts.followsYou }}</span> + <div v-if="$i" class="actions"> + <button class="menu _button" @click="menu"><i class="fas fa-ellipsis-h"></i></button> + <MkFollowButton v-if="$i.id != user.id" :user="user" :inline="true" :transparent="false" :full="true" class="koudoku"/> + </div> + </div> + <MkAvatar class="avatar" :user="user" :disable-preview="true" :show-indicator="true"/> + <div class="title"> + <MkUserName :user="user" :nowrap="false" class="name"/> + <div class="bottom"> + <span class="username"><MkAcct :user="user" :detail="true" /></span> + <span v-if="user.isAdmin" :title="$ts.isAdmin" style="color: var(--badge);"><i class="fas fa-bookmark"></i></span> + <span v-if="!user.isAdmin && user.isModerator" :title="$ts.isModerator" style="color: var(--badge);"><i class="far fa-bookmark"></i></span> + <span v-if="user.isLocked" :title="$ts.isLocked"><i class="fas fa-lock"></i></span> + <span v-if="user.isBot" :title="$ts.isBot"><i class="fas fa-robot"></i></span> + </div> + </div> + <div class="description"> + <Mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$i" :custom-emojis="user.emojis"/> + <p v-else class="empty">{{ $ts.noAccountDescription }}</p> + </div> + <div class="fields system"> + <dl v-if="user.location" class="field"> + <dt class="name"><i class="fas fa-map-marker fa-fw"></i> {{ $ts.location }}</dt> + <dd class="value">{{ user.location }}</dd> + </dl> + <dl v-if="user.birthday" class="field"> + <dt class="name"><i class="fas fa-birthday-cake fa-fw"></i> {{ $ts.birthday }}</dt> + <dd class="value">{{ user.birthday.replace('-', '/').replace('-', '/') }} ({{ $t('yearsOld', { age }) }})</dd> + </dl> + <dl class="field"> + <dt class="name"><i class="fas fa-calendar-alt fa-fw"></i> {{ $ts.registeredDate }}</dt> + <dd class="value">{{ new Date(user.createdAt).toLocaleString() }} (<MkTime :time="user.createdAt"/>)</dd> + </dl> + </div> + <div v-if="user.fields.length > 0" class="fields"> + <dl v-for="(field, i) in user.fields" :key="i" class="field"> + <dt class="name"> + <Mfm :text="field.name" :plain="true" :custom-emojis="user.emojis" :colored="false"/> + </dt> + <dd class="value"> + <Mfm :text="field.value" :author="user" :i="$i" :custom-emojis="user.emojis" :colored="false"/> + </dd> + </dl> + </div> + <div class="status"> + <MkA v-click-anime :to="userPage(user)" :class="{ active: page === 'index' }"> + <b>{{ number(user.notesCount) }}</b> + <span>{{ $ts.notes }}</span> + </MkA> + <MkA v-click-anime :to="userPage(user, 'following')" :class="{ active: page === 'following' }"> + <b>{{ number(user.followingCount) }}</b> + <span>{{ $ts.following }}</span> + </MkA> + <MkA v-click-anime :to="userPage(user, 'followers')" :class="{ active: page === 'followers' }"> + <b>{{ number(user.followersCount) }}</b> + <span>{{ $ts.followers }}</span> + </MkA> </div> - </div> - <span v-if="$i && $i.id != user.id && user.isFollowed" class="followed">{{ $ts.followsYou }}</span> - <div v-if="$i" class="actions"> - <button class="menu _button" @click="menu"><i class="fas fa-ellipsis-h"></i></button> - <MkFollowButton v-if="$i.id != user.id" :user="user" :inline="true" :transparent="false" :full="true" class="koudoku"/> - </div> - </div> - <MkAvatar class="avatar" :user="user" :disable-preview="true" :show-indicator="true"/> - <div class="title"> - <MkUserName :user="user" :nowrap="false" class="name"/> - <div class="bottom"> - <span class="username"><MkAcct :user="user" :detail="true" /></span> - <span v-if="user.isAdmin" :title="$ts.isAdmin" style="color: var(--badge);"><i class="fas fa-bookmark"></i></span> - <span v-if="!user.isAdmin && user.isModerator" :title="$ts.isModerator" style="color: var(--badge);"><i class="far fa-bookmark"></i></span> - <span v-if="user.isLocked" :title="$ts.isLocked"><i class="fas fa-lock"></i></span> - <span v-if="user.isBot" :title="$ts.isBot"><i class="fas fa-robot"></i></span> </div> </div> - <div class="description"> - <Mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$i" :custom-emojis="user.emojis"/> - <p v-else class="empty">{{ $ts.noAccountDescription }}</p> - </div> - <div class="fields system"> - <dl v-if="user.location" class="field"> - <dt class="name"><i class="fas fa-map-marker fa-fw"></i> {{ $ts.location }}</dt> - <dd class="value">{{ user.location }}</dd> - </dl> - <dl v-if="user.birthday" class="field"> - <dt class="name"><i class="fas fa-birthday-cake fa-fw"></i> {{ $ts.birthday }}</dt> - <dd class="value">{{ user.birthday.replace('-', '/').replace('-', '/') }} ({{ $t('yearsOld', { age }) }})</dd> - </dl> - <dl class="field"> - <dt class="name"><i class="fas fa-calendar-alt fa-fw"></i> {{ $ts.registeredDate }}</dt> - <dd class="value">{{ new Date(user.createdAt).toLocaleString() }} (<MkTime :time="user.createdAt"/>)</dd> - </dl> - </div> - <div v-if="user.fields.length > 0" class="fields"> - <dl v-for="(field, i) in user.fields" :key="i" class="field"> - <dt class="name"> - <Mfm :text="field.name" :plain="true" :custom-emojis="user.emojis" :colored="false"/> - </dt> - <dd class="value"> - <Mfm :text="field.value" :author="user" :i="$i" :custom-emojis="user.emojis" :colored="false"/> - </dd> - </dl> - </div> - <div class="status"> - <MkA v-click-anime :to="userPage(user)" :class="{ active: page === 'index' }"> - <b>{{ number(user.notesCount) }}</b> - <span>{{ $ts.notes }}</span> - </MkA> - <MkA v-click-anime :to="userPage(user, 'following')" :class="{ active: page === 'following' }"> - <b>{{ number(user.followingCount) }}</b> - <span>{{ $ts.following }}</span> - </MkA> - <MkA v-click-anime :to="userPage(user, 'followers')" :class="{ active: page === 'followers' }"> - <b>{{ number(user.followersCount) }}</b> - <span>{{ $ts.followers }}</span> - </MkA> - </div> - </div> - </div> - <div class="contents"> - <template v-if="page === 'index'"> - <div> - <div v-if="user.pinnedNotes.length > 0" class="_gap"> - <XNote v-for="note in user.pinnedNotes" :key="note.id" class="note _block" :note="note" :pinned="true" @update:note="pinnedNoteUpdated(note, $event)"/> - </div> - <MkInfo v-else-if="$i && $i.id === user.id">{{ $ts.userPagePinTip }}</MkInfo> - <XPhotos :key="user.id" :user="user"/> - <XActivity :key="user.id" :user="user" style="margin-top: var(--margin);"/> - </div> - <div> - <XUserTimeline :user="user"/> + <div class="contents"> + <template v-if="page === 'index'"> + <div> + <div v-if="user.pinnedNotes.length > 0" class="_gap"> + <XNote v-for="note in user.pinnedNotes" :key="note.id" class="note _block" :note="note" :pinned="true"/> + </div> + <MkInfo v-else-if="$i && $i.id === user.id">{{ $ts.userPagePinTip }}</MkInfo> + <template v-if="narrow"> + <XPhotos :key="user.id" :user="user"/> + <XActivity :key="user.id" :user="user" style="margin-top: var(--margin);"/> + </template> + </div> + <div> + <XUserTimeline :user="user"/> + </div> + </template> + <XFollowList v-else-if="page === 'following'" type="following" :user="user" class="_content _gap"/> + <XFollowList v-else-if="page === 'followers'" type="followers" :user="user" class="_content _gap"/> + <XReactions v-else-if="page === 'reactions'" :user="user" class="_gap"/> + <XClips v-else-if="page === 'clips'" :user="user" class="_gap"/> + <XPages v-else-if="page === 'pages'" :user="user" class="_gap"/> + <XGallery v-else-if="page === 'gallery'" :user="user" class="_gap"/> </div> - </template> - <XFollowList v-else-if="page === 'following'" type="following" :user="user" class="_content _gap"/> - <XFollowList v-else-if="page === 'followers'" type="followers" :user="user" class="_content _gap"/> - <XReactions v-else-if="page === 'reactions'" :user="user" class="_gap"/> - <XClips v-else-if="page === 'clips'" :user="user" class="_gap"/> - <XPages v-else-if="page === 'pages'" :user="user" class="_gap"/> - <XGallery v-else-if="page === 'gallery'" :user="user" class="_gap"/> + </div> + <div v-if="!narrow" class="sub"> + <XPhotos :key="user.id" :user="user"/> + <XActivity :key="user.id" :user="user" style="margin-top: var(--margin);"/> + </div> </div> - </div> - </MkSpacer> - <MkError v-else-if="error" @retry="fetch()"/> - <MkLoading v-else/> -</transition> + </MkSpacer> + <MkError v-else-if="error" @retry="fetch()"/> + <MkLoading v-else/> + </transition> </div> </template> @@ -314,7 +243,7 @@ export default defineComponent({ mounted() { window.requestAnimationFrame(this.parallaxLoop); - this.narrow = true//this.$el.clientWidth < 1000; + this.narrow = this.$el.clientWidth < 1000; }, beforeUnmount() { @@ -356,11 +285,6 @@ export default defineComponent({ banner.style.backgroundPosition = `center calc(50% - ${pos}px)`; }, - pinnedNoteUpdated(oldValue, newValue) { - const i = this.user.pinnedNotes.findIndex(n => n === oldValue); - this.user.pinnedNotes[i] = newValue; - }, - number, userPage @@ -378,447 +302,289 @@ export default defineComponent({ opacity: 0; } -.ftskorzw.wide { +.ftskorzw { - > .banner-container { - position: relative; - height: 300px; - overflow: hidden; - background-size: cover; - background-position: center; + > .main { - > .banner { - height: 100%; - background-color: #4c5e6d; - background-size: cover; - background-position: center; - box-shadow: 0 0 128px rgba(0, 0, 0, 0.5) inset; - will-change: background-position; + > .punished { + font-size: 0.8em; + padding: 16px; } - } - - > .contents { - display: flex; - padding: 16px; - - > .side { - width: 360px; - > .avatar { - display: block; - width: 180px; - height: 180px; - margin: -130px auto 0 auto; - } - - > .name { - padding: 16px 0px 20px 0; - text-align: center; - - > .name { - display: block; - font-size: 1.75em; - font-weight: bold; - } - } - - > .followed { - text-align: center; - - > span { - display: inline-block; - font-size: 80%; - padding: 8px 12px; - margin-bottom: 20px; - border: solid 0.5px var(--divider); - border-radius: 999px; - } - } + > .profile { - > .status { - display: flex; - padding: 20px 16px; - border-top: solid 0.5px var(--divider); - font-size: 90%; + > .main { + position: relative; + overflow: hidden; - > a { - flex: 1; - text-align: center; + > .banner-container { + position: relative; + height: 250px; + overflow: hidden; + background-size: cover; + background-position: center; - &.active { - color: var(--accent); + > .banner { + height: 100%; + background-color: #4c5e6d; + background-size: cover; + background-position: center; + box-shadow: 0 0 128px rgba(0, 0, 0, 0.5) inset; + will-change: background-position; } - &:hover { - text-decoration: none; + > .fade { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 78px; + background: linear-gradient(transparent, rgba(#000, 0.7)); } - > b { - display: block; - line-height: 16px; + > .followed { + position: absolute; + top: 12px; + left: 12px; + padding: 4px 8px; + color: #fff; + background: rgba(0, 0, 0, 0.7); + font-size: 0.7em; + border-radius: 6px; } - > span { - font-size: 75%; - } - } - } + > .actions { + position: absolute; + top: 12px; + right: 12px; + -webkit-backdrop-filter: var(--blur, blur(8px)); + backdrop-filter: var(--blur, blur(8px)); + background: rgba(0, 0, 0, 0.2); + padding: 8px; + border-radius: 24px; - > .description { - padding: 20px 16px; - border-top: solid 0.5px var(--divider); - font-size: 90%; - } + > .menu { + vertical-align: bottom; + height: 31px; + width: 31px; + color: #fff; + text-shadow: 0 0 8px #000; + font-size: 16px; + } - > .fields { - padding: 20px 16px; - border-top: solid 0.5px var(--divider); - font-size: 90%; + > .koudoku { + margin-left: 4px; + vertical-align: bottom; + } + } - > .field { - display: flex; - padding: 0; - margin: 0; - align-items: center; + > .title { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + padding: 0 0 8px 154px; + box-sizing: border-box; + color: #fff; - &:not(:last-child) { - margin-bottom: 8px; - } + > .name { + display: block; + margin: 0; + line-height: 32px; + font-weight: bold; + font-size: 1.8em; + text-shadow: 0 0 8px #000; + } - > .name { - width: 30%; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - font-weight: bold; - } + > .bottom { + > * { + display: inline-block; + margin-right: 16px; + line-height: 20px; + opacity: 0.8; - > .value { - width: 70%; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - margin: 0; + &.username { + font-weight: bold; + } + } + } } } - } - } - > .main { - flex: 1; - margin-left: var(--margin); - min-width: 0; - - > .nav { - display: flex; - align-items: center; - margin-top: var(--margin); - //font-size: 120%; - font-weight: bold; - - > .link { - display: inline-block; - padding: 15px 24px 12px 24px; + > .title { + display: none; text-align: center; - border-bottom: solid 3px transparent; - - &:hover { - text-decoration: none; - } - - &.active { - color: var(--accent); - border-bottom-color: var(--accent); - } + padding: 50px 8px 16px 8px; + font-weight: bold; + border-bottom: solid 0.5px var(--divider); - &:not(.active):hover { - color: var(--fgHighlighted); + > .bottom { + > * { + display: inline-block; + margin-right: 8px; + opacity: 0.8; + } } + } - > .icon { - margin-right: 6px; - } + > .avatar { + display: block; + position: absolute; + top: 170px; + left: 16px; + z-index: 2; + width: 120px; + height: 120px; + box-shadow: 1px 1px 3px rgba(#000, 0.2); } - > .actions { - display: flex; - align-items: center; - margin-left: auto; + > .description { + padding: 24px 24px 24px 154px; + font-size: 0.95em; - > .menu { - padding: 12px 16px; + > .empty { + margin: 0; + opacity: 0.5; } } - } - } - } -} - -.ftskorzw.narrow { - box-sizing: border-box; - overflow: clip; - background: var(--bg); - - > .punished { - font-size: 0.8em; - padding: 16px; - } - - > .profile { - - > .main { - position: relative; - overflow: hidden; - > .banner-container { - position: relative; - height: 250px; - overflow: hidden; - background-size: cover; - background-position: center; - - > .banner { - height: 100%; - background-color: #4c5e6d; - background-size: cover; - background-position: center; - box-shadow: 0 0 128px rgba(0, 0, 0, 0.5) inset; - will-change: background-position; - } + > .fields { + padding: 24px; + font-size: 0.9em; + border-top: solid 0.5px var(--divider); - > .fade { - position: absolute; - bottom: 0; - left: 0; - width: 100%; - height: 78px; - background: linear-gradient(transparent, rgba(#000, 0.7)); - } + > .field { + display: flex; + padding: 0; + margin: 0; + align-items: center; - > .followed { - position: absolute; - top: 12px; - left: 12px; - padding: 4px 8px; - color: #fff; - background: rgba(0, 0, 0, 0.7); - font-size: 0.7em; - border-radius: 6px; - } + &:not(:last-child) { + margin-bottom: 8px; + } - > .actions { - position: absolute; - top: 12px; - right: 12px; - -webkit-backdrop-filter: var(--blur, blur(8px)); - backdrop-filter: var(--blur, blur(8px)); - background: rgba(0, 0, 0, 0.2); - padding: 8px; - border-radius: 24px; + > .name { + width: 30%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + font-weight: bold; + text-align: center; + } - > .menu { - vertical-align: bottom; - height: 31px; - width: 31px; - color: #fff; - text-shadow: 0 0 8px #000; - font-size: 16px; + > .value { + width: 70%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + margin: 0; + } } - > .koudoku { - margin-left: 4px; - vertical-align: bottom; + &.system > .field > .name { } } - > .title { - position: absolute; - bottom: 0; - left: 0; - width: 100%; - padding: 0 0 8px 154px; - box-sizing: border-box; - color: #fff; + > .status { + display: flex; + padding: 24px; + border-top: solid 0.5px var(--divider); - > .name { - display: block; - margin: 0; - line-height: 32px; - font-weight: bold; - font-size: 1.8em; - text-shadow: 0 0 8px #000; - } + > a { + flex: 1; + text-align: center; - > .bottom { - > * { - display: inline-block; - margin-right: 16px; - line-height: 20px; - opacity: 0.8; + &.active { + color: var(--accent); + } - &.username { - font-weight: bold; - } + &:hover { + text-decoration: none; } - } - } - } - > .title { - display: none; - text-align: center; - padding: 50px 8px 16px 8px; - font-weight: bold; - border-bottom: solid 0.5px var(--divider); + > b { + display: block; + line-height: 16px; + } - > .bottom { - > * { - display: inline-block; - margin-right: 8px; - opacity: 0.8; + > span { + font-size: 70%; + } } } } + } - > .avatar { - display: block; - position: absolute; - top: 170px; - left: 16px; - z-index: 2; - width: 120px; - height: 120px; - box-shadow: 1px 1px 3px rgba(#000, 0.2); - } - - > .description { - padding: 24px 24px 24px 154px; - font-size: 0.95em; - - > .empty { - margin: 0; - opacity: 0.5; - } + > .contents { + > .content { + margin-bottom: var(--margin); } + } + } - > .fields { - padding: 24px; - font-size: 0.9em; - border-top: solid 0.5px var(--divider); - - > .field { - display: flex; - padding: 0; - margin: 0; - align-items: center; + &.max-width_500px { + > .main { + > .profile > .main { + > .banner-container { + height: 140px; - &:not(:last-child) { - margin-bottom: 8px; + > .fade { + display: none; } - > .name { - width: 30%; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - font-weight: bold; - text-align: center; - } - - > .value { - width: 70%; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - margin: 0; + > .title { + display: none; } } - &.system > .field > .name { + > .title { + display: block; } - } - > .status { - display: flex; - padding: 24px; - border-top: solid 0.5px var(--divider); + > .avatar { + top: 90px; + left: 0; + right: 0; + width: 92px; + height: 92px; + margin: auto; + } - > a { - flex: 1; + > .description { + padding: 16px; text-align: center; - - &.active { - color: var(--accent); - } - - &:hover { - text-decoration: none; - } - - > b { - display: block; - line-height: 16px; - } - - > span { - font-size: 70%; - } } - } - } - } - > .contents { - > .content { - margin-bottom: var(--margin); - } - } - - &.max-width_500px { - > .profile > .main { - > .banner-container { - height: 140px; - - > .fade { - display: none; + > .fields { + padding: 16px; } - > .title { - display: none; + > .status { + padding: 16px; } } - > .title { - display: block; - } - - > .avatar { - top: 90px; - left: 0; - right: 0; - width: 92px; - height: 92px; - margin: auto; - } - - > .description { - padding: 16px; - text-align: center; + > .contents { + > .nav { + font-size: 80%; + } } + } + } - > .fields { - padding: 16px; - } + &.wide { + display: flex; + width: 100%; - > .status { - padding: 16px; - } + > .main { + width: 100%; + min-width: 0; } - > .contents { - > .nav { - font-size: 80%; - } + > .sub { + max-width: 350px; + min-width: 350px; + margin-left: var(--margin); } } } diff --git a/packages/client/src/pages/user/pages.vue b/packages/client/src/pages/user/pages.vue index 40d1fe3842..ad101158e0 100644 --- a/packages/client/src/pages/user/pages.vue +++ b/packages/client/src/pages/user/pages.vue @@ -6,42 +6,23 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; +import * as misskey from 'misskey-js'; import MkPagePreview from '@/components/page-preview.vue'; import MkPagination from '@/components/ui/pagination.vue'; -export default defineComponent({ - components: { - MkPagination, - MkPagePreview, - }, +const props = defineProps<{ + user: misskey.entities.User; +}>(); - props: { - user: { - type: Object, - required: true - }, - }, - - data() { - return { - pagination: { - endpoint: 'users/pages', - limit: 20, - params: { - userId: this.user.id, - } - }, - }; - }, - - watch: { - user() { - this.$refs.list.reload(); - } - } -}); +const pagination = { + endpoint: 'users/pages' as const, + limit: 20, + params: computed(() => ({ + userId: props.user.id, + })), +}; </script> <style lang="scss" scoped> diff --git a/packages/client/src/pages/user/reactions.vue b/packages/client/src/pages/user/reactions.vue index 69c27de55b..d2c1f92ebb 100644 --- a/packages/client/src/pages/user/reactions.vue +++ b/packages/client/src/pages/user/reactions.vue @@ -7,50 +7,30 @@ <MkReactionIcon class="reaction" :reaction="item.type" :custom-emojis="item.note.emojis" :no-style="true"/> <MkTime :time="item.createdAt" class="createdAt"/> </div> - <MkNote :key="item.id" :note="item.note" @update:note="updated(note, $event)"/> + <MkNote :key="item.id" :note="item.note"/> </div> </MkPagination> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; +import * as misskey from 'misskey-js'; import MkPagination from '@/components/ui/pagination.vue'; import MkNote from '@/components/note.vue'; import MkReactionIcon from '@/components/reaction-icon.vue'; -export default defineComponent({ - components: { - MkPagination, - MkNote, - MkReactionIcon, - }, +const props = defineProps<{ + user: misskey.entities.User; +}>(); - props: { - user: { - type: Object, - required: true - }, - }, - - data() { - return { - pagination: { - endpoint: 'users/reactions', - limit: 20, - params: { - userId: this.user.id, - } - }, - }; - }, - - watch: { - user() { - this.$refs.list.reload(); - } - }, -}); +const pagination = { + endpoint: 'users/reactions' as const, + limit: 20, + params: computed(() => ({ + userId: props.user.id, + })), +}; </script> <style lang="scss" scoped> diff --git a/packages/client/src/pages/v.vue b/packages/client/src/pages/v.vue deleted file mode 100644 index 3b1bb20861..0000000000 --- a/packages/client/src/pages/v.vue +++ /dev/null @@ -1,29 +0,0 @@ -<template> -<div> - <section class="_section"> - <div class="_content" style="text-align: center;"> - <img src="/static-assets/icons/512.png" alt="" style="display: block; width: 100px; margin: 0 auto; border-radius: 16px;"/> - <div style="margin-top: 0.75em;">Misskey</div> - <div style="opacity: 0.5;">v{{ version }}</div> - </div> - </section> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import { version } from '@/config'; -import * as symbols from '@/symbols'; - -export default defineComponent({ - data() { - return { - [symbols.PAGE_INFO]: { - title: 'Misskey', - icon: null - }, - version, - } - }, -}); -</script> diff --git a/packages/client/src/pizzax.ts b/packages/client/src/pizzax.ts index 396abc2418..fa35df5511 100644 --- a/packages/client/src/pizzax.ts +++ b/packages/client/src/pizzax.ts @@ -1,6 +1,7 @@ import { onUnmounted, Ref, ref, watch } from 'vue'; import { $i } from './account'; import { api } from './os'; +import { stream } from './stream'; type StateDef = Record<string, { where: 'account' | 'device' | 'deviceAccount'; @@ -9,6 +10,8 @@ type StateDef = Record<string, { type ArrayElement<A> = A extends readonly (infer T)[] ? T : never; +const connection = $i && stream.useChannel('main'); + export class Storage<T extends StateDef> { public readonly key: string; public readonly keyForLocalStorage: string; @@ -51,7 +54,7 @@ export class Storage<T extends StateDef> { if ($i) { // なぜかsetTimeoutしないとapi関数内でエラーになる(おそらく循環参照してることに原因がありそう) - setTimeout(() => { + window.setTimeout(() => { api('i/registry/get-all', { scope: ['client', this.key] }).then(kvs => { const cache = {}; for (const [k, v] of Object.entries(def)) { @@ -69,8 +72,19 @@ export class Storage<T extends StateDef> { localStorage.setItem(this.keyForLocalStorage + '::cache::' + $i.id, JSON.stringify(cache)); }); }, 1); + // streamingのuser storage updateイベントを監視して更新 + connection?.on('registryUpdated', ({ scope, key, value }: { scope: string[], key: keyof T, value: T[typeof key]['default'] }) => { + if (scope.length !== 2 || scope[0] !== 'client' || scope[1] !== this.key || this.state[key] === value) return; + + this.state[key] = value; + this.reactiveState[key].value = value; - // TODO: streamingのuser storage updateイベントを監視して更新 + const cache = JSON.parse(localStorage.getItem(this.keyForLocalStorage + '::cache::' + $i.id) || '{}'); + if (cache[key] !== value) { + cache[key] = value; + localStorage.setItem(this.keyForLocalStorage + '::cache::' + $i.id, JSON.stringify(cache)); + } + }); } } diff --git a/packages/client/src/router.ts b/packages/client/src/router.ts index 9b4dd162f3..ec48b76fdf 100644 --- a/packages/client/src/router.ts +++ b/packages/client/src/router.ts @@ -20,7 +20,6 @@ const defaultRoutes = [ { path: '/@:acct/:page?', name: 'user', component: page('user/index'), props: route => ({ acct: route.params.acct, page: route.params.page || 'index' }) }, { path: '/@:user/pages/:page', component: page('page'), props: route => ({ pageName: route.params.page, username: route.params.user }) }, { path: '/@:user/pages/:pageName/view-source', component: page('page-editor/page-editor'), props: route => ({ initUser: route.params.user, initPageName: route.params.pageName }) }, - { path: '/@:acct/room', props: true, component: page('room/room') }, { path: '/settings/:page(.*)?', name: 'settings', component: page('settings/index'), props: route => ({ initialPage: route.params.page || null }) }, { path: '/reset-password/:token?', component: page('reset-password'), props: route => ({ token: route.params.token }) }, { path: '/signup-complete/:code', component: page('signup-complete'), props: route => ({ code: route.params.code }) }, @@ -34,7 +33,7 @@ const defaultRoutes = [ { path: '/explore/tags/:tag', props: true, component: page('explore') }, { path: '/federation', component: page('federation') }, { path: '/emojis', component: page('emojis') }, - { path: '/search', component: page('search') }, + { path: '/search', component: page('search'), props: route => ({ query: route.query.q, channel: route.query.channel }) }, { path: '/pages', name: 'pages', component: page('pages') }, { path: '/pages/new', component: page('page-editor/page-editor') }, { path: '/pages/edit/:pageId', component: page('page-editor/page-editor'), props: route => ({ initPageId: route.params.pageId }) }, @@ -73,10 +72,7 @@ const defaultRoutes = [ { path: '/notes/:note', name: 'note', component: page('note'), props: route => ({ noteId: route.params.note }) }, { path: '/tags/:tag', component: page('tag'), props: route => ({ tag: route.params.tag }) }, { path: '/user-info/:user', component: page('user-info'), props: route => ({ userId: route.params.user }) }, - { path: '/user-ap-info/:user', component: page('user-ap-info'), props: route => ({ userId: route.params.user }) }, { path: '/instance-info/:host', component: page('instance-info'), props: route => ({ host: route.params.host }) }, - { path: '/games/reversi', component: page('reversi/index') }, - { path: '/games/reversi/:gameId', component: page('reversi/game'), props: route => ({ gameId: route.params.gameId }) }, { path: '/mfm-cheat-sheet', component: page('mfm-cheat-sheet') }, { path: '/api-console', component: page('api-console') }, { path: '/preview', component: page('preview') }, @@ -119,11 +115,11 @@ export const router = createRouter({ window._scroll = () => { // さらにHacky if (to.name === 'index') { window.scroll({ top: indexScrollPos, behavior: 'instant' }); - const i = setInterval(() => { + const i = window.setInterval(() => { window.scroll({ top: indexScrollPos, behavior: 'instant' }); }, 10); - setTimeout(() => { - clearInterval(i); + window.setTimeout(() => { + window.clearInterval(i); }, 500); } else { window.scroll({ top: 0, behavior: 'instant' }); diff --git a/packages/client/src/scripts/autocomplete.ts b/packages/client/src/scripts/autocomplete.ts index f2d5806484..f4a3a4c0fc 100644 --- a/packages/client/src/scripts/autocomplete.ts +++ b/packages/client/src/scripts/autocomplete.ts @@ -1,4 +1,4 @@ -import { Ref, ref } from 'vue'; +import { nextTick, Ref, ref } from 'vue'; import * as getCaretCoordinates from 'textarea-caret'; import { toASCII } from 'punycode/'; import { popup } from '@/os'; @@ -10,26 +10,23 @@ export class Autocomplete { q: Ref<string | null>; close: Function; } | null; - private textarea: any; - private vm: any; + private textarea: HTMLInputElement | HTMLTextAreaElement; private currentType: string; - private opts: { - model: string; - }; + private textRef: Ref<string>; private opening: boolean; private get text(): string { - return this.vm[this.opts.model]; + return this.textRef.value; } private set text(text: string) { - this.vm[this.opts.model] = text; + this.textRef.value = text; } /** * 対象のテキストエリアを与えてインスタンスを初期化します。 */ - constructor(textarea, vm, opts) { + constructor(textarea: HTMLInputElement | HTMLTextAreaElement, textRef: Ref<string>) { //#region BIND this.onInput = this.onInput.bind(this); this.complete = this.complete.bind(this); @@ -38,8 +35,7 @@ export class Autocomplete { this.suggestion = null; this.textarea = textarea; - this.vm = vm; - this.opts = opts; + this.textRef = textRef; this.opening = false; this.attach(); @@ -218,7 +214,7 @@ export class Autocomplete { this.text = `${trimmedBefore}@${acct} ${after}`; // キャレットを戻す - this.vm.$nextTick(() => { + nextTick(() => { this.textarea.focus(); const pos = trimmedBefore.length + (acct.length + 2); this.textarea.setSelectionRange(pos, pos); @@ -234,7 +230,7 @@ export class Autocomplete { this.text = `${trimmedBefore}#${value} ${after}`; // キャレットを戻す - this.vm.$nextTick(() => { + nextTick(() => { this.textarea.focus(); const pos = trimmedBefore.length + (value.length + 2); this.textarea.setSelectionRange(pos, pos); @@ -250,7 +246,7 @@ export class Autocomplete { this.text = trimmedBefore + value + after; // キャレットを戻す - this.vm.$nextTick(() => { + nextTick(() => { this.textarea.focus(); const pos = trimmedBefore.length + value.length; this.textarea.setSelectionRange(pos, pos); @@ -266,7 +262,7 @@ export class Autocomplete { this.text = `${trimmedBefore}$[${value} ]${after}`; // キャレットを戻す - this.vm.$nextTick(() => { + nextTick(() => { this.textarea.focus(); const pos = trimmedBefore.length + (value.length + 3); this.textarea.setSelectionRange(pos, pos); diff --git a/packages/client/src/scripts/check-word-mute.ts b/packages/client/src/scripts/check-word-mute.ts index 3b1fa75b1e..55637bb3b3 100644 --- a/packages/client/src/scripts/check-word-mute.ts +++ b/packages/client/src/scripts/check-word-mute.ts @@ -1,4 +1,4 @@ -export async function checkWordMute(note: Record<string, any>, me: Record<string, any> | null | undefined, mutedWords: string[][]): Promise<boolean> { +export function checkWordMute(note: Record<string, any>, me: Record<string, any> | null | undefined, mutedWords: string[][]): boolean { // 自分自身 if (me && (note.userId === me.id)) return false; diff --git a/packages/client/src/scripts/emojilist.ts b/packages/client/src/scripts/emojilist.ts index de7591f5a0..bd8689e4f8 100644 --- a/packages/client/src/scripts/emojilist.ts +++ b/packages/client/src/scripts/emojilist.ts @@ -1,7 +1,11 @@ -// initial converted from https://github.com/muan/emojilib/commit/242fe68be86ed6536843b83f7e32f376468b38fb -export const emojilist = require('../emojilist.json') as { +export const unicodeEmojiCategories = ['face', 'people', 'animals_and_nature', 'food_and_drink', 'activity', 'travel_and_places', 'objects', 'symbols', 'flags'] as const; + +export type UnicodeEmojiDef = { name: string; keywords: string[]; char: string; - category: 'people' | 'animals_and_nature' | 'food_and_drink' | 'activity' | 'travel_and_places' | 'objects' | 'symbols' | 'flags'; -}[]; + category: typeof unicodeEmojiCategories[number]; +} + +// initial converted from https://github.com/muan/emojilib/commit/242fe68be86ed6536843b83f7e32f376468b38fb +export const emojilist = require('../emojilist.json') as UnicodeEmojiDef[]; diff --git a/packages/client/src/scripts/form.ts b/packages/client/src/scripts/form.ts index 7bf6cec452..7f321cc0ae 100644 --- a/packages/client/src/scripts/form.ts +++ b/packages/client/src/scripts/form.ts @@ -23,9 +23,37 @@ export type FormItem = { enum: string[]; } | { label?: string; + type: 'radio'; + default: unknown | null; + hidden?: boolean; + options: { + label: string; + value: unknown; + }[]; +} | { + label?: string; + type: 'object'; + default: Record<string, unknown> | null; + hidden: true; +} | { + label?: string; type: 'array'; default: unknown[] | null; - hidden?: boolean; + hidden: true; }; export type Form = Record<string, FormItem>; + +type GetItemType<Item extends FormItem> = + Item['type'] extends 'string' ? string : + Item['type'] extends 'number' ? number : + Item['type'] extends 'boolean' ? boolean : + Item['type'] extends 'radio' ? unknown : + Item['type'] extends 'enum' ? string : + Item['type'] extends 'array' ? unknown[] : + Item['type'] extends 'object' ? Record<string, unknown> + : never; + +export type GetFormResultType<F extends Form> = { + [P in keyof F]: GetItemType<F[P]>; +}; diff --git a/packages/client/src/scripts/games/reversi/core.ts b/packages/client/src/scripts/games/reversi/core.ts deleted file mode 100644 index 0cb8922e19..0000000000 --- a/packages/client/src/scripts/games/reversi/core.ts +++ /dev/null @@ -1,263 +0,0 @@ -import { count, concat } from '@/scripts/array'; - -// MISSKEY REVERSI ENGINE - -/** - * true ... 黒 - * false ... 白 - */ -export type Color = boolean; -const BLACK = true; -const WHITE = false; - -export type MapPixel = 'null' | 'empty'; - -export type Options = { - isLlotheo: boolean; - canPutEverywhere: boolean; - loopedBoard: boolean; -}; - -export type Undo = { - /** - * 色 - */ - color: Color; - - /** - * どこに打ったか - */ - pos: number; - - /** - * 反転した石の位置の配列 - */ - effects: number[]; - - /** - * ターン - */ - turn: Color | null; -}; - -/** - * リバーシエンジン - */ -export default class Reversi { - public map: MapPixel[]; - public mapWidth: number; - public mapHeight: number; - public board: (Color | null | undefined)[]; - public turn: Color | null = BLACK; - public opts: Options; - - public prevPos = -1; - public prevColor: Color | null = null; - - private logs: Undo[] = []; - - /** - * ゲームを初期化します - */ - constructor(map: string[], opts: Options) { - //#region binds - this.put = this.put.bind(this); - //#endregion - - //#region Options - this.opts = opts; - if (this.opts.isLlotheo == null) this.opts.isLlotheo = false; - if (this.opts.canPutEverywhere == null) this.opts.canPutEverywhere = false; - if (this.opts.loopedBoard == null) this.opts.loopedBoard = false; - //#endregion - - //#region Parse map data - this.mapWidth = map[0].length; - this.mapHeight = map.length; - const mapData = map.join(''); - - this.board = mapData.split('').map(d => d === '-' ? null : d === 'b' ? BLACK : d === 'w' ? WHITE : undefined); - - this.map = mapData.split('').map(d => d === '-' || d === 'b' || d === 'w' ? 'empty' : 'null'); - //#endregion - - // ゲームが始まった時点で片方の色の石しかないか、始まった時点で勝敗が決定するようなマップの場合がある - if (!this.canPutSomewhere(BLACK)) - this.turn = this.canPutSomewhere(WHITE) ? WHITE : null; - } - - /** - * 黒石の数 - */ - public get blackCount() { - return count(BLACK, this.board); - } - - /** - * 白石の数 - */ - public get whiteCount() { - return count(WHITE, this.board); - } - - public transformPosToXy(pos: number): number[] { - const x = pos % this.mapWidth; - const y = Math.floor(pos / this.mapWidth); - return [x, y]; - } - - public transformXyToPos(x: number, y: number): number { - return x + (y * this.mapWidth); - } - - /** - * 指定のマスに石を打ちます - * @param color 石の色 - * @param pos 位置 - */ - public put(color: Color, pos: number) { - this.prevPos = pos; - this.prevColor = color; - - this.board[pos] = color; - - // 反転させられる石を取得 - const effects = this.effects(color, pos); - - // 反転させる - for (const pos of effects) { - this.board[pos] = color; - } - - const turn = this.turn; - - this.logs.push({ - color, - pos, - effects, - turn - }); - - this.calcTurn(); - } - - private calcTurn() { - // ターン計算 - this.turn = - this.canPutSomewhere(!this.prevColor) ? !this.prevColor : - this.canPutSomewhere(this.prevColor!) ? this.prevColor : - null; - } - - public undo() { - const undo = this.logs.pop()!; - this.prevColor = undo.color; - this.prevPos = undo.pos; - this.board[undo.pos] = null; - for (const pos of undo.effects) { - const color = this.board[pos]; - this.board[pos] = !color; - } - this.turn = undo.turn; - } - - /** - * 指定した位置のマップデータのマスを取得します - * @param pos 位置 - */ - public mapDataGet(pos: number): MapPixel { - const [x, y] = this.transformPosToXy(pos); - return x < 0 || y < 0 || x >= this.mapWidth || y >= this.mapHeight ? 'null' : this.map[pos]; - } - - /** - * 打つことができる場所を取得します - */ - public puttablePlaces(color: Color): number[] { - return Array.from(this.board.keys()).filter(i => this.canPut(color, i)); - } - - /** - * 打つことができる場所があるかどうかを取得します - */ - public canPutSomewhere(color: Color): boolean { - return this.puttablePlaces(color).length > 0; - } - - /** - * 指定のマスに石を打つことができるかどうかを取得します - * @param color 自分の色 - * @param pos 位置 - */ - public canPut(color: Color, pos: number): boolean { - return ( - this.board[pos] !== null ? false : // 既に石が置いてある場所には打てない - this.opts.canPutEverywhere ? this.mapDataGet(pos) == 'empty' : // 挟んでなくても置けるモード - this.effects(color, pos).length !== 0); // 相手の石を1つでも反転させられるか - } - - /** - * 指定のマスに石を置いた時の、反転させられる石を取得します - * @param color 自分の色 - * @param initPos 位置 - */ - public effects(color: Color, initPos: number): number[] { - const enemyColor = !color; - - const diffVectors: [number, number][] = [ - [ 0, -1], // 上 - [ +1, -1], // 右上 - [ +1, 0], // 右 - [ +1, +1], // 右下 - [ 0, +1], // 下 - [ -1, +1], // 左下 - [ -1, 0], // 左 - [ -1, -1] // 左上 - ]; - - const effectsInLine = ([dx, dy]: [number, number]): number[] => { - const nextPos = (x: number, y: number): [number, number] => [x + dx, y + dy]; - - const found: number[] = []; // 挟めるかもしれない相手の石を入れておく配列 - let [x, y] = this.transformPosToXy(initPos); - while (true) { - [x, y] = nextPos(x, y); - - // 座標が指し示す位置がボード外に出たとき - if (this.opts.loopedBoard && this.transformXyToPos( - (x = ((x % this.mapWidth) + this.mapWidth) % this.mapWidth), - (y = ((y % this.mapHeight) + this.mapHeight) % this.mapHeight)) === initPos) - // 盤面の境界でループし、自分が石を置く位置に戻ってきたとき、挟めるようにしている (ref: Test4のマップ) - return found; - else if (x === -1 || y === -1 || x === this.mapWidth || y === this.mapHeight) - return []; // 挟めないことが確定 (盤面外に到達) - - const pos = this.transformXyToPos(x, y); - if (this.mapDataGet(pos) === 'null') return []; // 挟めないことが確定 (配置不可能なマスに到達) - const stone = this.board[pos]; - if (stone === null) return []; // 挟めないことが確定 (石が置かれていないマスに到達) - if (stone === enemyColor) found.push(pos); // 挟めるかもしれない (相手の石を発見) - if (stone === color) return found; // 挟めることが確定 (対となる自分の石を発見) - } - }; - - return concat(diffVectors.map(effectsInLine)); - } - - /** - * ゲームが終了したか否か - */ - public get isEnded(): boolean { - return this.turn === null; - } - - /** - * ゲームの勝者 (null = 引き分け) - */ - public get winner(): Color | null { - return this.isEnded ? - this.blackCount == this.whiteCount ? null : - this.opts.isLlotheo === this.blackCount > this.whiteCount ? WHITE : BLACK : - undefined as never; - } -} diff --git a/packages/client/src/scripts/games/reversi/maps.ts b/packages/client/src/scripts/games/reversi/maps.ts deleted file mode 100644 index dc0d1bf9d0..0000000000 --- a/packages/client/src/scripts/games/reversi/maps.ts +++ /dev/null @@ -1,896 +0,0 @@ -/** - * 組み込みマップ定義 - * - * データ値: - * (スペース) ... マス無し - * - ... マス - * b ... 初期配置される黒石 - * w ... 初期配置される白石 - */ - -export type Map = { - name?: string; - category?: string; - author?: string; - data: string[]; -}; - -export const fourfour: Map = { - name: '4x4', - category: '4x4', - data: [ - '----', - '-wb-', - '-bw-', - '----' - ] -}; - -export const sixsix: Map = { - name: '6x6', - category: '6x6', - data: [ - '------', - '------', - '--wb--', - '--bw--', - '------', - '------' - ] -}; - -export const roundedSixsix: Map = { - name: '6x6 rounded', - category: '6x6', - author: 'syuilo', - data: [ - ' ---- ', - '------', - '--wb--', - '--bw--', - '------', - ' ---- ' - ] -}; - -export const roundedSixsix2: Map = { - name: '6x6 rounded 2', - category: '6x6', - author: 'syuilo', - data: [ - ' -- ', - ' ---- ', - '--wb--', - '--bw--', - ' ---- ', - ' -- ' - ] -}; - -export const eighteight: Map = { - name: '8x8', - category: '8x8', - data: [ - '--------', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - '--------' - ] -}; - -export const eighteightH1: Map = { - name: '8x8 handicap 1', - category: '8x8', - data: [ - 'b-------', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - '--------' - ] -}; - -export const eighteightH2: Map = { - name: '8x8 handicap 2', - category: '8x8', - data: [ - 'b-------', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - '-------b' - ] -}; - -export const eighteightH3: Map = { - name: '8x8 handicap 3', - category: '8x8', - data: [ - 'b------b', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - '-------b' - ] -}; - -export const eighteightH4: Map = { - name: '8x8 handicap 4', - category: '8x8', - data: [ - 'b------b', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - 'b------b' - ] -}; - -export const eighteightH28: Map = { - name: '8x8 handicap 28', - category: '8x8', - data: [ - 'bbbbbbbb', - 'b------b', - 'b------b', - 'b--wb--b', - 'b--bw--b', - 'b------b', - 'b------b', - 'bbbbbbbb' - ] -}; - -export const roundedEighteight: Map = { - name: '8x8 rounded', - category: '8x8', - author: 'syuilo', - data: [ - ' ------ ', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - ' ------ ' - ] -}; - -export const roundedEighteight2: Map = { - name: '8x8 rounded 2', - category: '8x8', - author: 'syuilo', - data: [ - ' ---- ', - ' ------ ', - '--------', - '---wb---', - '---bw---', - '--------', - ' ------ ', - ' ---- ' - ] -}; - -export const roundedEighteight3: Map = { - name: '8x8 rounded 3', - category: '8x8', - author: 'syuilo', - data: [ - ' -- ', - ' ---- ', - ' ------ ', - '---wb---', - '---bw---', - ' ------ ', - ' ---- ', - ' -- ' - ] -}; - -export const eighteightWithNotch: Map = { - name: '8x8 with notch', - category: '8x8', - author: 'syuilo', - data: [ - '--- ---', - '--------', - '--------', - ' --wb-- ', - ' --bw-- ', - '--------', - '--------', - '--- ---' - ] -}; - -export const eighteightWithSomeHoles: Map = { - name: '8x8 with some holes', - category: '8x8', - author: 'syuilo', - data: [ - '--- ----', - '----- --', - '-- -----', - '---wb---', - '---bw- -', - ' -------', - '--- ----', - '--------' - ] -}; - -export const circle: Map = { - name: 'Circle', - category: '8x8', - author: 'syuilo', - data: [ - ' -- ', - ' ------ ', - ' ------ ', - '---wb---', - '---bw---', - ' ------ ', - ' ------ ', - ' -- ' - ] -}; - -export const smile: Map = { - name: 'Smile', - category: '8x8', - author: 'syuilo', - data: [ - ' ------ ', - '--------', - '-- -- --', - '---wb---', - '-- bw --', - '--- ---', - '--------', - ' ------ ' - ] -}; - -export const window: Map = { - name: 'Window', - category: '8x8', - author: 'syuilo', - data: [ - '--------', - '- -- -', - '- -- -', - '---wb---', - '---bw---', - '- -- -', - '- -- -', - '--------' - ] -}; - -export const reserved: Map = { - name: 'Reserved', - category: '8x8', - author: 'Aya', - data: [ - 'w------b', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - 'b------w' - ] -}; - -export const x: Map = { - name: 'X', - category: '8x8', - author: 'Aya', - data: [ - 'w------b', - '-w----b-', - '--w--b--', - '---wb---', - '---bw---', - '--b--w--', - '-b----w-', - 'b------w' - ] -}; - -export const parallel: Map = { - name: 'Parallel', - category: '8x8', - author: 'Aya', - data: [ - '--------', - '--------', - '--------', - '---bb---', - '---ww---', - '--------', - '--------', - '--------' - ] -}; - -export const lackOfBlack: Map = { - name: 'Lack of Black', - category: '8x8', - data: [ - '--------', - '--------', - '--------', - '---w----', - '---bw---', - '--------', - '--------', - '--------' - ] -}; - -export const squareParty: Map = { - name: 'Square Party', - category: '8x8', - author: 'syuilo', - data: [ - '--------', - '-wwwbbb-', - '-w-wb-b-', - '-wwwbbb-', - '-bbbwww-', - '-b-bw-w-', - '-bbbwww-', - '--------' - ] -}; - -export const minesweeper: Map = { - name: 'Minesweeper', - category: '8x8', - author: 'syuilo', - data: [ - 'b-b--w-w', - '-w-wb-b-', - 'w-b--w-b', - '-b-wb-w-', - '-w-bw-b-', - 'b-w--b-w', - '-b-bw-w-', - 'w-w--b-b' - ] -}; - -export const tenthtenth: Map = { - name: '10x10', - category: '10x10', - data: [ - '----------', - '----------', - '----------', - '----------', - '----wb----', - '----bw----', - '----------', - '----------', - '----------', - '----------' - ] -}; - -export const hole: Map = { - name: 'The Hole', - category: '10x10', - author: 'syuilo', - data: [ - '----------', - '----------', - '--wb--wb--', - '--bw--bw--', - '---- ----', - '---- ----', - '--wb--wb--', - '--bw--bw--', - '----------', - '----------' - ] -}; - -export const grid: Map = { - name: 'Grid', - category: '10x10', - author: 'syuilo', - data: [ - '----------', - '- - -- - -', - '----------', - '- - -- - -', - '----wb----', - '----bw----', - '- - -- - -', - '----------', - '- - -- - -', - '----------' - ] -}; - -export const cross: Map = { - name: 'Cross', - category: '10x10', - author: 'Aya', - data: [ - ' ---- ', - ' ---- ', - ' ---- ', - '----------', - '----wb----', - '----bw----', - '----------', - ' ---- ', - ' ---- ', - ' ---- ' - ] -}; - -export const charX: Map = { - name: 'Char X', - category: '10x10', - author: 'syuilo', - data: [ - '--- ---', - '---- ----', - '----------', - ' -------- ', - ' --wb-- ', - ' --bw-- ', - ' -------- ', - '----------', - '---- ----', - '--- ---' - ] -}; - -export const charY: Map = { - name: 'Char Y', - category: '10x10', - author: 'syuilo', - data: [ - '--- ---', - '---- ----', - '----------', - ' -------- ', - ' --wb-- ', - ' --bw-- ', - ' ------ ', - ' ------ ', - ' ------ ', - ' ------ ' - ] -}; - -export const walls: Map = { - name: 'Walls', - category: '10x10', - author: 'Aya', - data: [ - ' bbbbbbbb ', - 'w--------w', - 'w--------w', - 'w--------w', - 'w---wb---w', - 'w---bw---w', - 'w--------w', - 'w--------w', - 'w--------w', - ' bbbbbbbb ' - ] -}; - -export const cpu: Map = { - name: 'CPU', - category: '10x10', - author: 'syuilo', - data: [ - ' b b b b ', - 'w--------w', - ' -------- ', - 'w--------w', - ' ---wb--- ', - ' ---bw--- ', - 'w--------w', - ' -------- ', - 'w--------w', - ' b b b b ' - ] -}; - -export const checker: Map = { - name: 'Checker', - category: '10x10', - author: 'Aya', - data: [ - '----------', - '----------', - '----------', - '---wbwb---', - '---bwbw---', - '---wbwb---', - '---bwbw---', - '----------', - '----------', - '----------' - ] -}; - -export const japaneseCurry: Map = { - name: 'Japanese curry', - category: '10x10', - author: 'syuilo', - data: [ - 'w-b-b-b-b-', - '-w-b-b-b-b', - 'w-w-b-b-b-', - '-w-w-b-b-b', - 'w-w-wwb-b-', - '-w-wbb-b-b', - 'w-w-w-b-b-', - '-w-w-w-b-b', - 'w-w-w-w-b-', - '-w-w-w-w-b' - ] -}; - -export const mosaic: Map = { - name: 'Mosaic', - category: '10x10', - author: 'syuilo', - data: [ - '- - - - - ', - ' - - - - -', - '- - - - - ', - ' - w w - -', - '- - b b - ', - ' - w w - -', - '- - b b - ', - ' - - - - -', - '- - - - - ', - ' - - - - -', - ] -}; - -export const arena: Map = { - name: 'Arena', - category: '10x10', - author: 'syuilo', - data: [ - '- - -- - -', - ' - - - - ', - '- ------ -', - ' -------- ', - '- --wb-- -', - '- --bw-- -', - ' -------- ', - '- ------ -', - ' - - - - ', - '- - -- - -' - ] -}; - -export const reactor: Map = { - name: 'Reactor', - category: '10x10', - author: 'syuilo', - data: [ - '-w------b-', - 'b- - - -w', - '- --wb-- -', - '---b w---', - '- b wb w -', - '- w bw b -', - '---w b---', - '- --bw-- -', - 'w- - - -b', - '-b------w-' - ] -}; - -export const sixeight: Map = { - name: '6x8', - category: 'Special', - data: [ - '------', - '------', - '------', - '--wb--', - '--bw--', - '------', - '------', - '------' - ] -}; - -export const spark: Map = { - name: 'Spark', - category: 'Special', - author: 'syuilo', - data: [ - ' - - ', - '----------', - ' -------- ', - ' -------- ', - ' ---wb--- ', - ' ---bw--- ', - ' -------- ', - ' -------- ', - '----------', - ' - - ' - ] -}; - -export const islands: Map = { - name: 'Islands', - category: 'Special', - author: 'syuilo', - data: [ - '-------- ', - '---wb--- ', - '---bw--- ', - '-------- ', - ' - - ', - ' - - ', - ' --------', - ' --------', - ' --------', - ' --------' - ] -}; - -export const galaxy: Map = { - name: 'Galaxy', - category: 'Special', - author: 'syuilo', - data: [ - ' ------ ', - ' --www--- ', - ' ------w--- ', - '---bbb--w---', - '--b---b-w-b-', - '-b--wwb-w-b-', - '-b-w-bww--b-', - '-b-w-b---b--', - '---w--bbb---', - ' ---w------ ', - ' ---www-- ', - ' ------ ' - ] -}; - -export const triangle: Map = { - name: 'Triangle', - category: 'Special', - author: 'syuilo', - data: [ - ' -- ', - ' -- ', - ' ---- ', - ' ---- ', - ' --wb-- ', - ' --bw-- ', - ' -------- ', - ' -------- ', - '----------', - '----------' - ] -}; - -export const iphonex: Map = { - name: 'iPhone X', - category: 'Special', - author: 'syuilo', - data: [ - ' -- -- ', - '--------', - '--------', - '--------', - '--------', - '---wb---', - '---bw---', - '--------', - '--------', - '--------', - '--------', - ' ------ ' - ] -}; - -export const dealWithIt: Map = { - name: 'Deal with it!', - category: 'Special', - author: 'syuilo', - data: [ - '------------', - '--w-b-------', - ' --b-w------', - ' --w-b---- ', - ' ------- ' - ] -}; - -export const experiment: Map = { - name: 'Let\'s experiment', - category: 'Special', - author: 'syuilo', - data: [ - ' ------------ ', - '------wb------', - '------bw------', - '--------------', - ' - - ', - '------ ------', - 'bbbbbb wwwwww', - 'bbbbbb wwwwww', - 'bbbbbb wwwwww', - 'bbbbbb wwwwww', - 'wwwwww bbbbbb' - ] -}; - -export const bigBoard: Map = { - name: 'Big board', - category: 'Special', - data: [ - '----------------', - '----------------', - '----------------', - '----------------', - '----------------', - '----------------', - '----------------', - '-------wb-------', - '-------bw-------', - '----------------', - '----------------', - '----------------', - '----------------', - '----------------', - '----------------', - '----------------' - ] -}; - -export const twoBoard: Map = { - name: 'Two board', - category: 'Special', - author: 'Aya', - data: [ - '-------- --------', - '-------- --------', - '-------- --------', - '---wb--- ---wb---', - '---bw--- ---bw---', - '-------- --------', - '-------- --------', - '-------- --------' - ] -}; - -export const test1: Map = { - name: 'Test1', - category: 'Test', - data: [ - '--------', - '---wb---', - '---bw---', - '--------' - ] -}; - -export const test2: Map = { - name: 'Test2', - category: 'Test', - data: [ - '------', - '------', - '-b--w-', - '-w--b-', - '-w--b-' - ] -}; - -export const test3: Map = { - name: 'Test3', - category: 'Test', - data: [ - '-w-', - '--w', - 'w--', - '-w-', - '--w', - 'w--', - '-w-', - '--w', - 'w--', - '-w-', - '---', - 'b--', - ] -}; - -export const test4: Map = { - name: 'Test4', - category: 'Test', - data: [ - '-w--b-', - '-w--b-', - '------', - '-w--b-', - '-w--b-' - ] -}; - -// 検証用: この盤面で藍(lv3)が黒で始めると何故か(?)A1に打ってしまう -export const test6: Map = { - name: 'Test6', - category: 'Test', - data: [ - '--wwwww-', - 'wwwwwwww', - 'wbbbwbwb', - 'wbbbbwbb', - 'wbwbbwbb', - 'wwbwbbbb', - '--wbbbbb', - '-wwwww--', - ] -}; - -// 検証用: この盤面で藍(lv3)が黒で始めると何故か(?)G7に打ってしまう -export const test7: Map = { - name: 'Test7', - category: 'Test', - data: [ - 'b--w----', - 'b-wwww--', - 'bwbwwwbb', - 'wbwwwwb-', - 'wwwwwww-', - '-wwbbwwb', - '--wwww--', - '--wwww--', - ] -}; - -// 検証用: この盤面で藍(lv5)が黒で始めると何故か(?)A1に打ってしまう -export const test8: Map = { - name: 'Test8', - category: 'Test', - data: [ - '--------', - '-----w--', - 'w--www--', - 'wwwwww--', - 'bbbbwww-', - 'wwwwww--', - '--www---', - '--ww----', - ] -}; diff --git a/packages/client/src/scripts/games/reversi/package.json b/packages/client/src/scripts/games/reversi/package.json deleted file mode 100644 index a4415ad141..0000000000 --- a/packages/client/src/scripts/games/reversi/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "misskey-reversi", - "version": "0.0.5", - "description": "Misskey reversi engine", - "keywords": [ - "misskey" - ], - "author": "syuilo <i@syuilo.com>", - "license": "MIT", - "repository": "https://github.com/misskey-dev/misskey.git", - "bugs": "https://github.com/misskey-dev/misskey/issues", - "main": "./built/core.js", - "types": "./built/core.d.ts", - "scripts": { - "build": "tsc" - }, - "dependencies": {} -} diff --git a/packages/client/src/scripts/games/reversi/tsconfig.json b/packages/client/src/scripts/games/reversi/tsconfig.json deleted file mode 100644 index 851fb6b7e4..0000000000 --- a/packages/client/src/scripts/games/reversi/tsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "compilerOptions": { - "noEmitOnError": false, - "noImplicitAny": false, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "experimentalDecorators": true, - "declaration": true, - "sourceMap": false, - "target": "es2017", - "module": "commonjs", - "removeComments": false, - "noLib": false, - "outDir": "./built", - "rootDir": "./" - }, - "compileOnSave": false, - "include": [ - "./core.ts" - ] -} diff --git a/packages/client/src/scripts/get-note-menu.ts b/packages/client/src/scripts/get-note-menu.ts new file mode 100644 index 0000000000..3634f39632 --- /dev/null +++ b/packages/client/src/scripts/get-note-menu.ts @@ -0,0 +1,310 @@ +import { Ref } from 'vue'; +import * as misskey from 'misskey-js'; +import { $i } from '@/account'; +import { i18n } from '@/i18n'; +import { instance } from '@/instance'; +import * as os from '@/os'; +import copyToClipboard from '@/scripts/copy-to-clipboard'; +import { url } from '@/config'; +import { noteActions } from '@/store'; +import { pleaseLogin } from './please-login'; + +export function getNoteMenu(props: { + note: misskey.entities.Note; + menuButton: Ref<HTMLElement>; + translation: Ref<any>; + translating: Ref<boolean>; +}) { + const isRenote = ( + props.note.renote != null && + props.note.text == null && + props.note.fileIds.length === 0 && + props.note.poll == null + ); + + let appearNote = isRenote ? props.note.renote as misskey.entities.Note : props.note; + + function del(): void { + os.confirm({ + type: 'warning', + text: i18n.locale.noteDeleteConfirm, + }).then(({ canceled }) => { + if (canceled) return; + + os.api('notes/delete', { + noteId: appearNote.id + }); + }); + } + + function delEdit(): void { + os.confirm({ + type: 'warning', + text: i18n.locale.deleteAndEditConfirm, + }).then(({ canceled }) => { + if (canceled) return; + + os.api('notes/delete', { + noteId: appearNote.id + }); + + os.post({ initialNote: appearNote, renote: appearNote.renote, reply: appearNote.reply, channel: appearNote.channel }); + }); + } + + function toggleFavorite(favorite: boolean): void { + os.apiWithDialog(favorite ? 'notes/favorites/create' : 'notes/favorites/delete', { + noteId: appearNote.id + }); + } + + function toggleWatch(watch: boolean): void { + os.apiWithDialog(watch ? 'notes/watching/create' : 'notes/watching/delete', { + noteId: appearNote.id + }); + } + + function toggleThreadMute(mute: boolean): void { + os.apiWithDialog(mute ? 'notes/thread-muting/create' : 'notes/thread-muting/delete', { + noteId: appearNote.id + }); + } + + function copyContent(): void { + copyToClipboard(appearNote.text); + os.success(); + } + + function copyLink(): void { + copyToClipboard(`${url}/notes/${appearNote.id}`); + os.success(); + } + + function togglePin(pin: boolean): void { + os.apiWithDialog(pin ? 'i/pin' : 'i/unpin', { + noteId: appearNote.id + }, undefined, null, e => { + if (e.id === '72dab508-c64d-498f-8740-a8eec1ba385a') { + os.alert({ + type: 'error', + text: i18n.locale.pinLimitExceeded + }); + } + }); + } + + async function clip(): Promise<void> { + const clips = await os.api('clips/list'); + os.popupMenu([{ + icon: 'fas fa-plus', + text: i18n.locale.createNew, + action: async () => { + const { canceled, result } = await os.form(i18n.locale.createNewClip, { + name: { + type: 'string', + label: i18n.locale.name + }, + description: { + type: 'string', + required: false, + multiline: true, + label: i18n.locale.description + }, + isPublic: { + type: 'boolean', + label: i18n.locale.public, + default: false + } + }); + if (canceled) return; + + const clip = await os.apiWithDialog('clips/create', result); + + os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: appearNote.id }); + } + }, null, ...clips.map(clip => ({ + text: clip.name, + action: () => { + os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: appearNote.id }); + } + }))], props.menuButton.value, { + }).then(focus); + } + + async function promote(): Promise<void> { + const { canceled, result: days } = await os.inputNumber({ + title: i18n.locale.numberOfDays, + }); + + if (canceled) return; + + os.apiWithDialog('admin/promo/create', { + noteId: appearNote.id, + expiresAt: Date.now() + (86400000 * days), + }); + } + + function share(): void { + navigator.share({ + title: i18n.t('noteOf', { user: appearNote.user.name }), + text: appearNote.text, + url: `${url}/notes/${appearNote.id}`, + }); + } + + async function translate(): Promise<void> { + if (props.translation.value != null) return; + props.translating.value = true; + const res = await os.api('notes/translate', { + noteId: appearNote.id, + targetLang: localStorage.getItem('lang') || navigator.language, + }); + props.translating.value = false; + props.translation.value = res; + } + + let menu; + if ($i) { + const statePromise = os.api('notes/state', { + noteId: appearNote.id + }); + + menu = [{ + icon: 'fas fa-copy', + text: i18n.locale.copyContent, + action: copyContent + }, { + icon: 'fas fa-link', + text: i18n.locale.copyLink, + action: copyLink + }, (appearNote.url || appearNote.uri) ? { + icon: 'fas fa-external-link-square-alt', + text: i18n.locale.showOnRemote, + action: () => { + window.open(appearNote.url || appearNote.uri, '_blank'); + } + } : undefined, + { + icon: 'fas fa-share-alt', + text: i18n.locale.share, + action: share + }, + instance.translatorAvailable ? { + icon: 'fas fa-language', + text: i18n.locale.translate, + action: translate + } : undefined, + null, + statePromise.then(state => state.isFavorited ? { + icon: 'fas fa-star', + text: i18n.locale.unfavorite, + action: () => toggleFavorite(false) + } : { + icon: 'fas fa-star', + text: i18n.locale.favorite, + action: () => toggleFavorite(true) + }), + { + icon: 'fas fa-paperclip', + text: i18n.locale.clip, + action: () => clip() + }, + (appearNote.userId != $i.id) ? statePromise.then(state => state.isWatching ? { + icon: 'fas fa-eye-slash', + text: i18n.locale.unwatch, + action: () => toggleWatch(false) + } : { + icon: 'fas fa-eye', + text: i18n.locale.watch, + action: () => toggleWatch(true) + }) : undefined, + statePromise.then(state => state.isMutedThread ? { + icon: 'fas fa-comment-slash', + text: i18n.locale.unmuteThread, + action: () => toggleThreadMute(false) + } : { + icon: 'fas fa-comment-slash', + text: i18n.locale.muteThread, + action: () => toggleThreadMute(true) + }), + appearNote.userId == $i.id ? ($i.pinnedNoteIds || []).includes(appearNote.id) ? { + icon: 'fas fa-thumbtack', + text: i18n.locale.unpin, + action: () => togglePin(false) + } : { + icon: 'fas fa-thumbtack', + text: i18n.locale.pin, + action: () => togglePin(true) + } : undefined, + /* + ...($i.isModerator || $i.isAdmin ? [ + null, + { + icon: 'fas fa-bullhorn', + text: i18n.locale.promote, + action: promote + }] + : [] + ),*/ + ...(appearNote.userId != $i.id ? [ + null, + { + icon: 'fas fa-exclamation-circle', + text: i18n.locale.reportAbuse, + action: () => { + const u = appearNote.url || appearNote.uri || `${url}/notes/${appearNote.id}`; + os.popup(import('@/components/abuse-report-window.vue'), { + user: appearNote.user, + initialComment: `Note: ${u}\n-----\n` + }, {}, 'closed'); + } + }] + : [] + ), + ...(appearNote.userId == $i.id || $i.isModerator || $i.isAdmin ? [ + null, + appearNote.userId == $i.id ? { + icon: 'fas fa-edit', + text: i18n.locale.deleteAndEdit, + action: delEdit + } : undefined, + { + icon: 'fas fa-trash-alt', + text: i18n.locale.delete, + danger: true, + action: del + }] + : [] + )] + .filter(x => x !== undefined); + } else { + menu = [{ + icon: 'fas fa-copy', + text: i18n.locale.copyContent, + action: copyContent + }, { + icon: 'fas fa-link', + text: i18n.locale.copyLink, + action: copyLink + }, (appearNote.url || appearNote.uri) ? { + icon: 'fas fa-external-link-square-alt', + text: i18n.locale.showOnRemote, + action: () => { + window.open(appearNote.url || appearNote.uri, '_blank'); + } + } : undefined] + .filter(x => x !== undefined); + } + + if (noteActions.length > 0) { + menu = menu.concat([null, ...noteActions.map(action => ({ + icon: 'fas fa-plug', + text: action.title, + action: () => { + action.handler(appearNote); + } + }))]); + } + + return menu; +} diff --git a/packages/client/src/scripts/get-user-menu.ts b/packages/client/src/scripts/get-user-menu.ts index ebe101bc0f..7b910a0083 100644 --- a/packages/client/src/scripts/get-user-menu.ts +++ b/packages/client/src/scripts/get-user-menu.ts @@ -5,7 +5,7 @@ import * as Acct from 'misskey-js/built/acct'; import * as os from '@/os'; import { userActions } from '@/store'; import { router } from '@/router'; -import { $i } from '@/account'; +import { $i, iAmModerator } from '@/account'; export function getUserMenu(user) { const meId = $i ? $i.id : null; @@ -175,7 +175,7 @@ export function getUserMenu(user) { action: reportAbuse }]); - if ($i && ($i.isAdmin || $i.isModerator)) { + if (iAmModerator) { menu = menu.concat([null, { icon: 'fas fa-microphone-slash', text: user.isSilenced ? i18n.locale.unsilence : i18n.locale.silence, diff --git a/packages/client/src/scripts/paging.ts b/packages/client/src/scripts/paging.ts deleted file mode 100644 index ef63ecc450..0000000000 --- a/packages/client/src/scripts/paging.ts +++ /dev/null @@ -1,246 +0,0 @@ -import { markRaw } from 'vue'; -import * as os from '@/os'; -import { onScrollTop, isTopVisible, getScrollPosition, getScrollContainer } from './scroll'; - -const SECOND_FETCH_LIMIT = 30; - -// reversed: items 配列の中身を逆順にする(新しい方が最後) - -export default (opts) => ({ - emits: ['queue'], - - data() { - return { - items: [], - queue: [], - offset: 0, - fetching: true, - moreFetching: false, - inited: false, - more: false, - backed: false, // 遡り中か否か - isBackTop: false, - }; - }, - - computed: { - empty(): boolean { - return this.items.length === 0 && !this.fetching && this.inited; - }, - - error(): boolean { - return !this.fetching && !this.inited; - }, - }, - - watch: { - pagination: { - handler() { - this.init(); - }, - deep: true - }, - - queue: { - handler(a, b) { - if (a.length === 0 && b.length === 0) return; - this.$emit('queue', this.queue.length); - }, - deep: true - } - }, - - created() { - opts.displayLimit = opts.displayLimit || 30; - this.init(); - }, - - activated() { - this.isBackTop = false; - }, - - deactivated() { - this.isBackTop = window.scrollY === 0; - }, - - methods: { - reload() { - this.items = []; - this.init(); - }, - - replaceItem(finder, data) { - const i = this.items.findIndex(finder); - this.items[i] = data; - }, - - removeItem(finder) { - const i = this.items.findIndex(finder); - this.items.splice(i, 1); - }, - - async init() { - this.queue = []; - this.fetching = true; - if (opts.before) opts.before(this); - let params = typeof this.pagination.params === 'function' ? this.pagination.params(true) : this.pagination.params; - if (params && params.then) params = await params; - if (params === null) return; - const endpoint = typeof this.pagination.endpoint === 'function' ? this.pagination.endpoint() : this.pagination.endpoint; - await os.api(endpoint, { - ...params, - limit: this.pagination.noPaging ? (this.pagination.limit || 10) : (this.pagination.limit || 10) + 1, - }).then(items => { - for (let i = 0; i < items.length; i++) { - const item = items[i]; - markRaw(item); - if (this.pagination.reversed) { - if (i === items.length - 2) item._shouldInsertAd_ = true; - } else { - if (i === 3) item._shouldInsertAd_ = true; - } - } - if (!this.pagination.noPaging && (items.length > (this.pagination.limit || 10))) { - items.pop(); - this.items = this.pagination.reversed ? [...items].reverse() : items; - this.more = true; - } else { - this.items = this.pagination.reversed ? [...items].reverse() : items; - this.more = false; - } - this.offset = items.length; - this.inited = true; - this.fetching = false; - if (opts.after) opts.after(this, null); - }, e => { - this.fetching = false; - if (opts.after) opts.after(this, e); - }); - }, - - async fetchMore() { - if (!this.more || this.fetching || this.moreFetching || this.items.length === 0) return; - this.moreFetching = true; - this.backed = true; - let params = typeof this.pagination.params === 'function' ? this.pagination.params(false) : this.pagination.params; - if (params && params.then) params = await params; - const endpoint = typeof this.pagination.endpoint === 'function' ? this.pagination.endpoint() : this.pagination.endpoint; - await os.api(endpoint, { - ...params, - limit: SECOND_FETCH_LIMIT + 1, - ...(this.pagination.offsetMode ? { - offset: this.offset, - } : { - untilId: this.pagination.reversed ? this.items[0].id : this.items[this.items.length - 1].id, - }), - }).then(items => { - for (let i = 0; i < items.length; i++) { - const item = items[i]; - markRaw(item); - if (this.pagination.reversed) { - if (i === items.length - 9) item._shouldInsertAd_ = true; - } else { - if (i === 10) item._shouldInsertAd_ = true; - } - } - if (items.length > SECOND_FETCH_LIMIT) { - items.pop(); - this.items = this.pagination.reversed ? [...items].reverse().concat(this.items) : this.items.concat(items); - this.more = true; - } else { - this.items = this.pagination.reversed ? [...items].reverse().concat(this.items) : this.items.concat(items); - this.more = false; - } - this.offset += items.length; - this.moreFetching = false; - }, e => { - this.moreFetching = false; - }); - }, - - async fetchMoreFeature() { - if (!this.more || this.fetching || this.moreFetching || this.items.length === 0) return; - this.moreFetching = true; - let params = typeof this.pagination.params === 'function' ? this.pagination.params(false) : this.pagination.params; - if (params && params.then) params = await params; - const endpoint = typeof this.pagination.endpoint === 'function' ? this.pagination.endpoint() : this.pagination.endpoint; - await os.api(endpoint, { - ...params, - limit: SECOND_FETCH_LIMIT + 1, - ...(this.pagination.offsetMode ? { - offset: this.offset, - } : { - sinceId: this.pagination.reversed ? this.items[0].id : this.items[this.items.length - 1].id, - }), - }).then(items => { - for (const item of items) { - markRaw(item); - } - if (items.length > SECOND_FETCH_LIMIT) { - items.pop(); - this.items = this.pagination.reversed ? [...items].reverse().concat(this.items) : this.items.concat(items); - this.more = true; - } else { - this.items = this.pagination.reversed ? [...items].reverse().concat(this.items) : this.items.concat(items); - this.more = false; - } - this.offset += items.length; - this.moreFetching = false; - }, e => { - this.moreFetching = false; - }); - }, - - prepend(item) { - if (this.pagination.reversed) { - const container = getScrollContainer(this.$el); - const pos = getScrollPosition(this.$el); - const viewHeight = container.clientHeight; - const height = container.scrollHeight; - const isBottom = (pos + viewHeight > height - 32); - if (isBottom) { - // オーバーフローしたら古いアイテムは捨てる - if (this.items.length >= opts.displayLimit) { - // このやり方だとVue 3.2以降アニメーションが動かなくなる - //this.items = this.items.slice(-opts.displayLimit); - while (this.items.length >= opts.displayLimit) { - this.items.shift(); - } - this.more = true; - } - } - this.items.push(item); - // TODO - } else { - const isTop = this.isBackTop || (document.body.contains(this.$el) && isTopVisible(this.$el)); - - if (isTop) { - // Prepend the item - this.items.unshift(item); - - // オーバーフローしたら古いアイテムは捨てる - if (this.items.length >= opts.displayLimit) { - // このやり方だとVue 3.2以降アニメーションが動かなくなる - //this.items = this.items.slice(0, opts.displayLimit); - while (this.items.length >= opts.displayLimit) { - this.items.pop(); - } - this.more = true; - } - } else { - this.queue.push(item); - onScrollTop(this.$el, () => { - for (const item of this.queue) { - this.prepend(item); - } - this.queue = []; - }); - } - } - }, - - append(item) { - this.items.push(item); - }, - } -}); diff --git a/packages/client/src/scripts/physics.ts b/packages/client/src/scripts/physics.ts index 445b6296eb..36e476b6f9 100644 --- a/packages/client/src/scripts/physics.ts +++ b/packages/client/src/scripts/physics.ts @@ -136,7 +136,7 @@ export function physics(container: HTMLElement) { } // 奈落に落ちたオブジェクトは消す - const intervalId = setInterval(() => { + const intervalId = window.setInterval(() => { for (const obj of objs) { if (obj.position.y > (containerHeight + 1024)) Matter.World.remove(world, obj); } @@ -146,7 +146,7 @@ export function physics(container: HTMLElement) { stop: () => { stop = true; Matter.Runner.stop(runner); - clearInterval(intervalId); + window.clearInterval(intervalId); } }; } diff --git a/packages/client/src/scripts/popout.ts b/packages/client/src/scripts/popout.ts index 51b8d72868..b8286a2a76 100644 --- a/packages/client/src/scripts/popout.ts +++ b/packages/client/src/scripts/popout.ts @@ -1,8 +1,8 @@ import * as config from '@/config'; export function popout(path: string, w?: HTMLElement) { - let url = path.startsWith('http://') || path.startsWith('https://') ? path : config.url + path; - url += '?zen'; // TODO: ちゃんとURLパースしてクエリ付ける + let url = path.startsWith('http://') || path.startsWith('https://') ? path : config.url + "/" + path; + url += '?zen'; if (w) { const position = w.getBoundingClientRect(); const width = parseInt(getComputedStyle(w, '').width, 10); diff --git a/packages/client/src/scripts/room/furniture.ts b/packages/client/src/scripts/room/furniture.ts deleted file mode 100644 index 7734e32668..0000000000 --- a/packages/client/src/scripts/room/furniture.ts +++ /dev/null @@ -1,21 +0,0 @@ -export type RoomInfo = { - roomType: string; - carpetColor: string; - furnitures: Furniture[]; -}; - -export type Furniture = { - id: string; // 同じ家具が複数ある場合にそれぞれを識別するためのIDであり、家具IDではない - type: string; // こっちが家具ID(chairとか) - position: { - x: number; - y: number; - z: number; - }; - rotation: { - x: number; - y: number; - z: number; - }; - props?: Record<string, any>; -}; diff --git a/packages/client/src/scripts/room/furnitures.json5 b/packages/client/src/scripts/room/furnitures.json5 deleted file mode 100644 index 4a40994107..0000000000 --- a/packages/client/src/scripts/room/furnitures.json5 +++ /dev/null @@ -1,407 +0,0 @@ -// 家具メタデータ - -// 家具IDはglbファイル及びそのディレクトリ名と一致する必要があります - -// 家具にはユーザーが設定できるプロパティを設定可能です: -// -// props: { -// <propname>: <proptype> -// } -// -// proptype一覧: -// * image ... 画像選択ダイアログを出し、その画像のURLが格納されます -// * color ... 色選択コントロールを出し、選択された色が格納されます - -// 家具にカスタムテクスチャを適用できるようにするには、textureプロパティに以下の追加の情報を含めます: -// 便宜上そのUVのどの部分にカスタムテクスチャを貼り合わせるかのエリアをテクスチャエリアと呼びます。 -// UVは1024*1024だと仮定します。 -// -// <key>: { -// prop: <プロパティ名>, -// uv: { -// x: <テクスチャエリアX座標>, -// y: <テクスチャエリアY座標>, -// width: <テクスチャエリアの幅>, -// height: <テクスチャエリアの高さ>, -// }, -// } -// -// <key>には、カスタムテクスチャを適用したいメッシュ名を指定します -// <プロパティ名>には、カスタムテクスチャとして使用する画像を格納するプロパティ(前述)名を指定します - -// 家具にカスタムカラーを適用できるようにするには、colorプロパティに以下の追加の情報を含めます: -// -// <key>: <プロパティ名> -// -// <key>には、カスタムカラーを適用したいマテリアル名を指定します -// <プロパティ名>には、カスタムカラーとして使用する色を格納するプロパティ(前述)名を指定します - -[ - { - id: "milk", - place: "floor" - }, - { - id: "bed", - place: "floor" - }, - { - id: "low-table", - place: "floor", - props: { - color: 'color' - }, - color: { - Table: 'color' - } - }, - { - id: "desk", - place: "floor", - props: { - color: 'color' - }, - color: { - Board: 'color' - } - }, - { - id: "chair", - place: "floor", - props: { - color: 'color' - }, - color: { - Chair: 'color' - } - }, - { - id: "chair2", - place: "floor", - props: { - color1: 'color', - color2: 'color' - }, - color: { - Cushion: 'color1', - Leg: 'color2' - } - }, - { - id: "fan", - place: "wall" - }, - { - id: "pc", - place: "floor" - }, - { - id: "plant", - place: "floor" - }, - { - id: "plant2", - place: "floor" - }, - { - id: "eraser", - place: "floor" - }, - { - id: "pencil", - place: "floor" - }, - { - id: "pudding", - place: "floor" - }, - { - id: "cardboard-box", - place: "floor" - }, - { - id: "cardboard-box2", - place: "floor" - }, - { - id: "cardboard-box3", - place: "floor" - }, - { - id: "book", - place: "floor", - props: { - color: 'color' - }, - color: { - Cover: 'color' - } - }, - { - id: "book2", - place: "floor" - }, - { - id: "piano", - place: "floor" - }, - { - id: "facial-tissue", - place: "floor" - }, - { - id: "server", - place: "floor" - }, - { - id: "moon", - place: "floor" - }, - { - id: "corkboard", - place: "wall" - }, - { - id: "mousepad", - place: "floor", - props: { - color: 'color' - }, - color: { - Pad: 'color' - } - }, - { - id: "monitor", - place: "floor", - props: { - screen: 'image' - }, - texture: { - Screen: { - prop: 'screen', - uv: { - x: 0, - y: 434, - width: 1024, - height: 588, - }, - }, - }, - }, - { - id: "tv", - place: "floor", - props: { - screen: 'image' - }, - texture: { - Screen: { - prop: 'screen', - uv: { - x: 0, - y: 434, - width: 1024, - height: 588, - }, - }, - }, - }, - { - id: "keyboard", - place: "floor" - }, - { - id: "carpet-stripe", - place: "floor", - props: { - color1: 'color', - color2: 'color' - }, - color: { - CarpetAreaA: 'color1', - CarpetAreaB: 'color2' - }, - }, - { - id: "mat", - place: "floor", - props: { - color: 'color' - }, - color: { - Mat: 'color' - } - }, - { - id: "color-box", - place: "floor", - props: { - color: 'color' - }, - color: { - main: 'color' - } - }, - { - id: "wall-clock", - place: "wall" - }, - { - id: "cube", - place: "floor", - props: { - color: 'color' - }, - color: { - Cube: 'color' - } - }, - { - id: "photoframe", - place: "wall", - props: { - photo: 'image', - color: 'color' - }, - texture: { - Photo: { - prop: 'photo', - uv: { - x: 0, - y: 342, - width: 1024, - height: 683, - }, - }, - }, - color: { - Frame: 'color' - } - }, - { - id: "pinguin", - place: "floor", - props: { - body: 'color', - belly: 'color' - }, - color: { - Body: 'body', - Belly: 'belly', - } - }, - { - id: "rubik-cube", - place: "floor", - }, - { - id: "poster-h", - place: "wall", - props: { - picture: 'image' - }, - texture: { - Poster: { - prop: 'picture', - uv: { - x: 0, - y: 277, - width: 1024, - height: 745, - }, - }, - }, - }, - { - id: "poster-v", - place: "wall", - props: { - picture: 'image' - }, - texture: { - Poster: { - prop: 'picture', - uv: { - x: 0, - y: 0, - width: 745, - height: 1024, - }, - }, - }, - }, - { - id: "sofa", - place: "floor", - props: { - color: 'color' - }, - color: { - Sofa: 'color' - } - }, - { - id: "spiral", - place: "floor", - props: { - color: 'color' - }, - color: { - Step: 'color' - } - }, - { - id: "bin", - place: "floor", - props: { - color: 'color' - }, - color: { - Bin: 'color' - } - }, - { - id: "cup-noodle", - place: "floor" - }, - { - id: "holo-display", - place: "floor", - props: { - image: 'image' - }, - texture: { - Image_Front: { - prop: 'image', - uv: { - x: 0, - y: 0, - width: 1024, - height: 1024, - }, - }, - Image_Back: { - prop: 'image', - uv: { - x: 0, - y: 0, - width: 1024, - height: 1024, - }, - }, - }, - }, - { - id: 'energy-drink', - place: "floor", - }, - { - id: 'doll-ai', - place: "floor", - }, - { - id: 'banknote', - place: "floor", - }, -] diff --git a/packages/client/src/scripts/room/room.ts b/packages/client/src/scripts/room/room.ts deleted file mode 100644 index 7e04bec646..0000000000 --- a/packages/client/src/scripts/room/room.ts +++ /dev/null @@ -1,775 +0,0 @@ -import autobind from 'autobind-decorator'; -import { v4 as uuid } from 'uuid'; -import * as THREE from 'three'; -import { GLTFLoader, GLTF } from 'three/examples/jsm/loaders/GLTFLoader'; -import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; -import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'; -import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js'; -import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js'; -import { BloomPass } from 'three/examples/jsm/postprocessing/BloomPass.js'; -import { FXAAShader } from 'three/examples/jsm/shaders/FXAAShader.js'; -import { TransformControls } from 'three/examples/jsm/controls/TransformControls.js'; -import { Furniture, RoomInfo } from './furniture'; -import { query as urlQuery } from '@/scripts/url'; -const furnitureDefs = require('./furnitures.json5'); - -THREE.ImageUtils.crossOrigin = ''; - -type Options = { - graphicsQuality: Room['graphicsQuality']; - onChangeSelect: Room['onChangeSelect']; - useOrthographicCamera: boolean; -}; - -/** - * MisskeyRoom Core Engine - */ -export class Room { - private clock: THREE.Clock; - private scene: THREE.Scene; - private renderer: THREE.WebGLRenderer; - private camera: THREE.PerspectiveCamera | THREE.OrthographicCamera; - private controls: OrbitControls; - private composer: EffectComposer; - private mixers: THREE.AnimationMixer[] = []; - private furnitureControl: TransformControls; - private roomInfo: RoomInfo; - private graphicsQuality: 'cheep' | 'low' | 'medium' | 'high' | 'ultra'; - private roomObj: THREE.Object3D; - private objects: THREE.Object3D[] = []; - private selectedObject: THREE.Object3D = null; - private onChangeSelect: Function; - private isTransformMode = false; - private renderFrameRequestId: number; - - private get canvas(): HTMLCanvasElement { - return this.renderer.domElement; - } - - private get furnitures(): Furniture[] { - return this.roomInfo.furnitures; - } - - private set furnitures(furnitures: Furniture[]) { - this.roomInfo.furnitures = furnitures; - } - - private get enableShadow() { - return this.graphicsQuality != 'cheep'; - } - - private get usePostFXs() { - return this.graphicsQuality !== 'cheep' && this.graphicsQuality !== 'low'; - } - - private get shadowQuality() { - return ( - this.graphicsQuality === 'ultra' ? 16384 : - this.graphicsQuality === 'high' ? 8192 : - this.graphicsQuality === 'medium' ? 4096 : - this.graphicsQuality === 'low' ? 1024 : - 0); // cheep - } - - constructor(user, isMyRoom, roomInfo: RoomInfo, container: Element, options: Options) { - this.roomInfo = roomInfo; - this.graphicsQuality = options.graphicsQuality; - this.onChangeSelect = options.onChangeSelect; - - this.clock = new THREE.Clock(true); - - //#region Init a scene - this.scene = new THREE.Scene(); - - const width = container.clientWidth; - const height = container.clientHeight; - - //#region Init a renderer - this.renderer = new THREE.WebGLRenderer({ - antialias: false, - stencil: false, - alpha: false, - powerPreference: - this.graphicsQuality === 'ultra' ? 'high-performance' : - this.graphicsQuality === 'high' ? 'high-performance' : - this.graphicsQuality === 'medium' ? 'default' : - this.graphicsQuality === 'low' ? 'low-power' : - 'low-power' // cheep - }); - - this.renderer.setPixelRatio(window.devicePixelRatio); - this.renderer.setSize(width, height); - this.renderer.autoClear = false; - this.renderer.setClearColor(new THREE.Color(0x051f2d)); - this.renderer.shadowMap.enabled = this.enableShadow; - this.renderer.shadowMap.type = - this.graphicsQuality === 'ultra' ? THREE.PCFSoftShadowMap : - this.graphicsQuality === 'high' ? THREE.PCFSoftShadowMap : - this.graphicsQuality === 'medium' ? THREE.PCFShadowMap : - this.graphicsQuality === 'low' ? THREE.BasicShadowMap : - THREE.BasicShadowMap; // cheep - - container.insertBefore(this.canvas, container.firstChild); - //#endregion - - //#region Init a camera - this.camera = options.useOrthographicCamera - ? new THREE.OrthographicCamera( - width / - 2, width / 2, height / 2, height / - 2, -10, 10) - : new THREE.PerspectiveCamera(45, width / height); - - if (options.useOrthographicCamera) { - this.camera.position.x = 2; - this.camera.position.y = 2; - this.camera.position.z = 2; - this.camera.zoom = 100; - this.camera.updateProjectionMatrix(); - } else { - this.camera.position.x = 5; - this.camera.position.y = 2; - this.camera.position.z = 5; - } - - this.scene.add(this.camera); - //#endregion - - //#region AmbientLight - const ambientLight = new THREE.AmbientLight(0xffffff, 1); - this.scene.add(ambientLight); - //#endregion - - if (this.graphicsQuality !== 'cheep') { - //#region Room light - const roomLight = new THREE.SpotLight(0xffffff, 0.1); - - roomLight.position.set(0, 8, 0); - roomLight.castShadow = this.enableShadow; - roomLight.shadow.bias = -0.0001; - roomLight.shadow.mapSize.width = this.shadowQuality; - roomLight.shadow.mapSize.height = this.shadowQuality; - roomLight.shadow.camera.near = 0.1; - roomLight.shadow.camera.far = 9; - roomLight.shadow.camera.fov = 45; - - this.scene.add(roomLight); - //#endregion - } - - //#region Out light - const outLight1 = new THREE.SpotLight(0xffffff, 0.4); - outLight1.position.set(9, 3, -2); - outLight1.castShadow = this.enableShadow; - outLight1.shadow.bias = -0.001; // アクネ、アーチファクト対策 その代わりピーターパンが発生する可能性がある - outLight1.shadow.mapSize.width = this.shadowQuality; - outLight1.shadow.mapSize.height = this.shadowQuality; - outLight1.shadow.camera.near = 6; - outLight1.shadow.camera.far = 15; - outLight1.shadow.camera.fov = 45; - this.scene.add(outLight1); - - const outLight2 = new THREE.SpotLight(0xffffff, 0.2); - outLight2.position.set(-2, 3, 9); - outLight2.castShadow = false; - outLight2.shadow.bias = -0.001; // アクネ、アーチファクト対策 その代わりピーターパンが発生する可能性がある - outLight2.shadow.camera.near = 6; - outLight2.shadow.camera.far = 15; - outLight2.shadow.camera.fov = 45; - this.scene.add(outLight2); - //#endregion - - //#region Init a controller - this.controls = new OrbitControls(this.camera, this.canvas); - - this.controls.target.set(0, 1, 0); - this.controls.enableZoom = true; - this.controls.enablePan = isMyRoom; - this.controls.minPolarAngle = 0; - this.controls.maxPolarAngle = Math.PI / 2; - this.controls.minAzimuthAngle = 0; - this.controls.maxAzimuthAngle = Math.PI / 2; - this.controls.enableDamping = true; - this.controls.dampingFactor = 0.2; - //#endregion - - //#region POST FXs - if (!this.usePostFXs) { - this.composer = null; - } else { - const renderTarget = new THREE.WebGLRenderTarget(width, height, { - minFilter: THREE.LinearFilter, - magFilter: THREE.LinearFilter, - format: THREE.RGBFormat, - stencilBuffer: false, - }); - - const fxaa = new ShaderPass(FXAAShader); - fxaa.uniforms['resolution'].value = new THREE.Vector2(1 / width, 1 / height); - fxaa.renderToScreen = true; - - this.composer = new EffectComposer(this.renderer, renderTarget); - this.composer.addPass(new RenderPass(this.scene, this.camera)); - if (this.graphicsQuality === 'ultra') { - this.composer.addPass(new BloomPass(0.25, 30, 128.0, 512)); - } - this.composer.addPass(fxaa); - } - //#endregion - //#endregion - - //#region Label - //#region Avatar - const avatarUrl = `/proxy/?${urlQuery({ url: user.avatarUrl })}`; - - const textureLoader = new THREE.TextureLoader(); - textureLoader.crossOrigin = 'anonymous'; - - const iconTexture = textureLoader.load(avatarUrl); - iconTexture.wrapS = THREE.RepeatWrapping; - iconTexture.wrapT = THREE.RepeatWrapping; - iconTexture.anisotropy = 16; - - const avatarMaterial = new THREE.MeshBasicMaterial({ - map: iconTexture, - side: THREE.DoubleSide, - alphaTest: 0.5 - }); - - const iconGeometry = new THREE.PlaneGeometry(1, 1); - - const avatarObject = new THREE.Mesh(iconGeometry, avatarMaterial); - avatarObject.position.set(-3, 2.5, 2); - avatarObject.rotation.y = Math.PI / 2; - avatarObject.castShadow = false; - - this.scene.add(avatarObject); - //#endregion - - //#region Username - const name = user.username; - - new THREE.FontLoader().load('/assets/fonts/helvetiker_regular.typeface.json', font => { - const nameGeometry = new THREE.TextGeometry(name, { - size: 0.5, - height: 0, - curveSegments: 8, - font: font, - bevelThickness: 0, - bevelSize: 0, - bevelEnabled: false - }); - - const nameMaterial = new THREE.MeshLambertMaterial({ - color: 0xffffff - }); - - const nameObject = new THREE.Mesh(nameGeometry, nameMaterial); - nameObject.position.set(-3, 2.25, 1.25); - nameObject.rotation.y = Math.PI / 2; - nameObject.castShadow = false; - - this.scene.add(nameObject); - }); - //#endregion - //#endregion - - //#region Interaction - if (isMyRoom) { - this.furnitureControl = new TransformControls(this.camera, this.canvas); - this.scene.add(this.furnitureControl); - - // Hover highlight - this.canvas.onmousemove = this.onmousemove; - - // Click - this.canvas.onmousedown = this.onmousedown; - } - //#endregion - - //#region Init room - this.loadRoom(); - //#endregion - - //#region Load furnitures - for (const furniture of this.furnitures) { - this.loadFurniture(furniture).then(obj => { - this.scene.add(obj.scene); - this.objects.push(obj.scene); - }); - } - //#endregion - - // Start render - if (this.usePostFXs) { - this.renderWithPostFXs(); - } else { - this.renderWithoutPostFXs(); - } - } - - @autobind - private renderWithoutPostFXs() { - this.renderFrameRequestId = - window.requestAnimationFrame(this.renderWithoutPostFXs); - - // Update animations - const clock = this.clock.getDelta(); - for (const mixer of this.mixers) { - mixer.update(clock); - } - - this.controls.update(); - this.renderer.render(this.scene, this.camera); - } - - @autobind - private renderWithPostFXs() { - this.renderFrameRequestId = - window.requestAnimationFrame(this.renderWithPostFXs); - - // Update animations - const clock = this.clock.getDelta(); - for (const mixer of this.mixers) { - mixer.update(clock); - } - - this.controls.update(); - this.renderer.clear(); - this.composer.render(); - } - - @autobind - private loadRoom() { - const type = this.roomInfo.roomType; - new GLTFLoader().load(`/client-assets/room/rooms/${type}/${type}.glb`, gltf => { - gltf.scene.traverse(child => { - if (!(child instanceof THREE.Mesh)) return; - - child.receiveShadow = this.enableShadow; - - child.material = new THREE.MeshLambertMaterial({ - color: (child.material as THREE.MeshStandardMaterial).color, - map: (child.material as THREE.MeshStandardMaterial).map, - name: (child.material as THREE.MeshStandardMaterial).name, - }); - - // 異方性フィルタリング - if ((child.material as THREE.MeshLambertMaterial).map && this.graphicsQuality !== 'cheep') { - (child.material as THREE.MeshLambertMaterial).map.minFilter = THREE.LinearMipMapLinearFilter; - (child.material as THREE.MeshLambertMaterial).map.magFilter = THREE.LinearMipMapLinearFilter; - (child.material as THREE.MeshLambertMaterial).map.anisotropy = 8; - } - }); - - gltf.scene.position.set(0, 0, 0); - - this.scene.add(gltf.scene); - this.roomObj = gltf.scene; - if (this.roomInfo.roomType === 'default') { - this.applyCarpetColor(); - } - }); - } - - @autobind - private loadFurniture(furniture: Furniture) { - const def = furnitureDefs.find(d => d.id === furniture.type); - return new Promise<GLTF>((res, rej) => { - const loader = new GLTFLoader(); - loader.load(`/client-assets/room/furnitures/${furniture.type}/${furniture.type}.glb`, gltf => { - const model = gltf.scene; - - // Load animation - if (gltf.animations.length > 0) { - const mixer = new THREE.AnimationMixer(model); - this.mixers.push(mixer); - for (const clip of gltf.animations) { - mixer.clipAction(clip).play(); - } - } - - model.name = furniture.id; - model.position.x = furniture.position.x; - model.position.y = furniture.position.y; - model.position.z = furniture.position.z; - model.rotation.x = furniture.rotation.x; - model.rotation.y = furniture.rotation.y; - model.rotation.z = furniture.rotation.z; - - model.traverse(child => { - if (!(child instanceof THREE.Mesh)) return; - child.castShadow = this.enableShadow; - child.receiveShadow = this.enableShadow; - (child.material as THREE.MeshStandardMaterial).metalness = 0; - - // 異方性フィルタリング - if ((child.material as THREE.MeshStandardMaterial).map && this.graphicsQuality !== 'cheep') { - (child.material as THREE.MeshStandardMaterial).map.minFilter = THREE.LinearMipMapLinearFilter; - (child.material as THREE.MeshStandardMaterial).map.magFilter = THREE.LinearMipMapLinearFilter; - (child.material as THREE.MeshStandardMaterial).map.anisotropy = 8; - } - }); - - if (def.color) { // カスタムカラー - this.applyCustomColor(model); - } - - if (def.texture) { // カスタムテクスチャ - this.applyCustomTexture(model); - } - - res(gltf); - }, null, rej); - }); - } - - @autobind - private applyCarpetColor() { - this.roomObj.traverse(child => { - if (!(child instanceof THREE.Mesh)) return; - if (child.material && - (child.material as THREE.MeshStandardMaterial).name && - (child.material as THREE.MeshStandardMaterial).name === 'Carpet' - ) { - const colorHex = parseInt(this.roomInfo.carpetColor.substr(1), 16); - (child.material as THREE.MeshStandardMaterial).color.setHex(colorHex); - } - }); - } - - @autobind - private applyCustomColor(model: THREE.Object3D) { - const furniture = this.furnitures.find(furniture => furniture.id === model.name); - const def = furnitureDefs.find(d => d.id === furniture.type); - if (def.color == null) return; - model.traverse(child => { - if (!(child instanceof THREE.Mesh)) return; - for (const t of Object.keys(def.color)) { - if (!child.material || - !(child.material as THREE.MeshStandardMaterial).name || - (child.material as THREE.MeshStandardMaterial).name !== t - ) continue; - - const prop = def.color[t]; - const val = furniture.props ? furniture.props[prop] : undefined; - - if (val == null) continue; - - const colorHex = parseInt(val.substr(1), 16); - (child.material as THREE.MeshStandardMaterial).color.setHex(colorHex); - } - }); - } - - @autobind - private applyCustomTexture(model: THREE.Object3D) { - const furniture = this.furnitures.find(furniture => furniture.id === model.name); - const def = furnitureDefs.find(d => d.id === furniture.type); - if (def.texture == null) return; - - model.traverse(child => { - if (!(child instanceof THREE.Mesh)) return; - for (const t of Object.keys(def.texture)) { - if (child.name !== t) continue; - - const prop = def.texture[t].prop; - const val = furniture.props ? furniture.props[prop] : undefined; - - if (val == null) continue; - - const canvas = document.createElement('canvas'); - canvas.height = 1024; - canvas.width = 1024; - - child.material = new THREE.MeshLambertMaterial({ - emissive: 0x111111, - side: THREE.DoubleSide, - alphaTest: 0.5, - }); - - const img = new Image(); - img.crossOrigin = 'anonymous'; - img.onload = () => { - const uvInfo = def.texture[t].uv; - - const ctx = canvas.getContext('2d'); - ctx.drawImage(img, - 0, 0, img.width, img.height, - uvInfo.x, uvInfo.y, uvInfo.width, uvInfo.height); - - const texture = new THREE.Texture(canvas); - texture.wrapS = THREE.RepeatWrapping; - texture.wrapT = THREE.RepeatWrapping; - texture.anisotropy = 16; - texture.flipY = false; - - (child.material as THREE.MeshLambertMaterial).map = texture; - (child.material as THREE.MeshLambertMaterial).needsUpdate = true; - (child.material as THREE.MeshLambertMaterial).map.needsUpdate = true; - }; - img.src = val; - } - }); - } - - @autobind - private onmousemove(ev: MouseEvent) { - if (this.isTransformMode) return; - - const rect = (ev.target as HTMLElement).getBoundingClientRect(); - const x = ((ev.clientX - rect.left) / rect.width) * 2 - 1; - const y = -((ev.clientY - rect.top) / rect.height) * 2 + 1; - const pos = new THREE.Vector2(x, y); - - this.camera.updateMatrixWorld(); - - const raycaster = new THREE.Raycaster(); - raycaster.setFromCamera(pos, this.camera); - - const intersects = raycaster.intersectObjects(this.objects, true); - - for (const object of this.objects) { - if (this.isSelectedObject(object)) continue; - object.traverse(child => { - if (child instanceof THREE.Mesh) { - (child.material as THREE.MeshStandardMaterial).emissive.setHex(0x000000); - } - }); - } - - if (intersects.length > 0) { - const intersected = this.getRoot(intersects[0].object); - if (this.isSelectedObject(intersected)) return; - intersected.traverse(child => { - if (child instanceof THREE.Mesh) { - (child.material as THREE.MeshStandardMaterial).emissive.setHex(0x191919); - } - }); - } - } - - @autobind - private onmousedown(ev: MouseEvent) { - if (this.isTransformMode) return; - if (ev.target !== this.canvas || ev.button !== 0) return; - - const rect = (ev.target as HTMLElement).getBoundingClientRect(); - const x = ((ev.clientX - rect.left) / rect.width) * 2 - 1; - const y = -((ev.clientY - rect.top) / rect.height) * 2 + 1; - const pos = new THREE.Vector2(x, y); - - this.camera.updateMatrixWorld(); - - const raycaster = new THREE.Raycaster(); - raycaster.setFromCamera(pos, this.camera); - - const intersects = raycaster.intersectObjects(this.objects, true); - - for (const object of this.objects) { - object.traverse(child => { - if (child instanceof THREE.Mesh) { - (child.material as THREE.MeshStandardMaterial).emissive.setHex(0x000000); - } - }); - } - - if (intersects.length > 0) { - const selectedObj = this.getRoot(intersects[0].object); - this.selectFurniture(selectedObj); - } else { - this.selectedObject = null; - this.onChangeSelect(null); - } - } - - @autobind - private getRoot(obj: THREE.Object3D): THREE.Object3D { - let found = false; - let x = obj.parent; - while (!found) { - if (x.parent.parent == null) { - found = true; - } else { - x = x.parent; - } - } - return x; - } - - @autobind - private isSelectedObject(obj: THREE.Object3D): boolean { - if (this.selectedObject == null) { - return false; - } else { - return obj.name === this.selectedObject.name; - } - } - - @autobind - private selectFurniture(obj: THREE.Object3D) { - this.selectedObject = obj; - this.onChangeSelect(obj); - obj.traverse(child => { - if (child instanceof THREE.Mesh) { - (child.material as THREE.MeshStandardMaterial).emissive.setHex(0xff0000); - } - }); - } - - /** - * 家具の移動/回転モードにします - * @param type 移動か回転か - */ - @autobind - public enterTransformMode(type: 'translate' | 'rotate') { - this.isTransformMode = true; - this.furnitureControl.setMode(type); - this.furnitureControl.attach(this.selectedObject); - this.controls.enableRotate = false; - } - - /** - * 家具の移動/回転モードを終了します - */ - @autobind - public exitTransformMode() { - this.isTransformMode = false; - this.furnitureControl.detach(); - this.controls.enableRotate = true; - } - - /** - * 家具プロパティを更新します - * @param key プロパティ名 - * @param value 値 - */ - @autobind - public updateProp(key: string, value: any) { - const furniture = this.furnitures.find(furniture => furniture.id === this.selectedObject.name); - if (furniture.props == null) furniture.props = {}; - furniture.props[key] = value; - this.applyCustomColor(this.selectedObject); - this.applyCustomTexture(this.selectedObject); - } - - /** - * 部屋に家具を追加します - * @param type 家具の種類 - */ - @autobind - public addFurniture(type: string) { - const furniture = { - id: uuid(), - type: type, - position: { - x: 0, - y: 0, - z: 0, - }, - rotation: { - x: 0, - y: 0, - z: 0, - }, - }; - - this.furnitures.push(furniture); - - this.loadFurniture(furniture).then(obj => { - this.scene.add(obj.scene); - this.objects.push(obj.scene); - }); - } - - /** - * 現在選択されている家具を部屋から削除します - */ - @autobind - public removeFurniture() { - this.exitTransformMode(); - const obj = this.selectedObject; - this.scene.remove(obj); - this.objects = this.objects.filter(object => object.name !== obj.name); - this.furnitures = this.furnitures.filter(furniture => furniture.id !== obj.name); - this.selectedObject = null; - this.onChangeSelect(null); - } - - /** - * 全ての家具を部屋から削除します - */ - @autobind - public removeAllFurnitures() { - this.exitTransformMode(); - for (const obj of this.objects) { - this.scene.remove(obj); - } - this.objects = []; - this.furnitures = []; - this.selectedObject = null; - this.onChangeSelect(null); - } - - /** - * 部屋の床の色を変更します - * @param color 色 - */ - @autobind - public updateCarpetColor(color: string) { - this.roomInfo.carpetColor = color; - this.applyCarpetColor(); - } - - /** - * 部屋の種類を変更します - * @param type 種類 - */ - @autobind - public changeRoomType(type: string) { - this.roomInfo.roomType = type; - this.scene.remove(this.roomObj); - this.loadRoom(); - } - - /** - * 部屋データを取得します - */ - @autobind - public getRoomInfo() { - for (const obj of this.objects) { - const furniture = this.furnitures.find(f => f.id === obj.name); - furniture.position.x = obj.position.x; - furniture.position.y = obj.position.y; - furniture.position.z = obj.position.z; - furniture.rotation.x = obj.rotation.x; - furniture.rotation.y = obj.rotation.y; - furniture.rotation.z = obj.rotation.z; - } - - return this.roomInfo; - } - - /** - * 選択されている家具を取得します - */ - @autobind - public getSelectedObject() { - return this.selectedObject; - } - - @autobind - public findFurnitureById(id: string) { - return this.furnitures.find(furniture => furniture.id === id); - } - - /** - * レンダリングを終了します - */ - @autobind - public destroy() { - // Stop render loop - window.cancelAnimationFrame(this.renderFrameRequestId); - - this.controls.dispose(); - this.scene.dispose(); - } -} diff --git a/packages/client/src/scripts/select-file.ts b/packages/client/src/scripts/select-file.ts index 6019890444..6bb3f8bf8a 100644 --- a/packages/client/src/scripts/select-file.ts +++ b/packages/client/src/scripts/select-file.ts @@ -1,4 +1,5 @@ import * as os from '@/os'; +import { stream } from '@/stream'; import { i18n } from '@/i18n'; import { defaultStore } from '@/store'; import { DriveFile } from 'misskey-js/built/entities'; @@ -48,7 +49,7 @@ function select(src: any, label: string | null, multiple: boolean): Promise<Driv const marker = Math.random().toString(); // TODO: UUIDとか使う - const connection = os.stream.useChannel('main'); + const connection = stream.useChannel('main'); connection.on('urlUploadFinished', data => { if (data.marker === marker) { res(multiple ? [data.file] : data.file); diff --git a/packages/client/src/scripts/theme.ts b/packages/client/src/scripts/theme.ts index 3b7f003d0f..85c087331b 100644 --- a/packages/client/src/scripts/theme.ts +++ b/packages/client/src/scripts/theme.ts @@ -34,11 +34,11 @@ export const builtinThemes = [ let timeout = null; export function applyTheme(theme: Theme, persist = true) { - if (timeout) clearTimeout(timeout); + if (timeout) window.clearTimeout(timeout); document.documentElement.classList.add('_themeChanging_'); - timeout = setTimeout(() => { + timeout = window.setTimeout(() => { document.documentElement.classList.remove('_themeChanging_'); }, 1000); diff --git a/packages/client/src/scripts/touch.ts b/packages/client/src/scripts/touch.ts index 06b4f8b2ed..5251bc2e27 100644 --- a/packages/client/src/scripts/touch.ts +++ b/packages/client/src/scripts/touch.ts @@ -14,6 +14,10 @@ if (isTouchSupported) { }, { passive: true }); window.addEventListener('touchend', () => { + // 子要素のtouchstartイベントでstopPropagation()が呼ばれると親要素に伝搬されずタッチされたと判定されないため、 + // touchendイベントでもtouchstartイベントと同様にtrueにする + isTouchUsing = true; + isScreenTouching = false; }, { passive: true }); } diff --git a/packages/client/src/scripts/use-leave-guard.ts b/packages/client/src/scripts/use-leave-guard.ts new file mode 100644 index 0000000000..3984256251 --- /dev/null +++ b/packages/client/src/scripts/use-leave-guard.ts @@ -0,0 +1,46 @@ +import { inject, onUnmounted, Ref } from 'vue'; +import { onBeforeRouteLeave } from 'vue-router'; +import { i18n } from '@/i18n'; +import * as os from '@/os'; + +export function useLeaveGuard(enabled: Ref<boolean>) { + const setLeaveGuard = inject('setLeaveGuard'); + + if (setLeaveGuard) { + setLeaveGuard(async () => { + if (!enabled.value) return false; + + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.locale.leaveConfirm, + }); + + return canceled; + }); + } else { + onBeforeRouteLeave(async (to, from) => { + if (!enabled.value) return true; + + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.locale.leaveConfirm, + }); + + return !canceled; + }); + } + + /* + function onBeforeLeave(ev: BeforeUnloadEvent) { + if (enabled.value) { + ev.preventDefault(); + ev.returnValue = ''; + } + } + + window.addEventListener('beforeunload', onBeforeLeave); + onUnmounted(() => { + window.removeEventListener('beforeunload', onBeforeLeave); + }); + */ +} diff --git a/packages/client/src/scripts/use-note-capture.ts b/packages/client/src/scripts/use-note-capture.ts new file mode 100644 index 0000000000..bb00e464e3 --- /dev/null +++ b/packages/client/src/scripts/use-note-capture.ts @@ -0,0 +1,123 @@ +import { onUnmounted, Ref } from 'vue'; +import * as misskey from 'misskey-js'; +import { stream } from '@/stream'; +import { $i } from '@/account'; + +export function useNoteCapture(props: { + rootEl: Ref<HTMLElement>; + appearNote: Ref<misskey.entities.Note>; +}) { + const appearNote = props.appearNote; + const connection = $i ? stream : null; + + function onStreamNoteUpdated(data): void { + const { type, id, body } = data; + + if (id !== appearNote.value.id) return; + + switch (type) { + case 'reacted': { + const reaction = body.reaction; + + const updated = JSON.parse(JSON.stringify(appearNote.value)); + + if (body.emoji) { + const emojis = appearNote.value.emojis || []; + if (!emojis.includes(body.emoji)) { + updated.emojis = [...emojis, body.emoji]; + } + } + + // TODO: reactionsプロパティがない場合ってあったっけ? なければ || {} は消せる + const currentCount = (appearNote.value.reactions || {})[reaction] || 0; + + updated.reactions[reaction] = currentCount + 1; + + if ($i && (body.userId === $i.id)) { + updated.myReaction = reaction; + } + + appearNote.value = updated; + break; + } + + case 'unreacted': { + const reaction = body.reaction; + + const updated = JSON.parse(JSON.stringify(appearNote.value)); + + // TODO: reactionsプロパティがない場合ってあったっけ? なければ || {} は消せる + const currentCount = (appearNote.value.reactions || {})[reaction] || 0; + + updated.reactions[reaction] = Math.max(0, currentCount - 1); + + if ($i && (body.userId === $i.id)) { + updated.myReaction = null; + } + + appearNote.value = updated; + break; + } + + case 'pollVoted': { + const choice = body.choice; + + const updated = JSON.parse(JSON.stringify(appearNote.value)); + + const choices = [...appearNote.value.poll.choices]; + choices[choice] = { + ...choices[choice], + votes: choices[choice].votes + 1, + ...($i && (body.userId === $i.id) ? { + isVoted: true + } : {}) + }; + + updated.poll.choices = choices; + + appearNote.value = updated; + break; + } + + case 'deleted': { + const updated = JSON.parse(JSON.stringify(appearNote.value)); + updated.value = true; + appearNote.value = updated; + break; + } + } + } + + function capture(withHandler = false): void { + if (connection) { + // TODO: このノートがストリーミング経由で流れてきた場合のみ sr する + connection.send(document.body.contains(props.rootEl.value) ? 'sr' : 's', { id: appearNote.value.id }); + if (withHandler) connection.on('noteUpdated', onStreamNoteUpdated); + } + } + + function decapture(withHandler = false): void { + if (connection) { + connection.send('un', { + id: appearNote.value.id, + }); + if (withHandler) connection.off('noteUpdated', onStreamNoteUpdated); + } + } + + function onStreamConnected() { + capture(false); + } + + capture(true); + if (connection) { + connection.on('_connected_', onStreamConnected); + } + + onUnmounted(() => { + decapture(true); + if (connection) { + connection.off('_connected_', onStreamConnected); + } + }); +} diff --git a/packages/client/src/store.ts b/packages/client/src/store.ts index dc9c3b7b9e..cd358d29d0 100644 --- a/packages/client/src/store.ts +++ b/packages/client/src/store.ts @@ -97,7 +97,7 @@ export const defaultStore = markRaw(new Storage('base', { tl: { where: 'deviceAccount', default: { - src: 'home', + src: 'home' as 'home' | 'local' | 'social' | 'global', arg: null } }, @@ -160,7 +160,7 @@ export const defaultStore = markRaw(new Storage('base', { }, useReactionPickerForContextMenu: { where: 'device', - default: true + default: false }, showGapBetweenNotesInTimeline: { where: 'device', @@ -255,10 +255,6 @@ export class ColdDeviceStorage { sound_chatBg: { type: 'syuilo/waon', volume: 1 }, sound_antenna: { type: 'syuilo/triple', volume: 1 }, sound_channel: { type: 'syuilo/square-pico', volume: 1 }, - sound_reversiPutBlack: { type: 'syuilo/kick', volume: 0.3 }, - sound_reversiPutWhite: { type: 'syuilo/snare', volume: 0.3 }, - roomGraphicsQuality: 'medium' as 'cheep' | 'low' | 'medium' | 'high' | 'ultra', - roomUseOrthographicCamera: true, }; public static watchers = []; diff --git a/packages/client/src/stream.ts b/packages/client/src/stream.ts new file mode 100644 index 0000000000..dea3459b86 --- /dev/null +++ b/packages/client/src/stream.ts @@ -0,0 +1,8 @@ +import * as Misskey from 'misskey-js'; +import { markRaw } from 'vue'; +import { $i } from '@/account'; +import { url } from '@/config'; + +export const stream = markRaw(new Misskey.Stream(url, $i ? { + token: $i.token, +} : null)); diff --git a/packages/client/src/style.scss b/packages/client/src/style.scss index 181521b4f5..c1d47ffd08 100644 --- a/packages/client/src/style.scss +++ b/packages/client/src/style.scss @@ -26,6 +26,7 @@ html { background-size: cover; background-position: center; color: var(--fg); + accent-color: var(--accent); overflow: auto; overflow-wrap: break-word; font-family: "BIZ UDGothic", Roboto, HelveticaNeue, Arial, sans-serif; @@ -386,16 +387,6 @@ hr { backdrop-filter: var(--blur, blur(15px)); } -._inputSplit { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); - grid-gap: 12px; - - > * { - margin: 0 !important; - } -} - ._formBlock { margin: 1.5em 0; } diff --git a/packages/client/src/themes/_dark.json5 b/packages/client/src/themes/_dark.json5 index d8be16f60a..1d87788794 100644 --- a/packages/client/src/themes/_dark.json5 +++ b/packages/client/src/themes/_dark.json5 @@ -69,6 +69,9 @@ success: '#86b300', error: '#ec4137', warn: '#ecb637', + codeString: '#ffb675', + codeNumber: '#cfff9e', + codeBoolean: '#c59eff', htmlThemeColor: '@bg', X2: ':darken<2<@panel', X3: 'rgba(255, 255, 255, 0.05)', diff --git a/packages/client/src/themes/_light.json5 b/packages/client/src/themes/_light.json5 index 251aa36c7a..359b560688 100644 --- a/packages/client/src/themes/_light.json5 +++ b/packages/client/src/themes/_light.json5 @@ -69,6 +69,9 @@ success: '#86b300', error: '#ec4137', warn: '#ecb637', + codeString: '#b98710', + codeNumber: '#0fbbbb', + codeBoolean: '#62b70c', htmlThemeColor: '@bg', X2: ':darken<2<@panel', X3: 'rgba(0, 0, 0, 0.05)', diff --git a/packages/client/src/ui/_common_/common.vue b/packages/client/src/ui/_common_/common.vue index 956ec556c1..98069258d9 100644 --- a/packages/client/src/ui/_common_/common.vue +++ b/packages/client/src/ui/_common_/common.vue @@ -15,9 +15,10 @@ <script lang="ts"> import { defineAsyncComponent, defineComponent } from 'vue'; -import { stream, popup, popups, uploads, pendingApiRequestsCount } from '@/os'; +import { popup, popups, uploads, pendingApiRequestsCount } from '@/os'; import * as sound from '@/scripts/sound'; import { $i } from '@/account'; +import { stream } from '@/stream'; export default defineComponent({ components: { diff --git a/packages/client/src/ui/_common_/sidebar-for-mobile.vue b/packages/client/src/ui/_common_/sidebar-for-mobile.vue index 5babdb98a8..afcc50725b 100644 --- a/packages/client/src/ui/_common_/sidebar-for-mobile.vue +++ b/packages/client/src/ui/_common_/sidebar-for-mobile.vue @@ -61,7 +61,11 @@ export default defineComponent({ otherMenuItemIndicated, post: os.post, search, - openAccountMenu, + openAccountMenu:(ev) => { + openAccountMenu({ + withExtraOperation: true, + }, ev); + }, more: () => { os.popup(import('@/components/launch-pad.vue'), {}, { }, 'closed'); diff --git a/packages/client/src/ui/_common_/sidebar.vue b/packages/client/src/ui/_common_/sidebar.vue index fa712ba45d..94baacbee9 100644 --- a/packages/client/src/ui/_common_/sidebar.vue +++ b/packages/client/src/ui/_common_/sidebar.vue @@ -76,7 +76,11 @@ export default defineComponent({ iconOnly, post: os.post, search, - openAccountMenu, + openAccountMenu:(ev) => { + openAccountMenu({ + withExtraOperation: true, + }, ev); + }, more: () => { os.popup(import('@/components/launch-pad.vue'), {}, { }, 'closed'); diff --git a/packages/client/src/ui/_common_/stream-indicator.vue b/packages/client/src/ui/_common_/stream-indicator.vue index 3f86f94549..5e811e1b88 100644 --- a/packages/client/src/ui/_common_/stream-indicator.vue +++ b/packages/client/src/ui/_common_/stream-indicator.vue @@ -8,38 +8,28 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as os from '@/os'; +<script lang="ts" setup> +import { onUnmounted } from 'vue'; +import { stream } from '@/stream'; -export default defineComponent({ - data() { - return { - hasDisconnected: false, - } - }, - computed: { - stream() { - return os.stream; - }, - }, - created() { - os.stream.on('_disconnected_', this.onDisconnected); - }, - beforeUnmount() { - os.stream.off('_disconnected_', this.onDisconnected); - }, - methods: { - onDisconnected() { - this.hasDisconnected = true; - }, - resetDisconnected() { - this.hasDisconnected = false; - }, - reload() { - location.reload(); - }, - } +let hasDisconnected = $ref(false); + +function onDisconnected() { + hasDisconnected = true; +} + +function resetDisconnected() { + hasDisconnected = false; +} + +function reload() { + location.reload(); +} + +stream.on('_disconnected_', onDisconnected); + +onUnmounted(() => { + stream.off('_disconnected_', onDisconnected); }); </script> diff --git a/packages/client/src/ui/_common_/upload.vue b/packages/client/src/ui/_common_/upload.vue index a1c5dcdecc..ab7678a505 100644 --- a/packages/client/src/ui/_common_/upload.vue +++ b/packages/client/src/ui/_common_/upload.vue @@ -17,18 +17,12 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import * as os from '@/os'; -export default defineComponent({ - data() { - return { - uploads: os.uploads, - zIndex: os.claimZIndex('high'), - }; - }, -}); +const uploads = os.uploads; +const zIndex = os.claimZIndex('high'); </script> <style lang="scss" scoped> diff --git a/packages/client/src/ui/chat/date-separated-list.vue b/packages/client/src/ui/chat/date-separated-list.vue deleted file mode 100644 index 1a36aca6dd..0000000000 --- a/packages/client/src/ui/chat/date-separated-list.vue +++ /dev/null @@ -1,157 +0,0 @@ -<script lang="ts"> -import { defineComponent, h, PropType, TransitionGroup } from 'vue'; -import MkAd from '@/components/global/ad.vue'; - -export default defineComponent({ - props: { - items: { - type: Array as PropType<{ id: string; createdAt: string; _shouldInsertAd_: boolean; }[]>, - required: true, - }, - reversed: { - type: Boolean, - required: false, - default: false - }, - ad: { - type: Boolean, - required: false, - default: false - }, - }, - - render() { - const getDateText = (time: string) => { - const date = new Date(time).getDate(); - const month = new Date(time).getMonth() + 1; - return this.$t('monthAndDay', { - month: month.toString(), - day: date.toString() - }); - } - - return h(this.reversed ? 'div' : TransitionGroup, { - class: 'hmjzthxl', - name: this.reversed ? 'list-reversed' : 'list', - tag: 'div', - }, this.items.map((item, i) => { - const el = this.$slots.default({ - item: item - })[0]; - if (el.key == null && item.id) el.key = item.id; - - if ( - i != this.items.length - 1 && - new Date(item.createdAt).getDate() != new Date(this.items[i + 1].createdAt).getDate() - ) { - const separator = h('div', { - class: 'separator', - key: item.id + ':separator', - }, h('p', { - class: 'date' - }, [ - h('span', [ - h('i', { - class: 'fas fa-angle-up icon', - }), - getDateText(item.createdAt) - ]), - h('span', [ - getDateText(this.items[i + 1].createdAt), - h('i', { - class: 'fas fa-angle-down icon', - }) - ]) - ])); - - return [el, separator]; - } else { - if (this.ad && item._shouldInsertAd_) { - return [h(MkAd, { - class: 'a', // advertiseの意(ブロッカー対策) - key: item.id + ':ad', - prefer: ['horizontal', 'horizontal-big'], - }), el]; - } else { - return el; - } - } - })); - }, -}); -</script> - -<style lang="scss"> -.hmjzthxl { - > .list-move { - transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1); - } - > .list-enter-active { - transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1), opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1); - } - > .list-enter-from { - opacity: 0; - transform: translateY(-64px); - } - - > .list-reversed-enter-active, > .list-reversed-leave-active { - transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1), opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1); - } - > .list-reversed-enter-from { - opacity: 0; - transform: translateY(64px); - } -} -</style> - -<style lang="scss"> -.hmjzthxl { - > .separator { - text-align: center; - position: relative; - - &:before { - content: ""; - display: block; - position: absolute; - top: 50%; - left: 0; - right: 0; - margin: auto; - width: calc(100% - 32px); - height: 1px; - background: var(--divider); - } - - > .date { - display: inline-block; - position: relative; - margin: 0; - padding: 0 16px; - line-height: 32px; - text-align: center; - font-size: 12px; - color: var(--dateLabelFg); - background: var(--panel); - - > span { - &:first-child { - margin-right: 8px; - - > .icon { - margin-right: 8px; - } - } - - &:last-child { - margin-left: 8px; - - > .icon { - margin-left: 8px; - } - } - } - } - } -} -</style> diff --git a/packages/client/src/ui/chat/header-clock.vue b/packages/client/src/ui/chat/header-clock.vue deleted file mode 100644 index 3488289c21..0000000000 --- a/packages/client/src/ui/chat/header-clock.vue +++ /dev/null @@ -1,62 +0,0 @@ -<template> -<div class="acemodlh _monospace"> - <div> - <span v-text="y"></span>/<span v-text="m"></span>/<span v-text="d"></span> - </div> - <div> - <span v-text="hh"></span> - <span :style="{ visibility: showColon ? 'visible' : 'hidden' }">:</span> - <span v-text="mm"></span> - <span :style="{ visibility: showColon ? 'visible' : 'hidden' }">:</span> - <span v-text="ss"></span> - </div> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as os from '@/os'; - -export default defineComponent({ - data() { - return { - clock: null, - y: null, - m: null, - d: null, - hh: null, - mm: null, - ss: null, - showColon: true, - }; - }, - created() { - this.tick(); - this.clock = setInterval(this.tick, 1000); - }, - beforeUnmount() { - clearInterval(this.clock); - }, - methods: { - tick() { - const now = new Date(); - this.y = now.getFullYear().toString(); - this.m = (now.getMonth() + 1).toString().padStart(2, '0'); - this.d = now.getDate().toString().padStart(2, '0'); - this.hh = now.getHours().toString().padStart(2, '0'); - this.mm = now.getMinutes().toString().padStart(2, '0'); - this.ss = now.getSeconds().toString().padStart(2, '0'); - this.showColon = now.getSeconds() % 2 === 0; - } - } -}); -</script> - -<style lang="scss" scoped> -.acemodlh { - opacity: 0.7; - font-size: 0.85em; - line-height: 1em; - text-align: center; -} -</style> diff --git a/packages/client/src/ui/chat/index.vue b/packages/client/src/ui/chat/index.vue deleted file mode 100644 index f66ab4dcee..0000000000 --- a/packages/client/src/ui/chat/index.vue +++ /dev/null @@ -1,463 +0,0 @@ -<template> -<div class="mk-app" @contextmenu.self.prevent="onContextmenu"> - <XSidebar ref="menu" class="menu" :default-hidden="true"/> - - <div class="nav"> - <header class="header"> - <div class="left"> - <button class="_button account" @click="openAccountMenu"> - <MkAvatar :user="$i" class="avatar"/><!--<MkAcct class="text" :user="$i"/>--> - </button> - </div> - <div class="right"> - <MkA v-tooltip="$ts.messaging" class="item" to="/my/messaging"><i class="fas fa-comments icon"></i><span v-if="$i.hasUnreadMessagingMessage" class="indicator"><i class="fas fa-circle"></i></span></MkA> - <MkA v-tooltip="$ts.directNotes" class="item" to="/my/messages"><i class="fas fa-envelope icon"></i><span v-if="$i.hasUnreadSpecifiedNotes" class="indicator"><i class="fas fa-circle"></i></span></MkA> - <MkA v-tooltip="$ts.mentions" class="item" to="/my/mentions"><i class="fas fa-at icon"></i><span v-if="$i.hasUnreadMentions" class="indicator"><i class="fas fa-circle"></i></span></MkA> - <MkA v-tooltip="$ts.notifications" class="item" to="/my/notifications"><i class="fas fa-bell icon"></i><span v-if="$i.hasUnreadNotification" class="indicator"><i class="fas fa-circle"></i></span></MkA> - </div> - </header> - <div class="body"> - <div class="container"> - <div class="header">{{ $ts.timeline }}</div> - <div class="body"> - <MkA to="/timeline/home" class="item" :class="{ active: tl === 'home' }"><i class="fas fa-home icon"></i>{{ $ts._timelines.home }}</MkA> - <MkA to="/timeline/local" class="item" :class="{ active: tl === 'local' }"><i class="fas fa-comments icon"></i>{{ $ts._timelines.local }}</MkA> - <MkA to="/timeline/social" class="item" :class="{ active: tl === 'social' }"><i class="fas fa-share-alt icon"></i>{{ $ts._timelines.social }}</MkA> - <MkA to="/timeline/global" class="item" :class="{ active: tl === 'global' }"><i class="fas fa-globe icon"></i>{{ $ts._timelines.global }}</MkA> - </div> - </div> - <div v-if="followedChannels" class="container"> - <div class="header">{{ $ts.channel }} ({{ $ts.following }})<button class="_button add" @click="addChannel"><i class="fas fa-plus"></i></button></div> - <div class="body"> - <MkA v-for="channel in followedChannels" :key="channel.id" :to="`/channels/${ channel.id }`" class="item" :class="{ active: tl === `channel:${ channel.id }`, read: !channel.hasUnreadNote }"><i class="fas fa-satellite-dish icon"></i>{{ channel.name }}</MkA> - </div> - </div> - <div v-if="featuredChannels" class="container"> - <div class="header">{{ $ts.channel }}<button class="_button add" @click="addChannel"><i class="fas fa-plus"></i></button></div> - <div class="body"> - <MkA v-for="channel in featuredChannels" :key="channel.id" :to="`/channels/${ channel.id }`" class="item" :class="{ active: tl === `channel:${ channel.id }` }"><i class="fas fa-satellite-dish icon"></i>{{ channel.name }}</MkA> - </div> - </div> - <div v-if="lists" class="container"> - <div class="header">{{ $ts.lists }}<button class="_button add" @click="addList"><i class="fas fa-plus"></i></button></div> - <div class="body"> - <MkA v-for="list in lists" :key="list.id" :to="`/my/list/${ list.id }`" class="item" :class="{ active: tl === `list:${ list.id }` }"><i class="fas fa-list-ul icon"></i>{{ list.name }}</MkA> - </div> - </div> - <div v-if="antennas" class="container"> - <div class="header">{{ $ts.antennas }}<button class="_button add" @click="addAntenna"><i class="fas fa-plus"></i></button></div> - <div class="body"> - <MkA v-for="antenna in antennas" :key="antenna.id" :to="`/my/antenna/${ antenna.id }`" class="item" :class="{ active: tl === `antenna:${ antenna.id }` }"><i class="fas fa-satellite icon"></i>{{ antenna.name }}</MkA> - </div> - </div> - <div class="container"> - <div class="body"> - <MkA to="/my/favorites" class="item"><i class="fas fa-star icon"></i>{{ $ts.favorites }}</MkA> - </div> - </div> - <MkAd class="a" :prefer="['square']"/> - </div> - <footer class="footer"> - <div class="left"> - <button class="_button menu" @click="showMenu"> - <i class="fas fa-bars icon"></i> - </button> - </div> - <div class="right"> - <button v-tooltip="$ts.search" class="_button item search" @click="search"> - <i class="fas fa-search icon"></i> - </button> - <MkA v-tooltip="$ts.settings" class="item" to="/settings"><i class="fas fa-cog icon"></i></MkA> - </div> - </footer> - </div> - - <main class="main" @contextmenu.stop="onContextmenu"> - <header class="header"> - <MkHeader class="header" :info="pageInfo" :menu="menu" :center="false" @click="onHeaderClick"/> - </header> - <router-view v-slot="{ Component }"> - <transition :name="$store.state.animation ? 'page' : ''" mode="out-in" @enter="onTransition"> - <keep-alive :include="['timeline']"> - <component :is="Component" :ref="changePage" class="body"/> - </keep-alive> - </transition> - </router-view> - </main> - - <XSide ref="side" class="side" @open="sideViewOpening = true" @close="sideViewOpening = false"/> - <div class="side widgets" :class="{ sideViewOpening }"> - <XWidgets/> - </div> - - <XCommon/> -</div> -</template> - -<script lang="ts"> -import { defineComponent, defineAsyncComponent } from 'vue'; -import { instanceName, url } from '@/config'; -import XSidebar from '@/ui/_common_/sidebar.vue'; -import XWidgets from './widgets.vue'; -import XCommon from '../_common_/common.vue'; -import XSide from './side.vue'; -import XHeaderClock from './header-clock.vue'; -import * as os from '@/os'; -import { router } from '@/router'; -import { menuDef } from '@/menu'; -import { search } from '@/scripts/search'; -import copyToClipboard from '@/scripts/copy-to-clipboard'; -import { store } from './store'; -import * as symbols from '@/symbols'; -import { openAccountMenu } from '@/account'; - -export default defineComponent({ - components: { - XCommon, - XSidebar, - XWidgets, - XSide, // NOTE: dynamic importするとAsyncComponentWrapperが間に入るせいでref取得できなくて面倒になる - XHeaderClock, - }, - - provide() { - return { - sideViewHook: (path) => { - this.$refs.side.navigate(path); - } - }; - }, - - data() { - return { - pageInfo: null, - lists: null, - antennas: null, - followedChannels: null, - featuredChannels: null, - currentChannel: null, - menuDef: menuDef, - sideViewOpening: false, - instanceName, - }; - }, - - computed: { - menu() { - return [{ - icon: 'fas fa-columns', - text: this.$ts.openInSideView, - action: () => { - this.$refs.side.navigate(this.$route.path); - } - }, { - icon: 'fas fa-window-maximize', - text: this.$ts.openInWindow, - action: () => { - os.pageWindow(this.$route.path); - } - }]; - } - }, - - created() { - if (window.innerWidth < 1024) { - localStorage.setItem('ui', 'default'); - location.reload(); - } - - os.api('users/lists/list').then(lists => { - this.lists = lists; - }); - - os.api('antennas/list').then(antennas => { - this.antennas = antennas; - }); - - os.api('channels/followed', { limit: 20 }).then(channels => { - this.followedChannels = channels; - }); - - // TODO: pagination - os.api('channels/featured', { limit: 20 }).then(channels => { - this.featuredChannels = channels; - }); - }, - - methods: { - changePage(page) { - console.log(page); - if (page == null) return; - if (page[symbols.PAGE_INFO]) { - this.pageInfo = page[symbols.PAGE_INFO]; - document.title = `${this.pageInfo.title} | ${instanceName}`; - } - }, - - showMenu() { - this.$refs.menu.show(); - }, - - post() { - os.post(); - }, - - search() { - search(); - }, - - back() { - history.back(); - }, - - top() { - window.scroll({ top: 0, behavior: 'smooth' }); - }, - - onTransition() { - if (window._scroll) window._scroll(); - }, - - onHeaderClick() { - window.scroll({ top: 0, behavior: 'smooth' }); - }, - - onContextmenu(e) { - const isLink = (el: HTMLElement) => { - if (el.tagName === 'A') return true; - if (el.parentElement) { - return isLink(el.parentElement); - } - }; - if (isLink(e.target)) return; - if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(e.target.tagName) || e.target.attributes['contenteditable']) return; - if (window.getSelection().toString() !== '') return; - const path = this.$route.path; - os.contextMenu([{ - type: 'label', - text: path, - }, { - icon: 'fas fa-columns', - text: this.$ts.openInSideView, - action: () => { - this.$refs.side.navigate(path); - } - }, { - icon: 'fas fa-window-maximize', - text: this.$ts.openInWindow, - action: () => { - os.pageWindow(path); - } - }], e); - }, - - openAccountMenu, - } -}); -</script> - -<style lang="scss" scoped> -.mk-app { - $header-height: 54px; // TODO: どこかに集約したい - $ui-font-size: 1em; // TODO: どこかに集約したい - - // ほんとは単に 100vh と書きたいところだが... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/ - height: calc(var(--vh, 1vh) * 100); - display: flex; - - > .nav { - display: flex; - flex-direction: column; - width: 250px; - height: 100vh; - border-right: solid 4px var(--divider); - - > .header, > .footer { - $padding: 8px; - display: flex; - align-items: center; - z-index: 1000; - height: $header-height; - padding: $padding; - box-sizing: border-box; - user-select: none; - - &.header { - border-bottom: solid 0.5px var(--divider); - } - - &.footer { - border-top: solid 0.5px var(--divider); - } - - > .left, > .right { - > .item, > .menu { - display: inline-flex; - vertical-align: middle; - height: ($header-height - ($padding * 2)); - width: ($header-height - ($padding * 2)); - box-sizing: border-box; - //opacity: 0.6; - position: relative; - border-radius: 5px; - - &:hover { - background: rgba(0, 0, 0, 0.05); - } - - > .icon { - margin: auto; - } - - > .indicator { - position: absolute; - top: 8px; - right: 8px; - color: var(--indicator); - font-size: 8px; - line-height: 8px; - animation: blink 1s infinite; - } - } - } - - > .left { - flex: 1; - min-width: 0; - - > .account { - display: flex; - align-items: center; - padding: 0 8px; - - > .avatar { - width: 26px; - height: 26px; - margin-right: 8px; - } - - > .text { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - font-size: 0.9em; - } - } - } - - > .right { - margin-left: auto; - } - } - - > .body { - flex: 1; - min-width: 0; - overflow: auto; - - > .container { - margin-top: 8px; - margin-bottom: 8px; - - & + .container { - margin-top: 16px; - } - - > .header { - display: flex; - font-size: 0.9em; - padding: 8px 16px; - position: sticky; - top: 0; - background: var(--X17); - -webkit-backdrop-filter: var(--blur, blur(8px)); - backdrop-filter: var(--blur, blur(8px)); - z-index: 1; - color: var(--fgTransparentWeak); - - > .add { - margin-left: auto; - color: var(--fgTransparentWeak); - - &:hover { - color: var(--fg); - } - } - } - - > .body { - padding: 0 8px; - - > .item { - display: block; - padding: 6px 8px; - border-radius: 4px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - - &:hover { - text-decoration: none; - background: rgba(0, 0, 0, 0.05); - } - - &.active, &.active:hover { - background: var(--accent); - color: #fff !important; - } - - &.read { - color: var(--fgTransparent); - } - - > .icon { - margin-right: 8px; - opacity: 0.6; - } - } - } - } - - > .a { - margin: 12px; - } - } - } - - > .main { - display: flex; - flex: 1; - flex-direction: column; - min-width: 0; - height: 100vh; - position: relative; - background: var(--panel); - - > .header { - z-index: 1000; - height: $header-height; - background-color: var(--panel); - border-bottom: solid 0.5px var(--divider); - user-select: none; - } - - > .body { - width: 100%; - box-sizing: border-box; - overflow: auto; - } - } - - > .side { - width: 350px; - border-left: solid 4px var(--divider); - background: var(--panel); - - &.widgets.sideViewOpening { - @media (max-width: 1400px) { - display: none; - } - } - } -} -</style> diff --git a/packages/client/src/ui/chat/note-header.vue b/packages/client/src/ui/chat/note-header.vue deleted file mode 100644 index 5f87fdd14e..0000000000 --- a/packages/client/src/ui/chat/note-header.vue +++ /dev/null @@ -1,99 +0,0 @@ -<template> -<header class="dehvdgxo"> - <MkA v-user-preview="note.user.id" class="name" :to="userPage(note.user)"> - <MkUserName :user="note.user"/> - </MkA> - <span v-if="note.user.isBot" class="is-bot">bot</span> - <span class="username"><MkAcct :user="note.user"/></span> - <div class="info"> - <MkA class="created-at" :to="notePage(note)"> - <MkTime :time="note.createdAt"/> - </MkA> - <span v-if="note.visibility !== 'public'" class="visibility"> - <i v-if="note.visibility === 'home'" class="fas fa-home"></i> - <i v-else-if="note.visibility === 'followers'" class="fas fa-unlock"></i> - <i v-else-if="note.visibility === 'specified'" class="fas fa-envelope"></i> - </span> - <span v-if="note.localOnly" class="localOnly"><i class="fas fa-biohazard"></i></span> - </div> -</header> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import { notePage } from '@/filters/note'; -import { userPage } from '@/filters/user'; -import * as os from '@/os'; - -export default defineComponent({ - props: { - note: { - type: Object, - required: true - }, - }, - - data() { - return { - }; - }, - - methods: { - notePage, - userPage - } -}); -</script> - -<style lang="scss" scoped> -.dehvdgxo { - display: flex; - align-items: baseline; - white-space: nowrap; - font-size: 0.9em; - - > .name { - display: block; - margin: 0 .5em 0 0; - padding: 0; - overflow: hidden; - font-size: 1em; - font-weight: bold; - text-decoration: none; - text-overflow: ellipsis; - - &:hover { - text-decoration: underline; - } - } - - > .is-bot { - flex-shrink: 0; - align-self: center; - margin: 0 .5em 0 0; - padding: 1px 6px; - font-size: 80%; - border: solid 0.5px var(--divider); - border-radius: 3px; - } - - > .username { - margin: 0 .5em 0 0; - overflow: hidden; - text-overflow: ellipsis; - } - - > .info { - font-size: 0.9em; - opacity: 0.7; - - > .visibility { - margin-left: 8px; - } - - > .localOnly { - margin-left: 8px; - } - } -} -</style> diff --git a/packages/client/src/ui/chat/note-preview.vue b/packages/client/src/ui/chat/note-preview.vue deleted file mode 100644 index c28591815e..0000000000 --- a/packages/client/src/ui/chat/note-preview.vue +++ /dev/null @@ -1,112 +0,0 @@ -<template> -<div class="hduudsxk"> - <MkAvatar class="avatar" :user="note.user"/> - <div class="main"> - <XNoteHeader class="header" :note="note" :mini="true"/> - <div class="body"> - <p v-if="note.cw != null" class="cw"> - <span v-if="note.cw != ''" class="text">{{ note.cw }}</span> - <XCwButton v-model="showContent" :note="note"/> - </p> - <div v-show="note.cw == null || showContent" class="content"> - <XSubNote-content class="text" :note="note"/> - </div> - </div> - </div> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import XNoteHeader from './note-header.vue'; -import XSubNoteContent from './sub-note-content.vue'; -import XCwButton from '@/components/cw-button.vue'; -import * as os from '@/os'; - -export default defineComponent({ - components: { - XNoteHeader, - XSubNoteContent, - XCwButton, - }, - - props: { - note: { - type: Object, - required: true - } - }, - - data() { - return { - showContent: false - }; - } -}); -</script> - -<style lang="scss" scoped> -.hduudsxk { - display: flex; - margin: 0; - padding: 0; - overflow: hidden; - font-size: 0.95em; - - > .avatar { - - @media (min-width: 350px) { - margin: 0 10px 0 0; - width: 44px; - height: 44px; - } - - @media (min-width: 500px) { - margin: 0 12px 0 0; - width: 48px; - height: 48px; - } - } - - > .avatar { - flex-shrink: 0; - display: block; - margin: 0 10px 0 0; - width: 40px; - height: 40px; - border-radius: 8px; - } - - > .main { - flex: 1; - min-width: 0; - - > .header { - margin-bottom: 2px; - } - - > .body { - - > .cw { - cursor: default; - display: block; - margin: 0; - padding: 0; - overflow-wrap: break-word; - - > .text { - margin-right: 8px; - } - } - - > .content { - > .text { - cursor: default; - margin: 0; - padding: 0; - } - } - } - } -} -</style> diff --git a/packages/client/src/ui/chat/note.sub.vue b/packages/client/src/ui/chat/note.sub.vue deleted file mode 100644 index b61b7521a8..0000000000 --- a/packages/client/src/ui/chat/note.sub.vue +++ /dev/null @@ -1,137 +0,0 @@ -<template> -<div class="wrpstxzv" :class="{ children }"> - <div class="main"> - <MkAvatar class="avatar" :user="note.user"/> - <div class="body"> - <XNoteHeader class="header" :note="note" :mini="true"/> - <div class="body"> - <p v-if="note.cw != null" class="cw"> - <Mfm v-if="note.cw != ''" class="text" :text="note.cw" :author="note.user" :i="$i" :custom-emojis="note.emojis"/> - <XCwButton v-model="showContent" :note="note"/> - </p> - <div v-show="note.cw == null || showContent" class="content"> - <XSubNote-content class="text" :note="note"/> - </div> - </div> - </div> - </div> - <XSub v-for="reply in replies" :key="reply.id" :note="reply" class="reply" :detail="true" :children="true"/> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import XNoteHeader from './note-header.vue'; -import XSubNoteContent from './sub-note-content.vue'; -import XCwButton from '@/components/cw-button.vue'; -import * as os from '@/os'; - -export default defineComponent({ - name: 'XSub', - - components: { - XNoteHeader, - XSubNoteContent, - XCwButton, - }, - - props: { - note: { - type: Object, - required: true - }, - detail: { - type: Boolean, - required: false, - default: false - }, - children: { - type: Boolean, - required: false, - default: false - }, - // TODO - truncate: { - type: Boolean, - default: true - } - }, - - data() { - return { - showContent: false, - replies: [], - }; - }, - - created() { - if (this.detail) { - os.api('notes/children', { - noteId: this.note.id, - limit: 5 - }).then(replies => { - this.replies = replies; - }); - } - }, -}); -</script> - -<style lang="scss" scoped> -.wrpstxzv { - padding: 16px 16px; - font-size: 0.8em; - - &.children { - padding: 10px 0 0 16px; - font-size: 1em; - } - - > .main { - display: flex; - - > .avatar { - flex-shrink: 0; - display: block; - margin: 0 8px 0 0; - width: 36px; - height: 36px; - } - - > .body { - flex: 1; - min-width: 0; - - > .header { - margin-bottom: 2px; - } - - > .body { - > .cw { - cursor: default; - display: block; - margin: 0; - padding: 0; - overflow-wrap: break-word; - - > .text { - margin-right: 8px; - } - } - - > .content { - > .text { - margin: 0; - padding: 0; - } - } - } - } - } - - > .reply { - border-left: solid 0.5px var(--divider); - margin-top: 10px; - } -} -</style> diff --git a/packages/client/src/ui/chat/note.vue b/packages/client/src/ui/chat/note.vue deleted file mode 100644 index 6927dd0eaf..0000000000 --- a/packages/client/src/ui/chat/note.vue +++ /dev/null @@ -1,1142 +0,0 @@ -<template> -<div - v-if="!muted" - v-show="!isDeleted" - v-hotkey="keymap" - class="vfzoeqcg" - :tabindex="!isDeleted ? '-1' : null" - :class="{ renote: isRenote, highlighted: appearNote._prId_ || appearNote._featuredId_, operating }" -> - <XSub v-if="appearNote.reply" :note="appearNote.reply" class="reply-to"/> - <div v-if="pinned" class="info"><i class="fas fa-thumbtack"></i> {{ $ts.pinnedNote }}</div> - <div v-if="appearNote._prId_" class="info"><i class="fas fa-bullhorn"></i> {{ $ts.promotion }}<button class="_textButton hide" @click="readPromo()">{{ $ts.hideThisNote }} <i class="fas fa-times"></i></button></div> - <div v-if="appearNote._featuredId_" class="info"><i class="fas fa-bolt"></i> {{ $ts.featured }}</div> - <div v-if="isRenote" class="renote"> - <MkAvatar class="avatar" :user="note.user"/> - <i class="fas fa-retweet"></i> - <I18n :src="$ts.renotedBy" tag="span"> - <template #user> - <MkA v-user-preview="note.userId" class="name" :to="userPage(note.user)"> - <MkUserName :user="note.user"/> - </MkA> - </template> - </I18n> - <div class="info"> - <button ref="renoteTime" class="_button time" @click="showRenoteMenu()"> - <i v-if="isMyRenote" class="fas fa-ellipsis-h dropdownIcon"></i> - <MkTime :time="note.createdAt"/> - </button> - <span v-if="note.visibility !== 'public'" class="visibility"> - <i v-if="note.visibility === 'home'" class="fas fa-home"></i> - <i v-else-if="note.visibility === 'followers'" class="fas fa-unlock"></i> - <i v-else-if="note.visibility === 'specified'" class="fas fa-envelope"></i> - </span> - <span v-if="note.localOnly" class="localOnly"><i class="fas fa-biohazard"></i></span> - </div> - </div> - <article class="article" @contextmenu.stop="onContextmenu"> - <MkAvatar class="avatar" :user="appearNote.user"/> - <div class="main"> - <XNoteHeader class="header" :note="appearNote" :mini="true"/> - <MkInstanceTicker v-if="showTicker" class="ticker" :instance="appearNote.user.instance"/> - <div class="body"> - <p v-if="appearNote.cw != null" class="cw"> - <Mfm v-if="appearNote.cw != ''" class="text" :text="appearNote.cw" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/> - <XCwButton v-model="showContent" :note="appearNote"/> - </p> - <div v-show="appearNote.cw == null || showContent" class="content" :class="{ collapsed }"> - <div class="text"> - <span v-if="appearNote.isHidden" style="opacity: 0.5">({{ $ts.private }})</span> - <MkA v-if="appearNote.replyId" class="reply" :to="`/notes/${appearNote.replyId}`"><i class="fas fa-reply"></i></MkA> - <Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/> - <a v-if="appearNote.renote != null" class="rp">RN:</a> - </div> - <div v-if="appearNote.files.length > 0" class="files"> - <XMediaList :media-list="appearNote.files"/> - </div> - <XPoll v-if="appearNote.poll" ref="pollViewer" :note="appearNote" class="poll"/> - <MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" class="url-preview"/> - <div v-if="appearNote.renote" class="renote"><XNoteSimple :note="appearNote.renote"/></div> - <button v-if="collapsed" class="fade _button" @click="collapsed = false"> - <span>{{ $ts.showMore }}</span> - </button> - </div> - <MkA v-if="appearNote.channel && !inChannel" class="channel" :to="`/channels/${appearNote.channel.id}`"><i class="fas fa-satellite-dish"></i> {{ appearNote.channel.name }}</MkA> - </div> - <XReactionsViewer ref="reactionsViewer" :note="appearNote"/> - <footer class="footer _panel"> - <button v-tooltip="$ts.reply" class="button _button" @click="reply()"> - <template v-if="appearNote.reply"><i class="fas fa-reply-all"></i></template> - <template v-else><i class="fas fa-reply"></i></template> - <p v-if="appearNote.repliesCount > 0" class="count">{{ appearNote.repliesCount }}</p> - </button> - <button v-if="canRenote" ref="renoteButton" v-tooltip="$ts.renote" class="button _button" @click="renote()"> - <i class="fas fa-retweet"></i><p v-if="appearNote.renoteCount > 0" class="count">{{ appearNote.renoteCount }}</p> - </button> - <button v-else class="button _button"> - <i class="fas fa-ban"></i> - </button> - <button v-if="appearNote.myReaction == null" ref="reactButton" v-tooltip="$ts.reaction" class="button _button" @click="react()"> - <i class="fas fa-plus"></i> - </button> - <button v-if="appearNote.myReaction != null" ref="reactButton" v-tooltip="$ts.reaction" class="button _button reacted" @click="undoReact(appearNote)"> - <i class="fas fa-minus"></i> - </button> - <button ref="menuButton" class="button _button" @click="menu()"> - <i class="fas fa-ellipsis-h"></i> - </button> - </footer> - </div> - </article> -</div> -<div v-else class="muted" @click="muted = false"> - <I18n :src="$ts.userSaysSomething" tag="small"> - <template #name> - <MkA v-user-preview="appearNote.userId" class="name" :to="userPage(appearNote.user)"> - <MkUserName :user="appearNote.user"/> - </MkA> - </template> - </I18n> -</div> -</template> - -<script lang="ts"> -import { defineAsyncComponent, defineComponent, markRaw } from 'vue'; -import * as mfm from 'mfm-js'; -import { sum } from '@/scripts/array'; -import XSub from './note.sub.vue'; -import XNoteHeader from './note-header.vue'; -import XNoteSimple from './note-preview.vue'; -import XReactionsViewer from '@/components/reactions-viewer.vue'; -import XMediaList from '@/components/media-list.vue'; -import XCwButton from '@/components/cw-button.vue'; -import XPoll from '@/components/poll.vue'; -import { pleaseLogin } from '@/scripts/please-login'; -import { focusPrev, focusNext } from '@/scripts/focus'; -import { url } from '@/config'; -import copyToClipboard from '@/scripts/copy-to-clipboard'; -import { checkWordMute } from '@/scripts/check-word-mute'; -import { userPage } from '@/filters/user'; -import * as os from '@/os'; -import { noteActions, noteViewInterruptors } from '@/store'; -import { reactionPicker } from '@/scripts/reaction-picker'; -import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm'; - -export default defineComponent({ - components: { - XSub, - XNoteHeader, - XNoteSimple, - XReactionsViewer, - XMediaList, - XCwButton, - XPoll, - MkUrlPreview: defineAsyncComponent(() => import('@/components/url-preview.vue')), - MkInstanceTicker: defineAsyncComponent(() => import('@/components/instance-ticker.vue')), - }, - - inject: { - inChannel: { - default: null - }, - }, - - props: { - note: { - type: Object, - required: true - }, - pinned: { - type: Boolean, - required: false, - default: false - }, - }, - - emits: ['update:note'], - - data() { - return { - connection: null, - replies: [], - showContent: false, - collapsed: false, - isDeleted: false, - muted: false, - operating: false, - }; - }, - - computed: { - rs() { - return this.$store.state.reactions; - }, - keymap(): any { - return { - 'r': () => this.reply(true), - 'e|a|plus': () => this.react(true), - 'q': () => this.renote(true), - 'f|b': this.favorite, - 'delete|ctrl+d': this.del, - 'ctrl+q': this.renoteDirectly, - 'up|k|shift+tab': this.focusBefore, - 'down|j|tab': this.focusAfter, - 'esc': this.blur, - 'm|o': () => this.menu(true), - 's': this.toggleShowContent, - '1': () => this.reactDirectly(this.rs[0]), - '2': () => this.reactDirectly(this.rs[1]), - '3': () => this.reactDirectly(this.rs[2]), - '4': () => this.reactDirectly(this.rs[3]), - '5': () => this.reactDirectly(this.rs[4]), - '6': () => this.reactDirectly(this.rs[5]), - '7': () => this.reactDirectly(this.rs[6]), - '8': () => this.reactDirectly(this.rs[7]), - '9': () => this.reactDirectly(this.rs[8]), - '0': () => this.reactDirectly(this.rs[9]), - }; - }, - - isRenote(): boolean { - return (this.note.renote && - this.note.text == null && - this.note.fileIds.length == 0 && - this.note.poll == null); - }, - - appearNote(): any { - return this.isRenote ? this.note.renote : this.note; - }, - - isMyNote(): boolean { - return this.$i && (this.$i.id === this.appearNote.userId); - }, - - isMyRenote(): boolean { - return this.$i && (this.$i.id === this.note.userId); - }, - - canRenote(): boolean { - return ['public', 'home'].includes(this.appearNote.visibility) || this.isMyNote; - }, - - reactionsCount(): number { - return this.appearNote.reactions - ? sum(Object.values(this.appearNote.reactions)) - : 0; - }, - - urls(): string[] { - if (this.appearNote.text) { - return extractUrlFromMfm(mfm.parse(this.appearNote.text)); - } else { - return null; - } - }, - - showTicker() { - if (this.$store.state.instanceTicker === 'always') return true; - if (this.$store.state.instanceTicker === 'remote' && this.appearNote.user.instance) return true; - return false; - } - }, - - async created() { - if (this.$i) { - this.connection = os.stream; - } - - this.collapsed = this.appearNote.cw == null && this.appearNote.text && ( - (this.appearNote.text.split('\n').length > 9) || - (this.appearNote.text.length > 500) - ); - this.muted = await checkWordMute(this.appearNote, this.$i, this.$store.state.mutedWords); - - // plugin - if (noteViewInterruptors.length > 0) { - let result = this.note; - for (const interruptor of noteViewInterruptors) { - result = await interruptor.handler(JSON.parse(JSON.stringify(result))); - } - this.$emit('update:note', Object.freeze(result)); - } - }, - - mounted() { - this.capture(true); - - if (this.$i) { - this.connection.on('_connected_', this.onStreamConnected); - } - }, - - beforeUnmount() { - this.decapture(true); - - if (this.$i) { - this.connection.off('_connected_', this.onStreamConnected); - } - }, - - methods: { - updateAppearNote(v) { - this.$emit('update:note', Object.freeze(this.isRenote ? { - ...this.note, - renote: { - ...this.note.renote, - ...v - } - } : { - ...this.note, - ...v - })); - }, - - readPromo() { - os.api('promo/read', { - noteId: this.appearNote.id - }); - this.isDeleted = true; - }, - - capture(withHandler = false) { - if (this.$i) { - // TODO: このノートがストリーミング経由で流れてきた場合のみ sr する - this.connection.send(document.body.contains(this.$el) ? 'sr' : 's', { id: this.appearNote.id }); - if (withHandler) this.connection.on('noteUpdated', this.onStreamNoteUpdated); - } - }, - - decapture(withHandler = false) { - if (this.$i) { - this.connection.send('un', { - id: this.appearNote.id - }); - if (withHandler) this.connection.off('noteUpdated', this.onStreamNoteUpdated); - } - }, - - onStreamConnected() { - this.capture(); - }, - - onStreamNoteUpdated(data) { - const { type, id, body } = data; - - if (id !== this.appearNote.id) return; - - switch (type) { - case 'reacted': { - const reaction = body.reaction; - - // DeepではなくShallowコピーであることに注意。n.reactions[reaction] = hogeとかしないように(親からもらったデータをミューテートすることになるので) - let n = { - ...this.appearNote, - }; - - if (body.emoji) { - const emojis = this.appearNote.emojis || []; - if (!emojis.includes(body.emoji)) { - n.emojis = [...emojis, body.emoji]; - } - } - - // TODO: reactionsプロパティがない場合ってあったっけ? なければ || {} は消せる - const currentCount = (this.appearNote.reactions || {})[reaction] || 0; - - // Increment the count - n.reactions = { - ...this.appearNote.reactions, - [reaction]: currentCount + 1 - }; - - if (body.userId === this.$i.id) { - n.myReaction = reaction; - } - - this.updateAppearNote(n); - break; - } - - case 'unreacted': { - const reaction = body.reaction; - - // DeepではなくShallowコピーであることに注意。n.reactions[reaction] = hogeとかしないように(親からもらったデータをミューテートすることになるので) - let n = { - ...this.appearNote, - }; - - // TODO: reactionsプロパティがない場合ってあったっけ? なければ || {} は消せる - const currentCount = (this.appearNote.reactions || {})[reaction] || 0; - - // Decrement the count - n.reactions = { - ...this.appearNote.reactions, - [reaction]: Math.max(0, currentCount - 1) - }; - - if (body.userId === this.$i.id) { - n.myReaction = null; - } - - this.updateAppearNote(n); - break; - } - - case 'pollVoted': { - const choice = body.choice; - - // DeepではなくShallowコピーであることに注意。n.reactions[reaction] = hogeとかしないように(親からもらったデータをミューテートすることになるので) - let n = { - ...this.appearNote, - }; - - const choices = [...this.appearNote.poll.choices]; - choices[choice] = { - ...choices[choice], - votes: choices[choice].votes + 1, - ...(body.userId === this.$i.id ? { - isVoted: true - } : {}) - }; - - n.poll = { - ...this.appearNote.poll, - choices: choices - }; - - this.updateAppearNote(n); - break; - } - - case 'deleted': { - this.isDeleted = true; - break; - } - } - }, - - reply(viaKeyboard = false) { - pleaseLogin(); - this.operating = true; - os.post({ - reply: this.appearNote, - animation: !viaKeyboard, - }, () => { - this.operating = false; - this.focus(); - }); - }, - - renote(viaKeyboard = false) { - pleaseLogin(); - this.operating = true; - this.blur(); - os.popupMenu([{ - text: this.$ts.renote, - icon: 'fas fa-retweet', - action: () => { - os.api('notes/create', { - renoteId: this.appearNote.id - }); - } - }, { - text: this.$ts.quote, - icon: 'fas fa-quote-right', - action: () => { - os.post({ - renote: this.appearNote, - }); - } - }], this.$refs.renoteButton, { - viaKeyboard - }).then(() => { - this.operating = false; - }); - }, - - renoteDirectly() { - os.apiWithDialog('notes/create', { - renoteId: this.appearNote.id - }, undefined, (res: any) => { - os.alert({ - type: 'success', - text: this.$ts.renoted, - }); - }, (e: Error) => { - if (e.id === 'b5c90186-4ab0-49c8-9bba-a1f76c282ba4') { - os.alert({ - type: 'error', - text: this.$ts.cantRenote, - }); - } else if (e.id === 'fd4cc33e-2a37-48dd-99cc-9b806eb2031a') { - os.alert({ - type: 'error', - text: this.$ts.cantReRenote, - }); - } - }); - }, - - async react(viaKeyboard = false) { - pleaseLogin(); - this.operating = true; - this.blur(); - reactionPicker.show(this.$refs.reactButton, reaction => { - os.api('notes/reactions/create', { - noteId: this.appearNote.id, - reaction: reaction - }); - }, () => { - this.operating = false; - this.focus(); - }); - }, - - reactDirectly(reaction) { - os.api('notes/reactions/create', { - noteId: this.appearNote.id, - reaction: reaction - }); - }, - - undoReact(note) { - const oldReaction = note.myReaction; - if (!oldReaction) return; - os.api('notes/reactions/delete', { - noteId: note.id - }); - }, - - favorite() { - pleaseLogin(); - os.apiWithDialog('notes/favorites/create', { - noteId: this.appearNote.id - }, undefined, (res: any) => { - os.alert({ - type: 'success', - text: this.$ts.favorited, - }); - }, (e: Error) => { - if (e.id === 'a402c12b-34dd-41d2-97d8-4d2ffd96a1a6') { - os.alert({ - type: 'error', - text: this.$ts.alreadyFavorited, - }); - } else if (e.id === '6dd26674-e060-4816-909a-45ba3f4da458') { - os.alert({ - type: 'error', - text: this.$ts.cantFavorite, - }); - } - }); - }, - - del() { - os.confirm({ - type: 'warning', - text: this.$ts.noteDeleteConfirm, - }).then(({ canceled }) => { - if (canceled) return; - - os.api('notes/delete', { - noteId: this.appearNote.id - }); - }); - }, - - delEdit() { - os.confirm({ - type: 'warning', - text: this.$ts.deleteAndEditConfirm, - }).then(({ canceled }) => { - if (canceled) return; - - os.api('notes/delete', { - noteId: this.appearNote.id - }); - - os.post({ initialNote: this.appearNote, renote: this.appearNote.renote, reply: this.appearNote.reply, channel: this.appearNote.channel }); - }); - }, - - toggleFavorite(favorite: boolean) { - os.apiWithDialog(favorite ? 'notes/favorites/create' : 'notes/favorites/delete', { - noteId: this.appearNote.id - }); - }, - - toggleWatch(watch: boolean) { - os.apiWithDialog(watch ? 'notes/watching/create' : 'notes/watching/delete', { - noteId: this.appearNote.id - }); - }, - - getMenu() { - let menu; - if (this.$i) { - const statePromise = os.api('notes/state', { - noteId: this.appearNote.id - }); - - menu = [{ - icon: 'fas fa-copy', - text: this.$ts.copyContent, - action: this.copyContent - }, { - icon: 'fas fa-link', - text: this.$ts.copyLink, - action: this.copyLink - }, (this.appearNote.url || this.appearNote.uri) ? { - icon: 'fas fa-external-link-square-alt', - text: this.$ts.showOnRemote, - action: () => { - window.open(this.appearNote.url || this.appearNote.uri, '_blank'); - } - } : undefined, - { - icon: 'fas fa-share-alt', - text: this.$ts.share, - action: this.share - }, - null, - statePromise.then(state => state.isFavorited ? { - icon: 'fas fa-star', - text: this.$ts.unfavorite, - action: () => this.toggleFavorite(false) - } : { - icon: 'fas fa-star', - text: this.$ts.favorite, - action: () => this.toggleFavorite(true) - }), - { - icon: 'fas fa-paperclip', - text: this.$ts.clip, - action: () => this.clip() - }, - (this.appearNote.userId != this.$i.id) ? statePromise.then(state => state.isWatching ? { - icon: 'fas fa-eye-slash', - text: this.$ts.unwatch, - action: () => this.toggleWatch(false) - } : { - icon: 'fas fa-eye', - text: this.$ts.watch, - action: () => this.toggleWatch(true) - }) : undefined, - this.appearNote.userId == this.$i.id ? (this.$i.pinnedNoteIds || []).includes(this.appearNote.id) ? { - icon: 'fas fa-thumbtack', - text: this.$ts.unpin, - action: () => this.togglePin(false) - } : { - icon: 'fas fa-thumbtack', - text: this.$ts.pin, - action: () => this.togglePin(true) - } : undefined, - /* - ...(this.$i.isModerator || this.$i.isAdmin ? [ - null, - { - icon: 'fas fa-bullhorn', - text: this.$ts.promote, - action: this.promote - }] - : [] - ),*/ - ...(this.appearNote.userId != this.$i.id ? [ - null, - { - icon: 'fas fa-exclamation-circle', - text: this.$ts.reportAbuse, - action: () => { - const u = `${url}/notes/${this.appearNote.id}`; - os.popup(import('@/components/abuse-report-window.vue'), { - user: this.appearNote.user, - initialComment: `Note: ${u}\n-----\n` - }, {}, 'closed'); - } - }] - : [] - ), - ...(this.appearNote.userId == this.$i.id || this.$i.isModerator || this.$i.isAdmin ? [ - null, - this.appearNote.userId == this.$i.id ? { - icon: 'fas fa-edit', - text: this.$ts.deleteAndEdit, - action: this.delEdit - } : undefined, - { - icon: 'fas fa-trash-alt', - text: this.$ts.delete, - danger: true, - action: this.del - }] - : [] - )] - .filter(x => x !== undefined); - } else { - menu = [{ - icon: 'fas fa-copy', - text: this.$ts.copyContent, - action: this.copyContent - }, { - icon: 'fas fa-link', - text: this.$ts.copyLink, - action: this.copyLink - }, (this.appearNote.url || this.appearNote.uri) ? { - icon: 'fas fa-external-link-square-alt', - text: this.$ts.showOnRemote, - action: () => { - window.open(this.appearNote.url || this.appearNote.uri, '_blank'); - } - } : undefined] - .filter(x => x !== undefined); - } - - if (noteActions.length > 0) { - menu = menu.concat([null, ...noteActions.map(action => ({ - icon: 'fas fa-plug', - text: action.title, - action: () => { - action.handler(this.appearNote); - } - }))]); - } - - return menu; - }, - - onContextmenu(e) { - const isLink = (el: HTMLElement) => { - if (el.tagName === 'A') return true; - if (el.parentElement) { - return isLink(el.parentElement); - } - }; - if (isLink(e.target)) return; - if (window.getSelection().toString() !== '') return; - - if (this.$store.state.useReactionPickerForContextMenu) { - e.preventDefault(); - this.react(); - } else { - os.contextMenu(this.getMenu(), e).then(this.focus); - } - }, - - menu(viaKeyboard = false) { - this.operating = true; - os.popupMenu(this.getMenu(), this.$refs.menuButton, { - viaKeyboard - }).then(() => { - this.operating = false; - this.focus(); - }); - }, - - showRenoteMenu(viaKeyboard = false) { - if (!this.isMyRenote) return; - os.popupMenu([{ - text: this.$ts.unrenote, - icon: 'fas fa-trash-alt', - danger: true, - action: () => { - os.api('notes/delete', { - noteId: this.note.id - }); - this.isDeleted = true; - } - }], this.$refs.renoteTime, { - viaKeyboard: viaKeyboard - }); - }, - - toggleShowContent() { - this.showContent = !this.showContent; - }, - - copyContent() { - copyToClipboard(this.appearNote.text); - os.success(); - }, - - copyLink() { - copyToClipboard(`${url}/notes/${this.appearNote.id}`); - os.success(); - }, - - togglePin(pin: boolean) { - os.apiWithDialog(pin ? 'i/pin' : 'i/unpin', { - noteId: this.appearNote.id - }, undefined, null, e => { - if (e.id === '72dab508-c64d-498f-8740-a8eec1ba385a') { - os.alert({ - type: 'error', - text: this.$ts.pinLimitExceeded - }); - } - }); - }, - - async clip() { - const clips = await os.api('clips/list'); - os.popupMenu([{ - icon: 'fas fa-plus', - text: this.$ts.createNew, - action: async () => { - const { canceled, result } = await os.form(this.$ts.createNewClip, { - name: { - type: 'string', - label: this.$ts.name - }, - description: { - type: 'string', - required: false, - multiline: true, - label: this.$ts.description - }, - isPublic: { - type: 'boolean', - label: this.$ts.public, - default: false - } - }); - if (canceled) return; - - const clip = await os.apiWithDialog('clips/create', result); - - os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: this.appearNote.id }); - } - }, null, ...clips.map(clip => ({ - text: clip.name, - action: () => { - os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: this.appearNote.id }); - } - }))], this.$refs.menuButton, { - }).then(this.focus); - }, - - async promote() { - const { canceled, result: days } = await os.inputNumber({ - title: this.$ts.numberOfDays, - }); - - if (canceled) return; - - os.apiWithDialog('admin/promo/create', { - noteId: this.appearNote.id, - expiresAt: Date.now() + (86400000 * days) - }); - }, - - share() { - navigator.share({ - title: this.$t('noteOf', { user: this.appearNote.user.name }), - text: this.appearNote.text, - url: `${url}/notes/${this.appearNote.id}` - }); - }, - - focus() { - this.$el.focus(); - }, - - blur() { - this.$el.blur(); - }, - - focusBefore() { - focusPrev(this.$el); - }, - - focusAfter() { - focusNext(this.$el); - }, - - userPage - } -}); -</script> - -<style lang="scss" scoped> -.vfzoeqcg { - position: relative; - contain: content; - - // これらの指定はパフォーマンス向上には有効だが、ノートの高さは一定でないため、 - // 下の方までスクロールすると上のノートの高さがここで決め打ちされたものに変化し、表示しているノートの位置が変わってしまう - // ノートがマウントされたときに自身の高さを取得し contain-intrinsic-size を設定しなおせばほぼ解決できそうだが、 - // 今度はその処理自体がパフォーマンス低下の原因にならないか懸念される。また、被リアクションでも高さは変化するため、やはり多少のズレは生じる - // 一度レンダリングされた要素はブラウザがよしなにサイズを覚えておいてくれるような実装になるまで待った方が良さそう(なるのか?) - //content-visibility: auto; - //contain-intrinsic-size: 0 128px; - - &:focus-visible { - outline: none; - } - - &:hover { - background: rgba(0, 0, 0, 0.05); - } - - &:hover, &.operating { - > .article > .main > .footer { - display: block; - } - } - - &.renote { - background: rgba(128, 255, 0, 0.05); - } - - &.highlighted { - background: rgba(255, 128, 0, 0.05); - } - - > .info { - display: flex; - align-items: center; - padding: 12px 16px 4px 16px; - line-height: 24px; - font-size: 85%; - white-space: pre; - color: #d28a3f; - - > i { - margin-right: 4px; - } - - > .hide { - margin-left: 16px; - color: inherit; - opacity: 0.7; - } - } - - > .info + .article { - padding-top: 8px; - } - - > .reply-to { - opacity: 0.7; - padding-bottom: 0; - } - - > .renote { - display: flex; - align-items: center; - padding: 12px 16px 4px 16px; - line-height: 28px; - white-space: pre; - color: var(--renote); - font-size: 0.9em; - - > .avatar { - flex-shrink: 0; - display: inline-block; - width: 28px; - height: 28px; - margin: 0 8px 0 0; - border-radius: 6px; - } - - > i { - margin-right: 4px; - } - - > span { - overflow: hidden; - flex-shrink: 1; - text-overflow: ellipsis; - white-space: nowrap; - - > .name { - font-weight: bold; - } - } - - > .info { - margin-left: 8px; - font-size: 0.9em; - opacity: 0.7; - - > .time { - flex-shrink: 0; - color: inherit; - - > .dropdownIcon { - margin-right: 4px; - } - } - - > .visibility { - margin-left: 8px; - } - - > .localOnly { - margin-left: 8px; - } - } - } - - > .renote + .article { - padding-top: 8px; - } - - > .article { - display: flex; - padding: 12px 16px; - - > .avatar { - flex-shrink: 0; - display: block; - position: sticky; - top: 0; - margin: 0 14px 0 0; - width: 46px; - height: 46px; - } - - > .main { - flex: 1; - min-width: 0; - - > .body { - > .cw { - cursor: default; - display: block; - margin: 0; - padding: 0; - overflow-wrap: break-word; - - > .text { - margin-right: 8px; - } - } - - > .content { - &.collapsed { - position: relative; - max-height: 9em; - overflow: hidden; - - > .fade { - display: block; - position: absolute; - bottom: 0; - left: 0; - width: 100%; - height: 64px; - background: linear-gradient(0deg, var(--panel), var(--X15)); - - > span { - display: inline-block; - background: var(--panel); - padding: 6px 10px; - font-size: 0.8em; - border-radius: 999px; - box-shadow: 0 2px 6px rgb(0 0 0 / 20%); - } - - &:hover { - > span { - background: var(--panelHighlight); - } - } - } - } - - > .text { - overflow-wrap: break-word; - - > .reply { - color: var(--accent); - margin-right: 0.5em; - } - - > .rp { - margin-left: 4px; - font-style: oblique; - color: var(--renote); - } - } - - > .files { - max-width: 500px; - } - - > .url-preview { - margin-top: 8px; - max-width: 500px; - } - - > .poll { - font-size: 80%; - max-width: 500px; - } - - > .renote { - padding: 8px 0; - - > * { - padding: 16px; - border: dashed 1px var(--renote); - border-radius: 8px; - } - } - } - - > .channel { - opacity: 0.7; - font-size: 80%; - } - } - - > .footer { - display: none; - position: absolute; - top: 8px; - right: 8px; - padding: 0 6px; - opacity: 0.7; - - &:hover { - opacity: 1; - } - - > .button { - margin: 0; - padding: 8px; - opacity: 0.7; - - &:hover { - color: var(--accent); - } - - > .count { - display: inline; - margin: 0 0 0 8px; - opacity: 0.7; - } - - &.reacted { - color: var(--accent); - } - } - } - } - } - - > .reply { - border-top: solid 0.5px var(--divider); - } -} - -.muted { - padding: 8px 16px; - opacity: 0.7; - - &:hover { - background: rgba(0, 0, 0, 0.05); - } -} -</style> diff --git a/packages/client/src/ui/chat/notes.vue b/packages/client/src/ui/chat/notes.vue deleted file mode 100644 index 51d4afcf54..0000000000 --- a/packages/client/src/ui/chat/notes.vue +++ /dev/null @@ -1,94 +0,0 @@ -<template> -<div class=""> - <div v-if="empty" class="_fullinfo"> - <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> - <div>{{ $ts.noNotes }}</div> - </div> - - <MkLoading v-if="fetching"/> - - <MkError v-if="error" @retry="init()"/> - - <div v-show="more && reversed" style="margin-bottom: var(--margin);"> - <MkButton style="margin: 0 auto;" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore"> - <template v-if="!moreFetching">{{ $ts.loadMore }}</template> - <template v-if="moreFetching"><MkLoading inline/></template> - </MkButton> - </div> - - <XList ref="notes" v-slot="{ item: note }" :items="notes" :direction="reversed ? 'up' : 'down'" :reversed="reversed" :ad="true"> - <XNote :key="note._featuredId_ || note._prId_ || note.id" :note="note" @update:note="updated(note, $event)"/> - </XList> - - <div v-show="more && !reversed" style="margin-top: var(--margin);"> - <MkButton v-appear="$store.state.enableInfiniteScroll ? fetchMore : null" style="margin: 0 auto;" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore"> - <template v-if="!moreFetching">{{ $ts.loadMore }}</template> - <template v-if="moreFetching"><MkLoading inline/></template> - </MkButton> - </div> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import paging from '@/scripts/paging'; -import XNote from './note.vue'; -import XList from './date-separated-list.vue'; -import MkButton from '@/components/ui/button.vue'; - -export default defineComponent({ - components: { - XNote, XList, MkButton, - }, - - mixins: [ - paging({ - before: (self) => { - self.$emit('before'); - }, - - after: (self, e) => { - self.$emit('after', e); - } - }), - ], - - props: { - pagination: { - required: true - }, - - prop: { - type: String, - required: false - } - }, - - emits: ['before', 'after'], - - computed: { - notes(): any[] { - return this.prop ? this.items.map(item => item[this.prop]) : this.items; - }, - - reversed(): boolean { - return this.pagination.reversed; - } - }, - - methods: { - updated(oldValue, newValue) { - const i = this.notes.findIndex(n => n === oldValue); - if (this.prop) { - this.items[i][this.prop] = newValue; - } else { - this.items[i] = newValue; - } - }, - - focus() { - this.$refs.notes.focus(); - } - } -}); -</script> diff --git a/packages/client/src/ui/chat/pages/channel.vue b/packages/client/src/ui/chat/pages/channel.vue deleted file mode 100644 index 13c735cd47..0000000000 --- a/packages/client/src/ui/chat/pages/channel.vue +++ /dev/null @@ -1,258 +0,0 @@ -<template> -<div v-if="channel" class="hhizbblb"> - <div v-if="date" class="info"> - <MkInfo>{{ $ts.showingPastTimeline }} <button class="_textButton clear" @click="timetravel()">{{ $ts.clear }}</button></MkInfo> - </div> - <div ref="body" class="tl"> - <div v-if="queue > 0" class="new" :style="{ width: width + 'px', bottom: bottom + 'px' }"><button class="_buttonPrimary" @click="goTop()">{{ $ts.newNoteRecived }}</button></div> - <XNotes ref="tl" v-follow="true" class="tl" :pagination="pagination" @queue="queueUpdated"/> - </div> - <div class="bottom"> - <div v-if="typers.length > 0" class="typers"> - <I18n :src="$ts.typingUsers" text-tag="span" class="users"> - <template #users> - <b v-for="user in typers" :key="user.id" class="user">{{ user.username }}</b> - </template> - </I18n> - <MkEllipsis/> - </div> - <XPostForm :channel="channel"/> - </div> -</div> -</template> - -<script lang="ts"> -import { computed, defineComponent, markRaw } from 'vue'; -import * as Misskey from 'misskey-js'; -import XNotes from '../notes.vue'; -import * as os from '@/os'; -import * as sound from '@/scripts/sound'; -import { scrollToBottom, getScrollPosition, getScrollContainer } from '@/scripts/scroll'; -import follow from '@/directives/follow-append'; -import XPostForm from '../post-form.vue'; -import MkInfo from '@/components/ui/info.vue'; -import * as symbols from '@/symbols'; - -export default defineComponent({ - components: { - XNotes, - XPostForm, - MkInfo, - }, - - directives: { - follow - }, - - provide() { - return { - inChannel: true - }; - }, - - props: { - channelId: { - type: String, - required: true - }, - }, - - data() { - return { - channel: null as Misskey.entities.Channel | null, - connection: null, - pagination: null, - baseQuery: { - includeMyRenotes: this.$store.state.showMyRenotes, - includeRenotedMyNotes: this.$store.state.showRenotedMyNotes, - includeLocalRenotes: this.$store.state.showLocalRenotes - }, - queue: 0, - width: 0, - top: 0, - bottom: 0, - typers: [], - date: null, - [symbols.PAGE_INFO]: computed(() => ({ - title: this.channel ? this.channel.name : '-', - subtitle: this.channel ? this.channel.description : '-', - icon: 'fas fa-satellite-dish', - actions: [{ - icon: this.channel?.isFollowing ? 'fas fa-star' : 'far fa-star', - text: this.channel?.isFollowing ? this.$ts.unfollow : this.$ts.follow, - highlighted: this.channel?.isFollowing, - handler: this.toggleChannelFollow - }, { - icon: 'fas fa-search', - text: this.$ts.inChannelSearch, - handler: this.inChannelSearch - }, { - icon: 'fas fa-calendar-alt', - text: this.$ts.jumpToSpecifiedDate, - handler: this.timetravel - }] - })), - }; - }, - - async created() { - this.channel = await os.api('channels/show', { channelId: this.channelId }); - - const prepend = note => { - (this.$refs.tl as any).prepend(note); - - this.$emit('note'); - - sound.play(note.userId === this.$i.id ? 'noteMy' : 'note'); - }; - - this.connection = markRaw(os.stream.useChannel('channel', { - channelId: this.channelId - })); - this.connection.on('note', prepend); - this.connection.on('typers', typers => { - this.typers = this.$i ? typers.filter(u => u.id !== this.$i.id) : typers; - }); - - this.pagination = { - endpoint: 'channels/timeline', - reversed: true, - limit: 10, - params: init => ({ - channelId: this.channelId, - untilDate: this.date?.getTime(), - ...this.baseQuery - }) - }; - }, - - mounted() { - - }, - - beforeUnmount() { - this.connection.dispose(); - }, - - methods: { - focus() { - this.$refs.body.focus(); - }, - - goTop() { - const container = getScrollContainer(this.$refs.body); - container.scrollTop = 0; - }, - - queueUpdated(q) { - if (this.$refs.body.offsetWidth !== 0) { - const rect = this.$refs.body.getBoundingClientRect(); - this.width = this.$refs.body.offsetWidth; - this.top = rect.top; - this.bottom = this.$refs.body.offsetHeight; - } - this.queue = q; - }, - - async inChannelSearch() { - const { canceled, result: query } = await os.inputText({ - title: this.$ts.inChannelSearch, - }); - if (canceled || query == null || query === '') return; - router.push(`/search?q=${encodeURIComponent(query)}&channel=${this.channelId}`); - }, - - async toggleChannelFollow() { - if (this.channel.isFollowing) { - await os.apiWithDialog('channels/unfollow', { - channelId: this.channel.id - }); - this.channel.isFollowing = false; - } else { - await os.apiWithDialog('channels/follow', { - channelId: this.channel.id - }); - this.channel.isFollowing = true; - } - }, - - openChannelMenu(ev) { - os.popupMenu([{ - text: this.$ts.copyUrl, - icon: 'fas fa-link', - action: () => { - copyToClipboard(`${url}/channels/${this.currentChannel.id}`); - } - }], ev.currentTarget || ev.target); - }, - - timetravel(date?: Date) { - this.date = date; - this.$refs.tl.reload(); - } - } -}); -</script> - -<style lang="scss" scoped> -.hhizbblb { - display: flex; - flex-direction: column; - flex: 1; - overflow: auto; - - > .info { - padding: 16px 16px 0 16px; - } - - > .top { - padding: 16px 16px 0 16px; - } - - > .bottom { - padding: 0 16px 16px 16px; - position: relative; - - > .typers { - position: absolute; - bottom: 100%; - padding: 0 8px 0 8px; - font-size: 0.9em; - background: var(--panel); - border-radius: 0 8px 0 0; - color: var(--fgTransparentWeak); - - > .users { - > .user + .user:before { - content: ", "; - font-weight: normal; - } - - > .user:last-of-type:after { - content: " "; - } - } - } - } - - > .tl { - position: relative; - padding: 16px 0; - flex: 1; - min-width: 0; - overflow: auto; - - > .new { - position: fixed; - z-index: 1000; - - > button { - display: block; - margin: 16px auto; - padding: 8px 16px; - border-radius: 32px; - } - } - } -} -</style> diff --git a/packages/client/src/ui/chat/pages/timeline.vue b/packages/client/src/ui/chat/pages/timeline.vue deleted file mode 100644 index 07e847ad73..0000000000 --- a/packages/client/src/ui/chat/pages/timeline.vue +++ /dev/null @@ -1,221 +0,0 @@ -<template> -<div class="dbiokgaf"> - <div v-if="date" class="info"> - <MkInfo>{{ $ts.showingPastTimeline }} <button class="_textButton clear" @click="timetravel()">{{ $ts.clear }}</button></MkInfo> - </div> - <div class="top"> - <XPostForm/> - </div> - <div ref="body" class="tl"> - <div v-if="queue > 0" class="new" :style="{ width: width + 'px', top: top + 'px' }"><button class="_buttonPrimary" @click="goTop()">{{ $ts.newNoteRecived }}</button></div> - <XNotes ref="tl" class="tl" :pagination="pagination" @queue="queueUpdated"/> - </div> -</div> -</template> - -<script lang="ts"> -import { computed, defineComponent, markRaw } from 'vue'; -import XNotes from '../notes.vue'; -import * as os from '@/os'; -import * as sound from '@/scripts/sound'; -import { scrollToBottom, getScrollPosition, getScrollContainer } from '@/scripts/scroll'; -import follow from '@/directives/follow-append'; -import XPostForm from '../post-form.vue'; -import MkInfo from '@/components/ui/info.vue'; -import * as symbols from '@/symbols'; - -export default defineComponent({ - components: { - XNotes, - XPostForm, - MkInfo, - }, - - directives: { - follow - }, - - props: { - src: { - type: String, - required: true - }, - }, - - data() { - return { - connection: null, - connection2: null, - pagination: null, - baseQuery: { - includeMyRenotes: this.$store.state.showMyRenotes, - includeRenotedMyNotes: this.$store.state.showRenotedMyNotes, - includeLocalRenotes: this.$store.state.showLocalRenotes - }, - query: {}, - queue: 0, - width: 0, - top: 0, - bottom: 0, - typers: [], - date: null, - [symbols.PAGE_INFO]: computed(() => ({ - title: this.$ts.timeline, - icon: 'fas fa-home', - actions: [{ - icon: 'fas fa-calendar-alt', - text: this.$ts.jumpToSpecifiedDate, - handler: this.timetravel - }] - })), - }; - }, - - created() { - const prepend = note => { - (this.$refs.tl as any).prepend(note); - - this.$emit('note'); - - sound.play(note.userId === this.$i.id ? 'noteMy' : 'note'); - }; - - const onChangeFollowing = () => { - if (!this.$refs.tl.backed) { - this.$refs.tl.reload(); - } - }; - - let endpoint; - - if (this.src == 'home') { - endpoint = 'notes/timeline'; - this.connection = markRaw(os.stream.useChannel('homeTimeline')); - this.connection.on('note', prepend); - - this.connection2 = markRaw(os.stream.useChannel('main')); - this.connection2.on('follow', onChangeFollowing); - this.connection2.on('unfollow', onChangeFollowing); - } else if (this.src == 'local') { - endpoint = 'notes/local-timeline'; - this.connection = markRaw(os.stream.useChannel('localTimeline')); - this.connection.on('note', prepend); - } else if (this.src == 'social') { - endpoint = 'notes/hybrid-timeline'; - this.connection = markRaw(os.stream.useChannel('hybridTimeline')); - this.connection.on('note', prepend); - } else if (this.src == 'global') { - endpoint = 'notes/global-timeline'; - this.connection = markRaw(os.stream.useChannel('globalTimeline')); - this.connection.on('note', prepend); - } - - this.pagination = { - endpoint: endpoint, - limit: 10, - params: init => ({ - untilDate: this.date?.getTime(), - ...this.baseQuery, ...this.query - }) - }; - }, - - mounted() { - - }, - - beforeUnmount() { - this.connection.dispose(); - if (this.connection2) this.connection2.dispose(); - }, - - methods: { - focus() { - this.$refs.body.focus(); - }, - - goTop() { - const container = getScrollContainer(this.$refs.body); - container.scrollTop = 0; - }, - - queueUpdated(q) { - if (this.$refs.body.offsetWidth !== 0) { - const rect = this.$refs.body.getBoundingClientRect(); - this.width = this.$refs.body.offsetWidth; - this.top = rect.top; - this.bottom = this.$refs.body.offsetHeight; - } - this.queue = q; - }, - - timetravel(date?: Date) { - this.date = date; - this.$refs.tl.reload(); - } - } -}); -</script> - -<style lang="scss" scoped> -.dbiokgaf { - display: flex; - flex-direction: column; - flex: 1; - overflow: auto; - - > .info { - padding: 16px 16px 0 16px; - } - - > .top { - padding: 16px 16px 0 16px; - } - - > .bottom { - padding: 0 16px 16px 16px; - position: relative; - - > .typers { - position: absolute; - bottom: 100%; - padding: 0 8px 0 8px; - font-size: 0.9em; - background: var(--panel); - border-radius: 0 8px 0 0; - color: var(--fgTransparentWeak); - - > .users { - > .user + .user:before { - content: ", "; - font-weight: normal; - } - - > .user:last-of-type:after { - content: " "; - } - } - } - } - - > .tl { - position: relative; - padding: 16px 0; - flex: 1; - min-width: 0; - overflow: auto; - - > .new { - position: fixed; - z-index: 1000; - - > button { - display: block; - margin: 16px auto; - padding: 8px 16px; - border-radius: 32px; - } - } - } -} -</style> diff --git a/packages/client/src/ui/chat/post-form.vue b/packages/client/src/ui/chat/post-form.vue deleted file mode 100644 index 8c572e3b1c..0000000000 --- a/packages/client/src/ui/chat/post-form.vue +++ /dev/null @@ -1,769 +0,0 @@ -<template> -<div class="pxiwixjf" - @dragover.stop="onDragover" - @dragenter="onDragenter" - @dragleave="onDragleave" - @drop.stop="onDrop" -> - <div class="form"> - <div v-if="quoteId" class="with-quote"><i class="fas fa-quote-left"></i> {{ $ts.quoteAttached }}<button @click="quoteId = null"><i class="fas fa-times"></i></button></div> - <div v-if="visibility === 'specified'" class="to-specified"> - <span style="margin-right: 8px;">{{ $ts.recipient }}</span> - <div class="visibleUsers"> - <span v-for="u in visibleUsers" :key="u.id"> - <MkAcct :user="u"/> - <button class="_button" @click="removeVisibleUser(u)"><i class="fas fa-times"></i></button> - </span> - <button class="_buttonPrimary" @click="addVisibleUser"><i class="fas fa-plus fa-fw"></i></button> - </div> - </div> - <input v-show="useCw" ref="cw" v-model="cw" class="cw" :placeholder="$ts.annotation" @keydown="onKeydown"> - <textarea ref="text" v-model="text" class="text" :class="{ withCw: useCw }" :disabled="posting" :placeholder="placeholder" @keydown="onKeydown" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd"/> - <XPostFormAttaches class="attaches" :files="files" @updated="updateFiles" @detach="detachFile" @changeSensitive="updateFileSensitive" @changeName="updateFileName"/> - <XPollEditor v-if="poll" :poll="poll" @destroyed="poll = null" @updated="onPollUpdate"/> - <footer> - <div class="left"> - <button v-tooltip="$ts.attachFile" class="_button" @click="chooseFileFrom"><i class="fas fa-photo-video"></i></button> - <button v-tooltip="$ts.poll" class="_button" :class="{ active: poll }" @click="togglePoll"><i class="fas fa-poll-h"></i></button> - <button v-tooltip="$ts.useCw" class="_button" :class="{ active: useCw }" @click="useCw = !useCw"><i class="fas fa-eye-slash"></i></button> - <button v-tooltip="$ts.mention" class="_button" @click="insertMention"><i class="fas fa-at"></i></button> - <button v-tooltip="$ts.emoji" class="_button" @click="insertEmoji"><i class="fas fa-laugh-squint"></i></button> - <button v-if="postFormActions.length > 0" v-tooltip="$ts.plugin" class="_button" @click="showActions"><i class="fas fa-plug"></i></button> - </div> - <div class="right"> - <span class="text-count" :class="{ over: textLength > max }">{{ max - textLength }}</span> - <span v-if="localOnly" class="local-only"><i class="fas fa-biohazard"></i></span> - <button ref="visibilityButton" v-tooltip="$ts.visibility" class="_button visibility" :disabled="channel != null" @click="setVisibility"> - <span v-if="visibility === 'public'"><i class="fas fa-globe"></i></span> - <span v-if="visibility === 'home'"><i class="fas fa-home"></i></span> - <span v-if="visibility === 'followers'"><i class="fas fa-unlock"></i></span> - <span v-if="visibility === 'specified'"><i class="fas fa-envelope"></i></span> - </button> - <button class="submit _buttonPrimary" :disabled="!canPost" @click="post">{{ submitText }}<i :class="reply ? 'fas fa-reply' : renote ? 'fas fa-quote-right' : 'fas fa-paper-plane'"></i></button> - </div> - </footer> - </div> -</div> -</template> - -<script lang="ts"> -import { defineComponent, defineAsyncComponent } from 'vue'; -import insertTextAtCursor from 'insert-text-at-cursor'; -import { length } from 'stringz'; -import { toASCII } from 'punycode/'; -import * as mfm from 'mfm-js'; -import { host, url } from '@/config'; -import { erase, unique } from '@/scripts/array'; -import { extractMentions } from '@/scripts/extract-mentions'; -import * as Acct from 'misskey-js/built/acct'; -import { formatTimeString } from '@/scripts/format-time-string'; -import { Autocomplete } from '@/scripts/autocomplete'; -import * as os from '@/os'; -import { selectFiles } from '@/scripts/select-file'; -import { notePostInterruptors, postFormActions } from '@/store'; -import { throttle } from 'throttle-debounce'; - -export default defineComponent({ - components: { - XPostFormAttaches: defineAsyncComponent(() => import('@/components/post-form-attaches.vue')), - XPollEditor: defineAsyncComponent(() => import('@/components/poll-editor.vue')) - }, - - props: { - reply: { - type: Object, - required: false - }, - renote: { - type: Object, - required: false - }, - channel: { - type: String, - required: false - }, - mention: { - type: Object, - required: false - }, - specified: { - type: Object, - required: false - }, - initialText: { - type: String, - required: false - }, - initialNote: { - type: Object, - required: false - }, - share: { - type: Boolean, - required: false, - default: false - }, - autofocus: { - type: Boolean, - required: false, - default: false - }, - }, - - emits: ['posted', 'cancel', 'esc'], - - data() { - return { - posting: false, - text: '', - files: [], - poll: null, - useCw: false, - cw: null, - localOnly: this.$store.state.rememberNoteVisibility ? this.$store.state.localOnly : this.$store.state.defaultNoteLocalOnly, - visibility: this.$store.state.rememberNoteVisibility ? this.$store.state.visibility : this.$store.state.defaultNoteVisibility, - visibleUsers: [], - autocomplete: null, - draghover: false, - quoteId: null, - recentHashtags: JSON.parse(localStorage.getItem('hashtags') || '[]'), - imeText: '', - typing: throttle(3000, () => { - if (this.channel) { - os.stream.send('typingOnChannel', { channel: this.channel }); - } - }), - postFormActions, - }; - }, - - computed: { - draftKey(): string { - let key = this.channel ? `channel:${this.channel}` : ''; - - if (this.renote) { - key += `renote:${this.renote.id}`; - } else if (this.reply) { - key += `reply:${this.reply.id}`; - } else { - key += 'note'; - } - - return key; - }, - - placeholder(): string { - if (this.renote) { - return this.$ts._postForm.quotePlaceholder; - } else if (this.reply) { - return this.$ts._postForm.replyPlaceholder; - } else if (this.channel) { - return this.$ts._postForm.channelPlaceholder; - } else { - const xs = [ - this.$ts._postForm._placeholders.a, - this.$ts._postForm._placeholders.b, - this.$ts._postForm._placeholders.c, - this.$ts._postForm._placeholders.d, - this.$ts._postForm._placeholders.e, - this.$ts._postForm._placeholders.f - ]; - return xs[Math.floor(Math.random() * xs.length)]; - } - }, - - submitText(): string { - return this.renote - ? this.$ts.quote - : this.reply - ? this.$ts.reply - : this.$ts.note; - }, - - textLength(): number { - return length((this.text + this.imeText).trim()); - }, - - canPost(): boolean { - return !this.posting && - (1 <= this.textLength || 1 <= this.files.length || !!this.poll || !!this.renote) && - (this.textLength <= this.max) && - (!this.poll || this.poll.choices.length >= 2); - }, - - max(): number { - return this.$instance ? this.$instance.maxNoteTextLength : 1000; - } - }, - - mounted() { - if (this.initialText) { - this.text = this.initialText; - } - - if (this.mention) { - this.text = this.mention.host ? `@${this.mention.username}@${toASCII(this.mention.host)}` : `@${this.mention.username}`; - this.text += ' '; - } - - if (this.reply && (this.reply.user.username != this.$i.username || (this.reply.user.host != null && this.reply.user.host != host))) { - this.text = `@${this.reply.user.username}${this.reply.user.host != null ? '@' + toASCII(this.reply.user.host) : ''} `; - } - - if (this.reply && this.reply.text != null) { - const ast = mfm.parse(this.reply.text); - - for (const x of extractMentions(ast)) { - const mention = x.host ? `@${x.username}@${toASCII(x.host)}` : `@${x.username}`; - - // 自分は除外 - if (this.$i.username == x.username && x.host == null) continue; - if (this.$i.username == x.username && x.host == host) continue; - - // 重複は除外 - if (this.text.indexOf(`${mention} `) != -1) continue; - - this.text += `${mention} `; - } - } - - if (this.channel) { - this.visibility = 'public'; - this.localOnly = true; // TODO: チャンネルが連合するようになった折には消す - } - - // 公開以外へのリプライ時は元の公開範囲を引き継ぐ - if (this.reply && ['home', 'followers', 'specified'].includes(this.reply.visibility)) { - this.visibility = this.reply.visibility; - if (this.reply.visibility === 'specified') { - os.api('users/show', { - userIds: this.reply.visibleUserIds.filter(uid => uid !== this.$i.id && uid !== this.reply.userId) - }).then(users => { - this.visibleUsers.push(...users); - }); - - if (this.reply.userId !== this.$i.id) { - os.api('users/show', { userId: this.reply.userId }).then(user => { - this.visibleUsers.push(user); - }); - } - } - } - - if (this.specified) { - this.visibility = 'specified'; - this.visibleUsers.push(this.specified); - } - - // keep cw when reply - if (this.$store.state.keepCw && this.reply && this.reply.cw) { - this.useCw = true; - this.cw = this.reply.cw; - } - - if (this.autofocus) { - this.focus(); - - this.$nextTick(() => { - this.focus(); - }); - } - - // TODO: detach when unmount - new Autocomplete(this.$refs.text, this, { model: 'text' }); - new Autocomplete(this.$refs.cw, this, { model: 'cw' }); - - this.$nextTick(() => { - // 書きかけの投稿を復元 - if (!this.share && !this.mention && !this.specified) { - const draft = JSON.parse(localStorage.getItem('drafts') || '{}')[this.draftKey]; - if (draft) { - this.text = draft.data.text; - this.useCw = draft.data.useCw; - this.cw = draft.data.cw; - this.visibility = draft.data.visibility; - this.localOnly = draft.data.localOnly; - this.files = (draft.data.files || []).filter(e => e); - if (draft.data.poll) { - this.poll = draft.data.poll; - } - } - } - - // 削除して編集 - if (this.initialNote) { - const init = this.initialNote; - this.text = init.text ? init.text : ''; - this.files = init.files; - this.cw = init.cw; - this.useCw = init.cw != null; - if (init.poll) { - this.poll = init.poll; - } - this.visibility = init.visibility; - this.localOnly = init.localOnly; - this.quoteId = init.renote ? init.renote.id : null; - } - - this.$nextTick(() => this.watch()); - }); - }, - - methods: { - watch() { - this.$watch('text', () => this.saveDraft()); - this.$watch('useCw', () => this.saveDraft()); - this.$watch('cw', () => this.saveDraft()); - this.$watch('poll', () => this.saveDraft()); - this.$watch('files', () => this.saveDraft(), { deep: true }); - this.$watch('visibility', () => this.saveDraft()); - this.$watch('localOnly', () => this.saveDraft()); - }, - - togglePoll() { - if (this.poll) { - this.poll = null; - } else { - this.poll = { - choices: ['', ''], - multiple: false, - expiresAt: null, - expiredAfter: null, - }; - } - }, - - addTag(tag: string) { - insertTextAtCursor(this.$refs.text, ` #${tag} `); - }, - - focus() { - (this.$refs.text as any).focus(); - }, - - chooseFileFrom(ev) { - selectFiles(ev.currentTarget || ev.target, this.$ts.attachFile).then(files => { - for (const file of files) { - this.files.push(file); - } - }); - }, - - detachFile(id) { - this.files = this.files.filter(x => x.id != id); - }, - - updateFiles(files) { - this.files = files; - }, - - updateFileSensitive(file, sensitive) { - this.files[this.files.findIndex(x => x.id === file.id)].isSensitive = sensitive; - }, - - updateFileName(file, name) { - this.files[this.files.findIndex(x => x.id === file.id)].name = name; - }, - - upload(file: File, name?: string) { - os.upload(file, this.$store.state.uploadFolder, name).then(res => { - this.files.push(res); - }); - }, - - onPollUpdate(poll) { - this.poll = poll; - this.saveDraft(); - }, - - setVisibility() { - if (this.channel) { - // TODO: information dialog - return; - } - - os.popup(import('@/components/visibility-picker.vue'), { - currentVisibility: this.visibility, - currentLocalOnly: this.localOnly, - src: this.$refs.visibilityButton - }, { - changeVisibility: visibility => { - this.visibility = visibility; - if (this.$store.state.rememberNoteVisibility) { - this.$store.set('visibility', visibility); - } - }, - changeLocalOnly: localOnly => { - this.localOnly = localOnly; - if (this.$store.state.rememberNoteVisibility) { - this.$store.set('localOnly', localOnly); - } - } - }, 'closed'); - }, - - addVisibleUser() { - os.selectUser().then(user => { - this.visibleUsers.push(user); - }); - }, - - removeVisibleUser(user) { - this.visibleUsers = erase(user, this.visibleUsers); - }, - - clear() { - this.text = ''; - this.files = []; - this.poll = null; - this.quoteId = null; - }, - - onKeydown(e: KeyboardEvent) { - if ((e.which === 10 || e.which === 13) && (e.ctrlKey || e.metaKey) && this.canPost) this.post(); - if (e.which === 27) this.$emit('esc'); - this.typing(); - }, - - onCompositionUpdate(e: CompositionEvent) { - this.imeText = e.data; - this.typing(); - }, - - onCompositionEnd(e: CompositionEvent) { - this.imeText = ''; - }, - - async onPaste(e: ClipboardEvent) { - for (const { item, i } of Array.from(e.clipboardData.items).map((item, i) => ({item, i}))) { - if (item.kind == 'file') { - const file = item.getAsFile(); - const lio = file.name.lastIndexOf('.'); - const ext = lio >= 0 ? file.name.slice(lio) : ''; - const formatted = `${formatTimeString(new Date(file.lastModified), this.$store.state.pastedFileName).replace(/{{number}}/g, `${i + 1}`)}${ext}`; - this.upload(file, formatted); - } - } - - const paste = e.clipboardData.getData('text'); - - if (!this.renote && !this.quoteId && paste.startsWith(url + '/notes/')) { - e.preventDefault(); - - os.confirm({ - type: 'info', - text: this.$ts.quoteQuestion, - }).then(({ canceled }) => { - if (canceled) { - insertTextAtCursor(this.$refs.text, paste); - return; - } - - this.quoteId = paste.substr(url.length).match(/^\/notes\/(.+?)\/?$/)[1]; - }); - } - }, - - onDragover(e) { - if (!e.dataTransfer.items[0]) return; - const isFile = e.dataTransfer.items[0].kind == 'file'; - const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; - if (isFile || isDriveFile) { - e.preventDefault(); - this.draghover = true; - e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; - } - }, - - onDragenter(e) { - this.draghover = true; - }, - - onDragleave(e) { - this.draghover = false; - }, - - onDrop(e): void { - this.draghover = false; - - // ファイルだったら - if (e.dataTransfer.files.length > 0) { - e.preventDefault(); - for (const x of Array.from(e.dataTransfer.files)) this.upload(x); - return; - } - - //#region ドライブのファイル - const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); - if (driveFile != null && driveFile != '') { - const file = JSON.parse(driveFile); - this.files.push(file); - e.preventDefault(); - } - //#endregion - }, - - saveDraft() { - const data = JSON.parse(localStorage.getItem('drafts') || '{}'); - - data[this.draftKey] = { - updatedAt: new Date(), - data: { - text: this.text, - useCw: this.useCw, - cw: this.cw, - visibility: this.visibility, - localOnly: this.localOnly, - files: this.files, - poll: this.poll - } - }; - - localStorage.setItem('drafts', JSON.stringify(data)); - }, - - deleteDraft() { - const data = JSON.parse(localStorage.getItem('drafts') || '{}'); - - delete data[this.draftKey]; - - localStorage.setItem('drafts', JSON.stringify(data)); - }, - - async post() { - let data = { - text: this.text == '' ? undefined : this.text, - fileIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined, - replyId: this.reply ? this.reply.id : undefined, - renoteId: this.renote ? this.renote.id : this.quoteId ? this.quoteId : undefined, - channelId: this.channel ? this.channel : undefined, - poll: this.poll, - cw: this.useCw ? this.cw || '' : undefined, - localOnly: this.localOnly, - visibility: this.visibility, - visibleUserIds: this.visibility == 'specified' ? this.visibleUsers.map(u => u.id) : undefined, - }; - - // plugin - if (notePostInterruptors.length > 0) { - for (const interruptor of notePostInterruptors) { - data = await interruptor.handler(JSON.parse(JSON.stringify(data))); - } - } - - this.posting = true; - os.api('notes/create', data).then(() => { - this.clear(); - this.$nextTick(() => { - this.deleteDraft(); - this.$emit('posted'); - if (this.text && this.text != '') { - const hashtags = mfm.parse(this.text).filter(x => x.type === 'hashtag').map(x => x.props.hashtag); - const history = JSON.parse(localStorage.getItem('hashtags') || '[]') as string[]; - localStorage.setItem('hashtags', JSON.stringify(unique(hashtags.concat(history)))); - } - this.posting = false; - }); - }).catch(err => { - this.posting = false; - os.alert({ - type: 'error', - text: err.message + '\n' + (err as any).id, - }); - }); - }, - - cancel() { - this.$emit('cancel'); - }, - - insertMention() { - os.selectUser().then(user => { - insertTextAtCursor(this.$refs.text, '@' + Acct.toString(user) + ' '); - }); - }, - - async insertEmoji(ev) { - os.openEmojiPicker(ev.currentTarget || ev.target, {}, this.$refs.text); - }, - - showActions(ev) { - os.popupMenu(postFormActions.map(action => ({ - text: action.title, - action: () => { - action.handler({ - text: this.text - }, (key, value) => { - if (key === 'text') { this.text = value; } - }); - } - })), ev.currentTarget || ev.target); - } - } -}); -</script> - -<style lang="scss" scoped> -.pxiwixjf { - position: relative; - border: solid 0.5px var(--divider); - border-radius: 8px; - - > .form { - > .preview { - padding: 16px; - } - - > .with-quote { - margin: 0 0 8px 0; - color: var(--accent); - - > button { - padding: 4px 8px; - color: var(--accentAlpha04); - - &:hover { - color: var(--accentAlpha06); - } - - &:active { - color: var(--accentDarken30); - } - } - } - - > .to-specified { - padding: 6px 24px; - margin-bottom: 8px; - overflow: auto; - white-space: nowrap; - - > .visibleUsers { - display: inline; - top: -1px; - font-size: 14px; - - > button { - padding: 4px; - border-radius: 8px; - } - - > span { - margin-right: 14px; - padding: 8px 0 8px 8px; - border-radius: 8px; - background: var(--X4); - - > button { - padding: 4px 8px; - } - } - } - } - - > .cw, - > .text { - display: block; - box-sizing: border-box; - padding: 16px; - margin: 0; - width: 100%; - font-size: 16px; - border: none; - border-radius: 0; - background: transparent; - color: var(--fg); - font-family: inherit; - - &:focus { - outline: none; - } - - &:disabled { - opacity: 0.5; - } - } - - > .cw { - z-index: 1; - padding-bottom: 8px; - border-bottom: solid 0.5px var(--divider); - } - - > .text { - max-width: 100%; - min-width: 100%; - min-height: 60px; - - &.withCw { - padding-top: 8px; - } - } - - > footer { - $height: 44px; - display: flex; - padding: 0 8px 8px 8px; - line-height: $height; - - > .left { - > button { - display: inline-block; - padding: 0; - margin: 0; - font-size: 16px; - width: $height; - height: $height; - border-radius: 6px; - - &:hover { - background: var(--X5); - } - - &.active { - color: var(--accent); - } - } - } - - > .right { - margin-left: auto; - - > .text-count { - opacity: 0.7; - } - - > .visibility { - width: $height; - margin: 0 8px; - - & + .localOnly { - margin-left: 0 !important; - } - } - - > .local-only { - margin: 0 0 0 12px; - opacity: 0.7; - } - - > .submit { - margin: 0; - padding: 0 12px; - line-height: 34px; - font-weight: bold; - border-radius: 4px; - - &:disabled { - opacity: 0.7; - } - - > i { - margin-left: 6px; - } - } - } - } - } -} -</style> diff --git a/packages/client/src/ui/chat/side.vue b/packages/client/src/ui/chat/side.vue deleted file mode 100644 index 548a46102b..0000000000 --- a/packages/client/src/ui/chat/side.vue +++ /dev/null @@ -1,157 +0,0 @@ -<template> -<div v-if="component" class="mrajymqm _narrow_"> - <header class="header" @contextmenu.prevent.stop="onContextmenu"> - <MkHeader class="title" :info="pageInfo" :center="false"/> - </header> - <component :is="component" v-bind="props" :ref="changePage" class="body"/> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as os from '@/os'; -import copyToClipboard from '@/scripts/copy-to-clipboard'; -import { resolve } from '@/router'; -import { url } from '@/config'; -import * as symbols from '@/symbols'; - -export default defineComponent({ - components: { - }, - - provide() { - return { - navHook: (path) => { - this.navigate(path); - } - }; - }, - - data() { - return { - path: null, - component: null, - props: {}, - pageInfo: null, - history: [], - }; - }, - - computed: { - url(): string { - return url + this.path; - } - }, - - methods: { - changePage(page) { - if (page == null) return; - if (page[symbols.PAGE_INFO]) { - this.pageInfo = page[symbols.PAGE_INFO]; - } - }, - - navigate(path, record = true) { - if (record && this.path) this.history.push(this.path); - this.path = path; - const { component, props } = resolve(path); - this.component = component; - this.props = props; - this.$emit('open'); - }, - - back() { - this.navigate(this.history.pop(), false); - }, - - close() { - this.path = null; - this.component = null; - this.props = {}; - this.$emit('close'); - }, - - onContextmenu(e) { - os.contextMenu([{ - type: 'label', - text: this.path, - }, { - icon: 'fas fa-expand-alt', - text: this.$ts.showInPage, - action: () => { - this.$router.push(this.path); - this.close(); - } - }, { - icon: 'fas fa-window-maximize', - text: this.$ts.openInWindow, - action: () => { - os.pageWindow(this.path); - this.close(); - } - }, null, { - icon: 'fas fa-external-link-alt', - text: this.$ts.openInNewTab, - action: () => { - window.open(this.url, '_blank'); - this.close(); - } - }, { - icon: 'fas fa-link', - text: this.$ts.copyLink, - action: () => { - copyToClipboard(this.url); - } - }], e); - } - } -}); -</script> - -<style lang="scss" scoped> -.mrajymqm { - $header-height: 54px; // TODO: どこかに集約したい - - --root-margin: 16px; - --margin: var(--marginHalf); - - height: 100%; - overflow: auto; - box-sizing: border-box; - - > .header { - display: flex; - position: sticky; - z-index: 1000; - top: 0; - height: $header-height; - width: 100%; - font-weight: bold; - //background-color: var(--panel); - -webkit-backdrop-filter: var(--blur, blur(32px)); - backdrop-filter: var(--blur, blur(32px)); - background-color: var(--header); - border-bottom: solid 0.5px var(--divider); - box-sizing: border-box; - - > ._button { - height: $header-height; - width: $header-height; - - &:hover { - color: var(--fgHighlighted); - } - } - - > .title { - flex: 1; - position: relative; - } - } - - > .body { - - } -} -</style> - diff --git a/packages/client/src/ui/chat/store.ts b/packages/client/src/ui/chat/store.ts deleted file mode 100644 index 389d56afb6..0000000000 --- a/packages/client/src/ui/chat/store.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { markRaw } from 'vue'; -import { Storage } from '../../pizzax'; - -export const store = markRaw(new Storage('chatUi', { - widgets: { - where: 'account', - default: [] as { - name: string; - id: string; - data: Record<string, any>; - }[] - }, - tl: { - where: 'deviceAccount', - default: 'home' - }, -})); diff --git a/packages/client/src/ui/chat/sub-note-content.vue b/packages/client/src/ui/chat/sub-note-content.vue deleted file mode 100644 index a85096ebc9..0000000000 --- a/packages/client/src/ui/chat/sub-note-content.vue +++ /dev/null @@ -1,62 +0,0 @@ -<template> -<div class="wrmlmaau"> - <div class="body"> - <span v-if="note.isHidden" style="opacity: 0.5">({{ $ts.private }})</span> - <span v-if="note.deletedAt" style="opacity: 0.5">({{ $ts.deleted }})</span> - <MkA v-if="note.replyId" class="reply" :to="`/notes/${note.replyId}`"><i class="fas fa-reply"></i></MkA> - <Mfm v-if="note.text" :text="note.text" :author="note.user" :i="$i" :custom-emojis="note.emojis"/> - <MkA v-if="note.renoteId" class="rp" :to="`/notes/${note.renoteId}`">RN: ...</MkA> - </div> - <details v-if="note.files.length > 0"> - <summary>({{ $t('withNFiles', { n: note.files.length }) }})</summary> - <XMediaList :media-list="note.files"/> - </details> - <details v-if="note.poll"> - <summary>{{ $ts.poll }}</summary> - <XPoll :note="note"/> - </details> -</div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import XPoll from '@/components/poll.vue'; -import XMediaList from '@/components/media-list.vue'; -import * as os from '@/os'; - -export default defineComponent({ - components: { - XPoll, - XMediaList, - }, - props: { - note: { - type: Object, - required: true - } - }, - data() { - return { - }; - } -}); -</script> - -<style lang="scss" scoped> -.wrmlmaau { - overflow-wrap: break-word; - - > .body { - > .reply { - margin-right: 6px; - color: var(--accent); - } - - > .rp { - margin-left: 4px; - font-style: oblique; - color: var(--renote); - } - } -} -</style> diff --git a/packages/client/src/ui/chat/widgets.vue b/packages/client/src/ui/chat/widgets.vue deleted file mode 100644 index 337d5a7b58..0000000000 --- a/packages/client/src/ui/chat/widgets.vue +++ /dev/null @@ -1,62 +0,0 @@ -<template> -<div class="qydbhufi"> - <XWidgets :edit="edit" :widgets="widgets" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="edit = false"/> - - <button v-if="edit" class="_textButton" style="font-size: 0.9em;" @click="edit = false">{{ $ts.editWidgetsExit }}</button> - <button v-else class="_textButton" style="font-size: 0.9em;" @click="edit = true">{{ $ts.editWidgets }}</button> -</div> -</template> - -<script lang="ts"> -import { defineComponent, defineAsyncComponent } from 'vue'; -import XWidgets from '@/components/widgets.vue'; -import { store } from './store'; - -export default defineComponent({ - components: { - XWidgets, - }, - - data() { - return { - edit: false, - widgets: store.reactiveState.widgets - }; - }, - - methods: { - addWidget(widget) { - store.set('widgets', [widget, ...store.state.widgets]); - }, - - removeWidget(widget) { - store.set('widgets', store.state.widgets.filter(w => w.id != widget.id)); - }, - - updateWidget({ id, data }) { - // TODO: throttleしたい - store.set('widgets', store.state.widgets.map(w => w.id === id ? { - ...w, - data: data - } : w)); - }, - - updateWidgets(widgets) { - store.set('widgets', widgets); - } - } -}); -</script> - -<style lang="scss" scoped> -.qydbhufi { - height: 100%; - box-sizing: border-box; - overflow: auto; - padding: var(--margin); - - ::v-deep(._panel) { - box-shadow: none; - } -} -</style> diff --git a/packages/client/src/ui/classic.header.vue b/packages/client/src/ui/classic.header.vue index 3563e8a888..699b992668 100644 --- a/packages/client/src/ui/classic.header.vue +++ b/packages/client/src/ui/classic.header.vue @@ -105,7 +105,11 @@ export default defineComponent({ }, 'closed'); }, - openAccountMenu, + openAccountMenu:(ev) => { + openAccountMenu({ + withExtraOperation: true, + }, ev); + }, } }); </script> diff --git a/packages/client/src/ui/classic.side.vue b/packages/client/src/ui/classic.side.vue index ede658626a..f816834141 100644 --- a/packages/client/src/ui/classic.side.vue +++ b/packages/client/src/ui/classic.side.vue @@ -72,7 +72,7 @@ export default defineComponent({ this.props = {}; }, - onContextmenu(e) { + onContextmenu(ev: MouseEvent) { os.contextMenu([{ type: 'label', text: this.path, @@ -103,7 +103,7 @@ export default defineComponent({ action: () => { copyToClipboard(this.url); } - }], e); + }], ev); } } }); diff --git a/packages/client/src/ui/classic.sidebar.vue b/packages/client/src/ui/classic.sidebar.vue index cc9d7a9b48..afbca06c8e 100644 --- a/packages/client/src/ui/classic.sidebar.vue +++ b/packages/client/src/ui/classic.sidebar.vue @@ -125,7 +125,11 @@ export default defineComponent({ }, 'closed'); }, - openAccountMenu, + openAccountMenu:(ev) => { + openAccountMenu({ + withExtraOperation: true, + }, ev); + }, } }); </script> diff --git a/packages/client/src/ui/classic.vue b/packages/client/src/ui/classic.vue index 41da973152..c61cbc433e 100644 --- a/packages/client/src/ui/classic.vue +++ b/packages/client/src/ui/classic.vue @@ -16,7 +16,7 @@ <template #header><MkHeader v-if="pageInfo && !pageInfo.hideHeader" :info="pageInfo"/></template> <router-view v-slot="{ Component }"> <transition :name="$store.state.animation ? 'page' : ''" mode="out-in" @enter="onTransition"> - <keep-alive :include="['timeline']"> + <keep-alive :include="['MkTimelinePage']"> <component :is="Component" :ref="changePage"/> </keep-alive> </transition> @@ -30,7 +30,7 @@ </div> </div> - <transition name="tray-back"> + <transition :name="$store.state.animation ? 'tray-back' : ''"> <div v-if="widgetsShowing" class="tray-back _modalBg" @click="widgetsShowing = false" @@ -38,7 +38,7 @@ ></div> </transition> - <transition name="tray"> + <transition :name="$store.state.animation ? 'tray' : ''"> <XWidgets v-if="widgetsShowing" class="tray"/> </transition> @@ -167,15 +167,15 @@ export default defineComponent({ if (window._scroll) window._scroll(); }, - onContextmenu(e) { + onContextmenu(ev: MouseEvent) { const isLink = (el: HTMLElement) => { if (el.tagName === 'A') return true; if (el.parentElement) { return isLink(el.parentElement); } }; - if (isLink(e.target)) return; - if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(e.target.tagName) || e.target.attributes['contenteditable']) return; + if (isLink(ev.target)) return; + if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(ev.target.tagName) || ev.target.attributes['contenteditable']) return; if (window.getSelection().toString() !== '') return; const path = this.$route.path; os.contextMenu([{ @@ -193,7 +193,7 @@ export default defineComponent({ action: () => { os.pageWindow(path); } - }], e); + }], ev); }, onAiClick(ev) { diff --git a/packages/client/src/ui/deck.vue b/packages/client/src/ui/deck.vue index 73dc83180f..51a4853e9d 100644 --- a/packages/client/src/ui/deck.vue +++ b/packages/client/src/ui/deck.vue @@ -29,7 +29,7 @@ <button class="button post _button" @click="post()"><i class="fas fa-pencil-alt"></i></button> </div> - <transition name="menu-back"> + <transition :name="$store.state.animation ? 'menu-back' : ''"> <div v-if="drawerMenuShowing" class="menu-back _modalBg" @click="drawerMenuShowing = false" @@ -37,7 +37,7 @@ ></div> </transition> - <transition name="menu"> + <transition :name="$store.state.animation ? 'menu' : ''"> <XDrawerMenu v-if="drawerMenuShowing" class="menu"/> </transition> diff --git a/packages/client/src/ui/deck/column.vue b/packages/client/src/ui/deck/column.vue index d3c7cf8213..f1ce3ca838 100644 --- a/packages/client/src/ui/deck/column.vue +++ b/packages/client/src/ui/deck/column.vue @@ -207,8 +207,8 @@ export default defineComponent({ return items; }, - onContextmenu(e) { - os.contextMenu(this.getMenu(), e); + onContextmenu(ev: MouseEvent) { + os.contextMenu(this.getMenu(), ev); }, goTop() { @@ -224,7 +224,7 @@ export default defineComponent({ // Chromeのバグで、Dragstartハンドラ内ですぐにDOMを変更する(=リアクティブなプロパティを変更する)とDragが終了してしまう // SEE: https://stackoverflow.com/questions/19639969/html5-dragend-event-firing-immediately - setTimeout(() => { + window.setTimeout(() => { this.dragging = true; }, 10); }, diff --git a/packages/client/src/ui/deck/direct-column.vue b/packages/client/src/ui/deck/direct-column.vue index 6ef733dfd0..ca70f693c3 100644 --- a/packages/client/src/ui/deck/direct-column.vue +++ b/packages/client/src/ui/deck/direct-column.vue @@ -2,43 +2,26 @@ <XColumn :column="column" :is-stacked="isStacked"> <template #header><i class="fas fa-envelope" style="margin-right: 8px;"></i>{{ column.name }}</template> - <XNotes :pagination="pagination" @before="before()" @after="after()"/> + <XNotes :pagination="pagination"/> </XColumn> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; import XColumn from './column.vue'; import XNotes from '@/components/notes.vue'; import * as os from '@/os'; -export default defineComponent({ - components: { - XColumn, - XNotes - }, +const props = defineProps<{ + column: Record<string, unknown>; // TODO + isStacked: boolean; +}>(); - props: { - column: { - type: Object, - required: true - }, - isStacked: { - type: Boolean, - required: true - } - }, - - data() { - return { - pagination: { - endpoint: 'notes/mentions', - limit: 10, - params: () => ({ - visibility: 'specified' - }) - }, - } - }, -}); +const pagination = { + point: 'notes/mentions' as const, + limit: 10, + params: computed(() => ({ + visibility: 'specified' as const, + })), +}; </script> diff --git a/packages/client/src/ui/deck/main-column.vue b/packages/client/src/ui/deck/main-column.vue index 744056881c..cb045e9a46 100644 --- a/packages/client/src/ui/deck/main-column.vue +++ b/packages/client/src/ui/deck/main-column.vue @@ -11,7 +11,7 @@ <template #header><MkHeader v-if="pageInfo && !pageInfo.hideHeader" :info="pageInfo"/></template> <router-view v-slot="{ Component }"> <transition> - <keep-alive :include="['timeline']"> + <keep-alive :include="['MkTimelinePage']"> <component :is="Component" :ref="changePage" @contextmenu.stop="onContextmenu"/> </keep-alive> </transition> @@ -64,15 +64,15 @@ export default defineComponent({ history.back(); }, - onContextmenu(e) { + onContextmenu(ev: MouseEvent) { const isLink = (el: HTMLElement) => { if (el.tagName === 'A') return true; if (el.parentElement) { return isLink(el.parentElement); } }; - if (isLink(e.target)) return; - if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(e.target.tagName) || e.target.attributes['contenteditable']) return; + if (isLink(ev.target)) return; + if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(ev.target.tagName) || ev.target.attributes['contenteditable']) return; if (window.getSelection().toString() !== '') return; const path = this.$route.path; os.contextMenu([{ @@ -84,7 +84,7 @@ export default defineComponent({ action: () => { os.pageWindow(path); } - }], e); + }], ev); }, } }); diff --git a/packages/client/src/ui/deck/mentions-column.vue b/packages/client/src/ui/deck/mentions-column.vue index 4b8dc0c4ee..6822e7ef06 100644 --- a/packages/client/src/ui/deck/mentions-column.vue +++ b/packages/client/src/ui/deck/mentions-column.vue @@ -2,40 +2,23 @@ <XColumn :column="column" :is-stacked="isStacked"> <template #header><i class="fas fa-at" style="margin-right: 8px;"></i>{{ column.name }}</template> - <XNotes :pagination="pagination" @before="before()" @after="after()"/> + <XNotes :pagination="pagination"/> </XColumn> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import XColumn from './column.vue'; import XNotes from '@/components/notes.vue'; import * as os from '@/os'; -export default defineComponent({ - components: { - XColumn, - XNotes - }, +const props = defineProps<{ + column: Record<string, unknown>; // TODO + isStacked: boolean; +}>(); - props: { - column: { - type: Object, - required: true - }, - isStacked: { - type: Boolean, - required: true - } - }, - - data() { - return { - pagination: { - endpoint: 'notes/mentions', - limit: 10, - }, - } - }, -}); +const pagination = { + endpoint: 'notes/mentions' as const, + limit: 10, +}; </script> diff --git a/packages/client/src/ui/universal.vue b/packages/client/src/ui/universal.vue index 9fc2177ee0..16cc9a4f06 100644 --- a/packages/client/src/ui/universal.vue +++ b/packages/client/src/ui/universal.vue @@ -9,7 +9,7 @@ <template #header><MkHeader v-if="pageInfo && !pageInfo.hideHeader" :info="pageInfo"/></template> <router-view v-slot="{ Component }"> <transition :name="$store.state.animation ? 'page' : ''" mode="out-in" @enter="onTransition"> - <keep-alive :include="['timeline']"> + <keep-alive :include="['MkTimelinePage']"> <component :is="Component" :ref="changePage"/> </keep-alive> </transition> @@ -36,7 +36,7 @@ <button class="button post _button" @click="post()"><i class="fas fa-pencil-alt"></i></button> </div> - <transition name="menuDrawer-back"> + <transition :name="$store.state.animation ? 'menuDrawer-back' : ''"> <div v-if="drawerMenuShowing" class="menuDrawer-back _modalBg" @click="drawerMenuShowing = false" @@ -44,11 +44,11 @@ ></div> </transition> - <transition name="menuDrawer"> + <transition :name="$store.state.animation ? 'menuDrawer' : ''"> <XDrawerMenu v-if="drawerMenuShowing" class="menuDrawer"/> </transition> - <transition name="widgetsDrawer-back"> + <transition :name="$store.state.animation ? 'widgetsDrawer-back' : ''"> <div v-if="widgetsShowing" class="widgetsDrawer-back _modalBg" @click="widgetsShowing = false" @@ -56,7 +56,7 @@ ></div> </transition> - <transition name="widgetsDrawer"> + <transition :name="$store.state.animation ? 'widgetsDrawer' : ''"> <XWidgets v-if="widgetsShowing" class="widgetsDrawer"/> </transition> diff --git a/packages/client/src/ui/visitor/b.vue b/packages/client/src/ui/visitor/b.vue index f6ad13d9a0..c9c0a1f72e 100644 --- a/packages/client/src/ui/visitor/b.vue +++ b/packages/client/src/ui/visitor/b.vue @@ -25,7 +25,7 @@ </div> </div> - <transition name="tray-back"> + <transition :name="$store.state.animation ? 'tray-back' : ''"> <div v-if="showMenu" class="menu-back _modalBg" @click="showMenu = false" @@ -33,7 +33,7 @@ ></div> </transition> - <transition name="tray"> + <transition :name="$store.state.animation ? 'tray' : ''"> <div v-if="showMenu" class="menu"> <MkA to="/" class="link" active-class="active"><i class="fas fa-home icon"></i>{{ $ts.home }}</MkA> <MkA to="/explore" class="link" active-class="active"><i class="fas fa-hashtag icon"></i>{{ $ts.explore }}</MkA> diff --git a/packages/client/src/ui/zen.vue b/packages/client/src/ui/zen.vue index 7c72232cfd..a7234f729b 100644 --- a/packages/client/src/ui/zen.vue +++ b/packages/client/src/ui/zen.vue @@ -8,7 +8,7 @@ <div class="content"> <router-view v-slot="{ Component }"> <transition :name="$store.state.animation ? 'page' : ''" mode="out-in" @enter="onTransition"> - <keep-alive :include="['timeline']"> + <keep-alive :include="['MkTimelinePage']"> <component :is="Component" :ref="changePage"/> </keep-alive> </transition> diff --git a/packages/client/src/widgets/activity.vue b/packages/client/src/widgets/activity.vue index d322f4758a..acbbb7a97a 100644 --- a/packages/client/src/widgets/activity.vue +++ b/packages/client/src/widgets/activity.vue @@ -1,82 +1,89 @@ <template> -<MkContainer :show-header="props.showHeader" :naked="props.transparent"> +<MkContainer :show-header="widgetProps.showHeader" :naked="widgetProps.transparent"> <template #header><i class="fas fa-chart-bar"></i>{{ $ts._widgets.activity }}</template> <template #func><button class="_button" @click="toggleView()"><i class="fas fa-sort"></i></button></template> <div> <MkLoading v-if="fetching"/> <template v-else> - <XCalendar v-show="props.view === 0" :data="[].concat(activity)"/> - <XChart v-show="props.view === 1" :data="[].concat(activity)"/> + <XCalendar v-show="widgetProps.view === 0" :data="[].concat(activity)"/> + <XChart v-show="widgetProps.view === 1" :data="[].concat(activity)"/> </template> </div> </MkContainer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted, onUnmounted, reactive, ref, watch } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import * as os from '@/os'; import MkContainer from '@/components/ui/container.vue'; -import define from './define'; import XCalendar from './activity.calendar.vue'; import XChart from './activity.chart.vue'; -import * as os from '@/os'; +import { $i } from '@/account'; -const widget = define({ - name: 'activity', - props: () => ({ - showHeader: { - type: 'boolean', - default: true, - }, - transparent: { - type: 'boolean', - default: false, - }, - view: { - type: 'number', - default: 0, - hidden: true, - }, - }) -}); +const name = 'activity'; -export default defineComponent({ - components: { - MkContainer, - XCalendar, - XChart, +const widgetPropsDef = { + showHeader: { + type: 'boolean' as const, + default: true, }, - extends: widget, - data() { - return { - fetching: true, - activity: null, - }; + transparent: { + type: 'boolean' as const, + default: false, }, - mounted() { - os.api('charts/user/notes', { - userId: this.$i.id, - span: 'day', - limit: 7 * 21 - }).then(activity => { - this.activity = activity.diffs.normal.map((_, i) => ({ - total: activity.diffs.normal[i] + activity.diffs.reply[i] + activity.diffs.renote[i], - notes: activity.diffs.normal[i], - replies: activity.diffs.reply[i], - renotes: activity.diffs.renote[i] - })); - this.fetching = false; - }); + view: { + type: 'number' as const, + default: 0, + hidden: true, }, - methods: { - toggleView() { - if (this.props.view === 1) { - this.props.view = 0; - } else { - this.props.view++; - } - this.save(); - } +}; + +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; + +// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure, save } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const activity = ref(null); +const fetching = ref(true); + +const toggleView = () => { + if (widgetProps.view === 1) { + widgetProps.view = 0; + } else { + widgetProps.view++; } + save(); +}; + +os.api('charts/user/notes', { + userId: $i.id, + span: 'day', + limit: 7 * 21, +}).then(res => { + activity.value = res.diffs.normal.map((_, i) => ({ + total: res.diffs.normal[i] + res.diffs.reply[i] + res.diffs.renote[i], + notes: res.diffs.normal[i], + replies: res.diffs.reply[i], + renotes: res.diffs.renote[i] + })); + fetching.value = false; +}); + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/aichan.vue b/packages/client/src/widgets/aichan.vue index 891b7454d1..03e394b976 100644 --- a/packages/client/src/widgets/aichan.vue +++ b/packages/client/src/widgets/aichan.vue @@ -1,51 +1,65 @@ <template> -<MkContainer :naked="props.transparent" :show-header="false"> +<MkContainer :naked="widgetProps.transparent" :show-header="false"> <iframe ref="live2d" class="dedjhjmo" src="https://misskey-dev.github.io/mascot-web/?scale=1.5&y=1.1&eyeY=100" @click="touched"></iframe> </MkContainer> </template> -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; -import define from './define'; -import MkContainer from '@/components/ui/container.vue'; -import * as os from '@/os'; +<script lang="ts" setup> +import { onMounted, onUnmounted, reactive, ref } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; -const widget = define({ - name: 'ai', - props: () => ({ - transparent: { - type: 'boolean', - default: false, - }, - }) -}); +const name = 'ai'; -export default defineComponent({ - components: { - MkContainer, - }, - extends: widget, - data() { - return { - }; - }, - mounted() { - window.addEventListener('mousemove', ev => { - const iframeRect = this.$refs.live2d.getBoundingClientRect(); - this.$refs.live2d.contentWindow.postMessage({ - type: 'moveCursor', - body: { - x: ev.clientX - iframeRect.left, - y: ev.clientY - iframeRect.top, - } - }, '*'); - }, { passive: true }); +const widgetPropsDef = { + transparent: { + type: 'boolean' as const, + default: false, }, - methods: { - touched() { - //if (this.live2d) this.live2d.changeExpression('gurugurume'); - } - } +}; + +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; + +// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const live2d = ref<HTMLIFrameElement>(); + +const touched = () => { + //if (this.live2d) this.live2d.changeExpression('gurugurume'); +}; + +onMounted(() => { + const onMousemove = (ev: MouseEvent) => { + const iframeRect = live2d.value.getBoundingClientRect(); + live2d.value.contentWindow.postMessage({ + type: 'moveCursor', + body: { + x: ev.clientX - iframeRect.left, + y: ev.clientY - iframeRect.top, + } + }, '*'); + }; + + window.addEventListener('mousemove', onMousemove, { passive: true }); + onUnmounted(() => { + window.removeEventListener('mousemove', onMousemove); + }); +}); + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/aiscript.vue b/packages/client/src/widgets/aiscript.vue index 46c5094ee9..0a5c0d614d 100644 --- a/packages/client/src/widgets/aiscript.vue +++ b/packages/client/src/widgets/aiscript.vue @@ -1,9 +1,9 @@ <template> -<MkContainer :show-header="props.showHeader"> +<MkContainer :show-header="widgetProps.showHeader"> <template #header><i class="fas fa-terminal"></i>{{ $ts._widgets.aiscript }}</template> <div class="uylguesu _monospace"> - <textarea v-model="props.script" placeholder="(1 + 1)"></textarea> + <textarea v-model="widgetProps.script" placeholder="(1 + 1)"></textarea> <button class="_buttonPrimary" @click="run">RUN</button> <div class="logs"> <div v-for="log in logs" :key="log.id" class="log" :class="{ print: log.print }">{{ log.text }}</div> @@ -12,97 +12,109 @@ </MkContainer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import MkContainer from '@/components/ui/container.vue'; -import define from './define'; +<script lang="ts" setup> +import { onMounted, onUnmounted, ref, watch } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import * as os from '@/os'; +import MkContainer from '@/components/ui/container.vue'; import { AiScript, parse, utils } from '@syuilo/aiscript'; import { createAiScriptEnv } from '@/scripts/aiscript/api'; +import { $i } from '@/account'; -const widget = define({ - name: 'aiscript', - props: () => ({ - showHeader: { - type: 'boolean', - default: true, - }, - script: { - type: 'string', - multiline: true, - default: '(1 + 1)', - hidden: true, - }, - }) -}); +const name = 'aiscript'; -export default defineComponent({ - components: { - MkContainer +const widgetPropsDef = { + showHeader: { + type: 'boolean' as const, + default: true, }, - extends: widget, - - data() { - return { - logs: [], - }; + script: { + type: 'string' as const, + multiline: true, + default: '(1 + 1)', + hidden: true, }, +}; - methods: { - async run() { - this.logs = []; - const aiscript = new AiScript(createAiScriptEnv({ - storageKey: 'widget', - token: this.$i?.token, - }), { - in: (q) => { - return new Promise(ok => { - os.inputText({ - title: q, - }).then(({ canceled, result: a }) => { - ok(a); - }); - }); - }, - out: (value) => { - this.logs.push({ - id: Math.random(), - text: value.type === 'str' ? value.value : utils.valToString(value), - print: true - }); - }, - log: (type, params) => { - switch (type) { - case 'end': this.logs.push({ - id: Math.random(), - text: utils.valToString(params.val, true), - print: false - }); break; - default: break; - } - } - }); +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; - let ast; - try { - ast = parse(this.props.script); - } catch (e) { - os.alert({ - type: 'error', - text: 'Syntax error :(' - }); - return; - } - try { - await aiscript.exec(ast); - } catch (e) { - os.alert({ - type: 'error', - text: e +// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const logs = ref<{ + id: string; + text: string; + print: boolean; +}[]>([]); + +const run = async () => { + logs.value = []; + const aiscript = new AiScript(createAiScriptEnv({ + storageKey: 'widget', + token: $i?.token, + }), { + in: (q) => { + return new Promise(ok => { + os.inputText({ + title: q, + }).then(({ canceled, result: a }) => { + ok(a); }); - } + }); + }, + out: (value) => { + logs.value.push({ + id: Math.random().toString(), + text: value.type === 'str' ? value.value : utils.valToString(value), + print: true, + }); }, + log: (type, params) => { + switch (type) { + case 'end': logs.value.push({ + id: Math.random().toString(), + text: utils.valToString(params.val, true), + print: false, + }); break; + default: break; + } + } + }); + + let ast; + try { + ast = parse(widgetProps.script); + } catch (e) { + os.alert({ + type: 'error', + text: 'Syntax error :(', + }); + return; } + try { + await aiscript.exec(ast); + } catch (e) { + os.alert({ + type: 'error', + text: e, + }); + } +}; + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/button.vue b/packages/client/src/widgets/button.vue index e98570862e..a33afd6e7a 100644 --- a/packages/client/src/widgets/button.vue +++ b/packages/client/src/widgets/button.vue @@ -1,90 +1,99 @@ <template> <div class="mkw-button"> - <MkButton :primary="props.colored" full @click="run"> - {{ props.label }} + <MkButton :primary="widgetProps.colored" full @click="run"> + {{ widgetProps.label }} </MkButton> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import define from './define'; -import MkButton from '@/components/ui/button.vue'; +<script lang="ts" setup> +import { onMounted, onUnmounted, ref, watch } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import * as os from '@/os'; import { AiScript, parse, utils } from '@syuilo/aiscript'; import { createAiScriptEnv } from '@/scripts/aiscript/api'; +import { $i } from '@/account'; +import MkButton from '@/components/ui/button.vue'; -const widget = define({ - name: 'button', - props: () => ({ - label: { - type: 'string', - default: 'BUTTON', - }, - colored: { - type: 'boolean', - default: true, - }, - script: { - type: 'string', - multiline: true, - default: 'Mk:dialog("hello" "world")', - }, - }) -}); +const name = 'button'; -export default defineComponent({ - components: { - MkButton +const widgetPropsDef = { + label: { + type: 'string' as const, + default: 'BUTTON', }, - extends: widget, - data() { - return { - }; + colored: { + type: 'boolean' as const, + default: true, }, - methods: { - async run() { - const aiscript = new AiScript(createAiScriptEnv({ - storageKey: 'widget', - token: this.$i?.token, - }), { - in: (q) => { - return new Promise(ok => { - os.inputText({ - title: q, - }).then(({ canceled, result: a }) => { - ok(a); - }); - }); - }, - out: (value) => { - // nop - }, - log: (type, params) => { - // nop - } - }); + script: { + type: 'string' as const, + multiline: true, + default: 'Mk:dialog("hello" "world")', + }, +}; - let ast; - try { - ast = parse(this.props.script); - } catch (e) { - os.alert({ - type: 'error', - text: 'Syntax error :(' - }); - return; - } - try { - await aiscript.exec(ast); - } catch (e) { - os.alert({ - type: 'error', - text: e +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; + +// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const run = async () => { + const aiscript = new AiScript(createAiScriptEnv({ + storageKey: 'widget', + token: $i?.token, + }), { + in: (q) => { + return new Promise(ok => { + os.inputText({ + title: q, + }).then(({ canceled, result: a }) => { + ok(a); }); - } + }); }, + out: (value) => { + // nop + }, + log: (type, params) => { + // nop + } + }); + + let ast; + try { + ast = parse(widgetProps.script); + } catch (e) { + os.alert({ + type: 'error', + text: 'Syntax error :(', + }); + return; } + try { + await aiscript.exec(ast); + } catch (e) { + os.alert({ + type: 'error', + text: e, + }); + } +}; + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/calendar.vue b/packages/client/src/widgets/calendar.vue index c8b52d7afc..b0e3edcb12 100644 --- a/packages/client/src/widgets/calendar.vue +++ b/packages/client/src/widgets/calendar.vue @@ -1,5 +1,5 @@ <template> -<div class="mkw-calendar" :class="{ _panel: !props.transparent }"> +<div class="mkw-calendar" :class="{ _panel: !widgetProps.transparent }"> <div class="calendar" :class="{ isHoliday }"> <p class="month-and-year"> <span class="year">{{ $t('yearX', { year }) }}</span> @@ -32,77 +32,87 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import define from './define'; +<script lang="ts" setup> +import { onUnmounted, ref } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { i18n } from '@/i18n'; -const widget = define({ - name: 'calendar', - props: () => ({ - transparent: { - type: 'boolean', - default: false, - }, - }) -}); +const name = 'calendar'; -export default defineComponent({ - extends: widget, - data() { - return { - now: new Date(), - year: null, - month: null, - day: null, - weekDay: null, - yearP: null, - dayP: null, - monthP: null, - isHoliday: null, - clock: null - }; - }, - created() { - this.tick(); - this.clock = setInterval(this.tick, 1000); +const widgetPropsDef = { + transparent: { + type: 'boolean' as const, + default: false, }, - beforeUnmount() { - clearInterval(this.clock); - }, - methods: { - tick() { - const now = new Date(); - const nd = now.getDate(); - const nm = now.getMonth(); - const ny = now.getFullYear(); +}; - this.year = ny; - this.month = nm + 1; - this.day = nd; - this.weekDay = [ - this.$ts._weekday.sunday, - this.$ts._weekday.monday, - this.$ts._weekday.tuesday, - this.$ts._weekday.wednesday, - this.$ts._weekday.thursday, - this.$ts._weekday.friday, - this.$ts._weekday.saturday - ][now.getDay()]; +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; - const dayNumer = now.getTime() - new Date(ny, nm, nd).getTime(); - const dayDenom = 1000/*ms*/ * 60/*s*/ * 60/*m*/ * 24/*h*/; - const monthNumer = now.getTime() - new Date(ny, nm, 1).getTime(); - const monthDenom = new Date(ny, nm + 1, 1).getTime() - new Date(ny, nm, 1).getTime(); - const yearNumer = now.getTime() - new Date(ny, 0, 1).getTime(); - const yearDenom = new Date(ny + 1, 0, 1).getTime() - new Date(ny, 0, 1).getTime(); +// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); - this.dayP = dayNumer / dayDenom * 100; - this.monthP = monthNumer / monthDenom * 100; - this.yearP = yearNumer / yearDenom * 100; +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); - this.isHoliday = now.getDay() === 0 || now.getDay() === 6; - } - } +const year = ref(0); +const month = ref(0); +const day = ref(0); +const weekDay = ref(''); +const yearP = ref(0); +const monthP = ref(0); +const dayP = ref(0); +const isHoliday = ref(false); +const tick = () => { + const now = new Date(); + const nd = now.getDate(); + const nm = now.getMonth(); + const ny = now.getFullYear(); + + year.value = ny; + month.value = nm + 1; + day.value = nd; + weekDay.value = [ + i18n.locale._weekday.sunday, + i18n.locale._weekday.monday, + i18n.locale._weekday.tuesday, + i18n.locale._weekday.wednesday, + i18n.locale._weekday.thursday, + i18n.locale._weekday.friday, + i18n.locale._weekday.saturday + ][now.getDay()]; + + const dayNumer = now.getTime() - new Date(ny, nm, nd).getTime(); + const dayDenom = 1000/*ms*/ * 60/*s*/ * 60/*m*/ * 24/*h*/; + const monthNumer = now.getTime() - new Date(ny, nm, 1).getTime(); + const monthDenom = new Date(ny, nm + 1, 1).getTime() - new Date(ny, nm, 1).getTime(); + const yearNumer = now.getTime() - new Date(ny, 0, 1).getTime(); + const yearDenom = new Date(ny + 1, 0, 1).getTime() - new Date(ny, 0, 1).getTime(); + + dayP.value = dayNumer / dayDenom * 100; + monthP.value = monthNumer / monthDenom * 100; + yearP.value = yearNumer / yearDenom * 100; + + isHoliday.value = now.getDay() === 0 || now.getDay() === 6; +}; + +tick(); + +const intervalId = window.setInterval(tick, 1000); +onUnmounted(() => { + window.clearInterval(intervalId); +}); + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/clock.vue b/packages/client/src/widgets/clock.vue index 6ca7ecd430..6acb10d74d 100644 --- a/packages/client/src/widgets/clock.vue +++ b/packages/client/src/widgets/clock.vue @@ -1,45 +1,56 @@ <template> -<MkContainer :naked="props.transparent" :show-header="false"> +<MkContainer :naked="widgetProps.transparent" :show-header="false"> <div class="vubelbmv"> - <MkAnalogClock class="clock" :thickness="props.thickness"/> + <MkAnalogClock class="clock" :thickness="widgetProps.thickness"/> </div> </MkContainer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import define from './define'; +<script lang="ts" setup> +import { } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import MkContainer from '@/components/ui/container.vue'; import MkAnalogClock from '@/components/analog-clock.vue'; -import * as os from '@/os'; -const widget = define({ - name: 'clock', - props: () => ({ - transparent: { - type: 'boolean', - default: false, - }, - thickness: { - type: 'radio', - default: 0.1, - options: [{ - value: 0.1, label: 'thin' - }, { - value: 0.2, label: 'medium' - }, { - value: 0.3, label: 'thick' - }] - } - }) -}); +const name = 'clock'; -export default defineComponent({ - components: { - MkContainer, - MkAnalogClock +const widgetPropsDef = { + transparent: { + type: 'boolean' as const, + default: false, + }, + thickness: { + type: 'radio' as const, + default: 0.1, + options: [{ + value: 0.1, label: 'thin' + }, { + value: 0.2, label: 'medium' + }, { + value: 0.3, label: 'thick' + }], }, - extends: widget, +}; + +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; + +// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/define.ts b/packages/client/src/widgets/define.ts deleted file mode 100644 index 08a346d97c..0000000000 --- a/packages/client/src/widgets/define.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { defineComponent } from 'vue'; -import { throttle } from 'throttle-debounce'; -import { Form } from '@/scripts/form'; -import * as os from '@/os'; - -export default function <T extends Form>(data: { - name: string; - props?: () => T; -}) { - return defineComponent({ - props: { - widget: { - type: Object, - required: false - }, - settingCallback: { - required: false - } - }, - - emits: ['updateProps'], - - data() { - return { - props: this.widget ? JSON.parse(JSON.stringify(this.widget.data)) : {}, - save: throttle(3000, () => { - this.$emit('updateProps', this.props); - }), - }; - }, - - computed: { - id(): string { - return this.widget ? this.widget.id : null; - }, - }, - - created() { - this.mergeProps(); - - this.$watch('props', () => { - this.mergeProps(); - }, { deep: true }); - - if (this.settingCallback) this.settingCallback(this.setting); - }, - - methods: { - mergeProps() { - if (data.props) { - const defaultProps = data.props(); - for (const prop of Object.keys(defaultProps)) { - if (this.props.hasOwnProperty(prop)) continue; - this.props[prop] = defaultProps[prop].default; - } - } - }, - - async setting() { - const form = data.props(); - for (const item of Object.keys(form)) { - form[item].default = this.props[item]; - } - const { canceled, result } = await os.form(data.name, form); - if (canceled) return; - - for (const key of Object.keys(result)) { - this.props[key] = result[key]; - } - - this.save(); - }, - } - }); -} diff --git a/packages/client/src/widgets/digital-clock.vue b/packages/client/src/widgets/digital-clock.vue index fbf632d2de..62f052a692 100644 --- a/packages/client/src/widgets/digital-clock.vue +++ b/packages/client/src/widgets/digital-clock.vue @@ -1,73 +1,84 @@ <template> -<div class="mkw-digitalClock _monospace" :class="{ _panel: !props.transparent }" :style="{ fontSize: `${props.fontSize}em` }"> +<div class="mkw-digitalClock _monospace" :class="{ _panel: !widgetProps.transparent }" :style="{ fontSize: `${widgetProps.fontSize}em` }"> <span> <span v-text="hh"></span> <span :style="{ visibility: showColon ? 'visible' : 'hidden' }">:</span> <span v-text="mm"></span> <span :style="{ visibility: showColon ? 'visible' : 'hidden' }">:</span> <span v-text="ss"></span> - <span v-if="props.showMs" :style="{ visibility: showColon ? 'visible' : 'hidden' }">:</span> - <span v-if="props.showMs" v-text="ms"></span> + <span v-if="widgetProps.showMs" :style="{ visibility: showColon ? 'visible' : 'hidden' }">:</span> + <span v-if="widgetProps.showMs" v-text="ms"></span> </span> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import define from './define'; -import * as os from '@/os'; +<script lang="ts" setup> +import { onUnmounted, ref, watch } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; -const widget = define({ - name: 'digitalClock', - props: () => ({ - transparent: { - type: 'boolean', - default: false, - }, - fontSize: { - type: 'number', - default: 1.5, - step: 0.1, - }, - showMs: { - type: 'boolean', - default: true, - }, - }) -}); +const name = 'digitalClock'; -export default defineComponent({ - extends: widget, - data() { - return { - clock: null, - hh: null, - mm: null, - ss: null, - ms: null, - showColon: true, - }; +const widgetPropsDef = { + transparent: { + type: 'boolean' as const, + default: false, }, - created() { - this.tick(); - this.$watch(() => this.props.showMs, () => { - if (this.clock) clearInterval(this.clock); - this.clock = setInterval(this.tick, this.props.showMs ? 10 : 1000); - }, { immediate: true }); + fontSize: { + type: 'number' as const, + default: 1.5, + step: 0.1, }, - beforeUnmount() { - clearInterval(this.clock); + showMs: { + type: 'boolean' as const, + default: true, }, - methods: { - tick() { - const now = new Date(); - this.hh = now.getHours().toString().padStart(2, '0'); - this.mm = now.getMinutes().toString().padStart(2, '0'); - this.ss = now.getSeconds().toString().padStart(2, '0'); - this.ms = Math.floor(now.getMilliseconds() / 10).toString().padStart(2, '0'); - this.showColon = now.getSeconds() % 2 === 0; - } - } +}; + +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; + +// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +let intervalId; +const hh = ref(''); +const mm = ref(''); +const ss = ref(''); +const ms = ref(''); +const showColon = ref(true); +const tick = () => { + const now = new Date(); + hh.value = now.getHours().toString().padStart(2, '0'); + mm.value = now.getMinutes().toString().padStart(2, '0'); + ss.value = now.getSeconds().toString().padStart(2, '0'); + ms.value = Math.floor(now.getMilliseconds() / 10).toString().padStart(2, '0'); + showColon.value = now.getSeconds() % 2 === 0; +}; + +tick(); + +watch(() => widgetProps.showMs, () => { + if (intervalId) window.clearInterval(intervalId); + intervalId = window.setInterval(tick, widgetProps.showMs ? 10 : 1000); +}, { immediate: true }); + +onUnmounted(() => { + window.clearInterval(intervalId); +}); + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/federation.vue b/packages/client/src/widgets/federation.vue index 736a91c52e..4c43117e48 100644 --- a/packages/client/src/widgets/federation.vue +++ b/packages/client/src/widgets/federation.vue @@ -1,10 +1,10 @@ <template> -<MkContainer :show-header="props.showHeader" :foldable="foldable" :scrollable="scrollable"> +<MkContainer :show-header="widgetProps.showHeader" :foldable="foldable" :scrollable="scrollable"> <template #header><i class="fas fa-globe"></i>{{ $ts._widgets.federation }}</template> <div class="wbrkwalb"> <MkLoading v-if="fetching"/> - <transition-group v-else tag="div" name="chart" class="instances"> + <transition-group v-else tag="div" :name="$store.state.animation ? 'chart' : ''" class="instances"> <div v-for="(instance, i) in instances" :key="instance.id" class="instance"> <img v-if="instance.iconUrl" :src="instance.iconUrl" alt=""/> <div class="body"> @@ -18,66 +18,64 @@ </MkContainer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted, onUnmounted, ref } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import MkContainer from '@/components/ui/container.vue'; -import define from './define'; import MkMiniChart from '@/components/mini-chart.vue'; import * as os from '@/os'; -const widget = define({ - name: 'federation', - props: () => ({ - showHeader: { - type: 'boolean', - default: true, - }, - }) -}); +const name = 'federation'; -export default defineComponent({ - components: { - MkContainer, MkMiniChart - }, - extends: widget, - props: { - foldable: { - type: Boolean, - required: false, - default: false - }, - scrollable: { - type: Boolean, - required: false, - default: false - }, - }, - data() { - return { - instances: [], - charts: [], - fetching: true, - }; - }, - mounted() { - this.fetch(); - this.clock = setInterval(this.fetch, 1000 * 60); +const widgetPropsDef = { + showHeader: { + type: 'boolean' as const, + default: true, }, - beforeUnmount() { - clearInterval(this.clock); - }, - methods: { - async fetch() { - const instances = await os.api('federation/instances', { - sort: '+lastCommunicatedAt', - limit: 5 - }); - const charts = await Promise.all(instances.map(i => os.api('charts/instance', { host: i.host, limit: 16, span: 'hour' }))); - this.instances = instances; - this.charts = charts; - this.fetching = false; - } - } +}; + +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; + +// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない +//const props = defineProps<WidgetComponentProps<WidgetProps> & { foldable?: boolean; scrollable?: boolean; }>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; foldable?: boolean; scrollable?: boolean; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const instances = ref([]); +const charts = ref([]); +const fetching = ref(true); + +const fetch = async () => { + const instances = await os.api('federation/instances', { + sort: '+lastCommunicatedAt', + limit: 5 + }); + const charts = await Promise.all(instances.map(i => os.api('charts/instance', { host: i.host, limit: 16, span: 'hour' }))); + instances.value = instances; + charts.value = charts; + fetching.value = false; +}; + +onMounted(() => { + fetch(); + const intervalId = window.setInterval(fetch, 1000 * 60); + onUnmounted(() => { + window.clearInterval(intervalId); + }); +}); + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/job-queue.vue b/packages/client/src/widgets/job-queue.vue index ef440881e5..4a2a3cf233 100644 --- a/packages/client/src/widgets/job-queue.vue +++ b/packages/client/src/widgets/job-queue.vue @@ -1,133 +1,146 @@ <template> -<div class="mkw-jobQueue _monospace" :class="{ _panel: !props.transparent }"> +<div class="mkw-jobQueue _monospace" :class="{ _panel: !widgetProps.transparent }"> <div class="inbox"> - <div class="label">Inbox queue<i v-if="inbox.waiting > 0" class="fas fa-exclamation-triangle icon"></i></div> + <div class="label">Inbox queue<i v-if="current.inbox.waiting > 0" class="fas fa-exclamation-triangle icon"></i></div> <div class="values"> <div> <div>Process</div> - <div :class="{ inc: inbox.activeSincePrevTick > prev.inbox.activeSincePrevTick, dec: inbox.activeSincePrevTick < prev.inbox.activeSincePrevTick }">{{ number(inbox.activeSincePrevTick) }}</div> + <div :class="{ inc: current.inbox.activeSincePrevTick > prev.inbox.activeSincePrevTick, dec: current.inbox.activeSincePrevTick < prev.inbox.activeSincePrevTick }">{{ number(current.inbox.activeSincePrevTick) }}</div> </div> <div> <div>Active</div> - <div :class="{ inc: inbox.active > prev.inbox.active, dec: inbox.active < prev.inbox.active }">{{ number(inbox.active) }}</div> + <div :class="{ inc: current.inbox.active > prev.inbox.active, dec: current.inbox.active < prev.inbox.active }">{{ number(current.inbox.active) }}</div> </div> <div> <div>Delayed</div> - <div :class="{ inc: inbox.delayed > prev.inbox.delayed, dec: inbox.delayed < prev.inbox.delayed }">{{ number(inbox.delayed) }}</div> + <div :class="{ inc: current.inbox.delayed > prev.inbox.delayed, dec: current.inbox.delayed < prev.inbox.delayed }">{{ number(current.inbox.delayed) }}</div> </div> <div> <div>Waiting</div> - <div :class="{ inc: inbox.waiting > prev.inbox.waiting, dec: inbox.waiting < prev.inbox.waiting }">{{ number(inbox.waiting) }}</div> + <div :class="{ inc: current.inbox.waiting > prev.inbox.waiting, dec: current.inbox.waiting < prev.inbox.waiting }">{{ number(current.inbox.waiting) }}</div> </div> </div> </div> <div class="deliver"> - <div class="label">Deliver queue<i v-if="deliver.waiting > 0" class="fas fa-exclamation-triangle icon"></i></div> + <div class="label">Deliver queue<i v-if="current.deliver.waiting > 0" class="fas fa-exclamation-triangle icon"></i></div> <div class="values"> <div> <div>Process</div> - <div :class="{ inc: deliver.activeSincePrevTick > prev.deliver.activeSincePrevTick, dec: deliver.activeSincePrevTick < prev.deliver.activeSincePrevTick }">{{ number(deliver.activeSincePrevTick) }}</div> + <div :class="{ inc: current.deliver.activeSincePrevTick > prev.deliver.activeSincePrevTick, dec: current.deliver.activeSincePrevTick < prev.deliver.activeSincePrevTick }">{{ number(current.deliver.activeSincePrevTick) }}</div> </div> <div> <div>Active</div> - <div :class="{ inc: deliver.active > prev.deliver.active, dec: deliver.active < prev.deliver.active }">{{ number(deliver.active) }}</div> + <div :class="{ inc: current.deliver.active > prev.deliver.active, dec: current.deliver.active < prev.deliver.active }">{{ number(current.deliver.active) }}</div> </div> <div> <div>Delayed</div> - <div :class="{ inc: deliver.delayed > prev.deliver.delayed, dec: deliver.delayed < prev.deliver.delayed }">{{ number(deliver.delayed) }}</div> + <div :class="{ inc: current.deliver.delayed > prev.deliver.delayed, dec: current.deliver.delayed < prev.deliver.delayed }">{{ number(current.deliver.delayed) }}</div> </div> <div> <div>Waiting</div> - <div :class="{ inc: deliver.waiting > prev.deliver.waiting, dec: deliver.waiting < prev.deliver.waiting }">{{ number(deliver.waiting) }}</div> + <div :class="{ inc: current.deliver.waiting > prev.deliver.waiting, dec: current.deliver.waiting < prev.deliver.waiting }">{{ number(current.deliver.waiting) }}</div> </div> </div> </div> </div> </template> -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; -import define from './define'; -import * as os from '@/os'; +<script lang="ts" setup> +import { onMounted, onUnmounted, reactive, ref } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { stream } from '@/stream'; import number from '@/filters/number'; import * as sound from '@/scripts/sound'; +import * as os from '@/os'; -const widget = define({ - name: 'jobQueue', - props: () => ({ - transparent: { - type: 'boolean', - default: false, - }, - sound: { - type: 'boolean', - default: false, - }, - }) -}); +const name = 'jobQueue'; -export default defineComponent({ - extends: widget, - data() { - return { - connection: markRaw(os.stream.useChannel('queueStats')), - inbox: { - activeSincePrevTick: 0, - active: 0, - waiting: 0, - delayed: 0, - }, - deliver: { - activeSincePrevTick: 0, - active: 0, - waiting: 0, - delayed: 0, - }, - prev: {}, - sound: sound.setVolume(sound.getAudio('syuilo/queue-jammed'), 1) - }; +const widgetPropsDef = { + transparent: { + type: 'boolean' as const, + default: false, }, - created() { - for (const domain of ['inbox', 'deliver']) { - this.prev[domain] = JSON.parse(JSON.stringify(this[domain])); - } - - this.connection.on('stats', this.onStats); - this.connection.on('statsLog', this.onStatsLog); + sound: { + type: 'boolean' as const, + default: false, + }, +}; + +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; - this.connection.send('requestLog', { - id: Math.random().toString().substr(2, 8), - length: 1 - }); +// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const connection = stream.useChannel('queueStats'); +const current = reactive({ + inbox: { + activeSincePrevTick: 0, + active: 0, + waiting: 0, + delayed: 0, }, - beforeUnmount() { - this.connection.off('stats', this.onStats); - this.connection.off('statsLog', this.onStatsLog); - this.connection.dispose(); + deliver: { + activeSincePrevTick: 0, + active: 0, + waiting: 0, + delayed: 0, }, - methods: { - onStats(stats) { - for (const domain of ['inbox', 'deliver']) { - this.prev[domain] = JSON.parse(JSON.stringify(this[domain])); - this[domain].activeSincePrevTick = stats[domain].activeSincePrevTick; - this[domain].active = stats[domain].active; - this[domain].waiting = stats[domain].waiting; - this[domain].delayed = stats[domain].delayed; +}); +const prev = reactive({} as typeof current); +const jammedSound = sound.setVolume(sound.getAudio('syuilo/queue-jammed'), 1); - if (this[domain].waiting > 0 && this.props.sound && this.sound.paused) { - this.sound.play(); - } - } - }, +for (const domain of ['inbox', 'deliver']) { + prev[domain] = JSON.parse(JSON.stringify(current[domain])); +} - onStatsLog(statsLog) { - for (const stats of [...statsLog].reverse()) { - this.onStats(stats); - } - }, +const onStats = (stats) => { + for (const domain of ['inbox', 'deliver']) { + prev[domain] = JSON.parse(JSON.stringify(current[domain])); + current[domain].activeSincePrevTick = stats[domain].activeSincePrevTick; + current[domain].active = stats[domain].active; + current[domain].waiting = stats[domain].waiting; + current[domain].delayed = stats[domain].delayed; + + if (current[domain].waiting > 0 && widgetProps.sound && jammedSound.paused) { + jammedSound.play(); + } + } +}; - number +const onStatsLog = (statsLog) => { + for (const stats of [...statsLog].reverse()) { + onStats(stats); } +}; + +connection.on('stats', onStats); +connection.on('statsLog', onStatsLog); + +connection.send('requestLog', { + id: Math.random().toString().substr(2, 8), + length: 1, +}); + +onUnmounted(() => { + connection.off('stats', onStats); + connection.off('statsLog', onStatsLog); + connection.dispose(); +}); + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/memo.vue b/packages/client/src/widgets/memo.vue index 9b51ada220..450598f65a 100644 --- a/packages/client/src/widgets/memo.vue +++ b/packages/client/src/widgets/memo.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :show-header="props.showHeader"> +<MkContainer :show-header="widgetProps.showHeader"> <template #header><i class="fas fa-sticky-note"></i>{{ $ts._widgets.memo }}</template> <div class="otgbylcu"> @@ -9,56 +9,60 @@ </MkContainer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import MkContainer from '@/components/ui/container.vue'; -import define from './define'; +<script lang="ts" setup> +import { onMounted, onUnmounted, reactive, ref, watch } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import * as os from '@/os'; +import MkContainer from '@/components/ui/container.vue'; +import { defaultStore } from '@/store'; -const widget = define({ - name: 'memo', - props: () => ({ - showHeader: { - type: 'boolean', - default: true, - }, - }) -}); +const name = 'memo'; -export default defineComponent({ - components: { - MkContainer +const widgetPropsDef = { + showHeader: { + type: 'boolean' as const, + default: true, }, - extends: widget, +}; - data() { - return { - text: null, - changed: false, - timeoutId: null, - }; - }, +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; - created() { - this.text = this.$store.state.memo; +// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); - this.$watch(() => this.$store.reactiveState.memo, text => { - this.text = text; - }); - }, +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); - methods: { - onChange() { - this.changed = true; - clearTimeout(this.timeoutId); - this.timeoutId = setTimeout(this.saveMemo, 1000); - }, +const text = ref<string | null>(defaultStore.state.memo); +const changed = ref(false); +let timeoutId; - saveMemo() { - this.$store.set('memo', this.text); - this.changed = false; - } - } +const saveMemo = () => { + defaultStore.set('memo', text.value); + changed.value = false; +}; + +const onChange = () => { + changed.value = true; + window.clearTimeout(timeoutId); + timeoutId = window.setTimeout(saveMemo, 1000); +}; + +watch(() => defaultStore.reactiveState.memo, newText => { + text.value = newText.value; +}); + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/notifications.vue b/packages/client/src/widgets/notifications.vue index 568705b661..8cf29c9271 100644 --- a/packages/client/src/widgets/notifications.vue +++ b/packages/client/src/widgets/notifications.vue @@ -1,65 +1,68 @@ <template> -<MkContainer :style="`height: ${props.height}px;`" :show-header="props.showHeader" :scrollable="true"> +<MkContainer :style="`height: ${widgetProps.height}px;`" :show-header="widgetProps.showHeader" :scrollable="true"> <template #header><i class="fas fa-bell"></i>{{ $ts.notifications }}</template> - <template #func><button class="_button" @click="configure()"><i class="fas fa-cog"></i></button></template> + <template #func><button class="_button" @click="configureNotification()"><i class="fas fa-cog"></i></button></template> <div> - <XNotifications :include-types="props.includingTypes"/> + <XNotifications :include-types="widgetProps.includingTypes"/> </div> </MkContainer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import MkContainer from '@/components/ui/container.vue'; import XNotifications from '@/components/notifications.vue'; -import define from './define'; import * as os from '@/os'; -const widget = define({ - name: 'notifications', - props: () => ({ - showHeader: { - type: 'boolean', - default: true, - }, - height: { - type: 'number', - default: 300, - }, - includingTypes: { - type: 'array', - hidden: true, - default: null, - }, - }) -}); - -export default defineComponent({ +const name = 'notifications'; - components: { - MkContainer, - XNotifications, +const widgetPropsDef = { + showHeader: { + type: 'boolean' as const, + default: true, }, - extends: widget, - - data() { - return { - }; + height: { + type: 'number' as const, + default: 300, + }, + includingTypes: { + type: 'array' as const, + hidden: true, + default: null, }, +}; - methods: { - configure() { - os.popup(import('@/components/notification-setting-window.vue'), { - includingTypes: this.props.includingTypes, - }, { - done: async (res) => { - const { includingTypes } = res; - this.props.includingTypes = includingTypes; - this.save(); - } - }, 'closed'); +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; + +// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure, save } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const configureNotification = () => { + os.popup(import('@/components/notification-setting-window.vue'), { + includingTypes: widgetProps.includingTypes, + }, { + done: async (res) => { + const { includingTypes } = res; + widgetProps.includingTypes = includingTypes; + save(); } - } + }, 'closed'); +}; + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/online-users.vue b/packages/client/src/widgets/online-users.vue index 5b889f4816..1746a8314e 100644 --- a/packages/client/src/widgets/online-users.vue +++ b/packages/client/src/widgets/online-users.vue @@ -1,48 +1,60 @@ <template> -<div class="mkw-onlineUsers" :class="{ _panel: !props.transparent, pad: !props.transparent }"> +<div class="mkw-onlineUsers" :class="{ _panel: !widgetProps.transparent, pad: !widgetProps.transparent }"> <I18n v-if="onlineUsersCount" :src="$ts.onlineUsersCount" text-tag="span" class="text"> <template #n><b>{{ onlineUsersCount }}</b></template> </I18n> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import define from './define'; +<script lang="ts" setup> +import { onMounted, onUnmounted, ref } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import * as os from '@/os'; -const widget = define({ - name: 'onlineUsers', - props: () => ({ - transparent: { - type: 'boolean', - default: true, - }, - }) -}); +const name = 'onlineUsers'; -export default defineComponent({ - extends: widget, - data() { - return { - onlineUsersCount: null, - clock: null, - }; - }, - created() { - this.tick(); - this.clock = setInterval(this.tick, 1000 * 15); +const widgetPropsDef = { + transparent: { + type: 'boolean' as const, + default: true, }, - beforeUnmount() { - clearInterval(this.clock); - }, - methods: { - tick() { - os.api('get-online-users-count').then(res => { - this.onlineUsersCount = res.count; - }); - } - } +}; + +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; + +// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const onlineUsersCount = ref(0); + +const tick = () => { + os.api('get-online-users-count').then(res => { + onlineUsersCount.value = res.count; + }); +}; + +onMounted(() => { + tick(); + const intervalId = window.setInterval(tick, 1000 * 15); + onUnmounted(() => { + window.clearInterval(intervalId); + }); +}); + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/photos.vue b/packages/client/src/widgets/photos.vue index a91d4f6c49..8f948dc643 100644 --- a/packages/client/src/widgets/photos.vue +++ b/packages/client/src/widgets/photos.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :show-header="props.showHeader" :naked="props.transparent" :class="$style.root" :data-transparent="props.transparent ? true : null"> +<MkContainer :show-header="widgetProps.showHeader" :naked="widgetProps.transparent" :class="$style.root" :data-transparent="widgetProps.transparent ? true : null"> <template #header><i class="fas fa-camera"></i>{{ $ts._widgets.photos }}</template> <div class=""> @@ -14,69 +14,77 @@ </MkContainer> </template> -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; -import MkContainer from '@/components/ui/container.vue'; -import define from './define'; +<script lang="ts" setup> +import { onMounted, onUnmounted, reactive, ref } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { stream } from '@/stream'; import { getStaticImageUrl } from '@/scripts/get-static-image-url'; import * as os from '@/os'; +import MkContainer from '@/components/ui/container.vue'; +import { defaultStore } from '@/store'; -const widget = define({ - name: 'photos', - props: () => ({ - showHeader: { - type: 'boolean', - default: true, - }, - transparent: { - type: 'boolean', - default: false, - }, - }) -}); +const name = 'photos'; -export default defineComponent({ - components: { - MkContainer, +const widgetPropsDef = { + showHeader: { + type: 'boolean' as const, + default: true, }, - extends: widget, - data() { - return { - images: [], - fetching: true, - connection: null, - }; + transparent: { + type: 'boolean' as const, + default: false, }, - mounted() { - this.connection = markRaw(os.stream.useChannel('main')); +}; - this.connection.on('driveFileCreated', this.onDriveFileCreated); +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; - os.api('drive/stream', { - type: 'image/*', - limit: 9 - }).then(images => { - this.images = images; - this.fetching = false; - }); - }, - beforeUnmount() { - this.connection.dispose(); - }, - methods: { - onDriveFileCreated(file) { - if (/^image\/.+$/.test(file.type)) { - this.images.unshift(file); - if (this.images.length > 9) this.images.pop(); - } - }, +// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const connection = stream.useChannel('main'); +const images = ref([]); +const fetching = ref(true); - thumbnail(image: any): string { - return this.$store.state.disableShowingAnimatedImages - ? getStaticImageUrl(image.thumbnailUrl) - : image.thumbnailUrl; - }, +const onDriveFileCreated = (file) => { + if (/^image\/.+$/.test(file.type)) { + images.value.unshift(file); + if (images.value.length > 9) images.value.pop(); } +}; + +const thumbnail = (image: any): string => { + return defaultStore.state.disableShowingAnimatedImages + ? getStaticImageUrl(image.thumbnailUrl) + : image.thumbnailUrl; +}; + +os.api('drive/stream', { + type: 'image/*', + limit: 9 +}).then(res => { + images.value = res; + fetching.value = false; +}); + +connection.on('driveFileCreated', onDriveFileCreated); +onUnmounted(() => { + connection.dispose(); +}); + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/post-form.vue b/packages/client/src/widgets/post-form.vue index 6de0574cc1..51aa8fcf6b 100644 --- a/packages/client/src/widgets/post-form.vue +++ b/packages/client/src/widgets/post-form.vue @@ -2,22 +2,34 @@ <XPostForm class="_panel" :fixed="true" :autofocus="false"/> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import XPostForm from '@/components/post-form.vue'; -import define from './define'; -const widget = define({ - name: 'postForm', - props: () => ({ - }) -}); +const name = 'postForm'; + +const widgetPropsDef = { +}; + +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; + +// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); -export default defineComponent({ +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); - components: { - XPostForm, - }, - extends: widget, +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/rss.vue b/packages/client/src/widgets/rss.vue index b2dc77854e..9e2e503602 100644 --- a/packages/client/src/widgets/rss.vue +++ b/packages/client/src/widgets/rss.vue @@ -1,7 +1,7 @@ <template> -<MkContainer :show-header="props.showHeader"> +<MkContainer :show-header="widgetProps.showHeader"> <template #header><i class="fas fa-rss-square"></i>RSS</template> - <template #func><button class="_button" @click="setting"><i class="fas fa-cog"></i></button></template> + <template #func><button class="_button" @click="configure"><i class="fas fa-cog"></i></button></template> <div class="ekmkgxbj"> <MkLoading v-if="fetching"/> @@ -12,57 +12,66 @@ </MkContainer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import MkContainer from '@/components/ui/container.vue'; -import define from './define'; +<script lang="ts" setup> +import { onMounted, onUnmounted, ref, watch } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import * as os from '@/os'; +import MkContainer from '@/components/ui/container.vue'; -const widget = define({ - name: 'rss', - props: () => ({ - showHeader: { - type: 'boolean', - default: true, - }, - url: { - type: 'string', - default: 'http://feeds.afpbb.com/rss/afpbb/afpbbnews', - }, - }) -}); +const name = 'rss'; -export default defineComponent({ - components: { - MkContainer - }, - extends: widget, - data() { - return { - items: [], - fetching: true, - clock: null, - }; +const widgetPropsDef = { + showHeader: { + type: 'boolean' as const, + default: true, }, - mounted() { - this.fetch(); - this.clock = setInterval(this.fetch, 60000); - this.$watch(() => this.props.url, this.fetch); + url: { + type: 'string' as const, + default: 'http://feeds.afpbb.com/rss/afpbb/afpbbnews', }, - beforeUnmount() { - clearInterval(this.clock); - }, - methods: { - fetch() { - fetch(`https://api.rss2json.com/v1/api.json?rss_url=${this.props.url}`, { - }).then(res => { - res.json().then(feed => { - this.items = feed.items; - this.fetching = false; - }); - }); - }, - } +}; + +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; + +// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const items = ref([]); +const fetching = ref(true); + +const tick = () => { + fetch(`https://api.rss2json.com/v1/api.json?rss_url=${widgetProps.url}`, {}).then(res => { + res.json().then(feed => { + items.value = feed.items; + fetching.value = false; + }); + }); +}; + +watch(() => widgetProps.url, tick); + +onMounted(() => { + tick(); + const intervalId = window.setInterval(tick, 60000); + onUnmounted(() => { + window.clearInterval(intervalId); + }); +}); + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/server-metric/disk.vue b/packages/client/src/widgets/server-metric/disk.vue index 650101b0ee..052991b554 100644 --- a/packages/client/src/widgets/server-metric/disk.vue +++ b/packages/client/src/widgets/server-metric/disk.vue @@ -10,32 +10,19 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import XPie from './pie.vue'; import bytes from '@/filters/bytes'; -export default defineComponent({ - components: { - XPie - }, - props: { - meta: { - required: true, - } - }, - data() { - return { - usage: this.meta.fs.used / this.meta.fs.total, - total: this.meta.fs.total, - used: this.meta.fs.used, - available: this.meta.fs.total - this.meta.fs.used, - }; - }, - methods: { - bytes - } -}); +const props = defineProps<{ + meta: any; // TODO +}>(); + +const usage = $computed(() => props.meta.fs.used / props.meta.fs.total); +const total = $computed(() => props.meta.fs.total); +const used = $computed(() => props.meta.fs.used); +const available = $computed(() => props.meta.fs.total - props.meta.fs.used); </script> <style lang="scss" scoped> diff --git a/packages/client/src/widgets/server-metric/index.vue b/packages/client/src/widgets/server-metric/index.vue index 019e16fb33..2caa73fa74 100644 --- a/packages/client/src/widgets/server-metric/index.vue +++ b/packages/client/src/widgets/server-metric/index.vue @@ -1,21 +1,22 @@ <template> -<MkContainer :show-header="props.showHeader" :naked="props.transparent"> +<MkContainer :show-header="widgetProps.showHeader" :naked="widgetProps.transparent"> <template #header><i class="fas fa-server"></i>{{ $ts._widgets.serverMetric }}</template> <template #func><button class="_button" @click="toggleView()"><i class="fas fa-sort"></i></button></template> <div v-if="meta" class="mkw-serverMetric"> - <XCpuMemory v-if="props.view === 0" :connection="connection" :meta="meta"/> - <XNet v-if="props.view === 1" :connection="connection" :meta="meta"/> - <XCpu v-if="props.view === 2" :connection="connection" :meta="meta"/> - <XMemory v-if="props.view === 3" :connection="connection" :meta="meta"/> - <XDisk v-if="props.view === 4" :connection="connection" :meta="meta"/> + <XCpuMemory v-if="widgetProps.view === 0" :connection="connection" :meta="meta"/> + <XNet v-else-if="widgetProps.view === 1" :connection="connection" :meta="meta"/> + <XCpu v-else-if="widgetProps.view === 2" :connection="connection" :meta="meta"/> + <XMemory v-else-if="widgetProps.view === 3" :connection="connection" :meta="meta"/> + <XDisk v-else-if="widgetProps.view === 4" :connection="connection" :meta="meta"/> </div> </MkContainer> </template> -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; -import define from '../define'; +<script lang="ts" setup> +import { onMounted, onUnmounted, ref } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from '../widget'; import MkContainer from '@/components/ui/container.vue'; import XCpuMemory from './cpu-mem.vue'; import XNet from './net.vue'; @@ -23,60 +24,63 @@ import XCpu from './cpu.vue'; import XMemory from './mem.vue'; import XDisk from './disk.vue'; import * as os from '@/os'; +import { stream } from '@/stream'; -const widget = define({ - name: 'serverMetric', - props: () => ({ - showHeader: { - type: 'boolean', - default: true, - }, - transparent: { - type: 'boolean', - default: false, - }, - view: { - type: 'number', - default: 0, - hidden: true, - }, - }) -}); +const name = 'serverMetric'; -export default defineComponent({ - components: { - MkContainer, - XCpuMemory, - XNet, - XCpu, - XMemory, - XDisk, - }, - extends: widget, - data() { - return { - meta: null, - connection: null, - }; +const widgetPropsDef = { + showHeader: { + type: 'boolean' as const, + default: true, }, - created() { - os.api('server-info', {}).then(res => { - this.meta = res; - }); - this.connection = markRaw(os.stream.useChannel('serverStats')); + transparent: { + type: 'boolean' as const, + default: false, }, - unmounted() { - this.connection.dispose(); + view: { + type: 'number' as const, + default: 0, + hidden: true, }, - methods: { - toggleView() { - if (this.props.view == 4) { - this.props.view = 0; - } else { - this.props.view++; - } - this.save(); - }, +}; + +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; + +// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure, save } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const meta = ref(null); + +os.api('server-info', {}).then(res => { + meta.value = res; +}); + +const toggleView = () => { + if (widgetProps.view == 4) { + widgetProps.view = 0; + } else { + widgetProps.view++; } + save(); +}; + +const connection = stream.useChannel('serverStats'); +onUnmounted(() => { + connection.dispose(); +}); + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/server-metric/pie.vue b/packages/client/src/widgets/server-metric/pie.vue index 38dcf6fcd9..868dbc0484 100644 --- a/packages/client/src/widgets/server-metric/pie.vue +++ b/packages/client/src/widgets/server-metric/pie.vue @@ -20,30 +20,17 @@ </svg> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; -export default defineComponent({ - props: { - value: { - type: Number, - required: true - } - }, - data() { - return { - r: 0.45 - }; - }, - computed: { - color(): string { - return `hsl(${180 - (this.value * 180)}, 80%, 70%)`; - }, - strokeDashoffset(): number { - return (1 - this.value) * (Math.PI * (this.r * 2)); - } - } -}); +const props = defineProps<{ + value: number; +}>(); + +const r = 0.45; + +const color = $computed(() => `hsl(${180 - (props.value * 180)}, 80%, 70%)`); +const strokeDashoffset = $computed(() => (1 - props.value) * (Math.PI * (r * 2))); </script> <style lang="scss" scoped> diff --git a/packages/client/src/widgets/slideshow.vue b/packages/client/src/widgets/slideshow.vue index 0909bda67c..7b2e539685 100644 --- a/packages/client/src/widgets/slideshow.vue +++ b/packages/client/src/widgets/slideshow.vue @@ -1,126 +1,116 @@ <template> -<div class="kvausudm _panel"> +<div class="kvausudm _panel" :style="{ height: widgetProps.height + 'px' }"> <div @click="choose"> - <p v-if="props.folderId == null"> - <template v-if="isCustomizeMode">{{ $t('folder-customize-mode') }}</template> - <template v-else>{{ $ts.folder }}</template> + <p v-if="widgetProps.folderId == null"> + {{ $ts.folder }} </p> - <p v-if="props.folderId != null && images.length === 0 && !fetching">{{ $t('no-image') }}</p> + <p v-if="widgetProps.folderId != null && images.length === 0 && !fetching">{{ $t('no-image') }}</p> <div ref="slideA" class="slide a"></div> <div ref="slideB" class="slide b"></div> </div> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import define from './define'; +<script lang="ts" setup> +import { nextTick, onMounted, onUnmounted, reactive, ref } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import * as os from '@/os'; -const widget = define({ - name: 'slideshow', - props: () => ({ - height: { - type: 'number', - default: 300, - }, - folderId: { - type: 'string', - default: null, - hidden: true, - }, - }) -}); +const name = 'slideshow'; -export default defineComponent({ - extends: widget, - data() { - return { - images: [], - fetching: true, - clock: null - }; +const widgetPropsDef = { + height: { + type: 'number' as const, + default: 300, + }, + folderId: { + type: 'string' as const, + default: null, + hidden: true, }, - mounted() { - this.$nextTick(() => { - this.applySize(); - }); +}; - if (this.props.folderId != null) { - this.fetch(); - } +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; - this.clock = setInterval(this.change, 10000); - }, - beforeUnmount() { - clearInterval(this.clock); - }, - methods: { - applySize() { - let h; +// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); - if (this.props.size == 1) { - h = 250; - } else { - h = 170; - } +const { widgetProps, configure, save } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); - this.$el.style.height = `${h}px`; - }, - resize() { - if (this.props.size == 1) { - this.props.size = 0; - } else { - this.props.size++; - } - this.save(); +const images = ref([]); +const fetching = ref(true); +const slideA = ref<HTMLElement>(); +const slideB = ref<HTMLElement>(); - this.applySize(); - }, - change() { - if (this.images.length == 0) return; +const change = () => { + if (images.value.length == 0) return; - const index = Math.floor(Math.random() * this.images.length); - const img = `url(${ this.images[index].url })`; + const index = Math.floor(Math.random() * images.value.length); + const img = `url(${ images.value[index].url })`; - (this.$refs.slideB as any).style.backgroundImage = img; + slideB.value.style.backgroundImage = img; - this.$refs.slideB.classList.add('anime'); - setTimeout(() => { - // 既にこのウィジェットがunmountされていたら要素がない - if ((this.$refs.slideA as any) == null) return; + slideB.value.classList.add('anime'); + window.setTimeout(() => { + // 既にこのウィジェットがunmountされていたら要素がない + if (slideA.value == null) return; - (this.$refs.slideA as any).style.backgroundImage = img; + slideA.value.style.backgroundImage = img; - this.$refs.slideB.classList.remove('anime'); - }, 1000); - }, - fetch() { - this.fetching = true; + slideB.value.classList.remove('anime'); + }, 1000); +}; - os.api('drive/files', { - folderId: this.props.folderId, - type: 'image/*', - limit: 100 - }).then(images => { - this.images = images; - this.fetching = false; - (this.$refs.slideA as any).style.backgroundImage = ''; - (this.$refs.slideB as any).style.backgroundImage = ''; - this.change(); - }); - }, - choose() { - os.selectDriveFolder(false).then(folder => { - if (folder == null) { - return; - } - this.props.folderId = folder.id; - this.save(); - this.fetch(); - }); +const fetch = () => { + fetching.value = true; + + os.api('drive/files', { + folderId: widgetProps.folderId, + type: 'image/*', + limit: 100 + }).then(res => { + images.value = res; + fetching.value = false; + slideA.value.style.backgroundImage = ''; + slideB.value.style.backgroundImage = ''; + change(); + }); +}; + +const choose = () => { + os.selectDriveFolder(false).then(folder => { + if (folder == null) { + return; } + widgetProps.folderId = folder.id; + save(); + fetch(); + }); +}; + +onMounted(() => { + if (widgetProps.folderId != null) { + fetch(); } + + const intervalId = window.setInterval(change, 10000); + onUnmounted(() => { + window.clearInterval(intervalId); + }); +}); + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/timeline.vue b/packages/client/src/widgets/timeline.vue index aee6a35b1d..fa700cc8ee 100644 --- a/packages/client/src/widgets/timeline.vue +++ b/packages/client/src/widgets/timeline.vue @@ -1,116 +1,129 @@ <template> -<MkContainer :show-header="props.showHeader" :style="`height: ${props.height}px;`" :scrollable="true"> +<MkContainer :show-header="widgetProps.showHeader" :style="`height: ${widgetProps.height}px;`" :scrollable="true"> <template #header> <button class="_button" @click="choose"> - <i v-if="props.src === 'home'" class="fas fa-home"></i> - <i v-else-if="props.src === 'local'" class="fas fa-comments"></i> - <i v-else-if="props.src === 'social'" class="fas fa-share-alt"></i> - <i v-else-if="props.src === 'global'" class="fas fa-globe"></i> - <i v-else-if="props.src === 'list'" class="fas fa-list-ul"></i> - <i v-else-if="props.src === 'antenna'" class="fas fa-satellite"></i> - <span style="margin-left: 8px;">{{ props.src === 'list' ? props.list.name : props.src === 'antenna' ? props.antenna.name : $t('_timelines.' + props.src) }}</span> + <i v-if="widgetProps.src === 'home'" class="fas fa-home"></i> + <i v-else-if="widgetProps.src === 'local'" class="fas fa-comments"></i> + <i v-else-if="widgetProps.src === 'social'" class="fas fa-share-alt"></i> + <i v-else-if="widgetProps.src === 'global'" class="fas fa-globe"></i> + <i v-else-if="widgetProps.src === 'list'" class="fas fa-list-ul"></i> + <i v-else-if="widgetProps.src === 'antenna'" class="fas fa-satellite"></i> + <span style="margin-left: 8px;">{{ widgetProps.src === 'list' ? widgetProps.list.name : widgetProps.src === 'antenna' ? widgetProps.antenna.name : $t('_timelines.' + widgetProps.src) }}</span> <i :class="menuOpened ? 'fas fa-angle-up' : 'fas fa-angle-down'" style="margin-left: 8px;"></i> </button> </template> <div> - <XTimeline :key="props.src === 'list' ? `list:${props.list.id}` : props.src === 'antenna' ? `antenna:${props.antenna.id}` : props.src" :src="props.src" :list="props.list ? props.list.id : null" :antenna="props.antenna ? props.antenna.id : null"/> + <XTimeline :key="widgetProps.src === 'list' ? `list:${widgetProps.list.id}` : widgetProps.src === 'antenna' ? `antenna:${widgetProps.antenna.id}` : widgetProps.src" :src="widgetProps.src" :list="widgetProps.list ? widgetProps.list.id : null" :antenna="widgetProps.antenna ? widgetProps.antenna.id : null"/> </div> </MkContainer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted, onUnmounted, reactive, ref, watch } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import * as os from '@/os'; import MkContainer from '@/components/ui/container.vue'; import XTimeline from '@/components/timeline.vue'; -import define from './define'; -import * as os from '@/os'; +import { $i } from '@/account'; +import { i18n } from '@/i18n'; -const widget = define({ - name: 'timeline', - props: () => ({ - showHeader: { - type: 'boolean', - default: true, - }, - height: { - type: 'number', - default: 300, - }, - src: { - type: 'string', - default: 'home', - hidden: true, - }, - list: { - type: 'object', - default: null, - hidden: true, - }, - }) -}); +const name = 'timeline'; -export default defineComponent({ - components: { - MkContainer, - XTimeline, +const widgetPropsDef = { + showHeader: { + type: 'boolean' as const, + default: true, }, - extends: widget, - - data() { - return { - menuOpened: false, - }; + height: { + type: 'number' as const, + default: 300, + }, + src: { + type: 'string' as const, + default: 'home', + hidden: true, + }, + antenna: { + type: 'object' as const, + default: null, + hidden: true, }, + list: { + type: 'object' as const, + default: null, + hidden: true, + }, +}; + +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; + +// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure, save } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const menuOpened = ref(false); + +const setSrc = (src) => { + widgetProps.src = src; + save(); +}; - methods: { - async choose(ev) { - this.menuOpened = true; - const [antennas, lists] = await Promise.all([ - os.api('antennas/list'), - os.api('users/lists/list') - ]); - const antennaItems = antennas.map(antenna => ({ - text: antenna.name, - icon: 'fas fa-satellite', - action: () => { - this.props.antenna = antenna; - this.setSrc('antenna'); - } - })); - const listItems = lists.map(list => ({ - text: list.name, - icon: 'fas fa-list-ul', - action: () => { - this.props.list = list; - this.setSrc('list'); - } - })); - os.popupMenu([{ - text: this.$ts._timelines.home, - icon: 'fas fa-home', - action: () => { this.setSrc('home') } - }, { - text: this.$ts._timelines.local, - icon: 'fas fa-comments', - action: () => { this.setSrc('local') } - }, { - text: this.$ts._timelines.social, - icon: 'fas fa-share-alt', - action: () => { this.setSrc('social') } - }, { - text: this.$ts._timelines.global, - icon: 'fas fa-globe', - action: () => { this.setSrc('global') } - }, antennaItems.length > 0 ? null : undefined, ...antennaItems, listItems.length > 0 ? null : undefined, ...listItems], ev.currentTarget || ev.target).then(() => { - this.menuOpened = false; - }); - }, +const choose = async (ev) => { + menuOpened.value = true; + const [antennas, lists] = await Promise.all([ + os.api('antennas/list'), + os.api('users/lists/list') + ]); + const antennaItems = antennas.map(antenna => ({ + text: antenna.name, + icon: 'fas fa-satellite', + action: () => { + widgetProps.antenna = antenna; + setSrc('antenna'); + } + })); + const listItems = lists.map(list => ({ + text: list.name, + icon: 'fas fa-list-ul', + action: () => { + widgetProps.list = list; + setSrc('list'); + } + })); + os.popupMenu([{ + text: i18n.locale._timelines.home, + icon: 'fas fa-home', + action: () => { setSrc('home') } + }, { + text: i18n.locale._timelines.local, + icon: 'fas fa-comments', + action: () => { setSrc('local') } + }, { + text: i18n.locale._timelines.social, + icon: 'fas fa-share-alt', + action: () => { setSrc('social') } + }, { + text: i18n.locale._timelines.global, + icon: 'fas fa-globe', + action: () => { setSrc('global') } + }, antennaItems.length > 0 ? null : undefined, ...antennaItems, listItems.length > 0 ? null : undefined, ...listItems], ev.currentTarget || ev.target).then(() => { + menuOpened.value = false; + }); +}; - setSrc(src) { - this.props.src = src; - this.save(); - }, - } +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/trends.vue b/packages/client/src/widgets/trends.vue index ffad93c02b..eb5eb4049f 100644 --- a/packages/client/src/widgets/trends.vue +++ b/packages/client/src/widgets/trends.vue @@ -1,10 +1,10 @@ <template> -<MkContainer :show-header="props.showHeader"> +<MkContainer :show-header="widgetProps.showHeader"> <template #header><i class="fas fa-hashtag"></i>{{ $ts._widgets.trends }}</template> <div class="wbrkwala"> <MkLoading v-if="fetching"/> - <transition-group v-else tag="div" name="chart" class="tags"> + <transition-group v-else tag="div" :name="$store.state.animation ? 'chart' : ''" class="tags"> <div v-for="stat in stats" :key="stat.tag"> <div class="tag"> <MkA class="a" :to="`/tags/${ encodeURIComponent(stat.tag) }`" :title="stat.tag">#{{ stat.tag }}</MkA> @@ -17,49 +17,59 @@ </MkContainer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted, onUnmounted, ref } from 'vue'; +import { GetFormResultType } from '@/scripts/form'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import MkContainer from '@/components/ui/container.vue'; -import define from './define'; import MkMiniChart from '@/components/mini-chart.vue'; import * as os from '@/os'; -const widget = define({ - name: 'hashtags', - props: () => ({ - showHeader: { - type: 'boolean', - default: true, - }, - }) -}); +const name = 'hashtags'; -export default defineComponent({ - components: { - MkContainer, MkMiniChart - }, - extends: widget, - data() { - return { - stats: [], - fetching: true, - }; - }, - mounted() { - this.fetch(); - this.clock = setInterval(this.fetch, 1000 * 60); - }, - beforeUnmount() { - clearInterval(this.clock); +const widgetPropsDef = { + showHeader: { + type: 'boolean' as const, + default: true, }, - methods: { - fetch() { - os.api('hashtags/trend').then(stats => { - this.stats = stats; - this.fetching = false; - }); - } - } +}; + +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; + +// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +const stats = ref([]); +const fetching = ref(true); + +const fetch = () => { + os.api('hashtags/trend').then(stats => { + stats.value = stats; + fetching.value = false; + }); +}; + +onMounted(() => { + fetch(); + const intervalId = window.setInterval(fetch, 1000 * 60); + onUnmounted(() => { + window.clearInterval(intervalId); + }); +}); + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, }); </script> diff --git a/packages/client/src/widgets/widget.ts b/packages/client/src/widgets/widget.ts new file mode 100644 index 0000000000..81239bfb3b --- /dev/null +++ b/packages/client/src/widgets/widget.ts @@ -0,0 +1,71 @@ +import { reactive, watch } from 'vue'; +import { throttle } from 'throttle-debounce'; +import { Form, GetFormResultType } from '@/scripts/form'; +import * as os from '@/os'; + +export type Widget<P extends Record<string, unknown>> = { + id: string; + data: Partial<P>; +}; + +export type WidgetComponentProps<P extends Record<string, unknown>> = { + widget?: Widget<P>; +}; + +export type WidgetComponentEmits<P extends Record<string, unknown>> = { + (e: 'updateProps', props: P); +}; + +export type WidgetComponentExpose = { + name: string; + id: string | null; + configure: () => void; +}; + +export const useWidgetPropsManager = <F extends Form & Record<string, { default: any; }>>( + name: string, + propsDef: F, + props: Readonly<WidgetComponentProps<GetFormResultType<F>>>, + emit: WidgetComponentEmits<GetFormResultType<F>>, +): { + widgetProps: GetFormResultType<F>; + save: () => void; + configure: () => void; +} => { + const widgetProps = reactive(props.widget ? JSON.parse(JSON.stringify(props.widget.data)) : {}); + + const mergeProps = () => { + for (const prop of Object.keys(propsDef)) { + if (widgetProps.hasOwnProperty(prop)) continue; + widgetProps[prop] = propsDef[prop].default; + } + }; + watch(widgetProps, () => { + mergeProps(); + }, { deep: true, immediate: true, }); + + const save = throttle(3000, () => { + emit('updateProps', widgetProps) + }); + + const configure = async () => { + const form = JSON.parse(JSON.stringify(propsDef)); + for (const item of Object.keys(form)) { + form[item].default = widgetProps[item]; + } + const { canceled, result } = await os.form(name, form); + if (canceled) return; + + for (const key of Object.keys(result)) { + widgetProps[key] = result[key]; + } + + save(); + }; + + return { + widgetProps, + save, + configure, + }; +}; diff --git a/packages/client/webpack.config.js b/packages/client/webpack.config.js index 7bcfdcb15d..a50851e17f 100644 --- a/packages/client/webpack.config.js +++ b/packages/client/webpack.config.js @@ -47,6 +47,7 @@ module.exports = { loader: 'vue-loader', options: { cssSourceMap: false, + reactivityTransform: true, compilerOptions: { preserveWhitespace: false } diff --git a/packages/client/yarn.lock b/packages/client/yarn.lock index 4f666c252b..a45a24ce0e 100644 --- a/packages/client/yarn.lock +++ b/packages/client/yarn.lock @@ -177,94 +177,32 @@ "@nodelib/fs.scandir" "2.1.3" fastq "^1.6.0" -"@redocly/ajv@^8.6.2": - version "8.6.2" - resolved "https://registry.yarnpkg.com/@redocly/ajv/-/ajv-8.6.2.tgz#8c4e485e72f7864f91fae40093bed548ec2619b2" - integrity sha512-tU8fQs0D76ZKhJ2cWtnfQthWqiZgGBx0gH0+5D8JvaBEBaqA8foPPBt3Nonwr3ygyv5xrw2IzKWgIY86BlGs+w== +"@redocly/ajv@^8.6.4": + version "8.6.4" + resolved "https://registry.yarnpkg.com/@redocly/ajv/-/ajv-8.6.4.tgz#94053e7a9d4146d1a4feacd3813892873f229a85" + integrity sha512-y9qNj0//tZtWB2jfXNK3BX18BSBp9zNR7KE7lMysVHwbZtY392OJCjm6Rb/h4UHH2r1AqjNEHFD6bRn+DqU9Mw== dependencies: fast-deep-equal "^3.1.1" json-schema-traverse "^1.0.0" require-from-string "^2.0.2" uri-js "^4.2.2" -"@redocly/openapi-core@1.0.0-beta.54": - version "1.0.0-beta.54" - resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.0.0-beta.54.tgz#42575a849c4dd54b9d0c6413fb8ca547e087cd11" - integrity sha512-uYs0N1Trjkh7u8IMIuCU2VxCXhMyGWSZUkP/WNdTR1OgBUtvNdF9C32zoQV+hyCIH4gVu42ROHkjisy333ZX+w== +"@redocly/openapi-core@1.0.0-beta.79": + version "1.0.0-beta.79" + resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.0.0-beta.79.tgz#7512b3507ab99dc78226f9069669c5302abb0969" + integrity sha512-do79vGt3iiHsaVG9LKY8dH+d1E7TLHr+3T+CQ1lqagtWVjYOxqGaoxAT8tRD7R1W0z8BmS4e2poNON6c1sxP5g== dependencies: - "@redocly/ajv" "^8.6.2" + "@redocly/ajv" "^8.6.4" "@types/node" "^14.11.8" colorette "^1.2.0" js-levenshtein "^1.1.6" - js-yaml "^3.14.1" + js-yaml "^4.1.0" lodash.isequal "^4.5.0" minimatch "^3.0.4" node-fetch "^2.6.1" + pluralize "^8.0.0" yaml-ast-parser "0.0.43" -"@sentry/browser@5.29.2": - version "5.29.2" - resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.29.2.tgz#51adb4005511b1a4a70e4571880fc6653d651f91" - integrity sha512-uxZ7y7rp85tJll+RZtXRhXPbnFnOaxZqJEv05vJlXBtBNLQtlczV5iCtU9mZRLVHDtmZ5VVKUV8IKXntEqqDpQ== - dependencies: - "@sentry/core" "5.29.2" - "@sentry/types" "5.29.2" - "@sentry/utils" "5.29.2" - tslib "^1.9.3" - -"@sentry/core@5.29.2": - version "5.29.2" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.29.2.tgz#9e05fe197234161d57aabaf52fab336a7c520d81" - integrity sha512-7WYkoxB5IdlNEbwOwqSU64erUKH4laavPsM0/yQ+jojM76ErxlgEF0u//p5WaLPRzh3iDSt6BH+9TL45oNZeZw== - dependencies: - "@sentry/hub" "5.29.2" - "@sentry/minimal" "5.29.2" - "@sentry/types" "5.29.2" - "@sentry/utils" "5.29.2" - tslib "^1.9.3" - -"@sentry/hub@5.29.2": - version "5.29.2" - resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.29.2.tgz#208f10fe6674695575ad74182a1151f71d6df00a" - integrity sha512-LaAIo2hwUk9ykeh9RF0cwLy6IRw+DjEee8l1HfEaDFUM6TPGlNNGObMJNXb9/95jzWp7jWwOpQjoIE3jepdQJQ== - dependencies: - "@sentry/types" "5.29.2" - "@sentry/utils" "5.29.2" - tslib "^1.9.3" - -"@sentry/minimal@5.29.2": - version "5.29.2" - resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.29.2.tgz#420bebac8d03d30980fdb05c72d7b253d8aa541b" - integrity sha512-0aINSm8fGA1KyM7PavOBe1GDZDxrvnKt+oFnU0L+bTcw8Lr+of+v6Kwd97rkLRNOLw621xP076dL/7LSIzMuhw== - dependencies: - "@sentry/hub" "5.29.2" - "@sentry/types" "5.29.2" - tslib "^1.9.3" - -"@sentry/tracing@5.29.2": - version "5.29.2" - resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-5.29.2.tgz#6012788547d2ab7893799d82c4941bda145dcd47" - integrity sha512-iumYbVRpvoU3BUuIooxibydeaOOjl5ysc+mzsqhRs2NGW/C3uKAsFXdvyNfqt3bxtRQwJEhwJByLP2u3pLThpw== - dependencies: - "@sentry/hub" "5.29.2" - "@sentry/minimal" "5.29.2" - "@sentry/types" "5.29.2" - "@sentry/utils" "5.29.2" - tslib "^1.9.3" - -"@sentry/types@5.29.2": - version "5.29.2" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.29.2.tgz#ac87383df1222c2d9b9f8f9ed7a6b86ea41a098a" - integrity sha512-dM9wgt8wy4WRty75QkqQgrw9FV9F+BOMfmc0iaX13Qos7i6Qs2Q0dxtJ83SoR4YGtW8URaHzlDtWlGs5egBiMA== - -"@sentry/utils@5.29.2": - version "5.29.2" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.29.2.tgz#99a5cdda2ea19d34a41932f138d470adcb3ee673" - integrity sha512-nEwQIDjtFkeE4k6yIk4Ka5XjGRklNLThWLs2xfXlL7uwrYOH2B9UBBOOIRUraBm/g/Xrra3xsam/kRxuiwtXZQ== - dependencies: - "@sentry/types" "5.29.2" - tslib "^1.9.3" - "@sideway/address@^4.1.0": version "4.1.2" resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.2.tgz#811b84333a335739d3969cfc434736268170cad1" @@ -333,10 +271,10 @@ resolved "https://registry.yarnpkg.com/@types/dateformat/-/dateformat-3.0.1.tgz#98d747a2e5e9a56070c6bf14e27bff56204e34cc" integrity sha512-KlPPdikagvL6ELjWsljbyDIPzNCeliYkqRpI+zea99vBBbCIA5JNshZAwQKTON139c87y9qvTFVgkFd14rtS4g== -"@types/escape-regexp@0.0.0": - version "0.0.0" - resolved "https://registry.yarnpkg.com/@types/escape-regexp/-/escape-regexp-0.0.0.tgz#bff0225f9ef30d0dbdbe0e2a24283ee5342990c3" - integrity sha512-HTansGo4tJ7K7W9I9LBdQqnHtPB/Y7tlS+EMrkboaAQLsRPhRpHaqAHe01K1HVXM5e1u1IplRd8EBh+pJrp7Dg== +"@types/escape-regexp@0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@types/escape-regexp/-/escape-regexp-0.0.1.tgz#f1a977ccdf2ef059e9862bd3af5e92cbbe723e0e" + integrity sha512-ogj/ZTIdeFkiuxDwawYuZSIgC6suFGgBeZPr6Xs5lHEcvIXTjXGtH+/n8f1XhZhespaUwJ5LIGRICPji972FLw== "@types/eslint-scope@^3.7.0": version "3.7.0" @@ -374,10 +312,10 @@ resolved "https://registry.yarnpkg.com/@types/expect/-/expect-1.20.4.tgz#8288e51737bf7e3ab5d7c77bfa695883745264e5" integrity sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg== -"@types/fluent-ffmpeg@2.1.17": - version "2.1.17" - resolved "https://registry.yarnpkg.com/@types/fluent-ffmpeg/-/fluent-ffmpeg-2.1.17.tgz#6958dda400fe1b33c21f3683db76905cb210d053" - integrity sha512-/bdvjKw/mtBHlJ2370d04nt4CsWqU5MrwS/NtO96V01jxitJ4+iq8OFNcqc5CegeV3TQOK3uueK02kvRK+zjUg== +"@types/fluent-ffmpeg@2.1.20": + version "2.1.20" + resolved "https://registry.yarnpkg.com/@types/fluent-ffmpeg/-/fluent-ffmpeg-2.1.20.tgz#3b5f42fc8263761d58284fa46ee6759a64ce54ac" + integrity sha512-B+OvhCdJ3LgEq2PhvWNOiB/EfwnXLElfMCgc4Z1K5zXgSfo9I6uGKwR/lqmNPFQuebNnes7re3gqkV77SyypLg== dependencies: "@types/node" "*" @@ -478,11 +416,6 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.6.2.tgz#331b7b9f8621c638284787c5559423822fdffc50" integrity sha512-LSw8TZt12ZudbpHc6EkIyDM3nHVWKYrAvGy6EAJfNfjusbwnThqjqxUKKRwuV3iWYeW/LYMzNgaq3MaLffQ2xA== -"@types/node@16.11.12": - version "16.11.12" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.12.tgz#ac7fb693ac587ee182c3780c26eb65546a1a3c10" - integrity sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw== - "@types/node@^14.11.8", "@types/node@^14.14.31": version "14.17.9" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.9.tgz#b97c057e6138adb7b720df2bd0264b03c9f504fd" @@ -539,10 +472,10 @@ resolved "https://registry.yarnpkg.com/@types/seedrandom/-/seedrandom-2.4.28.tgz#9ce8fa048c1e8c85cb71d7fe4d704e000226036f" integrity sha512-SMA+fUwULwK7sd/ZJicUztiPs8F1yCPwF3O23Z9uQ32ME5Ha0NmDK9+QTsYE4O2tHXChzXomSWWeIhCnoN1LqA== -"@types/sinonjs__fake-timers@^6.0.2": - version "6.0.3" - resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.3.tgz#79df6f358ae8f79e628fe35a63608a0ea8e7cf08" - integrity sha512-E1dU4fzC9wN2QK2Cr1MLCfyHM8BoNnRFvuf45LYMPNDA+WqbNzC45S4UzPxvp1fFJ1rvSGU0bPvdd35VLmXG8g== +"@types/sinonjs__fake-timers@8.1.1": + version "8.1.1" + resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz#b49c2c70150141a15e0fa7e79cf1f92a72934ce3" + integrity sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g== "@types/sizzle@^2.3.2": version "2.3.3" @@ -593,10 +526,10 @@ dependencies: "@types/undertaker-registry" "*" -"@types/uuid@8.3.3": - version "8.3.3" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.3.tgz#c6a60686d953dbd1b1d45e66f4ecdbd5d471b4d0" - integrity sha512-0LbEEx1zxrYB3pgpd1M5lEhLcXjKJnYghvhTRgaBeUivLHMDM1TzF3IJ6hXU2+8uA4Xz+5BA63mtZo5DjVT8iA== +"@types/uuid@8.3.4": + version "8.3.4" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" + integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== "@types/vinyl-fs@*": version "2.4.11" @@ -681,13 +614,14 @@ dependencies: "@types/node" "*" -"@typescript-eslint/eslint-plugin@5.8.1": - version "5.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.8.1.tgz#97dfaa39f38e99f86801fdf34f9f1bed66704258" - integrity sha512-wTZ5oEKrKj/8/366qTM366zqhIKAp6NCMweoRONtfuC07OAU9nVI2GZZdqQ1qD30WAAtcPdkH+npDwtRFdp4Rw== +"@typescript-eslint/eslint-plugin@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.10.0.tgz#e90afea96dff8620892ad216b0e4ccdf8ee32d3a" + integrity sha512-XXVKnMsq2fuu9K2KsIxPUGqb6xAImz8MEChClbXmE3VbveFtBUU5bzM6IPVWqzyADIgdkS2Ws/6Xo7W2TeZWjQ== dependencies: - "@typescript-eslint/experimental-utils" "5.8.1" - "@typescript-eslint/scope-manager" "5.8.1" + "@typescript-eslint/scope-manager" "5.10.0" + "@typescript-eslint/type-utils" "5.10.0" + "@typescript-eslint/utils" "5.10.0" debug "^4.3.2" functional-red-black-tree "^1.0.1" ignore "^5.1.8" @@ -695,60 +629,69 @@ semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/experimental-utils@5.8.1": - version "5.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.8.1.tgz#01861eb2f0749f07d02db342b794145a66ed346f" - integrity sha512-fbodVnjIDU4JpeXWRDsG5IfIjYBxEvs8EBO8W1+YVdtrc2B9ppfof5sZhVEDOtgTfFHnYQJDI8+qdqLYO4ceww== +"@typescript-eslint/parser@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.10.0.tgz#8f59e036f5f1cffc178cacbd5ccdd02aeb96c91c" + integrity sha512-pJB2CCeHWtwOAeIxv8CHVGJhI5FNyJAIpx5Pt72YkK3QfEzt6qAlXZuyaBmyfOdM62qU0rbxJzNToPTVeJGrQw== dependencies: - "@types/json-schema" "^7.0.9" - "@typescript-eslint/scope-manager" "5.8.1" - "@typescript-eslint/types" "5.8.1" - "@typescript-eslint/typescript-estree" "5.8.1" - eslint-scope "^5.1.1" - eslint-utils "^3.0.0" + "@typescript-eslint/scope-manager" "5.10.0" + "@typescript-eslint/types" "5.10.0" + "@typescript-eslint/typescript-estree" "5.10.0" + debug "^4.3.2" -"@typescript-eslint/parser@5.8.1": - version "5.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.8.1.tgz#380f5f1e596b540059998aa3fc80d78f0f9b0d0a" - integrity sha512-K1giKHAjHuyB421SoXMXFHHVI4NdNY603uKw92++D3qyxSeYvC10CBJ/GE5Thpo4WTUvu1mmJI2/FFkz38F2Gw== +"@typescript-eslint/scope-manager@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.10.0.tgz#bb5d872e8b9e36203908595507fbc4d3105329cb" + integrity sha512-tgNgUgb4MhqK6DoKn3RBhyZ9aJga7EQrw+2/OiDk5hKf3pTVZWyqBi7ukP+Z0iEEDMF5FDa64LqODzlfE4O/Dg== dependencies: - "@typescript-eslint/scope-manager" "5.8.1" - "@typescript-eslint/types" "5.8.1" - "@typescript-eslint/typescript-estree" "5.8.1" - debug "^4.3.2" + "@typescript-eslint/types" "5.10.0" + "@typescript-eslint/visitor-keys" "5.10.0" -"@typescript-eslint/scope-manager@5.8.1": - version "5.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.8.1.tgz#7fc0604f7ade8833e4d42cebaa1e2debf8b932e4" - integrity sha512-DGxJkNyYruFH3NIZc3PwrzwOQAg7vvgsHsHCILOLvUpupgkwDZdNq/cXU3BjF4LNrCsVg0qxEyWasys5AiJ85Q== +"@typescript-eslint/type-utils@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.10.0.tgz#8524b9479c19c478347a7df216827e749e4a51e5" + integrity sha512-TzlyTmufJO5V886N+hTJBGIfnjQDQ32rJYxPaeiyWKdjsv2Ld5l8cbS7pxim4DeNs62fKzRSt8Q14Evs4JnZyQ== dependencies: - "@typescript-eslint/types" "5.8.1" - "@typescript-eslint/visitor-keys" "5.8.1" + "@typescript-eslint/utils" "5.10.0" + debug "^4.3.2" + tsutils "^3.21.0" -"@typescript-eslint/types@5.8.1": - version "5.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.8.1.tgz#04c6b49ebc8c99238238a6b8b43f2fc613983b5a" - integrity sha512-L/FlWCCgnjKOLefdok90/pqInkomLnAcF9UAzNr+DSqMC3IffzumHTQTrINXhP1gVp9zlHiYYjvozVZDPleLcA== +"@typescript-eslint/types@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.10.0.tgz#beb3cb345076f5b088afe996d57bcd1dfddaa75c" + integrity sha512-wUljCgkqHsMZbw60IbOqT/puLfyqqD5PquGiBo1u1IS3PLxdi3RDGlyf032IJyh+eQoGhz9kzhtZa+VC4eWTlQ== -"@typescript-eslint/typescript-estree@5.8.1": - version "5.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.8.1.tgz#a592855be688e7b729a1e9411d7d74ec992ed6ef" - integrity sha512-26lQ8l8tTbG7ri7xEcCFT9ijU5Fk+sx/KRRyyzCv7MQ+rZZlqiDPtMKWLC8P7o+dtCnby4c+OlxuX1tp8WfafQ== +"@typescript-eslint/typescript-estree@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.10.0.tgz#4be24a3dea0f930bb1397c46187d0efdd955a224" + integrity sha512-x+7e5IqfwLwsxTdliHRtlIYkgdtYXzE0CkFeV6ytAqq431ZyxCFzNMNR5sr3WOlIG/ihVZr9K/y71VHTF/DUQA== dependencies: - "@typescript-eslint/types" "5.8.1" - "@typescript-eslint/visitor-keys" "5.8.1" + "@typescript-eslint/types" "5.10.0" + "@typescript-eslint/visitor-keys" "5.10.0" debug "^4.3.2" globby "^11.0.4" is-glob "^4.0.3" semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/visitor-keys@5.8.1": - version "5.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.8.1.tgz#58a2c566265d5511224bc316149890451c1bbab0" - integrity sha512-SWgiWIwocK6NralrJarPZlWdr0hZnj5GXHIgfdm8hNkyKvpeQuFyLP6YjSIe9kf3YBIfU6OHSZLYkQ+smZwtNg== +"@typescript-eslint/utils@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.10.0.tgz#c3d152a85da77c400e37281355561c72fb1b5a65" + integrity sha512-IGYwlt1CVcFoE2ueW4/ioEwybR60RAdGeiJX/iDAw0t5w0wK3S7QncDwpmsM70nKgGTuVchEWB8lwZwHqPAWRg== + dependencies: + "@types/json-schema" "^7.0.9" + "@typescript-eslint/scope-manager" "5.10.0" + "@typescript-eslint/types" "5.10.0" + "@typescript-eslint/typescript-estree" "5.10.0" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" + +"@typescript-eslint/visitor-keys@5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.10.0.tgz#770215497ad67cd15a572b52089991d5dfe06281" + integrity sha512-GMxj0K1uyrFLPKASLmZzCuSddmjZVbVj3Ouy5QVuIGKZopxvOr24JsS7gruz6C3GExE01mublZ3mIBOaon9zuQ== dependencies: - "@typescript-eslint/types" "5.8.1" + "@typescript-eslint/types" "5.10.0" eslint-visitor-keys "^3.0.0" "@ungap/promise-all-settled@1.1.2": @@ -756,95 +699,95 @@ resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== -"@vue/compiler-core@3.2.26": - version "3.2.26" - resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.26.tgz#9ab92ae624da51f7b6064f4679c2d4564f437cc8" - integrity sha512-N5XNBobZbaASdzY9Lga2D9Lul5vdCIOXvUMd6ThcN8zgqQhPKfCV+wfAJNNJKQkSHudnYRO2gEB+lp0iN3g2Tw== +"@vue/compiler-core@3.2.29": + version "3.2.29" + resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.29.tgz#b06097ab8ff0493177c68c5ea5b63d379a061097" + integrity sha512-RePZ/J4Ub3sb7atQw6V6Rez+/5LCRHGFlSetT3N4VMrejqJnNPXKUt5AVm/9F5MJriy2w/VudEIvgscCfCWqxw== dependencies: "@babel/parser" "^7.16.4" - "@vue/shared" "3.2.26" + "@vue/shared" "3.2.29" estree-walker "^2.0.2" source-map "^0.6.1" -"@vue/compiler-dom@3.2.26": - version "3.2.26" - resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.26.tgz#c7a7b55d50a7b7981dd44fc28211df1450482667" - integrity sha512-smBfaOW6mQDxcT3p9TKT6mE22vjxjJL50GFVJiI0chXYGU/xzC05QRGrW3HHVuJrmLTLx5zBhsZ2dIATERbarg== +"@vue/compiler-dom@3.2.29": + version "3.2.29" + resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.29.tgz#ad0ead405bd2f2754161335aad9758aa12430715" + integrity sha512-y26vK5khdNS9L3ckvkqJk/78qXwWb75Ci8iYLb67AkJuIgyKhIOcR1E8RIt4mswlVCIeI9gQ+fmtdhaiTAtrBQ== dependencies: - "@vue/compiler-core" "3.2.26" - "@vue/shared" "3.2.26" + "@vue/compiler-core" "3.2.29" + "@vue/shared" "3.2.29" -"@vue/compiler-sfc@3.2.26": - version "3.2.26" - resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.26.tgz#3ce76677e4aa58311655a3bea9eb1cb804d2273f" - integrity sha512-ePpnfktV90UcLdsDQUh2JdiTuhV0Skv2iYXxfNMOK/F3Q+2BO0AulcVcfoksOpTJGmhhfosWfMyEaEf0UaWpIw== +"@vue/compiler-sfc@3.2.29": + version "3.2.29" + resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.29.tgz#f76d556cd5fca6a55a3ea84c88db1a2a53a36ead" + integrity sha512-X9+0dwsag2u6hSOP/XsMYqFti/edvYvxamgBgCcbSYuXx1xLZN+dS/GvQKM4AgGS4djqo0jQvWfIXdfZ2ET68g== dependencies: "@babel/parser" "^7.16.4" - "@vue/compiler-core" "3.2.26" - "@vue/compiler-dom" "3.2.26" - "@vue/compiler-ssr" "3.2.26" - "@vue/reactivity-transform" "3.2.26" - "@vue/shared" "3.2.26" + "@vue/compiler-core" "3.2.29" + "@vue/compiler-dom" "3.2.29" + "@vue/compiler-ssr" "3.2.29" + "@vue/reactivity-transform" "3.2.29" + "@vue/shared" "3.2.29" estree-walker "^2.0.2" magic-string "^0.25.7" postcss "^8.1.10" source-map "^0.6.1" -"@vue/compiler-ssr@3.2.26": - version "3.2.26" - resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.26.tgz#fd049523341fbf4ab5e88e25eef566d862894ba7" - integrity sha512-2mywLX0ODc4Zn8qBoA2PDCsLEZfpUGZcyoFRLSOjyGGK6wDy2/5kyDOWtf0S0UvtoyVq95OTSGIALjZ4k2q/ag== +"@vue/compiler-ssr@3.2.29": + version "3.2.29" + resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.29.tgz#37b15b32dcd2f6b410bb61fca3f37b1a92b7eb1e" + integrity sha512-LrvQwXlx66uWsB9/VydaaqEpae9xtmlUkeSKF6aPDbzx8M1h7ukxaPjNCAXuFd3fUHblcri8k42lfimHfzMICA== dependencies: - "@vue/compiler-dom" "3.2.26" - "@vue/shared" "3.2.26" + "@vue/compiler-dom" "3.2.29" + "@vue/shared" "3.2.29" -"@vue/reactivity-transform@3.2.26": - version "3.2.26" - resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.26.tgz#6d8f20a4aa2d19728f25de99962addbe7c4d03e9" - integrity sha512-XKMyuCmzNA7nvFlYhdKwD78rcnmPb7q46uoR00zkX6yZrUmcCQ5OikiwUEVbvNhL5hBJuvbSO95jB5zkUon+eQ== +"@vue/reactivity-transform@3.2.29": + version "3.2.29" + resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.29.tgz#a08d606e10016b7cf588d1a43dae4db2953f9354" + integrity sha512-YF6HdOuhdOw6KyRm59+3rML8USb9o8mYM1q+SH0G41K3/q/G7uhPnHGKvspzceD7h9J3VR1waOQ93CUZj7J7OA== dependencies: "@babel/parser" "^7.16.4" - "@vue/compiler-core" "3.2.26" - "@vue/shared" "3.2.26" + "@vue/compiler-core" "3.2.29" + "@vue/shared" "3.2.29" estree-walker "^2.0.2" magic-string "^0.25.7" -"@vue/reactivity@3.2.26": - version "3.2.26" - resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.26.tgz#d529191e581521c3c12e29ef986d4c8a933a0f83" - integrity sha512-h38bxCZLW6oFJVDlCcAiUKFnXI8xP8d+eO0pcDxx+7dQfSPje2AO6M9S9QO6MrxQB7fGP0DH0dYQ8ksf6hrXKQ== +"@vue/reactivity@3.2.29": + version "3.2.29" + resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.29.tgz#afdc9c111d4139b14600be17ad80267212af6052" + integrity sha512-Ryhb6Gy62YolKXH1gv42pEqwx7zs3n8gacRVZICSgjQz8Qr8QeCcFygBKYfJm3o1SccR7U+bVBQDWZGOyG1k4g== dependencies: - "@vue/shared" "3.2.26" + "@vue/shared" "3.2.29" -"@vue/runtime-core@3.2.26": - version "3.2.26" - resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.26.tgz#5c59cc440ed7a39b6dbd4c02e2d21c8d1988f0de" - integrity sha512-BcYi7qZ9Nn+CJDJrHQ6Zsmxei2hDW0L6AB4vPvUQGBm2fZyC0GXd/4nVbyA2ubmuhctD5RbYY8L+5GUJszv9mQ== +"@vue/runtime-core@3.2.29": + version "3.2.29" + resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.29.tgz#fb8577b2fcf52e8d967bd91cdf49ab9fb91f9417" + integrity sha512-VMvQuLdzoTGmCwIKTKVwKmIL0qcODIqe74JtK1pVr5lnaE0l25hopodmPag3RcnIcIXe+Ye3B2olRCn7fTCgig== dependencies: - "@vue/reactivity" "3.2.26" - "@vue/shared" "3.2.26" + "@vue/reactivity" "3.2.29" + "@vue/shared" "3.2.29" -"@vue/runtime-dom@3.2.26": - version "3.2.26" - resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.26.tgz#84d3ae2584488747717c2e072d5d9112c0d2e6c2" - integrity sha512-dY56UIiZI+gjc4e8JQBwAifljyexfVCkIAu/WX8snh8vSOt/gMSEGwPRcl2UpYpBYeyExV8WCbgvwWRNt9cHhQ== +"@vue/runtime-dom@3.2.29": + version "3.2.29" + resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.29.tgz#35e9a2bf04ef80b86ac2ca0e7b2ceaccf1e18f01" + integrity sha512-YJgLQLwr+SQyORzTsBQLL5TT/5UiV83tEotqjL7F9aFDIQdFBTCwpkCFvX9jqwHoyi9sJqM9XtTrMcc8z/OjPA== dependencies: - "@vue/runtime-core" "3.2.26" - "@vue/shared" "3.2.26" + "@vue/runtime-core" "3.2.29" + "@vue/shared" "3.2.29" csstype "^2.6.8" -"@vue/server-renderer@3.2.26": - version "3.2.26" - resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.26.tgz#f16a4b9fbcc917417b4cea70c99afce2701341cf" - integrity sha512-Jp5SggDUvvUYSBIvYEhy76t4nr1vapY/FIFloWmQzn7UxqaHrrBpbxrqPcTrSgGrcaglj0VBp22BKJNre4aA1w== +"@vue/server-renderer@3.2.29": + version "3.2.29" + resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.29.tgz#ea6afa361b9c781a868c8da18c761f9b7bc89102" + integrity sha512-lpiYx7ciV7rWfJ0tPkoSOlLmwqBZ9FTmQm33S+T4g0j1fO/LmhJ9b9Ctl1o5xvIFVDk9QkSUWANZn7H2pXuxVw== dependencies: - "@vue/compiler-ssr" "3.2.26" - "@vue/shared" "3.2.26" + "@vue/compiler-ssr" "3.2.29" + "@vue/shared" "3.2.29" -"@vue/shared@3.2.26": - version "3.2.26" - resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.26.tgz#7acd1621783571b9a82eca1f041b4a0a983481d9" - integrity sha512-vPV6Cq+NIWbH5pZu+V+2QHE9y1qfuTq49uNWw4f7FDEeZaDU2H2cx5jcUZOAKW7qTrUS4k6qZPbMy1x4N96nbA== +"@vue/shared@3.2.29": + version "3.2.29" + resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.29.tgz#07dac7051117236431d2f737d16932aa38bbb925" + integrity sha512-BjNpU8OK6Z0LVzGUppEk0CMYm/hKDnZfYdjSmPOs0N+TR1cLKJAkDwW8ASZUvaaSLEi6d3hVM7jnWnX+6yWnHw== "@webassemblyjs/ast@1.11.0": version "1.11.0" @@ -1162,6 +1105,11 @@ acorn@^8.6.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.6.0.tgz#e3692ba0eb1a0c83eaa4f37f5fa7368dd7142895" integrity sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw== +acorn@^8.7.0: + version "8.7.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" + integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== + agent-base@6: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" @@ -1358,10 +1306,10 @@ autobind-decorator@2.4.0, autobind-decorator@^2.4.0: resolved "https://registry.yarnpkg.com/autobind-decorator/-/autobind-decorator-2.4.0.tgz#ea9e1c98708cf3b5b356f7cf9f10f265ff18239c" integrity sha512-OGYhWUO72V6DafbF8PM8rm3EPbfuyMZcJhtm5/n26IDwO18pohE4eNazLoCGhPiXOCD0gEGmrbU3849QvM8bbw== -autosize@4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/autosize/-/autosize-4.0.4.tgz#924f13853a466b633b9309330833936d8bccce03" - integrity sha512-5yxLQ22O0fCRGoxGfeLSNt3J8LB1v+umtpMnPW6XjkTWXKoN0AmXAIhelJcDtFT/Y/wYWmfE+oqU10Q0b8FhaQ== +autosize@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/autosize/-/autosize-5.0.1.tgz#ed269b0fa9b7eb47627048a1bb3299e99e003a0f" + integrity sha512-UIWUlE4TOVPNNj2jjrU39wI4hEYbneUypEqcyRmRFIx5CC2gNdg3rQr+Zh7/3h6egbBvm33TDQjNQKtj9Tk1HA== autwh@0.1.0: version "0.1.0" @@ -1399,6 +1347,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + bcrypt-pbkdf@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" @@ -1426,7 +1379,7 @@ blob-util@^2.0.2: resolved "https://registry.yarnpkg.com/blob-util/-/blob-util-2.0.2.tgz#3b4e3c281111bb7f11128518006cdc60b403a1eb" integrity sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ== -bluebird@3.7.2: +bluebird@3.7.2, bluebird@^3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== @@ -1528,6 +1481,14 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== +buffer@^5.6.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + bufferutil@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.1.tgz#3a177e8e5819a1243fe16b63a199951a7ad8d4a7" @@ -1681,15 +1642,14 @@ cli-cursor@^3.1.0: dependencies: restore-cursor "^3.1.0" -cli-table3@~0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.0.tgz#b7b1bc65ca8e7b5cef9124e13dc2b21e2ce4faee" - integrity sha512-gnB85c3MGC7Nm9I/FkiasNBOKjOiO1RNuXXarQms37q4QMpWdlbBgD/VnOStA2faG1dpXMv31RFApjX1/QdgWQ== +cli-table3@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.1.tgz#36ce9b7af4847f288d3cdd081fbd09bf7bd237b8" + integrity sha512-w0q/enDHhPLq44ovMGdQeeDLvwxwavsJX7oQGYt/LrBlYsyaxyDnp6z3QzFut/6kLLKnlcUVJLrpB7KBfgG/RA== dependencies: - object-assign "^4.1.0" string-width "^4.2.0" optionalDependencies: - colors "^1.1.2" + colors "1.4.0" cli-truncate@^2.1.0: version "2.1.0" @@ -1774,7 +1734,7 @@ colorette@^2.0.14: resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.15.tgz#8e634aa0429b110d24be82eac4d42f5ea65ab2d5" integrity sha512-lIFQhufWaVvwi4wOlX9Gx5b0Nmw3XAZ8HzHNH9dfxhe+JaKNTmX6QLk4o7UHyI+tUY8ClvyfaHUm5bf61O3psA== -colors@^1.1.2: +colors@1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== @@ -1806,7 +1766,7 @@ commander@^7.2.0: resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== -commander@^8.0.0, commander@^8.2.0: +commander@^8.0.0, commander@^8.3.0: version "8.3.0" resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== @@ -1816,10 +1776,10 @@ common-tags@^1.8.0: resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937" integrity sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw== -compare-versions@3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62" - integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA== +compare-versions@4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-4.1.3.tgz#8f7b8966aef7dc4282b45dfa6be98434fc18a1a4" + integrity sha512-WQfnbDcrYnGr55UwbxKiQKASnTtNnaAWVi8jZyy8NTpVAXWACSne8lMD1iaIo9AiU6mnuLvSVshCzewVuWxHUg== concat-map@0.0.1: version "0.0.1" @@ -1972,52 +1932,52 @@ cssesc@^3.0.0: resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== -cssnano-preset-default@^5.1.9: - version "5.1.9" - resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.1.9.tgz#79628ac48eccbdad570f70b4018cc38d43d1b7df" - integrity sha512-RhkEucqlQ+OxEi14K1p8gdXcMQy1mSpo7P1oC44oRls7BYIj8p+cht4IFBFV3W4iOjTP8EUB33XV1fX9KhDzyA== +cssnano-preset-default@^5.1.10: + version "5.1.10" + resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.1.10.tgz#9350765fdf3c49bf78fac7673354fa58fa95daa4" + integrity sha512-BcpSzUVygHMOnp9uG5rfPzTOCb0GAHQkqtUQx8j1oMNF9A1Q8hziOOhiM4bdICpmrBIU85BE64RD5XGYsVQZNA== dependencies: css-declaration-sorter "^6.0.3" - cssnano-utils "^2.0.1" - postcss-calc "^8.0.0" - postcss-colormin "^5.2.2" + cssnano-utils "^3.0.0" + postcss-calc "^8.2.0" + postcss-colormin "^5.2.3" postcss-convert-values "^5.0.2" postcss-discard-comments "^5.0.1" postcss-discard-duplicates "^5.0.1" postcss-discard-empty "^5.0.1" - postcss-discard-overridden "^5.0.1" + postcss-discard-overridden "^5.0.2" postcss-merge-longhand "^5.0.4" - postcss-merge-rules "^5.0.3" - postcss-minify-font-values "^5.0.1" - postcss-minify-gradients "^5.0.3" - postcss-minify-params "^5.0.2" - postcss-minify-selectors "^5.1.0" + postcss-merge-rules "^5.0.4" + postcss-minify-font-values "^5.0.2" + postcss-minify-gradients "^5.0.4" + postcss-minify-params "^5.0.3" + postcss-minify-selectors "^5.1.1" postcss-normalize-charset "^5.0.1" - postcss-normalize-display-values "^5.0.1" - postcss-normalize-positions "^5.0.1" - postcss-normalize-repeat-style "^5.0.1" - postcss-normalize-string "^5.0.1" - postcss-normalize-timing-functions "^5.0.1" - postcss-normalize-unicode "^5.0.1" + postcss-normalize-display-values "^5.0.2" + postcss-normalize-positions "^5.0.2" + postcss-normalize-repeat-style "^5.0.2" + postcss-normalize-string "^5.0.2" + postcss-normalize-timing-functions "^5.0.2" + postcss-normalize-unicode "^5.0.2" postcss-normalize-url "^5.0.4" - postcss-normalize-whitespace "^5.0.1" - postcss-ordered-values "^5.0.2" + postcss-normalize-whitespace "^5.0.2" + postcss-ordered-values "^5.0.3" postcss-reduce-initial "^5.0.2" - postcss-reduce-transforms "^5.0.1" + postcss-reduce-transforms "^5.0.2" postcss-svgo "^5.0.3" postcss-unique-selectors "^5.0.2" -cssnano-utils@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-2.0.1.tgz#8660aa2b37ed869d2e2f22918196a9a8b6498ce2" - integrity sha512-i8vLRZTnEH9ubIyfdZCAdIdgnHAUeQeByEeQ2I7oTilvP9oHO6RScpeq3GsFUVqeB8uZgOQ9pw8utofNn32hhQ== +cssnano-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-3.0.0.tgz#c0b9fcd6e4f05c5155b07e9ab11bf94b97163057" + integrity sha512-Pzs7/BZ6OgT+tXXuF12DKR8SmSbzUeVYCtMBbS8lI0uAm3mrYmkyqCXXPsQESI6kmLfEVBppbdVY/el3hg3nAA== -cssnano@5.0.14: - version "5.0.14" - resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.0.14.tgz#99bc550f663b48c38e9b8e0ae795697c9de84b47" - integrity sha512-qzhRkFvBhv08tbyKCIfWbxBXmkIpLl1uNblt8SpTHkgLfON5OCPX/CCnkdNmEosvo8bANQYmTTMEgcVBlisHaw== +cssnano@5.0.15: + version "5.0.15" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.0.15.tgz#8779eaf60e3665e6a12687c814d375cc9f78db76" + integrity sha512-ppZsS7oPpi2sfiyV5+i+NbB/3GtQ+ab2Vs1azrZaXWujUSN4o+WdTxlCZIMcT9yLW3VO/5yX3vpyDaQ1nIn8CQ== dependencies: - cssnano-preset-default "^5.1.9" + cssnano-preset-default "^5.1.10" lilconfig "^2.0.3" yaml "^1.10.2" @@ -2040,24 +2000,25 @@ csstype@^2.6.8: resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.13.tgz#a6893015b90e84dd6e85d0e3b442a1e84f2dbe0f" integrity sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A== -cypress@9.2.0: - version "9.2.0" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.2.0.tgz#727c20b4662167890db81d5f6ba615231835b17d" - integrity sha512-Jn26Tprhfzh/a66Sdj9SoaYlnNX6Mjfmj5PHu2a7l3YHXhrgmavM368wjCmgrxC6KHTOv9SpMQGhAJn+upDViA== +cypress@9.3.1: + version "9.3.1" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.3.1.tgz#8116f52d49d6daf90a91e88f3eafd940234d2958" + integrity sha512-BODdPesxX6bkVUnH8BVsV8I/jn57zQtO1FEOUTiuG2us3kslW7g0tcuwiny7CKCmJUZz8S/D587ppC+s58a+5Q== dependencies: "@cypress/request" "^2.88.10" "@cypress/xvfb" "^1.2.4" "@types/node" "^14.14.31" - "@types/sinonjs__fake-timers" "^6.0.2" + "@types/sinonjs__fake-timers" "8.1.1" "@types/sizzle" "^2.3.2" arch "^2.2.0" blob-util "^2.0.2" - bluebird "3.7.2" + bluebird "^3.7.2" + buffer "^5.6.0" cachedir "^2.3.0" chalk "^4.1.0" check-more-types "^2.24.0" cli-cursor "^3.1.0" - cli-table3 "~0.6.0" + cli-table3 "~0.6.1" commander "^5.1.0" common-tags "^1.8.0" dayjs "^1.10.4" @@ -2107,11 +2068,6 @@ date-fns@2.28.0: resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.28.0.tgz#9570d656f5fc13143e50c975a3b6bbeb46cd08b2" integrity sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw== -dateformat@4.5.1: - version "4.5.1" - resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-4.5.1.tgz#c20e7a9ca77d147906b6dc2261a8be0a5bd2173c" - integrity sha512-OD0TZ+B7yP7ZgpJf5K2DIbj3FZvFvxgFUuaqA/V5zTjAtAAXZ1E8bktHxmAGs4x5b7PflqA9LeQ84Og7wYtF7Q== - dayjs@^1.10.4: version "1.10.6" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.6.tgz#288b2aa82f2d8418a6c9d4df5898c0737ad02a63" @@ -2367,7 +2323,7 @@ enhanced-resolve@^5.8.3: graceful-fs "^4.2.4" tapable "^2.2.0" -enquirer@^2.3.5, enquirer@^2.3.6: +enquirer@^2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== @@ -2507,38 +2463,37 @@ eslint-import-resolver-node@^0.3.6: debug "^3.2.7" resolve "^1.20.0" -eslint-module-utils@^2.7.1: - version "2.7.1" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.1.tgz#b435001c9f8dd4ab7f6d0efcae4b9696d4c24b7c" - integrity sha512-fjoetBXQZq2tSTWZ9yWVl2KuFrTZZH3V+9iD1V1RfpDgxzJR+mPd/KZmMiA8gbPqdBzpNiEHOuT7IYEWxrH0zQ== +eslint-module-utils@^2.7.2: + version "2.7.2" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.2.tgz#1d0aa455dcf41052339b63cada8ab5fd57577129" + integrity sha512-zquepFnWCY2ISMFwD/DqzaM++H+7PDzOpUvotJWm/y1BAFt5R4oeULgdrTejKqLkz7MA/tgstsUMNYc7wNdTrg== dependencies: debug "^3.2.7" find-up "^2.1.0" - pkg-dir "^2.0.0" -eslint-plugin-import@2.25.3: - version "2.25.3" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.25.3.tgz#a554b5f66e08fb4f6dc99221866e57cfff824766" - integrity sha512-RzAVbby+72IB3iOEL8clzPLzL3wpDrlwjsTBAQXgyp5SeTqqY+0bFubwuo+y/HLhNZcXV4XqTBO4LGsfyHIDXg== +eslint-plugin-import@2.25.4: + version "2.25.4" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz#322f3f916a4e9e991ac7af32032c25ce313209f1" + integrity sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA== dependencies: array-includes "^3.1.4" array.prototype.flat "^1.2.5" debug "^2.6.9" doctrine "^2.1.0" eslint-import-resolver-node "^0.3.6" - eslint-module-utils "^2.7.1" + eslint-module-utils "^2.7.2" has "^1.0.3" is-core-module "^2.8.0" is-glob "^4.0.3" minimatch "^3.0.4" object.values "^1.1.5" resolve "^1.20.0" - tsconfig-paths "^3.11.0" + tsconfig-paths "^3.12.0" -eslint-plugin-vue@8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-8.2.0.tgz#b404bc10e3f43b2b7aad4ebb3b38090a58040202" - integrity sha512-cLIdTuOAMXyHeQ4drYKcZfoyzdwdBpH279X8/N0DgmotEI9yFKb5O/cAgoie/CkQZCH/MOmh0xw/KEfS90zY2A== +eslint-plugin-vue@8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-8.3.0.tgz#2ae4f915ed3541a58c4a4c1145c1e60b86aa7e85" + integrity sha512-IIuLHw4vQxGlHcoP2dG6t/2OVdQf2qoyAzEGAxreU1afZOHGA7y3TWq8I+r3ZA6Wjs6xpeUWGHlT31QGr9Rb5g== dependencies: eslint-utils "^3.0.0" natural-compare "^1.4.0" @@ -2591,10 +2546,15 @@ eslint-visitor-keys@^3.1.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.1.0.tgz#eee4acea891814cda67a7d8812d9647dd0179af2" integrity sha512-yWJFpu4DtjsWKkt5GeNBBuZMlNcYVs6vRCLoCVEJrTjaSB6LC98gFipNK/erM2Heg/E8mIK+hXG/pJMLK+eRZA== -eslint@8.5.0: - version "8.5.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.5.0.tgz#ddd2c1afd8f412036f87ae2a063d2aa296d3175f" - integrity sha512-tVGSkgNbOfiHyVte8bCM8OmX+xG9PzVG/B4UCF60zx7j61WIVY/AqJECDgpLD4DbbESD0e174gOg3ZlrX15GDg== +eslint-visitor-keys@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.2.0.tgz#6fbb166a6798ee5991358bc2daa1ba76cc1254a1" + integrity sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ== + +eslint@8.7.0: + version "8.7.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.7.0.tgz#22e036842ee5b7cf87b03fe237731675b4d3633c" + integrity sha512-ifHYzkBGrzS2iDU7KjhCAVMGCvF6M3Xfs8X8b37cgrUlDt6bWRTpRh6T/gtSXv1HJ/BUGgmjvNvOEGu85Iif7w== dependencies: "@eslint/eslintrc" "^1.0.5" "@humanwhocodes/config-array" "^0.9.2" @@ -2603,12 +2563,11 @@ eslint@8.5.0: cross-spawn "^7.0.2" debug "^4.3.2" doctrine "^3.0.0" - enquirer "^2.3.5" escape-string-regexp "^4.0.0" eslint-scope "^7.1.0" eslint-utils "^3.0.0" - eslint-visitor-keys "^3.1.0" - espree "^9.2.0" + eslint-visitor-keys "^3.2.0" + espree "^9.3.0" esquery "^1.4.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" @@ -2616,7 +2575,7 @@ eslint@8.5.0: functional-red-black-tree "^1.0.1" glob-parent "^6.0.1" globals "^13.6.0" - ignore "^4.0.6" + ignore "^5.2.0" import-fresh "^3.0.0" imurmurhash "^0.1.4" is-glob "^4.0.0" @@ -2627,9 +2586,7 @@ eslint@8.5.0: minimatch "^3.0.4" natural-compare "^1.4.0" optionator "^0.9.1" - progress "^2.0.0" regexpp "^3.2.0" - semver "^7.2.1" strip-ansi "^6.0.1" strip-json-comments "^3.1.0" text-table "^0.2.0" @@ -2653,6 +2610,15 @@ espree@^9.2.0: acorn-jsx "^5.3.1" eslint-visitor-keys "^3.1.0" +espree@^9.3.0: + version "9.3.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.0.tgz#c1240d79183b72aaee6ccfa5a90bc9111df085a8" + integrity sha512-d/5nCsb0JcqsSEeQzFZ8DH1RmxPcglRWh24EFTlUEmCKoehXGdpsx0RkHDubqUI8LSAIKMQp4r9SzQ3n+sm4HQ== + dependencies: + acorn "^8.7.0" + acorn-jsx "^5.3.1" + eslint-visitor-keys "^3.1.0" + esprima@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" @@ -3182,6 +3148,11 @@ graceful-fs@^4.2.0: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== +graceful-fs@^4.2.9: + version "4.2.9" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" + integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== + growl@1.10.5: version "1.10.5" resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" @@ -3299,12 +3270,17 @@ icss-utils@^5.0.0, icss-utils@^5.1.0: resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== -idb-keyval@5.1.3: - version "5.1.3" - resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-5.1.3.tgz#6ef5dff371897c23f144322dc6374eadd6a345d9" - integrity sha512-N9HbCK/FaXSRVI+k6Xq4QgWxbcZRUv+SfG1y7HJ28JdV8yEJu6k+C/YLea7npGckX2DQJeEVuMc4bKOBeU/2LQ== +idb-keyval@6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-6.1.0.tgz#e659cff41188e6097d7fadd69926f6adbbe70041" + integrity sha512-u/qHZ75rlD3gH+Zah8dAJVJcGW/RfCnfNrFkElC5RpRCnpsCXXhqjVk+6MoVKJ3WhmNbRYdI6IIVP88e+5sxGw== dependencies: - safari-14-idb-fix "^1.0.4" + safari-14-idb-fix "^3.0.0" + +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== ignore@^4.0.6: version "4.0.6" @@ -3321,6 +3297,11 @@ ignore@^5.1.8: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.9.tgz#9ec1a5cbe8e1446ec60d4420060d43aa6e7382fb" integrity sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ== +ignore@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" + integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== + immutable@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.0.0.tgz#b86f78de6adef3608395efb269a91462797e2c23" @@ -3727,7 +3708,7 @@ js-yaml@4.0.0: dependencies: argparse "^2.0.1" -js-yaml@^3.13.1, js-yaml@^3.14.1: +js-yaml@^3.13.1: version "3.14.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== @@ -3877,10 +3858,10 @@ jws@^4.0.0: jwa "^2.0.0" safe-buffer "^5.0.1" -katex@0.15.1: - version "0.15.1" - resolved "https://registry.yarnpkg.com/katex/-/katex-0.15.1.tgz#cf4ce2fa1257c3279cc7a7fe0c8d1fab40800893" - integrity sha512-KIk+gizli0gl1XaJlCYS8/donGMbzXYTka6BbH3AgvDJTOwyDY4hJ+YmzJ1F0y/3XzX5B9ED8AqB2Hmn2AZ0uA== +katex@0.15.2: + version "0.15.2" + resolved "https://registry.yarnpkg.com/katex/-/katex-0.15.2.tgz#c05ece41ab497597b17abca2cecde3e4c0127f9d" + integrity sha512-FfZ/f6f8bQdLmJ3McXDNTkKenQkoXkItpW0I9bsG2wgb+8JAY5bwpXFtI8ZVrg5hc1wo1X/UIhdkVMpok46tEQ== dependencies: commander "^8.0.0" @@ -4069,10 +4050,10 @@ map-stream@~0.1.0: resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.1.0.tgz#e56aa94c4c8055a16404a0674b78f215f7c8e194" integrity sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ= -matter-js@0.17.1: - version "0.17.1" - resolved "https://registry.yarnpkg.com/matter-js/-/matter-js-0.17.1.tgz#b30ac4c708116258fbcaacd2efd8a94e34a91c7f" - integrity sha512-pSquoENJgvSAlQGcA0s5UkmEohGXZaUww2g3B6qG87x0iEcVf+aigMXn5UkFPdnh6w3B+C4vXSLaYqhHwKrOLA== +matter-js@0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/matter-js/-/matter-js-0.18.0.tgz#083ced04eb6768f7664dc7ca8948a10e46ad3ed6" + integrity sha512-/ZVem4WygUnbmo/iE4oHZpZS97btfBtYy5Iwn1396vUZU7YhgVEN8J4UWwfZwY1ZqoTYlPgjvSw9WXauuXL0mg== mdn-data@2.0.14: version "2.0.14" @@ -4104,10 +4085,10 @@ merge@^2.1.0: resolved "https://registry.yarnpkg.com/merge/-/merge-2.1.1.tgz#59ef4bf7e0b3e879186436e8481c06a6c162ca98" integrity sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w== -mfm-js@0.20.0: - version "0.20.0" - resolved "https://registry.yarnpkg.com/mfm-js/-/mfm-js-0.20.0.tgz#3afdcd7959461fd825aa8af9b9e8a57cdbddc290" - integrity sha512-1+3tV3nWUKQNh/ztX3wXu5iLBtdsg6q3wUhl+XyOhc2H3sQdG+sih/w2c0nR9TIawjN+Z1/pvgGzxMJHfmKQmA== +mfm-js@0.21.0: + version "0.21.0" + resolved "https://registry.yarnpkg.com/mfm-js/-/mfm-js-0.21.0.tgz#954cc6e7071700b0b1872c78a90bada10be7f772" + integrity sha512-nyQXaipa7rmAw9ER9uYigMvGcdCwhSv93abZBwccnSnPOc1W3S/WW0+sN28g3YSmlHDCA0i2q9aAFc9EgOi5KA== dependencies: twemoji-parser "13.1.x" @@ -4158,10 +4139,10 @@ minimist@^1.2.0, minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -misskey-js@0.0.10: - version "0.0.10" - resolved "https://registry.yarnpkg.com/misskey-js/-/misskey-js-0.0.10.tgz#f305dd37cecfbaeb7a277d5e0c769ca12c6eb9a6" - integrity sha512-2rdqFrCOwggMKYitsUPyupesqCNpNooqEHQQRfdjttbhiqLbNFJE1UuWQ04ffmiJ08UJt+1ZN2kVAYNEN3HRsg== +misskey-js@0.0.13: + version "0.0.13" + resolved "https://registry.yarnpkg.com/misskey-js/-/misskey-js-0.0.13.tgz#03a4e469186e28752d599dc4093519eb64647970" + integrity sha512-kBdJdfe281gtykzzsrN3IAxWUQIimzPiJGyKWf863ggWJlWYVPmP9hTFlX2z8oPOaypgVBPEPHyw/jNUdc2DbQ== dependencies: autobind-decorator "^2.4.0" eventemitter3 "^4.0.7" @@ -4220,7 +4201,7 @@ ms@2.1.3, ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -mylas@^2.1.4: +mylas@^2.1.6: version "2.1.6" resolved "https://registry.yarnpkg.com/mylas/-/mylas-2.1.6.tgz#40f3ac6faf77b966c2c2f7b9c0d21ea65b3d9800" integrity sha512-5ggCu4hVRJZE6NpQ309y6ArykK5vujK6LfSAXvsrmBNSX/9Gfq7D9zjxhHyjSR/sbFzCe2hI9LO1EY9KXv/XkQ== @@ -4328,7 +4309,7 @@ oauth@0.9.15: resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1" integrity sha1-vR/vr2hslrdUda7VGWQS/2DPucE= -object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -4591,9 +4572,9 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -"photoswipe@git://github.com/dimsemenov/photoswipe#v5-beta": +"photoswipe@git+https://github.com/dimsemenov/photoswipe#v5-beta": version "5.1.7" - resolved "git://github.com/dimsemenov/photoswipe#60040164333bd257409669e715e4327afdb3aec7" + resolved "git+https://github.com/dimsemenov/photoswipe#60040164333bd257409669e715e4327afdb3aec7" picocolors@^1.0.0: version "1.0.0" @@ -4610,13 +4591,6 @@ pify@^2.2.0: resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= -pkg-dir@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" - integrity sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s= - dependencies: - find-up "^2.1.0" - pkg-dir@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" @@ -4624,6 +4598,11 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +pluralize@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" + integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== + pngjs@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb" @@ -4637,18 +4616,18 @@ portscanner@2.2.0: async "^2.6.0" is-number-like "^1.0.3" -postcss-calc@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.0.0.tgz#a05b87aacd132740a5db09462a3612453e5df90a" - integrity sha512-5NglwDrcbiy8XXfPM11F3HeC6hoT9W7GUH/Zi5U/p7u3Irv4rHhdDcIZwG0llHXV4ftsBjpfWMXAnXNl4lnt8g== +postcss-calc@^8.2.0: + version "8.2.2" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.2.2.tgz#9706e7399e8ec8b61a47830dcf1f21391af23373" + integrity sha512-B5R0UeB4zLJvxNt1FVCaDZULdzsKLPc6FhjFJ+xwFiq7VG4i9cuaJLxVjNtExNK8ocm3n2o4unXXLiVX1SCqxA== dependencies: postcss-selector-parser "^6.0.2" postcss-value-parser "^4.0.2" -postcss-colormin@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.2.2.tgz#019cd6912bef9e7e0924462c5e4ffae241e2f437" - integrity sha512-tSEe3NpqWARUTidDlF0LntPkdlhXqfDFuA1yslqpvvGAfpZ7oBaw+/QXd935NKm2U9p4PED0HDZlzmMk7fVC6g== +postcss-colormin@^5.2.3: + version "5.2.3" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.2.3.tgz#da7fb80e81ad80d2867ea9e38672a892add5df15" + integrity sha512-dra4xoAjub2wha6RUXAgadHEn2lGxbj8drhFcIGLOMn914Eu7DkPUurugDXgstwttCYkJtZ/+PkWRWdp3UHRIA== dependencies: browserslist "^4.16.6" caniuse-api "^3.0.0" @@ -4677,10 +4656,10 @@ postcss-discard-empty@^5.0.1: resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-5.0.1.tgz#ee136c39e27d5d2ed4da0ee5ed02bc8a9f8bf6d8" integrity sha512-vfU8CxAQ6YpMxV2SvMcMIyF2LX1ZzWpy0lqHDsOdaKKLQVQGVP1pzhrI9JlsO65s66uQTfkQBKBD/A5gp9STFw== -postcss-discard-overridden@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.0.1.tgz#454b41f707300b98109a75005ca4ab0ff2743ac6" - integrity sha512-Y28H7y93L2BpJhrdUR2SR2fnSsT+3TVx1NmVQLbcnZWwIUpJ7mfcTC6Za9M2PG6w8j7UQRfzxqn8jU2VwFxo3Q== +postcss-discard-overridden@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.0.2.tgz#e6f51d83e66feffcf05ed94c4ad20b814d0aab5f" + integrity sha512-+56BLP6NSSUuWUXjRgAQuho1p5xs/hU5Sw7+xt9S3JSg+7R6+WMGnJW7Hre/6tTuZ2xiXMB42ObkiZJ2hy/Pew== postcss-loader@6.2.1: version "6.2.1" @@ -4699,46 +4678,46 @@ postcss-merge-longhand@^5.0.4: postcss-value-parser "^4.1.0" stylehacks "^5.0.1" -postcss-merge-rules@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.0.3.tgz#b5cae31f53129812a77e3eb1eeee448f8cf1a1db" - integrity sha512-cEKTMEbWazVa5NXd8deLdCnXl+6cYG7m2am+1HzqH0EnTdy8fRysatkaXb2dEnR+fdaDxTvuZ5zoBdv6efF6hg== +postcss-merge-rules@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.0.4.tgz#a50640fd832380f322bd2861a9b33fbde4219f9b" + integrity sha512-yOj7bW3NxlQxaERBB0lEY1sH5y+RzevjbdH4DBJurjKERNpknRByFNdNe+V72i5pIZL12woM9uGdS5xbSB+kDQ== dependencies: browserslist "^4.16.6" caniuse-api "^3.0.0" - cssnano-utils "^2.0.1" + cssnano-utils "^3.0.0" postcss-selector-parser "^6.0.5" -postcss-minify-font-values@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.0.1.tgz#a90cefbfdaa075bd3dbaa1b33588bb4dc268addf" - integrity sha512-7JS4qIsnqaxk+FXY1E8dHBDmraYFWmuL6cgt0T1SWGRO5bzJf8sUoelwa4P88LEWJZweHevAiDKxHlofuvtIoA== +postcss-minify-font-values@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.0.2.tgz#4603e956d85cd0719156e2b3eb68e3cd2f917092" + integrity sha512-R6MJZryq28Cw0AmnyhXrM7naqJZZLoa1paBltIzh2wM7yb4D45TLur+eubTQ4jCmZU9SGeZdWsc5KcSoqTMeTg== dependencies: - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-minify-gradients@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.0.3.tgz#f970a11cc71e08e9095e78ec3a6b34b91c19550e" - integrity sha512-Z91Ol22nB6XJW+5oe31+YxRsYooxOdFKcbOqY/V8Fxse1Y3vqlNRpi1cxCqoACZTQEhl+xvt4hsbWiV5R+XI9Q== +postcss-minify-gradients@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.0.4.tgz#f13146950513f5a201015306914e3c76d10b591d" + integrity sha512-RVwZA7NC4R4J76u8X0Q0j+J7ItKUWAeBUJ8oEEZWmtv3Xoh19uNJaJwzNpsydQjk6PkuhRrK+YwwMf+c+68EYg== dependencies: colord "^2.9.1" - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + cssnano-utils "^3.0.0" + postcss-value-parser "^4.2.0" -postcss-minify-params@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.0.2.tgz#1b644da903473fbbb18fbe07b8e239883684b85c" - integrity sha512-qJAPuBzxO1yhLad7h2Dzk/F7n1vPyfHfCCh5grjGfjhi1ttCnq4ZXGIW77GSrEbh9Hus9Lc/e/+tB4vh3/GpDg== +postcss-minify-params@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.0.3.tgz#9f933d37098ef1dcf007e159a47bb2c1cf06989d" + integrity sha512-NY92FUikE+wralaiVexFd5gwb7oJTIDhgTNeIw89i1Ymsgt4RWiPXfz3bg7hDy4NL6gepcThJwOYNtZO/eNi7Q== dependencies: alphanum-sort "^1.0.2" browserslist "^4.16.6" - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + cssnano-utils "^3.0.0" + postcss-value-parser "^4.2.0" -postcss-minify-selectors@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.1.0.tgz#4385c845d3979ff160291774523ffa54eafd5a54" - integrity sha512-NzGBXDa7aPsAcijXZeagnJBKBPMYLaJJzB8CQh6ncvyl2sIndLVWfbcDi0SBjRWk5VqEjXvf8tYwzoKf4Z07og== +postcss-minify-selectors@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.1.1.tgz#20ae03b411f7fb397451e3d7d85b989f944b871c" + integrity sha512-TOzqOPXt91O2luJInaVPiivh90a2SIK5Nf1Ea7yEIM/5w+XA5BGrZGUSW8aEx9pJ/oNj7ZJBhjvigSiBV+bC1Q== dependencies: alphanum-sort "^1.0.2" postcss-selector-parser "^6.0.5" @@ -4776,51 +4755,48 @@ postcss-normalize-charset@^5.0.1: resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-5.0.1.tgz#121559d1bebc55ac8d24af37f67bd4da9efd91d0" integrity sha512-6J40l6LNYnBdPSk+BHZ8SF+HAkS4q2twe5jnocgd+xWpz/mx/5Sa32m3W1AA8uE8XaXN+eg8trIlfu8V9x61eg== -postcss-normalize-display-values@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.0.1.tgz#62650b965981a955dffee83363453db82f6ad1fd" - integrity sha512-uupdvWk88kLDXi5HEyI9IaAJTE3/Djbcrqq8YgjvAVuzgVuqIk3SuJWUisT2gaJbZm1H9g5k2w1xXilM3x8DjQ== +postcss-normalize-display-values@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.0.2.tgz#8b5273c6c7d0a445e6ef226b8a5bb3204a55fb99" + integrity sha512-RxXoJPUR0shSjkMMzgEZDjGPrgXUVYyWA/YwQRicb48H15OClPuaDR7tYokLAlGZ2tCSENEN5WxjgxSD5m4cUw== dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-normalize-positions@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.0.1.tgz#868f6af1795fdfa86fbbe960dceb47e5f9492fe5" - integrity sha512-rvzWAJai5xej9yWqlCb1OWLd9JjW2Ex2BCPzUJrbaXmtKtgfL8dBMOOMTX6TnvQMtjk3ei1Lswcs78qKO1Skrg== +postcss-normalize-positions@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.0.2.tgz#799fa494b352a5da183be8f050024af6d92fa29c" + integrity sha512-tqghWFVDp2btqFg1gYob1etPNxXLNh3uVeWgZE2AQGh6b2F8AK2Gj36v5Vhyh+APwIzNjmt6jwZ9pTBP+/OM8g== dependencies: - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-normalize-repeat-style@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.0.1.tgz#cbc0de1383b57f5bb61ddd6a84653b5e8665b2b5" - integrity sha512-syZ2itq0HTQjj4QtXZOeefomckiV5TaUO6ReIEabCh3wgDs4Mr01pkif0MeVwKyU/LHEkPJnpwFKRxqWA/7O3w== +postcss-normalize-repeat-style@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.0.2.tgz#fd9bddba3e6fd5f5d95c18dfb42a09ecd563adea" + integrity sha512-/rIZn8X9bBzC7KvY4iKUhXUGW3MmbXwfPF23jC9wT9xTi7kAvgj8sEgwxjixBmoL6MVa4WOgxNz2hAR6wTK8tw== dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-normalize-string@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.0.1.tgz#d9eafaa4df78c7a3b973ae346ef0e47c554985b0" - integrity sha512-Ic8GaQ3jPMVl1OEn2U//2pm93AXUcF3wz+OriskdZ1AOuYV25OdgS7w9Xu2LO5cGyhHCgn8dMXh9bO7vi3i9pA== +postcss-normalize-string@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.0.2.tgz#1b2bbf91526f61266f28abf7f773e4136b2c4bd2" + integrity sha512-zaI1yzwL+a/FkIzUWMQoH25YwCYxi917J4pYm1nRXtdgiCdnlTkx5eRzqWEC64HtRa06WCJ9TIutpb6GmW4gFw== dependencies: - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-normalize-timing-functions@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.0.1.tgz#8ee41103b9130429c6cbba736932b75c5e2cb08c" - integrity sha512-cPcBdVN5OsWCNEo5hiXfLUnXfTGtSFiBU9SK8k7ii8UD7OLuznzgNRYkLZow11BkQiiqMcgPyh4ZqXEEUrtQ1Q== +postcss-normalize-timing-functions@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.0.2.tgz#db4f4f49721f47667afd1fdc5edb032f8d9cdb2e" + integrity sha512-Ao0PP6MoYsRU1LxeVUW740ioknvdIUmfr6uAA3xWlQJ9s69/Tupy8qwhuKG3xWfl+KvLMAP9p2WXF9cwuk/7Bg== dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-normalize-unicode@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.0.1.tgz#82d672d648a411814aa5bf3ae565379ccd9f5e37" - integrity sha512-kAtYD6V3pK0beqrU90gpCQB7g6AOfP/2KIPCVBKJM2EheVsBQmx/Iof+9zR9NFKLAx4Pr9mDhogB27pmn354nA== +postcss-normalize-unicode@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.0.2.tgz#c4db89a0116066716b9e9fcb6444ce63178f5ced" + integrity sha512-3y/V+vjZ19HNcTizeqwrbZSUsE69ZMRHfiiyLAJb7C7hJtYmM4Gsbajy7gKagu97E8q5rlS9k8FhojA8cpGhWw== dependencies: - browserslist "^4.16.0" - postcss-value-parser "^4.1.0" + browserslist "^4.16.6" + postcss-value-parser "^4.2.0" postcss-normalize-url@^5.0.4: version "5.0.4" @@ -4830,20 +4806,20 @@ postcss-normalize-url@^5.0.4: normalize-url "^6.0.1" postcss-value-parser "^4.2.0" -postcss-normalize-whitespace@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.0.1.tgz#b0b40b5bcac83585ff07ead2daf2dcfbeeef8e9a" - integrity sha512-iPklmI5SBnRvwceb/XH568yyzK0qRVuAG+a1HFUsFRf11lEJTiQQa03a4RSCQvLKdcpX7XsI1Gen9LuLoqwiqA== +postcss-normalize-whitespace@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.0.2.tgz#92c5eaffe5255b5c43fca0baf19227e607c534db" + integrity sha512-CXBx+9fVlzSgbk0IXA/dcZn9lXixnQRndnsPC5ht3HxlQ1bVh77KQDL1GffJx1LTzzfae8ftMulsjYmO2yegxA== dependencies: - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-ordered-values@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.0.2.tgz#1f351426977be00e0f765b3164ad753dac8ed044" - integrity sha512-8AFYDSOYWebJYLyJi3fyjl6CqMEG/UVworjiyK1r573I56kb3e879sCJLGvR3merj+fAdPpVplXKQZv+ey6CgQ== +postcss-ordered-values@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.0.3.tgz#d80a8565f2e21efe8a06abacd60629a783bbcf54" + integrity sha512-T9pDS+P9bWeFvqivXd5ACzQmrCmHjv3ZP+djn8E1UZY7iK79pFSm7i3WbKw2VSmFmdbMm8sQ12OPcNpzBo3Z2w== dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + cssnano-utils "^3.0.0" + postcss-value-parser "^4.2.0" postcss-reduce-initial@^5.0.2: version "5.0.2" @@ -4853,13 +4829,12 @@ postcss-reduce-initial@^5.0.2: browserslist "^4.16.6" caniuse-api "^3.0.0" -postcss-reduce-transforms@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.0.1.tgz#93c12f6a159474aa711d5269923e2383cedcf640" - integrity sha512-a//FjoPeFkRuAguPscTVmRQUODP+f3ke2HqFNgGPwdYnpeC29RZdCBvGRGTsKpMURb/I3p6jdKoBQ2zI+9Q7kA== +postcss-reduce-transforms@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.0.2.tgz#9242758629f9ad4d90312eadbc921259d15bee4d" + integrity sha512-25HeDeFsgiPSUx69jJXZn8I06tMxLQJJNF5h7i9gsUg8iP4KOOJ8EX8fj3seeoLt3SLU2YDD6UPnDYVGUO7DEA== dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4: version "6.0.4" @@ -4947,10 +4922,10 @@ printj@~1.1.0: resolved "https://registry.yarnpkg.com/printj/-/printj-1.1.2.tgz#d90deb2975a8b9f600fb3a1c94e3f4c53c78a222" integrity sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ== -prismjs@1.25.0: - version "1.25.0" - resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.25.0.tgz#6f822df1bdad965734b310b315a23315cf999756" - integrity sha512-WCjJHl1KEWbnkQom1+SzftbtXMKQoezOCYs5rECqMN+jP+apI7ftoflyqigqzopSO3hMhTEb0mFClA8lkolgEg== +prismjs@1.26.0: + version "1.26.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.26.0.tgz#16881b594828bb6b45296083a8cbab46b0accd47" + integrity sha512-HUoH9C5Z3jKkl3UunCyiD5jwk0+Hz0fIgQ2nbwU2Oo/ceuTAQAg+pPVnfdt2TJWRVLcxKh9iuoYDUSc8clb5UQ== private-ip@2.3.3: version "2.3.3" @@ -4962,11 +4937,6 @@ private-ip@2.3.3: is-ip "^3.1.0" netmask "^2.0.2" -progress@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== - promise-limit@2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/promise-limit/-/promise-limit-2.7.0.tgz#eb5737c33342a030eaeaecea9b3d3a93cb592b26" @@ -5323,10 +5293,10 @@ s-age@1.1.2: resolved "https://registry.yarnpkg.com/s-age/-/s-age-1.1.2.tgz#c0cf15233ccc93f41de92ea42c36d957977d1ea2" integrity sha512-aSN2TlF39WLoZA/6cgYSJZhKt63kJ4EaadejPWjWY9/h4rksIqvfWY3gfd+3uAegSM1IXsA9aWeEhJtkxkFQtA== -safari-14-idb-fix@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/safari-14-idb-fix/-/safari-14-idb-fix-1.0.4.tgz#5c68ba63e2a8ae0d89a0aa1e13fe89e3aef7da19" - integrity sha512-4+Y2baQdgJpzu84d0QjySl70Kyygzf0pepVg8NVg4NnQEPpfC91fAn0baNvtStlCjUUxxiu0BOMiafa98fRRuA== +safari-14-idb-fix@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/safari-14-idb-fix/-/safari-14-idb-fix-3.0.0.tgz#450fc049b996ec7f3fd9ca2f89d32e0761583440" + integrity sha512-eBNFLob4PMq8JA1dGyFn6G97q3/WzNtFK4RnzT1fnLq+9RyrGknzYiM/9B12MnKAxuj1IXr7UKYtTNtjyKMBog== safe-buffer@5.2.1: version "5.2.1" @@ -5351,10 +5321,10 @@ sass-loader@12.4.0: klona "^2.0.4" neo-async "^2.6.2" -sass@1.45.1: - version "1.45.1" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.45.1.tgz#fa03951f924d1ba5762949567eaf660e608a1ab0" - integrity sha512-pwPRiq29UR0o4X3fiQyCtrESldXvUQAAE0QmcJTpsI4kuHHcLzZ54M1oNBVIXybQv8QF2zfkpFcTxp8ta97dUA== +sass@1.49.0: + version "1.49.0" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.49.0.tgz#65ec1b1d9a6bc1bae8d2c9d4b392c13f5d32c078" + integrity sha512-TVwVdNDj6p6b4QymJtNtRS2YtLJ/CqZriGg0eIAbAKMlN8Xy6kbv33FsEZSF7FufFFM705SQviHjjThfaQ4VNw== dependencies: chokidar ">=3.0.0 <4.0.0" immutable "^4.0.0" @@ -5393,7 +5363,7 @@ seedrandom@3.0.5: resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7" integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg== -semver@^7.2.1, semver@^7.3.2, semver@^7.3.4: +semver@^7.3.2, semver@^7.3.4: version "7.3.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== @@ -5825,10 +5795,10 @@ textarea-caret@3.1.0: resolved "https://registry.yarnpkg.com/textarea-caret/-/textarea-caret-3.1.0.tgz#5d5a35bb035fd06b2ff0e25d5359e97f2655087f" integrity sha512-cXAvzO9pP5CGa6NKx0WYHl+8CHKZs8byMkt3PCJBCmq2a34YA9pO1NrQET5pzeqnBjBdToF5No4rrmkDUgQC2Q== -three@0.117.1: - version "0.117.1" - resolved "https://registry.yarnpkg.com/three/-/three-0.117.1.tgz#a49bcb1a6ddea2f250003e42585dc3e78e92b9d3" - integrity sha512-t4zeJhlNzUIj9+ub0l6nICVimSuRTZJOqvk3Rmlu+YGdTOJ49Wna8p7aumpkXJakJfITiybfpYE1XN1o1Z34UQ== +three@0.136.0: + version "0.136.0" + resolved "https://registry.yarnpkg.com/three/-/three-0.136.0.tgz#b1504db021b46398ef468aa7849f3dcabb814f50" + integrity sha512-+fEMX7nYLz2ZesVP/dyifli5Jf8gR3XPAnFJveQ80aMhibFduzrADnjMbARXh8+W9qLK7rshJCjAIL/6cDxC+A== throttle-debounce@3.0.1: version "3.0.1" @@ -5915,19 +5885,19 @@ ts-node@10.4.0: make-error "^1.1.1" yn "3.1.1" -tsc-alias@1.4.2: - version "1.4.2" - resolved "https://registry.yarnpkg.com/tsc-alias/-/tsc-alias-1.4.2.tgz#80bac036f8d163a34a734a6772a57291313e084f" - integrity sha512-6OyipS3p1E7679vk9c55zfQm9JtBAMK1MfgoKXucT4oyTL7TdFgQRcRT/urdWC9JaAtahr3aYqyEI22T82UFuA== +tsc-alias@1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/tsc-alias/-/tsc-alias-1.5.0.tgz#bc26f8dccf96e4ea350adc3f64ad3d2325cad967" + integrity sha512-Pb3y7ZjULKFHEV2US5dS58/hV76sE9Sn5iehiPjYqHcm/lx4eCGAJYoSmrVXQMPX+6baTnDFJD0MGOyqn94dIg== dependencies: chokidar "^3.5.2" - commander "^8.2.0" + commander "^8.3.0" find-node-modules "^2.1.2" globby "^11.0.4" - mylas "^2.1.4" + mylas "^2.1.6" normalize-path "^3.0.0" -tsconfig-paths@3.12.0: +tsconfig-paths@3.12.0, tsconfig-paths@^3.12.0: version "3.12.0" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz#19769aca6ee8f6a1a341e38c8fa45dd9fb18899b" integrity sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg== @@ -5937,26 +5907,11 @@ tsconfig-paths@3.12.0: minimist "^1.2.0" strip-bom "^3.0.0" -tsconfig-paths@^3.11.0: - version "3.11.0" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz#954c1fe973da6339c78e06b03ce2e48810b65f36" - integrity sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA== - dependencies: - "@types/json5" "^0.0.29" - json5 "^1.0.1" - minimist "^1.2.0" - strip-bom "^3.0.0" - tslib@^1.8.1, tslib@^1.9.0: version "1.11.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35" integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA== -tslib@^1.9.3: - version "1.14.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" - integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== - tslib@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" @@ -6020,10 +5975,10 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -typescript@4.5.4: - version "4.5.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.4.tgz#a17d3a0263bf5c8723b9c52f43c5084edf13c2e8" - integrity sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg== +typescript@4.5.5: + version "4.5.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3" + integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA== unbox-primitive@^1.0.1: version "1.0.1" @@ -6162,10 +6117,10 @@ vue-eslint-parser@^8.0.1: lodash "^4.17.21" semver "^7.3.5" -vue-loader@16.8.3: - version "16.8.3" - resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-16.8.3.tgz#d43e675def5ba9345d6c7f05914c13d861997087" - integrity sha512-7vKN45IxsKxe5GcVCbc2qFU5aWzyiLrYJyUuMz4BQLKctCj/fmCa0w6fGiiQ2cLFetNcek1ppGJQDCup0c1hpA== +vue-loader@17.0.0: + version "17.0.0" + resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-17.0.0.tgz#2eaa80aab125b19f00faa794b5bd867b17f85acb" + integrity sha512-OWSXjrzIvbF2LtOUmxT3HYgwwubbfFelN8PAP9R9dwpIkj48TVioHhWWSx7W7fk+iF5cgg3CBJRxwTdtLU4Ecg== dependencies: chalk "^4.1.0" hash-sum "^2.0.0" @@ -6198,16 +6153,16 @@ vue-svg-loader@0.17.0-beta.2: semver "^7.3.2" svgo "^1.3.2" -vue@3.2.26: - version "3.2.26" - resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.26.tgz#5db575583ecae495c7caa5c12fd590dffcbb763e" - integrity sha512-KD4lULmskL5cCsEkfhERVRIOEDrfEL9CwAsLYpzptOGjaGFNWo3BQ9g8MAb7RaIO71rmVOziZ/uEN/rHwcUIhg== +vue@3.2.29: + version "3.2.29" + resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.29.tgz#3571b65dbd796d3a6347e2fd45a8e6e11c13d56a" + integrity sha512-cFIwr7LkbtCRanjNvh6r7wp2yUxfxeM2yPpDQpAfaaLIGZSrUmLbNiSze9nhBJt5MrZ68Iqt0O5scwAMEVxF+Q== dependencies: - "@vue/compiler-dom" "3.2.26" - "@vue/compiler-sfc" "3.2.26" - "@vue/runtime-dom" "3.2.26" - "@vue/server-renderer" "3.2.26" - "@vue/shared" "3.2.26" + "@vue/compiler-dom" "3.2.29" + "@vue/compiler-sfc" "3.2.29" + "@vue/runtime-dom" "3.2.29" + "@vue/server-renderer" "3.2.29" + "@vue/shared" "3.2.29" vuedraggable@4.0.1: version "4.0.1" @@ -6294,10 +6249,10 @@ webpack-sources@^3.2.2: resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.2.tgz#d88e3741833efec57c4c789b6010db9977545260" integrity sha512-cp5qdmHnu5T8wRg2G3vZZHoJPN14aqQ89SyQ11NpGH5zEMDCclt49rzo+MaRazk7/UeILhAI+/sEtcM+7Fr0nw== -webpack@5.65.0: - version "5.65.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.65.0.tgz#ed2891d9145ba1f0d318e4ea4f89c3fa18e6f9be" - integrity sha512-Q5or2o6EKs7+oKmJo7LaqZaMOlDWQse9Tm5l1WAfU/ujLGN5Pb0SqGeVkN/4bpPmEqEP5RnVhiqsOtWtUVwGRw== +webpack@5.66.0: + version "5.66.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.66.0.tgz#789bf36287f407fc92b3e2d6f978ddff1bfc2dbb" + integrity sha512-NJNtGT7IKpGzdW7Iwpn/09OXz9inIkeIQ/ibY6B+MdV1x6+uReqz/5z1L89ezWnpPDWpXF0TY5PCYKQdWVn8Vg== dependencies: "@types/eslint-scope" "^3.7.0" "@types/estree" "^0.0.50" @@ -6313,7 +6268,7 @@ webpack@5.65.0: eslint-scope "5.1.1" events "^3.2.0" glob-to-regexp "^0.4.1" - graceful-fs "^4.2.4" + graceful-fs "^4.2.9" json-parse-better-errors "^1.0.2" loader-runner "^4.2.0" mime-types "^2.1.27" @@ -6450,10 +6405,10 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -ws@8.4.0: - version "8.4.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.4.0.tgz#f05e982a0a88c604080e8581576e2a063802bed6" - integrity sha512-IHVsKe2pjajSUIl4KYMQOdlyliovpEPquKkqbwswulszzI7r0SfQrxnXdWAEqOlDCLrVSJzo+O1hAwdog2sKSQ== +ws@8.4.2: + version "8.4.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.4.2.tgz#18e749868d8439f2268368829042894b6907aa0b" + integrity sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA== xml-js@^1.6.11: version "1.6.11" |