#136 Excalidraw-Block-Plugin
Neues Referenz-Block-Plugin „Excalidraw" (handgezeichnete Whiteboard-
Skizzen), analog zum draw.io-Plugin. Anders als draw.io (vendored Webapp)
ist Excalidraw eine React-npm-Lib: esbuild bündelt Controller + React +
Excalidraw in plugin.js, die Font-/Locale-/Data-Assets werden aus
node_modules in den ZIP-Root kopiert und zur Laufzeit über
EXCALIDRAW_ASSET_PATH (Plugin-Asset-Basis) geladen — nichts spricht mit
excalidraw.com, die Sandbox-CSP pinnt jede Anfrage auf self.
- manifest.json: kind=code, Block-Extension-Point diagram,
permissions blockData+ui, fallback "[Excalidraw]".
- src/plugin.tsx: Render-Modus zeigt gespeichertes SVG; Edit-Modus zeigt
Snapshot + Bearbeiten-Knopf (leerer Block öffnet direkt); Vollbild via
host.ui.enterFullscreen mountet <Excalidraw> (React), „Speichern &
Beenden" exportiert per exportToSvg, persistiert {scene, svg} über
host.blockData.setData → Fallback-Renderer bedient Lese-/Public-Ansicht
+ Exporte ohne Backend-Änderung.
- build.mjs: esbuild (jsx automatic, css→text, production-conditions) +
fflate-ZIP. Build erzeugt excalidraw-1.0.0.zip: 15,5 MiB zip /
22,3 MiB unpacked (Limits 64/256 MiB — passt).
- i18n de+en, globals.d.ts (CSS-Modul-Deklaration).
pnpm-Override @floating-ui/react-dom@2.1.2: Excalidraw 0.18.1 zieht sonst
@floating-ui/dom@^1.8.0, das (noch) nicht im Registry ist und `pnpm
install` repo-weit bricht (dokumentiert in pnpm-workspace.yaml).
VERIFIZIERT: typecheck/lint, Manifest-Validierung (SDK), Build+ZIP-Größe.
NICHT lokal verifiziert (braucht Preview/Test-Stage): Laufzeit —
Excalidraw-Rendering + Speichern unter Sandbox-CSP, Font-Laden vom
Asset-Pfad. Prod-Installation macht Stefan als Site-Admin.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
This commit is contained in:
parent
15376d4ac2
commit
c164a031e4
2
packages/plugins/excalidraw/.gitignore
vendored
Normal file
2
packages/plugins/excalidraw/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
vendor/
|
||||
dist/
|
||||
115
packages/plugins/excalidraw/build.mjs
Normal file
115
packages/plugins/excalidraw/build.mjs
Normal file
@ -0,0 +1,115 @@
|
||||
// Builds the installable Excalidraw plugin. Unlike draw.io (which vendors a
|
||||
// standalone webapp) Excalidraw is an npm React component: esbuild bundles the
|
||||
// controller + React + Excalidraw into plugin.js, and its font/worker assets
|
||||
// are copied from node_modules into `excalidraw-assets/` so the runtime loads
|
||||
// them from the plugin's own asset path (EXCALIDRAW_ASSET_PATH) — nothing ever
|
||||
// talks to excalidraw.com, and the sandbox CSP pins every request to self.
|
||||
import { createRequire } from 'node:module';
|
||||
import {
|
||||
existsSync,
|
||||
mkdirSync,
|
||||
readFileSync,
|
||||
readdirSync,
|
||||
rmSync,
|
||||
statSync,
|
||||
writeFileSync,
|
||||
} from 'node:fs';
|
||||
import { dirname, join, relative } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { build } from 'esbuild';
|
||||
import { zipSync } from 'fflate';
|
||||
|
||||
const root = dirname(fileURLToPath(import.meta.url));
|
||||
const manifest = JSON.parse(readFileSync(join(root, 'manifest.json'), 'utf8'));
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
// --- 1. Locate Excalidraw's prebuilt font/worker assets --------------------
|
||||
// The package's `exports` map hides ./package.json, so resolve the main entry
|
||||
// and walk up to the package root instead.
|
||||
function packageRootOf(specifier) {
|
||||
let dir = dirname(require.resolve(specifier));
|
||||
while (dir !== dirname(dir)) {
|
||||
const pj = join(dir, 'package.json');
|
||||
if (existsSync(pj)) {
|
||||
try {
|
||||
if (JSON.parse(readFileSync(pj, 'utf8')).name === specifier) return dir;
|
||||
} catch {
|
||||
// keep walking up
|
||||
}
|
||||
}
|
||||
dir = dirname(dir);
|
||||
}
|
||||
throw new Error(`package root not found for ${specifier}`);
|
||||
}
|
||||
const excalidrawPkg = packageRootOf('@excalidraw/excalidraw');
|
||||
const prodDir = join(excalidrawPkg, 'dist', 'prod');
|
||||
if (!existsSync(prodDir)) {
|
||||
throw new Error(`Excalidraw prod build not found at ${prodDir}. Check the dist layout.`);
|
||||
}
|
||||
// Runtime assets loaded relative to EXCALIDRAW_ASSET_PATH (set to the plugin's
|
||||
// asset base in plugin.tsx): fonts render the sketch, locales translate the
|
||||
// editor UI, data holds font metadata. Copied to the ZIP root so the sandbox
|
||||
// CSP (self only) can serve them.
|
||||
const ASSET_SUBDIRS = ['fonts', 'locales', 'data'];
|
||||
|
||||
/** Recursively collect files below `dir` (absolute), keyed by path relative to
|
||||
* it, each prefixed with `prefix`. */
|
||||
function collect(dir, prefix) {
|
||||
const files = {};
|
||||
for (const name of readdirSync(dir)) {
|
||||
const abs = join(dir, name);
|
||||
const rel = `${prefix}${name}`;
|
||||
if (statSync(abs).isDirectory()) Object.assign(files, collect(abs, `${rel}/`));
|
||||
else files[rel] = readFileSync(abs);
|
||||
}
|
||||
return files;
|
||||
}
|
||||
const assetFiles = {};
|
||||
for (const sub of ASSET_SUBDIRS) {
|
||||
const dir = join(prodDir, sub);
|
||||
if (existsSync(dir)) Object.assign(assetFiles, collect(dir, `${sub}/`));
|
||||
}
|
||||
|
||||
// --- 2. Bundle the plugin controller (React + Excalidraw) ------------------
|
||||
mkdirSync(join(root, 'dist'), { recursive: true });
|
||||
await build({
|
||||
entryPoints: [join(root, 'src/plugin.tsx')],
|
||||
bundle: true,
|
||||
format: 'esm',
|
||||
platform: 'browser',
|
||||
// Excalidraw's `exports` gate index.js / index.css behind `production` /
|
||||
// `development` conditions (no `default`); select the production build.
|
||||
conditions: ['production'],
|
||||
outfile: join(root, 'dist/plugin.js'),
|
||||
minify: true,
|
||||
jsx: 'automatic',
|
||||
loader: { '.css': 'text', '.woff2': 'dataurl', '.ttf': 'dataurl', '.svg': 'text' },
|
||||
define: {
|
||||
'process.env.NODE_ENV': '"production"',
|
||||
'process.env.IS_PREACT': '"false"',
|
||||
},
|
||||
});
|
||||
|
||||
// --- 3. Pack the ZIP --------------------------------------------------------
|
||||
const files = {
|
||||
'manifest.json': readFileSync(join(root, 'manifest.json')),
|
||||
'plugin.js': readFileSync(join(root, 'dist/plugin.js')),
|
||||
};
|
||||
for (const name of readdirSync(join(root, 'i18n'))) {
|
||||
files[`i18n/${name}`] = readFileSync(join(root, 'i18n', name));
|
||||
}
|
||||
// Asset paths already carry their subdir prefix (fonts/…, locales/…, data/…)
|
||||
// and sit at the ZIP root so they resolve under EXCALIDRAW_ASSET_PATH.
|
||||
Object.assign(files, assetFiles);
|
||||
|
||||
const target = join(root, 'dist', `${manifest.id}-${manifest.version}.zip`);
|
||||
rmSync(target, { force: true });
|
||||
writeFileSync(target, zipSync(files, { level: 6 }));
|
||||
const unpacked = Object.values(files).reduce((sum, bytes) => sum + bytes.length, 0);
|
||||
console.log(
|
||||
`wrote ${relative(process.cwd(), target)} ` +
|
||||
`(zip ${(statSync(target).size / 1024 / 1024).toFixed(1)} MiB, ` +
|
||||
`unpacked ${(unpacked / 1024 / 1024).toFixed(1)} MiB, ` +
|
||||
`${Object.keys(files).length} files)`,
|
||||
);
|
||||
10
packages/plugins/excalidraw/i18n/de.json
Normal file
10
packages/plugins/excalidraw/i18n/de.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"empty": "Noch keine Skizze.",
|
||||
"editButton": "Skizze im Vollbild bearbeiten",
|
||||
"createButton": "Skizze erstellen",
|
||||
"loading": "Excalidraw wird geladen …",
|
||||
"save": "Speichern & Beenden",
|
||||
"cancel": "Abbrechen",
|
||||
"saved": "Gespeichert — „Fertig“ schließt den Bearbeiten-Modus.",
|
||||
"renderHint": "Zum Bearbeiten in den Bearbeiten-Modus der Seite wechseln."
|
||||
}
|
||||
10
packages/plugins/excalidraw/i18n/en.json
Normal file
10
packages/plugins/excalidraw/i18n/en.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"empty": "No sketch yet.",
|
||||
"editButton": "Edit sketch in fullscreen",
|
||||
"createButton": "Create sketch",
|
||||
"loading": "Loading Excalidraw …",
|
||||
"save": "Save & Exit",
|
||||
"cancel": "Cancel",
|
||||
"saved": "Saved — \"Done\" closes edit mode.",
|
||||
"renderHint": "Switch the page to edit mode to change the sketch."
|
||||
}
|
||||
19
packages/plugins/excalidraw/manifest.json
Normal file
19
packages/plugins/excalidraw/manifest.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"id": "excalidraw",
|
||||
"name": "Excalidraw Whiteboard",
|
||||
"version": "1.0.0",
|
||||
"apiVersion": "1",
|
||||
"kind": "code",
|
||||
"extensionPoints": [
|
||||
{
|
||||
"type": "block",
|
||||
"id": "diagram",
|
||||
"title": { "de": "Excalidraw-Skizze", "en": "Excalidraw sketch" }
|
||||
}
|
||||
],
|
||||
"permissions": ["blockData", "ui"],
|
||||
"fallback": { "type": "text", "value": "[Excalidraw]" },
|
||||
"license": "MIT",
|
||||
"homepage": "https://excalidraw.com",
|
||||
"i18n": { "de": "i18n/de.json", "en": "i18n/en.json" }
|
||||
}
|
||||
25
packages/plugins/excalidraw/package.json
Normal file
25
packages/plugins/excalidraw/package.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "@dorfteich/plugin-excalidraw",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"description": "Reference block plugin: Excalidraw hand-drawn whiteboard sketches — fullscreen editing with the bundled Excalidraw editor, inline SVG rendering",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "node build.mjs",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "vitest run --passWithNoTests"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@dorfteich/plugin-sdk": "workspace:*",
|
||||
"@excalidraw/excalidraw": "0.18.1",
|
||||
"@types/node": "^26.1.0",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"esbuild": "^0.24.0",
|
||||
"fflate": "^0.8.2",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"typescript": "^5.7.0",
|
||||
"vitest": "^3.0.0"
|
||||
}
|
||||
}
|
||||
4
packages/plugins/excalidraw/src/globals.d.ts
vendored
Normal file
4
packages/plugins/excalidraw/src/globals.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
// The Excalidraw stylesheet is bundled as a string via esbuild's `.css` → text
|
||||
// loader (see build.mjs) and injected at runtime; declare the module so the
|
||||
// import type-checks.
|
||||
declare module '@excalidraw/excalidraw/index.css';
|
||||
268
packages/plugins/excalidraw/src/plugin.tsx
Normal file
268
packages/plugins/excalidraw/src/plugin.tsx
Normal file
@ -0,0 +1,268 @@
|
||||
import { createPlugin, windowTransport, type RenderContext } from '@dorfteich/plugin-sdk';
|
||||
import { Excalidraw, exportToSvg, serializeAsJSON } from '@excalidraw/excalidraw';
|
||||
import { createElement } from 'react';
|
||||
import { createRoot, type Root } from 'react-dom/client';
|
||||
|
||||
/** The subset of Excalidraw's imperative API this plugin uses — kept local so
|
||||
* the deep types-path export (which moves between versions) is not imported. */
|
||||
interface ExcalidrawApi {
|
||||
getSceneElements: () => readonly unknown[];
|
||||
getAppState: () => Record<string, unknown>;
|
||||
getFiles: () => Record<string, unknown>;
|
||||
}
|
||||
|
||||
// Bundled as a string (esbuild `.css` → text loader) and injected once, since
|
||||
// the sandbox frame document loads only `plugin.js` and the CSP forbids remote
|
||||
// stylesheets.
|
||||
import excalidrawCss from '@excalidraw/excalidraw/index.css';
|
||||
|
||||
import de from '../i18n/de.json';
|
||||
import en from '../i18n/en.json';
|
||||
|
||||
/**
|
||||
* Excalidraw reference plugin: hand-drawn whiteboard sketches edited in the
|
||||
* REAL Excalidraw editor, which the package bundles (npm, esbuild) together
|
||||
* with its font assets — nothing is ever loaded from excalidraw.com; the
|
||||
* sandbox CSP pins every request to the plugin's own version-pinned asset path
|
||||
* (`EXCALIDRAW_ASSET_PATH`, set below).
|
||||
*
|
||||
* Block data: `{ scene, svg }` — `scene` is the Excalidraw scene as a JSON
|
||||
* string (document of record), `svg` the rendered snapshot as raw markup, so
|
||||
* render mode, office/PDF exports (fallback renderer) and the public read view
|
||||
* can all show the sketch without running any editor code.
|
||||
*/
|
||||
const STRINGS: Record<string, Record<string, string>> = { de, en };
|
||||
|
||||
function labelFor(locale: string, key: string): string {
|
||||
const base = locale.split('-')[0] ?? locale;
|
||||
return STRINGS[base]?.[key] ?? STRINGS.en?.[key] ?? key;
|
||||
}
|
||||
|
||||
interface SketchData {
|
||||
/** Excalidraw scene as a serialized JSON string (serializeAsJSON). */
|
||||
scene?: string;
|
||||
/** Rendered snapshot as raw SVG markup. */
|
||||
svg?: string;
|
||||
}
|
||||
|
||||
function dataOf(context: RenderContext): SketchData {
|
||||
return context.data && typeof context.data === 'object' ? (context.data as SketchData) : {};
|
||||
}
|
||||
|
||||
// Fonts, locales and font metadata load from the plugin's own asset path (the
|
||||
// directory this module was served from), which the sandbox CSP allows.
|
||||
// Excalidraw resolves them relative to EXCALIDRAW_ASSET_PATH (e.g. `fonts/…`);
|
||||
// build.mjs copies dist/prod/{fonts,locales,data} to the ZIP root.
|
||||
(window as unknown as { EXCALIDRAW_ASSET_PATH: string }).EXCALIDRAW_ASSET_PATH = new URL(
|
||||
'.',
|
||||
import.meta.url,
|
||||
).href;
|
||||
|
||||
let cssInjected = false;
|
||||
function ensureCss(): void {
|
||||
if (cssInjected) return;
|
||||
const style = document.createElement('style');
|
||||
style.textContent = excalidrawCss;
|
||||
document.head.appendChild(style);
|
||||
cssInjected = true;
|
||||
}
|
||||
|
||||
const { host } = createPlugin({
|
||||
transport: windowTransport({
|
||||
target: { postMessage: (message) => window.parent.postMessage(message, '*') },
|
||||
source: window,
|
||||
}),
|
||||
onRender: (context) => renderMode(context),
|
||||
onEdit: (context) => editMode(context),
|
||||
onDestroy: () => closeEditor(),
|
||||
});
|
||||
|
||||
function resize(): void {
|
||||
void host.ui.resize(Math.max(64, document.body.scrollHeight + 16));
|
||||
}
|
||||
|
||||
/** The stored snapshot as an inline drawing, or the empty-state hint. */
|
||||
function snapshotElement(data: SketchData, locale: string): HTMLElement {
|
||||
if (data.svg && data.svg.trim() !== '') {
|
||||
const holder = document.createElement('div');
|
||||
holder.className = 'dt-excalidraw__snapshot';
|
||||
holder.innerHTML = data.svg;
|
||||
const svg = holder.querySelector('svg');
|
||||
if (svg) {
|
||||
svg.style.maxWidth = '100%';
|
||||
svg.style.height = 'auto';
|
||||
}
|
||||
return holder;
|
||||
}
|
||||
const hint = document.createElement('p');
|
||||
hint.textContent = labelFor(locale, 'empty');
|
||||
hint.style.color = '#6b7280';
|
||||
return hint;
|
||||
}
|
||||
|
||||
function renderMode(context: RenderContext): void {
|
||||
closeEditor();
|
||||
const data = dataOf(context);
|
||||
document.body.textContent = '';
|
||||
document.body.className = 'dt-excalidraw dt-excalidraw--render';
|
||||
document.body.appendChild(snapshotElement(data, context.locale));
|
||||
resize();
|
||||
}
|
||||
|
||||
function editMode(context: RenderContext): void {
|
||||
closeEditor();
|
||||
const data = dataOf(context);
|
||||
document.body.textContent = '';
|
||||
document.body.className = 'dt-excalidraw dt-excalidraw--edit';
|
||||
|
||||
document.body.appendChild(snapshotElement(data, context.locale));
|
||||
|
||||
const button = document.createElement('button');
|
||||
button.type = 'button';
|
||||
button.textContent = labelFor(context.locale, data.svg ? 'editButton' : 'createButton');
|
||||
button.style.cssText = 'display:block;margin:8px 0;padding:6px 12px;cursor:pointer;font:inherit;';
|
||||
button.addEventListener('click', () => void openEditor(context));
|
||||
document.body.appendChild(button);
|
||||
resize();
|
||||
|
||||
// A block that has no sketch yet goes straight into the editor.
|
||||
if (!data.svg && !data.scene) void openEditor(context);
|
||||
}
|
||||
|
||||
/** The active fullscreen editing session, if any. */
|
||||
let session: { container: HTMLDivElement; root: Root } | null = null;
|
||||
|
||||
function closeEditor(): void {
|
||||
if (!session) return;
|
||||
const { container, root } = session;
|
||||
session = null;
|
||||
root.unmount();
|
||||
container.remove();
|
||||
void host.ui.exitFullscreen();
|
||||
}
|
||||
|
||||
function parseScene(scene: string | undefined): {
|
||||
elements: readonly unknown[];
|
||||
appState: Record<string, unknown>;
|
||||
files: Record<string, unknown>;
|
||||
} {
|
||||
if (!scene) return { elements: [], appState: {}, files: {} };
|
||||
try {
|
||||
const parsed = JSON.parse(scene) as {
|
||||
elements?: unknown[];
|
||||
appState?: Record<string, unknown>;
|
||||
files?: Record<string, unknown>;
|
||||
};
|
||||
return {
|
||||
elements: parsed.elements ?? [],
|
||||
appState: parsed.appState ?? {},
|
||||
files: parsed.files ?? {},
|
||||
};
|
||||
} catch {
|
||||
return { elements: [], appState: {}, files: {} };
|
||||
}
|
||||
}
|
||||
|
||||
async function openEditor(context: RenderContext): Promise<void> {
|
||||
if (session) return;
|
||||
ensureCss();
|
||||
const data = dataOf(context);
|
||||
const initial = parseScene(data.scene);
|
||||
|
||||
await host.ui.enterFullscreen();
|
||||
|
||||
const container = document.createElement('div');
|
||||
container.className = 'dt-excalidraw__editor';
|
||||
container.style.cssText =
|
||||
'position:fixed;inset:0;background:#fff;display:flex;flex-direction:column;';
|
||||
document.body.appendChild(container);
|
||||
|
||||
const bar = document.createElement('div');
|
||||
bar.style.cssText =
|
||||
'display:flex;gap:8px;justify-content:flex-end;padding:8px;border-bottom:1px solid #e5e7eb;background:#fff;z-index:5;';
|
||||
const cancel = document.createElement('button');
|
||||
cancel.type = 'button';
|
||||
cancel.textContent = labelFor(context.locale, 'cancel');
|
||||
cancel.style.cssText = 'padding:6px 12px;cursor:pointer;font:inherit;';
|
||||
const save = document.createElement('button');
|
||||
save.type = 'button';
|
||||
save.textContent = labelFor(context.locale, 'save');
|
||||
save.style.cssText = 'padding:6px 12px;cursor:pointer;font:inherit;font-weight:600;';
|
||||
bar.append(cancel, save);
|
||||
container.appendChild(bar);
|
||||
|
||||
const canvas = document.createElement('div');
|
||||
canvas.style.cssText = 'flex:1;min-height:0;position:relative;';
|
||||
container.appendChild(canvas);
|
||||
|
||||
let api: ExcalidrawApi | null = null;
|
||||
const root = createRoot(canvas);
|
||||
session = { container, root };
|
||||
|
||||
root.render(
|
||||
createElement(Excalidraw, {
|
||||
initialData: {
|
||||
elements: initial.elements,
|
||||
appState: initial.appState,
|
||||
files: initial.files,
|
||||
},
|
||||
excalidrawAPI: (instance: ExcalidrawApi) => {
|
||||
api = instance;
|
||||
},
|
||||
} as Parameters<typeof Excalidraw>[0]),
|
||||
);
|
||||
|
||||
cancel.addEventListener('click', () => {
|
||||
closeEditor();
|
||||
finishEdit(dataOf(context), context.locale);
|
||||
});
|
||||
|
||||
save.addEventListener('click', () => {
|
||||
void (async () => {
|
||||
if (!api) return;
|
||||
const elements = api.getSceneElements();
|
||||
const appState = api.getAppState();
|
||||
const files = api.getFiles();
|
||||
const scene = serializeAsJSON(
|
||||
elements as Parameters<typeof serializeAsJSON>[0],
|
||||
appState as Parameters<typeof serializeAsJSON>[1],
|
||||
files as Parameters<typeof serializeAsJSON>[2],
|
||||
'local',
|
||||
);
|
||||
const svgEl = await exportToSvg({
|
||||
elements,
|
||||
appState: { ...appState, exportBackground: true, exportWithDarkMode: false },
|
||||
files,
|
||||
} as Parameters<typeof exportToSvg>[0]);
|
||||
const svg = new XMLSerializer().serializeToString(svgEl);
|
||||
await host.blockData.setData({ scene, svg } satisfies SketchData);
|
||||
closeEditor();
|
||||
finishEdit({ scene, svg }, context.locale);
|
||||
})();
|
||||
});
|
||||
}
|
||||
|
||||
/** Redraws the inline edit surface after the fullscreen editor closed. */
|
||||
function finishEdit(data: SketchData, locale: string): void {
|
||||
document.body.textContent = '';
|
||||
document.body.className = 'dt-excalidraw dt-excalidraw--edit';
|
||||
document.body.appendChild(snapshotElement(data, locale));
|
||||
|
||||
const button = document.createElement('button');
|
||||
button.type = 'button';
|
||||
button.textContent = labelFor(locale, data.svg ? 'editButton' : 'createButton');
|
||||
button.style.cssText = 'display:block;margin:8px 0;padding:6px 12px;cursor:pointer;font:inherit;';
|
||||
button.addEventListener(
|
||||
'click',
|
||||
() => void openEditor({ extensionPointId: 'diagram', locale, data }),
|
||||
);
|
||||
document.body.appendChild(button);
|
||||
|
||||
if (data.svg) {
|
||||
const note = document.createElement('p');
|
||||
note.textContent = labelFor(locale, 'saved');
|
||||
note.style.cssText = 'color:#6b7280;font-size:13px;';
|
||||
document.body.appendChild(note);
|
||||
}
|
||||
resize();
|
||||
}
|
||||
12
packages/plugins/excalidraw/tsconfig.json
Normal file
12
packages/plugins/excalidraw/tsconfig.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"resolveJsonModule": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"]
|
||||
},
|
||||
"include": ["src", "*.ts"]
|
||||
}
|
||||
1249
pnpm-lock.yaml
generated
1249
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -12,3 +12,11 @@ allowBuilds:
|
||||
argon2: true
|
||||
esbuild: true
|
||||
prisma: true
|
||||
# Excalidraw 0.18.1 (excalidraw plugin, issue #136) transitively pulls
|
||||
# @floating-ui/react-dom@2.1.9, which requires @floating-ui/dom@^1.8.0 — a
|
||||
# version not published on the registry (latest 1.7.6), breaking `pnpm install`
|
||||
# repo-wide. Pin react-dom to 2.1.2, which needs @floating-ui/dom@^1.0.0. Only
|
||||
# the excalidraw plugin pulls @floating-ui at all; drop this once upstream
|
||||
# @floating-ui/dom@1.8.0 is published.
|
||||
overrides:
|
||||
'@floating-ui/react-dom': '2.1.2'
|
||||
|
||||
Loading…
Reference in New Issue
Block a user