@farberg/reveal-template 1.1.22 → 1.1.24
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/css/dhbw.css
CHANGED
package/init-reveal.js
CHANGED
|
@@ -10,6 +10,7 @@ import ToggleSolutionsPlugin from './plugins/reveal-plugin-toggle-solutions.js';
|
|
|
10
10
|
import DirTreePlugin from './plugins/reveal-plugin-dir-tree.js';
|
|
11
11
|
import PrefixUrlPlugin from './plugins/reveal-plugin-prefix-with-base-url.js';
|
|
12
12
|
import AsciinemaPlugin from './plugins/reveal-plugin-asciinema.js';
|
|
13
|
+
import QuickNavPlugin from './plugins/reveal-plugin-quick-nav.js';
|
|
13
14
|
|
|
14
15
|
// All third-party paths below are written as bare-package specifiers
|
|
15
16
|
// (e.g. "reveal.js/dist/reveal.mjs"). At runtime they are resolved to
|
|
@@ -297,7 +298,8 @@ function buildDennisPlugins(deps) {
|
|
|
297
298
|
ToggleSolutionsPlugin,
|
|
298
299
|
DirTreePlugin(deps.fflate),
|
|
299
300
|
PrefixUrlPlugin,
|
|
300
|
-
AsciinemaPlugin(deps.asciinemaPlayer.create)
|
|
301
|
+
AsciinemaPlugin(deps.asciinemaPlayer.create),
|
|
302
|
+
QuickNavPlugin
|
|
301
303
|
]
|
|
302
304
|
}
|
|
303
305
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Quick Navigation Overlay
|
|
3
|
+
|
|
4
|
+
Press 'q' to open (or close) an overlay listing all h1 and h2 headings
|
|
5
|
+
across the deck. Each heading is a link that jumps to the slide. A text
|
|
6
|
+
input at the top filters the list as you type. Press ESC (or click the
|
|
7
|
+
backdrop / close button) to dismiss the overlay.
|
|
8
|
+
|
|
9
|
+
The list is rendered in multiple columns so 30-60 slides with headings
|
|
10
|
+
can be scanned at a glance.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
function collectHeadings(deck) {
|
|
14
|
+
const items = []
|
|
15
|
+
const horizontalSlides = deck.getSlidesElement().querySelectorAll(':scope > section')
|
|
16
|
+
|
|
17
|
+
horizontalSlides.forEach((hSection, h) => {
|
|
18
|
+
const verticals = hSection.querySelectorAll(':scope > section')
|
|
19
|
+
if (verticals.length > 0) {
|
|
20
|
+
verticals.forEach((vSection, v) => extractFromSection(vSection, h, v, items))
|
|
21
|
+
} else {
|
|
22
|
+
extractFromSection(hSection, h, 0, items)
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
return items
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function extractFromSection(section, h, v, items) {
|
|
30
|
+
const headings = section.querySelectorAll('h1, h2')
|
|
31
|
+
if (headings.length === 0) return
|
|
32
|
+
const slideText = (section.innerText || section.textContent || '').toLowerCase()
|
|
33
|
+
headings.forEach(heading => {
|
|
34
|
+
const text = heading.textContent.trim()
|
|
35
|
+
if (!text) return
|
|
36
|
+
items.push({
|
|
37
|
+
text,
|
|
38
|
+
level: heading.tagName.toLowerCase(),
|
|
39
|
+
searchText: slideText,
|
|
40
|
+
h,
|
|
41
|
+
v
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function injectStyles() {
|
|
47
|
+
if (document.getElementById('quick-nav-styles')) return
|
|
48
|
+
|
|
49
|
+
const css = `
|
|
50
|
+
#quick-nav-overlay {
|
|
51
|
+
position: fixed;
|
|
52
|
+
inset: 0;
|
|
53
|
+
background: rgba(0,0,0,0.65);
|
|
54
|
+
z-index: 10000;
|
|
55
|
+
display: none;
|
|
56
|
+
font-family: sans-serif;
|
|
57
|
+
}
|
|
58
|
+
#quick-nav-overlay.visible { display: block; }
|
|
59
|
+
#quick-nav-overlay .quick-nav-panel {
|
|
60
|
+
position: absolute;
|
|
61
|
+
top: 3vh;
|
|
62
|
+
left: 50%;
|
|
63
|
+
transform: translateX(-50%);
|
|
64
|
+
width: 92vw;
|
|
65
|
+
max-width: 1500px;
|
|
66
|
+
max-height: 94vh;
|
|
67
|
+
background: #fff;
|
|
68
|
+
border-radius: 8px;
|
|
69
|
+
box-shadow: 0 12px 48px rgba(0,0,0,0.4);
|
|
70
|
+
display: flex;
|
|
71
|
+
flex-direction: column;
|
|
72
|
+
overflow: hidden;
|
|
73
|
+
}
|
|
74
|
+
#quick-nav-overlay .quick-nav-header {
|
|
75
|
+
display: flex;
|
|
76
|
+
align-items: center;
|
|
77
|
+
padding: 12px 16px;
|
|
78
|
+
border-bottom: 1px solid #e0e0e0;
|
|
79
|
+
background: #f7f7f7;
|
|
80
|
+
gap: 12px;
|
|
81
|
+
}
|
|
82
|
+
#quick-nav-overlay .quick-nav-title {
|
|
83
|
+
font-weight: bold;
|
|
84
|
+
color: #555;
|
|
85
|
+
font-size: 13px;
|
|
86
|
+
white-space: nowrap;
|
|
87
|
+
}
|
|
88
|
+
#quick-nav-overlay .quick-nav-search {
|
|
89
|
+
flex: 1;
|
|
90
|
+
font-size: 14px;
|
|
91
|
+
padding: 6px 10px;
|
|
92
|
+
border: 1px solid #ccc;
|
|
93
|
+
border-radius: 4px;
|
|
94
|
+
outline: none;
|
|
95
|
+
}
|
|
96
|
+
#quick-nav-overlay .quick-nav-search:focus {
|
|
97
|
+
border-color: #e2001a;
|
|
98
|
+
box-shadow: 0 0 0 2px rgba(226,0,26,0.2);
|
|
99
|
+
}
|
|
100
|
+
#quick-nav-overlay .quick-nav-count {
|
|
101
|
+
color: #888;
|
|
102
|
+
font-size: 11px;
|
|
103
|
+
white-space: nowrap;
|
|
104
|
+
}
|
|
105
|
+
#quick-nav-overlay .quick-nav-close {
|
|
106
|
+
background: transparent;
|
|
107
|
+
border: none;
|
|
108
|
+
font-size: 26px;
|
|
109
|
+
line-height: 1;
|
|
110
|
+
cursor: pointer;
|
|
111
|
+
color: #555;
|
|
112
|
+
padding: 0 6px;
|
|
113
|
+
}
|
|
114
|
+
#quick-nav-overlay .quick-nav-close:hover { color: #e2001a; }
|
|
115
|
+
#quick-nav-overlay .quick-nav-list {
|
|
116
|
+
flex: 1;
|
|
117
|
+
overflow-y: auto;
|
|
118
|
+
padding: 12px 16px;
|
|
119
|
+
column-count: 2;
|
|
120
|
+
column-gap: 24px;
|
|
121
|
+
column-rule: 1px solid #eee;
|
|
122
|
+
}
|
|
123
|
+
@media (min-width: 1100px) {
|
|
124
|
+
#quick-nav-overlay .quick-nav-list { column-count: 3; }
|
|
125
|
+
}
|
|
126
|
+
@media (min-width: 1600px) {
|
|
127
|
+
#quick-nav-overlay .quick-nav-list { column-count: 4; }
|
|
128
|
+
}
|
|
129
|
+
#quick-nav-overlay .quick-nav-item {
|
|
130
|
+
display: block;
|
|
131
|
+
padding: 3px 6px;
|
|
132
|
+
margin: 1px 0;
|
|
133
|
+
border-radius: 3px;
|
|
134
|
+
color: #222;
|
|
135
|
+
text-decoration: none;
|
|
136
|
+
font-size: 12px;
|
|
137
|
+
line-height: 1.3;
|
|
138
|
+
break-inside: avoid;
|
|
139
|
+
cursor: pointer;
|
|
140
|
+
}
|
|
141
|
+
#quick-nav-overlay .quick-nav-item:hover,
|
|
142
|
+
#quick-nav-overlay .quick-nav-item.active {
|
|
143
|
+
background: #e2001a;
|
|
144
|
+
color: #fff;
|
|
145
|
+
}
|
|
146
|
+
#quick-nav-overlay .quick-nav-h1 {
|
|
147
|
+
font-weight: bold;
|
|
148
|
+
font-size: 13px;
|
|
149
|
+
margin-top: 5px;
|
|
150
|
+
}
|
|
151
|
+
#quick-nav-overlay .quick-nav-h2 {
|
|
152
|
+
padding-left: 16px;
|
|
153
|
+
color: #444;
|
|
154
|
+
}
|
|
155
|
+
#quick-nav-overlay .quick-nav-empty {
|
|
156
|
+
padding: 20px;
|
|
157
|
+
color: #888;
|
|
158
|
+
column-span: all;
|
|
159
|
+
text-align: center;
|
|
160
|
+
}
|
|
161
|
+
`
|
|
162
|
+
|
|
163
|
+
const style = document.createElement('style')
|
|
164
|
+
style.id = 'quick-nav-styles'
|
|
165
|
+
style.textContent = css
|
|
166
|
+
document.head.appendChild(style)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function buildOverlay(deck) {
|
|
170
|
+
const overlay = document.createElement('div')
|
|
171
|
+
overlay.id = 'quick-nav-overlay'
|
|
172
|
+
overlay.innerHTML = `
|
|
173
|
+
<div class="quick-nav-panel" role="dialog" aria-label="Quick slide navigation">
|
|
174
|
+
<div class="quick-nav-header">
|
|
175
|
+
<span class="quick-nav-title">Quick Nav</span>
|
|
176
|
+
<input type="text" class="quick-nav-search" placeholder="Filter headings…" autocomplete="off" spellcheck="false" />
|
|
177
|
+
<span class="quick-nav-count"></span>
|
|
178
|
+
<button class="quick-nav-close" title="Close (Esc / q)">×</button>
|
|
179
|
+
</div>
|
|
180
|
+
<div class="quick-nav-list"></div>
|
|
181
|
+
</div>
|
|
182
|
+
`
|
|
183
|
+
document.body.appendChild(overlay)
|
|
184
|
+
|
|
185
|
+
const panel = overlay.querySelector('.quick-nav-panel')
|
|
186
|
+
const input = overlay.querySelector('.quick-nav-search')
|
|
187
|
+
const list = overlay.querySelector('.quick-nav-list')
|
|
188
|
+
const count = overlay.querySelector('.quick-nav-count')
|
|
189
|
+
const closeBtn = overlay.querySelector('.quick-nav-close')
|
|
190
|
+
|
|
191
|
+
let allItems = []
|
|
192
|
+
let filtered = []
|
|
193
|
+
let activeIndex = -1
|
|
194
|
+
|
|
195
|
+
function render() {
|
|
196
|
+
list.innerHTML = ''
|
|
197
|
+
if (filtered.length === 0) {
|
|
198
|
+
const empty = document.createElement('div')
|
|
199
|
+
empty.className = 'quick-nav-empty'
|
|
200
|
+
empty.textContent = 'No matching headings'
|
|
201
|
+
list.appendChild(empty)
|
|
202
|
+
} else {
|
|
203
|
+
filtered.forEach((item, idx) => {
|
|
204
|
+
const a = document.createElement('a')
|
|
205
|
+
a.className = `quick-nav-item quick-nav-${item.level}`
|
|
206
|
+
if (idx === activeIndex) a.classList.add('active')
|
|
207
|
+
a.textContent = item.text
|
|
208
|
+
a.href = '#'
|
|
209
|
+
a.addEventListener('click', e => {
|
|
210
|
+
e.preventDefault()
|
|
211
|
+
jumpTo(item)
|
|
212
|
+
})
|
|
213
|
+
list.appendChild(a)
|
|
214
|
+
})
|
|
215
|
+
}
|
|
216
|
+
count.textContent = `${filtered.length} / ${allItems.length}`
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function applyFilter() {
|
|
220
|
+
const q = input.value.toLowerCase().trim()
|
|
221
|
+
if (q === '') {
|
|
222
|
+
filtered = allItems
|
|
223
|
+
} else {
|
|
224
|
+
filtered = allItems.filter(i => i.searchText.includes(q))
|
|
225
|
+
}
|
|
226
|
+
activeIndex = filtered.length > 0 ? 0 : -1
|
|
227
|
+
render()
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function jumpTo(item) {
|
|
231
|
+
hide()
|
|
232
|
+
deck.slide(item.h, item.v)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function show() {
|
|
236
|
+
allItems = collectHeadings(deck)
|
|
237
|
+
filtered = allItems
|
|
238
|
+
input.value = ''
|
|
239
|
+
activeIndex = filtered.length > 0 ? 0 : -1
|
|
240
|
+
render()
|
|
241
|
+
overlay.classList.add('visible')
|
|
242
|
+
setTimeout(() => input.focus(), 30)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function hide() {
|
|
246
|
+
overlay.classList.remove('visible')
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function isVisible() {
|
|
250
|
+
return overlay.classList.contains('visible')
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function toggle() {
|
|
254
|
+
if (isVisible()) hide()
|
|
255
|
+
else show()
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
input.addEventListener('input', applyFilter)
|
|
259
|
+
closeBtn.addEventListener('click', hide)
|
|
260
|
+
overlay.addEventListener('click', e => {
|
|
261
|
+
if (e.target === overlay) hide()
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
overlay.addEventListener('keydown', e => {
|
|
265
|
+
if (e.key === 'Escape') {
|
|
266
|
+
e.preventDefault()
|
|
267
|
+
e.stopPropagation()
|
|
268
|
+
hide()
|
|
269
|
+
return
|
|
270
|
+
}
|
|
271
|
+
if (e.key === 'Enter' && activeIndex >= 0 && filtered[activeIndex]) {
|
|
272
|
+
e.preventDefault()
|
|
273
|
+
jumpTo(filtered[activeIndex])
|
|
274
|
+
return
|
|
275
|
+
}
|
|
276
|
+
if (e.key === 'ArrowDown') {
|
|
277
|
+
e.preventDefault()
|
|
278
|
+
if (filtered.length === 0) return
|
|
279
|
+
activeIndex = (activeIndex + 1) % filtered.length
|
|
280
|
+
render()
|
|
281
|
+
scrollActiveIntoView()
|
|
282
|
+
return
|
|
283
|
+
}
|
|
284
|
+
if (e.key === 'ArrowUp') {
|
|
285
|
+
e.preventDefault()
|
|
286
|
+
if (filtered.length === 0) return
|
|
287
|
+
activeIndex = (activeIndex - 1 + filtered.length) % filtered.length
|
|
288
|
+
render()
|
|
289
|
+
scrollActiveIntoView()
|
|
290
|
+
return
|
|
291
|
+
}
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
function scrollActiveIntoView() {
|
|
295
|
+
const el = list.querySelector('.quick-nav-item.active')
|
|
296
|
+
if (el) el.scrollIntoView({ block: 'nearest' })
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return { show, hide, toggle, isVisible }
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export default () => {
|
|
303
|
+
|
|
304
|
+
return {
|
|
305
|
+
id: 'quick_nav',
|
|
306
|
+
init: (deck) => {
|
|
307
|
+
injectStyles()
|
|
308
|
+
|
|
309
|
+
deck.on('ready', () => {
|
|
310
|
+
const overlay = buildOverlay(deck)
|
|
311
|
+
|
|
312
|
+
deck.addKeyBinding(
|
|
313
|
+
{ keyCode: 81, key: 'Q', description: 'Quick navigation overlay' },
|
|
314
|
+
() => overlay.toggle()
|
|
315
|
+
)
|
|
316
|
+
})
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
@@ -28,12 +28,19 @@ function showTitle(deck, packageJson) {
|
|
|
28
28
|
let slideTitle = el.innerText;
|
|
29
29
|
|
|
30
30
|
// Mark the enclosing slide so simplemenu hides on it (handled in dhbw.css
|
|
31
|
-
// via body.hide-menubar)
|
|
32
|
-
// still read data-name. Equivalent to writing in the markdown:
|
|
31
|
+
// via body.hide-menubar). Equivalent to writing in the markdown:
|
|
33
32
|
// <!-- .slide: data-state="hide-menubar" -->
|
|
33
|
+
// Reveal mirrors data-state to body classes on every slidechanged event.
|
|
34
|
+
// We're running here in the 'ready' callback — after the first
|
|
35
|
+
// slidechanged has already fired during init — so for the *current*
|
|
36
|
+
// slide we also need to apply the body class manually. Subsequent
|
|
37
|
+
// navigations are then handled by Reveal automatically.
|
|
34
38
|
const section = el.closest('section');
|
|
35
39
|
if (section) {
|
|
36
40
|
section.setAttribute('data-state', 'hide-menubar');
|
|
41
|
+
if (section === deck.getCurrentSlide()) {
|
|
42
|
+
document.body.classList.add('hide-menubar');
|
|
43
|
+
}
|
|
37
44
|
}
|
|
38
45
|
|
|
39
46
|
el.innerHTML = `
|