diff --git a/.obsidian/community-plugins.json b/.obsidian/community-plugins.json
index 2485229..b5ef5f5 100644
--- a/.obsidian/community-plugins.json
+++ b/.obsidian/community-plugins.json
@@ -2,5 +2,6 @@
"obsidian-full-calendar",
"obsidian-icon-folder",
"obsidian-git",
- "obsidian-smart-typography"
+ "obsidian-smart-typography",
+ "copy-document-as-html"
]
\ No newline at end of file
diff --git a/.obsidian/plugins/copy-document-as-html/main.js b/.obsidian/plugins/copy-document-as-html/main.js
new file mode 100644
index 0000000..39f2857
--- /dev/null
+++ b/.obsidian/plugins/copy-document-as-html/main.js
@@ -0,0 +1,897 @@
+/*
+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(`
+
+ - Remove everything: Remove references and links.
+ - Display only: leave reference and foot-note, but don't display as a link.
+ - Display and link: attempt to link the reference to the footnote, may not work depending on paste target.
+
`)).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.
+
+ - Don't link: only render the link title
+ - Open with Obsidian: convert the link to an obsidian:// URI
+ - Link to HTML: keep the link, but convert the extension to .html
+ - Leave as is: keep the generated link
+
`)).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 :
+
+ \${title}: the document title
+ \${stylesheet}: the CSS stylesheet. The custom stylesheet will be applied if any is specified
+ \${MERMAID_STYLESHEET}: the CSS for mermaid diagrams
+ \${body}: the document body
+
`)).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);
+ });
+ });
+ }));
+ }
+};
+
+/* nosourcemap */
\ No newline at end of file
diff --git a/.obsidian/plugins/copy-document-as-html/manifest.json b/.obsidian/plugins/copy-document-as-html/manifest.json
new file mode 100644
index 0000000..4b5d7c3
--- /dev/null
+++ b/.obsidian/plugins/copy-document-as-html/manifest.json
@@ -0,0 +1,10 @@
+{
+ "id": "copy-document-as-html",
+ "name": "Copy document as HTML",
+ "version": "0.8.1",
+ "minAppVersion": "1.6.3",
+ "description": "Copy the current document to clipboard as HTML, including images, diagrams etc...",
+ "author": "mvdkwast",
+ "authorUrl": "https://github.com/mvdkwast",
+ "isDesktopOnly": true
+}
diff --git a/.obsidian/plugins/copy-document-as-html/styles.css b/.obsidian/plugins/copy-document-as-html/styles.css
new file mode 100644
index 0000000..5853e4e
--- /dev/null
+++ b/.obsidian/plugins/copy-document-as-html/styles.css
@@ -0,0 +1,25 @@
+
+.customizable-text-setting {
+ display: flex;
+ flex-direction: column;
+ border: none;
+}
+
+.customizable-text-setting .setting-item-info {
+ width: 100%;
+ margin-left: 16px;
+}
+
+.customizable-text-setting .setting-item-info .setting-item-description {
+ margin-bottom: 16px;
+}
+
+.customizable-text-setting .setting-item-control {
+ width: 100%;
+ flex-direction: column;
+}
+
+.customizable-text-setting .setting-item-control textarea {
+ width: 100%;
+ height: 15em;
+}
diff --git a/.obsidian/workspace.json b/.obsidian/workspace.json
index dc80082..883e6eb 100644
--- a/.obsidian/workspace.json
+++ b/.obsidian/workspace.json
@@ -7,32 +7,18 @@
"id": "b1afd552ee0aa86f",
"type": "tabs",
"children": [
- {
- "id": "ef7d005f7842eb2e",
- "type": "leaf",
- "state": {
- "type": "markdown",
- "state": {
- "file": "WORK & PROJECTS/Mol/Серверы/mail.mol-soft.ru.md",
- "mode": "source",
- "source": false
- },
- "icon": "lucide-file",
- "title": "mail.mol-soft.ru"
- }
- },
{
"id": "4e37f7bc6712d830",
"type": "leaf",
"state": {
"type": "markdown",
"state": {
- "file": "WORK & PROJECTS/Mol/Серверы/git.moldev.ru.md",
+ "file": "WORK & PROJECTS/Mol/Серверы/DOCS/Expiring Email.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
- "title": "git.moldev.ru"
+ "title": "Expiring Email"
}
}
]
@@ -175,8 +161,7 @@
}
],
"direction": "horizontal",
- "width": 300,
- "collapsed": true
+ "width": 300
},
"left-ribbon": {
"hiddenItems": {
@@ -190,14 +175,17 @@
"obsidian-git:Open Git source control": false
}
},
- "active": "ef7d005f7842eb2e",
+ "active": "4e37f7bc6712d830",
"lastOpenFiles": [
+ "WORK & PROJECTS/Mol/Серверы/mail.mol-soft.ru.md",
+ "WORK & PROJECTS/Mol/Серверы/DOCS/Expiring Email.md",
+ "WORK & PROJECTS/Mol/Серверы/Untitled.md",
+ "WORK & PROJECTS/Mol/Серверы/git.moldev.ru.md",
"WORK & PROJECTS/Mol/Серверы/Alfa cloud prod.canvas",
"WORK & PROJECTS/Mol/Серверы/Схема инфраструктуры.canvas",
"WORK & PROJECTS/Mol/Серверы/Jira - Service - Confluence - Crm.md",
"WORK & PROJECTS/Mol/Серверы/Alfa cloud readme.md",
"WORK & PROJECTS/Mol/Планы и диаграммы/Схема связей юрлиц и адресов.canvas",
- "WORK & PROJECTS/Mol/Серверы/git.moldev.ru.md",
"Схема связей юрлиц и адресов.png",
"WORK & PROJECTS/Mol/Документы для ТЗ ЛИМС/OA/Кострома/Расширение_ОА_ИЛ_26022024_окончат_1_NEW.pdf",
"WORK & PROJECTS/Mol/Документы для ТЗ ЛИМС/OA/Кострома/Расширение_ОА_ИЛ_26022024_окончат_1_NEW backup.pdf",
@@ -213,7 +201,6 @@
"WORK & PROJECTS/Mol/Серверы/Alfa prod.md",
"WORK & PROJECTS/Ulab/Московский проект/ACCESS TABLE.md",
"WORK & PROJECTS/Mol/Ideas/Все идеи для Моли.md",
- "WORK & PROJECTS/Mol/Серверы/mail.mol-soft.ru.md",
"WORK & PROJECTS/Mol/Ideas/Организационные идеи.md",
"WORK & PROJECTS/Mol/Планы и диаграммы/План СИЛА.md",
"WORK & PROJECTS/Mol/Планы и диаграммы/План разработки.md",
@@ -230,7 +217,6 @@
"WORK & PROJECTS/Mol/Серверы/Alfa cloud prod_fromCanvas_fromC2D.md",
"WORK & PROJECTS/Mol/Серверы/Alfa cloud prod_fromCanvas.md",
"WORK & PROJECTS/Mol/Серверы/Alfa cloud prod_canvas2doc-data/newdoc-node_b9a89b6c704bbab9_fromCanvas.md",
- "WORK & PROJECTS/Mol/Серверы/Alfa cloud prod_canvas2doc-data/newdoc-node_22120c2e0489d623_fromCanvas.md",
"WORK & PROJECTS/Mol/Планы и диаграммы/00001_Редактор_форм/Архитектура редактора и генератора (Alfa + Mol).canvas",
"WORK & PROJECTS/Mol/Планы и диаграммы/00001_Быстрый старт/Быстрый старт.canvas",
"PERSONAL PROJECTS/P2EP/cdRead.canvas",
diff --git a/WORK & PROJECTS/Mol/Серверы/DOCS/Expiring Email.md b/WORK & PROJECTS/Mol/Серверы/DOCS/Expiring Email.md
new file mode 100644
index 0000000..4dfbbe8
--- /dev/null
+++ b/WORK & PROJECTS/Mol/Серверы/DOCS/Expiring Email.md
@@ -0,0 +1 @@
+#### Здравствуйте, {name}
\ No newline at end of file