214 lines
4.5 KiB
TypeScript
214 lines
4.5 KiB
TypeScript
type Ln = {
|
|
l: string[],
|
|
n: number,
|
|
}
|
|
|
|
export type Block = {
|
|
tag: "h1" | "h2" | "h3" | "h4" | "h5" | "h6",
|
|
htm: string,
|
|
} | {
|
|
tag: "hr",
|
|
} | {
|
|
tag: "blockquote",
|
|
htm: string,
|
|
} | {
|
|
tag: "ol" | "ul",
|
|
li: string[],
|
|
} | {
|
|
tag: "table",
|
|
th: string[],
|
|
td: string[][],
|
|
} | {
|
|
tag: "p" | "pre" | "section",
|
|
htm: string,
|
|
}
|
|
|
|
function pln(
|
|
md: string
|
|
): Ln {
|
|
return { l: md.split("\n"), n: 0 }
|
|
}
|
|
|
|
function pl(
|
|
ln: Ln
|
|
): string {
|
|
return ln.l[ln.n]
|
|
}
|
|
|
|
function pn(
|
|
ln: Ln,
|
|
n: "check" | "back" | "line" | "block" = "line",
|
|
): boolean {
|
|
if (n == "back" && ln.n > 0) --ln.n
|
|
else if (n == "line") ++ln.n
|
|
else if (n == "block") while (ln.n < ln.l.length && ln.l[ln.n].trim() == "") ++ln.n
|
|
return ln.n < ln.l.length
|
|
}
|
|
|
|
function pb(
|
|
ln: Ln
|
|
): Block[] {
|
|
if (!pn(ln, "check")) return []
|
|
const lb: Block[] = []
|
|
while (pn(ln, "block")) {
|
|
let b: null | Block = null
|
|
for (const p of [ph, phr, pbq, pli, pt, pp])
|
|
if (b = p(ln)) { lb.push(b); break }
|
|
}
|
|
return lb
|
|
}
|
|
|
|
function pbi(
|
|
ln: Ln
|
|
): string {
|
|
const l = []
|
|
do {
|
|
if (pl(ln) == "") {
|
|
if (l.length > 0) l.push("")
|
|
continue
|
|
}
|
|
const m = pl(ln).match(/^( {4}|\t)(.*)/)
|
|
if (m) l.push(m[2])
|
|
else break
|
|
} while (pn(ln))
|
|
return l.length > 0 ? htm(pb({ l, n: 0 })) : ""
|
|
}
|
|
|
|
export function pmd(
|
|
md: string
|
|
): Block[] {
|
|
return pb(pln(md))
|
|
}
|
|
|
|
export function htm(
|
|
lb: Block[]
|
|
): string {
|
|
return lb.map(b => {
|
|
switch (b.tag) {
|
|
case "h1": case "h2": case "h3": case "h4": case "h5": case "h6":
|
|
case "blockquote": case "p": case "pre": case "section":
|
|
return `<${b.tag}>${b.htm}</${b.tag}>`
|
|
case "hr": return "<hr>"
|
|
case "ol": case "ul":
|
|
return `<${b.tag}>${b.li.map(li => `<li>${li}</li>`).join("\n")}</${b.tag}>`
|
|
case "table": {
|
|
const tr = (t: "th" | "td", c: string[]) =>
|
|
`<tr>${c.map(c => `<${t}>${c}</${t}>`).join("")}</tr>`
|
|
const [th, td] = [
|
|
(h: string[]) => tr("th", h),
|
|
(d: string[]) => tr("td", d),
|
|
]
|
|
return `<${b.tag}>${[th(b.th), ...b.td.map(td)].join("\n")}</${b.tag}>`
|
|
}
|
|
}
|
|
}).join("\n")
|
|
}
|
|
|
|
export function mdh(
|
|
md: string
|
|
): string {
|
|
return htm(pmd(md))
|
|
}
|
|
|
|
function ph(
|
|
ln: Ln
|
|
): null | Block & { tag: "h1" | "h2" | "h3" | "h4" | "h5" | "h6" } {
|
|
const m = pl(ln).match(/(^#{1,6}) (.*$)/) //@ts-ignore
|
|
if (m) { pn(ln); return { tag: `h${m[1].length}`, htm: phtm(m[2]) } }
|
|
return null
|
|
}
|
|
|
|
function phr(
|
|
ln: Ln
|
|
): null | Block & { tag: "hr" } {
|
|
if (["***", "---", "___"].some(h => pl(ln).startsWith(h))) { pn(ln); return { tag: "hr" } }
|
|
return null
|
|
}
|
|
|
|
function pbq(
|
|
ln: Ln
|
|
): null | Block & { tag: "blockquote" } {
|
|
const l = []
|
|
do {
|
|
const m = pl(ln).match(/^> ?(.*$)/)
|
|
if (m) l.push(pure(m[1]))
|
|
else break
|
|
} while (pn(ln))
|
|
return l.length > 0 ? { tag: "blockquote", htm: htm(pb({ l, n: 0 })) } : null
|
|
}
|
|
|
|
function pli(
|
|
ln: Ln
|
|
): null | Block & { tag: "ol" | "ul" } {
|
|
let tag: null | "ol" | "ul" = null
|
|
const li: string[] = []
|
|
do {
|
|
const m = pl(ln).match(/^([*+-]|^\d+\.) (.*)/)
|
|
if (m == null && li.length > 0) {
|
|
const bi = pbi(ln)
|
|
if (bi == "") break
|
|
li[li.length - 1] += bi
|
|
pn(ln, "back"); continue
|
|
}
|
|
if (m == null) break
|
|
const t = m[1].endsWith(".") ? "ol" : "ul"
|
|
if (tag == null) tag = t
|
|
else if (tag != t) break
|
|
li.push(phtm(m[2]))
|
|
} while (pn(ln))
|
|
return tag ? { tag, li } : null
|
|
}
|
|
|
|
function pt(
|
|
ln: Ln
|
|
): null | Block & { tag: "table" } {
|
|
const [th, td]: [string[], string[][]] = [[], []]
|
|
let tag: null | "table" = null
|
|
do {
|
|
if (tag == null) {
|
|
if (pl(ln) == "```csv") { tag = "table"; continue }
|
|
else return null
|
|
} else if (pl(ln) == "```") { pn(ln); break }
|
|
if (th.length == 0) th.push(...pl(ln).split(","))
|
|
else td.push(pl(ln).split(","))
|
|
} while (pn(ln))
|
|
return { tag, th, td }
|
|
}
|
|
|
|
function pp(
|
|
ln: Ln
|
|
): Block & { tag: "p" | "pre" } {
|
|
const l = []
|
|
let tag: "p" | "pre" = "p"
|
|
do {
|
|
if (tag == "p") {
|
|
if (pl(ln) == "") break
|
|
if (pl(ln).startsWith("```")) { tag = "pre"; continue }
|
|
} else if (pl(ln) == "```") { pn(ln); break }
|
|
l.push(tag == "p" ? phtm(pl(ln)) : pl(ln))
|
|
} while (pn(ln))
|
|
return { tag, htm: l.join("\n") }
|
|
}
|
|
|
|
function pure(
|
|
t: string
|
|
): string {
|
|
return t.replaceAll("<", "﹤")
|
|
.replaceAll(">", "﹥")
|
|
.replaceAll("'", "\"")
|
|
}
|
|
|
|
function phtm(
|
|
t: string
|
|
): string {
|
|
return pure(t)
|
|
.replace(/\*{3,}([^\*]+)\*{3,}/g, "<b><em>$1</em></b>")
|
|
.replace(/\*{2}([^\*]+)\*{2}/g, "<b>$1</b>")
|
|
.replace(/\*+([^\*]+)\*+/g, "<i>$1</i>")
|
|
.replace(/`+([^`]+)`+/g, "<code>$1</code>")
|
|
.replace(/={2,}([^=]+)={2,}/g, "<mark>$1</mark>")
|
|
.replace(/!\[(.*?)\]\((.*?)\)/g, "<img alt='$1' src='$2'/>")
|
|
.replace(/\[(.*?)\]\((.*?)\)/g, "<a href='$2'>$1</a>")
|
|
.replace(/ {2,}$/g, "<br>")
|
|
}
|