/* THIS IS A GENERATED/BUNDLED FILE BY ESBUILD if you want to view the source, please visit the github repository of this plugin */ var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // main.ts var main_exports = {}; __export(main_exports, { default: () => CopyDocumentAsHTMLPlugin }); module.exports = __toCommonJS(main_exports); var import_obsidian = require("obsidian"); function allWithProgress(promises, callback) { let count = 0; callback(0); for (const promise of promises) { promise.then(() => { count++; callback(count * 100 / promises.length); }); } return Promise.all(promises); } async function delay(milliseconds) { return new Promise((resolve) => setTimeout(resolve, milliseconds)); } var DEFAULT_STYLESHEET = `body,input { font-family: "Roboto","Helvetica Neue",Helvetica,Arial,sans-serif } code, kbd, pre { font-family: "Roboto Mono", "Courier New", Courier, monospace; background-color: #f5f5f5; } pre { padding: 1em 0.5em; } table { background: white; border: 1px solid #666; border-collapse: collapse; padding: 0.5em; } table thead th, table tfoot th { text-align: left; background-color: #eaeaea; color: black; } table th, table td { border: 1px solid #ddd; padding: 0.5em; } table td { color: #222222; } .callout[data-callout="abstract"] .callout-title, .callout[data-callout="summary"] .callout-title, .callout[data-callout="tldr"] .callout-title, .callout[data-callout="faq"] .callout-title, .callout[data-callout="info"] .callout-title, .callout[data-callout="help"] .callout-title { background-color: #828ee7; } .callout[data-callout="tip"] .callout-title, .callout[data-callout="hint"] .callout-title, .callout[data-callout="important"] .callout-title { background-color: #34bbe6; } .callout[data-callout="success"] .callout-title, .callout[data-callout="check"] .callout-title, .callout[data-callout="done"] .callout-title { background-color: #a3e048; } .callout[data-callout="question"] .callout-title, .callout[data-callout="todo"] .callout-title { background-color: #49da9a; } .callout[data-callout="caution"] .callout-title, .callout[data-callout="attention"] .callout-title { background-color: #f7d038; } .callout[data-callout="warning"] .callout-title, .callout[data-callout="missing"] .callout-title, .callout[data-callout="bug"] .callout-title { background-color: #eb7532; } .callout[data-callout="failure"] .callout-title, .callout[data-callout="fail"] .callout-title, .callout[data-callout="danger"] .callout-title, .callout[data-callout="error"] .callout-title { background-color: #e6261f; } .callout[data-callout="example"] .callout-title { background-color: #d23be7; } .callout[data-callout="quote"] .callout-title, .callout[data-callout="cite"] .callout-title { background-color: #aaaaaa; } .callout-icon { flex: 0 0 auto; display: flex; align-self: center; } svg.svg-icon { height: 18px; width: 18px; stroke-width: 1.75px; } .callout { overflow: hidden; margin: 1em 0; box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2); border-radius: 4px; } .callout-title { padding: .5em; display: flex; gap: 8px; font-size: inherit; color: black; line-height: 1.3em; } .callout-title-inner { font-weight: bold; color: black; } .callout-content { overflow-x: auto; padding: 0.25em .5em; color: #222222; background-color: white !important; } ul.contains-task-list { padding-left: 0; list-style: none; } ul.contains-task-list ul.contains-task-list { padding-left: 2em; } ul.contains-task-list li input[type="checkbox"] { margin-right: .5em; } .callout-table, .callout-table tr, .callout-table p { width: 100%; padding: 0; } .callout-table td { width: 100%; padding: 0 1em; } .callout-table p { padding-bottom: 0.5em; } .source-table { width: 100%; background-color: #f5f5f5; } `; var MERMAID_STYLESHEET = ` :root { --default-font: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Microsoft YaHei Light", sans-serif; --font-monospace: 'Source Code Pro', monospace; --background-primary: #ffffff; --background-modifier-border: #ddd; --text-accent: #705dcf; --text-accent-hover: #7a6ae6; --text-normal: #2e3338; --background-secondary: #f2f3f5; --background-secondary-alt: #fcfcfc; --text-muted: #888888; --font-mermaid: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Inter", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Microsoft YaHei Light", sans-serif; --text-error: #E4374B; --background-primary-alt: '#fafafa'; --background-accent: ''; --interactive-accent: hsl( 254, 80%, calc( 68% + 2.5%)); --background-modifier-error: #E4374B; --background-primary-alt: #fafafa; --background-modifier-border: #e0e0e0; } `; var DEFAULT_HTML_TEMPLATE = ` \${title} \${body} `; var copyIsRunning = false; var ppIsProcessing = false; var ppLastBlockDate = Date.now(); var documentRendererDefaults = { convertSvgToBitmap: true, removeFrontMatter: true, formatCodeWithTables: false, formatCalloutsWithTables: false, embedExternalLinks: false, removeDataviewMetadataLines: false, footnoteHandling: 2 /* REMOVE_LINK */, internalLinkHandling: 0 /* CONVERT_TO_TEXT */, disableImageEmbedding: false }; var DocumentRenderer = class { constructor(app, options = documentRendererDefaults) { this.app = app; this.options = options; this.optionRenderSettlingDelay = 100; this.mimeMap = /* @__PURE__ */ new Map([ ["svg", "image/svg+xml"], ["jpg", "image/jpeg"] ]); this.externalSchemes = ["http", "https"]; this.vaultPath = this.app.vault.getRoot().vault.adapter.getBasePath().replace(/\\/g, "/"); this.vaultLocalUriPrefix = `app://local/${this.vaultPath}`; this.vaultOpenUri = `obsidian://open?vault=${encodeURIComponent(this.app.vault.getName())}`; this.vaultSearchUri = `obsidian://search?vault=${encodeURIComponent(this.app.vault.getName())}`; this.view = new import_obsidian.Component(); } async renderDocument(markdown, path) { this.modal = new CopyingToHtmlModal(this.app); this.modal.open(); try { const topNode = await this.renderMarkdown(markdown, path); return await this.transformHTML(topNode); } finally { this.modal.close(); } } async renderMarkdown(markdown, path) { const processedMarkdown = this.preprocessMarkdown(markdown); const wrapper = document.createElement("div"); wrapper.style.display = "hidden"; document.body.appendChild(wrapper); await import_obsidian.MarkdownRenderer.render(this.app, processedMarkdown, wrapper, path, this.view); await this.untilRendered(); await this.loadComponents(this.view); const result = wrapper.cloneNode(true); document.body.removeChild(wrapper); this.view.unload(); return result; } async loadComponents(view) { const internalView = view; const loadChildren = async (component, visited = /* @__PURE__ */ new Set()) => { var _a, _b; if (visited.has(component)) { return; } visited.add(component); const internalComponent = component; if ((_a = internalComponent._children) == null ? void 0 : _a.length) { for (const child of internalComponent._children) { await loadChildren(child, visited); } } try { if (((_b = component == null ? void 0 : component.constructor) == null ? void 0 : _b.name) === "SheetElement") { await component.onload(); } } catch (error) { console.error(`Error calling onload()`, error); } }; await loadChildren(internalView); } preprocessMarkdown(markdown) { let processed = markdown; if (this.options.removeDataviewMetadataLines) { processed = processed.replace(/^[^ \t:#`<>][^:#`<>]+::.*$/gm, ""); } return processed; } async untilRendered() { while (ppIsProcessing || Date.now() - ppLastBlockDate < this.optionRenderSettlingDelay) { if (ppLastBlockDate === 0) { break; } await delay(20); } } async transformHTML(element) { const node = element.cloneNode(true); node.removeAttribute("style"); if (this.options.removeFrontMatter) { this.removeFrontMatter(node); } this.replaceLinksOfClass(node, "internal-link"); this.replaceLinksOfClass(node, "tag"); this.makeCheckboxesReadOnly(node); this.removeCollapseIndicators(node); this.removeButtons(node); this.removeStrangeNewWorldsLinks(node); if (this.options.formatCodeWithTables) { this.transformCodeToTables(node); } if (this.options.formatCalloutsWithTables) { this.transformCalloutsToTables(node); } if (this.options.footnoteHandling == 0 /* REMOVE_ALL */) { this.removeAllFootnotes(node); } if (this.options.footnoteHandling == 2 /* REMOVE_LINK */) { this.removeFootnoteLinks(node); } else if (this.options.footnoteHandling == 3 /* TITLE_ATTRIBUTE */) { } if (!this.options.disableImageEmbedding) { await this.embedImages(node); await this.renderSvg(node); } return node; } removeFrontMatter(node) { node.querySelectorAll(".frontmatter, .frontmatter-container").forEach((node2) => node2.remove()); } replaceLinksOfClass(node, className) { if (this.options.internalLinkHandling === 3 /* LEAVE_AS_IS */) { return; } node.querySelectorAll(`a.${className}`).forEach((node2) => { switch (this.options.internalLinkHandling) { case 1 /* CONVERT_TO_OBSIDIAN_URI */: { const linkNode = node2.parentNode.createEl("a"); linkNode.innerText = node2.getText(); if (className === "tag") { linkNode.href = this.vaultSearchUri + "&query=tag:" + encodeURIComponent(node2.getAttribute("href")); } else { if (node2.getAttribute("href").startsWith("#")) { linkNode.href = node2.getAttribute("href"); } else { linkNode.href = this.vaultOpenUri + "&file=" + encodeURIComponent(node2.getAttribute("href")); } } linkNode.className = className; node2.parentNode.replaceChild(linkNode, node2); } break; case 2 /* LINK_TO_HTML */: { const linkNode = node2.parentNode.createEl("a"); linkNode.innerText = node2.getAttribute("href"); linkNode.className = className; if (node2.getAttribute("href").startsWith("#")) { linkNode.href = node2.getAttribute("href"); } else { linkNode.href = node2.getAttribute("href").replace(/^(.*?)(?:\.md)?(#.*?)?$/, "$1.html$2"); } node2.parentNode.replaceChild(linkNode, node2); } break; case 0 /* CONVERT_TO_TEXT */: default: { const textNode = node2.parentNode.createEl("span"); textNode.innerText = node2.getText(); textNode.className = className; node2.parentNode.replaceChild(textNode, node2); } break; } }); } makeCheckboxesReadOnly(node) { node.querySelectorAll('input[type="checkbox"]').forEach((node2) => node2.setAttribute("disabled", "disabled")); } removeCollapseIndicators(node) { node.querySelectorAll(".collapse-indicator").forEach((node2) => node2.remove()); } removeButtons(node) { node.querySelectorAll("button").forEach((node2) => node2.remove()); } removeStrangeNewWorldsLinks(node) { node.querySelectorAll(".snw-reference").forEach((node2) => node2.remove()); } transformCodeToTables(node) { node.querySelectorAll("pre").forEach((node2) => { const codeEl = node2.querySelector("code"); if (codeEl) { const code = codeEl.innerHTML.replace(/\n*$/, ""); const table = node2.parentElement.createEl("table"); table.className = "source-table"; table.innerHTML = `
${code}
`; node2.parentElement.replaceChild(table, node2); } }); } transformCalloutsToTables(node) { node.querySelectorAll(".callout").forEach((node2) => { var _a; const callout = node2.parentElement.createEl("table"); callout.addClass("callout-table", "callout"); callout.setAttribute("data-callout", (_a = node2.getAttribute("data-callout")) != null ? _a : "quote"); const headRow = callout.createEl("tr"); const headColumn = headRow.createEl("td"); headColumn.addClass("callout-title"); const title = node2.querySelector(".callout-title-inner"); if (title) { const span = headColumn.createEl("span"); span.innerHTML = title.innerHTML; } const originalContent = node2.querySelector(".callout-content"); if (originalContent) { const row = callout.createEl("tr"); const column = row.createEl("td"); column.innerHTML = originalContent.innerHTML; } node2.replaceWith(callout); }); } removeAllFootnotes(node) { node.querySelectorAll("section.footnotes").forEach((section) => section.parentNode.removeChild(section)); node.querySelectorAll(".footnote-link").forEach((link) => { link.parentNode.parentNode.removeChild(link.parentNode); }); } removeFootnoteLinks(node) { node.querySelectorAll(".footnote-link").forEach((link) => { const text = link.getText(); if (text === "\u21A9\uFE0E") { link.parentNode.removeChild(link); } else { const span = link.parentNode.createEl("span", { text: link.getText(), cls: "footnote-link" }); link.parentNode.replaceChild(span, link); } }); } async embedImages(node) { const promises = []; node.querySelectorAll("img").forEach((img) => { if (img.src) { if (img.src.startsWith("data:image/svg+xml") && this.options.convertSvgToBitmap) { promises.push(this.replaceImageSource(img)); return; } if (!this.options.embedExternalLinks) { const [scheme] = img.src.split(":", 1); if (this.externalSchemes.includes(scheme.toLowerCase())) { return; } else { } } if (!img.src.startsWith("data:")) { promises.push(this.replaceImageSource(img)); return; } } }); this.modal.progress.max = 100; await allWithProgress(promises, (percentCompleted) => this.modal.progress.value = percentCompleted); return node; } async renderSvg(node) { const xmlSerializer = new XMLSerializer(); if (!this.options.convertSvgToBitmap) { return node; } const promises = []; const replaceSvg = async (svg) => { const style = svg.querySelector("style") || svg.appendChild(document.createElement("style")); style.innerHTML += MERMAID_STYLESHEET; const svgAsString = xmlSerializer.serializeToString(svg); const svgData = `data:image/svg+xml;base64,` + Buffer.from(svgAsString).toString("base64"); const dataUri = await this.imageToDataUri(svgData); const img = svg.createEl("img"); img.style.cssText = svg.style.cssText; img.src = dataUri; svg.parentElement.replaceChild(img, svg); }; node.querySelectorAll("svg").forEach((svg) => { promises.push(replaceSvg(svg)); }); this.modal.progress.max = 0; await allWithProgress(promises, (percentCompleted) => this.modal.progress.value = percentCompleted); return node; } async replaceImageSource(image) { const imageSourcePath = decodeURI(image.src); if (imageSourcePath.startsWith(this.vaultLocalUriPrefix)) { let path = imageSourcePath.substring(this.vaultLocalUriPrefix.length + 1).replace(/[?#].*/, ""); path = decodeURI(path); const mimeType = this.guessMimeType(path); const data = await this.readFromVault(path, mimeType); if (this.isSvg(mimeType) && this.options.convertSvgToBitmap) { image.src = await this.imageToDataUri(data); } else { image.src = data; } } else { image.src = await this.imageToDataUri(image.src); } } async imageToDataUri(url) { const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); const image = new Image(); image.setAttribute("crossOrigin", "anonymous"); const dataUriPromise = new Promise((resolve, reject) => { image.onload = () => { canvas.width = image.naturalWidth; canvas.height = image.naturalHeight; ctx.drawImage(image, 0, 0); try { const uri = canvas.toDataURL("image/png"); resolve(uri); } catch (err) { console.log(`failed ${url}`, err); resolve(url); } canvas.remove(); }; image.onerror = (err) => { console.log("could not load data uri"); resolve(url); }; }); image.src = url; return dataUriPromise; } async readFromVault(path, mimeType) { const tfile = this.app.vault.getAbstractFileByPath(path); const data = await this.app.vault.readBinary(tfile); return `data:${mimeType};base64,` + (0, import_obsidian.arrayBufferToBase64)(data); } guessMimeType(filePath) { const extension = this.getExtension(filePath) || "png"; return this.mimeMap.get(extension) || `image/${extension}`; } getExtension(filePath) { const fileName = filePath.slice(filePath.lastIndexOf("/") + 1); return fileName.slice(fileName.lastIndexOf(".") + 1 || fileName.length).toLowerCase(); } isSvg(mimeType) { return mimeType === "image/svg+xml"; } }; var CopyingToHtmlModal = class extends import_obsidian.Modal { constructor(app) { super(app); } get progress() { return this._progress; } onOpen() { const { titleEl, contentEl } = this; titleEl.setText("Copying to clipboard"); this._progress = contentEl.createEl("progress"); this._progress.style.width = "100%"; } onClose() { const { contentEl } = this; contentEl.empty(); } }; var _CopyDocumentAsHTMLSettingsTab = class extends import_obsidian.PluginSettingTab { constructor(app, plugin) { super(app, plugin); this.plugin = plugin; this.plugin = plugin; } display() { const { containerEl } = this; containerEl.empty(); containerEl.createEl("h2", { text: "Copy document as HTML Settings" }); containerEl.createEl("h3", { text: "Compatibility" }); new import_obsidian.Setting(containerEl).setName("Convert SVG files to bitmap").setDesc("If checked, SVG files are converted to bitmap. This makes the copied documents heavier but improves compatibility (eg. with gmail).").addToggle((toggle) => toggle.setValue(this.plugin.settings.convertSvgToBitmap).onChange(async (value) => { this.plugin.settings.convertSvgToBitmap = value; await this.plugin.saveSettings(); })); new import_obsidian.Setting(containerEl).setName("Embed external images").setDesc("If checked, external images are downloaded and embedded. If unchecked, the resulting document may contain links to external resources").addToggle((toggle) => toggle.setValue(this.plugin.settings.embedExternalLinks).onChange(async (value) => { this.plugin.settings.embedExternalLinks = value; await this.plugin.saveSettings(); })); new import_obsidian.Setting(containerEl).setName("Render code with tables").setDesc("If checked code blocks are rendered as tables, which makes pasting into Google docs somewhat prettier.").addToggle((toggle) => toggle.setValue(this.plugin.settings.formatCodeWithTables).onChange(async (value) => { this.plugin.settings.formatCodeWithTables = value; await this.plugin.saveSettings(); })); new import_obsidian.Setting(containerEl).setName("Render callouts with tables").setDesc("If checked callouts are rendered as tables, which makes pasting into Google docs somewhat prettier.").addToggle((toggle) => toggle.setValue(this.plugin.settings.formatCalloutsWithTables).onChange(async (value) => { this.plugin.settings.formatCalloutsWithTables = value; await this.plugin.saveSettings(); })); containerEl.createEl("h3", { text: "Rendering" }); new import_obsidian.Setting(containerEl).setName("Include filename as header").setDesc("If checked, the filename is inserted as a level 1 header. (only if an entire document is copied)").addToggle((toggle) => toggle.setValue(this.plugin.settings.fileNameAsHeader).onChange(async (value) => { this.plugin.settings.fileNameAsHeader = value; await this.plugin.saveSettings(); })); new import_obsidian.Setting(containerEl).setName("Copy HTML fragment only").setDesc("If checked, only generate a HTML fragment and not a full HTML document. This excludes the header, and effectively disables all styling.").addToggle((toggle) => toggle.setValue(this.plugin.settings.bareHtmlOnly).onChange(async (value) => { this.plugin.settings.bareHtmlOnly = value; await this.plugin.saveSettings(); })); new import_obsidian.Setting(containerEl).setName("Remove properties / front-matter sections").setDesc("If checked, the YAML content between --- lines at the front of the document are removed. If you don't know what this means, leave it on.").addToggle((toggle) => toggle.setValue(this.plugin.settings.removeFrontMatter).onChange(async (value) => { this.plugin.settings.removeFrontMatter = value; await this.plugin.saveSettings(); })); new import_obsidian.Setting(containerEl).setName("Remove dataview metadata lines").setDesc(_CopyDocumentAsHTMLSettingsTab.createFragmentWithHTML(`

