From b8f9130386ca8f884ba55cb149c33ad8992d968d Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih Date: Wed, 5 Apr 2023 15:48:44 +0900 Subject: [PATCH 01/21] add embed code --- .../src/server/web/ClientServerService.ts | 30 ++++ packages/frontend/package.json | 2 + .../src/components/MkNoteDetailed.vue | 107 +++++++++---- .../frontend/src/components/global/MkA.vue | 5 + packages/frontend/src/init.ts | 1 + packages/frontend/src/pages/note-embed.vue | 145 ++++++++++++++++++ packages/frontend/src/router.ts | 4 + .../frontend/src/scripts/get-embed-code.ts | 29 ++++ .../frontend/src/scripts/get-note-menu.ts | 29 +++- packages/frontend/src/ui/embed.vue | 48 ++++++ pnpm-lock.yaml | 15 ++ 11 files changed, 383 insertions(+), 32 deletions(-) create mode 100644 packages/frontend/src/pages/note-embed.vue create mode 100644 packages/frontend/src/scripts/get-embed-code.ts create mode 100644 packages/frontend/src/ui/embed.vue diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index fb76f07e4816..9e55a5674f12 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -476,6 +476,36 @@ export class ClientServerService { } }); + // Note Embed + fastify.get<{ Params: { note: string; } }>('/notes/:note/embed', async (request, reply) => { + reply.removeHeader('X-Frame-Options'); + reply.header("X-Robots-Tag", "noindex"); + + const note = await this.notesRepository.findOneBy({ + id: request.params.note, + visibility: In(['public', 'home']), + }); + + if (note) { + const _note = await this.noteEntityService.pack(note); + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: note.userId }); + const meta = await this.metaService.fetch(); + reply.header('Cache-Control', 'public, max-age=15'); + return await reply.view('note', { + note: _note, + profile, + avatarUrl: await this.userEntityService.getAvatarUrl(await this.usersRepository.findOneByOrFail({ id: note.userId })), + // TODO: Let locale changeable by instance setting + summary: getNoteSummary(_note), + instanceName: meta.name ?? 'Misskey', + icon: meta.iconUrl, + themeColor: meta.themeColor, + }); + } else { + return await renderBase(reply); + } + }); + // Page fastify.get<{ Params: { user: string; page: string; } }>('/@:user/pages/:page', async (request, reply) => { const { username, host } = Acct.parse(request.params.user); diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 2d96d5514eaf..436333ae27c4 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -39,6 +39,7 @@ "eventemitter3": "5.0.0", "gsap": "3.11.5", "idb-keyval": "6.2.0", + "iframe-resizer": "^4.3.6", "insert-text-at-cursor": "0.3.0", "is-file-animated": "1.0.2", "json5": "2.2.3", @@ -97,6 +98,7 @@ "@types/estree": "^1.0.0", "@types/gulp": "4.0.10", "@types/gulp-rename": "2.0.1", + "@types/iframe-resizer": "^3.5.9", "@types/matter-js": "0.18.2", "@types/node": "18.15.11", "@types/punycode": "2.1.0", diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index 67bdfd225820..3459b70005a5 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -54,6 +54,11 @@
+
+ +
@@ -91,32 +96,54 @@
- - - - - - + + @@ -158,6 +185,8 @@ import { defaultStore, noteViewInterruptors } from '@/store'; import { reactionPicker } from '@/scripts/reaction-picker'; import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm'; import { $i } from '@/account'; +import { instance } from '@/instance'; +import { openInstanceMenu } from '@/ui/_common_/common'; import { i18n } from '@/i18n'; import { getNoteClipMenu, getNoteMenu } from '@/scripts/get-note-menu'; import { useNoteCapture } from '@/scripts/use-note-capture'; @@ -170,6 +199,7 @@ import MkRippleEffect from '@/components/MkRippleEffect.vue'; const props = defineProps<{ note: misskey.entities.Note; pinned?: boolean; + embed?: boolean; }>(); const inChannel = inject('inChannel', null); @@ -378,12 +408,12 @@ function onContextmenu(ev: MouseEvent): void { ev.preventDefault(); react(); } else { - os.contextMenu(getNoteMenu({ note: note, translating, translation, menuButton, isDeleted }), ev).then(focus); + os.contextMenu(getNoteMenu({ note: note, translating, translation, menuButton, isDeleted, embed: props.embed }), ev).then(focus); } } function menu(viaKeyboard = false): void { - os.popupMenu(getNoteMenu({ note: note, translating, translation, menuButton, isDeleted }), menuButton.value, { + os.popupMenu(getNoteMenu({ note: note, translating, translation, menuButton, isDeleted, embed: props.embed }), menuButton.value, { viaKeyboard, }).then(focus); } @@ -464,6 +494,10 @@ if (appearNote.replyId) { &:hover > .article > .main > .footer > .button { opacity: 1; + + &:hover { + text-decoration: none; + } } > .reply-to { @@ -578,6 +612,19 @@ if (appearNote.replyId) { word-wrap: anywhere; } } + + > .instance-info { + flex-shrink: 0; + padding-left: 16px; + width: 39px; + height: 39px; + + img { + width: 100%; + height: auto; + border-radius: 4px; + } + } } > .main { @@ -734,6 +781,10 @@ if (appearNote.replyId) { width: 50px; height: 50px; } + > .instance-info { + width: 33px; + height: 33px; + } } > .main { diff --git a/packages/frontend/src/components/global/MkA.vue b/packages/frontend/src/components/global/MkA.vue index 40d134dffb33..a4e2cc4e54d8 100644 --- a/packages/frontend/src/components/global/MkA.vue +++ b/packages/frontend/src/components/global/MkA.vue @@ -79,6 +79,11 @@ function popout() { } function nav(ev: MouseEvent) { + if (router.currentRoute.value.name?.toLowerCase().includes("embed")) { + window.open(props.to, '_blank'); + return; + } + if (props.behavior === 'browser') { location.href = props.to; return; diff --git a/packages/frontend/src/init.ts b/packages/frontend/src/init.ts index 26c5adfc70d9..f0836c48b83b 100644 --- a/packages/frontend/src/init.ts +++ b/packages/frontend/src/init.ts @@ -187,6 +187,7 @@ try { } catch (err) {} const app = createApp( + window.location.href.includes("/embed") ? defineAsyncComponent(() => import('@/ui/embed.vue')) : window.location.search === '?zen' ? defineAsyncComponent(() => import('@/ui/zen.vue')) : !$i ? defineAsyncComponent(() => import('@/ui/visitor.vue')) : ui === 'deck' ? defineAsyncComponent(() => import('@/ui/deck.vue')) : diff --git a/packages/frontend/src/pages/note-embed.vue b/packages/frontend/src/pages/note-embed.vue new file mode 100644 index 000000000000..b823d3fad193 --- /dev/null +++ b/packages/frontend/src/pages/note-embed.vue @@ -0,0 +1,145 @@ + + + + + diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts index c8077edd288c..964d46608f50 100644 --- a/packages/frontend/src/router.ts +++ b/packages/frontend/src/router.ts @@ -30,6 +30,10 @@ export const routes = [{ name: 'note', path: '/notes/:noteId', component: page(() => import('./pages/note.vue')), +}, { + name: 'noteEmbed', + path: '/notes/:noteId/embed', + component: page(() => import('./pages/note-embed.vue')), }, { path: '/clips/:clipId', component: page(() => import('./pages/clip.vue')), diff --git a/packages/frontend/src/scripts/get-embed-code.ts b/packages/frontend/src/scripts/get-embed-code.ts new file mode 100644 index 000000000000..4338dc14a410 --- /dev/null +++ b/packages/frontend/src/scripts/get-embed-code.ts @@ -0,0 +1,29 @@ +import { url } from '@/config'; +import { v4 as uuid } from 'uuid'; + +/** + * 埋め込みコードを出力します。 + */ +export function getEmbedCode(props: { + entityType: 'notes'; + id: string; +}): string | null { + let src: string | undefined = ""; + + switch (props.entityType) { + case 'notes': + src = `${url}/notes/${props.id}/embed`; + break; + default: + src = undefined; + } + + if (src !== undefined) { + const id = uuid(); + return ` + +`; + } + + return null; +} \ No newline at end of file diff --git a/packages/frontend/src/scripts/get-note-menu.ts b/packages/frontend/src/scripts/get-note-menu.ts index d91f0b0eb656..b19aaf74e9ca 100644 --- a/packages/frontend/src/scripts/get-note-menu.ts +++ b/packages/frontend/src/scripts/get-note-menu.ts @@ -11,6 +11,7 @@ import { noteActions } from '@/store'; import { miLocalStorage } from '@/local-storage'; import { getUserMenu } from '@/scripts/get-user-menu'; import { clipsCache } from '@/cache'; +import { getEmbedCode } from './get-embed-code'; export async function getNoteClipMenu(props: { note: misskey.entities.Note; @@ -93,6 +94,7 @@ export function getNoteMenu(props: { translating: Ref; isDeleted: Ref; currentClip?: misskey.entities.Clip; + embed?: boolean; }) { const isRenote = ( props.note.renote != null && @@ -202,7 +204,17 @@ export function getNoteMenu(props: { } function openDetail(): void { - os.pageWindow(`/notes/${appearNote.id}`); + if (props.embed) { + window.open(`/notes/${appearNote.id}`, "_blank"); + } else { + os.pageWindow(`/notes/${appearNote.id}`); + } + } + + function copyEmbedCode(): void { + console.log(getEmbedCode({ entityType: 'notes', id: appearNote.id })); + copyToClipboard(getEmbedCode({entityType: 'notes', id: appearNote.id})); + os.success(); } function showReactions(): void { @@ -223,7 +235,7 @@ export function getNoteMenu(props: { } let menu; - if ($i) { + if ($i && !props.embed) { const statePromise = os.api('notes/state', { noteId: appearNote.id, }); @@ -264,6 +276,11 @@ export function getNoteMenu(props: { text: i18n.ts.share, action: share, }, + (!props.embed) ? { + icon: 'ti ti-code', + text: "Embed", + action: copyEmbedCode, + } : undefined, instance.translatorAvailable ? { icon: 'ti ti-language-hiragana', text: i18n.ts.translate, @@ -356,7 +373,7 @@ export function getNoteMenu(props: { } else { menu = [{ icon: 'ti ti-external-link', - text: i18n.ts.detailed, + text: i18n.ts.details, action: openDetail, }, { icon: 'ti ti-copy', @@ -372,7 +389,11 @@ export function getNoteMenu(props: { action: () => { window.open(appearNote.url ?? appearNote.uri, '_blank'); }, - } : undefined] + } : undefined, (!props.embed) ? { + icon: 'ti ti-code', + text: "Embed", + action: copyEmbedCode, + } : undefined,] .filter(x => x !== undefined); } diff --git a/packages/frontend/src/ui/embed.vue b/packages/frontend/src/ui/embed.vue new file mode 100644 index 000000000000..dffc56715fbe --- /dev/null +++ b/packages/frontend/src/ui/embed.vue @@ -0,0 +1,48 @@ + + + + + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a130bb12c6d6..98d44b51fc18 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -663,6 +663,9 @@ importers: idb-keyval: specifier: 6.2.0 version: 6.2.0 + iframe-resizer: + specifier: ^4.3.6 + version: 4.3.6 insert-text-at-cursor: specifier: 0.3.0 version: 0.3.0 @@ -832,6 +835,9 @@ importers: '@types/gulp-rename': specifier: 2.0.1 version: 2.0.1 + '@types/iframe-resizer': + specifier: ^3.5.9 + version: 3.5.9 '@types/matter-js': specifier: 0.18.2 version: 0.18.2 @@ -6614,6 +6620,10 @@ packages: /@types/http-cache-semantics@4.0.1: resolution: {integrity: sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==} + /@types/iframe-resizer@3.5.9: + resolution: {integrity: sha512-RQUBI75F+uXruB95BFpC/8V8lPgJg4MQ6HxOCtAZYBB/h0FNCfrFfb4I+u2pZJIV7sKeszZbFqy1UnGeBMrvsA==} + dev: true + /@types/ioredis@4.28.10: resolution: {integrity: sha512-69LyhUgrXdgcNDv7ogs1qXZomnfOEnSmrmMFqKgt1XMJxmoOSG/u3wYy13yACIfKuMJ8IhKgHafDO3sx19zVQQ==} dependencies: @@ -12590,6 +12600,11 @@ packages: /ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + /iframe-resizer@4.3.6: + resolution: {integrity: sha512-wz0WodRIF6eP0oGQa5NIP1yrITAZ59ZJvVaVJqJRjaeCtfm461vy2C3us6CKx0e7pooqpIGLpVMSTzrfAjX9Sg==} + engines: {node: '>=0.8.0'} + dev: false + /ignore@5.2.4: resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} engines: {node: '>= 4'} From 1ec8e267e009f0c25e3af088262bf916884cfe0e Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih Date: Wed, 5 Apr 2023 16:13:53 +0900 Subject: [PATCH 02/21] (i18n) translation --- locales/ja-JP.yml | 1 + packages/frontend/src/scripts/get-note-menu.ts | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index a4f1d802cc4e..d870d6a89563 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -986,6 +986,7 @@ enableChartsForFederatedInstances: "リモートサーバーのチャートを showClipButtonInNoteFooter: "ノートのアクションにクリップを追加" largeNoteReactions: "ノートのリアクションを大きく表示" noteIdOrUrl: "ノートIDまたはURL" +copyEmbedCode: "埋め込みコードをコピー" _achievements: earnedAt: "獲得日時" diff --git a/packages/frontend/src/scripts/get-note-menu.ts b/packages/frontend/src/scripts/get-note-menu.ts index b19aaf74e9ca..02ffca1fa009 100644 --- a/packages/frontend/src/scripts/get-note-menu.ts +++ b/packages/frontend/src/scripts/get-note-menu.ts @@ -278,7 +278,7 @@ export function getNoteMenu(props: { }, (!props.embed) ? { icon: 'ti ti-code', - text: "Embed", + text: i18n.ts.copyEmbedCode, action: copyEmbedCode, } : undefined, instance.translatorAvailable ? { @@ -391,7 +391,7 @@ export function getNoteMenu(props: { }, } : undefined, (!props.embed) ? { icon: 'ti ti-code', - text: "Embed", + text: i18n.ts.copyEmbedCode, action: copyEmbedCode, } : undefined,] .filter(x => x !== undefined); From e4762b4b8590df9eeac974da67cb3ae571670a1e Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih Date: Thu, 6 Apr 2023 09:11:35 +0900 Subject: [PATCH 03/21] (design) delete min-height --- .../frontend/src/components/global/MkA.vue | 84 ++++++++++++------- packages/frontend/src/pages/note-embed.vue | 4 +- .../frontend/src/scripts/get-embed-code.ts | 2 +- packages/frontend/src/ui/embed.vue | 5 -- 4 files changed, 58 insertions(+), 37 deletions(-) diff --git a/packages/frontend/src/components/global/MkA.vue b/packages/frontend/src/components/global/MkA.vue index a4e2cc4e54d8..901454021da5 100644 --- a/packages/frontend/src/components/global/MkA.vue +++ b/packages/frontend/src/components/global/MkA.vue @@ -11,6 +11,7 @@ import { url } from '@/config'; import { popout as popout_ } from '@/scripts/popout'; import { i18n } from '@/i18n'; import { useRouter } from '@/router'; +import { MenuItem } from '@/types/menu'; const props = withDefaults(defineProps<{ to: string; @@ -33,37 +34,60 @@ const active = $computed(() => { return resolved.route.name === router.currentRoute.value.name; }); -function onContextmenu(ev) { +function onContextmenu(ev : Event) { const selection = window.getSelection(); - if (selection && selection.toString() !== '') return; - os.contextMenu([{ - type: 'label', - text: props.to, - }, { - icon: 'ti ti-app-window', - text: i18n.ts.openInWindow, - action: () => { - os.pageWindow(props.to); - }, - }, { - icon: 'ti ti-player-eject', - text: i18n.ts.showInPage, - action: () => { - router.push(props.to, 'forcePage'); - }, - }, null, { - icon: 'ti ti-external-link', - text: i18n.ts.openInNewTab, - action: () => { - window.open(props.to, '_blank'); - }, - }, { - icon: 'ti ti-link', - text: i18n.ts.copyLink, - action: () => { - copyToClipboard(`${url}${props.to}`); - }, - }], ev); + if ((selection && selection.toString() !== '')) return; + + let contextMenuItem: MenuItem[] = []; + + if (router.currentRoute.value.name?.toLowerCase().includes("embed")) { + contextMenuItem = [{ + type: 'label', + text: props.to, + }, { + icon: 'ti ti-external-link', + text: i18n.ts.openInNewTab, + action: () => { + window.open(props.to, '_blank'); + }, + }, { + icon: 'ti ti-link', + text: i18n.ts.copyLink, + action: () => { + copyToClipboard(`${url}${props.to}`); + }, + }]; + } else { + contextMenuItem = [{ + type: 'label', + text: props.to, + }, { + icon: 'ti ti-app-window', + text: i18n.ts.openInWindow, + action: () => { + os.pageWindow(props.to); + }, + }, { + icon: 'ti ti-player-eject', + text: i18n.ts.showInPage, + action: () => { + router.push(props.to, 'forcePage'); + }, + }, null, { + icon: 'ti ti-external-link', + text: i18n.ts.openInNewTab, + action: () => { + window.open(props.to, '_blank'); + }, + }, { + icon: 'ti ti-link', + text: i18n.ts.copyLink, + action: () => { + copyToClipboard(`${url}${props.to}`); + }, + }]; + } + os.contextMenu(contextMenuItem, ev); } function openWindow() { diff --git a/packages/frontend/src/pages/note-embed.vue b/packages/frontend/src/pages/note-embed.vue index b823d3fad193..e80425a3e939 100644 --- a/packages/frontend/src/pages/note-embed.vue +++ b/packages/frontend/src/pages/note-embed.vue @@ -93,7 +93,9 @@ definePageMetadata(computed(() => note ? { } .fcuexfpr { - background: var(--bg); + border-radius: var(--radius); + border: 1px solid var(--divider); + background: var(--panel); > .note { height: 100%; diff --git a/packages/frontend/src/scripts/get-embed-code.ts b/packages/frontend/src/scripts/get-embed-code.ts index 4338dc14a410..b5492b6cc6e9 100644 --- a/packages/frontend/src/scripts/get-embed-code.ts +++ b/packages/frontend/src/scripts/get-embed-code.ts @@ -20,7 +20,7 @@ export function getEmbedCode(props: { if (src !== undefined) { const id = uuid(); - return ` + return ` `; } diff --git a/packages/frontend/src/ui/embed.vue b/packages/frontend/src/ui/embed.vue index dffc56715fbe..df65333719bd 100644 --- a/packages/frontend/src/ui/embed.vue +++ b/packages/frontend/src/ui/embed.vue @@ -31,17 +31,12 @@ provideMetadataReceiver((info) => { document.documentElement.style.backgroundColor = "transparent"; document.documentElement.style.maxWidth = "650px"; -document.documentElement.style.minHeight = "300px"; diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts index 964d46608f50..c8077edd288c 100644 --- a/packages/frontend/src/router.ts +++ b/packages/frontend/src/router.ts @@ -30,10 +30,6 @@ export const routes = [{ name: 'note', path: '/notes/:noteId', component: page(() => import('./pages/note.vue')), -}, { - name: 'noteEmbed', - path: '/notes/:noteId/embed', - component: page(() => import('./pages/note-embed.vue')), }, { path: '/clips/:clipId', component: page(() => import('./pages/clip.vue')), diff --git a/packages/frontend/src/scripts/get-note-menu.ts b/packages/frontend/src/scripts/get-note-menu.ts index 8bff2d362ef1..60e2b18a024a 100644 --- a/packages/frontend/src/scripts/get-note-menu.ts +++ b/packages/frontend/src/scripts/get-note-menu.ts @@ -94,7 +94,6 @@ export function getNoteMenu(props: { translating: Ref; isDeleted: Ref; currentClip?: misskey.entities.Clip; - embed?: boolean; }) { const isRenote = ( props.note.renote != null && @@ -235,7 +234,7 @@ export function getNoteMenu(props: { } let menu; - if ($i && !props.embed) { + if ($i) { const statePromise = os.api('notes/state', { noteId: appearNote.id, }); @@ -275,13 +274,11 @@ export function getNoteMenu(props: { icon: 'ti ti-share', text: i18n.ts.share, action: share, - }, - (!props.embed) ? { + }, { icon: 'ti ti-code', text: i18n.ts.copyEmbedCode, action: copyEmbedCode, - } : undefined, - instance.translatorAvailable ? { + }, instance.translatorAvailable ? { icon: 'ti ti-language-hiragana', text: i18n.ts.translate, action: translate, @@ -389,7 +386,7 @@ export function getNoteMenu(props: { action: () => { window.open(appearNote.url ?? appearNote.uri, '_blank'); }, - } : undefined, (!props.embed) ? { + } : undefined, (!appearNote.url && !appearNote.uri) ? { icon: 'ti ti-code', text: i18n.ts.copyEmbedCode, action: copyEmbedCode, diff --git a/packages/frontend/src/ui/embed.vue b/packages/frontend/src/ui/embed.vue deleted file mode 100644 index 3df3b9a2ec1e..000000000000 --- a/packages/frontend/src/ui/embed.vue +++ /dev/null @@ -1,43 +0,0 @@ - - - - - diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 521f34d7d9a8..ff9765b5c4ef 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -663,9 +663,6 @@ importers: idb-keyval: specifier: 6.2.0 version: 6.2.0 - iframe-resizer: - specifier: ^4.3.6 - version: 4.3.6 insert-text-at-cursor: specifier: 0.3.0 version: 0.3.0 @@ -835,9 +832,6 @@ importers: '@types/gulp-rename': specifier: 2.0.1 version: 2.0.1 - '@types/iframe-resizer': - specifier: ^3.5.9 - version: 3.5.9 '@types/matter-js': specifier: 0.18.2 version: 0.18.2 @@ -6599,10 +6593,6 @@ packages: /@types/http-cache-semantics@4.0.1: resolution: {integrity: sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==} - /@types/iframe-resizer@3.5.9: - resolution: {integrity: sha512-RQUBI75F+uXruB95BFpC/8V8lPgJg4MQ6HxOCtAZYBB/h0FNCfrFfb4I+u2pZJIV7sKeszZbFqy1UnGeBMrvsA==} - dev: true - /@types/ioredis@4.28.10: resolution: {integrity: sha512-69LyhUgrXdgcNDv7ogs1qXZomnfOEnSmrmMFqKgt1XMJxmoOSG/u3wYy13yACIfKuMJ8IhKgHafDO3sx19zVQQ==} dependencies: @@ -12536,11 +12526,6 @@ packages: /ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - /iframe-resizer@4.3.6: - resolution: {integrity: sha512-wz0WodRIF6eP0oGQa5NIP1yrITAZ59ZJvVaVJqJRjaeCtfm461vy2C3us6CKx0e7pooqpIGLpVMSTzrfAjX9Sg==} - engines: {node: '>=0.8.0'} - dev: false - /ignore@5.2.4: resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} engines: {node: '>= 4'} From 73d552814ad40f1a5a4775733d1c21f835f8ee1b Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih Date: Sun, 9 Apr 2023 04:43:38 +0900 Subject: [PATCH 07/21] (wip) ssr using pug --- gulpfile.js | 13 +- package.json | 7 +- packages/backend/src/config.ts | 7 +- .../src/server/web/ClientServerService.ts | 42 ++- packages/backend/src/server/web/embed.css | 21 ++ .../src/server/web/views/embed/404.pug | 16 + .../src/server/web/views/embed/base.pug | 98 ++++++ .../src/server/web/views/embed/note.pug | 53 +++ packages/frontend/package.json | 2 + packages/frontend/src/embed/embed.scss | 125 +++++++ packages/frontend/src/embed/init.ts | 82 +++++ .../frontend/src/embed/scripts/embed-i18n.ts | 46 +++ .../frontend/src/embed/scripts/link-anime.ts | 33 ++ .../frontend/src/embed/scripts/parse-mfm.ts | 329 ++++++++++++++++++ packages/frontend/tsconfig.json | 2 +- packages/frontend/vite.config.ts | 1 + pnpm-lock.yaml | 15 + 17 files changed, 884 insertions(+), 8 deletions(-) create mode 100644 packages/backend/src/server/web/embed.css create mode 100644 packages/backend/src/server/web/views/embed/404.pug create mode 100644 packages/backend/src/server/web/views/embed/base.pug create mode 100644 packages/backend/src/server/web/views/embed/note.pug create mode 100644 packages/frontend/src/embed/embed.scss create mode 100644 packages/frontend/src/embed/init.ts create mode 100644 packages/frontend/src/embed/scripts/embed-i18n.ts create mode 100644 packages/frontend/src/embed/scripts/link-anime.ts create mode 100644 packages/frontend/src/embed/scripts/parse-mfm.ts diff --git a/gulpfile.js b/gulpfile.js index a04ab4c1ad4e..a7620db42149 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -36,7 +36,11 @@ gulp.task('copy:frontend:locales', cb => { }); gulp.task('build:backend:script', () => { - return gulp.src(['./packages/backend/src/server/web/boot.js', './packages/backend/src/server/web/bios.js', './packages/backend/src/server/web/cli.js']) + return gulp.src([ + './packages/backend/src/server/web/boot.js', + './packages/backend/src/server/web/bios.js', + './packages/backend/src/server/web/cli.js', + ]) .pipe(replace('LANGS', JSON.stringify(Object.keys(locales)))) .pipe(terser({ toplevel: true @@ -45,7 +49,12 @@ gulp.task('build:backend:script', () => { }); gulp.task('build:backend:style', () => { - return gulp.src(['./packages/backend/src/server/web/style.css', './packages/backend/src/server/web/bios.css', './packages/backend/src/server/web/cli.css']) + return gulp.src([ + './packages/backend/src/server/web/style.css', + './packages/backend/src/server/web/bios.css', + './packages/backend/src/server/web/cli.css', + './packages/backend/src/server/web/embed.css' + ]) .pipe(cssnano({ zindex: false })) diff --git a/package.json b/package.json index d1c081c86df4..595eb7286174 100644 --- a/package.json +++ b/package.json @@ -4,13 +4,14 @@ "codename": "nasubi", "repository": { "type": "git", - "url": "https://github.com/misskey-dev/misskey.git" + "url": "https://github.com/kakkokari-gtyih/misskey.git" }, "packageManager": "pnpm@8.1.1", "workspaces": [ "packages/frontend", "packages/backend", - "packages/sw" + "packages/sw", + "packages/misskey-js" ], "private": true, "scripts": { @@ -66,4 +67,4 @@ "optionalDependencies": { "@tensorflow/tfjs-core": "4.2.0" } -} +} \ No newline at end of file diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index fd2b83cf2a39..707a37cad165 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -103,6 +103,7 @@ export type Mixin = { driveUrl: string; userAgent: string; clientEntry: string; + clientEmbedEntry: string; clientManifestExists: boolean; mediaProxy: string; externalMediaProxyEnabled: boolean; @@ -133,7 +134,10 @@ export function loadConfig() { const clientManifestExists = fs.existsSync(_dirname + '/../../../built/_vite_/manifest.json'); const clientManifest = clientManifestExists ? JSON.parse(fs.readFileSync(`${_dirname}/../../../built/_vite_/manifest.json`, 'utf-8')) - : { 'src/init.ts': { file: 'src/init.ts' } }; + : { + 'src/init.ts': { file: 'src/init.ts' }, + 'src/embed/init.ts': { file: 'src/embed/init.ts' }, + }; const config = yaml.load(fs.readFileSync(path, 'utf-8')) as Source; const mixin = {} as Mixin; @@ -155,6 +159,7 @@ export function loadConfig() { mixin.driveUrl = `${mixin.scheme}://${mixin.host}/files`; mixin.userAgent = `Misskey/${meta.version} (${config.url})`; mixin.clientEntry = clientManifest['src/init.ts']; + mixin.clientEmbedEntry = clientManifest['src/embed/init.ts']; mixin.clientManifestExists = clientManifestExists; const externalMediaProxy = config.mediaProxy ? diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index 99ae1b7af6d2..ed92b16ebe60 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -344,6 +344,17 @@ export class ClientServerService { }); }; + const renderEmbed404 = async (reply: FastifyReply) => { + reply.status(404); + const meta = await this.metaService.fetch(); + + return await reply.view('embed/404', { + instanceName: meta.name ?? 'Misskey', + icon: meta.iconUrl, + url: this.config.url, + }); + }; + // URL preview endpoint fastify.get<{ Querystring: { url: string; lang: string; } }>('/url', (request, reply) => this.urlPreviewService.handle(request, reply)); @@ -469,13 +480,42 @@ export class ClientServerService { summary: getNoteSummary(_note), instanceName: meta.name ?? 'Misskey', icon: meta.iconUrl, - themeColor: meta.themeColor, + themeColor: meta.themeColor }); } else { return await renderBase(reply); } }); + // Note Embed + fastify.get<{ Params: { note: string; } }>('/notes/:note/embed', async (request, reply) => { + vary(reply.raw, 'Accept'); + + const note = await this.notesRepository.findOneBy({ + id: request.params.note, + visibility: In(['public', 'home']), + }); + + if (note) { + const _note = await this.noteEntityService.pack(note); + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: note.userId }); + const meta = await this.metaService.fetch(); + reply.header('Cache-Control', 'public, max-age=15'); + return await reply.view('embed/note', { + note: _note, + profile, + avatarUrl: _note.user.avatarUrl, + // TODO: Let locale changeable by instance setting + summary: getNoteSummary(_note), + instanceName: meta.name ?? 'Misskey', + icon: meta.iconUrl, + themeColor: meta.themeColor, + }); + } else { + return await renderEmbed404(reply); + } + }); + // Page fastify.get<{ Params: { user: string; page: string; } }>('/@:user/pages/:page', async (request, reply) => { const { username, host } = Acct.parse(request.params.user); diff --git a/packages/backend/src/server/web/embed.css b/packages/backend/src/server/web/embed.css new file mode 100644 index 000000000000..21e5b9fcbf26 --- /dev/null +++ b/packages/backend/src/server/web/embed.css @@ -0,0 +1,21 @@ +html,body { + max-width: 650px; +} + +#splash { + max-width: 650px; + width: 100%; + height: 100%; +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} \ No newline at end of file diff --git a/packages/backend/src/server/web/views/embed/404.pug b/packages/backend/src/server/web/views/embed/404.pug new file mode 100644 index 000000000000..44ed0a327677 --- /dev/null +++ b/packages/backend/src/server/web/views/embed/404.pug @@ -0,0 +1,16 @@ +extends ./base + +block style + style. + +block content + div#error + div + div#instance-info + a.click-anime(href=url target='_blank') + img(src= icon || '/static-assets/splash.png') + span.sr-only(data-mi-i18n='aboutX' data-mi-i18n-ctx=`{"x": "${instanceName}"}`) + + img.main(src='https://xn--931a.moe/assets/not-found.jpg') + h2(data-mi-i18n='notFound') + p(data-mi-i18n='notFoundDescription') \ No newline at end of file diff --git a/packages/backend/src/server/web/views/embed/base.pug b/packages/backend/src/server/web/views/embed/base.pug new file mode 100644 index 000000000000..7bec25d83c7c --- /dev/null +++ b/packages/backend/src/server/web/views/embed/base.pug @@ -0,0 +1,98 @@ +block vars + +block loadClientEntry + - const clientEntry = config.clientEmbedEntry; + +doctype html + +// + - + _____ _ _ + | |_|___ ___| |_ ___ _ _ + | | | | |_ -|_ -| '_| -_| | | + |_|_|_|_|___|___|_,_|___|_ | + |___| + Thank you for using Misskey! + If you are reading this message... how about joining the development? + https://github.com/misskey-dev/misskey + + +html + + head + meta(charset='utf-8') + meta(name='application-name' content='Misskey') + meta(name='referrer' content='origin') + meta(name='theme-color' content= themeColor || '#86b300') + meta(name='theme-color-orig' content= themeColor || '#86b300') + meta(property='twitter:card' content='summary') + meta(property='og:site_name' content= instanceName || 'Misskey') + meta(name='viewport' content='width=device-width, initial-scale=1') + link(rel='icon' href= icon || '/favicon.ico') + link(rel='apple-touch-icon' href= icon || '/apple-touch-icon.png') + link(rel='manifest' href='/manifest.json') + link(rel='search' type='application/opensearchdescription+xml' title=(title || "Misskey") href=`${url}/opensearch.xml`) + link(rel='prefetch' href='https://xn--931a.moe/assets/info.jpg') + link(rel='prefetch' href='https://xn--931a.moe/assets/not-found.jpg') + link(rel='prefetch' href='https://xn--931a.moe/assets/error.jpg') + //- https://github.com/misskey-dev/misskey/issues/9842 + link(rel='stylesheet' href='/assets/tabler-icons/tabler-icons.min.css?v2.12.0') + link(rel='modulepreload' href=`/vite/${clientEntry.file}`) + + if !config.clientManifestExists + script(type="module" src="/vite/@vite/client") + + if Array.isArray(clientEntry.css) + each href in clientEntry.css + link(rel='stylesheet' href=`/vite/${href}`) + + title + block title + = title || 'Misskey' + + block desc + meta(name='description' content= desc || '✨🌎✨ A interplanetary communication platform ✨🚀✨') + + block meta + + block og + meta(property='og:title' content= title || 'Misskey') + meta(property='og:description' content= desc || '✨🌎✨ A interplanetary communication platform ✨🚀✨') + meta(property='og:image' content= img) + + style + include ../../style.css + include ../../embed.css + + block style + + script. + var VERSION = "#{version}"; + var CLIENT_ENTRY = "#{clientEntry.file}"; + var EMBED = true; + + script + include ../../boot.js + + body + noscript: p + | JavaScriptを有効にしてください + br + | Please turn on your JavaScript + + div#splash + img#splashIcon(src= icon || '/static-assets/splash.png') + div#splashSpinner + + + + + + + + + + + + div#container + block content \ No newline at end of file diff --git a/packages/backend/src/server/web/views/embed/note.pug b/packages/backend/src/server/web/views/embed/note.pug new file mode 100644 index 000000000000..cb951f55cb73 --- /dev/null +++ b/packages/backend/src/server/web/views/embed/note.pug @@ -0,0 +1,53 @@ +extends ./base + +block vars + - const user = note.user; + - const title = user.name ? `${user.name} (@${user.username})` : `@${user.username}`; + - const url = `${config.url}/notes/${note.id}`; + - const isRenote = note.renote && note.text == null && note.fileIds.length == 0 && note.poll == null; + - const displayUser = isRenote ? note.renote.user: note.user; + +block meta + if !user.host + link(rel='alternate' href=url type='application/activity+json') + if note.uri + link(rel='alternate' href=note.uri type='application/activity+json') + + script(type='application/json') !{JSON.stringify(note)} + +block content + div#note + + header + div.wrapper + if isRenote + div#renote + a.avatar(href=`${config.url}/@${note.user.username}` target="_blank" rel="noopener noreferrer") + img(src=note.user.avatarUrl) + + i.ti.ti-repeat + + span(data-mi-i18n='renotedBy' data-mi-i18n-ctx=`{"user": "${note.user.name || note.user.username}"}`) + a(href=`${config.url}/@${note.user.username}` target="_blank" rel="noopener noreferrer") + b(data-mi-i18n-target='user') + + div.author + a.avatar(href=`${config.url}/@${displayUser.username}` target="_blank" rel="noopener noreferrer") + img(src=displayUser.avatarUrl) + + div.user-info + a.user-name(href=`${config.url}/@${displayUser.username}` target="_blank" rel="noopener noreferrer") #{displayUser.name || displayUser.username} + div.user-id @#{displayUser.username} + + div#instance-info + a.click-anime(href=config.url target='_blank') + img(src= icon || '/static-assets/splash.png') + span.sr-only(data-mi-i18n='aboutX' data-mi-i18n-ctx=`{"x": "${instanceName}"}`) + + main + div.mfm !{isRenote ? note.renote.text : note.text} + if (!isRenote && note.renote) + div#quote.mfm !{note.renote.text} + + hr + pre(style='white-space: pre-wrap;') !{JSON.stringify(note, null, 2)} diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 79fb626a9a9e..1d74a35ddaee 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -39,6 +39,7 @@ "eventemitter3": "5.0.0", "gsap": "3.11.5", "idb-keyval": "6.2.0", + "iframe-resizer": "^4.3.6", "insert-text-at-cursor": "0.3.0", "is-file-animated": "1.0.2", "json5": "2.2.3", @@ -97,6 +98,7 @@ "@types/estree": "1.0.0", "@types/gulp": "4.0.10", "@types/gulp-rename": "2.0.1", + "@types/iframe-resizer": "^3.5.9", "@types/matter-js": "0.18.2", "@types/micromatch": "3.1.1", "@types/node": "18.15.11", diff --git a/packages/frontend/src/embed/embed.scss b/packages/frontend/src/embed/embed.scss new file mode 100644 index 000000000000..bd1da0e817c0 --- /dev/null +++ b/packages/frontend/src/embed/embed.scss @@ -0,0 +1,125 @@ +*,*::before,*::after{margin:0;padding:0;box-sizing:border-box}:where([hidden]:not([hidden='until-found'])){display:none!important} + +html,body { + background-color: transparent; +} + +#container { + padding: 1.5rem 2rem; + border-radius: var(--radius); + border: 1px solid var(--divider); + background: var(--bg); +} + +#instance-info img { + height: 2.5rem; + width: auto; +} + +.custom-emoji { + height: 2em; + width: auto; + display: inline-block; + vertical-align: center; + transition: transform .2s ease; + + &:hover { + transform: scale(1.2); + } +} + +#error { + padding: 0 0 3.5rem; + text-align: center; + + #instance-info { + text-align: end; + } + + img.main { + max-width: 128px; + width: 100%; + height: auto; + border-radius: 16px; + margin-bottom: .5rem; + } + + h2 { + font-size: 1.25rem; + margin-bottom: .5rem; + } + + p { + + } +} + +#note { + display: block; + position: relative; + + header { + display: flex; + margin-bottom: 1rem; + + .wrapper { + #renote { + display: flex; + align-items: center; + margin-bottom: .5rem; + color: var(--renote); + + >.avatar { + display: block; + margin-right: 1rem; + + >img { + width: 28px; + height: 28px; + border-radius: 50%; + } + } + } + + .author { + display: flex; + align-items: center; + + >.avatar { + display: block; + margin-right: 1rem; + + >img { + width: 54px; + height: 54px; + border-radius: 50%; + } + } + + >.user-info { + >.user-name { + font-weight: 700; + font-size: 1.1rem; + } + } + } + } + + #instance-info { + flex-shrink: 0; + margin-left: auto; + } + } + + main { + font-size: 1.05rem; + + #quote { + font-size: .95rem; + padding: .75rem; + margin: 1rem 0; + border-radius: var(--radius); + border: dashed 1px var(--renote); + } + } +} \ No newline at end of file diff --git a/packages/frontend/src/embed/init.ts b/packages/frontend/src/embed/init.ts new file mode 100644 index 000000000000..e583bf38e561 --- /dev/null +++ b/packages/frontend/src/embed/init.ts @@ -0,0 +1,82 @@ +import { miLocalStorage } from '@/local-storage'; +import { version, lang, updateLocale } from '@/config'; +import { updateI18n } from '@/i18n'; +import { embedInitI18n } from './scripts/embed-i18n'; +import '@/style.scss'; +import './embed.scss'; +import 'iframe-resizer/js/iframeResizer.contentWindow'; +import { embedInitLinkAnime } from './scripts/link-anime'; +import { parseMfm } from './scripts/parse-mfm'; + +console.info(`Misskey (Embed Sandbox) v${version}`); + +if (_DEV_) { + console.warn('Development mode!!!'); + + window.addEventListener('error', event => { + console.error(event); + /* + alert({ + type: 'error', + title: 'DEV: Unhandled error', + text: event.message + }); + */ + }); + + window.addEventListener('unhandledrejection', event => { + console.error(event); + /* + alert({ + type: 'error', + title: 'DEV: Unhandled promise rejection', + text: event.reason + }); + */ + }); +} + +//#region Detect language & fetch translations +const localeVersion = miLocalStorage.getItem('localeVersion'); +const localeOutdated = (localeVersion == null || localeVersion !== version); +if (localeOutdated) { + const res = await window.fetch(`/assets/locales/${lang}.${version}.json`); + if (res.status === 200) { + const newLocale = await res.text(); + const parsedNewLocale = JSON.parse(newLocale); + miLocalStorage.setItem('locale', newLocale); + miLocalStorage.setItem('localeVersion', version); + updateLocale(parsedNewLocale); + updateI18n(parsedNewLocale); + } +} +//#endregion + +// タッチデバイスでCSSの:hoverを機能させる +document.addEventListener('touchend', () => {}, { passive: true }); + +//#region Set lang attr +const html = document.documentElement; +html.setAttribute('lang', lang); +//#endregion + +embedInitI18n(); + +document.querySelectorAll(".mfm").forEach((e) => { + e.innerHTML = parseMfm(e.innerHTML).outerHTML; +}); + +//#region ロード画面解除 +const splash = document.getElementById('splash'); +// 念のためnullチェック(HTMLが古い場合があるため(そのうち消す)) +if (splash) splash.addEventListener('transitionend', () => { + splash.remove(); +}); + +if (splash) { + splash.style.opacity = '0'; + splash.style.pointerEvents = 'none'; +} +//#endregion + +embedInitLinkAnime(); diff --git a/packages/frontend/src/embed/scripts/embed-i18n.ts b/packages/frontend/src/embed/scripts/embed-i18n.ts new file mode 100644 index 000000000000..72104e081fb5 --- /dev/null +++ b/packages/frontend/src/embed/scripts/embed-i18n.ts @@ -0,0 +1,46 @@ +import { i18n } from "@/i18n"; + +/** + * 非vueページ向け翻訳適用関数 + * + * キー指定例: + * ```html + * + * ``` + */ +export function embedInitI18n() { + const els: NodeListOf = document.querySelectorAll("[data-mi-i18n]"); + els.forEach((tag: HTMLElement) => { + const key: string[] | null = tag.dataset.miI18n?.split('.') || null; + const translationContext: Record | null = JSON.parse(tag.dataset.miI18nCtx ?? 'null'); + if (!key) { + console.warn("[i18n] Key doesn't exist!", tag); + } else if (translationContext) { + let hasTranslationTarget: boolean = false; + let output: string = key.reduce((o, i) => o[i], i18n.ts); + Object.keys(translationContext).forEach((item) => { + const templateTag: NodeListOf = tag.querySelectorAll(`[data-mi-i18n-target="${item}"]`); + if (templateTag.length > 0) { + hasTranslationTarget = true; + templateTag.forEach((target: HTMLElement) => { + target.innerText = translationContext[item].toString(); + let parent: HTMLElement = target; + while (parent.parentElement != null && parent.parentElement !== tag) { + if (parent.parentElement != null) { + parent = parent.parentElement; + } + } + output = output.replace(new RegExp(`{\s*${item}\s*}`), parent.outerHTML); + }); + } + }); + if (!hasTranslationTarget) { + tag.innerText = i18n.t(key.join('.'), translationContext); + } else { + tag.innerHTML = output; + } + } else { + tag.innerText = key.reduce((o, i) => o[i], i18n.ts); + } + }); +} \ No newline at end of file diff --git a/packages/frontend/src/embed/scripts/link-anime.ts b/packages/frontend/src/embed/scripts/link-anime.ts new file mode 100644 index 000000000000..f1afd56ea650 --- /dev/null +++ b/packages/frontend/src/embed/scripts/link-anime.ts @@ -0,0 +1,33 @@ +export function embedInitLinkAnime() { + const animeEl: NodeListOf = document.querySelectorAll("a.click-anime,button.click-anime"); + if (animeEl.length > 0) { + animeEl.forEach((el: HTMLElement) => { + const target = el.children[0]; + + if (target == null) return; + + target.classList.add('_anime_bounce_standBy'); + + el.addEventListener('mousedown', () => { + target.classList.remove('_anime_bounce'); + + target.classList.add('_anime_bounce_standBy'); + target.classList.add('_anime_bounce_ready'); + + target.addEventListener('mouseleave', () => { + target.classList.remove('_anime_bounce_ready'); + }); + }); + + el.addEventListener('click', () => { + target.classList.add('_anime_bounce'); + target.classList.remove('_anime_bounce_ready'); + }); + + el.addEventListener('animationend', () => { + target.classList.remove('_anime_bounce'); + target.classList.add('_anime_bounce_standBy'); + }); + }); + } +} \ No newline at end of file diff --git a/packages/frontend/src/embed/scripts/parse-mfm.ts b/packages/frontend/src/embed/scripts/parse-mfm.ts new file mode 100644 index 000000000000..0020b9787fe0 --- /dev/null +++ b/packages/frontend/src/embed/scripts/parse-mfm.ts @@ -0,0 +1,329 @@ +import * as mfm from 'mfm-js'; +import { toUnicode } from 'punycode'; +import { host as localHost } from '@/config'; + +const QUOTE_STYLE = ` +display: block; +margin: 8px; +padding: 6px 0 6px 12px; +color: var(--fg); +border-left: solid 3px var(--fg); +opacity: 0.7; +`.split('\n').join(' '); + +interface MfmFn extends mfm.MfmFn { + props: { + name: string; + args: Record; + } +}; + +export function parseMfm(text: string): HTMLDivElement { + const ast = mfm.parse(text); + + const el = document.createElement("div"); + + function genEl(ast: (MfmFn | mfm.MfmNode)[]) { + return ast.map((token: (MfmFn | mfm.MfmNode)) => { + switch (token.type) { + case 'text': { + const text = token.props.text.replace(/(\r\n|\n|\r)/g, '\n'); + + const res: HTMLElement[] = []; + for (const t of text.split('\n')) { + res.push(document.createElement('br')); + const el = document.createElement('span'); + el.innerText = t; + res.push(el); + } + res.shift(); + return res; + } + + case 'bold': { + const el = document.createElement("b"); + genEl(token.children).forEach((e) => { + el.appendChild(e as HTMLElement); + }); + return [el]; + } + + case 'strike': { + const el = document.createElement("del"); + genEl(token.children).forEach((e) => { + el.appendChild(e as HTMLElement); + }); + return [el]; + } + + case 'italic': { + const el = document.createElement("i"); + el.style.fontStyle = 'oblique'; + genEl(token.children).forEach((e) => { + el.appendChild(e as HTMLElement); + }); + return [el]; + } + + case 'fn': { + // TODO: CSSを文字列で組み立てていくと token.props.args.~~~ 経由でCSSインジェクションできるのでよしなにやる + let style; + switch (token.props.name) { + case 'flip': { + const transform = + (token.props.args.h && token.props.args.v) ? 'scale(-1, -1)' : + token.props.args.v ? 'scaleY(-1)' : + 'scaleX(-1)'; + style = `transform: ${transform};`; + break; + } + case 'x2': { + const el = document.createElement("span"); + el.classList.add('mfm-x2'); + genEl(token.children).forEach((e) => { + el.appendChild(e as HTMLElement); + }) + return [el]; + } + case 'x3': { + const el = document.createElement("span"); + el.classList.add('mfm-x3'); + genEl(token.children).forEach((e) => { + el.appendChild(e as HTMLElement); + }) + return [el]; + } + case 'x4': { + const el = document.createElement("span"); + el.classList.add('mfm-x4'); + genEl(token.children).forEach((e) => { + el.appendChild(e as HTMLElement); + }) + return [el]; + } + case 'font': { + const family = + token.props.args.serif ? 'serif' : + token.props.args.monospace ? 'monospace' : + token.props.args.cursive ? 'cursive' : + token.props.args.fantasy ? 'fantasy' : + token.props.args.emoji ? 'emoji' : + token.props.args.math ? 'math' : + null; + + if (family) style = `font-family: ${family};`; + break; + } + case 'blur': { + const el = document.createElement("span"); + el.classList.add('_mfm_blur_'); + genEl(token.children).forEach((e) => { + el.appendChild(e as HTMLElement); + }) + return [el]; + } + case 'rotate': { + const degrees = parseFloat(token.props.args.deg ?? '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'); + style = `transform: translateX(${x}em) translateY(${y}em);`; + 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); + 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'; + style = `color: #${color};`; + break; + } + case 'bg': { + let color = token.props.args.color; + if (!/^[0-9a-f]{3,6}$/i.test(color)) color = 'f00'; + style = `background-color: #${color};`; + break; + } + } + if (style == null) { + const el = document.createElement("span"); + genEl(token.children).forEach((e) => { + el.appendChild(e as HTMLElement); + }); + el.innerHTML = `$[${token.props.name} ${el.innerHTML}]`; + return [el]; + } else { + const el = document.createElement("span"); + genEl(token.children).forEach((e) => { + el.appendChild(e as HTMLElement); + }); + el.setAttribute('style', `display: inline-block; ${style}`); + return [el]; + } + } + + case 'small': { + const el = document.createElement("small"); + el.style.opacity = '.7'; + genEl(token.children).forEach((e) => { + el.appendChild(e as HTMLElement); + }); + return [el]; + } + + case 'center': { + const el = document.createElement("div"); + el.style.textAlign = "center"; + genEl(token.children).forEach((e) => { + el.appendChild(e as HTMLElement); + }); + return [el]; + } + + case 'url': { + const el = document.createElement("a"); + el.href = token.props.url; + el.target = '_blank'; + el.rel = 'nofollow noopener'; + + el.innerText = token.props.url; + return [el]; + } + + case 'link': { + const el = document.createElement("a"); + el.href = token.props.url; + el.target = '_blank'; + el.rel = 'nofollow noopener'; + genEl(token.children).forEach((e) => { + el.appendChild(e as HTMLElement); + }); + return [el]; + } + + case 'mention': { + const el = document.createElement("a"); + const canonical = token.props.host === localHost ? `@${token.props.username}` : `@${token.props.username}@${toUnicode(token.props.host ?? '')}`; + el.href = `/${canonical}`; + el.target = '_blank'; + el.rel = 'nofollow noopener'; + el.style.display = 'inline-block'; + el.style.padding = '4px 8px 4px 4px'; + el.style.borderRadius = '999px'; + el.style.color = 'var(--mention)'; + el.style.fontWeight = '700'; + + el.innerText = `@${canonical}`; + return [el]; + } + + case 'hashtag': { + const el = document.createElement("a"); + el.href = `/tags/${encodeURIComponent(token.props.hashtag)}`; + el.target = '_blank'; + el.rel = 'nofollow noopener'; + el.style.color = 'var(--hashtag)'; + + el.innerText = `#${token.props.hashtag}`; + return [el]; + } + + case 'blockCode': { + const el = document.createElement('pre'); + el.style.overflowX = 'scroll'; + el.style.width = '100%'; + const elc = document.createElement('code'); + elc.innerText = token.props.code; + el.appendChild(elc); + return [el]; + } + + case 'inlineCode': { + const el = document.createElement('code'); + el.innerText = token.props.code; + return [el]; + } + + case 'quote': { + const el = document.createElement('div'); + el.setAttribute('style', QUOTE_STYLE); + genEl(token.children).forEach((e) => { + el.appendChild(e as HTMLElement); + }); + return [el]; + } + + case 'emojiCode': { + const el = document.createElement('span'); + el.classList.add('custom-emoji', 'needs-replacing'); + el.innerText = `:${token.props.name}:`; + return [el]; + } + + case 'unicodeEmoji': { + const el = document.createElement('span'); + el.classList.add('emoji'); + el.innerText = token.props.emoji; + return [el]; + } + + case 'mathInline': { + const el = document.createElement('code'); + el.innerText = token.props.formula; + return [el]; + } + + case 'mathBlock': { + const el = document.createElement('code'); + el.innerText = token.props.formula; + return [el]; + } + case 'search': { + const el = document.createElement('form'); + el.action = 'https://www.google.com/search'; + el.method = 'GET'; + + const text = document.createElement('input'); + text.type = 'search'; + text.value = token.props.query; + el.appendChild(text); + + const submit = document.createElement('button'); + submit.type = 'submit'; + submit.innerHTML = ' { + el.appendChild(e as HTMLElement); + }); + return [el]; + } + + default: { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + console.error('unrecognized ast type:', (token as any).type); + + return []; + } + } + }).flat(Infinity); + } + + genEl(ast).forEach((element) => { + el.appendChild(element as HTMLElement); + }); + + return el; +} \ No newline at end of file diff --git a/packages/frontend/tsconfig.json b/packages/frontend/tsconfig.json index 4d582daa3c33..ba494a1951f3 100644 --- a/packages/frontend/tsconfig.json +++ b/packages/frontend/tsconfig.json @@ -43,7 +43,7 @@ ".eslintrc.js", "./**/*.ts", "./**/*.vue" - ], +, "src/embed/scripts/parse-mfm.ts" ], "exclude": [ ".storybook/**/*", ] diff --git a/packages/frontend/vite.config.ts b/packages/frontend/vite.config.ts index 425f3aa45d93..56ce4eea050a 100644 --- a/packages/frontend/vite.config.ts +++ b/packages/frontend/vite.config.ts @@ -100,6 +100,7 @@ export function getConfig(): UserConfig { rollupOptions: { input: { app: './src/init.ts', + embed: './src/embed/init.ts' }, output: { manualChunks: { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ff9765b5c4ef..521f34d7d9a8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -663,6 +663,9 @@ importers: idb-keyval: specifier: 6.2.0 version: 6.2.0 + iframe-resizer: + specifier: ^4.3.6 + version: 4.3.6 insert-text-at-cursor: specifier: 0.3.0 version: 0.3.0 @@ -832,6 +835,9 @@ importers: '@types/gulp-rename': specifier: 2.0.1 version: 2.0.1 + '@types/iframe-resizer': + specifier: ^3.5.9 + version: 3.5.9 '@types/matter-js': specifier: 0.18.2 version: 0.18.2 @@ -6593,6 +6599,10 @@ packages: /@types/http-cache-semantics@4.0.1: resolution: {integrity: sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==} + /@types/iframe-resizer@3.5.9: + resolution: {integrity: sha512-RQUBI75F+uXruB95BFpC/8V8lPgJg4MQ6HxOCtAZYBB/h0FNCfrFfb4I+u2pZJIV7sKeszZbFqy1UnGeBMrvsA==} + dev: true + /@types/ioredis@4.28.10: resolution: {integrity: sha512-69LyhUgrXdgcNDv7ogs1qXZomnfOEnSmrmMFqKgt1XMJxmoOSG/u3wYy13yACIfKuMJ8IhKgHafDO3sx19zVQQ==} dependencies: @@ -12526,6 +12536,11 @@ packages: /ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + /iframe-resizer@4.3.6: + resolution: {integrity: sha512-wz0WodRIF6eP0oGQa5NIP1yrITAZ59ZJvVaVJqJRjaeCtfm461vy2C3us6CKx0e7pooqpIGLpVMSTzrfAjX9Sg==} + engines: {node: '>=0.8.0'} + dev: false + /ignore@5.2.4: resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} engines: {node: '>= 4'} From 9cbdcdc3280fd1d27dce4729180d763ed470836f Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Sun, 9 Apr 2023 04:52:59 +0900 Subject: [PATCH 08/21] Update package.json --- package.json | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 9f3ebf4a6634..c590388f3d15 100644 --- a/package.json +++ b/package.json @@ -4,14 +4,13 @@ "codename": "nasubi", "repository": { "type": "git", - "url": "https://github.com/kakkokari-gtyih/misskey.git" + "url": "https://github.com/misskey-dev/misskey.git" }, "packageManager": "pnpm@8.1.1", "workspaces": [ "packages/frontend", "packages/backend", - "packages/sw", - "packages/misskey-js" + "packages/sw" ], "private": true, "scripts": { @@ -67,4 +66,4 @@ "optionalDependencies": { "@tensorflow/tfjs-core": "4.2.0" } -} \ No newline at end of file +} From dd6c1790a90499241bbb5f6bf27d6ced55be00a6 Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Sun, 9 Apr 2023 04:56:38 +0900 Subject: [PATCH 09/21] Update tsconfig.json Revert auto-refactoring --- packages/frontend/tsconfig.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/frontend/tsconfig.json b/packages/frontend/tsconfig.json index ba494a1951f3..f21f701fa345 100644 --- a/packages/frontend/tsconfig.json +++ b/packages/frontend/tsconfig.json @@ -42,8 +42,8 @@ "include": [ ".eslintrc.js", "./**/*.ts", - "./**/*.vue" -, "src/embed/scripts/parse-mfm.ts" ], + "./**/*.vue", + ], "exclude": [ ".storybook/**/*", ] From 10fbea1224b9a4a0957697d175d07a4a563f261c Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih Date: Sun, 9 Apr 2023 18:02:03 +0900 Subject: [PATCH 10/21] (add) note embed page --- .../src/server/web/ClientServerService.ts | 2 +- packages/backend/src/server/web/style.css | 2 +- .../src/server/web/views/embed/note.pug | 95 +++++++- packages/frontend/src/embed/README.md | 9 + packages/frontend/src/embed/embed.scss | 227 ++++++++++++------ packages/frontend/src/embed/init.ts | 106 ++++++-- packages/frontend/src/embed/notes.scss | 178 ++++++++++++++ packages/frontend/src/embed/pages/notes.ts | 60 +++++ .../frontend/src/embed/scripts/embed-i18n.ts | 7 +- .../frontend/src/embed/scripts/parse-emoji.ts | 39 +++ .../frontend/src/embed/scripts/parse-mfm.ts | 81 ++++++- .../src/embed/scripts/render-not-found.ts | 22 ++ packages/frontend/src/embed/scripts/theme.ts | 140 +++++++++++ 13 files changed, 860 insertions(+), 108 deletions(-) create mode 100644 packages/frontend/src/embed/README.md create mode 100644 packages/frontend/src/embed/notes.scss create mode 100644 packages/frontend/src/embed/pages/notes.ts create mode 100644 packages/frontend/src/embed/scripts/parse-emoji.ts create mode 100644 packages/frontend/src/embed/scripts/render-not-found.ts create mode 100644 packages/frontend/src/embed/scripts/theme.ts diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index ed92b16ebe60..46791f425035 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -489,7 +489,7 @@ export class ClientServerService { // Note Embed fastify.get<{ Params: { note: string; } }>('/notes/:note/embed', async (request, reply) => { - vary(reply.raw, 'Accept'); + reply.removeHeader('X-Frame-Options'); const note = await this.notesRepository.findOneBy({ id: request.params.note, diff --git a/packages/backend/src/server/web/style.css b/packages/backend/src/server/web/style.css index d59f00fe164a..421597e6a4fd 100644 --- a/packages/backend/src/server/web/style.css +++ b/packages/backend/src/server/web/style.css @@ -11,7 +11,7 @@ html { width: 100vw; height: 100vh; cursor: wait; - background-color: var(--bg); + background-color: var(--bg, #fff); opacity: 1; transition: opacity 0.5s ease; } diff --git a/packages/backend/src/server/web/views/embed/note.pug b/packages/backend/src/server/web/views/embed/note.pug index cb951f55cb73..15a1247e9001 100644 --- a/packages/backend/src/server/web/views/embed/note.pug +++ b/packages/backend/src/server/web/views/embed/note.pug @@ -4,16 +4,19 @@ block vars - const user = note.user; - const title = user.name ? `${user.name} (@${user.username})` : `@${user.username}`; - const url = `${config.url}/notes/${note.id}`; + - const userUrl = (user) => user.host ? `${config.url}/@${user.username}@${user.host}` : `${config.url}/@${user.username}`; - const isRenote = note.renote && note.text == null && note.fileIds.length == 0 && note.poll == null; - const displayUser = isRenote ? note.renote.user: note.user; + - const mainNote = isRenote ? note.renote : note; + - const displayMedia = mainNote.files.filter((item) => item.type.startsWith('image') || item.type.startsWith('video')).slice(0, 4); + - const noteEmoji = Object.entries(note.emojis || {}).map((e) => { return { name: e[0], url: e[1], host: note.user.host }; }).concat(Object.entries(note.renote?.emojis || {}).map((e) => { return { name: e[0], url: e[1], host: note.renote?.user.host }; })) block meta if !user.host link(rel='alternate' href=url type='application/activity+json') if note.uri link(rel='alternate' href=note.uri type='application/activity+json') - - script(type='application/json') !{JSON.stringify(note)} + script(id='remote_custom_emojis' type='application/json') !{JSON.stringify(noteEmoji)} block content div#note @@ -22,22 +25,24 @@ block content div.wrapper if isRenote div#renote - a.avatar(href=`${config.url}/@${note.user.username}` target="_blank" rel="noopener noreferrer") + a.avatar(href=userUrl(note.user) target="_blank" rel="noopener noreferrer") img(src=note.user.avatarUrl) i.ti.ti-repeat span(data-mi-i18n='renotedBy' data-mi-i18n-ctx=`{"user": "${note.user.name || note.user.username}"}`) - a(href=`${config.url}/@${note.user.username}` target="_blank" rel="noopener noreferrer") + a(href=userUrl(note.user) target="_blank" rel="noopener noreferrer") b(data-mi-i18n-target='user') div.author - a.avatar(href=`${config.url}/@${displayUser.username}` target="_blank" rel="noopener noreferrer") + a.avatar(href=userUrl(displayUser) target="_blank" rel="noopener noreferrer") img(src=displayUser.avatarUrl) div.user-info - a.user-name(href=`${config.url}/@${displayUser.username}` target="_blank" rel="noopener noreferrer") #{displayUser.name || displayUser.username} + a.user-name(href=userUrl(displayUser) target="_blank" rel="noopener noreferrer") #{displayUser.name || displayUser.username} div.user-id @#{displayUser.username} + if displayUser.host + span(style="opacity: .5;") @#{displayUser.host} div#instance-info a.click-anime(href=config.url target='_blank') @@ -45,9 +50,77 @@ block content span.sr-only(data-mi-i18n='aboutX' data-mi-i18n-ctx=`{"x": "${instanceName}"}`) main - div.mfm !{isRenote ? note.renote.text : note.text} + if (mainNote.cw != null) + div.cw-meta + span.mfm.cw-summary #{mainNote.cw} + button._button#cw-button + span#cw-info-show + b(data-mi-i18n="showMore") + span.cw-info-wrapper + if(mainNote.text) + span.cw-info(data-mi-i18n='_cw.chars' data-mi-i18n-ctx=`{"count": ${mainNote.text.length}}`) + if(mainNote.files && mainNote.files.length !== 0) + span.cw-info(data-mi-i18n='_cw.files' data-mi-i18n-ctx=`{"count": ${mainNote.files.length}}`) + if(mainNote.poll != null) + span.cw-info(data-mi-i18n='poll') + span.hide#cw-info-hide + b(data-mi-i18n="hide") + div#note-body.mfm.hide #{mainNote.text} + + else + div#note-body.mfm #{mainNote.text} + if (!isRenote && note.renote) - div#quote.mfm !{note.renote.text} - - hr - pre(style='white-space: pre-wrap;') !{JSON.stringify(note, null, 2)} + div#quote + div.quote-avatar + a.avatar(href=userUrl(note.renote.user) target="_blank" rel="noopener noreferrer") + img(src=note.renote.user.avatarUrl) + + div.quote-body + div.meta + div.author + a(href=userUrl(note.renote.user) target="_blank" rel="noopener noreferrer") + b #{note.renote.user.name || note.renote.user.username} + span @#{note.renote.user.username} + if note.renote.user.host + span(style="opacity: .5;") @#{note.renote.user.host} + + a.time(href=`${config.url}/notes/${note.renote.id}` target="_blank" rel="noopener noreferrer") + time.locale-string(datetime=note.renote.createdAt data-mi-date-mode="relative") + + if (note.renote.cw != null) + div.cw-meta + span.mfm.cw-summary #{note.renote.cw} + button._button#quote-cw-button + span#quote-cw-info-show + b(data-mi-i18n="showMore") + span.cw-info-wrapper + if(mainNote.text) + span.cw-info(data-mi-i18n='_cw.chars' data-mi-i18n-ctx=`{"count": ${mainNote.text.length}}`) + if(mainNote.files && mainNote.files.length !== 0) + span.cw-info(data-mi-i18n='_cw.files' data-mi-i18n-ctx=`{"count": ${mainNote.files.length}}`) + if(mainNote.poll != null) + span.cw-info(data-mi-i18n='poll') + span.hide#quote-cw-info-hide + b(data-mi-i18n="hide") + div#quote-note-body.mfm.hide #{mainNote.text} + else + div#quote-note-body.mfm #{note.renote.text} + footer + div.info + a(href=`${config.url}/notes/${mainNote.id}`, target="_blank", rel="noopener noreferrer") + time.locale-string(datetime=mainNote.createdAt data-mi-date-mode="detailed") + div.reactions + each val, key in mainNote.reactions + span.reaction-item + span.emoji #{key} + span.count #{val} + + a._button.button(href=`${config.url}/notes/${mainNote.id}`, target="_blank", rel="noopener noreferrer") + i.ti.ti-arrow-back-up + a._button.button(href=`${config.url}/notes/${mainNote.id}`, target="_blank", rel="noopener noreferrer") + i.ti.ti-repeat + a._button.button(href=`${config.url}/notes/${mainNote.id}`, target="_blank", rel="noopener noreferrer") + i.ti.ti-plus + a._button.button(href=`${config.url}/notes/${mainNote.id}`, target="_blank", rel="noopener noreferrer") + i.ti.ti-dots diff --git a/packages/frontend/src/embed/README.md b/packages/frontend/src/embed/README.md new file mode 100644 index 000000000000..0d2f97f1a944 --- /dev/null +++ b/packages/frontend/src/embed/README.md @@ -0,0 +1,9 @@ +# Misskey 埋め込みのカスタマイズについて + +URLパラメータで、埋め込みの形式をカスタマイズできます。 + +## 共通 + +- `rounded` … 外枠を丸める。`0`で無効(デフォルト…`1`) +- `theme` … プリインストールテーマのID、もしくは `light` `dark` を指定可能。指定なしの場合は、最後に使用したテーマもしくはデバイスのカラーモードに応じたデフォルトテーマを適用。(デフォルト:指定なし) +- `mfm` … `animated` を指定可能。指定すると、動きのあるMFMが有効になります。(デフォルト…指定なし) \ No newline at end of file diff --git a/packages/frontend/src/embed/embed.scss b/packages/frontend/src/embed/embed.scss index bd1da0e817c0..fcd886c061b8 100644 --- a/packages/frontend/src/embed/embed.scss +++ b/packages/frontend/src/embed/embed.scss @@ -16,18 +16,171 @@ html,body { width: auto; } -.custom-emoji { +.hide { + display: none; +} + +.emoji { height: 2em; width: auto; - display: inline-block; + display: inline; vertical-align: center; transition: transform .2s ease; + &.custom-emoji>img,svg { + height: 2em; + vertical-align: middle; + } + + &.unicode-emoji>img,svg { + height: 1.25em; + vertical-align: -.25em; + } + &:hover { transform: scale(1.2); } } +.mfm { + white-space: pre-wrap; + + ._mfm_blur_ { + filter: blur(6px); + transition: filter 0.3s; + + &:hover { + filter: blur(0px); + } + } + + .mfm-x2 { + --mfm-zoom-size: 200%; + } + + .mfm-x3 { + --mfm-zoom-size: 400%; + } + + .mfm-x4 { + --mfm-zoom-size: 600%; + } + + .mfm-x2, .mfm-x3, .mfm-x4 { + font-size: var(--mfm-zoom-size); + + .mfm-x2, .mfm-x3, .mfm-x4 { + /* only half effective */ + font-size: calc(var(--mfm-zoom-size) / 2 + 50%); + + .mfm-x2, .mfm-x3, .mfm-x4 { + /* disabled */ + font-size: 100%; + } + } + } + + @keyframes mfm-spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } + } + + @keyframes mfm-spinX { + 0% { transform: perspective(128px) rotateX(0deg); } + 100% { transform: perspective(128px) rotateX(360deg); } + } + + @keyframes mfm-spinY { + 0% { transform: perspective(128px) rotateY(0deg); } + 100% { transform: perspective(128px) rotateY(360deg); } + } + + @keyframes mfm-jump { + 0% { transform: translateY(0); } + 25% { transform: translateY(-16px); } + 50% { transform: translateY(0); } + 75% { transform: translateY(-8px); } + 100% { transform: translateY(0); } + } + + @keyframes mfm-bounce { + 0% { transform: translateY(0) scale(1, 1); } + 25% { transform: translateY(-16px) scale(1, 1); } + 50% { transform: translateY(0) scale(1, 1); } + 75% { transform: translateY(0) scale(1.5, 0.75); } + 100% { transform: translateY(0) scale(1, 1); } + } + + // const val = () => `translate(${Math.floor(Math.random() * 20) - 10}px, ${Math.floor(Math.random() * 20) - 10}px)`; + // let css = ''; + // for (let i = 0; i <= 100; i += 5) { css += `${i}% { transform: ${val()} }\n`; } + @keyframes mfm-twitch { + 0% { transform: translate(7px, -2px) } + 5% { transform: translate(-3px, 1px) } + 10% { transform: translate(-7px, -1px) } + 15% { transform: translate(0px, -1px) } + 20% { transform: translate(-8px, 6px) } + 25% { transform: translate(-4px, -3px) } + 30% { transform: translate(-4px, -6px) } + 35% { transform: translate(-8px, -8px) } + 40% { transform: translate(4px, 6px) } + 45% { transform: translate(-3px, 1px) } + 50% { transform: translate(2px, -10px) } + 55% { transform: translate(-7px, 0px) } + 60% { transform: translate(-2px, 4px) } + 65% { transform: translate(3px, -8px) } + 70% { transform: translate(6px, 7px) } + 75% { transform: translate(-7px, -2px) } + 80% { transform: translate(-7px, -8px) } + 85% { transform: translate(9px, 3px) } + 90% { transform: translate(-3px, -2px) } + 95% { transform: translate(-10px, 2px) } + 100% { transform: translate(-2px, -6px) } + } + + // const val = () => `translate(${Math.floor(Math.random() * 6) - 3}px, ${Math.floor(Math.random() * 6) - 3}px) rotate(${Math.floor(Math.random() * 24) - 12}deg)`; + // let css = ''; + // for (let i = 0; i <= 100; i += 5) { css += `${i}% { transform: ${val()} }\n`; } + @keyframes mfm-shake { + 0% { transform: translate(-3px, -1px) rotate(-8deg) } + 5% { transform: translate(0px, -1px) rotate(-10deg) } + 10% { transform: translate(1px, -3px) rotate(0deg) } + 15% { transform: translate(1px, 1px) rotate(11deg) } + 20% { transform: translate(-2px, 1px) rotate(1deg) } + 25% { transform: translate(-1px, -2px) rotate(-2deg) } + 30% { transform: translate(-1px, 2px) rotate(-3deg) } + 35% { transform: translate(2px, 1px) rotate(6deg) } + 40% { transform: translate(-2px, -3px) rotate(-9deg) } + 45% { transform: translate(0px, -1px) rotate(-12deg) } + 50% { transform: translate(1px, 2px) rotate(10deg) } + 55% { transform: translate(0px, -3px) rotate(8deg) } + 60% { transform: translate(1px, -1px) rotate(8deg) } + 65% { transform: translate(0px, -1px) rotate(-7deg) } + 70% { transform: translate(-1px, -3px) rotate(6deg) } + 75% { transform: translate(0px, -2px) rotate(4deg) } + 80% { transform: translate(-2px, -1px) rotate(3deg) } + 85% { transform: translate(1px, -3px) rotate(-10deg) } + 90% { transform: translate(1px, 0px) rotate(3deg) } + 95% { transform: translate(-2px, 0px) rotate(-3deg) } + 100% { transform: translate(2px, 1px) rotate(2deg) } + } + + @keyframes mfm-rubberBand { + from { transform: scale3d(1, 1, 1); } + 30% { transform: scale3d(1.25, 0.75, 1); } + 40% { transform: scale3d(0.75, 1.25, 1); } + 50% { transform: scale3d(1.15, 0.85, 1); } + 65% { transform: scale3d(0.95, 1.05, 1); } + 75% { transform: scale3d(1.05, 0.95, 1); } + to { transform: scale3d(1, 1, 1); } + } + + @keyframes mfm-rainbow { + 0% { filter: hue-rotate(0deg) contrast(150%) saturate(150%); } + 100% { filter: hue-rotate(360deg) contrast(150%) saturate(150%); } + } +} + #error { padding: 0 0 3.5rem; text-align: center; @@ -52,74 +205,4 @@ html,body { p { } -} - -#note { - display: block; - position: relative; - - header { - display: flex; - margin-bottom: 1rem; - - .wrapper { - #renote { - display: flex; - align-items: center; - margin-bottom: .5rem; - color: var(--renote); - - >.avatar { - display: block; - margin-right: 1rem; - - >img { - width: 28px; - height: 28px; - border-radius: 50%; - } - } - } - - .author { - display: flex; - align-items: center; - - >.avatar { - display: block; - margin-right: 1rem; - - >img { - width: 54px; - height: 54px; - border-radius: 50%; - } - } - - >.user-info { - >.user-name { - font-weight: 700; - font-size: 1.1rem; - } - } - } - } - - #instance-info { - flex-shrink: 0; - margin-left: auto; - } - } - - main { - font-size: 1.05rem; - - #quote { - font-size: .95rem; - padding: .75rem; - margin: 1rem 0; - border-radius: var(--radius); - border: dashed 1px var(--renote); - } - } } \ No newline at end of file diff --git a/packages/frontend/src/embed/init.ts b/packages/frontend/src/embed/init.ts index e583bf38e561..2217de17416c 100644 --- a/packages/frontend/src/embed/init.ts +++ b/packages/frontend/src/embed/init.ts @@ -1,15 +1,25 @@ +import JSON5 from 'json5'; import { miLocalStorage } from '@/local-storage'; -import { version, lang, updateLocale } from '@/config'; -import { updateI18n } from '@/i18n'; +import { version, lang, updateLocale, url } from '@/config'; import { embedInitI18n } from './scripts/embed-i18n'; import '@/style.scss'; import './embed.scss'; import 'iframe-resizer/js/iframeResizer.contentWindow'; import { embedInitLinkAnime } from './scripts/link-anime'; import { parseMfm } from './scripts/parse-mfm'; +import { renderNotFound } from './scripts/render-not-found'; +import { parseEmoji } from './scripts/parse-emoji'; +import { applyTheme } from './scripts/theme'; +import lightTheme from '@/themes/_light.json5'; +import darkTheme from '@/themes/_dark.json5'; +import { isDeviceDarkmode } from '@/scripts/is-device-darkmode'; console.info(`Misskey (Embed Sandbox) v${version}`); +const supportedEmbedEntity: string[] = [ + 'notes' +]; + if (_DEV_) { console.warn('Development mode!!!'); @@ -47,7 +57,7 @@ if (localeOutdated) { miLocalStorage.setItem('locale', newLocale); miLocalStorage.setItem('localeVersion', version); updateLocale(parsedNewLocale); - updateI18n(parsedNewLocale); + embedInitI18n(); } } //#endregion @@ -60,23 +70,85 @@ const html = document.documentElement; html.setAttribute('lang', lang); //#endregion -embedInitI18n(); +//#region ページのパスをパース +// パス構造: /{entityName}/{id}/embed +const path = location.pathname; +if (!path.includes('/embed')) { + location.href = url; + throw new Error('Embed script was loaded on non-embed page. Force redirect to the top page.'); +} +const pageMetaValues:string[] = path.split('/').filter((e) => e != '' && e != 'embed'); +const pageMeta: { entityName: string; id: string; } = { + entityName: pageMetaValues[0], + id: pageMetaValues[1], +}; +const URLParams = new URLSearchParams(location.search); +const enableAnimatedMfm = URLParams.get("mfm") === 'animated'; +const disableRootRound = URLParams.get("rounded") === "0"; +//#endregion -document.querySelectorAll(".mfm").forEach((e) => { - e.innerHTML = parseMfm(e.innerHTML).outerHTML; -}); +const rootEl = document.getElementById("container"); +if (disableRootRound && rootEl) { + rootEl.style.borderRadius = "0"; +} -//#region ロード画面解除 -const splash = document.getElementById('splash'); -// 念のためnullチェック(HTMLが古い場合があるため(そのうち消す)) -if (splash) splash.addEventListener('transitionend', () => { - splash.remove(); -}); +//#region テーマ適用 +const themePreferences = localStorage.getItem('theme'); +const getTheme = async () => { + // テーマが指定されている場合 + if (URLParams.has("theme")) { + switch (URLParams.get("theme")) { + case 'light': + return lightTheme; + case 'dark': + return darkTheme; + default: + return await import(`../themes/${URLParams.get("theme")}.json5`).catch((_) => { + return isDeviceDarkmode() ? darkTheme : lightTheme; + }); + } + } else { + return isDeviceDarkmode() ? darkTheme : lightTheme; + } +}; -if (splash) { - splash.style.opacity = '0'; - splash.style.pointerEvents = 'none'; +if (!themePreferences || URLParams.has("theme")) { + applyTheme(await getTheme(), false); } //#endregion -embedInitLinkAnime(); +//埋め込みページごとのスクリプト読み込み +if (!supportedEmbedEntity.includes(pageMeta.entityName)) { + renderNotFound(); + afterPageInitialization(); +} else { + import(`./pages/${pageMeta.entityName}.ts`).then(() => { + afterPageInitialization(); + }); +} + +function afterPageInitialization() { + embedInitI18n(); + + //@ts-ignore + document.querySelectorAll(".mfm").forEach((e: HTMLElement) => { + e.innerHTML = parseMfm(e.innerText, enableAnimatedMfm).outerHTML; + }); + + parseEmoji(); + + //#region ロード画面解除 + const splash = document.getElementById('splash'); + // 念のためnullチェック(HTMLが古い場合があるため(そのうち消す)) + if (splash) splash.addEventListener('transitionend', () => { + splash.remove(); + }); + + if (splash) { + splash.style.opacity = '0'; + splash.style.pointerEvents = 'none'; + } + //#endregion + + embedInitLinkAnime(); +} \ No newline at end of file diff --git a/packages/frontend/src/embed/notes.scss b/packages/frontend/src/embed/notes.scss new file mode 100644 index 000000000000..d29c09a70bfa --- /dev/null +++ b/packages/frontend/src/embed/notes.scss @@ -0,0 +1,178 @@ +#note { + display: block; + position: relative; + + header { + display: flex; + margin-bottom: 1rem; + + .wrapper { + #renote { + display: flex; + align-items: center; + margin-bottom: .5rem; + color: var(--renote); + + >.avatar { + display: block; + margin-right: 1rem; + + >img { + width: 28px; + height: 28px; + border-radius: 50%; + } + } + } + + .author { + display: flex; + align-items: center; + + >.avatar { + display: block; + margin-right: 1rem; + + >img { + width: 54px; + height: 54px; + border-radius: 50%; + } + } + + >.user-info { + >.user-name { + font-weight: 700; + font-size: 1.1rem; + } + } + } + } + + #instance-info { + flex-shrink: 0; + margin-left: auto; + } + } + + main { + #note-body { + position: relative; + font-size: 1.05rem; + overflow-wrap: break-word; + } + + #quote { + font-size: .95rem; + padding: 1rem; + margin: 1rem 0; + border-radius: var(--radius); + border: dashed 1px var(--renote); + display: flex; + + .quote-avatar { + margin-right: .5rem; + flex-shrink: 0; + + img { + width: 48px; + height: 48px; + border-radius: 50%; + } + } + + .quote-body { + flex-grow: 1; + + >.meta { + display: flex; + + >.author>a { + display: inline-block; + margin-right: .25rem; + } + + >a.time { + margin-left: auto; + } + } + } + } + + .cw-meta { + margin-bottom: .5em; + + >.cw-summary>div { + display: inline-block; + margin-right: 8px; + } + + #cw-button, + #quote-cw-button { + display: inline-block; + padding: .25em .5em; + font-size: 0.75em; + color: var(--cwFg); + background: var(--cwBg); + border-radius: 2px; + + &:hover { + background: var(--cwHoverBg); + } + + .cw-info-wrapper { + >:first-child { + margin-left: 4px; + &:before { + content: '('; + } + } + + >:after { + content: ' / '; + } + + >:last-child:after { + content: ')'; + } + } + } + } + } + + footer { + .info { + margin: 1rem 0; + opacity: .7; + } + .reactions { + .reaction-item { + border-radius: 4px; + padding: 0 6px; + margin: 2px; + height: 32px; + background-color: var(--fgOnAccent); + display: inline-block; + + img { + height: 1.25rem; + vertical-align: -.25rem; + } + span { + display: inline-block; + margin-left: 4px; + line-height: 32px; + } + } + } + .button { + margin-right: 2rem; + padding: .5rem; + font-size: 1.2rem; + + &:hover { + text-decoration: none; + } + } + } +} \ No newline at end of file diff --git a/packages/frontend/src/embed/pages/notes.ts b/packages/frontend/src/embed/pages/notes.ts new file mode 100644 index 000000000000..19f73b964b7a --- /dev/null +++ b/packages/frontend/src/embed/pages/notes.ts @@ -0,0 +1,60 @@ +import { embedI18n as i18n } from '../scripts/embed-i18n'; +import { dateTimeFormat } from '@/scripts/intl-const'; +import '../notes.scss'; + +//CWボタン +document.getElementById("cw-button")?.addEventListener("click", () => { + document.getElementById("note-body")?.classList.toggle("hide"); + document.getElementById("cw-info-show")?.classList.toggle("hide"); + document.getElementById("cw-info-hide")?.classList.toggle("hide"); +}); +document.getElementById("quote-cw-button")?.addEventListener("click", () => { + document.getElementById("quote-note-body")?.classList.toggle("hide"); + document.getElementById("quote-cw-info-show")?.classList.toggle("hide"); + document.getElementById("quote-cw-info-hide")?.classList.toggle("hide"); +}); + +//時刻(タイムゾーン関連がややこしいのでJSでレンダリング) +document.querySelectorAll("time.locale-string").forEach((el) => { + const dateTimeRaw = el.getAttribute("datetime"); + const mode = el.getAttribute("data-mi-date-mode"); + if (dateTimeRaw !== null) { + const _time = new Date(dateTimeRaw).getTime(); + + const invalid = Number.isNaN(_time); + const absolute:string = !invalid ? dateTimeFormat.format(_time) : i18n.ts._ago.invalid; + let now = new Date().getTime(); + + const relative = () => { + if (invalid) return i18n.ts._ago.invalid; + + const ago = (now - _time) / 1000/*ms*/; + + return ( + ago >= 31536000 ? i18n.t('_ago.yearsAgo', { n: Math.round(ago / 31536000).toString() }) : + ago >= 2592000 ? i18n.t('_ago.monthsAgo', { n: Math.round(ago / 2592000).toString() }) : + ago >= 604800 ? i18n.t('_ago.weeksAgo', { n: Math.round(ago / 604800).toString() }) : + ago >= 86400 ? i18n.t('_ago.daysAgo', { n: Math.round(ago / 86400).toString() }) : + ago >= 3600 ? i18n.t('_ago.hoursAgo', { n: Math.round(ago / 3600).toString() }) : + ago >= 60 ? i18n.t('_ago.minutesAgo', { n: (~~(ago / 60)).toString() }) : + ago >= 10 ? i18n.t('_ago.secondsAgo', { n: (~~(ago % 60)).toString() }) : + ago >= -1 ? i18n.ts._ago.justNow : + i18n.ts._ago.future); + }; + + switch (mode) { + case 'absolute': + el.innerHTML = absolute; + break; + case 'detailed': + el.innerHTML = `${absolute} (${relative()})`; + break; + case 'relative': + default: + el.innerHTML = relative(); + break; + } + } +}); + +export { }; \ No newline at end of file diff --git a/packages/frontend/src/embed/scripts/embed-i18n.ts b/packages/frontend/src/embed/scripts/embed-i18n.ts index 72104e081fb5..b88cddc96fa9 100644 --- a/packages/frontend/src/embed/scripts/embed-i18n.ts +++ b/packages/frontend/src/embed/scripts/embed-i18n.ts @@ -1,4 +1,5 @@ -import { i18n } from "@/i18n"; +import { locale } from "@/config"; +import { I18n } from "@/scripts/i18n"; /** * 非vueページ向け翻訳適用関数 @@ -8,6 +9,10 @@ import { i18n } from "@/i18n"; * * ``` */ +const i18n = new I18n(locale); + +export const embedI18n = i18n; + export function embedInitI18n() { const els: NodeListOf = document.querySelectorAll("[data-mi-i18n]"); els.forEach((tag: HTMLElement) => { diff --git a/packages/frontend/src/embed/scripts/parse-emoji.ts b/packages/frontend/src/embed/scripts/parse-emoji.ts new file mode 100644 index 000000000000..17fe6ab5d1fc --- /dev/null +++ b/packages/frontend/src/embed/scripts/parse-emoji.ts @@ -0,0 +1,39 @@ +import { char2twemojiFilePath } from '@/scripts/emoji-base'; + +const char2path = char2twemojiFilePath; + +const remoteCustomEmojiEl = document.getElementById("remote_custom_emojis"); +let remoteCustomEmoji: { name: string; url: string; host?: string; }[] = []; +if (remoteCustomEmojiEl) { + remoteCustomEmoji = JSON.parse(remoteCustomEmojiEl.innerHTML); +} + +function getCustomEmojiName(ceNameRaw: string) { + return (ceNameRaw.startsWith(":") ? ceNameRaw.substr(1, ceNameRaw.length - 2) : ceNameRaw).replace('@.', '') +} + +function getCustomEmojiUrl(ceName: string) { + const remote = remoteCustomEmoji.find((e) => e.name === ceName); + if (remote) { + return remote.url; + } + return `/emoji/${ceName}.webp`; +} + +export function parseEmoji() { + document.querySelectorAll(".emoji").forEach((el) => { + let src: (string | null) = null; + if (el.innerHTML.startsWith(":")) { + console.log(getCustomEmojiName(el.innerHTML)); + src = getCustomEmojiUrl(getCustomEmojiName(el.innerHTML)); + } else { + src = char2path(el.innerHTML); + } + if (src) { + const emojiEl = document.createElement("img"); + emojiEl.src = src; + emojiEl.title = getCustomEmojiName(el.innerHTML); + el.innerHTML = emojiEl.outerHTML; + } + }); +} \ No newline at end of file diff --git a/packages/frontend/src/embed/scripts/parse-mfm.ts b/packages/frontend/src/embed/scripts/parse-mfm.ts index 0020b9787fe0..82165fb13f74 100644 --- a/packages/frontend/src/embed/scripts/parse-mfm.ts +++ b/packages/frontend/src/embed/scripts/parse-mfm.ts @@ -1,6 +1,9 @@ import * as mfm from 'mfm-js'; import { toUnicode } from 'punycode'; import { host as localHost } from '@/config'; +import sanitizeHtml from 'sanitize-html'; +import Prism from 'prismjs'; +import 'prismjs/themes/prism-okaidia.css'; const QUOTE_STYLE = ` display: block; @@ -18,11 +21,16 @@ interface MfmFn extends mfm.MfmFn { } }; -export function parseMfm(text: string): HTMLDivElement { +export function parseMfm(text: string, useAnim: boolean = false): HTMLDivElement { const ast = mfm.parse(text); const el = document.createElement("div"); + const validTime = (t: string | null | undefined) => { + if (t == null) return null; + return t.match(/^[0-9.]+s$/) ? t : null; + }; + function genEl(ast: (MfmFn | mfm.MfmNode)[]) { return ast.map((token: (MfmFn | mfm.MfmNode)) => { switch (token.type) { @@ -69,6 +77,49 @@ export function parseMfm(text: string): HTMLDivElement { // TODO: CSSを文字列で組み立てていくと token.props.args.~~~ 経由でCSSインジェクションできるのでよしなにやる let style; switch (token.props.name) { + case 'tada': { + const speed = validTime(token.props.args.speed) ?? '1s'; + style = 'font-size: 150%;' + (useAnim ? `animation: tada ${speed} linear infinite both;` : ''); + break; + } + case 'jelly': { + const speed = validTime(token.props.args.speed) ?? '1s'; + style = (useAnim ? `animation: mfm-rubberBand ${speed} linear infinite both;` : ''); + break; + } + case 'twitch': { + const speed = validTime(token.props.args.speed) ?? '0.5s'; + style = useAnim ? `animation: mfm-twitch ${speed} ease infinite;` : ''; + break; + } + case 'shake': { + const speed = validTime(token.props.args.speed) ?? '0.5s'; + style = useAnim ? `animation: mfm-shake ${speed} ease infinite;` : ''; + break; + } + case 'spin': { + const direction = + token.props.args.left ? 'reverse' : + token.props.args.alternate ? 'alternate' : + 'normal'; + const anime = + token.props.args.x ? 'mfm-spinX' : + token.props.args.y ? 'mfm-spinY' : + 'mfm-spin'; + const speed = validTime(token.props.args.speed) ?? '1.5s'; + style = useAnim ? `animation: ${anime} ${speed} linear infinite; animation-direction: ${direction};` : ''; + break; + } + case 'jump': { + const speed = validTime(token.props.args.speed) ?? '0.75s'; + style = useAnim ? `animation: mfm-jump ${speed} linear infinite;` : ''; + break; + } + case 'bounce': { + const speed = validTime(token.props.args.speed) ?? '0.75s'; + style = useAnim ? `animation: mfm-bounce ${speed} linear infinite; transform-origin: center bottom;` : ''; + break; + } case 'flip': { const transform = (token.props.args.h && token.props.args.v) ? 'scale(-1, -1)' : @@ -122,6 +173,12 @@ export function parseMfm(text: string): HTMLDivElement { }) return [el]; } + case 'rainbow': { + const speed = validTime(token.props.args.speed) ?? '1s'; + style = useAnim ? `animation: mfm-rainbow ${speed} linear infinite;` : ''; + break; + } + // TODO: Sparkle の プレーンHTML実装 case 'rotate': { const degrees = parseFloat(token.props.args.deg ?? '90'); style = `transform: rotate(${degrees}deg); transform-origin: center center;`; @@ -151,6 +208,9 @@ export function parseMfm(text: string): HTMLDivElement { style = `background-color: #${color};`; break; } + default: + style = ''; + break; } if (style == null) { const el = document.createElement("span"); @@ -240,14 +300,19 @@ export function parseMfm(text: string): HTMLDivElement { el.style.overflowX = 'scroll'; el.style.width = '100%'; const elc = document.createElement('code'); - elc.innerText = token.props.code; + const prismLang = Prism.languages[token.props.lang] ? token.props.lang : 'js'; + elc.innerHTML = Prism.highlight(token.props.code, Prism.languages[prismLang], prismLang); + el.classList.add(`language-${prismLang}`); + elc.classList.add(`language-${prismLang}`); el.appendChild(elc); return [el]; } case 'inlineCode': { const el = document.createElement('code'); - el.innerText = token.props.code; + const prismLang = 'js'; + el.innerHTML = Prism.highlight(token.props.code, Prism.languages[prismLang], prismLang); + el.classList.add(`language-${prismLang}`); return [el]; } @@ -262,14 +327,14 @@ export function parseMfm(text: string): HTMLDivElement { case 'emojiCode': { const el = document.createElement('span'); - el.classList.add('custom-emoji', 'needs-replacing'); + el.classList.add('emoji', 'custom-emoji'); el.innerText = `:${token.props.name}:`; return [el]; } case 'unicodeEmoji': { const el = document.createElement('span'); - el.classList.add('emoji'); + el.classList.add('emoji', 'unicode-emoji'); el.innerText = token.props.emoji; return [el]; } @@ -325,5 +390,11 @@ export function parseMfm(text: string): HTMLDivElement { el.appendChild(element as HTMLElement); }); + el.innerHTML = sanitizeHtml(el.innerHTML, { + allowedAttributes: { + '*': ['style', 'class'] + } + }); + return el; } \ No newline at end of file diff --git a/packages/frontend/src/embed/scripts/render-not-found.ts b/packages/frontend/src/embed/scripts/render-not-found.ts new file mode 100644 index 000000000000..7bd34a31a66e --- /dev/null +++ b/packages/frontend/src/embed/scripts/render-not-found.ts @@ -0,0 +1,22 @@ +import { instanceName } from "@/config"; +import { embedI18n as i18n } from '../scripts/embed-i18n'; + +export function renderNotFound() { + const el = document.getElementById("container"); + if (el) { + el.innerHTML = `
+
+ + +

