orz-slides 0.1.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.
@@ -0,0 +1,149 @@
1
+ import { renderLayout } from './layout.js';
2
+ function escapeAttr(v) {
3
+ return v.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
4
+ }
5
+ function regionMap(regions) {
6
+ const m = {};
7
+ for (const r of regions)
8
+ m[r.name] = r.markdown;
9
+ return m;
10
+ }
11
+ /** Fill each empty `.orz-region` placeholder with its rendered markdown. */
12
+ function fillRegions(grid, names, rmap, md) {
13
+ let html = grid;
14
+ for (const name of names) {
15
+ const src = rmap[name] || '';
16
+ const inner = `<div class="markdown-body">${src.trim() ? md.render(src) : ''}</div>`;
17
+ const cell = `<div class="orz-region" data-region="${escapeAttr(name)}"></div>`;
18
+ html = html.replace(cell, `<div class="orz-region" data-region="${escapeAttr(name)}">${inner}</div>`);
19
+ }
20
+ return html;
21
+ }
22
+ function titleBand(slide, md) {
23
+ if (!slide.title)
24
+ return '';
25
+ const inline = md.renderInline ? md.renderInline(slide.title) : md.render(slide.title).replace(/^<p>|<\/p>\s*$/g, '');
26
+ return `<header class="orz-title"><div class="markdown-body"><h2>${inline}</h2></div></header>`;
27
+ }
28
+ function footerBand(slide, deck, md) {
29
+ // Per-slide @footer always wins (and shows on that slide alone). The deck-wide
30
+ // footer shows on every slide EXCEPT the opening title page, which reads
31
+ // cleaner without it (add a @footer to a title slide to force one).
32
+ const isTitle = slide.kind === 'template' && (slide.template ?? 'title') === 'title';
33
+ const src = slide.footer ?? (isTitle ? undefined : deck.footer);
34
+ if (!src)
35
+ return '';
36
+ return `<footer class="orz-footer"><div class="markdown-body">${md.render(src)}</div></footer>`;
37
+ }
38
+ function inlineOr(md, s) {
39
+ if (!s)
40
+ return '';
41
+ return md.renderInline ? md.renderInline(s) : md.render(s).replace(/^<p>|<\/p>\s*$/g, '');
42
+ }
43
+ /** Split a template body into its title (h1), subtitle (h2), and the rest. */
44
+ function splitFields(src) {
45
+ let title = '';
46
+ let subtitle = '';
47
+ const meta = [];
48
+ for (const line of src.split('\n')) {
49
+ const h1 = line.match(/^#\s+(.*\S)\s*$/);
50
+ const h2 = line.match(/^##\s+(.*\S)\s*$/);
51
+ if (h1 && !title)
52
+ title = h1[1];
53
+ else if (h2 && !subtitle)
54
+ subtitle = h2[1];
55
+ else if (line.trim())
56
+ meta.push(line);
57
+ }
58
+ return { title, subtitle, meta: meta.join('\n') };
59
+ }
60
+ function floatBoxes(floats, md) {
61
+ return floats.map((f) => {
62
+ const s = ['position:absolute'];
63
+ const g = f.geom;
64
+ if (g.left)
65
+ s.push(`left:${g.left}`);
66
+ if (g.right)
67
+ s.push(`right:${g.right}`);
68
+ if (g.top)
69
+ s.push(`top:${g.top}`);
70
+ if (g.bottom)
71
+ s.push(`bottom:${g.bottom}`);
72
+ if (g.w)
73
+ s.push(`width:${g.w}`);
74
+ if (g.h)
75
+ s.push(`height:${g.h}`);
76
+ if (g.z != null)
77
+ s.push(`z-index:${g.z}`);
78
+ return `<div class="orz-float" style="${s.join(';')}"><div class="markdown-body">${md.render(f.markdown)}</div></div>`;
79
+ }).join('');
80
+ }
81
+ function notesBlock(slide, md) {
82
+ return slide.notes ? `<aside class="notes">${md.render(slide.notes)}</aside>` : '';
83
+ }
84
+ function sectionAttrs(slide, kind, template) {
85
+ const a = [`class="orz-slide${slide.options.class ? ' ' + escapeAttr(slide.options.class) : ''}"`];
86
+ a.push(`data-fit="${slide.options.fit || 'fit'}"`);
87
+ a.push(`data-kind="${kind}"`);
88
+ if (template)
89
+ a.push(`data-template="${escapeAttr(template)}"`);
90
+ if (slide.options.id)
91
+ a.push(`id="${escapeAttr(slide.options.id)}"`);
92
+ if (slide.options.transition)
93
+ a.push(`data-transition="${escapeAttr(slide.options.transition)}"`);
94
+ if (slide.options.step)
95
+ a.push(`data-step="1"`);
96
+ if (slide.options.bg)
97
+ a.push(`data-background-color="${escapeAttr(slide.options.bg)}"`);
98
+ return a.join(' ');
99
+ }
100
+ function renderNormal(slide, md, deck) {
101
+ const { html: grid, regions } = renderLayout(slide.layout);
102
+ const content = fillRegions(grid, regions, regionMap(slide.regions), md);
103
+ return `<section ${sectionAttrs(slide, 'normal')}>`
104
+ + `<div class="orz-frame">`
105
+ + titleBand(slide, md)
106
+ + `<div class="orz-content">${content}</div>`
107
+ + footerBand(slide, deck, md)
108
+ + `</div>`
109
+ + floatBoxes(slide.floats, md)
110
+ + notesBlock(slide, md)
111
+ + `</section>`;
112
+ }
113
+ function renderTemplate(slide, md, deck) {
114
+ const name = slide.template || 'title';
115
+ const v = slide.templateVariant ? ` orz-v${slide.templateVariant}` : '';
116
+ const body = regionMap(slide.regions)['body'] || '';
117
+ let inner;
118
+ if (name === 'outline') {
119
+ inner = `<div class="orz-outline markdown-body">${md.render(body)}</div>`;
120
+ }
121
+ else if (name === 'section') {
122
+ const f = splitFields(body);
123
+ inner = (f.title ? `<div class="orz-section-main">${inlineOr(md, f.title)}</div>` : '')
124
+ + (f.meta ? `<div class="orz-section-meta markdown-body">${md.render(f.meta)}</div>` : '');
125
+ }
126
+ else {
127
+ // title / closing / unknown → title-style fields (h1 · h2 · meta)
128
+ const f = splitFields(body);
129
+ inner = (f.title ? `<h1 class="orz-title-main">${inlineOr(md, f.title)}</h1>` : '')
130
+ + (f.subtitle ? `<div class="orz-title-sub">${inlineOr(md, f.subtitle)}</div>` : '')
131
+ + (f.meta ? `<div class="orz-title-meta markdown-body">${md.render(f.meta)}</div>` : '');
132
+ }
133
+ return `<section ${sectionAttrs(slide, 'template', name)}>`
134
+ + `<div class="orz-frame">`
135
+ + `<div class="orz-template orz-template-${escapeAttr(name)}${v}" data-orz-copy>${inner}</div>`
136
+ + footerBand(slide, deck, md)
137
+ + `</div>`
138
+ + floatBoxes(slide.floats, md)
139
+ + notesBlock(slide, md)
140
+ + `</section>`;
141
+ }
142
+ /** Render one slide to a reveal `<section>`. */
143
+ export function renderSlide(slide, md, deck) {
144
+ return slide.kind === 'template' ? renderTemplate(slide, md, deck) : renderNormal(slide, md, deck);
145
+ }
146
+ /** Render a whole deck's slides (joined `<section>`s for `.reveal .slides`). */
147
+ export function renderDeck(deck, md) {
148
+ return deck.slides.map((s) => renderSlide(s, md, deck.config)).join('\n');
149
+ }