@markdstage/markdstage 0.1.3 → 2.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/README.md +21 -6
- package/package.json +3 -1
- package/shared/README.md +29 -12
- package/shared/architecture-editor/editor.css +155 -0
- package/shared/architecture-editor/editor.js +1775 -0
- package/shared/architecture-editor/index.html +99 -0
- package/shared/markdstage-guide.mjs +1 -1
- package/shared/renderer/architecture.mjs +233 -0
- package/shared/renderer/index.html +7 -6
- package/shared/renderer/renderer.js +1163 -16
- package/shared/renderer/slides.css +42 -2
- package/shared/runtime/architecture-editor-server.mjs +651 -0
- package/shared/runtime/architecture-source.mjs +195 -0
- package/shared/runtime/browser.mjs +71 -4
- package/shared/runtime/deck-session.mjs +3 -1
- package/shared/runtime/output-paths.mjs +18 -1
- package/shared/runtime/output.mjs +267 -4
- package/shared/runtime/pptx-package.mjs +1088 -0
- package/shared/runtime/presentation-server.mjs +268 -12
- package/src/cli.mjs +42 -4
- package/src/commands/export.mjs +31 -11
- package/src/commands/present.mjs +138 -49
- package/src/deck.mjs +2 -0
- package/src/runtime.mjs +8 -2
- package/src/skills.mjs +9 -3
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Architecture Editor</title>
|
|
7
|
+
<link rel="stylesheet" href="./renderer/slides.css">
|
|
8
|
+
<link rel="stylesheet" href="./editor/editor.css">
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<header class="editor-header">
|
|
12
|
+
<div class="editor-title">
|
|
13
|
+
<strong>Architecture Editor</strong>
|
|
14
|
+
<span id="sourceLabel"></span>
|
|
15
|
+
<span id="dirtyBadge" class="dirty-badge" hidden>Unsaved</span>
|
|
16
|
+
</div>
|
|
17
|
+
<div class="editor-actions" role="toolbar" aria-label="Architecture editing controls">
|
|
18
|
+
<button type="button" data-action="undo" title="Undo (Ctrl+Z)">↶</button>
|
|
19
|
+
<button type="button" data-action="redo" title="Redo (Ctrl+Y)">↷</button>
|
|
20
|
+
<span class="toolbar-separator" aria-hidden="true"></span>
|
|
21
|
+
<button type="button" data-action="add-node">Node</button>
|
|
22
|
+
<button type="button" data-action="add-group">Group</button>
|
|
23
|
+
<button type="button" data-action="add-image">Image</button>
|
|
24
|
+
<button type="button" data-action="add-connector">Connector</button>
|
|
25
|
+
<button type="button" data-action="duplicate" title="Duplicate (Ctrl+D)">Duplicate</button>
|
|
26
|
+
<button type="button" data-action="delete" title="Delete (Delete)">Delete</button>
|
|
27
|
+
<button type="button" data-action="release-layout">Release layout</button>
|
|
28
|
+
<span class="toolbar-separator" aria-hidden="true"></span>
|
|
29
|
+
<button type="button" data-action="order-back" title="Move earlier in declaration order">Send backward</button>
|
|
30
|
+
<button type="button" data-action="order-front" title="Move later in declaration order">Bring forward</button>
|
|
31
|
+
<span class="toolbar-separator" aria-hidden="true"></span>
|
|
32
|
+
<button type="button" data-action="zoom-out" aria-label="Zoom out">−</button>
|
|
33
|
+
<button type="button" data-action="zoom-fit">Fit</button>
|
|
34
|
+
<button type="button" data-action="zoom-in" aria-label="Zoom in">+</button>
|
|
35
|
+
<label class="snap-toggle"><input id="snapToggle" type="checkbox" checked> Grid</label>
|
|
36
|
+
<button type="button" class="save-button" data-action="save" title="Save to Markdown (Ctrl+S)">Save</button>
|
|
37
|
+
<button type="button" data-action="reload" title="Reload from Markdown">Reload</button>
|
|
38
|
+
</div>
|
|
39
|
+
</header>
|
|
40
|
+
|
|
41
|
+
<main class="editor-layout">
|
|
42
|
+
<aside class="editor-sidebar element-panel" aria-label="Elements">
|
|
43
|
+
<h2>Elements</h2>
|
|
44
|
+
<div id="elementTree" class="element-tree" role="tree"></div>
|
|
45
|
+
</aside>
|
|
46
|
+
|
|
47
|
+
<section id="viewport" class="editor-viewport" aria-label="Drawing area">
|
|
48
|
+
<div id="canvasSurface" class="canvas-surface"></div>
|
|
49
|
+
</section>
|
|
50
|
+
|
|
51
|
+
<aside class="editor-sidebar inspector-panel" aria-label="Properties">
|
|
52
|
+
<h2>Properties</h2>
|
|
53
|
+
<form id="inspector" class="inspector" autocomplete="off"></form>
|
|
54
|
+
</aside>
|
|
55
|
+
</main>
|
|
56
|
+
|
|
57
|
+
<footer class="editor-footer">
|
|
58
|
+
<span id="status" role="status" aria-live="polite">Loading…</span>
|
|
59
|
+
<span id="zoomStatus">100%</span>
|
|
60
|
+
</footer>
|
|
61
|
+
|
|
62
|
+
<div id="contextMenu" class="context-menu" role="menu" aria-label="Editing actions" tabindex="-1" hidden></div>
|
|
63
|
+
|
|
64
|
+
<dialog id="assetDialog" class="asset-dialog" aria-labelledby="assetDialogTitle">
|
|
65
|
+
<div class="asset-dialog-header">
|
|
66
|
+
<div>
|
|
67
|
+
<h2 id="assetDialogTitle">Select an image</h2>
|
|
68
|
+
<p>Select an image from the workspace assets/ directory or import one from your PC.</p>
|
|
69
|
+
</div>
|
|
70
|
+
<button id="assetDialogClose" type="button" aria-label="Close image selection">×</button>
|
|
71
|
+
</div>
|
|
72
|
+
<div class="asset-dialog-tools">
|
|
73
|
+
<label for="assetSearch">Search images</label>
|
|
74
|
+
<input id="assetSearch" type="search" placeholder="File name or path">
|
|
75
|
+
<button id="assetImportButton" type="button">Import from PC</button>
|
|
76
|
+
<input id="assetFileInput" type="file" accept=".svg,.png,.webp,.jpg,.jpeg,image/svg+xml,image/png,image/webp,image/jpeg" hidden>
|
|
77
|
+
</div>
|
|
78
|
+
<div class="asset-dialog-body">
|
|
79
|
+
<div id="assetList" class="asset-list" role="listbox" aria-label="Available images"></div>
|
|
80
|
+
<div class="asset-preview">
|
|
81
|
+
<div class="asset-preview-frame">
|
|
82
|
+
<div id="assetPreviewImageHost"></div>
|
|
83
|
+
<span id="assetPreviewEmpty">Select an image</span>
|
|
84
|
+
</div>
|
|
85
|
+
<p id="assetPreviewPath"></p>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
<div class="asset-dialog-footer">
|
|
89
|
+
<span id="assetDialogStatus" role="status" aria-live="polite"></span>
|
|
90
|
+
<div>
|
|
91
|
+
<button id="assetCancelButton" type="button">Cancel</button>
|
|
92
|
+
<button id="assetChooseButton" class="save-button" type="button" disabled>Select</button>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
</dialog>
|
|
96
|
+
|
|
97
|
+
<script type="module" src="./editor/editor.js"></script>
|
|
98
|
+
</body>
|
|
99
|
+
</html>
|
|
@@ -110,7 +110,7 @@ export async function readGuide(topic = "overview") {
|
|
|
110
110
|
section(readme, "## How it works"),
|
|
111
111
|
"",
|
|
112
112
|
"Users can load workspace Markdown directly with the canvas 📂 button (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 MarkdStage's ✎ control to adjust the placement of an existing Architecture diagram.
|
|
113
|
+
"Use MarkdStage's ✎ control 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");
|
|
@@ -3826,6 +3826,233 @@ export function architectureSemanticSnapshot(model) {
|
|
|
3826
3826
|
};
|
|
3827
3827
|
}
|
|
3828
3828
|
|
|
3829
|
+
/**
|
|
3830
|
+
* Preserve the normalized DSL and final routed geometry for hybrid PowerPoint
|
|
3831
|
+
* export. Coordinates remain in Architecture canvas units here; renderer.js
|
|
3832
|
+
* maps them through the rendered SVG viewBox into slide pixels.
|
|
3833
|
+
*/
|
|
3834
|
+
export function architecturePowerPointSnapshot(model, documentRef = globalThis.document) {
|
|
3835
|
+
const lookup = new Map(
|
|
3836
|
+
model.elements
|
|
3837
|
+
.filter((element) => element.type !== "connector")
|
|
3838
|
+
.map((element) => [element.id, element]),
|
|
3839
|
+
);
|
|
3840
|
+
const { routes, diagnostics } = planConnectorRoutes(model, lookup);
|
|
3841
|
+
const objects = [];
|
|
3842
|
+
const fallbacks = [];
|
|
3843
|
+
const icons = [];
|
|
3844
|
+
const frontLabels = [];
|
|
3845
|
+
const source = (element, kind = element.type) => ({
|
|
3846
|
+
kind,
|
|
3847
|
+
id: element.id || "",
|
|
3848
|
+
sourcePath: element.sourcePath,
|
|
3849
|
+
order: element.order,
|
|
3850
|
+
z: element.z,
|
|
3851
|
+
});
|
|
3852
|
+
const style = (element) => ({
|
|
3853
|
+
fill: element.style.fill,
|
|
3854
|
+
stroke: element.style.stroke,
|
|
3855
|
+
strokeWidth: element.style.strokeWidth,
|
|
3856
|
+
dash: element.style.dash,
|
|
3857
|
+
opacity: element.style.opacity,
|
|
3858
|
+
cornerRadius: element.style.cornerRadius,
|
|
3859
|
+
});
|
|
3860
|
+
const shapeText = (element, text, options = {}) =>
|
|
3861
|
+
text
|
|
3862
|
+
? {
|
|
3863
|
+
paragraphs: [
|
|
3864
|
+
{
|
|
3865
|
+
alignment: options.alignment || "center",
|
|
3866
|
+
runs: [
|
|
3867
|
+
{
|
|
3868
|
+
text,
|
|
3869
|
+
fontSize: options.fontSize || element.style.fontSize,
|
|
3870
|
+
fontWeight: options.fontWeight || 600,
|
|
3871
|
+
bold: (options.fontWeight || 600) >= 600,
|
|
3872
|
+
color: element.style.textColor,
|
|
3873
|
+
},
|
|
3874
|
+
],
|
|
3875
|
+
},
|
|
3876
|
+
],
|
|
3877
|
+
}
|
|
3878
|
+
: undefined;
|
|
3879
|
+
|
|
3880
|
+
for (const element of model.elements) {
|
|
3881
|
+
if (element.type === "group") {
|
|
3882
|
+
objects.push({
|
|
3883
|
+
type: "shape",
|
|
3884
|
+
shape: "roundedRect",
|
|
3885
|
+
x: element.x,
|
|
3886
|
+
y: element.y,
|
|
3887
|
+
width: element.width,
|
|
3888
|
+
height: element.height,
|
|
3889
|
+
...style(element),
|
|
3890
|
+
text: shapeText(element, element.title, {
|
|
3891
|
+
alignment: "left",
|
|
3892
|
+
fontWeight: 700,
|
|
3893
|
+
}),
|
|
3894
|
+
verticalAlignment: "top",
|
|
3895
|
+
textWrap: "none",
|
|
3896
|
+
textInsets: {
|
|
3897
|
+
left: 24,
|
|
3898
|
+
top: Math.max(0, element.style.fontSize * 0.2),
|
|
3899
|
+
right: 24,
|
|
3900
|
+
bottom: 0,
|
|
3901
|
+
},
|
|
3902
|
+
architecture: source(element),
|
|
3903
|
+
});
|
|
3904
|
+
continue;
|
|
3905
|
+
}
|
|
3906
|
+
|
|
3907
|
+
if (element.type === "node") {
|
|
3908
|
+
const iconSize = element.icon
|
|
3909
|
+
? Math.min(58, element.height * 0.36, element.width * 0.2)
|
|
3910
|
+
: 0;
|
|
3911
|
+
const iconX = element.x + Math.max(20, element.width * 0.08);
|
|
3912
|
+
const iconY = element.y + element.height / 2 - iconSize / 2;
|
|
3913
|
+
const textInset = element.icon
|
|
3914
|
+
? Math.max(20, element.width * 0.08) + iconSize + 16
|
|
3915
|
+
: 16;
|
|
3916
|
+
const availableTextWidth = Math.max(32, element.width - textInset - 16);
|
|
3917
|
+
const longestLine = Math.max(
|
|
3918
|
+
...element.text.split(/\r?\n/).slice(0, 8).map(textWidthUnits),
|
|
3919
|
+
1,
|
|
3920
|
+
);
|
|
3921
|
+
const fittedFontSize = Math.min(
|
|
3922
|
+
element.style.fontSize,
|
|
3923
|
+
Math.max(MIN_FONT_SIZE, availableTextWidth / longestLine),
|
|
3924
|
+
);
|
|
3925
|
+
objects.push({
|
|
3926
|
+
type: "shape",
|
|
3927
|
+
shape:
|
|
3928
|
+
element.shape === "ellipse"
|
|
3929
|
+
? "ellipse"
|
|
3930
|
+
: element.shape === "rounded-rect"
|
|
3931
|
+
? "roundedRect"
|
|
3932
|
+
: "rect",
|
|
3933
|
+
x: element.x,
|
|
3934
|
+
y: element.y,
|
|
3935
|
+
width: element.width,
|
|
3936
|
+
height: element.height,
|
|
3937
|
+
...style(element),
|
|
3938
|
+
text: shapeText(element, element.text, {
|
|
3939
|
+
fontSize: fittedFontSize,
|
|
3940
|
+
}),
|
|
3941
|
+
verticalAlignment: "middle",
|
|
3942
|
+
textWrap: element.text.includes("\n") ? "square" : "none",
|
|
3943
|
+
textInsets: {
|
|
3944
|
+
left: textInset,
|
|
3945
|
+
top: 12,
|
|
3946
|
+
right: 16,
|
|
3947
|
+
bottom: 12,
|
|
3948
|
+
},
|
|
3949
|
+
icon: element.icon || "",
|
|
3950
|
+
architecture: source(element),
|
|
3951
|
+
});
|
|
3952
|
+
if (element.icon) {
|
|
3953
|
+
icons.push({
|
|
3954
|
+
id: element.id,
|
|
3955
|
+
icon: element.icon,
|
|
3956
|
+
sourcePath: element.sourcePath,
|
|
3957
|
+
x: iconX,
|
|
3958
|
+
y: iconY,
|
|
3959
|
+
width: iconSize,
|
|
3960
|
+
height: iconSize,
|
|
3961
|
+
opacity: element.style.opacity,
|
|
3962
|
+
order: element.order,
|
|
3963
|
+
z: element.z,
|
|
3964
|
+
});
|
|
3965
|
+
}
|
|
3966
|
+
continue;
|
|
3967
|
+
}
|
|
3968
|
+
|
|
3969
|
+
if (element.type === "image") {
|
|
3970
|
+
objects.push({
|
|
3971
|
+
type: "image",
|
|
3972
|
+
x: element.x,
|
|
3973
|
+
y: element.y,
|
|
3974
|
+
width: element.width,
|
|
3975
|
+
height: element.height,
|
|
3976
|
+
src: localAssetUrl(documentRef || {}, element.src),
|
|
3977
|
+
source: element.src,
|
|
3978
|
+
fit: element.fit,
|
|
3979
|
+
opacity: element.style.opacity,
|
|
3980
|
+
architecture: source(element),
|
|
3981
|
+
});
|
|
3982
|
+
continue;
|
|
3983
|
+
}
|
|
3984
|
+
|
|
3985
|
+
const points = routes.get(element);
|
|
3986
|
+
objects.push({
|
|
3987
|
+
type: "connector",
|
|
3988
|
+
points,
|
|
3989
|
+
x: Math.min(...points.map((point) => point.x)),
|
|
3990
|
+
y: Math.min(...points.map((point) => point.y)),
|
|
3991
|
+
width:
|
|
3992
|
+
Math.max(...points.map((point) => point.x)) -
|
|
3993
|
+
Math.min(...points.map((point) => point.x)),
|
|
3994
|
+
height:
|
|
3995
|
+
Math.max(...points.map((point) => point.y)) -
|
|
3996
|
+
Math.min(...points.map((point) => point.y)),
|
|
3997
|
+
stroke: element.style.stroke,
|
|
3998
|
+
strokeWidth: element.style.strokeWidth,
|
|
3999
|
+
dash: element.style.dash,
|
|
4000
|
+
opacity: element.style.opacity,
|
|
4001
|
+
arrowEnd: element.arrow,
|
|
4002
|
+
from: element.from,
|
|
4003
|
+
to: element.to,
|
|
4004
|
+
architecture: source(element),
|
|
4005
|
+
});
|
|
4006
|
+
if (element.label) {
|
|
4007
|
+
const metrics = connectorLabelMetrics(element, model.canvas);
|
|
4008
|
+
const anchor = connectorLabelAnchor(element, points, metrics, model.canvas);
|
|
4009
|
+
const labelShape = {
|
|
4010
|
+
type: "shape",
|
|
4011
|
+
shape: "roundedRect",
|
|
4012
|
+
x: anchor.x - metrics.width / 2,
|
|
4013
|
+
y: anchor.y - metrics.height / 2,
|
|
4014
|
+
width: metrics.width,
|
|
4015
|
+
height: metrics.height,
|
|
4016
|
+
fill: "var(--surface)",
|
|
4017
|
+
stroke: "var(--border)",
|
|
4018
|
+
strokeWidth: CONNECTOR_LABEL_STROKE_WIDTH,
|
|
4019
|
+
dash: "",
|
|
4020
|
+
opacity: element.style.opacity,
|
|
4021
|
+
cornerRadius: metrics.height / 2,
|
|
4022
|
+
text: shapeText(element, metrics.text, {
|
|
4023
|
+
fontSize: metrics.fontSize,
|
|
4024
|
+
}),
|
|
4025
|
+
verticalAlignment: "middle",
|
|
4026
|
+
textWrap: "none",
|
|
4027
|
+
textInsets: {
|
|
4028
|
+
left: 8,
|
|
4029
|
+
top: 2,
|
|
4030
|
+
right: 8,
|
|
4031
|
+
bottom: 2,
|
|
4032
|
+
},
|
|
4033
|
+
architecture: source(element, "connector-label"),
|
|
4034
|
+
};
|
|
4035
|
+
if (element.labelLayer === "front") frontLabels.push(labelShape);
|
|
4036
|
+
else objects.push(labelShape);
|
|
4037
|
+
}
|
|
4038
|
+
}
|
|
4039
|
+
objects.push(...frontLabels);
|
|
4040
|
+
|
|
4041
|
+
return {
|
|
4042
|
+
version: model.version,
|
|
4043
|
+
title: model.title,
|
|
4044
|
+
description: model.description,
|
|
4045
|
+
canvas: { ...model.canvas },
|
|
4046
|
+
routing: {
|
|
4047
|
+
degraded: diagnostics.length > 0,
|
|
4048
|
+
diagnostics,
|
|
4049
|
+
},
|
|
4050
|
+
objects,
|
|
4051
|
+
icons,
|
|
4052
|
+
fallbacks,
|
|
4053
|
+
};
|
|
4054
|
+
}
|
|
4055
|
+
|
|
3829
4056
|
export function renderArchitectureDiagram(
|
|
3830
4057
|
model,
|
|
3831
4058
|
documentRef = globalThis.document,
|
|
@@ -3931,6 +4158,12 @@ export function renderArchitectureDiagram(
|
|
|
3931
4158
|
});
|
|
3932
4159
|
frontLabels.forEach((label) => svg.appendChild(label));
|
|
3933
4160
|
wrapper.appendChild(svg);
|
|
4161
|
+
const powerPointSnapshot = architecturePowerPointSnapshot(model, documentRef);
|
|
4162
|
+
Object.defineProperty(wrapper, "__presentationPptxSnapshot", {
|
|
4163
|
+
value: powerPointSnapshot,
|
|
4164
|
+
enumerable: true,
|
|
4165
|
+
});
|
|
4166
|
+
wrapper.setAttribute("data-architecture-pptx", "ready");
|
|
3934
4167
|
appendRoutingWarning(documentRef, wrapper, routingDiagnostics);
|
|
3935
4168
|
return wrapper;
|
|
3936
4169
|
}
|
|
@@ -22,12 +22,13 @@
|
|
|
22
22
|
<button id="navPrev" class="nav-btn" type="button" title="Previous (←)" aria-label="Previous slide">◀</button>
|
|
23
23
|
<span id="navCounter" class="nav-counter" aria-live="polite"></span>
|
|
24
24
|
<button id="navNext" class="nav-btn" type="button" title="Next (→)" aria-label="Next slide">▶</button>
|
|
25
|
-
<button id="navEdit" class="nav-btn" type="button" title="Shape editing mode" aria-label="Shape editing mode">✎</button>
|
|
26
|
-
<button id="navPresent" class="nav-btn" type="button" title="Open in external window (F11 for full screen)" aria-label="Open in external window">⛶</button>
|
|
27
|
-
<button id="navPresenterView" class="nav-btn" type="button" title="Presenter view" aria-label="Presenter view">👤</button>
|
|
25
|
+
<button id="navEdit" class="nav-btn" type="button" title="Shape editing mode" aria-label="Shape editing mode" hidden>✎</button>
|
|
26
|
+
<button id="navPresent" class="nav-btn" type="button" title="Open in external window (F11 for full screen)" aria-label="Open in external window" hidden>⛶</button>
|
|
27
|
+
<button id="navPresenterView" class="nav-btn" type="button" title="Presenter view" aria-label="Presenter view" hidden>👤</button>
|
|
28
28
|
<button id="navFixedPreview" class="nav-btn nav-fixed-preview" type="button" title="Preview PDF layout at 16:9" aria-label="Preview PDF layout at 16:9" aria-pressed="false">16:9</button>
|
|
29
|
-
<button id="navExport" class="nav-btn" type="button" title="Save as PDF" aria-label="Save as PDF">🖶</button>
|
|
30
|
-
<button id="
|
|
29
|
+
<button id="navExport" class="nav-btn" type="button" title="Save as PDF" aria-label="Save as PDF" hidden>🖶</button>
|
|
30
|
+
<button id="navExportPptx" class="nav-btn" type="button" title="Save as editable PowerPoint" aria-label="Save as editable PowerPoint" hidden>P</button>
|
|
31
|
+
<button id="navImport" class="nav-btn" type="button" title="Load Markdown (I)" aria-label="Load Markdown" hidden>📁</button>
|
|
31
32
|
<button id="navSourceMode" class="nav-btn" type="button" title="Automatically refresh when Markdown is saved" aria-label="Automatically refresh when Markdown is saved" hidden>↻</button>
|
|
32
33
|
<button id="navList" class="nav-btn" type="button" title="Slide list (O)" aria-label="Slide list">☰</button>
|
|
33
34
|
<span id="presentStatus" class="sr-only" aria-live="polite"></span>
|
|
@@ -64,7 +65,7 @@
|
|
|
64
65
|
<span id="presenterCounter" class="presenter-counter" aria-live="polite"></span>
|
|
65
66
|
<button id="presenterNextButton" class="presenter-button" type="button">Next</button>
|
|
66
67
|
<button id="presenterListButton" class="presenter-button" type="button">Slide list</button>
|
|
67
|
-
<button id="presenterToggleButton" class="presenter-button presenter-toggle" type="button">Start presentation</button>
|
|
68
|
+
<button id="presenterToggleButton" class="presenter-button presenter-toggle" type="button" hidden>Start presentation</button>
|
|
68
69
|
</footer>
|
|
69
70
|
</section>
|
|
70
71
|
|