From de4da3c1fd491376e4b59585e28d339e26722989 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=81=E5=91=A8=E9=83=A8=E8=90=BD?= Date: Tue, 19 Mar 2024 22:37:46 +0800 Subject: [PATCH 001/139] docs: add minimum dependencies --- dev/docs/local-installation.md | 2 ++ docs/install.md | 30 ++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/dev/docs/local-installation.md b/dev/docs/local-installation.md index a13aa6437f..132d86bd56 100644 --- a/dev/docs/local-installation.md +++ b/dev/docs/local-installation.md @@ -42,6 +42,8 @@ cargo --version ### PostgreSQL and PGroonga +Firefish requires PostgreSQL v12 or later. While you can choose any versions between v12 and the latest version (v16.2 as of writing), we recommend that you install v12.x so as not to use new features inadvertently and introduce incompatibility issues. + PostgreSQL install instructions can be found at [this page](https://www.postgresql.org/download/). ```sh diff --git a/docs/install.md b/docs/install.md index 061000fa32..f030e87125 100644 --- a/docs/install.md +++ b/docs/install.md @@ -4,6 +4,8 @@ This document shows an example procedure for installing Firefish on Debian 12. N If you want to use the pre-built container image, please refer to [`install-container.md`](./install-container.md). +If you do not prepare your environment as document, be sure to meet the minimum dependencies given at the bottom of the page. + Make sure that you can use the `sudo` command before proceeding. ## 1. Install dependencies @@ -315,3 +317,31 @@ cd ~/firefish - Run `psql -d firefish` (or whatever the database name is) - Run `UPDATE "user" SET "isAdmin" = true WHERE id='999999';` (replace `999999` with the copied ID) - Restart your Firefish server + +## Dependencies + +**We only recommend that you use components that are still within the upstream support cycle for better performance and security, and it is recommended that new sites meet the recommended dependency version requirements.** + +- At least [NodeJS](https://nodejs.org/en/) v18.17.0 (v20/v21 recommended) +- At least [PostgreSQL](https://www.postgresql.org/) v12 (v16 recommended) with [PGroonga](https://pgroonga.github.io/) extension +- At least [Redis](https://redis.io/) v7 +- Web Proxy (one of the following) + - Caddy (recommended for new users) + - Nginx (recommended) + - Apache + +### Optional dependencies + +- [FFmpeg](https://ffmpeg.org/) for video transcoding +- Caching server (one of the following) + - [DragonflyDB](https://www.dragonflydb.io/) (recommended) + - [KeyDB](https://keydb.dev/) + - Another [Redis](https://redis.io/) server + +### Build dependencies + +- At least [Rust](https://www.rust-lang.org/) v1.74 +- C/C++ compiler & build tools + - `build-essential` on Debian/Ubuntu Linux + - `base-devel` on Arch Linux +- [Python 3](https://www.python.org/) From db0bd21edcc4681dac033b7ec4184cb555940607 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=81=E5=91=A8=E9=83=A8=E8=90=BD?= Date: Mon, 11 Mar 2024 08:33:47 +0800 Subject: [PATCH 002/139] feat(client): attachment without description opened when choose add alt --- packages/client/src/components/MkPostForm.vue | 81 ++++++++++++++++++- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/packages/client/src/components/MkPostForm.vue b/packages/client/src/components/MkPostForm.vue index 3890908026..ad8c4c5ba3 100644 --- a/packages/client/src/components/MkPostForm.vue +++ b/packages/client/src/components/MkPostForm.vue @@ -500,6 +500,8 @@ const withHashtags = computed( ); const hashtags = computed(defaultStore.makeGetterSetter("postFormHashtags")); +let firstTryPost = true; + watch(text, () => { checkMissingMention(); }); @@ -1048,16 +1050,91 @@ async function post() { defaultStore.state.showNoAltTextWarning && files.value.some((f) => f.comment == null || f.comment.length === 0) ) { + if (firstTryPost) { + for (const file of files.value) { + if (file.comment == null || file.comment.length === 0) { + os.popup( + defineAsyncComponent( + () => import("@/components/MkMediaCaption.vue"), + ), + { + title: i18n.ts.describeFile, + input: { + placeholder: i18n.ts.inputNewDescription, + default: + file.comment !== null ? file.comment : "", + }, + image: file, + }, + { + done: (result) => { + if (!result || result.canceled) return; + const comment = + result.result.length === 0 + ? null + : result.result; + os.api("drive/files/update", { + fileId: file.id, + comment, + }).then(() => { + file.comment = comment; + }); + }, + }, + "closed", + ); + } + } + firstTryPost = false; + return; + } + // "canceled" means "post anyway" const { canceled } = await os.confirm({ type: "warning", text: i18n.ts.noAltTextWarning, - okText: i18n.ts.goBack, + okText: i18n.ts.describeFile, cancelText: i18n.ts.toPost, isPlaintext: true, }); - if (!canceled) return; + if (!canceled) { + for (const file of files.value) { + if (file.comment == null || file.comment.length === 0) { + os.popup( + defineAsyncComponent( + () => import("@/components/MkMediaCaption.vue"), + ), + { + title: i18n.ts.describeFile, + input: { + placeholder: i18n.ts.inputNewDescription, + default: + file.comment !== null ? file.comment : "", + }, + image: file, + }, + { + done: (result) => { + if (!result || result.canceled) return; + const comment = + result.result.length === 0 + ? null + : result.result; + os.api("drive/files/update", { + fileId: file.id, + comment, + }).then(() => { + file.comment = comment; + }); + }, + }, + "closed", + ); + } + } + return; + } } const processedText = preprocess(text.value); From 2267e90d3bfb9ac51ed6dcdaee051c39c594bd19 Mon Sep 17 00:00:00 2001 From: naskya Date: Mon, 25 Mar 2024 03:12:59 +0900 Subject: [PATCH 003/139] refactor (client): await asynchronous processes, remove duplicate code --- packages/client/src/components/MkPostForm.vue | 99 +++++++------------ 1 file changed, 35 insertions(+), 64 deletions(-) diff --git a/packages/client/src/components/MkPostForm.vue b/packages/client/src/components/MkPostForm.vue index ad8c4c5ba3..6478838f4e 100644 --- a/packages/client/src/components/MkPostForm.vue +++ b/packages/client/src/components/MkPostForm.vue @@ -309,6 +309,8 @@ import XNoteSimple from "@/components/MkNoteSimple.vue"; import XNotePreview from "@/components/MkNotePreview.vue"; import XPostFormAttaches from "@/components/MkPostFormAttaches.vue"; import XPollEditor from "@/components/MkPollEditor.vue"; +import XCheatSheet from "@/components/MkCheatSheetDialog.vue"; +import XMediaCaption from "@/components/MkMediaCaption.vue"; import { host, url } from "@/config"; import { erase, unique } from "@/scripts/array"; import { extractMentions } from "@/scripts/extract-mentions"; @@ -325,7 +327,6 @@ import { getAccounts, openAccountMenu as openAccountMenu_ } from "@/account"; import { me } from "@/me"; import { uploadFile } from "@/scripts/upload"; import { deepClone } from "@/scripts/clone"; -import XCheatSheet from "@/components/MkCheatSheetDialog.vue"; import preprocess from "@/scripts/preprocess"; import { vibrate } from "@/scripts/vibrate"; import { langmap } from "@/scripts/langmap"; @@ -500,7 +501,7 @@ const withHashtags = computed( ); const hashtags = computed(defaultStore.makeGetterSetter("postFormHashtags")); -let firstTryPost = true; +let isFirstPostAttempt = true; watch(text, () => { checkMissingMention(); @@ -1012,6 +1013,34 @@ function deleteDraft() { localStorage.setItem("drafts", JSON.stringify(draftData)); } +async function openFileDescriptionWindow(file: DriveFile) { + await os.popup( + XMediaCaption, + { + title: i18n.ts.describeFile, + input: { + placeholder: i18n.ts.inputNewDescription, + default: file.comment !== null ? file.comment : "", + }, + image: file, + }, + { + done: (result) => { + if (!result || result.canceled) return; + const comment = + result.result.length === 0 ? null : result.result; + os.api("drive/files/update", { + fileId: file.id, + comment, + }).then(() => { + file.comment = comment; + }); + }, + }, + "closed", + ); +} + async function post() { // For text that is too short, the false positive rate may be too high, so we don't show alarm. if (defaultStore.state.autocorrectNoteLanguage && text.value.length > 10) { @@ -1050,42 +1079,13 @@ async function post() { defaultStore.state.showNoAltTextWarning && files.value.some((f) => f.comment == null || f.comment.length === 0) ) { - if (firstTryPost) { + if (isFirstPostAttempt) { for (const file of files.value) { if (file.comment == null || file.comment.length === 0) { - os.popup( - defineAsyncComponent( - () => import("@/components/MkMediaCaption.vue"), - ), - { - title: i18n.ts.describeFile, - input: { - placeholder: i18n.ts.inputNewDescription, - default: - file.comment !== null ? file.comment : "", - }, - image: file, - }, - { - done: (result) => { - if (!result || result.canceled) return; - const comment = - result.result.length === 0 - ? null - : result.result; - os.api("drive/files/update", { - fileId: file.id, - comment, - }).then(() => { - file.comment = comment; - }); - }, - }, - "closed", - ); + await openFileDescriptionWindow(file); } } - firstTryPost = false; + isFirstPostAttempt = false; return; } @@ -1101,36 +1101,7 @@ async function post() { if (!canceled) { for (const file of files.value) { if (file.comment == null || file.comment.length === 0) { - os.popup( - defineAsyncComponent( - () => import("@/components/MkMediaCaption.vue"), - ), - { - title: i18n.ts.describeFile, - input: { - placeholder: i18n.ts.inputNewDescription, - default: - file.comment !== null ? file.comment : "", - }, - image: file, - }, - { - done: (result) => { - if (!result || result.canceled) return; - const comment = - result.result.length === 0 - ? null - : result.result; - os.api("drive/files/update", { - fileId: file.id, - comment, - }).then(() => { - file.comment = comment; - }); - }, - }, - "closed", - ); + await openFileDescriptionWindow(file); } } return; From f346d2e2f6b4674c9af03e761bf991b1c98f7d17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=81=E5=91=A8=E9=83=A8=E8=90=BD?= Date: Mon, 25 Mar 2024 22:52:37 +0800 Subject: [PATCH 004/139] feat: add showAddFileDescriptionAtFirstPost and allow cancel adding alt-text midway --- locales/en-US.yml | 2 + locales/zh-CN.yml | 1 + packages/client/src/components/MkPostForm.vue | 86 ++++++++++++------- .../client/src/pages/settings/general.vue | 6 ++ .../pages/settings/preferences-backups.vue | 1 + packages/client/src/store.ts | 4 + 6 files changed, 71 insertions(+), 29 deletions(-) diff --git a/locales/en-US.yml b/locales/en-US.yml index d736a7d881..116b8eabd0 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -1226,6 +1226,8 @@ publishTimelinesDescription: "If enabled, the Local and Global timelines will be on {url} even when signed out." noAltTextWarning: "Some attached file(s) have no description. Did you forget to write?" showNoAltTextWarning: "Show a warning if you attempt to post files without a description" +showAddFileDescriptionAtFirstPost: "Show add description page automatically when + first try to post a post attachment without a description" _emojiModPerm: unauthorized: "None" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index f67bf4a600..8f5d009acc 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -2058,5 +2058,6 @@ searchRangeDescription: "如果您要过滤时间段,请按以下格式输入 messagingUnencryptedInfo: "Firefish 上的聊天没有经过端到端加密,请不要在聊天中分享您的敏感信息。" noAltTextWarning: 有些附件没有描述。您是否忘记写描述了? showNoAltTextWarning: 当您尝试发布没有描述的帖子附件时显示警告 +showAddFileDescriptionAtFirstPost: 当您首次尝试发布没有描述的帖子附件时自动弹出添加描述页面 autocorrectNoteLanguage: 当帖子语言不符合自动检测的结果的时候显示警告 incorrectLanguageWarning: "看上去您帖子使用的语言是{detected},但您选择的语言是{current}。\n要改为以{detected}发帖吗?" diff --git a/packages/client/src/components/MkPostForm.vue b/packages/client/src/components/MkPostForm.vue index 6478838f4e..fee812317e 100644 --- a/packages/client/src/components/MkPostForm.vue +++ b/packages/client/src/components/MkPostForm.vue @@ -1013,32 +1013,43 @@ function deleteDraft() { localStorage.setItem("drafts", JSON.stringify(draftData)); } -async function openFileDescriptionWindow(file: DriveFile) { - await os.popup( - XMediaCaption, - { - title: i18n.ts.describeFile, - input: { - placeholder: i18n.ts.inputNewDescription, - default: file.comment !== null ? file.comment : "", +/** + * @returns whether the file is described + */ + function openFileDescriptionWindow(file: entities.DriveFile) { + return new Promise((resolve, reject) => { + os.popup( + XMediaCaption, + { + title: i18n.ts.describeFile, + input: { + placeholder: i18n.ts.inputNewDescription, + default: file.comment !== null ? file.comment : "", + }, + image: file, }, - image: file, - }, - { - done: (result) => { - if (!result || result.canceled) return; - const comment = - result.result.length === 0 ? null : result.result; - os.api("drive/files/update", { - fileId: file.id, - comment, - }).then(() => { - file.comment = comment; - }); + { + done: (result) => { + if (!result || result.canceled) { + resolve(false); + return; + } + const comment = + result.result.length === 0 ? null : result.result; + os.api("drive/files/update", { + fileId: file.id, + comment, + }).then(() => { + resolve(true); + file.comment = comment; + }).catch((err: unknown) => { + reject(err); + }); + }, }, - }, - "closed", - ); + "closed", + ); + }) } async function post() { @@ -1076,19 +1087,30 @@ async function post() { } if ( - defaultStore.state.showNoAltTextWarning && + defaultStore.state.showAddFileDescriptionAtFirstPost && files.value.some((f) => f.comment == null || f.comment.length === 0) ) { if (isFirstPostAttempt) { for (const file of files.value) { if (file.comment == null || file.comment.length === 0) { - await openFileDescriptionWindow(file); + const described = await openFileDescriptionWindow(file); + if (!described) { + return; + } } } isFirstPostAttempt = false; - return; + // Continue if all files have alt-text added. + if (files.value.some((f) => f.comment == null || f.comment.length === 0)) { + return; + } } + } + if ( + defaultStore.state.showNoAltTextWarning && + files.value.some((f) => f.comment == null || f.comment.length === 0) + ) { // "canceled" means "post anyway" const { canceled } = await os.confirm({ type: "warning", @@ -1101,10 +1123,16 @@ async function post() { if (!canceled) { for (const file of files.value) { if (file.comment == null || file.comment.length === 0) { - await openFileDescriptionWindow(file); + const described = await openFileDescriptionWindow(file); + if (!described) { + return; + } } } - return; + // Continue if all files have alt-text added. + if (files.value.some((f) => f.comment == null || f.comment.length === 0)) { + return; + } } } diff --git a/packages/client/src/pages/settings/general.vue b/packages/client/src/pages/settings/general.vue index 15e1172169..bc21efc132 100644 --- a/packages/client/src/pages/settings/general.vue +++ b/packages/client/src/pages/settings/general.vue @@ -124,6 +124,9 @@ {{ i18n.ts.showNoAltTextWarning }} + {{ + i18n.ts.showAddFileDescriptionAtFirstPost + }} {{ i18n.ts.autocorrectNoteLanguage }} @@ -533,6 +536,9 @@ const pullToRefreshThreshold = computed( const showNoAltTextWarning = computed( defaultStore.makeGetterSetter("showNoAltTextWarning"), ); +const showAddFileDescriptionAtFirstPost = computed( + defaultStore.makeGetterSetter("showAddFileDescriptionAtFirstPost"), +); const autocorrectNoteLanguage = computed( defaultStore.makeGetterSetter("autocorrectNoteLanguage"), ); diff --git a/packages/client/src/pages/settings/preferences-backups.vue b/packages/client/src/pages/settings/preferences-backups.vue index 202de0a082..885fec70c1 100644 --- a/packages/client/src/pages/settings/preferences-backups.vue +++ b/packages/client/src/pages/settings/preferences-backups.vue @@ -125,6 +125,7 @@ const defaultStoreSaveKeys: (keyof (typeof defaultStore)["state"])[] = [ "enablePullToRefresh", "pullToRefreshThreshold", "showNoAltTextWarning", + "showAddFileDescriptionAtFirstPost", "autocorrectNoteLanguage", ]; const coldDeviceStorageSaveKeys: (keyof typeof ColdDeviceStorage.default)[] = [ diff --git a/packages/client/src/store.ts b/packages/client/src/store.ts index cf17917477..11abb2e30f 100644 --- a/packages/client/src/store.ts +++ b/packages/client/src/store.ts @@ -432,6 +432,10 @@ export const defaultStore = markRaw( where: "account", default: true, }, + showAddFileDescriptionAtFirstPost: { + where: "account", + default: false, + }, autocorrectNoteLanguage: { where: "account", default: true, From 46bf02cdd591df1bed64677ccb15e358aa6b921d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=81=E5=91=A8=E9=83=A8=E8=90=BD?= Date: Sun, 31 Mar 2024 06:44:00 +0800 Subject: [PATCH 005/139] chore: format --- packages/client/src/components/MkPostForm.vue | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/packages/client/src/components/MkPostForm.vue b/packages/client/src/components/MkPostForm.vue index fee812317e..ffeb1a1cc5 100644 --- a/packages/client/src/components/MkPostForm.vue +++ b/packages/client/src/components/MkPostForm.vue @@ -1016,7 +1016,7 @@ function deleteDraft() { /** * @returns whether the file is described */ - function openFileDescriptionWindow(file: entities.DriveFile) { +function openFileDescriptionWindow(file: entities.DriveFile) { return new Promise((resolve, reject) => { os.popup( XMediaCaption, @@ -1034,22 +1034,23 @@ function deleteDraft() { resolve(false); return; } - const comment = - result.result.length === 0 ? null : result.result; - os.api("drive/files/update", { + const comment = result.result.length === 0 ? null : result.result; + os.api("drive/files/update", { fileId: file.id, comment, - }).then(() => { - resolve(true); - file.comment = comment; - }).catch((err: unknown) => { - reject(err); - }); + }) + .then(() => { + resolve(true); + file.comment = comment; + }) + .catch((err: unknown) => { + reject(err); + }); }, }, "closed", ); - }) + }); } async function post() { @@ -1101,7 +1102,9 @@ async function post() { } isFirstPostAttempt = false; // Continue if all files have alt-text added. - if (files.value.some((f) => f.comment == null || f.comment.length === 0)) { + if ( + files.value.some((f) => f.comment == null || f.comment.length === 0) + ) { return; } } @@ -1130,7 +1133,9 @@ async function post() { } } // Continue if all files have alt-text added. - if (files.value.some((f) => f.comment == null || f.comment.length === 0)) { + if ( + files.value.some((f) => f.comment == null || f.comment.length === 0) + ) { return; } } From 268b7aeb3f03c57c6a34e0461d1eb4d54cf56392 Mon Sep 17 00:00:00 2001 From: Lhcfl Date: Wed, 10 Apr 2024 17:16:25 +0800 Subject: [PATCH 006/139] refactor: Fix type errors of mfm.ts --- packages/client/src/components/mfm.ts | 229 +++++++++++++++----------- 1 file changed, 137 insertions(+), 92 deletions(-) diff --git a/packages/client/src/components/mfm.ts b/packages/client/src/components/mfm.ts index 0800e8270b..f2b100a207 100644 --- a/packages/client/src/components/mfm.ts +++ b/packages/client/src/components/mfm.ts @@ -1,6 +1,6 @@ import { defineComponent, h } from "vue"; import * as mfm from "mfm-js"; -import type { VNode, PropType } from "vue"; +import type { PropType, VNodeArrayChildren } from "vue"; import MkUrl from "@/components/global/MkUrl.vue"; import MkLink from "@/components/MkLink.vue"; import MkMention from "@/components/MkMention.vue"; @@ -30,11 +30,12 @@ export default defineComponent({ default: false, }, author: { - type: Object, + type: Object as PropType, default: null, }, + // TODO: This variable is not used in the code and may be removed i: { - type: Object, + type: Object as PropType, default: null, }, customEmojis: { @@ -58,14 +59,16 @@ export default defineComponent({ const ast = (isPlain ? mfm.parseSimple : mfm.parse)(this.text); - const validTime = (t: string | null | undefined) => { + const validTime = (t: string | null | undefined | boolean) => { if (t == null) return null; + if (typeof t !== "string") return null; return t.match(/^[0-9.]+s$/) ? t : null; }; - const validNumber = (n: string | null | undefined) => { + const validNumber = (n: string | null | undefined | boolean) => { if (n == null) return null; - const parsed = parseFloat(n); + if (typeof n !== "string") return null; + const parsed = Number.parseFloat(n); return !Number.isNaN(parsed) && Number.isFinite(parsed) && parsed > 0; }; // const validEase = (e: string | null | undefined) => { @@ -77,13 +80,13 @@ export default defineComponent({ const genEl = (ast: mfm.MfmNode[]) => concat( - ast.map((token, index): VNode[] => { + ast.map((token, index): VNodeArrayChildren => { switch (token.type) { case "text": { const text = token.props.text.replace(/(\r\n|\n|\r)/g, "\n"); if (!this.plain) { - const res = []; + const res: VNodeArrayChildren = []; for (const t of text.split("\n")) { res.push(h("br")); res.push(t); @@ -104,18 +107,20 @@ export default defineComponent({ } case "italic": { - return h( - "i", - { - style: "font-style: oblique;", - }, - genEl(token.children), - ); + return [ + h( + "i", + { + style: "font-style: oblique;", + }, + genEl(token.children), + ), + ]; } case "fn": { // TODO: CSSを文字列で組み立てていくと token.props.args.~~~ 経由でCSSインジェクションできるのでよしなにやる - let style: string; + let style: string | null = null; switch (token.props.name) { case "tada": { const speed = validTime(token.props.args.speed) || "1s"; @@ -188,7 +193,7 @@ export default defineComponent({ if (reducedMotion()) { return genEl(token.children); } - return h(MkSparkle, {}, genEl(token.children)); + return [h(MkSparkle, {}, genEl(token.children))]; } case "fade": { const direction = token.props.args.out @@ -211,31 +216,37 @@ export default defineComponent({ break; } case "x2": { - return h( - "span", - { - class: "mfm-x2", - }, - genEl(token.children), - ); + return [ + h( + "span", + { + class: "mfm-x2", + }, + genEl(token.children), + ), + ]; } case "x3": { - return h( - "span", - { - class: "mfm-x3", - }, - genEl(token.children), - ); + return [ + h( + "span", + { + class: "mfm-x3", + }, + genEl(token.children), + ), + ]; } case "x4": { - return h( - "span", - { - class: "mfm-x4", - }, - genEl(token.children), - ); + return [ + h( + "span", + { + class: "mfm-x4", + }, + genEl(token.children), + ), + ]; } case "font": { const family = token.props.args.serif @@ -255,13 +266,15 @@ export default defineComponent({ break; } case "blur": { - return h( - "span", - { - class: "_blur_text", - }, - genEl(token.children), - ); + return [ + h( + "span", + { + class: "_blur_text", + }, + genEl(token.children), + ), + ]; } case "rotate": { const rotate = token.props.args.x @@ -269,77 +282,105 @@ export default defineComponent({ : token.props.args.y ? "perspective(128px) rotateY" : "rotate"; - const degrees = parseFloat(token.props.args.deg ?? "90"); + const degrees = Number.parseFloat( + token.props.args.deg.toString() ?? "90", + ); style = `transform: ${rotate}(${degrees}deg); transform-origin: center center;`; break; } case "position": { - const x = parseFloat(token.props.args.x ?? "0"); - const y = parseFloat(token.props.args.y ?? "0"); + const x = Number.parseFloat( + token.props.args.x.toString() ?? "0", + ); + const y = Number.parseFloat( + token.props.args.y.toString() ?? "0", + ); style = `transform: translateX(${x}em) translateY(${y}em);`; break; } case "crop": { - const top = parseFloat(token.props.args.top ?? "0"); - const right = parseFloat(token.props.args.right ?? "0"); - const bottom = parseFloat(token.props.args.bottom ?? "0"); - const left = parseFloat(token.props.args.left ?? "0"); + const top = Number.parseFloat( + token.props.args.top.toString() ?? "0", + ); + const right = Number.parseFloat( + token.props.args.right.toString() ?? "0", + ); + const bottom = Number.parseFloat( + token.props.args.bottom.toString() ?? "0", + ); + const left = Number.parseFloat( + token.props.args.left.toString() ?? "0", + ); style = `clip-path: inset(${top}% ${right}% ${bottom}% ${left}%);`; break; } case "scale": { - const x = Math.min(parseFloat(token.props.args.x ?? "1"), 5); - const y = Math.min(parseFloat(token.props.args.y ?? "1"), 5); + const x = Math.min( + Number.parseFloat(token.props.args.x.toString() ?? "1"), + 5, + ); + const y = Math.min( + Number.parseFloat(token.props.args.y.toString() ?? "1"), + 5, + ); style = `transform: scale(${x}, ${y});`; break; } case "fg": { let color = token.props.args.color; - if (!/^[0-9a-f]{3,6}$/i.test(color)) color = "f00"; + if (!/^[0-9a-f]{3,6}$/i.test(color.toString())) color = "f00"; style = `color: #${color};`; break; } case "bg": { let color = token.props.args.color; - if (!/^[0-9a-f]{3,6}$/i.test(color)) color = "f00"; + if (!/^[0-9a-f]{3,6}$/i.test(color.toString())) color = "f00"; style = `background-color: #${color};`; break; } case "small": { - return h( - "small", - { - style: "opacity: 0.7;", - }, - genEl(token.children), - ); + return [ + h( + "small", + { + style: "opacity: 0.7;", + }, + genEl(token.children), + ), + ]; } case "center": { - return h( - "div", - { - style: "text-align: center;", - }, - genEl(token.children), - ); + return [ + h( + "div", + { + style: "text-align: center;", + }, + genEl(token.children), + ), + ]; } } if (style == null) { - return h("span", {}, [ - "$[", - token.props.name, - " ", - ...genEl(token.children), - "]", - ]); + return [ + h("span", {}, [ + "$[", + token.props.name, + " ", + ...genEl(token.children), + "]", + ]), + ]; } else { - return h( - "span", - { - style: `display: inline-block;${style}`, - }, - genEl(token.children), - ); + return [ + h( + "span", + { + style: `display: inline-block;${style}`, + }, + genEl(token.children), + ), + ]; } } @@ -425,7 +466,7 @@ export default defineComponent({ h(MkCode, { key: Math.random(), code: token.props.code, - lang: token.props.lang, + lang: token.props.lang ?? undefined, }), ]; } @@ -506,13 +547,15 @@ export default defineComponent({ const ast2 = (isPlain ? mfm.parseSimple : mfm.parse)( token.props.content.slice(0, -6) + sentinel, ); + function isMfmText(n: mfm.MfmNode): n is mfm.MfmText { + return n.type === "text"; + } + const txtNode = ast2[ast2.length - 1]; if ( - ast2[ast2.length - 1].type === "text" && - ast2[ast2.length - 1].props.text.endsWith(sentinel) + isMfmText(txtNode) && + txtNode.props.text.endsWith(sentinel) ) { - ast2[ast2.length - 1].props.text = ast2[ - ast2.length - 1 - ].props.text.slice(0, -1); + txtNode.props.text = txtNode.props.text.slice(0, -1); } else { // I don't think this scope is reachable console.warn( @@ -554,8 +597,10 @@ export default defineComponent({ } default: { - console.error("unrecognized ast type:", token.type); - + console.error( + "unrecognized ast type:", + (token as { type: never }).type, + ); return []; } } From 23145c61af00349061a312f7f86edce79d037a22 Mon Sep 17 00:00:00 2001 From: Lhcfl Date: Wed, 10 Apr 2024 17:55:52 +0800 Subject: [PATCH 007/139] refactor: fix type of MkAbuseReport --- packages/backend/src/misc/schema.ts | 2 + .../models/repositories/abuse-user-report.ts | 6 +- .../src/models/schema/abuse-user-report.ts | 69 +++++++++++++++++++ .../api/endpoints/admin/abuse-user-reports.ts | 63 +---------------- .../client/src/components/MkAbuseReport.vue | 5 +- packages/client/src/pages/admin/abuses.vue | 5 +- packages/firefish-js/src/api.types.ts | 14 +++- packages/firefish-js/src/entities.ts | 31 +++++---- 8 files changed, 114 insertions(+), 81 deletions(-) create mode 100644 packages/backend/src/models/schema/abuse-user-report.ts diff --git a/packages/backend/src/misc/schema.ts b/packages/backend/src/misc/schema.ts index c793d3b2ed..73600832ce 100644 --- a/packages/backend/src/misc/schema.ts +++ b/packages/backend/src/misc/schema.ts @@ -33,8 +33,10 @@ import { packedGalleryPostSchema } from "@/models/schema/gallery-post.js"; import { packedEmojiSchema } from "@/models/schema/emoji.js"; import { packedNoteEdit } from "@/models/schema/note-edit.js"; import { packedNoteFileSchema } from "@/models/schema/note-file.js"; +import { packedAbuseUserReportSchema } from "@/models/schema/abuse-user-report.js"; export const refs = { + AbuseUserReport: packedAbuseUserReportSchema, UserLite: packedUserLiteSchema, UserDetailedNotMeOnly: packedUserDetailedNotMeOnlySchema, MeDetailedOnly: packedMeDetailedOnlySchema, diff --git a/packages/backend/src/models/repositories/abuse-user-report.ts b/packages/backend/src/models/repositories/abuse-user-report.ts index 16ce159955..b8d953d052 100644 --- a/packages/backend/src/models/repositories/abuse-user-report.ts +++ b/packages/backend/src/models/repositories/abuse-user-report.ts @@ -2,6 +2,7 @@ import { db } from "@/db/postgre.js"; import { Users } from "../index.js"; import { AbuseUserReport } from "@/models/entities/abuse-user-report.js"; import { awaitAll } from "@/prelude/await-all.js"; +import type { Packed } from "@/misc/schema.js"; export const AbuseUserReportRepository = db .getRepository(AbuseUserReport) @@ -10,7 +11,7 @@ export const AbuseUserReportRepository = db const report = typeof src === "object" ? src : await this.findOneByOrFail({ id: src }); - return await awaitAll({ + const packed: Packed<"AbuseUserReport"> = await awaitAll({ id: report.id, createdAt: report.createdAt.toISOString(), comment: report.comment, @@ -31,9 +32,10 @@ export const AbuseUserReportRepository = db : null, forwarded: report.forwarded, }); + return packed; }, - packMany(reports: any[]) { + packMany(reports: (AbuseUserReport["id"] | AbuseUserReport)[]) { return Promise.all(reports.map((x) => this.pack(x))); }, }); diff --git a/packages/backend/src/models/schema/abuse-user-report.ts b/packages/backend/src/models/schema/abuse-user-report.ts new file mode 100644 index 0000000000..47e56c7415 --- /dev/null +++ b/packages/backend/src/models/schema/abuse-user-report.ts @@ -0,0 +1,69 @@ +export const packedAbuseUserReportSchema = { + type: "object", + properties: { + id: { + type: "string", + optional: false, + nullable: false, + format: "id", + example: "xxxxxxxxxx", + }, + createdAt: { + type: "string", + optional: false, + nullable: false, + format: "date-time", + }, + comment: { + type: "string", + optional: false, + nullable: false, + }, + resolved: { + type: "boolean", + optional: false, + nullable: false, + }, + reporterId: { + type: "string", + optional: false, + nullable: false, + format: "id", + }, + targetUserId: { + type: "string", + optional: false, + nullable: false, + format: "id", + }, + assigneeId: { + type: "string", + optional: false, + nullable: true, + format: "id", + }, + reporter: { + type: "object", + optional: false, + nullable: false, + ref: "UserDetailed", + }, + targetUser: { + type: "object", + optional: false, + nullable: false, + ref: "UserDetailed", + }, + assignee: { + type: "object", + optional: true, + nullable: true, + ref: "UserDetailed", + }, + forwarded: { + type: "boolean", + optional: false, + nullable: false, + }, + }, +} as const; 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 78034917f0..4063af5c5c 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 @@ -16,68 +16,7 @@ export const meta = { type: "object", optional: false, nullable: false, - properties: { - id: { - type: "string", - nullable: false, - optional: false, - format: "id", - example: "xxxxxxxxxx", - }, - createdAt: { - type: "string", - nullable: false, - optional: false, - format: "date-time", - }, - comment: { - type: "string", - nullable: false, - optional: false, - }, - resolved: { - type: "boolean", - nullable: false, - optional: false, - example: false, - }, - reporterId: { - type: "string", - nullable: false, - optional: false, - format: "id", - }, - targetUserId: { - type: "string", - nullable: false, - optional: false, - format: "id", - }, - assigneeId: { - type: "string", - nullable: true, - optional: false, - format: "id", - }, - reporter: { - type: "object", - nullable: false, - optional: false, - ref: "User", - }, - targetUser: { - type: "object", - nullable: false, - optional: false, - ref: "User", - }, - assignee: { - type: "object", - nullable: true, - optional: true, - ref: "User", - }, - }, + ref: "AbuseUserReport", }, }, } as const; diff --git a/packages/client/src/components/MkAbuseReport.vue b/packages/client/src/components/MkAbuseReport.vue index 5536523948..26b91fae2e 100644 --- a/packages/client/src/components/MkAbuseReport.vue +++ b/packages/client/src/components/MkAbuseReport.vue @@ -72,13 +72,14 @@ import MkSwitch from "@/components/form/switch.vue"; import MkKeyValue from "@/components/MkKeyValue.vue"; import * as os from "@/os"; import { i18n } from "@/i18n"; +import type { entities } from "firefish-js"; const props = defineProps<{ - report: any; + report: entities.AbuseUserReport; }>(); const emit = defineEmits<{ - (ev: "resolved", reportId: string): void; + resolved: [reportId: string]; }>(); const forward = ref(props.report.forwarded); diff --git a/packages/client/src/pages/admin/abuses.vue b/packages/client/src/pages/admin/abuses.vue index 174b4d5713..f94e24cf7c 100644 --- a/packages/client/src/pages/admin/abuses.vue +++ b/packages/client/src/pages/admin/abuses.vue @@ -99,12 +99,13 @@ import XAbuseReport from "@/components/MkAbuseReport.vue"; import { i18n } from "@/i18n"; import { definePageMetadata } from "@/scripts/page-metadata"; import icon from "@/scripts/icon"; +import type { entities } from "firefish-js"; const reports = ref>(); const state = ref("unresolved"); -const reporterOrigin = ref("combined"); -const targetUserOrigin = ref("combined"); +const reporterOrigin = ref("combined"); +const targetUserOrigin = ref("combined"); // const searchUsername = ref(""); // const searchHost = ref(""); diff --git a/packages/firefish-js/src/api.types.ts b/packages/firefish-js/src/api.types.ts index d3564573f0..81e9a4a72c 100644 --- a/packages/firefish-js/src/api.types.ts +++ b/packages/firefish-js/src/api.types.ts @@ -1,4 +1,5 @@ import type { + AbuseUserReport, Ad, Announcement, Antenna, @@ -68,7 +69,18 @@ type NoteSubmitReq = { export type Endpoints = { // admin - "admin/abuse-user-reports": { req: TODO; res: TODO }; + "admin/abuse-user-reports": { + req: { + limit?: number; + sinceId?: AbuseUserReport["id"]; + untilId?: AbuseUserReport["id"]; + state?: string; + reporterOrigin?: OriginType; + targetUserOrigin?: OriginType; + forwarded?: boolean; + }; + res: AbuseUserReport[]; + }; "admin/delete-all-files-of-a-user": { req: { userId: User["id"] }; res: null; diff --git a/packages/firefish-js/src/entities.ts b/packages/firefish-js/src/entities.ts index ce940a1481..8501d6c51f 100644 --- a/packages/firefish-js/src/entities.ts +++ b/packages/firefish-js/src/entities.ts @@ -18,10 +18,7 @@ export type UserLite = { avatarBlurhash: string; alsoKnownAs: string[]; movedToUri: any; - emojis: { - name: string; - url: string; - }[]; + emojis: EmojiLite[]; instance?: { name: Instance["name"]; softwareName: Instance["softwareName"]; @@ -171,10 +168,7 @@ export type Note = { votes: number; }[]; }; - emojis: { - name: string; - url: string; - }[]; + emojis: EmojiLite[]; uri?: string; url?: string; updatedAt?: DateString; @@ -191,10 +185,7 @@ export type NoteEdit = { updatedAt: string; fileIds: DriveFile["id"][]; files: DriveFile[]; - emojis: { - name: string; - url: string; - }[]; + emojis: EmojiLite[]; }; export type NoteReaction = { @@ -325,6 +316,8 @@ export type EmojiLite = { id: string; name: string; url: string; + width: number | null; + height: number | null; }; export type LiteInstanceMetadata = { @@ -547,3 +540,17 @@ export type UserSorting = | "+updatedAt" | "-updatedAt"; export type OriginType = "combined" | "local" | "remote"; + +export type AbuseUserReport = { + id: string; + createdAt: DateString; + comment: string; + resolved: boolean; + reporterId: User["id"]; + targetUserId: User["id"]; + assigneeId: User["id"] | null; + reporter: UserDetailed; + targetUser: UserDetailed; + assignee?: UserDetailed | null; + forwarded: boolean; +}; From b6baded2e3aabe9217e72b7f7ad2452d3793027b Mon Sep 17 00:00:00 2001 From: Lhcfl Date: Wed, 10 Apr 2024 17:58:02 +0800 Subject: [PATCH 008/139] refactor: fix MkAcct type --- packages/client/src/components/global/MkAcct.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/components/global/MkAcct.vue b/packages/client/src/components/global/MkAcct.vue index 02112d4481..dff995f878 100644 --- a/packages/client/src/components/global/MkAcct.vue +++ b/packages/client/src/components/global/MkAcct.vue @@ -16,7 +16,7 @@ import { host as hostRaw } from "@/config"; import { defaultStore } from "@/store"; defineProps<{ - user: entities.UserDetailed; + user: entities.UserLite; detail?: boolean; }>(); From 6ac6a4cfa93c5758c1fed5aace0eb1a036f0e0c0 Mon Sep 17 00:00:00 2001 From: Lhcfl Date: Wed, 10 Apr 2024 20:10:43 +0800 Subject: [PATCH 009/139] refactor: fix type errors of MkAnnouncements --- packages/client/src/components/MkActiveUsersHeatmap.vue | 8 ++++---- packages/client/src/components/MkAnnouncement.vue | 5 +++-- packages/client/src/components/MkModal.vue | 2 +- packages/firefish-js/src/entities.ts | 2 ++ 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/client/src/components/MkActiveUsersHeatmap.vue b/packages/client/src/components/MkActiveUsersHeatmap.vue index 58eb42f5a6..8099e812f4 100644 --- a/packages/client/src/components/MkActiveUsersHeatmap.vue +++ b/packages/client/src/components/MkActiveUsersHeatmap.vue @@ -18,8 +18,8 @@ import { initChart } from "@/scripts/init-chart"; initChart(); -const rootEl = shallowRef(); -const chartEl = shallowRef(); +const rootEl = shallowRef(null); +const chartEl = shallowRef(null); const now = new Date(); let chartInstance: Chart | null = null; const fetching = ref(true); @@ -33,8 +33,8 @@ async function renderActiveUsersChart() { chartInstance.destroy(); } - const wide = rootEl.value.offsetWidth > 700; - const narrow = rootEl.value.offsetWidth < 400; + const wide = rootEl.value!.offsetWidth > 700; + const narrow = rootEl.value!.offsetWidth < 400; const weeks = wide ? 50 : narrow ? 10 : 25; const chartLimit = 7 * weeks; diff --git a/packages/client/src/components/MkAnnouncement.vue b/packages/client/src/components/MkAnnouncement.vue index 4f26cd8bac..0aaa519cb0 100644 --- a/packages/client/src/components/MkAnnouncement.vue +++ b/packages/client/src/components/MkAnnouncement.vue @@ -35,9 +35,10 @@ import MkSparkle from "@/components/MkSparkle.vue"; import MkButton from "@/components/MkButton.vue"; import { i18n } from "@/i18n"; import * as os from "@/os"; +import type { entities } from "firefish-js"; const props = defineProps<{ - announcement: Announcement; + announcement: entities.Announcement; }>(); const { id, text, title, imageUrl, isGoodNews } = props.announcement; @@ -45,7 +46,7 @@ const { id, text, title, imageUrl, isGoodNews } = props.announcement; const modal = shallowRef>(); const gotIt = () => { - modal.value.close(); + modal.value!.close(); os.api("i/read-announcement", { announcementId: id }); }; diff --git a/packages/client/src/components/MkModal.vue b/packages/client/src/components/MkModal.vue index a55525c632..6a2897cebf 100644 --- a/packages/client/src/components/MkModal.vue +++ b/packages/client/src/components/MkModal.vue @@ -190,7 +190,7 @@ const transitionDuration = computed(() => let contentClicking = false; const focusedElement = document.activeElement; -function close(_ev, opts: { useSendAnimation?: boolean } = {}) { +function close(_ev?, opts: { useSendAnimation?: boolean } = {}) { // removeEventListener("popstate", close); // if (props.preferType == "dialog") { // history.forward(); diff --git a/packages/firefish-js/src/entities.ts b/packages/firefish-js/src/entities.ts index 8501d6c51f..086bf26193 100644 --- a/packages/firefish-js/src/entities.ts +++ b/packages/firefish-js/src/entities.ts @@ -422,6 +422,8 @@ export type Announcement = { title: string; imageUrl: string | null; isRead?: boolean; + isGoodNews?: boolean; + showPopUp?: boolean; }; export type Antenna = { From 73537ec6fa3db65bbb72ce17b8fe34514ce06ff2 Mon Sep 17 00:00:00 2001 From: Lhcfl Date: Wed, 10 Apr 2024 20:36:20 +0800 Subject: [PATCH 010/139] fix type errors of MkAutoComplete --- packages/client/src/components/MkAutocomplete.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/client/src/components/MkAutocomplete.vue b/packages/client/src/components/MkAutocomplete.vue index 9a8582f19a..332785b467 100644 --- a/packages/client/src/components/MkAutocomplete.vue +++ b/packages/client/src/components/MkAutocomplete.vue @@ -62,7 +62,7 @@ {{ emoji.emoji }} ({{ emoji.aliasOf }}) Date: Wed, 10 Apr 2024 20:37:34 +0800 Subject: [PATCH 011/139] fix type errors of MkAvatars --- packages/client/src/components/MkAvatars.vue | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/client/src/components/MkAvatars.vue b/packages/client/src/components/MkAvatars.vue index d92eee22c1..2b2029a8b5 100644 --- a/packages/client/src/components/MkAvatars.vue +++ b/packages/client/src/components/MkAvatars.vue @@ -9,12 +9,13 @@ diff --git a/packages/client/src/components/MkChannelPreview.vue b/packages/client/src/components/MkChannelPreview.vue index f824a1b2f5..8b2e12dc8b 100644 --- a/packages/client/src/components/MkChannelPreview.vue +++ b/packages/client/src/components/MkChannelPreview.vue @@ -54,9 +54,10 @@ import { computed } from "vue"; import { i18n } from "@/i18n"; import icon from "@/scripts/icon"; +import type { entities } from "firefish-js"; const props = defineProps<{ - channel: Record; + channel: entities.Channel; }>(); const bannerStyle = computed(() => { diff --git a/packages/client/src/components/MkPostForm.vue b/packages/client/src/components/MkPostForm.vue index ba58ea652a..3254591bc3 100644 --- a/packages/client/src/components/MkPostForm.vue +++ b/packages/client/src/components/MkPostForm.vue @@ -354,7 +354,7 @@ const props = withDefaults( defineProps<{ reply?: entities.Note; renote?: entities.Note; - channel?: any; // TODO + channel?: entities.Channel; mention?: entities.User; specified?: entities.User; initialText?: string; diff --git a/packages/client/src/components/MkPostFormDialog.vue b/packages/client/src/components/MkPostFormDialog.vue index 60637ff237..5ae9e520d4 100644 --- a/packages/client/src/components/MkPostFormDialog.vue +++ b/packages/client/src/components/MkPostFormDialog.vue @@ -28,7 +28,7 @@ import MkPostForm from "@/components/MkPostForm.vue"; const props = defineProps<{ reply?: entities.Note; renote?: entities.Note; - channel?: any; // TODO + channel?: entities.Channel; mention?: entities.User; specified?: entities.User; initialText?: string; diff --git a/packages/client/src/pages/channel.vue b/packages/client/src/pages/channel.vue index 15e8b6e256..b8ec491ecb 100644 --- a/packages/client/src/pages/channel.vue +++ b/packages/client/src/pages/channel.vue @@ -33,7 +33,7 @@ :style="{ backgroundImage: channel.bannerUrl ? `url(${channel.bannerUrl})` - : null, + : undefined, }" class="banner" > @@ -88,8 +88,6 @@ class="_gap" src="channel" :channel="channelId" - @before="before" - @after="after" /> @@ -107,6 +105,7 @@ import { me } from "@/me"; import { i18n } from "@/i18n"; import { definePageMetadata } from "@/scripts/page-metadata"; import icon from "@/scripts/icon"; +import type { entities } from "firefish-js"; const router = useRouter(); @@ -114,7 +113,11 @@ const props = defineProps<{ channelId: string; }>(); -const channel = ref(null); +const channel = ref( + await os.api("channels/show", { + channelId: props.channelId, + }), +); const showBanner = ref(true); watch( @@ -124,7 +127,6 @@ watch( channelId: props.channelId, }); }, - { immediate: true }, ); function edit() { diff --git a/packages/client/src/pages/channels.vue b/packages/client/src/pages/channels.vue index ec92af54aa..5286f909d4 100644 --- a/packages/client/src/pages/channels.vue +++ b/packages/client/src/pages/channels.vue @@ -125,6 +125,7 @@ import { defaultStore } from "@/store"; import icon from "@/scripts/icon"; import "swiper/scss"; import "swiper/scss/virtual"; +import type { Swiper as SwiperType } from "swiper/types"; const router = useRouter(); @@ -216,18 +217,18 @@ definePageMetadata( })), ); -let swiperRef = null; +let swiperRef: SwiperType | null = null; -function setSwiperRef(swiper) { +function setSwiperRef(swiper: SwiperType) { swiperRef = swiper; syncSlide(tabs.indexOf(tab.value)); } function onSlideChange() { - tab.value = tabs[swiperRef.activeIndex]; + tab.value = tabs[swiperRef!.activeIndex]; } -function syncSlide(index) { - swiperRef.slideTo(index); +function syncSlide(index: number) { + swiperRef!.slideTo(index); } diff --git a/packages/firefish-js/src/api.types.ts b/packages/firefish-js/src/api.types.ts index 81e9a4a72c..9d1123e7ad 100644 --- a/packages/firefish-js/src/api.types.ts +++ b/packages/firefish-js/src/api.types.ts @@ -218,16 +218,31 @@ export type Endpoints = { }; // channels - "channels/create": { req: TODO; res: TODO }; - "channels/featured": { req: TODO; res: TODO }; + "channels/create": { + req: { + name: string; + description?: string; + bannerId: DriveFile["id"] | null; + }; + res: Channel; + }; + "channels/featured": { req: TODO; res: Channel[] }; "channels/follow": { req: TODO; res: TODO }; - "channels/followed": { req: TODO; res: TODO }; - "channels/owned": { req: TODO; res: TODO }; + "channels/followed": { req: TODO; res: Channel[] }; + "channels/owned": { req: TODO; res: Channel[] }; "channels/pin-note": { req: TODO; res: TODO }; - "channels/show": { req: TODO; res: TODO }; + "channels/show": { req: TODO; res: Channel }; "channels/timeline": { req: TODO; res: Note[] }; "channels/unfollow": { req: TODO; res: TODO }; - "channels/update": { req: TODO; res: TODO }; + "channels/update": { + req: { + channelId: Channel["id"]; + name: string; + description?: string; + bannerId: DriveFile["id"] | null; + }; + res: Channel; + }; // charts "charts/active-users": { diff --git a/packages/firefish-js/src/entities.ts b/packages/firefish-js/src/entities.ts index 086bf26193..74e97f2781 100644 --- a/packages/firefish-js/src/entities.ts +++ b/packages/firefish-js/src/entities.ts @@ -471,8 +471,17 @@ export type FollowRequest = { export type Channel = { id: ID; + createdAt: DateString; + lastNotedAt: DateString | null; name: string; - // TODO + description: string | null; + bannerId: DriveFile["id"]; + bannerUrl: string | null; + notesCount: number; + usersCount: number; + isFollowing?: boolean; + userId: User["id"] | null; + hasUnreadNote?: boolean; }; export type Following = { From 69828437161751568ec291fc77cb07d9583c9585 Mon Sep 17 00:00:00 2001 From: Lhcfl Date: Wed, 10 Apr 2024 22:25:45 +0800 Subject: [PATCH 016/139] fix: channel editor cannot remove channel banner --- .../src/models/repositories/channel.ts | 1 + packages/backend/src/models/schema/channel.ts | 12 +++ .../server/api/endpoints/channels/update.ts | 2 +- packages/client/src/pages/channel-editor.vue | 80 ++++++++++++------- 4 files changed, 67 insertions(+), 28 deletions(-) diff --git a/packages/backend/src/models/repositories/channel.ts b/packages/backend/src/models/repositories/channel.ts index 857470f4ec..809129db6c 100644 --- a/packages/backend/src/models/repositories/channel.ts +++ b/packages/backend/src/models/repositories/channel.ts @@ -40,6 +40,7 @@ export const ChannelRepository = db.getRepository(Channel).extend({ name: channel.name, description: channel.description, userId: channel.userId, + bannerId: channel.bannerId, bannerUrl: banner ? DriveFiles.getPublicUrl(banner, false) : null, usersCount: channel.usersCount, notesCount: channel.notesCount, diff --git a/packages/backend/src/models/schema/channel.ts b/packages/backend/src/models/schema/channel.ts index 67833cb0dd..d3ec222c8d 100644 --- a/packages/backend/src/models/schema/channel.ts +++ b/packages/backend/src/models/schema/channel.ts @@ -36,6 +36,13 @@ export const packedChannelSchema = { nullable: true, optional: false, }, + bannerId: { + type: "string", + optional: false, + nullable: true, + format: "id", + example: "xxxxxxxxxx", + }, notesCount: { type: "number", nullable: false, @@ -57,5 +64,10 @@ export const packedChannelSchema = { optional: false, format: "id", }, + hasUnreadNote: { + type: "boolean", + optional: true, + nullable: false, + }, }, } as const; diff --git a/packages/backend/src/server/api/endpoints/channels/update.ts b/packages/backend/src/server/api/endpoints/channels/update.ts index 0de7a837a1..fdd21da65f 100644 --- a/packages/backend/src/server/api/endpoints/channels/update.ts +++ b/packages/backend/src/server/api/endpoints/channels/update.ts @@ -83,7 +83,7 @@ export default define(meta, paramDef, async (ps, me) => { await Channels.update(channel.id, { ...(ps.name !== undefined ? { name: ps.name } : {}), ...(ps.description !== undefined ? { description: ps.description } : {}), - ...(banner ? { bannerId: banner.id } : {}), + ...(banner ? { bannerId: banner.id } : { bannerId: null }), }); return await Channels.pack(channel.id, me); diff --git a/packages/client/src/pages/channel-editor.vue b/packages/client/src/pages/channel-editor.vue index 60bcfe990e..3791a98d05 100644 --- a/packages/client/src/pages/channel-editor.vue +++ b/packages/client/src/pages/channel-editor.vue @@ -50,6 +50,7 @@ import { useRouter } from "@/router"; import { definePageMetadata } from "@/scripts/page-metadata"; import { i18n } from "@/i18n"; import icon from "@/scripts/icon"; +import type { entities } from "firefish-js"; const router = useRouter(); @@ -57,26 +58,24 @@ const props = defineProps<{ channelId?: string; }>(); -const channel = ref(null); -const name = ref(null); -const description = ref(null); +const channel = ref(null); +const name = ref(""); +const description = ref(""); const bannerUrl = ref(null); const bannerId = ref(null); -watch( - () => bannerId.value, - async () => { - if (bannerId.value == null) { - bannerUrl.value = null; - } else { - bannerUrl.value = ( - await os.api("drive/files/show", { - fileId: bannerId.value, - }) - ).url; - } - }, -); +let bannerUrlUpdated = false; + +/** + * Set banner url and id when we already know the url + * Prevent redundant network requests from being sent + */ +function setBanner(opt: { bannerId: string | null; bannerUrl: string | null }) { + bannerUrlUpdated = true; + bannerUrl.value = opt.bannerUrl; + bannerId.value = opt.bannerId; + bannerUrlUpdated = false; +} async function fetchChannel() { if (props.channelId == null) return; @@ -86,23 +85,44 @@ async function fetchChannel() { }); name.value = channel.value.name; - description.value = channel.value.description; - bannerId.value = channel.value.bannerId; - bannerUrl.value = channel.value.bannerUrl; + description.value = channel.value.description ?? ""; + setBanner(channel.value); } -fetchChannel(); +await fetchChannel(); + +watch(bannerId, async () => { + if (bannerUrlUpdated) { + bannerUrlUpdated = false; + return; + } + if (bannerId.value == null) { + bannerUrl.value = null; + } else { + bannerUrl.value = ( + await os.api("drive/files/show", { + fileId: bannerId.value, + }) + ).url; + } +}); function save() { - const params = { + const params: { + name: string; + description: string; + bannerId: string | null; + } = { name: name.value, description: description.value, bannerId: bannerId.value, }; if (props.channelId) { - params.channelId = props.channelId; - os.api("channels/update", params).then(() => { + os.api("channels/update", { + ...params, + channelId: props.channelId, + }).then(() => { os.success(); }); } else { @@ -113,14 +133,20 @@ function save() { } } -function setBannerImage(evt) { +function setBannerImage(evt: MouseEvent) { selectFile(evt.currentTarget ?? evt.target, null).then((file) => { - bannerId.value = file.id; + setBanner({ + bannerId: file.id, + bannerUrl: file.url, + }); }); } function removeBannerImage() { - bannerId.value = null; + setBanner({ + bannerId: null, + bannerUrl: null, + }); } const headerActions = computed(() => []); From 0b7ab1b90dc97e94007a5c17251c921fc9282019 Mon Sep 17 00:00:00 2001 From: Lhcfl Date: Wed, 10 Apr 2024 22:45:01 +0800 Subject: [PATCH 017/139] fix type errors of MkChatPreview --- .../client/src/components/MkChatPreview.vue | 24 +++++++++++-------- packages/firefish-js/src/entities.ts | 4 +++- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/packages/client/src/components/MkChatPreview.vue b/packages/client/src/components/MkChatPreview.vue index 1b330b7b2d..6976a3f18a 100644 --- a/packages/client/src/components/MkChatPreview.vue +++ b/packages/client/src/components/MkChatPreview.vue @@ -4,14 +4,14 @@ :class="{ isMe: isMe(message), isRead: message.groupId - ? message.reads.includes(me?.id) + ? message.reads.includes(me!.id) : message.isRead, }" :to=" message.groupId ? `/my/messaging/group/${message.groupId}` : `/my/messaging/${acct.toString( - isMe(message) ? message.recipient : message.user, + isMe(message) ? message.recipient! : message.user, )}` " > @@ -22,27 +22,27 @@ message.groupId ? message.user : isMe(message) - ? message.recipient + ? message.recipient! : message.user " :show-indicator="true" disable-link />
- {{ message.group.name }} + {{ message.group!.name }}
@{{ acct.toString( - isMe(message) ? message.recipient : message.user, + isMe(message) ? message.recipient! : message.user, ) }} @@ -65,16 +65,20 @@ diff --git a/packages/firefish-js/src/entities.ts b/packages/firefish-js/src/entities.ts index 74e97f2781..16cef32827 100644 --- a/packages/firefish-js/src/entities.ts +++ b/packages/firefish-js/src/entities.ts @@ -76,7 +76,9 @@ export type UserDetailed = UserLite & { url: string | null; }; -export type UserGroup = TODO; +export type UserGroup = { + id: ID; +} & Record; export type UserList = { id: ID; From 124b2244d62b259dc20f103bd2004cfd94ada76d Mon Sep 17 00:00:00 2001 From: Lhcfl Date: Wed, 10 Apr 2024 22:50:30 +0800 Subject: [PATCH 018/139] fix type errors of MkCode & MkModalWindow --- packages/client/src/components/MkCode.core.vue | 1 + packages/client/src/components/MkModalWindow.vue | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/client/src/components/MkCode.core.vue b/packages/client/src/components/MkCode.core.vue index 11720b4f62..7e3cdac562 100644 --- a/packages/client/src/components/MkCode.core.vue +++ b/packages/client/src/components/MkCode.core.vue @@ -29,6 +29,7 @@ if (props.lang != null && !(props.lang in Prism.languages)) { const { lang } = props; loadLanguage(props.lang).then( // onLoaded + // biome-ignore lint/suspicious/noAssignInExpressions: assign intentionally () => (prismLang.value = lang), // onError () => {}, diff --git a/packages/client/src/components/MkModalWindow.vue b/packages/client/src/components/MkModalWindow.vue index b7ba30dd17..3d04ce00de 100644 --- a/packages/client/src/components/MkModalWindow.vue +++ b/packages/client/src/components/MkModalWindow.vue @@ -97,7 +97,7 @@ const modal = shallowRef>(); const rootEl = shallowRef(); const headerEl = shallowRef(); -const close = (ev) => { +const close = (ev?) => { modal.value?.close(ev); }; From 909125e519829b59e06c2a2f4213a9d521877f9f Mon Sep 17 00:00:00 2001 From: Lhcfl Date: Wed, 10 Apr 2024 23:35:14 +0800 Subject: [PATCH 019/139] refactor: rewrite MkContainer using composition api --- .../client/src/components/MkContainer.vue | 201 ++++++++---------- 1 file changed, 92 insertions(+), 109 deletions(-) diff --git a/packages/client/src/components/MkContainer.vue b/packages/client/src/components/MkContainer.vue index 404f984415..6d91b39fca 100644 --- a/packages/client/src/components/MkContainer.vue +++ b/packages/client/src/components/MkContainer.vue @@ -9,6 +9,7 @@ scrollable, closed: !showBody, }" + ref="el" >
@@ -59,123 +60,105 @@ - From 43aeec32ce1c8d3c49793cdf8bcffce6663a0d40 Mon Sep 17 00:00:00 2001 From: Lhcfl Date: Wed, 10 Apr 2024 23:36:42 +0800 Subject: [PATCH 020/139] fix types error of MkContextMenu --- packages/client/src/components/MkContextMenu.vue | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/client/src/components/MkContextMenu.vue b/packages/client/src/components/MkContextMenu.vue index 319f7fd0fe..86ca3b3f3e 100644 --- a/packages/client/src/components/MkContextMenu.vue +++ b/packages/client/src/components/MkContextMenu.vue @@ -28,7 +28,7 @@ const emit = defineEmits<{ (ev: "closed"): void; }>(); -const rootEl = ref(); +const rootEl = ref(null); const zIndex = ref(os.claimZIndex("high")); @@ -36,8 +36,8 @@ onMounted(() => { let left = props.ev.pageX + 1; // 間違って右ダブルクリックした場合に意図せずアイテムがクリックされるのを防ぐため + 1 let top = props.ev.pageY + 1; // 間違って右ダブルクリックした場合に意図せずアイテムがクリックされるのを防ぐため + 1 - const width = rootEl.value.offsetWidth; - const height = rootEl.value.offsetHeight; + const width = rootEl.value!.offsetWidth; + const height = rootEl.value!.offsetHeight; if (left + width - window.scrollX > window.innerWidth) { left = window.innerWidth - width + window.scrollX; @@ -55,8 +55,8 @@ onMounted(() => { left = 0; } - rootEl.value.style.top = `${top}px`; - rootEl.value.style.left = `${left}px`; + rootEl.value!.style.top = `${top}px`; + rootEl.value!.style.left = `${left}px`; document.body.addEventListener("mousedown", onMousedown); }); From 18ba024cbb06f24406ad207be6b933f96bf87980 Mon Sep 17 00:00:00 2001 From: Lhcfl Date: Thu, 11 Apr 2024 00:00:54 +0800 Subject: [PATCH 021/139] chore: format --- .../client/src/components/MkContainer.vue | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/packages/client/src/components/MkContainer.vue b/packages/client/src/components/MkContainer.vue index 6d91b39fca..dfc5ec26ba 100644 --- a/packages/client/src/components/MkContainer.vue +++ b/packages/client/src/components/MkContainer.vue @@ -68,13 +68,13 @@ import icon from "@/scripts/icon"; const props = withDefaults( defineProps<{ - showHeader?: boolean, - thin?: boolean, - naked?: boolean, - foldable?: boolean, - expanded?: boolean, - scrollable?: boolean, - maxHeight?: number | null, + showHeader?: boolean; + thin?: boolean; + naked?: boolean; + foldable?: boolean; + expanded?: boolean; + scrollable?: boolean; + maxHeight?: number | null; }>(), { showHeader: true, @@ -84,7 +84,7 @@ const props = withDefaults( expanded: true, scrollable: false, maxHeight: null, - } + }, ); const showBody = ref(props.expanded); @@ -123,9 +123,7 @@ onMounted(() => { watch( showBody, (showBody) => { - const headerHeight = props.showHeader - ? header.value!.offsetHeight - : 0; + const headerHeight = props.showHeader ? header.value!.offsetHeight : 0; el.value!.style.minHeight = `${headerHeight}px`; if (showBody) { el.value!.style.flexBasis = "auto"; @@ -135,7 +133,7 @@ onMounted(() => { }, { immediate: true, - } + }, ); if (props.maxHeight != null) { From f275fc9cdf0e1d80c4528a6cf13f525cd3930b22 Mon Sep 17 00:00:00 2001 From: Lhcfl Date: Thu, 11 Apr 2024 00:14:36 +0800 Subject: [PATCH 022/139] refactor: fix type errors of MkCropperDialog --- .../client/src/components/MkCropperDialog.vue | 56 +++++++++++-------- .../client/src/components/MkModalWindow.vue | 5 +- packages/client/src/os.ts | 8 +-- 3 files changed, 40 insertions(+), 29 deletions(-) diff --git a/packages/client/src/components/MkCropperDialog.vue b/packages/client/src/components/MkCropperDialog.vue index 16b42c2f2a..955481bdad 100644 --- a/packages/client/src/components/MkCropperDialog.vue +++ b/packages/client/src/components/MkCropperDialog.vue @@ -68,40 +68,48 @@ let cropper: Cropper | null = null; const loading = ref(true); const ok = async () => { - const promise = new Promise(async (res) => { + async function UploadCroppedImg(): Promise { const croppedCanvas = await cropper?.getCropperSelection()?.$toCanvas(); - croppedCanvas.toBlob((blob) => { - const formData = new FormData(); - formData.append("file", blob); - if (defaultStore.state.uploadFolder) { - formData.append("folderId", defaultStore.state.uploadFolder); - } - fetch(apiUrl + "/drive/files/create", { - method: "POST", - body: formData, - headers: { - authorization: `Bearer ${me.token}`, - }, - }) - .then((response) => response.json()) - .then((f) => { - res(f); - }); + const blob = await new Promise((resolve) => + croppedCanvas!.toBlob((blob) => resolve(blob)), + ); + + // MDN says `null` may be passed if the image cannot be created for any reason. + // But I don't think this is reachable for normal case. + if (blob == null) { + throw "Cropping image failed."; + } + + const formData = new FormData(); + formData.append("file", blob); + if (defaultStore.state.uploadFolder) { + formData.append("folderId", defaultStore.state.uploadFolder); + } + + const response = await fetch(`${apiUrl}/drive/files/create`, { + method: "POST", + body: formData, + headers: { + authorization: `Bearer ${me!.token}`, + }, }); - }); + return await response.json(); + } + + const promise = UploadCroppedImg(); os.promiseDialog(promise); const f = await promise; emit("ok", f); - dialogEl.value.close(); + dialogEl.value!.close(); }; const cancel = () => { emit("cancel"); - dialogEl.value.close(); + dialogEl.value!.close(); }; const onImageLoad = () => { @@ -114,7 +122,7 @@ const onImageLoad = () => { }; onMounted(() => { - cropper = new Cropper(imgEl.value, {}); + cropper = new Cropper(imgEl.value!, {}); const computedStyle = getComputedStyle(document.documentElement); @@ -127,13 +135,13 @@ onMounted(() => { selection.outlined = true; window.setTimeout(() => { - cropper.getCropperImage()!.$center("contain"); + cropper!.getCropperImage()!.$center("contain"); selection.$center(); }, 100); // モーダルオープンアニメーションが終わったあとで再度調整 window.setTimeout(() => { - cropper.getCropperImage()!.$center("contain"); + cropper!.getCropperImage()!.$center("contain"); selection.$center(); }, 500); }); diff --git a/packages/client/src/components/MkModalWindow.vue b/packages/client/src/components/MkModalWindow.vue index 3d04ce00de..c9d126ee90 100644 --- a/packages/client/src/components/MkModalWindow.vue +++ b/packages/client/src/components/MkModalWindow.vue @@ -54,7 +54,10 @@
- +
diff --git a/packages/client/src/os.ts b/packages/client/src/os.ts index b8fac741ea..328b961b21 100644 --- a/packages/client/src/os.ts +++ b/packages/client/src/os.ts @@ -125,12 +125,12 @@ export const apiWithDialog = (( return promise; }) as typeof api; -export function promiseDialog>( - promise: T, - onSuccess?: ((res: any) => void) | null, +export function promiseDialog( + promise: Promise, + onSuccess?: ((res: T) => void) | null, onFailure?: ((err: Error) => void) | null, text?: string, -): T { +): Promise { const showing = ref(true); const success = ref(false); From 3ddd68097abcc2416d1e42cfa2c11ec497789ac4 Mon Sep 17 00:00:00 2001 From: Lhcfl Date: Thu, 11 Apr 2024 00:48:35 +0800 Subject: [PATCH 023/139] fix type errors --- packages/client/src/components/MkCwButton.vue | 2 +- packages/client/src/components/MkDigitalClock.vue | 2 +- packages/client/src/components/MkDonation.vue | 7 ++++--- packages/client/src/components/MkDrive.file.vue | 3 ++- packages/client/src/components/MkEmojiPicker.section.vue | 2 +- packages/firefish-js/src/entities.ts | 1 + 6 files changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/client/src/components/MkCwButton.vue b/packages/client/src/components/MkCwButton.vue index e7a2d3d77a..ccdea3ec90 100644 --- a/packages/client/src/components/MkCwButton.vue +++ b/packages/client/src/components/MkCwButton.vue @@ -48,7 +48,7 @@ const toggle = () => { }; function focus() { - el.value.focus(); + el.value?.focus(); } defineExpose({ diff --git a/packages/client/src/components/MkDigitalClock.vue b/packages/client/src/components/MkDigitalClock.vue index b42ecf19eb..42eabdf749 100644 --- a/packages/client/src/components/MkDigitalClock.vue +++ b/packages/client/src/components/MkDigitalClock.vue @@ -26,7 +26,7 @@ const props = withDefaults( }, ); -let intervalId; +let intervalId: number; const hh = ref(""); const mm = ref(""); const ss = ref(""); diff --git a/packages/client/src/components/MkDonation.vue b/packages/client/src/components/MkDonation.vue index 1c13754c13..ad9df629f4 100644 --- a/packages/client/src/components/MkDonation.vue +++ b/packages/client/src/components/MkDonation.vue @@ -29,7 +29,7 @@ {{ i18n.t("_aboutFirefish.donateHost", { host: hostname, @@ -73,7 +73,8 @@ const emit = defineEmits<{ (ev: "closed"): void; }>(); -const hostname = instance.name?.length < 38 ? instance.name : host; +const hostname = + instance.name?.length && instance.name?.length < 38 ? instance.name : host; const zIndex = os.claimZIndex("low"); @@ -97,7 +98,7 @@ function neverShow() { close(); } -function openExternal(link) { +function openExternal(link: string) { window.open(link, "_blank"); } diff --git a/packages/client/src/components/MkDrive.file.vue b/packages/client/src/components/MkDrive.file.vue index 5e8a50eec2..9d09da663e 100644 --- a/packages/client/src/components/MkDrive.file.vue +++ b/packages/client/src/components/MkDrive.file.vue @@ -47,6 +47,7 @@ import * as os from "@/os"; import { i18n } from "@/i18n"; import { me } from "@/me"; import icon from "@/scripts/icon"; +import type { MenuItem } from "@/types/menu"; const props = withDefaults( defineProps<{ @@ -72,7 +73,7 @@ const title = computed( () => `${props.file.name}\n${props.file.type} ${bytes(props.file.size)}`, ); -function getMenu() { +function getMenu(): MenuItem[] { return [ { text: i18n.ts.rename, diff --git a/packages/client/src/components/MkEmojiPicker.section.vue b/packages/client/src/components/MkEmojiPicker.section.vue index b91195fe65..9fcd5d363d 100644 --- a/packages/client/src/components/MkEmojiPicker.section.vue +++ b/packages/client/src/components/MkEmojiPicker.section.vue @@ -14,7 +14,7 @@ class="_button" @click.stop=" applyUnicodeSkinTone( - props.skinTones.indexOf(skinTone) + 1, + props.skinTones!.indexOf(skinTone) + 1, ) " > diff --git a/packages/firefish-js/src/entities.ts b/packages/firefish-js/src/entities.ts index 16cef32827..387f4eb608 100644 --- a/packages/firefish-js/src/entities.ts +++ b/packages/firefish-js/src/entities.ts @@ -329,6 +329,7 @@ export type LiteInstanceMetadata = { name: string | null; uri: string; description: string | null; + donationLink?: string; tosUrl: string | null; disableRegistration: boolean; disableLocalTimeline: boolean; From 783f5481bb762043aed1cec90355000e4ba6bc4a Mon Sep 17 00:00:00 2001 From: Lhcfl Date: Thu, 11 Apr 2024 16:58:58 +0800 Subject: [PATCH 024/139] remove unnecessary assertion --- packages/client/src/components/MkChatPreview.vue | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/client/src/components/MkChatPreview.vue b/packages/client/src/components/MkChatPreview.vue index 6976a3f18a..62235b9a22 100644 --- a/packages/client/src/components/MkChatPreview.vue +++ b/packages/client/src/components/MkChatPreview.vue @@ -69,10 +69,6 @@ import { acct, type entities } from "firefish-js"; import { i18n } from "@/i18n"; import { me } from "@/me"; -if (me == null) { - throw "No me"; -} - defineProps<{ message: entities.MessagingMessage; }>(); From 7a4e6334f16c73edff97f7cbd225e26c85bbd881 Mon Sep 17 00:00:00 2001 From: Lhcfl Date: Thu, 11 Apr 2024 17:01:37 +0800 Subject: [PATCH 025/139] fix type error --- packages/firefish-js/src/entities.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/firefish-js/src/entities.ts b/packages/firefish-js/src/entities.ts index 387f4eb608..cafbdc7864 100644 --- a/packages/firefish-js/src/entities.ts +++ b/packages/firefish-js/src/entities.ts @@ -425,8 +425,8 @@ export type Announcement = { title: string; imageUrl: string | null; isRead?: boolean; - isGoodNews?: boolean; - showPopUp?: boolean; + isGoodNews: boolean; + showPopUp: boolean; }; export type Antenna = { From 3716e7f74cde6e1aa2753074eecfc37715214277 Mon Sep 17 00:00:00 2001 From: Lhcfl Date: Thu, 11 Apr 2024 17:11:48 +0800 Subject: [PATCH 026/139] fix: expose toggleContent for it was a method --- packages/client/src/components/MkContainer.vue | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/client/src/components/MkContainer.vue b/packages/client/src/components/MkContainer.vue index dfc5ec26ba..e933b02dbf 100644 --- a/packages/client/src/components/MkContainer.vue +++ b/packages/client/src/components/MkContainer.vue @@ -94,7 +94,6 @@ const el = ref(null); const header = ref(null); const content = ref(null); -// FIXME: This function is not used, why? function toggleContent(show: boolean) { if (!props.foldable) return; showBody.value = show; @@ -158,6 +157,14 @@ onMounted(() => { calcOmit(); }).observe(content.value!); }); + +defineExpose({ + toggleContent, + enter, + afterEnter, + leave, + afterLeave, +}); From ec7578e78e652c2f2a152c4945d678f067ab48c2 Mon Sep 17 00:00:00 2001 From: naskya Date: Wed, 17 Apr 2024 19:49:02 +0900 Subject: [PATCH 065/139] docs: specify max-old-space-size in example config files --- docker-compose.example.yml | 1 + docs/install.md | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docker-compose.example.yml b/docker-compose.example.yml index fc6c0268a2..9cd6d1cdce 100644 --- a/docker-compose.example.yml +++ b/docker-compose.example.yml @@ -17,6 +17,7 @@ services: # - web environment: NODE_ENV: production + NODE_OPTIONS: --max-old-space-size=3072 volumes: - ./custom:/firefish/custom:ro - ./files:/firefish/files diff --git a/docs/install.md b/docs/install.md index 061000fa32..324923c6a7 100644 --- a/docs/install.md +++ b/docs/install.md @@ -154,7 +154,7 @@ sudo apt install ffmpeg 1. Build ```sh pnpm install --frozen-lockfile - NODE_ENV=production pnpm run build + NODE_ENV=production NODE_OPTIONS='--max-old-space-size=3072' pnpm run build ``` 1. Execute database migrations ```sh @@ -242,6 +242,7 @@ In this instruction, we use [Caddy](https://caddyserver.com/) to make the Firefi WorkingDirectory=/home/firefish/firefish Environment="NODE_ENV=production" Environment="npm_config_cache=/tmp" + Environment="NODE_OPTIONS=--max-old-space-size=3072" # uncomment the following line if you use jemalloc (note that the path varies on different environments) # Environment="LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2" StandardOutput=journal From 22f4278ab5ee7a66cdaf3bbc234301b165a8c166 Mon Sep 17 00:00:00 2001 From: naskya Date: Wed, 17 Apr 2024 23:14:43 +0900 Subject: [PATCH 066/139] meta: update issue/merge request templates --- .gitlab/issue_templates/bug.md | 15 ++++++++++++--- .gitlab/issue_templates/feature.md | 4 ++++ .gitlab/merge_request_templates/default.md | 1 + 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/.gitlab/issue_templates/bug.md b/.gitlab/issue_templates/bug.md index a94fae0f0d..f96bdec01e 100644 --- a/.gitlab/issue_templates/bug.md +++ b/.gitlab/issue_templates/bug.md @@ -5,30 +5,39 @@ ## What happened? + ## What did you expect to happen? + ## Version + ## What type of issue is this? -- [] server-side -- [] client-side -- [] not sure +- [ ] server-side +- [ ] client-side +- [ ] not sure
### Instance + ### What browser are you using? (client-side issues only) + ### What operating system are you using? (client-side issues only) + ### How do you deploy Firefish on your server? (server-side issues only) + ### What operating system are you using? (Server-side issues only) + ### Relevant log output +
## Contribution Guidelines diff --git a/.gitlab/issue_templates/feature.md b/.gitlab/issue_templates/feature.md index 7b4917ecb3..4c9ee56226 100644 --- a/.gitlab/issue_templates/feature.md +++ b/.gitlab/issue_templates/feature.md @@ -5,12 +5,16 @@ ## What feature would you like implemented? + ## Why should we add this feature? + ## Version + ## Instance + ## Contribution Guidelines By submitting this issue, you agree to follow our [Contribution Guidelines](https://firefish.dev/firefish/firefish/-/blob/develop/CONTRIBUTING.md) - [ ] I agree to follow this project's Contribution Guidelines diff --git a/.gitlab/merge_request_templates/default.md b/.gitlab/merge_request_templates/default.md index c2382481e3..d13a146da0 100644 --- a/.gitlab/merge_request_templates/default.md +++ b/.gitlab/merge_request_templates/default.md @@ -2,6 +2,7 @@ ## What does this PR do? + ## Contribution Guidelines By submitting this merge request, you agree to follow our [Contribution Guidelines](https://firefish.dev/firefish/firefish/-/blob/develop/CONTRIBUTING.md) - [ ] This change is reviewed in an issue / This is a minor bug fix From a411f4e4d9ee61ca68ff286d6c9e543d5f719fc7 Mon Sep 17 00:00:00 2001 From: yumeko Date: Wed, 17 Apr 2024 18:19:17 +0000 Subject: [PATCH 067/139] Fix internal error in api/pinned-users if one or more name fails to resolve --- packages/backend/src/server/api/endpoints/pinned-users.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/server/api/endpoints/pinned-users.ts b/packages/backend/src/server/api/endpoints/pinned-users.ts index 325b54f350..5685b698eb 100644 --- a/packages/backend/src/server/api/endpoints/pinned-users.ts +++ b/packages/backend/src/server/api/endpoints/pinned-users.ts @@ -45,7 +45,7 @@ export default define(meta, paramDef, async (ps, me) => { ); return await Users.packMany( - users.filter((x) => x !== undefined) as User[], + users.filter((x) => (x != null)) as User[], me, { detail: true }, ); From 8337863ed3da565224a966d2489225f485015dfb Mon Sep 17 00:00:00 2001 From: naskya Date: Thu, 18 Apr 2024 05:01:49 +0900 Subject: [PATCH 068/139] chore: format --- packages/backend/src/server/api/endpoints/pinned-users.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/backend/src/server/api/endpoints/pinned-users.ts b/packages/backend/src/server/api/endpoints/pinned-users.ts index 5685b698eb..65241becae 100644 --- a/packages/backend/src/server/api/endpoints/pinned-users.ts +++ b/packages/backend/src/server/api/endpoints/pinned-users.ts @@ -44,9 +44,7 @@ export default define(meta, paramDef, async (ps, me) => { ), ); - return await Users.packMany( - users.filter((x) => (x != null)) as User[], - me, - { detail: true }, - ); + return await Users.packMany(users.filter((x) => x != null) as User[], me, { + detail: true, + }); }); From 30969ad81799b7688ae3565066a57ad4d2dee83d Mon Sep 17 00:00:00 2001 From: naskya Date: Thu, 18 Apr 2024 05:02:00 +0900 Subject: [PATCH 069/139] refactor (backend): port get-note-summary to backend-rs I removed trim() as it wasn't strictly neccessary --- packages/backend-rs/index.d.ts | 13 ++- packages/backend-rs/index.js | 3 +- .../backend-rs/src/misc/check_word_mute.rs | 3 +- .../backend-rs/src/misc/get_note_summary.rs | 90 +++++++++++++++++++ packages/backend-rs/src/misc/mod.rs | 1 + packages/backend/src/misc/get-note-summary.ts | 53 ----------- packages/backend/src/models/schema/note.ts | 9 +- packages/backend/src/server/web/index.ts | 11 ++- .../backend/src/services/push-notification.ts | 11 ++- 9 files changed, 123 insertions(+), 71 deletions(-) create mode 100644 packages/backend-rs/src/misc/get_note_summary.rs delete mode 100644 packages/backend/src/misc/get-note-summary.ts diff --git a/packages/backend-rs/index.d.ts b/packages/backend-rs/index.d.ts index ae050e01d8..5ee69969c6 100644 --- a/packages/backend-rs/index.d.ts +++ b/packages/backend-rs/index.d.ts @@ -128,7 +128,8 @@ export interface Acct { } export function stringToAcct(acct: string): Acct export function acctToString(acct: Acct): string -export interface NoteLike { +/** TODO: handle name collisions better */ +export interface NoteLikeForCheckWordMute { fileIds: Array userId: string | null text: string | null @@ -136,7 +137,7 @@ export interface NoteLike { renoteId: string | null replyId: string | null } -export function checkWordMute(note: NoteLike, mutedWordLists: Array>, mutedPatterns: Array): Promise +export function checkWordMute(note: NoteLikeForCheckWordMute, mutedWordLists: Array>, mutedPatterns: Array): Promise export function getFullApAccount(username: string, host?: string | undefined | null): string export function isSelfHost(host?: string | undefined | null): boolean export function isSameOrigin(uri: string): boolean @@ -147,6 +148,14 @@ export function sqlLikeEscape(src: string): string export function safeForSql(src: string): boolean /** Convert milliseconds to a human readable string */ export function formatMilliseconds(milliseconds: number): string +/** TODO: handle name collisions better */ +export interface NoteLikeForGetNoteSummary { + fileIds: Array + text: string | null + cw: string | null + hasPoll: boolean +} +export function getNoteSummary(note: NoteLikeForGetNoteSummary): string export function toMastodonId(firefishId: string): string | null export function fromMastodonId(mastodonId: string): string | null export function fetchMeta(useCache: boolean): Promise diff --git a/packages/backend-rs/index.js b/packages/backend-rs/index.js index 6acd3ca68d..1ea7bb5bed 100644 --- a/packages/backend-rs/index.js +++ b/packages/backend-rs/index.js @@ -310,7 +310,7 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { readEnvironmentConfig, readServerConfig, stringToAcct, acctToString, checkWordMute, getFullApAccount, isSelfHost, isSameOrigin, extractHost, toPuny, isUnicodeEmoji, sqlLikeEscape, safeForSql, formatMilliseconds, toMastodonId, fromMastodonId, fetchMeta, metaToPugArgs, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, AntennaSrcEnum, MutedNoteReasonEnum, NoteVisibilityEnum, NotificationTypeEnum, PageVisibilityEnum, PollNotevisibilityEnum, RelayStatusEnum, UserEmojimodpermEnum, UserProfileFfvisibilityEnum, UserProfileMutingnotificationtypesEnum, initIdGenerator, getTimestamp, genId, secureRndstr } = nativeBinding +const { readEnvironmentConfig, readServerConfig, stringToAcct, acctToString, checkWordMute, getFullApAccount, isSelfHost, isSameOrigin, extractHost, toPuny, isUnicodeEmoji, sqlLikeEscape, safeForSql, formatMilliseconds, getNoteSummary, toMastodonId, fromMastodonId, fetchMeta, metaToPugArgs, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, AntennaSrcEnum, MutedNoteReasonEnum, NoteVisibilityEnum, NotificationTypeEnum, PageVisibilityEnum, PollNotevisibilityEnum, RelayStatusEnum, UserEmojimodpermEnum, UserProfileFfvisibilityEnum, UserProfileMutingnotificationtypesEnum, initIdGenerator, getTimestamp, genId, secureRndstr } = nativeBinding module.exports.readEnvironmentConfig = readEnvironmentConfig module.exports.readServerConfig = readServerConfig @@ -326,6 +326,7 @@ module.exports.isUnicodeEmoji = isUnicodeEmoji module.exports.sqlLikeEscape = sqlLikeEscape module.exports.safeForSql = safeForSql module.exports.formatMilliseconds = formatMilliseconds +module.exports.getNoteSummary = getNoteSummary module.exports.toMastodonId = toMastodonId module.exports.fromMastodonId = fromMastodonId module.exports.fetchMeta = fetchMeta diff --git a/packages/backend-rs/src/misc/check_word_mute.rs b/packages/backend-rs/src/misc/check_word_mute.rs index 801175c2af..18b550c29b 100644 --- a/packages/backend-rs/src/misc/check_word_mute.rs +++ b/packages/backend-rs/src/misc/check_word_mute.rs @@ -4,7 +4,8 @@ use once_cell::sync::Lazy; use regex::Regex; use sea_orm::{prelude::*, QuerySelect}; -#[crate::export(object)] +/// TODO: handle name collisions better +#[crate::export(object, js_name = "NoteLikeForCheckWordMute")] pub struct NoteLike { pub file_ids: Vec, pub user_id: Option, diff --git a/packages/backend-rs/src/misc/get_note_summary.rs b/packages/backend-rs/src/misc/get_note_summary.rs new file mode 100644 index 0000000000..3b759b04f5 --- /dev/null +++ b/packages/backend-rs/src/misc/get_note_summary.rs @@ -0,0 +1,90 @@ +/// TODO: handle name collisions better +#[crate::export(object, js_name = "NoteLikeForGetNoteSummary")] +pub struct NoteLike { + pub file_ids: Vec, + pub text: Option, + pub cw: Option, + pub has_poll: bool, +} + +#[crate::export] +pub fn get_note_summary(note: NoteLike) -> String { + let mut buf: Vec = vec![]; + + if let Some(cw) = note.cw { + buf.push(cw) + } else if let Some(text) = note.text { + buf.push(text) + } + + match note.file_ids.len() { + 0 => (), + 1 => buf.push("📎".to_string()), + n => buf.push(format!("📎 ({})", n)), + }; + + if note.has_poll { + buf.push("📊".to_string()) + } + + buf.join(" ") +} + +#[cfg(test)] +mod unit_test { + use super::{get_note_summary, NoteLike}; + use pretty_assertions::assert_eq; + + #[test] + fn test_note_summary() { + let note = NoteLike { + file_ids: vec![], + text: Some("Hello world!".to_string()), + cw: None, + has_poll: false, + }; + assert_eq!(get_note_summary(note), "Hello world!"); + + let note_with_cw = NoteLike { + file_ids: vec![], + text: Some("Hello world!".to_string()), + cw: Some("Content warning".to_string()), + has_poll: false, + }; + assert_eq!(get_note_summary(note_with_cw), "Content warning"); + + let note_with_file_and_cw = NoteLike { + file_ids: vec!["9s7fmcqogiq4igin".to_string()], + text: None, + cw: Some("Selfie, no ec".to_string()), + has_poll: false, + }; + assert_eq!(get_note_summary(note_with_file_and_cw), "Selfie, no ec 📎"); + + let note_with_files_only = NoteLike { + file_ids: vec![ + "9s7fmcqogiq4igin".to_string(), + "9s7qrld5u14cey98".to_string(), + "9s7gebs5zgts4kca".to_string(), + "9s5z3e4vefqd29ee".to_string(), + ], + text: None, + cw: None, + has_poll: false, + }; + assert_eq!(get_note_summary(note_with_files_only), "📎 (4)"); + + let note_all = NoteLike { + file_ids: vec![ + "9s7fmcqogiq4igin".to_string(), + "9s7qrld5u14cey98".to_string(), + "9s7gebs5zgts4kca".to_string(), + "9s5z3e4vefqd29ee".to_string(), + ], + text: Some("Hello world!".to_string()), + cw: Some("Content warning".to_string()), + has_poll: true, + }; + assert_eq!(get_note_summary(note_all), "Content warning 📎 (4) 📊"); + } +} diff --git a/packages/backend-rs/src/misc/mod.rs b/packages/backend-rs/src/misc/mod.rs index 45fd31cdcd..a9d7074dbf 100644 --- a/packages/backend-rs/src/misc/mod.rs +++ b/packages/backend-rs/src/misc/mod.rs @@ -4,6 +4,7 @@ pub mod convert_host; pub mod emoji; pub mod escape_sql; pub mod format_milliseconds; +pub mod get_note_summary; pub mod mastodon_id; pub mod meta; pub mod nyaify; diff --git a/packages/backend/src/misc/get-note-summary.ts b/packages/backend/src/misc/get-note-summary.ts deleted file mode 100644 index 0a662e434e..0000000000 --- a/packages/backend/src/misc/get-note-summary.ts +++ /dev/null @@ -1,53 +0,0 @@ -import type { Packed } from "./schema.js"; - -/** - * 投稿を表す文字列を取得します。 - * @param {*} note (packされた)投稿 - */ -export const getNoteSummary = (note: Packed<"Note">): string => { - if (note.deletedAt) { - return "❌"; - } - - let summary = ""; - - // 本文 - if (note.cw != null) { - summary += note.cw; - } else { - summary += note.text ? note.text : ""; - } - - // ファイルが添付されているとき - if ((note.files || []).length !== 0) { - const len = note.files?.length; - summary += ` 📎${len !== 1 ? ` (${len})` : ""}`; - } - - // 投票が添付されているとき - if (note.poll) { - summary += " 📊"; - } - - /* - // 返信のとき - if (note.replyId) { - if (note.reply) { - summary += `\n\nRE: ${getNoteSummary(note.reply)}`; - } else { - summary += '\n\nRE: ...'; - } - } - - // Renoteのとき - if (note.renoteId) { - if (note.renote) { - summary += `\n\nRN: ${getNoteSummary(note.renote)}`; - } else { - summary += '\n\nRN: ...'; - } - } - */ - - return summary.trim(); -}; diff --git a/packages/backend/src/models/schema/note.ts b/packages/backend/src/models/schema/note.ts index 7dcdbc9b03..fff872b69f 100644 --- a/packages/backend/src/models/schema/note.ts +++ b/packages/backend/src/models/schema/note.ts @@ -28,7 +28,7 @@ export const packedNoteSchema = { }, cw: { type: "string", - optional: true, + optional: false, nullable: true, }, userId: { @@ -98,7 +98,7 @@ export const packedNoteSchema = { }, fileIds: { type: "array", - optional: true, + optional: false, nullable: false, items: { type: "string", @@ -128,6 +128,11 @@ export const packedNoteSchema = { nullable: false, }, }, + hasPoll: { + type: "boolean", + optional: false, + nullable: false, + }, poll: { type: "object", optional: true, diff --git a/packages/backend/src/server/web/index.ts b/packages/backend/src/server/web/index.ts index 6473073370..939fcfab14 100644 --- a/packages/backend/src/server/web/index.ts +++ b/packages/backend/src/server/web/index.ts @@ -27,8 +27,7 @@ import { Emojis, GalleryPosts, } from "@/models/index.js"; -import { stringToAcct } from "backend-rs"; -import { getNoteSummary } from "@/misc/get-note-summary.js"; +import { getNoteSummary, stringToAcct } from "backend-rs"; import { queues } from "@/queue/queues.js"; import { genOpenapiSpec } from "../api/openapi/gen-spec.js"; import { urlPreviewHandler } from "./url-preview.js"; @@ -517,8 +516,8 @@ router.get("/notes/:note", async (ctx, next) => { }); try { - if (note) { - const _note = await Notes.pack(note); + if (note != null) { + const packedNote = await Notes.pack(note); const profile = await UserProfiles.findOneByOrFail({ userId: note.userId, @@ -526,13 +525,13 @@ router.get("/notes/:note", async (ctx, next) => { const meta = await fetchMeta(true); await ctx.render("note", { ...metaToPugArgs(meta), - note: _note, + note: packedNote, profile, avatarUrl: await Users.getAvatarUrl( await Users.findOneByOrFail({ id: note.userId }), ), // TODO: Let locale changeable by instance setting - summary: getNoteSummary(_note), + summary: getNoteSummary(note), }); ctx.set("Cache-Control", "public, max-age=15"); diff --git a/packages/backend/src/services/push-notification.ts b/packages/backend/src/services/push-notification.ts index 1a772ff9c5..3f1f2cfb1a 100644 --- a/packages/backend/src/services/push-notification.ts +++ b/packages/backend/src/services/push-notification.ts @@ -1,9 +1,8 @@ import push from "web-push"; import config from "@/config/index.js"; import { SwSubscriptions } from "@/models/index.js"; -import { fetchMeta } from "backend-rs"; +import { fetchMeta, getNoteSummary } from "backend-rs"; import type { Packed } from "@/misc/schema.js"; -import { getNoteSummary } from "@/misc/get-note-summary.js"; // Defined also packages/sw/types.ts#L14-L21 type pushNotificationsTypes = { @@ -17,15 +16,15 @@ type pushNotificationsTypes = { // プッシュメッセージサーバーには文字数制限があるため、内容を削減します function truncateNotification(notification: Packed<"Notification">): any { - if (notification.note) { + if (notification.note != null) { return { ...notification, note: { ...notification.note, - // textをgetNoteSummaryしたものに置き換える + // replace the text with summary text: getNoteSummary( - notification.type === "renote" - ? (notification.note.renote as Packed<"Note">) + notification.type === "renote" && notification.note.renote != null + ? notification.note.renote : notification.note, ), From c19c439ac11692efd15e5fade5bed13ae52f9886 Mon Sep 17 00:00:00 2001 From: naskya Date: Thu, 18 Apr 2024 05:08:14 +0900 Subject: [PATCH 070/139] fix (backend): add hasPoll to packed note --- packages/backend/src/models/repositories/note.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/backend/src/models/repositories/note.ts b/packages/backend/src/models/repositories/note.ts index 1bf4ef657f..c877048709 100644 --- a/packages/backend/src/models/repositories/note.ts +++ b/packages/backend/src/models/repositories/note.ts @@ -232,6 +232,7 @@ export const NoteRepository = db.getRepository(Note).extend({ uri: note.uri || undefined, url: note.url || undefined, updatedAt: note.updatedAt?.toISOString() || undefined, + hasPoll: note.hasPoll, poll: note.hasPoll ? populatePoll(note, meId) : undefined, ...(meId ? { From ff08d044b574aaa1ba36922bf97d64f461067641 Mon Sep 17 00:00:00 2001 From: naskya Date: Thu, 18 Apr 2024 05:22:54 +0900 Subject: [PATCH 071/139] chore: format --- packages/client/src/components/MkMenu.vue | 2 +- packages/client/src/os.ts | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/client/src/components/MkMenu.vue b/packages/client/src/components/MkMenu.vue index 1391024264..a90e42fc29 100644 --- a/packages/client/src/components/MkMenu.vue +++ b/packages/client/src/components/MkMenu.vue @@ -244,7 +244,7 @@ const itemsEl = ref(); /** * Strictly speaking, this type conversion is wrong - * because `ref` will deeply unpack the `ref` in `MenuSwitch`. + * because `ref` will deeply unpack the `ref` in `MenuSwitch`. * But it performs correctly, so who cares? */ const items2 = ref([]) as Ref; diff --git a/packages/client/src/os.ts b/packages/client/src/os.ts index 0bdacd3adb..8fbe8b8041 100644 --- a/packages/client/src/os.ts +++ b/packages/client/src/os.ts @@ -841,7 +841,9 @@ export async function openEmojiPicker( activeTextarea = initialTextarea; - const textareas = document.querySelectorAll("textarea, input"); + const textareas = document.querySelectorAll< + HTMLTextAreaElement | HTMLInputElement + >("textarea, input"); for (const textarea of Array.from(textareas)) { textarea.addEventListener("focus", () => { activeTextarea = textarea; @@ -853,7 +855,9 @@ export async function openEmojiPicker( for (const node of Array.from(record.addedNodes).filter( (node) => node instanceof HTMLElement, ) as HTMLElement[]) { - const textareas = node.querySelectorAll("textarea, input"); + const textareas = node.querySelectorAll< + HTMLTextAreaElement | HTMLInputElement + >("textarea, input"); for (const textarea of Array.from(textareas).filter( (textarea) => textarea.dataset.preventEmojiInsert == null, )) { From 78092cd4bedf339176ba367580bd8f5c7dc9c960 Mon Sep 17 00:00:00 2001 From: naskya Date: Thu, 18 Apr 2024 05:32:42 +0900 Subject: [PATCH 072/139] dev (client): update eslint rules --- packages/client/.eslintrc.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/client/.eslintrc.json b/packages/client/.eslintrc.json index b0e97b2fa6..37d80f6588 100644 --- a/packages/client/.eslintrc.json +++ b/packages/client/.eslintrc.json @@ -4,10 +4,10 @@ "ignorePatterns": ["**/*.json5"], "rules": { "file-progress/activate": 1, - "prettier/prettier": 0, - "one-var": ["error", "never"], + "prettier/prettier": "off", + "one-var": ["warn", "never"], "@typescript-eslint/no-unused-vars": [ - "error", + "warn", { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_", From 9d876798004f3dcbe812cab2c08cfc221625f910 Mon Sep 17 00:00:00 2001 From: naskya Date: Thu, 18 Apr 2024 05:34:23 +0900 Subject: [PATCH 073/139] chore: lint --- packages/client/src/components/MkAbuseReport.vue | 2 +- packages/client/src/components/MkAnnouncement.vue | 2 +- packages/client/src/components/MkAvatars.vue | 2 +- packages/client/src/components/MkChannelFollowButton.vue | 2 +- packages/client/src/components/MkChannelPreview.vue | 2 +- packages/client/src/components/MkContainer.vue | 2 +- packages/client/src/components/MkGalleryPostPreview.vue | 2 +- packages/client/src/components/MkInstanceStats.vue | 2 +- packages/client/src/components/MkInstanceTicker.vue | 2 +- packages/client/src/components/MkMediaCaption.vue | 2 +- packages/client/src/components/MkNotes.vue | 2 +- packages/client/src/components/MkNotification.vue | 4 ++-- packages/client/src/components/MkNotificationToast.vue | 2 +- packages/client/src/components/MkPagePreview.vue | 2 +- packages/client/src/components/MkReactionTooltip.vue | 2 +- .../client/src/components/MkReactionsViewer.details.vue | 4 ++-- packages/client/src/components/MkSignin.vue | 2 +- packages/client/src/components/MkUserList.vue | 2 +- packages/client/src/components/MkUsersTooltip.vue | 4 ++-- packages/client/src/components/MkWaitingDialog.vue | 3 ++- packages/client/src/os.ts | 6 +++--- packages/client/src/pages/admin/abuses.vue | 2 +- packages/client/src/pages/channel-editor.vue | 2 +- packages/client/src/pages/channel.vue | 2 +- packages/client/src/pages/channels.vue | 2 +- packages/client/src/pages/gallery/post.vue | 2 +- packages/client/src/pages/my-antennas/index.vue | 3 ++- packages/client/src/pages/note-history.vue | 2 +- packages/client/src/scripts/use-chart-tooltip.ts | 6 +++--- packages/client/src/store.ts | 2 +- packages/client/src/types/form.ts | 4 ++-- packages/client/src/types/note.ts | 4 ++-- packages/client/src/types/post-form.ts | 4 ++-- 33 files changed, 45 insertions(+), 43 deletions(-) diff --git a/packages/client/src/components/MkAbuseReport.vue b/packages/client/src/components/MkAbuseReport.vue index 26b91fae2e..b190652052 100644 --- a/packages/client/src/components/MkAbuseReport.vue +++ b/packages/client/src/components/MkAbuseReport.vue @@ -67,12 +67,12 @@