From a16e921ffcb4d1ecd84d41caa33692bf2c00bc20 Mon Sep 17 00:00:00 2001 From: Puniko Date: Sun, 21 May 2023 16:52:11 +0200 Subject: [PATCH 1/8] add position token Co-authored-by: syuilo --- packages/client/src/components/mfm.ts | 6 ++++++ packages/client/src/scripts/mfm-tags.ts | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/client/src/components/mfm.ts b/packages/client/src/components/mfm.ts index 64fe5bdb5..58e8571f4 100644 --- a/packages/client/src/components/mfm.ts +++ b/packages/client/src/components/mfm.ts @@ -185,6 +185,12 @@ export default defineComponent({ style = `transform: rotate(${degrees}deg); transform-origin: center center;`; 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; + } } if (style == null) { return h('span', {}, ['$[', token.props.name, ' ', ...genEl(token.children), ']']); diff --git a/packages/client/src/scripts/mfm-tags.ts b/packages/client/src/scripts/mfm-tags.ts index 18e8d7038..7f721361e 100644 --- a/packages/client/src/scripts/mfm-tags.ts +++ b/packages/client/src/scripts/mfm-tags.ts @@ -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']; From 11121bbb4caf447c79cd774684b947b8b12768d1 Mon Sep 17 00:00:00 2001 From: Puniko Date: Sun, 21 May 2023 17:16:22 +0200 Subject: [PATCH 2/8] add scale tag Co-authored-by: syuilo Co-authored-by: tamaina --- packages/client/src/components/mfm.ts | 51 +++++++++++++++---------- packages/client/src/scripts/mfm-tags.ts | 2 +- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/packages/client/src/components/mfm.ts b/packages/client/src/components/mfm.ts index 58e8571f4..75f1db07d 100644 --- a/packages/client/src/components/mfm.ts +++ b/packages/client/src/components/mfm.ts @@ -37,6 +37,10 @@ export default defineComponent({ type: Boolean, default: true, }, + rootScale: { + type: Number, + default: 1 + } }, render() { @@ -50,7 +54,7 @@ export default defineComponent({ 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[] => { switch (token.type) { case 'text': { const text = token.props.text.replace(/(\r\n|\n|\r)/g, '\n'); @@ -69,17 +73,17 @@ export default defineComponent({ } case 'bold': { - return h('b', genEl(token.children)); + return h('b', genEl(token.children, scale)); } case 'strike': { - return h('del', genEl(token.children)); + return h('del', genEl(token.children, scale)); } case 'italic': { return h('i', { style: 'font-style: oblique;', - }, genEl(token.children)); + }, genEl(token.children, scale)); } case 'fn': { @@ -139,18 +143,18 @@ export default defineComponent({ } case 'x2': { return h('span', { - class: 'mfm-x2', - }, genEl(token.children)); + class: 'mfm-x2' + }, genEl(token.children, scale * 2)); } case 'x3': { return h('span', { - class: 'mfm-x3', - }, genEl(token.children)); + class: 'mfm-x3' + }, genEl(token.children, scale * 3)); } case 'x4': { return h('span', { - class: 'mfm-x4', - }, genEl(token.children)); + class: 'mfm-x4' + }, genEl(token.children, scale * 4)); } case 'font': { const family = @@ -167,7 +171,7 @@ export default defineComponent({ case 'blur': { return h('span', { class: '_mfm_blur_', - }, genEl(token.children)); + }, genEl(token.children, scale)); } case 'rainbow': { const speed = validTime(token.props.args.speed) || '1s'; @@ -176,9 +180,9 @@ export default defineComponent({ } case 'sparkle': { 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': { 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);`; 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});`; + scale = scale * Math.max(x, y); + break; + } } if (style == null) { - return h('span', {}, ['$[', token.props.name, ' ', ...genEl(token.children), ']']); + return h('span', {}, ['$[', token.props.name, ' ', ...genEl(token.children, scale), ']']); } else { return h('span', { style: 'display: inline-block;' + style, - }, genEl(token.children)); + }, genEl(token.children, scale)); } } case 'small': { return h('small', { class: '_mfm_small_' - }, genEl(token.children)); + }, genEl(token.children, scale)); } case 'center': { return h('div', { style: 'text-align:center;', - }, genEl(token.children)); + }, genEl(token.children, scale)); } case 'url': { @@ -226,7 +237,7 @@ export default defineComponent({ key: Math.random(), url: token.props.url, rel: 'nofollow noopener', - }, genEl(token.children)); + }, genEl(token.children, scale)); } case 'mention': { @@ -264,7 +275,7 @@ export default defineComponent({ case 'quote': { return h(this.nowrap ? 'span' : 'div', { class: 'quote', - }, genEl(token.children)); + }, genEl(token.children, scale)); } case 'emojiCode': { @@ -317,6 +328,6 @@ export default defineComponent({ }).flat(); // Parse ast to DOM - return h('span', genEl(ast)); + return h('span', genEl(ast, this.rootScale ?? 1)); }, }); diff --git a/packages/client/src/scripts/mfm-tags.ts b/packages/client/src/scripts/mfm-tags.ts index 7f721361e..73de7b3be 100644 --- a/packages/client/src/scripts/mfm-tags.ts +++ b/packages/client/src/scripts/mfm-tags.ts @@ -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']; From a45b611a7e300872937601a052be7b7d9e907912 Mon Sep 17 00:00:00 2001 From: Puniko Date: Sun, 21 May 2023 17:23:26 +0200 Subject: [PATCH 3/8] add fg and bg tag Co-authored-by: syuilo --- packages/client/src/components/mfm.ts | 12 ++++++++++++ packages/client/src/scripts/mfm-tags.ts | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/client/src/components/mfm.ts b/packages/client/src/components/mfm.ts index 75f1db07d..e056627bf 100644 --- a/packages/client/src/components/mfm.ts +++ b/packages/client/src/components/mfm.ts @@ -202,6 +202,18 @@ export default defineComponent({ 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'; + style = `color: #${color};`; + break; + } + case 'bg': { + let color = token.props.args.color; + if (!/^[0-9a-f]{3,6}$/i.test(color)) color = 'f00'; + style = `background-color: #${color};`; + break; + } } if (style == null) { return h('span', {}, ['$[', token.props.name, ' ', ...genEl(token.children, scale), ']']); diff --git a/packages/client/src/scripts/mfm-tags.ts b/packages/client/src/scripts/mfm-tags.ts index 73de7b3be..84415f400 100644 --- a/packages/client/src/scripts/mfm-tags.ts +++ b/packages/client/src/scripts/mfm-tags.ts @@ -1 +1 @@ -export const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'font', 'blur', 'rainbow', 'sparkle', 'rotate', 'position', 'scale']; +export const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'font', 'blur', 'rainbow', 'sparkle', 'rotate', 'position', 'scale', 'fg', 'bg']; From f6ff21ee538a1467ef78ab4e6fd105661e8ae9d5 Mon Sep 17 00:00:00 2001 From: Puniko Date: Sun, 21 May 2023 18:41:46 +0200 Subject: [PATCH 4/8] update mfm-js lib --- packages/backend/package.json | 2 +- packages/client/package.json | 2 +- packages/client/src/components/mfm.ts | 3 +-- yarn.lock | 16 ++++++++-------- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/packages/backend/package.json b/packages/backend/package.json index 6c62d9231..097f6ee73 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -67,7 +67,7 @@ "koa-send": "5.0.1", "koa-slow": "2.1.0", "koa-views": "7.0.2", - "mfm-js": "0.22.1", + "mfm-js": "0.23.3", "mime-types": "2.1.35", "mocha": "10.2.0", "multer": "1.4.5-lts.1", diff --git a/packages/client/package.json b/packages/client/package.json index e8ce60b35..545c58c9f 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -34,7 +34,7 @@ "json5": "2.2.1", "katex": "0.16.0", "matter-js": "0.18.0", - "mfm-js": "0.22.1", + "mfm-js": "0.23.3", "photoswipe": "5.2.8", "prismjs": "1.28.0", "punycode": "2.1.1", diff --git a/packages/client/src/components/mfm.ts b/packages/client/src/components/mfm.ts index e056627bf..68f447add 100644 --- a/packages/client/src/components/mfm.ts +++ b/packages/client/src/components/mfm.ts @@ -10,7 +10,6 @@ import MkSearch from '@/components/mfm-search.vue'; import MkSparkle from '@/components/sparkle.vue'; import MkA from '@/components/global/a.vue'; import { host } from '@/config'; -import { MFM_TAGS } from '@/scripts/mfm-tags'; export default defineComponent({ props: { @@ -46,7 +45,7 @@ export default defineComponent({ render() { 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) => { if (typeof t !== 'string') return null; diff --git a/yarn.lock b/yarn.lock index 1786f0c51..e4e95a074 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3750,7 +3750,7 @@ __metadata: koa-send: 5.0.1 koa-slow: 2.1.0 koa-views: 7.0.2 - mfm-js: 0.22.1 + mfm-js: 0.23.3 mime-types: 2.1.35 mocha: 10.2.0 multer: 1.4.5-lts.1 @@ -4720,7 +4720,7 @@ __metadata: json5: 2.2.1 katex: 0.16.0 matter-js: 0.18.0 - mfm-js: 0.22.1 + mfm-js: 0.23.3 photoswipe: 5.2.8 prismjs: 1.28.0 punycode: 2.1.1 @@ -11671,12 +11671,12 @@ __metadata: languageName: node linkType: hard -"mfm-js@npm:0.22.1": - version: 0.22.1 - resolution: "mfm-js@npm:0.22.1" +"mfm-js@npm:0.23.3": + version: 0.23.3 + resolution: "mfm-js@npm:0.23.3" dependencies: - twemoji-parser: 14.0.x - checksum: 6d9756c7bd8abf6462fb6403de4656f607a83839eb6b66a05b10eddcd201b5f78f5fe3d0df029936546143fd9cbf112e8369287aed32026e50bb03ce89b4c4f8 + twemoji-parser: 14.0.0 + checksum: 7079f80a53a9afc8599333f3256fb18a6bf7c01102a2f8f2be657843726a34835e2af34e26bc5b27e45b217fb2f120c0d3006e9fab2a972c845e9f7361e3cc1b languageName: node linkType: hard @@ -16723,7 +16723,7 @@ __metadata: languageName: node linkType: hard -"twemoji-parser@npm:14.0.0, twemoji-parser@npm:14.0.x": +"twemoji-parser@npm:14.0.0": version: 14.0.0 resolution: "twemoji-parser@npm:14.0.0" checksum: 8eede69cf71f94735de7b6fddf5dfbfe3cb2e01baefc3201360984ccc97cfc659f206c8f73bd1405a2282779af3b79a8c9bed3864c672e15e2dc6f8ce4810452 From 404bb3b6b6e72cf31c64bd4baf825467354e9009 Mon Sep 17 00:00:00 2001 From: Puniko Date: Sun, 21 May 2023 19:16:44 +0200 Subject: [PATCH 5/8] parseSimple in backend --- packages/backend/src/server/api/endpoints/i/update.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index 1a73e31df..b5abf56b5 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -178,7 +178,7 @@ export default define(meta, paramDef, async (ps, _user, token) => { const newDescription = profileUpdates.description === undefined ? profile.description : profileUpdates.description; if (newName != null) { - const tokens = mfm.parsePlain(newName); + const tokens = mfm.parseSimple(newName); emojis = emojis.concat(extractCustomEmojisFromMfm(tokens!)); } From a72f4300aa7b1415b06557f385d6fc4c806df0a1 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sat, 27 May 2023 10:51:36 +0200 Subject: [PATCH 6/8] remove unused rootScale and scale params --- packages/client/src/components/mfm.ts | 39 ++++++++++++--------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/packages/client/src/components/mfm.ts b/packages/client/src/components/mfm.ts index 68f447add..bb0b71a5a 100644 --- a/packages/client/src/components/mfm.ts +++ b/packages/client/src/components/mfm.ts @@ -36,10 +36,6 @@ export default defineComponent({ type: Boolean, default: true, }, - rootScale: { - type: Number, - default: 1 - } }, render() { @@ -53,7 +49,7 @@ export default defineComponent({ return t.match(/^[0-9.]+s$/) ? t : null; }; - const genEl = (ast: mfm.MfmNode[], scale: Number) => ast.map((token): VNode | VNode[] => { + const genEl = (ast: mfm.MfmNode[]) => ast.map((token): VNode | VNode[] => { switch (token.type) { case 'text': { const text = token.props.text.replace(/(\r\n|\n|\r)/g, '\n'); @@ -72,17 +68,17 @@ export default defineComponent({ } case 'bold': { - return h('b', genEl(token.children, scale)); + return h('b', genEl(token.children)); } case 'strike': { - return h('del', genEl(token.children, scale)); + return h('del', genEl(token.children)); } case 'italic': { return h('i', { style: 'font-style: oblique;', - }, genEl(token.children, scale)); + }, genEl(token.children)); } case 'fn': { @@ -143,17 +139,17 @@ export default defineComponent({ case 'x2': { return h('span', { class: 'mfm-x2' - }, genEl(token.children, scale * 2)); + }, genEl(token.children)); } case 'x3': { return h('span', { class: 'mfm-x3' - }, genEl(token.children, scale * 3)); + }, genEl(token.children)); } case 'x4': { return h('span', { class: 'mfm-x4' - }, genEl(token.children, scale * 4)); + }, genEl(token.children)); } case 'font': { const family = @@ -170,7 +166,7 @@ export default defineComponent({ case 'blur': { return h('span', { class: '_mfm_blur_', - }, genEl(token.children, scale)); + }, genEl(token.children)); } case 'rainbow': { const speed = validTime(token.props.args.speed) || '1s'; @@ -179,9 +175,9 @@ export default defineComponent({ } case 'sparkle': { if (!this.$store.state.animatedMfm) { - return genEl(token.children, scale); + return genEl(token.children); } - return h(MkSparkle, {}, genEl(token.children, scale)); + return h(MkSparkle, {}, genEl(token.children)); } case 'rotate': { const degrees = (typeof token.props.args.deg === 'string' ? parseInt(token.props.args.deg) : null) || '90'; @@ -198,7 +194,6 @@ export default defineComponent({ 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});`; - scale = scale * Math.max(x, y); break; } case 'fg': { @@ -215,24 +210,24 @@ export default defineComponent({ } } if (style == null) { - return h('span', {}, ['$[', token.props.name, ' ', ...genEl(token.children, scale), ']']); + return h('span', {}, ['$[', token.props.name, ' ', ...genEl(token.children), ']']); } else { return h('span', { style: 'display: inline-block;' + style, - }, genEl(token.children, scale)); + }, genEl(token.children)); } } case 'small': { return h('small', { class: '_mfm_small_' - }, genEl(token.children, scale)); + }, genEl(token.children)); } case 'center': { return h('div', { style: 'text-align:center;', - }, genEl(token.children, scale)); + }, genEl(token.children)); } case 'url': { @@ -248,7 +243,7 @@ export default defineComponent({ key: Math.random(), url: token.props.url, rel: 'nofollow noopener', - }, genEl(token.children, scale)); + }, genEl(token.children)); } case 'mention': { @@ -286,7 +281,7 @@ export default defineComponent({ case 'quote': { return h(this.nowrap ? 'span' : 'div', { class: 'quote', - }, genEl(token.children, scale)); + }, genEl(token.children)); } case 'emojiCode': { @@ -339,6 +334,6 @@ export default defineComponent({ }).flat(); // Parse ast to DOM - return h('span', genEl(ast, this.rootScale ?? 1)); + return h('span', genEl(ast)); }, }); From 3f9b09e3e7da50e0d1b35d996ebc5b4cfbddd5f8 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sat, 27 May 2023 11:37:30 +0200 Subject: [PATCH 7/8] simplify MFM position CSS --- packages/client/src/components/mfm.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/components/mfm.ts b/packages/client/src/components/mfm.ts index bb0b71a5a..40b11535f 100644 --- a/packages/client/src/components/mfm.ts +++ b/packages/client/src/components/mfm.ts @@ -187,7 +187,7 @@ export default defineComponent({ 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);`; + style = `transform: translate(${x}em, ${y}em);`; break; } case 'scale': { From 989f0ce41db432c70038075f0c72aa3a9b560458 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sat, 27 May 2023 11:40:34 +0200 Subject: [PATCH 8/8] fix MFM fg/bg color regex CSS colors are either 3 or 6 hex digits, not 3 to 6 (which would allow 4 and 5 digit hex codes, which are not accepted). Also adds an explicit null/undefined check. Changes the default color for the $[bg ] function to something different than the fg color so if you use both functions on a piece of text with default values, the text stays somewhat readable. --- packages/client/src/components/mfm.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/client/src/components/mfm.ts b/packages/client/src/components/mfm.ts index 40b11535f..2d04a9731 100644 --- a/packages/client/src/components/mfm.ts +++ b/packages/client/src/components/mfm.ts @@ -197,14 +197,14 @@ export default defineComponent({ break; } case 'fg': { - let color = token.props.args.color; - if (!/^[0-9a-f]{3,6}$/i.test(color)) color = 'f00'; + let color = token.props.args.color ?? 'f00'; + if (!/^([0-9a-f]{3}){1,2}$/i.test(color)) color = 'f00'; style = `color: #${color};`; break; } case 'bg': { - let color = token.props.args.color; - if (!/^[0-9a-f]{3,6}$/i.test(color)) color = 'f00'; + let color = token.props.args.color ?? '0f0'; + if (!/^([0-9a-f]{3}){1,2}$/i.test(color)) color = '0f0'; style = `background-color: #${color};`; break; }