summaryrefslogtreecommitdiff
path: root/packages/frontend-embed
diff options
context:
space:
mode:
Diffstat (limited to 'packages/frontend-embed')
-rw-r--r--packages/frontend-embed/@types/global.d.ts2
-rw-r--r--packages/frontend-embed/eslint.config.js18
-rw-r--r--packages/frontend-embed/package.json6
-rw-r--r--packages/frontend-embed/src/boot.ts14
-rw-r--r--packages/frontend-embed/src/components/EmAvatar.vue5
-rw-r--r--packages/frontend-embed/src/components/EmMediaBanner.vue1
-rw-r--r--packages/frontend-embed/src/components/EmMediaImage.vue2
-rw-r--r--packages/frontend-embed/src/components/EmMfm.ts82
-rw-r--r--packages/frontend-embed/src/components/EmNote.vue14
-rw-r--r--packages/frontend-embed/src/components/EmNoteDetailed.vue12
-rw-r--r--packages/frontend-embed/src/components/EmNoteSimple.vue11
-rw-r--r--packages/frontend-embed/src/components/EmNoteSub.vue11
-rw-r--r--packages/frontend-embed/src/components/EmSubNoteContent.vue2
-rw-r--r--packages/frontend-embed/src/pages/note.vue2
-rw-r--r--packages/frontend-embed/src/style.scss26
-rw-r--r--packages/frontend-embed/tsconfig.json2
-rw-r--r--packages/frontend-embed/vite.config.ts17
17 files changed, 156 insertions, 71 deletions
diff --git a/packages/frontend-embed/@types/global.d.ts b/packages/frontend-embed/@types/global.d.ts
index 1025d1bedb..c8a9bf4323 100644
--- a/packages/frontend-embed/@types/global.d.ts
+++ b/packages/frontend-embed/@types/global.d.ts
@@ -6,6 +6,7 @@
type FIXME = any;
declare const _LANGS_: string[][];
+declare const _LANGS_VERSION_: string;
declare const _VERSION_: string;
declare const _ENV_: string;
declare const _DEV_: boolean;
@@ -13,6 +14,7 @@ declare const _PERF_PREFIX_: string;
declare const _DATA_TRANSFER_DRIVE_FILE_: string;
declare const _DATA_TRANSFER_DRIVE_FOLDER_: string;
declare const _DATA_TRANSFER_DECK_COLUMN_: string;
+declare const _RUFFLE_VERSION_: string;
// for dev-mode
declare const _LANGS_FULL_: string[][];
diff --git a/packages/frontend-embed/eslint.config.js b/packages/frontend-embed/eslint.config.js
index 7805256fd4..fbe91f3660 100644
--- a/packages/frontend-embed/eslint.config.js
+++ b/packages/frontend-embed/eslint.config.js
@@ -4,16 +4,19 @@ import parser from 'vue-eslint-parser';
import pluginVue from 'eslint-plugin-vue';
import pluginMisskey from '@misskey-dev/eslint-plugin';
import sharedConfig from '../shared/eslint.config.js';
+import localeRule from '../../eslint/locale.js';
+import { build as buildLocales } from '../../locales/index.js';
export default [
...sharedConfig,
{
- files: ['src/**/*.vue'],
+ files: ['{src,test,js,@types}/**/*.vue'],
...pluginMisskey.configs.typescript,
},
...pluginVue.configs['flat/recommended'],
{
- files: ['src/**/*.{ts,vue}'],
+ files: ['{src,test,js,@types}/**/*.{ts,vue}'],
+ plugins: { sharkey: { rules: { locale: localeRule } } },
languageOptions: {
globals: {
...Object.fromEntries(Object.entries(globals.node).map(([key]) => [key, 'off'])),
@@ -44,6 +47,8 @@ export default [
},
},
rules: {
+ 'sharkey/locale': ['error', buildLocales()['en-US']],
+
'@typescript-eslint/no-empty-interface': ['error', {
allowSingleExtends: true,
}],
@@ -93,4 +98,13 @@ export default [
'vue/attribute-hyphenation': ['error', 'never'],
},
},
+ {
+ ignores: [
+ "**/lib/",
+ "**/temp/",
+ "**/built/",
+ "**/coverage/",
+ "**/node_modules/",
+ ]
+ },
];
diff --git a/packages/frontend-embed/package.json b/packages/frontend-embed/package.json
index 22b59c5a92..297a06fd8a 100644
--- a/packages/frontend-embed/package.json
+++ b/packages/frontend-embed/package.json
@@ -6,22 +6,22 @@
"watch": "vite",
"build": "vite build",
"typecheck": "vue-tsc --noEmit",
- "eslint": "eslint --quiet \"src/**/*.{ts,vue}\"",
+ "eslint": "eslint --quiet \"{src,test,js,@types}/**/*.{js,jsx,ts,tsx,vue}\" --cache",
"lint": "pnpm typecheck && pnpm eslint"
},
"dependencies": {
"@discordapp/twemoji": "15.1.0",
+ "@phosphor-icons/web": "^2.0.3",
"@rollup/plugin-json": "6.1.0",
"@rollup/plugin-replace": "6.0.2",
"@rollup/pluginutils": "5.1.4",
- "@tabler/icons-webfont": "3.31.0",
+ "@transfem-org/sfm-js": "0.24.5",
"@twemoji/parser": "15.1.1",
"@vitejs/plugin-vue": "5.2.3",
"@vue/compiler-sfc": "3.5.13",
"astring": "1.9.0",
"buraha": "0.0.1",
"estree-walker": "3.0.3",
- "mfm-js": "0.24.0",
"misskey-js": "workspace:*",
"frontend-shared": "workspace:*",
"punycode.js": "2.3.1",
diff --git a/packages/frontend-embed/src/boot.ts b/packages/frontend-embed/src/boot.ts
index c1b2b58beb..7c8336ce3f 100644
--- a/packages/frontend-embed/src/boot.ts
+++ b/packages/frontend-embed/src/boot.ts
@@ -6,8 +6,6 @@
// https://vitejs.dev/config/build-options.html#build-modulepreload
import 'vite/modulepreload-polyfill';
-import '@tabler/icons-webfont/dist/tabler-icons.scss';
-
import '@/style.scss';
import { createApp, defineAsyncComponent } from 'vue';
import defaultLightTheme from '@@/themes/l-light.json5';
@@ -17,7 +15,7 @@ import { applyTheme, assertIsTheme } from '@/theme.js';
import { fetchCustomEmojis } from '@/custom-emojis.js';
import { DI } from '@/di.js';
import { serverMetadata } from '@/server-metadata.js';
-import { url, version, locale, lang, updateLocale } from '@@/js/config.js';
+import { url, version, locale, lang, updateLocale, langsVersion } from '@@/js/config.js';
import { parseEmbedParams } from '@@/js/embed-page.js';
import { postMessageToParentWindow, setIframeId } from '@/post-message.js';
import { serverContext } from '@/server-context.js';
@@ -25,7 +23,7 @@ import { i18n, updateI18n } from '@/i18n.js';
import type { Theme } from '@/theme.js';
-console.log('Misskey Embed');
+console.log('Sharkey Embed');
//#region Embedパラメータの取得・パース
const params = new URLSearchParams(location.search);
@@ -73,14 +71,14 @@ if (embedParams.colorMode === 'dark') {
//#region Detect language & fetch translations
const localeVersion = localStorage.getItem('localeVersion');
-const localeOutdated = (localeVersion == null || localeVersion !== version || locale == null);
+const localeOutdated = (localeVersion == null || localeVersion !== langsVersion || locale == null);
if (localeOutdated) {
- const res = await window.fetch(`/assets/locales/${lang}.${version}.json`);
+ const res = await window.fetch(`/assets/locales/${lang}.${langsVersion}.json`);
if (res.status === 200) {
const newLocale = await res.text();
const parsedNewLocale = JSON.parse(newLocale);
localStorage.setItem('locale', newLocale);
- localStorage.setItem('localeVersion', version);
+ localStorage.setItem('localeVersion', langsVersion);
updateLocale(parsedNewLocale);
updateI18n(parsedNewLocale);
}
@@ -119,7 +117,7 @@ app.provide(DI.embedParams, embedParams);
// https://github.com/misskey-dev/misskey/pull/8575#issuecomment-1114239210
// なぜか2回実行されることがあるため、mountするdivを1つに制限する
const rootEl = ((): HTMLElement => {
- const MISSKEY_MOUNT_DIV_ID = 'misskey_app';
+ const MISSKEY_MOUNT_DIV_ID = 'sharkey_app';
const currentRoot = document.getElementById(MISSKEY_MOUNT_DIV_ID);
diff --git a/packages/frontend-embed/src/components/EmAvatar.vue b/packages/frontend-embed/src/components/EmAvatar.vue
index 58c35c8ef0..50d46781d9 100644
--- a/packages/frontend-embed/src/components/EmAvatar.vue
+++ b/packages/frontend-embed/src/components/EmAvatar.vue
@@ -30,6 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only
rotate: getDecorationAngle(decoration),
scale: getDecorationScale(decoration),
translate: getDecorationOffset(decoration),
+ zIndex: getDecorationZIndex(decoration),
}"
alt=""
>
@@ -86,6 +87,10 @@ function getDecorationOffset(decoration: Omit<Misskey.entities.UserDetailed['ava
const offsetY = decoration.offsetY ?? 0;
return offsetX === 0 && offsetY === 0 ? undefined : `${offsetX * 100}% ${offsetY * 100}%`;
}
+
+function getDecorationZIndex(decoration: Omit<Misskey.entities.UserDetailed['avatarDecorations'][number], 'id'>) {
+ return decoration.showBelow ? '-1' : undefined;
+}
</script>
<style lang="scss" module>
diff --git a/packages/frontend-embed/src/components/EmMediaBanner.vue b/packages/frontend-embed/src/components/EmMediaBanner.vue
index cf4a4c53b5..24874940f3 100644
--- a/packages/frontend-embed/src/components/EmMediaBanner.vue
+++ b/packages/frontend-embed/src/components/EmMediaBanner.vue
@@ -7,6 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<a :href="href" target="_blank" :class="$style.root">
<div :class="$style.label">
<template v-if="media.type.startsWith('audio')"><i class="ti ti-music"></i> {{ i18n.ts.audio }}</template>
+ <template v-else-if="media.type.startsWith('application') && media.type.includes('flash')"><i class="ti ti-bolt"></i> {{ i18n.ts.flash }}</template>
<template v-else><i class="ti ti-file"></i> {{ i18n.ts.file }}</template>
</div>
<div :class="$style.go">
diff --git a/packages/frontend-embed/src/components/EmMediaImage.vue b/packages/frontend-embed/src/components/EmMediaImage.vue
index 2c96ce3215..e9126d4665 100644
--- a/packages/frontend-embed/src/components/EmMediaImage.vue
+++ b/packages/frontend-embed/src/components/EmMediaImage.vue
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div :class="[hide ? $style.hidden : $style.visible]" @click="onclick">
<a
- :title="image.name"
+ :title="image.comment || image.name"
:class="$style.imageContainer"
:href="href ?? image.url"
target="_blank"
diff --git a/packages/frontend-embed/src/components/EmMfm.ts b/packages/frontend-embed/src/components/EmMfm.ts
index 1f9ce9d4f4..d377d492e0 100644
--- a/packages/frontend-embed/src/components/EmMfm.ts
+++ b/packages/frontend-embed/src/components/EmMfm.ts
@@ -5,7 +5,7 @@
import { h, provide } from 'vue';
import type { VNode, SetupContext } from 'vue';
-import * as mfm from 'mfm-js';
+import * as mfm from '@transfem-org/sfm-js';
import * as Misskey from 'misskey-js';
import { host } from '@@/js/config.js';
import EmUrl from '@/components/EmUrl.vue';
@@ -42,6 +42,7 @@ type MfmProps = {
rootScale?: number;
nyaize?: boolean | 'respect';
parsedNodes?: mfm.MfmNode[] | null;
+ isBlock?: boolean;
};
type MfmEvents = {
@@ -51,7 +52,7 @@ type MfmEvents = {
// eslint-disable-next-line import/no-default-export
export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEvents>['emit'] }) {
const isNote = props.isNote ?? true;
- const shouldNyaize = props.nyaize ? props.nyaize === 'respect' ? props.author?.isCat : false : false;
+ const shouldNyaize = props.nyaize === 'respect' && props.author?.isCat && props.author?.speakAsCat;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (props.text == null || props.text === '') return;
@@ -71,6 +72,8 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
const useAnim = true;
+ const isBlock = props.isBlock ?? false;
+
/**
* Gen Vue Elements from MFM AST
* @param ast MFM AST
@@ -220,17 +223,46 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
case 'sparkle': {
return genEl(token.children, scale);
}
+ case 'fade': {
+ const direction = token.props.args.out
+ ? 'alternate-reverse'
+ : 'alternate';
+ const speed = validTime(token.props.args.speed) ?? '1.5s';
+ const delay = validTime(token.props.args.delay) ?? '0s';
+ const loop = safeParseFloat(token.props.args.loop) ?? 'infinite';
+ style = `animation: mfm-fade ${speed} ${delay} linear ${loop}; animation-direction: ${direction};`;
+ break;
+ }
case 'rotate': {
const degrees = safeParseFloat(token.props.args.deg) ?? 90;
style = `transform: rotate(${degrees}deg); transform-origin: center center;`;
break;
}
+ case 'followmouse': {
+ return genEl(token.children, scale);
+ }
case 'position': {
const x = safeParseFloat(token.props.args.x) ?? 0;
const y = safeParseFloat(token.props.args.y) ?? 0;
style = `transform: translateX(${x}em) translateY(${y}em);`;
break;
}
+ case 'crop': {
+ const top = Number.parseFloat(
+ (token.props.args.top ?? '0').toString(),
+ );
+ const right = Number.parseFloat(
+ (token.props.args.right ?? '0').toString(),
+ );
+ const bottom = Number.parseFloat(
+ (token.props.args.bottom ?? '0').toString(),
+ );
+ const left = Number.parseFloat(
+ (token.props.args.left ?? '0').toString(),
+ );
+ style = `clip-path: inset(${top}% ${right}% ${bottom}% ${left}%);`;
+ break;
+ }
case 'scale': {
const x = Math.min(safeParseFloat(token.props.args.x) ?? 1, 5);
const y = Math.min(safeParseFloat(token.props.args.y) ?? 1, 5);
@@ -325,63 +357,63 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
case 'center': {
return [h('div', {
style: 'text-align:center;',
- }, genEl(token.children, scale))];
+ }, h('bdi', genEl(token.children, scale)))];
}
case 'url': {
- return [h(EmUrl, {
+ return [h('bdi', h(EmUrl, {
key: Math.random(),
url: token.props.url,
rel: 'nofollow noopener',
- })];
+ }))];
}
case 'link': {
- return [h(EmLink, {
+ return [h('bdi', h(EmLink, {
key: Math.random(),
url: token.props.url,
rel: 'nofollow noopener',
- }, genEl(token.children, scale, true))];
+ }, genEl(token.children, scale, true)))];
}
case 'mention': {
- return [h(EmMention, {
+ return [h('bdi', h(EmMention, {
key: Math.random(),
host: (token.props.host == null && props.author && props.author.host != null ? props.author.host : token.props.host) ?? host,
username: token.props.username,
- })];
+ }))];
}
case 'hashtag': {
- return [h(EmA, {
+ return [h('bdi', h(EmA, {
key: Math.random(),
to: isNote ? `/tags/${encodeURIComponent(token.props.hashtag)}` : `/user-tags/${encodeURIComponent(token.props.hashtag)}`,
style: 'color:var(--MI_THEME-hashtag);',
- }, `#${token.props.hashtag}`)];
+ }, `#${token.props.hashtag}`))];
}
case 'blockCode': {
- return [h('code', {
+ return [h('bdi', { class: 'block' }, h('code', {
key: Math.random(),
lang: token.props.lang ?? undefined,
- }, token.props.code)];
+ }, token.props.code))];
}
case 'inlineCode': {
- return [h('code', {
+ return [h('bdi', h('code', {
key: Math.random(),
- }, token.props.code)];
+ }, token.props.code))];
}
case 'quote': {
if (!props.nowrap) {
- return [h('div', {
+ return [h('bdi', { class: 'block' }, h('div', {
style: QUOTE_STYLE,
- }, genEl(token.children, scale, true))];
+ }, h('bdi', genEl(token.children, scale, true))))];
} else {
return [h('span', {
style: QUOTE_STYLE,
- }, genEl(token.children, scale, true))];
+ }, h('bdi', genEl(token.children, scale, true)))];
}
}
@@ -420,21 +452,21 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
}
case 'mathInline': {
- return [h('code', token.props.formula)];
+ return [h('bdi', h('code', token.props.formula))];
}
case 'mathBlock': {
- return [h('code', token.props.formula)];
+ return [h('bdi', h('code', token.props.formula))];
}
case 'search': {
- return [h('div', {
+ return [h('bdi', h('div', {
key: Math.random(),
- }, token.props.query)];
+ }, token.props.query))];
}
case 'plain': {
- return [h('span', genEl(token.children, scale, true))];
+ return [h('bdi', h('span', genEl(token.children, scale, true)))];
}
default: {
@@ -446,8 +478,8 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
}
}).flat(Infinity) as (VNode | string)[];
- return h('span', {
+ return h('bdi', { ...( isBlock ? { class: 'block' } : {}) }, h('span', {
// https://codeday.me/jp/qa/20190424/690106.html
style: props.nowrap ? 'white-space: pre; word-wrap: normal; overflow: hidden; text-overflow: ellipsis;' : 'white-space: pre-wrap;',
- }, genEl(rootAst, props.rootScale ?? 1));
+ }, genEl(rootAst, props.rootScale ?? 1)));
}
diff --git a/packages/frontend-embed/src/components/EmNote.vue b/packages/frontend-embed/src/components/EmNote.vue
index f5b064c293..bf96c557ea 100644
--- a/packages/frontend-embed/src/components/EmNote.vue
+++ b/packages/frontend-embed/src/components/EmNote.vue
@@ -46,11 +46,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<EmNoteHeader :note="appearNote" :mini="true"/>
<EmInstanceTicker v-if="appearNote.user.instance != null" :instance="appearNote.user.instance"/>
<div style="container-type: inline-size;">
- <p v-if="appearNote.cw != null" :class="$style.cw">
- <EmMfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :nyaize="'respect'"/>
+ <p v-if="mergedCW != null" :class="$style.cw">
+ <EmMfm v-if="mergedCW != ''" style="margin-right: 8px;" :text="mergedCW" :author="appearNote.user" :nyaize="'respect'" :isBlock="true"/>
<button style="display: block; width: 100%; margin: 4px 0;" class="_buttonGray _buttonRounded" @click="showContent = !showContent">{{ showContent ? i18n.ts._cw.hide : i18n.ts._cw.show }}</button>
</p>
- <div v-show="appearNote.cw == null || showContent" :class="[{ [$style.contentCollapsed]: collapsed }]">
+ <div v-show="mergedCW == null || showContent" :class="[{ [$style.contentCollapsed]: collapsed }]">
<div :class="$style.text">
<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
<EmA v-if="appearNote.replyId" :class="$style.replyIcon" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></EmA>
@@ -61,8 +61,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:author="appearNote.user"
:nyaize="'respect'"
:emojiUrls="appearNote.emojis"
- :enableEmojiMenu="!true"
- :enableEmojiMenuReaction="true"
+ :isBlock="true"
/>
</div>
<div v-if="appearNote.files && appearNote.files.length > 0">
@@ -106,10 +105,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed, inject, ref, shallowRef } from 'vue';
-import * as mfm from 'mfm-js';
+import * as mfm from '@transfem-org/sfm-js';
import * as Misskey from 'misskey-js';
import { shouldCollapsed } from '@@/js/collapsed.js';
import { url } from '@@/js/config.js';
+import { computeMergedCw } from '@@/js/compute-merged-cw.js';
import I18n from '@/components/I18n.vue';
import EmNoteSub from '@/components/EmNoteSub.vue';
import EmNoteHeader from '@/components/EmNoteHeader.vue';
@@ -155,6 +155,8 @@ const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value
const isLong = shouldCollapsed(appearNote.value, []);
const collapsed = ref(appearNote.value.cw == null && isLong);
const isDeleted = ref(false);
+
+const mergedCW = computed(() => computeMergedCw(appearNote.value));
</script>
<style lang="scss" module>
diff --git a/packages/frontend-embed/src/components/EmNoteDetailed.vue b/packages/frontend-embed/src/components/EmNoteDetailed.vue
index b39b47c065..0961b36e35 100644
--- a/packages/frontend-embed/src/components/EmNoteDetailed.vue
+++ b/packages/frontend-embed/src/components/EmNoteDetailed.vue
@@ -58,11 +58,11 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</header>
<div :class="[$style.noteContent, { [$style.contentCollapsed]: collapsed }]">
- <p v-if="appearNote.cw != null" :class="$style.cw">
- <EmMfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :nyaize="'respect'"/>
+ <p v-if="mergedCW != null" :class="$style.cw">
+ <EmMfm v-if="mergedCW != ''" style="margin-right: 8px;" :text="mergedCW" :author="appearNote.user" :nyaize="'respect'" :isBlock="true"/>
<button style="display: block; width: 100%; margin: 4px 0;" class="_buttonGray _buttonRounded" @click="showContent = !showContent">{{ showContent ? i18n.ts._cw.hide : i18n.ts._cw.show }}</button>
</p>
- <div v-show="appearNote.cw == null || showContent">
+ <div v-show="mergedCW == null || showContent">
<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
<EmA v-if="appearNote.replyId" :class="$style.noteReplyTarget" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></EmA>
<EmMfm
@@ -72,6 +72,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:author="appearNote.user"
:nyaize="'respect'"
:emojiUrls="appearNote.emojis"
+ :isBlock="true"
/>
<a v-if="appearNote.renote != null" :class="$style.rn">RN:</a>
<div v-if="appearNote.files && appearNote.files.length > 0">
@@ -127,8 +128,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed, inject, ref } from 'vue';
-import * as mfm from 'mfm-js';
+import * as mfm from '@transfem-org/sfm-js';
import * as Misskey from 'misskey-js';
+import { computeMergedCw } from '@@/js/compute-merged-cw.js';
import I18n from '@/components/I18n.vue';
import EmMediaList from '@/components/EmMediaList.vue';
import EmNoteSub from '@/components/EmNoteSub.vue';
@@ -174,6 +176,8 @@ const isDeleted = ref(false);
const parsed = appearNote.value.text ? mfm.parse(appearNote.value.text) : null;
const isLong = shouldCollapsed(appearNote.value, []);
const collapsed = ref(appearNote.value.cw == null && isLong);
+
+const mergedCW = computed(() => computeMergedCw(appearNote.value));
</script>
<style lang="scss" module>
diff --git a/packages/frontend-embed/src/components/EmNoteSimple.vue b/packages/frontend-embed/src/components/EmNoteSimple.vue
index b9aaf3fa4a..688758edb6 100644
--- a/packages/frontend-embed/src/components/EmNoteSimple.vue
+++ b/packages/frontend-embed/src/components/EmNoteSimple.vue
@@ -9,11 +9,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.main">
<EmNoteHeader :class="$style.header" :note="note" :mini="true"/>
<div>
- <p v-if="note.cw != null" :class="$style.cw">
- <EmMfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :author="note.user" :nyaize="'respect'" :emojiUrls="note.emojis"/>
+ <p v-if="mergedCW != null" :class="$style.cw">
+ <EmMfm v-if="mergedCW != ''" style="margin-right: 8px;" :text="mergedCW" :author="note.user" :nyaize="'respect'" :emojiUrls="note.emojis" :isBlock="true"/>
<button style="display: block; width: 100%;" class="_buttonGray _buttonRounded" @click="showContent = !showContent">{{ showContent ? i18n.ts._cw.hide : i18n.ts._cw.show }}</button>
</p>
- <div v-show="note.cw == null || showContent">
+ <div v-show="mergedCW == null || showContent">
<EmSubNoteContent :class="$style.text" :note="note"/>
</div>
</div>
@@ -22,8 +22,9 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { ref } from 'vue';
+import { computed, ref } from 'vue';
import * as Misskey from 'misskey-js';
+import { computeMergedCw } from '@@/js/compute-merged-cw.js';
import { i18n } from '@/i18n.js';
import EmAvatar from '@/components/EmAvatar.vue';
import EmNoteHeader from '@/components/EmNoteHeader.vue';
@@ -35,6 +36,8 @@ const props = defineProps<{
}>();
const showContent = ref(false);
+
+const mergedCW = computed(() => computeMergedCw(props.note));
</script>
<style lang="scss" module>
diff --git a/packages/frontend-embed/src/components/EmNoteSub.vue b/packages/frontend-embed/src/components/EmNoteSub.vue
index 59be8608e0..629f0bffcd 100644
--- a/packages/frontend-embed/src/components/EmNoteSub.vue
+++ b/packages/frontend-embed/src/components/EmNoteSub.vue
@@ -11,11 +11,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.body">
<EmNoteHeader :class="$style.header" :note="note" :mini="true"/>
<div>
- <p v-if="note.cw != null" :class="$style.cw">
- <EmMfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :author="note.user" :nyaize="'respect'"/>
+ <p v-if="mergedCW != null" :class="$style.cw">
+ <EmMfm v-if="mergedCW != ''" style="margin-right: 8px;" :text="mergedCW" :author="note.user" :nyaize="'respect'" :isBlock="true"/>
<button style="display: block; width: 100%;" class="_buttonGray _buttonRounded" @click="showContent = !showContent">{{ showContent ? i18n.ts._cw.hide : i18n.ts._cw.show }}</button>
</p>
- <div v-show="note.cw == null || showContent">
+ <div v-show="mergedCW == null || showContent">
<EmSubNoteContent :class="$style.text" :note="note"/>
</div>
</div>
@@ -31,8 +31,9 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { ref } from 'vue';
+import { computed, ref } from 'vue';
import * as Misskey from 'misskey-js';
+import { computeMergedCw } from '@@/js/compute-merged-cw.js';
import EmA from '@/components/EmA.vue';
import EmAvatar from '@/components/EmAvatar.vue';
import EmNoteHeader from '@/components/EmNoteHeader.vue';
@@ -55,6 +56,8 @@ const props = withDefaults(defineProps<{
const showContent = ref(false);
const replies = ref<Misskey.entities.Note[]>([]);
+const mergedCW = computed(() => computeMergedCw(props.note));
+
if (props.detail) {
misskeyApi('notes/children', {
noteId: props.note.id,
diff --git a/packages/frontend-embed/src/components/EmSubNoteContent.vue b/packages/frontend-embed/src/components/EmSubNoteContent.vue
index 61815ddfd8..e9acbcb293 100644
--- a/packages/frontend-embed/src/components/EmSubNoteContent.vue
+++ b/packages/frontend-embed/src/components/EmSubNoteContent.vue
@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<span v-if="note.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
<span v-if="note.deletedAt" style="opacity: 0.5">({{ i18n.ts.deletedNote }})</span>
<EmA v-if="note.replyId" :class="$style.reply" :to="`/notes/${note.replyId}`"><i class="ti ti-arrow-back-up"></i></EmA>
- <EmMfm v-if="note.text" :text="note.text" :author="note.user" :nyaize="'respect'" :emojiUrls="note.emojis"/>
+ <EmMfm v-if="note.text" :text="note.text" :author="note.user" :nyaize="'respect'" :emojiUrls="note.emojis" :isBlock="true"/>
<EmA v-if="note.renoteId" :class="$style.rp" :to="`/notes/${note.renoteId}`">RN: ...</EmA>
</div>
<details v-if="note.files && note.files.length > 0">
diff --git a/packages/frontend-embed/src/pages/note.vue b/packages/frontend-embed/src/pages/note.vue
index e879430286..4e0ea9c145 100644
--- a/packages/frontend-embed/src/pages/note.vue
+++ b/packages/frontend-embed/src/pages/note.vue
@@ -17,7 +17,7 @@ import EmNoteDetailed from '@/components/EmNoteDetailed.vue';
import XNotFound from '@/pages/not-found.vue';
import { DI } from '@/di.js';
import { misskeyApi } from '@/misskey-api.js';
-import { assertServerContext } from '@/server-context';
+import { assertServerContext } from '@/server-context.js';
const props = defineProps<{
noteId: string;
diff --git a/packages/frontend-embed/src/style.scss b/packages/frontend-embed/src/style.scss
index b67f929933..ba3238cd4c 100644
--- a/packages/frontend-embed/src/style.scss
+++ b/packages/frontend-embed/src/style.scss
@@ -63,7 +63,7 @@ html, body {
scroll-behavior: smooth;
}
-#misskey_app {
+#sharkey_app {
height: 100%;
}
@@ -310,6 +310,8 @@ rt {
// MFM -----------------------------
+bdi.block { display: block }
+
._mfm_blur_ {
filter: blur(6px);
transition: filter 0.3s;
@@ -451,3 +453,25 @@ rt {
0% { filter: hue-rotate(0deg) contrast(150%) saturate(150%); }
100% { filter: hue-rotate(360deg) contrast(150%) saturate(150%); }
}
+
+@keyframes mfm-fade {
+ 0% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
+}
+
+@media (prefers-reduced-motion) {
+ @keyframes mfm-spin { 0% { transform: none; filter: none; opacity: 1 } }
+ @keyframes mfm-spinX { 0% { transform: none; filter: none; opacity: 1 } }
+ @keyframes mfm-spinY { 0% { transform: none; filter: none; opacity: 1 } }
+ @keyframes mfm-jump { 0% { transform: none; filter: none; opacity: 1 } }
+ @keyframes mfm-bounce { 0% { transform: none; filter: none; opacity: 1 } }
+ @keyframes mfm-twitch { 0% { transform: none; filter: none; opacity: 1 } }
+ @keyframes mfm-shake { 0% { transform: none; filter: none; opacity: 1 } }
+ @keyframes mfm-rubberBand { 0% { transform: none; filter: none; opacity: 1 } }
+ @keyframes mfm-rainbow { 0% { transform: none; filter: none; opacity: 1 } }
+ @keyframes mfm-fade { 0% { transform: none; filter: none; opacity: 1 } }
+}
diff --git a/packages/frontend-embed/tsconfig.json b/packages/frontend-embed/tsconfig.json
index 63e637c844..e0ee08188d 100644
--- a/packages/frontend-embed/tsconfig.json
+++ b/packages/frontend-embed/tsconfig.json
@@ -22,6 +22,7 @@
"isolatedModules": true,
"useDefineForClassFields": true,
"verbatimModuleSyntax": true,
+ "skipLibCheck": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
@@ -50,6 +51,7 @@
"./@types/**/*.ts"
],
"exclude": [
+ "node_modules",
".storybook/**/*"
]
}
diff --git a/packages/frontend-embed/vite.config.ts b/packages/frontend-embed/vite.config.ts
index 3d628c800e..1cd47b2754 100644
--- a/packages/frontend-embed/vite.config.ts
+++ b/packages/frontend-embed/vite.config.ts
@@ -1,16 +1,13 @@
import path from 'path';
import pluginVue from '@vitejs/plugin-vue';
import { type UserConfig, defineConfig } from 'vite';
-import * as yaml from 'js-yaml';
-import { promises as fsp } from 'fs';
+import { localesVersion } from '../../locales/version.js';
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 url = process.env.NODE_ENV === 'development' ? yaml.load(await fsp.readFile('../../.config/default.yml', 'utf-8')).url : null;
-const host = url ? (new URL(url)).hostname : undefined;
+import { pluginReplaceIcons } from '../frontend/vite.replaceIcons.js';
const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.json', '.json5', '.svg', '.sass', '.scss', '.css', '.vue'];
@@ -67,7 +64,6 @@ export function getConfig(): UserConfig {
base: '/embed_vite/',
server: {
- host,
port: 5174,
hmr: {
// バックエンド経由での起動時、Viteは5174経由でアセットを参照していると思い込んでいるが実際は3000から配信される
@@ -80,6 +76,7 @@ export function getConfig(): UserConfig {
plugins: [
pluginVue(),
pluginJson5(),
+ ...pluginReplaceIcons(),
],
resolve: {
@@ -96,11 +93,8 @@ export function getConfig(): UserConfig {
modules: {
generateScopedName(name, filename, _css): string {
const id = (path.relative(__dirname, filename.split('?')[0]) + '-' + name).replace(/[\\\/\.\?&=]/g, '-').replace(/(src-|vue-)/g, '');
- if (process.env.NODE_ENV === 'production') {
- return 'x' + toBase62(hash(id)).substring(0, 4);
- } else {
- return id;
- }
+ const shortId = id.replace(/^(components(-global)?|widgets|ui(-_common_)?)-/, '');
+ return shortId + '-' + toBase62(hash(id)).substring(0, 4);
},
},
preprocessorOptions: {
@@ -113,6 +107,7 @@ export function getConfig(): UserConfig {
define: {
_VERSION_: JSON.stringify(meta.version),
_LANGS_: JSON.stringify(Object.entries(locales).map(([k, v]) => [k, v._lang_])),
+ _LANGS_VERSION_: JSON.stringify(localesVersion),
_ENV_: JSON.stringify(process.env.NODE_ENV),
_DEV_: process.env.NODE_ENV !== 'production',
_PERF_PREFIX_: JSON.stringify('Misskey:'),