diff --git a/.obsidian/plugins/obsidian-desmos/.gitattributes b/.obsidian/plugins/obsidian-desmos/.gitattributes new file mode 100644 index 0000000..7334baf --- /dev/null +++ b/.obsidian/plugins/obsidian-desmos/.gitattributes @@ -0,0 +1 @@ +/main.js linguist-generated \ No newline at end of file diff --git a/.obsidian/plugins/obsidian-desmos/.gitignore b/.obsidian/plugins/obsidian-desmos/.gitignore new file mode 100644 index 0000000..cc8c870 --- /dev/null +++ b/.obsidian/plugins/obsidian-desmos/.gitignore @@ -0,0 +1,2 @@ +/data.json +/node_modules/ \ No newline at end of file diff --git a/.obsidian/plugins/obsidian-desmos/.prettierrc b/.obsidian/plugins/obsidian-desmos/.prettierrc new file mode 100644 index 0000000..fd5f378 --- /dev/null +++ b/.obsidian/plugins/obsidian-desmos/.prettierrc @@ -0,0 +1,3 @@ +{ + "tabWidth": 4 +} \ No newline at end of file diff --git a/.obsidian/plugins/obsidian-desmos/README.md b/.obsidian/plugins/obsidian-desmos/README.md new file mode 100644 index 0000000..1c828b2 --- /dev/null +++ b/.obsidian/plugins/obsidian-desmos/README.md @@ -0,0 +1,95 @@ +# Obsidian Desmos + +Render [Desmos](https://www.desmos.com/calculator) graphs right inside your notes. + +# Installation + +## Using Git + +If your vault is hosted using git then congratulations, you can use the easy method. Run +`git subtree add --prefix .obsidian/plugins/obsidian-desmos https://github.com/Nigecat/obsidian-desmos master --squash` +to add the plugin to the vault in the current working directory of your terminal. +You can then run +`git subtree pull --prefix .obsidian/plugins/obsidian-desmos https://github.com/Nigecat/obsidian-desmos master --squash` +to update the plugin to the latest version (from the same working directory). + +## Using anything else + +Alternatively, if you do not use git or are not comfortable using the terminal, you can manually install the plugin. Download [manifest.json](manifest.json), [versions.json](versions.json), and [main.js](main.js) and place them in `/.obsidian/plugins/obsidian-desmos` (you may have to create any missing folders). +This process must be repeated to update the application. + +# Usage + +The most basic usage of this plugin involves creating a codeblock with the tag `desmos-graph` and placing the equations you wish to graph in the body: + +```` + ```desmos-graph + y=x + ``` +```` + +Equations use the [LaTeX math](https://en.wikibooks.org/wiki/LaTeX/Mathematics) format and you can graph multiple equations by placing each one on a seperate line: + +```` + ```desmos-graph + y=\sin(x) + y=\frac{1}{x} + ``` +```` + +You can restrict the bounds of the graph and apply other settings by placing a `---` seperator before your equations. The content before it must be a set of `key=value` pairs seperated by either **newlines or semicolons** (or both): + +```` + ```desmos-graph + boundary_left=0; boundary_right=100; + boundary_top=10; boundary_bottom=-10; + --- + y=\sin(x) + ``` +```` + +You can set the dimensions of the rendered image by using the `height` and `width` fields. + +#### Restrictions + +Note that graph restrictions follow the same format as desmos itself (except we use a `|` to denote the beginning of the restrictions): + +```` + ```desmos-graph + y=\sin(x)|{y > 0} + ``` +```` + +### Style + +We support six different types of (case-insensitive) styles: +Line: `SOLID` `DASHED` `DOTTED` +Point: `POINT` `OPEN` `CROSS` + +These are placed after the graph restrictions, following another `|`: + +```` + ```desmos-graph + y=\sin(x)|{y > 0}|DASHED + ``` +```` + +If you do not wish to apply any restrictions, the center field can be left blank: + +```` + ```desmos-graph + y=\sin(x)||DASHED + (1,2)||OPEN + ``` +```` + +## Important + +Note that to be able to render these graphs into a PDF the following conditions must be fulfilled + +1. Memory caching **must** be enabled +2. Obsidian **must** have been restarted since you initially created the graph +3. You **must** have viewed the rendered graph in the preview since the restart + +After these are complete, a standard PDF export should work fine. +In the future these steps will be removed and you will be able to directly export them. diff --git a/.obsidian/plugins/obsidian-desmos/main.js b/.obsidian/plugins/obsidian-desmos/main.js new file mode 100644 index 0000000..a7bc73d --- /dev/null +++ b/.obsidian/plugins/obsidian-desmos/main.js @@ -0,0 +1,406 @@ +'use strict'; + +var crypto = require('crypto'); +var path = require('path'); +var os = require('os'); +var obsidian = require('obsidian'); +var fs = require('fs'); + +function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } + +var path__default = /*#__PURE__*/_interopDefaultLegacy(path); + +/*! ***************************************************************************** +Copyright (c) Microsoft Corporation. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. +***************************************************************************** */ + +function __awaiter(thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +} + +const FIELD_DEFAULTS = { + width: 600, + height: 400, + boundary_left: -10, + boundary_right: 10, + boundary_bottom: -7, + boundary_top: 7, +}; +class Dsl { + constructor(equations, fields) { + this.equations = equations; + this.fields = Object.assign(Object.assign({}, FIELD_DEFAULTS), fields); + Dsl.assert_sanity(this.fields); + this.hash = crypto.createHash("sha256") + .update(JSON.stringify(this)) + .digest("hex"); + } + /** Check if the fields are sane, throws a `SyntaxError` if they aren't */ + static assert_sanity(fields) { + // Ensure boundaries are complete and in order + if (fields.boundary_left >= fields.boundary_right) { + throw new SyntaxError(`Right boundary (${fields.boundary_right}) must be greater than left boundary (${fields.boundary_left})`); + } + if (fields.boundary_bottom >= fields.boundary_top) { + throw new SyntaxError(` + Top boundary (${fields.boundary_top}) must be greater than bottom boundary (${fields.boundary_bottom}) + `); + } + } + static parse(source) { + const split = source.split("---"); + let equations; + let fields; + switch (split.length) { + case 0: { + equations = []; + break; + } + case 1: { + equations = split[0].split("\n").filter(Boolean); + break; + } + case 2: { + // If there are two segments then we know the first one must contain the settings + fields = split[0] + // Allow either a newline or semicolon as a delimiter + .split(/[;\n]+/) + .map((setting) => setting.trim()) + // Remove any empty elements + .filter(Boolean) + // Split each field on the first equals sign to create the key=value pair + .map((setting) => { + const [key, ...value] = setting.split("="); + return [key, value.join("=")]; + }) + .reduce((settings, [key, value]) => { + if (FIELD_DEFAULTS.hasOwnProperty(key)) { + if (!value) { + throw new SyntaxError(`Field '${key}' must have a value`); + } + // We can use the defaults to determine the type of each field + const field_v = FIELD_DEFAULTS[key]; + const field_t = typeof field_v; + switch (field_t) { + case "number": { + const s = parseInt(value); + if (Number.isNaN(s)) { + throw new SyntaxError(`Field '${key}' must have an integer value`); + } + settings[key] = s; + break; + } + case "string": { + settings[key] = value; + break; + } + case "object": { + const val = JSON.parse(value); + if (val.constructor === field_v.constructor) { + settings[key] = val; + } + break; + } + } + } + else { + throw new SyntaxError(`Unrecognised field: ${key}`); + } + return settings; + }, {}); + equations = split[1].split("\n").filter(Boolean); + break; + } + default: { + fields = {}; + } + } + if (!equations) { + throw new SyntaxError("Too many segments"); + } + return new Dsl(equations, fields); + } +} + +function renderError(err, el) { + el.innerHTML = ` +
+ Desmos Graph Error: ${err} +
`; +} + +class Renderer { + static render(args, settings, el, plugin) { + const { fields, equations, hash } = args; + // Calculate cache info for filesystem caching + const vault_root = plugin.app.vault.adapter.basePath; + const cache_dir = settings.cache_directory + ? path__default['default'].isAbsolute(settings.cache_directory) + ? settings.cache_directory + : path__default['default'].join(vault_root, settings.cache_directory) + : os.tmpdir(); + const cache_target = path__default['default'].join(cache_dir, `desmos-graph-${hash}.png`); + // If this graph is in the cache then fetch it + if (settings.cache) { + if (settings.cache_location == "memory" && + hash in plugin.graph_cache) { + const data = plugin.graph_cache[hash]; + const img = document.createElement("img"); + img.src = data; + el.appendChild(img); + return; + } + else if (settings.cache_location == "filesystem" && + fs.existsSync(cache_target)) { + fs.promises.readFile(cache_target).then((data) => { + const b64 = "data:image/png;base64," + + Buffer.from(data).toString("base64"); + const img = document.createElement("img"); + img.src = b64; + el.appendChild(img); + }); + return; + } + } + const expressions = equations.map((equation) => { + var _a; + return `calculator.setExpression({ + latex: "${equation.split("|")[0].replace("\\", "\\\\")}${((_a = equation.split("|")[1]) !== null && _a !== void 0 ? _a : "") + .replace("{", "\\\\{") + .replace("}", "\\\\}") + .replace("<=", "\\\\leq ") + .replace(">=", "\\\\geq ") + .replace("<", "\\\\le ") + .replace(">", "\\\\ge ")}", + + ${(() => { + const mode = equation.split("|")[2]; + if (mode) { + if (["solid", "dashed", "dotted"].contains(mode.toLowerCase())) { + return `lineStyle: Desmos.Styles.${mode.toUpperCase()}`; + } + else if (["point", "open", "cross"].contains(mode.toLowerCase())) { + return `pointStyle: Desmos.Styles.${mode.toUpperCase()}`; + } + } + return ""; + })()} + });`; + }); + // Because of the electron sandboxing we have to do this inside an iframe, + // otherwise we can't include the desmos API (although it would be nice if they had a REST API of some sort) + const html_src_head = ``; + const html_src_body = ` +
+ + `; + const html_src = `${html_src_head}${html_src_body}`; + const iframe = document.createElement("iframe"); + iframe.width = fields.width.toString(); + iframe.height = fields.height.toString(); + iframe.style.border = "none"; + iframe.scrolling = "no"; // fixme use a non-depreciated function + iframe.srcdoc = html_src; + // iframe.style.display = "none"; //fixme hiding the iframe breaks the positioning + el.appendChild(iframe); + const handler = (message) => { + if (message.origin === "app://obsidian.md" && + message.data.t === "desmos-graph" && + message.data.hash === hash) { + el.empty(); + if (message.data.d === "error") { + renderError(message.data.data, el); + } + if (message.data.d === "render") { + const { data } = message.data; + window.removeEventListener("message", handler); + const img = document.createElement("img"); + img.src = data; + el.appendChild(img); + if (settings.cache) { + if (settings.cache_location == "memory") { + plugin.graph_cache[hash] = data; + } + else if (settings.cache_location == "filesystem") { + if (fs.existsSync(cache_dir)) { + fs.promises.writeFile(cache_target, data.replace(/^data:image\/png;base64,/, ""), "base64").catch((err) => new obsidian.Notice(`desmos-graph: unexpected error when trying to cache graph: ${err}`, 10000)); + } + else { + new obsidian.Notice(`desmos-graph: cache directory not found: '${cache_dir}'`, 10000); + } + } + } + } + } + }; + window.addEventListener("message", handler); + } +} + +const DEFAULT_SETTINGS = { + debounce: 500, + cache: true, + cache_location: "memory", + cache_directory: null, +}; +class SettingsTab extends obsidian.PluginSettingTab { + constructor(app, plugin) { + super(app, plugin); + this.plugin = plugin; + } + display() { + let { containerEl } = this; + containerEl.empty(); + new obsidian.Setting(containerEl) + .setName("Debounce Time (ms)") + .setDesc("How long to wait after a keypress to render the graph (requires restart to take effect)") + .addText((text) => text + .setValue(this.plugin.settings.debounce.toString()) + .onChange((value) => __awaiter(this, void 0, void 0, function* () { + const val = parseInt(value); + this.plugin.settings.debounce = + val === NaN ? DEFAULT_SETTINGS.debounce : val; + yield this.plugin.saveSettings(); + }))); + new obsidian.Setting(containerEl) + .setName("Cache") + .setDesc("Whether to cache the rendered graphs") + .addToggle((toggle) => toggle + .setValue(this.plugin.settings.cache) + .onChange((value) => __awaiter(this, void 0, void 0, function* () { + this.plugin.settings.cache = value; + yield this.plugin.saveSettings(); + // Reset the display so the new state can render + this.display(); + }))); + if (this.plugin.settings.cache) { + new obsidian.Setting(containerEl) + .setName("Cache in memory (alternate: filesystem)") + .setDesc("Cache rendered graphs in memory or on the filesystem (note that memory caching is not persistent).") + .addToggle((toggle) => toggle + .setValue(this.plugin.settings.cache_location === "memory" + ? true + : false) + .onChange((value) => __awaiter(this, void 0, void 0, function* () { + this.plugin.settings.cache_location = value + ? "memory" + : "filesystem"; + yield this.plugin.saveSettings(); + // Reset the display so the new state can render + this.display(); + }))); + if (this.plugin.settings.cache_location == "filesystem") { + new obsidian.Setting(containerEl) + .setName("Cache Directory") + .setDesc("The directory to save cached graphs in (technical note: the graphs will be saved as `desmos-graph-.png` where the name is a SHA-256 hash of the graph source). The default directory is the system tempdir for your current operating system, and this value may be either a path relative to the root of your vault or an absolute path. Also note that a lot of junk will be saved to this folder, you have been warned.") + .addText((text) => text + .setPlaceholder(os.tmpdir()) + .setValue(this.plugin.settings.cache_directory) + .onChange((value) => __awaiter(this, void 0, void 0, function* () { + this.plugin.settings.cache_directory = value; + yield this.plugin.saveSettings(); + }))); + } + } + } +} + +class Desmos extends obsidian.Plugin { + onload() { + return __awaiter(this, void 0, void 0, function* () { + this.graph_cache = {}; + yield this.loadSettings(); + this.addSettingTab(new SettingsTab(this.app, this)); + // Keep track of the total number of graphs in each file + // This allows us to skip the debounce on recently opened files to make it feel snappier to use + let total = 0; + this.app.workspace.on("file-open", (file) => __awaiter(this, void 0, void 0, function* () { + const contents = yield this.app.vault.cachedRead(file); + // Attempt to figure out the number of graphs there are in this file + // In this case it is fine if we overestimate because we only need a general idea since this just makes it skip the debounce + total = (contents.match(/```desmos-graph/g) || []).length; + })); + const render = (source, el) => { + try { + Renderer.render(Dsl.parse(source), this.settings, el, this); + } + catch (err) { + renderError(err.message, el); + } + }; + const debounce_render = obsidian.debounce((source, el) => render(source, el), this.settings.debounce); + this.registerMarkdownCodeBlockProcessor("desmos-graph", (source, el) => { + if (total > 0) { + total--; + // Skip the debounce on initial render + render(source, el); + } + else { + debounce_render(source, el); + } + }); + }); + } + loadSettings() { + return __awaiter(this, void 0, void 0, function* () { + this.settings = Object.assign({}, DEFAULT_SETTINGS, yield this.loadData()); + }); + } + saveSettings() { + return __awaiter(this, void 0, void 0, function* () { + yield this.saveData(this.settings); + }); + } +} + +module.exports = Desmos; +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFpbi5qcyIsInNvdXJjZXMiOlsibm9kZV9tb2R1bGVzL3RzbGliL3RzbGliLmVzNi5qcyIsInNyYy9kc2wudHMiLCJzcmMvZXJyb3IudHMiLCJzcmMvcmVuZGVyZXIudHMiLCJzcmMvc2V0dGluZ3MudHMiLCJzcmMvbWFpbi50cyJdLCJzb3VyY2VzQ29udGVudCI6bnVsbCwibmFtZXMiOlsiY3JlYXRlSGFzaCIsInBhdGgiLCJ0bXBkaXIiLCJleGlzdHNTeW5jIiwiZnMiLCJOb3RpY2UiLCJQbHVnaW5TZXR0aW5nVGFiIiwiU2V0dGluZyIsIlBsdWdpbiIsImRlYm91bmNlIl0sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7QUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBdURBO0FBQ08sU0FBUyxTQUFTLENBQUMsT0FBTyxFQUFFLFVBQVUsRUFBRSxDQUFDLEVBQUUsU0FBUyxFQUFFO0FBQzdELElBQUksU0FBUyxLQUFLLENBQUMsS0FBSyxFQUFFLEVBQUUsT0FBTyxLQUFLLFlBQVksQ0FBQyxHQUFHLEtBQUssR0FBRyxJQUFJLENBQUMsQ0FBQyxVQUFVLE9BQU8sRUFBRSxFQUFFLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxFQUFFO0FBQ2hILElBQUksT0FBTyxLQUFLLENBQUMsS0FBSyxDQUFDLEdBQUcsT0FBTyxDQUFDLEVBQUUsVUFBVSxPQUFPLEVBQUUsTUFBTSxFQUFFO0FBQy9ELFFBQVEsU0FBUyxTQUFTLENBQUMsS0FBSyxFQUFFLEVBQUUsSUFBSSxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLE9BQU8sQ0FBQyxFQUFFLEVBQUUsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRTtBQUNuRyxRQUFRLFNBQVMsUUFBUSxDQUFDLEtBQUssRUFBRSxFQUFFLElBQUks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 diff --git a/.obsidian/plugins/obsidian-desmos/manifest.json b/.obsidian/plugins/obsidian-desmos/manifest.json new file mode 100644 index 0000000..56dd89b --- /dev/null +++ b/.obsidian/plugins/obsidian-desmos/manifest.json @@ -0,0 +1,9 @@ +{ + "id": "obsidian-desmos", + "name": "Desmos", + "version": "0.0.1", + "minAppVersion": "0.9.12", + "description": "Embed Desmos graphs into your notes", + "author": "Nigecat", + "isDesktopOnly": true +} \ No newline at end of file diff --git a/.obsidian/plugins/obsidian-desmos/package-lock.json b/.obsidian/plugins/obsidian-desmos/package-lock.json new file mode 100644 index 0000000..5460440 --- /dev/null +++ b/.obsidian/plugins/obsidian-desmos/package-lock.json @@ -0,0 +1,343 @@ +{ + "name": "obsidian-desmos", + "version": "0.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@rollup/plugin-commonjs": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-19.0.0.tgz", + "integrity": "sha512-adTpD6ATGbehdaQoZQ6ipDFhdjqsTgpOAhFiPwl+dzre4pPshsecptDPyEFb61JMJ1+mGljktaC4jI8ARMSNyw==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^3.1.0", + "commondir": "^1.0.1", + "estree-walker": "^2.0.1", + "glob": "^7.1.6", + "is-reference": "^1.2.1", + "magic-string": "^0.25.7", + "resolve": "^1.17.0" + } + }, + "@rollup/plugin-node-resolve": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.0.0.tgz", + "integrity": "sha512-41X411HJ3oikIDivT5OKe9EZ6ud6DXudtfNrGbC4nniaxx2esiWjkLOzgnZsWq1IM8YIeL2rzRGLZLBjlhnZtQ==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^3.1.0", + "@types/resolve": "1.17.1", + "builtin-modules": "^3.1.0", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.19.0" + } + }, + "@rollup/plugin-typescript": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-8.2.1.tgz", + "integrity": "sha512-Qd2E1pleDR4bwyFxqbjt4eJf+wB0UKVMLc7/BAFDGVdAXQMCsD4DUv5/7/ww47BZCYxWtJqe1Lo0KVNswBJlRw==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^3.1.0", + "resolve": "^1.17.0" + } + }, + "@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "dev": true, + "requires": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + }, + "dependencies": { + "estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "dev": true + } + } + }, + "@types/codemirror": { + "version": "0.0.108", + "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-0.0.108.tgz", + "integrity": "sha512-3FGFcus0P7C2UOGCNUVENqObEb4SFk+S8Dnxq7K6aIsLVs/vDtlangl3PEO0ykaKXyK56swVF6Nho7VsA44uhw==", + "dev": true, + "requires": { + "@types/tern": "*" + } + }, + "@types/debounce": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/debounce/-/debounce-1.2.0.tgz", + "integrity": "sha512-bWG5wapaWgbss9E238T0R6bfo5Fh3OkeoSt245CM7JJwVwpw6MEBCbIxLq5z8KzsE3uJhzcIuQkyiZmzV3M/Dw==", + "dev": true + }, + "@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "dev": true + }, + "@types/node": { + "version": "15.12.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.2.tgz", + "integrity": "sha512-zjQ69G564OCIWIOHSXyQEEDpdpGl+G348RAKY0XXy9Z5kU9Vzv1GMNnkar/ZJ8dzXB3COzD9Mo9NtRZ4xfgUww==", + "dev": true + }, + "@types/resolve": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", + "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/tern": { + "version": "0.23.3", + "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.3.tgz", + "integrity": "sha512-imDtS4TAoTcXk0g7u4kkWqedB3E4qpjXzCpD2LU5M5NAXHzCDsypyvXSaG7mM8DKYkCRa7tFp4tS/lp/Wo7Q3w==", + "dev": true, + "requires": { + "@types/estree": "*" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "builtin-modules": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz", + "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true + }, + "estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-core-module": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz", + "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", + "dev": true + }, + "is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "dev": true, + "requires": { + "@types/estree": "*" + } + }, + "magic-string": { + "version": "0.25.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", + "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", + "dev": true, + "requires": { + "sourcemap-codec": "^1.4.4" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "moment": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", + "dev": true + }, + "obsidian": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/obsidian/-/obsidian-0.12.5.tgz", + "integrity": "sha512-dQkcHWVgPzVlCxvkeu02Co8P1t4Ii95dC2NsFJV5bKK04J4//YxEdTTI/TFKr77CtYcH78LlUmOFeY5rHwyU/Q==", + "dev": true, + "requires": { + "@types/codemirror": "0.0.108", + "moment": "2.29.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true + }, + "resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + }, + "rollup": { + "version": "2.51.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.51.2.tgz", + "integrity": "sha512-ReV2eGEadA7hmXSzjxdDKs10neqH2QURf2RxJ6ayAlq93ugy6qIvXMmbc5cWMGCDh1h5T4thuWO1e2VNbMq8FA==", + "dev": true, + "requires": { + "fsevents": "~2.3.1" + } + }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "dev": true + }, + "tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "dev": true + }, + "typescript": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.2.tgz", + "integrity": "sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + } + } +} diff --git a/.obsidian/plugins/obsidian-desmos/package.json b/.obsidian/plugins/obsidian-desmos/package.json new file mode 100644 index 0000000..f9e2c34 --- /dev/null +++ b/.obsidian/plugins/obsidian-desmos/package.json @@ -0,0 +1,22 @@ +{ + "name": "obsidian-desmos", + "version": "0.0.1", + "description": "Embed Desmos graphs into your notes", + "main": "main.js", + "author": "Nigecat", + "scripts": { + "build": "rollup --config rollup.config.js --environment BUILD:production", + "build:dev": "rollup --config rollup.config.js -w" + }, + "devDependencies": { + "@rollup/plugin-commonjs": "^19.0.0", + "@rollup/plugin-node-resolve": "^13.0.0", + "@rollup/plugin-typescript": "^8.2.1", + "@types/debounce": "^1.2.0", + "@types/node": "^15.12.2", + "obsidian": "^0.12.5", + "rollup": "^2.51.2", + "tslib": "^2.3.0", + "typescript": "^4.3.2" + } +} diff --git a/.obsidian/plugins/obsidian-desmos/rollup.config.js b/.obsidian/plugins/obsidian-desmos/rollup.config.js new file mode 100644 index 0000000..8e4cf62 --- /dev/null +++ b/.obsidian/plugins/obsidian-desmos/rollup.config.js @@ -0,0 +1,18 @@ +import typescript from "@rollup/plugin-typescript"; +import { nodeResolve } from "@rollup/plugin-node-resolve"; +import commonjs from "@rollup/plugin-commonjs"; + +const isProd = process.env.BUILD === "production"; + +export default { + input: "src/main.ts", + output: { + dir: ".", + sourcemap: "inline", + sourcemapExcludeSources: isProd, + format: "cjs", + exports: "default", + }, + external: ["obsidian"], + plugins: [typescript(), nodeResolve({ browser: true }), commonjs()], +}; diff --git a/.obsidian/plugins/obsidian-desmos/src/dsl.ts b/.obsidian/plugins/obsidian-desmos/src/dsl.ts new file mode 100644 index 0000000..6aaed93 --- /dev/null +++ b/.obsidian/plugins/obsidian-desmos/src/dsl.ts @@ -0,0 +1,141 @@ +import { createHash } from "crypto"; + +export interface Fields { + width: number; + height: number; + boundary_left: number; + boundary_right: number; + boundary_bottom: number; + boundary_top: number; +} + +const FIELD_DEFAULTS: Fields = { + width: 600, + height: 400, + boundary_left: -10, + boundary_right: 10, + boundary_bottom: -7, + boundary_top: 7, +}; + +export class Dsl { + /** A (hex) SHA-256 hash of the fields of this object */ + public readonly hash: string; + public readonly equations: string[]; + public readonly fields: Fields; + + private constructor(equations: string[], fields: Partial) { + this.equations = equations; + this.fields = { ...FIELD_DEFAULTS, ...fields }; + Dsl.assert_sanity(this.fields); + this.hash = createHash("sha256") + .update(JSON.stringify(this)) + .digest("hex"); + } + + /** Check if the fields are sane, throws a `SyntaxError` if they aren't */ + private static assert_sanity(fields: Fields) { + // Ensure boundaries are complete and in order + if (fields.boundary_left >= fields.boundary_right) { + throw new SyntaxError( + `Right boundary (${fields.boundary_right}) must be greater than left boundary (${fields.boundary_left})` + ); + } + + if (fields.boundary_bottom >= fields.boundary_top) { + throw new SyntaxError(` + Top boundary (${fields.boundary_top}) must be greater than bottom boundary (${fields.boundary_bottom}) + `); + } + } + + public static parse(source: string): Dsl { + const split = source.split("---"); + + let equations: string[]; + let fields: Partial; + switch (split.length) { + case 0: { + equations = []; + break; + } + + case 1: { + equations = split[0].split("\n").filter(Boolean); + break; + } + + case 2: { + // If there are two segments then we know the first one must contain the settings + fields = split[0] + // Allow either a newline or semicolon as a delimiter + .split(/[;\n]+/) + .map((setting) => setting.trim()) + // Remove any empty elements + .filter(Boolean) + // Split each field on the first equals sign to create the key=value pair + .map((setting) => { + const [key, ...value] = setting.split("="); + return [key, value.join("=")]; + }) + .reduce((settings, [key, value]) => { + if (FIELD_DEFAULTS.hasOwnProperty(key)) { + if (!value) { + throw new SyntaxError( + `Field '${key}' must have a value` + ); + } + + // We can use the defaults to determine the type of each field + const field_v = (FIELD_DEFAULTS as any)[key]; + const field_t = typeof field_v; + + switch (field_t) { + case "number": { + const s = parseInt(value); + if (Number.isNaN(s)) { + throw new SyntaxError( + `Field '${key}' must have an integer value` + ); + } + (settings as any)[key] = s; + break; + } + + case "string": { + (settings as any)[key] = value; + break; + } + + case "object": { + const val = JSON.parse(value); + if ( + val.constructor === field_v.constructor + ) { + (settings as any)[key] = val; + } + break; + } + } + } else { + throw new SyntaxError(`Unrecognised field: ${key}`); + } + + return settings; + }, {} as Partial); + + equations = split[1].split("\n").filter(Boolean); + break; + } + + default: { + fields = {}; + } + } + if (!equations) { + throw new SyntaxError("Too many segments"); + } + + return new Dsl(equations, fields); + } +} diff --git a/.obsidian/plugins/obsidian-desmos/src/error.ts b/.obsidian/plugins/obsidian-desmos/src/error.ts new file mode 100644 index 0000000..a16235e --- /dev/null +++ b/.obsidian/plugins/obsidian-desmos/src/error.ts @@ -0,0 +1,6 @@ +export function renderError(err: string, el: HTMLElement) { + el.innerHTML = ` +
+ Desmos Graph Error: ${err} +
`; +} diff --git a/.obsidian/plugins/obsidian-desmos/src/main.ts b/.obsidian/plugins/obsidian-desmos/src/main.ts new file mode 100644 index 0000000..f7222ea --- /dev/null +++ b/.obsidian/plugins/obsidian-desmos/src/main.ts @@ -0,0 +1,64 @@ +import { Dsl } from "./dsl"; +import { Renderer } from "./renderer"; +import { renderError } from "./error"; +import { debounce, Plugin } from "obsidian"; +import { Settings, SettingsTab, DEFAULT_SETTINGS } from "./settings"; + +export default class Desmos extends Plugin { + settings: Settings; + /** Helper for in-memory graph caching */ + graph_cache: Record; + + async onload() { + this.graph_cache = {}; + await this.loadSettings(); + this.addSettingTab(new SettingsTab(this.app, this)); + + // Keep track of the total number of graphs in each file + // This allows us to skip the debounce on recently opened files to make it feel snappier to use + let total = 0; + this.app.workspace.on("file-open", async (file) => { + const contents = await this.app.vault.cachedRead(file); + + // Attempt to figure out the number of graphs there are in this file + // In this case it is fine if we overestimate because we only need a general idea since this just makes it skip the debounce + total = (contents.match(/```desmos-graph/g) || []).length; + }); + + const render = (source: string, el: HTMLElement) => { + try { + Renderer.render(Dsl.parse(source), this.settings, el, this); + } catch (err) { + renderError(err.message, el); + } + }; + const debounce_render = debounce( + (source: string, el: HTMLElement) => render(source, el), + this.settings.debounce + ); + this.registerMarkdownCodeBlockProcessor( + "desmos-graph", + (source, el) => { + if (total > 0) { + total--; + // Skip the debounce on initial render + render(source, el); + } else { + debounce_render(source, el); + } + } + ); + } + + async loadSettings() { + this.settings = Object.assign( + {}, + DEFAULT_SETTINGS, + await this.loadData() + ); + } + + async saveSettings() { + await this.saveData(this.settings); + } +} diff --git a/.obsidian/plugins/obsidian-desmos/src/renderer.ts b/.obsidian/plugins/obsidian-desmos/src/renderer.ts new file mode 100644 index 0000000..e4868ed --- /dev/null +++ b/.obsidian/plugins/obsidian-desmos/src/renderer.ts @@ -0,0 +1,205 @@ +import path from "path"; +import Desmos from "./main"; +import { Dsl } from "./dsl"; +import { tmpdir } from "os"; +import { Notice } from "obsidian"; +import { Settings } from "./settings"; +import { renderError } from "./error"; +import { existsSync, promises as fs } from "fs"; + +export class Renderer { + static render( + args: Dsl, + settings: Settings, + el: HTMLElement, + plugin: Desmos + ) { + const { fields, equations, hash } = args; + + // Calculate cache info for filesystem caching + const vault_root = (plugin.app.vault.adapter as any).basePath; + const cache_dir = settings.cache_directory + ? path.isAbsolute(settings.cache_directory) + ? settings.cache_directory + : path.join(vault_root, settings.cache_directory) + : tmpdir(); + const cache_target = path.join(cache_dir, `desmos-graph-${hash}.png`); + + // If this graph is in the cache then fetch it + if (settings.cache) { + if ( + settings.cache_location == "memory" && + hash in plugin.graph_cache + ) { + const data = plugin.graph_cache[hash]; + const img = document.createElement("img"); + img.src = data; + el.appendChild(img); + return; + } else if ( + settings.cache_location == "filesystem" && + existsSync(cache_target) + ) { + fs.readFile(cache_target).then((data) => { + const b64 = + "data:image/png;base64," + + Buffer.from(data).toString("base64"); + const img = document.createElement("img"); + img.src = b64; + el.appendChild(img); + }); + return; + } + } + + const expressions = equations.map( + (equation) => + `calculator.setExpression({ + latex: "${equation.split("|")[0].replace("\\", "\\\\")}${( + equation.split("|")[1] ?? "" + ) + .replace("{", "\\\\{") + .replace("}", "\\\\}") + .replace("<=", "\\\\leq ") + .replace(">=", "\\\\geq ") + .replace("<", "\\\\le ") + .replace(">", "\\\\ge ")}", + + ${(() => { + const mode = equation.split("|")[2]; + + if (mode) { + if ( + ["solid", "dashed", "dotted"].contains( + mode.toLowerCase() + ) + ) { + return `lineStyle: Desmos.Styles.${mode.toUpperCase()}`; + } else if ( + ["point", "open", "cross"].contains( + mode.toLowerCase() + ) + ) { + return `pointStyle: Desmos.Styles.${mode.toUpperCase()}`; + } + } + + return ""; + })()} + });` + ); + + // Because of the electron sandboxing we have to do this inside an iframe, + // otherwise we can't include the desmos API (although it would be nice if they had a REST API of some sort) + const html_src_head = ``; + const html_src_body = ` +
+ + `; + const html_src = `${html_src_head}${html_src_body}`; + + const iframe = document.createElement("iframe"); + iframe.width = fields.width.toString(); + iframe.height = fields.height.toString(); + iframe.style.border = "none"; + iframe.scrolling = "no"; // fixme use a non-depreciated function + iframe.srcdoc = html_src; + // iframe.style.display = "none"; //fixme hiding the iframe breaks the positioning + + el.appendChild(iframe); + + const handler = ( + message: MessageEvent<{ + t: string; + d: string; + data: string; + hash: string; + }> + ) => { + if ( + message.origin === "app://obsidian.md" && + message.data.t === "desmos-graph" && + message.data.hash === hash + ) { + el.empty(); + + if (message.data.d === "error") { + renderError(message.data.data, el); + } + + if (message.data.d === "render") { + const { data } = message.data; + window.removeEventListener("message", handler); + + const img = document.createElement("img"); + img.src = data; + el.appendChild(img); + + if (settings.cache) { + if (settings.cache_location == "memory") { + plugin.graph_cache[hash] = data; + } else if (settings.cache_location == "filesystem") { + if (existsSync(cache_dir)) { + fs.writeFile( + cache_target, + data.replace( + /^data:image\/png;base64,/, + "" + ), + "base64" + ).catch( + (err) => + new Notice( + `desmos-graph: unexpected error when trying to cache graph: ${err}`, + 10000 + ) + ); + } else { + new Notice( + `desmos-graph: cache directory not found: '${cache_dir}'`, + 10000 + ); + } + } + } + } + } + }; + + window.addEventListener("message", handler); + } +} diff --git a/.obsidian/plugins/obsidian-desmos/src/settings.ts b/.obsidian/plugins/obsidian-desmos/src/settings.ts new file mode 100644 index 0000000..be4517a --- /dev/null +++ b/.obsidian/plugins/obsidian-desmos/src/settings.ts @@ -0,0 +1,105 @@ +import { tmpdir } from "os"; +import Desmos from "./main"; +import { PluginSettingTab, App, Setting } from "obsidian"; + +export interface Settings { + debounce: number; + cache: boolean; + cache_location: "memory" | "filesystem"; + cache_directory: string | null; +} + +export const DEFAULT_SETTINGS: Settings = { + debounce: 500, + cache: true, + cache_location: "memory", + cache_directory: null, +}; + +export class SettingsTab extends PluginSettingTab { + plugin: Desmos; + + constructor(app: App, plugin: Desmos) { + super(app, plugin); + this.plugin = plugin; + } + + display() { + let { containerEl } = this; + + containerEl.empty(); + + new Setting(containerEl) + .setName("Debounce Time (ms)") + .setDesc( + "How long to wait after a keypress to render the graph (requires restart to take effect)" + ) + .addText((text) => + text + .setValue(this.plugin.settings.debounce.toString()) + .onChange(async (value) => { + const val = parseInt(value); + this.plugin.settings.debounce = + val === NaN ? DEFAULT_SETTINGS.debounce : val; + await this.plugin.saveSettings(); + }) + ); + + new Setting(containerEl) + .setName("Cache") + .setDesc("Whether to cache the rendered graphs") + .addToggle((toggle) => + toggle + .setValue(this.plugin.settings.cache) + .onChange(async (value) => { + this.plugin.settings.cache = value; + await this.plugin.saveSettings(); + + // Reset the display so the new state can render + this.display(); + }) + ); + + if (this.plugin.settings.cache) { + new Setting(containerEl) + .setName("Cache in memory (alternate: filesystem)") + .setDesc( + "Cache rendered graphs in memory or on the filesystem (note that memory caching is not persistent)." + ) + .addToggle((toggle) => + toggle + .setValue( + this.plugin.settings.cache_location === "memory" + ? true + : false + ) + .onChange(async (value) => { + this.plugin.settings.cache_location = value + ? "memory" + : "filesystem"; + await this.plugin.saveSettings(); + + // Reset the display so the new state can render + this.display(); + }) + ); + + if (this.plugin.settings.cache_location == "filesystem") { + new Setting(containerEl) + .setName("Cache Directory") + .setDesc( + "The directory to save cached graphs in (technical note: the graphs will be saved as `desmos-graph-.png` where the name is a SHA-256 hash of the graph source). The default directory is the system tempdir for your current operating system, and this value may be either a path relative to the root of your vault or an absolute path. Also note that a lot of junk will be saved to this folder, you have been warned." + ) + .addText((text) => + text + .setPlaceholder(tmpdir()) + .setValue(this.plugin.settings.cache_directory) + .onChange(async (value) => { + this.plugin.settings.cache_directory = value; + await this.plugin.saveSettings(); + }) + ); + } + } + } +} diff --git a/.obsidian/plugins/obsidian-desmos/test/.obsidian/.gitignore b/.obsidian/plugins/obsidian-desmos/test/.obsidian/.gitignore new file mode 100644 index 0000000..d76a1e1 --- /dev/null +++ b/.obsidian/plugins/obsidian-desmos/test/.obsidian/.gitignore @@ -0,0 +1,3 @@ +* +!.gitignore +!/plugins/**/* diff --git a/.obsidian/plugins/obsidian-desmos/test/.obsidian/plugins/obsidian-desmos/main.js b/.obsidian/plugins/obsidian-desmos/test/.obsidian/plugins/obsidian-desmos/main.js new file mode 120000 index 0000000..21099ae --- /dev/null +++ b/.obsidian/plugins/obsidian-desmos/test/.obsidian/plugins/obsidian-desmos/main.js @@ -0,0 +1 @@ +../../../../main.js \ No newline at end of file diff --git a/.obsidian/plugins/obsidian-desmos/test/.obsidian/plugins/obsidian-desmos/manifest.json b/.obsidian/plugins/obsidian-desmos/test/.obsidian/plugins/obsidian-desmos/manifest.json new file mode 120000 index 0000000..3b65e69 --- /dev/null +++ b/.obsidian/plugins/obsidian-desmos/test/.obsidian/plugins/obsidian-desmos/manifest.json @@ -0,0 +1 @@ +../../../../manifest.json \ No newline at end of file diff --git a/.obsidian/plugins/obsidian-desmos/test/.obsidian/plugins/obsidian-desmos/versions.json b/.obsidian/plugins/obsidian-desmos/test/.obsidian/plugins/obsidian-desmos/versions.json new file mode 120000 index 0000000..6ce86db --- /dev/null +++ b/.obsidian/plugins/obsidian-desmos/test/.obsidian/plugins/obsidian-desmos/versions.json @@ -0,0 +1 @@ +../../../../versions.json \ No newline at end of file diff --git a/.obsidian/plugins/obsidian-desmos/test/test.md b/.obsidian/plugins/obsidian-desmos/test/test.md new file mode 100644 index 0000000..ce3b848 --- /dev/null +++ b/.obsidian/plugins/obsidian-desmos/test/test.md @@ -0,0 +1,9 @@ +```desmos-graph +boundary_left=-2; boundary_right=2; +boundary_bottom=-1; boundary_top=3; +--- +y=x^2|{x<0}|DASHED +y=x||DOTTED +(1,2)||OPEN +(-1,2)||CROSS +``` diff --git a/.obsidian/plugins/obsidian-desmos/tsconfig.json b/.obsidian/plugins/obsidian-desmos/tsconfig.json new file mode 100644 index 0000000..557dbbe --- /dev/null +++ b/.obsidian/plugins/obsidian-desmos/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "inlineSourceMap": true, + "inlineSources": true, + "module": "ESNext", + "target": "es6", + "allowJs": true, + "noImplicitAny": true, + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "importHelpers": true, + "lib": ["dom", "es5", "scripthost", "es2015"] + }, + "include": ["**/*.ts"] +} diff --git a/.obsidian/plugins/obsidian-desmos/versions.json b/.obsidian/plugins/obsidian-desmos/versions.json new file mode 100644 index 0000000..44dfbf5 --- /dev/null +++ b/.obsidian/plugins/obsidian-desmos/versions.json @@ -0,0 +1,3 @@ +{ + "0.0.1": "0.9.12" +} \ No newline at end of file