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
6 changed files with 62 additions and 34 deletions

View file

@ -67,7 +67,7 @@
"koa-send": "5.0.1", "koa-send": "5.0.1",
"koa-slow": "2.1.0", "koa-slow": "2.1.0",
"koa-views": "7.0.2", "koa-views": "7.0.2",
"mfm-js": "0.22.1", "mfm-js": "0.23.3",
"mime-types": "2.1.35", "mime-types": "2.1.35",
"mocha": "10.2.0", "mocha": "10.2.0",
"multer": "1.4.5-lts.1", "multer": "1.4.5-lts.1",

View file

@ -178,7 +178,7 @@ export default define(meta, paramDef, async (ps, _user, token) => {
const newDescription = profileUpdates.description === undefined ? profile.description : profileUpdates.description; const newDescription = profileUpdates.description === undefined ? profile.description : profileUpdates.description;
if (newName != null) { if (newName != null) {
const tokens = mfm.parsePlain(newName); const tokens = mfm.parseSimple(newName);
emojis = emojis.concat(extractCustomEmojisFromMfm(tokens!)); emojis = emojis.concat(extractCustomEmojisFromMfm(tokens!));
} }

View file

@ -34,7 +34,7 @@
"json5": "2.2.1", "json5": "2.2.1",
"katex": "0.16.0", "katex": "0.16.0",
"matter-js": "0.18.0", "matter-js": "0.18.0",
"mfm-js": "0.22.1", "mfm-js": "0.23.3",
"photoswipe": "5.2.8", "photoswipe": "5.2.8",
"prismjs": "1.28.0", "prismjs": "1.28.0",
"punycode": "2.1.1", "punycode": "2.1.1",

View file

@ -10,7 +10,6 @@ import MkSearch from '@/components/mfm-search.vue';
import MkSparkle from '@/components/sparkle.vue'; import MkSparkle from '@/components/sparkle.vue';
import MkA from '@/components/global/a.vue'; import MkA from '@/components/global/a.vue';
import { host } from '@/config'; import { host } from '@/config';
import { MFM_TAGS } from '@/scripts/mfm-tags';
export default defineComponent({ export default defineComponent({
props: { props: {
@ -37,12 +36,16 @@ export default defineComponent({
type: Boolean, type: Boolean,
default: true, default: true,
}, },
rootScale: {
type: Number,
default: 1
}
}, },
render() { render() {
if (this.text == null || this.text === '') return; if (this.text == null || this.text === '') return;
const ast = (this.plain ? mfm.parsePlain : mfm.parse)(this.text, { fnNameList: MFM_TAGS }); const ast = (this.plain ? mfm.parseSimple : mfm.parse)(this.text);
const validTime = (t: string | true) => { const validTime = (t: string | true) => {
if (typeof t !== 'string') return null; if (typeof t !== 'string') return null;
@ -50,7 +53,7 @@ export default defineComponent({
return t.match(/^[0-9.]+s$/) ? t : null; return t.match(/^[0-9.]+s$/) ? t : null;
}; };
const genEl = (ast: mfm.MfmNode[]) => ast.map((token): VNode | VNode[] => { const genEl = (ast: mfm.MfmNode[], scale: Number) => ast.map((token): VNode | VNode[] => {
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.
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 +72,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 +142,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 +170,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,35 +179,60 @@ 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';
style = `transform: rotate(${degrees}deg); transform-origin: center center;`; style = `transform: rotate(${degrees}deg); transform-origin: center center;`;
break; break;
} }
case 'position': {
const x = parseFloat(token.props.args.x ?? '0');
const y = parseFloat(token.props.args.y ?? '0');
style = `transform: translateX(${x}em) translateY(${y}em);`;
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);
style = `transform: scale(${x}, ${y});`;
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.
scale = scale * Math.max(x, y);
break;
}
case 'fg': {
let color = token.props.args.color;
if (!/^[0-9a-f]{3,6}$/i.test(color)) color = 'f00';
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)
style = `color: #${color};`;
break;
}
case 'bg': {
let color = token.props.args.color;
if (!/^[0-9a-f]{3,6}$/i.test(color)) color = 'f00';
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.
style = `background-color: #${color};`;
break;
}
} }
if (style == null) { if (style == null) {
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));
} }
} }
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': {
@ -220,7 +248,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': {
@ -258,7 +286,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': {
@ -311,6 +339,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']; export const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'font', 'blur', 'rainbow', 'sparkle', 'rotate', 'position', 'scale', 'fg', 'bg'];

View file

@ -3750,7 +3750,7 @@ __metadata:
koa-send: 5.0.1 koa-send: 5.0.1
koa-slow: 2.1.0 koa-slow: 2.1.0
koa-views: 7.0.2 koa-views: 7.0.2
mfm-js: 0.22.1 mfm-js: 0.23.3
mime-types: 2.1.35 mime-types: 2.1.35
mocha: 10.2.0 mocha: 10.2.0
multer: 1.4.5-lts.1 multer: 1.4.5-lts.1
@ -4720,7 +4720,7 @@ __metadata:
json5: 2.2.1 json5: 2.2.1
katex: 0.16.0 katex: 0.16.0
matter-js: 0.18.0 matter-js: 0.18.0
mfm-js: 0.22.1 mfm-js: 0.23.3
photoswipe: 5.2.8 photoswipe: 5.2.8
prismjs: 1.28.0 prismjs: 1.28.0
punycode: 2.1.1 punycode: 2.1.1
@ -11671,12 +11671,12 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"mfm-js@npm:0.22.1": "mfm-js@npm:0.23.3":
version: 0.22.1 version: 0.23.3
resolution: "mfm-js@npm:0.22.1" resolution: "mfm-js@npm:0.23.3"
dependencies: dependencies:
twemoji-parser: 14.0.x twemoji-parser: 14.0.0
checksum: 6d9756c7bd8abf6462fb6403de4656f607a83839eb6b66a05b10eddcd201b5f78f5fe3d0df029936546143fd9cbf112e8369287aed32026e50bb03ce89b4c4f8 checksum: 7079f80a53a9afc8599333f3256fb18a6bf7c01102a2f8f2be657843726a34835e2af34e26bc5b27e45b217fb2f120c0d3006e9fab2a972c845e9f7361e3cc1b
languageName: node languageName: node
linkType: hard linkType: hard
@ -16723,7 +16723,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"twemoji-parser@npm:14.0.0, twemoji-parser@npm:14.0.x": "twemoji-parser@npm:14.0.0":
version: 14.0.0 version: 14.0.0
resolution: "twemoji-parser@npm:14.0.0" resolution: "twemoji-parser@npm:14.0.0"
checksum: 8eede69cf71f94735de7b6fddf5dfbfe3cb2e01baefc3201360984ccc97cfc659f206c8f73bd1405a2282779af3b79a8c9bed3864c672e15e2dc6f8ce4810452 checksum: 8eede69cf71f94735de7b6fddf5dfbfe3cb2e01baefc3201360984ccc97cfc659f206c8f73bd1405a2282779af3b79a8c9bed3864c672e15e2dc6f8ce4810452