@markdstage/markdstage 2.3.0 → 2.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/package.json +1 -1
- package/shared/README.md +29 -22
- package/shared/markdstage-guide.mjs +2 -2
- package/shared/renderer/index.html +52 -12
- package/shared/renderer/renderer.js +101 -10
- package/shared/renderer/slides.css +39 -4
- package/src/skills.mjs +39 -19
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markdstage/markdstage",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "Present, validate, inspect, capture, and export MarkdStage Markdown decks from the command line — no Copilot canvas required.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "runceel",
|
package/shared/README.md
CHANGED
|
@@ -27,8 +27,8 @@ Canvas iframe (renderer/)
|
|
|
27
27
|
| highlights language-tagged code fences with highlight.js
|
|
28
28
|
| converts ```mermaid blocks with mermaid.run
|
|
29
29
|
| converts validated ```architecture JSON DSL into a safe SVG DOM
|
|
30
|
-
|
|
|
31
|
-
|
|
|
30
|
+
| keeps ◀ page ▶, ☰, and ⋯ visible in a compact control bar
|
|
31
|
+
| groups editing, presentation, preview, import, refresh, and export under ⋯
|
|
32
32
|
v
|
|
33
33
|
The themed slide is displayed and updates automatically
|
|
34
34
|
```
|
|
@@ -75,26 +75,30 @@ The themed slide is displayed and updates automatically
|
|
|
75
75
|
- Put **speaker notes** in top-level HTML comments on each slide. Presenter view
|
|
76
76
|
renders notes as Markdown and follows navigation. Notes are absent from
|
|
77
77
|
regular slides, the external presenter, and PDF output.
|
|
78
|
-
- The **canvas renderer** owns navigation controls,
|
|
79
|
-
and current position.
|
|
80
|
-
`edit_architecture`. For Markdown loaded
|
|
78
|
+
- The **canvas renderer** owns the compact navigation controls, shape editing,
|
|
79
|
+
the slide list, and current position. **More controls > Shape editing**
|
|
80
|
+
toggles the same placement mode as `edit_architecture`. For Markdown loaded
|
|
81
|
+
with **More controls > Open Markdown**, **Advanced editing** opens
|
|
81
82
|
the dedicated `architecture-editor` canvas. The agent only opens the deck and
|
|
82
83
|
does not run an `ask_user` loop. Margin clicks are installed only in normal
|
|
83
84
|
canvas and presenter modes, never print mode. `goto_slide` remains available
|
|
84
85
|
for an explicit page request from chat.
|
|
85
|
-
- **PDF Export is available from
|
|
86
|
+
- **PDF Export is available from More controls > Export PDF.** When
|
|
87
|
+
`sourceName` is passed
|
|
86
88
|
to open / `load_deck`, the printer saves `<source-name>.pdf` in the workspace.
|
|
87
89
|
It does not load or watch that file. AI may call `export_pdf` with another
|
|
88
90
|
`outputPath`. Hidden print mode renders every page, then headless Edge/Chrome
|
|
89
91
|
produces a 16:9 PDF with backgrounds, images, highlighted code, and Mermaid.
|
|
90
|
-
- **Hybrid editable PowerPoint export is available from
|
|
92
|
+
- **Hybrid editable PowerPoint export is available from More controls > Export
|
|
93
|
+
PowerPoint.** It
|
|
91
94
|
preserves supported text, lists, links, tables, raster images, and Architecture
|
|
92
95
|
DSL objects as native PowerPoint content. Architecture nodes, groups, and
|
|
93
96
|
connector-label pills are visible AutoShapes with integrated text; icons use a
|
|
94
97
|
transparent foreground picture layer. Mermaid and unsupported styling stay
|
|
95
98
|
visible as background artwork and are listed in the export report. AI may call
|
|
96
99
|
`export_pptx` with another workspace-confined `.pptx` path.
|
|
97
|
-
- Use
|
|
100
|
+
- Use **More controls > Output preview** to letterbox the current slide inside
|
|
101
|
+
the canvas with
|
|
98
102
|
the same fixed 1280×720 typography, spacing, diagram limits, and clipping used
|
|
99
103
|
by PDF output. This preview is local to the canvas and does not change deck
|
|
100
104
|
state. A visible and accessible warning identifies content that would be
|
|
@@ -109,7 +113,8 @@ The themed slide is displayed and updates automatically
|
|
|
109
113
|
element hints. Call `capture_slides` only when visual inspection is needed;
|
|
110
114
|
PNGs are fixed 1280×720 files and the action returns paths instead of inline
|
|
111
115
|
image data.
|
|
112
|
-
- Open the **external presenter** with
|
|
116
|
+
- Open the **external presenter** with **More controls > External window** or
|
|
117
|
+
`open_presenter`. Edge / Chrome /
|
|
113
118
|
Chromium starts in a dedicated temporary profile as a movable, resizable
|
|
114
119
|
1280×720 app-mode window. It shares `/state`, `/navigate`, and SSE with the
|
|
115
120
|
canvas, so keyboard and Surface Pen position remain synchronized. Move it to
|
|
@@ -124,12 +129,13 @@ The themed slide is displayed and updates automatically
|
|
|
124
129
|
- Add language names such as `csharp`, `json`, or `diff` to code fences for
|
|
125
130
|
highlight.js syntax highlighting.
|
|
126
131
|
|
|
127
|
-
## Markdown import
|
|
132
|
+
## Markdown import
|
|
128
133
|
|
|
129
|
-
Users can load Markdown directly from the canvas without AI.
|
|
130
|
-
|
|
131
|
-
loads it, splits it into slides, and
|
|
132
|
-
the
|
|
134
|
+
Users can load Markdown directly from the canvas without AI. Select **More
|
|
135
|
+
controls > Open Markdown** or press `I` to open a filtered list of workspace
|
|
136
|
+
`*.md` / `*.markdown` files. Selecting one loads it, splits it into slides, and
|
|
137
|
+
replaces the deck. Import also works from the initial waiting view. The control
|
|
138
|
+
is not shown in presenter or print mode.
|
|
133
139
|
The workspace root is the Git repository root when available, otherwise the
|
|
134
140
|
folder opened for the current session.
|
|
135
141
|
|
|
@@ -728,7 +734,7 @@ should determine placement without writing every coordinate.
|
|
|
728
734
|
### Placement editing
|
|
729
735
|
|
|
730
736
|
Architecture diagrams can be moved directly over the rendered result. For decks
|
|
731
|
-
imported through
|
|
737
|
+
imported through **More controls > Open Markdown**, edits write back to the source `architecture` fence and
|
|
732
738
|
survive re-import. Decks supplied directly through open / `load_deck` cannot be
|
|
733
739
|
reversibly mapped to a source file, so they save only to canvas deck state.
|
|
734
740
|
In the standalone CLI, `markdstage present slides.md --watch` enables the same
|
|
@@ -808,7 +814,7 @@ blocks.
|
|
|
808
814
|
- Saving reloads any MarkdStage canvas or watched CLI presentation showing the
|
|
809
815
|
same Markdown while preserving the current page and theme.
|
|
810
816
|
|
|
811
|
-
Source-backed decks imported through
|
|
817
|
+
Source-backed decks imported through **More controls > Open Markdown** and CLI `present --watch` enable
|
|
812
818
|
**Advanced editing**. Agents can open the editor canvas directly:
|
|
813
819
|
|
|
814
820
|
```json
|
|
@@ -907,7 +913,8 @@ special layout and are never auto-enlarged.
|
|
|
907
913
|
| Long press | `Win+F18` | Previous slide |
|
|
908
914
|
|
|
909
915
|
`Win+F19` double press and pen connection/removal/docking do not launch
|
|
910
|
-
MarkdStage. Start the presenter through
|
|
916
|
+
MarkdStage. Start the presenter through **More controls > External window** or
|
|
917
|
+
explicit `open_presenter`.
|
|
911
918
|
|
|
912
919
|
The Windows setting that lets apps override shortcut-button behavior may remain
|
|
913
920
|
enabled. This implementation listens directly for Windows pen shortcuts rather
|
|
@@ -956,14 +963,14 @@ fails with `invalid_input`; pass the complete `slides` array or call
|
|
|
956
963
|
| `GET /export-data` | Return the token-bound deck snapshot to print, inspection, capture, or PowerPoint mode. |
|
|
957
964
|
| `POST /export-status` | Let headless output modes report rendering status and fixed-layout diagnostics. |
|
|
958
965
|
| `POST /navigate` | Accept absolute `{ index }` or relative `{ delta }`, update position, and notify all clients through SSE. |
|
|
959
|
-
| `POST /present` | Start the presenter from
|
|
960
|
-
| `POST /export` | Start source-named PDF export from
|
|
961
|
-
| `POST /export-pptx` | Start source-named hybrid PowerPoint export from
|
|
966
|
+
| `POST /present` | Start the presenter from **More controls > External window**; accepts same-origin POST only. |
|
|
967
|
+
| `POST /export` | Start source-named PDF export from **More controls > Export PDF**; accepts same-origin POST only. |
|
|
968
|
+
| `POST /export-pptx` | Start source-named hybrid PowerPoint export from **More controls > Export PowerPoint**; accepts same-origin POST only. |
|
|
962
969
|
| `POST /edit` | Write an edited fence with `{ index, block, source }`; returns `409 edit_mode_disabled` when editing is off. |
|
|
963
970
|
| `POST /edit-mode` | Set `{ enabled }`, including for `?architectureEdit=1`; same-origin POST only. |
|
|
964
971
|
| `POST /architecture-editor/open` | Convert source-backed slide/block indexes to a file-wide block index and open Architecture Editor. |
|
|
965
972
|
| `GET /events` | SSE nudge for low-latency `version` changes. |
|
|
966
|
-
| `GET /markdown-files` | Return bounded workspace-relative `*.md` / `*.markdown` paths for
|
|
973
|
+
| `GET /markdown-files` | Return bounded workspace-relative `*.md` / `*.markdown` paths for **Open Markdown**, excluding `.git`, `node_modules`, and dot-prefixed entries. |
|
|
967
974
|
| `POST /import` | Load and split `{ path, sourceMode?: "snapshot" | "live" }`; reject paths outside workspace, wrong extensions, and oversized files. Same-origin POST only. |
|
|
968
975
|
| `POST /source-mode` | Switch a source-backed deck with `{ mode: "snapshot" | "live" }`; entering live mode loads the latest source. |
|
|
969
976
|
|
|
@@ -978,7 +985,7 @@ fails with `invalid_input`; pass the complete `slides` array or call
|
|
|
978
985
|
editor.css # Workspace, tree, and inspector styles
|
|
979
986
|
editor.js # Diagram commands, draft, and explicit-save UI
|
|
980
987
|
copilot-extension.json # Manifest for Gist sharing
|
|
981
|
-
markdown-deck.mjs # Raw Markdown splitting for
|
|
988
|
+
markdown-deck.mjs # Raw Markdown splitting for canvas import
|
|
982
989
|
scripts/
|
|
983
990
|
markdown-blocks.mjs # Scan and replace architecture fences
|
|
984
991
|
markdown-files.mjs # Scan workspace Markdown
|
|
@@ -109,8 +109,8 @@ export async function readGuide(topic = "overview") {
|
|
|
109
109
|
return [
|
|
110
110
|
section(readme, "## How it works"),
|
|
111
111
|
"",
|
|
112
|
-
"Users can load workspace Markdown directly with
|
|
113
|
-
"Use
|
|
112
|
+
"Users can load workspace Markdown directly with **More controls > Open Markdown** (deterministic splitting without AI; natural-language summarization remains the AI's responsibility). The workspace root is the Git repository root when available, otherwise the folder opened for the current session.",
|
|
113
|
+
"Use **More controls > Shape editing** to adjust the placement of an existing Architecture diagram. In the CLI, run `markdstage present slides.md --watch`; it starts in viewing mode and enables the same placement editor plus the detailed Architecture designer. CLI `present` without `--watch` is read-only. Comprehensive edits affect the source Markdown only when explicitly saved.",
|
|
114
114
|
"",
|
|
115
115
|
"For details, request `slide-format`, `themes`, `custom-themes`, `theme-schema`, `architecture-dsl`, or `architecture-schema`.",
|
|
116
116
|
].join("\n");
|
|
@@ -19,18 +19,58 @@
|
|
|
19
19
|
|
|
20
20
|
<!-- In-canvas navigation controls (persist across slide re-renders). -->
|
|
21
21
|
<nav id="nav" class="nav" hidden aria-label="Slide controls">
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
<
|
|
32
|
-
|
|
33
|
-
|
|
22
|
+
<div class="nav-main" role="group" aria-label="Slide navigation">
|
|
23
|
+
<button id="navPrev" class="nav-btn" type="button" title="Previous (←)" aria-label="Previous slide">◀</button>
|
|
24
|
+
<span id="navCounter" class="nav-counter" aria-live="polite"></span>
|
|
25
|
+
<button id="navNext" class="nav-btn" type="button" title="Next (→)" aria-label="Next slide">▶</button>
|
|
26
|
+
<button id="navList" class="nav-btn" type="button" title="Slide list (O)" aria-label="Slide list">☰</button>
|
|
27
|
+
</div>
|
|
28
|
+
<div id="navTools" class="nav-tools">
|
|
29
|
+
<button id="navMore" class="nav-btn nav-more-toggle" type="button" title="More controls" aria-label="More controls" aria-expanded="false" aria-controls="navMorePanel">⋯</button>
|
|
30
|
+
</div>
|
|
31
|
+
<div id="navMorePanel" class="nav-more-panel" role="group" aria-label="More slide controls" hidden>
|
|
32
|
+
<section class="nav-more-group" data-nav-group="present" aria-labelledby="navMorePresentLabel">
|
|
33
|
+
<span id="navMorePresentLabel" class="nav-more-title">Present</span>
|
|
34
|
+
<button id="navPresent" class="nav-more-item" type="button" title="Open in external window (F11 for full screen)" aria-label="Open in external window" hidden>
|
|
35
|
+
<span class="nav-more-icon" aria-hidden="true">⛶</span>
|
|
36
|
+
<span class="nav-more-label">External window</span>
|
|
37
|
+
</button>
|
|
38
|
+
<button id="navPresenterView" class="nav-more-item" type="button" title="Presenter view" aria-label="Presenter view" hidden>
|
|
39
|
+
<span class="nav-more-icon" aria-hidden="true">👤</span>
|
|
40
|
+
<span class="nav-more-label">Presenter view</span>
|
|
41
|
+
</button>
|
|
42
|
+
</section>
|
|
43
|
+
<section class="nav-more-group" data-nav-group="view" aria-labelledby="navMoreViewLabel">
|
|
44
|
+
<span id="navMoreViewLabel" class="nav-more-title">View & edit</span>
|
|
45
|
+
<button id="navEdit" class="nav-more-item" type="button" title="Shape editing mode" aria-label="Shape editing mode" hidden>
|
|
46
|
+
<span class="nav-more-icon" aria-hidden="true">✎</span>
|
|
47
|
+
<span class="nav-more-label">Shape editing</span>
|
|
48
|
+
</button>
|
|
49
|
+
<button id="navFixedPreview" class="nav-more-item" type="button" title="Preview PDF layout at 16:9" aria-label="Preview PDF layout at 16:9" aria-pressed="false">
|
|
50
|
+
<span class="nav-more-icon nav-fixed-preview" aria-hidden="true">16:9</span>
|
|
51
|
+
<span class="nav-more-label">Output preview</span>
|
|
52
|
+
</button>
|
|
53
|
+
</section>
|
|
54
|
+
<section class="nav-more-group" data-nav-group="file" aria-labelledby="navMoreFileLabel">
|
|
55
|
+
<span id="navMoreFileLabel" class="nav-more-title">File</span>
|
|
56
|
+
<button id="navImport" class="nav-more-item" type="button" title="Open Markdown (I)" aria-label="Open Markdown" hidden>
|
|
57
|
+
<span class="nav-more-icon" aria-hidden="true">📁</span>
|
|
58
|
+
<span class="nav-more-label">Open Markdown</span>
|
|
59
|
+
</button>
|
|
60
|
+
<button id="navSourceMode" class="nav-more-item" type="button" title="Automatically refresh when Markdown is saved" aria-label="Automatically refresh when Markdown is saved" hidden>
|
|
61
|
+
<span class="nav-more-icon" aria-hidden="true">↻</span>
|
|
62
|
+
<span class="nav-more-label">Automatic refresh</span>
|
|
63
|
+
</button>
|
|
64
|
+
<button id="navExport" class="nav-more-item" type="button" title="Save as PDF" aria-label="Save as PDF" hidden>
|
|
65
|
+
<span class="nav-more-icon" aria-hidden="true">🖶</span>
|
|
66
|
+
<span class="nav-more-label">Export PDF</span>
|
|
67
|
+
</button>
|
|
68
|
+
<button id="navExportPptx" class="nav-more-item" type="button" title="Save as editable PowerPoint" aria-label="Save as editable PowerPoint" hidden>
|
|
69
|
+
<span class="nav-more-icon" aria-hidden="true">P</span>
|
|
70
|
+
<span class="nav-more-label">Export PowerPoint</span>
|
|
71
|
+
</button>
|
|
72
|
+
</section>
|
|
73
|
+
</div>
|
|
34
74
|
<span id="presentStatus" class="sr-only" aria-live="polite"></span>
|
|
35
75
|
<span id="exportStatus" class="sr-only" aria-live="polite"></span>
|
|
36
76
|
<span id="sourceStatus" class="sr-only" aria-live="polite"></span>
|
|
@@ -98,6 +98,7 @@ let previewMode = false;
|
|
|
98
98
|
let previewOffset = 0;
|
|
99
99
|
let navigationEnabled = true;
|
|
100
100
|
let fixedPreviewMode = false;
|
|
101
|
+
let moreControlsOpen = false;
|
|
101
102
|
// Markdown for the most recently rendered slide, retained for editing-mode rerenders.
|
|
102
103
|
let lastMarkdown = "";
|
|
103
104
|
// Editing UI attached to the rendered slide; destroyed on every rerender.
|
|
@@ -427,12 +428,14 @@ function updateFixedPreviewWarning() {
|
|
|
427
428
|
warning.textContent = "";
|
|
428
429
|
}
|
|
429
430
|
if (button) button.dataset.state = fixedPreviewMode ? "active" : "";
|
|
431
|
+
syncMoreControls();
|
|
430
432
|
return;
|
|
431
433
|
}
|
|
432
434
|
|
|
433
435
|
const diagnostic = collectSlideLayout(layoutTarget, navIndex);
|
|
434
436
|
document.body.classList.toggle("fixed-preview-overflow", diagnostic.pdfClipped);
|
|
435
437
|
if (button) button.dataset.state = diagnostic.pdfClipped ? "error" : "active";
|
|
438
|
+
syncMoreControls();
|
|
436
439
|
if (!warning) return;
|
|
437
440
|
if (!diagnostic.pdfClipped) {
|
|
438
441
|
warning.hidden = true;
|
|
@@ -2141,6 +2144,7 @@ function updateArchitectureEditButton(enabled = architectureEditMode) {
|
|
|
2141
2144
|
button.dataset.state = enabled && !presenterMode ? "active" : "";
|
|
2142
2145
|
button.title = enabled ? "Exit shape editing mode" : "Shape editing mode";
|
|
2143
2146
|
button.setAttribute("aria-label", button.title);
|
|
2147
|
+
syncMoreControls();
|
|
2144
2148
|
}
|
|
2145
2149
|
|
|
2146
2150
|
function sourceWatchErrorMessage(code) {
|
|
@@ -2160,6 +2164,7 @@ function updateSourceModeButton() {
|
|
|
2160
2164
|
if (!sourceBacked) {
|
|
2161
2165
|
button.dataset.state = "";
|
|
2162
2166
|
if (status) status.textContent = "";
|
|
2167
|
+
syncMoreControls();
|
|
2163
2168
|
return;
|
|
2164
2169
|
}
|
|
2165
2170
|
if (sourceMode === "live" && sourceWatchStatus === "error") {
|
|
@@ -2168,6 +2173,7 @@ function updateSourceModeButton() {
|
|
|
2168
2173
|
button.title = `${message}. Click to pin the display to the loaded snapshot`;
|
|
2169
2174
|
button.setAttribute("aria-label", button.title);
|
|
2170
2175
|
if (status) status.textContent = message;
|
|
2176
|
+
syncMoreControls();
|
|
2171
2177
|
return;
|
|
2172
2178
|
}
|
|
2173
2179
|
const live = sourceMode === "live";
|
|
@@ -2181,6 +2187,7 @@ function updateSourceModeButton() {
|
|
|
2181
2187
|
? "Slides refresh automatically when Markdown is saved"
|
|
2182
2188
|
: "Markdown retains the display from the loaded snapshot";
|
|
2183
2189
|
}
|
|
2190
|
+
syncMoreControls();
|
|
2184
2191
|
}
|
|
2185
2192
|
|
|
2186
2193
|
async function requestSourceMode(mode) {
|
|
@@ -2508,6 +2515,7 @@ async function setPresenterRunning(running) {
|
|
|
2508
2515
|
button.dataset.state = "error";
|
|
2509
2516
|
button.title = message;
|
|
2510
2517
|
}
|
|
2518
|
+
syncMoreControls();
|
|
2511
2519
|
} finally {
|
|
2512
2520
|
presenterRequestPending = false;
|
|
2513
2521
|
if (button) button.disabled = false;
|
|
@@ -2522,6 +2530,64 @@ function togglePresenterWindow() {
|
|
|
2522
2530
|
return setPresenterRunning(!presenterRunning);
|
|
2523
2531
|
}
|
|
2524
2532
|
|
|
2533
|
+
function visibleMoreControlButtons() {
|
|
2534
|
+
const panel = document.getElementById("navMorePanel");
|
|
2535
|
+
if (!panel) return [];
|
|
2536
|
+
return [...panel.querySelectorAll(".nav-more-item")].filter((button) => !button.hidden);
|
|
2537
|
+
}
|
|
2538
|
+
|
|
2539
|
+
function setMoreControlsOpen(enabled, { focusFirst = false, restoreFocus = false } = {}) {
|
|
2540
|
+
const nav = document.getElementById("nav");
|
|
2541
|
+
const trigger = document.getElementById("navMore");
|
|
2542
|
+
const panel = document.getElementById("navMorePanel");
|
|
2543
|
+
if (!nav || !trigger || !panel) return;
|
|
2544
|
+
|
|
2545
|
+
const next = Boolean(enabled) && !nav.classList.contains("nav-empty") && !trigger.hidden;
|
|
2546
|
+
moreControlsOpen = next;
|
|
2547
|
+
trigger.setAttribute("aria-expanded", next ? "true" : "false");
|
|
2548
|
+
panel.hidden = nav.classList.contains("nav-empty") ? !markdownImportAvailable : !next;
|
|
2549
|
+
|
|
2550
|
+
if (next && focusFirst) {
|
|
2551
|
+
requestAnimationFrame(() => visibleMoreControlButtons()[0]?.focus());
|
|
2552
|
+
} else if (!next && restoreFocus) {
|
|
2553
|
+
trigger.focus();
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2557
|
+
function toggleMoreControls() {
|
|
2558
|
+
setMoreControlsOpen(!moreControlsOpen, { focusFirst: !moreControlsOpen });
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2561
|
+
function syncMoreControls() {
|
|
2562
|
+
const nav = document.getElementById("nav");
|
|
2563
|
+
const trigger = document.getElementById("navMore");
|
|
2564
|
+
const panel = document.getElementById("navMorePanel");
|
|
2565
|
+
if (!nav || !trigger || !panel) return;
|
|
2566
|
+
|
|
2567
|
+
panel.querySelectorAll(".nav-more-group").forEach((group) => {
|
|
2568
|
+
group.hidden = ![...group.querySelectorAll(".nav-more-item")].some(
|
|
2569
|
+
(button) => !button.hidden,
|
|
2570
|
+
);
|
|
2571
|
+
});
|
|
2572
|
+
|
|
2573
|
+
const buttons = visibleMoreControlButtons();
|
|
2574
|
+
const hasError = buttons.some((button) => button.dataset.state === "error");
|
|
2575
|
+
const hasActive = buttons.some((button) => button.dataset.state === "active");
|
|
2576
|
+
trigger.dataset.state = hasError ? "error" : hasActive ? "active" : "";
|
|
2577
|
+
trigger.title = hasError
|
|
2578
|
+
? "More controls (attention required)"
|
|
2579
|
+
: hasActive
|
|
2580
|
+
? "More controls (an option is active)"
|
|
2581
|
+
: "More controls";
|
|
2582
|
+
trigger.setAttribute("aria-label", trigger.title);
|
|
2583
|
+
|
|
2584
|
+
const empty = nav.classList.contains("nav-empty");
|
|
2585
|
+
trigger.hidden = empty || buttons.length === 0;
|
|
2586
|
+
if (trigger.hidden) moreControlsOpen = false;
|
|
2587
|
+
trigger.setAttribute("aria-expanded", moreControlsOpen ? "true" : "false");
|
|
2588
|
+
panel.hidden = empty ? !markdownImportAvailable : !moreControlsOpen;
|
|
2589
|
+
}
|
|
2590
|
+
|
|
2525
2591
|
function updateHostActionButtons() {
|
|
2526
2592
|
const present = document.getElementById("navPresent");
|
|
2527
2593
|
if (present) present.hidden = presenterMode || !presenterWindowAvailable;
|
|
@@ -2537,6 +2603,7 @@ function updateHostActionButtons() {
|
|
|
2537
2603
|
if (importButton) importButton.hidden = presenterMode || !markdownImportAvailable;
|
|
2538
2604
|
if (!presenterViewAvailable && presenterViewOpen) closePresenterView();
|
|
2539
2605
|
if (!markdownImportAvailable && importOpen) closeImportPicker();
|
|
2606
|
+
syncMoreControls();
|
|
2540
2607
|
}
|
|
2541
2608
|
|
|
2542
2609
|
function updatePresenterButton(running, message = "") {
|
|
@@ -2553,6 +2620,7 @@ function updatePresenterButton(running, message = "") {
|
|
|
2553
2620
|
toggle.textContent = running ? "End presentation" : "Start presentation";
|
|
2554
2621
|
toggle.dataset.state = running ? "active" : "";
|
|
2555
2622
|
}
|
|
2623
|
+
syncMoreControls();
|
|
2556
2624
|
}
|
|
2557
2625
|
|
|
2558
2626
|
async function exportPdfFromCanvas() {
|
|
@@ -2582,6 +2650,7 @@ async function exportPdfFromCanvas() {
|
|
|
2582
2650
|
button.dataset.state = "active";
|
|
2583
2651
|
button.title = message;
|
|
2584
2652
|
}
|
|
2653
|
+
syncMoreControls();
|
|
2585
2654
|
} catch (error) {
|
|
2586
2655
|
const message = error?.message || "Could not save the PDF.";
|
|
2587
2656
|
console.error("PDF export failed", error);
|
|
@@ -2590,6 +2659,7 @@ async function exportPdfFromCanvas() {
|
|
|
2590
2659
|
button.dataset.state = "error";
|
|
2591
2660
|
button.title = message;
|
|
2592
2661
|
}
|
|
2662
|
+
syncMoreControls();
|
|
2593
2663
|
} finally {
|
|
2594
2664
|
pdfExportPending = false;
|
|
2595
2665
|
if (button) button.disabled = false;
|
|
@@ -2626,6 +2696,7 @@ async function exportPptxFromCanvas() {
|
|
|
2626
2696
|
button.dataset.state = "active";
|
|
2627
2697
|
button.title = message;
|
|
2628
2698
|
}
|
|
2699
|
+
syncMoreControls();
|
|
2629
2700
|
} catch (error) {
|
|
2630
2701
|
const message = error?.message || "Could not save the PowerPoint presentation.";
|
|
2631
2702
|
console.error("PowerPoint export failed", error);
|
|
@@ -2634,6 +2705,7 @@ async function exportPptxFromCanvas() {
|
|
|
2634
2705
|
button.dataset.state = "error";
|
|
2635
2706
|
button.title = message;
|
|
2636
2707
|
}
|
|
2708
|
+
syncMoreControls();
|
|
2637
2709
|
} finally {
|
|
2638
2710
|
pptxExportPending = false;
|
|
2639
2711
|
if (button) button.disabled = false;
|
|
@@ -2653,6 +2725,7 @@ function setFixedPreviewMode(enabled) {
|
|
|
2653
2725
|
? "Return to responsive canvas layout"
|
|
2654
2726
|
: "Preview PDF layout at 16:9";
|
|
2655
2727
|
}
|
|
2728
|
+
syncMoreControls();
|
|
2656
2729
|
if (fixedPreviewMode) {
|
|
2657
2730
|
updateFixedPreviewScale();
|
|
2658
2731
|
} else {
|
|
@@ -2687,6 +2760,7 @@ function updateNav() {
|
|
|
2687
2760
|
if (next) next.disabled = navMode === "deck" && navIndex >= navTotal - 1;
|
|
2688
2761
|
highlightOverview();
|
|
2689
2762
|
updatePresenterView();
|
|
2763
|
+
syncMoreControls();
|
|
2690
2764
|
}
|
|
2691
2765
|
|
|
2692
2766
|
function openPresenterView() {
|
|
@@ -2711,7 +2785,7 @@ function closePresenterView() {
|
|
|
2711
2785
|
document.body.classList.remove("presenter-view-mode");
|
|
2712
2786
|
const view = document.getElementById("presenterView");
|
|
2713
2787
|
if (view) view.hidden = true;
|
|
2714
|
-
document.getElementById("
|
|
2788
|
+
document.getElementById("navMore")?.focus();
|
|
2715
2789
|
}
|
|
2716
2790
|
|
|
2717
2791
|
function updatePresenterView() {
|
|
@@ -3035,10 +3109,11 @@ function wirePreviewKeyboardNavigation() {
|
|
|
3035
3109
|
}
|
|
3036
3110
|
|
|
3037
3111
|
function wireControls() {
|
|
3038
|
-
const bind = (id, fn) => {
|
|
3112
|
+
const bind = (id, fn, { closeMore = false } = {}) => {
|
|
3039
3113
|
const el = document.getElementById(id);
|
|
3040
3114
|
if (!el) return;
|
|
3041
3115
|
el.addEventListener("click", () => {
|
|
3116
|
+
if (closeMore) setMoreControlsOpen(false);
|
|
3042
3117
|
fn();
|
|
3043
3118
|
// Drop focus so a follow-up Space/Enter doesn't re-trigger the button on
|
|
3044
3119
|
// top of the global keyboard handler.
|
|
@@ -3047,15 +3122,16 @@ function wireControls() {
|
|
|
3047
3122
|
};
|
|
3048
3123
|
bind("navPrev", goPrev);
|
|
3049
3124
|
bind("navNext", goNext);
|
|
3050
|
-
bind("navEdit", toggleArchitectureEditMode);
|
|
3051
|
-
bind("navPresent", openPresenterWindow);
|
|
3052
|
-
bind("navPresenterView", openPresenterView);
|
|
3053
|
-
bind("navFixedPreview", toggleFixedPreviewMode);
|
|
3054
|
-
bind("navExport", exportPdfFromCanvas);
|
|
3055
|
-
bind("navExportPptx", exportPptxFromCanvas);
|
|
3056
|
-
bind("navImport", toggleImportPicker);
|
|
3057
|
-
bind("navSourceMode", toggleSourceMode);
|
|
3058
3125
|
bind("navList", toggleOverview);
|
|
3126
|
+
bind("navMore", toggleMoreControls);
|
|
3127
|
+
bind("navEdit", toggleArchitectureEditMode, { closeMore: true });
|
|
3128
|
+
bind("navPresent", openPresenterWindow, { closeMore: true });
|
|
3129
|
+
bind("navPresenterView", openPresenterView, { closeMore: true });
|
|
3130
|
+
bind("navFixedPreview", toggleFixedPreviewMode, { closeMore: true });
|
|
3131
|
+
bind("navExport", exportPdfFromCanvas, { closeMore: true });
|
|
3132
|
+
bind("navExportPptx", exportPptxFromCanvas, { closeMore: true });
|
|
3133
|
+
bind("navImport", toggleImportPicker, { closeMore: true });
|
|
3134
|
+
bind("navSourceMode", toggleSourceMode, { closeMore: true });
|
|
3059
3135
|
bind("overviewClose", closeOverview);
|
|
3060
3136
|
bind("importClose", closeImportPicker);
|
|
3061
3137
|
bind("presenterPrevButton", goPrev);
|
|
@@ -3083,6 +3159,14 @@ function wireControls() {
|
|
|
3083
3159
|
});
|
|
3084
3160
|
}
|
|
3085
3161
|
|
|
3162
|
+
document.addEventListener("pointerdown", (e) => {
|
|
3163
|
+
if (!moreControlsOpen) return;
|
|
3164
|
+
const tools = document.getElementById("navTools");
|
|
3165
|
+
const panel = document.getElementById("navMorePanel");
|
|
3166
|
+
if (tools?.contains(e.target) || panel?.contains(e.target)) return;
|
|
3167
|
+
setMoreControlsOpen(false);
|
|
3168
|
+
});
|
|
3169
|
+
|
|
3086
3170
|
wirePointerNavigation();
|
|
3087
3171
|
|
|
3088
3172
|
// The iframe must be focused to receive key events; grab focus up front and
|
|
@@ -3105,16 +3189,23 @@ function wireControls() {
|
|
|
3105
3189
|
e.preventDefault();
|
|
3106
3190
|
return;
|
|
3107
3191
|
}
|
|
3192
|
+
if (e.key === "Escape" && moreControlsOpen) {
|
|
3193
|
+
setMoreControlsOpen(false, { restoreFocus: true });
|
|
3194
|
+
e.preventDefault();
|
|
3195
|
+
return;
|
|
3196
|
+
}
|
|
3108
3197
|
if (handleSlideNavigationKey(e)) return;
|
|
3109
3198
|
if (t && (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.isContentEditable)) return;
|
|
3110
3199
|
switch (e.key) {
|
|
3111
3200
|
case "o":
|
|
3112
3201
|
case "O":
|
|
3202
|
+
setMoreControlsOpen(false);
|
|
3113
3203
|
toggleOverview();
|
|
3114
3204
|
e.preventDefault();
|
|
3115
3205
|
break;
|
|
3116
3206
|
case "i":
|
|
3117
3207
|
case "I":
|
|
3208
|
+
setMoreControlsOpen(false);
|
|
3118
3209
|
toggleImportPicker();
|
|
3119
3210
|
e.preventDefault();
|
|
3120
3211
|
break;
|
|
@@ -389,6 +389,8 @@ footer .page{color:var(--accent-strong);font-weight:600;background:var(--accent-
|
|
|
389
389
|
opacity:.45;transition:opacity .2s ease;}
|
|
390
390
|
.nav:hover,.nav:focus-within{opacity:1;}
|
|
391
391
|
@media (prefers-reduced-motion:reduce){.nav{transition:none;}}
|
|
392
|
+
.nav-main{display:flex;align-items:center;gap:.4em;}
|
|
393
|
+
.nav-tools{position:relative;display:flex;align-items:center;}
|
|
392
394
|
.nav-btn{display:inline-flex;align-items:center;justify-content:center;
|
|
393
395
|
min-width:2em;height:2em;padding:0 .55em;
|
|
394
396
|
font-size:clamp(13px,1.9vh,17px);line-height:1;cursor:pointer;
|
|
@@ -400,10 +402,37 @@ footer .page{color:var(--accent-strong);font-weight:600;background:var(--accent-
|
|
|
400
402
|
.nav-btn:disabled{opacity:.32;cursor:default;}
|
|
401
403
|
.nav-btn[data-state="active"]{color:var(--accent-strong);background:var(--accent-soft);}
|
|
402
404
|
.nav-btn[data-state="error"]{color:#d13438;}
|
|
403
|
-
.nav-
|
|
405
|
+
.nav-more-toggle{position:relative;}
|
|
406
|
+
.nav-more-toggle[data-state]::after{content:"";position:absolute;right:.22em;top:.22em;
|
|
407
|
+
width:.38em;height:.38em;border-radius:50%;background:transparent;}
|
|
408
|
+
.nav-more-toggle[data-state="active"]::after{background:var(--accent-strong);}
|
|
409
|
+
.nav-more-toggle[data-state="error"]::after{background:#d13438;}
|
|
404
410
|
.nav-counter{min-width:4.2em;text-align:center;font-variant-numeric:tabular-nums;
|
|
405
411
|
font-weight:600;color:var(--accent-strong);font-size:clamp(12px,1.7vh,16px);
|
|
406
412
|
padding:0 .3em;user-select:none;}
|
|
413
|
+
.nav-more-panel[hidden],.nav-more-group[hidden]{display:none;}
|
|
414
|
+
.nav-more-panel{position:absolute;left:50%;bottom:calc(100% + .65em);transform:translateX(-50%);
|
|
415
|
+
width:min(20em,calc(100vw - 24px));max-height:min(70vh,32em);overflow:auto;
|
|
416
|
+
display:grid;gap:.7em;padding:.7em;border:1px solid var(--border);border-radius:12px;
|
|
417
|
+
color:var(--fg);background:color-mix(in srgb,var(--surface) 96%,transparent);
|
|
418
|
+
box-shadow:0 12px 34px rgba(0,0,0,.3);}
|
|
419
|
+
.nav-more-group{display:grid;gap:.18em;}
|
|
420
|
+
.nav-more-title{padding:.2em .65em;color:var(--muted);font-size:.72em;font-weight:750;
|
|
421
|
+
letter-spacing:.06em;text-transform:uppercase;}
|
|
422
|
+
.nav-more-item{display:grid;grid-template-columns:2.8em minmax(0,1fr);align-items:center;
|
|
423
|
+
width:100%;min-height:2.55em;padding:.42em .65em;border:1px solid transparent;
|
|
424
|
+
border-radius:8px;color:var(--fg);background:transparent;font:inherit;text-align:left;
|
|
425
|
+
cursor:pointer;}
|
|
426
|
+
.nav-more-item[hidden]{display:none;}
|
|
427
|
+
.nav-more-item:hover:not(:disabled){background:var(--accent-soft);color:var(--accent-strong);}
|
|
428
|
+
.nav-more-item:focus-visible{outline:2px solid var(--accent);outline-offset:1px;}
|
|
429
|
+
.nav-more-item:disabled{opacity:.4;cursor:default;}
|
|
430
|
+
.nav-more-item[data-state="active"]{color:var(--accent-strong);background:var(--accent-soft);}
|
|
431
|
+
.nav-more-item[data-state="error"]{color:#d13438;}
|
|
432
|
+
.nav-more-icon{display:inline-flex;align-items:center;justify-content:center;min-width:2.2em;
|
|
433
|
+
font-size:.95em;font-weight:700;}
|
|
434
|
+
.nav-more-icon.nav-fixed-preview{font-size:.72em;}
|
|
435
|
+
.nav-more-label{min-width:0;font-size:.88em;font-weight:600;line-height:1.25;}
|
|
407
436
|
.layout-warning{position:fixed;top:14px;right:14px;z-index:55;
|
|
408
437
|
max-width:min(420px,calc(100vw - 28px));padding:.55em .8em;border-radius:8px;
|
|
409
438
|
color:#fff;background:#a4262c;border:1px solid #f1aeb5;
|
|
@@ -418,14 +447,20 @@ body.presenter-mode #navImport{display:none;}
|
|
|
418
447
|
body.presenter-mode #navSourceMode{display:none;}
|
|
419
448
|
body.preview-mode .nav,body.preview-mode .overview{display:none!important;}
|
|
420
449
|
/* When no deck is loaded, leave only the load button as the available action. */
|
|
421
|
-
.nav.nav-empty
|
|
450
|
+
.nav.nav-empty .nav-main,.nav.nav-empty #navMore{display:none;}
|
|
451
|
+
.nav.nav-empty .nav-more-panel{position:static;display:block;width:auto;max-height:none;
|
|
452
|
+
overflow:visible;padding:0;border:0;border-radius:0;transform:none;background:transparent;
|
|
453
|
+
box-shadow:none;}
|
|
454
|
+
.nav.nav-empty .nav-more-group,.nav.nav-empty .nav-more-title,
|
|
455
|
+
.nav.nav-empty .nav-more-item{display:none;}
|
|
456
|
+
.nav.nav-empty .nav-more-group[data-nav-group="file"]{display:block;}
|
|
457
|
+
.nav.nav-empty #navImport{display:inline-flex;gap:.55em;width:auto;min-width:2.5em;height:2.5em;
|
|
458
|
+
padding:0 1em;}
|
|
422
459
|
body.markdstage-empty .nav{
|
|
423
460
|
opacity:1;padding:.42em;border-color:#3b4562;background:#151b2f;
|
|
424
461
|
box-shadow:0 10px 30px rgba(0,0,0,.36);}
|
|
425
462
|
body.markdstage-empty #navImport{
|
|
426
|
-
width:auto;min-width:2.5em;height:2.5em;padding:0 1em;
|
|
427
463
|
border-color:#ffb547;background:#ffb547;color:#0b1020;font-weight:750;}
|
|
428
|
-
body.markdstage-empty #navImport::after{content:" Open Markdown";}
|
|
429
464
|
body.markdstage-empty #navImport:hover{background:#ffd77a;color:#0b1020;}
|
|
430
465
|
|
|
431
466
|
/* ===== Presenter view ===== */
|
package/src/skills.mjs
CHANGED
|
@@ -24,11 +24,12 @@ export const SKILL_TARGETS = {
|
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
const DESCRIPTION =
|
|
27
|
-
"Turn Markdown into 16:9 slides with the MarkdStage CLI. Use when the user " +
|
|
28
|
-
|
|
29
|
-
'"turn this file into slides", "export the deck to PDF
|
|
30
|
-
"
|
|
31
|
-
"browser,
|
|
27
|
+
"Turn Markdown into 16:9 slides with the MarkdStage CLI. Use when the user asks to create, " +
|
|
28
|
+
"refine, present, preview, validate, inspect, screenshot, or export a Markdown deck " +
|
|
29
|
+
'("present slides.md", "turn this file into slides", "export the deck to PDF or PowerPoint", ' +
|
|
30
|
+
'"check whether my slides fit"). Provides a deterministic create-review-deliver workflow, ' +
|
|
31
|
+
"browser-based Architecture DSL editing, theme validation, 1280x720 clipping diagnostics, " +
|
|
32
|
+
"targeted PNG capture, and PDF/PowerPoint export.";
|
|
32
33
|
|
|
33
34
|
function frontMatter(fields) {
|
|
34
35
|
const lines = ["---"];
|
|
@@ -66,32 +67,51 @@ what the MarkdStage canvas and MarkdStage Desktop render.
|
|
|
66
67
|
- An installed Microsoft Edge, Google Chrome, or Chromium (never downloaded automatically).
|
|
67
68
|
- The CLI: \`npx @markdstage/markdstage <command>\` or \`npm install --global @markdstage/markdstage\`.
|
|
68
69
|
|
|
69
|
-
##
|
|
70
|
-
|
|
71
|
-
1.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
70
|
+
## Recommended authoring workflow
|
|
71
|
+
|
|
72
|
+
1. Establish the source material, audience, objective, approximate length, theme,
|
|
73
|
+
required diagrams, and output format.
|
|
74
|
+
2. Read only the relevant guidance. Start with
|
|
75
|
+
\`markdstage guide slide-format\`, then retrieve \`themes\`,
|
|
76
|
+
\`custom-themes\`, or \`architecture-dsl\` when needed.
|
|
77
|
+
3. Create the complete deck as one Markdown source file (see
|
|
78
|
+
\`references/slide-format.md\`).
|
|
79
|
+
4. Validate structure, themes, and Architecture DSL before visual review:
|
|
80
|
+
\`markdstage validate slides.md --json\`.
|
|
81
|
+
5. Use \`markdstage present slides.md --watch\` for live source-backed authoring.
|
|
82
|
+
It reloads on save without losing the current slide and keeps the last valid
|
|
83
|
+
deck while a save is incomplete.
|
|
84
|
+
6. Check fixed 16:9 output with \`markdstage inspect slides.md --json\`. Use
|
|
85
|
+
\`--slide <n>\` after localized changes and \`--fail-on-issues\` in CI.
|
|
86
|
+
7. Run \`markdstage capture slides.md\` only after inspection. Without
|
|
87
|
+
\`--pages\`, it captures only clipped slides; use \`--pages 2,4\` for pages
|
|
88
|
+
whose balance, spacing, or diagrams need visual judgment.
|
|
89
|
+
8. Revise Markdown and repeat validation plus targeted inspection until the deck
|
|
90
|
+
is valid, unclipped, concise, and visually balanced.
|
|
91
|
+
9. Deliver from the same source with \`markdstage presentation slides.md\`,
|
|
92
|
+
\`markdstage export slides.md --output slides.pdf\`, or
|
|
93
|
+
\`markdstage export slides.md --output slides.pptx\`.
|
|
94
|
+
|
|
95
|
+
The browser in \`present --watch\` starts in viewing mode. The user can activate
|
|
96
|
+
the pencil control to move Architecture elements, then choose **Advanced edit**
|
|
97
|
+
for the detailed designer. Placement changes save immediately, while the
|
|
80
98
|
detailed designer saves only when the user selects **Save**. \`present\` without
|
|
81
99
|
\`--watch\` is read-only.
|
|
82
100
|
|
|
83
101
|
Never hand-write HTML or CSS for a slide. Fix layout problems by shortening the
|
|
84
|
-
content or by changing the layout in front matter.
|
|
102
|
+
content or by changing the layout in front matter. Prefer structured validation
|
|
103
|
+
and layout diagnostics over capturing every slide.
|
|
85
104
|
|
|
86
105
|
## Commands
|
|
87
106
|
|
|
88
107
|
| Command | Purpose |
|
|
89
108
|
| --- | --- |
|
|
109
|
+
| \`markdstage presentation <file> [--watch]\` | Open presenter view with the current slide, next-slide preview, speaker notes, and controls for a synchronized audience window. |
|
|
90
110
|
| \`markdstage present <file> [--watch]\` | Serve the deck on loopback and open it in a browser window. \`--watch\` reloads on save, keeps the current slide, and enables Architecture placement and detailed editing. Without it, the source is read-only. |
|
|
91
111
|
| \`markdstage validate <file> [--json]\` | Check deck structure, Architecture DSL blocks, and themes. |
|
|
92
|
-
| \`markdstage inspect <file> [--json]\` | Report 1280x720 clipping diagnostics for the deck or one slide. |
|
|
112
|
+
| \`markdstage inspect <file> [--json]\` | Report 1280x720 clipping diagnostics for the deck or one slide; use \`--fail-on-issues\` for quality gates. |
|
|
93
113
|
| \`markdstage capture <file> [--pages 2,4]\` | Write 1280x720 PNG files; without \`--pages\` only clipped slides are captured. |
|
|
94
|
-
| \`markdstage export <file> [--output slides.pdf]\` | Produce
|
|
114
|
+
| \`markdstage export <file> [--output slides.pdf|slides.pptx]\` | Produce a 16:9 PDF or hybrid editable PowerPoint. |
|
|
95
115
|
| \`markdstage guide <topic>\` | Print the canonical MarkdStage authoring guide. |
|
|
96
116
|
|
|
97
117
|
Exit codes: \`0\` success, \`1\` usage error, \`2\` deck or input error, \`3\` no
|