@vcgstudiosy-beep/dlof 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.
Files changed (40) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +103 -0
  3. package/bin/dlof.js +2 -0
  4. package/dist/cli.d.ts +2 -0
  5. package/dist/cli.js +237 -0
  6. package/dist/dlof/parseDlof.d.ts +6 -0
  7. package/dist/dlof/parseDlof.js +181 -0
  8. package/dist/dlof/serializeDlof.d.ts +3 -0
  9. package/dist/dlof/serializeDlof.js +176 -0
  10. package/dist/dlof/validate.d.ts +11 -0
  11. package/dist/dlof/validate.js +76 -0
  12. package/dist/index.d.ts +12 -0
  13. package/dist/index.js +47 -0
  14. package/dist/loop/loopUtils.d.ts +16 -0
  15. package/dist/loop/loopUtils.js +66 -0
  16. package/dist/packages/dlofSeries.d.ts +9 -0
  17. package/dist/packages/dlofSeries.js +60 -0
  18. package/dist/packages/dlofpkg.d.ts +10 -0
  19. package/dist/packages/dlofpkg.js +72 -0
  20. package/dist/player/renderPlayerHtml.d.ts +13 -0
  21. package/dist/player/renderPlayerHtml.js +235 -0
  22. package/dist/types.d.ts +141 -0
  23. package/dist/types.js +6 -0
  24. package/dist/viewer/renderContent.d.ts +3 -0
  25. package/dist/viewer/renderContent.js +77 -0
  26. package/dist/viewer/renderViewerHtml.d.ts +11 -0
  27. package/dist/viewer/renderViewerHtml.js +46 -0
  28. package/dist/viewer/theme.d.ts +13 -0
  29. package/dist/viewer/theme.js +102 -0
  30. package/dist/xml/parser.d.ts +22 -0
  31. package/dist/xml/parser.js +199 -0
  32. package/dist/xml/serializer.d.ts +11 -0
  33. package/dist/xml/serializer.js +46 -0
  34. package/dist/zip/crc32.d.ts +2 -0
  35. package/dist/zip/crc32.js +22 -0
  36. package/dist/zip/zipReader.d.ts +14 -0
  37. package/dist/zip/zipReader.js +109 -0
  38. package/dist/zip/zipWriter.d.ts +9 -0
  39. package/dist/zip/zipWriter.js +106 -0
  40. package/package.json +42 -0
