refactor: Fix type errors of mfm.ts

This commit is contained in:
Lhcfl 2024-04-10 17:16:25 +08:00
parent 12f04a19b4
commit 268b7aeb3f
1 changed files with 137 additions and 92 deletions

View File

@ -1,6 +1,6 @@
import { defineComponent, h } from "vue";
import * as mfm from "mfm-js";
import type { VNode, PropType } from "vue";
import type { PropType, VNodeArrayChildren } from "vue";
import MkUrl from "@/components/global/MkUrl.vue";
import MkLink from "@/components/MkLink.vue";
import MkMention from "@/components/MkMention.vue";
@ -30,11 +30,12 @@ export default defineComponent({
default: false,
},
author: {
type: Object,
type: Object as PropType<entities.User>,
default: null,
},
// TODO: This variable is not used in the code and may be removed
i: {
type: Object,
type: Object as PropType<entities.User>,
default: null,
},
customEmojis: {
@ -58,14 +59,16 @@ export default defineComponent({
const ast = (isPlain ? mfm.parseSimple : mfm.parse)(this.text);
const validTime = (t: string | null | undefined) => {
const validTime = (t: string | null | undefined | boolean) => {
if (t == null) return null;
if (typeof t !== "string") return null;
return t.match(/^[0-9.]+s$/) ? t : null;
};
const validNumber = (n: string | null | undefined) => {
const validNumber = (n: string | null | undefined | boolean) => {
if (n == null) return null;
const parsed = parseFloat(n);
if (typeof n !== "string") return null;
const parsed = Number.parseFloat(n);
return !Number.isNaN(parsed) && Number.isFinite(parsed) && parsed > 0;
};
// const validEase = (e: string | null | undefined) => {
@ -77,13 +80,13 @@ export default defineComponent({
const genEl = (ast: mfm.MfmNode[]) =>
concat(
ast.map((token, index): VNode[] => {
ast.map((token, index): VNodeArrayChildren => {
switch (token.type) {
case "text": {
const text = token.props.text.replace(/(\r\n|\n|\r)/g, "\n");
if (!this.plain) {
const res = [];
const res: VNodeArrayChildren = [];
for (const t of text.split("\n")) {
res.push(h("br"));
res.push(t);
@ -104,18 +107,20 @@ export default defineComponent({
}
case "italic": {
return h(
"i",
{
style: "font-style: oblique;",
},
genEl(token.children),
);
return [
h(
"i",
{
style: "font-style: oblique;",
},
genEl(token.children),
),
];
}
case "fn": {
// TODO: CSSを文字列で組み立てていくと token.props.args.~~~ 経由でCSSインジェクションできるのでよしなにやる
let style: string;
let style: string | null = null;
switch (token.props.name) {
case "tada": {
const speed = validTime(token.props.args.speed) || "1s";
@ -188,7 +193,7 @@ export default defineComponent({
if (reducedMotion()) {
return genEl(token.children);
}
return h(MkSparkle, {}, genEl(token.children));
return [h(MkSparkle, {}, genEl(token.children))];
}
case "fade": {
const direction = token.props.args.out
@ -211,31 +216,37 @@ export default defineComponent({
break;
}
case "x2": {
return h(
"span",
{
class: "mfm-x2",
},
genEl(token.children),
);
return [
h(
"span",
{
class: "mfm-x2",
},
genEl(token.children),
),
];
}
case "x3": {
return h(
"span",
{
class: "mfm-x3",
},
genEl(token.children),
);
return [
h(
"span",
{
class: "mfm-x3",
},
genEl(token.children),
),
];
}
case "x4": {
return h(
"span",
{
class: "mfm-x4",
},
genEl(token.children),
);
return [
h(
"span",
{
class: "mfm-x4",
},
genEl(token.children),
),
];
}
case "font": {
const family = token.props.args.serif
@ -255,13 +266,15 @@ export default defineComponent({
break;
}
case "blur": {
return h(
"span",
{
class: "_blur_text",
},
genEl(token.children),
);
return [
h(
"span",
{
class: "_blur_text",
},
genEl(token.children),
),
];
}
case "rotate": {
const rotate = token.props.args.x
@ -269,77 +282,105 @@ export default defineComponent({
: token.props.args.y
? "perspective(128px) rotateY"
: "rotate";
const degrees = parseFloat(token.props.args.deg ?? "90");
const degrees = Number.parseFloat(
token.props.args.deg.toString() ?? "90",
);
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");
const x = Number.parseFloat(
token.props.args.x.toString() ?? "0",
);
const y = Number.parseFloat(
token.props.args.y.toString() ?? "0",
);
style = `transform: translateX(${x}em) translateY(${y}em);`;
break;
}
case "crop": {
const top = parseFloat(token.props.args.top ?? "0");
const right = parseFloat(token.props.args.right ?? "0");
const bottom = parseFloat(token.props.args.bottom ?? "0");
const left = parseFloat(token.props.args.left ?? "0");
const top = Number.parseFloat(
token.props.args.top.toString() ?? "0",
);
const right = Number.parseFloat(
token.props.args.right.toString() ?? "0",
);
const bottom = Number.parseFloat(
token.props.args.bottom.toString() ?? "0",
);
const left = Number.parseFloat(
token.props.args.left.toString() ?? "0",
);
style = `clip-path: inset(${top}% ${right}% ${bottom}% ${left}%);`;
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);
const x = Math.min(
Number.parseFloat(token.props.args.x.toString() ?? "1"),
5,
);
const y = Math.min(
Number.parseFloat(token.props.args.y.toString() ?? "1"),
5,
);
style = `transform: scale(${x}, ${y});`;
break;
}
case "fg": {
let color = token.props.args.color;
if (!/^[0-9a-f]{3,6}$/i.test(color)) color = "f00";
if (!/^[0-9a-f]{3,6}$/i.test(color.toString())) 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";
if (!/^[0-9a-f]{3,6}$/i.test(color.toString())) color = "f00";
style = `background-color: #${color};`;
break;
}
case "small": {
return h(
"small",
{
style: "opacity: 0.7;",
},
genEl(token.children),
);
return [
h(
"small",
{
style: "opacity: 0.7;",
},
genEl(token.children),
),
];
}
case "center": {
return h(
"div",
{
style: "text-align: center;",
},
genEl(token.children),
);
return [
h(
"div",
{
style: "text-align: center;",
},
genEl(token.children),
),
];
}
}
if (style == null) {
return h("span", {}, [
"$[",
token.props.name,
" ",
...genEl(token.children),
"]",
]);
return [
h("span", {}, [
"$[",
token.props.name,
" ",
...genEl(token.children),
"]",
]),
];
} else {
return h(
"span",
{
style: `display: inline-block;${style}`,
},
genEl(token.children),
);
return [
h(
"span",
{
style: `display: inline-block;${style}`,
},
genEl(token.children),
),
];
}
}
@ -425,7 +466,7 @@ export default defineComponent({
h(MkCode, {
key: Math.random(),
code: token.props.code,
lang: token.props.lang,
lang: token.props.lang ?? undefined,
}),
];
}
@ -506,13 +547,15 @@ export default defineComponent({
const ast2 = (isPlain ? mfm.parseSimple : mfm.parse)(
token.props.content.slice(0, -6) + sentinel,
);
function isMfmText(n: mfm.MfmNode): n is mfm.MfmText {
return n.type === "text";
}
const txtNode = ast2[ast2.length - 1];
if (
ast2[ast2.length - 1].type === "text" &&
ast2[ast2.length - 1].props.text.endsWith(sentinel)
isMfmText(txtNode) &&
txtNode.props.text.endsWith(sentinel)
) {
ast2[ast2.length - 1].props.text = ast2[
ast2.length - 1
].props.text.slice(0, -1);
txtNode.props.text = txtNode.props.text.slice(0, -1);
} else {
// I don't think this scope is reachable
console.warn(
@ -554,8 +597,10 @@ export default defineComponent({
}
default: {
console.error("unrecognized ast type:", token.type);
console.error(
"unrecognized ast type:",
(token as { type: never }).type,
);
return [];
}
}