mirror of
https://github.com/tmcw/obsidian-freeform.git
synced 2026-07-22 05:12:45 +00:00
Update reamde
This commit is contained in:
parent
a9bdbb0297
commit
b6e85b0d81
4 changed files with 129 additions and 72 deletions
66
README.md
Normal file
66
README.md
Normal 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.
|
||||
127
main.js
127
main.js
|
|
@ -2,40 +2,20 @@ const { Plugin, MarkdownRenderer } = require("obsidian");
|
|||
|
||||
module.exports = class IframeBlockPlugin extends Plugin {
|
||||
async onload() {
|
||||
this.registerMarkdownCodeBlockProcessor("iframe", 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 = 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(
|
||||
"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" });
|
||||
|
||||
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>
|
||||
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}
|
||||
</style>
|
||||
|
|
@ -43,53 +23,64 @@ 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";
|
||||
iframe.addEventListener("load", () => {
|
||||
const height = iframe.contentWindow?.document.body.offsetHeight + 40;
|
||||
iframe.height = height + "px";
|
||||
});
|
||||
window.addEventListener("message", (evt) => {
|
||||
if (evt.data.type === 'height') {
|
||||
iframe.height = evt.data.height + 40 + "px";
|
||||
}
|
||||
});
|
||||
const srcEl = rootEl.createEl("div", { cls: "run-block-src" });
|
||||
MarkdownRenderer.render(
|
||||
this.app,
|
||||
`\`\`\`js\n${src}\n\`\`\``,
|
||||
srcEl,
|
||||
ctx.sourcePath,
|
||||
this,
|
||||
);
|
||||
} catch (error) {
|
||||
el.createEl("h3", { text: error });
|
||||
}
|
||||
});
|
||||
iframe.height = "200px";
|
||||
|
||||
// Resize the iframe as soon as it loads
|
||||
iframe.addEventListener("load", () => {
|
||||
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" && 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\`\`\``,
|
||||
srcEl,
|
||||
ctx.sourcePath,
|
||||
this,
|
||||
);
|
||||
} catch (error) {
|
||||
el.createEl("h3", { text: error });
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
onunload() {
|
||||
console.log("Unloading iframe plugin...");
|
||||
// console.log("Unloading iframe plugin...");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue