Ported cutiekeys followmouse mfm

This commit is contained in:
Leah 2024-05-23 18:08:31 +00:00 committed by Amelia Yukii
parent 95ec40d3c8
commit 4c4b431248
5 changed files with 184 additions and 62 deletions

View file

@ -2478,6 +2478,7 @@ _moderationLogTypes:
unsetUserAvatar: "Unset this user's avatar" unsetUserAvatar: "Unset this user's avatar"
unsetUserBanner: "Unset this user's banner" unsetUserBanner: "Unset this user's banner"
_mfm: _mfm:
uncommonFeature: "This is not a widespread feature, it may not display properly on most other fedi software, including other Misskey forks"
intro: "MFM is a markup language used on Misskey, Sharkey, Firefish, Akkoma, and more that can be used in many places. Here you can view a list of all available MFM syntax." intro: "MFM is a markup language used on Misskey, Sharkey, Firefish, Akkoma, and more that can be used in many places. Here you can view a list of all available MFM syntax."
dummy: "Sharkey expands the world of the Fediverse" dummy: "Sharkey expands the world of the Fediverse"
mention: "Mention" mention: "Mention"
@ -2542,6 +2543,8 @@ _mfm:
rotateDescription: "Turns content by a specified angle." rotateDescription: "Turns content by a specified angle."
position: "Position" position: "Position"
positionDescription: "Move content by a specified amount." positionDescription: "Move content by a specified amount."
followMouse: "Follow Mouse"
followMouseDescription: "Content will follow the mouse. On mobile it will follow wherever the user taps."
scale: "Scale" scale: "Scale"
scaleDescription: "Scale content by a specified amount." scaleDescription: "Scale content by a specified amount."
foreground: "Foreground color" foreground: "Foreground color"

View file

@ -0,0 +1,81 @@
<template>
<span ref="container" :class="$style.root">
<span ref="el" :class="$style.inner" style="position: absolute">
<slot></slot>
</span>
</span>
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted, shallowRef } from 'vue';
const el = shallowRef<HTMLElement>();
const container = shallowRef<HTMLElement>();
const props = defineProps({
x: {
type: Boolean,
default: true,
},
y: {
type: Boolean,
default: true,
},
speed: {
type: String,
default: '0.1s',
},
rotateByVelocity: {
type: Boolean,
default: true,
},
});
let lastX = 0;
let lastY = 0;
let oldAngle = 0;
function lerp(a, b, alpha) {
return a + alpha * (b - a);
}
const updatePosition = (mouseEvent: MouseEvent) => {
if (el.value && container.value) {
const containerRect = container.value.getBoundingClientRect();
const newX = mouseEvent.clientX - containerRect.left;
const newY = mouseEvent.clientY - containerRect.top;
let transform = `translate(calc(${props.x ? newX : 0}px - 50%), calc(${props.y ? newY : 0}px - 50%))`;
if (props.rotateByVelocity) {
const deltaX = newX - lastX;
const deltaY = newY - lastY;
const angle = lerp(
oldAngle,
Math.atan2(deltaY, deltaX) * (180 / Math.PI),
0.1,
);
transform += ` rotate(${angle}deg)`;
oldAngle = angle;
}
el.value.style.transform = transform;
el.value.style.transition = `transform ${props.speed}`;
lastX = newX;
lastY = newY;
}
};
onMounted(() => {
window.addEventListener('mousemove', updatePosition);
});
onUnmounted(() => {
window.removeEventListener('mousemove', updatePosition);
});
</script>
<style lang="scss" module>
.root {
position: relative;
display: inline-block;
}
.inner {
transform-origin: center center;
}
</style>

View file

