Updates mfm-js and adds position, scale, fg and bg function to mfm render #385

Manually merged
Johann150 merged 5 commits from puniko/FoundKey:update-mfm-core into main 2023-05-27 10:14:40 +00:00
2 changed files with 32 additions and 21 deletions
Showing only changes of commit eb8463b292 - Show all commits

View file

@ -37,6 +37,10 @@ export default defineComponent({
type: Boolean, type: Boolean,
default: true, default: true,
}, },
rootScale: {
type: Number,
default: 1
}
}, },
render() { render() {
@ -50,7 +54,7 @@ export default defineComponent({
return t.match(/^[0-9.]+s$/) ? t : null; return t.match(/^[0-9.]+s$/) ? t : null;
}; };
Johann150 marked this conversation as resolved
Review

I don't understand what this scale parameter is for, it doesn't seem to be read at any point and having to carry it through is kind of annoying.

I don't understand what this `scale` parameter is for, it doesn't seem to be read at any point and having to carry it through is kind of annoying.
const genEl = (ast: mfm.MfmNode[]) => ast.map((token): VNode | VNode[] => { const genEl = (ast: mfm.MfmNode[], scale: Number) => ast.map((token): VNode | VNode[] => {
switch (token.type) { switch (token.type) {
case 'text': { case 'text': {
const text = token.props.text.replace(/(\r\n|\n|\r)/g, '\n'); const text = token.props.text.replace(/(\r\n|\n|\r)/g, '\n');
@ -69,17 +73,17 @@ export default defineComponent({
} }
case 'bold': { case 'bold': {
return h('b', genEl(token.children)); return h('b', genEl(token.children, scale));
} }
case 'strike': { case 'strike': {
return h('del', genEl(token.children)); return h('del', genEl(token.children, scale));
} }
case 'italic': { case 'italic': {
return h('i', { return h('i', {
style: 'font-style: oblique;', style: 'font-style: oblique;',
}, genEl(token.children)); }, genEl(token.children, scale));
} }
case 'fn': { case 'fn': {
@ -139,18 +143,18 @@ export default defineComponent({
} }
case 'x2': { case 'x2': {
return h('span', { return h('span', {
class: 'mfm-x2', class: 'mfm-x2'
}, genEl(token.children)); }, genEl(token.children, scale * 2));
} }
case 'x3': { case 'x3': {
return h('span', { return h('span', {
class: 'mfm-x3', class: 'mfm-x3'
}, genEl(token.children)); }, genEl(token.children, scale * 3));
} }
case 'x4': { case 'x4': {
return h('span', { return h('span', {
class: 'mfm-x4', class: 'mfm-x4'
}, genEl(token.children)); }, genEl(token.children, scale * 4));
} }
case 'font': { case 'font': {
const family = const family =
@ -167,7 +171,7 @@ export default defineComponent({
case 'blur': { case 'blur': {
return h('span', { return h('span', {
class: '_mfm_blur_', class: '_mfm_blur_',
}, genEl(token.children)); }, genEl(token.children, scale));
} }
case 'rainbow': { case 'rainbow': {
const speed = validTime(token.props.args.speed) || '1s'; const speed = validTime(token.props.args.speed) || '1s';
@ -176,9 +180,9 @@ export default defineComponent({
} }
case 'sparkle': { case 'sparkle': {
if (!this.$store.state.animatedMfm) { if (!this.$store.state.animatedMfm) {
return genEl(token.children); return genEl(token.children, scale);
} }
return h(MkSparkle, {}, genEl(token.children)); return h(MkSparkle, {}, genEl(token.children, scale));
} }
case 'rotate': { case 'rotate': {
const degrees = (typeof token.props.args.deg === 'string' ? parseInt(token.props.args.deg) : null) || '90'; const degrees = (typeof token.props.args.deg === 'string' ? parseInt(token.props.args.deg) : null) || '90';
@ -191,26 +195,33 @@ export default defineComponent({
style = `transform: translateX(${x}em) translateY(${y}em);`; style = `transform: translateX(${x}em) translateY(${y}em);`;
break; break;
} }
case 'scale': {
const x = Math.min(parseFloat(token.props.args.x ?? '1'), 5);
const y = Math.min(parseFloat(token.props.args.y ?? '1'), 5);
Johann150 marked this conversation as resolved
Review

Not really happy with this because this allows to bypass the limitations that were created for x2, x3 and x4 to not allow someone else to insert MFM resulting in people having HUGE things on their timeline that they have to scroll by for hours.

My idea to fix it would be to use CSS and add it to these existing rules for x2, x3 and x4 functions:

.mfm-x2 {
--mfm-zoom-size: 200%;
}
.mfm-x3 {
--mfm-zoom-size: 400%;
}
.mfm-x4 {
--mfm-zoom-size: 600%;
}
.mfm-x2, .mfm-x3, .mfm-x4 {
font-size: var(--mfm-zoom-size);
.mfm-x2, .mfm-x3, .mfm-x4 {
/* only half effective */
font-size: calc(var(--mfm-zoom-size) / 2 + 50%);
.mfm-x2, .mfm-x3, .mfm-x4 {
/* disabled */
font-size: 100%;
}
}
}

(I'm not entirely sure if that works because those rules use font-size while you used transform: scale.)

Not really happy with this because this allows to bypass the limitations that were created for `x2`, `x3` and `x4` to not allow someone else to insert MFM resulting in people having HUGE things on their timeline that they have to scroll by for hours. My idea to fix it would be to use CSS and add it to these existing rules for `x2`, `x3` and `x4` functions: https://akkoma.dev/FoundKeyGang/FoundKey/src/commit/7a94e9f2d5804b6331272cc5d9db2ff12d8fde05/packages/client/src/components/global/misskey-flavored-markdown.vue#L33-L57 (I'm not entirely sure if that works because those rules use `font-size` while you used `transform: scale`.)
Review

Transform's don't affect the content height

If you don't want it to overflow into the replies, then you should add overflow: hidden or clip to the note (unless you already have)

Transform's don't affect the content height If you don't want it to overflow into the replies, then you should add overflow: hidden or clip to the note (unless you already have)
Review

Thanks for pointing this out. I guess this is not an issue then.

Thanks for pointing this out. I guess this is not an issue then.
style = `transform: scale(${x}, ${y});`;
scale = scale * Math.max(x, y);
break;
}
} }
if (style == null) { if (style == null) {
Johann150 marked this conversation as resolved
Review

I think this should have an explicit check for undefined (if the argument is not specified at all) and true (if the argument is specified without value).

All values are coerced to strings, so omitting it or passing undefined causes test() to search for the string "undefined", which is rarely what you want.
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test

I also think the regex is incorrect since {3,6} means "3 to 6 repetitions" and not "3 or 6 repetitions". I.e. this would allow 4 and 5 character colour codes which I think is not understood by browsers.

Putting the default if it is an invalid colour seems a bit weird to me but I guess if that's what Misskey does we should probably do the same.

I think this should have an explicit check for `undefined` (if the argument is not specified at all) and `true` (if the argument is specified without value). > All values are coerced to strings, so omitting it or passing `undefined` causes `test()` to search for the string `"undefined"`, which is rarely what you want. -- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test I also think the regex is incorrect since `{3,6}` means "3 to 6 repetitions" and not "3 or 6 repetitions". I.e. this would allow 4 and 5 character colour codes which I think is not understood by browsers. Putting the default if it is an invalid colour seems a bit weird to me but I guess if that's what Misskey does we should probably do the same.
Review

Recommended alternative regex: /^[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$/ or so.

Recommended alternative regex: `/^[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$/` or so.
Review

or /^([0-9a-f]{3}){1,2}$/i stolen from this gist

or `/^([0-9a-f]{3}){1,2}$/i` stolen from [this gist](https://gist.github.com/olmokramer/82ccce673f86db7cda5e)
return h('span', {}, ['$[', token.props.name, ' ', ...genEl(token.children), ']']); return h('span', {}, ['$[', token.props.name, ' ', ...genEl(token.children, scale), ']']);
} else { } else {
return h('span', { return h('span', {
style: 'display: inline-block;' + style, style: 'display: inline-block;' + style,
}, genEl(token.children)); }, genEl(token.children, scale));
} }
Johann150 marked this conversation as resolved
Review

The comment for fg applies here as well of course.

The comment for `fg` applies here as well of course.
} }
case 'small': { case 'small': {
return h('small', { return h('small', {
class: '_mfm_small_' class: '_mfm_small_'
}, genEl(token.children)); }, genEl(token.children, scale));
} }
case 'center': { case 'center': {
return h('div', { return h('div', {
style: 'text-align:center;', style: 'text-align:center;',
}, genEl(token.children)); }, genEl(token.children, scale));
} }
case 'url': { case 'url': {
@ -226,7 +237,7 @@ export default defineComponent({
key: Math.random(), key: Math.random(),
url: token.props.url, url: token.props.url,
rel: 'nofollow noopener', rel: 'nofollow noopener',
}, genEl(token.children)); }, genEl(token.children, scale));
} }
case 'mention': { case 'mention': {
@ -264,7 +275,7 @@ export default defineComponent({
case 'quote': { case 'quote': {
return h(this.nowrap ? 'span' : 'div', { return h(this.nowrap ? 'span' : 'div', {
class: 'quote', class: 'quote',
}, genEl(token.children)); }, genEl(token.children, scale));
} }
case 'emojiCode': { case 'emojiCode': {
@ -317,6 +328,6 @@ export default defineComponent({
}).flat(); }).flat();
// Parse ast to DOM // Parse ast to DOM
return h('span', genEl(ast)); return h('span', genEl(ast, this.rootScale ?? 1));
}, },
}); });

View file

@ -1 +1 @@
export const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'font', 'blur', 'rainbow', 'sparkle', 'rotate', 'position']; export const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'font', 'blur', 'rainbow', 'sparkle', 'rotate', 'position', 'scale'];