hyperbook 0.84.4 → 0.85.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/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-embed/client.js +112 -0
- package/dist/assets/directive-embed/style.css +70 -1
- 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/directive-youtube/client.js +99 -0
- package/dist/assets/directive-youtube/style.css +63 -0
- package/dist/assets/hyperbook.types.js +209 -0
- package/dist/assets/i18n.js +15 -1
- package/dist/assets/store.js +174 -139
- package/dist/assets/ui.js +279 -0
- package/dist/index.js +632 -413
- package/dist/locales/de.json +9 -1
- package/dist/locales/en.json +9 -1
- package/package.json +4 -4
- package/dist/assets/client.js +0 -506
package/dist/assets/store.js
CHANGED
|
@@ -1,166 +1,201 @@
|
|
|
1
|
+
/// <reference path="./hyperbook.types.js" />
|
|
2
|
+
window.hyperbook = window.hyperbook || {};
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
|
-
*
|
|
5
|
+
* Persistent store backed by Dexie (IndexedDB).
|
|
6
|
+
* Provides table access, import/export, and reset functionality.
|
|
7
|
+
* @type {HyperbookStore}
|
|
8
|
+
* @memberof hyperbook
|
|
9
|
+
* @see hyperbook.i18n
|
|
10
|
+
* @see hyperbook.cloud
|
|
3
11
|
*/
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
store.currentState.put({
|
|
41
|
-
id: 1,
|
|
42
|
-
path: window.location.pathname,
|
|
43
|
-
mouseX: 0,
|
|
44
|
-
mouseY: 0,
|
|
45
|
-
scrollX: window.scrollX,
|
|
46
|
-
scrollY: window.scrollY,
|
|
47
|
-
windowWidth: window.innerWidth,
|
|
48
|
-
windowHeight: window.innerHeight,
|
|
49
|
-
});
|
|
50
|
-
window.addEventListener("mousemove", (e) => {
|
|
51
|
-
store.currentState.update(1, { mouseX: e.clientX, mouseY: e.clientY });
|
|
12
|
+
hyperbook.store = (function () {
|
|
13
|
+
/** @type {import("dexie").Dexie} */
|
|
14
|
+
var db = new Dexie("Hyperbook");
|
|
15
|
+
db.version(3).stores({
|
|
16
|
+
consent: `id`,
|
|
17
|
+
currentState: `
|
|
18
|
+
id,
|
|
19
|
+
path,
|
|
20
|
+
mouseX,
|
|
21
|
+
mouseY,
|
|
22
|
+
scrollX,
|
|
23
|
+
scrollY,
|
|
24
|
+
windowWidth,
|
|
25
|
+
windowHeight
|
|
26
|
+
`,
|
|
27
|
+
collapsibles: `id`,
|
|
28
|
+
abcMusic: `id,tune`,
|
|
29
|
+
audio: `id,time`,
|
|
30
|
+
bookmarks: `path,label`,
|
|
31
|
+
p5: `id,sketch`,
|
|
32
|
+
protect: `id,passwordHash`,
|
|
33
|
+
pyide: `id,script`,
|
|
34
|
+
slideshow: `id,active`,
|
|
35
|
+
tabs: `id,active`,
|
|
36
|
+
excalidraw: `id,excalidrawElements,appState,files`,
|
|
37
|
+
webide: `id,html,css,js`,
|
|
38
|
+
h5p: `id,userData`,
|
|
39
|
+
geogebra: `id,state`,
|
|
40
|
+
learningmap: `id,nodes,x,y,zoom`,
|
|
41
|
+
textinput: `id,text`,
|
|
42
|
+
custom: `id,payload`,
|
|
43
|
+
onlineide: `scriptId,script`,
|
|
44
|
+
sqlideScripts: `scriptId,script`,
|
|
45
|
+
sqlideDatabases: `databaseId,database`,
|
|
46
|
+
multievent: `id,state`,
|
|
47
|
+
typst: `id,code`,
|
|
52
48
|
});
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
|
|
50
|
+
/** @returns {Promise<void>} */
|
|
51
|
+
const init = async () => {
|
|
52
|
+
db.currentState.put({
|
|
53
|
+
id: 1,
|
|
54
|
+
path: window.location.pathname,
|
|
55
|
+
mouseX: 0,
|
|
56
|
+
mouseY: 0,
|
|
55
57
|
scrollX: window.scrollX,
|
|
56
58
|
scrollY: window.scrollY,
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
window.addEventListener("resize", (e) => {
|
|
60
|
-
store.currentState.update(1, {
|
|
61
59
|
windowWidth: window.innerWidth,
|
|
62
60
|
windowHeight: window.innerHeight,
|
|
63
61
|
});
|
|
64
|
-
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
window.addEventListener("mousemove", (e) => {
|
|
63
|
+
db.currentState.update(1, { mouseX: e.clientX, mouseY: e.clientY });
|
|
64
|
+
});
|
|
65
|
+
window.addEventListener("scroll", (e) => {
|
|
66
|
+
db.currentState.update(1, {
|
|
67
|
+
scrollX: window.scrollX,
|
|
68
|
+
scrollY: window.scrollY,
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
window.addEventListener("resize", (e) => {
|
|
72
|
+
db.currentState.update(1, {
|
|
73
|
+
windowWidth: window.innerWidth,
|
|
74
|
+
windowHeight: window.innerHeight,
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
};
|
|
68
78
|
|
|
69
|
-
|
|
70
|
-
|
|
79
|
+
init();
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Export all store data as a JSON file download.
|
|
83
|
+
* @returns {Promise<void>}
|
|
84
|
+
*/
|
|
85
|
+
async function hyperbookExport() {
|
|
86
|
+
const exp = await db.export({ prettyJson: true });
|
|
87
|
+
|
|
88
|
+
const data = {
|
|
89
|
+
version: 1,
|
|
90
|
+
origin: window.location.origin,
|
|
91
|
+
data: {
|
|
92
|
+
hyperbook: JSON.parse(await exp.text()),
|
|
93
|
+
},
|
|
94
|
+
};
|
|
71
95
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
96
|
+
const json = JSON.stringify(data);
|
|
97
|
+
const blob = new Blob([json], { type: "application/json" });
|
|
98
|
+
const url = URL.createObjectURL(blob);
|
|
99
|
+
const a = document.createElement("a");
|
|
100
|
+
const date = new Date().toISOString().split("T")[0];
|
|
101
|
+
a.href = url;
|
|
102
|
+
a.download = `hyperbook-export-${date}.json`;
|
|
103
|
+
a.click();
|
|
104
|
+
URL.revokeObjectURL(url);
|
|
105
|
+
}
|
|
79
106
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
async function hyperbookReset() {
|
|
92
|
-
async function clearTable(db) {
|
|
93
|
-
for (const table of db.tables) {
|
|
94
|
-
await table.clear();
|
|
107
|
+
/**
|
|
108
|
+
* Clear all store data after user confirmation.
|
|
109
|
+
* Syncs the reset to the cloud if connected.
|
|
110
|
+
* @returns {Promise<void>}
|
|
111
|
+
*/
|
|
112
|
+
async function hyperbookReset() {
|
|
113
|
+
async function clearTable(database) {
|
|
114
|
+
for (const table of database.tables) {
|
|
115
|
+
await table.clear();
|
|
116
|
+
}
|
|
95
117
|
}
|
|
96
|
-
}
|
|
97
118
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
119
|
+
if (!confirm(hyperbook.i18n.get("store-reset-confirm"))) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
101
122
|
|
|
102
|
-
|
|
123
|
+
clearTable(db);
|
|
103
124
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
125
|
+
// Send empty snapshot to cloud
|
|
126
|
+
if (hyperbook.cloud) {
|
|
127
|
+
try {
|
|
128
|
+
await hyperbook.cloud.sendSnapshot();
|
|
129
|
+
} catch (e) {
|
|
130
|
+
console.error("Failed to sync reset to cloud:", e);
|
|
131
|
+
}
|
|
110
132
|
}
|
|
133
|
+
|
|
134
|
+
alert(hyperbook.i18n.get("store-reset-sucessful"));
|
|
135
|
+
window.location.reload();
|
|
111
136
|
}
|
|
112
137
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (
|
|
129
|
-
|
|
130
|
-
|
|
138
|
+
/**
|
|
139
|
+
* Import store data from a user-selected JSON file.
|
|
140
|
+
* Syncs the import to the cloud if connected.
|
|
141
|
+
* @returns {Promise<void>}
|
|
142
|
+
*/
|
|
143
|
+
async function hyperbookImport() {
|
|
144
|
+
const input = document.createElement("input");
|
|
145
|
+
input.type = "file";
|
|
146
|
+
input.accept = "application/json";
|
|
147
|
+
input.onchange = async (event) => {
|
|
148
|
+
const file = event.target.files[0];
|
|
149
|
+
if (!file) return;
|
|
150
|
+
const reader = new FileReader();
|
|
151
|
+
reader.onload = async (e) => {
|
|
152
|
+
const data = JSON.parse(e.target.result);
|
|
153
|
+
if (data.origin !== window.location.origin) {
|
|
154
|
+
if (
|
|
155
|
+
!confirm(hyperbook.i18n.get("store-different-origin", { origin: data.origin }))
|
|
156
|
+
) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (data.version !== 1) {
|
|
161
|
+
alert(
|
|
162
|
+
hyperbook.i18n.get("store-not-supported-file-version", {
|
|
163
|
+
version: data.version,
|
|
164
|
+
}),
|
|
165
|
+
);
|
|
131
166
|
return;
|
|
132
167
|
}
|
|
133
|
-
}
|
|
134
|
-
if (data.version !== 1) {
|
|
135
|
-
alert(
|
|
136
|
-
i18n.get("store-not-supported-file-version", {
|
|
137
|
-
version: data.version,
|
|
138
|
-
}),
|
|
139
|
-
);
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
168
|
|
|
143
|
-
|
|
169
|
+
const { hyperbook: hyperbookData } = data.data;
|
|
144
170
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
171
|
+
const hyperbookBlob = new Blob([JSON.stringify(hyperbookData)], {
|
|
172
|
+
type: "application/json",
|
|
173
|
+
});
|
|
148
174
|
|
|
149
|
-
|
|
175
|
+
await db.import(hyperbookBlob, { clearTablesBeforeImport: true });
|
|
150
176
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
177
|
+
// Send full snapshot to cloud after import
|
|
178
|
+
if (hyperbook.cloud) {
|
|
179
|
+
try {
|
|
180
|
+
await hyperbook.cloud.sendSnapshot();
|
|
181
|
+
} catch (e) {
|
|
182
|
+
console.error("Failed to sync import to cloud:", e);
|
|
183
|
+
}
|
|
157
184
|
}
|
|
158
|
-
}
|
|
159
185
|
|
|
160
|
-
|
|
161
|
-
|
|
186
|
+
alert(hyperbook.i18n.get("store-import-sucessful"));
|
|
187
|
+
window.location.reload();
|
|
188
|
+
};
|
|
189
|
+
reader.readAsText(file);
|
|
162
190
|
};
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
|
|
191
|
+
input.click();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Expose the Dexie db instance properties and public API
|
|
195
|
+
return Object.assign(db, {
|
|
196
|
+
db,
|
|
197
|
+
export: hyperbookExport,
|
|
198
|
+
reset: hyperbookReset,
|
|
199
|
+
import: hyperbookImport,
|
|
200
|
+
});
|
|
201
|
+
})();
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/// <reference path="./hyperbook.types.js" />
|
|
2
|
+
window.hyperbook = window.hyperbook || {};
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Core UI functions called from onclick handlers in the generated HTML.
|
|
6
|
+
* @type {HyperbookUI}
|
|
7
|
+
* @memberof hyperbook
|
|
8
|
+
* @see hyperbook.store
|
|
9
|
+
*/
|
|
10
|
+
hyperbook.ui = (function () {
|
|
11
|
+
/**
|
|
12
|
+
* Toggle the lightbox view of an element.
|
|
13
|
+
* @param {HTMLElement} el - The image element to display.
|
|
14
|
+
*/
|
|
15
|
+
function toggleLightbox(el) {
|
|
16
|
+
const overlay = document.createElement("div");
|
|
17
|
+
overlay.classList.add("lightbox-overlay");
|
|
18
|
+
|
|
19
|
+
const captionText =
|
|
20
|
+
el.parentElement.querySelector("figcaption")?.textContent || "";
|
|
21
|
+
|
|
22
|
+
const content = document.createElement("div");
|
|
23
|
+
content.classList.add("lightbox-content");
|
|
24
|
+
|
|
25
|
+
const imgContainer = document.createElement("div");
|
|
26
|
+
imgContainer.classList.add("lightbox-image-container");
|
|
27
|
+
|
|
28
|
+
const lightboxImg = document.createElement("img");
|
|
29
|
+
lightboxImg.src = el.src;
|
|
30
|
+
imgContainer.appendChild(lightboxImg);
|
|
31
|
+
|
|
32
|
+
content.appendChild(imgContainer);
|
|
33
|
+
|
|
34
|
+
if (captionText) {
|
|
35
|
+
const caption = document.createElement("div");
|
|
36
|
+
caption.classList.add("caption");
|
|
37
|
+
caption.textContent = captionText;
|
|
38
|
+
content.appendChild(caption);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
overlay.appendChild(content);
|
|
42
|
+
|
|
43
|
+
overlay.addEventListener("click", () => {
|
|
44
|
+
document.body.removeChild(overlay);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
document.body.appendChild(overlay);
|
|
48
|
+
overlay.style.display = "flex";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Toggle a bookmark on/off.
|
|
53
|
+
* @param {string} key - The bookmark path key.
|
|
54
|
+
* @param {string} label - The bookmark label.
|
|
55
|
+
*/
|
|
56
|
+
function toggleBookmark(key, label) {
|
|
57
|
+
const el = document.querySelectorAll(`.bookmark[data-key="${key}"]`);
|
|
58
|
+
hyperbook.store.bookmarks.get(key).then((bookmark) => {
|
|
59
|
+
if (!bookmark) {
|
|
60
|
+
hyperbook.store.bookmarks.add({ path: key, label }).then(() => {
|
|
61
|
+
el.forEach((e) => e.classList.add("active"));
|
|
62
|
+
hyperbook.bookmarks.update();
|
|
63
|
+
});
|
|
64
|
+
} else {
|
|
65
|
+
hyperbook.store.bookmarks.delete(key).then(() => {
|
|
66
|
+
el.forEach((e) => e.classList.remove("active"));
|
|
67
|
+
hyperbook.bookmarks.update();
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Toggle the navigation drawer.
|
|
75
|
+
*/
|
|
76
|
+
function navToggle() {
|
|
77
|
+
const navDrawerEl = document.getElementById("nav-drawer");
|
|
78
|
+
navDrawerEl.open = !navDrawerEl.open;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Toggle the table of contents drawer.
|
|
83
|
+
*/
|
|
84
|
+
function tocToggle() {
|
|
85
|
+
const tocDrawerEl = document.getElementById("toc-drawer");
|
|
86
|
+
tocDrawerEl.open = !tocDrawerEl.open;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Toggle the search drawer.
|
|
91
|
+
*/
|
|
92
|
+
function searchToggle() {
|
|
93
|
+
const searchDrawerEl = document.getElementById("search-drawer");
|
|
94
|
+
searchDrawerEl.open = !searchDrawerEl.open;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Perform a search and display the results.
|
|
99
|
+
*/
|
|
100
|
+
function search() {
|
|
101
|
+
const resultsEl = document.getElementById("search-results");
|
|
102
|
+
resultsEl.innerHTML = "";
|
|
103
|
+
const searchInputEl = document.querySelector("#search-input");
|
|
104
|
+
const query = searchInputEl.value;
|
|
105
|
+
const idx = window.lunr.Index.load(LUNR_INDEX);
|
|
106
|
+
const documents = SEARCH_DOCUMENTS;
|
|
107
|
+
const results = idx.search(query);
|
|
108
|
+
for (let result of results) {
|
|
109
|
+
const doc = documents[result.ref];
|
|
110
|
+
|
|
111
|
+
const container = document.createElement("a");
|
|
112
|
+
container.href = doc.href;
|
|
113
|
+
container.classList.add("search-result");
|
|
114
|
+
const heading = document.createElement("div");
|
|
115
|
+
heading.textContent = doc.heading;
|
|
116
|
+
heading.classList.add("search-result-heading");
|
|
117
|
+
const content = document.createElement("div");
|
|
118
|
+
content.classList.add("search-result-content");
|
|
119
|
+
const href = document.createElement("div");
|
|
120
|
+
href.classList.add("search-result-href");
|
|
121
|
+
href.textContent = doc.href;
|
|
122
|
+
|
|
123
|
+
let contentHTML = "";
|
|
124
|
+
const terms = Object.keys(result.matchData.metadata);
|
|
125
|
+
const term = terms[0];
|
|
126
|
+
if (result?.matchData?.metadata?.[term]?.content?.position?.length > 0) {
|
|
127
|
+
const pos = result.matchData.metadata[term].content.position[0];
|
|
128
|
+
const start = pos[0];
|
|
129
|
+
const len = pos[1];
|
|
130
|
+
let cutoffBefore = start - 50;
|
|
131
|
+
if (cutoffBefore < 0) {
|
|
132
|
+
cutoffBefore = 0;
|
|
133
|
+
} else {
|
|
134
|
+
contentHTML += "...";
|
|
135
|
+
}
|
|
136
|
+
contentHTML += doc.content.slice(cutoffBefore, start);
|
|
137
|
+
|
|
138
|
+
contentHTML += `<mark>${doc.content.slice(start, start + len)}</mark>`;
|
|
139
|
+
let cutoffAfter = start + len + 50;
|
|
140
|
+
|
|
141
|
+
contentHTML += doc.content.slice(start + len, cutoffAfter);
|
|
142
|
+
if (cutoffAfter < doc.content.length) {
|
|
143
|
+
contentHTML += "...";
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
content.innerHTML = contentHTML;
|
|
148
|
+
|
|
149
|
+
container.appendChild(heading);
|
|
150
|
+
container.appendChild(content);
|
|
151
|
+
container.appendChild(href);
|
|
152
|
+
resultsEl.appendChild(container);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
toggleLightbox,
|
|
158
|
+
toggleBookmark,
|
|
159
|
+
navToggle,
|
|
160
|
+
tocToggle,
|
|
161
|
+
searchToggle,
|
|
162
|
+
search,
|
|
163
|
+
};
|
|
164
|
+
})();
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* QR code dialog functions.
|
|
168
|
+
* @type {HyperbookQrcode}
|
|
169
|
+
* @memberof hyperbook
|
|
170
|
+
*/
|
|
171
|
+
hyperbook.qrcode = (function () {
|
|
172
|
+
/**
|
|
173
|
+
* Open the QR code dialog.
|
|
174
|
+
*/
|
|
175
|
+
function open() {
|
|
176
|
+
const qrCodeDialog = document.getElementById("qrcode-dialog");
|
|
177
|
+
const qrcodeEls = qrCodeDialog.getElementsByClassName("make-qrcode");
|
|
178
|
+
const urlEls = qrCodeDialog.getElementsByClassName("url");
|
|
179
|
+
const qrcodeEl = qrcodeEls[0];
|
|
180
|
+
const qrcode = new window.QRCode({
|
|
181
|
+
content: window.location.href,
|
|
182
|
+
padding: 0,
|
|
183
|
+
join: true,
|
|
184
|
+
color: "var(--color-text)",
|
|
185
|
+
container: "svg-viewbox",
|
|
186
|
+
background: "var(--color-background)",
|
|
187
|
+
ecl: "M",
|
|
188
|
+
});
|
|
189
|
+
qrcodeEl.innerHTML = qrcode.svg();
|
|
190
|
+
for (let urlEl of urlEls[0].children) {
|
|
191
|
+
const href = urlEl.getAttribute("data-href");
|
|
192
|
+
urlEl.innerHTML = `${window.location.origin}${href}`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
qrCodeDialog.showModal();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Close the QR code dialog.
|
|
200
|
+
*/
|
|
201
|
+
function close() {
|
|
202
|
+
const qrCodeDialog = document.getElementById("qrcode-dialog");
|
|
203
|
+
qrCodeDialog.close();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return { open, close };
|
|
207
|
+
})();
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Share dialog functions.
|
|
211
|
+
* @type {HyperbookShare}
|
|
212
|
+
* @memberof hyperbook
|
|
213
|
+
* @see hyperbook.i18n
|
|
214
|
+
*/
|
|
215
|
+
hyperbook.share = (function () {
|
|
216
|
+
/**
|
|
217
|
+
* Open the share dialog.
|
|
218
|
+
*/
|
|
219
|
+
function open() {
|
|
220
|
+
const shareDialog = document.getElementById("share-dialog");
|
|
221
|
+
updatePreview();
|
|
222
|
+
shareDialog.showModal();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Close the share dialog.
|
|
227
|
+
*/
|
|
228
|
+
function close() {
|
|
229
|
+
const shareDialog = document.getElementById("share-dialog");
|
|
230
|
+
shareDialog.close();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Update the URL preview in the share dialog.
|
|
235
|
+
*/
|
|
236
|
+
function updatePreview() {
|
|
237
|
+
const standaloneCheckbox = document.getElementById("share-standalone-checkbox");
|
|
238
|
+
const sectionCheckboxes = document.querySelectorAll("#share-dialog input[data-anchor]");
|
|
239
|
+
const previewEl = document.getElementById("share-url-preview");
|
|
240
|
+
|
|
241
|
+
const baseUrl = window.location.origin + window.location.pathname;
|
|
242
|
+
const params = new URLSearchParams();
|
|
243
|
+
|
|
244
|
+
if (standaloneCheckbox && standaloneCheckbox.checked) {
|
|
245
|
+
params.append("standalone", "true");
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const selectedSections = Array.from(sectionCheckboxes)
|
|
249
|
+
.filter(cb => cb.checked)
|
|
250
|
+
.map(cb => cb.getAttribute("data-anchor"));
|
|
251
|
+
|
|
252
|
+
if (selectedSections.length > 0) {
|
|
253
|
+
params.append("sections", selectedSections.join(","));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const finalUrl = params.toString() ? `${baseUrl}?${params.toString()}` : baseUrl;
|
|
257
|
+
previewEl.textContent = finalUrl;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Copy the shareable URL to clipboard.
|
|
262
|
+
*/
|
|
263
|
+
function copyUrl() {
|
|
264
|
+
const previewEl = document.getElementById("share-url-preview");
|
|
265
|
+
const url = previewEl.textContent;
|
|
266
|
+
|
|
267
|
+
navigator.clipboard.writeText(url).then(() => {
|
|
268
|
+
const button = document.querySelector("#share-dialog .copy-button");
|
|
269
|
+
const originalText = button.textContent;
|
|
270
|
+
button.textContent = hyperbook.i18n.get("share-dialog-url-copied");
|
|
271
|
+
|
|
272
|
+
setTimeout(() => {
|
|
273
|
+
button.textContent = originalText;
|
|
274
|
+
}, 2000);
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return { open, close, updatePreview, copyUrl };
|
|
279
|
+
})();
|