@ -314,6 +314,18 @@
</div> </div>
</div> </div>
</div> </div>
<div class="section _block" style="overflow: hidden">
<div class="title">{{ i18n.ts._mfm.followMouse }}</div>
<MkInfo warn>{{ i18n.ts._mfm.uncommonFeature }}</MkInfo>
<br/>
<div class="content">
<p>{{ i18n.ts._mfm.followMouseDescription }}</p>
<div class="preview">
<Mfm :text="preview_followmouse"/>
<MkTextarea v-model="preview_followmouse"><span>MFM</span></MkTextarea>
</div>
</div>
</div>
<div class="section _block"> <div class="section _block">
<div class="title">{{ i18n.ts._mfm.scale }}</div> <div class="title">{{ i18n.ts._mfm.scale }}</div>
<div class="content"> <div class="content">
@ -362,18 +374,19 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue'; import { ref } from 'vue';
import MkInfo from './MkInfo.vue';
import MkWindow from '@/components/MkWindow.vue'; import MkWindow from '@/components/MkWindow.vue';
import MkTextarea from '@/components/MkTextarea.vue'; import MkTextarea from '@/components/MkTextarea.vue';
import { i18n } from "@/i18n.js"; import { i18n } from '@/i18n.js';
const emit = defineEmits<{ const emit = defineEmits<{
(ev: 'closed'): void; (ev: 'closed'): void;
}>(); }>();
const preview_mention = ref("@example"); const preview_mention = ref('@example');
const preview_hashtag = ref("#test"); const preview_hashtag = ref('#test');
const preview_link = ref(`[${i18n.ts._mfm.dummy}](https://joinsharkey.org)`); const preview_link = ref(`[${i18n.ts._mfm.dummy}](https://joinsharkey.org)`);
const preview_emoji = ref(`:heart:`); const preview_emoji = ref(':heart:');
const preview_bold = ref(`**${i18n.ts._mfm.dummy}**`); const preview_bold = ref(`**${i18n.ts._mfm.dummy}**`);
const preview_small = ref( const preview_small = ref(
`<small>${i18n.ts._mfm.dummy}</small>`, `<small>${i18n.ts._mfm.dummy}</small>`,
@ -386,33 +399,33 @@ const preview_blockCode = ref(
'```\n~ (#i, 100) {\n\t<: ? ((i % 15) = 0) "FizzBuzz"\n\t\t.? ((i % 3) = 0) "Fizz"\n\t\t.? ((i % 5) = 0) "Buzz"\n\t\t. i\n}\n```', '```\n~ (#i, 100) {\n\t<: ? ((i % 15) = 0) "FizzBuzz"\n\t\t.? ((i % 3) = 0) "Fizz"\n\t\t.? ((i % 5) = 0) "Buzz"\n\t\t. i\n}\n```',
); );
const preview_inlineMath = ref( const preview_inlineMath = ref(
"\\(x= \\frac{-b' \\pm \\sqrt{(b')^2-ac}}{a}\\)", '\\(x= \\frac{-b\' \\pm \\sqrt{(b\')^2-ac}}{a}\\)',
); );
const preview_blockMath = ref("\\[x= \\frac{-b' \\pm \\sqrt{(b')^2-ac}}{a}\\]"); const preview_blockMath = ref('\\[x= \\frac{-b\' \\pm \\sqrt{(b\')^2-ac}}{a}\\]');
const preview_quote = ref(`> ${i18n.ts._mfm.dummy}`); const preview_quote = ref(`> ${i18n.ts._mfm.dummy}`);
const preview_search = ref( const preview_search = ref(
`${i18n.ts._mfm.dummy} [search]\n${i18n.ts._mfm.dummy} [検索]`, `${i18n.ts._mfm.dummy} [search]\n${i18n.ts._mfm.dummy} [検索]`,
); );
const preview_jelly = ref( const preview_jelly = ref(
"$[jelly 🍮] $[jelly.speed=3s 🍮] $[jelly.delay=3s 🍮] $[jelly.loop=3 🍮]", '$[jelly 🍮] $[jelly.speed=3s 🍮] $[jelly.delay=3s 🍮] $[jelly.loop=3 🍮]',
); );
const preview_tada = ref( const preview_tada = ref(
"$[tada 🍮] $[tada.speed=3s 🍮] $[tada.delay=3s 🍮] $[tada.loop=3 🍮]", '$[tada 🍮] $[tada.speed=3s 🍮] $[tada.delay=3s 🍮] $[tada.loop=3 🍮]',
); );
const preview_jump = ref( const preview_jump = ref(
"$[jump 🍮] $[jump.speed=3s 🍮] $[jump.delay=3s 🍮] $[jump.loop=3 🍮]", '$[jump 🍮] $[jump.speed=3s 🍮] $[jump.delay=3s 🍮] $[jump.loop=3 🍮]',
); );
const preview_bounce = ref( const preview_bounce = ref(
"$[bounce 🍮] $[bounce.speed=3s 🍮] $[bounce.delay=3s 🍮] $[bounce.loop=3 🍮]", '$[bounce 🍮] $[bounce.speed=3s 🍮] $[bounce.delay=3s 🍮] $[bounce.loop=3 🍮]',
); );
const preview_shake = ref( const preview_shake = ref(
"$[shake 🍮] $[shake.speed=3s 🍮] $[shake.delay=3s 🍮] $[shake.loop=3 🍮]", '$[shake 🍮] $[shake.speed=3s 🍮] $[shake.delay=3s 🍮] $[shake.loop=3 🍮]',
); );
const preview_twitch = ref( const preview_twitch = ref(
"$[twitch 🍮] $[twitch.speed=3s 🍮] $[twitch.delay=3s 🍮] $[twitch.loop=3 🍮]", '$[twitch 🍮] $[twitch.speed=3s 🍮] $[twitch.delay=3s 🍮] $[twitch.loop=3 🍮]',
); );
const preview_spin = ref( const preview_spin = ref(
"$[spin 🍮] $[spin.left 🍮] $[spin.alternate 🍮]\n$[spin.x 🍮] $[spin.x,left 🍮] $[spin.x,alternate 🍮]\n$[spin.y 🍮] $[spin.y,left 🍮] $[spin.y,alternate 🍮]\n\n$[spin.speed=3s 🍮] $[spin.delay=3s 🍮] $[spin.loop=3 🍮]", '$[spin 🍮] $[spin.left 🍮] $[spin.alternate 🍮]\n$[spin.x 🍮] $[spin.x,left 🍮] $[spin.x,alternate 🍮]\n$[spin.y 🍮] $[spin.y,left 🍮] $[spin.y,alternate 🍮]\n\n$[spin.speed=3s 🍮] $[spin.delay=3s 🍮] $[spin.loop=3 🍮]',
); );
const preview_flip = ref( const preview_flip = ref(
`$[flip ${i18n.ts._mfm.dummy}]\n$[flip.v ${i18n.ts._mfm.dummy}]\n$[flip.h,v ${i18n.ts._mfm.dummy}]`, `$[flip ${i18n.ts._mfm.dummy}]\n$[flip.v ${i18n.ts._mfm.dummy}]\n$[flip.h,v ${i18n.ts._mfm.dummy}]`,
@ -420,25 +433,26 @@ const preview_flip = ref(
const preview_font = ref( const preview_font = ref(
`$[font.serif ${i18n.ts._mfm.dummy}]\n$[font.monospace ${i18n.ts._mfm.dummy}]`, `$[font.serif ${i18n.ts._mfm.dummy}]\n$[font.monospace ${i18n.ts._mfm.dummy}]`,
); );
const preview_x2 = ref("$[x2 🍮]"); const preview_x2 = ref('$[x2 🍮]');
const preview_x3 = ref("$[x3 🍮]"); const preview_x3 = ref('$[x3 🍮]');
const preview_x4 = ref("$[x4 🍮]"); const preview_x4 = ref('$[x4 🍮]');
const preview_blur = ref(`$[blur ${i18n.ts._mfm.dummy}]`); const preview_blur = ref(`$[blur ${i18n.ts._mfm.dummy}]`);
const preview_rainbow = ref( const preview_rainbow = ref(
"$[rainbow 🍮] $[rainbow.speed=3s 🍮] $[rainbow.delay=3s 🍮] $[rainbow.loop=3 🍮]", '$[rainbow 🍮] $[rainbow.speed=3s 🍮] $[rainbow.delay=3s 🍮] $[rainbow.loop=3 🍮]',
); );
const preview_sparkle = ref("$[sparkle 🍮]"); const preview_sparkle = ref('$[sparkle 🍮]');
const preview_rotate = ref( const preview_rotate = ref(
"$[rotate 🍮]\n$[rotate.deg=45 🍮]\n$[rotate.x,deg=45 Hello, world!]", '$[rotate 🍮]\n$[rotate.deg=45 🍮]\n$[rotate.x,deg=45 Hello, world!]',
); );
const preview_position = ref("$[position.y=-1 🍮]\n$[position.x=-1 🍮]"); const preview_position = ref('$[position.y=-1 🍮]\n$[position.x=-1 🍮]');
const preview_followmouse = ref('$[followmouse.x 🍮]\n$[followmouse.x,y,rotateByVelocity,speed=0.4 🍮]');
const preview_scale = ref( const preview_scale = ref(
"$[scale.x=1.3 🍮]\n$[scale.x=1.5,y=3 🍮]\n$[scale.y=0.3 🍮]", '$[scale.x=1.3 🍮]\n$[scale.x=1.5,y=3 🍮]\n$[scale.y=0.3 🍮]',
); );
const preview_fg = ref("$[fg.color=eb6f92 Text color]"); const preview_fg = ref('$[fg.color=eb6f92 Text color]');
const preview_bg = ref("$[bg.color=31748f Background color]"); const preview_bg = ref('$[bg.color=31748f Background color]');
const preview_plain = ref( const preview_plain = ref(
"<plain>**bold** @mention #hashtag `code` $[x2 🍮]</plain>", '<plain>**bold** @mention #hashtag `code` $[x2 🍮]</plain>',
); );
</script> </script>

View file

@ -6,6 +6,7 @@
import { VNode, h, defineAsyncComponent, SetupContext } from 'vue'; import { VNode, h, defineAsyncComponent, SetupContext } from 'vue';
import * as mfm from '@transfem-org/sfm-js'; import * as mfm from '@transfem-org/sfm-js';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import CkFollowMouse from '../CkFollowMouse.vue';
import MkUrl from '@/components/global/MkUrl.vue'; import MkUrl from '@/components/global/MkUrl.vue';
import MkTime from '@/components/global/MkTime.vue'; import MkTime from '@/components/global/MkTime.vue';
import MkLink from '@/components/MkLink.vue'; import MkLink from '@/components/MkLink.vue';
@ -232,6 +233,28 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
style = `transform: rotate(${degrees}deg); transform-origin: center center;`; style = `transform: rotate(${degrees}deg); transform-origin: center center;`;
break; break;
} }
case 'followmouse': {
// Make sure advanced MFM is on and that reduced motion is off
if (!useAnim) {
style = '';
break;
}
let x = (!!token.props.args.x);
let y = (!!token.props.args.y);
if (!x && !y) {
x = true;
y = true;
}
return h(CkFollowMouse, {
x: x,
y: y,
speed: validTime(token.props.args.speed) ?? '0.1s',
rotateByVelocity: !!token.props.args.rotateByVelocity,
}, genEl(token.children, scale));
}
case 'position': { case 'position': {
if (!defaultStore.state.advancedMfm) break; if (!defaultStore.state.advancedMfm) break;
const x = safeParseFloat(token.props.args.x) ?? 0; const x = safeParseFloat(token.props.args.x) ?? 0;

View file

@ -162,7 +162,7 @@ export const DEFAULT_SERVER_ERROR_IMAGE_URL = 'https://launcher.moe/error.png';
export const DEFAULT_NOT_FOUND_IMAGE_URL = 'https://launcher.moe/missingpage.webp'; export const DEFAULT_NOT_FOUND_IMAGE_URL = 'https://launcher.moe/missingpage.webp';
export const DEFAULT_INFO_IMAGE_URL = 'https://launcher.moe/nothinghere.png'; export const DEFAULT_INFO_IMAGE_URL = 'https://launcher.moe/nothinghere.png';
export const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'scale', 'position', 'fg', 'bg', 'border', 'font', 'blur', 'rainbow', 'sparkle', 'rotate', 'ruby', 'unixtime']; export const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'scale', 'position', 'fg', 'bg', 'border', 'font', 'blur', 'rainbow', 'sparkle', 'rotate', 'ruby', 'unixtime', 'followmouse'];
export const MFM_PARAMS: Record<typeof MFM_TAGS[number], string[]> = { export const MFM_PARAMS: Record<typeof MFM_TAGS[number], string[]> = {
tada: ['speed=', 'delay='], tada: ['speed=', 'delay='],
jelly: ['speed=', 'delay='], jelly: ['speed=', 'delay='],
@ -186,4 +186,5 @@ export const MFM_PARAMS: Record<typeof MFM_TAGS[number], string[]> = {
rotate: ['deg='], rotate: ['deg='],
ruby: [], ruby: [],
unixtime: [], unixtime: [],
followmouse: ['x', 'y', 'rotateByVelocity', 'speed='],
}; };