@riebeckite/plugin-excalidraw 0.0.22 → 0.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -76,6 +76,23 @@ The following source is rendered as an SVG drawing on this page.
76
76
  - Success → `data-excalidraw="ready"` (the SVG replaces the empty canvas)
77
77
  - Failure → `data-excalidraw="error"` plus a placeholder message
78
78
 
79
+ ## Standalone pages
80
+
81
+ An Excalidraw drawing stored as Markdown is also served as a standalone page
82
+ instead of a plain Markdown page. The plugin registers a page type
83
+ (`excalidraw-document`) that claims the entry's permalink with a trailing
84
+ `.excalidraw` removed.
85
+
86
+ - Detection: the slug ends in `.excalidraw` (for `*.excalidraw.md`), or the
87
+ frontmatter contains `excalidraw-plugin: parsed`
88
+ - `drawing.excalidraw.md` (permalink `/drawing.excalidraw`) is served at
89
+ `/drawing`
90
+ - The page body provides its own `<main>` and `<h1>`, and reuses the same
91
+ placeholder figure and `initExcalidraw` client as embedded wikilinks
92
+
93
+ The page type resolves before normal content routing, so no extra app route is
94
+ required. Only public entries are listed.
95
+
79
96
  ## Options
80
97
 
81
98
  | Option | Type | Default | Description |
package/README_ja.md CHANGED
@@ -48,6 +48,16 @@ export default defineConfig({
48
48
  | --- | --- | --- | --- |
49
49
  | `lazy` | `boolean` | `true` | 表示領域に近づいてから SVG を描画するか |
50
50
 
51
+ ## 単体ページとしての表示
52
+
53
+ Excalidraw の描画である Markdown エントリは、通常の Markdown ページではなく専用ページとして表示されます。プラグインはページ型(`excalidraw-document`)を登録し、パーマリンク末尾の `.excalidraw` を除いたパスを引き受けます。
54
+
55
+ - 判定: slug が `.excalidraw` で終わる(`*.excalidraw.md`)、または frontmatter に `excalidraw-plugin: parsed` がある
56
+ - `drawing.excalidraw.md`(パーマリンク `/drawing.excalidraw`)は `/drawing` で表示される
57
+ - ページ本文は自前の `<main>` と `<h1>` を持ち、埋め込みウィキリンクと同じプレースホルダーと `initExcalidraw` を再利用する
58
+
59
+ ページ型は通常のコンテンツルーティングより先に解決されるため、アプリ側のルート追加は不要です。対象は公開エントリのみです。
60
+
51
61
  ## 公開 API
52
62
 
53
63
  - `excalidraw(options?)` / `excalidrawPlugin` — プラグインファクトリ
package/dist/index.js CHANGED
@@ -105,6 +105,17 @@ import {
105
105
  escapeHtmlAttribute,
106
106
  escapeScriptJson
107
107
  } from "@riebeckite/core";
108
+ function renderExcalidrawPage(input) {
109
+ const heading = input.title ? `<h1 class="rr-excalidraw-page__title">${escapeHtml(input.title)}</h1>` : "";
110
+ return `<main class="rr-excalidraw-page">${heading}${renderExcalidrawPlaceholder(
111
+ {
112
+ title: input.title,
113
+ scene: input.scene,
114
+ lazy: input.lazy,
115
+ error: input.error
116
+ }
117
+ )}</main>`;
118
+ }
108
119
  function renderExcalidrawPlaceholder(input) {
109
120
  const style = renderSizeStyle(input.size);
110
121
  const error = input.error ? `<p class="rr-excalidraw__error">${escapeHtml(input.error)}</p>` : "";
@@ -158,8 +169,24 @@ function excalidraw(options = {}) {
158
169
  render: async (context) => renderAttachment(context, options)
159
170
  }
160
171
  ],
161
- assets: [createStyleAsset(PLUGIN_NAME)],
162
- clientEntries: [createClientEntry(PLUGIN_NAME, "initExcalidraw")]
172
+ pageTypes: [
173
+ {
174
+ id: "excalidraw-document",
175
+ paths: ({ manifest }) => listExcalidrawDocumentPaths(manifest),
176
+ outputDependencies: [{ type: "global" }],
177
+ resolve: (context) => resolveExcalidrawDocument(context, options)
178
+ }
179
+ ],
180
+ assets: [
181
+ createStyleAsset(PLUGIN_NAME, "@riebeckite/plugin-excalidraw/style.css")
182
+ ],
183
+ clientEntries: [
184
+ createClientEntry(
185
+ PLUGIN_NAME,
186
+ "@riebeckite/plugin-excalidraw/client",
187
+ "initExcalidraw"
188
+ )
189
+ ]
163
190
  });
164
191
  }
165
192
  var excalidrawPlugin = excalidraw;
@@ -211,6 +238,95 @@ function isExcalidrawDocument(filePath, raw) {
211
238
  if (lowerPath.endsWith(".excalidraw.md")) return true;
212
239
  return lowerPath.endsWith(".md") && isObsidianExcalidrawMarkdown(raw);
213
240
  }
