Frontend: Switched to MMM rendering where possible
ci/woodpecker/push/ociImagePush Pipeline was successful
Details
ci/woodpecker/push/ociImagePush Pipeline was successful
Details
This commit is contained in:
parent
7ad46191ec
commit
76c4d0267f
|
@ -0,0 +1,59 @@
|
||||||
|
<template>
|
||||||
|
<MkLink class="mention" :url="url" @click.stop>
|
||||||
|
<span class="icon">matrix /</span>
|
||||||
|
<span class="main">
|
||||||
|
<span class="username">@{{ username }}</span>
|
||||||
|
<span class="host">:{{ toUnicode(host) }}</span>
|
||||||
|
</span>
|
||||||
|
</MkLink>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { toUnicode } from "punycode";
|
||||||
|
import MkLink from "@/components/MkLink.vue";
|
||||||
|
import { computed } from "vue";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
username: string;
|
||||||
|
host: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const url = computed(
|
||||||
|
() => `https://matrix.to/#/@${props.username}:${props.host}`
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mention {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px 8px 2px 2px;
|
||||||
|
margin-block: 2px;
|
||||||
|
border-radius: 999px;
|
||||||
|
max-width: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
color: var(--mention);
|
||||||
|
isolation: isolate;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
border-radius: inherit;
|
||||||
|
background: var(--mention);
|
||||||
|
opacity: 0.1;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .icon {
|
||||||
|
margin: 0 0.2em 0 0.35em;
|
||||||
|
border-radius: 100%;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .main > .host {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -60,6 +60,7 @@
|
||||||
>
|
>
|
||||||
<Mfm
|
<Mfm
|
||||||
v-if="user.description"
|
v-if="user.description"
|
||||||
|
:mm="user.description_mm"
|
||||||
:text="user.description"
|
:text="user.description"
|
||||||
:author="user"
|
:author="user"
|
||||||
:i="$i"
|
:i="$i"
|
||||||
|
@ -86,6 +87,7 @@
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="value">
|
<dd class="value">
|
||||||
<Mfm
|
<Mfm
|
||||||
|
:mm="field.value_mm"
|
||||||
:text="field.value"
|
:text="field.value"
|
||||||
:author="user"
|
:author="user"
|
||||||
:i="$i"
|
:i="$i"
|
||||||
|
|
|
@ -38,6 +38,7 @@
|
||||||
<Mfm
|
<Mfm
|
||||||
v-if="note.cw != ''"
|
v-if="note.cw != ''"
|
||||||
class="text"
|
class="text"
|
||||||
|
:mm="magMaybeProperty(note, 'cw_mm')"
|
||||||
:text="note.cw"
|
:text="note.cw"
|
||||||
:author="note.user"
|
:author="note.user"
|
||||||
:i="$i"
|
:i="$i"
|
||||||
|
@ -126,6 +127,7 @@
|
||||||
</template>
|
</template>
|
||||||
<Mfm
|
<Mfm
|
||||||
v-if="note.text"
|
v-if="note.text"
|
||||||
|
:mm="magMaybeProperty(note, 'text_mm')"
|
||||||
:text="note.text"
|
:text="note.text"
|
||||||
:author="note.user"
|
:author="note.user"
|
||||||
:i="$i"
|
:i="$i"
|
||||||
|
@ -236,7 +238,7 @@ import { extractMfmWithAnimation } from "@/scripts/extract-mfm";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { defaultStore } from "@/store";
|
import { defaultStore } from "@/store";
|
||||||
import { $i } from "@/account";
|
import { $i } from "@/account";
|
||||||
import { magTransProperty } from "@/scripts-mag/mag-util";
|
import { magMaybeProperty, magTransProperty } from "@/scripts-mag/mag-util";
|
||||||
import { packed } from "magnetar-common";
|
import { packed } from "magnetar-common";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<MfmCore
|
<MfmCore
|
||||||
|
:mm="mm"
|
||||||
:text="text"
|
:text="text"
|
||||||
:plain="plain"
|
:plain="plain"
|
||||||
:nowrap="nowrap"
|
:nowrap="nowrap"
|
||||||
|
@ -9,19 +10,23 @@
|
||||||
class="mfm-object"
|
class="mfm-object"
|
||||||
:class="{
|
:class="{
|
||||||
nowrap,
|
nowrap,
|
||||||
advancedMfm: defaultStore.state.advancedMfm || advancedMfm,
|
advancedMfm: advancedMfmStore || advancedMfm,
|
||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import MfmCore from "@/components/mfm";
|
import MfmCore from "@/components/mfm.vue";
|
||||||
import { defaultStore } from "@/store";
|
|
||||||
import * as Misskey from "calckey-js";
|
import * as Misskey from "calckey-js";
|
||||||
import { packed } from "magnetar-common";
|
import { packed } from "magnetar-common";
|
||||||
|
import { defaultStore } from "@/store";
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
const advancedMfmStore = ref(defaultStore.state.advancedMfm);
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
mm?: string;
|
||||||
text: string;
|
text: string;
|
||||||
plain?: boolean;
|
plain?: boolean;
|
||||||
nowrap?: boolean;
|
nowrap?: boolean;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<Mfm
|
<Mfm
|
||||||
:class="$style.root"
|
:class="$style.root"
|
||||||
|
:mm="magMaybeProperty(user, 'display_name_mm')"
|
||||||
:text="magTransUsername(user)"
|
:text="magTransUsername(user)"
|
||||||
:plain="true"
|
:plain="true"
|
||||||
:nowrap="nowrap"
|
:nowrap="nowrap"
|
||||||
|
@ -11,7 +12,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import * as misskey from "calckey-js";
|
import * as misskey from "calckey-js";
|
||||||
import { packed } from "magnetar-common";
|
import { packed } from "magnetar-common";
|
||||||
import { magTransUsername } from "@/scripts-mag/mag-util";
|
import { magMaybeProperty, magTransUsername } from "@/scripts-mag/mag-util";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
|
|
@ -1,654 +0,0 @@
|
||||||
import type { VNode } from "vue";
|
|
||||||
import { defineComponent, h } from "vue";
|
|
||||||
import * as mfm from "mfm-js";
|
|
||||||
import MkUrl from "@/components/global/MkUrl.vue";
|
|
||||||
import MkLink from "@/components/MkLink.vue";
|
|
||||||
import MagMention from "@/components/MagMention.vue";
|
|
||||||
import { concat } from "@/scripts/array";
|
|
||||||
import MkFormula from "@/components/MkFormula.vue";
|
|
||||||
import MkCode from "@/components/MkCode.vue";
|
|
||||||
import MkSparkle from "@/components/MkSparkle.vue";
|
|
||||||
import MkA from "@/components/global/MkA.vue";
|
|
||||||
import { host } from "@/config";
|
|
||||||
import { reducedMotion } from "@/scripts/reduced-motion";
|
|
||||||
import MagEmoji from "@/components/global/MagEmoji.vue";
|
|
||||||
import {
|
|
||||||
magConvertReaction,
|
|
||||||
magIsMissingEmoji,
|
|
||||||
magReactionEquals,
|
|
||||||
magTransProperty,
|
|
||||||
} from "@/scripts-mag/mag-util";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
text: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
plain: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
nowrap: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
author: {
|
|
||||||
type: Object,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
i: {
|
|
||||||
type: Object,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
customEmojis: {
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
isNote: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
render() {
|
|
||||||
if (this.text == null || this.text === "") return;
|
|
||||||
|
|
||||||
const isPlain = this.plain;
|
|
||||||
|
|
||||||
const ast = (isPlain ? mfm.parseSimple : mfm.parse)(this.text);
|
|
||||||
|
|
||||||
const validTime = (t: string | null | undefined) => {
|
|
||||||
if (t == null) return null;
|
|
||||||
return t.match(/^[0-9.]+s$/) ? t : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const validNumber = (n: string | null | undefined) => {
|
|
||||||
if (n == null) return null;
|
|
||||||
const parsed = parseFloat(n);
|
|
||||||
return !isNaN(parsed) && isFinite(parsed) && parsed > 0;
|
|
||||||
};
|
|
||||||
// const validEase = (e: string | null | undefined) => {
|
|
||||||
// if (e == null) return null;
|
|
||||||
// return e.match(/(steps)?\(-?[0-9.]+,-?[0-9.]+,-?[0-9.]+,-?[0-9.]+\)/)
|
|
||||||
// ? (e.startsWith("steps") ? e : "cubic-bezier" + e)
|
|
||||||
// : null
|
|
||||||
// }
|
|
||||||
|
|
||||||
const genEl = (ast: mfm.MfmNode[]) =>
|
|
||||||
concat(
|
|
||||||
ast.map((token, index): VNode[] => {
|
|
||||||
switch (token.type) {
|
|
||||||
case "text": {
|
|
||||||
const text = token.props.text.replace(
|
|
||||||
/(\r\n|\n|\r)/g,
|
|
||||||
"\n"
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!this.plain) {
|
|
||||||
const res = [];
|
|
||||||
for (const t of text.split("\n")) {
|
|
||||||
res.push(h("br"));
|
|
||||||
res.push(t);
|
|
||||||
}
|
|
||||||
res.shift();
|
|
||||||
return res;
|
|
||||||
} else {
|
|
||||||
return [text.replace(/\n/g, " ")];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case "bold": {
|
|
||||||
return [h("b", genEl(token.children))];
|
|
||||||
}
|
|
||||||
|
|
||||||
case "strike": {
|
|
||||||
return [h("del", genEl(token.children))];
|
|
||||||
}
|
|
||||||
|
|
||||||
case "italic": {
|
|
||||||
return h(
|
|
||||||
"i",
|
|
||||||
{
|
|
||||||
style: "font-style: oblique;",
|
|
||||||
},
|
|
||||||
genEl(token.children)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
case "fn": {
|
|
||||||
// TODO: CSSを文字列で組み立てていくと token.props.args.~~~ 経由でCSSインジェクションできるのでよしなにやる
|
|
||||||
let style: string;
|
|
||||||
switch (token.props.name) {
|
|
||||||
case "tada": {
|
|
||||||
const speed =
|
|
||||||
validTime(token.props.args.speed) ||
|
|
||||||
"1s";
|
|
||||||
const delay =
|
|
||||||
validTime(token.props.args.delay) ||
|
|
||||||
"0s";
|
|
||||||
const loop =
|
|
||||||
validNumber(token.props.args.loop) ||
|
|
||||||
"infinite";
|
|
||||||
// const ease = validEase(token.props.args.ease) || "linear";
|
|
||||||
style = `font-size: 150%; animation: tada ${speed} ${delay} linear ${loop} both;`;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "jelly": {
|
|
||||||
const speed =
|
|
||||||
validTime(token.props.args.speed) ||
|
|
||||||
"1s";
|
|
||||||
const delay =
|
|
||||||
validTime(token.props.args.delay) ||
|
|
||||||
"0s";
|
|
||||||
const loop =
|
|
||||||
validNumber(token.props.args.loop) ||
|
|
||||||
"infinite";
|
|
||||||
style = `animation: mfm-rubberBand ${speed} ${delay} linear ${loop} both;`;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "twitch": {
|
|
||||||
const speed =
|
|
||||||
validTime(token.props.args.speed) ||
|
|
||||||
"0.5s";
|
|
||||||
const delay =
|
|
||||||
validTime(token.props.args.delay) ||
|
|
||||||
"0s";
|
|
||||||
const loop =
|
|
||||||
validNumber(token.props.args.loop) ||
|
|
||||||
"infinite";
|
|
||||||
style = `animation: mfm-twitch ${speed} ${delay} ease ${loop};`;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "shake": {
|
|
||||||
const speed =
|
|
||||||
validTime(token.props.args.speed) ||
|
|
||||||
"0.5s";
|
|
||||||
const delay =
|
|
||||||
validTime(token.props.args.delay) ||
|
|
||||||
"0s";
|
|
||||||
const loop =
|
|
||||||
validNumber(token.props.args.loop) ||
|
|
||||||
"infinite";
|
|
||||||
style = `animation: mfm-shake ${speed} ${delay} ease ${loop};`;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "spin": {
|
|
||||||
const direction = token.props.args.left
|
|
||||||
? "reverse"
|
|
||||||
: token.props.args.alternate
|
|
||||||
? "alternate"
|
|
||||||
: "normal";
|
|
||||||
const anime = token.props.args.x
|
|
||||||
? "mfm-spinX"
|
|
||||||
: token.props.args.y
|
|
||||||
? "mfm-spinY"
|
|
||||||
: "mfm-spin";
|
|
||||||
const speed =
|
|
||||||
validTime(token.props.args.speed) ||
|
|
||||||
"1.5s";
|
|
||||||
const delay =
|
|
||||||
validTime(token.props.args.delay) ||
|
|
||||||
"0s";
|
|
||||||
const loop =
|
|
||||||
validNumber(token.props.args.loop) ||
|
|
||||||
"infinite";
|
|
||||||
style = `animation: ${anime} ${speed} ${delay} linear ${loop}; animation-direction: ${direction};`;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "jump": {
|
|
||||||
const speed =
|
|
||||||
validTime(token.props.args.speed) ||
|
|
||||||
"0.75s";
|
|
||||||
const delay =
|
|
||||||
validTime(token.props.args.delay) ||
|
|
||||||
"0s";
|
|
||||||
const loop =
|
|
||||||
validNumber(token.props.args.loop) ||
|
|
||||||
"infinite";
|
|
||||||
style = `animation: mfm-jump ${speed} ${delay} linear ${loop};`;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "bounce": {
|
|
||||||
const speed =
|
|
||||||
validTime(token.props.args.speed) ||
|
|
||||||
"0.75s";
|
|
||||||
const delay =
|
|
||||||
validTime(token.props.args.delay) ||
|
|
||||||
"0s";
|
|
||||||
const loop =
|
|
||||||
validNumber(token.props.args.loop) ||
|
|
||||||
"infinite";
|
|
||||||
style = `animation: mfm-bounce ${speed} ${delay} linear ${loop}; transform-origin: center bottom;`;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "rainbow": {
|
|
||||||
const speed =
|
|
||||||
validTime(token.props.args.speed) ||
|
|
||||||
"1s";
|
|
||||||
const delay =
|
|
||||||
validTime(token.props.args.delay) ||
|
|
||||||
"0s";
|
|
||||||
const loop =
|
|
||||||
validNumber(token.props.args.loop) ||
|
|
||||||
"infinite";
|
|
||||||
style = `animation: mfm-rainbow ${speed} ${delay} linear ${loop};`;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "sparkle": {
|
|
||||||
if (reducedMotion()) {
|
|
||||||
return genEl(token.children);
|
|
||||||
}
|
|
||||||
return h(
|
|
||||||
MkSparkle,
|
|
||||||
{},
|
|
||||||
genEl(token.children)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
case "fade": {
|
|
||||||
const direction = token.props.args.out
|
|
||||||
? "alternate-reverse"
|
|
||||||
: "alternate";
|
|
||||||
const speed =
|
|
||||||
validTime(token.props.args.speed) ||
|
|
||||||
"1.5s";
|
|
||||||
const delay =
|
|
||||||
validTime(token.props.args.delay) ||
|
|
||||||
"0s";
|
|
||||||
const loop =
|
|
||||||
validNumber(token.props.args.loop) ||
|
|
||||||
"infinite";
|
|
||||||
style = `animation: mfm-fade ${speed} ${delay} linear ${loop}; animation-direction: ${direction};`;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "flip": {
|
|
||||||
const transform =
|
|
||||||
token.props.args.h && token.props.args.v
|
|
||||||
? "scale(-1, -1)"
|
|
||||||
: token.props.args.v
|
|
||||||
? "scaleY(-1)"
|
|
||||||
: "scaleX(-1)";
|
|
||||||
style = `transform: ${transform};`;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "x2": {
|
|
||||||
return h(
|
|
||||||
"span",
|
|
||||||
{
|
|
||||||
class: "mfm-x2",
|
|
||||||
},
|
|
||||||
genEl(token.children)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
case "x3": {
|
|
||||||
return h(
|
|
||||||
"span",
|
|
||||||
{
|
|
||||||
class: "mfm-x3",
|
|
||||||
},
|
|
||||||
genEl(token.children)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
case "x4": {
|
|
||||||
return h(
|
|
||||||
"span",
|
|
||||||
{
|
|
||||||
class: "mfm-x4",
|
|
||||||
},
|
|
||||||
genEl(token.children)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
case "font": {
|
|
||||||
const family = token.props.args.serif
|
|
||||||
? "serif"
|
|
||||||
: token.props.args.monospace
|
|
||||||
? "monospace"
|
|
||||||
: token.props.args.cursive
|
|
||||||
? "cursive"
|
|
||||||
: token.props.args.fantasy
|
|
||||||
? "fantasy"
|
|
||||||
: token.props.args.emoji
|
|
||||||
? "emoji"
|
|
||||||
: token.props.args.math
|
|
||||||
? "math"
|
|
||||||
: null;
|
|
||||||
if (family)
|
|
||||||
style = `font-family: ${family};`;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "blur": {
|
|
||||||
return h(
|
|
||||||
"span",
|
|
||||||
{
|
|
||||||
class: "_blur_text",
|
|
||||||
},
|
|
||||||
genEl(token.children)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
case "rotate": {
|
|
||||||
const rotate = token.props.args.x
|
|
||||||
? "perspective(128px) rotateX"
|
|
||||||
: token.props.args.y
|
|
||||||
? "perspective(128px) rotateY"
|
|
||||||
: "rotate";
|
|
||||||
const degrees =
|
|
||||||
parseInt(token.props.args.deg) || "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"
|
|
||||||
);
|
|
||||||
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"
|
|
||||||
);
|
|
||||||
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
|
|
||||||
);
|
|
||||||
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";
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
case "small": {
|
|
||||||
return h(
|
|
||||||
"small",
|
|
||||||
{
|
|
||||||
style: "opacity: 0.7;",
|
|
||||||
},
|
|
||||||
genEl(token.children)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
case "center": {
|
|
||||||
return h(
|
|
||||||
"div",
|
|
||||||
{
|
|
||||||
style: "text-align: center;",
|
|
||||||
},
|
|
||||||
genEl(token.children)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (style == null) {
|
|
||||||
return h("span", {}, [
|
|
||||||
"$[",
|
|
||||||
token.props.name,
|
|
||||||
" ",
|
|
||||||
...genEl(token.children),
|
|
||||||
"]",
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
return h(
|
|
||||||
"span",
|
|
||||||
{
|
|
||||||
style: `display: inline-block;${style}`,
|
|
||||||
},
|
|
||||||
genEl(token.children)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case "small": {
|
|
||||||
return [
|
|
||||||
h(
|
|
||||||
"small",
|
|
||||||
{
|
|
||||||
style: "opacity: 0.7;",
|
|
||||||
},
|
|
||||||
genEl(token.children)
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
case "center": {
|
|
||||||
return [
|
|
||||||
h(
|
|
||||||
"div",
|
|
||||||
{
|
|
||||||
style: "text-align: center;",
|
|
||||||
},
|
|
||||||
genEl(token.children)
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
case "url": {
|
|
||||||
return [
|
|
||||||
h(MkUrl, {
|
|
||||||
key: Math.random(),
|
|
||||||
url: token.props.url,
|
|
||||||
rel: "nofollow noopener",
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
case "link": {
|
|
||||||
return [
|
|
||||||
h(
|
|
||||||
MkLink,
|
|
||||||
{
|
|
||||||
key: Math.random(),
|
|
||||||
url: token.props.url,
|
|
||||||
rel: "nofollow noopener",
|
|
||||||
},
|
|
||||||
genEl(token.children)
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
case "mention": {
|
|
||||||
return [
|
|
||||||
h(MagMention, {
|
|
||||||
key: Math.random(),
|
|
||||||
username: token.props.username,
|
|
||||||
host:
|
|
||||||
(token.props.host == null &&
|
|
||||||
this.author &&
|
|
||||||
this.author.host != null
|
|
||||||
? this.author.host
|
|
||||||
: token.props.host) || host,
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
case "hashtag": {
|
|
||||||
return [
|
|
||||||
h(
|
|
||||||
MkA,
|
|
||||||
{
|
|
||||||
key: Math.random(),
|
|
||||||
to: `/tags/${encodeURIComponent(
|
|
||||||
token.props.hashtag
|
|
||||||
)}`,
|
|
||||||
style: "color:var(--hashtag);",
|
|
||||||
},
|
|
||||||
`#${token.props.hashtag}`
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
case "blockCode": {
|
|
||||||
return [
|
|
||||||
h(MkCode, {
|
|
||||||
key: Math.random(),
|
|
||||||
code: token.props.code,
|
|
||||||
lang: token.props.lang,
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
case "inlineCode": {
|
|
||||||
return [
|
|
||||||
h(MkCode, {
|
|
||||||
key: Math.random(),
|
|
||||||
code: token.props.code,
|
|
||||||
inline: true,
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
case "quote": {
|
|
||||||
if (!this.nowrap) {
|
|
||||||
return [h("blockquote", genEl(token.children))];
|
|
||||||
} else {
|
|
||||||
return [
|
|
||||||
h(
|
|
||||||
"span",
|
|
||||||
{
|
|
||||||
class: "quote",
|
|
||||||
},
|
|
||||||
genEl(token.children)
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case "emojiCode": {
|
|
||||||
const shortcode = `:${token.props.name}:`;
|
|
||||||
const emoji = magConvertReaction(
|
|
||||||
shortcode,
|
|
||||||
(name, host) =>
|
|
||||||
this.customEmojis.find((e) =>
|
|
||||||
magReactionEquals(
|
|
||||||
magConvertReaction(
|
|
||||||
`:${magTransProperty(
|
|
||||||
e,
|
|
||||||
"shortcode",
|
|
||||||
"name"
|
|
||||||
)}:`
|
|
||||||
),
|
|
||||||
{ name, host, url: null! }
|
|
||||||
)
|
|
||||||
)?.url ?? null
|
|
||||||
);
|
|
||||||
|
|
||||||
if (magIsMissingEmoji(emoji)) {
|
|
||||||
return [shortcode];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
h(MagEmoji, {
|
|
||||||
key: Math.random(),
|
|
||||||
emoji,
|
|
||||||
normal: this.plain,
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
case "unicodeEmoji": {
|
|
||||||
return [
|
|
||||||
h(MagEmoji, {
|
|
||||||
key: Math.random(),
|
|
||||||
emoji: token.props.emoji,
|
|
||||||
normal: this.plain,
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
case "mathInline": {
|
|
||||||
return [
|
|
||||||
h(MkFormula, {
|
|
||||||
key: Math.random(),
|
|
||||||
formula: token.props.formula,
|
|
||||||
block: false,
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
case "mathBlock": {
|
|
||||||
return [
|
|
||||||
h(MkFormula, {
|
|
||||||
key: Math.random(),
|
|
||||||
formula: token.props.formula,
|
|
||||||
block: true,
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
case "search": {
|
|
||||||
const sentinel = "#";
|
|
||||||
let ast2 = (isPlain ? mfm.parseSimple : mfm.parse)(
|
|
||||||
token.props.content + sentinel
|
|
||||||
);
|
|
||||||
if (
|
|
||||||
ast2[ast2.length - 1].type === "text" &&
|
|
||||||
ast2[ast2.length - 1].props.text.endsWith(
|
|
||||||
sentinel
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
ast2[ast2.length - 1].props.text = ast2[
|
|
||||||
ast2.length - 1
|
|
||||||
].props.text.slice(0, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let prefix = "\n";
|
|
||||||
if (
|
|
||||||
index === 0 ||
|
|
||||||
[
|
|
||||||
"blockCode",
|
|
||||||
"center",
|
|
||||||
"mathBlock",
|
|
||||||
"quote",
|
|
||||||
"search",
|
|
||||||
].includes(ast[index - 1].type)
|
|
||||||
) {
|
|
||||||
prefix = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
return [prefix, ...genEl(ast2)];
|
|
||||||
}
|
|
||||||
|
|
||||||
case "plain": {
|
|
||||||
return [h("span", genEl(token.children))];
|
|
||||||
}
|
|
||||||
|
|
||||||
default: {
|
|
||||||
console.error("unrecognized ast type:", token.type);
|
|
||||||
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// Parse ast to DOM
|
|
||||||
return h("span", genEl(ast));
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -0,0 +1,697 @@
|
||||||
|
<template v-slot="{ mfmTree }">
|
||||||
|
<span>
|
||||||
|
<component :is="mfmTree" />
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { h, shallowRef, VNodeArrayChildren, VNodeChild, watch } from "vue";
|
||||||
|
import * as mfm from "mfm-js";
|
||||||
|
import MkUrl from "@/components/global/MkUrl.vue";
|
||||||
|
import MkLink from "@/components/MkLink.vue";
|
||||||
|
import MagMention from "@/components/MagMention.vue";
|
||||||
|
import MagMatrixMention from "@/components/MagMatrixMention.vue";
|
||||||
|
import { concat } from "@/scripts/array";
|
||||||
|
import MkFormula from "@/components/MkFormula.vue";
|
||||||
|
import MkCode from "@/components/MkCode.vue";
|
||||||
|
import MkSparkle from "@/components/MkSparkle.vue";
|
||||||
|
import MkA from "@/components/global/MkA.vue";
|
||||||
|
import { host } from "@/config";
|
||||||
|
import { reducedMotion } from "@/scripts/reduced-motion";
|
||||||
|
import MagEmoji from "@/components/global/MagEmoji.vue";
|
||||||
|
import {
|
||||||
|
magConvertReaction,
|
||||||
|
magIsMissingEmoji,
|
||||||
|
magReactionEquals,
|
||||||
|
magTransProperty,
|
||||||
|
} from "@/scripts-mag/mag-util";
|
||||||
|
import * as Misskey from "calckey-js";
|
||||||
|
import { packed } from "magnetar-common";
|
||||||
|
import {
|
||||||
|
magnetarMarkdownToMfm,
|
||||||
|
MagnetarParseError,
|
||||||
|
MagNode,
|
||||||
|
parseMagnetarMarkdownXml,
|
||||||
|
} from "@/scripts-mag/mmm-util";
|
||||||
|
import { Result } from "@/types/result";
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
mm?: string;
|
||||||
|
text: string;
|
||||||
|
plain?: boolean;
|
||||||
|
nowrap?: boolean;
|
||||||
|
author?: any;
|
||||||
|
customEmojis?: (packed.PackEmojiBase | Misskey.entities.CustomEmoji)[];
|
||||||
|
isNote?: boolean;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
plain: false,
|
||||||
|
nowrap: false,
|
||||||
|
author: null,
|
||||||
|
isNote: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
let ast: MagNode[];
|
||||||
|
|
||||||
|
let result: Result<MagNode[], MagnetarParseError> | null = null;
|
||||||
|
if (props.mm) {
|
||||||
|
result = parseMagnetarMarkdownXml(props.mm).flatMap(
|
||||||
|
magnetarMarkdownToMfm
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result && result.isOk()) {
|
||||||
|
ast = result.unwrap();
|
||||||
|
} else if (!props.text) {
|
||||||
|
return h("span");
|
||||||
|
} else {
|
||||||
|
const isPlain = props.plain;
|
||||||
|
ast = (isPlain ? mfm.parseSimple : mfm.parse)(props.text);
|
||||||
|
}
|
||||||
|
|
||||||
|
const validTime = (t: string | boolean | null | undefined) => {
|
||||||
|
if (t == null) return null;
|
||||||
|
if (typeof t === "boolean") return null;
|
||||||
|
return t.match(/^[0-9.]+s$/) ? t : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const validNumber = (n: string | boolean | null | undefined) => {
|
||||||
|
if (n == null) return null;
|
||||||
|
if (typeof n === "boolean") return null;
|
||||||
|
const parsed = parseFloat(n);
|
||||||
|
return !isNaN(parsed) && isFinite(parsed) && parsed > 0;
|
||||||
|
};
|
||||||
|
// const validEase = (e: string | null | undefined) => {
|
||||||
|
// if (e == null) return null;
|
||||||
|
// return e.match(/(steps)?\(-?[0-9.]+,-?[0-9.]+,-?[0-9.]+,-?[0-9.]+\)/)
|
||||||
|
// ? (e.startsWith("steps") ? e : "cubic-bezier" + e)
|
||||||
|
// : null
|
||||||
|
// }
|
||||||
|
|
||||||
|
const genEl = (ast: MagNode[]) =>
|
||||||
|
concat(
|
||||||
|
ast.map((token, index): VNodeChild[] => {
|
||||||
|
switch (token.type) {
|
||||||
|
case "text": {
|
||||||
|
const text = token.props.text.replace(
|
||||||
|
/(\r\n|\n|\r)/g,
|
||||||
|
"\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!props.plain) {
|
||||||
|
const res = [] as VNodeArrayChildren;
|
||||||
|
for (const t of text.split("\n")) {
|
||||||
|
res.push(h("br"));
|
||||||
|
res.push(t);
|
||||||
|
}
|
||||||
|
res.shift();
|
||||||
|
return res;
|
||||||
|
} else {
|
||||||
|
return [text.replace(/\n/g, " ")];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case "bold": {
|
||||||
|
return [h("b", genEl(token.children))];
|
||||||
|
}
|
||||||
|
|
||||||
|
case "strike": {
|
||||||
|
return [h("del", genEl(token.children))];
|
||||||
|
}
|
||||||
|
|
||||||
|
case "italic": {
|
||||||
|
return [
|
||||||
|
h(
|
||||||
|
"i",
|
||||||
|
{
|
||||||
|
style: "font-style: oblique;",
|
||||||
|
},
|
||||||
|
genEl(token.children)
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
case "fn": {
|
||||||
|
let style: string | null = null;
|
||||||
|
switch (token.props.name) {
|
||||||
|
case "tada": {
|
||||||
|
const speed =
|
||||||
|
validTime(token.props.args.speed) || "1s";
|
||||||
|
const delay =
|
||||||
|
validTime(token.props.args.delay) || "0s";
|
||||||
|
const loop =
|
||||||
|
validNumber(token.props.args.loop) ||
|
||||||
|
"infinite";
|
||||||
|
// const ease = validEase(token.props.args.ease) || "linear";
|
||||||
|
style = `font-size: 150%; animation: tada ${speed} ${delay} linear ${loop} both;`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "jelly": {
|
||||||
|
const speed =
|
||||||
|
validTime(token.props.args.speed) || "1s";
|
||||||
|
const delay =
|
||||||
|
validTime(token.props.args.delay) || "0s";
|
||||||
|
const loop =
|
||||||
|
validNumber(token.props.args.loop) ||
|
||||||
|
"infinite";
|
||||||
|
style = `animation: mfm-rubberBand ${speed} ${delay} linear ${loop} both;`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "twitch": {
|
||||||
|
const speed =
|
||||||
|
validTime(token.props.args.speed) || "0.5s";
|
||||||
|
const delay =
|
||||||
|
validTime(token.props.args.delay) || "0s";
|
||||||
|
const loop =
|
||||||
|
validNumber(token.props.args.loop) ||
|
||||||
|
"infinite";
|
||||||
|
style = `animation: mfm-twitch ${speed} ${delay} ease ${loop};`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "shake": {
|
||||||
|
const speed =
|
||||||
|
validTime(token.props.args.speed) || "0.5s";
|
||||||
|
const delay =
|
||||||
|
validTime(token.props.args.delay) || "0s";
|
||||||
|
const loop =
|
||||||
|
validNumber(token.props.args.loop) ||
|
||||||
|
"infinite";
|
||||||
|
style = `animation: mfm-shake ${speed} ${delay} ease ${loop};`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "spin": {
|
||||||
|
const direction = token.props.args.left
|
||||||
|
? "reverse"
|
||||||
|
: token.props.args.alternate
|
||||||
|
? "alternate"
|
||||||
|
: "normal";
|
||||||
|
const anime = token.props.args.x
|
||||||
|
? "mfm-spinX"
|
||||||
|
: token.props.args.y
|
||||||
|
? "mfm-spinY"
|
||||||
|
: "mfm-spin";
|
||||||
|
const speed =
|
||||||
|
validTime(token.props.args.speed) || "1.5s";
|
||||||
|
const delay =
|
||||||
|
validTime(token.props.args.delay) || "0s";
|
||||||
|
const loop =
|
||||||
|
validNumber(token.props.args.loop) ||
|
||||||
|
"infinite";
|
||||||
|
style = `animation: ${anime} ${speed} ${delay} linear ${loop}; animation-direction: ${direction};`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "jump": {
|
||||||
|
const speed =
|
||||||
|
validTime(token.props.args.speed) ||
|
||||||
|
"0.75s";
|
||||||
|
const delay =
|
||||||
|
validTime(token.props.args.delay) || "0s";
|
||||||
|
const loop =
|
||||||
|
validNumber(token.props.args.loop) ||
|
||||||
|
"infinite";
|
||||||
|
style = `animation: mfm-jump ${speed} ${delay} linear ${loop};`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "bounce": {
|
||||||
|
const speed =
|
||||||
|
validTime(token.props.args.speed) ||
|
||||||
|
"0.75s";
|
||||||
|
const delay =
|
||||||
|
validTime(token.props.args.delay) || "0s";
|
||||||
|
const loop =
|
||||||
|
validNumber(token.props.args.loop) ||
|
||||||
|
"infinite";
|
||||||
|
style = `animation: mfm-bounce ${speed} ${delay} linear ${loop}; transform-origin: center bottom;`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "rainbow": {
|
||||||
|
const speed =
|
||||||
|
validTime(token.props.args.speed) || "1s";
|
||||||
|
const delay =
|
||||||
|
validTime(token.props.args.delay) || "0s";
|
||||||
|
const loop =
|
||||||
|
validNumber(token.props.args.loop) ||
|
||||||
|
"infinite";
|
||||||
|
style = `animation: mfm-rainbow ${speed} ${delay} linear ${loop};`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "sparkle": {
|
||||||
|
if (reducedMotion()) {
|
||||||
|
return genEl(token.children);
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
h(MkSparkle, {}, genEl(token.children)),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
case "fade": {
|
||||||
|
const direction = token.props.args.out
|
||||||
|
? "alternate-reverse"
|
||||||
|
: "alternate";
|
||||||
|
const speed =
|
||||||
|
validTime(token.props.args.speed) || "1.5s";
|
||||||
|
const delay =
|
||||||
|
validTime(token.props.args.delay) || "0s";
|
||||||
|
const loop =
|
||||||
|
validNumber(token.props.args.loop) ||
|
||||||
|
"infinite";
|
||||||
|
style = `animation: mfm-fade ${speed} ${delay} linear ${loop}; animation-direction: ${direction};`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "flip": {
|
||||||
|
const transform =
|
||||||
|
token.props.args.h && token.props.args.v
|
||||||
|
? "scale(-1, -1)"
|
||||||
|
: token.props.args.v
|
||||||
|
? "scaleY(-1)"
|
||||||
|
: "scaleX(-1)";
|
||||||
|
style = `transform: ${transform};`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "x2": {
|
||||||
|
return [
|
||||||
|
h(
|
||||||
|
"span",
|
||||||
|
{
|
||||||
|
class: "mfm-x2",
|
||||||
|
},
|
||||||
|
genEl(token.children)
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
case "x3": {
|
||||||
|
return [
|
||||||
|
h(
|
||||||
|
"span",
|
||||||
|
{
|
||||||
|
class: "mfm-x3",
|
||||||
|
},
|
||||||
|
genEl(token.children)
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
case "x4": {
|
||||||
|
return [
|
||||||
|
h(
|
||||||
|
"span",
|
||||||
|
{
|
||||||
|
class: "mfm-x4",
|
||||||
|
},
|
||||||
|
genEl(token.children)
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
case "font": {
|
||||||
|
const family = token.props.args.serif
|
||||||
|
? "serif"
|
||||||
|
: token.props.args.monospace
|
||||||
|
? "monospace"
|
||||||
|
: token.props.args.cursive
|
||||||
|
? "cursive"
|
||||||
|
: token.props.args.fantasy
|
||||||
|
? "fantasy"
|
||||||
|
: token.props.args.emoji
|
||||||
|
? "emoji"
|
||||||
|
: token.props.args.math
|
||||||
|
? "math"
|
||||||
|
: null;
|
||||||
|
if (family) style = `font-family: ${family};`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "blur": {
|
||||||
|
return [
|
||||||
|
h(
|
||||||
|
"span",
|
||||||
|
{
|
||||||
|
class: "_blur_text",
|
||||||
|
},
|
||||||
|
genEl(token.children)
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
case "rotate": {
|
||||||
|
const rotate = token.props.args.x
|
||||||
|
? "perspective(128px) rotateX"
|
||||||
|
: token.props.args.y
|
||||||
|
? "perspective(128px) rotateY"
|
||||||
|
: "rotate";
|
||||||
|
const degrees =
|
||||||
|
parseInt("" + token.props.args.deg) || "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"
|
||||||
|
);
|
||||||
|
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"
|
||||||
|
);
|
||||||
|
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
|
||||||
|
);
|
||||||
|
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";
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
case "small": {
|
||||||
|
return [
|
||||||
|
h(
|
||||||
|
"small",
|
||||||
|
{
|
||||||
|
style: "opacity: 0.7;",
|
||||||
|
},
|
||||||
|
genEl(token.children)
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
case "center": {
|
||||||
|
return [
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
style: "text-align: center;",
|
||||||
|
},
|
||||||
|
genEl(token.children)
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (style) {
|
||||||
|
return [
|
||||||
|
h(
|
||||||
|
"span",
|
||||||
|
{
|
||||||
|
style: `display: inline-block;${style}`,
|
||||||
|
},
|
||||||
|
genEl(token.children)
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
h("span", {}, [
|
||||||
|
"$[",
|
||||||
|
token.props.name,
|
||||||
|
" ",
|
||||||
|
...genEl(token.children),
|
||||||
|
"]",
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
case "small": {
|
||||||
|
return [
|
||||||
|
h(
|
||||||
|
"small",
|
||||||
|
{
|
||||||
|
style: "opacity: 0.7;",
|
||||||
|
},
|
||||||
|
genEl(token.children)
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
case "center": {
|
||||||
|
return [
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
style: "text-align: center;",
|
||||||
|
},
|
||||||
|
genEl(token.children)
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
case "url": {
|
||||||
|
return [
|
||||||
|
h(MkUrl, {
|
||||||
|
key: Math.random(),
|
||||||
|
url: token.props.url,
|
||||||
|
rel: "nofollow noopener",
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
case "link": {
|
||||||
|
return [
|
||||||
|
h(
|
||||||
|
MkLink,
|
||||||
|
{
|
||||||
|
key: Math.random(),
|
||||||
|
url: token.props.url,
|
||||||
|
rel: "nofollow noopener",
|
||||||
|
},
|
||||||
|
genEl(token.children)
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
case "mention": {
|
||||||
|
return [
|
||||||
|
h(MagMention, {
|
||||||
|
key: Math.random(),
|
||||||
|
username: token.props.username,
|
||||||
|
host:
|
||||||
|
(token.props.host == null &&
|
||||||
|
props.author &&
|
||||||
|
props.author.host != null
|
||||||
|
? props.author.host
|
||||||
|
: token.props.host) || host,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
case "matrixMention": {
|
||||||
|
return [
|
||||||
|
h(MagMatrixMention, {
|
||||||
|
key: Math.random(),
|
||||||
|
username: token.props.username,
|
||||||
|
host: token.props.host,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
case "hashtag": {
|
||||||
|
return [
|
||||||
|
h(
|
||||||
|
MkA,
|
||||||
|
{
|
||||||
|
key: Math.random(),
|
||||||
|
to: `/tags/${encodeURIComponent(
|
||||||
|
token.props.hashtag
|
||||||
|
)}`,
|
||||||
|
style: "color:var(--hashtag);",
|
||||||
|
},
|
||||||
|
`#${token.props.hashtag}`
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
case "blockCode": {
|
||||||
|
return [
|
||||||
|
h(MkCode, {
|
||||||
|
key: Math.random(),
|
||||||
|
code: token.props.code,
|
||||||
|
lang: token.props.lang,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
case "inlineCode": {
|
||||||
|
return [
|
||||||
|
h(MkCode, {
|
||||||
|
key: Math.random(),
|
||||||
|
code: token.props.code,
|
||||||
|
inline: true,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
case "quote": {
|
||||||
|
if (!props.nowrap) {
|
||||||
|
return [h("blockquote", genEl(token.children))];
|
||||||
|
} else {
|
||||||
|
return [
|
||||||
|
h(
|
||||||
|
"span",
|
||||||
|
{
|
||||||
|
class: "quote",
|
||||||
|
},
|
||||||
|
genEl(token.children)
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case "emojiCode": {
|
||||||
|
const shortcode = `:${token.props.name}:`;
|
||||||
|
const emoji = magConvertReaction(
|
||||||
|
shortcode,
|
||||||
|
(name, host) =>
|
||||||
|
props.customEmojis?.find((e) =>
|
||||||
|
magReactionEquals(
|
||||||
|
magConvertReaction(
|
||||||
|
`:${magTransProperty(
|
||||||
|
e,
|
||||||
|
"shortcode",
|
||||||
|
"name"
|
||||||
|
)}:`
|
||||||
|
),
|
||||||
|
{ name, host, url: null! }
|
||||||
|
)
|
||||||
|
)?.url ?? null!
|
||||||
|
);
|
||||||
|
|
||||||
|
if (magIsMissingEmoji(emoji)) {
|
||||||
|
return [shortcode];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
h(MagEmoji, {
|
||||||
|
key: Math.random(),
|
||||||
|
emoji,
|
||||||
|
normal: props.plain,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
case "unicodeEmoji": {
|
||||||
|
return [
|
||||||
|
h(MagEmoji, {
|
||||||
|
key: Math.random(),
|
||||||
|
emoji: token.props.emoji,
|
||||||
|
normal: props.plain,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
case "mathInline": {
|
||||||
|
return [
|
||||||
|
h(MkFormula, {
|
||||||
|
key: Math.random(),
|
||||||
|
formula: token.props.formula,
|
||||||
|
block: false,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
case "mathBlock": {
|
||||||
|
return [
|
||||||
|
h(MkFormula, {
|
||||||
|
key: Math.random(),
|
||||||
|
formula: token.props.formula,
|
||||||
|
block: true,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
case "search": {
|
||||||
|
const sentinel = "#";
|
||||||
|
let ast2 = (props.plain ? mfm.parseSimple : mfm.parse)(
|
||||||
|
token.props.content + sentinel
|
||||||
|
);
|
||||||
|
|
||||||
|
const lastNode = ast2[ast2.length - 1];
|
||||||
|
if (
|
||||||
|
lastNode.type === "text" &&
|
||||||
|
(
|
||||||
|
lastNode.props as
|
||||||
|
| mfm.MfmText["props"]
|
||||||
|
| undefined
|
||||||
|
)?.text?.endsWith(sentinel)
|
||||||
|
) {
|
||||||
|
lastNode.props.text = lastNode.props.text.slice(
|
||||||
|
0,
|
||||||
|
-1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let prefix = "\n";
|
||||||
|
if (
|
||||||
|
index === 0 ||
|
||||||
|
[
|
||||||
|
"blockCode",
|
||||||
|
"center",
|
||||||
|
"mathBlock",
|
||||||
|
"quote",
|
||||||
|
"search",
|
||||||
|
].includes(ast[index - 1].type)
|
||||||
|
) {
|
||||||
|
prefix = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return [prefix, ...genEl(ast2)];
|
||||||
|
}
|
||||||
|
|
||||||
|
case "plain": {
|
||||||
|
return [h("span", genEl(token.children))];
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
console.error(
|
||||||
|
"unrecognized ast type:",
|
||||||
|
(token as any)?.type
|
||||||
|
);
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return h("span", genEl(ast));
|
||||||
|
}
|
||||||
|
|
||||||
|
const mfmTree = shallowRef<VNodeChild>(props.text);
|
||||||
|
watch(
|
||||||
|
() => props.text,
|
||||||
|
() => {
|
||||||
|
mfmTree.value = render();
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
|
@ -12,6 +12,7 @@ import "@phosphor-icons/web/fill";
|
||||||
//#region account indexedDB migration
|
//#region account indexedDB migration
|
||||||
import { set } from "@/scripts/idb-proxy";
|
import { set } from "@/scripts/idb-proxy";
|
||||||
import {
|
import {
|
||||||
|
App,
|
||||||
computed,
|
computed,
|
||||||
createApp,
|
createApp,
|
||||||
defineAsyncComponent,
|
defineAsyncComponent,
|
||||||
|
@ -42,8 +43,6 @@ import { reactionPicker } from "@/scripts/reaction-picker";
|
||||||
import { getUrlWithoutLoginId } from "@/scripts/login-id";
|
import { getUrlWithoutLoginId } from "@/scripts/login-id";
|
||||||
import { getAccountFromId } from "@/scripts/get-account-from-id";
|
import { getAccountFromId } from "@/scripts/get-account-from-id";
|
||||||
|
|
||||||
import { App } from "vue";
|
|
||||||
|
|
||||||
import Mfm from "./components/global/MkMisskeyFlavoredMarkdown.vue";
|
import Mfm from "./components/global/MkMisskeyFlavoredMarkdown.vue";
|
||||||
import MkA from "./components/global/MkA.vue";
|
import MkA from "./components/global/MkA.vue";
|
||||||
import MkAcct from "./components/global/MkAcct.vue";
|
import MkAcct from "./components/global/MkAcct.vue";
|
||||||
|
|
|
@ -392,7 +392,7 @@ import { $i } from "@/account";
|
||||||
import MkFollowApproveButton from "@/components/MkFollowApproveButton.vue";
|
import MkFollowApproveButton from "@/components/MkFollowApproveButton.vue";
|
||||||
import MkUserName from "@/components/global/MkUserName.vue";
|
import MkUserName from "@/components/global/MkUserName.vue";
|
||||||
import MkAvatar from "@/components/global/MkAvatar.vue";
|
import MkAvatar from "@/components/global/MkAvatar.vue";
|
||||||
import Mfm from "@/components/mfm";
|
import Mfm from "@/components/mfm.vue";
|
||||||
import MkTime from "@/components/global/MkTime.vue";
|
import MkTime from "@/components/global/MkTime.vue";
|
||||||
import MkA from "@/components/global/MkA.vue";
|
import MkA from "@/components/global/MkA.vue";
|
||||||
import page from "@/components/page/page.vue";
|
import page from "@/components/page/page.vue";
|
||||||
|
|
|
@ -33,6 +33,19 @@ export function magTransProperty<
|
||||||
return x[keyB];
|
return x[keyB];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function magMaybeProperty<
|
||||||
|
A extends Record<string, any>,
|
||||||
|
AA extends keyof UnionToIntersection<A> & string
|
||||||
|
>(x: A, keyA: AA): UnionIntersectionMerge<A>[AA] | undefined {
|
||||||
|
const a = x[keyA];
|
||||||
|
|
||||||
|
if (typeof a !== "undefined") {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export function magTransMap<
|
export function magTransMap<
|
||||||
A extends Record<string, any>,
|
A extends Record<string, any>,
|
||||||
AA extends keyof UnionIntersectionMerge<A> & string,
|
AA extends keyof UnionIntersectionMerge<A> & string,
|
||||||
|
|
|
@ -0,0 +1,222 @@
|
||||||
|
import { Err, Ok, Result } from "@/types/result";
|
||||||
|
import * as mfm from "mfm-js";
|
||||||
|
import { types } from "magnetar-common";
|
||||||
|
|
||||||
|
export type MagnetarParseError =
|
||||||
|
| "XMLParseError"
|
||||||
|
| "XMLParseException"
|
||||||
|
| "InvalidRootNode";
|
||||||
|
|
||||||
|
export function parseMagnetarMarkdownXml(
|
||||||
|
xml: types.MmXml
|
||||||
|
): Result<XMLDocument, MagnetarParseError> {
|
||||||
|
const parser = new DOMParser();
|
||||||
|
const document = parser.parseFromString(
|
||||||
|
xml,
|
||||||
|
"application/xml"
|
||||||
|
) as XMLDocument;
|
||||||
|
const error = document.querySelector("parsererror");
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return Err("XMLParseError");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.documentElement.tagName !== "mmm") {
|
||||||
|
return Err("InvalidRootNode");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(document);
|
||||||
|
}
|
||||||
|
|
||||||
|
export declare type MmmMatrixMention = {
|
||||||
|
type: "matrixMention";
|
||||||
|
props: {
|
||||||
|
username: string;
|
||||||
|
host: string;
|
||||||
|
};
|
||||||
|
children?: [];
|
||||||
|
};
|
||||||
|
|
||||||
|
type MagnetarChildren<N extends { children?: any[] }> = {
|
||||||
|
[T in keyof N]: T extends "children"
|
||||||
|
? N[T] extends []
|
||||||
|
? []
|
||||||
|
: MagNode[]
|
||||||
|
: N[T];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MagNodeInline = mfm.MfmNode | MmmMatrixMention;
|
||||||
|
export type MagNode = MagnetarChildren<mfm.MfmNode> | MmmMatrixMention;
|
||||||
|
|
||||||
|
const mkBase = <T>(type: T): { type: T; props: Record<string, unknown> } => ({
|
||||||
|
type,
|
||||||
|
props: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const mk = <T, P extends Record<string, unknown>, C>(
|
||||||
|
type: T,
|
||||||
|
props: P,
|
||||||
|
children?: C
|
||||||
|
): {
|
||||||
|
type: T;
|
||||||
|
props: P;
|
||||||
|
children: C extends (infer I & {})[] ? I[] : [];
|
||||||
|
} => {
|
||||||
|
return {
|
||||||
|
...mkBase(type),
|
||||||
|
props,
|
||||||
|
...(typeof children === "undefined" ? children : { children }),
|
||||||
|
} as {
|
||||||
|
type: T;
|
||||||
|
props: P;
|
||||||
|
children: C extends (infer I & {})[] ? I[] : [];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function mapMmXmlNodeListToMfm(nodes: NodeListOf<ChildNode>): MagNode[] {
|
||||||
|
return Array.from(nodes).map(mapMmXmlNodeToMfm);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mapMmXmlNodeToMfm(node: Node): MagNode {
|
||||||
|
switch (node.nodeType) {
|
||||||
|
case Node.TEXT_NODE:
|
||||||
|
return mfm.TEXT(node.textContent ?? "");
|
||||||
|
case Node.ELEMENT_NODE: {
|
||||||
|
const el = node as Element;
|
||||||
|
switch (el.tagName) {
|
||||||
|
case "quote":
|
||||||
|
case "small":
|
||||||
|
case "center":
|
||||||
|
return mk(
|
||||||
|
el.tagName,
|
||||||
|
{},
|
||||||
|
mapMmXmlNodeListToMfm(el.childNodes)
|
||||||
|
);
|
||||||
|
case "b":
|
||||||
|
return mk("bold", {}, mapMmXmlNodeListToMfm(el.childNodes));
|
||||||
|
case "i":
|
||||||
|
return mk(
|
||||||
|
"italic",
|
||||||
|
{},
|
||||||
|
mapMmXmlNodeListToMfm(el.childNodes)
|
||||||
|
);
|
||||||
|
case "s":
|
||||||
|
return mk(
|
||||||
|
"strike",
|
||||||
|
{},
|
||||||
|
mapMmXmlNodeListToMfm(el.childNodes)
|
||||||
|
);
|
||||||
|
case "inline-code":
|
||||||
|
return mk("inlineCode", {
|
||||||
|
code: el.textContent ?? "",
|
||||||
|
});
|
||||||
|
case "inline-math":
|
||||||
|
return mk("mathInline", {
|
||||||
|
formula: el.textContent ?? "",
|
||||||
|
});
|
||||||
|
case "a":
|
||||||
|
const url = el.getAttribute("href");
|
||||||
|
|
||||||
|
if (!url) break;
|
||||||
|
|
||||||
|
return mk(
|
||||||
|
"link",
|
||||||
|
{
|
||||||
|
url,
|
||||||
|
silent: el.getAttribute("embed") !== "true",
|
||||||
|
},
|
||||||
|
mapMmXmlNodeListToMfm(el.childNodes)
|
||||||
|
);
|
||||||
|
case "code":
|
||||||
|
return mk("blockCode", {
|
||||||
|
code: el.textContent ?? "",
|
||||||
|
lang: el.getAttribute("lang"),
|
||||||
|
});
|
||||||
|
case "math":
|
||||||
|
return mk("mathBlock", {
|
||||||
|
formula: el.textContent ?? "",
|
||||||
|
});
|
||||||
|
case "hashtag":
|
||||||
|
if (!el.textContent) break;
|
||||||
|
|
||||||
|
return mk("hashtag", {
|
||||||
|
hashtag: el.textContent,
|
||||||
|
});
|
||||||
|
case "function":
|
||||||
|
return mk("mathBlock", {
|
||||||
|
formula: el.textContent ?? "",
|
||||||
|
});
|
||||||
|
case "ue":
|
||||||
|
if (!el.textContent) break;
|
||||||
|
return mk("unicodeEmoji", {
|
||||||
|
emoji: el.textContent,
|
||||||
|
});
|
||||||
|
case "ee":
|
||||||
|
if (!el.textContent) break;
|
||||||
|
return mk("emojiCode", {
|
||||||
|
name: el.textContent,
|
||||||
|
});
|
||||||
|
case "mention":
|
||||||
|
const username = el.getAttribute("name");
|
||||||
|
const host = el.getAttribute("host");
|
||||||
|
const type = el.getAttribute("type");
|
||||||
|
if (!username || (type === "matrix_user" && !host)) break;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case "matrix_user":
|
||||||
|
return mk("matrixMention", {
|
||||||
|
username,
|
||||||
|
host: host!,
|
||||||
|
});
|
||||||
|
default:
|
||||||
|
return mk("mention", {
|
||||||
|
username,
|
||||||
|
host,
|
||||||
|
acct: host
|
||||||
|
? `@${username}@${host}`
|
||||||
|
: `@${username}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
case "fn":
|
||||||
|
const name = el.getAttribute("name") ?? "";
|
||||||
|
const args = el
|
||||||
|
.getAttributeNames()
|
||||||
|
.filter((v) => v.startsWith("arg-"))
|
||||||
|
.map((v) => [
|
||||||
|
v.substring("arg-".length),
|
||||||
|
el.getAttribute(v) || true,
|
||||||
|
])
|
||||||
|
.reduce<Record<string, string | true>>(
|
||||||
|
(acc, [k, v]) => ({
|
||||||
|
...acc,
|
||||||
|
[k as string]: v as string | true,
|
||||||
|
}),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
return mk(
|
||||||
|
"fn",
|
||||||
|
{
|
||||||
|
name,
|
||||||
|
args,
|
||||||
|
},
|
||||||
|
mapMmXmlNodeListToMfm(el.childNodes)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mfm.TEXT(node.textContent ?? "");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function magnetarMarkdownToMfm(
|
||||||
|
doc: XMLDocument
|
||||||
|
): Result<MagNode[], MagnetarParseError> {
|
||||||
|
const el = doc.documentElement;
|
||||||
|
|
||||||
|
if (el.tagName !== "mmm") {
|
||||||
|
return Err("InvalidRootNode");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(mapMmXmlNodeListToMfm(el.childNodes));
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
export class ResultOk<T, E> {
|
||||||
|
public ok: T;
|
||||||
|
|
||||||
|
constructor(value: T) {
|
||||||
|
this.ok = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public map<U>(mapper: (t: T) => U): Result<U, E> {
|
||||||
|
return new ResultOk(mapper(this.ok));
|
||||||
|
}
|
||||||
|
|
||||||
|
public mapError<UE>(mapper: (t: E) => UE): Result<T, UE> {
|
||||||
|
return new ResultOk(this.ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
public flatMap<U, E2>(
|
||||||
|
mapper: (t: T) => Result<U, [E] extends [E2] ? E2 : E>
|
||||||
|
): Result<U, [E] extends [E2] ? E2 : E> {
|
||||||
|
return mapper(this.ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
public errorInto<E2>(): Result<T, [E] extends [E2] ? E2 : E> {
|
||||||
|
return this as Result<T, [E] extends [E2] ? E2 : E>;
|
||||||
|
}
|
||||||
|
|
||||||
|
public isOk(): this is ResultOk<T, E> {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public isErr(): this is ResultErr<T, E> {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public unwrap(): T {
|
||||||
|
return this.ok;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ResultErr<T, E> {
|
||||||
|
public error: E;
|
||||||
|
|
||||||
|
constructor(errorValue: E) {
|
||||||
|
this.error = errorValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public map<U>(_mapper: (t: T) => U): Result<T, E> {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public mapError<UE>(mapper: (t: E) => UE): Result<T, UE> {
|
||||||
|
return new ResultErr(mapper(this.error));
|
||||||
|
}
|
||||||
|
|
||||||
|
public flatMap<U, E2>(
|
||||||
|
_mapper: (t: T) => Result<U, [E] extends [E2] ? E2 : E>
|
||||||
|
): Result<U, [E] extends [E2] ? E2 : E> {
|
||||||
|
return Err<[E] extends [E2] ? E2 : E, U>(
|
||||||
|
this.error as [E] extends [E2] ? E2 : E
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public errorInto<E2>(): Result<T, [E] extends [E2] ? E2 : E> {
|
||||||
|
return this as Result<T, [E] extends [E2] ? E2 : E>;
|
||||||
|
}
|
||||||
|
|
||||||
|
public isOk(): this is ResultOk<T, E> {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public isErr(): this is ResultOk<T, E> {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public unwrap(): never {
|
||||||
|
throw this.error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Ok<T, E = never>(value: T): Result<T, E> {
|
||||||
|
return new ResultOk(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Err<E, T = never>(errorValue: E): Result<T, E> {
|
||||||
|
return new ResultErr(errorValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Result<T, E> = ResultOk<T, E> | ResultErr<T, E>;
|
Loading…
Reference in New Issue