M17–M19: #133–#137 (Kommentare inline, Statuszeile, Transklusion, Excalidraw, Checkbox-Fix) #138
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
|
argon2: true
|
||||||
esbuild: true
|
esbuild: true
|
||||||
prisma: 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