upd: play animated MFM button
This commit is contained in:
parent
5698a0e0fb
commit
2dfd8b2842
6 changed files with 93 additions and 4 deletions
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
29
packages/frontend/src/scripts/check-animated-mfm.ts
Normal file
29
packages/frontend/src/scripts/check-animated-mfm.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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',
|
||||||
|
|
Loading…
Reference in a new issue