2022-04-04 09:51:41 +00:00
|
|
|
export type MarkdownTree = {
|
|
|
|
type?: string; // undefined === text node
|
2022-04-04 13:25:07 +00:00
|
|
|
from?: number;
|
|
|
|
to?: number;
|
2022-04-04 09:51:41 +00:00
|
|
|
text?: string;
|
|
|
|
children?: MarkdownTree[];
|
|
|
|
parent?: MarkdownTree;
|
|
|
|
};
|
|
|
|
|
|
|
|
export function addParentPointers(mdTree: MarkdownTree) {
|
|
|
|
if (!mdTree.children) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
for (let child of mdTree.children) {
|
|
|
|
child.parent = mdTree;
|
|
|
|
addParentPointers(child);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function removeParentPointers(mdTree: MarkdownTree) {
|
|
|
|
delete mdTree.parent;
|
|
|
|
if (!mdTree.children) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
for (let child of mdTree.children) {
|
|
|
|
removeParentPointers(child);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function findParentMatching(
|
|
|
|
mdTree: MarkdownTree,
|
|
|
|
matchFn: (mdTree: MarkdownTree) => boolean
|
|
|
|
): MarkdownTree | null {
|
|
|
|
let node = mdTree.parent;
|
|
|
|
while (node) {
|
|
|
|
if (matchFn(node)) {
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
node = node.parent;
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function collectNodesMatching(
|
|
|
|
mdTree: MarkdownTree,
|
|
|
|
matchFn: (mdTree: MarkdownTree) => boolean
|
|
|
|
): MarkdownTree[] {
|
|
|
|
if (matchFn(mdTree)) {
|
|
|
|
return [mdTree];
|
|
|
|
}
|
|
|
|
let results: MarkdownTree[] = [];
|
|
|
|
if (mdTree.children) {
|
|
|
|
for (let child of mdTree.children) {
|
|
|
|
results = [...results, ...collectNodesMatching(child, matchFn)];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
|
2022-04-04 13:25:07 +00:00
|
|
|
export function replaceNodesMatching(
|
|
|
|
mdTree: MarkdownTree,
|
|
|
|
substituteFn: (mdTree: MarkdownTree) => MarkdownTree | null | undefined
|
|
|
|
) {
|
|
|
|
let subst = substituteFn(mdTree);
|
|
|
|
if (subst !== undefined) {
|
|
|
|
if (!mdTree.parent) {
|
|
|
|
throw Error("Need parent pointers for this");
|
|
|
|
}
|
|
|
|
let parentChildren = mdTree.parent.children!;
|
|
|
|
let pos = parentChildren.indexOf(mdTree);
|
|
|
|
if (subst) {
|
|
|
|
parentChildren.splice(pos, 1, subst);
|
|
|
|
} else {
|
|
|
|
// null = delete
|
|
|
|
parentChildren.splice(pos, 1);
|
|
|
|
}
|
|
|
|
} else if (mdTree.children) {
|
|
|
|
for (let child of mdTree.children) {
|
|
|
|
replaceNodesMatching(child, substituteFn);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-04 09:51:41 +00:00
|
|
|
export function findNodeMatching(
|
|
|
|
mdTree: MarkdownTree,
|
|
|
|
matchFn: (mdTree: MarkdownTree) => boolean
|
|
|
|
): MarkdownTree | null {
|
|
|
|
return collectNodesMatching(mdTree, matchFn)[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Finds non-text node at position
|
|
|
|
export function nodeAtPos(
|
|
|
|
mdTree: MarkdownTree,
|
|
|
|
pos: number
|
|
|
|
): MarkdownTree | null {
|
2022-04-04 13:25:07 +00:00
|
|
|
if (pos < mdTree.from! || pos > mdTree.to!) {
|
2022-04-04 09:51:41 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
if (!mdTree.children) {
|
|
|
|
return mdTree;
|
|
|
|
}
|
|
|
|
for (let child of mdTree.children) {
|
|
|
|
let n = nodeAtPos(child, pos);
|
|
|
|
if (n && n.text !== undefined) {
|
|
|
|
// Got a text node, let's return its parent
|
|
|
|
return mdTree;
|
|
|
|
} else if (n) {
|
|
|
|
// Got it
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Turn MarkdownTree back into regular markdown text
|
2022-04-04 13:25:07 +00:00
|
|
|
export function renderMarkdown(mdTree: MarkdownTree): string {
|
2022-04-04 09:51:41 +00:00
|
|
|
let pieces: string[] = [];
|
|
|
|
if (mdTree.text !== undefined) {
|
|
|
|
return mdTree.text;
|
|
|
|
}
|
|
|
|
for (let child of mdTree.children!) {
|
2022-04-04 13:25:07 +00:00
|
|
|
pieces.push(renderMarkdown(child));
|
2022-04-04 09:51:41 +00:00
|
|
|
}
|
|
|
|
return pieces.join("");
|
|
|
|
}
|