${i18n.ts.notFound}

+

${i18n.ts.notFoundDescription}

+
+
`; + + } +} \ No newline at end of file diff --git a/packages/frontend/src/embed/scripts/theme.ts b/packages/frontend/src/embed/scripts/theme.ts new file mode 100644 index 000000000000..9920257d78b0 --- /dev/null +++ b/packages/frontend/src/embed/scripts/theme.ts @@ -0,0 +1,140 @@ +import tinycolor from 'tinycolor2'; + +export type Theme = { + id: string; + name: string; + author: string; + desc?: string; + base?: 'dark' | 'light'; + props: Record; +}; + +import lightTheme from '@/themes/_light.json5'; +import darkTheme from '@/themes/_dark.json5'; +import { deepClone } from '@/scripts/clone'; +import { miLocalStorage } from '@/local-storage'; + +export const themeProps = Object.keys(lightTheme.props).filter(key => !key.startsWith('X')); + +export const getBuiltinThemes = () => Promise.all( + [ + 'l-light', + 'l-coffee', + 'l-apricot', + 'l-rainy', + 'l-botanical', + 'l-vivid', + 'l-cherry', + 'l-sushi', + 'l-u0', + + 'd-dark', + 'd-persimmon', + 'd-astro', + 'd-future', + 'd-botanical', + 'd-green-lime', + 'd-green-orange', + 'd-cherry', + 'd-ice', + 'd-u0', + ].map(name => import(`../../themes/${name}.json5`).then(({ default: _default }): Theme => _default)), +); + +let timeout = null; + +export function applyTheme(theme: Theme, persist = true) { + if (timeout) window.clearTimeout(timeout); + + document.documentElement.classList.add('_themeChanging_'); + + timeout = window.setTimeout(() => { + document.documentElement.classList.remove('_themeChanging_'); + }, 1000); + + const colorSchema = theme.base === 'dark' ? 'dark' : 'light'; + + // Deep copy + const _theme = deepClone(theme); + + if (_theme.base) { + const base = [lightTheme, darkTheme].find(x => x.id === _theme.base); + if (base) _theme.props = Object.assign({}, base.props, _theme.props); + } + + const props = compile(_theme); + + for (const tag of document.head.children) { + if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') { + tag.setAttribute('content', props['htmlThemeColor']); + break; + } + } + + for (const [k, v] of Object.entries(props)) { + document.documentElement.style.setProperty(`--${k}`, v.toString()); + } + + document.documentElement.style.setProperty('color-schema', colorSchema); + + if (persist) { + miLocalStorage.setItem('theme', JSON.stringify(props)); + miLocalStorage.setItem('colorSchema', colorSchema); + } + +} + +function compile(theme: Theme): Record { + function getColor(val: string): tinycolor.Instance { + // ref (prop) + if (val[0] === '@') { + return getColor(theme.props[val.substr(1)]); + } + + // ref (const) + else if (val[0] === '$') { + return getColor(theme.props[val]); + } + + // func + else if (val[0] === ':') { + const parts = val.split('<'); + const func = parts.shift().substr(1); + const arg = parseFloat(parts.shift()); + const color = getColor(parts.join('<')); + + switch (func) { + case 'darken': return color.darken(arg); + case 'lighten': return color.lighten(arg); + case 'alpha': return color.setAlpha(arg); + case 'hue': return color.spin(arg); + case 'saturate': return color.saturate(arg); + } + } + + // other case + return tinycolor(val); + } + + const props = {}; + + for (const [k, v] of Object.entries(theme.props)) { + if (k.startsWith('$')) continue; // ignore const + + props[k] = v.startsWith('"') ? v.replace(/^"\s*/, '') : genValue(getColor(v)); + } + + return props; +} + +function genValue(c: tinycolor.Instance): string { + return c.toRgbString(); +} + +export function validateTheme(theme: Record): boolean { + if (theme.id == null || typeof theme.id !== 'string') return false; + if (theme.name == null || typeof theme.name !== 'string') return false; + if (theme.base == null || !['light', 'dark'].includes(theme.base)) return false; + if (theme.props == null || typeof theme.props !== 'object') return false; + return true; +} From 50e401426fa4a7fd9e3c340e3671d7dc433b7dda Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih Date: Sun, 9 Apr 2023 19:00:16 +0900 Subject: [PATCH 11/21] (fix) lint issues --- .../frontend/src/components/global/MkA.vue | 2 +- packages/frontend/src/embed/init.ts | 17 +++-- packages/frontend/src/embed/pages/notes.ts | 4 +- .../frontend/src/embed/scripts/embed-i18n.ts | 20 ++++-- .../frontend/src/embed/scripts/link-anime.ts | 2 +- .../frontend/src/embed/scripts/parse-emoji.ts | 10 +-- .../frontend/src/embed/scripts/parse-mfm.ts | 66 +++++++++---------- .../src/embed/scripts/render-not-found.ts | 9 ++- packages/frontend/src/embed/scripts/theme.ts | 1 - 9 files changed, 69 insertions(+), 62 deletions(-) diff --git a/packages/frontend/src/components/global/MkA.vue b/packages/frontend/src/components/global/MkA.vue index e3039008f9a1..40d134dffb33 100644 --- a/packages/frontend/src/components/global/MkA.vue +++ b/packages/frontend/src/components/global/MkA.vue @@ -98,4 +98,4 @@ function nav(ev: MouseEvent) { router.push(props.to, ev.ctrlKey ? 'forcePage' : null); } - \ No newline at end of file + diff --git a/packages/frontend/src/embed/init.ts b/packages/frontend/src/embed/init.ts index 2217de17416c..62d233992146 100644 --- a/packages/frontend/src/embed/init.ts +++ b/packages/frontend/src/embed/init.ts @@ -1,4 +1,7 @@ -import JSON5 from 'json5'; +/** + * Client (Embed) entry point + */ +import 'vite/modulepreload-polyfill'; import { miLocalStorage } from '@/local-storage'; import { version, lang, updateLocale, url } from '@/config'; import { embedInitI18n } from './scripts/embed-i18n'; @@ -14,7 +17,7 @@ import lightTheme from '@/themes/_light.json5'; import darkTheme from '@/themes/_dark.json5'; import { isDeviceDarkmode } from '@/scripts/is-device-darkmode'; -console.info(`Misskey (Embed Sandbox) v${version}`); +console.info(`Misskey (Embed) v${version}`); const supportedEmbedEntity: string[] = [ 'notes' @@ -77,7 +80,7 @@ if (!path.includes('/embed')) { location.href = url; throw new Error('Embed script was loaded on non-embed page. Force redirect to the top page.'); } -const pageMetaValues:string[] = path.split('/').filter((e) => e != '' && e != 'embed'); +const pageMetaValues:string[] = path.split('/').filter((dir) => dir !== '' && dir !== 'embed'); const pageMeta: { entityName: string; id: string; } = { entityName: pageMetaValues[0], id: pageMetaValues[1], @@ -131,8 +134,8 @@ function afterPageInitialization() { embedInitI18n(); //@ts-ignore - document.querySelectorAll(".mfm").forEach((e: HTMLElement) => { - e.innerHTML = parseMfm(e.innerText, enableAnimatedMfm).outerHTML; + document.querySelectorAll(".mfm").forEach((el: HTMLElement) => { + el.innerHTML = parseMfm(el.innerText, enableAnimatedMfm).outerHTML; }); parseEmoji(); @@ -150,5 +153,5 @@ function afterPageInitialization() { } //#endregion - embedInitLinkAnime(); -} \ No newline at end of file + embedInitLinkAnime(); +} diff --git a/packages/frontend/src/embed/pages/notes.ts b/packages/frontend/src/embed/pages/notes.ts index 19f73b964b7a..c7c1db7a69a3 100644 --- a/packages/frontend/src/embed/pages/notes.ts +++ b/packages/frontend/src/embed/pages/notes.ts @@ -23,7 +23,7 @@ document.querySelectorAll("time.locale-string").forEach((el) => { const invalid = Number.isNaN(_time); const absolute:string = !invalid ? dateTimeFormat.format(_time) : i18n.ts._ago.invalid; - let now = new Date().getTime(); + const now = new Date().getTime(); const relative = () => { if (invalid) return i18n.ts._ago.invalid; @@ -57,4 +57,4 @@ document.querySelectorAll("time.locale-string").forEach((el) => { } }); -export { }; \ No newline at end of file +export { }; diff --git a/packages/frontend/src/embed/scripts/embed-i18n.ts b/packages/frontend/src/embed/scripts/embed-i18n.ts index b88cddc96fa9..246859bb93e4 100644 --- a/packages/frontend/src/embed/scripts/embed-i18n.ts +++ b/packages/frontend/src/embed/scripts/embed-i18n.ts @@ -1,22 +1,28 @@ import { locale } from "@/config"; import { I18n } from "@/scripts/i18n"; +const i18n = new I18n(locale); + +export const embedI18n = i18n; + /** * 非vueページ向け翻訳適用関数 * * キー指定例: * ```html - * + * + * + * ... + * + * ... + * + * * ``` */ -const i18n = new I18n(locale); - -export const embedI18n = i18n; - export function embedInitI18n() { const els: NodeListOf = document.querySelectorAll("[data-mi-i18n]"); els.forEach((tag: HTMLElement) => { - const key: string[] | null = tag.dataset.miI18n?.split('.') || null; + const key: string[] | null = tag.dataset.miI18n?.split('.') ?? null; const translationContext: Record | null = JSON.parse(tag.dataset.miI18nCtx ?? 'null'); if (!key) { console.warn("[i18n] Key doesn't exist!", tag); @@ -48,4 +54,4 @@ export function embedInitI18n() { tag.innerText = key.reduce((o, i) => o[i], i18n.ts); } }); -} \ No newline at end of file +} diff --git a/packages/frontend/src/embed/scripts/link-anime.ts b/packages/frontend/src/embed/scripts/link-anime.ts index f1afd56ea650..20d49f8bf3e8 100644 --- a/packages/frontend/src/embed/scripts/link-anime.ts +++ b/packages/frontend/src/embed/scripts/link-anime.ts @@ -30,4 +30,4 @@ export function embedInitLinkAnime() { }); }); } -} \ No newline at end of file +} diff --git a/packages/frontend/src/embed/scripts/parse-emoji.ts b/packages/frontend/src/embed/scripts/parse-emoji.ts index 17fe6ab5d1fc..cf0816c87ccf 100644 --- a/packages/frontend/src/embed/scripts/parse-emoji.ts +++ b/packages/frontend/src/embed/scripts/parse-emoji.ts @@ -3,17 +3,17 @@ import { char2twemojiFilePath } from '@/scripts/emoji-base'; const char2path = char2twemojiFilePath; const remoteCustomEmojiEl = document.getElementById("remote_custom_emojis"); -let remoteCustomEmoji: { name: string; url: string; host?: string; }[] = []; +let remoteCustomEmojis: { name: string; url: string; host?: string; }[] = []; if (remoteCustomEmojiEl) { - remoteCustomEmoji = JSON.parse(remoteCustomEmojiEl.innerHTML); + remoteCustomEmojis = JSON.parse(remoteCustomEmojiEl.innerHTML); } function getCustomEmojiName(ceNameRaw: string) { - return (ceNameRaw.startsWith(":") ? ceNameRaw.substr(1, ceNameRaw.length - 2) : ceNameRaw).replace('@.', '') + return (ceNameRaw.startsWith(":") ? ceNameRaw.substr(1, ceNameRaw.length - 2) : ceNameRaw).replace('@.', ''); } function getCustomEmojiUrl(ceName: string) { - const remote = remoteCustomEmoji.find((e) => e.name === ceName); + const remote = remoteCustomEmojis.find((emoji) => emoji.name === ceName); if (remote) { return remote.url; } @@ -36,4 +36,4 @@ export function parseEmoji() { el.innerHTML = emojiEl.outerHTML; } }); -} \ No newline at end of file +} diff --git a/packages/frontend/src/embed/scripts/parse-mfm.ts b/packages/frontend/src/embed/scripts/parse-mfm.ts index 82165fb13f74..14460d31dc00 100644 --- a/packages/frontend/src/embed/scripts/parse-mfm.ts +++ b/packages/frontend/src/embed/scripts/parse-mfm.ts @@ -50,16 +50,16 @@ export function parseMfm(text: string, useAnim: boolean = false): HTMLDivElement case 'bold': { const el = document.createElement("b"); - genEl(token.children).forEach((e) => { - el.appendChild(e as HTMLElement); + genEl(token.children).forEach((child) => { + el.appendChild(child as HTMLElement); }); return [el]; } case 'strike': { const el = document.createElement("del"); - genEl(token.children).forEach((e) => { - el.appendChild(e as HTMLElement); + genEl(token.children).forEach((child) => { + el.appendChild(child as HTMLElement); }); return [el]; } @@ -67,8 +67,8 @@ export function parseMfm(text: string, useAnim: boolean = false): HTMLDivElement case 'italic': { const el = document.createElement("i"); el.style.fontStyle = 'oblique'; - genEl(token.children).forEach((e) => { - el.appendChild(e as HTMLElement); + genEl(token.children).forEach((child) => { + el.appendChild(child as HTMLElement); }); return [el]; } @@ -131,25 +131,25 @@ export function parseMfm(text: string, useAnim: boolean = false): HTMLDivElement case 'x2': { const el = document.createElement("span"); el.classList.add('mfm-x2'); - genEl(token.children).forEach((e) => { - el.appendChild(e as HTMLElement); - }) + genEl(token.children).forEach((child) => { + el.appendChild(child as HTMLElement); + }); return [el]; } case 'x3': { const el = document.createElement("span"); el.classList.add('mfm-x3'); - genEl(token.children).forEach((e) => { - el.appendChild(e as HTMLElement); - }) + genEl(token.children).forEach((child) => { + el.appendChild(child as HTMLElement); + }); return [el]; } case 'x4': { const el = document.createElement("span"); el.classList.add('mfm-x4'); - genEl(token.children).forEach((e) => { - el.appendChild(e as HTMLElement); - }) + genEl(token.children).forEach((child) => { + el.appendChild(child as HTMLElement); + }); return [el]; } case 'font': { @@ -168,9 +168,9 @@ export function parseMfm(text: string, useAnim: boolean = false): HTMLDivElement case 'blur': { const el = document.createElement("span"); el.classList.add('_mfm_blur_'); - genEl(token.children).forEach((e) => { - el.appendChild(e as HTMLElement); - }) + genEl(token.children).forEach((child) => { + el.appendChild(child as HTMLElement); + }); return [el]; } case 'rainbow': { @@ -214,15 +214,15 @@ export function parseMfm(text: string, useAnim: boolean = false): HTMLDivElement } if (style == null) { const el = document.createElement("span"); - genEl(token.children).forEach((e) => { - el.appendChild(e as HTMLElement); + genEl(token.children).forEach((child) => { + el.appendChild(child as HTMLElement); }); el.innerHTML = `$[${token.props.name} ${el.innerHTML}]`; return [el]; } else { const el = document.createElement("span"); - genEl(token.children).forEach((e) => { - el.appendChild(e as HTMLElement); + genEl(token.children).forEach((child) => { + el.appendChild(child as HTMLElement); }); el.setAttribute('style', `display: inline-block; ${style}`); return [el]; @@ -232,8 +232,8 @@ export function parseMfm(text: string, useAnim: boolean = false): HTMLDivElement case 'small': { const el = document.createElement("small"); el.style.opacity = '.7'; - genEl(token.children).forEach((e) => { - el.appendChild(e as HTMLElement); + genEl(token.children).forEach((child) => { + el.appendChild(child as HTMLElement); }); return [el]; } @@ -241,8 +241,8 @@ export function parseMfm(text: string, useAnim: boolean = false): HTMLDivElement case 'center': { const el = document.createElement("div"); el.style.textAlign = "center"; - genEl(token.children).forEach((e) => { - el.appendChild(e as HTMLElement); + genEl(token.children).forEach((child) => { + el.appendChild(child as HTMLElement); }); return [el]; } @@ -262,8 +262,8 @@ export function parseMfm(text: string, useAnim: boolean = false): HTMLDivElement el.href = token.props.url; el.target = '_blank'; el.rel = 'nofollow noopener'; - genEl(token.children).forEach((e) => { - el.appendChild(e as HTMLElement); + genEl(token.children).forEach((child) => { + el.appendChild(child as HTMLElement); }); return [el]; } @@ -319,8 +319,8 @@ export function parseMfm(text: string, useAnim: boolean = false): HTMLDivElement case 'quote': { const el = document.createElement('div'); el.setAttribute('style', QUOTE_STYLE); - genEl(token.children).forEach((e) => { - el.appendChild(e as HTMLElement); + genEl(token.children).forEach((child) => { + el.appendChild(child as HTMLElement); }); return [el]; } @@ -370,8 +370,8 @@ export function parseMfm(text: string, useAnim: boolean = false): HTMLDivElement case 'plain': { const el = document.createElement('span'); - genEl(token.children).forEach((e) => { - el.appendChild(e as HTMLElement); + genEl(token.children).forEach((child) => { + el.appendChild(child as HTMLElement); }); return [el]; } @@ -397,4 +397,4 @@ export function parseMfm(text: string, useAnim: boolean = false): HTMLDivElement }); return el; -} \ No newline at end of file +} diff --git a/packages/frontend/src/embed/scripts/render-not-found.ts b/packages/frontend/src/embed/scripts/render-not-found.ts index 7bd34a31a66e..ea0bfd7b0c6e 100644 --- a/packages/frontend/src/embed/scripts/render-not-found.ts +++ b/packages/frontend/src/embed/scripts/render-not-found.ts @@ -9,14 +9,13 @@ export function renderNotFound() { -

${i18n.ts.notFound}

-

${i18n.ts.notFoundDescription}

+

${ i18n.ts.notFound }

+

${ i18n.ts.notFoundDescription }

`; - } -} \ No newline at end of file +} diff --git a/packages/frontend/src/embed/scripts/theme.ts b/packages/frontend/src/embed/scripts/theme.ts index 9920257d78b0..3adac761af7d 100644 --- a/packages/frontend/src/embed/scripts/theme.ts +++ b/packages/frontend/src/embed/scripts/theme.ts @@ -81,7 +81,6 @@ export function applyTheme(theme: Theme, persist = true) { miLocalStorage.setItem('theme', JSON.stringify(props)); miLocalStorage.setItem('colorSchema', colorSchema); } - } function compile(theme: Theme): Record { From c349afd80b16052d4286e525dde56852d9878079 Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih Date: Sun, 9 Apr 2023 19:03:29 +0900 Subject: [PATCH 12/21] (delete) link jiggle animation --- .../src/server/web/views/embed/404.pug | 2 +- .../src/server/web/views/embed/note.pug | 2 +- packages/frontend/src/embed/init.ts | 3 -- .../frontend/src/embed/scripts/link-anime.ts | 33 ------------------- 4 files changed, 2 insertions(+), 38 deletions(-) delete mode 100644 packages/frontend/src/embed/scripts/link-anime.ts diff --git a/packages/backend/src/server/web/views/embed/404.pug b/packages/backend/src/server/web/views/embed/404.pug index 44ed0a327677..51f8e24e489f 100644 --- a/packages/backend/src/server/web/views/embed/404.pug +++ b/packages/backend/src/server/web/views/embed/404.pug @@ -7,7 +7,7 @@ block content div#error div div#instance-info - a.click-anime(href=url target='_blank') + a(href=url target='_blank') img(src= icon || '/static-assets/splash.png') span.sr-only(data-mi-i18n='aboutX' data-mi-i18n-ctx=`{"x": "${instanceName}"}`) diff --git a/packages/backend/src/server/web/views/embed/note.pug b/packages/backend/src/server/web/views/embed/note.pug index 15a1247e9001..00c33d5c146e 100644 --- a/packages/backend/src/server/web/views/embed/note.pug +++ b/packages/backend/src/server/web/views/embed/note.pug @@ -45,7 +45,7 @@ block content span(style="opacity: .5;") @#{displayUser.host} div#instance-info - a.click-anime(href=config.url target='_blank') + a(href=config.url target='_blank') img(src= icon || '/static-assets/splash.png') span.sr-only(data-mi-i18n='aboutX' data-mi-i18n-ctx=`{"x": "${instanceName}"}`) diff --git a/packages/frontend/src/embed/init.ts b/packages/frontend/src/embed/init.ts index 62d233992146..f054673a7e67 100644 --- a/packages/frontend/src/embed/init.ts +++ b/packages/frontend/src/embed/init.ts @@ -8,7 +8,6 @@ import { embedInitI18n } from './scripts/embed-i18n'; import '@/style.scss'; import './embed.scss'; import 'iframe-resizer/js/iframeResizer.contentWindow'; -import { embedInitLinkAnime } from './scripts/link-anime'; import { parseMfm } from './scripts/parse-mfm'; import { renderNotFound } from './scripts/render-not-found'; import { parseEmoji } from './scripts/parse-emoji'; @@ -152,6 +151,4 @@ function afterPageInitialization() { splash.style.pointerEvents = 'none'; } //#endregion - - embedInitLinkAnime(); } diff --git a/packages/frontend/src/embed/scripts/link-anime.ts b/packages/frontend/src/embed/scripts/link-anime.ts deleted file mode 100644 index 20d49f8bf3e8..000000000000 --- a/packages/frontend/src/embed/scripts/link-anime.ts +++ /dev/null @@ -1,33 +0,0 @@ -export function embedInitLinkAnime() { - const animeEl: NodeListOf = document.querySelectorAll("a.click-anime,button.click-anime"); - if (animeEl.length > 0) { - animeEl.forEach((el: HTMLElement) => { - const target = el.children[0]; - - if (target == null) return; - - target.classList.add('_anime_bounce_standBy'); - - el.addEventListener('mousedown', () => { - target.classList.remove('_anime_bounce'); - - target.classList.add('_anime_bounce_standBy'); - target.classList.add('_anime_bounce_ready'); - - target.addEventListener('mouseleave', () => { - target.classList.remove('_anime_bounce_ready'); - }); - }); - - el.addEventListener('click', () => { - target.classList.add('_anime_bounce'); - target.classList.remove('_anime_bounce_ready'); - }); - - el.addEventListener('animationend', () => { - target.classList.remove('_anime_bounce'); - target.classList.add('_anime_bounce_standBy'); - }); - }); - } -} From bac138d004263cb5352d6676d7a2947caa119d74 Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih Date: Sun, 9 Apr 2023 19:10:10 +0900 Subject: [PATCH 13/21] (remove) embed theme.ts --- packages/frontend/src/embed/init.ts | 2 +- packages/frontend/src/embed/scripts/theme.ts | 139 ------------------- 2 files changed, 1 insertion(+), 140 deletions(-) delete mode 100644 packages/frontend/src/embed/scripts/theme.ts diff --git a/packages/frontend/src/embed/init.ts b/packages/frontend/src/embed/init.ts index f054673a7e67..aeba2f63bf51 100644 --- a/packages/frontend/src/embed/init.ts +++ b/packages/frontend/src/embed/init.ts @@ -11,7 +11,7 @@ import 'iframe-resizer/js/iframeResizer.contentWindow'; import { parseMfm } from './scripts/parse-mfm'; import { renderNotFound } from './scripts/render-not-found'; import { parseEmoji } from './scripts/parse-emoji'; -import { applyTheme } from './scripts/theme'; +import { applyTheme } from '@/scripts/theme'; import lightTheme from '@/themes/_light.json5'; import darkTheme from '@/themes/_dark.json5'; import { isDeviceDarkmode } from '@/scripts/is-device-darkmode'; diff --git a/packages/frontend/src/embed/scripts/theme.ts b/packages/frontend/src/embed/scripts/theme.ts deleted file mode 100644 index 3adac761af7d..000000000000 --- a/packages/frontend/src/embed/scripts/theme.ts +++ /dev/null @@ -1,139 +0,0 @@ -import tinycolor from 'tinycolor2'; - -export type Theme = { - id: string; - name: string; - author: string; - desc?: string; - base?: 'dark' | 'light'; - props: Record; -}; - -import lightTheme from '@/themes/_light.json5'; -import darkTheme from '@/themes/_dark.json5'; -import { deepClone } from '@/scripts/clone'; -import { miLocalStorage } from '@/local-storage'; - -export const themeProps = Object.keys(lightTheme.props).filter(key => !key.startsWith('X')); - -export const getBuiltinThemes = () => Promise.all( - [ - 'l-light', - 'l-coffee', - 'l-apricot', - 'l-rainy', - 'l-botanical', - 'l-vivid', - 'l-cherry', - 'l-sushi', - 'l-u0', - - 'd-dark', - 'd-persimmon', - 'd-astro', - 'd-future', - 'd-botanical', - 'd-green-lime', - 'd-green-orange', - 'd-cherry', - 'd-ice', - 'd-u0', - ].map(name => import(`../../themes/${name}.json5`).then(({ default: _default }): Theme => _default)), -); - -let timeout = null; - -export function applyTheme(theme: Theme, persist = true) { - if (timeout) window.clearTimeout(timeout); - - document.documentElement.classList.add('_themeChanging_'); - - timeout = window.setTimeout(() => { - document.documentElement.classList.remove('_themeChanging_'); - }, 1000); - - const colorSchema = theme.base === 'dark' ? 'dark' : 'light'; - - // Deep copy - const _theme = deepClone(theme); - - if (_theme.base) { - const base = [lightTheme, darkTheme].find(x => x.id === _theme.base); - if (base) _theme.props = Object.assign({}, base.props, _theme.props); - } - - const props = compile(_theme); - - for (const tag of document.head.children) { - if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') { - tag.setAttribute('content', props['htmlThemeColor']); - break; - } - } - - for (const [k, v] of Object.entries(props)) { - document.documentElement.style.setProperty(`--${k}`, v.toString()); - } - - document.documentElement.style.setProperty('color-schema', colorSchema); - - if (persist) { - miLocalStorage.setItem('theme', JSON.stringify(props)); - miLocalStorage.setItem('colorSchema', colorSchema); - } -} - -function compile(theme: Theme): Record { - function getColor(val: string): tinycolor.Instance { - // ref (prop) - if (val[0] === '@') { - return getColor(theme.props[val.substr(1)]); - } - - // ref (const) - else if (val[0] === '$') { - return getColor(theme.props[val]); - } - - // func - else if (val[0] === ':') { - const parts = val.split('<'); - const func = parts.shift().substr(1); - const arg = parseFloat(parts.shift()); - const color = getColor(parts.join('<')); - - switch (func) { - case 'darken': return color.darken(arg); - case 'lighten': return color.lighten(arg); - case 'alpha': return color.setAlpha(arg); - case 'hue': return color.spin(arg); - case 'saturate': return color.saturate(arg); - } - } - - // other case - return tinycolor(val); - } - - const props = {}; - - for (const [k, v] of Object.entries(theme.props)) { - if (k.startsWith('$')) continue; // ignore const - - props[k] = v.startsWith('"') ? v.replace(/^"\s*/, '') : genValue(getColor(v)); - } - - return props; -} - -function genValue(c: tinycolor.Instance): string { - return c.toRgbString(); -} - -export function validateTheme(theme: Record): boolean { - if (theme.id == null || typeof theme.id !== 'string') return false; - if (theme.name == null || typeof theme.name !== 'string') return false; - if (theme.base == null || !['light', 'dark'].includes(theme.base)) return false; - if (theme.props == null || typeof theme.props !== 'object') return false; - return true; -} From 0836b55dfa651ed8f77b75ce364a161c0232b22b Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih Date: Thu, 21 Sep 2023 13:00:51 +0900 Subject: [PATCH 14/21] Merge branch 'develop' into notes-embed --- .config/docker_example.yml | 23 +- .config/example.yml | 51 +- .devcontainer/devcontainer.json | 2 +- .devcontainer/devcontainer.yml | 23 +- .devcontainer/docker-compose.yml | 2 +- .dockerignore | 1 - .editorconfig | 4 + .github/ISSUE_TEMPLATE/01_bug-report.md | 25 +- .github/ISSUE_TEMPLATE/config.yml | 3 - .github/dependabot.yml | 32 +- .github/labeler.yml | 21 +- .github/misskey/test.yml | 2 +- .github/pull_request_template.md | 1 + .github/reviewer-lottery.yml | 10 - .github/workflows/api-misskey-js.yml | 4 +- .github/workflows/check_copyright_year.yml | 2 +- .github/workflows/docker-develop.yml | 10 +- .github/workflows/docker.yml | 10 +- .github/workflows/dockle.yml | 2 +- .github/workflows/lint.yml | 14 +- .github/workflows/ok-to-test.yml | 4 +- .github/workflows/pr-preview-deploy.yml | 2 +- .github/workflows/reviewer_lottery.yml | 13 - .github/workflows/storybook.yml | 74 - .github/workflows/test-backend.yml | 8 +- .github/workflows/test-frontend.yml | 18 +- .github/workflows/test-misskey-js.yml | 6 +- .github/workflows/test-production.yml | 42 + .gitignore | 5 +- .node-version | 2 +- .vscode/settings.json | 1 + CHANGELOG.md | 291 +- CONTRIBUTING.md | 39 +- Dockerfile | 5 +- README.md | 6 +- ROADMAP.md | 2 +- assets/title_float.svg | 4 +- chart/files/default.yml | 17 +- cypress/e2e/basic.cy.js | 63 +- cypress/e2e/widgets.cy.js | 18 +- cypress/support/e2e.js | 2 + docker-compose.yml.example | 14 +- docs/DONATORS.md | 25 - gulpfile.js | 75 - healthcheck.sh | 3 + locales/ar-SA.yml | 404 +- locales/bn-BD.yml | 38 +- locales/ca-ES.yml | 23 +- locales/cs-CZ.yml | 1263 +- locales/de-DE.yml | 301 +- locales/el-GR.yml | 5 +- locales/en-US.yml | 297 +- locales/es-ES.yml | 294 +- locales/fr-FR.yml | 244 +- locales/generateDTS.js | 73 + locales/hu-HU.yml | 104 + locales/id-ID.yml | 485 +- locales/index.d.ts | 2245 ++- locales/index.js | 8 +- locales/it-IT.yml | 353 +- locales/ja-JP.yml | 313 +- locales/ja-KS.yml | 277 +- locales/jbo-EN.yml | 2 + locales/kab-KAB.yml | 1 + locales/kn-IN.yml | 1 + locales/ko-KR.yml | 327 +- locales/lo-LA.yml | 79 +- locales/nl-NL.yml | 15 +- locales/no-NO.yml | 725 + locales/package.json | 3 + locales/pl-PL.yml | 88 +- locales/pt-PT.yml | 1205 +- locales/ro-RO.yml | 7 +- locales/ru-RU.yml | 193 +- locales/sk-SK.yml | 41 +- locales/sv-SE.yml | 116 +- locales/th-TH.yml | 323 +- locales/tr-TR.yml | 388 + locales/uk-UA.yml | 44 +- locales/uz-UZ.yml | 1086 ++ locales/vi-VN.yml | 103 +- locales/zh-CN.yml | 677 +- locales/zh-TW.yml | 1073 +- package.json | 39 +- packages/backend/.swcrc | 4 +- packages/backend/assets/avatar.png | Bin 0 -> 13477 bytes packages/backend/check_connect.js | 14 +- .../backend/migration/1000000000000-Init.js | 5 +- .../backend/migration/1556348509290-Pages.js | 5 +- .../migration/1556746559567-UserProfile.js | 5 +- .../migration/1557476068003-PinnedUsers.js | 5 +- .../migration/1557761316509-AddSomeUrls.js | 5 +- .../1557932705754-ObjectStorageSetting.js | 5 +- .../migration/1558072954435-PageLike.js | 5 +- .../migration/1558103093633-UserGroup.js | 5 +- .../1558257926829-UserGroupInvite.js | 5 +- .../1558266512381-UserListJoining.js | 5 +- .../migration/1561706992953-webauthn.js | 5 +- .../migration/1561873850023-ChartIndexes.js | 5 +- .../1562422242907-PasswordLessLogin.js | 5 +- .../migration/1562444565093-PinnedPage.js | 5 +- .../1562448332510-PageTitleHideOption.js | 5 +- .../migration/1562869971568-ModerationLog.js | 5 +- .../migration/1563757595828-UsedUsername.js | 5 +- .../backend/migration/1565634203341-room.js | 5 +- .../1571220798684-CustomEmojiCategory.js | 5 +- .../migration/1572760203493-nodeinfo.js | 5 +- .../1576269851876-TalkFederationId.js | 5 +- .../1576869585998-ProxyRemoteFiles.js | 5 +- .../backend/migration/1579267006611-v12.js | 5 +- .../backend/migration/1579270193251-v12-2.js | 5 +- .../backend/migration/1579282808087-v12-3.js | 5 +- .../backend/migration/1579544426412-v12-4.js | 5 +- .../backend/migration/1579977526288-v12-5.js | 5 +- .../backend/migration/1579993013959-v12-6.js | 5 +- .../backend/migration/1580069531114-v12-7.js | 5 +- .../backend/migration/1580148575182-v12-8.js | 5 +- .../backend/migration/1580154400017-v12-9.js | 5 +- .../backend/migration/1580276619901-v12-10.js | 5 +- .../backend/migration/1580331224276-v12-11.js | 5 +- .../backend/migration/1580508795118-v12-12.js | 5 +- .../backend/migration/1580543501339-v12-13.js | 5 +- .../backend/migration/1580864313253-v12-14.js | 5 +- .../1581526429287-user-group-invitation.js | 5 +- .../1581695816408-user-group-antenna.js | 5 +- ...581708415836-drive-user-folder-id-index.js | 5 +- .../backend/migration/1581979837262-promo.js | 5 +- .../1582019042083-featured-injecttion.js | 5 +- .../1582210532752-antenna-exclude.js | 5 +- .../1582875306439-note-reaction-length.js | 5 +- .../backend/migration/1585361548360-miauth.js | 5 +- .../1585385921215-custom-notification.js | 5 +- .../backend/migration/1585772678853-ap-url.js | 5 +- .../1586624197029-AddObjectStorageUseProxy.js | 5 +- .../1586641139527-remote-reaction.js | 5 +- .../migration/1586708940386-pageAiScript.js | 5 +- .../migration/1588044505511-hCaptcha.js | 5 +- .../migration/1589023282116-pubRelay.js | 5 +- .../migration/1595075960584-blurhash.js | 5 +- ...595077605646-blurhash-for-avatar-banner.js | 5 +- .../1595676934834-instance-icon-url.js | 5 +- .../migration/1595771249699-word-mute.js | 5 +- .../migration/1595782306083-word-mute2.js | 5 +- .../migration/1596548170836-channel.js | 5 +- .../migration/1596786425167-channel2.js | 5 +- ...597230137744-objectStorageSetPublicRead.js | 5 +- ...597236229720-IncludingNotificationTypes.js | 5 +- .../1597385880794-add-sensitive-index.js | 5 +- .../migration/1597459042300-channel-unread.js | 5 +- .../1597893996136-ChannelNoteIdDescIndex.js | 5 +- .../1600353287890-mutingNotificationTypes.js | 5 +- .../1603094348345-refine-abuse-user-report.js | 5 +- ...1603095701770-refine-abuse-user-report2.js | 5 +- .../1603776877564-instance-theme-color.js | 5 +- .../1603781553011-instance-favicon.js | 5 +- .../1604821689616-delete-auto-watch.js | 5 +- .../1605408848373-clip-description.js | 5 +- .../migration/1605408971051-comments.js | 5 +- .../1605585339718-instance-pinned-pages.js | 5 +- .../1605965516823-instance-images.js | 5 +- .../migration/1606191203881-no-crawle.js | 5 +- .../1607151207216-instance-pinned-clip.js | 5 +- .../migration/1607353487793-isExplorable.js | 5 +- .../migration/1610277136869-registry.js | 5 +- .../migration/1610277585759-registry2.js | 5 +- .../migration/1610283021566-registry3.js | 5 +- .../migration/1611354329133-followersUri.js | 5 +- .../migration/1611397665007-gallery.js | 5 +- ...547387175-objectStorageS3ForcePathStyle.js | 5 +- .../1612619156584-announcement-email.js | 5 +- .../1613155914446-emailNotificationTypes.js | 5 +- .../migration/1613181457597-user-lang.js | 5 +- ...1613503367223-use-bigint-for-driveUsage.js | 5 +- .../migration/1615965918224-chart-v2.js | 5 +- .../migration/1615966519402-chart-v2-2.js | 5 +- .../1618637372000-user-last-active-date.js | 5 +- .../1618639857000-user-hide-online-status.js | 5 +- .../migration/1619942102890-password-reset.js | 5 +- .../backend/migration/1620019354680-ad.js | 5 +- .../backend/migration/1620364649428-ad2.js | 5 +- .../1621479946000-add-note-indexes.js | 5 +- ...9304522-user-profile-description-length.js | 5 +- .../1622681548499-log-message-length.js | 5 +- .../1626509500668-fix-remote-file-proxy.js | 5 +- .../migration/1629004542760-chart-reindex.js | 5 +- .../1629024377804-deepl-integration.js | 5 +- .../1629288472000-fix-channel-userId.js | 5 +- .../1629512953000-user-is-deleted.js | 5 +- .../1629778475000-deepl-integration2.js | 5 +- .../1629833361000-AddShowTLReplies.js | 5 +- .../1629968054000_userInstanceBlocks.js | 5 +- ...1633068642000-email-required-for-signup.js | 5 +- .../migration/1633071909016-user-pending.js | 5 +- .../1634486652000-user-public-reactions.js | 5 +- .../migration/1634902659689-delete-log.js | 5 +- .../1635500777168-note-thread-mute.js | 5 +- .../migration/1636197624383-ff-visibility.js | 5 +- .../1636697408073-remove-via-mobile.js | 5 +- .../1637320813000-forwarded-report.js | 5 +- .../migration/1639325650583-chart-v3.js | 5 +- .../migration/1642611822809-emoji-url.js | 5 +- ...1642613870898-drive-file-webpublic-type.js | 5 +- .../migration/1643963705770-chart-v4.js | 5 +- .../migration/1643966656277-chart-v5.js | 5 +- .../migration/1643967331284-chart-v6.js | 5 +- .../1644010796173-convert-hard-mutes.js | 6 +- .../migration/1644058404077-chart-v7.js | 5 +- .../migration/1644059847460-chart-v8.js | 9 +- .../migration/1644060125705-chart-v9.js | 9 +- .../migration/1644073149413-chart-v10.js | 5 +- .../migration/1644095659741-chart-v11.js | 5 +- .../migration/1644328606241-chart-v12.js | 5 +- .../migration/1644331238153-chart-v13.js | 5 +- .../migration/1644344266289-chart-v14.js | 5 +- .../1644395759931-instance-theme-color.js | 5 +- .../migration/1644481657998-chart-v15.js | 5 +- .../1644551208096-following-indexes.js | 5 +- ...45340161439-remove-max-note-text-length.js | 5 +- .../1645599900873-federation-chart-pubsub.js | 5 +- .../1646143552768-instance-default-theme.js | 5 + .../1646387162108-mute-expires-at.js | 5 + .../1646549089451-poll-ended-notification.js | 4 + .../1646633030285-chart-federation-active.js | 5 + ...655454495-remove-instance-drive-columns.js | 5 + ...2390560-chart-federation-active-sub-pub.js | 5 + .../migration/1648548247382-webhook.js | 5 + .../migration/1648816172177-webhook-2.js | 4 + .../migration/1651224615271-foreign-key.js | 5 + .../1652859567549-uniform-themecolor.js | 5 + .../migration/1655368940105-nsfw-detection.js | 5 + .../1655371960534-nsfw-detection-2.js | 5 + .../1655388169582-nsfw-detection-3.js | 5 + .../1655393015659-nsfw-detection-4.js | 5 + .../1655813815729-driveCapacityOverrideMb.js | 5 + .../migration/1655918165614-user-ip.js | 5 + .../migration/1656122560740-file-ip.js | 5 + .../1656251734807-nsfw-detection-5.js | 5 + .../backend/migration/1656328812281-ip-2.js | 5 + .../1656408772602-nsfw-detection-6.js | 5 + .../1656772790599-user-moderation-note.js | 5 + .../1657346559800-active-email-validation.js | 5 + .../migration/1664694635394-turnstile.js | 5 + .../1665091090561-add-renote-muting.js | 8 + ...6634-whetherPushNotifyToSendReadMessage.js | 5 + .../1671924750884-RetentionAggregation.js | 5 + .../1671926422832-RetentionAggregation2.js | 5 + .../migration/1672562400597-PerUserPvChart.js | 5 + ...672703171386-remove-latestRequestSentAt.js | 5 + ...1672704017999-remove-lastCommunicatedAt.js | 5 + .../1672704136584-remove-latestStatus.js | 5 + .../backend/migration/1672822262496-Flash.js | 5 + .../1673336077243-PollChoiceLength.js | 5 + .../backend/migration/1673500412259-Role.js | 5 + .../migration/1673515526953-RoleColor.js | 5 + .../migration/1673522856499-RoleIroiro.js | 5 + .../migration/1673524604156-RoleLastUsedAt.js | 5 + .../1673570377815-RoleConditional.js | 5 + .../migration/1673575973645-MetaClean.js | 5 + .../migration/1673783015567-Policies.js | 5 + .../1673812883772-firstRetrievedAt.js | 5 + .../1674086433654-flashScriptLength.js | 5 + .../migration/1674118260469-achievement.js | 5 + .../migration/1674255666603-loggedInDates.js | 5 + .../1675053125067-fixforeignkeyreports.js | 5 + .../migration/1675404035646-cleanup.js | 5 + .../1675557528704-role-icon-badge.js | 5 + .../migration/1676434944993-drop-group.js | 5 + .../backend/migration/1676438468213-ad3.js | 5 + .../backend/migration/1677054292210-ad4.js | 14 + ...677570181236-role-assignment-expires-at.js | 5 + ...8164627293-per-note-reaction-acceptance.js | 5 + .../1678426061773-tweak-varchar-length.js | 5 + .../migration/1678427401214-remove-unused.js | 5 + .../1678602320354-role-display-order.js | 5 + .../1678694614599-sensitive-words.js | 5 + .../1678869617549-retention-date-key.js | 5 + ...678945242650-add-props-for-custom-emoji.js | 5 + .../migration/1678953978856-clip-favorite.js | 5 + .../migration/1679309757174-antenna-active.js | 5 + ...1679639483253-enableChartsForRemoteUser.js | 5 + .../migration/1679651580149-cleanup.js | 5 + ...81809-enableChartsForFederatedInstances.js | 5 + .../1680228513388-channelFavorite.js | 5 + .../1680238118084-channelNotePining.js | 5 + .../migration/1680491187535-cleanup.js | 5 + .../migration/1680582195041-cleanup.js | 7 +- .../migration/1680702787050-UserMemo.js | 5 + ...1680775031481-avatar-url-and-banner-url.js | 5 + .../migration/1680931179228-account-move.js | 5 + .../migration/1681400427971-serverRules.js | 16 + .../migration/1681870960239-RoleTLSetting.js | 17 + .../migration/1682190963894-movedAt.js | 18 + .../1682754135458-preservedUsernames.js | 16 + .../migration/1682985520254-channelColor.js | 16 + .../migration/1683328299359-channelArchive.js | 18 + .../1683682889948-prevent-ai-larning.js | 16 + ...683083083-public-reactions-default-true.js | 16 + .../migration/1683789676867-fix-typo.js | 16 + .../migration/1683847157541-UserList.js | 18 + .../1683869758873-UserListFavorites.js | 24 + ...684206886988-remove-showTimelineReplies.js | 16 + .../migration/1684386446061-emoji-improve.js | 20 + .../migration/1685973839966-errorImageUrl.js | 22 + .../1688280713783-add-meta-options.js | 18 + .../1688720440658-refactor-invite-system.js | 30 + .../1688880985544-add-index-to-relations.js | 18 + .../migration/1689102832143-nsfw-cache.js | 16 + .../1690417561185-fix-renote-muting.js | 12 + ...417561186-ChangeCacheRemoteFilesDefault.js | 11 + .../backend/migration/1690417561187-Fix.js | 81 + .../1690569881926-user-2fa-backup-codes.js | 11 + .../1690782653311-SensitiveChannel.js | 17 + .../1690796169261-play-visibility.js | 15 + .../1691649257651-refine-announcement.js | 27 + .../1691657412740-refine-announcement-2.js | 11 + .../1691959191872-passkey-support.js | 49 + ...1694850832075-server-icons-and-manifest.js | 20 + .../migration/1694915420864-clipped-count.js | 16 + packages/backend/package.json | 236 +- packages/backend/src/@types/hcaptcha.d.ts | 5 + .../backend/src/@types/http-signature.d.ts | 5 + packages/backend/src/@types/os-utils.d.ts | 5 + packages/backend/src/@types/package.json.d.ts | 5 + .../backend/src/@types/probe-image-size.d.ts | 5 + packages/backend/src/@types/redis-lock.d.ts | 5 + packages/backend/src/GlobalModule.ts | 66 +- packages/backend/src/MainModule.ts | 5 + packages/backend/src/NestLogger.ts | 5 + packages/backend/src/boot/common.ts | 17 +- .../backend/src/boot/{index.ts => entry.ts} | 4 + packages/backend/src/boot/master.ts | 47 +- packages/backend/src/boot/worker.ts | 5 + packages/backend/src/config.ts | 256 +- packages/backend/src/const.ts | 10 + .../backend/src/core/AccountMoveService.ts | 327 +- .../backend/src/core/AccountUpdateService.ts | 15 +- .../backend/src/core/AchievementService.ts | 16 +- packages/backend/src/core/AiService.ts | 29 +- .../backend/src/core/AnnouncementService.ts | 135 + packages/backend/src/core/AntennaService.ts | 79 +- packages/backend/src/core/AppLockService.ts | 10 +- packages/backend/src/core/CacheService.ts | 84 +- packages/backend/src/core/CaptchaService.ts | 37 +- packages/backend/src/core/CoreModule.ts | 36 +- .../src/core/CreateSystemUserService.ts | 57 +- .../backend/src/core/CustomEmojiService.ts | 102 +- .../backend/src/core/DeleteAccountService.ts | 13 +- packages/backend/src/core/DownloadService.ts | 31 +- packages/backend/src/core/DriveService.ts | 81 +- packages/backend/src/core/EmailService.ts | 25 +- .../src/core/FederatedInstanceService.ts | 62 +- .../src/core/FetchInstanceMetadataService.ts | 176 +- packages/backend/src/core/FileInfoService.ts | 61 +- .../backend/src/core/GlobalEventService.ts | 29 +- packages/backend/src/core/HashtagService.ts | 24 +- .../backend/src/core/HttpRequestService.ts | 30 +- packages/backend/src/core/IdService.ts | 14 +- .../src/core/ImageProcessingService.ts | 11 +- .../backend/src/core/InstanceActorService.ts | 23 +- .../src/core/InternalStorageService.ts | 5 + packages/backend/src/core/LoggerService.ts | 13 +- packages/backend/src/core/MetaService.ts | 44 +- packages/backend/src/core/MfmService.ts | 115 +- .../backend/src/core/ModerationLogService.ts | 11 +- .../backend/src/core/NoteCreateService.ts | 191 +- .../backend/src/core/NoteDeleteService.ts | 55 +- .../backend/src/core/NotePiningService.ts | 21 +- packages/backend/src/core/NoteReadService.ts | 47 +- .../backend/src/core/NotificationService.ts | 53 +- packages/backend/src/core/PollService.ts | 33 +- .../backend/src/core/ProxyAccountService.ts | 13 +- .../src/core/PushNotificationService.ts | 39 +- packages/backend/src/core/QueryService.ts | 71 +- packages/backend/src/core/QueueModule.ts | 103 +- packages/backend/src/core/QueueService.ts | 157 +- packages/backend/src/core/ReactionService.ts | 86 +- packages/backend/src/core/RelayService.ts | 55 +- .../backend/src/core/RemoteLoggerService.ts | 5 + .../src/core/RemoteUserResolveService.ts | 73 +- packages/backend/src/core/RoleService.ts | 82 +- packages/backend/src/core/S3Service.ts | 22 +- packages/backend/src/core/SearchService.ts | 226 + packages/backend/src/core/SignupService.ts | 94 +- .../core/TwoFactorAuthenticationService.ts | 445 - .../backend/src/core/UserBlockingService.ts | 22 +- .../backend/src/core/UserFollowingService.ts | 296 +- .../backend/src/core/UserKeypairService.ts | 31 +- packages/backend/src/core/UserListService.ts | 22 +- .../backend/src/core/UserMutingService.ts | 13 +- .../backend/src/core/UserSuspendService.ts | 48 +- packages/backend/src/core/UtilityService.ts | 5 + .../src/core/VideoProcessingService.ts | 9 +- packages/backend/src/core/WebAuthnService.ts | 252 + packages/backend/src/core/WebfingerService.ts | 26 +- packages/backend/src/core/WebhookService.ts | 20 +- .../src/core/activitypub/ApAudienceService.ts | 63 +- .../core/activitypub/ApDbResolverService.ts | 108 +- .../activitypub/ApDeliverManagerService.ts | 217 +- .../src/core/activitypub/ApInboxService.ts | 174 +- .../src/core/activitypub/ApLoggerService.ts | 5 + .../src/core/activitypub/ApMfmService.ts | 25 +- .../src/core/activitypub/ApRendererService.ts | 231 +- .../src/core/activitypub/ApRequestService.ts | 11 +- .../src/core/activitypub/ApResolverService.ts | 23 +- .../core/activitypub/LdSignatureService.ts | 56 +- .../src/core/activitypub/misc/contexts.ts | 15 +- .../core/activitypub/models/ApImageService.ts | 64 +- .../activitypub/models/ApMentionService.ts | 29 +- .../core/activitypub/models/ApNoteService.ts | 323 +- .../activitypub/models/ApPersonService.ts | 433 +- .../activitypub/models/ApQuestionService.ts | 49 +- .../src/core/activitypub/models/icon.ts | 5 + .../src/core/activitypub/models/identifier.ts | 5 + .../src/core/activitypub/models/tag.ts | 7 +- packages/backend/src/core/activitypub/type.ts | 6 +- .../src/core/chart/ChartLoggerService.ts | 5 + .../src/core/chart/ChartManagementService.ts | 15 +- .../src/core/chart/charts/active-users.ts | 14 +- .../src/core/chart/charts/ap-request.ts | 8 +- .../backend/src/core/chart/charts/drive.ts | 12 +- .../chart/charts/entities/active-users.ts | 5 + .../core/chart/charts/entities/ap-request.ts | 5 + .../src/core/chart/charts/entities/drive.ts | 5 + .../core/chart/charts/entities/federation.ts | 5 + .../core/chart/charts/entities/instance.ts | 5 + .../src/core/chart/charts/entities/notes.ts | 5 + .../chart/charts/entities/per-user-drive.ts | 5 + .../charts/entities/per-user-following.ts | 5 + .../chart/charts/entities/per-user-notes.ts | 5 + .../core/chart/charts/entities/per-user-pv.ts | 5 + .../charts/entities/per-user-reactions.ts | 5 + .../chart/charts/entities/test-grouped.ts | 5 + .../charts/entities/test-intersection.ts | 5 + .../core/chart/charts/entities/test-unique.ts | 5 + .../src/core/chart/charts/entities/test.ts | 5 + .../src/core/chart/charts/entities/users.ts | 5 + .../src/core/chart/charts/federation.ts | 10 +- .../backend/src/core/chart/charts/instance.ts | 18 +- .../backend/src/core/chart/charts/notes.ts | 14 +- .../src/core/chart/charts/per-user-drive.ts | 14 +- .../core/chart/charts/per-user-following.ts | 14 +- .../src/core/chart/charts/per-user-notes.ts | 16 +- .../src/core/chart/charts/per-user-pv.ts | 14 +- .../core/chart/charts/per-user-reactions.ts | 14 +- .../src/core/chart/charts/test-grouped.ts | 8 +- .../core/chart/charts/test-intersection.ts | 8 +- .../src/core/chart/charts/test-unique.ts | 8 +- .../backend/src/core/chart/charts/test.ts | 8 +- .../backend/src/core/chart/charts/users.ts | 14 +- packages/backend/src/core/chart/core.ts | 9 +- packages/backend/src/core/chart/entities.ts | 5 + .../entities/AbuseUserReportEntityService.ts | 13 +- .../src/core/entities/AntennaEntityService.ts | 11 +- .../src/core/entities/AppEntityService.ts | 15 +- .../core/entities/AuthSessionEntityService.ts | 17 +- .../core/entities/BlockingEntityService.ts | 17 +- .../src/core/entities/ChannelEntityService.ts | 49 +- .../src/core/entities/ClipEntityService.ts | 19 +- .../core/entities/DriveFileEntityService.ts | 45 +- .../core/entities/DriveFolderEntityService.ts | 11 +- .../src/core/entities/EmojiEntityService.ts | 18 +- .../src/core/entities/FlashEntityService.ts | 21 +- .../core/entities/FlashLikeEntityService.ts | 17 +- .../entities/FollowRequestEntityService.ts | 17 +- .../core/entities/FollowingEntityService.ts | 33 +- .../core/entities/GalleryLikeEntityService.ts | 13 +- .../core/entities/GalleryPostEntityService.ts | 21 +- .../src/core/entities/HashtagEntityService.ts | 20 +- .../core/entities/InstanceEntityService.ts | 18 +- .../core/entities/InviteCodeEntityService.ts | 57 + .../entities/ModerationLogEntityService.ts | 13 +- .../src/core/entities/MutingEntityService.ts | 17 +- .../src/core/entities/NoteEntityService.ts | 77 +- .../entities/NoteFavoriteEntityService.ts | 19 +- .../entities/NoteReactionEntityService.ts | 17 +- .../entities/NotificationEntityService.ts | 38 +- .../src/core/entities/PageEntityService.ts | 27 +- .../core/entities/PageLikeEntityService.ts | 19 +- .../entities/RenoteMutingEntityService.ts | 17 +- .../src/core/entities/RoleEntityService.ts | 21 +- .../src/core/entities/SigninEntityService.ts | 18 +- .../src/core/entities/UserEntityService.ts | 115 +- .../core/entities/UserListEntityService.ts | 15 +- packages/backend/src/daemons/DaemonModule.ts | 8 +- .../backend/src/daemons/JanitorService.ts | 40 - .../backend/src/daemons/QueueStatsService.ts | 30 +- .../backend/src/daemons/ServerStatsService.ts | 26 +- packages/backend/src/decorators.ts | 5 + packages/backend/src/di-symbols.ts | 8 +- packages/backend/src/env.ts | 5 + packages/backend/src/global.d.ts | 5 + packages/backend/src/logger.ts | 8 +- packages/backend/src/misc/acct.ts | 7 +- packages/backend/src/misc/api-permissions.ts | 5 + packages/backend/src/misc/cache.ts | 73 +- packages/backend/src/misc/check-https.ts | 9 + packages/backend/src/misc/check-word-mute.ts | 37 +- packages/backend/src/misc/clone.ts | 5 + .../backend/src/misc/content-disposition.ts | 5 + packages/backend/src/misc/correct-filename.ts | 61 +- packages/backend/src/misc/create-temp.ts | 5 + packages/backend/src/misc/dev-null.ts | 19 +- packages/backend/src/misc/emoji-regex.ts | 5 + .../misc/extract-custom-emojis-from-mfm.ts | 5 + packages/backend/src/misc/extract-hashtags.ts | 5 + packages/backend/src/misc/extract-mentions.ts | 5 + .../backend/src/misc/fastify-reply-error.ts | 5 + packages/backend/src/misc/gen-identicon.ts | 5 + packages/backend/src/misc/gen-key-pair.ts | 5 + .../backend/src/misc/generate-invite-code.ts | 25 + .../src/misc/generate-native-user-token.ts | 8 +- packages/backend/src/misc/get-ip-hash.ts | 7 +- packages/backend/src/misc/get-note-summary.ts | 5 + .../backend/src/misc/get-reaction-emoji.ts | 6 + packages/backend/src/misc/i18n.ts | 5 + packages/backend/src/misc/id/aid.ts | 7 +- packages/backend/src/misc/id/aidx.ts | 44 + packages/backend/src/misc/id/meid.ts | 5 + packages/backend/src/misc/id/meidg.ts | 5 + packages/backend/src/misc/id/object-id.ts | 5 + packages/backend/src/misc/id/ulid.ts | 17 +- .../backend/src/misc/identifiable-error.ts | 5 + .../src/misc/is-duplicate-key-value-error.ts | 9 +- .../backend/src/misc/is-instance-muted.ts | 5 + packages/backend/src/misc/is-mime-image.ts | 5 + packages/backend/src/misc/is-native-token.ts | 6 + packages/backend/src/misc/is-not-null.ts | 5 + packages/backend/src/misc/is-quote.ts | 10 +- packages/backend/src/misc/is-user-related.ts | 5 + packages/backend/src/misc/json-schema.ts | 11 +- packages/backend/src/misc/langmap.ts | 5 + .../backend/src/misc/normalize-for-search.ts | 5 + packages/backend/src/misc/nyaize.ts | 5 + packages/backend/src/misc/prelude/array.ts | 10 +- .../backend/src/misc/prelude/await-all.ts | 7 +- packages/backend/src/misc/prelude/math.ts | 5 + packages/backend/src/misc/prelude/maybe.ts | 5 + packages/backend/src/misc/prelude/relation.ts | 5 + packages/backend/src/misc/prelude/string.ts | 5 + packages/backend/src/misc/prelude/symbol.ts | 5 + packages/backend/src/misc/prelude/time.ts | 24 +- packages/backend/src/misc/prelude/url.ts | 7 +- packages/backend/src/misc/prelude/xml.ts | 5 + packages/backend/src/misc/reset-db.ts | 5 + packages/backend/src/misc/safe-for-sql.ts | 5 + packages/backend/src/misc/secure-rndstr.ts | 10 +- .../backend/src/misc/show-machine-info.ts | 5 + packages/backend/src/misc/sql-like-escape.ts | 5 + packages/backend/src/misc/status-error.ts | 5 + packages/backend/src/misc/truncate.ts | 5 + .../backend/src/models/RepositoryModule.ts | 151 +- packages/backend/src/models/_.ts | 205 + .../src/models/entities/AbuseUserReport.ts | 29 +- .../src/models/entities/AccessToken.ts | 25 +- packages/backend/src/models/entities/Ad.ts | 16 +- .../src/models/entities/Announcement.ts | 60 +- .../src/models/entities/AnnouncementRead.ts | 25 +- .../backend/src/models/entities/Antenna.ts | 25 +- packages/backend/src/models/entities/App.ts | 17 +- .../models/entities/AttestationChallenge.ts | 46 - .../src/models/entities/AuthSession.ts | 25 +- .../backend/src/models/entities/Blocking.ts | 23 +- .../backend/src/models/entities/Channel.ts | 42 +- .../src/models/entities/ChannelFavorite.ts | 25 +- .../src/models/entities/ChannelFollowing.ts | 25 +- packages/backend/src/models/entities/Clip.ts | 17 +- .../src/models/entities/ClipFavorite.ts | 25 +- .../backend/src/models/entities/ClipNote.ts | 25 +- .../backend/src/models/entities/DriveFile.ts | 25 +- .../src/models/entities/DriveFolder.ts | 23 +- packages/backend/src/models/entities/Emoji.ts | 25 +- packages/backend/src/models/entities/Flash.ts | 26 +- .../backend/src/models/entities/FlashLike.ts | 25 +- .../src/models/entities/FollowRequest.ts | 23 +- .../backend/src/models/entities/Following.ts | 23 +- .../src/models/entities/GalleryLike.ts | 25 +- .../src/models/entities/GalleryPost.ts | 23 +- .../backend/src/models/entities/Hashtag.ts | 23 +- .../backend/src/models/entities/Instance.ts | 9 +- packages/backend/src/models/entities/Meta.ts | 81 +- .../src/models/entities/ModerationLog.ts | 17 +- .../backend/src/models/entities/MutedNote.ts | 25 +- .../backend/src/models/entities/Muting.ts | 23 +- packages/backend/src/models/entities/Note.ts | 58 +- .../src/models/entities/NoteFavorite.ts | 25 +- .../src/models/entities/NoteReaction.ts | 25 +- .../src/models/entities/NoteThreadMuting.ts | 17 +- .../backend/src/models/entities/NoteUnread.ts | 31 +- .../src/models/entities/Notification.ts | 24 +- packages/backend/src/models/entities/Page.ts | 29 +- .../backend/src/models/entities/PageLike.ts | 25 +- .../models/entities/PasswordResetRequest.ts | 17 +- packages/backend/src/models/entities/Poll.ts | 23 +- .../backend/src/models/entities/PollVote.ts | 25 +- .../backend/src/models/entities/PromoNote.ts | 21 +- .../backend/src/models/entities/PromoRead.ts | 25 +- .../src/models/entities/RegistrationTicket.ts | 60 +- .../src/models/entities/RegistryItem.ts | 17 +- packages/backend/src/models/entities/Relay.ts | 9 +- .../src/models/entities/RenoteMuting.ts | 23 +- .../models/entities/RetentionAggregation.ts | 13 +- packages/backend/src/models/entities/Role.ts | 14 +- .../src/models/entities/RoleAssignment.ts | 25 +- .../backend/src/models/entities/Signin.ts | 17 +- .../src/models/entities/SwSubscription.ts | 17 +- .../src/models/entities/UsedUsername.ts | 11 +- packages/backend/src/models/entities/User.ts | 53 +- .../backend/src/models/entities/UserIp.ts | 13 +- .../src/models/entities/UserKeypair.ts | 19 +- .../backend/src/models/entities/UserList.ts | 23 +- .../src/models/entities/UserListFavorite.ts | 38 + .../src/models/entities/UserListJoining.ts | 25 +- .../backend/src/models/entities/UserMemo.ts | 23 +- .../src/models/entities/UserNotePining.ts | 25 +- .../src/models/entities/UserPending.ts | 9 +- .../src/models/entities/UserProfile.ts | 41 +- .../src/models/entities/UserPublickey.ts | 19 +- .../src/models/entities/UserSecurityKey.ts | 56 +- .../backend/src/models/entities/Webhook.ts | 17 +- packages/backend/src/models/id.ts | 5 + packages/backend/src/models/index.ts | 200 - .../src/models/json-schema/announcement.ts | 58 + .../backend/src/models/json-schema/antenna.ts | 5 + .../backend/src/models/json-schema/app.ts | 5 + .../src/models/json-schema/blocking.ts | 5 + .../backend/src/models/json-schema/channel.ts | 17 + .../backend/src/models/json-schema/clip.ts | 5 + .../src/models/json-schema/drive-file.ts | 5 + .../src/models/json-schema/drive-folder.ts | 5 + .../backend/src/models/json-schema/emoji.ts | 35 + .../models/json-schema/federation-instance.ts | 5 + .../backend/src/models/json-schema/flash.ts | 5 + .../src/models/json-schema/following.ts | 5 + .../src/models/json-schema/gallery-post.ts | 5 + .../backend/src/models/json-schema/hashtag.ts | 5 + .../src/models/json-schema/invite-code.ts | 50 + .../backend/src/models/json-schema/muting.ts | 5 + .../src/models/json-schema/note-favorite.ts | 5 + .../src/models/json-schema/note-reaction.ts | 5 + .../backend/src/models/json-schema/note.ts | 9 + .../src/models/json-schema/notification.ts | 5 + .../backend/src/models/json-schema/page.ts | 5 + .../backend/src/models/json-schema/queue.ts | 5 + .../src/models/json-schema/renote-muting.ts | 5 + .../src/models/json-schema/user-list.ts | 10 + .../backend/src/models/json-schema/user.ts | 25 +- packages/backend/src/postgres.ts | 267 +- .../src/queue/DbQueueProcessorsService.ts | 63 - .../ObjectStorageQueueProcessorsService.ts | 25 - .../backend/src/queue/QueueLoggerService.ts | 5 + .../backend/src/queue/QueueProcessorModule.ts | 17 +- .../src/queue/QueueProcessorService.ts | 393 +- .../RelationshipQueueProcessorsService.ts | 26 - .../src/queue/SystemQueueProcessorsService.ts | 37 - packages/backend/src/queue/const.ts | 28 + packages/backend/src/queue/get-job-info.ts | 15 - .../AggregateRetentionProcessorService.ts | 17 +- .../CheckExpiredMutingsProcessorService.ts | 16 +- .../processors/CleanChartsProcessorService.ts | 17 +- .../queue/processors/CleanProcessorService.ts | 28 +- .../CleanRemoteFilesProcessorService.ts | 26 +- .../DeleteAccountProcessorService.ts | 37 +- .../DeleteDriveFilesProcessorService.ts | 27 +- .../processors/DeleteFileProcessorService.ts | 16 +- .../processors/DeliverProcessorService.ts | 45 +- .../EndedPollNotificationProcessorService.ts | 18 +- .../ExportAntennasProcessorService.ts | 102 + .../ExportBlockingProcessorService.ts | 28 +- .../ExportCustomEmojisProcessorService.ts | 46 +- .../ExportFavoritesProcessorService.ts | 39 +- .../ExportFollowingProcessorService.ts | 28 +- .../ExportMutingProcessorService.ts | 31 +- .../processors/ExportNotesProcessorService.ts | 44 +- .../ExportUserListsProcessorService.ts | 20 +- .../ImportAntennasProcessorService.ts | 101 + .../ImportBlockingProcessorService.ts | 20 +- .../ImportCustomEmojisProcessorService.ts | 47 +- .../ImportFollowingProcessorService.ts | 20 +- .../ImportMutingProcessorService.ts | 22 +- .../ImportUserListsProcessorService.ts | 18 +- .../queue/processors/InboxProcessorService.ts | 67 +- .../RelationshipProcessorService.ts | 16 +- .../ResyncChartsProcessorService.ts | 34 +- .../processors/TickChartsProcessorService.ts | 17 +- .../WebhookDeliverProcessorService.ts | 24 +- packages/backend/src/queue/types.ts | 35 +- .../src/server/ActivityPubServerService.ts | 58 +- .../backend/src/server/FileServerService.ts | 45 +- .../src/server/NodeinfoServerService.ts | 48 +- packages/backend/src/server/ServerModule.ts | 7 + packages/backend/src/server/ServerService.ts | 49 +- .../src/server/WellKnownServerService.ts | 25 +- .../backend/src/server/api/ApiCallService.ts | 135 +- .../src/server/api/ApiLoggerService.ts | 5 + .../src/server/api/ApiServerService.ts | 10 +- .../src/server/api/AuthenticateService.ts | 55 +- .../backend/src/server/api/EndpointsModule.ts | 72 +- .../backend/src/server/api/GetterService.ts | 21 +- .../src/server/api/RateLimiterService.ts | 25 +- .../src/server/api/SigninApiService.ts | 131 +- .../backend/src/server/api/SigninService.ts | 17 +- .../src/server/api/SignupApiService.ts | 92 +- .../server/api/StreamingApiServerService.ts | 177 +- .../backend/src/server/api/endpoint-base.ts | 29 +- packages/backend/src/server/api/endpoints.ts | 48 +- .../api/endpoints/admin/abuse-user-reports.ts | 12 +- .../api/endpoints/admin/accounts/create.ts | 11 +- .../api/endpoints/admin/accounts/delete.ts | 12 +- .../server/api/endpoints/admin/ad/create.ts | 14 +- .../server/api/endpoints/admin/ad/delete.ts | 10 +- .../src/server/api/endpoints/admin/ad/list.ts | 12 +- .../server/api/endpoints/admin/ad/update.ts | 14 +- .../endpoints/admin/announcements/create.ts | 36 +- .../endpoints/admin/announcements/delete.ts | 10 +- .../api/endpoints/admin/announcements/list.ts | 28 +- .../endpoints/admin/announcements/update.ts | 27 +- .../api/endpoints/admin/delete-account.ts | 10 +- .../admin/delete-all-files-of-a-user.ts | 10 +- .../admin/drive/clean-remote-files.ts | 8 +- .../api/endpoints/admin/drive/cleanup.ts | 10 +- .../server/api/endpoints/admin/drive/files.ts | 12 +- .../api/endpoints/admin/drive/show-file.ts | 10 +- .../endpoints/admin/emoji/add-aliases-bulk.ts | 10 +- .../server/api/endpoints/admin/emoji/add.ts | 48 +- .../server/api/endpoints/admin/emoji/copy.ts | 18 +- .../api/endpoints/admin/emoji/delete-bulk.ts | 10 +- .../api/endpoints/admin/emoji/delete.ts | 10 +- .../api/endpoints/admin/emoji/import-zip.ts | 8 +- .../api/endpoints/admin/emoji/list-remote.ts | 12 +- .../server/api/endpoints/admin/emoji/list.ts | 22 +- .../admin/emoji/remove-aliases-bulk.ts | 10 +- .../endpoints/admin/emoji/set-aliases-bulk.ts | 10 +- .../admin/emoji/set-category-bulk.ts | 10 +- .../endpoints/admin/emoji/set-license-bulk.ts | 41 + .../api/endpoints/admin/emoji/update.ts | 35 +- .../admin/federation/delete-all-files.ts | 10 +- .../refresh-remote-instance-metadata.ts | 10 +- .../admin/federation/remove-all-following.ts | 14 +- .../admin/federation/update-instance.ts | 14 +- .../api/endpoints/admin/get-index-stats.ts | 8 +- .../api/endpoints/admin/get-table-stats.ts | 8 +- .../api/endpoints/admin/get-user-ips.ts | 10 +- .../api/endpoints/admin/invite/create.ts | 84 + .../server/api/endpoints/admin/invite/list.ts | 74 + .../src/server/api/endpoints/admin/meta.ts | 63 +- .../api/endpoints/admin/promo/create.ts | 14 +- .../server/api/endpoints/admin/queue/clear.ts | 8 +- .../endpoints/admin/queue/deliver-delayed.ts | 8 +- .../endpoints/admin/queue/inbox-delayed.ts | 8 +- .../api/endpoints/admin/queue/promote.ts | 34 +- .../server/api/endpoints/admin/queue/stats.ts | 8 +- .../server/api/endpoints/admin/relays/add.ts | 10 +- .../server/api/endpoints/admin/relays/list.ts | 8 +- .../api/endpoints/admin/relays/remove.ts | 8 +- .../api/endpoints/admin/reset-password.ts | 14 +- .../admin/resolve-abuse-user-report.ts | 10 +- .../api/endpoints/admin/roles/assign.ts | 10 +- .../api/endpoints/admin/roles/create.ts | 14 +- .../api/endpoints/admin/roles/delete.ts | 10 +- .../server/api/endpoints/admin/roles/list.ts | 10 +- .../server/api/endpoints/admin/roles/show.ts | 10 +- .../api/endpoints/admin/roles/unassign.ts | 10 +- .../admin/roles/update-default-policies.ts | 8 +- .../api/endpoints/admin/roles/update.ts | 16 +- .../server/api/endpoints/admin/roles/users.ts | 12 +- .../server/api/endpoints/admin/send-email.ts | 8 +- .../server/api/endpoints/admin/server-info.ts | 8 +- .../endpoints/admin/show-moderation-logs.ts | 12 +- .../server/api/endpoints/admin/show-user.ts | 19 +- .../server/api/endpoints/admin/show-users.ts | 14 +- .../api/endpoints/admin/suspend-user.ts | 14 +- .../api/endpoints/admin/unsuspend-user.ts | 10 +- .../server/api/endpoints/admin/update-meta.ts | 81 +- .../api/endpoints/admin/update-user-note.ts | 10 +- .../src/server/api/endpoints/announcements.ts | 75 +- .../server/api/endpoints/antennas/create.ts | 16 +- .../server/api/endpoints/antennas/delete.ts | 10 +- .../src/server/api/endpoints/antennas/list.ts | 10 +- .../server/api/endpoints/antennas/notes.ts | 19 +- .../src/server/api/endpoints/antennas/show.ts | 10 +- .../server/api/endpoints/antennas/update.ts | 19 +- .../src/server/api/endpoints/ap/get.ts | 8 +- .../src/server/api/endpoints/ap/show.ts | 26 +- .../src/server/api/endpoints/app/create.ts | 12 +- .../src/server/api/endpoints/app/show.ts | 10 +- .../src/server/api/endpoints/auth/accept.ts | 25 +- .../api/endpoints/auth/session/generate.ts | 14 +- .../server/api/endpoints/auth/session/show.ts | 10 +- .../api/endpoints/auth/session/userkey.ts | 13 +- .../server/api/endpoints/blocking/create.ts | 20 +- .../server/api/endpoints/blocking/delete.ts | 22 +- .../src/server/api/endpoints/blocking/list.ts | 12 +- .../server/api/endpoints/channels/create.ts | 20 +- .../server/api/endpoints/channels/favorite.ts | 12 +- .../server/api/endpoints/channels/featured.ts | 13 +- .../server/api/endpoints/channels/follow.ts | 13 +- .../server/api/endpoints/channels/followed.ts | 12 +- .../api/endpoints/channels/my-favorites.ts | 12 +- .../server/api/endpoints/channels/owned.ts | 15 +- .../server/api/endpoints/channels/search.ts | 31 +- .../src/server/api/endpoints/channels/show.ts | 10 +- .../server/api/endpoints/channels/timeline.ts | 16 +- .../api/endpoints/channels/unfavorite.ts | 12 +- .../server/api/endpoints/channels/unfollow.ts | 13 +- .../server/api/endpoints/channels/update.ts | 16 +- .../api/endpoints/charts/active-users.ts | 8 +- .../server/api/endpoints/charts/ap-request.ts | 8 +- .../src/server/api/endpoints/charts/drive.ts | 8 +- .../server/api/endpoints/charts/federation.ts | 8 +- .../server/api/endpoints/charts/instance.ts | 8 +- .../src/server/api/endpoints/charts/notes.ts | 8 +- .../server/api/endpoints/charts/user/drive.ts | 8 +- .../api/endpoints/charts/user/following.ts | 8 +- .../server/api/endpoints/charts/user/notes.ts | 8 +- .../server/api/endpoints/charts/user/pv.ts | 8 +- .../api/endpoints/charts/user/reactions.ts | 8 +- .../src/server/api/endpoints/charts/users.ts | 8 +- .../server/api/endpoints/clips/add-note.ts | 29 +- .../src/server/api/endpoints/clips/create.ts | 14 +- .../src/server/api/endpoints/clips/delete.ts | 10 +- .../server/api/endpoints/clips/favorite.ts | 22 +- .../src/server/api/endpoints/clips/list.ts | 10 +- .../api/endpoints/clips/my-favorites.ts | 10 +- .../src/server/api/endpoints/clips/notes.ts | 12 +- .../server/api/endpoints/clips/remove-note.ts | 19 +- .../src/server/api/endpoints/clips/show.ts | 10 +- .../server/api/endpoints/clips/unfavorite.ts | 12 +- .../src/server/api/endpoints/clips/update.ts | 12 +- .../backend/src/server/api/endpoints/drive.ts | 8 +- .../src/server/api/endpoints/drive/files.ts | 12 +- .../endpoints/drive/files/attached-notes.ts | 10 +- .../endpoints/drive/files/check-existence.ts | 20 +- .../api/endpoints/drive/files/create.ts | 17 +- .../api/endpoints/drive/files/delete.ts | 10 +- .../api/endpoints/drive/files/find-by-hash.ts | 10 +- .../server/api/endpoints/drive/files/find.ts | 10 +- .../server/api/endpoints/drive/files/show.ts | 14 +- .../api/endpoints/drive/files/update.ts | 23 +- .../endpoints/drive/files/upload-from-url.ts | 17 +- .../src/server/api/endpoints/drive/folders.ts | 12 +- .../api/endpoints/drive/folders/create.ts | 10 +- .../api/endpoints/drive/folders/delete.ts | 10 +- .../api/endpoints/drive/folders/find.ts | 10 +- .../api/endpoints/drive/folders/show.ts | 10 +- .../api/endpoints/drive/folders/update.ts | 10 +- .../src/server/api/endpoints/drive/stream.ts | 12 +- .../api/endpoints/email-address/available.ts | 8 +- .../backend/src/server/api/endpoints/emoji.ts | 14 +- .../src/server/api/endpoints/emojis.ts | 14 +- .../src/server/api/endpoints/endpoint.ts | 8 +- .../src/server/api/endpoints/endpoints.ts | 8 +- .../api/endpoints/export-custom-emojis.ts | 8 +- .../api/endpoints/federation/followers.ts | 12 +- .../api/endpoints/federation/following.ts | 12 +- .../api/endpoints/federation/instances.ts | 12 +- .../api/endpoints/federation/show-instance.ts | 10 +- .../server/api/endpoints/federation/stats.ts | 10 +- .../federation/update-remote-user.ts | 8 +- .../server/api/endpoints/federation/users.ts | 12 +- .../src/server/api/endpoints/fetch-rss.ts | 15 +- .../src/server/api/endpoints/flash/create.ts | 12 +- .../src/server/api/endpoints/flash/delete.ts | 10 +- .../server/api/endpoints/flash/featured.ts | 12 +- .../src/server/api/endpoints/flash/like.ts | 22 +- .../server/api/endpoints/flash/my-likes.ts | 12 +- .../src/server/api/endpoints/flash/my.ts | 12 +- .../src/server/api/endpoints/flash/show.ts | 13 +- .../src/server/api/endpoints/flash/unlike.ts | 12 +- .../src/server/api/endpoints/flash/update.ts | 16 +- .../server/api/endpoints/following/create.ts | 25 +- .../server/api/endpoints/following/delete.ts | 25 +- .../api/endpoints/following/invalidate.ts | 15 +- .../endpoints/following/requests/accept.ts | 8 +- .../endpoints/following/requests/cancel.ts | 15 +- .../api/endpoints/following/requests/list.ts | 12 +- .../endpoints/following/requests/reject.ts | 8 +- .../server/api/endpoints/gallery/featured.ts | 12 +- .../server/api/endpoints/gallery/popular.ts | 12 +- .../src/server/api/endpoints/gallery/posts.ts | 12 +- .../api/endpoints/gallery/posts/create.ts | 20 +- .../api/endpoints/gallery/posts/delete.ts | 10 +- .../api/endpoints/gallery/posts/like.ts | 22 +- .../api/endpoints/gallery/posts/show.ts | 10 +- .../api/endpoints/gallery/posts/unlike.ts | 12 +- .../api/endpoints/gallery/posts/update.ts | 16 +- .../api/endpoints/get-online-users-count.ts | 12 +- .../src/server/api/endpoints/hashtags/list.ts | 12 +- .../server/api/endpoints/hashtags/search.ts | 14 +- .../src/server/api/endpoints/hashtags/show.ts | 10 +- .../server/api/endpoints/hashtags/trend.ts | 16 +- .../server/api/endpoints/hashtags/users.ts | 17 +- .../backend/src/server/api/endpoints/i.ts | 17 +- .../src/server/api/endpoints/i/2fa/done.ts | 21 +- .../server/api/endpoints/i/2fa/key-done.ts | 139 +- .../api/endpoints/i/2fa/password-less.ts | 10 +- .../api/endpoints/i/2fa/register-key.ts | 84 +- .../server/api/endpoints/i/2fa/register.ts | 23 +- .../server/api/endpoints/i/2fa/remove-key.ts | 23 +- .../server/api/endpoints/i/2fa/unregister.ts | 24 +- .../server/api/endpoints/i/2fa/update-key.ts | 17 +- .../src/server/api/endpoints/i/apps.ts | 15 +- .../server/api/endpoints/i/authorized-apps.ts | 10 +- .../server/api/endpoints/i/change-password.ts | 10 +- .../api/endpoints/i/claim-achievement.ts | 9 +- .../server/api/endpoints/i/delete-account.ts | 10 +- .../server/api/endpoints/i/export-antennas.ts | 35 + .../server/api/endpoints/i/export-blocking.ts | 8 +- .../api/endpoints/i/export-favorites.ts | 8 +- .../api/endpoints/i/export-following.ts | 8 +- .../src/server/api/endpoints/i/export-mute.ts | 8 +- .../server/api/endpoints/i/export-notes.ts | 8 +- .../api/endpoints/i/export-user-lists.ts | 8 +- .../src/server/api/endpoints/i/favorites.ts | 12 +- .../server/api/endpoints/i/gallery/likes.ts | 12 +- .../server/api/endpoints/i/gallery/posts.ts | 12 +- .../endpoints/i/get-word-muted-notes-count.ts | 10 +- .../server/api/endpoints/i/import-antennas.ts | 89 + .../server/api/endpoints/i/import-blocking.ts | 21 +- .../api/endpoints/i/import-following.ts | 21 +- .../server/api/endpoints/i/import-muting.ts | 21 +- .../api/endpoints/i/import-user-lists.ts | 21 +- .../src/server/api/endpoints/i/known-as.ts | 92 - .../src/server/api/endpoints/i/move.ts | 86 +- .../server/api/endpoints/i/notifications.ts | 29 +- .../src/server/api/endpoints/i/page-likes.ts | 12 +- .../src/server/api/endpoints/i/pages.ts | 12 +- .../backend/src/server/api/endpoints/i/pin.ts | 9 +- .../api/endpoints/i/read-all-unread-notes.ts | 10 +- .../api/endpoints/i/read-announcement.ts | 61 +- .../api/endpoints/i/regenerate-token.ts | 10 +- .../api/endpoints/i/registry/get-all.ts | 10 +- .../api/endpoints/i/registry/get-detail.ts | 10 +- .../server/api/endpoints/i/registry/get.ts | 10 +- .../endpoints/i/registry/keys-with-type.ts | 10 +- .../server/api/endpoints/i/registry/keys.ts | 10 +- .../server/api/endpoints/i/registry/remove.ts | 10 +- .../server/api/endpoints/i/registry/scopes.ts | 10 +- .../server/api/endpoints/i/registry/set.ts | 10 +- .../server/api/endpoints/i/revoke-token.ts | 17 +- .../server/api/endpoints/i/signin-history.ts | 12 +- .../src/server/api/endpoints/i/unpin.ts | 8 +- .../server/api/endpoints/i/update-email.ts | 17 +- .../src/server/api/endpoints/i/update.ts | 99 +- .../server/api/endpoints/i/webhooks/create.ts | 14 +- .../server/api/endpoints/i/webhooks/delete.ts | 10 +- .../server/api/endpoints/i/webhooks/list.ts | 10 +- .../server/api/endpoints/i/webhooks/show.ts | 10 +- .../server/api/endpoints/i/webhooks/update.ts | 14 +- .../src/server/api/endpoints/invite.ts | 61 - .../src/server/api/endpoints/invite/create.ts | 86 + .../src/server/api/endpoints/invite/delete.ts | 75 + .../src/server/api/endpoints/invite/limit.ts | 58 + .../src/server/api/endpoints/invite/list.ts | 62 + .../backend/src/server/api/endpoints/meta.ts | 65 +- .../server/api/endpoints/miauth/gen-token.ts | 12 +- .../src/server/api/endpoints/mute/create.ts | 21 +- .../src/server/api/endpoints/mute/delete.ts | 10 +- .../src/server/api/endpoints/mute/list.ts | 12 +- .../src/server/api/endpoints/my/apps.ts | 10 +- .../backend/src/server/api/endpoints/notes.ts | 28 +- .../server/api/endpoints/notes/children.ts | 12 +- .../src/server/api/endpoints/notes/clips.ts | 10 +- .../api/endpoints/notes/conversation.ts | 16 +- .../server/api/endpoints/notes/create.test.ts | 5 + .../src/server/api/endpoints/notes/create.ts | 56 +- .../src/server/api/endpoints/notes/delete.ts | 10 +- .../api/endpoints/notes/favorites/create.ts | 21 +- .../api/endpoints/notes/favorites/delete.ts | 10 +- .../server/api/endpoints/notes/featured.ts | 12 +- .../api/endpoints/notes/global-timeline.ts | 23 +- .../api/endpoints/notes/hybrid-timeline.ts | 23 +- .../api/endpoints/notes/local-timeline.ts | 23 +- .../server/api/endpoints/notes/mentions.ts | 14 +- .../endpoints/notes/polls/recommendation.ts | 14 +- .../server/api/endpoints/notes/polls/vote.ts | 16 +- .../server/api/endpoints/notes/reactions.ts | 14 +- .../api/endpoints/notes/reactions/create.ts | 10 +- .../api/endpoints/notes/reactions/delete.ts | 8 +- .../src/server/api/endpoints/notes/renotes.ts | 12 +- .../src/server/api/endpoints/notes/replies.ts | 12 +- .../api/endpoints/notes/search-by-tag.ts | 16 +- .../src/server/api/endpoints/notes/search.ts | 57 +- .../src/server/api/endpoints/notes/show.ts | 17 +- .../src/server/api/endpoints/notes/state.ts | 10 +- .../endpoints/notes/thread-muting/create.ts | 10 +- .../endpoints/notes/thread-muting/delete.ts | 10 +- .../server/api/endpoints/notes/timeline.ts | 24 +- .../server/api/endpoints/notes/translate.ts | 19 +- .../server/api/endpoints/notes/unrenote.ts | 12 +- .../api/endpoints/notes/user-list-timeline.ts | 16 +- .../api/endpoints/notifications/create.ts | 13 +- .../notifications/mark-all-as-read.ts | 11 +- .../notifications/test-notification.ts | 38 + .../src/server/api/endpoints/page-push.ts | 10 +- .../src/server/api/endpoints/pages/create.ts | 16 +- .../src/server/api/endpoints/pages/delete.ts | 10 +- .../server/api/endpoints/pages/featured.ts | 12 +- .../src/server/api/endpoints/pages/like.ts | 22 +- .../src/server/api/endpoints/pages/show.ts | 14 +- .../src/server/api/endpoints/pages/unlike.ts | 12 +- .../src/server/api/endpoints/pages/update.ts | 16 +- .../backend/src/server/api/endpoints/ping.ts | 8 +- .../src/server/api/endpoints/pinned-users.ts | 14 +- .../src/server/api/endpoints/promo/read.ts | 22 +- .../api/endpoints/renote-mute/create.ts | 17 +- .../api/endpoints/renote-mute/delete.ts | 12 +- .../server/api/endpoints/renote-mute/list.ts | 12 +- .../api/endpoints/request-reset-password.ts | 16 +- .../src/server/api/endpoints/reset-db.ts | 10 +- .../server/api/endpoints/reset-password.ts | 10 +- .../src/server/api/endpoints/retention.ts | 10 +- .../src/server/api/endpoints/roles/list.ts | 11 +- .../src/server/api/endpoints/roles/notes.ts | 16 +- .../src/server/api/endpoints/roles/show.ts | 10 +- .../src/server/api/endpoints/roles/users.ts | 13 +- .../src/server/api/endpoints/server-info.ts | 27 +- .../backend/src/server/api/endpoints/stats.ts | 16 +- .../src/server/api/endpoints/sw/register.ts | 10 +- .../api/endpoints/sw/show-registration.ts | 10 +- .../src/server/api/endpoints/sw/unregister.ts | 10 +- .../api/endpoints/sw/update-registration.ts | 12 +- .../backend/src/server/api/endpoints/test.ts | 8 +- .../api/endpoints/username/available.ts | 19 +- .../backend/src/server/api/endpoints/users.ts | 19 +- .../api/endpoints/users/achievements.ts | 10 +- .../src/server/api/endpoints/users/clips.ts | 12 +- .../src/server/api/endpoints/users/flashs.ts | 62 + .../server/api/endpoints/users/followers.ts | 22 +- .../server/api/endpoints/users/following.ts | 22 +- .../api/endpoints/users/gallery/posts.ts | 12 +- .../users/get-frequently-replied-users.ts | 15 +- .../users/lists/create-from-public.ts | 159 + .../api/endpoints/users/lists/create.ts | 18 +- .../api/endpoints/users/lists/delete.ts | 10 +- .../api/endpoints/users/lists/favorite.ts | 79 + .../server/api/endpoints/users/lists/list.ts | 50 +- .../server/api/endpoints/users/lists/pull.ts | 12 +- .../server/api/endpoints/users/lists/push.ts | 30 +- .../server/api/endpoints/users/lists/show.ts | 42 +- .../api/endpoints/users/lists/unfavorite.ts | 70 + .../api/endpoints/users/lists/update.ts | 15 +- .../src/server/api/endpoints/users/notes.ts | 18 +- .../src/server/api/endpoints/users/pages.ts | 12 +- .../server/api/endpoints/users/reactions.ts | 12 +- .../api/endpoints/users/recommendation.ts | 14 +- .../server/api/endpoints/users/relation.ts | 15 +- .../api/endpoints/users/report-abuse.ts | 15 +- .../users/search-by-username-and-host.ts | 20 +- .../src/server/api/endpoints/users/search.ts | 29 +- .../src/server/api/endpoints/users/show.ts | 17 +- .../src/server/api/endpoints/users/stats.ts | 228 - .../server/api/endpoints/users/update-memo.ts | 10 +- packages/backend/src/server/api/error.ts | 5 + .../api/openapi/OpenApiServerService.ts | 5 + .../backend/src/server/api/openapi/errors.ts | 4 + .../src/server/api/openapi/gen-spec.ts | 5 + .../backend/src/server/api/openapi/schemas.ts | 5 + .../src/server/api/stream/ChannelsService.ts | 7 +- .../api/stream/{index.ts => Connection.ts} | 47 +- .../backend/src/server/api/stream/channel.ts | 8 +- .../src/server/api/stream/channels/admin.ts | 5 + .../src/server/api/stream/channels/antenna.ts | 5 + .../src/server/api/stream/channels/channel.ts | 5 + .../src/server/api/stream/channels/drive.ts | 5 + .../api/stream/channels/global-timeline.ts | 10 +- .../src/server/api/stream/channels/hashtag.ts | 7 +- .../api/stream/channels/home-timeline.ts | 10 +- .../api/stream/channels/hybrid-timeline.ts | 10 +- .../api/stream/channels/local-timeline.ts | 10 +- .../src/server/api/stream/channels/main.ts | 5 + .../server/api/stream/channels/queue-stats.ts | 5 + .../api/stream/channels/role-timeline.ts | 14 + .../api/stream/channels/server-stats.ts | 5 + .../server/api/stream/channels/user-list.ts | 25 +- .../backend/src/server/api/stream/types.ts | 139 +- .../src/server/oauth/OAuth2ProviderService.ts | 487 + .../src/server/web/ClientLoggerService.ts | 5 + .../src/server/web/ClientServerService.ts | 163 +- .../backend/src/server/web/FeedService.ts | 26 +- .../src/server/web/UrlPreviewService.ts | 9 +- packages/backend/src/server/web/bios.css | 6 + packages/backend/src/server/web/bios.js | 11 +- packages/backend/src/server/web/boot.js | 81 +- packages/backend/src/server/web/cli.css | 6 + packages/backend/src/server/web/cli.js | 11 +- packages/backend/src/server/web/error.css | 6 + packages/backend/src/server/web/style.css | 6 + .../backend/src/server/web/views/base.pug | 22 +- .../backend/src/server/web/views/channel.pug | 1 + .../backend/src/server/web/views/clip.pug | 4 + .../backend/src/server/web/views/error.pug | 4 +- .../backend/src/server/web/views/flash.pug | 4 + .../src/server/web/views/gallery-post.pug | 10 +- .../src/server/web/views/info-card.pug | 2 +- .../backend/src/server/web/views/note.pug | 23 +- .../backend/src/server/web/views/oauth.pug | 9 + .../backend/src/server/web/views/page.pug | 4 + .../backend/src/server/web/views/user.pug | 4 + packages/backend/src/types.ts | 7 +- packages/backend/test/e2e/2fa.ts | 178 +- packages/backend/test/e2e/antennas.ts | 658 + packages/backend/test/e2e/api-visibility.ts | 16 +- packages/backend/test/e2e/api.ts | 189 +- packages/backend/test/e2e/block.ts | 12 +- packages/backend/test/e2e/clips.ts | 155 +- packages/backend/test/e2e/endpoints.ts | 131 +- packages/backend/test/e2e/fetch-resource.ts | 226 +- packages/backend/test/e2e/ff-visibility.ts | 10 +- packages/backend/test/e2e/move.ts | 462 + packages/backend/test/e2e/mute.ts | 12 +- packages/backend/test/e2e/note.ts | 138 +- packages/backend/test/e2e/oauth.ts | 944 + packages/backend/test/e2e/renote-mute.ts | 12 +- packages/backend/test/e2e/streaming.ts | 20 +- packages/backend/test/e2e/thread-mute.ts | 12 +- packages/backend/test/e2e/user-notes.ts | 8 +- packages/backend/test/e2e/users.ts | 134 +- packages/backend/test/misc/mock-resolver.ts | 32 +- .../backend/test/prelude/get-api-validator.ts | 19 +- packages/backend/test/prelude/maybe.ts | 5 + packages/backend/test/prelude/url.ts | 5 + .../backend/test/resources/kick_gaba7.aac | Bin 0 -> 7291 bytes .../backend/test/resources/kick_gaba7.flac | Bin 0 -> 108793 bytes .../backend/test/resources/kick_gaba7.mp3 | Bin 0 -> 19853 bytes .../backend/test/resources/kick_gaba7.wav | Bin 0 -> 87630 bytes .../backend/test/resources/kick_gaba7.webm | Bin 0 -> 8879 bytes packages/backend/test/tsconfig.json | 8 +- .../backend/test/unit/AnnouncementService.ts | 194 + packages/backend/test/unit/DriveService.ts | 7 +- .../test/unit/FetchInstanceMetadataService.ts | 114 + packages/backend/test/unit/FileInfoService.ts | 402 +- packages/backend/test/unit/MetaService.ts | 7 +- packages/backend/test/unit/MfmService.ts | 5 + packages/backend/test/unit/ReactionService.ts | 47 +- packages/backend/test/unit/RelayService.ts | 25 +- packages/backend/test/unit/RoleService.ts | 49 +- packages/backend/test/unit/S3Service.ts | 17 +- packages/backend/test/unit/activitypub.ts | 270 +- packages/backend/test/unit/ap-request.ts | 5 + packages/backend/test/unit/chart.ts | 19 +- .../backend/test/unit/extract-mentions.ts | 5 + .../backend/test/unit/misc/check-word-mute.ts | 54 + .../test/unit/misc/correct-filename.ts | 48 + packages/backend/test/unit/misc/id.ts | 77 +- packages/backend/test/unit/misc/others.ts | 51 +- packages/backend/test/utils.ts | 215 +- packages/backend/tsconfig.json | 9 +- packages/backend/watch.mjs | 5 + .../frontend/{.eslintrc.js => .eslintrc.cjs} | 8 +- packages/frontend/.storybook/changes.ts | 16 +- packages/frontend/.storybook/fakes.ts | 30 + packages/frontend/.storybook/generate.tsx | 34 +- packages/frontend/.storybook/main.ts | 14 +- packages/frontend/.storybook/manager.ts | 5 + packages/frontend/.storybook/mocks.ts | 5 + packages/frontend/.storybook/package.json | 3 + .../frontend/.storybook/preload-locale.ts | 12 +- packages/frontend/.storybook/preload-theme.ts | 12 +- .../frontend/.storybook/preview-head.html | 2 +- packages/frontend/.storybook/preview.ts | 47 +- packages/frontend/.storybook/tsconfig.json | 3 + packages/frontend/@types/global.d.ts | 5 + packages/frontend/@types/theme.d.ts | 7 +- ...lugin-unwind-css-module-class-name.test.ts | 602 + ...lup-plugin-unwind-css-module-class-name.ts | 280 + packages/frontend/package.json | 171 +- packages/frontend/public/mockServiceWorker.js | 5 + packages/frontend/src/_boot_.ts | 19 + packages/frontend/src/account.ts | 128 +- packages/frontend/src/boot/common.ts | 267 + packages/frontend/src/boot/main-boot.ts | 275 + packages/frontend/src/boot/sub-boot.ts | 13 + packages/frontend/src/cache.ts | 17 +- .../components/MkAbuseReport.stories.impl.ts | 5 + .../frontend/src/components/MkAbuseReport.vue | 13 +- .../MkAbuseReportWindow.stories.impl.ts | 5 + .../src/components/MkAbuseReportWindow.vue | 19 +- .../components/MkAccountMoved.stories.impl.ts | 5 + .../src/components/MkAccountMoved.vue | 25 +- .../components/MkAchievements.stories.impl.ts | 7 +- .../src/components/MkAchievements.vue | 24 +- .../components/MkAnalogClock.stories.impl.ts | 7 +- .../frontend/src/components/MkAnalogClock.vue | 39 +- packages/frontend/src/components/MkAnimBg.vue | 248 + .../MkAnnouncementDialog.stories.impl.ts | 47 + .../src/components/MkAnnouncementDialog.vue | 104 + .../src/components/MkAsUi.stories.impl.ts | 5 + packages/frontend/src/components/MkAsUi.vue | 40 +- .../components/MkAutocomplete.stories.impl.ts | 7 +- .../src/components/MkAutocomplete.vue | 47 +- .../src/components/MkAvatars.stories.impl.ts | 5 + .../frontend/src/components/MkAvatars.vue | 24 +- .../src/components/MkButton.stories.impl.ts | 5 + packages/frontend/src/components/MkButton.vue | 28 +- .../src/components/MkCaptcha.stories.impl.ts | 5 + .../frontend/src/components/MkCaptcha.vue | 9 +- .../src/components/MkChannelFollowButton.vue | 31 +- .../frontend/src/components/MkChannelList.vue | 13 +- .../src/components/MkChannelPreview.vue | 21 +- packages/frontend/src/components/MkChart.vue | 55 +- .../frontend/src/components/MkChartLegend.vue | 5 + .../src/components/MkChartTooltip.vue | 7 +- .../frontend/src/components/MkCheckbox.vue | 144 - .../frontend/src/components/MkClickerGame.vue | 21 +- .../frontend/src/components/MkClipPreview.vue | 7 +- .../frontend/src/components/MkCode.core.vue | 5 + packages/frontend/src/components/MkCode.vue | 5 + .../frontend/src/components/MkColorInput.vue | 115 + .../frontend/src/components/MkContainer.vue | 210 +- .../frontend/src/components/MkContextMenu.vue | 27 +- .../src/components/MkCropperDialog.vue | 50 +- .../frontend/src/components/MkCwButton.vue | 13 +- .../src/components/MkDateSeparatedList.vue | 17 +- packages/frontend/src/components/MkDialog.vue | 27 +- .../components/MkDigitalClock.stories.impl.ts | 37 + .../src/components/MkDigitalClock.vue | 26 +- .../frontend/src/components/MkDonation.vue | 15 +- .../frontend/src/components/MkDrive.file.vue | 200 +- .../src/components/MkDrive.folder.vue | 121 +- .../src/components/MkDrive.navFolder.vue | 33 +- packages/frontend/src/components/MkDrive.vue | 340 +- .../src/components/MkDriveFileThumbnail.vue | 57 +- .../src/components/MkDriveSelectDialog.vue | 15 +- .../frontend/src/components/MkDriveWindow.vue | 15 +- .../src/components/MkEmojiPicker.section.vue | 7 +- .../frontend/src/components/MkEmojiPicker.vue | 72 +- .../src/components/MkEmojiPickerDialog.vue | 37 +- .../src/components/MkEmojiPickerWindow.vue | 16 +- .../src/components/MkFeaturedPhotos.vue | 7 +- .../components/MkFileCaptionEditWindow.vue | 13 +- .../src/components/MkFileListForAdmin.vue | 17 +- .../src/components/MkFlashPreview.vue | 11 +- .../src/components/MkFoldableSection.vue | 206 +- packages/frontend/src/components/MkFolder.vue | 39 +- .../src/components/MkFollowButton.vue | 48 +- .../src/components/MkForgotPassword.vue | 66 +- .../frontend/src/components/MkFormDialog.vue | 97 +- .../MkGalleryPostPreview.stories.impl.ts | 17 +- .../src/components/MkGalleryPostPreview.vue | 52 +- packages/frontend/src/components/MkGoogle.vue | 7 +- .../frontend/src/components/MkHeatmap.vue | 15 +- .../frontend/src/components/MkImageViewer.vue | 78 - .../src/components/MkImgWithBlurhash.vue | 208 +- packages/frontend/src/components/MkInfo.vue | 6 + packages/frontend/src/components/MkInput.vue | 207 +- .../src/components/MkInstanceCardMini.vue | 13 +- .../src/components/MkInstanceStats.vue | 26 +- .../src/components/MkInstanceTicker.vue | 11 +- .../components/MkInviteCode.stories.impl.ts | 65 + .../frontend/src/components/MkInviteCode.vue | 129 + .../frontend/src/components/MkKeyValue.vue | 37 +- .../frontend/src/components/MkLaunchPad.vue | 11 +- packages/frontend/src/components/MkLink.vue | 13 +- .../frontend/src/components/MkMarquee.vue | 5 + .../frontend/src/components/MkMediaBanner.vue | 108 +- .../frontend/src/components/MkMediaImage.vue | 164 +- .../frontend/src/components/MkMediaList.vue | 161 +- .../frontend/src/components/MkMediaVideo.vue | 125 +- .../frontend/src/components/MkMention.vue | 13 +- .../frontend/src/components/MkMenu.child.vue | 7 +- packages/frontend/src/components/MkMenu.vue | 124 +- .../frontend/src/components/MkMiniChart.vue | 11 +- packages/frontend/src/components/MkModal.vue | 82 +- .../src/components/MkModalPageWindow.vue | 182 - .../frontend/src/components/MkModalWindow.vue | 118 +- packages/frontend/src/components/MkNote.vue | 204 +- .../src/components/MkNoteDetailed.vue | 816 +- .../frontend/src/components/MkNoteHeader.vue | 16 +- .../frontend/src/components/MkNotePreview.vue | 9 +- .../frontend/src/components/MkNoteSimple.vue | 15 +- .../frontend/src/components/MkNoteSub.vue | 57 +- packages/frontend/src/components/MkNotes.vue | 12 +- .../src/components/MkNotification.vue | 50 +- .../MkNotificationSettingWindow.vue | 13 +- .../src/components/MkNotifications.vue | 22 +- packages/frontend/src/components/MkNumber.vue | 7 +- .../frontend/src/components/MkNumberDiff.vue | 58 +- .../src/components/MkObjectView.value.vue | 71 +- .../frontend/src/components/MkObjectView.vue | 13 +- packages/frontend/src/components/MkOmit.vue | 33 +- .../frontend/src/components/MkPagePreview.vue | 54 +- .../frontend/src/components/MkPageWindow.vue | 50 +- .../frontend/src/components/MkPagination.vue | 202 +- .../src/components/MkPlusOneEffect.vue | 7 +- packages/frontend/src/components/MkPoll.vue | 137 +- .../frontend/src/components/MkPollEditor.vue | 13 +- .../frontend/src/components/MkPopupMenu.vue | 51 +- .../frontend/src/components/MkPostForm.vue | 122 +- .../src/components/MkPostFormAttaches.vue | 141 +- .../src/components/MkPostFormDialog.vue | 34 +- .../MkPushNotificationAllowButton.vue | 53 +- packages/frontend/src/components/MkRadio.vue | 98 +- packages/frontend/src/components/MkRadios.vue | 41 +- packages/frontend/src/components/MkRange.vue | 13 +- .../src/components/MkReactedUsersDialog.vue | 98 - .../src/components/MkReactionEffect.vue | 7 +- .../src/components/MkReactionIcon.vue | 9 +- .../src/components/MkReactionTooltip.vue | 9 +- .../components/MkReactionsViewer.details.vue | 11 +- .../components/MkReactionsViewer.reaction.vue | 80 +- .../src/components/MkReactionsViewer.vue | 23 +- .../src/components/MkRemoteCaution.vue | 7 +- .../src/components/MkRetentionHeatmap.vue | 59 +- .../src/components/MkRetentionLineChart.vue | 132 + .../src/components/MkRippleEffect.vue | 23 +- .../frontend/src/components/MkRolePreview.vue | 20 +- packages/frontend/src/components/MkSample.vue | 118 - packages/frontend/src/components/MkSelect.vue | 210 +- packages/frontend/src/components/MkSignin.vue | 156 +- .../src/components/MkSigninDialog.vue | 11 +- packages/frontend/src/components/MkSignup.vue | 263 - .../src/components/MkSignupDialog.form.vue | 300 + .../MkSignupDialog.rules.stories.impl.ts | 99 + .../src/components/MkSignupDialog.rules.vue | 175 + .../src/components/MkSignupDialog.vue | 52 +- .../frontend/src/components/MkSparkle.vue | 9 +- .../src/components/MkSubNoteContent.vue | 53 +- .../frontend/src/components/MkSuperMenu.vue | 30 +- .../src/components/MkSwitch.button.vue | 88 + packages/frontend/src/components/MkSwitch.vue | 152 +- packages/frontend/src/components/MkTab.vue | 17 +- .../frontend/src/components/MkTagCloud.vue | 33 +- .../frontend/src/components/MkTextarea.vue | 363 +- .../frontend/src/components/MkTimeline.vue | 63 +- packages/frontend/src/components/MkToast.vue | 19 +- .../src/components/MkTokenGenerateWindow.vue | 19 +- .../frontend/src/components/MkTooltip.vue | 30 +- .../frontend/src/components/MkUpdated.vue | 13 +- .../frontend/src/components/MkUrlPreview.vue | 104 +- .../src/components/MkUrlPreviewPopup.vue | 19 +- .../MkUserAnnouncementEditDialog.vue | 145 + .../src/components/MkUserCardMini.vue | 13 +- .../frontend/src/components/MkUserInfo.vue | 235 +- .../frontend/src/components/MkUserList.vue | 16 +- .../src/components/MkUserOnlineIndicator.vue | 21 +- .../frontend/src/components/MkUserPopup.vue | 53 +- .../src/components/MkUserSelectDialog.vue | 39 +- .../MkUserSetupDialog.Follow.stories.impl.ts | 56 + .../components/MkUserSetupDialog.Follow.vue | 64 + .../MkUserSetupDialog.Privacy.stories.impl.ts | 36 + .../components/MkUserSetupDialog.Privacy.vue | 73 + .../MkUserSetupDialog.Profile.stories.impl.ts | 36 + .../components/MkUserSetupDialog.Profile.vue | 102 + .../MkUserSetupDialog.User.stories.impl.ts | 37 + .../src/components/MkUserSetupDialog.User.vue | 106 + .../MkUserSetupDialog.stories.impl.ts | 56 + .../src/components/MkUserSetupDialog.vue | 226 + .../src/components/MkUsersTooltip.vue | 19 +- .../src/components/MkVisibilityPicker.vue | 17 +- .../MkVisitorDashboard.ActiveUsersChart.vue | 162 + .../src/components/MkVisitorDashboard.vue | 228 + .../src/components/MkWaitingDialog.vue | 7 +- .../frontend/src/components/MkWidgets.vue | 53 +- packages/frontend/src/components/MkWindow.vue | 27 +- .../src/components/MkYouTubePlayer.vue | 11 +- .../frontend/src/components/form/link.vue | 121 +- .../frontend/src/components/form/section.vue | 5 + .../frontend/src/components/form/slot.vue | 43 +- .../frontend/src/components/form/split.vue | 5 + .../frontend/src/components/form/suspense.vue | 141 +- .../src/components/global/MkA.stories.impl.ts | 11 +- .../frontend/src/components/global/MkA.vue | 29 +- .../components/global/MkAcct.stories.impl.ts | 37 + .../frontend/src/components/global/MkAcct.vue | 20 +- .../components/global/MkAd.stories.impl.ts | 88 +- .../frontend/src/components/global/MkAd.vue | 29 +- .../global/MkAvatar.stories.impl.ts | 5 + .../src/components/global/MkAvatar.vue | 44 +- .../global/MkCondensedLine.stories.impl.ts | 44 + .../src/components/global/MkCondensedLine.vue | 77 + .../global/MkCustomEmoji.stories.impl.ts | 5 + .../src/components/global/MkCustomEmoji.vue | 15 +- .../global/MkEllipsis.stories.impl.ts | 5 + .../src/components/global/MkEllipsis.vue | 5 + .../components/global/MkEmoji.stories.impl.ts | 5 + .../src/components/global/MkEmoji.vue | 11 +- .../components/global/MkError.stories.impl.ts | 5 + .../components/global/MkError.stories.meta.ts | 5 + .../src/components/global/MkError.vue | 12 +- .../global/MkLoading.stories.impl.ts | 5 + .../src/components/global/MkLoading.vue | 5 + .../MkMisskeyFlavoredMarkdown.stories.impl.ts | 7 +- .../global/MkMisskeyFlavoredMarkdown.ts | 377 + .../global/MkMisskeyFlavoredMarkdown.vue | 171 - .../global/MkPageHeader.stories.impl.ts | 5 + .../global/MkPageHeader.tabs.stories.impl.ts | 5 + .../components/global/MkPageHeader.tabs.vue | 11 +- .../src/components/global/MkPageHeader.vue | 17 +- .../src/components/global/MkSpacer.vue | 7 +- .../global/MkStickyContainer.stories.impl.ts | 5 + .../components/global/MkStickyContainer.vue | 14 +- .../components/global/MkTime.stories.impl.ts | 9 +- .../frontend/src/components/global/MkTime.vue | 35 +- .../components/global/MkUrl.stories.impl.ts | 5 + .../frontend/src/components/global/MkUrl.vue | 15 +- .../global/MkUserName.stories.impl.ts | 5 + .../src/components/global/MkUserName.vue | 11 +- .../global/RouterView.stories.impl.ts | 5 + .../src/components/global/RouterView.vue | 7 +- .../frontend/src/components/global/i18n.ts | 61 +- packages/frontend/src/components/index.ts | 10 +- packages/frontend/src/components/mfm.ts | 390 - .../src/components/page/block.type.ts | 34 + .../src/components/page/page.block.vue | 60 +- .../src/components/page/page.button.vue | 66 - .../src/components/page/page.canvas.vue | 48 - .../src/components/page/page.counter.vue | 51 - .../frontend/src/components/page/page.if.vue | 31 - .../src/components/page/page.image.vue | 35 +- .../src/components/page/page.note.vue | 55 +- .../src/components/page/page.number-input.vue | 54 - .../src/components/page/page.post.vue | 111 - .../src/components/page/page.radio-button.vue | 44 - .../src/components/page/page.section.vue | 89 +- .../src/components/page/page.switch.vue | 54 - .../src/components/page/page.text-input.vue | 54 - .../src/components/page/page.text.vue | 83 +- .../components/page/page.textarea-input.vue | 45 - .../src/components/page/page.textarea.vue | 39 - .../frontend/src/components/page/page.vue | 64 +- packages/frontend/src/config.ts | 7 +- packages/frontend/src/const.ts | 18 + packages/frontend/src/custom-emojis.ts | 32 +- packages/frontend/src/debug.ts | 5 + .../frontend/src/directives/adaptive-bg.ts | 7 +- .../src/directives/adaptive-border.ts | 7 +- packages/frontend/src/directives/anim.ts | 5 + packages/frontend/src/directives/appear.ts | 5 + .../frontend/src/directives/click-anime.ts | 7 +- packages/frontend/src/directives/container.ts | 21 - .../frontend/src/directives/follow-append.ts | 7 +- packages/frontend/src/directives/get-size.ts | 5 + packages/frontend/src/directives/hotkey.ts | 5 + packages/frontend/src/directives/index.ts | 7 +- packages/frontend/src/directives/panel.ts | 7 +- packages/frontend/src/directives/ripple.ts | 7 +- packages/frontend/src/directives/tooltip.ts | 25 +- .../frontend/src/directives/user-preview.ts | 7 +- packages/frontend/src/emojilist.json | 3565 ++-- packages/frontend/src/events.ts | 5 + packages/frontend/src/filters/bytes.ts | 5 + packages/frontend/src/filters/date.ts | 9 +- packages/frontend/src/filters/note.ts | 5 + packages/frontend/src/filters/number.ts | 7 +- packages/frontend/src/filters/user.ts | 15 +- packages/frontend/src/i18n.ts | 12 +- packages/frontend/src/init.ts | 513 - packages/frontend/src/instance.ts | 18 +- packages/frontend/src/local-storage.ts | 17 +- packages/frontend/src/navbar.ts | 21 +- packages/frontend/src/nirax.ts | 63 +- packages/frontend/src/os.ts | 34 +- packages/frontend/src/pages/_empty_.vue | 5 + packages/frontend/src/pages/_error_.vue | 78 +- packages/frontend/src/pages/_loading_.vue | 5 + packages/frontend/src/pages/about-misskey.vue | 92 +- packages/frontend/src/pages/about.emojis.vue | 67 +- .../frontend/src/pages/about.federation.vue | 37 +- packages/frontend/src/pages/about.vue | 115 +- packages/frontend/src/pages/achievements.vue | 15 +- packages/frontend/src/pages/admin-file.vue | 21 +- .../pages/{user-info.vue => admin-user.vue} | 267 +- .../src/pages/admin/RolesEditorFormula.vue | 25 +- .../frontend/src/pages/admin/_header_.vue | 11 +- packages/frontend/src/pages/admin/abuses.vue | 21 +- packages/frontend/src/pages/admin/ads.vue | 48 +- .../src/pages/admin/announcements.vue | 120 +- .../src/pages/admin/bot-protection.vue | 11 +- .../frontend/src/pages/admin/branding.vue | 175 + .../frontend/src/pages/admin/database.vue | 17 +- .../src/pages/admin/email-settings.vue | 25 +- .../frontend/src/pages/admin/federation.vue | 36 +- packages/frontend/src/pages/admin/files.vue | 61 +- packages/frontend/src/pages/admin/index.vue | 37 +- .../src/pages/admin/instance-block.vue | 15 +- packages/frontend/src/pages/admin/invites.vue | 131 + .../frontend/src/pages/admin/moderation.vue | 62 +- .../src/pages/admin/object-storage.vue | 19 +- .../src/pages/admin/other-settings.vue | 64 +- .../src/pages/admin/overview.active-users.vue | 15 +- .../src/pages/admin/overview.ap-requests.vue | 19 +- .../src/pages/admin/overview.federation.vue | 15 +- .../src/pages/admin/overview.heatmap.vue | 5 + .../src/pages/admin/overview.instances.vue | 35 +- .../src/pages/admin/overview.moderators.vue | 11 +- .../frontend/src/pages/admin/overview.pie.vue | 13 +- .../src/pages/admin/overview.queue.chart.vue | 19 +- .../src/pages/admin/overview.queue.vue | 15 +- .../src/pages/admin/overview.retention.vue | 5 + .../src/pages/admin/overview.stats.vue | 15 +- .../src/pages/admin/overview.users.vue | 13 +- .../frontend/src/pages/admin/overview.vue | 27 +- .../src/pages/admin/proxy-account.vue | 15 +- .../src/pages/admin/queue.chart.chart.vue | 19 +- .../frontend/src/pages/admin/queue.chart.vue | 104 +- packages/frontend/src/pages/admin/queue.vue | 15 +- packages/frontend/src/pages/admin/relays.vue | 43 +- .../frontend/src/pages/admin/roles.edit.vue | 18 +- .../frontend/src/pages/admin/roles.editor.vue | 144 +- .../frontend/src/pages/admin/roles.role.vue | 26 +- packages/frontend/src/pages/admin/roles.vue | 54 +- .../frontend/src/pages/admin/security.vue | 21 +- .../frontend/src/pages/admin/server-rules.vue | 133 + .../frontend/src/pages/admin/settings.vue | 130 +- packages/frontend/src/pages/admin/users.vue | 25 +- packages/frontend/src/pages/ads.vue | 15 +- packages/frontend/src/pages/announcements.vue | 160 +- .../frontend/src/pages/antenna-timeline.vue | 87 +- packages/frontend/src/pages/api-console.vue | 13 +- packages/frontend/src/pages/auth.form.vue | 14 +- packages/frontend/src/pages/auth.vue | 19 +- .../frontend/src/pages/channel-editor.vue | 61 +- packages/frontend/src/pages/channel.vue | 99 +- packages/frontend/src/pages/channels.vue | 17 +- packages/frontend/src/pages/clicker.vue | 9 +- packages/frontend/src/pages/clip.vue | 74 +- .../src/pages/custom-emojis-manager.vue | 55 +- packages/frontend/src/pages/drive.vue | 9 +- .../frontend/src/pages/emoji-edit-dialog.vue | 249 +- packages/frontend/src/pages/emojis.emoji.vue | 65 +- .../frontend/src/pages/explore.featured.vue | 9 +- packages/frontend/src/pages/explore.roles.vue | 11 +- packages/frontend/src/pages/explore.users.vue | 37 +- packages/frontend/src/pages/explore.vue | 9 +- packages/frontend/src/pages/favorites.vue | 16 +- .../frontend/src/pages/flash/flash-edit.vue | 46 +- .../frontend/src/pages/flash/flash-index.vue | 45 +- packages/frontend/src/pages/flash/flash.vue | 29 +- .../frontend/src/pages/follow-requests.vue | 18 +- packages/frontend/src/pages/follow.vue | 19 +- packages/frontend/src/pages/gallery/edit.vue | 29 +- packages/frontend/src/pages/gallery/index.vue | 35 +- packages/frontend/src/pages/gallery/post.vue | 22 +- packages/frontend/src/pages/instance-info.vue | 56 +- packages/frontend/src/pages/invite.vue | 119 + packages/frontend/src/pages/list.vue | 154 + packages/frontend/src/pages/miauth.vue | 15 +- .../frontend/src/pages/my-antennas/create.vue | 23 +- .../frontend/src/pages/my-antennas/edit.vue | 23 +- .../frontend/src/pages/my-antennas/editor.vue | 31 +- .../frontend/src/pages/my-antennas/index.vue | 103 +- .../frontend/src/pages/my-clips/index.vue | 13 +- .../frontend/src/pages/my-lists/index.vue | 107 +- packages/frontend/src/pages/my-lists/list.vue | 157 +- packages/frontend/src/pages/not-found.vue | 23 +- packages/frontend/src/pages/note.vue | 124 +- packages/frontend/src/pages/notifications.vue | 15 +- packages/frontend/src/pages/oauth.vue | 72 + .../page-editor/els/page-editor.el.image.vue | 27 +- .../page-editor/els/page-editor.el.note.vue | 9 +- .../els/page-editor.el.section.vue | 11 +- .../page-editor/els/page-editor.el.text.vue | 45 +- .../pages/page-editor/page-editor.blocks.vue | 87 +- .../page-editor/page-editor.container.vue | 93 +- .../src/pages/page-editor/page-editor.vue | 21 +- packages/frontend/src/pages/page.vue | 57 +- packages/frontend/src/pages/pages.vue | 49 +- packages/frontend/src/pages/preview.vue | 27 - packages/frontend/src/pages/registry.keys.vue | 18 +- .../frontend/src/pages/registry.value.vue | 18 +- packages/frontend/src/pages/registry.vue | 16 +- .../frontend/src/pages/reset-password.vue | 21 +- packages/frontend/src/pages/role.vue | 56 +- packages/frontend/src/pages/scratchpad.vue | 51 +- packages/frontend/src/pages/search.note.vue | 111 + packages/frontend/src/pages/search.user.vue | 82 + packages/frontend/src/pages/search.vue | 128 +- .../src/pages/settings/2fa.qrdialog.vue | 185 +- packages/frontend/src/pages/settings/2fa.vue | 108 +- .../src/pages/settings/account-info.vue | 158 - .../frontend/src/pages/settings/accounts.vue | 15 +- packages/frontend/src/pages/settings/api.vue | 11 +- packages/frontend/src/pages/settings/apps.vue | 60 +- .../src/pages/settings/custom-css.vue | 17 +- packages/frontend/src/pages/settings/deck.vue | 14 +- .../src/pages/settings/delete-account.vue | 52 - .../src/pages/settings/drive-cleaner.vue | 19 +- .../frontend/src/pages/settings/drive.vue | 60 +- .../frontend/src/pages/settings/email.vue | 19 +- .../frontend/src/pages/settings/general.vue | 254 +- .../src/pages/settings/import-export.vue | 46 +- .../frontend/src/pages/settings/index.vue | 31 +- .../src/pages/settings/instance-mute.vue | 13 +- .../frontend/src/pages/settings/migration.vue | 127 +- .../src/pages/settings/mute-block.vue | 30 +- .../frontend/src/pages/settings/navbar.vue | 23 +- .../src/pages/settings/notifications.vue | 24 +- .../frontend/src/pages/settings/other.vue | 130 +- .../src/pages/settings/plugin.install.vue | 26 +- .../frontend/src/pages/settings/plugin.vue | 21 +- .../pages/settings/preferences-backups.vue | 96 +- .../frontend/src/pages/settings/privacy.vue | 43 +- .../frontend/src/pages/settings/profile.vue | 194 +- .../frontend/src/pages/settings/reaction.vue | 43 +- .../frontend/src/pages/settings/roles.vue | 19 +- .../frontend/src/pages/settings/security.vue | 15 +- .../src/pages/settings/sounds.sound.vue | 11 +- .../frontend/src/pages/settings/sounds.vue | 13 +- .../pages/settings/statusbar.statusbar.vue | 21 +- .../frontend/src/pages/settings/statusbar.vue | 15 +- .../src/pages/settings/theme.install.vue | 13 +- .../src/pages/settings/theme.manage.vue | 21 +- .../frontend/src/pages/settings/theme.vue | 23 +- .../src/pages/settings/webhook.edit.vue | 13 +- .../src/pages/settings/webhook.new.vue | 11 +- .../frontend/src/pages/settings/webhook.vue | 9 +- .../frontend/src/pages/settings/word-mute.vue | 17 +- packages/frontend/src/pages/share.vue | 50 +- .../frontend/src/pages/signup-complete.vue | 92 +- packages/frontend/src/pages/tag.vue | 48 +- packages/frontend/src/pages/theme-editor.vue | 33 +- .../frontend/src/pages/timeline.tutorial.vue | 78 +- packages/frontend/src/pages/timeline.vue | 66 +- .../frontend/src/pages/user-list-timeline.vue | 87 +- packages/frontend/src/pages/user-tag.vue | 11 +- .../frontend/src/pages/user/achievements.vue | 17 +- .../src/pages/user/activity.following.vue | 21 +- .../src/pages/user/activity.heatmap.vue | 19 +- .../src/pages/user/activity.notes.vue | 21 +- .../frontend/src/pages/user/activity.pv.vue | 21 +- packages/frontend/src/pages/user/activity.vue | 15 +- packages/frontend/src/pages/user/clips.vue | 41 +- packages/frontend/src/pages/user/flashs.vue | 31 + .../frontend/src/pages/user/follow-list.vue | 27 +- .../frontend/src/pages/user/followers.vue | 23 +- .../frontend/src/pages/user/following.vue | 23 +- packages/frontend/src/pages/user/gallery.vue | 17 +- .../src/pages/user/home.stories.impl.ts | 5 + packages/frontend/src/pages/user/home.vue | 105 +- .../src/pages/user/index.activity.vue | 15 +- .../frontend/src/pages/user/index.photos.vue | 25 +- .../src/pages/user/index.timeline.vue | 15 +- packages/frontend/src/pages/user/index.vue | 71 +- packages/frontend/src/pages/user/lists.vue | 56 + packages/frontend/src/pages/user/pages.vue | 15 +- .../frontend/src/pages/user/reactions.vue | 67 +- .../frontend/src/pages/welcome.entrance.a.vue | 185 +- .../frontend/src/pages/welcome.entrance.b.vue | 239 - .../frontend/src/pages/welcome.entrance.c.vue | 308 - packages/frontend/src/pages/welcome.setup.vue | 113 +- .../frontend/src/pages/welcome.timeline.vue | 23 +- packages/frontend/src/pages/welcome.vue | 11 +- packages/frontend/src/pizzax.ts | 33 +- packages/frontend/src/plugin.ts | 11 +- packages/frontend/src/router.ts | 85 +- packages/frontend/src/scripts/2fa.ts | 33 - packages/frontend/src/scripts/achievements.ts | 22 +- packages/frontend/src/scripts/aiscript/api.ts | 29 +- packages/frontend/src/scripts/aiscript/ui.ts | 65 +- packages/frontend/src/scripts/api.ts | 9 +- packages/frontend/src/scripts/array.ts | 12 +- packages/frontend/src/scripts/autocomplete.ts | 33 +- packages/frontend/src/scripts/cache.ts | 63 +- packages/frontend/src/scripts/chart-legend.ts | 5 + packages/frontend/src/scripts/chart-vline.ts | 5 + .../frontend/src/scripts/check-word-mute.ts | 5 + packages/frontend/src/scripts/clicker-game.ts | 7 +- packages/frontend/src/scripts/clone.ts | 7 + packages/frontend/src/scripts/collapsed.ts | 24 + .../frontend/src/scripts/collect-page-vars.ts | 5 + packages/frontend/src/scripts/color.ts | 5 + packages/frontend/src/scripts/confetti.ts | 7 +- packages/frontend/src/scripts/contains.ts | 5 + .../frontend/src/scripts/copy-to-clipboard.ts | 5 + packages/frontend/src/scripts/device-kind.ts | 7 +- packages/frontend/src/scripts/emoji-base.ts | 9 +- packages/frontend/src/scripts/emojilist.ts | 23 +- .../extract-avg-color-from-blurhash.ts | 13 +- .../frontend/src/scripts/extract-mentions.ts | 5 + .../src/scripts/extract-url-from-mfm.ts | 7 +- packages/frontend/src/scripts/focus.ts | 5 + packages/frontend/src/scripts/form.ts | 8 +- .../src/scripts/format-time-string.ts | 5 + .../frontend/src/scripts/gen-search-query.ts | 13 +- .../src/scripts/get-account-from-id.ts | 7 +- .../src/scripts/get-drive-file-menu.ts | 54 +- .../frontend/src/scripts/get-embed-code.ts | 2 +- .../frontend/src/scripts/get-note-menu.ts | 124 +- .../frontend/src/scripts/get-note-summary.ts | 11 +- .../frontend/src/scripts/get-user-menu.ts | 135 +- .../frontend/src/scripts/get-user-name.ts | 5 + packages/frontend/src/scripts/hotkey.ts | 7 +- packages/frontend/src/scripts/hpml/block.ts | 109 - .../frontend/src/scripts/hpml/evaluator.ts | 171 - packages/frontend/src/scripts/hpml/expr.ts | 79 - packages/frontend/src/scripts/hpml/index.ts | 100 - packages/frontend/src/scripts/hpml/lib.ts | 245 - .../frontend/src/scripts/hpml/type-checker.ts | 182 - packages/frontend/src/scripts/i18n.ts | 5 + packages/frontend/src/scripts/idb-proxy.ts | 5 + packages/frontend/src/scripts/idle-render.ts | 60 + packages/frontend/src/scripts/init-chart.ts | 7 +- .../frontend/src/scripts/initialize-sw.ts | 9 +- packages/frontend/src/scripts/intl-const.ts | 19 +- .../src/scripts/is-device-darkmode.ts | 5 + .../frontend/src/scripts/isFfVisibleForMe.ts | 16 + packages/frontend/src/scripts/keycode.ts | 5 + packages/frontend/src/scripts/langmap.ts | 5 + packages/frontend/src/scripts/login-id.ts | 5 + packages/frontend/src/scripts/lookup-user.ts | 15 +- packages/frontend/src/scripts/lookup.ts | 20 +- packages/frontend/src/scripts/media-proxy.ts | 13 +- packages/frontend/src/scripts/mfm-tags.ts | 5 + .../frontend/src/scripts/page-metadata.ts | 11 +- packages/frontend/src/scripts/physics.ts | 5 + packages/frontend/src/scripts/please-login.ts | 13 +- packages/frontend/src/scripts/popout.ts | 9 +- .../frontend/src/scripts/popup-position.ts | 4 + .../frontend/src/scripts/reaction-picker.ts | 7 +- .../frontend/src/scripts/safe-uri-decode.ts | 5 + packages/frontend/src/scripts/scroll.ts | 9 +- packages/frontend/src/scripts/select-file.ts | 148 +- .../frontend/src/scripts/show-moved-dialog.ts | 21 + .../src/scripts/show-suspended-dialog.ts | 9 +- packages/frontend/src/scripts/shuffle.ts | 5 + packages/frontend/src/scripts/sound.ts | 29 +- .../frontend/src/scripts/sticky-sidebar.ts | 5 + packages/frontend/src/scripts/test-utils.ts | 5 +- packages/frontend/src/scripts/theme-editor.ts | 11 +- packages/frontend/src/scripts/theme.ts | 17 +- packages/frontend/src/scripts/time.ts | 24 +- packages/frontend/src/scripts/timezones.ts | 5 + packages/frontend/src/scripts/touch.ts | 7 +- .../frontend/src/scripts/unison-reload.ts | 5 + packages/frontend/src/scripts/upload.ts | 25 +- .../src/scripts/upload/compress-config.ts | 15 +- .../src/scripts/upload/isWebpSupported.ts | 15 + packages/frontend/src/scripts/url.ts | 7 +- .../frontend/src/scripts/use-chart-tooltip.ts | 7 +- .../src/scripts/use-document-visibility.ts | 5 + packages/frontend/src/scripts/use-interval.ts | 5 + .../frontend/src/scripts/use-leave-guard.ts | 5 + .../frontend/src/scripts/use-note-capture.ts | 19 +- packages/frontend/src/scripts/use-tooltip.ts | 5 + .../src/scripts/worker-multi-dispatch.ts | 80 + packages/frontend/src/store.ts | 69 +- packages/frontend/src/stream.ts | 32 +- packages/frontend/src/style.scss | 179 +- packages/frontend/src/theme-store.ts | 13 +- packages/frontend/src/themes/_dark.json5 | 3 +- packages/frontend/src/themes/_light.json5 | 3 +- packages/frontend/src/themes/d-astro.json5 | 1 + .../frontend/src/themes/d-botanical.json5 | 4 +- packages/frontend/src/themes/d-cherry.json5 | 1 + packages/frontend/src/themes/d-dark.json5 | 4 +- packages/frontend/src/themes/d-future.json5 | 4 +- .../frontend/src/themes/d-green-lime.json5 | 4 +- .../frontend/src/themes/d-green-orange.json5 | 4 +- packages/frontend/src/themes/d-ice.json5 | 1 + .../frontend/src/themes/d-persimmon.json5 | 1 + packages/frontend/src/themes/d-u0.json5 | 3 +- packages/frontend/src/themes/l-apricot.json5 | 1 + .../frontend/src/themes/l-botanical.json5 | 3 +- packages/frontend/src/themes/l-cherry.json5 | 1 + packages/frontend/src/themes/l-coffee.json5 | 1 + packages/frontend/src/themes/l-light.json5 | 1 + packages/frontend/src/themes/l-rainy.json5 | 1 + packages/frontend/src/themes/l-sushi.json5 | 1 + packages/frontend/src/themes/l-u0.json5 | 1 + packages/frontend/src/themes/l-vivid.json5 | 1 + .../frontend/src/types/date-separated-list.ts | 5 + packages/frontend/src/types/menu.ts | 7 +- .../src/ui/_common_/announcements.vue | 80 + packages/frontend/src/ui/_common_/common.ts | 42 +- packages/frontend/src/ui/_common_/common.vue | 159 +- .../src/ui/_common_/navbar-for-mobile.vue | 417 +- packages/frontend/src/ui/_common_/navbar.vue | 760 +- .../frontend/src/ui/_common_/notification.vue | 9 +- .../src/ui/_common_/statusbar-federation.vue | 86 +- .../src/ui/_common_/statusbar-rss.vue | 66 +- .../src/ui/_common_/statusbar-user-list.vue | 104 +- .../frontend/src/ui/_common_/statusbars.vue | 128 +- .../src/ui/_common_/stream-indicator.vue | 24 +- .../frontend/src/ui/_common_/sw-inject.ts | 15 +- packages/frontend/src/ui/_common_/upload.vue | 11 +- packages/frontend/src/ui/classic.header.vue | 27 +- packages/frontend/src/ui/classic.sidebar.vue | 33 +- packages/frontend/src/ui/classic.vue | 29 +- packages/frontend/src/ui/deck.vue | 179 +- .../frontend/src/ui/deck/antenna-column.vue | 41 +- .../frontend/src/ui/deck/channel-column.vue | 26 +- packages/frontend/src/ui/deck/column-core.vue | 38 - packages/frontend/src/ui/deck/column.vue | 134 +- packages/frontend/src/ui/deck/deck-store.ts | 15 +- .../frontend/src/ui/deck/direct-column.vue | 13 +- packages/frontend/src/ui/deck/list-column.vue | 39 +- packages/frontend/src/ui/deck/main-column.vue | 32 +- .../frontend/src/ui/deck/mentions-column.vue | 13 +- .../src/ui/deck/notifications-column.vue | 19 +- .../src/ui/deck/role-timeline-column.vue | 22 +- packages/frontend/src/ui/deck/tl-column.vue | 24 +- .../frontend/src/ui/deck/widgets-column.vue | 17 +- packages/frontend/src/ui/minimum.vue | 41 + packages/frontend/src/ui/universal.vue | 153 +- .../frontend/src/ui/universal.widgets.vue | 39 +- packages/frontend/src/ui/visitor.vue | 294 +- packages/frontend/src/ui/visitor/a.vue | 263 - packages/frontend/src/ui/visitor/b.vue | 266 - packages/frontend/src/ui/visitor/header.vue | 211 - packages/frontend/src/ui/visitor/kanban.vue | 261 - packages/frontend/src/ui/zen.vue | 61 +- .../src/unicode-emoji-indexes/en-US.json | 1784 ++ .../src/widgets/WidgetActivity.calendar.vue | 23 +- .../src/widgets/WidgetActivity.chart.vue | 23 +- .../frontend/src/widgets/WidgetActivity.vue | 24 +- .../frontend/src/widgets/WidgetAichan.vue | 24 +- .../frontend/src/widgets/WidgetAiscript.vue | 26 +- .../src/widgets/WidgetAiscriptApp.vue | 26 +- .../frontend/src/widgets/WidgetButton.vue | 27 +- .../frontend/src/widgets/WidgetCalendar.vue | 20 +- .../frontend/src/widgets/WidgetClicker.vue | 18 +- packages/frontend/src/widgets/WidgetClock.vue | 104 +- .../src/widgets/WidgetDigitalClock.vue | 20 +- .../frontend/src/widgets/WidgetFederation.vue | 28 +- .../src/widgets/WidgetInstanceCloud.vue | 28 +- .../src/widgets/WidgetInstanceInfo.vue | 20 +- .../frontend/src/widgets/WidgetJobQueue.vue | 28 +- packages/frontend/src/widgets/WidgetMemo.vue | 22 +- .../src/widgets/WidgetNotifications.vue | 24 +- .../src/widgets/WidgetOnlineUsers.vue | 52 +- .../frontend/src/widgets/WidgetPhotos.vue | 30 +- .../frontend/src/widgets/WidgetPostForm.vue | 16 +- .../frontend/src/widgets/WidgetProfile.vue | 20 +- packages/frontend/src/widgets/WidgetRss.vue | 27 +- .../frontend/src/widgets/WidgetRssTicker.vue | 26 +- .../frontend/src/widgets/WidgetSlideshow.vue | 22 +- .../frontend/src/widgets/WidgetTimeline.vue | 26 +- .../frontend/src/widgets/WidgetTrends.vue | 28 +- .../frontend/src/widgets/WidgetUnixClock.vue | 16 +- .../frontend/src/widgets/WidgetUserList.vue | 24 +- packages/frontend/src/widgets/index.ts | 5 + .../src/widgets/server-metric/cpu-mem.vue | 15 +- .../src/widgets/server-metric/cpu.vue | 5 + .../src/widgets/server-metric/disk.vue | 7 +- .../src/widgets/server-metric/index.vue | 19 +- .../src/widgets/server-metric/mem.vue | 7 +- .../src/widgets/server-metric/net.vue | 17 +- .../src/widgets/server-metric/pie.vue | 33 +- packages/frontend/src/widgets/widget.ts | 11 +- .../frontend/src/workers/draw-blurhash.ts | 22 + packages/frontend/src/workers/test-webgl2.ts | 14 + packages/frontend/src/workers/tsconfig.json | 5 + packages/frontend/test/home.test.ts | 9 +- packages/frontend/test/init.ts | 7 +- packages/frontend/test/note.test.ts | 9 +- packages/frontend/test/scroll.test.ts | 64 + packages/frontend/test/tsconfig.json | 10 +- packages/frontend/test/url-preview.test.ts | 25 +- packages/frontend/tsconfig.json | 19 +- packages/frontend/vite.config.ts | 23 +- packages/misskey-js/.swcrc | 2 +- packages/misskey-js/etc/misskey-js.api.md | 163 +- packages/misskey-js/package.json | 30 +- packages/misskey-js/src/acct.ts | 2 +- packages/misskey-js/src/api.types.ts | 42 +- packages/misskey-js/src/consts.ts | 4 + packages/misskey-js/src/entities.ts | 59 +- packages/misskey-js/src/index.ts | 3 +- packages/misskey-js/src/streaming.ts | 10 +- packages/misskey-js/test-d/streaming.ts | 2 +- packages/misskey-js/test/api.ts | 8 +- packages/misskey-js/test/streaming.ts | 2 +- packages/misskey-js/tsconfig.json | 8 +- packages/shared/.eslintrc.js | 9 +- packages/sw/{.eslintrc.js => .eslintrc.cjs} | 0 packages/sw/build.js | 14 +- packages/sw/package.json | 15 +- packages/sw/src/@types/global.d.ts | 5 + .../sw/src/scripts/create-notification.ts | 22 +- .../sw/src/scripts/get-account-from-id.ts | 5 + packages/sw/src/scripts/get-user-name.ts | 5 + packages/sw/src/scripts/i18n.ts | 5 + packages/sw/src/scripts/lang.ts | 7 +- packages/sw/src/scripts/login-id.ts | 5 + packages/sw/src/scripts/operations.ts | 9 +- packages/sw/src/scripts/twemoji-base.ts | 5 + packages/sw/src/sw.ts | 34 +- packages/sw/src/types.ts | 5 + packages/sw/tsconfig.json | 12 +- pnpm-lock.yaml | 14363 ++++++++-------- scripts/build-assets.mjs | 91 + scripts/build-pre.js | 5 + scripts/clean-all.js | 5 + scripts/clean.js | 5 + scripts/dev.js | 59 - scripts/dev.mjs | 79 + 1886 files changed, 64355 insertions(+), 35080 deletions(-) delete mode 100644 .github/reviewer-lottery.yml delete mode 100644 .github/workflows/reviewer_lottery.yml delete mode 100644 .github/workflows/storybook.yml create mode 100644 .github/workflows/test-production.yml delete mode 100644 docs/DONATORS.md delete mode 100644 gulpfile.js create mode 100644 locales/generateDTS.js create mode 100644 locales/hu-HU.yml create mode 100644 locales/package.json create mode 100644 locales/uz-UZ.yml create mode 100644 packages/backend/assets/avatar.png create mode 100644 packages/backend/migration/1677054292210-ad4.js create mode 100644 packages/backend/migration/1681400427971-serverRules.js create mode 100644 packages/backend/migration/1681870960239-RoleTLSetting.js create mode 100644 packages/backend/migration/1682190963894-movedAt.js create mode 100644 packages/backend/migration/1682754135458-preservedUsernames.js create mode 100644 packages/backend/migration/1682985520254-channelColor.js create mode 100644 packages/backend/migration/1683328299359-channelArchive.js create mode 100644 packages/backend/migration/1683682889948-prevent-ai-larning.js create mode 100644 packages/backend/migration/1683683083083-public-reactions-default-true.js create mode 100644 packages/backend/migration/1683789676867-fix-typo.js create mode 100644 packages/backend/migration/1683847157541-UserList.js create mode 100644 packages/backend/migration/1683869758873-UserListFavorites.js create mode 100644 packages/backend/migration/1684206886988-remove-showTimelineReplies.js create mode 100644 packages/backend/migration/1684386446061-emoji-improve.js create mode 100644 packages/backend/migration/1685973839966-errorImageUrl.js create mode 100644 packages/backend/migration/1688280713783-add-meta-options.js create mode 100644 packages/backend/migration/1688720440658-refactor-invite-system.js create mode 100644 packages/backend/migration/1688880985544-add-index-to-relations.js create mode 100644 packages/backend/migration/1689102832143-nsfw-cache.js create mode 100644 packages/backend/migration/1690417561185-fix-renote-muting.js create mode 100644 packages/backend/migration/1690417561186-ChangeCacheRemoteFilesDefault.js create mode 100644 packages/backend/migration/1690417561187-Fix.js create mode 100644 packages/backend/migration/1690569881926-user-2fa-backup-codes.js create mode 100644 packages/backend/migration/1690782653311-SensitiveChannel.js create mode 100644 packages/backend/migration/1690796169261-play-visibility.js create mode 100644 packages/backend/migration/1691649257651-refine-announcement.js create mode 100644 packages/backend/migration/1691657412740-refine-announcement-2.js create mode 100644 packages/backend/migration/1691959191872-passkey-support.js create mode 100644 packages/backend/migration/1694850832075-server-icons-and-manifest.js create mode 100644 packages/backend/migration/1694915420864-clipped-count.js rename packages/backend/src/boot/{index.ts => entry.ts} (94%) create mode 100644 packages/backend/src/core/AnnouncementService.ts create mode 100644 packages/backend/src/core/SearchService.ts delete mode 100644 packages/backend/src/core/TwoFactorAuthenticationService.ts create mode 100644 packages/backend/src/core/WebAuthnService.ts create mode 100644 packages/backend/src/core/entities/InviteCodeEntityService.ts delete mode 100644 packages/backend/src/daemons/JanitorService.ts create mode 100644 packages/backend/src/misc/check-https.ts create mode 100644 packages/backend/src/misc/generate-invite-code.ts create mode 100644 packages/backend/src/misc/id/aidx.ts create mode 100644 packages/backend/src/models/_.ts delete mode 100644 packages/backend/src/models/entities/AttestationChallenge.ts create mode 100644 packages/backend/src/models/entities/UserListFavorite.ts delete mode 100644 packages/backend/src/models/index.ts create mode 100644 packages/backend/src/models/json-schema/announcement.ts create mode 100644 packages/backend/src/models/json-schema/invite-code.ts delete mode 100644 packages/backend/src/queue/DbQueueProcessorsService.ts delete mode 100644 packages/backend/src/queue/ObjectStorageQueueProcessorsService.ts delete mode 100644 packages/backend/src/queue/RelationshipQueueProcessorsService.ts delete mode 100644 packages/backend/src/queue/SystemQueueProcessorsService.ts create mode 100644 packages/backend/src/queue/const.ts delete mode 100644 packages/backend/src/queue/get-job-info.ts create mode 100644 packages/backend/src/queue/processors/ExportAntennasProcessorService.ts create mode 100644 packages/backend/src/queue/processors/ImportAntennasProcessorService.ts create mode 100644 packages/backend/src/server/api/endpoints/admin/emoji/set-license-bulk.ts create mode 100644 packages/backend/src/server/api/endpoints/admin/invite/create.ts create mode 100644 packages/backend/src/server/api/endpoints/admin/invite/list.ts create mode 100644 packages/backend/src/server/api/endpoints/i/export-antennas.ts create mode 100644 packages/backend/src/server/api/endpoints/i/import-antennas.ts delete mode 100644 packages/backend/src/server/api/endpoints/i/known-as.ts delete mode 100644 packages/backend/src/server/api/endpoints/invite.ts create mode 100644 packages/backend/src/server/api/endpoints/invite/create.ts create mode 100644 packages/backend/src/server/api/endpoints/invite/delete.ts create mode 100644 packages/backend/src/server/api/endpoints/invite/limit.ts create mode 100644 packages/backend/src/server/api/endpoints/invite/list.ts create mode 100644 packages/backend/src/server/api/endpoints/notifications/test-notification.ts create mode 100644 packages/backend/src/server/api/endpoints/users/flashs.ts create mode 100644 packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts create mode 100644 packages/backend/src/server/api/endpoints/users/lists/favorite.ts create mode 100644 packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts delete mode 100644 packages/backend/src/server/api/endpoints/users/stats.ts rename packages/backend/src/server/api/stream/{index.ts => Connection.ts} (88%) create mode 100644 packages/backend/src/server/oauth/OAuth2ProviderService.ts create mode 100644 packages/backend/src/server/web/views/oauth.pug create mode 100644 packages/backend/test/e2e/antennas.ts create mode 100644 packages/backend/test/e2e/move.ts create mode 100644 packages/backend/test/e2e/oauth.ts create mode 100644 packages/backend/test/resources/kick_gaba7.aac create mode 100644 packages/backend/test/resources/kick_gaba7.flac create mode 100644 packages/backend/test/resources/kick_gaba7.mp3 create mode 100644 packages/backend/test/resources/kick_gaba7.wav create mode 100644 packages/backend/test/resources/kick_gaba7.webm create mode 100644 packages/backend/test/unit/AnnouncementService.ts create mode 100644 packages/backend/test/unit/FetchInstanceMetadataService.ts create mode 100644 packages/backend/test/unit/misc/check-word-mute.ts create mode 100644 packages/backend/test/unit/misc/correct-filename.ts rename packages/frontend/{.eslintrc.js => .eslintrc.cjs} (91%) create mode 100644 packages/frontend/.storybook/package.json create mode 100644 packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.test.ts create mode 100644 packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.ts create mode 100644 packages/frontend/src/_boot_.ts create mode 100644 packages/frontend/src/boot/common.ts create mode 100644 packages/frontend/src/boot/main-boot.ts create mode 100644 packages/frontend/src/boot/sub-boot.ts create mode 100644 packages/frontend/src/components/MkAnimBg.vue create mode 100644 packages/frontend/src/components/MkAnnouncementDialog.stories.impl.ts create mode 100644 packages/frontend/src/components/MkAnnouncementDialog.vue delete mode 100644 packages/frontend/src/components/MkCheckbox.vue create mode 100644 packages/frontend/src/components/MkColorInput.vue create mode 100644 packages/frontend/src/components/MkDigitalClock.stories.impl.ts delete mode 100644 packages/frontend/src/components/MkImageViewer.vue create mode 100644 packages/frontend/src/components/MkInviteCode.stories.impl.ts create mode 100644 packages/frontend/src/components/MkInviteCode.vue delete mode 100644 packages/frontend/src/components/MkModalPageWindow.vue delete mode 100644 packages/frontend/src/components/MkReactedUsersDialog.vue create mode 100644 packages/frontend/src/components/MkRetentionLineChart.vue delete mode 100644 packages/frontend/src/components/MkSample.vue delete mode 100644 packages/frontend/src/components/MkSignup.vue create mode 100644 packages/frontend/src/components/MkSignupDialog.form.vue create mode 100644 packages/frontend/src/components/MkSignupDialog.rules.stories.impl.ts create mode 100644 packages/frontend/src/components/MkSignupDialog.rules.vue create mode 100644 packages/frontend/src/components/MkSwitch.button.vue create mode 100644 packages/frontend/src/components/MkUserAnnouncementEditDialog.vue create mode 100644 packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts create mode 100644 packages/frontend/src/components/MkUserSetupDialog.Follow.vue create mode 100644 packages/frontend/src/components/MkUserSetupDialog.Privacy.stories.impl.ts create mode 100644 packages/frontend/src/components/MkUserSetupDialog.Privacy.vue create mode 100644 packages/frontend/src/components/MkUserSetupDialog.Profile.stories.impl.ts create mode 100644 packages/frontend/src/components/MkUserSetupDialog.Profile.vue create mode 100644 packages/frontend/src/components/MkUserSetupDialog.User.stories.impl.ts create mode 100644 packages/frontend/src/components/MkUserSetupDialog.User.vue create mode 100644 packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts create mode 100644 packages/frontend/src/components/MkUserSetupDialog.vue create mode 100644 packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue create mode 100644 packages/frontend/src/components/MkVisitorDashboard.vue create mode 100644 packages/frontend/src/components/global/MkCondensedLine.stories.impl.ts create mode 100644 packages/frontend/src/components/global/MkCondensedLine.vue create mode 100644 packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts delete mode 100644 packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.vue delete mode 100644 packages/frontend/src/components/mfm.ts create mode 100644 packages/frontend/src/components/page/block.type.ts delete mode 100644 packages/frontend/src/components/page/page.button.vue delete mode 100644 packages/frontend/src/components/page/page.canvas.vue delete mode 100644 packages/frontend/src/components/page/page.counter.vue delete mode 100644 packages/frontend/src/components/page/page.if.vue delete mode 100644 packages/frontend/src/components/page/page.number-input.vue delete mode 100644 packages/frontend/src/components/page/page.post.vue delete mode 100644 packages/frontend/src/components/page/page.radio-button.vue delete mode 100644 packages/frontend/src/components/page/page.switch.vue delete mode 100644 packages/frontend/src/components/page/page.text-input.vue delete mode 100644 packages/frontend/src/components/page/page.textarea-input.vue delete mode 100644 packages/frontend/src/components/page/page.textarea.vue delete mode 100644 packages/frontend/src/directives/container.ts delete mode 100644 packages/frontend/src/init.ts rename packages/frontend/src/pages/{user-info.vue => admin-user.vue} (63%) create mode 100644 packages/frontend/src/pages/admin/branding.vue create mode 100644 packages/frontend/src/pages/admin/invites.vue create mode 100644 packages/frontend/src/pages/admin/server-rules.vue create mode 100644 packages/frontend/src/pages/invite.vue create mode 100644 packages/frontend/src/pages/list.vue create mode 100644 packages/frontend/src/pages/oauth.vue delete mode 100644 packages/frontend/src/pages/preview.vue create mode 100644 packages/frontend/src/pages/search.note.vue create mode 100644 packages/frontend/src/pages/search.user.vue delete mode 100644 packages/frontend/src/pages/settings/account-info.vue delete mode 100644 packages/frontend/src/pages/settings/delete-account.vue create mode 100644 packages/frontend/src/pages/user/flashs.vue create mode 100644 packages/frontend/src/pages/user/lists.vue delete mode 100644 packages/frontend/src/pages/welcome.entrance.b.vue delete mode 100644 packages/frontend/src/pages/welcome.entrance.c.vue delete mode 100644 packages/frontend/src/scripts/2fa.ts create mode 100644 packages/frontend/src/scripts/collapsed.ts delete mode 100644 packages/frontend/src/scripts/hpml/block.ts delete mode 100644 packages/frontend/src/scripts/hpml/evaluator.ts delete mode 100644 packages/frontend/src/scripts/hpml/expr.ts delete mode 100644 packages/frontend/src/scripts/hpml/index.ts delete mode 100644 packages/frontend/src/scripts/hpml/lib.ts delete mode 100644 packages/frontend/src/scripts/hpml/type-checker.ts create mode 100644 packages/frontend/src/scripts/idle-render.ts create mode 100644 packages/frontend/src/scripts/isFfVisibleForMe.ts create mode 100644 packages/frontend/src/scripts/show-moved-dialog.ts create mode 100644 packages/frontend/src/scripts/upload/isWebpSupported.ts create mode 100644 packages/frontend/src/scripts/worker-multi-dispatch.ts create mode 100644 packages/frontend/src/ui/_common_/announcements.vue delete mode 100644 packages/frontend/src/ui/deck/column-core.vue create mode 100644 packages/frontend/src/ui/minimum.vue delete mode 100644 packages/frontend/src/ui/visitor/a.vue delete mode 100644 packages/frontend/src/ui/visitor/b.vue delete mode 100644 packages/frontend/src/ui/visitor/header.vue delete mode 100644 packages/frontend/src/ui/visitor/kanban.vue create mode 100644 packages/frontend/src/unicode-emoji-indexes/en-US.json create mode 100644 packages/frontend/src/workers/draw-blurhash.ts create mode 100644 packages/frontend/src/workers/test-webgl2.ts create mode 100644 packages/frontend/src/workers/tsconfig.json create mode 100644 packages/frontend/test/scroll.test.ts rename packages/sw/{.eslintrc.js => .eslintrc.cjs} (100%) create mode 100644 scripts/build-assets.mjs delete mode 100644 scripts/dev.js create mode 100644 scripts/dev.mjs diff --git a/.config/docker_example.yml b/.config/docker_example.yml index af0a90dc9558..940b095fe29c 100644 --- a/.config/docker_example.yml +++ b/.config/docker_example.yml @@ -95,15 +95,15 @@ redis: # #prefix: example-prefix # #db: 1 -# ┌─────────────────────────────┐ -#───┘ Elasticsearch configuration └───────────────────────────── +# ┌───────────────────────────┐ +#───┘ MeiliSearch configuration └───────────────────────────── -#elasticsearch: -# host: localhost -# port: 9200 -# ssl: false -# user: -# pass: +#meilisearch: +# host: meilisearch +# port: 7700 +# apiKey: '' +# ssl: true +# index: '' # ┌───────────────┐ #───┘ ID generation └─────────────────────────────────────────── @@ -114,6 +114,7 @@ redis: # Available methods: # aid ... Short, Millisecond accuracy +# aidx ... Millisecond accuracy # meid ... Similar to ObjectID, Millisecond accuracy # ulid ... Millisecond accuracy # objectid ... This is left for backward compatibility @@ -121,7 +122,7 @@ redis: # ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE # ID SETTINGS AFTER THAT! -id: 'aid' +id: 'aidx' # ┌─────────────────────┐ #───┘ Other configuration └───────────────────────────────────── @@ -165,8 +166,8 @@ proxyBypassHosts: # Media Proxy #mediaProxy: https://example.com/proxy -# Proxy remote files (default: false) -#proxyRemoteFiles: true +# Proxy remote files (default: true) +proxyRemoteFiles: true # Sign to ActivityPub GET request (default: true) signToActivityPubGet: true diff --git a/.config/example.yml b/.config/example.yml index 57e2b56b78c5..086a6ca8fc99 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -30,6 +30,10 @@ url: https://example.tld/ # The port that your Misskey server should listen on. port: 3000 +# You can also use UNIX domain socket. +# socket: /path/to/misskey.sock +# chmodSocket: '777' + # ┌──────────────────────────┐ #───┘ PostgreSQL configuration └──────────────────────────────── @@ -78,6 +82,8 @@ redis: #pass: example-pass #prefix: example-prefix #db: 1 + # You can specify more ioredis options... + #username: example-username #redisForPubsub: # host: localhost @@ -86,6 +92,8 @@ redis: # #pass: example-pass # #prefix: example-prefix # #db: 1 +# # You can specify more ioredis options... +# #username: example-username #redisForJobQueue: # host: localhost @@ -94,16 +102,19 @@ redis: # #pass: example-pass # #prefix: example-prefix # #db: 1 +# # You can specify more ioredis options... +# #username: example-username -# ┌─────────────────────────────┐ -#───┘ Elasticsearch configuration └───────────────────────────── +# ┌───────────────────────────┐ +#───┘ MeiliSearch configuration └───────────────────────────── -#elasticsearch: +#meilisearch: # host: localhost -# port: 9200 -# ssl: false -# user: -# pass: +# port: 7700 +# apiKey: '' +# ssl: true +# index: '' +# scope: local # ┌───────────────┐ #───┘ ID generation └─────────────────────────────────────────── @@ -114,6 +125,7 @@ redis: # Available methods: # aid ... Short, Millisecond accuracy +# aidx ... Millisecond accuracy # meid ... Similar to ObjectID, Millisecond accuracy # ulid ... Millisecond accuracy # objectid ... This is left for backward compatibility @@ -121,7 +133,7 @@ redis: # ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE # ID SETTINGS AFTER THAT! -id: 'aid' +id: 'aidx' # ┌─────────────────────┐ #───┘ Other configuration └───────────────────────────────────── @@ -133,16 +145,23 @@ id: 'aid' #clusterLimit: 1 # Job concurrency per worker -# deliverJobConcurrency: 128 -# inboxJobConcurrency: 16 +#deliverJobConcurrency: 128 +#inboxJobConcurrency: 16 +#relashionshipJobConcurrency: 16 +# What's relashionshipJob?: +# Follow, unfollow, block and unblock(ings) while following-imports, etc. or account migrations. # Job rate limiter -# deliverJobPerSec: 128 -# inboxJobPerSec: 16 +#deliverJobPerSec: 128 +#inboxJobPerSec: 16 +#relashionshipJobPerSec: 64 # Job attempts -# deliverJobMaxAttempts: 12 -# inboxJobMaxAttempts: 8 +#deliverJobMaxAttempts: 12 +#inboxJobMaxAttempts: 8 + +# Local address used for outgoing requests +#outgoingAddress: 127.0.0.1 # IP address family used for outgoing request (ipv4, ipv6 or dual) #outgoingAddressFamily: ipv4 @@ -168,9 +187,9 @@ proxyBypassHosts: # * Perform image compression (on a different server resource than the main process) #mediaProxy: https://example.com/proxy -# Proxy remote files (default: false) +# Proxy remote files (default: true) # Proxy remote files by this instance or mediaProxy to prevent remote files from running in remote domains. -#proxyRemoteFiles: true +proxyRemoteFiles: true # Movie Thumbnail Generation URL # There is no reference implementation. diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index a47804ab07a0..861b0008a047 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -6,7 +6,7 @@ "features": { "ghcr.io/devcontainers-contrib/features/pnpm:2": {}, "ghcr.io/devcontainers/features/node:1": { - "version": "18.16.0" + "version": "20.5.1" } }, "forwardPorts": [3000], diff --git a/.devcontainer/devcontainer.yml b/.devcontainer/devcontainer.yml index 2af306e3dac8..5dcd41599acd 100644 --- a/.devcontainer/devcontainer.yml +++ b/.devcontainer/devcontainer.yml @@ -95,15 +95,15 @@ redis: # #prefix: example-prefix # #db: 1 -# ┌─────────────────────────────┐ -#───┘ Elasticsearch configuration └───────────────────────────── +# ┌───────────────────────────┐ +#───┘ MeiliSearch configuration └───────────────────────────── -#elasticsearch: -# host: localhost -# port: 9200 -# ssl: false -# user: -# pass: +#meilisearch: +# host: meilisearch +# port: 7700 +# apiKey: '' +# ssl: true +# index: '' # ┌───────────────┐ #───┘ ID generation └─────────────────────────────────────────── @@ -114,6 +114,7 @@ redis: # Available methods: # aid ... Short, Millisecond accuracy +# aidx ... Millisecond accuracy # meid ... Similar to ObjectID, Millisecond accuracy # ulid ... Millisecond accuracy # objectid ... This is left for backward compatibility @@ -121,7 +122,7 @@ redis: # ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE # ID SETTINGS AFTER THAT! -id: 'aid' +id: 'aidx' # ┌─────────────────────┐ #───┘ Other configuration └───────────────────────────────────── @@ -165,8 +166,8 @@ proxyBypassHosts: # Media Proxy #mediaProxy: https://example.com/proxy -# Proxy remote files (default: false) -#proxyRemoteFiles: true +# Proxy remote files (default: true) +proxyRemoteFiles: true # Sign to ActivityPub GET request (default: true) signToActivityPubGet: true diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 8f8c5a13ab72..2809cd2ca469 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -2,7 +2,7 @@ version: '3.8' services: app: - build: + build: context: . dockerfile: Dockerfile diff --git a/.dockerignore b/.dockerignore index 151ede038eb3..1de0c7982bcd 100644 --- a/.dockerignore +++ b/.dockerignore @@ -8,7 +8,6 @@ build/ built/ db/ docker-compose.yml -elasticsearch/ node_modules/ packages/*/node_modules redis/ diff --git a/.editorconfig b/.editorconfig index a6f988f8d7c7..def7baa1a8f1 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,6 +6,10 @@ indent_size = 2 charset = utf-8 insert_final_newline = true end_of_line = lf +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false [*.{yml,yaml}] indent_style = space diff --git a/.github/ISSUE_TEMPLATE/01_bug-report.md b/.github/ISSUE_TEMPLATE/01_bug-report.md index b4a2f698793d..b889d96eb39c 100644 --- a/.github/ISSUE_TEMPLATE/01_bug-report.md +++ b/.github/ISSUE_TEMPLATE/01_bug-report.md @@ -37,7 +37,24 @@ Please include errors from the developer console and/or server log files if you ## 📌 Environment - -Misskey version: -Your OS: -Your browser: + + +### 💻 Frontend +* Model and OS of the device(s): + +* Browser: + +* Server URL: + +* Misskey: + 13.x.x + +### 🛰 Backend (for server admin) + + +* Installation Method or Hosting Service: +* Misskey: 13.x.x +* Node: 20.x.x +* PostgreSQL: 15.x.x +* Redis: 7.x.x +* OS and Architecture: diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 730647b08634..e8b65dc3b9fc 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,7 +1,4 @@ contact_links: - - name: 👪 Misskey Forum - url: https://forum.misskey.io/ - about: Ask questions and share knowledge - name: 💬 Misskey official Discord url: https://discord.gg/Wp8gVStHW3 about: Chat freely about Misskey diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e878e5836ad0..5955f6b5d9ac 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -9,24 +9,24 @@ updates: directory: "/" schedule: interval: daily - open-pull-requests-limit: 0 + open-pull-requests-limit: 100 + +# Add only the root, not each workspace item +# https://github.com/dependabot/dependabot-core/issues/4993#issuecomment-1289133027 - package-ecosystem: npm directory: "/" schedule: interval: daily + # PNPM has an issue with dependabot. See: + # https://github.com/dependabot/dependabot-core/issues/7258 + # https://github.com/pnpm/pnpm/issues/6530 + # TODO: Restore this when the issue is solved open-pull-requests-limit: 0 -- package-ecosystem: npm - directory: "/packages/backend" - schedule: - interval: daily - open-pull-requests-limit: 0 -- package-ecosystem: npm - directory: "/packages/frontend" - schedule: - interval: daily - open-pull-requests-limit: 0 -- package-ecosystem: npm - directory: "/packages/sw" - schedule: - interval: daily - open-pull-requests-limit: 0 + groups: + swc: + patterns: + - "@swc/*" + storybook: + patterns: + - "storybook*" + - "@storybook/*" diff --git a/.github/labeler.yml b/.github/labeler.yml index b4fd0dd5dffa..137be487c026 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,12 +1,21 @@ -'⚙️Server': +'packages/backend': - packages/backend/**/* -'🖥️Client': +'packages/backend:test': +- packages/backend/test/**/* + +'packages/frontend': - packages/frontend/**/* -'🧪Test': +'packages/frontend:test': - cypress/**/* -- packages/backend/test/**/* -'‼️ wrong locales': -- any: ['locales/*.yml', '!locales/ja-JP.yml'] +'packages/sw': +- packages/sw/**/* + +'packages/misskey-js': +- packages/misskey-js/**/* + +'packages/misskey-js:test': +- packages/misskey-js/test/**/* +- packages/misskey-js/test-d/**/* diff --git a/.github/misskey/test.yml b/.github/misskey/test.yml index f43f74be149a..7a4aa4ae6cce 100644 --- a/.github/misskey/test.yml +++ b/.github/misskey/test.yml @@ -12,4 +12,4 @@ db: redis: host: 127.0.0.1 port: 56312 -id: aid +id: aidx diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 0739fee70920..e78b82c47ccb 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -19,5 +19,6 @@ https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md ## Checklist - [ ] Read the [contribution guide](https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md) - [ ] Test working in a local environment +- [ ] (If needed) Add story of storybook - [ ] (If needed) Update CHANGELOG.md - [ ] (If possible) Add tests diff --git a/.github/reviewer-lottery.yml b/.github/reviewer-lottery.yml deleted file mode 100644 index fd2fb1913ffa..000000000000 --- a/.github/reviewer-lottery.yml +++ /dev/null @@ -1,10 +0,0 @@ -groups: - - name: devs - reviewers: 2 - internal_reviewers: 1 - usernames: - - syuilo - - acid-chicken - - EbiseLutica - - rinsuki - - tamaina diff --git a/.github/workflows/api-misskey-js.yml b/.github/workflows/api-misskey-js.yml index ed004c78dc18..4cf523a6b913 100644 --- a/.github/workflows/api-misskey-js.yml +++ b/.github/workflows/api-misskey-js.yml @@ -9,12 +9,12 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3.3.0 + uses: actions/checkout@v4.0.0 - run: corepack enable - name: Setup Node.js - uses: actions/setup-node@v3.6.0 + uses: actions/setup-node@v3.8.1 with: node-version-file: '.node-version' cache: 'pnpm' diff --git a/.github/workflows/check_copyright_year.yml b/.github/workflows/check_copyright_year.yml index 8daea44a8347..313265f6718e 100644 --- a/.github/workflows/check_copyright_year.yml +++ b/.github/workflows/check_copyright_year.yml @@ -10,7 +10,7 @@ jobs: check_copyright_year: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3.2.0 + - uses: actions/checkout@v4.0.0 - run: | if [ "$(grep Copyright COPYING | sed -e 's/.*2014-\([0-9]*\) .*/\1/g')" -ne "$(date +%Y)" ]; then echo "Please change copyright year!" diff --git a/.github/workflows/docker-develop.yml b/.github/workflows/docker-develop.yml index 09a2c33e0c83..05bb7f77f9c5 100644 --- a/.github/workflows/docker-develop.yml +++ b/.github/workflows/docker-develop.yml @@ -13,24 +13,24 @@ jobs: if: github.repository == 'misskey-dev/misskey' steps: - name: Check out the repo - uses: actions/checkout@v3.3.0 + uses: actions/checkout@v4.0.0 - name: Set up Docker Buildx id: buildx - uses: docker/setup-buildx-action@v2.3.0 + uses: docker/setup-buildx-action@v3.0.0 with: platforms: linux/amd64,linux/arm64 - name: Docker meta id: meta - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: images: misskey/misskey - name: Log in to Docker Hub - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and Push to Docker Hub - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: builder: ${{ steps.buildx.outputs.name }} context: . diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index a465d92eafb1..32a98a416d64 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -12,15 +12,15 @@ jobs: steps: - name: Check out the repo - uses: actions/checkout@v3.3.0 + uses: actions/checkout@v4.0.0 - name: Set up Docker Buildx id: buildx - uses: docker/setup-buildx-action@v2.3.0 + uses: docker/setup-buildx-action@v3.0.0 with: platforms: linux/amd64,linux/arm64 - name: Docker meta id: meta - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: images: misskey/misskey tags: | @@ -31,12 +31,12 @@ jobs: type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}} - name: Log in to Docker Hub - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and Push to Docker Hub - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: builder: ${{ steps.buildx.outputs.name }} context: . diff --git a/.github/workflows/dockle.yml b/.github/workflows/dockle.yml index 9b79ee54f0ac..d811944d61f6 100644 --- a/.github/workflows/dockle.yml +++ b/.github/workflows/dockle.yml @@ -14,7 +14,7 @@ jobs: env: DOCKER_CONTENT_TRUST: 1 steps: - - uses: actions/checkout@v3.2.0 + - uses: actions/checkout@v4.0.0 - run: | curl -L -o dockle.deb "https://github.com/goodwithtech/dockle/releases/download/v0.4.10/dockle_0.4.10_Linux-64bit.deb" sudo dpkg -i dockle.deb diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index a15742dba797..7c10c23e7789 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -11,15 +11,15 @@ jobs: pnpm_install: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3.3.0 + - uses: actions/checkout@v4.0.0 with: fetch-depth: 0 submodules: true - uses: pnpm/action-setup@v2 with: - version: 7 + version: 8 run_install: false - - uses: actions/setup-node@v3.6.0 + - uses: actions/setup-node@v3.8.1 with: node-version-file: '.node-version' cache: 'pnpm' @@ -38,7 +38,7 @@ jobs: - sw - misskey-js steps: - - uses: actions/checkout@v3.3.0 + - uses: actions/checkout@v4.0.0 with: fetch-depth: 0 submodules: true @@ -46,7 +46,7 @@ jobs: with: version: 7 run_install: false - - uses: actions/setup-node@v3.6.0 + - uses: actions/setup-node@v3.8.1 with: node-version-file: '.node-version' cache: 'pnpm' @@ -64,7 +64,7 @@ jobs: - backend - misskey-js steps: - - uses: actions/checkout@v3.3.0 + - uses: actions/checkout@v4.0.0 with: fetch-depth: 0 submodules: true @@ -72,7 +72,7 @@ jobs: with: version: 7 run_install: false - - uses: actions/setup-node@v3.6.0 + - uses: actions/setup-node@v3.8.1 with: node-version-file: '.node-version' cache: 'pnpm' diff --git a/.github/workflows/ok-to-test.yml b/.github/workflows/ok-to-test.yml index 87af3a6ba6ac..c02b980e4da6 100644 --- a/.github/workflows/ok-to-test.yml +++ b/.github/workflows/ok-to-test.yml @@ -17,13 +17,13 @@ jobs: # See app.yml for an example app manifest - name: Generate token id: generate_token - uses: tibdex/github-app-token@v1 + uses: tibdex/github-app-token@v2 with: app_id: ${{ secrets.DEPLOYBOT_APP_ID }} private_key: ${{ secrets.DEPLOYBOT_PRIVATE_KEY }} - name: Slash Command Dispatch - uses: peter-evans/slash-command-dispatch@v1 + uses: peter-evans/slash-command-dispatch@v3 env: TOKEN: ${{ steps.generate_token.outputs.token }} with: diff --git a/.github/workflows/pr-preview-deploy.yml b/.github/workflows/pr-preview-deploy.yml index 9b786d34aaf3..702d8917e35a 100644 --- a/.github/workflows/pr-preview-deploy.yml +++ b/.github/workflows/pr-preview-deploy.yml @@ -53,7 +53,7 @@ jobs: # Check out merge commit - name: Fork based /deploy checkout - uses: actions/checkout@v3.3.0 + uses: actions/checkout@v4.0.0 with: ref: 'refs/pull/${{ github.event.client_payload.pull_request.number }}/merge' diff --git a/.github/workflows/reviewer_lottery.yml b/.github/workflows/reviewer_lottery.yml deleted file mode 100644 index 33228d7465a6..000000000000 --- a/.github/workflows/reviewer_lottery.yml +++ /dev/null @@ -1,13 +0,0 @@ -name: "Reviewer lottery" -on: - pull_request_target: - types: [opened, ready_for_review, reopened] - -jobs: - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - uses: uesteibar/reviewer-lottery@v2 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/storybook.yml b/.github/workflows/storybook.yml deleted file mode 100644 index f77daf58684f..000000000000 --- a/.github/workflows/storybook.yml +++ /dev/null @@ -1,74 +0,0 @@ -name: Storybook - -on: - push: - branches-ignore: - - l10n_develop - -jobs: - build: - runs-on: ubuntu-latest - - env: - NODE_OPTIONS: "--max_old_space_size=7168" - - steps: - - uses: actions/checkout@v3.3.0 - with: - fetch-depth: 0 - submodules: true - - name: Install pnpm - uses: pnpm/action-setup@v2 - with: - version: 7 - run_install: false - - name: Use Node.js 18.x - uses: actions/setup-node@v3.6.0 - with: - node-version-file: '.node-version' - cache: 'pnpm' - - run: corepack enable - - run: pnpm i --frozen-lockfile - - name: Check pnpm-lock.yaml - run: git diff --exit-code pnpm-lock.yaml - - name: Build misskey-js - run: pnpm --filter misskey-js build - - name: Build storybook - run: pnpm --filter frontend build-storybook - - name: Publish to Chromatic - if: github.ref == 'refs/heads/master' - run: pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static - env: - CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} - - name: Publish to Chromatic - if: github.ref != 'refs/heads/master' - id: chromatic - run: | - DIFF="${{ github.event.before }} HEAD" - if [ "$DIFF" = "0000000000000000000000000000000000000000 HEAD" ]; then - DIFF="HEAD" - fi - CHROMATIC_PARAMETER="$(node packages/frontend/.storybook/changes.js $(git diff-tree --no-commit-id --name-only -r $(echo "$DIFF") | xargs))" - if [ "$CHROMATIC_PARAMETER" = " --skip" ]; then - echo "skip=true" >> $GITHUB_OUTPUT - fi - pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static $(echo "$CHROMATIC_PARAMETER") - env: - CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} - - name: Notify that Chromatic will skip testing - uses: actions/github-script@v6.4.0 - if: github.ref != 'refs/heads/master' && github.ref != 'refs/heads/develop' && steps.chromatic.outputs.skip == 'true' - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - github.rest.repos.createCommitComment({ - owner: context.repo.owner, - repo: context.repo.repo, - commit_sha: context.sha, - body: 'Chromatic will skip testing but you may still have to [review the changes on Chromatic](https://www.chromatic.com/pullrequests?appId=6428f7d7b962f0b79f97d6e4).' - }) - - name: Upload Artifacts - uses: actions/upload-artifact@v3 - with: - name: storybook - path: packages/frontend/storybook-static diff --git a/.github/workflows/test-backend.yml b/.github/workflows/test-backend.yml index f1e414dbbc25..19496c8959f0 100644 --- a/.github/workflows/test-backend.yml +++ b/.github/workflows/test-backend.yml @@ -13,7 +13,7 @@ jobs: strategy: matrix: - node-version: [18.x] + node-version: [20.5.1] services: postgres: @@ -29,16 +29,16 @@ jobs: - 56312:6379 steps: - - uses: actions/checkout@v3.3.0 + - uses: actions/checkout@v4.0.0 with: submodules: true - name: Install pnpm uses: pnpm/action-setup@v2 with: - version: 7 + version: 8 run_install: false - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3.6.0 + uses: actions/setup-node@v3.8.1 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/test-frontend.yml b/.github/workflows/test-frontend.yml index a5505d30d869..0618a0ef0af8 100644 --- a/.github/workflows/test-frontend.yml +++ b/.github/workflows/test-frontend.yml @@ -13,19 +13,19 @@ jobs: strategy: matrix: - node-version: [18.x] + node-version: [20.5.1] steps: - - uses: actions/checkout@v3.3.0 + - uses: actions/checkout@v4.0.0 with: submodules: true - name: Install pnpm uses: pnpm/action-setup@v2 with: - version: 7 + version: 8 run_install: false - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3.6.0 + uses: actions/setup-node@v3.8.1 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' @@ -51,7 +51,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [18.x] + node-version: [20.5.1] browser: [chrome] services: @@ -68,7 +68,7 @@ jobs: - 56312:6379 steps: - - uses: actions/checkout@v3.3.0 + - uses: actions/checkout@v4.0.0 with: submodules: true # https://github.com/cypress-io/cypress-docker-images/issues/150 @@ -83,7 +83,7 @@ jobs: version: 7 run_install: false - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3.6.0 + uses: actions/setup-node@v3.8.1 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' @@ -101,12 +101,12 @@ jobs: - name: Cypress install run: pnpm exec cypress install - name: Cypress run - uses: cypress-io/github-action@v5 + uses: cypress-io/github-action@v6 with: install: false start: pnpm start:test wait-on: 'http://localhost:61812' - headless: false + headed: true browser: ${{ matrix.browser }} - uses: actions/upload-artifact@v2 if: failure() diff --git a/.github/workflows/test-misskey-js.yml b/.github/workflows/test-misskey-js.yml index b15e704c7fa3..7999c183b160 100644 --- a/.github/workflows/test-misskey-js.yml +++ b/.github/workflows/test-misskey-js.yml @@ -16,17 +16,17 @@ jobs: strategy: matrix: - node-version: [18.x] + node-version: [20.5.1] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: - name: Checkout - uses: actions/checkout@v3.3.0 + uses: actions/checkout@v4.0.0 - run: corepack enable - name: Setup Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3.6.0 + uses: actions/setup-node@v3.8.1 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/test-production.yml b/.github/workflows/test-production.yml new file mode 100644 index 000000000000..0504f42d169e --- /dev/null +++ b/.github/workflows/test-production.yml @@ -0,0 +1,42 @@ +name: Test (production install and build) + +on: + push: + branches: + - master + - develop + pull_request: + +env: + NODE_ENV: production + +jobs: + production: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [20.5.1] + + steps: + - uses: actions/checkout@v4.0.0 + with: + submodules: true + - name: Install pnpm + uses: pnpm/action-setup@v2 + with: + version: 8 + run_install: false + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3.8.1 + with: + node-version: ${{ matrix.node-version }} + cache: 'pnpm' + - run: corepack enable + - run: pnpm i --frozen-lockfile + - name: Check pnpm-lock.yaml + run: git diff --exit-code pnpm-lock.yaml + - name: Copy Configure + run: cp .github/misskey/test.yml .config/default.yml + - name: Build + run: pnpm build diff --git a/.gitignore b/.gitignore index fbe224550263..a66e527db059 100644 --- a/.gitignore +++ b/.gitignore @@ -44,7 +44,7 @@ built /data /.cache-loader /db -/elasticsearch +/meili_data npm-debug.log *.pem run.bat @@ -64,3 +64,6 @@ temp *.blend3 *.blend4 *.blend5 + +# VSCode addon +.favorites.json diff --git a/.node-version b/.node-version index 6d80269a4f04..7cc2069986ba 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -18.16.0 +20.5.1 diff --git a/.vscode/settings.json b/.vscode/settings.json index baffbe18ecdd..71fb02a59dfa 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,5 +6,6 @@ "files.associations": { "*.test.ts": "typescript" }, + "jest.jestCommandLine": "pnpm run jest", "jest.autoRun": "off" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index d0c662a54bbc..23ec300a2048 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ -## 13.x.x (unreleased) + +## 2023.9.0 (unreleased) + +### General +- OAuth 2.0のサポート +- お知らせ機能の強化 + - ユーザー個別のお知らせを作成可能に + - お知らせのバナー表示やダイアログ表示が可能に + - お知らせのアイコンを設定可能に +- チャンネルをセンシティブ指定できるようになりました + - センシティブチャンネルのNoteのReNoteはデフォルトでHome TLに流れるようになりました + - センシティブチャンネルのノートはユーザープロフィールに表示されません +- 二要素認証のバックアップコードが生成されるようになりました ref. https://github.com/MisskeyIO/misskey/pull/121 +- 二要素認証でパスキーをサポートするようになりました +- 通知をテストできるようになりました +- PWAのアイコンが設定できるようになりました +- manifest.jsonをオーバーライド可能に +- 依存関係の更新 +- ローカリゼーションの更新 + +### Client +- 任意のユーザーリストをタイムラインページにピン留めできるように + - 設定->クライアント設定->全般 から設定可能です +- ノート詳細ページを改修 + - 読み込み時のパフォーマンスが向上しました + - リノート一覧、リアクション一覧がタブとして追加されました + - ノートのメニューからは当該項目は消えました +- プロフィールにその人が作ったPlayの一覧出せるように +- メニューのスイッチの動作を改善 +- 絵文字ピッカーの検索の表示件数を100件に増加 +- 投稿フォームのプレビューの表示状態を記憶するように +- AiScriptからMisskeyサーバーAPIを呼び出す際の制限を撤廃 +- Playで直接投稿フォームを埋め込めるように(`Ui:C:postForm`) +- Enhance: ユーザーメニューでスイッチでユーザーリストに追加・削除できるように +- Enhance: 自分が押したリアクションのデザインを改善 +- Enhance: ノート検索にローカルのみ検索可能なオプションの追加 +- Enhance: AiScriptで`LOCALE`として現在の設定言語を取得できるように +- Enhance: Renote自体を通報できるように +- Enhance: データセーバーモードの強化 +- Enhance: Renoteを管理者権限で削除可能に +- `$[rainbow ]`記法が、動きのあるMFMが無効になっていても使用できるようになりました +- Playの操作を行うAPI TokenをAPIコンソールから発行できるように +- リアクションの表示サイズをより大きくできるように +- AiScriptを0.16.0に更新 +- Mk:apiが失敗した時にエラー型の値(AiScript 0.16.0で追加)を返すように +- タイムラインでリスト/アンテナ選択時のパフォーマンスを改善 +- ScratchpadでAsync:系関数やボタンのコールバックなどのエラーにもダイアログを出すように(試験的なためPlayなどには未実装) +- 「Moderation note」、「Add moderation note」をローカライズできるように +- 新しい実績を追加 +- Fix: サーバー情報画面(`/instance-info/{domain}`)でブロックができないのを修正 +- Fix: 未読のお知らせの「わかった」をクリック・タップしてもその場で「わかった」が消えない問題を修正 +- Fix: iOSで画面を回転させるとテキストサイズが変わる問題を修正 +- Fix: word mute for sub note is not applied +- Fix: タイムラインを下にスクロールしてノート画面に移動して再び戻ったら以前のスクロール位置を失う問題を修正 +- Fix: Misskeyプラグインをインストールする際のAiScriptバージョンのチェックが0.14.0以降に対応していない問題を修正 +- Fix: 他のサーバーのユーザーへ「メッセージを送信」した時の初期テキストのメンションが間違っている問題を修正 +- Fix: 環境によってはMisskey Webが開けない問題を修正 + +### Server +- cacheRemoteFilesの初期値はfalseになりました +- ファイルアップロード時等にファイル名の拡張子を修正する関数(correctFilename)の挙動を改善 +- Webhookのペイロードにサーバーのurlが含まれるようになりました +- Webhook設定でsecretを空に出来るように +- 使われていないアンテナの自動停止を設定可能に +- nodeinfo 2.1対応 +- 自分へのメンション一覧を取得する際のパフォーマンスを向上 +- Docker環境でjemallocを使用することでメモリ使用量を削減 +- Fix: MK_ONLY_SERVERオプションを指定した際にクラッシュする問題を修正 +- Fix: ノート検索 `notes/search` にてhostを指定した際に検索結果に反映されるように +- Fix: 一部のfeatured noteを照会できない問題を修正 +- Fix: muteがapiからのuser list timeline取得で機能しない問題を修正 +- Fix: ジョブキュー管理画面の認証を回避できる問題を修正 +- Fix: 一部のサーバー内部エラーがスタックトレースを返さないように修正 + +## 13.14.2 + +### Client +- リストTLで、ユーザーが追加・削除されてもTLを初期化しないように +- URL取得変数を関数に変更 CURRENT_URL -> Mk:url() +- Fix: モバイル表示のときページ下部がナビゲーションバーに隠れる問題を修正 +- Fix: 一部モーダルダイアログでスクロールできない問題を修正 +- Fix: Selecting all emojis in Custom emoji is impossible +- Fix: PhotoSwipeによるメモリリークの修正 + +### Server +- Fix: APIのオフセットが壊れていたせいで「もっと見る」でもっと見れない問題を修正 +- Fix: 外部サーバーの投稿がタイムラインに表示されないことがある問題を修正 +- Enhance: Add address bind config option (outgoingAddress) + +## 13.14.1 + +### General +- 招待機能を改善しました + * 過去に発行した招待コードを確認できるようになりました + * ロールごとに招待コードの発行数制限と制限対象期間、有効期限を設定できるようになりました + * 招待コードを作成したユーザーと使用したユーザーを確認できるようになりました +- ユーザーにロールが期限付きでアサインされている場合、その期限をユーザーのモデレーションページで確認できるようになりました +- identicon生成を無効にしてパフォーマンスを向上させることができるようになりました +- サーバーのマシン情報の公開を無効にしてパフォーマンスを向上させることができるようになりました + +### Client +- deck UIのカラムのメニューからアンテナとリストの編集画面を開けるように +- ドライブファイルのメニューで画像をクロップできるように +- 画像を動画と同様に簡単に隠せるように +- Enhance: ノートの埋め込みが複数画像と動画を表示されるように +- オリジナル画像を保持せずにアップロードする場合webpでアップロードされるように(Safari以外) +- 見たことのあるRenoteを省略して表示をオンのときに自分のnoteのrenoteを省略するように +- フォルダーやファイルに対しても開発者モード使用時、IDをコピーできるように +- 引用対象を「もっと見る」で展開した場合、「閉じる」で畳めるように +- プロフィールURLをコピーできるボタンを追加 #11190 +- `CURRENT_URL`で現在表示中のURLを取得できるように(AiScript) +- ユーザーのContextMenuに「アンテナに追加」ボタンを追加 +- フォローやお気に入り登録をしていないチャンネルを開く時は概要ページを開くように +- 画面ビューワをタップした場合、マウスクリックと同様に画像ビューワを閉じるように +- オフライン時の画面にリロードボタンを追加 +- Renote時に公開範囲のデフォルト設定が適用されるように +- Deckで非ルートページにアクセスした際に簡易UIで表示しない設定を追加 +- ロール設定画面でロールIDを確認できるように +- コンテキストメニュー表示時のパフォーマンスを改善 +- フォロー/フォロワー非公開時の表示を改善 +- 本文にMFMが含まれている場合に自動でたたまれる機能が、返信先や引用RNにも適用されるように + - position は対象外になりました +- AiScriptを0.15.0に更新 +- Fix: サーバーメトリクスが90度傾いている +- Fix: 非ログイン時にクレデンシャルが必要なページに行くとエラーが出る問題を修正 +- Fix: sparkle内にリンクを入れるとクリック不能になる問題の修正 +- Fix: ZenUIでポップアップの表示位置がおかしい問題を修正 +- Fix: ページ遷移でスクロール位置が保持されない問題を修正 +- Fix: フォルダーのページネーションが機能しない #11180 +- Fix: 長い文章を投稿する際、プレビューが画面からはみ出る問題を修正 +- Fix: システムフォント設定が正しく反映されない問題を修正 +- Fix: アンケート終了時のプッシュ通知が正しく表示されない問題を修正 +- Fix: MasterVolumeが0の時だけでなく各通知音の音量設定が0のときも、HTMLAudioElement.playが実行されないように変更 + +### Server +- JSON.parse の回数を削減することで、ストリーミングのパフォーマンスを向上しました +- nsfwjs のモデルロードを排他することで、重複ロードによってメモリ使用量が増加しないように +- 連合の配送ジョブのパフォーマンスを向上(ロック機構の見直し、Redisキャッシュの活用) +- featuredノートのsignedGet回数を減らしました +- ActivityPubの署名用鍵長を2048bitに変更しパフォーマンスを向上(新規アカウントのみ) +- リモートサーバーのセンシティブなファイルのキャッシュだけを無効化できるオプションを追加 +- MeilisearchにIndexするノートの範囲を設定できるように +- Export notes with file detail +- Add unix socket support +- 設定ファイルでioredisの全てのオプションを指定可能に +- Fix: エクスポートしたカスタム絵文字のzipが大きいと読み込めない問題を修正 +- Fix: リモートサーバーに無意味なActivityPubの配信を行うことがあるのを修正 +- Fix: Remove Meilisearch index when notes are deleted +- Fix: 非英語環境でのPostgreSQLのエラーハンドリングを修正 +- Fix: インスタンスのアイコンがbase64の場合の挙動を修正 +- Fix: ローカルの `Person` を指す `acct` URI を解析するときのバグを修正しました +- Fix: 無効化されたアンテナが再度有効化されないことがある問題を修正 + +## 13.13.2 + +### General +- エラー時や項目が存在しないときなどのアイコン画像をサーバー管理者が設定できるように +- ロールが付与されているユーザーリストを非公開にできるように +- サーバーの負荷が非常に高いため、ユーザー統計表示機能を削除しました + +### Client +- Fix: タブがバックグラウンドでもstreamが切断されないように + +### Server +- Fix: キャッシュが溜まり続けないように + +## 13.13.1 + +### Client +- Fix: タブがアクティブな間はstreamが切断されないように + +### Server +- Fix: api/metaで`TypeError: JSON5.parse is not a function`エラーが発生する問題を修正 + +## 13.13.0 + +### General +- カスタム絵文字ごとにそれをリアクションとして使えるロールを設定できるように +- カスタム絵文字ごとに連合するかどうか設定できるように +- カスタム絵文字ごとにセンシティブフラグを設定できるように +- センシティブなカスタム絵文字のリアクションを受け入れない設定が可能に +- タイムラインにフォロイーの行った他人へのリプライを含めるかどうかの設定をアカウントに保存するのをやめるように + - 今後はAPI呼び出し時およびストリーミング接続時に設定するようになります +- リストを公開できるようになりました + +### Client +- リアクションの取り消し/変更時に確認ダイアログを出すように +- 開発者モードを追加 +- AiScriptを0.13.3に更新 +- Deck UIを使用している場合、`/`以外にアクセスした際にZen UIで表示するように + - メインカラムを設置していない場合の問題を解決 +- ハッシュタグのノート一覧ページから、そのハッシュタグで投稿するボタンを追加 +- アカウント初期設定ウィザードに戻るボタンを追加 +- アカウントの初期設定ウィザードにあとでボタンを追加 +- サーバーにカスタム絵文字の種類が多い場合のパフォーマンスの改善 +- Fix: URLプレビューで情報が取得できなかった際の挙動を修正 +- Fix: Safari、Firefoxでの新規登録時、パスワードマネージャーにメールアドレスが登録されていた挙動を修正 +- Fix: ロールタイムラインが無効でも投稿が流れてしまう問題の修正 +- Fix: ロールタイムラインにて全ての投稿が流れてしまう問題の修正 +- Fix: 「アクセストークンの管理」画面でアプリの情報が表示されない問題の修正 +- Fix: Firefoxにおける絵文字ピッカーのTabキーフォーカス問題の修正 +- Fix: フォローボタンがテーマのカラースキームによって視認性が悪くなる問題を修正 + - 新しいプロパティ `fgOnWhite` が追加されました + +### Server +- bullをbull-mqにアップグレードし、ジョブキューのパフォーマンスを改善 +- ストリーミングのパフォーマンスを改善 +- Fix: 無効化されたアンテナにアクセスがあった際に再度有効化するように +- Fix: お知らせの画像URLを空にできない問題を修正 +- Fix: i/notificationsのsinceIdが機能しない問題を修正 +- Fix: pageのピン留めを解除することができない問題を修正 + +## 13.12.2 + +## NOTE +Meilisearchの設定に`index`が必要になりました。値はMisskeyサーバーのホスト名にすることをお勧めします(アルファベット、ハイフン、アンダーバーのみ使用可能)。例: `misskey-io` +過去に作成された`notes`インデックスは、`---notes`にリネームが必要です。例: `misskey-io---notes` + +### General +- 投稿したコンテンツのAIによる学習を軽減するオプションを追加 + +### Client +- ユーザーを指定してのノート検索が可能に +- アカウント初期設定ウィザードにプライバシー設定を追加 +- リテンション率チャートに折れ線グラフを追加 +- Fix: ブラーエフェクトを有効にしている状態で高負荷になる問題を修正 +- Fix: Pageにおいて画像ブロックに画像を設定できない問題を修正 +- Fix: カラーバーがリプライには表示されないのを修正 +- Fix: チャンネル内の検索ボックスが挙動不審な問題を修正 +- Fix: リテンションチャートのレンダリングを修正 +- Fix: リアクションエフェクトのレンダリングの問題を修正 + +### Server +- センシティブワードの登録にAnd、正規表現が使用できるようになりました。 +- Fix: ひとつのMeilisearchサーバーを複数のMisskeyサーバーで使えない問題を修正 + +## 13.12.1 + +### Client +- プロフィール画面におけるモデレーションノートの表示を調整 +- Fix: 一部ダイアログが表示されない問題を修正 +- Fix: MkUserInfoのフォローボタンが変な位置にある問題を修正 + +### Server +- Fix: リモートサーバーの情報が更新できない問題を修正 +- Fix: 13.11を経験しない状態で13.12にアップデートした場合ユーザープロフィール関連の画像が消失する問題を修正 + +## 13.12.0 + +### NOTE +- Node.js 18.16.0以上が必要になりました ### General +- アカウントの引っ越し(フォロワー引き継ぎ)に対応 +- Meilisearchを全文検索に使用できるようになりました + * 「フォロワーのみ」の投稿は検索結果に表示されません。 +- 新規登録前に簡潔なルールをユーザーに表示できる、サーバールール機能を追加 - ユーザーへの自分用メモ機能 * ユーザーに対して、自分だけが見られるメモを追加できるようになりました。 (自分自身に対してもメモを追加できます。) * ユーザーメニューから追加できます。 (デスクトップ表示ではusernameの右側のボタンからも追加可能) +- チャンネルに色を設定できるようになりました。各ノートに設定した色のインジケーターが表示されます。 +- チャンネルをアーカイブできるようになりました。 + * アーカイブすると、チャンネル一覧や検索結果に表示されなくなり、新たな書き込みもできなくなります。 +- アンテナのエクスポート・インポートができるようになりました +- ロールタイムラインをロールごとに表示するかどうかの選択できるようになりました。 + * デフォルトがオフになるので、ロールタイムラインを表示する場合はオンにしてください。 +- ロールに強制的にNSFWを付与するポリシーを追加 + * アップロード済みのファイルはNSFWにならない為注意してください。 +- モデレーションノートがユーザーのプロフィールページからも閲覧および編集できるようになりました。 +- カスタム絵文字のライセンスを複数でセットできるようになりました。 +- 管理者が予約ユーザー名を設定できるようになりました。 +- Fix: フォローリクエストの通知が残る問題を修正 ### Client +- アカウント作成時に初期設定ウィザードを表示するように +- チャンネル内検索ができるように +- チャンネル検索ですべてのチャンネルの取得/表示ができるように +- 通知の表示をカスタマイズできるように +- ドライブのファイル一覧から直接ノートを作成できるように +- ノートメニューからRenoteしたユーザーの一覧を見れるように - コントロールパネルのカスタム絵文字ページおよびaboutのカスタム絵文字の検索インプットで、`:emojiname1::emojiname2:`のように検索して絵文字を検索できるように * 絵文字ピッカーから入力可能になります - データセーバーモードを追加 * 画像が全て隠れた状態で表示されるようになります +- 閲覧注意設定された画像は表示した状態でもそれが閲覧注意だと分かる表示をするように +- モデレーターはノートに添付された画像上から直接NSFW設定できるように - 1枚だけのメディアリストの画像のアスペクト比を画像に応じて縦長にするように +- プロフィール設定「追加情報」の項目の削除と並び替えができるように +- 新しい実績を追加 +- AiScriptを0.13.2に更新 +- Fix: AiScript APIのMk:dialogで何も返していなかったのをNULLを返すように修正 +- Fix: 1:1ではない画像のリアクション通知バッジが左や上に寄ってしまっていたのを中央に来るように修正 - Fix: リアクションをホバーした時のユーザーリストで猫耳が切れてしまっていた問題を修正 +- Fix: NSFWメディアの上に表示された「もっと見る」ボタンが押しづらい問題を修正 ### Server +- channel/searchのqueryが空の場合に全てのチャンネルを返すように変更 +- 環境変数MISSKEY_CONFIG_YMLで設定ファイルをdefault.ymlから変更可能に +- Fix: 他のサーバーの情報が取得できないことがある問題を修正 - Fix: エクスポートデータの拡張子がunknownになる問題を修正 - Fix: Content-Dispositionのパースでエラーが発生した場合にダウンロードが完了しない問題を修正 - Fix: API: i/update avatarIdとbannerIdにnullを渡した時、画像がリセットされない問題を修正 +- Fix: .wav, .flacが再生できない問題を修正(新しくアップロードされたファイルのみ修正が適用されます) +- Fix: 凍結されたユーザーが一部APIのレスポンスに含まれる問題を修正 +- Fix: メモリの使用量を`used - buffers - cached`ではなく`total - available`で求めるように(環境によって正常に計測できていなかったため) ## 13.11.3 @@ -261,6 +547,7 @@ - アンテナでCWも検索対象にするように - ノートの操作部をホバー時のみ表示するオプションを追加 - サウンドを追加 +- enhance(client): MFMのx2, scale, positionが含まれていたらノートをたたむように - サーバーのパフォーマンスを改善 ### Bugfixes diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b8a20c8078cd..484fd994130c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -106,7 +106,7 @@ If your language is not listed in Crowdin, please open an issue. ![Crowdin](https://d322cqt584bo4o.cloudfront.net/misskey/localized.svg) ## Development -During development, it is useful to use the +During development, it is useful to use the ``` pnpm dev @@ -150,7 +150,7 @@ Prepare DB/Redis for testing. ``` docker compose -f packages/backend/test/docker-compose.yml up ``` -Alternatively, prepare an empty (data can be erased) DB and edit `.config/test.yml`. +Alternatively, prepare an empty (data can be erased) DB and edit `.config/test.yml`. Run all test. ``` @@ -165,6 +165,11 @@ pnpm jest -- foo.ts ### e2e tests TODO +## Environment Variable + +- `MISSKEY_CONFIG_YML`: Specify the file path of config.yml instead of default.yml (e.g. `2nd.yml`). +- `MISSKEY_WEBFINGER_USE_HTTP`: If it's set true, WebFinger requests will be http instead of https, useful for testing federation between servers in localhost. NEVER USE IN PRODUCTION. + ## Continuous integration Misskey uses GitHub Actions for executing automated tests. Configuration files are located in [`/.github/workflows`](/.github/workflows). @@ -209,30 +214,13 @@ Misskey uses [Storybook](https://storybook.js.org/) for UI development. ### Setup & Run -#### Universal - -##### Setup - -```bash -pnpm --filter misskey-js build -pnpm --filter frontend tsc -p .storybook && (node packages/frontend/.storybook/preload-locale.js & node packages/frontend/.storybook/preload-theme.js) -``` - -##### Run - -```bash -node packages/frontend/.storybook/generate.js && pnpm --filter frontend storybook dev -``` - -#### macOS & Linux - -##### Setup +#### Setup ```bash pnpm --filter misskey-js build ``` -##### Run +#### Run ```bash pnpm --filter frontend storybook-dev @@ -313,6 +301,12 @@ export const handlers = [ Don't forget to re-run the `.storybook/generate.js` script after adding, editing, or removing the above files. ## Notes + +### Misskeyのドメイン固有の概念は`Mi`をprefixする +例えばGoogleが自社サービスをMap、Earth、DriveではなくGoogle Map、Google Earth、Google Driveのように命名するのと同じ +コード上でMisskeyのドメイン固有の概念には`Mi`をprefixすることで、他のドメインの同様の概念と区別できるほか、名前の衝突を防ぐ。 +ただし、文脈上Misskeyのものを指すことが明らかであり、名前の衝突の恐れがない場合は、一時的なローカル変数に限って`Mi`を省略してもよい。 + ### How to resolve conflictions occurred at pnpm-lock.yaml? Just execute `pnpm` to fix it. @@ -442,3 +436,6 @@ marginはそのコンポーネントを使う側が設定する ## その他 ### HTMLのクラス名で follow という単語は使わない 広告ブロッカーで誤ってブロックされる + +### indexというファイル名を使うな +ESMではディレクトリインポートは廃止されているのと、ディレクトリインポートせずともファイル名が index だと何故か一部のライブラリ?でディレクトリインポートだと見做されてエラーになる diff --git a/Dockerfile b/Dockerfile index fb389659bc93..654af605dc55 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # syntax = docker/dockerfile:1.4 -ARG NODE_VERSION=18.16.0-bullseye +ARG NODE_VERSION=20.5.1-bullseye # build assets & compile TypeScript @@ -62,7 +62,7 @@ ARG GID="991" RUN apt-get update \ && apt-get install -y --no-install-recommends \ - ffmpeg tini curl \ + ffmpeg tini curl libjemalloc-dev libjemalloc2 \ && corepack enable \ && groupadd -g "${GID}" misskey \ && useradd -l -u "${UID}" -g "${GID}" -m -d /misskey misskey \ @@ -81,6 +81,7 @@ COPY --chown=misskey:misskey --from=native-builder /misskey/packages/backend/bui COPY --chown=misskey:misskey --from=native-builder /misskey/fluent-emojis /misskey/fluent-emojis COPY --chown=misskey:misskey . ./ +ENV LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2 ENV NODE_ENV=production HEALTHCHECK --interval=5s --retries=20 CMD ["/bin/bash", "/misskey/healthcheck.sh"] ENTRYPOINT ["/usr/bin/tini", "--"] diff --git a/README.md b/README.md index 2aae4bb865ba..ab4388c2ebd2 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ Misskey logo - + **🌎 **[Misskey](https://misskey-hub.net/)** is an open source, decentralized social media platform that's free forever! 🚀** - + --- @@ -21,7 +21,7 @@ become a patron - + --- [![codecov](https://codecov.io/gh/misskey-dev/misskey/branch/develop/graph/badge.svg?token=R6IQZ3QJOL)](https://codecov.io/gh/misskey-dev/misskey) diff --git a/ROADMAP.md b/ROADMAP.md index 420f7287582a..3077c41e7340 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -22,7 +22,7 @@ This is the phase we are at now. We need to make a high-maintenance environment Once Phase 1 is complete and an environment conducive to the development of a stable system is in place, the implementation of new functions can begin gradually. - Improve features for moderation -- OAuth2 support https://github.com/misskey-dev/misskey/issues/8262 +- ~~OAuth2 support https://github.com/misskey-dev/misskey/issues/8262~~ → Done ✔️ - GraphQL support? ## (3) Improve scalability diff --git a/assets/title_float.svg b/assets/title_float.svg index 43205ac1c482..ed1749e321fb 100644 --- a/assets/title_float.svg +++ b/assets/title_float.svg @@ -23,13 +23,13 @@ diff --git a/packages/frontend/src/components/MkAccountMoved.stories.impl.ts b/packages/frontend/src/components/MkAccountMoved.stories.impl.ts index bed9d9431159..a6d4d18c1b6e 100644 --- a/packages/frontend/src/components/MkAccountMoved.stories.impl.ts +++ b/packages/frontend/src/components/MkAccountMoved.stories.impl.ts @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { StoryObj } from '@storybook/vue3'; import { userDetailed } from '../../.storybook/fakes'; diff --git a/packages/frontend/src/components/MkAccountMoved.vue b/packages/frontend/src/components/MkAccountMoved.vue index 98979de23606..155d9fe3a909 100644 --- a/packages/frontend/src/components/MkAccountMoved.vue +++ b/packages/frontend/src/components/MkAccountMoved.vue @@ -1,20 +1,31 @@ + + diff --git a/packages/frontend/src/components/MkAnnouncementDialog.stories.impl.ts b/packages/frontend/src/components/MkAnnouncementDialog.stories.impl.ts new file mode 100644 index 000000000000..42cfb90f7c1d --- /dev/null +++ b/packages/frontend/src/components/MkAnnouncementDialog.stories.impl.ts @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { StoryObj } from '@storybook/vue3'; +import MkAnnouncementDialog from './MkAnnouncementDialog.vue'; +export const Default = { + render(args) { + return { + components: { + MkAnnouncementDialog, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + }, + template: '', + }; + }, + args: { + announcement: { + id: '1', + title: 'Title', + text: 'Text', + createdAt: new Date().toISOString(), + updatedAt: null, + icon: 'info', + imageUrl: null, + display: 'dialog', + needConfirmationToRead: false, + forYou: true, + }, + }, + parameters: { + layout: 'centered', + }, +} satisfies StoryObj; diff --git a/packages/frontend/src/components/MkAnnouncementDialog.vue b/packages/frontend/src/components/MkAnnouncementDialog.vue new file mode 100644 index 000000000000..aaac3dd29bca --- /dev/null +++ b/packages/frontend/src/components/MkAnnouncementDialog.vue @@ -0,0 +1,104 @@ + + + + + + + diff --git a/packages/frontend/src/components/MkAsUi.stories.impl.ts b/packages/frontend/src/components/MkAsUi.stories.impl.ts index b67c0e679d58..564fa902baae 100644 --- a/packages/frontend/src/components/MkAsUi.stories.impl.ts +++ b/packages/frontend/src/components/MkAsUi.stories.impl.ts @@ -1,2 +1,7 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + import MkAsUi from './MkAsUi.vue'; void MkAsUi; diff --git a/packages/frontend/src/components/MkAsUi.vue b/packages/frontend/src/components/MkAsUi.vue index 6ade5316c681..099baf0d7299 100644 --- a/packages/frontend/src/components/MkAsUi.vue +++ b/packages/frontend/src/components/MkAsUi.vue @@ -1,3 +1,8 @@ + +