[] = [];
+ let foldedCallouts: HTMLElement[] = [];
+ for (let i = 0; i < sections.length; i++)
+ {
+ let section = sections[i];
+
+ section.shown = true;
+ section.rendered = false;
+ // @ts-ignore
+ section.resetCompute();
+ // @ts-ignore
+ section.setCollapsed(false);
+ section.el.empty();
+
+ newSizerEl.appendChild(section.el);
+
+ // @ts-ignore
+ await section.render();
+
+ // @ts-ignore
+ let success = await Utils.waitUntil(() => (section.el && section.rendered) || checkCancelled(), 2000, 1);
+ if (!success) return failRender(preview.file, "Failed to render section!");
+
+ await renderer.measureSection(section);
+ success = await Utils.waitUntil(() => section.computed || checkCancelled(), 2000, 1);
+ if (!success) return failRender(preview.file, "Failed to compute section!");
+
+ // @ts-ignore
+ await preview.postProcess(section, promises, renderer.frontmatter);
+
+ // unfold callouts
+ let folded = Array.from(section.el.querySelectorAll(".callout-content[style*='display: none']")) as HTMLElement[];
+ for (let callout of folded)
+ {
+ callout.style.display = "";
+ }
+ foldedCallouts.push(...folded);
+
+ // dataview support
+ // @ts-ignore
+ let dataview = app.plugins.plugins["dataview"];
+ if (dataview)
+ {
+ let jsKeyword = dataview.settings?.dataviewJsKeyword ?? "dataviewjs";
+ let emptyDataviewSelector = `:is(.block-language-dataview, .block-language-${jsKeyword}):not(.node-insert-event), :is(.block-language-dataview, .block-language-${jsKeyword}):empty`
+ await Utils.waitUntil(() => !section.el.querySelector(emptyDataviewSelector) || checkCancelled(), 4000, 1);
+ if (checkCancelled()) return undefined;
+
+ if (section.el.querySelector(emptyDataviewSelector))
+ {
+ ExportLog.warning("Dataview plugin elements were not rendered correctly in file " + preview.file.name + "!");
+ }
+ }
+
+ // wait for transclusions
+ await Utils.waitUntil(() => !section.el.querySelector(".markdown-preview-sizer:empty") || checkCancelled(), 500, 1);
+ if (checkCancelled()) return undefined;
+
+ if (section.el.querySelector(".markdown-preview-sizer:empty"))
+ {
+ ExportLog.warning("Transclusions were not rendered correctly in file " + preview.file.name + "!");
+ }
+
+ // wait for generic plugins
+ await Utils.waitUntil(() => !section.el.querySelector("[class^='block-language-']:empty") || checkCancelled(), 500, 1);
+ if (checkCancelled()) return undefined;
+
+ // convert canvas elements into images here because otherwise they will lose their data when moved
+ let canvases = Array.from(section.el.querySelectorAll("canvas:not(.pdf-embed canvas)")) as HTMLCanvasElement[];
+ for (let canvas of canvases)
+ {
+ let data = canvas.toDataURL();
+ if (data.length < 100)
+ {
+ ExportLog.log(canvas.outerHTML, "Failed to render canvas based plugin element in file " + preview.file.name + ":");
+ canvas.remove();
+ continue;
+ }
+
+ let image = document.createElement("img");
+ image.src = data;
+ image.style.width = canvas.style.width || "100%";
+ image.style.maxWidth = "100%";
+ canvas.replaceWith(image);
+ };
+
+ console.debug(section.el.outerHTML); // for some reason adding this line here fixes an issue where some plugins wouldn't render
+
+ let invalidPluginBlocks = Array.from(section.el.querySelectorAll("[class^='block-language-']:empty"));
+ for (let block of invalidPluginBlocks)
+ {
+ ExportLog.warning(`Plugin element ${block.className || block.parentElement?.className || "unknown"} from ${preview.file.name} not rendered correctly!`);
+ }
+ }
+
+ // @ts-ignore
+ await Promise.all(promises);
+
+ // refold callouts
+ for (let callout of foldedCallouts)
+ {
+ callout.style.display = "none";
+ }
+
+ newSizerEl.empty();
+ // move all of them back in since rendering can cause some sections to move themselves out of their container
+ for (let i = 0; i < sections.length; i++)
+ {
+ let section = sections[i];
+ newSizerEl.appendChild(section.el.cloneNode(true));
+ }
+
+ // get banner plugin banner and insert it before the sizer element
+ let banner = preview.containerEl.querySelector(".obsidian-banner-wrapper");
+ if (banner)
+ {
+ newSizerEl.before(banner);
+ }
+
+ // if we aren't kepping the view element then only keep the content of the sizer element
+ if (options.keepViewContainer === false)
+ {
+ newMarkdownEl.outerHTML = newSizerEl.innerHTML;
+ console.log("keeping only sizer content");
+ }
+
+ options.container?.appendChild(newMarkdownEl);
+
+ return newMarkdownEl;
+ }
+
+ export async function renderSimpleMarkdown(markdown: string, container: HTMLElement)
+ {
+ let renderComp = new Component();
+ renderComp.load();
+ await ObsidianRenderer.render(app, markdown, container, "/", renderComp);
+ renderComp.unload();
+
+ let renderedEl = container.children[container.children.length - 1];
+ if (renderedEl && renderedEl.tagName == "P")
+ {
+ renderedEl.outerHTML = renderedEl.innerHTML; // remove the outer tag
+ }
+
+ // remove tags
+ container.querySelectorAll("a.tag").forEach((element: HTMLAnchorElement) =>
+ {
+ element.remove();
+ });
+
+ //remove rendered lists and replace them with plain text
+ container.querySelectorAll("ol").forEach((listEl: HTMLElement) =>
+ {
+ if(listEl.parentElement)
+ {
+ let start = listEl.getAttribute("start") ?? "1";
+ listEl.parentElement.createSpan().outerHTML = `${start}. ${listEl.innerText}`;
+ listEl.remove();
+ }
+ });
+ container.querySelectorAll("ul").forEach((listEl: HTMLElement) =>
+ {
+ if(listEl.parentElement)
+ {
+ listEl.parentElement.createSpan().innerHTML = "- " + listEl.innerHTML;
+ listEl.remove();
+ }
+ });
+ container.querySelectorAll("li").forEach((listEl: HTMLElement) =>
+ {
+ if(listEl.parentElement)
+ {
+ listEl.parentElement.createSpan().innerHTML = listEl.innerHTML;
+ listEl.remove();
+ }
+ });
+ }
+
+ async function renderGeneric(view: View, options: MarkdownRendererAPIOptions): Promise
+ {
+ await Utils.delay(2000);
+
+ if (checkCancelled()) return undefined;
+
+ // @ts-ignore
+ let contentEl = view.containerEl;
+ options.container?.appendChild(contentEl);
+
+ return contentEl;
+ }
+
+ async function renderExcalidraw(view: any, options: MarkdownRendererAPIOptions): Promise
+ {
+ await Utils.delay(500);
+
+ // @ts-ignore
+ let scene = view.excalidrawData.scene;
+
+ // @ts-ignore
+ let svg = await view.svg(scene, "", false);
+
+ // remove rect fill
+ let isLight = !svg.getAttribute("filter");
+ if (!isLight) svg.removeAttribute("filter");
+ svg.classList.add(isLight ? "light" : "dark");
+
+ let contentEl = document.createElement("div");
+ contentEl.classList.add("view-content");
+ let sizerEl = contentEl.createDiv();
+ sizerEl.classList.add("excalidraw-plugin");
+
+ sizerEl.appendChild(svg);
+
+ if (checkCancelled()) return undefined;
+
+ if (options.keepViewContainer === false)
+ {
+ contentEl = svg;
+ }
+
+ options.container?.appendChild(contentEl);
+
+ return contentEl;
+ }
+
+ export async function renderCanvas(view: any, options: MarkdownRendererAPIOptions): Promise
+ {
+ if (checkCancelled()) return undefined;
+
+ let canvas = view.canvas;
+
+ let nodes = canvas.nodes;
+ let edges = canvas.edges;
+
+ for (const node of nodes)
+ {
+ await node[1].render();
+ }
+
+ for (const edge of edges)
+ {
+ await edge[1].render();
+ }
+
+ canvas.zoomToFit();
+ await Utils.delay(500);
+
+ let contentEl = view.contentEl;
+ let canvasEl = contentEl.querySelector(".canvas");
+ canvasEl.innerHTML = "";
+
+ let edgeContainer = canvasEl.createEl("svg", { cls: "canvas-edges" });
+ let edgeHeadContainer = canvasEl.createEl("svg", { cls: "canvas-edges" });
+
+ for (const node of nodes)
+ {
+ let nodeEl = node[1].nodeEl;
+ let childPreview = node[1]?.child?.previewMode;
+ let embedEl = nodeEl.querySelector(".markdown-embed-content.node-insert-event");
+
+ if (childPreview && embedEl)
+ {
+ node[1].render();
+ embedEl.innerHTML = "";
+ let optionsCopy = Object.assign({}, options);
+ optionsCopy.container = embedEl;
+ await renderMarkdownView(childPreview, optionsCopy);
+ }
+
+ canvasEl.appendChild(nodeEl);
+ }
+
+ for (const edge of edges)
+ {
+ let edgeEl = edge[1].lineGroupEl;
+ let headEl = edge[1].lineEndGroupEl;
+
+ edgeContainer.appendChild(edgeEl);
+ edgeHeadContainer.appendChild(headEl);
+
+ if(edge[1].label)
+ {
+ let labelEl = edge[1].labelElement.wrapperEl;
+ canvasEl.appendChild(labelEl);
+ }
+ }
+
+ if (checkCancelled()) return undefined;
+
+ if (options.keepViewContainer === false)
+ {
+ contentEl = canvasEl;
+ }
+
+ options.container?.appendChild(contentEl);
+
+ return contentEl;
+ }
+
+ export async function postProcessHTML(html: HTMLElement, options: MarkdownRendererAPIOptions)
+ {
+ // remove the extra elements if they are not wanted
+ if (options.keepViewContainer === false)
+ {
+ html.querySelectorAll(".mod-header, .mod-footer").forEach((e: HTMLElement) => e.remove());
+ }
+
+ // transclusions put a div inside a p tag, which is invalid html. Fix it here
+ html.querySelectorAll("p:has(div)").forEach((element) =>
+ {
+ // replace the p tag with a span
+ let span = document.body.createEl("span");
+ span.innerHTML = element.innerHTML;
+ element.replaceWith(span);
+ span.style.display = "block";
+ span.style.marginBlockStart = "var(--p-spacing)";
+ span.style.marginBlockEnd = "var(--p-spacing)";
+ });
+
+ // encode all text input values into attributes
+ html.querySelectorAll("input[type=text]").forEach((element: HTMLElement) =>
+ {
+ // @ts-ignore
+ element.setAttribute("value", element.value);
+ // @ts-ignore
+ element.value = "";
+ });
+
+ // encode all text area values into text content
+ html.querySelectorAll("textarea").forEach((element: HTMLElement) =>
+ {
+ // @ts-ignore
+ element.textContent = element.value;
+ });
+
+ // convert tag href to search query
+ html.querySelectorAll("a.tag").forEach((element: HTMLAnchorElement) =>
+ {
+ let split = element.href.split("#");
+ let tag = split[1] ?? element.href.substring(1); // remove the #
+ element.setAttribute("href", `?query=tag:${tag}`);
+ });
+
+ // convert all hard coded image / media widths into max widths
+ html.querySelectorAll("img, video, .media-embed:has( > :is(img, video))").forEach((element: HTMLElement) =>
+ {
+ let width = element.getAttribute("width");
+ if (width)
+ {
+ element.removeAttribute("width");
+ element.style.width = (width.trim() != "") ? (width + "px") : "";
+ element.style.maxWidth = "100%";
+ }
+ });
+
+ // replace obsidian's pdf embeds with normal embeds
+ // this has to happen before converting canvases because the pdf embeds use canvas elements
+ html.querySelectorAll("span.internal-embed.pdf-embed").forEach((pdf: HTMLElement) =>
+ {
+ let embed = document.createElement("embed");
+ embed.setAttribute("src", pdf.getAttribute("src") ?? "");
+ embed.style.width = pdf.style.width || '100%';
+ embed.style.maxWidth = "100%";
+ embed.style.height = pdf.style.height || '800px';
+
+ let container = pdf.parentElement?.parentElement;
+
+ container?.querySelectorAll("*").forEach((el) => el.remove());
+
+ if (container) container.appendChild(embed);
+ });
+
+ // remove all MAKE.md elements
+ html.querySelectorAll("div[class^='mk-']").forEach((element: HTMLElement) =>
+ {
+ element.remove();
+ });
+
+ // move frontmatter before markdown-preview-sizer
+ let frontmatter = html.querySelector(".frontmatter");
+ if (frontmatter)
+ {
+ let frontmatterParent = frontmatter.parentElement;
+ let sizer = html.querySelector(".markdown-preview-sizer");
+ if (sizer)
+ {
+ sizer.before(frontmatter);
+ }
+ frontmatterParent?.remove();
+ }
+
+ // add lazy loading to iframe elements
+ html.querySelectorAll("iframe").forEach((element: HTMLIFrameElement) =>
+ {
+ element.setAttribute("loading", "lazy");
+ });
+
+ // add collapse icons to lists if they don't already have them
+ var collapsableListItems = Array.from(html.querySelectorAll("li:has(ul), li:has(ol)"));
+ for (const item of collapsableListItems)
+ {
+ let collapseIcon = item.querySelector(".collapse-icon");
+ if (!collapseIcon)
+ {
+ collapseIcon = item.createDiv({ cls: "list-collapse-indicator collapse-indicator collapse-icon" });
+ collapseIcon.innerHTML = this.arrowHTML;
+ item.prepend(collapseIcon);
+ }
+ }
+
+ // if the dynamic table of contents plugin is included on this page
+ // then parse each list item and render markdown for it
+ let tocEls = Array.from(html.querySelectorAll(".block-language-toc.dynamic-toc li > a"));
+ for (const element of tocEls)
+ {
+ let renderEl = document.body.createDiv();
+ renderSimpleMarkdown(element.textContent ?? "", renderEl);
+ element.textContent = renderEl.textContent;
+ renderEl.remove();
+ }
+ }
+
+ export async function beginBatch(options: MarkdownRendererAPIOptions | MarkdownWebpageRendererAPIOptions)
+ {
+ if(batchStarted) return;
+
+ errorInBatch = false;
+ cancelled = false;
+ batchStarted = true;
+ loadingContainer = undefined;
+ logContainer = undefined;
+ logShowing = false;
+ AssetHandler.exportOptions = options;
+
+ renderLeaf = TabManager.openNewTab("window", "vertical");
+
+ // @ts-ignore
+ let parentFound = await Utils.waitUntil(() => (renderLeaf && renderLeaf.parent) || checkCancelled(), 2000, 1);
+ if (!parentFound)
+ {
+ try
+ {
+ renderLeaf.detach();
+ }
+ catch (e)
+ {
+ ExportLog.error(e, "Failed to detach render leaf: ");
+ }
+
+ if (!checkCancelled())
+ {
+ new Notice("Error: Failed to create leaf for rendering!");
+ throw new Error("Failed to create leaf for rendering!");
+ }
+
+ return;
+ }
+
+ let obsidianWindow = renderLeaf.view.containerEl.win;
+ // @ts-ignore
+ electronWindow = obsidianWindow.electronWindow as electron.BrowserWindow;
+
+ if (!electronWindow)
+ {
+ new Notice("Failed to get the render window, please try again.");
+ errorInBatch = false;
+ cancelled = false;
+ batchStarted = false;
+ renderLeaf = undefined;
+ electronWindow = undefined;
+ return;
+ }
+
+ if (options.displayProgress === false)
+ {
+ let newPosition = {x: 0, y: window.screen.height};
+ obsidianWindow.moveTo(newPosition.x, newPosition.y);
+ electronWindow.hide();
+ }
+ else
+ {
+ // hide the leaf so we can render without intruding on the user
+ // @ts-ignore
+ renderLeaf.parent.containerEl.style.height = "0";
+ // @ts-ignore
+ renderLeaf.parent.parent.containerEl.querySelector(".clickable-icon, .workspace-tab-header-container-inner").style.display = "none";
+ // @ts-ignore
+ renderLeaf.parent.containerEl.style.maxHeight = "var(--header-height)";
+ // @ts-ignore
+ renderLeaf.parent.parent.containerEl.classList.remove("mod-vertical");
+ // @ts-ignore
+ renderLeaf.parent.parent.containerEl.classList.add("mod-horizontal");
+
+ let newSize = { width: 800, height: 400 };
+ obsidianWindow.resizeTo(newSize.width, newSize.height);
+ let newPosition = {x: window.screen.width / 2 - 450, y: window.screen.height - 450 - 75};
+ obsidianWindow.moveTo(newPosition.x, newPosition.y);
+ }
+
+ electronWindow.setAlwaysOnTop(true, "floating", 1);
+ electronWindow.webContents.setBackgroundThrottling(false);
+
+ function windowClosed()
+ {
+ if (cancelled) return;
+ endBatch();
+ cancelled = true;
+ electronWindow?.off("close", windowClosed);
+ }
+
+ electronWindow.on("close", windowClosed);
+
+
+ createLoadingContainer();
+ }
+
+ export function endBatch()
+ {
+ if (!batchStarted) return;
+
+ if (renderLeaf)
+ {
+ if (!errorInBatch)
+ {
+ ExportLog.log("Closing render window");
+ renderLeaf.detach();
+ }
+ else
+ {
+ ExportLog.warning("Error in batch, leaving render window open");
+ _reportProgress(1, 1, "Completed with errors", "Please see the log for more details.", errorColor);
+ }
+ }
+
+ electronWindow = undefined;
+ renderLeaf = undefined;
+
+ batchStarted = false;
+ }
+
+ function generateLogEl(title: string, message: any, textColor: string, backgroundColor: string): HTMLElement
+ {
+ let logEl = document.createElement("div");
+ logEl.className = "html-render-log-item";
+ logEl.style.display = "flex";
+ logEl.style.flexDirection = "column";
+ logEl.style.marginBottom = "2px";
+ logEl.style.fontSize = "12px";
+ logEl.innerHTML =
+ `
+
+
+ `;
+ logEl.querySelector(".html-render-log-title")!.textContent = title;
+ logEl.querySelector(".html-render-log-message")!.textContent = message.toString();
+
+ logEl.style.color = textColor;
+ logEl.style.backgroundColor = backgroundColor;
+ logEl.style.borderLeft = `5px solid ${textColor}`;
+ logEl.style.borderBottom = "1px solid var(--divider-color)";
+ logEl.style.borderTop = "1px solid var(--divider-color)";
+
+ return logEl;
+ }
+
+ function createLoadingContainer()
+ {
+ if (!loadingContainer)
+ {
+ loadingContainer = document.createElement("div");
+ loadingContainer.className = `html-render-progress-container`;
+ loadingContainer.setAttribute("style", "height: 100%; min-width: 100%; display:flex; flex-direction:column; align-content: center; justify-content: center; align-items: center;");
+ loadingContainer.innerHTML =
+ `
+