diff --git a/packages/frontend-embed/package.json b/packages/frontend-embed/package.json index 816b028cbd..8c9a1b82cb 100644 --- a/packages/frontend-embed/package.json +++ b/packages/frontend-embed/package.json @@ -20,7 +20,6 @@ "@twemoji/parser": "15.1.1", "@vitejs/plugin-vue": "5.1.0", "@vue/compiler-sfc": "3.4.37", - "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.11", "astring": "1.8.6", "buraha": "0.0.1", "compare-versions": "6.1.1", @@ -30,7 +29,6 @@ "eventemitter3": "5.0.1", "idb-keyval": "6.2.1", "is-file-animated": "1.0.2", - "json5": "2.2.3", "mfm-js": "0.24.0", "misskey-js": "workspace:*", "punycode": "2.3.1", diff --git a/packages/frontend-embed/src/components/EmInstanceTicker.vue b/packages/frontend-embed/src/components/EmInstanceTicker.vue new file mode 100644 index 0000000000..82c82199b5 --- /dev/null +++ b/packages/frontend-embed/src/components/EmInstanceTicker.vue @@ -0,0 +1,85 @@ + + + + + + + diff --git a/packages/frontend-embed/src/components/EmNoteDetailed.vue b/packages/frontend-embed/src/components/EmNoteDetailed.vue index 1e8c140ad8..1ab00af036 100644 --- a/packages/frontend-embed/src/components/EmNoteDetailed.vue +++ b/packages/frontend-embed/src/components/EmNoteDetailed.vue @@ -129,13 +129,16 @@ SPDX-License-Identifier: AGPL-3.0-only import { computed, inject, ref } from 'vue'; import * as mfm from 'mfm-js'; import * as Misskey from 'misskey-js'; -import EmMediaList from './EmMediaList.vue'; +import EmMediaList from '@/components/EmMediaList.vue'; import EmNoteSub from '@/components/EmNoteSub.vue'; import EmNoteSimple from '@/components/EmNoteSimple.vue'; import EmReactionsViewer from '@/components/EmReactionsViewer.vue'; import EmCwButton from '@/components/EmCwButton.vue'; import EmPoll from '@/components/EmPoll.vue'; import EmInstanceTicker from '@/components/EmInstanceTicker.vue'; +import EmA from '@/components/EmA.vue'; +import EmAvatar from '@/components/EmAvatar.vue'; +import EmTime from '@/components/EmTime.vue'; import { userPage } from '@/utils.js'; import { notePage } from '@/utils.js'; import { i18n } from '@/i18n.js'; diff --git a/packages/frontend-embed/src/instance.ts b/packages/frontend-embed/src/instance.ts index 4d41b2aac0..3b8c7324b2 100644 --- a/packages/frontend-embed/src/instance.ts +++ b/packages/frontend-embed/src/instance.ts @@ -9,4 +9,5 @@ import { misskeyApi } from '@/misskey-api.js'; export const instance = { iconUrl: 'TODO', + mediaProxy: 'TODO', }; diff --git a/packages/frontend-embed/src/to-be-shared/media-proxy.ts b/packages/frontend-embed/src/to-be-shared/media-proxy.ts new file mode 100644 index 0000000000..a12871c3b1 --- /dev/null +++ b/packages/frontend-embed/src/to-be-shared/media-proxy.ts @@ -0,0 +1,53 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { query } from './url.js'; +import { url } from '@/config.js'; +import { instance } from '@/instance.js'; + +export function getProxiedImageUrl(imageUrl: string, type?: 'preview' | 'emoji' | 'avatar', mustOrigin = false, noFallback = false): string { + const localProxy = `${url}/proxy`; + + if (imageUrl.startsWith(instance.mediaProxy + '/') || imageUrl.startsWith('/proxy/') || imageUrl.startsWith(localProxy + '/')) { + // もう既にproxyっぽそうだったらurlを取り出す + imageUrl = (new URL(imageUrl)).searchParams.get('url') ?? imageUrl; + } + + return `${mustOrigin ? localProxy : instance.mediaProxy}/${ + type === 'preview' ? 'preview.webp' + : 'image.webp' + }?${query({ + url: imageUrl, + ...(!noFallback ? { 'fallback': '1' } : {}), + ...(type ? { [type]: '1' } : {}), + ...(mustOrigin ? { origin: '1' } : {}), + })}`; +} + +export function getProxiedImageUrlNullable(imageUrl: string | null | undefined, type?: 'preview'): string | null { + if (imageUrl == null) return null; + return getProxiedImageUrl(imageUrl, type); +} + +export function getStaticImageUrl(baseUrl: string): string { + const u = baseUrl.startsWith('http') ? new URL(baseUrl) : new URL(baseUrl, url); + + if (u.href.startsWith(`${url}/emoji/`)) { + // もう既にemojiっぽそうだったらsearchParams付けるだけ + u.searchParams.set('static', '1'); + return u.href; + } + + if (u.href.startsWith(instance.mediaProxy + '/')) { + // もう既にproxyっぽそうだったらsearchParams付けるだけ + u.searchParams.set('static', '1'); + return u.href; + } + + return `${instance.mediaProxy}/static.webp?${query({ + url: u.href, + static: '1', + })}`; +} diff --git a/packages/frontend-embed/src/to-be-shared/url.ts b/packages/frontend-embed/src/to-be-shared/url.ts new file mode 100644 index 0000000000..5a8265af9e --- /dev/null +++ b/packages/frontend-embed/src/to-be-shared/url.ts @@ -0,0 +1,28 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +/* objを検査して + * 1. 配列に何も入っていない時はクエリを付けない + * 2. プロパティがundefinedの時はクエリを付けない + * (new URLSearchParams(obj)ではそこまで丁寧なことをしてくれない) + */ +export function query(obj: Record): string { + const params = Object.entries(obj) + .filter(([, v]) => Array.isArray(v) ? v.length : v !== undefined) + .reduce((a, [k, v]) => (a[k] = v, a), {} as Record); + + return Object.entries(params) + .map((p) => `${p[0]}=${encodeURIComponent(p[1])}`) + .join('&'); +} + +export function appendQuery(url: string, query: string): string { + return `${url}${/\?/.test(url) ? url.endsWith('?') ? '' : '&' : '?'}${query}`; +} + +export function extractDomain(url: string) { + const match = url.match(/^(?:https?:)?(?:\/\/)?(?:[^@\n]+@)?([^:\/\n]+)/im); + return match ? match[1] : null; +} diff --git a/packages/frontend-embed/vite.config.ts b/packages/frontend-embed/vite.config.ts index 61be63ee21..137954b802 100644 --- a/packages/frontend-embed/vite.config.ts +++ b/packages/frontend-embed/vite.config.ts @@ -5,7 +5,6 @@ import { type UserConfig, defineConfig } from 'vite'; import locales from '../../locales/index.js'; import meta from '../../package.json'; import packageInfo from './package.json' with { type: 'json' }; -import pluginJson5 from './vite.json5.js'; const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.json', '.json5', '.svg', '.sass', '.scss', '.css', '.vue']; @@ -67,7 +66,6 @@ export function getConfig(): UserConfig { plugins: [ pluginVue(), - pluginJson5(), ], resolve: { diff --git a/packages/frontend-embed/vite.json5.ts b/packages/frontend-embed/vite.json5.ts deleted file mode 100644 index 87b67c2142..0000000000 --- a/packages/frontend-embed/vite.json5.ts +++ /dev/null @@ -1,48 +0,0 @@ -// Original: https://github.com/rollup/plugins/tree/8835dd2aed92f408d7dc72d7cc25a9728e16face/packages/json - -import JSON5 from 'json5'; -import { Plugin } from 'rollup'; -import { createFilter, dataToEsm } from '@rollup/pluginutils'; -import { RollupJsonOptions } from '@rollup/plugin-json'; - -// json5 extends SyntaxError with additional fields (without subclassing) -// https://github.com/json5/json5/blob/de344f0619bda1465a6e25c76f1c0c3dda8108d9/lib/parse.js#L1111-L1112 -interface Json5SyntaxError extends SyntaxError { - lineNumber: number; - columnNumber: number; -} - -export default function json5(options: RollupJsonOptions = {}): Plugin { - const filter = createFilter(options.include, options.exclude); - const indent = 'indent' in options ? options.indent : '\t'; - - return { - name: 'json5', - - // eslint-disable-next-line no-shadow - transform(json, id) { - if (id.slice(-6) !== '.json5' || !filter(id)) return null; - - try { - const parsed = JSON5.parse(json); - return { - code: dataToEsm(parsed, { - preferConst: options.preferConst, - compact: options.compact, - namedExports: options.namedExports, - indent, - }), - map: { mappings: '' }, - }; - } catch (err) { - if (!(err instanceof SyntaxError)) { - throw err; - } - const message = 'Could not parse JSON5 file'; - const { lineNumber, columnNumber } = err as Json5SyntaxError; - this.warn({ message, id, loc: { line: lineNumber, column: columnNumber } }); - return null; - } - }, - }; -}