@@ -0,0 +1,176 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.serializeDlof = serializeDlof;
4
+ const serializer_1 = require("../xml/serializer");
5
+ const DLOF_NAMESPACE = "https://dlof.org/schema/1.0";
6
+ /** يحوّل كائن DocumentLoop مُنمَّط إلى نص XML صالح لملف .dlof */
7
+ function serializeDlof(doc) {
8
+ const metadataChildren = [
9
+ { tag: "title", text: doc.metadata.title },
10
+ { tag: "domain", text: doc.metadata.domain },
11
+ ];
12
+ if (doc.metadata.author)
13
+ metadataChildren.push({ tag: "author", text: doc.metadata.author });
14
+ if (doc.metadata.createdAt)
15
+ metadataChildren.push({ tag: "createdAt", text: doc.metadata.createdAt });
16
+ if (doc.metadata.updatedAt)
17
+ metadataChildren.push({ tag: "updatedAt", text: doc.metadata.updatedAt });
18
+ if (doc.metadata.language)
19
+ metadataChildren.push({ tag: "language", text: doc.metadata.language });
20
+ if (doc.metadata.tags && doc.metadata.tags.length) {
21
+ metadataChildren.push({
22
+ tag: "tags",
23
+ children: doc.metadata.tags.map((t) => ({ tag: "tag", text: t })),
24
+ });
25
+ }
26
+ const loopLinksChildren = [];
27
+ if (doc.loopLinks.previous) {
28
+ loopLinksChildren.push({
29
+ tag: "previous",
30
+ attrs: { ref: doc.loopLinks.previous.ref, title: doc.loopLinks.previous.title },
31
+ });
32
+ }
33
+ if (doc.loopLinks.next) {
34
+ loopLinksChildren.push({
35
+ tag: "next",
36
+ attrs: { ref: doc.loopLinks.next.ref, title: doc.loopLinks.next.title },
37
+ });
38
+ }
39
+ loopLinksChildren.push({ tag: "loopRoot", text: String(doc.loopLinks.loopRoot) });
40
+ let contentChild;
41
+ const c = doc.content;
42
+ switch (c.kind) {
43
+ case "genericItem":
44
+ contentChild = {
45
+ tag: "genericItem",
46
+ attrs: { customType: c.customType },
47
+ children: [
48
+ c.type !== undefined ? { tag: "type", text: c.type } : undefined,
49
+ c.element !== undefined ? { tag: "element", text: c.element } : undefined,
50
+ c.body !== undefined ? { tag: "body", text: c.body } : undefined,
51
+ ].filter(Boolean),
52
+ };
53
+ break;
54
+ case "qaItem":
55
+ contentChild = {
56
+ tag: "qaItem",
57
+ children: [
58
+ { tag: "question", text: c.question },
59
+ { tag: "answer", text: c.answer },
60
+ c.explanation !== undefined ? { tag: "explanation", text: c.explanation } : undefined,
61
+ c.difficulty !== undefined ? { tag: "difficulty", text: c.difficulty } : undefined,
62
+ ].filter(Boolean),
63
+ };
64
+ break;
65
+ case "bookChapter":
66
+ contentChild = {
67
+ tag: "bookChapter",
68
+ children: [
69
+ c.chapterNumber !== undefined
70
+ ? { tag: "chapterNumber", text: String(c.chapterNumber) }
71
+ : undefined,
72
+ { tag: "chapterTitle", text: c.chapterTitle },
73
+ { tag: "text", text: c.text },
74
+ c.summary !== undefined ? { tag: "summary", text: c.summary } : undefined,
75
+ ].filter(Boolean),
76
+ };
77
+ break;
78
+ case "termDefinition":
79
+ contentChild = {
80
+ tag: "termDefinition",
81
+ children: [
82
+ { tag: "term", text: c.term },
83
+ { tag: "definition", text: c.definition },
84
+ c.example !== undefined ? { tag: "example", text: c.example } : undefined,
85
+ ].filter(Boolean),
86
+ };
87
+ break;
88
+ case "infoExplain":
89
+ contentChild = {
90
+ tag: "infoExplain",
91
+ children: [
92
+ { tag: "topic", text: c.topic },
93
+ { tag: "explanation", text: c.explanation },
94
+ c.source !== undefined ? { tag: "source", text: c.source } : undefined,
95
+ ].filter(Boolean),
96
+ };
97
+ break;
98
+ case "episodeItem":
99
+ contentChild = {
100
+ tag: "episodeItem",
101
+ children: [
102
+ c.episodeNumber !== undefined
103
+ ? { tag: "episodeNumber", text: String(c.episodeNumber) }
104
+ : undefined,
105
+ c.seasonNumber !== undefined
106
+ ? { tag: "seasonNumber", text: String(c.seasonNumber) }
107
+ : undefined,
108
+ { tag: "episodeTitle", text: c.episodeTitle },
109
+ c.synopsis !== undefined ? { tag: "synopsis", text: c.synopsis } : undefined,
110
+ c.duration !== undefined ? { tag: "duration", text: String(c.duration) } : undefined,
111
+ c.seriesTitle !== undefined ? { tag: "seriesTitle", text: c.seriesTitle } : undefined,
112
+ c.mediaRef !== undefined ? { tag: "mediaRef", text: c.mediaRef } : undefined,
113
+ c.releaseDate !== undefined ? { tag: "releaseDate", text: c.releaseDate } : undefined,
114
+ c.body !== undefined ? { tag: "body", text: c.body } : undefined,
115
+ c.thumbnailBase64 !== undefined
116
+ ? { tag: "thumbnailBase64", text: c.thumbnailBase64 }
117
+ : undefined,
118
+ ].filter(Boolean),
119
+ };
120
+ break;
121
+ }
122
+ const rootChildren = [
123
+ { tag: "metadata", children: metadataChildren },
124
+ { tag: "loopLinks", children: loopLinksChildren },
125
+ { tag: "content", children: [contentChild] },
126
+ ];
127
+ if (doc.attachments && doc.attachments.length) {
128
+ rootChildren.push({
129
+ tag: "attachments",
130
+ children: doc.attachments.map((a) => ({
131
+ tag: "attachment",
132
+ attrs: {
133
+ id: a.id,
134
+ fileName: a.fileName,
135
+ mimeType: a.mimeType,
136
+ kind: a.kind,
137
+ sizeBytes: a.sizeBytes,
138
+ },
139
+ children: [
140
+ a.data !== undefined ? { tag: "data", text: a.data } : undefined,
141
+ a.uri !== undefined ? { tag: "uri", text: a.uri } : undefined,
142
+ a.caption !== undefined ? { tag: "caption", text: a.caption } : undefined,
143
+ ].filter(Boolean),
144
+ })),
145
+ });
146
+ }
147
+ if (doc.template) {
148
+ rootChildren.push({
149
+ tag: "template",
150
+ attrs: {
151
+ ref: doc.template.ref,
152
+ primaryColor: doc.template.primaryColor,
153
+ secondaryColor: doc.template.secondaryColor,
154
+ backgroundColor: doc.template.backgroundColor,
155
+ textColor: doc.template.textColor,
156
+ fontFamily: doc.template.fontFamily,
157
+ layout: doc.template.layout,
158
+ headerAttachmentRef: doc.template.headerAttachmentRef,
159
+ },
160
+ });
161
+ }
162
+ if (doc.mediaFolder && doc.mediaFolder.length) {
163
+ rootChildren.push({
164
+ tag: "mediaFolder",
165
+ children: doc.mediaFolder.map((m) => ({
166
+ tag: "mediaFile",
167
+ attrs: { path: m.path, kind: m.kind, label: m.label },
168
+ })),
169
+ });
170
+ }
171
+ return (0, serializer_1.buildXmlDocument)({
172
+ tag: "documentLoop",
173
+ attrs: { xmlns: DLOF_NAMESPACE, version: doc.version, id: doc.id },
174
+ children: rootChildren,
175
+ });
176
+ }
@@ -0,0 +1,11 @@
1
+ import { DocumentLoop } from "../types";
2
+ export interface ValidationIssue {
3
+ level: "error" | "warning";
4
+ message: string;
5
+ }
6
+ export interface ValidationResult {
7
+ valid: boolean;
8
+ issues: ValidationIssue[];
9
+ }
10
+ /** يتحقق من مستند DLoF مقابل قواعد المواصفة الإلزامية (docs/SPECIFICATION.md) */
11
+ export declare function validateDlof(doc: DocumentLoop): ValidationResult;
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateDlof = validateDlof;
4
+ const VALID_DOMAINS = ["education", "book", "infoApp", "infoLoop", "series", "custom"];
5
+ const VALID_ATTACHMENT_KINDS = ["image", "video", "file"];
6
+ const MAX_INLINE_ATTACHMENT_BYTES = 15 * 1024 * 1024; // 15MB، كما توصي المواصفة
7
+ /** يتحقق من مستند DLoF مقابل قواعد المواصفة الإلزامية (docs/SPECIFICATION.md) */
8
+ function validateDlof(doc) {
9
+ const issues = [];
10
+ const err = (message) => issues.push({ level: "error", message });
11
+ const warn = (message) => issues.push({ level: "warning", message });
12
+ if (!doc.version)
13
+ err("الصفة version مفقودة على <documentLoop>");
14
+ if (!doc.id)
15
+ err("الصفة id مفقودة على <documentLoop>");
16
+ if (!doc.metadata.title)
17
+ err("العنصر <title> مطلوب داخل <metadata>");
18
+ if (!doc.metadata.domain) {
19
+ err("العنصر <domain> مطلوب داخل <metadata>");
20
+ }
21
+ else if (!VALID_DOMAINS.includes(doc.metadata.domain)) {
22
+ warn(`قيمة domain "${doc.metadata.domain}" غير معروفة (المتوقع أحد: ${VALID_DOMAINS.join(", ")})`);
23
+ }
24
+ if (!doc.loopLinks.previous && !doc.loopLinks.next) {
25
+ warn("لا يوجد previous ولا next: هذا المستند حلقة وحيدة معزولة");
26
+ }
27
+ switch (doc.content.kind) {
28
+ case "qaItem":
29
+ if (!doc.content.question)
30
+ err("qaItem: العنصر <question> مطلوب");
31
+ if (!doc.content.answer)
32
+ err("qaItem: العنصر <answer> مطلوب");
33
+ break;
34
+ case "bookChapter":
35
+ if (!doc.content.chapterTitle)
36
+ err("bookChapter: العنصر <chapterTitle> مطلوب");
37
+ if (!doc.content.text)
38
+ err("bookChapter: العنصر <text> مطلوب");
39
+ break;
40
+ case "termDefinition":
41
+ if (!doc.content.term)
42
+ err("termDefinition: العنصر <term> مطلوب");
43
+ if (!doc.content.definition)
44
+ err("termDefinition: العنصر <definition> مطلوب");
45
+ break;
46
+ case "infoExplain":
47
+ if (!doc.content.topic)
48
+ err("infoExplain: العنصر <topic> مطلوب");
49
+ if (!doc.content.explanation)
50
+ err("infoExplain: العنصر <explanation> مطلوب");
51
+ break;
52
+ case "episodeItem":
53
+ if (!doc.content.episodeTitle)
54
+ err("episodeItem: العنصر <episodeTitle> مطلوب");
55
+ break;
56
+ }
57
+ for (const a of doc.attachments ?? []) {
58
+ if (!a.id)
59
+ err("attachment: الصفة id مطلوبة");
60
+ if (!a.fileName)
61
+ err("attachment: الصفة fileName مطلوبة");
62
+ if (!VALID_ATTACHMENT_KINDS.includes(a.kind)) {
63
+ warn(`attachment "${a.id}": قيمة kind "${a.kind}" غير معروفة`);
64
+ }
65
+ if (!a.data && !a.uri) {
66
+ err(`attachment "${a.id}": يجب توفير <data> أو <uri>`);
67
+ }
68
+ if (a.data) {
69
+ const approxBytes = Math.floor((a.data.length * 3) / 4);
70
+ if (approxBytes > MAX_INLINE_ATTACHMENT_BYTES) {
71
+ warn(`attachment "${a.id}": الحجم التقريبي ${(approxBytes / 1024 / 1024).toFixed(1)}MB يتجاوز الحد الموصى به (15MB) للتضمين المباشر`);
72
+ }
73
+ }
74
+ }
75
+ return { valid: !issues.some((i) => i.level === "error"), issues };
76
+ }
@@ -0,0 +1,12 @@
1
+ export * from "./types";
2
+ export { parseDlof, isDlofFileName, DLOF_EXTENSIONS } from "./dlof/parseDlof";
3
+ export { serializeDlof } from "./dlof/serializeDlof";
4
+ export { validateDlof, ValidationIssue, ValidationResult } from "./dlof/validate";
5
+ export { readDlofPkg, writeDlofPkg, buildDefaultMeta } from "./packages/dlofpkg";
6
+ export { readDlofSeries, writeDlofSeries } from "./packages/dlofSeries";
7
+ export { readZip, findEntry, ZipEntry } from "./zip/zipReader";
8
+ export { writeZip, ZipInputEntry } from "./zip/zipWriter";
9
+ export { resolveLoopChain, buildNavigationMap, LoopChainEntry } from "./loop/loopUtils";
10
+ export { renderViewerHtml, ViewerOptions } from "./viewer/renderViewerHtml";
11
+ export { renderPlayerHtml, PlayerOptions } from "./player/renderPlayerHtml";
12
+ export { resolveTheme, ResolvedTheme } from "./viewer/theme";
package/dist/index.js ADDED
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.resolveTheme = exports.renderPlayerHtml = exports.renderViewerHtml = exports.buildNavigationMap = exports.resolveLoopChain = exports.writeZip = exports.findEntry = exports.readZip = exports.writeDlofSeries = exports.readDlofSeries = exports.buildDefaultMeta = exports.writeDlofPkg = exports.readDlofPkg = exports.validateDlof = exports.serializeDlof = exports.DLOF_EXTENSIONS = exports.isDlofFileName = exports.parseDlof = void 0;
18
+ __exportStar(require("./types"), exports);
19
+ var parseDlof_1 = require("./dlof/parseDlof");
20
+ Object.defineProperty(exports, "parseDlof", { enumerable: true, get: function () { return parseDlof_1.parseDlof; } });
21
+ Object.defineProperty(exports, "isDlofFileName", { enumerable: true, get: function () { return parseDlof_1.isDlofFileName; } });
22
+ Object.defineProperty(exports, "DLOF_EXTENSIONS", { enumerable: true, get: function () { return parseDlof_1.DLOF_EXTENSIONS; } });
23
+ var serializeDlof_1 = require("./dlof/serializeDlof");
24
+ Object.defineProperty(exports, "serializeDlof", { enumerable: true, get: function () { return serializeDlof_1.serializeDlof; } });
25
+ var validate_1 = require("./dlof/validate");
26
+ Object.defineProperty(exports, "validateDlof", { enumerable: true, get: function () { return validate_1.validateDlof; } });
27
+ var dlofpkg_1 = require("./packages/dlofpkg");
28
+ Object.defineProperty(exports, "readDlofPkg", { enumerable: true, get: function () { return dlofpkg_1.readDlofPkg; } });
29
+ Object.defineProperty(exports, "writeDlofPkg", { enumerable: true, get: function () { return dlofpkg_1.writeDlofPkg; } });
30
+ Object.defineProperty(exports, "buildDefaultMeta", { enumerable: true, get: function () { return dlofpkg_1.buildDefaultMeta; } });
31
+ var dlofSeries_1 = require("./packages/dlofSeries");
32
+ Object.defineProperty(exports, "readDlofSeries", { enumerable: true, get: function () { return dlofSeries_1.readDlofSeries; } });
33
+ Object.defineProperty(exports, "writeDlofSeries", { enumerable: true, get: function () { return dlofSeries_1.writeDlofSeries; } });
34
+ var zipReader_1 = require("./zip/zipReader");
35
+ Object.defineProperty(exports, "readZip", { enumerable: true, get: function () { return zipReader_1.readZip; } });
36
+ Object.defineProperty(exports, "findEntry", { enumerable: true, get: function () { return zipReader_1.findEntry; } });
37
+ var zipWriter_1 = require("./zip/zipWriter");
38
+ Object.defineProperty(exports, "writeZip", { enumerable: true, get: function () { return zipWriter_1.writeZip; } });
39
+ var loopUtils_1 = require("./loop/loopUtils");
40
+ Object.defineProperty(exports, "resolveLoopChain", { enumerable: true, get: function () { return loopUtils_1.resolveLoopChain; } });
41
+ Object.defineProperty(exports, "buildNavigationMap", { enumerable: true, get: function () { return loopUtils_1.buildNavigationMap; } });
42
+ var renderViewerHtml_1 = require("./viewer/renderViewerHtml");
43
+ Object.defineProperty(exports, "renderViewerHtml", { enumerable: true, get: function () { return renderViewerHtml_1.renderViewerHtml; } });
44
+ var renderPlayerHtml_1 = require("./player/renderPlayerHtml");
45
+ Object.defineProperty(exports, "renderPlayerHtml", { enumerable: true, get: function () { return renderPlayerHtml_1.renderPlayerHtml; } });
46
+ var theme_1 = require("./viewer/theme");
47
+ Object.defineProperty(exports, "resolveTheme", { enumerable: true, get: function () { return theme_1.resolveTheme; } });
@@ -0,0 +1,16 @@
1
+ import { DocumentLoop } from "../types";
2
+ export interface LoopChainEntry {
3
+ fileName: string;
4
+ document: DocumentLoop;
5
+ }
6
+ /**
7
+ * يحلّ سلسلة الحلقة كاملة انطلاقاً من ملف بداية (أو loopRoot تلقائياً إن لم يُحدَّد)،
8
+ * متتبعاً روابط next بالترتيب حتى: نهاية مفتوحة، أو العودة لبداية حلقة مغلقة، أو ref مفقود.
9
+ * لا يفشل عند فقدان ملف مُشار إليه (توصية القسم 10 من المواصفة) بل يتوقف بهدوء.
10
+ */
11
+ export declare function resolveLoopChain(documents: Record<string, DocumentLoop>, startFileName?: string): LoopChainEntry[];
12
+ /** يبني خريطة تنقّل سريعة: fileName -> {previous?, next?} كأسماء ملفات محلولة فعلياً */
13
+ export declare function buildNavigationMap(chain: LoopChainEntry[]): Record<string, {
14
+ previous?: string;
15
+ next?: string;
16
+ }>;
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveLoopChain = resolveLoopChain;
4
+ exports.buildNavigationMap = buildNavigationMap;
5
+ /** يطبّع اسم مرجع ref لمطابقته بمفاتيح خريطة المستندات (يتجاهل المسارات النسبية الزائدة) */
6
+ function normalizeRef(ref) {
7
+ const cleaned = ref.replace(/^\.?\//, "");
8
+ const parts = cleaned.split("/");
9
+ return parts[parts.length - 1];
10
+ }
11
+ /**
12
+ * يحلّ سلسلة الحلقة كاملة انطلاقاً من ملف بداية (أو loopRoot تلقائياً إن لم يُحدَّد)،
13
+ * متتبعاً روابط next بالترتيب حتى: نهاية مفتوحة، أو العودة لبداية حلقة مغلقة، أو ref مفقود.
14
+ * لا يفشل عند فقدان ملف مُشار إليه (توصية القسم 10 من المواصفة) بل يتوقف بهدوء.
15
+ */
16
+ function resolveLoopChain(documents, startFileName) {
17
+ const byId = new Map(); // id -> fileName، لدعم المراجع بالمعرف أيضاً
18
+ for (const [fileName, doc] of Object.entries(documents)) {
19
+ byId.set(doc.id, fileName);
20
+ }
21
+ function resolveFileName(ref) {
22
+ const norm = normalizeRef(ref);
23
+ if (documents[norm])
24
+ return norm;
25
+ if (documents[ref])
26
+ return ref;
27
+ if (byId.has(ref))
28
+ return byId.get(ref);
29
+ // حاول المطابقة بلا امتداد
30
+ const withoutExt = norm.replace(/\.(dlof|ep|episode)$/i, "");
31
+ const match = Object.keys(documents).find((f) => f.replace(/\.(dlof|ep|episode)$/i, "") === withoutExt);
32
+ return match;
33
+ }
34
+ let current = startFileName && documents[startFileName]
35
+ ? startFileName
36
+ : Object.keys(documents).find((f) => documents[f].loopLinks.loopRoot) ??
37
+ Object.keys(documents)[0];
38
+ if (!current)
39
+ return [];
40
+ const chain = [];
41
+ const visited = new Set();
42
+ while (current && !visited.has(current)) {
43
+ visited.add(current);
44
+ const doc = documents[current];
45
+ chain.push({ fileName: current, document: doc });
46
+ const nextRef = doc.loopLinks.next?.ref;
47
+ if (!nextRef)
48
+ break;
49
+ const nextFile = resolveFileName(nextRef);
50
+ if (!nextFile)
51
+ break; // ref لا يُعثر عليه: توقف بهدوء دون انهيار
52
+ current = nextFile;
53
+ }
54
+ return chain;
55
+ }
56
+ /** يبني خريطة تنقّل سريعة: fileName -> {previous?, next?} كأسماء ملفات محلولة فعلياً */
57
+ function buildNavigationMap(chain) {
58
+ const map = {};
59
+ chain.forEach((entry, idx) => {
60
+ map[entry.fileName] = {
61
+ previous: idx > 0 ? chain[idx - 1].fileName : undefined,
62
+ next: idx < chain.length - 1 ? chain[idx + 1].fileName : undefined,
63
+ };
64
+ });
65
+ return map;
66
+ }
@@ -0,0 +1,9 @@
1
+ import { DlofSeries } from "../types";
2
+ /**
3
+ * يقرأ حزمة .dlofSeries: أرشيف ZIP يحتوي مجلداً واحداً باسم السلسلة،
4
+ * بداخله ملفات .dlof (أو .ep / .episode) بالإضافة لأصول اختيارية
5
+ * (fonts/, media/, set.txt...). راجع spec/PACKAGE_FORMATS.md.
6
+ */
7
+ export declare function readDlofSeries(buf: Buffer): DlofSeries;
8
+ /** ينشئ بيانات ثنائية لحزمة .dlofSeries جاهزة للكتابة إلى القرص */
9
+ export declare function writeDlofSeries(series: DlofSeries): Buffer;
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readDlofSeries = readDlofSeries;
4
+ exports.writeDlofSeries = writeDlofSeries;
5
+ const zipReader_1 = require("../zip/zipReader");
6
+ const zipWriter_1 = require("../zip/zipWriter");
7
+ const parseDlof_1 = require("../dlof/parseDlof");
8
+ const serializeDlof_1 = require("../dlof/serializeDlof");
9
+ /**
10
+ * يقرأ حزمة .dlofSeries: أرشيف ZIP يحتوي مجلداً واحداً باسم السلسلة،
11
+ * بداخله ملفات .dlof (أو .ep / .episode) بالإضافة لأصول اختيارية
12
+ * (fonts/, media/, set.txt...). راجع spec/PACKAGE_FORMATS.md.
13
+ */
14
+ function readDlofSeries(buf) {
15
+ const entries = (0, zipReader_1.readZip)(buf).filter((e) => !e.isDirectory);
16
+ if (!entries.length) {
17
+ throw new Error("حزمة .dlofSeries فارغة أو غير صالحة");
18
+ }
19
+ // اسم مجلد السلسلة هو الجزء الأول المشترك من مسارات المُدخلات
20
+ const firstSegment = entries[0].name.split("/")[0];
21
+ const allShareRoot = entries.every((e) => e.name.split("/")[0] === firstSegment);
22
+ const name = allShareRoot ? firstSegment : "series";
23
+ const docsParsed = {};
24
+ const assets = {};
25
+ let rootFileName;
26
+ for (const entry of entries) {
27
+ const relPath = allShareRoot ? entry.name.slice(firstSegment.length + 1) : entry.name;
28
+ if (!relPath)
29
+ continue;
30
+ if ((0, parseDlof_1.isDlofFileName)(relPath) && !relPath.includes("/")) {
31
+ const doc = (0, parseDlof_1.parseDlof)(entry.data.toString("utf8"), relPath);
32
+ docsParsed[relPath] = doc;
33
+ if (doc.loopLinks.loopRoot)
34
+ rootFileName = relPath;
35
+ }
36
+ else {
37
+ assets[relPath] = entry.data;
38
+ }
39
+ }
40
+ // إن لم يُعلن أي ملف عن نفسه كـ loopRoot، جرّب series-index.dlof كافتراضي شائع
41
+ if (!rootFileName && docsParsed["series-index.dlof"]) {
42
+ rootFileName = "series-index.dlof";
43
+ }
44
+ return { name, documents: docsParsed, rootFileName, assets };
45
+ }
46
+ /** ينشئ بيانات ثنائية لحزمة .dlofSeries جاهزة للكتابة إلى القرص */
47
+ function writeDlofSeries(series) {
48
+ const entries = [];
49
+ const base = series.name.replace(/\/+$/, "");
50
+ for (const [fileName, doc] of Object.entries(series.documents)) {
51
+ entries.push({
52
+ name: `${base}/${fileName}`,
53
+ data: Buffer.from((0, serializeDlof_1.serializeDlof)(doc), "utf8"),
54
+ });
55
+ }
56
+ for (const [path, data] of Object.entries(series.assets)) {
57
+ entries.push({ name: `${base}/${path}`, data });
58
+ }
59
+ return (0, zipWriter_1.writeZip)(entries);
60
+ }
@@ -0,0 +1,10 @@
1
+ import { DlofPkg, DlofPkgMeta, DocumentLoop } from "../types";
2
+ /**
3
+ * يقرأ حزمة .dlofpkg (أرشيف ZIP يحتوي package.dlof + meta.json + attachments/)
4
+ * راجع spec/PACKAGE_FORMATS.md للبنية الرسمية.
5
+ */
6
+ export declare function readDlofPkg(buf: Buffer): DlofPkg;
7
+ /** ينشئ بيانات ثنائية لحزمة .dlofpkg جاهزة للكتابة إلى القرص */
8
+ export declare function writeDlofPkg(pkg: DlofPkg): Buffer;
9
+ /** يبني meta.json افتراضياً انطلاقاً من مستند DocumentLoop */
10
+ export declare function buildDefaultMeta(doc: DocumentLoop): DlofPkgMeta;
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readDlofPkg = readDlofPkg;
4
+ exports.writeDlofPkg = writeDlofPkg;
5
+ exports.buildDefaultMeta = buildDefaultMeta;
6
+ const zipReader_1 = require("../zip/zipReader");
7
+ const zipWriter_1 = require("../zip/zipWriter");
8
+ const parseDlof_1 = require("../dlof/parseDlof");
9
+ const serializeDlof_1 = require("../dlof/serializeDlof");
10
+ /**
11
+ * يقرأ حزمة .dlofpkg (أرشيف ZIP يحتوي package.dlof + meta.json + attachments/)
12
+ * راجع spec/PACKAGE_FORMATS.md للبنية الرسمية.
13
+ */
14
+ function readDlofPkg(buf) {
15
+ const entries = (0, zipReader_1.readZip)(buf);
16
+ const dlofEntry = (0, zipReader_1.findEntry)(entries, "package.dlof");
17
+ if (!dlofEntry) {
18
+ throw new Error('حزمة .dlofpkg غير صالحة: لا يحتوي على "package.dlof"');
19
+ }
20
+ const document = (0, parseDlof_1.parseDlof)(dlofEntry.data.toString("utf8"), "package.dlof");
21
+ const metaEntry = (0, zipReader_1.findEntry)(entries, "meta.json");
22
+ let meta;
23
+ if (metaEntry) {
24
+ meta = JSON.parse(metaEntry.data.toString("utf8"));
25
+ }
26
+ else {
27
+ // لا يوجد meta.json: نبني واحداً من البيانات الوصفية داخل المستند نفسه (تسامح مع حزم غير كاملة)
28
+ meta = {
29
+ id: document.id,
30
+ title: document.metadata.title,
31
+ domain: document.metadata.domain,
32
+ version: document.version,
33
+ author: document.metadata.author,
34
+ language: document.metadata.language,
35
+ createdAt: document.metadata.createdAt,
36
+ dlofpkg_version: "1.0",
37
+ };
38
+ }
39
+ const attachments = {};
40
+ for (const entry of entries) {
41
+ if (entry.isDirectory)
42
+ continue;
43
+ if (entry.name.startsWith("attachments/")) {
44
+ attachments[entry.name] = entry.data;
45
+ }
46
+ }
47
+ return { meta, document, attachments };
48
+ }
49
+ /** ينشئ بيانات ثنائية لحزمة .dlofpkg جاهزة للكتابة إلى القرص */
50
+ function writeDlofPkg(pkg) {
51
+ const entries = [
52
+ { name: "package.dlof", data: Buffer.from((0, serializeDlof_1.serializeDlof)(pkg.document), "utf8") },
53
+ { name: "meta.json", data: Buffer.from(JSON.stringify(pkg.meta, null, 2), "utf8") },
54
+ ];
55
+ for (const [path, data] of Object.entries(pkg.attachments)) {
56
+ entries.push({ name: path.startsWith("attachments/") ? path : `attachments/${path}`, data });
57
+ }
58
+ return (0, zipWriter_1.writeZip)(entries);
59
+ }
60
+ /** يبني meta.json افتراضياً انطلاقاً من مستند DocumentLoop */
61
+ function buildDefaultMeta(doc) {
62
+ return {
63
+ id: doc.id,
64
+ title: doc.metadata.title,
65
+ domain: doc.metadata.domain,
66
+ version: doc.version,
67
+ author: doc.metadata.author,
68
+ language: doc.metadata.language,
69
+ createdAt: doc.metadata.createdAt ?? new Date().toISOString(),
70
+ dlofpkg_version: "1.0",
71
+ };
72
+ }
@@ -0,0 +1,13 @@
1
+ import { DlofSeries } from "../types";
2
+ export interface PlayerOptions {
3
+ /** الحد الأقصى بالبايت لتضمين ملف وسائط كـ data URL مباشرة داخل الصفحة (افتراضي 20MB) */
4
+ maxInlineMediaBytes?: number;
5
+ title?: string;
6
+ }
7
+ /**
8
+ * مشغّل dlof: يبني تطبيق HTML/JS تفاعلي واحد لسلسلة أو حلقة كاملة من ملفات .dlof.
9
+ * يوفر: قائمة تصفح جانبية، أزرار التالي/السابق (تتبع loopLinks)، تشغيل الوسائط
10
+ * المرجعية (mediaRef / mediaFolder) مع شريط تقدّم قابل للسحب، وحفظ آخر موضع
11
+ * تصفح في التخزين المحلي للمتصفح بين الجلسات.
12
+ */
13
+ export declare function renderPlayerHtml(series: DlofSeries, options?: PlayerOptions): string;