notes/.obsidian/plugins/d2-obsidian/main.js
2024-08-31 11:58:50 -06:00

534 lines
21 KiB
JavaScript

/*
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 = `
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.33325 83.3334V58.3334H33.3333" stroke="#2E3346" stroke-width="8.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M91.6667 16.6666V41.6666H66.6667" stroke="#2E3346" stroke-width="8.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14.6249 37.5004C16.7381 31.5287 20.3296 26.1896 25.0644 21.9813C29.7991 17.773 35.5227 14.8328 41.7011 13.4348C47.8795 12.0369 54.3114 12.2268 60.3965 13.987C66.4817 15.7471 72.0218 19.02 76.4999 23.5004C80.978 27.9808 91.6666 41.667 91.6666 41.667M8.33325 58.3337C8.33325 58.3337 19.0218 72.02 23.4999 76.5004C27.978 80.9808 33.5181 84.2537 39.6033 86.0138C45.6884 87.774 52.1203 87.9639 58.2987 86.566C64.4771 85.168 70.2007 82.2277 74.9355 78.0195C79.6702 73.8112 83.2617 68.4721 85.3749 62.5004" stroke="#2E3346" stroke-width="8.33333" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
`;
// 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);
}
};