Remove lines that only contain dataview meta-data, eg. "rating:: 9". Metadata between square brackets is left intact.

Current limitations are that lines starting with a space are not removed, and lines that look like metadata in code blocks are removed if they don't start with a space

`)).addToggle((toggle) => toggle.setValue(this.plugin.settings.removeDataviewMetadataLines).onChange(async (value) => { this.plugin.settings.removeDataviewMetadataLines = value; await this.plugin.saveSettings(); })); new import_obsidian.Setting(containerEl).setName("Footnote handling").setDesc(_CopyDocumentAsHTMLSettingsTab.createFragmentWithHTML(` `)).addDropdown((dropdown) => dropdown.addOption(0 /* REMOVE_ALL */.toString(), "Remove everything").addOption(2 /* REMOVE_LINK */.toString(), "Display only").addOption(1 /* LEAVE_LINK */.toString(), "Display and link").setValue(this.plugin.settings.footnoteHandling.toString()).onChange(async (value) => { switch (value) { case 3 /* TITLE_ATTRIBUTE */.toString(): this.plugin.settings.footnoteHandling = 3 /* TITLE_ATTRIBUTE */; break; case 0 /* REMOVE_ALL */.toString(): this.plugin.settings.footnoteHandling = 0 /* REMOVE_ALL */; break; case 2 /* REMOVE_LINK */.toString(): this.plugin.settings.footnoteHandling = 2 /* REMOVE_LINK */; break; case 1 /* LEAVE_LINK */.toString(): default: this.plugin.settings.footnoteHandling = 1 /* LEAVE_LINK */; break; } await this.plugin.saveSettings(); })); new import_obsidian.Setting(containerEl).setName("Link handling").setDesc(_CopyDocumentAsHTMLSettingsTab.createFragmentWithHTML(` This option controls how links to Obsidian documents and tags are handled. `)).addDropdown((dropdown) => dropdown.addOption(0 /* CONVERT_TO_TEXT */.toString(), "Don't link").addOption(1 /* CONVERT_TO_OBSIDIAN_URI */.toString(), "Open with Obsidian").addOption(2 /* LINK_TO_HTML */.toString(), "Link to HTML").addOption(3 /* LEAVE_AS_IS */.toString(), "Leave as is").setValue(this.plugin.settings.internalLinkHandling.toString()).onChange(async (value) => { switch (value) { case 1 /* CONVERT_TO_OBSIDIAN_URI */.toString(): this.plugin.settings.internalLinkHandling = 1 /* CONVERT_TO_OBSIDIAN_URI */; break; case 2 /* LINK_TO_HTML */.toString(): this.plugin.settings.internalLinkHandling = 2 /* LINK_TO_HTML */; break; case 3 /* LEAVE_AS_IS */.toString(): this.plugin.settings.internalLinkHandling = 3 /* LEAVE_AS_IS */; break; case 0 /* CONVERT_TO_TEXT */.toString(): default: this.plugin.settings.internalLinkHandling = 0 /* CONVERT_TO_TEXT */; break; } await this.plugin.saveSettings(); })); containerEl.createEl("h3", { text: "Custom templates (advanced)" }); const useCustomStylesheetSetting = new import_obsidian.Setting(containerEl).setName("Provide a custom stylesheet").setDesc("The default stylesheet provides minimalistic theming. You may want to customize it for better looks. Disabling this setting will restore the default stylesheet."); const customStylesheetSetting = new import_obsidian.Setting(containerEl).setClass("customizable-text-setting").addTextArea((textArea) => textArea.setValue(this.plugin.settings.styleSheet).onChange(async (value) => { this.plugin.settings.styleSheet = value; await this.plugin.saveSettings(); })); useCustomStylesheetSetting.addToggle((toggle) => { customStylesheetSetting.settingEl.toggle(this.plugin.settings.useCustomStylesheet); toggle.setValue(this.plugin.settings.useCustomStylesheet).onChange(async (value) => { this.plugin.settings.useCustomStylesheet = value; customStylesheetSetting.settingEl.toggle(this.plugin.settings.useCustomStylesheet); if (!value) { this.plugin.settings.styleSheet = DEFAULT_STYLESHEET; } await this.plugin.saveSettings(); }); }); const useCustomHtmlTemplateSetting = new import_obsidian.Setting(containerEl).setName("Provide a custom HTML template").setDesc(_CopyDocumentAsHTMLSettingsTab.createFragmentWithHTML(`For even more customization, you can provide a custom HTML template. Disabling this setting will restore the default template.

