upd: play animated MFM button

This commit is contained in:
Mar0xy 2023-11-06 00:26:23 +01:00
parent 5698a0e0fb
commit 2dfd8b2842
No known key found for this signature in database
GPG key ID: 56569BBE47D2C828
6 changed files with 93 additions and 4 deletions

View file

@ -70,6 +70,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:emojiUrls="appearNote.emojis" :emojiUrls="appearNote.emojis"
:enableEmojiMenu="true" :enableEmojiMenu="true"
:enableEmojiMenuReaction="true" :enableEmojiMenuReaction="true"
:isAnim="allowAnim"
/> />
<div v-if="translating || translation" :class="$style.translation"> <div v-if="translating || translation" :class="$style.translation">
<MkLoading v-if="translating" mini/> <MkLoading v-if="translating" mini/>
@ -78,6 +79,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'account'" :emojiUrls="appearNote.emojis"/> <Mfm :text="translation.text" :author="appearNote.user" :nyaize="'account'" :emojiUrls="appearNote.emojis"/>
</div> </div>
</div> </div>
<MkButton v-if="!allowAnim && animated" :small="true" @click="animatedMFM()" v-on:click.stop><i class="ph-play ph-bold ph-lg "></i> Animated MFM</MkButton>
<MkButton v-else-if="!defaultStore.state.animatedMfm && allowAnim && animated" :small="true" @click="animatedMFM()" v-on:click.stop><i class="ph-stop ph-bold ph-lg "></i> Animated MFM</MkButton>
</div> </div>
<div v-if="appearNote.files.length > 0"> <div v-if="appearNote.files.length > 0">
<MkMediaList :mediaList="appearNote.files" v-on:click.stop/> <MkMediaList :mediaList="appearNote.files" v-on:click.stop/>
@ -175,6 +178,7 @@ import MkPoll from '@/components/MkPoll.vue';
import MkUsersTooltip from '@/components/MkUsersTooltip.vue'; import MkUsersTooltip from '@/components/MkUsersTooltip.vue';
import MkUrlPreview from '@/components/MkUrlPreview.vue'; import MkUrlPreview from '@/components/MkUrlPreview.vue';
import MkInstanceTicker from '@/components/MkInstanceTicker.vue'; import MkInstanceTicker from '@/components/MkInstanceTicker.vue';
import MkButton from '@/components/MkButton.vue';
import { pleaseLogin } from '@/scripts/please-login.js'; import { pleaseLogin } from '@/scripts/please-login.js';
import { focusPrev, focusNext } from '@/scripts/focus.js'; import { focusPrev, focusNext } from '@/scripts/focus.js';
import { checkWordMute } from '@/scripts/check-word-mute.js'; import { checkWordMute } from '@/scripts/check-word-mute.js';
@ -183,9 +187,10 @@ import * as os from '@/os.js';
import { defaultStore, noteViewInterruptors } from '@/store.js'; import { defaultStore, noteViewInterruptors } from '@/store.js';
import { reactionPicker } from '@/scripts/reaction-picker.js'; import { reactionPicker } from '@/scripts/reaction-picker.js';
import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm.js'; import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm.js';
import { checkAnimationFromMfm } from '@/scripts/check-animated-mfm.js';
import { $i } from '@/account.js'; import { $i } from '@/account.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { getAbuseNoteMenu, getCopyNoteLinkMenu, getNoteClipMenu, getNoteMenu, getRenoteMenu } from '@/scripts/get-note-menu.js'; import { getAbuseNoteMenu, getCopyNoteLinkMenu, getNoteClipMenu, getNoteMenu } from '@/scripts/get-note-menu.js';
import { getNoteVersionsMenu } from '@/scripts/get-note-versions-menu.js'; import { getNoteVersionsMenu } from '@/scripts/get-note-versions-menu.js';
import { useNoteCapture } from '@/scripts/use-note-capture.js'; import { useNoteCapture } from '@/scripts/use-note-capture.js';
import { deepClone } from '@/scripts/clone.js'; import { deepClone } from '@/scripts/clone.js';
@ -261,6 +266,8 @@ const isMyRenote = $i && ($i.id === note.userId);
const showContent = ref(false); const showContent = ref(false);
const parsed = $computed(() => appearNote.text ? mfm.parse(appearNote.text) : null); const parsed = $computed(() => appearNote.text ? mfm.parse(appearNote.text) : null);
const urls = parsed ? extractUrlFromMfm(parsed) : null; const urls = parsed ? extractUrlFromMfm(parsed) : null;
const animated = $computed(() => parsed ? checkAnimationFromMfm(parsed) : null);
const allowAnim = ref(defaultStore.state.advancedMfm && defaultStore.state.animatedMfm ? true : false);
const isLong = shouldCollapsed(appearNote, urls ?? []); const isLong = shouldCollapsed(appearNote, urls ?? []);
const collapsed = ref(appearNote.cw == null && isLong); const collapsed = ref(appearNote.cw == null && isLong);
const isDeleted = ref(false); const isDeleted = ref(false);
@ -700,6 +707,18 @@ function showRenoteMenu(viaKeyboard = false): void {
} }
} }
function animatedMFM() {
if (allowAnim.value) {
allowAnim.value = false;
} else {
os.confirm({
type: 'warning',
text: 'Animated MFMs could include flashing lights and fast moving text/emojis.',
okText: 'Animate',
}).then((res) => { if (!res.canceled) allowAnim.value = true; });
}
}
function focus() { function focus() {
el.value.focus(); el.value.focus();
} }