241
+ function listExcalidrawDocumentPaths(manifest) {
242
+ const paths = /* @__PURE__ */ new Set();
243
+ for (const entry of manifest.publicEntries) {
244
+ const path = getExcalidrawPagePath(entry);
245
+ if (path) paths.add(path);
246
+ }
247
+ return [...paths];
248
+ }
249
+ function isExcalidrawEntry(entry) {
250
+ if (entry.slug.toLowerCase().endsWith(".excalidraw")) return true;
251
+ return entry.frontmatter["excalidraw-plugin"] === "parsed";
252
+ }
253
+ function getExcalidrawPagePath(entry) {
254
+ if (!isExcalidrawEntry(entry)) return null;
255
+ const path = normalizePath(entry.permalink).replace(/\.excalidraw$/i, "");
256
+ if (!path || path === "/") return null;
257
+ if (/\.[a-zA-Z0-9]+$/.test(path.slice(1))) return null;
258
+ return path;
259
+ }
260
+ function findExcalidrawEntry(manifest, pathname) {
261
+ for (const entry of manifest.publicEntries) {
262
+ if (getExcalidrawPagePath(entry) === pathname) return entry;
263
+ }
264
+ return null;
265
+ }
266
+ async function resolveExcalidrawDocument(context, options) {
267
+ const entry = findExcalidrawEntry(context.manifest, context.pathname);
268
+ if (!entry) return null;
269
+ const title = resolveEntryTitle(entry);
270
+ const description = typeof entry.frontmatter.description === "string" ? entry.frontmatter.description : void 0;
271
+ const lazy = options.lazy !== false;
272
+ if (!context.contentSource) {
273
+ return {
274
+ type: "excalidraw-document",
275
+ pathname: context.pathname,
276
+ title,
277
+ description,
278
+ body: renderExcalidrawPage({
279
+ title,
280
+ lazy,
281
+ error: "Content source is not available."
282
+ })
283
+ };
284
+ }
285
+ const sourcePath = `${entry.slug}.md`;
286
+ try {
287
+ const raw = await readText(context.contentSource, sourcePath);
288
+ if (raw === null) return null;
289
+ const parsed = parseExcalidrawScene(raw, sourcePath);
290
+ const scene = await mergeEmbeddedFiles({
291
+ scene: parsed.scene,
292
+ embeddedFiles: parsed.embeddedFiles,
293
+ contentSource: context.contentSource,
294
+ contentIndex: context.contentIndex
295
+ });
296
+ return {
297
+ type: "excalidraw-document",
298
+ pathname: context.pathname,
299
+ title,
300
+ description,
301
+ body: renderExcalidrawPage({ title, scene, lazy })
302
+ };
303
+ } catch (error) {
304
+ console.error("[plugin-excalidraw] Failed to render Excalidraw page", {
305
+ path: sourcePath,
306
+ error
307
+ });
308
+ return {
309
+ type: "excalidraw-document",
310
+ pathname: context.pathname,
311
+ title,
312
+ description,
313
+ body: renderExcalidrawPage({
314
+ title,
315
+ lazy,
316
+ error: "Excalidraw could not be rendered."
317
+ })
318
+ };
319
+ }
320
+ }
321
+ function resolveEntryTitle(entry) {
322
+ const trimmed = entry.title.trim().replace(/\.excalidraw$/i, "");
323
+ if (trimmed) return trimmed;
324
+ return getFileName(entry.slug).replace(/\.excalidraw$/i, "");
325
+ }
326
+ function normalizePath(pathname) {
327
+ const collapsed = `/${pathname.split("/").filter(Boolean).join("/")}`;
328
+ return collapsed === "/" ? "/" : collapsed.replace(/\/+$/, "");
329
+ }
214
330
  function parseSize(label) {
215
331
  const value = label.trim();
216
332
  if (/^\d{1,5}$/.test(value)) return { width: Number(value) };
@@ -1,4 +1,10 @@
1
1
  import type { ExcalidrawScene } from "./types.js";
2
+ export declare function renderExcalidrawPage(input: {
3
+ title: string;
4
+ scene?: ExcalidrawScene;
5
+ lazy?: boolean;
6
+ error?: string;
7
+ }): string;
2
8
  export declare function renderExcalidrawPlaceholder(input: {
3
9
  title: string;
4
10
  scene?: ExcalidrawScene;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riebeckite/plugin-excalidraw",
3
- "version": "0.0.22",
3
+ "version": "0.0.23",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -24,10 +24,11 @@
24
24
  "@excalidraw/excalidraw": "^0.18.0",
25
25
  "lz-string": "^1.5.0",
26
26
  "unist-util-visit": "^5.0.0",
27
- "@riebeckite/core": "0.0.22"
27
+ "@riebeckite/core": "0.0.23"
28
28
  },
29
29
  "devDependencies": {
30
- "@types/mdast": "^4.0.4"
30
+ "@types/mdast": "^4.0.4",
31
+ "tsx": "^4.22.4"
31
32
  },
32
33
  "publishConfig": {
33
34
  "access": "public"
@@ -62,6 +63,7 @@
62
63
  "./style.css"
63
64
  ],
64
65
  "scripts": {
65
- "build": "node ../../../scripts/build_package.mjs"
66
+ "build": "node ../../../scripts/build_package.mjs",
67
+ "test": "node --import tsx --test \"test/*.test.ts\""
66
68
  }
67
69
  }
package/style.css CHANGED
@@ -54,3 +54,22 @@
54
54
  --rr-excalidraw-ink: var(--rb-color-paper, #ffffff);
55
55
  }
56
56
  }
57
+
58
+ .rr-excalidraw-page {
59
+ width: min(100%, 96rem);
60
+ margin-inline: auto;
61
+ padding-inline: var(--rb-space-2, 1rem);
62
+ }
63
+
64
+ .rr-excalidraw-page__title {
65
+ margin: 0 0 var(--rb-space-2, 1rem);
66
+ font-family: var(--rb-font-heading, inherit);
67
+ font-size: clamp(1.75rem, 4vw, 2.75rem);
68
+ line-height: 1.05;
69
+ letter-spacing: -0.02em;
70
+ }
71
+
72
+ .rr-excalidraw-page .rr-excalidraw {
73
+ width: 100%;
74
+ margin: 0;
75
+ }