Open iceprosurface opened 4 months ago
Excalidraw is very large, with the source code being over 1MB. I don't want to directly inline it in postscript.js.
We can try lazily instantiating it with the data it needs? This is what we do with mermaid for example
I understand. It might be a better approach to first build an Excalidraw specifically for quartz and publish it to npm. Using upkg to asynchronously fetch the script could be a better option. Additionally, you must export a plugin to handle transform and emits.
Since the default API provided by Excalidraw does not perfectly match the data format of the obsidian-excalidraw-plugin, and the UI also needs some customization, it’s a bit troublesome that Quartz does not seem to support using Preact in inline scripts.
I'll push an implementation on Sunday and give it a try.
Hey, can u tell me how to setup this? i mean ur plugin. i want to use that :)
@tharushkadinujaya05
I might need some help (ideas) to implement it into Quartz. Additionally, I haven't had much time recently to develop the feature.
BTW, currently it`s very hard to use the plugin.
For example:
https://github.com/iceprosurface/quartz-blog/tree/v4/packages/quartz-excalidraw-plugin
Add an emit to quartz plugin for output excalidraw file(very hack way, use name)
Cannot get correct raw data of markdown ( any data with %%{raw}%%
will ignore), so we should parse the origin markdown.
For example, https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/assets.ts#L12
const assets = await glob("**", argv.directory, ["**/*.md", ...cfg.configuration.ignorePatterns])
const excalidrawAssets = await glob('**/*.excalidraw.md', argv.directory, cfg.configuration.ignorePatterns)
return [...excalidrawAssets, ...assets]
import { FullSlug, resolveRelative } from "../util/path"
import excalidrawScript from '<path-to-excalidraw-script>'
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
export const Exclidraw: QuartzComponent = ({ classString, fileData }: QuartzComponentProps) => {
return <article class={classString + ' popover-hint'}>
<div class="excalidraw" style="width: 100%;height: 500px" data-excalidraw={resolveRelative(`${fileData.filePath!}` as FullSlug, fileData.slug!) + ".md"}></div>
</article>
}
Exclidraw.afterDOMLoaded = excalidrawScript
Under: quartz/components/pages/Content.tsx
import { Exclidraw } from './../Excalidraw';
const Content: QuartzComponent = (props: QuartzComponentProps) => {
//....
const isExcalidraw = fileData.frontmatter?.['excalidraw-plugin'] ?? false;
if (isExcalidraw) {
return <Exclidraw {...props} />
}
//....
Add load script for SPA mode, if there is not a SPA, async import should more convenient.
declare global {
interface Window {
scriptPromiseMap: Map<string, Promise<void>>;
}
}
if (!window.scriptPromiseMap) {
window.scriptPromiseMap = new Map();
}
export function loadScript(url: string, preserve = true) {
let resolve: (value: void) => void = () => { };
let reject: (reason?: any) => void = () => { };
const promise = new Promise<void>((_resolve, _reject) => {
resolve = _resolve;
reject = _reject;
});
if (!url) {
reject?.(new Error('URL is required'));
return promise
}
if (window.scriptPromiseMap.get(url) && preserve) {
return window.scriptPromiseMap.get(url) || Promise.resolve();
}
const script = document.createElement('script');
script.src = url;
script.async = true;
if (preserve) {
script.setAttribute('spa-preserve', 'true');
}
script.onload = () => {
resolve();
};
script.onerror = () => {
reject(new Error(`Failed to load script: ${url}`));
};
document.head.appendChild(script);
if (preserve) {
window.scriptPromiseMap.set(url, promise);
}
return promise
}
Add Excalidraw init script.
const pluginPath = '<path-to-plugin>'
export async function initExcalidraw() {
await new Promise<void>((resolve) => {
setTimeout(() => {
resolve();
}, 100);
})
const hasExcalidraw = document.querySelector('[data-excalidraw]');
if (!hasExcalidraw) {
return;
}
// can use import('xxx'),
await loadScript(pluginPath, false);
const elements = document.querySelectorAll('[data-excalidraw]');
if (!elements || !elements.length) {
return;
}
elements.forEach((element) => {
loadExcalidraw(element as HTMLElement);
});
}
import { initExcalidraw } from "./util";
document.addEventListener('nav', (event) => {
initExcalidraw();
})
under: https://github.com/jackyzha0/quartz/blob/v4/quartz/components/scripts/popover.inline.ts#L87
const excalidraw = html.querySelector("[data-excalidraw]")
elts.forEach((elt) => popoverInner.appendChild(elt))
if (excalidraw) {
initExcalidraw()
}
Custom by yourself, excalidraw css will load by itself, and use unpkg
cdn
Under: https://github.com/iceprosurface/quartz-blog/blob/v4/quartz/plugins/transformers/ofm.ts#L224
When markdownPlugins step, we should turn img into excalidraw layout.
// embeds with excalidraw
plugins.push(() => {
return (tree: Root, file) => {
// excalidraw
visit(tree, "element", (node, index, parent) => {
const path = node.properties.src;
if (node.tagName === "img" && typeof path === "string" && path.endsWith(".excalidraw.md")) {
node.tagName = "div"
node.properties = {
class: "excalidraw",
style: "width: 100%;height: 500px",
"data-excalidraw": path,
}
}
})
}
})
It is too complex and not so stable.
I want to add excalidraw support for quartz.
I made a plugin for setup excalidraw.
https://github.com/iceprosurface/quartz-excalidraw-plugin
And add Integration to quartz
https://github.com/iceprosurface/quartz-blog/commit/44fc388605e4dd4b0aa9fe2034c266d6d86d54a0
Demo:
demo-link
Currently, the integration method feels very cumbersome. Is there a better approach to directly embed it within Quartz?