wip: parsing & rendering of markdown ulists
This commit is contained in:
parent
17e1c34a3b
commit
00d7e1afc7
3 changed files with 142 additions and 68 deletions
|
|
@ -73,10 +73,10 @@ onMounted(() => {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<OptionalParent :is="tag">
|
<OptionalParent :is="tag">
|
||||||
<template v-for="(line, index) in markdown.lines" :key="index">
|
<template v-for="(line, index) in markdown.elements" :key="index">
|
||||||
<p v-if="line.type === 'paragraph'" :class="lineClass">
|
<p v-if="line.type === 'paragraph'" :class="lineClass">
|
||||||
<MarkdownParts
|
<MarkdownParts
|
||||||
:elements="line.elements"
|
:elements="line.paragraph.spans"
|
||||||
:slots="markdown.slots">
|
:slots="markdown.slots">
|
||||||
<template
|
<template
|
||||||
v-for="(slot, name) in providedSlots"
|
v-for="(slot, name) in providedSlots"
|
||||||
|
|
@ -88,7 +88,7 @@ onMounted(() => {
|
||||||
</p>
|
</p>
|
||||||
<h3 v-else-if="line.type === 'header'" :class="lineClass">
|
<h3 v-else-if="line.type === 'header'" :class="lineClass">
|
||||||
<MarkdownParts
|
<MarkdownParts
|
||||||
:elements="line.elements"
|
:elements="line.header.spans"
|
||||||
:slots="markdown.slots">
|
:slots="markdown.slots">
|
||||||
<template
|
<template
|
||||||
v-for="(slot, name) in providedSlots"
|
v-for="(slot, name) in providedSlots"
|
||||||
|
|
@ -98,11 +98,18 @@ onMounted(() => {
|
||||||
</template>
|
</template>
|
||||||
</MarkdownParts>
|
</MarkdownParts>
|
||||||
</h3>
|
</h3>
|
||||||
<!-- <ul v-else-if="line.type === 'ulist'">
|
<ul v-else-if="line.type === 'ulist'" :class="lineClass">
|
||||||
<li v-for="(li, index) in line.ulist" :key="index">
|
<li v-for="item in line.ulist.items">
|
||||||
<RichTemplate :template="li" />
|
<MarkdownParts :elements="item" :slots="markdown.slots">
|
||||||
|
<template
|
||||||
|
v-for="(slot, name) in providedSlots"
|
||||||
|
:key="name"
|
||||||
|
#[name]>
|
||||||
|
<component :is="slot" />
|
||||||
|
</template>
|
||||||
|
</MarkdownParts>
|
||||||
</li>
|
</li>
|
||||||
</ul> -->
|
</ul>
|
||||||
</template>
|
</template>
|
||||||
</OptionalParent>
|
</OptionalParent>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
<script setup lang="ts" generic="Slot extends string">
|
<script setup lang="ts" generic="Slot extends string">
|
||||||
import { type DeepReadonly, type VNode } from "vue";
|
import { type DeepReadonly, type VNode } from "vue";
|
||||||
import SmartLink from "../atoms/SmartLink.vue";
|
import SmartLink from "../atoms/SmartLink.vue";
|
||||||
import type { MarkdownLineElement } from "@/new-i18n-lib/markdown";
|
import type { MarkdownSpan } from "@/new-i18n-lib/markdown";
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-arguments
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-arguments
|
||||||
elements: DeepReadonly<MarkdownLineElement<Slot>[]>;
|
elements: DeepReadonly<MarkdownSpan<Slot>[]>;
|
||||||
slots: DeepReadonly<Slot[]>;
|
slots: DeepReadonly<Slot[]>;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,30 +8,29 @@ import type { RouteNamedMap } from "vue-router/auto-routes";
|
||||||
import { routes } from "vue-router/auto-routes";
|
import { routes } from "vue-router/auto-routes";
|
||||||
|
|
||||||
export interface Markdown<Slot extends string = string> {
|
export interface Markdown<Slot extends string = string> {
|
||||||
lines: MarkdownLine<Slot>[];
|
elements: MarkdownElement<Slot>[];
|
||||||
slots: Slot[];
|
slots: Slot[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MarkdownLine<Slot extends string = string> = {
|
export type MarkdownElement<Slot extends string = string> =
|
||||||
type: "paragraph" | "header";
|
| { type: "paragraph"; paragraph: { spans: MarkdownSpan<Slot>[] } }
|
||||||
elements: MarkdownLineElement<Slot>[];
|
| { type: "header"; header: { spans: MarkdownSpan<Slot>[] } }
|
||||||
|
| { type: "ulist"; ulist: { items: MarkdownSpan<Slot>[][] } };
|
||||||
|
|
||||||
|
type MarkdownLine<Slot extends string = string> = {
|
||||||
|
type: "paragraph" | "header" | "ulistItem";
|
||||||
|
spans: MarkdownSpan<Slot>[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MarkdownFeature =
|
export type MarkdownFeature = "header" | "ulist" | "italic" | "bold" | "link";
|
||||||
| Exclude<MarkdownLine["type"], "paragraph">
|
|
||||||
| Exclude<MarkdownLineElement["type"], "plain">;
|
|
||||||
|
|
||||||
export type MarkdownLineElement<Slot extends string = string> =
|
export type MarkdownSpan<Slot extends string = string> =
|
||||||
| { type: "plain"; plain: string }
|
| { type: "plain"; plain: string }
|
||||||
| { type: "italic"; italic: MarkdownLineElement[] }
|
| { type: "italic"; italic: MarkdownSpan[] }
|
||||||
| { type: "bold"; bold: MarkdownLineElement[] }
|
| { type: "bold"; bold: MarkdownSpan[] }
|
||||||
| {
|
| {
|
||||||
type: "link";
|
type: "link";
|
||||||
link: {
|
link: { label: MarkdownSpan[]; to: SmartDest; newTab: boolean };
|
||||||
label: MarkdownLineElement[];
|
|
||||||
to: SmartDest;
|
|
||||||
newTab: boolean;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
| { type: "slot"; slot: Slot };
|
| { type: "slot"; slot: Slot };
|
||||||
|
|
||||||
|
|
@ -39,8 +38,62 @@ export function parseMarkdown<Slot extends string>(
|
||||||
markdownString: string,
|
markdownString: string,
|
||||||
slots: readonly Slot[],
|
slots: readonly Slot[],
|
||||||
): Result<Markdown<Slot>, string> {
|
): Result<Markdown<Slot>, string> {
|
||||||
|
const linesRes = parseMarkdownLines(markdownString, slots);
|
||||||
|
if (linesRes.type === "err") {
|
||||||
|
return linesRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lines = linesRes.ok;
|
||||||
|
const elements: MarkdownElement<Slot>[] = [];
|
||||||
|
while (true) {
|
||||||
|
const line = lines.shift();
|
||||||
|
if (line === undefined) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const element = ((): MarkdownElement<Slot> => {
|
||||||
|
switch (line.type) {
|
||||||
|
case "paragraph": {
|
||||||
|
return {
|
||||||
|
type: "paragraph",
|
||||||
|
paragraph: { spans: line.spans },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "header": {
|
||||||
|
return { type: "header", header: { spans: line.spans } };
|
||||||
|
}
|
||||||
|
case "ulistItem": {
|
||||||
|
const items: MarkdownSpan<Slot>[][] = [line.spans];
|
||||||
|
while (true) {
|
||||||
|
const peekLine = lines[0];
|
||||||
|
if (
|
||||||
|
peekLine === undefined
|
||||||
|
|| peekLine.type !== "ulistItem"
|
||||||
|
) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
items.push(peekLine.spans);
|
||||||
|
lines.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
return { type: "ulist", ulist: { items } };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
elements.push(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { type: "ok", ok: { elements, slots: [...slots] } };
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseMarkdownLines<Slot extends string>(
|
||||||
|
markdownString: string,
|
||||||
|
slots: readonly Slot[],
|
||||||
|
): Result<MarkdownLine<Slot>[], string> {
|
||||||
if (markdownString.trim() === "--") {
|
if (markdownString.trim() === "--") {
|
||||||
return { type: "ok", ok: { lines: [], slots: [] } };
|
return { type: "ok", ok: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
const lines = markdownString.split("\n");
|
const lines = markdownString.split("\n");
|
||||||
|
|
@ -84,7 +137,7 @@ export function parseMarkdown<Slot extends string>(
|
||||||
markdownLines.push(markdownLine);
|
markdownLines.push(markdownLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { type: "ok", ok: { lines: markdownLines, slots: [...slots] } };
|
return { type: "ok", ok: markdownLines };
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseMarkdownLine<Slot extends string>(
|
function parseMarkdownLine<Slot extends string>(
|
||||||
|
|
@ -92,20 +145,34 @@ function parseMarkdownLine<Slot extends string>(
|
||||||
slots: readonly Slot[],
|
slots: readonly Slot[],
|
||||||
): Result<MarkdownLine<Slot>, string> {
|
): Result<MarkdownLine<Slot>, string> {
|
||||||
if (line.startsWith("#")) {
|
if (line.startsWith("#")) {
|
||||||
const elementsRes = parseMarkdownLineElements(line.substring(1), slots);
|
const elementsRes = parseMarkdownSpans(line.substring(1), slots);
|
||||||
|
|
||||||
if (elementsRes.type === "err") {
|
if (elementsRes.type === "err") {
|
||||||
return {
|
return {
|
||||||
type: "err",
|
type: "err",
|
||||||
err: `While parsing elements:\n${elementsRes.err}`,
|
err: `While parsing header spans:\n${elementsRes.err}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const elements = elementsRes.ok;
|
const elements = elementsRes.ok;
|
||||||
return { type: "ok", ok: { type: "header", elements } };
|
return { type: "ok", ok: { type: "header", spans: elements } };
|
||||||
}
|
}
|
||||||
|
|
||||||
const elementsRes = parseMarkdownLineElements(line, slots);
|
if (line.startsWith("-")) {
|
||||||
|
const elementsRes = parseMarkdownSpans(line.substring(1), slots);
|
||||||
|
|
||||||
|
if (elementsRes.type === "err") {
|
||||||
|
return {
|
||||||
|
type: "err",
|
||||||
|
err: `While parsing ulist item spans:\n${elementsRes.err}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const elements = elementsRes.ok;
|
||||||
|
return { type: "ok", ok: { type: "ulistItem", spans: elements } };
|
||||||
|
}
|
||||||
|
|
||||||
|
const elementsRes = parseMarkdownSpans(line, slots);
|
||||||
if (elementsRes.type === "err") {
|
if (elementsRes.type === "err") {
|
||||||
return {
|
return {
|
||||||
type: "err",
|
type: "err",
|
||||||
|
|
@ -114,13 +181,13 @@ function parseMarkdownLine<Slot extends string>(
|
||||||
}
|
}
|
||||||
|
|
||||||
const elements = elementsRes.ok;
|
const elements = elementsRes.ok;
|
||||||
return { type: "ok", ok: { type: "paragraph", elements } };
|
return { type: "ok", ok: { type: "paragraph", spans: elements } };
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseMarkdownLineElements<Slot extends string>(
|
function parseMarkdownSpans<Slot extends string>(
|
||||||
line: string,
|
line: string,
|
||||||
slots: readonly Slot[],
|
slots: readonly Slot[],
|
||||||
): Result<MarkdownLineElement<Slot>[], string> {
|
): Result<MarkdownSpan<Slot>[], string> {
|
||||||
if (line.startsWith("#")) {
|
if (line.startsWith("#")) {
|
||||||
// subheaders may be supported in the future,
|
// subheaders may be supported in the future,
|
||||||
// so ignoring them or treating them as h1 headers now would be a breaking change when
|
// so ignoring them or treating them as h1 headers now would be a breaking change when
|
||||||
|
|
@ -132,7 +199,7 @@ function parseMarkdownLineElements<Slot extends string>(
|
||||||
|
|
||||||
const trimmedLine = line.trim();
|
const trimmedLine = line.trim();
|
||||||
const chars = trimmedLine.split("");
|
const chars = trimmedLine.split("");
|
||||||
const elementsRes = readMarkdownLineElements(chars, slots, {
|
const elementsRes = readMarkdownSpans(chars, slots, {
|
||||||
inItalic: false,
|
inItalic: false,
|
||||||
inBold: false,
|
inBold: false,
|
||||||
});
|
});
|
||||||
|
|
@ -144,19 +211,19 @@ function parseMarkdownLineElements<Slot extends string>(
|
||||||
return { type: "ok", ok: elements };
|
return { type: "ok", ok: elements };
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ReadMarkdownLineElementCtx {
|
interface ReadMarkdownSpanCtx {
|
||||||
inItalic: boolean;
|
inItalic: boolean;
|
||||||
inBold: boolean;
|
inBold: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function readMarkdownLineElements<Slot extends string>(
|
function readMarkdownSpans<Slot extends string>(
|
||||||
chars: string[],
|
chars: string[],
|
||||||
slots: readonly Slot[],
|
slots: readonly Slot[],
|
||||||
ctx: ReadMarkdownLineElementCtx,
|
ctx: ReadMarkdownSpanCtx,
|
||||||
): Result<MarkdownLineElement<Slot>[], string> {
|
): Result<MarkdownSpan<Slot>[], string> {
|
||||||
const elements: MarkdownLineElement<Slot>[] = [];
|
const elements: MarkdownSpan<Slot>[] = [];
|
||||||
while (true) {
|
while (true) {
|
||||||
const elementRes = readMarkdownLineElement(chars, slots, ctx);
|
const elementRes = readMarkdownSpan(chars, slots, ctx);
|
||||||
if (elementRes.type === "err") {
|
if (elementRes.type === "err") {
|
||||||
return elementRes;
|
return elementRes;
|
||||||
}
|
}
|
||||||
|
|
@ -172,24 +239,24 @@ function readMarkdownLineElements<Slot extends string>(
|
||||||
return { type: "ok", ok: elements };
|
return { type: "ok", ok: elements };
|
||||||
}
|
}
|
||||||
|
|
||||||
function readMarkdownLineElement<Slot extends string>(
|
function readMarkdownSpan<Slot extends string>(
|
||||||
chars: string[],
|
chars: string[],
|
||||||
slots: readonly Slot[],
|
slots: readonly Slot[],
|
||||||
ctx: ReadMarkdownLineElementCtx,
|
ctx: ReadMarkdownSpanCtx,
|
||||||
): Result<MarkdownLineElement<Slot>[], string> {
|
): Result<MarkdownSpan<Slot>[], string> {
|
||||||
const [firstChar, secondChar] = chars;
|
const [firstChar, secondChar] = chars;
|
||||||
if (firstChar === undefined) {
|
if (firstChar === undefined) {
|
||||||
return { type: "ok", ok: [] };
|
return { type: "ok", ok: [] };
|
||||||
} else if (firstChar === "<") {
|
} else if (firstChar === "<") {
|
||||||
return readMarkdownLineElementSlot(chars, slots);
|
return readMarkdownSpanSlot(chars, slots);
|
||||||
} else if (firstChar === "[") {
|
} else if (firstChar === "[") {
|
||||||
return readMarkdownLineElementLink(chars, slots, ctx);
|
return readMarkdownSpanLink(chars, slots, ctx);
|
||||||
} else if (firstChar === "*" && secondChar === "*" && !ctx.inBold) {
|
} else if (firstChar === "*" && secondChar === "*" && !ctx.inBold) {
|
||||||
return readMarkdownLineElementBold(chars, slots, ctx);
|
return readMarkdownSpanBold(chars, slots, ctx);
|
||||||
} else if (firstChar === "*" && secondChar !== "*" && !ctx.inItalic) {
|
} else if (firstChar === "*" && secondChar !== "*" && !ctx.inItalic) {
|
||||||
return readMarkdownLineElementItalic(chars, slots, ctx);
|
return readMarkdownSpanItalic(chars, slots, ctx);
|
||||||
} else {
|
} else {
|
||||||
return readMarkdownLineElementPlain(chars);
|
return readMarkdownSpanPlain(chars);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -198,10 +265,10 @@ function isInArray<T, U extends T>(value: T, array: readonly U[]): value is U {
|
||||||
return array.includes(value as U);
|
return array.includes(value as U);
|
||||||
}
|
}
|
||||||
|
|
||||||
function readMarkdownLineElementSlot<Slot extends string>(
|
function readMarkdownSpanSlot<Slot extends string>(
|
||||||
chars: string[],
|
chars: string[],
|
||||||
slots: readonly Slot[],
|
slots: readonly Slot[],
|
||||||
): Result<MarkdownLineElement<Slot>[], string> {
|
): Result<MarkdownSpan<Slot>[], string> {
|
||||||
const openAngleRes = expectReadChar(chars, "<");
|
const openAngleRes = expectReadChar(chars, "<");
|
||||||
if (openAngleRes.type === "err") {
|
if (openAngleRes.type === "err") {
|
||||||
return openAngleRes;
|
return openAngleRes;
|
||||||
|
|
@ -225,17 +292,17 @@ function readMarkdownLineElementSlot<Slot extends string>(
|
||||||
return { type: "ok", ok: [{ type: "slot", slot: slotName }] };
|
return { type: "ok", ok: [{ type: "slot", slot: slotName }] };
|
||||||
}
|
}
|
||||||
|
|
||||||
function readMarkdownLineElementLink<Slot extends string>(
|
function readMarkdownSpanLink<Slot extends string>(
|
||||||
chars: string[],
|
chars: string[],
|
||||||
slots: readonly Slot[],
|
slots: readonly Slot[],
|
||||||
ctx: ReadMarkdownLineElementCtx,
|
ctx: ReadMarkdownSpanCtx,
|
||||||
): Result<MarkdownLineElement<Slot>[], string> {
|
): Result<MarkdownSpan<Slot>[], string> {
|
||||||
const openSquareRes = expectReadChar(chars, "[");
|
const openSquareRes = expectReadChar(chars, "[");
|
||||||
if (openSquareRes.type === "err") {
|
if (openSquareRes.type === "err") {
|
||||||
return openSquareRes;
|
return openSquareRes;
|
||||||
}
|
}
|
||||||
|
|
||||||
const labelElementsRes = readMarkdownLineElements(chars, slots, ctx);
|
const labelElementsRes = readMarkdownSpans(chars, slots, ctx);
|
||||||
if (labelElementsRes.type === "err") {
|
if (labelElementsRes.type === "err") {
|
||||||
return labelElementsRes;
|
return labelElementsRes;
|
||||||
}
|
}
|
||||||
|
|
@ -411,7 +478,7 @@ function readMarkdownLineElementLink<Slot extends string>(
|
||||||
|
|
||||||
const { dest, newTab } = linkPropsRes.ok;
|
const { dest, newTab } = linkPropsRes.ok;
|
||||||
|
|
||||||
const resolvedLabel: MarkdownLineElement[] =
|
const resolvedLabel: MarkdownSpan[] =
|
||||||
labelElements.length === 0 ?
|
labelElements.length === 0 ?
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
|
@ -500,11 +567,11 @@ function validateInternalDest(dest: string): Result<SmartInternalDest, string> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function readMarkdownLineElementBold<Slot extends string>(
|
function readMarkdownSpanBold<Slot extends string>(
|
||||||
chars: string[],
|
chars: string[],
|
||||||
slots: readonly Slot[],
|
slots: readonly Slot[],
|
||||||
ctx: ReadMarkdownLineElementCtx,
|
ctx: ReadMarkdownSpanCtx,
|
||||||
): Result<MarkdownLineElement<Slot>[], string> {
|
): Result<MarkdownSpan<Slot>[], string> {
|
||||||
const firstStarRes = expectReadChar(chars, "*");
|
const firstStarRes = expectReadChar(chars, "*");
|
||||||
if (firstStarRes.type === "err") {
|
if (firstStarRes.type === "err") {
|
||||||
return firstStarRes;
|
return firstStarRes;
|
||||||
|
|
@ -516,10 +583,10 @@ function readMarkdownLineElementBold<Slot extends string>(
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.inBold = true;
|
ctx.inBold = true;
|
||||||
const elements: MarkdownLineElement<Slot>[] = [];
|
const elements: MarkdownSpan<Slot>[] = [];
|
||||||
let closed = false;
|
let closed = false;
|
||||||
while (true) {
|
while (true) {
|
||||||
const elementRes = readMarkdownLineElement(chars, slots, ctx);
|
const elementRes = readMarkdownSpan(chars, slots, ctx);
|
||||||
if (elementRes.type === "err") {
|
if (elementRes.type === "err") {
|
||||||
return elementRes;
|
return elementRes;
|
||||||
}
|
}
|
||||||
|
|
@ -553,21 +620,21 @@ function readMarkdownLineElementBold<Slot extends string>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function readMarkdownLineElementItalic<Slot extends string>(
|
function readMarkdownSpanItalic<Slot extends string>(
|
||||||
chars: string[],
|
chars: string[],
|
||||||
slots: readonly Slot[],
|
slots: readonly Slot[],
|
||||||
ctx: ReadMarkdownLineElementCtx,
|
ctx: ReadMarkdownSpanCtx,
|
||||||
): Result<MarkdownLineElement<Slot>[], string> {
|
): Result<MarkdownSpan<Slot>[], string> {
|
||||||
const starRes = expectReadChar(chars, "*");
|
const starRes = expectReadChar(chars, "*");
|
||||||
if (starRes.type === "err") {
|
if (starRes.type === "err") {
|
||||||
return starRes;
|
return starRes;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.inItalic = true;
|
ctx.inItalic = true;
|
||||||
const elements: MarkdownLineElement<Slot>[] = [];
|
const elements: MarkdownSpan<Slot>[] = [];
|
||||||
let closed = false;
|
let closed = false;
|
||||||
while (true) {
|
while (true) {
|
||||||
const elementRes = readMarkdownLineElement(chars, slots, ctx);
|
const elementRes = readMarkdownSpan(chars, slots, ctx);
|
||||||
if (elementRes.type === "err") {
|
if (elementRes.type === "err") {
|
||||||
return elementRes;
|
return elementRes;
|
||||||
}
|
}
|
||||||
|
|
@ -597,9 +664,9 @@ function readMarkdownLineElementItalic<Slot extends string>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function readMarkdownLineElementPlain<Slot extends string>(
|
function readMarkdownSpanPlain<Slot extends string>(
|
||||||
chars: string[],
|
chars: string[],
|
||||||
): Result<MarkdownLineElement<Slot>[], string> {
|
): Result<MarkdownSpan<Slot>[], string> {
|
||||||
let plain = "";
|
let plain = "";
|
||||||
let escaped = false;
|
let escaped = false;
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|
@ -739,5 +806,5 @@ export function isEmptyMarkdown(markdown: Markdown): boolean {
|
||||||
|
|
||||||
// return false;
|
// return false;
|
||||||
|
|
||||||
return markdown.lines.length === 0;
|
return markdown.elements.length === 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue