diff --git a/.obsidian/plugins/d2-obsidian/main.js b/.obsidian/plugins/d2-obsidian/main.js new file mode 100644 index 0000000..cc367f4 --- /dev/null +++ b/.obsidian/plugins/d2-obsidian/main.js @@ -0,0 +1,533 @@ +/* +THIS IS A GENERATED/BUNDLED FILE BY ESBUILD +if you want to view the source, please visit the github repository of this plugin: https://github.com/terrastruct/d2-obsidian +*/ +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __commonJS = (cb, mod) => function __require() { + return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; +}; +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 __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// node_modules/lodash.debounce/index.js +var require_lodash = __commonJS({ + "node_modules/lodash.debounce/index.js"(exports, module2) { + var FUNC_ERROR_TEXT = "Expected a function"; + var NAN = 0 / 0; + var symbolTag = "[object Symbol]"; + var reTrim = /^\s+|\s+$/g; + var reIsBadHex = /^[-+]0x[0-9a-f]+$/i; + var reIsBinary = /^0b[01]+$/i; + var reIsOctal = /^0o[0-7]+$/i; + var freeParseInt = parseInt; + var freeGlobal = typeof global == "object" && global && global.Object === Object && global; + var freeSelf = typeof self == "object" && self && self.Object === Object && self; + var root = freeGlobal || freeSelf || Function("return this")(); + var objectProto = Object.prototype; + var objectToString = objectProto.toString; + var nativeMax = Math.max; + var nativeMin = Math.min; + var now = function() { + return root.Date.now(); + }; + function debounce2(func, wait, options) { + var lastArgs, lastThis, maxWait, result, timerId, lastCallTime, lastInvokeTime = 0, leading = false, maxing = false, trailing = true; + if (typeof func != "function") { + throw new TypeError(FUNC_ERROR_TEXT); + } + wait = toNumber(wait) || 0; + if (isObject(options)) { + leading = !!options.leading; + maxing = "maxWait" in options; + maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait; + trailing = "trailing" in options ? !!options.trailing : trailing; + } + function invokeFunc(time) { + var args = lastArgs, thisArg = lastThis; + lastArgs = lastThis = void 0; + lastInvokeTime = time; + result = func.apply(thisArg, args); + return result; + } + function leadingEdge(time) { + lastInvokeTime = time; + timerId = setTimeout(timerExpired, wait); + return leading ? invokeFunc(time) : result; + } + function remainingWait(time) { + var timeSinceLastCall = time - lastCallTime, timeSinceLastInvoke = time - lastInvokeTime, result2 = wait - timeSinceLastCall; + return maxing ? nativeMin(result2, maxWait - timeSinceLastInvoke) : result2; + } + function shouldInvoke(time) { + var timeSinceLastCall = time - lastCallTime, timeSinceLastInvoke = time - lastInvokeTime; + return lastCallTime === void 0 || timeSinceLastCall >= wait || timeSinceLastCall < 0 || maxing && timeSinceLastInvoke >= maxWait; + } + function timerExpired() { + var time = now(); + if (shouldInvoke(time)) { + return trailingEdge(time); + } + timerId = setTimeout(timerExpired, remainingWait(time)); + } + function trailingEdge(time) { + timerId = void 0; + if (trailing && lastArgs) { + return invokeFunc(time); + } + lastArgs = lastThis = void 0; + return result; + } + function cancel() { + if (timerId !== void 0) { + clearTimeout(timerId); + } + lastInvokeTime = 0; + lastArgs = lastCallTime = lastThis = timerId = void 0; + } + function flush() { + return timerId === void 0 ? result : trailingEdge(now()); + } + function debounced() { + var time = now(), isInvoking = shouldInvoke(time); + lastArgs = arguments; + lastThis = this; + lastCallTime = time; + if (isInvoking) { + if (timerId === void 0) { + return leadingEdge(lastCallTime); + } + if (maxing) { + timerId = setTimeout(timerExpired, wait); + return invokeFunc(lastCallTime); + } + } + if (timerId === void 0) { + timerId = setTimeout(timerExpired, wait); + } + return result; + } + debounced.cancel = cancel; + debounced.flush = flush; + return debounced; + } + function isObject(value) { + var type = typeof value; + return !!value && (type == "object" || type == "function"); + } + function isObjectLike(value) { + return !!value && typeof value == "object"; + } + function isSymbol(value) { + return typeof value == "symbol" || isObjectLike(value) && objectToString.call(value) == symbolTag; + } + function toNumber(value) { + if (typeof value == "number") { + return value; + } + if (isSymbol(value)) { + return NAN; + } + if (isObject(value)) { + var other = typeof value.valueOf == "function" ? value.valueOf() : value; + value = isObject(other) ? other + "" : other; + } + if (typeof value != "string") { + return value === 0 ? value : +value; + } + value = value.replace(reTrim, ""); + var isBinary = reIsBinary.test(value); + return isBinary || reIsOctal.test(value) ? freeParseInt(value.slice(2), isBinary ? 2 : 8) : reIsBadHex.test(value) ? NAN : +value; + } + module2.exports = debounce2; + } +}); + +// src/main.ts +var main_exports = {}; +__export(main_exports, { + default: () => D2Plugin +}); +module.exports = __toCommonJS(main_exports); +var import_obsidian3 = require("obsidian"); + +// src/settings.ts +var import_obsidian = require("obsidian"); + +// src/constants.ts +var LAYOUT_ENGINES = { + DAGRE: { + value: "dagre", + label: "dagre" + }, + ELK: { + value: "elk", + label: "ELK" + }, + TALA: { + value: "tala", + label: "TALA" + } +}; +var RecompileIcon = ` + + + + + +`; + +// src/settings.ts +var DEFAULT_SETTINGS = { + layoutEngine: "dagre", + debounce: 500, + theme: 0, + apiToken: "", + d2Path: "", + pad: 100, + sketch: false, + containerHeight: 800 +}; +var D2SettingsTab = class extends import_obsidian.PluginSettingTab { + constructor(app, plugin) { + super(app, plugin); + this.plugin = plugin; + } + addTALASettings() { + const talaSettings = this.containerEl.createEl("div"); + talaSettings.createEl("h3", { + text: "TALA settings" + }); + new import_obsidian.Setting(talaSettings).setName("API token").setDesc('To use TALA, copy your API token here or in ~/.local/state/tstruct/auth.json under the field "api_token"').addText((text) => text.setPlaceholder("tstruct_...").setValue(this.plugin.settings.apiToken).setDisabled(this.plugin.settings.layoutEngine !== LAYOUT_ENGINES.TALA.value).onChange(async (value) => { + if (value && !value.startsWith("tstruct_")) { + new import_obsidian.Notice("Invalid API token"); + } else { + this.plugin.settings.apiToken = value; + await this.plugin.saveSettings(); + } + })); + this.talaSettings = talaSettings; + } + display() { + const { containerEl } = this; + containerEl.empty(); + containerEl.createEl("h1", { text: "D2 plugin settings" }); + new import_obsidian.Setting(containerEl).setName("Layout engine").setDesc('Available layout engines include "dagre", "ELK", and "TALA" (TALA must be installed separately from D2)').addDropdown((dropdown) => { + dropdown.addOption(LAYOUT_ENGINES.DAGRE.value, LAYOUT_ENGINES.DAGRE.label).addOption(LAYOUT_ENGINES.ELK.value, LAYOUT_ENGINES.ELK.label).addOption(LAYOUT_ENGINES.TALA.value, LAYOUT_ENGINES.TALA.label).setValue(this.plugin.settings.layoutEngine).onChange(async (value) => { + var _a; + this.plugin.settings.layoutEngine = value; + await this.plugin.saveSettings(); + if (value === LAYOUT_ENGINES.TALA.value) { + this.addTALASettings(); + } else { + (_a = this.talaSettings) == null ? void 0 : _a.remove(); + } + }); + }); + new import_obsidian.Setting(containerEl).setName("Theme ID").setDesc("Available themes are located at https://github.com/terrastruct/d2/tree/master/d2themes").addText((text) => text.setPlaceholder("Enter a theme ID").setValue(String(this.plugin.settings.theme)).onChange(async (value) => { + if (!isNaN(Number(value)) || value === "") { + this.plugin.settings.theme = Number(value || DEFAULT_SETTINGS.theme); + await this.plugin.saveSettings(); + } else { + new import_obsidian.Notice("Please specify a valid number"); + } + })); + new import_obsidian.Setting(containerEl).setName("Pad").setDesc("Pixels padded around the rendered diagram").addText((text) => text.setPlaceholder(String(DEFAULT_SETTINGS.pad)).setValue(String(this.plugin.settings.pad)).onChange(async (value) => { + if (isNaN(Number(value))) { + new import_obsidian.Notice("Please specify a valid number"); + this.plugin.settings.pad = Number(DEFAULT_SETTINGS.pad); + } else if (value === "") { + this.plugin.settings.pad = Number(DEFAULT_SETTINGS.pad); + } else { + this.plugin.settings.pad = Number(value); + } + await this.plugin.saveSettings(); + })); + new import_obsidian.Setting(containerEl).setName("Sketch mode").setDesc("Render the diagram to look like it was sketched by hand").addToggle((toggle) => toggle.setValue(this.plugin.settings.sketch).onChange(async (value) => { + this.plugin.settings.sketch = value; + await this.plugin.saveSettings(); + })); + new import_obsidian.Setting(containerEl).setName("Container height").setDesc("Diagram max render height in pixels (Requires d2 v0.2.2 and up)").addText((text) => text.setPlaceholder(String(DEFAULT_SETTINGS.containerHeight)).setValue(String(this.plugin.settings.containerHeight)).onChange(async (value) => { + if (isNaN(Number(value))) { + new import_obsidian.Notice("Please specify a valid number"); + this.plugin.settings.containerHeight = Number(DEFAULT_SETTINGS.containerHeight); + } else if (value === "") { + this.plugin.settings.containerHeight = Number(DEFAULT_SETTINGS.containerHeight); + } else { + this.plugin.settings.containerHeight = Number(value); + } + await this.plugin.saveSettings(); + })); + new import_obsidian.Setting(containerEl).setName("Debounce").setDesc("How often should the diagram refresh in milliseconds (min 100)").addText((text) => text.setPlaceholder(String(DEFAULT_SETTINGS.debounce)).setValue(String(this.plugin.settings.debounce)).onChange(async (value) => { + if (isNaN(Number(value))) { + new import_obsidian.Notice("Please specify a valid number"); + this.plugin.settings.debounce = Number(DEFAULT_SETTINGS.debounce); + } else if (value === "") { + this.plugin.settings.debounce = Number(DEFAULT_SETTINGS.debounce); + } else if (Number(value) < 100) { + new import_obsidian.Notice("The value must be greater than 100"); + this.plugin.settings.debounce = Number(DEFAULT_SETTINGS.debounce); + } else { + this.plugin.settings.debounce = Number(value); + } + await this.plugin.saveSettings(); + })); + new import_obsidian.Setting(containerEl).setName("Path (optional)").setDesc("Customize the local path to the directory `d2` is installed in (ex. if d2 is located at `/usr/local/bin/d2`, then the path is `/usr/local/bin`). This is only necessary if `d2` is not found automatically by the plugin (but is installed).").addText((text) => { + text.setPlaceholder("/usr/local/Cellar").setValue(this.plugin.settings.d2Path).onChange(async (value) => { + this.plugin.settings.d2Path = value; + await this.plugin.saveSettings(); + }); + }); + if (this.plugin.settings.layoutEngine === LAYOUT_ENGINES.TALA.value) { + this.addTALASettings(); + } + } +}; + +// src/processor.ts +var import_obsidian2 = require("obsidian"); +var import_child_process = require("child_process"); +var import_path = require("path"); +var import_lodash = __toESM(require_lodash()); +var import_os = __toESM(require("os")); +var D2Processor = class { + constructor(plugin) { + this.attemptExport = async (source, el, ctx) => { + var _a; + el.createEl("h6", { + text: "Generating D2 diagram...", + cls: "D2__Loading" + }); + const pageContainer = ctx.containerEl; + let pageID = pageContainer.dataset.pageID; + if (!pageID) { + pageID = Math.floor(Math.random() * Date.now()).toString(); + pageContainer.dataset.pageID = pageID; + } + let debouncedFunc = this.debouncedMap.get(pageID); + if (!debouncedFunc) { + await this.export(source, el, ctx); + debouncedFunc = (0, import_lodash.default)(this.export, this.plugin.settings.debounce, { + leading: true + }); + this.debouncedMap.set(pageID, debouncedFunc); + return; + } + (_a = this.abortControllerMap.get(pageID)) == null ? void 0 : _a.abort(); + const newAbortController = new AbortController(); + this.abortControllerMap.set(pageID, newAbortController); + await debouncedFunc(source, el, ctx, newAbortController.signal); + }; + this.isValidUrl = (urlString) => { + let url; + try { + url = new URL(urlString); + } catch (e) { + return false; + } + return url.protocol === "http:" || url.protocol === "https:"; + }; + this.formatLinks = (svgEl) => { + const links = svgEl.querySelectorAll("a"); + links.forEach((link) => { + var _a; + const href = (_a = link.getAttribute("href")) != null ? _a : ""; + if (!this.isValidUrl(href)) { + link.classList.add("internal-link"); + link.setAttribute("data-href", href); + link.setAttribute("target", "_blank"); + link.setAttribute("rel", "noopener"); + } + }); + }; + this.sanitizeSVGIDs = (svgEl, docID) => { + const overrides = svgEl.querySelectorAll("marker, mask, filter"); + const overrideIDs = []; + overrides.forEach((override) => { + const id = override.getAttribute("id"); + if (id) { + overrideIDs.push(id); + } + }); + return overrideIDs.reduce((svgHTML, overrideID) => { + return svgHTML.replaceAll(overrideID, [overrideID, docID].join("-")); + }, svgEl.outerHTML); + }; + this.export = async (source, el, ctx, signal) => { + try { + const image = await this.generatePreview(source, signal); + if (image) { + el.empty(); + this.prevImage = image; + this.insertImage(image, el, ctx); + const button = new import_obsidian2.ButtonComponent(el).setClass("Preview__Recompile").setIcon("recompile").onClick((e) => { + e.preventDefault(); + e.stopPropagation(); + el.empty(); + this.attemptExport(source, el, ctx); + }); + button.buttonEl.createEl("span", { + text: "Recompile" + }); + } + } catch (err) { + el.empty(); + const errorEl = el.createEl("pre", { + cls: "markdown-rendered pre Preview__Error" + }); + errorEl.createEl("code", { + text: "D2 Compilation Error:", + cls: "Preview__Error--Title" + }); + errorEl.createEl("code", { + text: err.message + }); + if (this.prevImage) { + this.insertImage(this.prevImage, el, ctx); + } + } finally { + const pageContainer = ctx.containerEl; + this.abortControllerMap.delete(pageContainer.dataset.id); + } + }; + this.plugin = plugin; + this.debouncedMap = /* @__PURE__ */ new Map(); + this.abortControllerMap = /* @__PURE__ */ new Map(); + } + insertImage(image, el, ctx) { + const parser = new DOMParser(); + const svg = parser.parseFromString(image, "image/svg+xml"); + const containerEl = el.createDiv(); + const svgEl = svg.documentElement; + svgEl.style.maxHeight = `${this.plugin.settings.containerHeight}px`; + svgEl.style.maxWidth = "100%"; + svgEl.style.height = "fit-content"; + svgEl.style.width = "fit-content"; + this.formatLinks(svgEl); + containerEl.innerHTML = this.sanitizeSVGIDs(svgEl, ctx.docId); + } + async generatePreview(source, signal) { + var _a, _b; + const pathArray = [process.env.PATH, "/opt/homebrew/bin", "/usr/local/bin"]; + if (import_os.default.platform() === "win32") { + pathArray.push(`C:Program FilesD2`); + } else { + pathArray.push(`${process.env.HOME}/.local/bin`); + } + let GOPATH = ""; + try { + GOPATH = (0, import_child_process.execSync)("go env GOPATH", { + env: { + ...process.env, + PATH: pathArray.join(import_path.delimiter) + } + }).toString(); + } catch (error) { + } + if (GOPATH) { + pathArray.push(`${GOPATH.replace("\n", "")}/bin`); + } + if (this.plugin.settings.d2Path) { + pathArray.push(this.plugin.settings.d2Path); + } + const options = { + ...process.env, + env: { + PATH: pathArray.join(import_path.delimiter) + }, + signal + }; + if (this.plugin.settings.apiToken) { + options.env.TSTRUCT_TOKEN = this.plugin.settings.apiToken; + } + let args = [ + `d2`, + "-", + `--theme=${this.plugin.settings.theme}`, + `--layout=${this.plugin.settings.layoutEngine}`, + `--pad=${this.plugin.settings.pad}`, + `--sketch=${this.plugin.settings.sketch}`, + "--bundle=false", + "--scale=1" + ]; + const cmd = args.join(" "); + const child = (0, import_child_process.exec)(cmd, options); + (_a = child.stdin) == null ? void 0 : _a.write(source); + (_b = child.stdin) == null ? void 0 : _b.end(); + let stdout; + let stderr; + if (child.stdout) { + child.stdout.on("data", (data) => { + if (stdout === void 0) { + stdout = data; + } else { + stdout += data; + } + }); + } + if (child.stderr) { + child.stderr.on("data", (data) => { + if (stderr === void 0) { + stderr = data; + } else { + stderr += data; + } + }); + } + return new Promise((resolve, reject) => { + child.on("error", reject); + child.on("close", (code) => { + if (code === 0) { + resolve(stdout); + return; + } else if (stderr) { + console.error(stderr); + reject(new Error(stderr)); + } else if (stdout) { + console.error(stdout); + reject(new Error(stdout)); + } + }); + }); + } +}; + +// src/main.ts +var D2Plugin = class extends import_obsidian3.Plugin { + async onload() { + (0, import_obsidian3.addIcon)("recompile", RecompileIcon); + await this.loadSettings(); + this.addSettingTab(new D2SettingsTab(this.app, this)); + const processor = new D2Processor(this); + this.registerMarkdownCodeBlockProcessor("d2", processor.attemptExport); + this.processor = processor; + } + onunload() { + const abortControllers = this.processor.abortControllerMap.values(); + Array.from(abortControllers).forEach((controller) => { + controller.abort(); + }); + } + async loadSettings() { + this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); + } + async saveSettings() { + await this.saveData(this.settings); + } +}; diff --git a/.obsidian/plugins/d2-obsidian/manifest.json b/.obsidian/plugins/d2-obsidian/manifest.json new file mode 100644 index 0000000..4bffa8b --- /dev/null +++ b/.obsidian/plugins/d2-obsidian/manifest.json @@ -0,0 +1,10 @@ +{ + "id": "d2-obsidian", + "name": "D2", + "version": "1.1.4", + "minAppVersion": "0.15.0", + "description": "The official D2 plugin for Obsidian. D2 is a modern diagram scripting language that turns text to diagrams.", + "author": "Terrastruct", + "authorUrl": "https://d2lang.com", + "isDesktopOnly": true +} diff --git a/.obsidian/plugins/d2-obsidian/styles.css b/.obsidian/plugins/d2-obsidian/styles.css new file mode 100644 index 0000000..46e334a --- /dev/null +++ b/.obsidian/plugins/d2-obsidian/styles.css @@ -0,0 +1,49 @@ +.D2__Loading { + font-style: italic; +} + +.Preview__Error--Title { + color: #be0b41 !important; +} + +.Preview__Error { + display: flex; + flex-direction: column; + white-space: pre; + white-space: pre-wrap; +} + +.Preview__Recompile { + position: absolute; + top: 4px; + left: 4px; + height: 24px; + padding: 6px; + background-color: white !important; + color: #2e3346; + display: flex; + gap: 4px; + box-shadow: none !important; + border: 1px solid #dee1eb; + filter: drop-shadow(1px 1px 4px rgba(31, 36, 58, 0.08)); +} + +.Preview__Recompile:hover { + filter: drop-shadow(2px 2px 16px rgba(31, 36, 58, 0.12)); + cursor: pointer; +} + +.Preview__Recompile > .svg-icon { + height: 12px !important; + width: 12px !important; +} + +.block-language-d2 { + position: relative; +} + +@media print { + .Preview__Recompile { + display: none; + } +}