View file

@ -83,6 +83,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:emojiUrls="appearNote.emojis" :emojiUrls="appearNote.emojis"
:enableEmojiMenu="true" :enableEmojiMenu="true"
:enableEmojiMenuReaction="true" :enableEmojiMenuReaction="true"
:isAnim="allowAnim"
/> />
<a v-if="appearNote.renote != null" :class="$style.rn">RN:</a> <a v-if="appearNote.renote != null" :class="$style.rn">RN:</a>
<div v-if="translating || translation" :class="$style.translation"> <div v-if="translating || translation" :class="$style.translation">
@ -92,6 +93,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'account'" :emojiUrls="appearNote.emojis"/> <Mfm :text="translation.text" :author="appearNote.user" :nyaize="'account'" :emojiUrls="appearNote.emojis"/>
</div> </div>
</div> </div>
<MkButton v-if="!allowAnim && animated" :small="true" @click="animatedMFM()" v-on:click.stop><i class="ph-play ph-bold ph-lg "></i> Animated MFM</MkButton>
<MkButton v-else-if="!defaultStore.state.animatedMfm && allowAnim && animated" :small="true" @click="animatedMFM()" v-on:click.stop><i class="ph-stop ph-bold ph-lg "></i> Animated MFM</MkButton>
<div v-if="appearNote.files.length > 0"> <div v-if="appearNote.files.length > 0">
<MkMediaList :mediaList="appearNote.files"/> <MkMediaList :mediaList="appearNote.files"/>
</div> </div>
@ -247,6 +250,7 @@ import { deepClone } from '@/scripts/clone.js';
import { useTooltip } from '@/scripts/use-tooltip.js'; import { useTooltip } from '@/scripts/use-tooltip.js';
import { claimAchievement } from '@/scripts/achievements.js'; import { claimAchievement } from '@/scripts/achievements.js';
import { MenuItem } from '@/types/menu.js'; import { MenuItem } from '@/types/menu.js';
import { checkAnimationFromMfm } from '@/scripts/check-animated-mfm.js';
import MkRippleEffect from '@/components/MkRippleEffect.vue'; import MkRippleEffect from '@/components/MkRippleEffect.vue';
import { showMovedDialog } from '@/scripts/show-moved-dialog.js'; import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
import MkUserCardMini from '@/components/MkUserCardMini.vue'; import MkUserCardMini from '@/components/MkUserCardMini.vue';
@ -306,6 +310,8 @@ const translation = ref(null);
const translating = ref(false); const translating = ref(false);
const parsed = $computed(() => appearNote.text ? mfm.parse(appearNote.text) : null); const parsed = $computed(() => appearNote.text ? mfm.parse(appearNote.text) : null);
const urls = parsed ? extractUrlFromMfm(parsed) : null; const urls = parsed ? extractUrlFromMfm(parsed) : null;
const animated = $computed(() => parsed ? checkAnimationFromMfm(parsed) : null);
const allowAnim = ref(defaultStore.state.advancedMfm && defaultStore.state.animatedMfm ? true : false);
const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.user.instance); const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.user.instance);
const conversation = ref<Misskey.entities.Note[]>([]); const conversation = ref<Misskey.entities.Note[]>([]);
const replies = ref<Misskey.entities.Note[]>([]); const replies = ref<Misskey.entities.Note[]>([]);
@ -734,6 +740,18 @@ function loadConversation() {
} }
if (appearNote.reply && appearNote.reply.replyId && defaultStore.state.autoloadConversation) loadConversation(); if (appearNote.reply && appearNote.reply.replyId && defaultStore.state.autoloadConversation) loadConversation();
function animatedMFM() {
if (allowAnim.value) {
allowAnim.value = false;
} else {
os.confirm({
type: 'warning',
text: 'Animated MFMs could include flashing lights and fast moving text/emojis.',
okText: 'Animate',
}).then((res) => { if (!res.canceled) allowAnim.value = true; });
}
}
</script> </script>
<style lang="scss" module> <style lang="scss" module>

View file

