Skip to content

Commit 6557bb3

Browse files
authored
add h5p element (#964)
1 parent d180283 commit 6557bb3

22 files changed

Lines changed: 1128 additions & 298 deletions

File tree

.changeset/thin-eggs-look.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"hyperbook": minor
3+
"@hyperbook/markdown": minor
4+
"hyperbook-studio": minor
5+
---
6+
7+
Add h5p element

packages/hyperbook/build.ts

Lines changed: 66 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { hyperproject, vfile, hyperbook } from "@hyperbook/fs";
66
import { runArchive } from "./archive";
77
import { makeDir } from "./helpers/make-dir";
88
import { rimraf } from "rimraf";
9+
import extractZip from "extract-zip";
910
import {
1011
Link,
1112
Hyperproject,
@@ -22,7 +23,7 @@ export async function runBuildProject(
2223
project: Hyperproject,
2324
rootProject: Hyperproject,
2425
out?: string,
25-
filter?: string
26+
filter?: string,
2627
): Promise<void> {
2728
const name = hyperproject.getName(project);
2829
if (project.type === "book") {
@@ -33,7 +34,7 @@ export async function runBuildProject(
3334
project.basePath,
3435
name,
3536
out,
36-
filter
37+
filter,
3738
);
3839
} else {
3940
console.log(`${chalk.cyan(`[${name}]`)} Building Library.`);
@@ -53,7 +54,7 @@ async function runBuild(
5354
basePath?: string,
5455
prefix?: string,
5556
out?: string,
56-
filter?: string
57+
filter?: string,
5758
): Promise<void> {
5859
console.log(`${chalk.blue(`[${prefix}]`)} Reading hyperbook.json.`);
5960
const hyperbookJson = await hyperbook.getJson(root);
@@ -91,7 +92,7 @@ async function runBuild(
9192
rootOut = path.join(out, ".hyperbook", "out", basePath || "");
9293
}
9394
console.log(
94-
`${chalk.blue(`[${prefix}]`)} Cleaning output folder ${rootOut}.`
95+
`${chalk.blue(`[${prefix}]`)} Cleaning output folder ${rootOut}.`,
9596
);
9697

9798
await runArchive(root, rootOut, prefix);
@@ -133,7 +134,7 @@ async function runBuild(
133134
const pagesAndSections = await hyperbook.getPagesAndSections(root);
134135
const pageList = hyperbook.getPageList(
135136
pagesAndSections.sections,
136-
pagesAndSections.pages
137+
pagesAndSections.pages,
137138
);
138139
const searchDocuments: any[] = [];
139140

@@ -180,7 +181,7 @@ async function runBuild(
180181
});
181182
const permaFileOut = path.join(
182183
permaOut,
183-
file.markdown.data.permaid + ".html"
184+
file.markdown.data.permaid + ".html",
184185
);
185186
await fs.writeFile(permaFileOut, result.value);
186187
}
@@ -190,7 +191,7 @@ async function runBuild(
190191
readline.cursorTo(process.stdout, 0);
191192
}
192193
process.stdout.write(
193-
`${chalk.blue(`[${prefix}]`)} Buildung book: [${i++}/${bookFiles.length}]`
194+
`${chalk.blue(`[${prefix}]`)} Buildung book: [${i++}/${bookFiles.length}]`,
194195
);
195196
if (process.env.CI) {
196197
process.stdout.write("\n");
@@ -202,7 +203,7 @@ async function runBuild(
202203
const glossaryOut = path.join(rootOut, "glossary");
203204
if (filter) {
204205
glossaryFiles = glossaryFiles.filter((f) =>
205-
f.path.absolute?.endsWith(filter)
206+
f.path.absolute?.endsWith(filter),
206207
);
207208
}
208209

@@ -242,44 +243,68 @@ async function runBuild(
242243
readline.cursorTo(process.stdout, 0);
243244
}
244245
process.stdout.write(
245-
`${chalk.blue(`[${prefix}]`)} Buildung glossary: [${i++}/${glossaryFiles.length}]`
246+
`${chalk.blue(`[${prefix}]`)} Buildung glossary: [${i++}/${glossaryFiles.length}]`,
246247
);
247248
if (process.env.CI) {
248249
process.stdout.write("\n");
249250
}
250251
}
251252
process.stdout.write("\n");
252253

254+
const assetsPath = path.join(__dirname, "assets");
255+
const assetsOut = path.join(rootOut, ASSETS_FOLDER);
256+
await mkdir(assetsOut, {
257+
recursive: true,
258+
});
259+
253260
let otherFiles = await vfile.listForFolder(root, "public");
254261
i = 1;
255262
for (let file of otherFiles) {
256263
const directoryOut = path.join(rootOut, file.path.directory);
257264
await makeDir(directoryOut, {
258265
recursive: true,
259266
});
260-
if (file.path.href) {
267+
if (file.path.href && file.extension !== ".h5p") {
261268
const fileOut = path.join(rootOut, file.path.href);
262269
await cp(file.path.absolute, fileOut);
270+
} else if (file.path.href && file.extension === ".h5p") {
271+
const fileOut = path.join(rootOut, file.path.href);
272+
await extractZip(file.path.absolute, {
273+
dir: fileOut,
274+
});
275+
276+
const h5pLibraries = path.join(assetsOut, "directive-h5p", "libraries");
277+
await makeDir(h5pLibraries, { recursive: true });
278+
279+
const files = await fs.readdir(fileOut);
280+
const libraryFolders = files.filter(
281+
(file) => !["content", "h5p.json"].includes(file),
282+
);
283+
for (const libraryFolder of libraryFolders) {
284+
await cp(
285+
path.join(fileOut, libraryFolder),
286+
path.join(h5pLibraries, libraryFolder),
287+
{
288+
recursive: true,
289+
force: true,
290+
},
291+
);
292+
await rimraf(path.join(fileOut, libraryFolder));
293+
}
263294
}
264295
if (!process.env.CI) {
265296
readline.clearLine(process.stdout, 0);
266297
readline.cursorTo(process.stdout, 0);
267298
}
268299
process.stdout.write(
269-
`${chalk.blue(`[${prefix}]`)} Copying public files: [${i++}/${otherFiles.length}]`
300+
`${chalk.blue(`[${prefix}]`)} Copying public files: [${i++}/${otherFiles.length}]`,
270301
);
271302
if (process.env.CI) {
272303
process.stdout.write("\n");
273304
}
274305
}
275306
process.stdout.write("\n");
276307

277-
const assetsPath = path.join(__dirname, "assets");
278-
const assetsOut = path.join(rootOut, ASSETS_FOLDER);
279-
await mkdir(assetsOut, {
280-
recursive: true,
281-
});
282-
283308
i = 1;
284309
for (let directive of directives) {
285310
const assetsDirectivePath = path.join(assetsPath, `directive-${directive}`);
@@ -289,7 +314,7 @@ async function runBuild(
289314
readline.cursorTo(process.stdout, 0);
290315
}
291316
process.stdout.write(
292-
`${chalk.blue(`[${prefix}]`)} Copying directive assets: [${i++}/${directives.size}]`
317+
`${chalk.blue(`[${prefix}]`)} Copying directive assets: [${i++}/${directives.size}]`,
293318
);
294319
if (process.env.CI) {
295320
process.stdout.write("\n");
@@ -303,7 +328,7 @@ async function runBuild(
303328
} catch (e) {
304329
process.stdout.write("\n");
305330
process.stdout.write(
306-
`${chalk.red(`[${prefix}]`)} Failed copying directive assets: ${directive}`
331+
`${chalk.red(`[${prefix}]`)} Failed copying directive assets: ${directive}`,
307332
);
308333
process.stdout.write("\n");
309334
}
@@ -337,7 +362,7 @@ async function runBuild(
337362
readline.cursorTo(process.stdout, 0);
338363
}
339364
process.stdout.write(
340-
`${chalk.blue(`[${prefix}]`)} Copying hyperbook assets: [${i++}/${mainAssets.length}]`
365+
`${chalk.blue(`[${prefix}]`)} Copying hyperbook assets: [${i++}/${mainAssets.length}]`,
341366
);
342367
if (process.env.CI) {
343368
process.stdout.write("\n");
@@ -357,7 +382,7 @@ async function runBuild(
357382
foundLanguage = true;
358383
} catch (e) {
359384
console.log(
360-
`${chalk.yellow(`[${prefix}]`)} ${hyperbookJson.language} is no valid value for the lanuage key. See https://github.com/MihaiValentin/lunr-languages for possible values. Falling back to English.`
385+
`${chalk.yellow(`[${prefix}]`)} ${hyperbookJson.language} is no valid value for the lanuage key. See https://github.com/MihaiValentin/lunr-languages for possible values. Falling back to English.`,
361386
);
362387
}
363388
}
@@ -396,17 +421,31 @@ const SEARCH_DOCUMENTS = ${JSON.stringify(documents)};
396421
.readdir(path.join(__dirname, "locales"))
397422
.then((files) => files.map((file) => file.split(".")[0]));
398423
let language = "en";
399-
if (hyperbookJson.language && supportLanguages.includes(hyperbookJson.language)) {
424+
if (
425+
hyperbookJson.language &&
426+
supportLanguages.includes(hyperbookJson.language)
427+
) {
400428
language = hyperbookJson.language;
401429
}
402430

403-
const i18nJS = await fs.readFile(path.join(rootOut, ASSETS_FOLDER, "i18n.js"), "utf-8");
404-
const locales = await fs.readFile(path.join(__dirname, "locales", `${language}.json`), "utf-8");
405-
await fs.writeFile(path.join(rootOut, ASSETS_FOLDER, "i18n.js"), i18nJS.replace(/\/\/[\s]*LOCALES[\s\S]*?[\s]*\/\/[\s]*LOCALES/g, `
431+
const i18nJS = await fs.readFile(
432+
path.join(rootOut, ASSETS_FOLDER, "i18n.js"),
433+
"utf-8",
434+
);
435+
const locales = await fs.readFile(
436+
path.join(__dirname, "locales", `${language}.json`),
437+
"utf-8",
438+
);
439+
await fs.writeFile(
440+
path.join(rootOut, ASSETS_FOLDER, "i18n.js"),
441+
i18nJS.replace(
442+
/\/\/[\s]*LOCALES[\s\S]*?[\s]*\/\/[\s]*LOCALES/g,
443+
`
406444
// GENERATED
407445
const locales = ${locales};
408-
`));
409-
446+
`,
447+
),
448+
);
410449

411450
console.log(`${chalk.green(`[${prefix}]`)} Build success: ${rootOut}`);
412451
}

packages/hyperbook/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"cpy": "11.1.0",
5252
"cross-spawn": "7.0.6",
5353
"domutils": "^3.2.2",
54+
"extract-zip": "^2.0.1",
5455
"got": "12.6.0",
5556
"htmlparser2": "^10.0.0",
5657
"lunr": "^2.3.9",
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
hyperbook.h5p = (function () {
2+
/**
3+
* Initialize H5P elements within the given root element.
4+
* @param {HTMLElement} [root=document] - The root element to initialize.
5+
*/
6+
7+
const save = (id) =>
8+
H5P.getUserData(id, "state", (error, userData) => {
9+
if (!error) {
10+
store.h5p.put({ id, userData });
11+
}
12+
});
13+
14+
const init = async (root) => {
15+
const els = root.getElementsByClassName("directive-h5p");
16+
const assets = `${HYPERBOOK_ASSETS}directive-h5p`;
17+
18+
const h5pBaseOptions = {
19+
frameJs: `${assets}/frame.bundle.js`,
20+
frameCss: `${assets}/styles/h5p.css`,
21+
librariesPath: `${assets}/libraries`,
22+
saveFreq: 1,
23+
contentUserData: undefined,
24+
frame: false,
25+
copyright: false,
26+
export: false,
27+
icon: false,
28+
embed: false,
29+
};
30+
31+
for (const el of els) {
32+
const h5pFrame = el.querySelector(".h5p-frame");
33+
const src = el.getAttribute("data-src");
34+
const id = el.getAttribute("data-id");
35+
if (h5pFrame && src) {
36+
const result = await store.h5p.get(id);
37+
const h5pOptions = {
38+
...h5pBaseOptions,
39+
id,
40+
h5pJsonPath: src,
41+
contentUserData: result
42+
? [{ state: JSON.stringify(result.userData) }]
43+
: undefined,
44+
};
45+
46+
new H5PStandalone.H5P(h5pFrame, h5pOptions);
47+
48+
// save from time to time
49+
setInterval(() => save(id), 1000);
50+
}
51+
}
52+
};
53+
54+
init(document);
55+
})();
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.directive-h5p .h5p-frame {
2+
border: 1px solid var(--color-spacer);
3+
border-radius: 8px;
4+
overflow: hidden;
5+
padding: 8px;
6+
background-color: #fefefe;
7+
}
8+
9+
.directive-h5p {
10+
margin-bottom: 8px;
11+
}

packages/markdown/assets/store.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ store.version(1).stores({
2323
slideshow: `id,active`,
2424
tabs: `id,active`,
2525
excalidraw: `id,excalidrawElements,appState,files`,
26-
webide: `id,html,css,js`
26+
webide: `id,html,css,js`,
27+
h5p: `id,userData`,
2728
});
2829
var sqlIdeDB = new Dexie("SQL-IDE");
2930
sqlIdeDB.version(0.1).stores({

0 commit comments

Comments
 (0)