hyperbook 0.84.4 → 0.84.5
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/dist/assets/bootstrap.js +248 -0
- package/dist/assets/cloud.js +28 -17
- package/dist/assets/directive-abc-music/client.js +11 -3
- package/dist/assets/directive-archive/client.js +17 -4
- package/dist/assets/directive-audio/client.js +67 -28
- package/dist/assets/directive-bookmarks/client.js +9 -1
- package/dist/assets/directive-download/client.js +10 -2
- package/dist/assets/directive-excalidraw/client.js +9 -0
- package/dist/assets/directive-excalidraw/hyperbook-excalidraw.umd.js +1 -1
- package/dist/assets/directive-geogebra/client.js +16 -3
- package/dist/assets/directive-h5p/client.js +32 -3
- package/dist/assets/directive-learningmap/client.js +11 -3
- package/dist/assets/directive-mermaid/client.js +11 -1
- package/dist/assets/directive-multievent/multievent.js +2 -2
- package/dist/assets/directive-onlineide/client.js +7 -0
- package/dist/assets/directive-onlineide/include/online-ide-embedded.js +43 -43
- package/dist/assets/directive-p5/client.js +39 -7
- package/dist/assets/directive-protect/client.js +11 -3
- package/dist/assets/directive-pyide/client.js +20 -9
- package/dist/assets/directive-scratchblock/client.js +9 -0
- package/dist/assets/directive-slideshow/client.js +12 -4
- package/dist/assets/directive-sqlide/client.js +7 -0
- package/dist/assets/directive-sqlide/include/sql-ide-embedded.js +3 -3
- package/dist/assets/directive-tabs/client.js +14 -3
- package/dist/assets/directive-textinput/client.js +14 -3
- package/dist/assets/directive-webide/client.js +45 -10
- package/dist/assets/hyperbook.types.js +209 -0
- package/dist/assets/i18n.js +15 -1
- package/dist/assets/store.js +173 -139
- package/dist/assets/ui.js +279 -0
- package/dist/index.js +27 -18
- package/package.json +3 -3
- package/dist/assets/client.js +0 -506
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/// <reference path="./hyperbook.types.js" />
|
|
2
|
+
window.hyperbook = window.hyperbook || {};
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Bootstrap script: initializes collapsibles, search, bookmarks,
|
|
6
|
+
* standalone mode, and section filtering on DOMContentLoaded.
|
|
7
|
+
* @type {HyperbookBootstrap}
|
|
8
|
+
* @memberof hyperbook
|
|
9
|
+
* @see hyperbook.store
|
|
10
|
+
*/
|
|
11
|
+
hyperbook.bootstrap = (function () {
|
|
12
|
+
/**
|
|
13
|
+
* Initialize collapsible elements within the given root.
|
|
14
|
+
* @param {HTMLElement} root
|
|
15
|
+
*/
|
|
16
|
+
const initCollapsibles = (root) => {
|
|
17
|
+
const detailsEls = root.querySelectorAll("details.section, details.directive-collapsible");
|
|
18
|
+
for (let details of detailsEls) {
|
|
19
|
+
const id = details.getAttribute("data-id");
|
|
20
|
+
|
|
21
|
+
// Prevent link clicks from toggling the details element in navigation
|
|
22
|
+
if (id && id.startsWith("_nav:") && !details.classList.contains("empty")) {
|
|
23
|
+
const link = details.querySelector("summary a");
|
|
24
|
+
link?.addEventListener("click", (event) => {
|
|
25
|
+
event.stopPropagation();
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Listen for toggle events to persist state and sync with other elements
|
|
30
|
+
details.addEventListener("toggle", () => {
|
|
31
|
+
if (id) {
|
|
32
|
+
if (details.open) {
|
|
33
|
+
hyperbook.store.collapsibles.put({ id });
|
|
34
|
+
} else {
|
|
35
|
+
hyperbook.store.collapsibles.delete(id);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Sync all elements with the same ID
|
|
39
|
+
const allWithSameId = document.querySelectorAll(`[data-id="${id}"]`);
|
|
40
|
+
for (let el of allWithSameId) {
|
|
41
|
+
if (el !== details && el.tagName === "DETAILS") {
|
|
42
|
+
el.open = details.open;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
setTimeout(() => {
|
|
48
|
+
window.dispatchEvent(new Event("resize"));
|
|
49
|
+
}, 100);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
updateCollapsibles(root);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Restore collapsible open/close state from the store.
|
|
57
|
+
* @param {HTMLElement} root
|
|
58
|
+
*/
|
|
59
|
+
const updateCollapsibles = (root) => {
|
|
60
|
+
hyperbook.store.collapsibles.toArray().then((collapsibles) => {
|
|
61
|
+
const detailsEls = root.querySelectorAll("details.section, details.directive-collapsible");
|
|
62
|
+
for (let details of detailsEls) {
|
|
63
|
+
const id = details.getAttribute("data-id");
|
|
64
|
+
if (id) {
|
|
65
|
+
const shouldBeOpen = collapsibles.some((c) => c.id === id);
|
|
66
|
+
if (!id.startsWith("_nav:") && details.open !== shouldBeOpen) {
|
|
67
|
+
details.open = shouldBeOpen;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Initialize search input Enter key handling.
|
|
76
|
+
* @param {HTMLElement} root
|
|
77
|
+
*/
|
|
78
|
+
const initSearch = (root) => {
|
|
79
|
+
const searchInputEl = root.querySelector("#search-input");
|
|
80
|
+
if (searchInputEl) {
|
|
81
|
+
searchInputEl.addEventListener("keypress", (event) => {
|
|
82
|
+
if (event.key === "Enter") {
|
|
83
|
+
event.preventDefault();
|
|
84
|
+
hyperbook.ui.search();
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Initialize bookmark active states within the given root.
|
|
92
|
+
* @param {HTMLElement} [root=document]
|
|
93
|
+
*/
|
|
94
|
+
function initBookmarks(root = document) {
|
|
95
|
+
const bookmarkEls = root.getElementsByClassName("bookmark");
|
|
96
|
+
for (let bookmarkEl of bookmarkEls) {
|
|
97
|
+
const key = bookmarkEl.getAttribute("data-key");
|
|
98
|
+
hyperbook.store.bookmarks.get(key).then((bookmark) => {
|
|
99
|
+
if (bookmark) {
|
|
100
|
+
bookmarkEl.classList.add("active");
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Initialize all hyperbook elements within a root.
|
|
108
|
+
* @param {HTMLElement} root
|
|
109
|
+
*/
|
|
110
|
+
function init(root) {
|
|
111
|
+
initCollapsibles(root);
|
|
112
|
+
initSearch(root);
|
|
113
|
+
initBookmarks(root);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Hide TOC toggle button when sections are filtered.
|
|
118
|
+
*/
|
|
119
|
+
function hideTocWhenFiltered() {
|
|
120
|
+
const tocToggle = document.getElementById('toc-toggle');
|
|
121
|
+
if (tocToggle) {
|
|
122
|
+
tocToggle.style.display = 'none';
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Filter sections based on the `sections` query parameter.
|
|
128
|
+
*/
|
|
129
|
+
function filterSections() {
|
|
130
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
131
|
+
const sectionsParam = urlParams.get('sections');
|
|
132
|
+
|
|
133
|
+
if (!sectionsParam) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const sectionIds = sectionsParam.split(',').map(id => id.trim()).filter(id => id);
|
|
138
|
+
|
|
139
|
+
if (sectionIds.length === 0) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const allHeadings = document.querySelectorAll('h1, h2, h3, h4, h5, h6');
|
|
144
|
+
const headingsArray = Array.from(allHeadings);
|
|
145
|
+
|
|
146
|
+
const visibleElements = new Set();
|
|
147
|
+
|
|
148
|
+
sectionIds.forEach(sectionId => {
|
|
149
|
+
const heading = document.getElementById(sectionId);
|
|
150
|
+
if (!heading) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const headingLevel = parseInt(heading.tagName.substring(1));
|
|
155
|
+
|
|
156
|
+
visibleElements.add(heading);
|
|
157
|
+
|
|
158
|
+
const headingIndex = headingsArray.indexOf(heading);
|
|
159
|
+
if (headingIndex === -1) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
let currentElement = heading.nextElementSibling;
|
|
164
|
+
|
|
165
|
+
while (currentElement) {
|
|
166
|
+
const isHeading = /^H[1-6]$/.test(currentElement.tagName);
|
|
167
|
+
|
|
168
|
+
if (isHeading) {
|
|
169
|
+
const currentLevel = parseInt(currentElement.tagName.substring(1));
|
|
170
|
+
|
|
171
|
+
if (currentLevel <= headingLevel) {
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
visibleElements.add(currentElement);
|
|
176
|
+
} else {
|
|
177
|
+
visibleElements.add(currentElement);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
currentElement = currentElement.nextElementSibling;
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const markdownDiv = document.querySelector('main article .hyperbook-markdown');
|
|
185
|
+
if (markdownDiv) {
|
|
186
|
+
Array.from(markdownDiv.children).forEach(element => {
|
|
187
|
+
const isUIElement = element.id === 'floating-buttons-container' ||
|
|
188
|
+
element.tagName === 'SIDE-DRAWER';
|
|
189
|
+
|
|
190
|
+
if (!visibleElements.has(element) && !isUIElement) {
|
|
191
|
+
element.style.display = 'none';
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
hideTocWhenFiltered();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Check for standalone layout URL parameter or iframe context.
|
|
201
|
+
*/
|
|
202
|
+
function checkStandaloneMode() {
|
|
203
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
204
|
+
const standaloneParam = urlParams.get('standalone') === 'true';
|
|
205
|
+
|
|
206
|
+
const isVSCodeWebview = typeof acquireVsCodeApi !== 'undefined';
|
|
207
|
+
const isInIframe = window.self !== window.top && !isVSCodeWebview;
|
|
208
|
+
|
|
209
|
+
if (standaloneParam || isInIframe) {
|
|
210
|
+
const mainGrid = document.querySelector('.main-grid');
|
|
211
|
+
if (mainGrid && !mainGrid.classList.contains('layout-standalone')) {
|
|
212
|
+
mainGrid.classList.add('layout-standalone');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const tocToggle = document.getElementById('toc-toggle');
|
|
216
|
+
if (tocToggle) {
|
|
217
|
+
tocToggle.style.display = 'none';
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const qrcodeOpen = document.getElementById('qrcode-open');
|
|
221
|
+
if (qrcodeOpen) {
|
|
222
|
+
qrcodeOpen.style.display = 'none';
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Initialize on DOMContentLoaded
|
|
228
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
229
|
+
init(document);
|
|
230
|
+
checkStandaloneMode();
|
|
231
|
+
filterSections();
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// Observe for new elements added to the DOM
|
|
235
|
+
const observer = new MutationObserver((mutations) => {
|
|
236
|
+
mutations.forEach((mutation) => {
|
|
237
|
+
mutation.addedNodes.forEach((node) => {
|
|
238
|
+
if (node.nodeType === 1) {
|
|
239
|
+
init(node);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
246
|
+
|
|
247
|
+
return { init };
|
|
248
|
+
})();
|
package/dist/assets/cloud.js
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
/// <reference path="./hyperbook.types.js" />
|
|
2
|
+
window.hyperbook = window.hyperbook || {};
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Cloud sync integration for hyperbook store data.
|
|
6
|
+
* Handles authentication, event-based sync, and snapshot sync.
|
|
7
|
+
* @type {HyperbookCloud}
|
|
8
|
+
* @memberof hyperbook
|
|
9
|
+
* @see hyperbook.store
|
|
10
|
+
* @see hyperbook.i18n
|
|
11
|
+
*/
|
|
12
|
+
hyperbook.cloud = (function () {
|
|
2
13
|
// ===== Cloud Integration =====
|
|
3
14
|
const AUTH_TOKEN_KEY = "hyperbook_auth_token";
|
|
4
15
|
const AUTH_USER_KEY = "hyperbook_auth_user";
|
|
@@ -188,8 +199,8 @@ window.hyperbook.cloud = (function () {
|
|
|
188
199
|
}
|
|
189
200
|
|
|
190
201
|
async sendSnapshot() {
|
|
191
|
-
const
|
|
192
|
-
const exportData = JSON.parse(await
|
|
202
|
+
const storeExport = await hyperbook.store.db.export({ prettyJson: false });
|
|
203
|
+
const exportData = JSON.parse(await storeExport.text());
|
|
193
204
|
|
|
194
205
|
const data = await apiRequest(
|
|
195
206
|
`/api/store/${HYPERBOOK_CLOUD.id}/snapshot`,
|
|
@@ -532,7 +543,7 @@ window.hyperbook.cloud = (function () {
|
|
|
532
543
|
const blob = new Blob([JSON.stringify(hyperbook)], {
|
|
533
544
|
type: "application/json",
|
|
534
545
|
});
|
|
535
|
-
await store.import(blob, { clearTablesBeforeImport: true });
|
|
546
|
+
await hyperbook.store.db.import(blob, { clearTablesBeforeImport: true });
|
|
536
547
|
}
|
|
537
548
|
|
|
538
549
|
// Track the server's lastEventId
|
|
@@ -589,7 +600,7 @@ window.hyperbook.cloud = (function () {
|
|
|
589
600
|
});
|
|
590
601
|
|
|
591
602
|
// Hook Dexie tables to capture granular events (skip currentState — ephemeral UI data)
|
|
592
|
-
store.tables.forEach((table) => {
|
|
603
|
+
hyperbook.store.tables.forEach((table) => {
|
|
593
604
|
if (table.name === "currentState") return;
|
|
594
605
|
|
|
595
606
|
table.hook("creating", function (primKey, obj) {
|
|
@@ -656,29 +667,29 @@ window.hyperbook.cloud = (function () {
|
|
|
656
667
|
statusEl.className = status;
|
|
657
668
|
|
|
658
669
|
if (status === "unsaved") {
|
|
659
|
-
statusEl.textContent = i18n.get("user-unsaved", {}, "Unsaved changes");
|
|
670
|
+
statusEl.textContent = hyperbook.i18n.get("user-unsaved", {}, "Unsaved changes");
|
|
660
671
|
updateUserIconState("logged-in");
|
|
661
672
|
} else if (status === "saving") {
|
|
662
|
-
statusEl.textContent = i18n.get("user-saving", {}, "Saving...");
|
|
673
|
+
statusEl.textContent = hyperbook.i18n.get("user-saving", {}, "Saving...");
|
|
663
674
|
updateUserIconState("syncing");
|
|
664
675
|
} else if (status === "saved") {
|
|
665
|
-
statusEl.textContent = i18n.get("user-saved", {}, "Saved");
|
|
676
|
+
statusEl.textContent = hyperbook.i18n.get("user-saved", {}, "Saved");
|
|
666
677
|
updateUserIconState("synced");
|
|
667
678
|
} else if (status === "error") {
|
|
668
|
-
statusEl.textContent = i18n.get("user-save-error", {}, "Save Error");
|
|
679
|
+
statusEl.textContent = hyperbook.i18n.get("user-save-error", {}, "Save Error");
|
|
669
680
|
updateUserIconState("unsynced");
|
|
670
681
|
} else if (status === "offline") {
|
|
671
|
-
statusEl.textContent = i18n.get("user-offline", {}, "Offline");
|
|
682
|
+
statusEl.textContent = hyperbook.i18n.get("user-offline", {}, "Offline");
|
|
672
683
|
updateUserIconState("unsynced");
|
|
673
684
|
} else if (status === "offline-queued") {
|
|
674
|
-
statusEl.textContent = i18n.get(
|
|
685
|
+
statusEl.textContent = hyperbook.i18n.get(
|
|
675
686
|
"user-offline-queued",
|
|
676
687
|
{},
|
|
677
688
|
"Saved locally",
|
|
678
689
|
);
|
|
679
690
|
updateUserIconState("logged-in");
|
|
680
691
|
} else if (status === "readonly") {
|
|
681
|
-
statusEl.textContent = i18n.get("user-readonly", {}, "Read-Only Mode");
|
|
692
|
+
statusEl.textContent = hyperbook.i18n.get("user-readonly", {}, "Read-Only Mode");
|
|
682
693
|
statusEl.className = "readonly";
|
|
683
694
|
updateUserIconState("synced");
|
|
684
695
|
}
|
|
@@ -707,7 +718,7 @@ window.hyperbook.cloud = (function () {
|
|
|
707
718
|
const errorEl = document.getElementById("user-login-error");
|
|
708
719
|
|
|
709
720
|
if (!username || !password) {
|
|
710
|
-
errorEl.textContent = i18n.get(
|
|
721
|
+
errorEl.textContent = hyperbook.i18n.get(
|
|
711
722
|
"user-login-required",
|
|
712
723
|
{},
|
|
713
724
|
"Username and password required",
|
|
@@ -721,14 +732,14 @@ window.hyperbook.cloud = (function () {
|
|
|
721
732
|
errorEl.textContent = "";
|
|
722
733
|
} catch (error) {
|
|
723
734
|
errorEl.textContent =
|
|
724
|
-
error.message || i18n.get("user-login-failed", {}, "Login failed");
|
|
735
|
+
error.message || hyperbook.i18n.get("user-login-failed", {}, "Login failed");
|
|
725
736
|
}
|
|
726
737
|
};
|
|
727
738
|
|
|
728
739
|
const logout = () => {
|
|
729
740
|
if (
|
|
730
741
|
confirm(
|
|
731
|
-
i18n.get("user-logout-confirm", {}, "Are you sure you want to logout?"),
|
|
742
|
+
hyperbook.i18n.get("user-logout-confirm", {}, "Are you sure you want to logout?"),
|
|
732
743
|
)
|
|
733
744
|
) {
|
|
734
745
|
hyperbookLogout();
|
|
@@ -761,8 +772,8 @@ window.hyperbook.cloud = (function () {
|
|
|
761
772
|
const banner = document.createElement("div");
|
|
762
773
|
banner.id = "impersonation-banner";
|
|
763
774
|
banner.innerHTML = `
|
|
764
|
-
<span>${i18n.get("user-impersonating", {}, "Impersonating")}: <strong>${user ? user.username : ""}</strong> — ${i18n.get("user-readonly", {}, "Read-Only Mode")}</span>
|
|
765
|
-
<a href="#" id="exit-impersonation">${i18n.get("user-exit-impersonation", {}, "Exit Impersonation")}</a>
|
|
775
|
+
<span>${hyperbook.i18n.get("user-impersonating", {}, "Impersonating")}: <strong>${user ? user.username : ""}</strong> — ${hyperbook.i18n.get("user-readonly", {}, "Read-Only Mode")}</span>
|
|
776
|
+
<a href="#" id="exit-impersonation">${hyperbook.i18n.get("user-exit-impersonation", {}, "Exit Impersonation")}</a>
|
|
766
777
|
`;
|
|
767
778
|
document.body.prepend(banner);
|
|
768
779
|
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/// <reference path="../hyperbook.types.js" />
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ABC music notation rendering and editing.
|
|
5
|
+
* @type {HyperbookAbc}
|
|
6
|
+
* @memberof hyperbook
|
|
7
|
+
* @see hyperbook.store
|
|
8
|
+
*/
|
|
1
9
|
hyperbook.abc = (function () {
|
|
2
10
|
window.codeInput?.registerTemplate(
|
|
3
11
|
"abc-highlighted",
|
|
@@ -33,7 +41,7 @@ hyperbook.abc = (function () {
|
|
|
33
41
|
});
|
|
34
42
|
|
|
35
43
|
resetEl?.addEventListener("click", () => {
|
|
36
|
-
store.abcMusic.delete(id);
|
|
44
|
+
hyperbook.store.abcMusic.delete(id);
|
|
37
45
|
window.location.reload();
|
|
38
46
|
});
|
|
39
47
|
|
|
@@ -46,7 +54,7 @@ hyperbook.abc = (function () {
|
|
|
46
54
|
});
|
|
47
55
|
|
|
48
56
|
editorEl.addEventListener("code-input_load", async () => {
|
|
49
|
-
const storeResult = await store.abcMusic
|
|
57
|
+
const storeResult = await hyperbook.store.abcMusic
|
|
50
58
|
.where("id")
|
|
51
59
|
.equals(editorEl.id)
|
|
52
60
|
.first();
|
|
@@ -70,7 +78,7 @@ hyperbook.abc = (function () {
|
|
|
70
78
|
});
|
|
71
79
|
|
|
72
80
|
editorEl.addEventListener("change", () => {
|
|
73
|
-
store.abcMusic.put({
|
|
81
|
+
hyperbook.store.abcMusic.put({
|
|
74
82
|
id: editorEl.id,
|
|
75
83
|
tune: editorEl.value,
|
|
76
84
|
});
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
hyperbook.
|
|
1
|
+
/// <reference path="../hyperbook.types.js" />
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Archive download management.
|
|
5
|
+
* @type {HyperbookArchive}
|
|
6
|
+
* @memberof hyperbook
|
|
7
|
+
* @see hyperbook.i18n
|
|
8
|
+
*/
|
|
9
|
+
hyperbook.archive = (function () {
|
|
2
10
|
function initElement(el) {
|
|
3
11
|
const labelEl = el.getElementsByClassName("label")[0];
|
|
4
12
|
const src = el.href;
|
|
@@ -7,10 +15,10 @@ hyperbook.download = (function () {
|
|
|
7
15
|
const isOnline = r.ok;
|
|
8
16
|
if (isOnline) {
|
|
9
17
|
labelEl.classList.remove("offline");
|
|
10
|
-
labelEl.innerHTML = labelEl.innerHTML.replace(`(${i18n.get("archive-offline")})`, "");
|
|
18
|
+
labelEl.innerHTML = labelEl.innerHTML.replace(`(${hyperbook.i18n.get("archive-offline")})`, "");
|
|
11
19
|
} else {
|
|
12
20
|
labelEl.classList.add("offline");
|
|
13
|
-
labelEl.innerHTML += ` (${i18n.get("archive-offline")})`;
|
|
21
|
+
labelEl.innerHTML += ` (${hyperbook.i18n.get("archive-offline")})`;
|
|
14
22
|
}
|
|
15
23
|
});
|
|
16
24
|
}
|
|
@@ -36,5 +44,10 @@ hyperbook.download = (function () {
|
|
|
36
44
|
|
|
37
45
|
observer.observe(document.body, { childList: true, subtree: true });
|
|
38
46
|
|
|
39
|
-
|
|
47
|
+
// Initialize existing elements on document load
|
|
48
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
49
|
+
init();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return { init };
|
|
40
53
|
})();
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/// <reference path="../hyperbook.types.js" />
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Audio player with playback controls and state persistence.
|
|
5
|
+
* @type {HyperbookAudio}
|
|
6
|
+
* @memberof hyperbook
|
|
7
|
+
* @see hyperbook.store
|
|
8
|
+
*/
|
|
1
9
|
hyperbook.audio = (function () {
|
|
2
10
|
/**
|
|
3
11
|
* @param {number} seconds
|
|
@@ -23,37 +31,68 @@ hyperbook.audio = (function () {
|
|
|
23
31
|
*/
|
|
24
32
|
const wavesurferInstances = {};
|
|
25
33
|
|
|
26
|
-
function
|
|
27
|
-
const
|
|
34
|
+
function initElement(el) {
|
|
35
|
+
const src = el.getAttribute("data-src");
|
|
36
|
+
const id = el.id;
|
|
37
|
+
|
|
38
|
+
if (wavesurferInstances[id]) return;
|
|
39
|
+
|
|
40
|
+
const wavesurfer = window.WaveSurfer.create({
|
|
41
|
+
container: el,
|
|
42
|
+
cursorWidth: 4,
|
|
43
|
+
barWidth: 4,
|
|
44
|
+
barGap: 5,
|
|
45
|
+
barRadius: 2,
|
|
46
|
+
height: 64,
|
|
47
|
+
url: src,
|
|
48
|
+
});
|
|
49
|
+
wavesurfer.on("ready", () => update(id));
|
|
50
|
+
wavesurfer.on("audioprocess", () => update(id));
|
|
51
|
+
wavesurfer.on("pause", () => update(id));
|
|
52
|
+
wavesurfer.on("finish", () => update(id));
|
|
53
|
+
wavesurfer.on("play", () => update(id));
|
|
54
|
+
wavesurferInstances[id] = wavesurfer;
|
|
55
|
+
|
|
56
|
+
hyperbook.store.audio.get(id).then((result) => {
|
|
57
|
+
if (result) {
|
|
58
|
+
wavesurfer.setTime(result.time);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function init(root) {
|
|
64
|
+
const els = root.getElementsByClassName("wave");
|
|
28
65
|
for (let el of els) {
|
|
29
|
-
|
|
30
|
-
const id = el.id;
|
|
31
|
-
|
|
32
|
-
const wavesurfer = window.WaveSurfer.create({
|
|
33
|
-
container: el,
|
|
34
|
-
cursorWidth: 4,
|
|
35
|
-
barWidth: 4,
|
|
36
|
-
barGap: 5,
|
|
37
|
-
barRadius: 2,
|
|
38
|
-
height: 64,
|
|
39
|
-
url: src,
|
|
40
|
-
});
|
|
41
|
-
wavesurfer.on("ready", () => update(id));
|
|
42
|
-
wavesurfer.on("audioprocess", () => update(id));
|
|
43
|
-
wavesurfer.on("pause", () => update(id));
|
|
44
|
-
wavesurfer.on("finish", () => update(id));
|
|
45
|
-
wavesurfer.on("play", () => update(id));
|
|
46
|
-
wavesurferInstances[id] = wavesurfer;
|
|
47
|
-
|
|
48
|
-
store.audio.get(id).then((result) => {
|
|
49
|
-
if (result) {
|
|
50
|
-
wavesurfer.setTime(result.time);
|
|
51
|
-
}
|
|
52
|
-
});
|
|
66
|
+
initElement(el);
|
|
53
67
|
}
|
|
54
68
|
}
|
|
55
69
|
|
|
56
|
-
|
|
70
|
+
// Initialize existing elements on document load
|
|
71
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
72
|
+
init(document);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Observe for new elements added to the DOM
|
|
76
|
+
const observer = new MutationObserver((mutations) => {
|
|
77
|
+
mutations.forEach((mutation) => {
|
|
78
|
+
mutation.addedNodes.forEach((node) => {
|
|
79
|
+
if (node.nodeType === 1) {
|
|
80
|
+
if (node.classList.contains("wave")) {
|
|
81
|
+
initElement(node);
|
|
82
|
+
} else {
|
|
83
|
+
const waves = node.getElementsByClassName?.("wave");
|
|
84
|
+
if (waves) {
|
|
85
|
+
for (let el of waves) {
|
|
86
|
+
initElement(el);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
57
96
|
|
|
58
97
|
/**
|
|
59
98
|
* @param {string} id
|
|
@@ -78,7 +117,7 @@ hyperbook.audio = (function () {
|
|
|
78
117
|
playEl.classList.remove("playing");
|
|
79
118
|
}
|
|
80
119
|
|
|
81
|
-
store.audio.put({ id, time });
|
|
120
|
+
hyperbook.store.audio.put({ id, time });
|
|
82
121
|
|
|
83
122
|
durationEl.innerHTML = ` ${secondsToTimestamp(time)}/${secondsToTimestamp(duration)}`;
|
|
84
123
|
}
|
|
@@ -1,6 +1,14 @@
|
|
|
1
|
+
/// <reference path="../hyperbook.types.js" />
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Bookmark management and navigation.
|
|
5
|
+
* @type {HyperbookBookmarks}
|
|
6
|
+
* @memberof hyperbook
|
|
7
|
+
* @see hyperbook.store
|
|
8
|
+
*/
|
|
1
9
|
hyperbook.bookmarks = (function () {
|
|
2
10
|
function update(root = document) {
|
|
3
|
-
store.bookmarks
|
|
11
|
+
hyperbook.store.bookmarks
|
|
4
12
|
.toArray()
|
|
5
13
|
.then((bookmarks) => {
|
|
6
14
|
return bookmarks.map((bookmark) => {
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/// <reference path="../hyperbook.types.js" />
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* File download management.
|
|
5
|
+
* @type {HyperbookDownload}
|
|
6
|
+
* @memberof hyperbook
|
|
7
|
+
* @see hyperbook.i18n
|
|
8
|
+
*/
|
|
1
9
|
hyperbook.download = (function () {
|
|
2
10
|
const init = (root) => {
|
|
3
11
|
const els = root.getElementsByClassName("directive-download");
|
|
@@ -10,10 +18,10 @@ hyperbook.download = (function () {
|
|
|
10
18
|
const isOnline = r.ok;
|
|
11
19
|
if (isOnline) {
|
|
12
20
|
labelEl.classList.remove("offline");
|
|
13
|
-
labelEl.innerHTML.replace(`(${i18n.get("download-offline")})`, "");
|
|
21
|
+
labelEl.innerHTML.replace(`(${hyperbook.i18n.get("download-offline")})`, "");
|
|
14
22
|
} else {
|
|
15
23
|
labelEl.classList.add("offline");
|
|
16
|
-
labelEl.innerHTML += ` (${i18n.get("download-offline")})`;
|
|
24
|
+
labelEl.innerHTML += ` (${hyperbook.i18n.get("download-offline")})`;
|
|
17
25
|
}
|
|
18
26
|
});
|
|
19
27
|
}
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
+
/// <reference path="../hyperbook.types.js" />
|
|
2
|
+
|
|
1
3
|
window.EXCALIDRAW_ASSET_PATH =
|
|
2
4
|
window.HYPERBOOK_ASSETS + "directive-excalidraw/";
|
|
3
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Excalidraw whiteboard integration.
|
|
8
|
+
* @type {HyperbookExcalidraw}
|
|
9
|
+
* @memberof hyperbook
|
|
10
|
+
*/
|
|
4
11
|
hyperbook.excalidraw = (function () {
|
|
5
12
|
const elems = document.getElementsByClassName("directive-excalidraw");
|
|
13
|
+
|
|
14
|
+
return {};
|
|
6
15
|
})();
|