@samuelbradshaw/highlight-helper-js 0.9.0 → 1.0.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 +563 -101
- package/demo.html +911 -341
- package/highlight-helper.js +1883 -1358
- package/highlight-helper.mjs +1 -1
- package/package.json +1 -1
- package/test-load.html +252 -216
package/demo.html
CHANGED
|
@@ -6,375 +6,945 @@
|
|
|
6
6
|
<meta name="viewport" content="width=device-width, user-scalable=yes, initial-scale=1.0">
|
|
7
7
|
<meta name="google" content="notranslate">
|
|
8
8
|
<style>
|
|
9
|
+
/* GLOBAL STYLES for all demos */
|
|
9
10
|
:root {
|
|
10
|
-
-
|
|
11
|
-
--
|
|
12
|
-
--
|
|
13
|
-
--gray
|
|
14
|
-
--gray-50: hsl(0, 0%, 50%);
|
|
11
|
+
--page-bg: light-dark(white, #222);
|
|
12
|
+
--text-color: light-dark(black, white);
|
|
13
|
+
--light-gray: light-dark(#eee, #444);
|
|
14
|
+
--dark-gray: light-dark(#777, #ccc);
|
|
15
15
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
--gray-90: hsl(0, 0%, 30%);
|
|
20
|
-
--gray-50: hsl(0, 0%, 70%);
|
|
21
|
-
}}
|
|
22
|
-
|
|
23
|
-
body {
|
|
16
|
+
html {
|
|
17
|
+
color-scheme: light dark;
|
|
18
|
+
margin: 1em;
|
|
24
19
|
background-color: var(--page-bg);
|
|
25
20
|
color: var(--text-color);
|
|
21
|
+
font-family: serif;
|
|
26
22
|
line-height: 1.4;
|
|
23
|
+
overflow-x: hidden;
|
|
27
24
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
html:has([name="theme"][value="light"]:checked) { color-scheme: light; }
|
|
26
|
+
html:has([name="theme"][value="dark"]:checked) { color-scheme: dark; }
|
|
27
|
+
section {
|
|
28
|
+
margin: 3em 0;
|
|
29
|
+
border-top: 1px solid var(--text-color);
|
|
33
30
|
}
|
|
34
|
-
|
|
35
|
-
#
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
31
|
+
a { color: var(--dark-gray); }
|
|
32
|
+
#jump-links {
|
|
33
|
+
margin: 0;
|
|
34
|
+
padding-left: 1.5em;
|
|
35
|
+
}
|
|
36
|
+
label {
|
|
37
|
+
display: flex;
|
|
38
|
+
gap: 0.4em;
|
|
39
|
+
align-items: center;
|
|
40
|
+
}
|
|
41
|
+
details { margin: 1em 0; }
|
|
42
|
+
.code-block {
|
|
43
|
+
display: block;
|
|
44
|
+
background-color: var(--light-gray);
|
|
45
|
+
margin: 1em 0; padding: 0.5em;
|
|
46
|
+
border: 1px solid var(--dark-gray);
|
|
47
|
+
}
|
|
48
|
+
.code-block figcaption {
|
|
49
|
+
color: var(--page-bg);
|
|
50
|
+
background-color: var(--dark-gray);
|
|
51
|
+
font-weight: bold;
|
|
52
|
+
margin: -0.5em -0.5em 0.5em -0.5em; padding: 0.3em 0.5em;
|
|
53
|
+
}
|
|
54
|
+
.code-block code {
|
|
55
|
+
display: block;
|
|
56
|
+
text-indent: -2em;
|
|
57
|
+
margin-left: calc(var(--indent) + 2em);
|
|
58
|
+
overflow-wrap: break-word;
|
|
59
|
+
}
|
|
60
|
+
/* Add an invisible space after each code line, to prevent empty code lines from collapsing */
|
|
61
|
+
code::after { content: '\200B'; }
|
|
62
|
+
</style>
|
|
63
|
+
<script src="highlight-helper.js"></script>
|
|
64
|
+
</head>
|
|
65
|
+
<body>
|
|
66
|
+
<p>← <a href="https://github.com/samuelbradshaw/highlight-helper-js">Back to GitHub</a></p>
|
|
67
|
+
<h1>HighlightHelper.js Demo</h1>
|
|
68
|
+
|
|
69
|
+
<p>
|
|
70
|
+
Theme:
|
|
71
|
+
<label><input type="radio" name="theme" value="system-default" checked> System Default</label>
|
|
72
|
+
<label><input type="radio" name="theme" value="light"> Light</label>
|
|
73
|
+
<label><input type="radio" name="theme" value="dark"> Dark</label>
|
|
74
|
+
</p>
|
|
75
|
+
|
|
76
|
+
Jump to:
|
|
77
|
+
<ul id="jump-links"></ul>
|
|
78
|
+
|
|
79
|
+
<section id="basic-highlighter">
|
|
80
|
+
<h2>Demo 1: Basic highlighter</h2>
|
|
81
|
+
|
|
82
|
+
<div class="container">
|
|
83
|
+
<p id="p1"><b>HighlightHelper.js</b> is a JavaScript library for drawing highlights in HTML content. Select text, or tap an existing highlight. Then choose a color or style. Highlights can be drawn across element boundaries, and they can overlap!</p>
|
|
84
|
+
<p id="p2">You can use default fill and underline styles, or <a target="_blank" href="https://github.com/samuelbradshaw/highlight-helper-js#options">define your own</a>. The library can adapt to a variety of <a target="_blank" href="https://github.com/samuelbradshaw/highlight-helper-js#features">use cases</a> and languages.</p>
|
|
85
|
+
<p id="p3">わたしニーファイは<ruby><rb>善</rb><rt>よ</rt></ruby>い<ruby><rb>両</rb><rt>りょう</rt></ruby><ruby><rb>親</rb><rt>しん</rt></ruby>から<ruby><rb>生</rb><rt>う</rt></ruby>まれたので,<ruby><rb>父</rb><rt>ちち</rt></ruby>が<ruby><rb>学</rb><rt>まな</rt></ruby>んだすべてのことの<ruby><rb>中</rb><rt>なか</rt></ruby>から<ruby><rb>幾</rb><rt>いく</rt></ruby>らかの<ruby><rb>教</rb><rt>おし</rt></ruby>えを<ruby><rb>受</rb><rt>う</rt></ruby>けた。</p>
|
|
86
|
+
<p id="p4" style="direction: rtl;">فِي الْبَدْءِ خَلَقَ اللهُ السَّمَاوَاتِ وَالأَرْضَ. وَكَانَتِ الأَرْضُ خَرِبَةً وَخَالِيَةً، وَعَلَى وَجْهِ الْغَمْرِ ظُلْمَةٌ، وَرُوحُ اللهِ يَرِفُّ عَلَى وَجْهِ الْمِيَاهِ. وَقَالَ اللهُ: «لِيَكُنْ نُورٌ»، فَكَانَ نُورٌ.</p>
|
|
87
|
+
<p id="p5">១ព្រះបន្ទូលទាំងឡាយនៃព្រះ ដែលទ្រង់មានព្រះបន្ទូលទៅម៉ូសេ ក្នុងកាលម៉ូសេត្រូវបាននាំឡើងទៅលើភ្នំដ៏ខ្ពស់បំផុត</p>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<div class="controls">
|
|
91
|
+
<div class="toggle-buttons">
|
|
92
|
+
<label class="toggle-proxy" style="--hh-color: hsl(360, 100%, 70%)">
|
|
93
|
+
<input type="radio" name="color" value="red"> R</label>
|
|
94
|
+
<label class="toggle-proxy" style="--hh-color: hsl(30, 100%, 60%)">
|
|
95
|
+
<input type="radio" name="color" value="orange"> O</label>
|
|
96
|
+
<label class="toggle-proxy" style="--hh-color: hsl(50, 100%, 60%)">
|
|
97
|
+
<input type="radio" name="color" value="yellow"> Y</label>
|
|
98
|
+
<label class="toggle-proxy" style="--hh-color: hsl(80, 100%, 45%)">
|
|
99
|
+
<input type="radio" name="color" value="green"> G</label>
|
|
100
|
+
<label class="toggle-proxy" style="--hh-color: hsl(180, 100%, 45%)">
|
|
101
|
+
<input type="radio" name="color" value="blue"> B</label>
|
|
102
|
+
</div>
|
|
103
|
+
<div class="toggle-buttons">
|
|
104
|
+
<button id="remove">Remove</button>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<div class="other-controls">
|
|
108
|
+
<label>
|
|
109
|
+
Drawing mode:
|
|
110
|
+
<select name="drawingMode">
|
|
111
|
+
<option value="svg" selected>SVG</option>
|
|
112
|
+
<option value="highlight-api">Highlight API</option>
|
|
113
|
+
<option value="mark-elements">Mark elements</option>
|
|
114
|
+
</select>
|
|
115
|
+
</label>
|
|
116
|
+
<label>
|
|
117
|
+
Style:
|
|
118
|
+
<select name="style">
|
|
119
|
+
<option value="fill" selected>Fill</option>
|
|
120
|
+
<option value="underline">Underline</option>
|
|
121
|
+
</select>
|
|
122
|
+
</label>
|
|
123
|
+
<label>
|
|
124
|
+
Wrapper:
|
|
125
|
+
<select name="wrapper">
|
|
126
|
+
<option value="" selected>None</option>
|
|
127
|
+
<option value="🙂">🙂 Smile</option>
|
|
128
|
+
<option value="✅">✅ Checkmark</option>
|
|
129
|
+
</select>
|
|
130
|
+
</label>
|
|
131
|
+
<label><input type="checkbox" name="highContrast"> High contrast</label>
|
|
132
|
+
<label><input type="checkbox" name="liveHighlighting"> Live highlighting</label>
|
|
133
|
+
<label><input type="checkbox" name="showHandlesSelect"> Show handles for text selection</label>
|
|
134
|
+
<label><input type="checkbox" name="snapToWord"> Snap to word</label>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<dialog id="disambiguation-panel">
|
|
139
|
+
<h2>Select One</h2>
|
|
140
|
+
<div id="disambiguation-buttons"></div>
|
|
141
|
+
<button id="disambiguation-cancel">Cancel</button>
|
|
142
|
+
</dialog>
|
|
143
|
+
|
|
144
|
+
<script type="module">
|
|
145
|
+
// Prepare highlights
|
|
146
|
+
const highlights = [
|
|
147
|
+
{
|
|
148
|
+
highlightId: 'hh-01',
|
|
149
|
+
color: 'green', style: 'fill',
|
|
150
|
+
wrapper: 'margin-indicator', variables: { content: '🙂' },
|
|
151
|
+
startParagraphId: 'p1', startParagraphOffset: 24,
|
|
152
|
+
endParagraphId: 'p1', endParagraphOffset: 42,
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
highlightId: 'hh-02',
|
|
156
|
+
color: 'yellow', style: 'fill',
|
|
157
|
+
startParagraphId: 'p1', startParagraphOffset: 211,
|
|
158
|
+
endParagraphId: 'p1', endParagraphOffset: 219,
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
highlightId: 'hh-03',
|
|
162
|
+
color: 'blue', style: 'fill',
|
|
163
|
+
startParagraphId: 'p1', startParagraphOffset: 216,
|
|
164
|
+
endParagraphId: 'p1', endParagraphOffset: 228,
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
highlightId: 'hh-04',
|
|
168
|
+
color: 'red', style: 'fill',
|
|
169
|
+
startParagraphId: 'p2', startParagraphOffset: 12,
|
|
170
|
+
endParagraphId: 'p2', endParagraphOffset: 24,
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
highlightId: 'hh-05',
|
|
174
|
+
color: 'red', style: 'underline',
|
|
175
|
+
startParagraphId: 'p2', startParagraphOffset: 29,
|
|
176
|
+
endParagraphId: 'p2', endParagraphOffset: 38,
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
highlightId: 'hh-06',
|
|
180
|
+
color: 'orange', style: 'fill',
|
|
181
|
+
startParagraphId: 'p3', startParagraphOffset: 56,
|
|
182
|
+
endParagraphId: 'p3', endParagraphOffset: 65,
|
|
183
|
+
},
|
|
184
|
+
];
|
|
185
|
+
|
|
186
|
+
// Variables
|
|
187
|
+
const container = document.querySelector('#basic-highlighter .container');
|
|
188
|
+
let activeHighlightId = null;
|
|
189
|
+
let previousColor = 'yellow';
|
|
190
|
+
let previousStyle = 'fill';
|
|
191
|
+
let previousWrapper = '';
|
|
192
|
+
let liveHighlighting = false;
|
|
193
|
+
|
|
194
|
+
// Create a Highlighter instance
|
|
195
|
+
const highlighter = new Highlighter('#basic-highlighter .container', '[id]');
|
|
196
|
+
|
|
197
|
+
// Set options and load highlights
|
|
198
|
+
const options = {
|
|
199
|
+
drawingMode: 'svg',
|
|
200
|
+
enableHover: true,
|
|
201
|
+
styleDefs: {
|
|
202
|
+
'fill': {
|
|
203
|
+
css: 'background-color: hsl(from var(--hh-color) h s l / var(--highlight-opacity, 50%));',
|
|
204
|
+
cssActive: 'background-color: hsl(from var(--hh-color) h s l / var(--highlight-opacity-active, 80%));',
|
|
205
|
+
svg: '<rect x="{line.x}" y="{line.y}" style="fill: hsl(from var(--hh-color) h s l / var(--highlight-opacity, 50%)); width: {line.width}px; height: calc({line.height}px * 0.85); transform: translateY(calc({line.height}px * 0.15));" />',
|
|
206
|
+
svgActive: `
|
|
207
|
+
<rect x="{line.x}" y="{line.y}" style="fill: hsl(from var(--hh-color) h s l / var(--highlight-opacity-active, 80%)); width: {line.width}px; height: calc({line.height}px * 0.85); transform: translateY(calc({line.height}px * 0.15));" />
|
|
208
|
+
`,
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
wrapperDefs: {
|
|
212
|
+
'margin-indicator': {
|
|
213
|
+
start: '<span class="margin-indicator"><span class="note" data-content="{content}" aria-label="{ariaLabel}"></span></span>⁠',
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
highlighter.setOptions(options);
|
|
218
|
+
highlighter.loadHighlights(highlights);
|
|
219
|
+
|
|
220
|
+
// Add event listener for hover
|
|
221
|
+
container.addEventListener('hh:hover', (event) => {
|
|
222
|
+
container.style.cursor = (event.detail.targetCount > 0 && !highlighter.getActiveHighlightId()) ? 'pointer' : '';
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// Add event listener for tap
|
|
226
|
+
container.addEventListener('hh:tap', respondToTap);
|
|
227
|
+
function respondToTap(event) {
|
|
228
|
+
if (event.detail.targetCount === 0 || event.detail.isLongPress || event.detail.selectionType === 'Range') return;
|
|
229
|
+
if (event.detail.targetCount > 1) {
|
|
230
|
+
handleAmbiguousTap(event);
|
|
231
|
+
} else if (event.detail.targetCount === 1) {
|
|
232
|
+
if (event.detail.highlights.length === 1) {
|
|
233
|
+
highlighter.activateHighlight(event.detail.highlights[0].highlightId);
|
|
234
|
+
} else if (event.detail.wrappers.length === 1) {
|
|
235
|
+
highlighter.activateHighlight(event.detail.wrappers[0].highlight.highlightId);
|
|
236
|
+
} else if (event.detail.hyperlinks.length === 1) {
|
|
237
|
+
highlighter.activateHyperlink(event.detail.hyperlinks[0].index);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Load disambiguation panel for ambiguous taps
|
|
243
|
+
const disambiguationPanel = document.getElementById('disambiguation-panel');
|
|
244
|
+
const disambiguationButtons = document.getElementById('disambiguation-buttons');
|
|
245
|
+
const disambiguationCancelButton = document.getElementById('disambiguation-cancel');
|
|
246
|
+
disambiguationCancelButton.addEventListener('click', (event) => disambiguationPanel.close());
|
|
247
|
+
const handleAmbiguousTap = (event) => {
|
|
248
|
+
// Build buttons
|
|
249
|
+
let buttonsHtml = '';
|
|
250
|
+
const highlightIds = new Set();
|
|
251
|
+
for (const highlight of event.detail.highlights) highlightIds.add(highlight.highlightId);
|
|
252
|
+
for (const wrapper of event.detail.wrappers) highlightIds.add(wrapper.highlight.highlightId);
|
|
253
|
+
for (const highlight of highlighter.getHighlightInfo(highlightIds)) {
|
|
254
|
+
let colorString = highlighter.getOptions().colorDefs[highlight.color];
|
|
255
|
+
let styleString = highlighter.getOptions().styleDefs[highlight.style]['css'];
|
|
256
|
+
buttonsHtml += `
|
|
257
|
+
<button data-highlight-id="${highlight.highlightId}" style="--hh-color: ${colorString};">
|
|
258
|
+
<span style="${styleString}">${highlight.rangeText}</span>
|
|
259
|
+
</button>
|
|
260
|
+
`;
|
|
261
|
+
}
|
|
262
|
+
for (const hyperlink of event.detail.hyperlinks) {
|
|
263
|
+
buttonsHtml += `
|
|
264
|
+
<button data-hyperlink-index="${hyperlink.index}">
|
|
265
|
+
<span style="color: var(--dark-gray); text-decoration: underline;">${hyperlink.text}</span>
|
|
266
|
+
</button>
|
|
267
|
+
`;
|
|
49
268
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
269
|
+
disambiguationButtons.innerHTML = buttonsHtml;
|
|
270
|
+
|
|
271
|
+
// Add event listeners to buttons
|
|
272
|
+
for (const button of disambiguationButtons.children) {
|
|
273
|
+
button.addEventListener('click', () => {
|
|
274
|
+
disambiguationPanel.close();
|
|
275
|
+
if (button.dataset.highlightId) {
|
|
276
|
+
highlighter.activateHighlight(button.dataset.highlightId);
|
|
277
|
+
} else if (button.dataset.hyperlinkIndex) {
|
|
278
|
+
highlighter.activateHyperlink(button.dataset.hyperlinkIndex);
|
|
279
|
+
}
|
|
280
|
+
});
|
|
62
281
|
}
|
|
282
|
+
|
|
283
|
+
// Waiting 10 milliseconds before showing the disambiguation panel is a workaround for an issue in Android Chrome where a click event fires right after the pointerup event that triggered hh:tap. If the click event happens to be at the same position as a button in the panel, the button will receive the click, dismissing the panel before the user sees it.
|
|
284
|
+
setTimeout(function() { disambiguationPanel.showModal(); }, 10);
|
|
63
285
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
line-height: 0.5em;
|
|
70
|
-
&::before {
|
|
71
|
-
content: attr(data-marker) / attr(data-accessibility-label);
|
|
286
|
+
|
|
287
|
+
// Add event listeners for removing a highlight
|
|
288
|
+
function removeHighlight(event) {
|
|
289
|
+
const activeHighlightId = highlighter.getActiveHighlightId();
|
|
290
|
+
highlighter.removeHighlights([activeHighlightId]);
|
|
72
291
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
292
|
+
document.getElementById('remove').addEventListener('click', removeHighlight);
|
|
293
|
+
document.addEventListener('keydown', (event) => {
|
|
294
|
+
if (event.key === 'Backspace') removeHighlight(event);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// Add an event listener for selection change (used for live highlighting)
|
|
298
|
+
function respondToSelectionChange(event) {
|
|
299
|
+
if (liveHighlighting && !highlighter.getActiveHighlightId()) {
|
|
300
|
+
highlighter.createOrUpdateHighlight({
|
|
301
|
+
color: previousColor,
|
|
302
|
+
style: previousStyle,
|
|
303
|
+
wrapper: previousWrapper ? 'margin-indicator' : '',
|
|
304
|
+
variables: previousWrapper ? { content: previousWrapper } : {},
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
container.addEventListener('hh:selectionchange', respondToSelectionChange);
|
|
309
|
+
|
|
310
|
+
const colorButtons = document.querySelectorAll('[name="color"]');
|
|
311
|
+
for (const colorButton of colorButtons) {
|
|
312
|
+
colorButton.addEventListener('change', (event) => {
|
|
313
|
+
highlighter.createOrUpdateHighlight({ color: colorButton.value });
|
|
314
|
+
if (window.getSelection().type !== 'Range') colorButton.checked = false;
|
|
315
|
+
if (colorButton.value) previousColor = colorButton.value;
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const drawingModeMenu = document.querySelector('[name="drawingMode"]');
|
|
320
|
+
drawingModeMenu.addEventListener('change', (event) => {
|
|
321
|
+
highlighter.setOptions({ drawingMode: drawingModeMenu.value });
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
const styleMenu = document.querySelector('[name="style"]');
|
|
325
|
+
styleMenu.addEventListener('change', (event) => {
|
|
326
|
+
highlighter.createOrUpdateHighlight({ style: styleMenu.value });
|
|
327
|
+
previousStyle = styleMenu.value;
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
const wrapperMenu = document.querySelector('[name="wrapper"]');
|
|
331
|
+
wrapperMenu.addEventListener('change', (event) => {
|
|
332
|
+
highlighter.createOrUpdateHighlight({
|
|
333
|
+
wrapper: wrapperMenu.value ? 'margin-indicator' : '',
|
|
334
|
+
variables: { content: wrapperMenu.value },
|
|
335
|
+
});
|
|
336
|
+
previousWrapper = wrapperMenu.value;
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
function setColorOpacity(highContrast = false) {
|
|
340
|
+
if (highContrast) {
|
|
341
|
+
container.style.setProperty('--highlight-opacity', '80%');
|
|
342
|
+
container.style.setProperty('--highlight-opacity-active', '100%');
|
|
343
|
+
} else {
|
|
344
|
+
container.style.setProperty('--highlight-opacity', '50%');
|
|
345
|
+
container.style.setProperty('--highlight-opacity-active', '80%');
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
const highContrastToggle = document.querySelector('[name="highContrast"]');
|
|
349
|
+
highContrastToggle.addEventListener('change', (event) => { setColorOpacity(highContrastToggle.checked) });
|
|
350
|
+
setColorOpacity();
|
|
351
|
+
|
|
352
|
+
const liveHighlightingToggle = document.querySelector('[name="liveHighlighting"]');
|
|
353
|
+
liveHighlightingToggle.addEventListener('change', (event) => {
|
|
354
|
+
liveHighlighting = liveHighlightingToggle.checked;
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
const showHandlesSelectToggle = document.querySelector('[name="showHandlesSelect"]');
|
|
358
|
+
showHandlesSelectToggle.addEventListener('change', (event) => {
|
|
359
|
+
const showDragHandles = ['highlights'];
|
|
360
|
+
if (showHandlesSelectToggle.checked) showDragHandles.push('selection');
|
|
361
|
+
highlighter.setOptions({ showDragHandles: showDragHandles });
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
const snapToWordToggle = document.querySelector('[name="snapToWord"]');
|
|
365
|
+
snapToWordToggle.addEventListener('change', (event) => {
|
|
366
|
+
highlighter.setOptions({ snapToWord: snapToWordToggle.checked });
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
function handleSelectionChange(event) {
|
|
370
|
+
if (activeHighlightId !== highlighter.getActiveHighlightId()) {
|
|
371
|
+
activeHighlightId = highlighter.getActiveHighlightId();
|
|
372
|
+
const activeHighlightInfo = highlighter.getHighlightInfo([activeHighlightId])?.[0] ?? null;
|
|
373
|
+
if (activeHighlightInfo) {
|
|
374
|
+
document.querySelector(`[name="color"][value="${activeHighlightInfo.color}"]`).checked = true;
|
|
375
|
+
} else {
|
|
376
|
+
const checkedColorButton = document.querySelector(`[name="color"]:checked`);
|
|
377
|
+
if (checkedColorButton) checkedColorButton.checked = false;
|
|
378
|
+
}
|
|
379
|
+
styleMenu.value = activeHighlightInfo?.style ?? 'fill';
|
|
380
|
+
wrapperMenu.value = activeHighlightInfo?.variables?.content ?? '';
|
|
381
|
+
}
|
|
382
|
+
moveMarginIndicator(event);
|
|
383
|
+
}
|
|
384
|
+
container.addEventListener('hh:selectionchange', handleSelectionChange);
|
|
385
|
+
|
|
386
|
+
// Update margin indicator position when a highlight with a start wrapper is active
|
|
387
|
+
function moveMarginIndicator(event) {
|
|
388
|
+
const activeHighlightId = highlighter.getActiveHighlightId();
|
|
389
|
+
if (!event.detail.activeHighlightId) return;
|
|
390
|
+
const marginIndicator = container.querySelector(`[data-hh-wrapper][data-hh-position="start"][data-hh-highlight-id="${activeHighlightId}"] .note`);
|
|
391
|
+
if (marginIndicator) {
|
|
392
|
+
marginIndicator.style.top = `${event.detail.rangeRect.top}px`;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
</script>
|
|
397
|
+
<style>
|
|
398
|
+
#basic-highlighter .container {
|
|
399
|
+
padding-right: 1em;
|
|
400
|
+
}
|
|
401
|
+
#disambiguation-panel {
|
|
402
|
+
width: 400px; max-width: 100%;
|
|
403
|
+
box-sizing: border-box;
|
|
404
|
+
}
|
|
405
|
+
#disambiguation-panel h2 { margin: 0 }
|
|
83
406
|
#disambiguation-buttons {
|
|
84
407
|
display: flex;
|
|
85
408
|
flex-direction: column;
|
|
86
|
-
|
|
87
|
-
display: inline-block;
|
|
88
|
-
border: none;
|
|
89
|
-
background-color: inherit;
|
|
90
|
-
color: inherit;
|
|
91
|
-
font-family: inherit;
|
|
92
|
-
font-size: inherit;
|
|
93
|
-
text-align: inherit;
|
|
94
|
-
margin-bottom: 1em;
|
|
95
|
-
& * {
|
|
96
|
-
display: inline;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
409
|
+
margin: 0.5em 0;
|
|
99
410
|
}
|
|
100
|
-
|
|
411
|
+
#disambiguation-buttons button {
|
|
412
|
+
all: unset;
|
|
413
|
+
display: block;
|
|
414
|
+
cursor: pointer;
|
|
415
|
+
padding: 0.5em 0;
|
|
416
|
+
}
|
|
417
|
+
#disambiguation-buttons button * { display: inline; }
|
|
418
|
+
#disambiguation-panel::backdrop {
|
|
101
419
|
background-color: hsla(0, 0%, 0%, 0.5);
|
|
102
420
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
421
|
+
#basic-highlighter .controls {
|
|
422
|
+
border: 1px solid var(--dark-gray);
|
|
423
|
+
background-color: var(--page-bg);
|
|
424
|
+
margin: 2em 0; padding: 1em;
|
|
425
|
+
-webkit-user-select: none;
|
|
426
|
+
user-select: none;
|
|
427
|
+
max-width: 600px;
|
|
428
|
+
font-family: sans-serif;
|
|
429
|
+
font-size: 0.8em;
|
|
430
|
+
}
|
|
431
|
+
#basic-highlighter .toggle-buttons {
|
|
432
|
+
display: flex;
|
|
433
|
+
justify-content: stretch;
|
|
434
|
+
gap: 0.8em;
|
|
435
|
+
margin-bottom: 0.8em;
|
|
436
|
+
}
|
|
437
|
+
#basic-highlighter .toggle-buttons label,
|
|
438
|
+
#basic-highlighter .toggle-buttons button {
|
|
439
|
+
all: unset;
|
|
440
|
+
flex-grow: 1;
|
|
441
|
+
border: 1px solid var(--dark-gray);
|
|
442
|
+
background-color: var(--hh-color, var(--light-gray));
|
|
443
|
+
padding: 0.3em 0.6em;
|
|
444
|
+
border-radius: 0.25em;
|
|
445
|
+
text-align: center;
|
|
446
|
+
cursor: pointer;
|
|
447
|
+
}
|
|
448
|
+
#basic-highlighter .toggle-proxy:has(input:checked),
|
|
449
|
+
#basic-highlighter button:focus-visible {
|
|
450
|
+
outline: 4px solid hsl(from var(--hh-color, var(--dark-gray)) h s l / 60%);
|
|
451
|
+
outline-offset: 0;
|
|
452
|
+
}
|
|
453
|
+
#basic-highlighter .toggle-proxy input {
|
|
454
|
+
position: absolute;
|
|
455
|
+
width: 1px; height: 1px;
|
|
456
|
+
margin: -1px; padding: 0;
|
|
457
|
+
overflow: hidden;
|
|
458
|
+
clip-path: inset(50%);
|
|
459
|
+
border: 0;
|
|
460
|
+
}
|
|
461
|
+
#basic-highlighter .other-controls {
|
|
462
|
+
display: flex;
|
|
463
|
+
flex-wrap: wrap;
|
|
464
|
+
gap: 0.6em;
|
|
465
|
+
margin-top: 1.2em;
|
|
466
|
+
}
|
|
467
|
+
[data-hh-wrapper="margin-indicator"] {
|
|
468
|
+
white-space: nowrap;
|
|
469
|
+
}
|
|
470
|
+
#basic-highlighter .margin-indicator {
|
|
471
|
+
display: inline-block;
|
|
472
|
+
font-style: normal;
|
|
473
|
+
height: 1em;
|
|
474
|
+
}
|
|
475
|
+
#basic-highlighter .margin-indicator .note {
|
|
476
|
+
visibility: visible;
|
|
477
|
+
position: absolute;
|
|
478
|
+
inset-inline-end: 0.8rem;
|
|
479
|
+
font-size: 1.2rem;
|
|
480
|
+
margin-top: -0.2rem;
|
|
481
|
+
}
|
|
482
|
+
#basic-highlighter .margin-indicator .note::before {
|
|
483
|
+
content: attr(data-content);
|
|
484
|
+
}
|
|
485
|
+
@media (width <= 500px) {
|
|
486
|
+
#basic-highlighter .controls {
|
|
487
|
+
position: sticky;
|
|
488
|
+
bottom: 0;
|
|
489
|
+
margin: 0 -1em;
|
|
490
|
+
border-width: 1px 0 0 0;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
</style>
|
|
494
|
+
|
|
495
|
+
</section>
|
|
496
|
+
|
|
497
|
+
<p id="compatibility-warning">Warning: For simpler code snippets, the below demos use <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@scope">CSS @scope</a>, which is not supported on your browser.</p>
|
|
498
|
+
|
|
499
|
+
<section id="audio-sync">
|
|
500
|
+
<h2>Demo 2: Audio sync</h2>
|
|
501
|
+
<audio src="https://media2.ldscdn.org/assets/scriptures/the-new-testament/2015-11-1460-1-corinthians-13-male-voice-64k-eng.mp3" controls></audio>
|
|
502
|
+
<div class="container">
|
|
503
|
+
<p id="title-1">CHAPTER 13</p>
|
|
504
|
+
<p id="summary-1">Paul discusses the high status of charity—Charity, a pure love, excels and exceeds almost all else.</p>
|
|
505
|
+
<div class="columns">
|
|
506
|
+
<div class="verse-group">
|
|
507
|
+
<p class="verse" id="verse-1"> <span class="verse-number">1</span> <span class="drop-cap">T</span>hough I speak with the tongues of men and of angels, and have not charity, I am become as sounding brass, or a tinkling cymbal.</p>
|
|
508
|
+
<p class="verse" id="verse-2"><span class="verse-number">2</span> And though I have the gift of prophecy, and understand all mysteries, and all knowledge; and though I have all faith, so that I could remove mountains, and have not charity, I am nothing.</p>
|
|
509
|
+
<p class="verse" id="verse-3"><span class="verse-number">3</span> And though I bestow all my goods to feed the poor, and though I give my body to be burned, and have not charity, it profiteth me nothing.</p>
|
|
510
|
+
</div>
|
|
511
|
+
<div class="verse-group">
|
|
512
|
+
<p class="verse" id="verse-4"><span class="verse-number">4</span> Charity suffereth long, and is kind; charity envieth not; charity vaunteth not itself, is not puffed up,</p>
|
|
513
|
+
<p class="verse" id="verse-5"><span class="verse-number">5</span> Doth not behave itself unseemly, seeketh not her own, is not easily provoked, thinketh no evil;</p>
|
|
514
|
+
<p class="verse" id="verse-6"><span class="verse-number">6</span> Rejoiceth not in iniquity, but rejoiceth in the truth;</p>
|
|
515
|
+
<p class="verse" id="verse-7"><span class="verse-number">7</span> Beareth all things, believeth all things, hopeth all things, endureth all things.</p>
|
|
516
|
+
<p class="verse" id="verse-8"><span class="verse-number">8</span> Charity never faileth: but whether there be prophecies, they shall fail; whether there be tongues, they shall cease; whether there be knowledge, it shall vanish away.</p>
|
|
517
|
+
<p class="verse" id="verse-9"><span class="verse-number">9</span> For we know in part, and we prophesy in part.</p>
|
|
518
|
+
<p class="verse" id="verse-10"><span class="verse-number">10</span> But when that which is perfect is come, then that which is in part shall be done away.</p>
|
|
519
|
+
<p class="verse" id="verse-11"><span class="verse-number">11</span> When I was a child, I spake as a child, I understood as a child, I thought as a child: but when I became a man, I put away childish things.</p>
|
|
520
|
+
<p class="verse" id="verse-12"><span class="verse-number">12</span> For now we see through a glass, darkly; but then face to face: now I know in part; but then shall I know even as also I am known.</p>
|
|
521
|
+
</div>
|
|
522
|
+
<div class="verse-group">
|
|
523
|
+
<p class="verse" id="verse-13"><span class="verse-number">13</span> And now abideth faith, hope, charity, these three; but the greatest of these is charity.</p>
|
|
524
|
+
</div>
|
|
525
|
+
<hr>
|
|
526
|
+
<p class="explanation">This demo example has two highlights: a bold highlight for the current word, and an invisible highlight for the current verse. The highlights move as the audio advances. The callout bar is a wrapper on the verse highlight.</p>
|
|
527
|
+
</div>
|
|
159
528
|
</div>
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
{
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
styleButton.addEventListener('click', () => { highlighter.createOrUpdateHighlight({ style: styleButton.value }); });
|
|
240
|
-
}
|
|
241
|
-
const wrapperButtons = document.getElementsByName('hh-wrapper');
|
|
242
|
-
for (const wrapperButton of wrapperButtons) {
|
|
243
|
-
wrapperButton.addEventListener('click', () => { highlighter.createOrUpdateHighlight({ wrapper: wrapperButton.value }); });
|
|
244
|
-
}
|
|
245
|
-
const removeButton = document.getElementsByName('hh-remove')[0];
|
|
246
|
-
removeButton.addEventListener('click', () => { highlighter.removeHighlights([highlighter.getActiveHighlightId()]); });
|
|
247
|
-
document.addEventListener('keydown', respondToKeyPress);
|
|
248
|
-
function respondToKeyPress(event) {
|
|
249
|
-
if (event.key === 'Backspace') highlighter.removeHighlights([highlighter.getActiveHighlightId()]);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// Call HighlightHelper.js methods when options are changed
|
|
253
|
-
const rememberStyleCheckbox = document.getElementById('hh-remember-style');
|
|
254
|
-
const snapToWordCheckbox = document.getElementById('hh-snap-to-word');
|
|
255
|
-
const pointerModeRadioButtons = document.getElementsByName('hh-pointer-mode');
|
|
256
|
-
const drawingModeRadioButtons = document.getElementsByName('hh-drawing-mode');
|
|
257
|
-
rememberStyleCheckbox.addEventListener('change', (event) => { highlighter.setOption('rememberStyle', event.currentTarget.checked); });
|
|
258
|
-
snapToWordCheckbox.addEventListener('change', (event) => { highlighter.setOption('snapToWord', event.currentTarget.checked); });
|
|
259
|
-
for (const pointerModeRadioButton of pointerModeRadioButtons) {
|
|
260
|
-
pointerModeRadioButton.addEventListener('change', (event) => {
|
|
261
|
-
let pointerMode = document.querySelector('[name="hh-pointer-mode"]:checked').value;
|
|
262
|
-
highlighter.setOption('pointerMode', pointerMode);
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
for (const drawingModeRadioButton of drawingModeRadioButtons) {
|
|
266
|
-
drawingModeRadioButton.addEventListener('change', (event) => {
|
|
267
|
-
let drawingMode = document.querySelector('[name="hh-drawing-mode"]:checked').value;
|
|
268
|
-
if (drawingMode === 'highlight-api' && !CSS.highlights) {
|
|
269
|
-
alert('Your browser doesn’t support the Custom Highlight API.');
|
|
270
|
-
} else {
|
|
271
|
-
highlighter.setOption('drawingMode', drawingMode);
|
|
529
|
+
|
|
530
|
+
<script type="module">
|
|
531
|
+
const options = {
|
|
532
|
+
colorDefs: { 'red': '#d00' },
|
|
533
|
+
styleDefs: {
|
|
534
|
+
'bold': { css: 'font-weight: bold;' },
|
|
535
|
+
'unstyled': {},
|
|
536
|
+
},
|
|
537
|
+
wrapperDefs: {
|
|
538
|
+
'callout-bar': {
|
|
539
|
+
start: '<div class="callout-bar" style="top: calc({range.top}px + 0.3em); left: calc({column.left}px - 0.8em); height: {range.height}px; background-color: var(--hh-color);" data-verse-number="{verseNumber}"></div>',
|
|
540
|
+
},
|
|
541
|
+
},
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
// Load Highlighter instance
|
|
545
|
+
const highlighter = new Highlighter('#audio-sync .container', '[id]');
|
|
546
|
+
highlighter.setOptions(options);
|
|
547
|
+
|
|
548
|
+
// Paragraph/verse and word timestamps
|
|
549
|
+
const paragraphTimestamps = [0.88, 2.64, 11.6, 21.56, 35.82, 45.86, 55.68, 64.54, 69.52, 76.36, 88.74, 93.14, 99.1, 111.38, 122.36];
|
|
550
|
+
const wordTimeStamps = [0.88, 1.1, 2.64, 2.8, 3.38, 3.52, 3.74, 4.22, 4.52, 5.84, 6.78, 6.88, 7.18, 7.98, 8.38, 8.8, 9.24, 9.78, 10.28, 11.6, 11.74, 11.94, 12.28, 12.66, 12.84, 13.18, 13.46, 13.78, 14.2, 14.34, 15.28, 15.4, 15.58, 15.8, 17.08, 17.22, 17.38, 17.76, 18.08, 18.52, 19.62, 19.74, 19.88, 20.26, 21.56, 21.66, 21.88, 22.12, 22.36, 22.6, 23.0, 23.12, 24.26, 24.4, 24.9, 25.32, 26.26, 26.36, 26.54, 27.62, 27.72, 27.92, 28.12, 28.4, 28.7, 29.84, 30.02, 30.18, 30.34, 30.54, 30.96, 32.06, 32.18, 32.36, 32.64, 33.96, 34.1, 34.26, 35.82, 35.82, 36.0, 36.22, 36.74, 37.04, 37.28, 37.64, 38.1, 38.26, 38.5, 39.44, 39.54, 39.76, 39.92, 40.08, 40.3, 40.72, 40.94, 41.12, 41.94, 42.06, 42.28, 42.52, 43.78, 44.02, 44.46, 44.7, 45.86, 46.26, 46.96, 47.78, 47.9, 48.06, 49.18, 49.5, 50.16, 51.3, 51.7, 52.36, 52.58, 53.68, 53.84, 54.04, 54.54, 55.68, 55.82, 56.04, 56.44, 56.94, 58.4, 58.86, 59.06, 59.38, 60.26, 60.4, 60.6, 61.1, 62.46, 62.94, 63.16, 64.54, 65.06, 65.36, 65.72, 66.76, 66.88, 67.62, 67.94, 68.08, 69.52, 69.74, 70.0, 70.96, 71.44, 71.68, 72.66, 73.0, 73.34, 74.32, 75.0, 75.16, 76.36, 76.78, 77.3, 78.66, 78.76, 79.06, 79.28, 79.46, 80.72, 80.88, 81.16, 82.18, 82.34, 82.56, 82.74, 83.74, 83.82, 84.06, 85.12, 85.3, 85.54, 85.74, 86.74, 86.84, 87.06, 87.44, 88.74, 88.84, 89.02, 89.34, 89.56, 90.42, 90.52, 90.68, 91.4, 91.82, 93.14, 93.14, 93.36, 93.58, 93.82, 94.2, 94.46, 94.84, 95.74, 95.88, 96.08, 96.34, 96.54, 96.72, 97.44, 97.54, 97.72, 97.94, 99.1, 99.18, 99.36, 99.54, 99.74, 100.46, 100.56, 100.98, 101.18, 101.3, 102.36, 102.46, 102.88, 103.6, 103.72, 104.68, 104.8, 105.1, 105.58, 105.68, 106.82, 106.92, 107.12, 107.3, 107.6, 107.9, 108.8, 108.9, 109.1, 109.4, 109.96, 111.38, 111.48, 111.74, 112.02, 112.34, 112.62, 112.82, 113.42, 114.36, 114.46, 114.76, 115.16, 115.4, 116.42, 116.58, 116.8, 117.1, 117.38, 118.32, 118.42, 118.64, 118.9, 119.12, 119.42, 119.9, 120.16, 120.56, 121.0, 121.16, 122.36, 122.36, 122.66, 123.3, 124.26, 125.3, 126.4, 126.62, 127.84, 127.94, 128.1, 128.48, 128.8, 129.16, 129.6];
|
|
551
|
+
|
|
552
|
+
const audio = document.querySelector('#audio-sync audio');
|
|
553
|
+
const container = document.querySelector('#audio-sync .container');
|
|
554
|
+
const paragraphs = container.querySelectorAll('[id]');
|
|
555
|
+
const dataTrack = audio.addTextTrack('metadata');
|
|
556
|
+
let currentParagraphId;
|
|
557
|
+
|
|
558
|
+
// Add event listeners for playback
|
|
559
|
+
audio.addEventListener('play', () => { container.dataset.playing = '' });
|
|
560
|
+
audio.addEventListener('pause', () => { delete container.dataset.playing });
|
|
561
|
+
audio.addEventListener('ended', () => { highlighter.removeHighlights(); });
|
|
562
|
+
|
|
563
|
+
// Add audio cues for paragraphs and words
|
|
564
|
+
let wordCounter = 0;
|
|
565
|
+
for (const paragraph of paragraphs) {
|
|
566
|
+
let charCounter = 0;
|
|
567
|
+
const words = paragraph.textContent.trim().split(/(?<=[^\s—]+[\s—]+)/g);
|
|
568
|
+
for (const word of words) {
|
|
569
|
+
const startOffset = charCounter;
|
|
570
|
+
charCounter += word.length;
|
|
571
|
+
if (/^\d+\s$/.test(word)) continue; // Skip verse numbers
|
|
572
|
+
|
|
573
|
+
const cue = new VTTCue(wordTimeStamps[wordCounter], wordTimeStamps[wordCounter + 1] ?? 1e9, '');
|
|
574
|
+
cue.data = {
|
|
575
|
+
paragraphId: paragraph.id,
|
|
576
|
+
startOffset: startOffset,
|
|
577
|
+
endOffset: charCounter,
|
|
578
|
+
}
|
|
579
|
+
dataTrack.addCue(cue);
|
|
580
|
+
wordCounter += 1;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// Add event listener for audio cue change
|
|
585
|
+
dataTrack.addEventListener('cuechange', (event) => {
|
|
586
|
+
const cueData = dataTrack.activeCues[0]?.data;
|
|
587
|
+
if (!cueData) return;
|
|
588
|
+
const highlights = highlighter.getHighlightInfo();
|
|
589
|
+
const paragraphHighlight = highlights.find(hl => hl.highlightId === 'hh-paragraph');
|
|
590
|
+
const wordHighlight = highlights.find(hl => hl.highlightId === 'hh-word');
|
|
591
|
+
|
|
592
|
+
// Update paragraph/verse highlight
|
|
593
|
+
if (cueData.paragraphId !== currentParagraphId) {
|
|
594
|
+
const newParagraph = document.getElementById(cueData.paragraphId);
|
|
595
|
+
highlighter.createOrUpdateHighlight({
|
|
596
|
+
highlightId: 'hh-paragraph',
|
|
597
|
+
color: 'red', style: 'unstyled',
|
|
598
|
+
wrapper: 'callout-bar',
|
|
599
|
+
startParagraphId: newParagraph.id,
|
|
600
|
+
startParagraphOffset: 0,
|
|
601
|
+
endParagraphId: newParagraph.id,
|
|
602
|
+
endParagraphOffset: newParagraph.textContent.length,
|
|
603
|
+
variables: {
|
|
604
|
+
verseNumber: newParagraph.querySelector('.verse-number')?.textContent ?? '',
|
|
605
|
+
},
|
|
606
|
+
}, true, false);
|
|
607
|
+
currentParagraphId = newParagraph.id;
|
|
272
608
|
}
|
|
609
|
+
|
|
610
|
+
// Update word highlight
|
|
611
|
+
highlighter.createOrUpdateHighlight({
|
|
612
|
+
highlightId: 'hh-word',
|
|
613
|
+
style: 'bold',
|
|
614
|
+
startParagraphId: currentParagraphId,
|
|
615
|
+
startParagraphOffset: cueData.startOffset,
|
|
616
|
+
endParagraphId: currentParagraphId,
|
|
617
|
+
endParagraphOffset: cueData.endOffset,
|
|
618
|
+
}, true, false);
|
|
273
619
|
});
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
620
|
+
|
|
621
|
+
</script>
|
|
622
|
+
<style>
|
|
623
|
+
@scope {
|
|
624
|
+
.container { padding: 1em; }
|
|
625
|
+
#title-1 { font-weight: bold; }
|
|
626
|
+
#summary-1, .explanation { font-style: italic; }
|
|
627
|
+
.columns {
|
|
628
|
+
column-width: 300px;
|
|
629
|
+
gap: 3em;
|
|
630
|
+
}
|
|
631
|
+
.verse-group { margin-bottom: 1em; }
|
|
632
|
+
.verse { display: inline; }
|
|
633
|
+
.verse-number {
|
|
634
|
+
vertical-align: text-top;
|
|
635
|
+
font-size: 0.8em;
|
|
636
|
+
}
|
|
637
|
+
#verse-1 .verse-number { display: none; }
|
|
638
|
+
#verse-1 .drop-cap mark { font-weight: normal; }
|
|
639
|
+
.drop-cap {
|
|
640
|
+
float: left;
|
|
641
|
+
font-size: 3.1em;
|
|
642
|
+
line-height: 0.9;
|
|
643
|
+
padding-right: 0.06em;
|
|
644
|
+
margin-bottom: -0.3em;
|
|
645
|
+
}
|
|
646
|
+
.callout-bar {
|
|
647
|
+
width: 0.2em;
|
|
648
|
+
position: absolute;
|
|
649
|
+
transition: top 0.25s ease, height 0.25s ease, left 0.25s ease;
|
|
650
|
+
}
|
|
651
|
+
.callout-bar::before {
|
|
652
|
+
content: attr(data-verse-number);
|
|
653
|
+
position: absolute;
|
|
654
|
+
inset-block-start: 0;
|
|
655
|
+
inset-inline-end: 100%;
|
|
656
|
+
padding-inline-end: 0.3em;
|
|
657
|
+
white-space: nowrap;
|
|
658
|
+
text-align: end;
|
|
659
|
+
}
|
|
660
|
+
.container[data-playing] p:not(:has(.callout-bar)) {
|
|
661
|
+
opacity: 0.5;
|
|
662
|
+
}
|
|
304
663
|
}
|
|
305
|
-
|
|
664
|
+
</style>
|
|
665
|
+
</section>
|
|
666
|
+
|
|
667
|
+
<section id="document-draft">
|
|
668
|
+
<h2>Demo 3: Document draft</h2>
|
|
669
|
+
<article>
|
|
670
|
+
<div class="container">
|
|
671
|
+
<h3 id="cereal-title">About Cereal</h3>
|
|
672
|
+
<p id="cereal">A cereal is a grass (family Poaceae) cultivated for its edible grain. Cereals are the worlds largest crops, and are therefore staple foods. They include rice, wheat, rye, oats, barley, millet, and maize. Edible grains from other plant families, such as amaranth, buckwheat and quinoa, are pseudocereals (Eudicots). Most cereals are annuals, producing one crop from each planting, though rice is sometimes grown as a perennial. Winter varieties are hardy enough to be planted in the autumn, becoming dormant in the winter, and harvested in spring or early summer; spring varieties are planted in spring and harvested in late summer. The term cereal is derived from the name of the Roman goddess of grain crops and fertility, Ceres.</p>
|
|
673
|
+
<img src="https://upload.wikimedia.org/wikipedia/commons/7/76/Unload_wheat_by_the_combine_Claas_Lexion_584.jpg" loading="lazy">
|
|
674
|
+
<p id="more-cereal">Cereals were domesticated in the Neolithic around 8,000 years ago. Wheat and barley were domesticated in the Fertile Crescent. Rice and some millets were domesticated in East Asia, while sorghum and other millets were domesticated in Sudan. Maize was domesticated by Indigenous peoples of the Americas in southern Mexico about 9,000 years ago.</p>
|
|
675
|
+
<p id="even-more-cereal">In the 20th century, cereal productivity was greatly increased by the Green Revolution. This increase in production has acompanied a growing international trade, with some countrys producing large portions of the cereal supply for other countries.</p>
|
|
676
|
+
<p class="explanation">This demo uses highlights to show spelling errors, review comments, and the location of other people working in the document. Highlight wrappers are used for hover/tap popups and caret indicators.</p>
|
|
677
|
+
|
|
678
|
+
</div>
|
|
679
|
+
</article>
|
|
680
|
+
|
|
681
|
+
<script type="module">
|
|
682
|
+
const highlights = [
|
|
683
|
+
{
|
|
684
|
+
highlightId: 'spellcheck-1',
|
|
685
|
+
color: 'red', style: 'spelling-error',
|
|
686
|
+
wrapper: 'spelling-suggestion',
|
|
687
|
+
variables: {
|
|
688
|
+
'suggestionText': 'world’s',
|
|
689
|
+
},
|
|
690
|
+
startParagraphId: 'cereal', startParagraphOffset: 86,
|
|
691
|
+
endParagraphId: 'cereal', endParagraphOffset: 92,
|
|
692
|
+
},
|
|
306
693
|
{
|
|
307
|
-
highlightId:
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
694
|
+
highlightId: 'spellcheck-2',
|
|
695
|
+
color: 'red', style: 'spelling-error',
|
|
696
|
+
wrapper: 'spelling-suggestion',
|
|
697
|
+
variables: {
|
|
698
|
+
'suggestionText': 'countries',
|
|
699
|
+
},
|
|
700
|
+
startParagraphId: 'even-more-cereal', startParagraphOffset: 172,
|
|
701
|
+
endParagraphId: 'even-more-cereal', endParagraphOffset: 180,
|
|
702
|
+
},
|
|
703
|
+
{
|
|
704
|
+
highlightId: 'comment-1',
|
|
705
|
+
color: 'gray', style: 'comment',
|
|
706
|
+
wrapper: 'comment',
|
|
707
|
+
variables: {
|
|
708
|
+
'name': 'Alice',
|
|
709
|
+
'commentText': 'Start new paragraph?',
|
|
710
|
+
},
|
|
711
|
+
startParagraphId: 'cereal', startParagraphOffset: 315,
|
|
712
|
+
endParagraphId: 'cereal', endParagraphOffset: 327,
|
|
713
|
+
},
|
|
714
|
+
{
|
|
715
|
+
highlightId: 'comment-2',
|
|
716
|
+
color: 'gray', style: 'comment',
|
|
717
|
+
wrapper: 'comment',
|
|
718
|
+
variables: {
|
|
719
|
+
'name': 'Alice',
|
|
720
|
+
'commentText': 'Add citation',
|
|
721
|
+
},
|
|
722
|
+
startParagraphId: 'cereal', startParagraphOffset: 93,
|
|
723
|
+
endParagraphId: 'cereal', endParagraphOffset: 107,
|
|
724
|
+
},
|
|
725
|
+
{
|
|
726
|
+
highlightId: 'alice',
|
|
727
|
+
color: 'green', style: 'text-selection',
|
|
728
|
+
wrapper: 'user-caret',
|
|
729
|
+
variables: {
|
|
730
|
+
'name': 'Alice',
|
|
731
|
+
},
|
|
732
|
+
startParagraphId: 'more-cereal', startParagraphOffset: 67,
|
|
733
|
+
endParagraphId: 'more-cereal', endParagraphOffset: 88,
|
|
734
|
+
},
|
|
735
|
+
{
|
|
736
|
+
highlightId: 'henry',
|
|
737
|
+
color: 'orange', style: 'text-selection',
|
|
738
|
+
wrapper: 'user-caret',
|
|
739
|
+
variables: {
|
|
740
|
+
'name': 'Henry',
|
|
741
|
+
},
|
|
742
|
+
startParagraphId: 'even-more-cereal', startParagraphOffset: 10,
|
|
743
|
+
endParagraphId: 'even-more-cereal', endParagraphOffset: 11,
|
|
744
|
+
},
|
|
745
|
+
{
|
|
746
|
+
highlightId: 'bob',
|
|
747
|
+
color: 'blue', style: 'text-selection',
|
|
748
|
+
wrapper: 'user-caret',
|
|
749
|
+
variables: {
|
|
750
|
+
'name': 'Bob',
|
|
751
|
+
},
|
|
752
|
+
startParagraphId: 'cereal-title', startParagraphOffset: 0,
|
|
753
|
+
endParagraphId: 'cereal-title', endParagraphOffset: 5,
|
|
312
754
|
},
|
|
313
755
|
];
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
756
|
+
|
|
757
|
+
const options = {
|
|
758
|
+
drawingMode: 'svg',
|
|
759
|
+
enableHover: true,
|
|
760
|
+
colorDefs: {
|
|
761
|
+
'gray': 'var(--text-color)',
|
|
762
|
+
},
|
|
763
|
+
styleDefs: {
|
|
764
|
+
'spelling-error': {
|
|
765
|
+
css: 'text-decoration: var(--hh-color) underline wavy; text-decoration-skip-ink: none;',
|
|
766
|
+
},
|
|
767
|
+
'comment': {
|
|
768
|
+
css: 'background-color: hsl(from var(--hh-color) h s l / 15%);',
|
|
769
|
+
svg: '<rect x="{line.x}" y="{line.y}" style="fill: hsl(from var(--hh-color) h s l / 15%); width: {line.width}px; height: calc({line.height}px * 0.85); transform: translateY(calc({line.height}px * 0.15));" />',
|
|
770
|
+
},
|
|
771
|
+
'text-selection': {
|
|
772
|
+
css: 'background-color: hsl(from var(--hh-color) h s l / 30%);',
|
|
773
|
+
svg: '<rect x="{line.x}" y="{line.y}" style="fill: hsl(from var(--hh-color) h s l / 30%); width: {line.width}px; height: calc({line.height}px * 0.85); transform: translateY(calc({line.height}px * 0.15));" />',
|
|
774
|
+
},
|
|
775
|
+
},
|
|
776
|
+
wrapperDefs: {
|
|
777
|
+
'spelling-suggestion': {
|
|
778
|
+
start: '<label class="spelling-error toggle-proxy"><input type="checkbox" aria-label="Spelling suggestion"></label><div class="popup">{suggestionText}</div>',
|
|
779
|
+
},
|
|
780
|
+
'comment': {
|
|
781
|
+
start: '<label class="comment toggle-proxy"><input type="checkbox" aria-label="Show/hide comment"></label><div class="popup"><b>{name}:</b> {commentText}</div>',
|
|
782
|
+
},
|
|
783
|
+
'user-caret': {
|
|
784
|
+
end: '<span class="user-caret" data-name="{name}" style="bottom: calc(-1 * {line.height}px * 0.3);"></span>',
|
|
785
|
+
},
|
|
786
|
+
},
|
|
787
|
+
};
|
|
788
|
+
|
|
789
|
+
// Load highlights
|
|
790
|
+
const highlighter = new Highlighter('#document-draft .container', '[id]');
|
|
791
|
+
highlighter.setOptions(options);
|
|
792
|
+
highlighter.loadHighlights(highlights);
|
|
793
|
+
|
|
794
|
+
// Add event listener for tap and hover
|
|
795
|
+
let hoverHighlightId;
|
|
796
|
+
const container = document.querySelector('#document-draft .container');
|
|
797
|
+
const checkbox = (id) => container.querySelector(`[data-hh-wrapper][data-hh-highlight-id="${id}"] [type="checkbox"]`);
|
|
798
|
+
function showPopup(event) {
|
|
799
|
+
const highlight = event.detail.highlights[0];
|
|
800
|
+
if (hoverHighlightId && hoverHighlightId !== highlight?.highlightId) {
|
|
801
|
+
checkbox(hoverHighlightId).checked = false;
|
|
802
|
+
hoverHighlightId = null;
|
|
803
|
+
}
|
|
804
|
+
if (highlight?.style === 'spelling-error' || highlight?.style === 'comment') {
|
|
805
|
+
if (event.type === 'hh:hover') {
|
|
806
|
+
container.style.cursor = 'pointer';
|
|
807
|
+
hoverHighlightId = highlight?.highlightId;
|
|
808
|
+
}
|
|
809
|
+
checkbox(hoverHighlightId).checked = !checkbox(hoverHighlightId)?.checked;
|
|
810
|
+
} else {
|
|
811
|
+
container.style.cursor = '';
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
container.addEventListener('hh:tap', showPopup);
|
|
815
|
+
container.addEventListener('hh:hover', showPopup);
|
|
816
|
+
</script>
|
|
817
|
+
<style>
|
|
818
|
+
@scope {
|
|
819
|
+
article {
|
|
820
|
+
position: relative;
|
|
821
|
+
padding-right: 1em;
|
|
822
|
+
}
|
|
823
|
+
img { width: 100%; }
|
|
824
|
+
.explanation { font-style: italic; }
|
|
825
|
+
|
|
826
|
+
.user-caret {
|
|
827
|
+
position: relative;
|
|
828
|
+
display: inline-block;
|
|
829
|
+
height: 1em;
|
|
830
|
+
margin: 0 1px 0 -1px;
|
|
831
|
+
}
|
|
832
|
+
.user-caret::after {
|
|
833
|
+
display: inline-block;
|
|
834
|
+
content: "";
|
|
835
|
+
position: absolute; bottom: 0;
|
|
836
|
+
width: 3px;
|
|
837
|
+
height: 1.6em;
|
|
838
|
+
background-color: var(--hh-color);
|
|
839
|
+
}
|
|
840
|
+
.user-caret:hover::before {
|
|
841
|
+
position: absolute; top: -2em;
|
|
842
|
+
content: attr(data-name);
|
|
843
|
+
color: white;
|
|
844
|
+
font-size: 0.7rem;
|
|
845
|
+
padding: 0 0.4em;
|
|
846
|
+
font-family: sans-serif;
|
|
847
|
+
font-weight: normal;
|
|
848
|
+
background-color: var(--hh-color);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
.toggle-proxy { display: inline; }
|
|
852
|
+
.toggle-proxy:has(input:focus-visible) {
|
|
853
|
+
outline: 2px solid -webkit-focus-ring-color;
|
|
854
|
+
}
|
|
855
|
+
.toggle-proxy input {
|
|
856
|
+
position: absolute;
|
|
857
|
+
width: 1px; height: 1px;
|
|
858
|
+
margin: -1px; padding: 0;
|
|
859
|
+
overflow: hidden;
|
|
860
|
+
clip-path: inset(50%);
|
|
861
|
+
border: 0;
|
|
862
|
+
}
|
|
863
|
+
.popup {
|
|
864
|
+
position: absolute;
|
|
865
|
+
padding: 0.2em 0.4em;
|
|
866
|
+
background-color: var(--page-bg);
|
|
867
|
+
border: 1px solid var(--dark-gray);
|
|
868
|
+
z-index: 1;
|
|
869
|
+
}
|
|
870
|
+
.toggle-proxy:has(input:not(:checked)) + .popup {
|
|
871
|
+
display: none;
|
|
872
|
+
}
|
|
873
|
+
[data-hh-wrapper="spelling-suggestion"] { position: relative; }
|
|
874
|
+
.toggle-proxy.comment::after {
|
|
875
|
+
content: "💬";
|
|
876
|
+
cursor: pointer;
|
|
877
|
+
position: absolute; right: -0.8rem;
|
|
878
|
+
}
|
|
879
|
+
[data-hh-wrapper="comment"] .popup { right: 0; }
|
|
880
|
+
[data-hh-wrapper="spelling-suggestion"] .popup {
|
|
881
|
+
bottom: 1.2em; left: -0.4em;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
</style>
|
|
885
|
+
</section>
|
|
886
|
+
|
|
887
|
+
<!-- Demo template -->
|
|
888
|
+
<!--
|
|
889
|
+
<section id="demo-name">
|
|
890
|
+
<h2>Demo name</h2>
|
|
891
|
+
<div class="container">
|
|
892
|
+
</div>
|
|
893
|
+
|
|
894
|
+
<script type="module">
|
|
895
|
+
</script>
|
|
896
|
+
<style>
|
|
897
|
+
</style>
|
|
898
|
+
</section>
|
|
899
|
+
-->
|
|
900
|
+
|
|
901
|
+
|
|
902
|
+
<section>
|
|
903
|
+
<h3>Demo Credits</h3>
|
|
904
|
+
<ul>
|
|
905
|
+
<li>1 Corinthians 13 text and audio from <a href="https://www.churchofjesuschrist.org/study/scriptures/nt/1-cor/13?lang=eng">ChurchofJesusChrist.org</a>.</li>
|
|
906
|
+
<li>“Cereal” text adapted from <a href="https://en.wikipedia.org/wiki/Cereal">Wikipedia</a>.</li>
|
|
907
|
+
</ul>
|
|
908
|
+
</section>
|
|
909
|
+
|
|
910
|
+
<!-- GLOBAL JS for all demos -->
|
|
911
|
+
<script>
|
|
912
|
+
const jumpLinksList = document.getElementById('jump-links');
|
|
913
|
+
const sections = document.querySelectorAll('section');
|
|
914
|
+
|
|
915
|
+
if (typeof CSSScopeRule != 'undefined') document.getElementById('compatibility-warning').style.display = 'none';
|
|
916
|
+
|
|
917
|
+
for (const section of sections) {
|
|
918
|
+
showCode(section);
|
|
919
|
+
const title = section.querySelector('h2')?.textContent;
|
|
920
|
+
if (title) jumpLinksList.insertAdjacentHTML('beforeend', `<li><a href="#${section.id}">${title}</a></li>`)
|
|
326
921
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
<span style="${buttonStyle}">${highlight.rangeHtml}</span>
|
|
341
|
-
</button>
|
|
342
|
-
`;
|
|
343
|
-
disambiguationButtonsDiv.insertAdjacentHTML('beforeend', buttonHtml);
|
|
922
|
+
|
|
923
|
+
function showCode(section) {
|
|
924
|
+
const buildCodeBlock = (code, caption) => {
|
|
925
|
+
if (!code) return;
|
|
926
|
+
const codeLines = code.replaceAll('&', '&').replaceAll('<', '<').split('\n').slice(1, -1);
|
|
927
|
+
const minIndent = codeLines.reduce((min, line) => {
|
|
928
|
+
const spaces = line.length - line.trimStart().length;
|
|
929
|
+
return spaces === line.length ? min : Math.min(min, spaces);
|
|
930
|
+
}, Infinity);
|
|
931
|
+
return `<figure class="code-block"><figcaption>${caption}</figcaption>` + codeLines.map(line => {
|
|
932
|
+
const indent = line.length - line.trimStart().length - minIndent;
|
|
933
|
+
return `<code style="--indent: ${indent}em;">${line}</code>`;
|
|
934
|
+
}).join('') + '</figure>';
|
|
344
935
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
936
|
+
const jsCodeBlock = buildCodeBlock(section.querySelector('script')?.textContent ?? '', 'JavaScript');
|
|
937
|
+
const cssCodeBlock = buildCodeBlock(section.querySelector('style')?.textContent ?? '', 'CSS');
|
|
938
|
+
if (jsCodeBlock || cssCodeBlock) {
|
|
939
|
+
section.insertAdjacentHTML('beforeend', `
|
|
940
|
+
<details data-hh-ignore="">
|
|
941
|
+
<summary>See code</summary>
|
|
942
|
+
${jsCodeBlock}
|
|
943
|
+
${cssCodeBlock}
|
|
944
|
+
</details>
|
|
945
|
+
`);
|
|
353
946
|
}
|
|
354
|
-
// TODO: Waiting 10 milliseconds before showing the disambiguation panel is a workaround for an issue in Android Chrome where a click event fires right after the pointerup event that triggered hh:ambiguousaction. If the click event happens to be at the same position as a button in the panel, the button will receive the click, dismissing the panel before the user sees it.
|
|
355
|
-
setTimeout(function() { disambiguationPanel.showModal(); }, 10);
|
|
356
947
|
}
|
|
357
|
-
|
|
358
|
-
// Log events for debugging
|
|
359
|
-
container.addEventListener('hh:highlightcreate', console.info)
|
|
360
|
-
container.addEventListener('hh:highlightupdate', console.info)
|
|
361
|
-
container.addEventListener('hh:highlightremove', console.info)
|
|
362
|
-
container.addEventListener('hh:highlightsload', console.info)
|
|
363
|
-
container.addEventListener('hh:highlightactivate', console.info)
|
|
364
|
-
container.addEventListener('hh:highlightdeactivate', console.info)
|
|
365
|
-
container.addEventListener('hh:selectionupdate', console.info)
|
|
366
|
-
container.addEventListener('hh:ambiguousaction', console.info)
|
|
367
|
-
container.addEventListener('hh:tap', console.info)
|
|
368
|
-
|
|
369
|
-
// snippetDiv.addEventListener('hh:highlightcreate', console.warn)
|
|
370
|
-
// snippetDiv.addEventListener('hh:highlightupdate', console.warn)
|
|
371
|
-
// snippetDiv.addEventListener('hh:highlightremove', console.warn)
|
|
372
|
-
// snippetDiv.addEventListener('hh:highlightsload', console.warn)
|
|
373
|
-
// snippetDiv.addEventListener('hh:highlightactivate', console.warn)
|
|
374
|
-
// snippetDiv.addEventListener('hh:highlightdeactivate', console.warn)
|
|
375
|
-
// snippetDiv.addEventListener('hh:selectionupdate', console.warn)
|
|
376
|
-
// snippetDiv.addEventListener('hh:ambiguousaction', console.warn)
|
|
377
|
-
// snippetDiv.addEventListener('hh:tap', console.warn)
|
|
378
948
|
</script>
|
|
379
949
|
</body>
|
|
380
950
|
</html>
|