@cyber-dash-tech/revela 0.19.7 → 0.19.9
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 +37 -54
- package/README.zh-CN.md +36 -53
- package/designs/lucent/design.css +10 -7
- package/designs/lucent-dark/design.css +16 -13
- package/designs/monet/design.css +38 -12
- package/designs/starter/design.css +14 -11
- package/designs/summit/design.css +14 -11
- package/lib/deck-html/foundation.ts +16 -2
- package/lib/design/designs.ts +13 -1
- package/lib/page-templates/render.ts +10 -7
- package/lib/pptx/export.ts +154 -2
- package/lib/runtime/index.ts +95 -3
- package/lib/runtime/open-deck.ts +190 -0
- package/package.json +1 -1
- package/plugins/revela/.codex-plugin/plugin.json +4 -3
- package/plugins/revela/mcp/revela-server.ts +17 -6
- package/plugins/revela/skills/revela/SKILL.md +1 -1
- package/plugins/revela/skills/revela-design/SKILL.md +5 -4
- package/plugins/revela/skills/revela-helper/SKILL.md +1 -1
- package/plugins/revela/skills/revela-review/SKILL.md +57 -0
|
@@ -99,24 +99,24 @@ body { margin: 0; background: var(--bg-frame, #07111f); color: var(--text-primar
|
|
|
99
99
|
linear-gradient(135deg, #07111f, #101a2b 62%, #243a73);
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
.template-slide[data-
|
|
102
|
+
.template-slide[data-template="cover"] .slide-canvas {
|
|
103
103
|
background:
|
|
104
104
|
linear-gradient(90deg, rgba(7,17,31,0.82), rgba(7,17,31,0.42) 52%, rgba(7,17,31,0.24)),
|
|
105
105
|
url("./assets/cover-background.jpg") center center / cover no-repeat;
|
|
106
106
|
}
|
|
107
|
-
.template-slide[data-
|
|
107
|
+
.template-slide[data-template="agenda"] .slide-canvas {
|
|
108
108
|
background:
|
|
109
109
|
linear-gradient(90deg, rgba(7,17,31,0.86), rgba(7,17,31,0.58) 52%, rgba(7,17,31,0.32)),
|
|
110
110
|
url("./assets/cover-background.jpg") center center / cover no-repeat;
|
|
111
111
|
}
|
|
112
|
-
.template-slide[data-
|
|
112
|
+
.template-slide[data-template="section-divider"] .slide-canvas {
|
|
113
113
|
background:
|
|
114
114
|
linear-gradient(90deg, rgba(7,17,31,0.86), rgba(16,26,43,0.62) 58%, rgba(36,58,115,0.36)),
|
|
115
115
|
url("./assets/cover-background.jpg") center center / cover no-repeat;
|
|
116
116
|
}
|
|
117
117
|
.template-slide[data-template="closing"] .slide-canvas { background: linear-gradient(135deg, #07111f, #315eea 58%, #18a8d8); }
|
|
118
118
|
|
|
119
|
-
.template-slide[data-
|
|
119
|
+
.template-slide[data-template="closing"] .slide-canvas {
|
|
120
120
|
background:
|
|
121
121
|
linear-gradient(90deg, rgba(7,17,31,0.82), rgba(49,94,234,0.42) 58%, rgba(24,168,216,0.24)),
|
|
122
122
|
url("./assets/closing-background.jpg") center center / cover no-repeat;
|
|
@@ -162,10 +162,13 @@ body { margin: 0; background: var(--bg-frame, #07111f); color: var(--text-primar
|
|
|
162
162
|
.text-panel-formula-fallback { display: block; white-space: normal; overflow-wrap: anywhere; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.82em; line-height: 1.35; color: inherit; }
|
|
163
163
|
.text-panel-formula-caption { margin: 0; font-size: 12px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-muted); }
|
|
164
164
|
.template-chart-takeaway-list { display: grid; gap: 22px; width: 100%; }
|
|
165
|
-
.template-chart-takeaway-item { display: grid; gap: 7px; padding-top: 18px; border-top: 1px solid
|
|
165
|
+
.template-chart-takeaway-item { display: grid; gap: 7px; padding-top: 18px; border-top: 1px solid var(--line); }
|
|
166
|
+
.template-text-panel--color .template-chart-takeaway-item { border-top-color: rgba(255,255,255,0.24); }
|
|
166
167
|
.template-chart-takeaway-item:first-child { padding-top: 0; border-top: 0; }
|
|
167
|
-
.template-chart-takeaway-item h3 { margin: 0; font-size: 25px; line-height: 1.24; color:
|
|
168
|
-
.template-chart-takeaway-item
|
|
168
|
+
.template-chart-takeaway-item h3 { margin: 0; font-size: 25px; line-height: 1.24; color: var(--text-primary); }
|
|
169
|
+
.template-text-panel--color .template-chart-takeaway-item h3 { color: white; }
|
|
170
|
+
.template-chart-takeaway-item p { margin: 0; font-size: 20px; line-height: 1.46; color: var(--text-secondary); }
|
|
171
|
+
.template-text-panel--color .template-chart-takeaway-item p { color: rgba(255,255,255,0.78); }
|
|
169
172
|
.template-bar { flex: 1; background: linear-gradient(180deg, var(--accent-primary), var(--accent-cyan)); min-height: 80px; }
|
|
170
173
|
.template-table-layout { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); gap: 34px; height: 100%; align-items: stretch; }
|
|
171
174
|
.template-table-layout .template-side-panel { grid-column: 1; grid-row: 1; }
|
|
@@ -333,14 +336,14 @@ body { margin: 0; background: var(--bg-frame, #07111f); color: var(--text-primar
|
|
|
333
336
|
.template-table,
|
|
334
337
|
.template-insight-panel,
|
|
335
338
|
.template-catalog-panel { box-shadow: none; }
|
|
336
|
-
.template-slide[data-
|
|
337
|
-
.template-slide[data-
|
|
338
|
-
.template-slide[data-
|
|
339
|
+
.template-slide[data-template="cover"] .slide-canvas,
|
|
340
|
+
.template-slide[data-template="agenda"] .slide-canvas,
|
|
341
|
+
.template-slide[data-template="section-divider"] .slide-canvas {
|
|
339
342
|
background:
|
|
340
343
|
linear-gradient(90deg, rgba(16,19,22,0.82), rgba(16,19,22,0.42) 52%, rgba(16,19,22,0.18)),
|
|
341
344
|
url("./assets/cover-background.jpg") center center / cover no-repeat;
|
|
342
345
|
}
|
|
343
|
-
.template-slide[data-
|
|
346
|
+
.template-slide[data-template="closing"] .slide-canvas {
|
|
344
347
|
background:
|
|
345
348
|
linear-gradient(90deg, rgba(16,19,22,0.72), rgba(47,115,218,0.34) 58%, rgba(43,159,195,0.22)),
|
|
346
349
|
url("./assets/closing-background.jpg") center center / cover no-repeat;
|
|
@@ -99,24 +99,24 @@ body { margin: 0; background: var(--bg-frame, #07111f); color: var(--text-primar
|
|
|
99
99
|
linear-gradient(135deg, #07111f, #101a2b 62%, #243a73);
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
.template-slide[data-
|
|
102
|
+
.template-slide[data-template="cover"] .slide-canvas {
|
|
103
103
|
background:
|
|
104
104
|
linear-gradient(90deg, rgba(7,17,31,0.82), rgba(7,17,31,0.42) 52%, rgba(7,17,31,0.24)),
|
|
105
105
|
url("./assets/cover-background.jpg") center center / cover no-repeat;
|
|
106
106
|
}
|
|
107
|
-
.template-slide[data-
|
|
107
|
+
.template-slide[data-template="agenda"] .slide-canvas {
|
|
108
108
|
background:
|
|
109
109
|
linear-gradient(90deg, rgba(7,17,31,0.86), rgba(7,17,31,0.58) 52%, rgba(7,17,31,0.32)),
|
|
110
110
|
url("./assets/cover-background.jpg") center center / cover no-repeat;
|
|
111
111
|
}
|
|
112
|
-
.template-slide[data-
|
|
112
|
+
.template-slide[data-template="section-divider"] .slide-canvas {
|
|
113
113
|
background:
|
|
114
114
|
linear-gradient(90deg, rgba(7,17,31,0.86), rgba(16,26,43,0.62) 58%, rgba(36,58,115,0.36)),
|
|
115
115
|
url("./assets/cover-background.jpg") center center / cover no-repeat;
|
|
116
116
|
}
|
|
117
117
|
.template-slide[data-template="closing"] .slide-canvas { background: linear-gradient(135deg, #07111f, #315eea 58%, #18a8d8); }
|
|
118
118
|
|
|
119
|
-
.template-slide[data-
|
|
119
|
+
.template-slide[data-template="closing"] .slide-canvas {
|
|
120
120
|
background:
|
|
121
121
|
linear-gradient(90deg, rgba(7,17,31,0.82), rgba(49,94,234,0.42) 58%, rgba(24,168,216,0.24)),
|
|
122
122
|
url("./assets/closing-background.jpg") center center / cover no-repeat;
|
|
@@ -162,10 +162,13 @@ body { margin: 0; background: var(--bg-frame, #07111f); color: var(--text-primar
|
|
|
162
162
|
.text-panel-formula-fallback { display: block; white-space: normal; overflow-wrap: anywhere; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.82em; line-height: 1.35; color: inherit; }
|
|
163
163
|
.text-panel-formula-caption { margin: 0; font-size: 12px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-muted); }
|
|
164
164
|
.template-chart-takeaway-list { display: grid; gap: 22px; width: 100%; }
|
|
165
|
-
.template-chart-takeaway-item { display: grid; gap: 7px; padding-top: 18px; border-top: 1px solid
|
|
165
|
+
.template-chart-takeaway-item { display: grid; gap: 7px; padding-top: 18px; border-top: 1px solid var(--line); }
|
|
166
|
+
.template-text-panel--color .template-chart-takeaway-item { border-top-color: rgba(255,255,255,0.24); }
|
|
166
167
|
.template-chart-takeaway-item:first-child { padding-top: 0; border-top: 0; }
|
|
167
|
-
.template-chart-takeaway-item h3 { margin: 0; font-size: 25px; line-height: 1.24; color:
|
|
168
|
-
.template-chart-takeaway-item
|
|
168
|
+
.template-chart-takeaway-item h3 { margin: 0; font-size: 25px; line-height: 1.24; color: var(--text-primary); }
|
|
169
|
+
.template-text-panel--color .template-chart-takeaway-item h3 { color: white; }
|
|
170
|
+
.template-chart-takeaway-item p { margin: 0; font-size: 20px; line-height: 1.46; color: var(--text-secondary); }
|
|
171
|
+
.template-text-panel--color .template-chart-takeaway-item p { color: rgba(255,255,255,0.78); }
|
|
169
172
|
.template-bar { flex: 1; background: linear-gradient(180deg, var(--accent-primary), var(--accent-cyan)); min-height: 80px; }
|
|
170
173
|
.template-table-layout { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); gap: 34px; height: 100%; align-items: stretch; }
|
|
171
174
|
.template-table-layout .template-side-panel { grid-column: 1; grid-row: 1; }
|
|
@@ -345,14 +348,14 @@ body { margin: 0; background: var(--bg-frame, #07111f); color: var(--text-primar
|
|
|
345
348
|
.template-timeline-layout .template-text-panel--color {
|
|
346
349
|
background: linear-gradient(135deg, #176c63 0%, #2a8f84 54%, #b0822e 125%);
|
|
347
350
|
}
|
|
348
|
-
.template-slide[data-
|
|
349
|
-
.template-slide[data-
|
|
350
|
-
.template-slide[data-
|
|
351
|
+
.template-slide[data-template="cover"] .slide-canvas,
|
|
352
|
+
.template-slide[data-template="agenda"] .slide-canvas,
|
|
353
|
+
.template-slide[data-template="section-divider"] .slide-canvas {
|
|
351
354
|
background:
|
|
352
355
|
linear-gradient(90deg, rgba(10,16,16,0.84), rgba(10,16,16,0.46) 52%, rgba(10,16,16,0.18)),
|
|
353
356
|
url("./assets/cover-background.jpg") center center / cover no-repeat;
|
|
354
357
|
}
|
|
355
|
-
.template-slide[data-
|
|
358
|
+
.template-slide[data-template="closing"] .slide-canvas {
|
|
356
359
|
background:
|
|
357
360
|
linear-gradient(90deg, rgba(10,16,16,0.78), rgba(23,108,99,0.4) 58%, rgba(176,130,46,0.22)),
|
|
358
361
|
url("./assets/closing-background.jpg") center center / cover no-repeat;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, writeFileSync } from "fs"
|
|
2
|
-
import { dirname, isAbsolute, normalize, resolve } from "path"
|
|
2
|
+
import { basename, dirname, isAbsolute, normalize, resolve } from "path"
|
|
3
3
|
import { activeDesign, getDesignSection, materializeDesignCssSnapshot } from "../design/designs"
|
|
4
4
|
|
|
5
5
|
export type DeckFoundationMode = "create" | "repair"
|
|
@@ -48,7 +48,12 @@ export function createDeckFoundation(input: CreateDeckFoundationInput): CreateDe
|
|
|
48
48
|
const foundation = getDesignSection("foundation", design)
|
|
49
49
|
const parts = parseFoundationParts(foundation)
|
|
50
50
|
if (parts.scriptBlocks.length === 0) throw new Error(`Design '${design}' foundation does not include a SlidePresentation JavaScript code block.`)
|
|
51
|
-
const snapshot = materializeDesignCssSnapshot({
|
|
51
|
+
const snapshot = materializeDesignCssSnapshot({
|
|
52
|
+
workspaceRoot: input.workspaceRoot,
|
|
53
|
+
outputPath,
|
|
54
|
+
designName: design,
|
|
55
|
+
snapshotName: activeDesignSnapshotName(outputPath),
|
|
56
|
+
})
|
|
52
57
|
|
|
53
58
|
const html = renderFoundationHtml({
|
|
54
59
|
language: input.language || "en",
|
|
@@ -69,6 +74,7 @@ export function createDeckFoundation(input: CreateDeckFoundationInput): CreateDe
|
|
|
69
74
|
"design:foundation",
|
|
70
75
|
parts.fontLinks.length > 0 ? "foundation:font-links" : "foundation:font-links:none",
|
|
71
76
|
snapshot.generatedFallback ? "design-css:fallback" : "design-css:snapshot",
|
|
77
|
+
`design-css:active-snapshot:${snapshot.snapshotName}`,
|
|
72
78
|
snapshot.assetCount > 0 ? "design-assets:snapshot" : "design-assets:none",
|
|
73
79
|
"foundation:SlidePresentation",
|
|
74
80
|
],
|
|
@@ -81,6 +87,14 @@ export function createDeckFoundation(input: CreateDeckFoundationInput): CreateDe
|
|
|
81
87
|
}
|
|
82
88
|
}
|
|
83
89
|
|
|
90
|
+
export function activeDesignSnapshotName(outputPath: string): string {
|
|
91
|
+
const stem = basename(normalizeOutputPath(outputPath), ".html")
|
|
92
|
+
.toLowerCase()
|
|
93
|
+
.replace(/[^a-z0-9-]+/g, "-")
|
|
94
|
+
.replace(/^-+|-+$/g, "")
|
|
95
|
+
return `${stem || "deck"}-active`
|
|
96
|
+
}
|
|
97
|
+
|
|
84
98
|
export function normalizeOutputPath(outputPath: string): string {
|
|
85
99
|
const trimmed = outputPath.trim()
|
|
86
100
|
if (!trimmed) throw new Error("outputPath is required")
|
package/lib/design/designs.ts
CHANGED
|
@@ -180,6 +180,7 @@ export interface MaterializeDesignPreviewResult {
|
|
|
180
180
|
export interface DesignCssSnapshotResult {
|
|
181
181
|
ok: true
|
|
182
182
|
design: string
|
|
183
|
+
snapshotName: string
|
|
183
184
|
sourcePath: string
|
|
184
185
|
snapshotDir: string
|
|
185
186
|
cssPath: string
|
|
@@ -425,11 +426,13 @@ export function materializeDesignCssSnapshot(input: {
|
|
|
425
426
|
workspaceRoot: string
|
|
426
427
|
outputPath: string
|
|
427
428
|
designName?: string
|
|
429
|
+
snapshotName?: string
|
|
428
430
|
}): DesignCssSnapshotResult {
|
|
429
431
|
const designName = normalizeDesignName(input.designName || activeDesign())
|
|
432
|
+
const snapshotName = normalizeDesignSnapshotName(input.snapshotName || designName)
|
|
430
433
|
const designDir = resolveDesignPackageDir(designName)
|
|
431
434
|
const outputDir = dirname(normalize(input.outputPath))
|
|
432
|
-
const snapshotRelDir = normalize(join(outputDir, "_revela-design",
|
|
435
|
+
const snapshotRelDir = normalize(join(outputDir, "_revela-design", snapshotName)).replace(/\\/g, "/")
|
|
433
436
|
const snapshotDir = resolve(input.workspaceRoot, snapshotRelDir)
|
|
434
437
|
const cssPath = join(snapshotDir, "design.css")
|
|
435
438
|
const cssRead = readDesignCss(designName)
|
|
@@ -450,6 +453,7 @@ export function materializeDesignCssSnapshot(input: {
|
|
|
450
453
|
return {
|
|
451
454
|
ok: true,
|
|
452
455
|
design: designName,
|
|
456
|
+
snapshotName,
|
|
453
457
|
sourcePath: cssRead.path || join(designDir, "DESIGN.md"),
|
|
454
458
|
snapshotDir,
|
|
455
459
|
cssPath,
|
|
@@ -460,6 +464,14 @@ export function materializeDesignCssSnapshot(input: {
|
|
|
460
464
|
}
|
|
461
465
|
}
|
|
462
466
|
|
|
467
|
+
function normalizeDesignSnapshotName(name: string): string {
|
|
468
|
+
const normalized = name.trim().toLowerCase()
|
|
469
|
+
if (!/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(normalized)) {
|
|
470
|
+
throw new Error("Design snapshot name must be kebab-case using lowercase letters, numbers, and hyphens")
|
|
471
|
+
}
|
|
472
|
+
return normalized
|
|
473
|
+
}
|
|
474
|
+
|
|
463
475
|
/** Normalize and validate a design package name. */
|
|
464
476
|
export function normalizeDesignName(name: string): string {
|
|
465
477
|
const normalized = name.trim().toLowerCase()
|
|
@@ -475,23 +475,23 @@ export function templateDeckCss(input: { designName?: string; designAssetBasePat
|
|
|
475
475
|
const lucentCoverBackground = designName === "lucent" && assetBasePath ? cssUrl(`${assetBasePath}/cover-background.jpg`) : ""
|
|
476
476
|
const lucentClosingBackground = designName === "lucent" && assetBasePath ? cssUrl(`${assetBasePath}/closing-background.jpg`) : ""
|
|
477
477
|
const lucentCoverBackgroundCss = lucentCoverBackground ? `
|
|
478
|
-
.template-slide[data-
|
|
478
|
+
.template-slide[data-template="cover"] .slide-canvas {
|
|
479
479
|
background:
|
|
480
480
|
linear-gradient(90deg, rgba(7,17,31,0.82), rgba(7,17,31,0.42) 52%, rgba(7,17,31,0.24)),
|
|
481
481
|
url("${lucentCoverBackground}") center center / cover no-repeat;
|
|
482
482
|
}
|
|
483
|
-
.template-slide[data-
|
|
483
|
+
.template-slide[data-template="agenda"] .slide-canvas {
|
|
484
484
|
background:
|
|
485
485
|
linear-gradient(90deg, rgba(7,17,31,0.86), rgba(7,17,31,0.58) 52%, rgba(7,17,31,0.32)),
|
|
486
486
|
url("${lucentCoverBackground}") center center / cover no-repeat;
|
|
487
487
|
}
|
|
488
|
-
.template-slide[data-
|
|
488
|
+
.template-slide[data-template="section-divider"] .slide-canvas {
|
|
489
489
|
background:
|
|
490
490
|
linear-gradient(90deg, rgba(7,17,31,0.86), rgba(16,26,43,0.62) 58%, rgba(36,58,115,0.36)),
|
|
491
491
|
url("${lucentCoverBackground}") center center / cover no-repeat;
|
|
492
492
|
}` : ""
|
|
493
493
|
const lucentClosingBackgroundCss = lucentClosingBackground ? `
|
|
494
|
-
.template-slide[data-
|
|
494
|
+
.template-slide[data-template="closing"] .slide-canvas {
|
|
495
495
|
background:
|
|
496
496
|
linear-gradient(90deg, rgba(7,17,31,0.82), rgba(49,94,234,0.42) 58%, rgba(24,168,216,0.24)),
|
|
497
497
|
url("${lucentClosingBackground}") center center / cover no-repeat;
|
|
@@ -591,10 +591,13 @@ ${lucentClosingBackgroundCss}
|
|
|
591
591
|
.template-text-panel-formula-caption { margin: 0; font-size: 14px; line-height: 1.35; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-muted); }
|
|
592
592
|
.template-text-panel--color .template-text-panel-formula-caption { color: rgba(255,255,255,0.72); }
|
|
593
593
|
.template-chart-takeaway-list { display: grid; gap: 22px; width: 100%; }
|
|
594
|
-
.template-chart-takeaway-item { display: grid; gap: 7px; padding-top: 18px; border-top: 1px solid
|
|
594
|
+
.template-chart-takeaway-item { display: grid; gap: 7px; padding-top: 18px; border-top: 1px solid var(--line); }
|
|
595
|
+
.template-text-panel--color .template-chart-takeaway-item { border-top-color: rgba(255,255,255,0.24); }
|
|
595
596
|
.template-chart-takeaway-item:first-child { padding-top: 0; border-top: 0; }
|
|
596
|
-
.template-chart-takeaway-item h3 { margin: 0; font-size: 25px; line-height: 1.24; color:
|
|
597
|
-
.template-chart-takeaway-item
|
|
597
|
+
.template-chart-takeaway-item h3 { margin: 0; font-size: 25px; line-height: 1.24; color: var(--text-primary); }
|
|
598
|
+
.template-text-panel--color .template-chart-takeaway-item h3 { color: white; }
|
|
599
|
+
.template-chart-takeaway-item p { margin: 0; font-size: 20px; line-height: 1.46; color: var(--text-secondary); }
|
|
600
|
+
.template-text-panel--color .template-chart-takeaway-item p { color: rgba(255,255,255,0.78); }
|
|
598
601
|
.template-bar { flex: 1; background: linear-gradient(180deg, var(--accent-primary), var(--accent-cyan)); min-height: 80px; }
|
|
599
602
|
.template-table-layout { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); gap: 34px; height: 100%; align-items: stretch; }
|
|
600
603
|
.template-table-layout .template-side-panel { grid-column: 1; grid-row: 1; }
|
package/lib/pptx/export.ts
CHANGED
|
@@ -577,25 +577,177 @@ async function exportSlidePptx(
|
|
|
577
577
|
host.style.zIndex = "2147483647"
|
|
578
578
|
host.style.background = "transparent"
|
|
579
579
|
|
|
580
|
-
const exportTarget =
|
|
580
|
+
const exportTarget = targetSlide.cloneNode(true) as HTMLElement
|
|
581
581
|
exportTarget.style.width = "1920px"
|
|
582
|
+
exportTarget.style.minWidth = "1920px"
|
|
582
583
|
exportTarget.style.height = "1080px"
|
|
584
|
+
exportTarget.style.minHeight = "1080px"
|
|
583
585
|
exportTarget.style.position = "relative"
|
|
584
586
|
exportTarget.style.left = "0"
|
|
585
587
|
exportTarget.style.top = "0"
|
|
588
|
+
exportTarget.style.margin = "0"
|
|
589
|
+
exportTarget.style.display = "flex"
|
|
590
|
+
exportTarget.style.alignItems = "center"
|
|
591
|
+
exportTarget.style.justifyContent = "center"
|
|
592
|
+
exportTarget.style.overflow = "hidden"
|
|
586
593
|
exportTarget.style.transform = "none"
|
|
587
594
|
exportTarget.style.transformOrigin = "top left"
|
|
588
595
|
exportTarget.style.transition = "none"
|
|
589
596
|
exportTarget.style.animation = "none"
|
|
597
|
+
const exportCanvas = exportTarget.querySelector(".slide-canvas") as HTMLElement | null
|
|
598
|
+
if (!exportCanvas) {
|
|
599
|
+
host.remove()
|
|
600
|
+
throw new Error(`Missing cloned .slide-canvas for slide ${index + 1}`)
|
|
601
|
+
}
|
|
602
|
+
exportCanvas.style.width = "1920px"
|
|
603
|
+
exportCanvas.style.minWidth = "1920px"
|
|
604
|
+
exportCanvas.style.height = "1080px"
|
|
605
|
+
exportCanvas.style.minHeight = "1080px"
|
|
606
|
+
exportCanvas.style.position = "relative"
|
|
607
|
+
exportCanvas.style.left = "0"
|
|
608
|
+
exportCanvas.style.top = "0"
|
|
609
|
+
exportCanvas.style.flexShrink = "0"
|
|
610
|
+
exportCanvas.style.transform = "none"
|
|
611
|
+
exportCanvas.style.transformOrigin = "top left"
|
|
612
|
+
exportCanvas.style.transition = "none"
|
|
613
|
+
exportCanvas.style.animation = "none"
|
|
614
|
+
|
|
615
|
+
const splitCssList = (value: string): string[] => {
|
|
616
|
+
const parts: string[] = []
|
|
617
|
+
let depth = 0
|
|
618
|
+
let quote: string | null = null
|
|
619
|
+
let start = 0
|
|
620
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
621
|
+
const char = value[i]
|
|
622
|
+
if (quote) {
|
|
623
|
+
if (char === quote && value[i - 1] !== "\\") quote = null
|
|
624
|
+
continue
|
|
625
|
+
}
|
|
626
|
+
if (char === "\"" || char === "'") {
|
|
627
|
+
quote = char
|
|
628
|
+
continue
|
|
629
|
+
}
|
|
630
|
+
if (char === "(") depth += 1
|
|
631
|
+
if (char === ")") depth = Math.max(0, depth - 1)
|
|
632
|
+
if (char === "," && depth === 0) {
|
|
633
|
+
parts.push(value.slice(start, i).trim())
|
|
634
|
+
start = i + 1
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
parts.push(value.slice(start).trim())
|
|
638
|
+
return parts.filter(Boolean)
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
const extractCssUrl = (value: string): string | null => {
|
|
642
|
+
const match = value.match(/url\((["']?)(.*?)\1\)/i)
|
|
643
|
+
return match?.[2] ?? null
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const normalizeBackgroundLayersForPptx = (canvas: HTMLElement, sourceStyle: CSSStyleDeclaration) => {
|
|
647
|
+
const computed = sourceStyle
|
|
648
|
+
const imageLayers = splitCssList(computed.backgroundImage)
|
|
649
|
+
const hasUrlLayer = imageLayers.some((layer) => /url\(/i.test(layer))
|
|
650
|
+
const paddingLeft = Number.parseFloat(computed.paddingLeft) || 0
|
|
651
|
+
const paddingRight = Number.parseFloat(computed.paddingRight) || 0
|
|
652
|
+
const paddingTop = Number.parseFloat(computed.paddingTop) || 0
|
|
653
|
+
const paddingBottom = Number.parseFloat(computed.paddingBottom) || 0
|
|
654
|
+
const hasPadding = paddingLeft > 0 || paddingRight > 0 || paddingTop > 0 || paddingBottom > 0
|
|
655
|
+
if (!hasUrlLayer && !hasPadding) return
|
|
656
|
+
|
|
657
|
+
const sizeLayers = splitCssList(computed.backgroundSize)
|
|
658
|
+
const positionLayers = splitCssList(computed.backgroundPosition)
|
|
659
|
+
const repeatLayers = splitCssList(computed.backgroundRepeat)
|
|
660
|
+
const layerCount = imageLayers.length
|
|
661
|
+
const layers = hasUrlLayer ? document.createElement("div") : null
|
|
662
|
+
if (layers) {
|
|
663
|
+
layers.setAttribute("data-revela-pptx-background-layers", "true")
|
|
664
|
+
layers.style.position = "absolute"
|
|
665
|
+
layers.style.inset = "0"
|
|
666
|
+
layers.style.pointerEvents = "none"
|
|
667
|
+
layers.style.zIndex = "0"
|
|
668
|
+
layers.style.overflow = "hidden"
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if (layers) imageLayers.forEach((layer, layerIndex) => {
|
|
672
|
+
if (layer === "none") return
|
|
673
|
+
const zIndex = String(layerCount - layerIndex)
|
|
674
|
+
const url = extractCssUrl(layer)
|
|
675
|
+
const size = sizeLayers[layerIndex] ?? sizeLayers[0] ?? "auto"
|
|
676
|
+
const position = positionLayers[layerIndex] ?? positionLayers[0] ?? "50% 50%"
|
|
677
|
+
const repeat = repeatLayers[layerIndex] ?? repeatLayers[0] ?? "repeat"
|
|
678
|
+
if (url) {
|
|
679
|
+
const img = document.createElement("img")
|
|
680
|
+
img.src = url
|
|
681
|
+
img.alt = ""
|
|
682
|
+
img.setAttribute("aria-hidden", "true")
|
|
683
|
+
img.style.position = "absolute"
|
|
684
|
+
img.style.inset = "0"
|
|
685
|
+
img.style.width = "100%"
|
|
686
|
+
img.style.height = "100%"
|
|
687
|
+
img.style.display = "block"
|
|
688
|
+
img.style.zIndex = zIndex
|
|
689
|
+
img.style.objectFit = size.includes("contain") ? "contain" : "cover"
|
|
690
|
+
img.style.objectPosition = position
|
|
691
|
+
if (!repeat.includes("no-repeat") && !size.includes("cover") && !size.includes("contain")) {
|
|
692
|
+
img.style.objectFit = "fill"
|
|
693
|
+
}
|
|
694
|
+
layers.appendChild(img)
|
|
695
|
+
return
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
const gradient = document.createElement("div")
|
|
699
|
+
gradient.style.position = "absolute"
|
|
700
|
+
gradient.style.inset = "0"
|
|
701
|
+
gradient.style.zIndex = zIndex
|
|
702
|
+
gradient.style.backgroundImage = layer
|
|
703
|
+
gradient.style.backgroundSize = size
|
|
704
|
+
gradient.style.backgroundPosition = position
|
|
705
|
+
gradient.style.backgroundRepeat = repeat
|
|
706
|
+
layers.appendChild(gradient)
|
|
707
|
+
})
|
|
708
|
+
|
|
709
|
+
if (layers && !layers.childElementCount) return
|
|
710
|
+
|
|
711
|
+
const content = document.createElement("div")
|
|
712
|
+
content.setAttribute("data-revela-pptx-content-layer", "true")
|
|
713
|
+
content.style.position = "absolute"
|
|
714
|
+
content.style.zIndex = String(layerCount + 1)
|
|
715
|
+
content.style.left = `${paddingLeft}px`
|
|
716
|
+
content.style.top = `${paddingTop}px`
|
|
717
|
+
content.style.width = `${Math.max(1, 1920 - paddingLeft - paddingRight)}px`
|
|
718
|
+
content.style.height = `${Math.max(1, 1080 - paddingTop - paddingBottom)}px`
|
|
719
|
+
content.style.minHeight = "0"
|
|
720
|
+
content.style.boxSizing = "border-box"
|
|
721
|
+
while (canvas.firstChild) {
|
|
722
|
+
content.appendChild(canvas.firstChild)
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
canvas.style.padding = "0"
|
|
726
|
+
if (layers) {
|
|
727
|
+
canvas.style.backgroundImage = "none"
|
|
728
|
+
canvas.style.backgroundColor = computed.backgroundColor
|
|
729
|
+
canvas.appendChild(layers)
|
|
730
|
+
}
|
|
731
|
+
canvas.appendChild(content)
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
normalizeBackgroundLayersForPptx(exportCanvas, window.getComputedStyle(target))
|
|
590
735
|
host.appendChild(exportTarget)
|
|
591
736
|
document.body.appendChild(host)
|
|
592
737
|
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)))
|
|
593
738
|
|
|
594
739
|
const rect = exportTarget.getBoundingClientRect()
|
|
740
|
+
const canvasRect = exportCanvas.getBoundingClientRect()
|
|
595
741
|
if (Math.abs(rect.width - 1920) > 2 || Math.abs(rect.height - 1080) > 2) {
|
|
596
742
|
host.remove()
|
|
597
743
|
throw new Error(
|
|
598
|
-
`Slide ${index + 1} export
|
|
744
|
+
`Slide ${index + 1} export slide is ${Math.round(rect.width)}x${Math.round(rect.height)}, expected 1920x1080.`
|
|
745
|
+
)
|
|
746
|
+
}
|
|
747
|
+
if (Math.abs(canvasRect.width - 1920) > 2 || Math.abs(canvasRect.height - 1080) > 2) {
|
|
748
|
+
host.remove()
|
|
749
|
+
throw new Error(
|
|
750
|
+
`Slide ${index + 1} export canvas is ${Math.round(canvasRect.width)}x${Math.round(canvasRect.height)}, expected 1920x1080.`
|
|
599
751
|
)
|
|
600
752
|
}
|
|
601
753
|
|
package/lib/runtime/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "crypto"
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"
|
|
3
|
-
import { dirname, resolve } from "path"
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "fs"
|
|
3
|
+
import { dirname, isAbsolute, resolve, sep } from "path"
|
|
4
4
|
import {
|
|
5
5
|
activeDesign,
|
|
6
6
|
activateDesign,
|
|
@@ -16,13 +16,14 @@ import {
|
|
|
16
16
|
listDesignAssets,
|
|
17
17
|
listDesigns,
|
|
18
18
|
materializeDesignPreview,
|
|
19
|
+
materializeDesignCssSnapshot,
|
|
19
20
|
packDesignPackage,
|
|
20
21
|
seedBuiltinDesigns,
|
|
21
22
|
validateDesignDraftPackage,
|
|
22
23
|
validateDesignPackage,
|
|
23
24
|
type DesignPackageAssetInput,
|
|
24
25
|
} from "../design/designs"
|
|
25
|
-
import { createDeckFoundation as createDeckFoundationShell } from "../deck-html/foundation"
|
|
26
|
+
import { activeDesignSnapshotName, createDeckFoundation as createDeckFoundationShell } from "../deck-html/foundation"
|
|
26
27
|
import { activeDomain, activateDomain, createDomainDraftPackage, createDomainPackage, getDomainSkillMd, installDomainDraftPackage, listDomains, seedBuiltinDomains, validateDomainDraftPackage, validateDomainPackage } from "../domain/domains"
|
|
27
28
|
import { compileNarrativeVault } from "../narrative-vault/compile"
|
|
28
29
|
import { autoCompileNarrativeVault } from "../narrative-vault/auto-compile"
|
|
@@ -36,6 +37,8 @@ import { recordRenderedArtifact, workspaceRelative } from "../workspace-state/re
|
|
|
36
37
|
import { existingWorkspaceMetaPath, workspaceMetaPath } from "../workspace-meta"
|
|
37
38
|
import { checkMaterialIntake, extractMaterial, materialIntakeNoticeForCommand, prepareLocalMaterials, recordMaterialReview } from "../material-intake"
|
|
38
39
|
import type { ReviewDeckOpenInput, ReviewDeckReadInput } from "./review"
|
|
40
|
+
export type { OpenDeckInput } from "./open-deck"
|
|
41
|
+
import { openDeck as openDeckDirect } from "./open-deck"
|
|
39
42
|
import pkg from "../../package.json"
|
|
40
43
|
export { bindResearchFindings, evaluateResearchFindings, researchSave, researchTargets } from "./research"
|
|
41
44
|
export { storyRead } from "./story"
|
|
@@ -72,6 +75,12 @@ export interface RuntimeDesignInventoryInput {
|
|
|
72
75
|
name?: string
|
|
73
76
|
}
|
|
74
77
|
|
|
78
|
+
export interface RuntimeDeckDesignSwitchInput extends RuntimeWorkspaceInput {
|
|
79
|
+
file: string
|
|
80
|
+
name: string
|
|
81
|
+
openBrowser?: boolean
|
|
82
|
+
}
|
|
83
|
+
|
|
75
84
|
export interface RuntimeDesignLayoutReadInput {
|
|
76
85
|
name?: string
|
|
77
86
|
layout: string | string[]
|
|
@@ -380,6 +389,16 @@ export async function reviewDeckOpen(input: ReviewDeckOpenInput) {
|
|
|
380
389
|
return review.reviewDeckOpen(input)
|
|
381
390
|
}
|
|
382
391
|
|
|
392
|
+
export async function openDeck(input: RuntimeFileInput & { openBrowser?: boolean; openUrl?: (url: string) => void }) {
|
|
393
|
+
const direct = await import("./open-deck")
|
|
394
|
+
return direct.openDeck(input)
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export async function stopOpenDeckServers() {
|
|
398
|
+
const direct = await import("./open-deck")
|
|
399
|
+
return direct.stopOpenDeckServers()
|
|
400
|
+
}
|
|
401
|
+
|
|
383
402
|
export function designList() {
|
|
384
403
|
return {
|
|
385
404
|
ok: true,
|
|
@@ -586,6 +605,50 @@ export function designActivate(input: RuntimeNameInput) {
|
|
|
586
605
|
}
|
|
587
606
|
}
|
|
588
607
|
|
|
608
|
+
export function switchDeckDesign(input: RuntimeDeckDesignSwitchInput) {
|
|
609
|
+
seedBuiltinDesigns()
|
|
610
|
+
const workspaceRoot = root(input.workspaceRoot)
|
|
611
|
+
const file = normalizeDeckFile(workspaceRoot, requiredString(input.file, "file"))
|
|
612
|
+
const design = requiredName({ name: input.name }, "design")
|
|
613
|
+
activateDesign(design)
|
|
614
|
+
|
|
615
|
+
const snapshot = materializeDesignCssSnapshot({
|
|
616
|
+
workspaceRoot,
|
|
617
|
+
outputPath: file,
|
|
618
|
+
designName: design,
|
|
619
|
+
snapshotName: activeDesignSnapshotName(file),
|
|
620
|
+
})
|
|
621
|
+
|
|
622
|
+
const htmlPath = resolve(workspaceRoot, file)
|
|
623
|
+
const html = readFileSync(htmlPath, "utf-8")
|
|
624
|
+
const migratedHtml = migrateDeckDesignLink(html, snapshot.href)
|
|
625
|
+
const migratedLink = migratedHtml !== html
|
|
626
|
+
if (migratedLink) writeFileSync(htmlPath, migratedHtml, "utf-8")
|
|
627
|
+
|
|
628
|
+
const opened = input.openBrowser === false
|
|
629
|
+
? undefined
|
|
630
|
+
: openDeckDirect({ workspaceRoot, file, openBrowser: input.openBrowser })
|
|
631
|
+
|
|
632
|
+
return {
|
|
633
|
+
ok: true,
|
|
634
|
+
file,
|
|
635
|
+
activeDesign: activeDesign(),
|
|
636
|
+
design,
|
|
637
|
+
snapshotHref: snapshot.href,
|
|
638
|
+
snapshotDir: workspaceRelative(workspaceRoot, snapshot.snapshotDir),
|
|
639
|
+
assetCount: snapshot.assetCount,
|
|
640
|
+
migratedLink,
|
|
641
|
+
opened: opened ? {
|
|
642
|
+
ok: opened.ok,
|
|
643
|
+
url: opened.url,
|
|
644
|
+
openedBrowser: opened.openedBrowser,
|
|
645
|
+
mode: opened.mode,
|
|
646
|
+
readOnly: opened.readOnly,
|
|
647
|
+
} : undefined,
|
|
648
|
+
warnings: snapshot.warnings,
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
589
652
|
export function domainList() {
|
|
590
653
|
seedBuiltinDomains()
|
|
591
654
|
return {
|
|
@@ -659,6 +722,35 @@ function root(workspaceRoot: string | undefined): string {
|
|
|
659
722
|
return resolve(workspaceRoot || process.cwd())
|
|
660
723
|
}
|
|
661
724
|
|
|
725
|
+
function normalizeDeckFile(workspaceRoot: string, fileInput: string): string {
|
|
726
|
+
const requested = fileInput.trim()
|
|
727
|
+
const absolute = isAbsolute(requested) ? resolve(requested) : resolve(workspaceRoot, requested)
|
|
728
|
+
if (!isInside(workspaceRoot, absolute)) throw new Error(`Deck HTML file is outside the workspace: ${requested}`)
|
|
729
|
+
if (!existsSync(absolute) || !statSync(absolute).isFile()) throw new Error(`Deck HTML file not found: ${requested}`)
|
|
730
|
+
const file = workspaceRelative(workspaceRoot, absolute)
|
|
731
|
+
if (!file.startsWith("decks/") || !file.endsWith(".html")) throw new Error(`Deck HTML file must be under decks/*.html: ${file}`)
|
|
732
|
+
return file
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
function migrateDeckDesignLink(html: string, activeHref: string): string {
|
|
736
|
+
const escapedHref = activeHref.replace(/"/g, """)
|
|
737
|
+
if (html.includes(`href="${escapedHref}"`) || html.includes(`href='${escapedHref}'`)) return html
|
|
738
|
+
const linkRe = /<link\b(?=[^>]*\brel=(["'])stylesheet\1)(?=[^>]*\bhref=(["'])\.\/_revela-design\/[^"']+\/design\.css\2)[^>]*>/i
|
|
739
|
+
if (linkRe.test(html)) {
|
|
740
|
+
return html.replace(linkRe, `<link rel="stylesheet" href="${escapedHref}" data-revela-design-link="active">`)
|
|
741
|
+
}
|
|
742
|
+
if (/<\/head>/i.test(html)) {
|
|
743
|
+
return html.replace(/<\/head>/i, ` <link rel="stylesheet" href="${escapedHref}" data-revela-design-link="active">\n</head>`)
|
|
744
|
+
}
|
|
745
|
+
return html
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
function isInside(rootPath: string, candidate: string): boolean {
|
|
749
|
+
const normalizedRoot = resolve(rootPath)
|
|
750
|
+
const normalizedCandidate = resolve(candidate)
|
|
751
|
+
return normalizedCandidate === normalizedRoot || normalizedCandidate.startsWith(normalizedRoot.endsWith(sep) ? normalizedRoot : normalizedRoot + sep)
|
|
752
|
+
}
|
|
753
|
+
|
|
662
754
|
function safe<T>(fn: () => T): T | undefined {
|
|
663
755
|
try {
|
|
664
756
|
return fn()
|