@ -9,7 +9,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<span v-if="note.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span> <span v-if="note.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
<span v-if="note.deletedAt" style="opacity: 0.5">({{ i18n.ts.deleted }})</span> <span v-if="note.deletedAt" style="opacity: 0.5">({{ i18n.ts.deleted }})</span>
<MkA v-if="note.replyId" :class="$style.reply" :to="`/notes/${note.replyId}`" v-on:click.stop><i class="ph-arrow-bend-left-up ph-bold ph-lg"></i></MkA> <MkA v-if="note.replyId" :class="$style.reply" :to="`/notes/${note.replyId}`" v-on:click.stop><i class="ph-arrow-bend-left-up ph-bold ph-lg"></i></MkA>
<Mfm v-if="note.text" :text="note.text" :author="note.user" :nyaize="'account'" :emojiUrls="note.emojis"/> <Mfm v-if="note.text" :text="note.text" :author="note.user" :nyaize="'account'" :isAnim="allowAnim" :emojiUrls="note.emojis"/>
<MkButton v-if="!allowAnim && animated && !hideFiles" :small="true" @click="animatedMFM()" v-on:click.stop><i class="ph-play ph-bold ph-lg "></i> Animated MFM</MkButton>
<MkButton v-else-if="!defaultStore.state.animatedMfm && allowAnim && animated && !hideFiles" :small="true" @click="animatedMFM()" v-on:click.stop><i class="ph-stop ph-bold ph-lg "></i> Animated MFM</MkButton>
<div v-if="note.text && translating || note.text && translation" :class="$style.translation"> <div v-if="note.text && translating || note.text && translation" :class="$style.translation">
<MkLoading v-if="translating" mini/> <MkLoading v-if="translating" mini/>
<div v-else> <div v-else>
@ -39,13 +41,17 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { } from 'vue'; import { } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import * as mfm from 'mfm-js';
import MkMediaList from '@/components/MkMediaList.vue'; import MkMediaList from '@/components/MkMediaList.vue';
import MkPoll from '@/components/MkPoll.vue'; import MkPoll from '@/components/MkPoll.vue';
import MkButton from '@/components/MkButton.vue';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { $i } from '@/account.js'; import { $i } from '@/account.js';
import { shouldCollapsed } from '@/scripts/collapsed.js'; import { shouldCollapsed } from '@/scripts/collapsed.js';
import { defaultStore } from '@/store.js'; import { defaultStore } from '@/store.js';
import { useRouter } from '@/router.js'; import { useRouter } from '@/router.js';
import * as os from '@/os.js';
import { checkAnimationFromMfm } from '@/scripts/check-animated-mfm.js';
const props = defineProps<{ const props = defineProps<{
note: Misskey.entities.Note; note: Misskey.entities.Note;
@ -60,8 +66,24 @@ function noteclick(id: string) {
router.push(`/notes/${id}`); router.push(`/notes/${id}`);
} }
const parsed = $computed(() => props.note.text ? mfm.parse(props.note.text) : null);
const animated = $computed(() => parsed ? checkAnimationFromMfm(parsed) : null);
let allowAnim = $ref(defaultStore.state.advancedMfm && defaultStore.state.animatedMfm ? true : false);
const isLong = shouldCollapsed(props.note, []); const isLong = shouldCollapsed(props.note, []);
function animatedMFM() {
if (allowAnim) {
allowAnim = false;
} else {
os.confirm({
type: 'warning',
text: 'Animated MFMs could include flashing lights and fast moving text/emojis.',
okText: 'Animate',
}).then((res) => { if (!res.canceled) allowAnim = true; });
}
}
const collapsed = $ref(isLong); const collapsed = $ref(isLong);
</script> </script>

View file

@ -40,6 +40,7 @@ type MfmProps = {
parsedNodes?: mfm.MfmNode[] | null; parsedNodes?: mfm.MfmNode[] | null;
enableEmojiMenu?: boolean; enableEmojiMenu?: boolean;
enableEmojiMenuReaction?: boolean; enableEmojiMenuReaction?: boolean;
isAnim?: boolean;
}; };
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export
@ -57,7 +58,7 @@ export default function(props: MfmProps) {
return t.match(/^[0-9.]+s$/) ? t : null; return t.match(/^[0-9.]+s$/) ? t : null;
}; };
const useAnim = defaultStore.state.advancedMfm && defaultStore.state.animatedMfm; const useAnim = defaultStore.state.advancedMfm && defaultStore.state.animatedMfm ? true : props.isAnim ? true : false;
/** /**
* Gen Vue Elements from MFM AST * Gen Vue Elements from MFM AST

View file

@ -0,0 +1,29 @@
import * as mfm from 'mfm-js';
export function checkAnimationFromMfm(nodes: mfm.MfmNode[]): boolean {
const animatedNodes = mfm.extract(nodes, (node) => {
if (node.type === 'fn') {
if (node.props.name === 'tada' ||
node.props.name === 'jelly' ||
node.props.name === 'twitch' ||
node.props.name === 'shake' ||
node.props.name === 'spin' ||
node.props.name === 'jump' ||
node.props.name === 'bounce' ||
node.props.name === 'rainbow' ||
node.props.name === 'sparkle') {
return true;
} else {
return false;
}
} else {
return false;
}
});
if (animatedNodes.length > 0) {
return true;
} else {
return false;
}
}

View file

@ -200,7 +200,7 @@ export const defaultStore = markRaw(new Storage('base', {
}, },
animatedMfm: { animatedMfm: {
where: 'device', where: 'device',
default: true, default: false,
}, },
advancedMfm: { advancedMfm: {
where: 'device', where: 'device',