Feat: クリックイベントを発生させるMFM構文を追加 (#12798)

* Update MkMisskeyFlavoredMarkdown.ts

* fix MkMisskeyFlavoredMarkdown.ts

* Update MkAsUi.vue

* Update ui.ts

* Fix MkMisskeyFlavoredMarkdown.ts

* Update CHANGELOG.md

* fix ui.ts

* revert CHANGELOG.md

* Update CHANGELOG.md
This commit is contained in:
FineArchs 2023-12-25 18:03:06 +09:00 committed by Marie
parent adf13f79bd
commit 9c17e0f976
No known key found for this signature in database
GPG key ID: 56569BBE47D2C828
4 changed files with 21 additions and 3 deletions

View file

@ -20,6 +20,7 @@
### Client ### Client
- Fix: 一部のモデログ(logYellowでの表示対象)について、表示の色が変わらない問題を修正 - Fix: 一部のモデログ(logYellowでの表示対象)について、表示の色が変わらない問題を修正
- Feat: AiScript専用のMFM構文`$[clickable.ev=EVENTNAME ...]`を追加。`Mk:C:mfm`のオプション`onClickEv`に関数を渡すと、クリック時に`EVENTNAME`を引数にして呼び出す
### Server ### Server
- Enhance: センシティブワードの設定がハッシュタグトレンドにも適用されるようになりました - Enhance: センシティブワードの設定がハッシュタグトレンドにも適用されるようになりました

View file

@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
</div> </div>
<span v-else-if="c.type === 'text'" :class="{ [$style.fontSerif]: c.font === 'serif', [$style.fontMonospace]: c.font === 'monospace' }" :style="{ fontSize: c.size ? `${c.size * 100}%` : null, fontWeight: c.bold ? 'bold' : null, color: c.color ?? null }">{{ c.text }}</span> <span v-else-if="c.type === 'text'" :class="{ [$style.fontSerif]: c.font === 'serif', [$style.fontMonospace]: c.font === 'monospace' }" :style="{ fontSize: c.size ? `${c.size * 100}%` : null, fontWeight: c.bold ? 'bold' : null, color: c.color ?? null }">{{ c.text }}</span>
<Mfm v-else-if="c.type === 'mfm'" :class="{ [$style.fontSerif]: c.font === 'serif', [$style.fontMonospace]: c.font === 'monospace' }" :style="{ fontSize: c.size ? `${c.size * 100}%` : null, fontWeight: c.bold ? 'bold' : null, color: c.color ?? null }" :text="c.text"/> <Mfm v-else-if="c.type === 'mfm'" :class="{ [$style.fontSerif]: c.font === 'serif', [$style.fontMonospace]: c.font === 'monospace' }" :style="{ fontSize: c.size ? `${c.size * 100}%` : null, fontWeight: c.bold ? 'bold' : null, color: c.color ?? null }" :text="c.text" @clickEv="c.onClickEv"/>
<MkButton v-else-if="c.type === 'button'" :primary="c.primary" :rounded="c.rounded" :disabled="c.disabled" :small="size === 'small'" inline @click="c.onClick">{{ c.text }}</MkButton> <MkButton v-else-if="c.type === 'button'" :primary="c.primary" :rounded="c.rounded" :disabled="c.disabled" :small="size === 'small'" inline @click="c.onClick">{{ c.text }}</MkButton>
<div v-else-if="c.type === 'buttons'" class="_buttons" :style="{ justifyContent: align }"> <div v-else-if="c.type === 'buttons'" class="_buttons" :style="{ justifyContent: align }">
<MkButton v-for="button in c.buttons" :primary="button.primary" :rounded="button.rounded" :disabled="button.disabled" inline :small="size === 'small'" @click="button.onClick">{{ button.text }}</MkButton> <MkButton v-for="button in c.buttons" :primary="button.primary" :rounded="button.rounded" :disabled="button.disabled" inline :small="size === 'small'" @click="button.onClick">{{ button.text }}</MkButton>

View file

@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { VNode, h, defineAsyncComponent } from 'vue'; import { VNode, h, defineAsyncComponent, SetupContext } from 'vue';
import * as mfm from '@sharkey/sfm-js'; import * as mfm from '@sharkey/sfm-js';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import MkUrl from '@/components/global/MkUrl.vue'; import MkUrl from '@/components/global/MkUrl.vue';
@ -44,8 +44,12 @@ type MfmProps = {
isAnim?: boolean; isAnim?: boolean;
}; };
type MfmEvents = {
clickEv(id: string): void;
};
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export
export default function(props: MfmProps) { export default function(props: MfmProps, context: SetupContext<MfmEvents>) {
const isNote = props.isNote ?? true; const isNote = props.isNote ?? true;
const shouldNyaize = props.nyaize ? props.nyaize === 'respect' ? props.author?.isCat ? props.author?.speakAsCat : false : false : false; const shouldNyaize = props.nyaize ? props.nyaize === 'respect' ? props.author?.isCat ? props.author?.speakAsCat : false : false : false;
@ -290,6 +294,13 @@ export default function(props: MfmProps) {
}), }),
]); ]);
} }
case 'clickable': {
return h('span', { onClick(ev: MouseEvent): void {
ev.stopPropagation();
ev.preventDefault();
context.emit('clickEv', token.props.args.ev ?? '');
} }, genEl(token.children, scale));
}
} }
if (style === undefined) { if (style === undefined) {
return h('span', {}, ['$[', token.props.name, ' ', ...genEl(token.children, scale), ']']); return h('span', {}, ['$[', token.props.name, ' ', ...genEl(token.children, scale), ']']);

View file

@ -47,6 +47,7 @@ export type AsUiMfm = AsUiComponentBase & {
bold?: boolean; bold?: boolean;
color?: string; color?: string;
font?: 'serif' | 'sans-serif' | 'monospace'; font?: 'serif' | 'sans-serif' | 'monospace';
onClickEv?: (evId: string) => void
}; };
export type AsUiButton = AsUiComponentBase & { export type AsUiButton = AsUiComponentBase & {
@ -230,6 +231,8 @@ function getMfmOptions(def: values.Value | undefined): Omit<AsUiMfm, 'id' | 'typ
if (color) utils.assertString(color); if (color) utils.assertString(color);
const font = def.value.get('font'); const font = def.value.get('font');
if (font) utils.assertString(font); if (font) utils.assertString(font);
const onClickEv = def.value.get('onClickEv');
if (onClickEv) utils.assertFunction(onClickEv);
return { return {
text: text?.value, text: text?.value,
@ -237,6 +240,9 @@ function getMfmOptions(def: values.Value | undefined): Omit<AsUiMfm, 'id' | 'typ
bold: bold?.value, bold: bold?.value,
color: color?.value, color: color?.value,
font: font?.value, font: font?.value,
onClickEv: (evId: string) => {
if (onClickEv) call(onClickEv, values.STR(evId));
},
}; };
} }