From be6dc38c520219b214edbe7c4c4fe22e84a1a03b Mon Sep 17 00:00:00 2001 From: sShemet Date: Tue, 25 Mar 2025 00:00:32 +0500 Subject: [PATCH] vault backup: 2025-03-25 00:00:32 --- .obsidian/community-plugins.json | 3 +- .../plugins/copy-document-as-html/main.js | 897 ++++++++++++++++++ .../copy-document-as-html/manifest.json | 10 + .../plugins/copy-document-as-html/styles.css | 25 + .obsidian/workspace.json | 30 +- .../Mol/Серверы/DOCS/Expiring Email.md | 1 + 6 files changed, 943 insertions(+), 23 deletions(-) create mode 100644 .obsidian/plugins/copy-document-as-html/main.js create mode 100644 .obsidian/plugins/copy-document-as-html/manifest.json create mode 100644 .obsidian/plugins/copy-document-as-html/styles.css create mode 100644 WORK & PROJECTS/Mol/Серверы/DOCS/Expiring Email.md 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(` + `)).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); + }); + }); + })); + } +}; + +/* 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