mirror of
https://git.joinsharkey.org/Sharkey/Sharkey.git
synced 2024-11-22 21:23:08 +02:00
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"
|
||||
:enableEmojiMenu="true"
|
||||
:enableEmojiMenuReaction="true"
|
||||
:isAnim="allowAnim"
|
||||
/>
|
||||
<div v-if="translating || translation" :class="$style.translation">
|
||||
<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"/>
|
||||
</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 v-if="appearNote.files.length > 0">
|
||||
<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 MkUrlPreview from '@/components/MkUrlPreview.vue';
|
||||
import MkInstanceTicker from '@/components/MkInstanceTicker.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { pleaseLogin } from '@/scripts/please-login.js';
|
||||
import { focusPrev, focusNext } from '@/scripts/focus.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 { reactionPicker } from '@/scripts/reaction-picker.js';
|
||||
import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm.js';
|
||||
import { checkAnimationFromMfm } from '@/scripts/check-animated-mfm.js';
|
||||
import { $i } from '@/account.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 { useNoteCapture } from '@/scripts/use-note-capture.js';
|
||||
import { deepClone } from '@/scripts/clone.js';
|
||||
|
@ -261,6 +266,8 @@ const isMyRenote = $i && ($i.id === note.userId);
|
|||
const showContent = ref(false);
|
||||
const parsed = $computed(() => appearNote.text ? mfm.parse(appearNote.text) : 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 collapsed = ref(appearNote.cw == null && isLong);
|
||||
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() {
|
||||
el.value.focus();
|
||||
}
|
||||
|
|
|
@ -83,6 +83,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
:emojiUrls="appearNote.emojis"
|
||||
:enableEmojiMenu="true"
|
||||
:enableEmojiMenuReaction="true"
|
||||
:isAnim="allowAnim"
|
||||
/>
|
||||
<a v-if="appearNote.renote != null" :class="$style.rn">RN:</a>
|
||||
<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"/>
|
||||
</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">
|
||||
<MkMediaList :mediaList="appearNote.files"/>
|
||||
</div>
|
||||
|
@ -247,6 +250,7 @@ import { deepClone } from '@/scripts/clone.js';
|
|||
import { useTooltip } from '@/scripts/use-tooltip.js';
|
||||
import { claimAchievement } from '@/scripts/achievements.js';
|
||||
import { MenuItem } from '@/types/menu.js';
|
||||
import { checkAnimationFromMfm } from '@/scripts/check-animated-mfm.js';
|
||||
import MkRippleEffect from '@/components/MkRippleEffect.vue';
|
||||
import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
|
||||
import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
||||
|
@ -306,6 +310,8 @@ const translation = ref(null);
|
|||
const translating = ref(false);
|
||||
const parsed = $computed(() => appearNote.text ? mfm.parse(appearNote.text) : 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 conversation = 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();
|
||||
|
||||
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>
|
||||
|
||||
<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.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>
|
||||
<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">
|
||||
<MkLoading v-if="translating" mini/>
|
||||
<div v-else>
|
||||
|
@ -39,13 +41,17 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<script lang="ts" setup>
|
||||
import { } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import * as mfm from 'mfm-js';
|
||||
import MkMediaList from '@/components/MkMediaList.vue';
|
||||
import MkPoll from '@/components/MkPoll.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { $i } from '@/account.js';
|
||||
import { shouldCollapsed } from '@/scripts/collapsed.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { useRouter } from '@/router.js';
|
||||
import * as os from '@/os.js';
|
||||
import { checkAnimationFromMfm } from '@/scripts/check-animated-mfm.js';
|
||||
|
||||
const props = defineProps<{
|
||||
note: Misskey.entities.Note;
|
||||
|
@ -60,8 +66,24 @@ function noteclick(id: string) {
|
|||
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, []);
|
||||
|
||||
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);
|
||||
</script>
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ type MfmProps = {
|
|||
parsedNodes?: mfm.MfmNode[] | null;
|
||||
enableEmojiMenu?: boolean;
|
||||
enableEmojiMenuReaction?: boolean;
|
||||
isAnim?: boolean;
|
||||
};
|
||||
|
||||
// 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;
|
||||
};
|
||||
|
||||
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
|
||||
|
|
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: {
|
||||
where: 'device',
|
||||
default: true,
|
||||
default: false,
|
||||
},
|
||||
advancedMfm: {
|
||||
where: 'device',
|
||||
|
|
Loading…
Reference in a new issue