// Reading-view post-processor. We re-parse the original markdown for each
// rendered table (via `MarkdownPostProcessorContext.getSectionInfo`) and rebuild
// the `
` so MultiMarkdown features (rowspan / colspan / caption /
// multi-header / multi-tbody / multiline cells) survive the trip through
// Obsidian's GFM renderer.
import {
Component,
MarkdownPostProcessor,
MarkdownPostProcessorContext,
} from "obsidian";
import { applyModelToTable } from "./tableRenderer";
import { isSeparatorLine, looksLikeTableLine, parseTable } from "../table/parser";
interface PostProcessorOptions {
component: Component;
}
export function buildTableMergePostProcessor(opts: PostProcessorOptions): MarkdownPostProcessor {
return (el, ctx) => {
const tables = Array.from(el.querySelectorAll("table"));
if (!tables.length) return;
const sources = collectTableSources(el, ctx);
for (let i = 0; i < tables.length; i++) {
const table = tables[i];
const src = sources[i];
if (!src) continue;
try {
const { model } = parseTable(src);
// Render asynchronously; failures shouldn't break the section.
void applyModelToTable(table, model, {
sourcePath: ctx.sourcePath,
component: opts.component,
renderInline: true,
});
} catch {
// Leave the original GFM rendering in place if the source can't be
// parsed as an extended table.
}
}
};
}
/**
* Recover the markdown source for each table in `el` using the section info
* Obsidian gives us. We split the section into table blocks (separated by at
* least two blank lines) so each `` lines up with the right snippet.
*/
function collectTableSources(el: HTMLElement, ctx: MarkdownPostProcessorContext): string[] {
const info = ctx.getSectionInfo(el);
if (!info) return [];
const lines = info.text.split(/\r?\n/).slice(info.lineStart, info.lineEnd + 1);
const blocks: string[] = [];
let buf: string[] = [];
let blanks = 0;
let inTable = false;
let hasSep = false;
const flush = () => {
while (buf.length && buf[buf.length - 1].trim() === "") buf.pop();
if (buf.length && hasSep) blocks.push(buf.join("\n"));
buf = [];
blanks = 0;
inTable = false;
hasSep = false;
};
for (const line of lines) {
if (line.trim() === "") {
if (!inTable) continue;
blanks++;
if (blanks >= 2) {
flush();
} else {
buf.push(line);
}
continue;
}
if (looksLikeTableLine(line) || isSeparatorLine(line) || /^\s*\[[^\]]+\](?:\s*\[[^\]]+\])?\s*$/.test(line)) {
buf.push(line);
blanks = 0;
inTable = true;
if (isSeparatorLine(line)) hasSep = true;
continue;
}
if (inTable) flush();
}
if (buf.length) flush();
return blocks;
}