Update reamde

This commit is contained in:
Tom MacWright 2024-05-18 16:13:12 -04:00
parent a9bdbb0297
commit b6e85b0d81
No known key found for this signature in database
GPG key ID: CEBC26A37F3D1D9F
4 changed files with 129 additions and 72 deletions

66
README.md Normal file
View file

@ -0,0 +1,66 @@
# Freeform
Obsidian freeform plugin. This lets you write arbitrary JavaScript,
including importing ESM modules, injecting styles, and much more.
### Inspired by Observable
This brings a taste of [Observable](https://observablehq.com/) to
[Obsidian](https://obsidian.md/). Some common elements include:
- You can write blocks of code which run in a light sandboxed environment
- There's a `display()` function to show values and elements, just like
Observable Framework's [explicit display system](https://observablehq.com/framework/javascript#explicit-display).
### Based on iframes
Everything you write is run within a sandboxed iframe, making it safer to do more
creative coding within Obsidian without affecting the surrounding page.
## Get started
Install the freeform plugin, and create a fenced code block with the language
set to `freeform`. For example:
```freeform
display(42);
```
That should, once you're done editing it and have clicked away from the code block,
show the number 42. Now you've learned all of the concepts in freeform! It's
JavaScript, and you can use the `display()` method to show a value. See the rest
of this readme for some examples.
### Examples
```freeform
import * as Plot from "https://cdn.jsdelivr.net/npm/@observablehq/plot@0.6/+esm";
const widths = [
["Val Town", 1024],
["Val Town (after)", 900],
["GitHub", 830],
["Radicle", 723],
["Replicate", 653],
["Glitch", 612],
["GitLab", 842],
["Observable", 640]
].map(([name, width]) => ({ name, width }))
display(Plot.barX(widths, {
x: "width",
y: "name",
marginLeft: 100,
fill: "name"
}).plot({ height: 400, width }))
```
### Notes
- There is a `width` variable, much like [Observable's](https://observablehq.com/framework/javascript#width), but
it is not live-updating or reactive. This project does not include
Observable-style reactivity: your JavaScript runs just the same
way as it does on any webpage.
- Only HTTP ESM imports are supported. This isn't Node.js or Deno - there
isn't a node_modules directory, and you don't have short names for dependencies.
Thankfully, this usually isn't a problem because you can use https://esm.sh/
https://www.jsdelivr.com/ and more to import modules.

77
main.js
View file

@ -2,39 +2,19 @@ const { Plugin, MarkdownRenderer } = require("obsidian");
module.exports = class IframeBlockPlugin extends Plugin {
async onload() {
this.registerMarkdownCodeBlockProcessor("iframe", async (src, el, ctx) => {
// Get Parameters
this.registerMarkdownCodeBlockProcessor(
"freeform",
async (src, el, ctx) => {
try {
// Give this iframe an identifier so that when it sends messages
// from the iframe context back up, we know which iframe's messages
// belong to which code blocks
const iframeId = crypto.randomUUID();
const rootEl = el.createEl("div", {
cls: "run-block",
});
const iframe = rootEl.createEl("iframe", { cls: "run-block-frame" });
iframe.srcdoc = src;
iframe.height = "200px";
iframe.addEventListener("load", () => {
const height = iframe.contentWindow?.document.body.offsetHeight + 40;
iframe.height = height + "px";
});
const srcEl = rootEl.createEl("div", { cls: "run-block-src" });
MarkdownRenderer.render(
this.app,
`\`\`\`html\n${src}\n\`\`\``,
srcEl,
ctx.sourcePath,
this,
);
} catch (error) {
el.createEl("h3", { text: error });
}
});
this.registerMarkdownCodeBlockProcessor("iframeturbo", async (src, el, ctx) => {
// Get Parameters
try {
const rootEl = el.createEl("div", {
cls: "run-block",
});
const iframe = rootEl.createEl("iframe", { cls: "run-block-frame" });
iframe.srcdoc = `<!DOCTYPE html>
<style>
:root{--syntax_normal:#1b1e23;--syntax_comment:#a9b0bc;--syntax_number:#20a5ba;--syntax_keyword:#c30771;--syntax_atom:#10a778;--syntax_string:#008ec4;--syntax_error:#ffbedc;--syntax_unknown_variable:#838383;--syntax_known_variable:#005f87;--syntax_matchbracket:#20bbfc;--syntax_key:#6636b4;--mono_fonts:82%/1.5 Menlo,Consolas,monospace}.observablehq--collapsed,.observablehq--expanded,.observablehq--function,.observablehq--gray,.observablehq--import,.observablehq--string:after,.observablehq--string:before{color:var(--syntax_normal)}.observablehq--collapsed,.observablehq--inspect a{cursor:pointer}.observablehq--field{text-indent:-1em;margin-left:1em}.observablehq--empty{color:var(--syntax_comment)}.observablehq--blue,.observablehq--keyword{color:#3182bd}.observablehq--forbidden,.observablehq--pink{color:#e377c2}.observablehq--orange{color:#e6550d}.observablehq--boolean,.observablehq--null,.observablehq--undefined{color:var(--syntax_atom)}.observablehq--bigint,.observablehq--date,.observablehq--green,.observablehq--number,.observablehq--regexp,.observablehq--symbol{color:var(--syntax_number)}.observablehq--index,.observablehq--key{color:var(--syntax_key)}.observablehq--prototype-key{color:#aaa}.observablehq--empty{font-style:oblique}.observablehq--purple,.observablehq--string{color:var(--syntax_string)}.observablehq--error,.observablehq--red{color:#e7040f}.observablehq--inspect{font:var(--mono_fonts);overflow-x:auto;display:block;white-space:pre}.observablehq--error .observablehq--inspect{word-break:break-all;white-space:pre-wrap}
@ -43,39 +23,49 @@ module.exports = class IframeBlockPlugin extends Plugin {
import * as Plot from "https://cdn.jsdelivr.net/npm/@observablehq/plot@0.6/+esm";
import { Inspector } from "https://unpkg.com/@observablehq/inspector@5.0.0?module";
try {
${src}
if (typeof output !== 'undefined') {
inspect(output);
}
} catch (e) {
inspect(e);
}
function inspect(val) {
globalThis.display = function display(val) {
const d = document.body.appendChild(document.createElement('div'))
const inspector = new Inspector(d);
inspector.fulfilled(val);
}
globalThis.width = window.innerWidth;
const resizeObserver = new ResizeObserver((entries) => {
parent.postMessage({ type: 'height', height: document.body.offsetHeight }, '*');
parent.postMessage({
type: 'height',
height: document.body.offsetHeight,
id: ${JSON.stringify(iframeId)}
}, '*');
});
resizeObserver.observe(document.body);
import(${JSON.stringify(`data:text/javascript;base64,${btoa(src)}`)}).catch(e => {
console.log(e);
display(e);
});
</script>`;
iframe.height = "200px";
// Resize the iframe as soon as it loads
iframe.addEventListener("load", () => {
const height = iframe.contentWindow?.document.body.offsetHeight + 40;
const height =
iframe.contentWindow?.document.body.offsetHeight + 40;
iframe.height = height + "px";
});
// Resize the iframe when we get a message that its contents
// have been measured.
window.addEventListener("message", (evt) => {
if (evt.data.type === 'height') {
if (evt.data.type === "height" && evt.data.id === iframeId) {
iframe.height = evt.data.height + 40 + "px";
}
});
const srcEl = rootEl.createEl("div", { cls: "run-block-src" });
// Show the source code below the iframe
MarkdownRenderer.render(
this.app,
`\`\`\`js\n${src}\n\`\`\``,
@ -86,10 +76,11 @@ resizeObserver.observe(document.body);
} catch (error) {
el.createEl("h3", { text: error });
}
});
},
);
}
onunload() {
console.log("Unloading iframe plugin...");
}
// console.log("Unloading iframe plugin...");
}
};

View file

@ -1,9 +1,9 @@
{
"id": "iframe-block-plugin",
"name": "Iframe code block",
"id": "iframe-freeform",
"name": "Freeform",
"version": "0.0.0",
"minAppVersion": "0.0.0",
"description": "Creates an iframe block",
"description": "Make visualizations and run arbitrary code with JavaScript + iframe blocks.",
"author": "tmcw",
"authorUrl": "https://github.com/tmcw",
"isDesktopOnly": false

View file

@ -6,7 +6,7 @@
.run-block-frame {
border-top-left-radius: 4px;
border-top-right: 4px;
border-top-right-radius: 4px;
background: #fff;
width: 100%;
padding: 10px;