@matfsw/docusaurus-plantuml-plugin 1.1.2 → 1.3.0
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/CHANGELOG.md +103 -1
- package/README.md +211 -32
- package/assets/stdlib/LICENSES.md +21 -0
- package/assets/stdlib/archimate.min.js +22 -0
- package/assets/stdlib/azure.min.js +305 -0
- package/assets/stdlib/c4.min.js +47 -0
- package/assets/stdlib/cloudinsight.min.js +93 -0
- package/assets/stdlib/eip.min.js +14 -0
- package/assets/stdlib/k8s.min.js +52 -0
- package/assets/stdlib/kubernetes.min.js +17 -0
- package/assets/stdlib/manifest.json +112 -0
- package/assets/stdlib/office.min.js +557 -0
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +2 -0
- package/dist/index.cjs +384 -8
- package/dist/index.d.cts +91 -3
- package/dist/index.d.ts +12 -2
- package/dist/index.js +20 -1
- package/dist/options.d.ts +57 -1
- package/dist/options.js +84 -0
- package/dist/runtime/assetLoader.d.ts +13 -1
- package/dist/runtime/assetLoader.js +36 -13
- package/dist/runtime/cache.d.ts +6 -1
- package/dist/runtime/cache.js +2 -2
- package/dist/runtime/errors.d.ts +3 -1
- package/dist/runtime/errors.js +27 -1
- package/dist/runtime/renderer.d.ts +3 -0
- package/dist/runtime/renderer.js +21 -7
- package/dist/runtime/stdlibLoader.d.ts +31 -0
- package/dist/runtime/stdlibLoader.js +99 -0
- package/dist/stdlib.d.ts +35 -0
- package/dist/stdlib.js +163 -0
- package/dist/stdlibBundle.d.ts +60 -0
- package/dist/stdlibBundle.js +218 -0
- package/dist/stdlibShared.d.ts +51 -0
- package/dist/stdlibShared.js +54 -0
- package/dist/theme/MDXComponents/Code/index.js +1 -1
- package/dist/theme/PlantUmlDiagram/index.d.ts +6 -1
- package/dist/theme/PlantUmlDiagram/index.js +53 -5
- package/dist/theme/PlantUmlDiagram/styles.module.css +117 -0
- package/dist/theme/usePlantUmlConfig.d.ts +8 -0
- package/dist/theme/usePlantUmlConfig.js +14 -8
- package/package.json +3 -1
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the Node side and the browser side of standard library support both need: the shape
|
|
3
|
+
* of the manifest, and the one definition of what a `<namespace/…>` include looks like.
|
|
4
|
+
*
|
|
5
|
+
* Deliberately free of `node:` imports. `stdlibBundle.ts` reads the file system and
|
|
6
|
+
* `stdlib.ts` resolves paths; neither may be pulled into a theme component's bundle, and
|
|
7
|
+
* keeping the shared half here is what stops that from happening by accident.
|
|
8
|
+
*/
|
|
9
|
+
/** Directory holding a namespace's includable examples, which upstream keeps in the bundle. */
|
|
10
|
+
export const STDLIB_EXAMPLES_DIRECTORY = '_examples_';
|
|
11
|
+
/**
|
|
12
|
+
* `<namespace/path>` in an `!include` and its variants.
|
|
13
|
+
*
|
|
14
|
+
* Anchored to the start of a line because `!include` is only a directive there. Applied to
|
|
15
|
+
* stdlib sources when bundling and to author sources when rendering — one syntax, one place
|
|
16
|
+
* to correct it.
|
|
17
|
+
*/
|
|
18
|
+
export const STDLIB_INCLUDE_PATTERN = /^[^\S\r\n]*!include(?:sub|url|_many)?[^\S\r\n]+<([^>\r\n]+)>/gim;
|
|
19
|
+
/**
|
|
20
|
+
* Extracts the `<namespace/…>` includes of a PlantUML source.
|
|
21
|
+
*
|
|
22
|
+
* Includes whose path is computed (`!include <material2.1.19/$icon>`, as DomainStory does
|
|
23
|
+
* inside an `!if`) are skipped: the namespace is knowable but the file is not, so treating it
|
|
24
|
+
* as a hard dependency would make every DomainStory diagram drag in 6.8 MB of icons for a
|
|
25
|
+
* branch it will probably not take.
|
|
26
|
+
*/
|
|
27
|
+
export function findStdlibIncludes(source) {
|
|
28
|
+
const found = [];
|
|
29
|
+
const seen = new Set();
|
|
30
|
+
// `matchAll` on a shared global regex is safe: it clones the regex rather than advancing
|
|
31
|
+
// `lastIndex` on the original.
|
|
32
|
+
for (const match of source.matchAll(STDLIB_INCLUDE_PATTERN)) {
|
|
33
|
+
const raw = match[1]?.trim() ?? '';
|
|
34
|
+
if (raw.includes('$') || raw.includes('%'))
|
|
35
|
+
continue;
|
|
36
|
+
const slash = raw.indexOf('/');
|
|
37
|
+
if (slash <= 0)
|
|
38
|
+
continue;
|
|
39
|
+
const namespace = raw.slice(0, slash).toLowerCase();
|
|
40
|
+
const target = raw.slice(slash + 1).toLowerCase();
|
|
41
|
+
if (target === '')
|
|
42
|
+
continue;
|
|
43
|
+
const key = `${namespace}/${target}`;
|
|
44
|
+
if (seen.has(key))
|
|
45
|
+
continue;
|
|
46
|
+
seen.add(key);
|
|
47
|
+
found.push({
|
|
48
|
+
namespace,
|
|
49
|
+
target,
|
|
50
|
+
fromExamples: target.startsWith(`${STDLIB_EXAMPLES_DIRECTORY}/`),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return found;
|
|
54
|
+
}
|
|
@@ -29,7 +29,7 @@ export default function Code(props) {
|
|
|
29
29
|
if (engine === null || source === null) {
|
|
30
30
|
return _jsx(OriginalCode, { ...props });
|
|
31
31
|
}
|
|
32
|
-
return (_jsx(PlantUmlDiagram, { source: source, title: parseTitle(props), language: language, engine: engine, layout: engine === 'graphviz' ? parseStringMeta(props, 'engine') : undefined, zoom: parseBooleanMeta(props, 'zoom') }));
|
|
32
|
+
return (_jsx(PlantUmlDiagram, { source: source, title: parseTitle(props), language: language, engine: engine, layout: engine === 'graphviz' ? parseStringMeta(props, 'engine') : undefined, zoom: parseBooleanMeta(props, 'zoom'), showSource: parseBooleanMeta(props, 'showSource') }));
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
35
35
|
* Decides which engine — if either — owns this fence.
|
|
@@ -19,6 +19,11 @@ export interface PlantUmlDiagramProps {
|
|
|
19
19
|
* Set from the fence metastring: `zoom` or `zoom=false`.
|
|
20
20
|
*/
|
|
21
21
|
zoom?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Overrides the `showSource` plugin option for this diagram. `undefined` follows the option.
|
|
24
|
+
* Set from the fence metastring: `showSource` or `showSource=false`.
|
|
25
|
+
*/
|
|
26
|
+
showSource?: boolean;
|
|
22
27
|
}
|
|
23
28
|
/**
|
|
24
29
|
* Renders one diagram in the browser, with either engine.
|
|
@@ -30,4 +35,4 @@ export interface PlantUmlDiagramProps {
|
|
|
30
35
|
* Nothing happens during server-side rendering: the markup emitted on the server is the
|
|
31
36
|
* same deferred placeholder the client renders first, so hydration never mismatches.
|
|
32
37
|
*/
|
|
33
|
-
export default function PlantUmlDiagram({ source, title, language, engine, layout, zoom: zoomProp, }: PlantUmlDiagramProps): ReactNode;
|
|
38
|
+
export default function PlantUmlDiagram({ source, title, language, engine, layout, zoom: zoomProp, showSource: showSourceProp, }: PlantUmlDiagramProps): ReactNode;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useId, useRef, useState } from 'react';
|
|
2
|
+
import { useCallback, useEffect, useId, useRef, useState } from 'react';
|
|
3
3
|
import { useColorMode } from '@docusaurus/theme-common';
|
|
4
4
|
import { DATA_ATTR } from '../../constants.js';
|
|
5
5
|
import { getSharedCache } from '../../runtime/cache.js';
|
|
@@ -19,6 +19,8 @@ const ENGINE_LABEL = {
|
|
|
19
19
|
};
|
|
20
20
|
/** Margin large enough that a diagram is usually ready by the time it is scrolled into view. */
|
|
21
21
|
const LAZY_ROOT_MARGIN = '300px';
|
|
22
|
+
/** How long the copy result stays on screen before the control goes quiet again. */
|
|
23
|
+
const COPY_FEEDBACK_MS = 2_000;
|
|
22
24
|
const INITIAL_STATE = { status: 'idle', svg: null, error: null };
|
|
23
25
|
function escapeHtml(value) {
|
|
24
26
|
return value
|
|
@@ -44,11 +46,12 @@ function messageFor(error) {
|
|
|
44
46
|
* Nothing happens during server-side rendering: the markup emitted on the server is the
|
|
45
47
|
* same deferred placeholder the client renders first, so hydration never mismatches.
|
|
46
48
|
*/
|
|
47
|
-
export default function PlantUmlDiagram({ source, title, language, engine = 'plantuml', layout, zoom: zoomProp, }) {
|
|
49
|
+
export default function PlantUmlDiagram({ source, title, language, engine = 'plantuml', layout, zoom: zoomProp, showSource: showSourceProp, }) {
|
|
48
50
|
const config = usePlantUmlConfig();
|
|
49
51
|
const { colorMode } = useColorMode();
|
|
50
52
|
const containerRef = useRef(null);
|
|
51
53
|
const hintId = useId();
|
|
54
|
+
const sourcePanelId = useId();
|
|
52
55
|
const themeOption = config?.options.theme ?? 'auto';
|
|
53
56
|
const dark = themeOption === 'auto' ? colorMode === 'dark' : themeOption === 'dark';
|
|
54
57
|
const lazy = config?.options.lazy ?? true;
|
|
@@ -62,8 +65,39 @@ export default function PlantUmlDiagram({ source, title, language, engine = 'pla
|
|
|
62
65
|
const renderDark = engine === 'graphviz' ? false : dark;
|
|
63
66
|
// A fence flag wins over the plugin option, which in turn wins over the built-in default.
|
|
64
67
|
const interactive = zoomProp ?? config?.options.zoom ?? true;
|
|
68
|
+
const sourceAvailable = showSourceProp ?? config?.options.showSource ?? true;
|
|
65
69
|
const [state, setState] = useState(INITIAL_STATE);
|
|
66
70
|
const [inView, setInView] = useState(!lazy);
|
|
71
|
+
const [sourceOpen, setSourceOpen] = useState(false);
|
|
72
|
+
const [copyState, setCopyState] = useState('idle');
|
|
73
|
+
const copyTimer = useRef(undefined);
|
|
74
|
+
// A pending "Copied" message must not fire into an unmounted component.
|
|
75
|
+
useEffect(() => () => clearTimeout(copyTimer.current), []);
|
|
76
|
+
/**
|
|
77
|
+
* Copies the source, and says whether it worked.
|
|
78
|
+
*
|
|
79
|
+
* `navigator.clipboard` is undefined outside a secure context, which a documentation site
|
|
80
|
+
* served over plain HTTP genuinely is. Reporting that plainly is better than a control that
|
|
81
|
+
* silently does nothing — the panel is open, so the reader can still select the text.
|
|
82
|
+
*/
|
|
83
|
+
const copySource = useCallback(() => {
|
|
84
|
+
const settle = (next) => {
|
|
85
|
+
setCopyState(next);
|
|
86
|
+
clearTimeout(copyTimer.current);
|
|
87
|
+
copyTimer.current = setTimeout(() => setCopyState('idle'), COPY_FEEDBACK_MS);
|
|
88
|
+
};
|
|
89
|
+
const clipboard = typeof navigator === 'undefined' ? undefined : navigator.clipboard;
|
|
90
|
+
if (typeof clipboard?.writeText !== 'function') {
|
|
91
|
+
settle('failed');
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
void clipboard.writeText(source).then(() => settle('copied'), () => settle('failed'));
|
|
95
|
+
}, [source]);
|
|
96
|
+
// A diagram whose source changed is a different diagram: an open panel would otherwise keep
|
|
97
|
+
// showing the old text's copy result, and the reader would have no idea it was stale.
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
setCopyState('idle');
|
|
100
|
+
}, [source]);
|
|
67
101
|
// Called unconditionally, as hooks must be; it attaches nothing when not interactive.
|
|
68
102
|
const zoom = useZoomPan({
|
|
69
103
|
enabled: interactive && state.status === 'ready',
|
|
@@ -136,7 +170,7 @@ export default function PlantUmlDiagram({ source, title, language, engine = 'pla
|
|
|
136
170
|
transparentBackground: options.graphviz.transparentBackground,
|
|
137
171
|
maxSourceBytes: options.graphviz.maxSourceBytes,
|
|
138
172
|
})
|
|
139
|
-
: renderDiagram({ ...shared, dark: renderDark });
|
|
173
|
+
: renderDiagram({ ...shared, dark: renderDark, stdlib: config.stdlib });
|
|
140
174
|
void pending
|
|
141
175
|
.then((svg) => commit({ status: 'ready', svg, error: null }))
|
|
142
176
|
.catch((error) => {
|
|
@@ -156,6 +190,7 @@ export default function PlantUmlDiagram({ source, title, language, engine = 'pla
|
|
|
156
190
|
config,
|
|
157
191
|
config?.assetsBaseUrl,
|
|
158
192
|
config?.coreVersion,
|
|
193
|
+
config?.stdlib,
|
|
159
194
|
config?.options.sanitizeSvg,
|
|
160
195
|
config?.options.renderTimeoutMs,
|
|
161
196
|
config?.options.cache,
|
|
@@ -167,16 +202,29 @@ export default function PlantUmlDiagram({ source, title, language, engine = 'pla
|
|
|
167
202
|
const busy = state.status === 'loading' || state.status === 'rendering';
|
|
168
203
|
// The sanitized SVG always lives in this element, zoomable or not, so `role="img" > svg`
|
|
169
204
|
// stays a stable contract for tests and for author CSS.
|
|
170
|
-
const canvas = state.svg !== null && (_jsx("div", { className: styles.canvas, role: "img", "aria-label": label,
|
|
205
|
+
const canvas = state.svg !== null && (_jsx("div", { className: !interactive && sourceOpen ? `${styles.canvas} ${styles.hiddenView}` : styles.canvas, role: "img", "aria-label": label,
|
|
171
206
|
// Sanitized above unless `sanitizeSvg: false` was explicitly opted into.
|
|
172
207
|
dangerouslySetInnerHTML: { __html: state.svg } }));
|
|
208
|
+
const copyControl = sourceAvailable && sourceOpen && state.status === 'ready' && (_jsx("button", { type: "button", className: `${styles.toolbarButton} ${styles.toolbarTextButton}`, "aria-label": `Copy ${engineName} source to clipboard`, onClick: copySource, children: "Copy" }));
|
|
209
|
+
const sourceToggle = sourceAvailable && (_jsx("button", { type: "button", className: styles.toolbarButton, "aria-label": sourceOpen ? 'Hide diagram source' : 'Show diagram source', "aria-expanded": sourceOpen, "aria-controls": sourcePanelId, onClick: () => setSourceOpen((open) => !open), children: _jsx("span", { "aria-hidden": "true", children: '</>' }) }));
|
|
210
|
+
/**
|
|
211
|
+
* The source *replaces the diagram in its own frame* rather than appearing beneath it.
|
|
212
|
+
*
|
|
213
|
+
* Below the diagram it had two failure modes that the frame removes by construction: a
|
|
214
|
+
* diagram taller than the window pushed the panel off-screen, so the control looked broken;
|
|
215
|
+
* and while maximized the panel was painted behind the `position: fixed` overlay, so it was
|
|
216
|
+
* invisible however far you scrolled. Living in the same box as the picture, it is visible
|
|
217
|
+
* exactly when and where the picture was.
|
|
218
|
+
*/
|
|
219
|
+
const sourceView = sourceAvailable && sourceOpen && state.status === 'ready' && (_jsxs("div", { className: styles.sourceView, id: sourcePanelId, children: [_jsxs("div", { className: styles.sourceViewBar, children: [_jsxs("span", { className: styles.sourceViewTitle, children: [engineName, " source"] }), _jsxs("span", { className: styles.copyStatus, role: "status", children: [copyState === 'copied' ? 'Copied to clipboard' : null, copyState === 'failed' ? 'Could not copy — select the text instead' : null] })] }), _jsx("pre", { className: styles.sourceCode, children: source })] }));
|
|
173
220
|
return (_jsxs("figure", { ref: containerRef, className: styles.figure, "aria-busy": busy || undefined, [DATA_ATTR.diagram]: language ?? engine,
|
|
174
221
|
[DATA_ATTR.engine]: engine,
|
|
175
222
|
...(engine === 'graphviz' ? { [DATA_ATTR.layout]: resolvedLayout } : {}),
|
|
176
223
|
[DATA_ATTR.status]: state.status,
|
|
177
224
|
[DATA_ATTR.theme]: dark ? 'dark' : 'light',
|
|
178
225
|
...(interactive ? { [DATA_ATTR.interactive]: 'true' } : {}),
|
|
179
|
-
...(zoom.maximized ? { [DATA_ATTR.maximized]: 'true' } : {}),
|
|
226
|
+
...(zoom.maximized ? { [DATA_ATTR.maximized]: 'true' } : {}),
|
|
227
|
+
...(sourceOpen ? { [DATA_ATTR.sourceOpen]: 'true' } : {}), children: [state.status === 'ready' && state.svg !== null && !interactive && canvas, state.status === 'ready' && state.svg !== null && !interactive && sourceView, state.status === 'ready' && state.svg !== null && !interactive && sourceAvailable && (_jsxs("div", { className: styles.plainToolbar, role: "group", "aria-label": `${label} source controls`, children: [sourceToggle, copyControl] })), state.status === 'ready' && state.svg !== null && interactive && (_jsxs("div", { ref: zoom.stageRef, className: zoom.maximized ? `${styles.stage} ${styles.maximized}` : styles.stage, children: [_jsxs("div", { className: styles.stageBody, children: [_jsx("div", { ref: zoom.viewportRef, className: sourceOpen ? `${styles.viewport} ${styles.invisibleView}` : styles.viewport, tabIndex: sourceOpen ? -1 : 0, "aria-describedby": hintId, "aria-keyshortcuts": "Plus Minus 0 ArrowUp ArrowDown ArrowLeft ArrowRight", onKeyDown: zoom.onKeyDown, [DATA_ATTR.zoom]: '1', children: _jsx("div", { ref: zoom.layerRef, className: styles.layer, children: canvas }) }), sourceView] }), _jsxs("div", { className: styles.toolbar, role: "group", "aria-label": `${label} zoom controls`, children: [!sourceOpen && (_jsx("button", { type: "button", className: styles.toolbarButton, "aria-label": "Zoom out", onClick: zoom.zoomOut, children: _jsx("span", { "aria-hidden": "true", children: "\u2212" }) })), !sourceOpen && (_jsx("button", { type: "button", className: styles.toolbarButton, "aria-label": "Zoom in", onClick: zoom.zoomIn, children: _jsx("span", { "aria-hidden": "true", children: "+" }) })), !sourceOpen && (_jsx("button", { type: "button", className: styles.toolbarButton, "aria-label": "Reset zoom", onClick: zoom.reset, children: _jsx("span", { "aria-hidden": "true", children: "\u27F2" }) })), _jsx("button", { type: "button", className: styles.toolbarButton, "aria-label": "Maximize diagram", "aria-pressed": zoom.maximized, onClick: zoom.toggleMaximize, children: _jsx("span", { "aria-hidden": "true", children: zoom.maximized ? '✕' : '⛶' }) }), sourceToggle, copyControl, _jsx("span", { ref: zoom.readoutRef, className: sourceOpen ? `${styles.readout} ${styles.hiddenView}` : styles.readout, "aria-hidden": "true", children: "100%" })] })] })), state.status === 'ready' && state.svg !== null && interactive && (_jsx("p", { id: hintId, className: styles.visuallyHidden, children: "Zoomable diagram. Use the zoom controls, or press plus and minus to zoom, the arrow keys to pan, and zero to reset." })), state.status !== 'ready' && state.status !== 'error' && (
|
|
180
228
|
// `aria-busy` on the figure already conveys progress; announcing every phase change
|
|
181
229
|
// would make a page with several diagrams unusable with a screen reader.
|
|
182
230
|
_jsxs("div", { className: styles.placeholder, children: [_jsx("span", { className: styles.spinner, "aria-hidden": "true" }), _jsxs("span", { children: [state.status === 'loading' ? `Loading ${engineName} runtime…` : null, state.status === 'rendering' ? `Rendering ${label}…` : null, state.status === 'idle' ? `${label} (waiting to render)` : null] })] })), state.status === 'error' && (_jsxs("div", { className: styles.error, role: "alert", children: [_jsxs("p", { className: styles.errorHeading, children: [_jsx("span", { className: styles.errorIcon, "aria-hidden": "true", children: "\u26A0" }), _jsxs("span", { children: ["Error: ", engineName, " diagram could not be rendered"] })] }), _jsx("pre", { className: styles.errorMessage, children: state.error }), config?.options.showSourceOnError !== false && (_jsxs("details", { className: styles.errorDetails, children: [_jsx("summary", { children: "Show diagram source" }), _jsx("pre", { className: styles.source, children: source })] }))] })), title !== undefined && _jsx("figcaption", { className: styles.caption, children: title }), _jsx("noscript", { dangerouslySetInnerHTML: {
|
|
@@ -291,3 +291,120 @@
|
|
|
291
291
|
overflow-x: auto;
|
|
292
292
|
font-size: 0.8125rem;
|
|
293
293
|
}
|
|
294
|
+
|
|
295
|
+
/*
|
|
296
|
+
* Source view.
|
|
297
|
+
*
|
|
298
|
+
* The source takes the diagram's place inside the same frame rather than appearing beneath it.
|
|
299
|
+
* Below the diagram it had two failure modes this removes by construction: a diagram taller
|
|
300
|
+
* than the window pushed it off-screen, and while maximized it was painted behind the
|
|
301
|
+
* `position: fixed` overlay. In the frame it is visible exactly where the picture was.
|
|
302
|
+
*/
|
|
303
|
+
|
|
304
|
+
/* One grid cell shared by the viewport and the source view, so they occupy the same box. */
|
|
305
|
+
.stageBody {
|
|
306
|
+
position: relative;
|
|
307
|
+
display: grid;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.stageBody > * {
|
|
311
|
+
grid-area: 1 / 1;
|
|
312
|
+
/* Without this a wide <pre> would stretch the grid column instead of scrolling. */
|
|
313
|
+
min-width: 0;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/*
|
|
317
|
+
* `visibility`, not `display`: the hidden viewport keeps contributing its height, so the frame
|
|
318
|
+
* does not resize when flipping, and the zoom hook's layout measurements stay valid for the
|
|
319
|
+
* flip back. A `visibility: hidden` subtree is also not focusable and takes no pointer events,
|
|
320
|
+
* so the invisible diagram cannot be tabbed into or dragged.
|
|
321
|
+
*/
|
|
322
|
+
.invisibleView {
|
|
323
|
+
visibility: hidden;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/* Collapses entirely — used where there is no frame height worth preserving. */
|
|
327
|
+
.hiddenView {
|
|
328
|
+
display: none;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/*
|
|
332
|
+
* Inside a zoom frame the source is taken out of flow so that it cannot size the grid row: the
|
|
333
|
+
* row stays exactly as tall as the (hidden but still laid-out) viewport, and a long source
|
|
334
|
+
* scrolls within it. Left in flow it grew the frame from the diagram's height to the source's —
|
|
335
|
+
* on a long diagram that pushed the text back off the bottom of the window, which is the very
|
|
336
|
+
* problem moving it into the frame was meant to solve.
|
|
337
|
+
*/
|
|
338
|
+
.stageBody .sourceView {
|
|
339
|
+
position: absolute;
|
|
340
|
+
inset: 0;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.sourceView {
|
|
344
|
+
display: flex;
|
|
345
|
+
flex-direction: column;
|
|
346
|
+
min-height: 0;
|
|
347
|
+
border: 1px solid var(--ifm-color-emphasis-300);
|
|
348
|
+
border-radius: var(--ifm-global-radius);
|
|
349
|
+
background: var(--ifm-background-surface-color);
|
|
350
|
+
overflow: hidden;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.sourceViewBar {
|
|
354
|
+
display: flex;
|
|
355
|
+
align-items: center;
|
|
356
|
+
flex-wrap: wrap;
|
|
357
|
+
gap: 0.5rem;
|
|
358
|
+
/* Clears the toolbar, which floats over the top-right of the same frame. */
|
|
359
|
+
padding: 0.5rem 12rem 0.25rem 0.75rem;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.sourceViewTitle {
|
|
363
|
+
color: var(--ifm-color-emphasis-700);
|
|
364
|
+
font-size: 0.8125rem;
|
|
365
|
+
font-weight: 600;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.copyStatus {
|
|
369
|
+
flex: 1;
|
|
370
|
+
color: var(--ifm-color-emphasis-700);
|
|
371
|
+
font-size: 0.75rem;
|
|
372
|
+
text-align: right;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/*
|
|
376
|
+
* The copy control is a toolbar button like the rest, just carrying a word instead of a glyph,
|
|
377
|
+
* so it needs width where the icon buttons are square.
|
|
378
|
+
*/
|
|
379
|
+
.toolbarTextButton {
|
|
380
|
+
width: auto;
|
|
381
|
+
padding: 0 0.5rem;
|
|
382
|
+
font-size: 0.8125rem;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.sourceCode {
|
|
386
|
+
flex: 1;
|
|
387
|
+
min-height: 0;
|
|
388
|
+
margin: 0;
|
|
389
|
+
padding: 0.75rem;
|
|
390
|
+
overflow: auto;
|
|
391
|
+
background: transparent;
|
|
392
|
+
font-size: 0.8125rem;
|
|
393
|
+
/* Wraps rather than scrolls sideways: diagram source is read, not just copied. */
|
|
394
|
+
white-space: pre-wrap;
|
|
395
|
+
word-break: break-word;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/*
|
|
399
|
+
* Without a zoom frame the source has no box to fill, so it needs a floor of its own — and a
|
|
400
|
+
* ceiling, so a long diagram cannot produce an endless wall of text.
|
|
401
|
+
*/
|
|
402
|
+
.figure:not([data-plantuml-interactive]) .sourceView {
|
|
403
|
+
max-height: 30rem;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
.plainToolbar {
|
|
407
|
+
display: flex;
|
|
408
|
+
justify-content: flex-end;
|
|
409
|
+
margin-top: 0.25rem;
|
|
410
|
+
}
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import { type ResolvedPlantUmlOptions } from '../options.js';
|
|
2
|
+
import type { StdlibRuntimeManifest } from '../stdlibShared.js';
|
|
3
|
+
/** The standard library as the browser sees it: a URL to fetch bundles from, and an index. */
|
|
4
|
+
export interface StdlibRuntimeConfig {
|
|
5
|
+
baseUrl: string;
|
|
6
|
+
manifest: StdlibRuntimeManifest;
|
|
7
|
+
}
|
|
2
8
|
export interface PlantUmlRuntimeConfig {
|
|
3
9
|
options: ResolvedPlantUmlOptions;
|
|
4
10
|
/** Absolute, `baseUrl`-prefixed URL of the directory holding the runtime assets. */
|
|
5
11
|
assetsBaseUrl: string;
|
|
6
12
|
coreVersion: string;
|
|
13
|
+
/** `null` when the standard library is switched off for this site. */
|
|
14
|
+
stdlib: StdlibRuntimeConfig | null;
|
|
7
15
|
}
|
|
8
16
|
/**
|
|
9
17
|
* Reads the plugin's configuration from Docusaurus global data.
|
|
@@ -2,7 +2,7 @@ import { useMemo } from 'react';
|
|
|
2
2
|
import useBaseUrl from '@docusaurus/useBaseUrl';
|
|
3
3
|
import { useAllPluginInstancesData } from '@docusaurus/useGlobalData';
|
|
4
4
|
import { PLUGIN_NAME } from '../constants.js';
|
|
5
|
-
import { DEFAULT_GRAPHVIZ_OPTIONS } from '../options.js';
|
|
5
|
+
import { DEFAULT_GRAPHVIZ_OPTIONS, DEFAULT_STDLIB_OPTIONS, } from '../options.js';
|
|
6
6
|
function isGlobalData(value) {
|
|
7
7
|
if (typeof value !== 'object' || value === null)
|
|
8
8
|
return false;
|
|
@@ -23,26 +23,32 @@ export function usePlantUmlConfig() {
|
|
|
23
23
|
const first = instances ? Object.values(instances).find(isGlobalData) : undefined;
|
|
24
24
|
// `useBaseUrl` must be called unconditionally; the placeholder path is discarded below.
|
|
25
25
|
const assetsBaseUrl = useBaseUrl(first ? first.assetsDir : '/');
|
|
26
|
+
const stdlibBaseUrl = useBaseUrl(first?.stdlib ? first.stdlib.dir : '/');
|
|
26
27
|
// Memoized because consumers use this object as an effect dependency. Returning a fresh
|
|
27
28
|
// object on every render would restart the render effect on every state update, which
|
|
28
29
|
// re-enters rendering forever.
|
|
29
30
|
return useMemo(() => first
|
|
30
31
|
? {
|
|
31
|
-
options:
|
|
32
|
+
options: withDefaults(first.options),
|
|
32
33
|
assetsBaseUrl,
|
|
33
34
|
coreVersion: first.coreVersion,
|
|
35
|
+
stdlib: first.stdlib ? { baseUrl: stdlibBaseUrl, manifest: first.stdlib.manifest } : null,
|
|
34
36
|
}
|
|
35
|
-
: null, [first, assetsBaseUrl]);
|
|
37
|
+
: null, [first, assetsBaseUrl, stdlibBaseUrl]);
|
|
36
38
|
}
|
|
37
39
|
/**
|
|
38
|
-
* Fills in the
|
|
40
|
+
* Fills in the nested option groups when they are missing from global data.
|
|
39
41
|
*
|
|
40
|
-
* `resolveOptions` always produces
|
|
42
|
+
* `resolveOptions` always produces them, so this only matters for data written by an older
|
|
41
43
|
* version of the plugin — a stale `.docusaurus` cache surviving an upgrade, most plausibly.
|
|
42
44
|
* Substituting the defaults keeps such a site rendering instead of failing on a missing key.
|
|
43
45
|
*/
|
|
44
|
-
function
|
|
45
|
-
if (options.graphviz)
|
|
46
|
+
function withDefaults(options) {
|
|
47
|
+
if (options.graphviz && options.stdlib)
|
|
46
48
|
return options;
|
|
47
|
-
return {
|
|
49
|
+
return {
|
|
50
|
+
...options,
|
|
51
|
+
graphviz: options.graphviz ?? DEFAULT_GRAPHVIZ_OPTIONS,
|
|
52
|
+
stdlib: options.stdlib ?? DEFAULT_STDLIB_OPTIONS,
|
|
53
|
+
};
|
|
48
54
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@matfsw/docusaurus-plantuml-plugin",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Render PlantUML diagrams in Docusaurus 3 entirely in the browser - no Java, no PlantUML server, no CDN.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"docusaurus",
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"./package.json": "./package.json"
|
|
36
36
|
},
|
|
37
37
|
"files": [
|
|
38
|
+
"assets",
|
|
38
39
|
"dist",
|
|
39
40
|
"README.md",
|
|
40
41
|
"LICENSE",
|
|
@@ -62,6 +63,7 @@
|
|
|
62
63
|
"verify:tag": "node scripts/verify-tag-version.mjs",
|
|
63
64
|
"sync:meta": "node scripts/sync-project-metadata.mjs",
|
|
64
65
|
"sync:check": "node scripts/sync-project-metadata.mjs --check",
|
|
66
|
+
"stdlib:update": "node scripts/update-stdlib.mjs",
|
|
65
67
|
"example:install": "npm --prefix examples/docusaurus install",
|
|
66
68
|
"example:build": "npm --prefix examples/docusaurus run build",
|
|
67
69
|
"example:serve": "npm --prefix examples/docusaurus run serve",
|