Note that the template is not used if the "Copy HTML fragment only" setting is enabled.`)); const customHtmlTemplateSetting = new import_obsidian.Setting(containerEl).setDesc(_CopyDocumentAsHTMLSettingsTab.createFragmentWithHTML(` The template should include the following placeholders :
`)).setClass("customizable-text-setting").addTextArea((textArea) => textArea.setValue(this.plugin.settings.htmlTemplate).onChange(async (value) => { this.plugin.settings.htmlTemplate = value; await this.plugin.saveSettings(); })); useCustomHtmlTemplateSetting.addToggle((toggle) => { customHtmlTemplateSetting.settingEl.toggle(this.plugin.settings.useCustomHtmlTemplate); toggle.setValue(this.plugin.settings.useCustomHtmlTemplate).onChange(async (value) => { this.plugin.settings.useCustomHtmlTemplate = value; customHtmlTemplateSetting.settingEl.toggle(this.plugin.settings.useCustomHtmlTemplate); if (!value) { this.plugin.settings.htmlTemplate = DEFAULT_HTML_TEMPLATE; } await this.plugin.saveSettings(); }); }); containerEl.createEl("h3", { text: "Exotic / Developer options" }); new import_obsidian.Setting(containerEl).setName("Don't embed images").setDesc("When this option is enabled, images will not be embedded in the HTML document, but broken links will be left in place. This is not recommended.").addToggle((toggle) => toggle.setValue(this.plugin.settings.disableImageEmbedding).onChange(async (value) => { this.plugin.settings.disableImageEmbedding = value; await this.plugin.saveSettings(); })); } }; var CopyDocumentAsHTMLSettingsTab = _CopyDocumentAsHTMLSettingsTab; CopyDocumentAsHTMLSettingsTab.createFragmentWithHTML = (html) => createFragment((documentFragment) => documentFragment.createDiv().innerHTML = html); var DEFAULT_SETTINGS = { removeFrontMatter: true, convertSvgToBitmap: true, useCustomStylesheet: false, useCustomHtmlTemplate: false, embedExternalLinks: false, removeDataviewMetadataLines: false, formatCodeWithTables: false, formatCalloutsWithTables: false, footnoteHandling: 2 /* REMOVE_LINK */, internalLinkHandling: 0 /* CONVERT_TO_TEXT */, styleSheet: DEFAULT_STYLESHEET, htmlTemplate: DEFAULT_HTML_TEMPLATE, bareHtmlOnly: false, fileNameAsHeader: false, disableImageEmbedding: false }; var CopyDocumentAsHTMLPlugin = class extends import_obsidian.Plugin { async onload() { await this.loadSettings(); this.addCommand({ id: "smart-copy-as-html", name: "Copy selection or document to clipboard", checkCallback: this.buildCheckCallback((view) => this.copyFromView(view, view.editor.somethingSelected())) }); this.addCommand({ id: "copy-as-html", name: "Copy entire document to clipboard", checkCallback: this.buildCheckCallback((view) => this.copyFromView(view, false)) }); this.addCommand({ id: "copy-selection-as-html", name: "Copy current selection to clipboard", checkCallback: this.buildCheckCallback((view) => this.copyFromView(view, true)) }); const beforeAllPostProcessor = this.registerMarkdownPostProcessor(async () => { ppIsProcessing = true; }); beforeAllPostProcessor.sortOrder = -1e4; const afterAllPostProcessor = this.registerMarkdownPostProcessor(async () => { ppLastBlockDate = Date.now(); ppIsProcessing = false; }); afterAllPostProcessor.sortOrder = 1e4; this.addSettingTab(new CopyDocumentAsHTMLSettingsTab(this.app, this)); this.setupEditorMenuEntry(); } async loadSettings() { this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); if (!this.settings.useCustomStylesheet) { this.settings.styleSheet = DEFAULT_STYLESHEET; } if (!this.settings.useCustomHtmlTemplate) { this.settings.htmlTemplate = DEFAULT_HTML_TEMPLATE; } } async saveSettings() { await this.saveData(this.settings); } buildCheckCallback(action) { return (checking) => { if (copyIsRunning) { console.log("Document is already being copied"); return false; } const activeView = this.app.workspace.getActiveViewOfType(import_obsidian.MarkdownView); if (!activeView) { console.log("Nothing to copy: No active markdown view"); return false; } if (!checking) { action(activeView); } return true; }; } async copyFromView(activeView, onlySelected) { if (!activeView.editor) { console.error("No editor in active view, nothing to copy"); return; } if (!activeView.file) { console.error("No file in active view, nothing to copy"); return; } const markdown = onlySelected ? activeView.editor.getSelection() : activeView.data; const path = activeView.file.path; const name = activeView.file.name; return this.doCopy(markdown, path, name, !onlySelected); } async copyFromFile(file) { if (!(file instanceof import_obsidian.TFile)) { console.log(`cannot copy folder to HTML: ${file.path}`); return; } if (file.extension.toLowerCase() !== "md") { console.log(`cannot only copy .md files to HTML: ${file.path}`); return; } const markdown = await file.vault.cachedRead(file); return this.doCopy(markdown, file.path, file.name, true); } async doCopy(markdown, path, name, isFullDocument) { console.log(`Copying "${path}" to clipboard...`); const title = name.replace(/\.md$/i, ""); const copier = new DocumentRenderer(this.app, this.settings); try { copyIsRunning = true; ppLastBlockDate = Date.now(); ppIsProcessing = true; const htmlBody = await copier.renderDocument(markdown, path); if (this.settings.fileNameAsHeader && isFullDocument) { const h1 = htmlBody.createEl("h1"); h1.innerHTML = title; htmlBody.insertBefore(h1, htmlBody.firstChild); } const htmlDocument = this.settings.bareHtmlOnly ? htmlBody.outerHTML : this.expandHtmlTemplate(htmlBody.outerHTML, title); const data = new ClipboardItem({ "text/html": new Blob([htmlDocument], { type: ["text/html", "text/plain"] }), "text/plain": new Blob([htmlDocument], { type: "text/plain" }) }); await navigator.clipboard.write([data]); console.log(`Copied to clipboard as HTML`); new import_obsidian.Notice(`Copied to clipboard as HTML`); } catch (error) { new import_obsidian.Notice(`copy failed: ${error}`); console.error("copy failed", error); } finally { copyIsRunning = false; } } expandHtmlTemplate(html, title) { const template = this.settings.useCustomHtmlTemplate ? this.settings.htmlTemplate : DEFAULT_HTML_TEMPLATE; return template.replace("${title}", title).replace("${body}", html).replace("${stylesheet}", this.settings.styleSheet).replace("${MERMAID_STYLESHEET}", MERMAID_STYLESHEET); } setupEditorMenuEntry() { this.registerEvent(this.app.workspace.on("file-menu", (menu, file, view) => { menu.addItem((item) => { item.setTitle("Copy as HTML").setIcon("clipboard-copy").onClick(async () => { return this.copyFromFile(file); }); }); })); } };