@markdstage/markdstage 3.2.0 → 3.4.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/README.md +23 -12
- package/package.json +1 -1
- package/shared/README.md +18 -7
- package/shared/renderer/architecture-scene.mjs +312 -0
- package/shared/renderer/architecture.mjs +41 -18
- package/shared/renderer/index.html +9 -1
- package/shared/renderer/mermaid-scene.mjs +1248 -0
- package/shared/renderer/renderer.js +444 -234
- package/shared/renderer/scene-graph.mjs +739 -0
- package/shared/renderer/scene-pptx.mjs +265 -0
- package/shared/renderer/scene-svg.mjs +321 -0
- package/shared/renderer/slides.css +23 -0
- package/shared/renderer/theme.mjs +54 -0
- package/shared/runtime/architecture-editor-server.mjs +3 -0
- package/shared/runtime/deck-session.mjs +37 -6
- package/shared/runtime/pptx-package.mjs +4 -3
- package/shared/runtime/presentation-server.mjs +368 -95
- package/src/cli.mjs +92 -19
- package/src/commands/present.mjs +133 -143
- package/src/deck.mjs +4 -0
- package/src/skills.mjs +12 -8
|
@@ -91,6 +91,60 @@ export function serializeThemeVariables(variables) {
|
|
|
91
91
|
.join("");
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
// Mermaid's "base" theme accepts a `themeVariables` palette instead of one of
|
|
95
|
+
// its built-in named themes (dark/default/neutral/forest). Deriving that
|
|
96
|
+
// palette from the rendered deck's custom properties makes Mermaid diagrams
|
|
97
|
+
// share the slide's background, border, and text colors instead of only
|
|
98
|
+
// approximating the deck theme, and it reuses the same primary/secondary
|
|
99
|
+
// roles as the Architecture DSL (nodes: surface+border+fg, groups:
|
|
100
|
+
// accent-soft+accent-line+accent-strong) so both diagram types match.
|
|
101
|
+
//
|
|
102
|
+
// `secondaryColor`/`tertiaryColor` also back many categorical fills across
|
|
103
|
+
// Mermaid's diagram types (pie slices, git graph nodes, venn/quadrant charts,
|
|
104
|
+
// activation highlights, ...), so they must stay solid and clearly visible
|
|
105
|
+
// against `--bg` rather than reusing the translucent `--accent-soft` wash or
|
|
106
|
+
// `--bg` itself, which rendered those fills nearly invisible. `noteBkgColor`/
|
|
107
|
+
// `noteBorderColor`/`noteTextColor` are hardcoded by Mermaid's base theme
|
|
108
|
+
// (always a pale yellow) unless set explicitly, so sequence-diagram notes
|
|
109
|
+
// need their own override to follow the deck theme too.
|
|
110
|
+
export function mermaidThemeVariables(style) {
|
|
111
|
+
const read = (name) => style.getPropertyValue(name).trim();
|
|
112
|
+
const background = read("--bg");
|
|
113
|
+
const surface = read("--surface");
|
|
114
|
+
const border = read("--border");
|
|
115
|
+
const foreground = read("--fg");
|
|
116
|
+
const muted = read("--muted");
|
|
117
|
+
const accent = read("--accent");
|
|
118
|
+
const accentStrong = read("--accent-strong");
|
|
119
|
+
const accentSoft = read("--accent-soft");
|
|
120
|
+
const accentLine = read("--accent-line");
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
background,
|
|
124
|
+
primaryColor: surface,
|
|
125
|
+
primaryTextColor: foreground,
|
|
126
|
+
primaryBorderColor: border,
|
|
127
|
+
secondaryColor: accentStrong,
|
|
128
|
+
secondaryTextColor: background,
|
|
129
|
+
secondaryBorderColor: accentLine,
|
|
130
|
+
tertiaryColor: muted,
|
|
131
|
+
tertiaryTextColor: background,
|
|
132
|
+
tertiaryBorderColor: border,
|
|
133
|
+
lineColor: accent,
|
|
134
|
+
textColor: foreground,
|
|
135
|
+
mainBkg: surface,
|
|
136
|
+
nodeBorder: border,
|
|
137
|
+
clusterBkg: accentSoft,
|
|
138
|
+
clusterBorder: accentLine,
|
|
139
|
+
titleColor: foreground,
|
|
140
|
+
edgeLabelBackground: background,
|
|
141
|
+
noteBkgColor: accentSoft,
|
|
142
|
+
noteBorderColor: accentLine,
|
|
143
|
+
noteTextColor: accentStrong,
|
|
144
|
+
pie1: accent,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
94
148
|
function assertPlainObject(value, path) {
|
|
95
149
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
96
150
|
throw new Error(`${path} must be an object`);
|
|
@@ -581,6 +581,9 @@ export async function startArchitectureEditorServer({
|
|
|
581
581
|
if (
|
|
582
582
|
[
|
|
583
583
|
"/renderer/architecture.mjs",
|
|
584
|
+
"/renderer/architecture-scene.mjs",
|
|
585
|
+
"/renderer/scene-graph.mjs",
|
|
586
|
+
"/renderer/scene-svg.mjs",
|
|
584
587
|
"/renderer/architecture-contract.mjs",
|
|
585
588
|
"/renderer/architecture-diagnostics.mjs",
|
|
586
589
|
"/renderer/architecture-edit.mjs",
|
|
@@ -112,14 +112,25 @@ export async function createDeckSession({
|
|
|
112
112
|
// An explicit --workspace wins; otherwise confine the deck to its Git
|
|
113
113
|
// repository root (or the folder holding the Markdown file).
|
|
114
114
|
const deckDirectory = file ? resolve(file, "..") : process.cwd();
|
|
115
|
-
const
|
|
115
|
+
const requestedRoot = workspaceRoot
|
|
116
116
|
? resolve(workspaceRoot)
|
|
117
117
|
: resolveWorkspaceRoot(deckDirectory, deckDirectory);
|
|
118
|
-
|
|
118
|
+
let root;
|
|
119
|
+
try {
|
|
120
|
+
root = await realpath(requestedRoot);
|
|
121
|
+
} catch (_) {
|
|
122
|
+
throw new MarkdStageError(
|
|
123
|
+
"workspace_not_found",
|
|
124
|
+
`Could not read workspace directory: ${requestedRoot}`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
const resolved = file ? await resolveDeckFile(file, root) : null;
|
|
119
128
|
const session = {
|
|
120
|
-
file: resolved
|
|
121
|
-
workspaceRoot: resolved
|
|
122
|
-
sourceName:
|
|
129
|
+
file: resolved?.path ?? "",
|
|
130
|
+
workspaceRoot: resolved?.workspaceRoot ?? root,
|
|
131
|
+
sourceName: resolved
|
|
132
|
+
? workspaceRelative(resolved.workspaceRoot, resolved.path)
|
|
133
|
+
: "",
|
|
123
134
|
url: "",
|
|
124
135
|
version: 0,
|
|
125
136
|
deckVersion: 0,
|
|
@@ -145,6 +156,9 @@ export async function createDeckSession({
|
|
|
145
156
|
};
|
|
146
157
|
|
|
147
158
|
session.load = async ({ preserveIndex = false } = {}) => {
|
|
159
|
+
if (!session.file) {
|
|
160
|
+
throw new MarkdStageError("no_deck", "Open a Markdown file first.");
|
|
161
|
+
}
|
|
148
162
|
const { markdown, slides } = await readDeckSlides(session.file);
|
|
149
163
|
const selection = resolveDeckTheme({
|
|
150
164
|
slides,
|
|
@@ -176,6 +190,23 @@ export async function createDeckSession({
|
|
|
176
190
|
return session.slides.length;
|
|
177
191
|
};
|
|
178
192
|
|
|
193
|
+
session.openFile = async (nextFile, { preserveIndex = false } = {}) => {
|
|
194
|
+
const next = await resolveDeckFile(nextFile, session.workspaceRoot);
|
|
195
|
+
const previous = {
|
|
196
|
+
file: session.file,
|
|
197
|
+
sourceName: session.sourceName,
|
|
198
|
+
};
|
|
199
|
+
session.file = next.path;
|
|
200
|
+
session.sourceName = workspaceRelative(next.workspaceRoot, next.path);
|
|
201
|
+
try {
|
|
202
|
+
return await session.load({ preserveIndex });
|
|
203
|
+
} catch (error) {
|
|
204
|
+
session.file = previous.file;
|
|
205
|
+
session.sourceName = previous.sourceName;
|
|
206
|
+
throw error;
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
|
|
179
210
|
session.navigate = (target) => {
|
|
180
211
|
const next = clampIndex(target, session.slides.length);
|
|
181
212
|
if (next === session.index) return false;
|
|
@@ -185,6 +216,6 @@ export async function createDeckSession({
|
|
|
185
216
|
return true;
|
|
186
217
|
};
|
|
187
218
|
|
|
188
|
-
await session.load();
|
|
219
|
+
if (session.file) await session.load();
|
|
189
220
|
return session;
|
|
190
221
|
}
|
|
@@ -707,7 +707,7 @@ function tableXml(element, path, id, relationships) {
|
|
|
707
707
|
return `<p:graphicFrame><p:nvGraphicFramePr><p:cNvPr id="${id}" name="Table ${id}"/><p:cNvGraphicFramePr/><p:nvPr/></p:nvGraphicFramePr>${xfrmXml(bounds, "p:xfrm")}<a:graphic><a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/table"><a:tbl><a:tblPr firstRow="1" bandRow="1"/><a:tblGrid>${columns}</a:tblGrid>${rows}</a:tbl></a:graphicData></a:graphic></p:graphicFrame>`;
|
|
708
708
|
}
|
|
709
709
|
|
|
710
|
-
function arrowXml(value, path) {
|
|
710
|
+
function arrowXml(value, path, end = "tailEnd") {
|
|
711
711
|
if (value === undefined || value === null || value === false || value === "none") {
|
|
712
712
|
return "";
|
|
713
713
|
}
|
|
@@ -722,7 +722,7 @@ function arrowXml(value, path) {
|
|
|
722
722
|
oval: "oval",
|
|
723
723
|
}[value];
|
|
724
724
|
if (!type) fail(`${path} is not a supported arrow end`);
|
|
725
|
-
return `<a
|
|
725
|
+
return `<a:${end} type="${type}"/>`;
|
|
726
726
|
}
|
|
727
727
|
|
|
728
728
|
function connectorXml(element, path, nextId, relationships) {
|
|
@@ -756,6 +756,7 @@ function connectorXml(element, path, nextId, relationships) {
|
|
|
756
756
|
const y = Math.min(start.y, end.y);
|
|
757
757
|
const flipH = end.x < start.x ? ' flipH="1"' : "";
|
|
758
758
|
const flipV = end.y < start.y ? ' flipV="1"' : "";
|
|
759
|
+
const head = index === 0 ? arrowXml(element.arrowStart, `${path}.arrowStart`, "headEnd") : "";
|
|
759
760
|
const tail =
|
|
760
761
|
index === points.length - 2
|
|
761
762
|
? arrowXml(element.arrowEnd, `${path}.arrowEnd`)
|
|
@@ -763,7 +764,7 @@ function connectorXml(element, path, nextId, relationships) {
|
|
|
763
764
|
shapes.push(
|
|
764
765
|
`<p:sp><p:nvSpPr><p:cNvPr id="${id}" name="Connector ${id}"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr><p:spPr><a:xfrm${flipH}${flipV}><a:off x="${emu(x)}" y="${emu(y)}"/><a:ext cx="${emu(Math.abs(end.x - start.x))}" cy="${emu(Math.abs(end.y - start.y))}"/></a:xfrm><a:prstGeom prst="line"><a:avLst/></a:prstGeom><a:ln w="${emu(width)}"><a:solidFill><a:srgbClr val="${color.hex}">${
|
|
765
766
|
alpha < 100000 ? `<a:alpha val="${alpha}"/>` : ""
|
|
766
|
-
}</a:srgbClr></a:solidFill>${dash}${tail}</a:ln></p:spPr></p:sp>`,
|
|
767
|
+
}</a:srgbClr></a:solidFill>${dash}${head}${tail}</a:ln></p:spPr></p:sp>`,
|
|
767
768
|
);
|
|
768
769
|
}
|
|
769
770
|
if (element.label !== undefined) {
|