@vcgstudiosy-beep/dlof 1.0.0 → 1.0.2
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 +2 -1
- package/dist/browser.d.ts +11 -0
- package/dist/browser.js +55 -0
- package/dist/dlof.browser.js +189 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +10 -1
- package/dist/player/renderPlayerHtml.d.ts +6 -4
- package/dist/player/renderPlayerHtml.js +169 -33
- package/dist/viewer/brand.d.ts +50 -0
- package/dist/viewer/brand.js +118 -0
- package/dist/viewer/renderContent.js +37 -14
- package/dist/viewer/renderViewerHtml.d.ts +3 -2
- package/dist/viewer/renderViewerHtml.js +18 -5
- package/dist/viewer/theme.d.ts +11 -1
- package/dist/viewer/theme.js +105 -43
- package/dist/zip/zipReaderBrowser.d.ts +12 -0
- package/dist/zip/zipReaderBrowser.js +82 -0
- package/package.json +11 -5
|
@@ -4,10 +4,12 @@ exports.renderViewerHtml = renderViewerHtml;
|
|
|
4
4
|
const theme_1 = require("./theme");
|
|
5
5
|
const renderContent_1 = require("./renderContent");
|
|
6
6
|
const validate_1 = require("../dlof/validate");
|
|
7
|
+
const brand_1 = require("./brand");
|
|
7
8
|
/**
|
|
8
9
|
* عارض dlofpkg / dlof: يبني صفحة HTML مستقلة واحدة (بلا اعتماديات خارجية)
|
|
9
|
-
*
|
|
10
|
-
*
|
|
10
|
+
* بنفس الهوية البصرية لتطبيق DLoF الأصلي (الشعار، الألوان، بطاقات الملفات)،
|
|
11
|
+
* تعرض بيانات meta.json الوصفية، محتوى المستند، مرفقاته كبطاقات ملفات، وتصميمه
|
|
12
|
+
* المخصّص (template) إن وُجد. الصفحة الناتجة قابلة للفتح مباشرة في أي متصفح دون خادم.
|
|
11
13
|
*/
|
|
12
14
|
function renderViewerHtml(input, options = {}) {
|
|
13
15
|
const doc = "document" in input ? input.document : input;
|
|
@@ -16,7 +18,7 @@ function renderViewerHtml(input, options = {}) {
|
|
|
16
18
|
const validation = (0, validate_1.validateDlof)(doc);
|
|
17
19
|
const showValidation = options.showValidation ?? true;
|
|
18
20
|
const issuesHtml = showValidation && validation.issues.length
|
|
19
|
-
? `<div class="card minimal" style="border:1px dashed ${validation.valid ? "
|
|
21
|
+
? `<div class="card minimal" style="border:1px dashed ${validation.valid ? "var(--muted)" : "#c62828"}; margin-top:8px">
|
|
20
22
|
<strong>${validation.valid ? "ملاحظات التحقق" : "أخطاء في الملف"}</strong>
|
|
21
23
|
<ul>${validation.issues
|
|
22
24
|
.map((i) => `<li>[${i.level === "error" ? "خطأ" : "تنبيه"}] ${(0, theme_1.escapeHtml)(i.message)}</li>`)
|
|
@@ -24,8 +26,19 @@ function renderViewerHtml(input, options = {}) {
|
|
|
24
26
|
</div>`
|
|
25
27
|
: "";
|
|
26
28
|
const packageInfo = meta
|
|
27
|
-
? `<div class="meta-row">حزمة dlofpkg
|
|
29
|
+
? `<div class="meta-row"><span class="badge ghost">حزمة dlofpkg</span><span>الإصدار ${(0, theme_1.escapeHtml)(meta.dlofpkg_version)}</span><span>المعرّف: ${(0, theme_1.escapeHtml)(meta.id)}</span></div>`
|
|
28
30
|
: "";
|
|
31
|
+
const header = `<header class="dlof-header">
|
|
32
|
+
${(0, theme_1.loopLogoSvg)(36, "var(--secondary)", "var(--primary)")}
|
|
33
|
+
<div>
|
|
34
|
+
<div class="logo-title">DLoF <span class="accent">Viewer</span></div>
|
|
35
|
+
<div class="logo-sub">صيغة حلقة المستندات المستودعة</div>
|
|
36
|
+
</div>
|
|
37
|
+
<div style="margin-inline-start:auto;display:flex;gap:6px">
|
|
38
|
+
<span class="badge">${(0, theme_1.escapeHtml)((0, brand_1.domainLabel)(doc.metadata.domain))}</span>
|
|
39
|
+
${doc.loopLinks.loopRoot ? `<span class="badge outline">بداية الحلقة</span>` : ""}
|
|
40
|
+
</div>
|
|
41
|
+
</header>`;
|
|
29
42
|
return `<!DOCTYPE html>
|
|
30
43
|
<html lang="${(0, theme_1.escapeHtml)(doc.metadata.language ?? "ar")}" dir="rtl">
|
|
31
44
|
<head>
|
|
@@ -36,7 +49,7 @@ function renderViewerHtml(input, options = {}) {
|
|
|
36
49
|
</head>
|
|
37
50
|
<body>
|
|
38
51
|
<div class="app">
|
|
39
|
-
|
|
52
|
+
${header}
|
|
40
53
|
${packageInfo}
|
|
41
54
|
${(0, renderContent_1.renderDocumentCard)(doc, theme.layout)}
|
|
42
55
|
${issuesHtml}
|
package/dist/viewer/theme.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Template } from "../types";
|
|
2
|
+
import { loopLogoSvg } from "./brand";
|
|
2
3
|
export interface ResolvedTheme {
|
|
3
4
|
primaryColor: string;
|
|
4
5
|
secondaryColor: string;
|
|
@@ -6,8 +7,17 @@ export interface ResolvedTheme {
|
|
|
6
7
|
textColor: string;
|
|
7
8
|
fontFamily: string;
|
|
8
9
|
layout: string;
|
|
10
|
+
/** true إذا حدّد المستند قالباً مخصّصاً صراحةً (يوقف التبديل التلقائي بين الوضع الليلي/النهاري) */
|
|
11
|
+
custom: boolean;
|
|
9
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* يحلّ القالب النهائي: إذا حدّد المستند تصميماً مخصّصاً (template) صراحةً، يُستخدم
|
|
15
|
+
* حرفياً (تماماً كما في تطبيق Android الأصلي عند وجود template.backgroundColor/
|
|
16
|
+
* textColor/primaryColor). بخلاف ذلك تُستخدم هوية DLoF الرسمية (Copper/Moss)
|
|
17
|
+
* كافتراضية، مع دعم تلقائي للوضع الليلي حسب تفضيل نظام المستخدم.
|
|
18
|
+
*/
|
|
10
19
|
export declare function resolveTheme(template?: Template): ResolvedTheme;
|
|
11
|
-
/** يبني كتلة CSS أساسية مشتركة بين العارض والمشغّل،
|
|
20
|
+
/** يبني كتلة CSS أساسية مشتركة بين العارض والمشغّل، بهوية DLoF البصرية الرسمية ودعم RTL كامل */
|
|
12
21
|
export declare function baseCss(theme: ResolvedTheme): string;
|
|
13
22
|
export declare function escapeHtml(input: string): string;
|
|
23
|
+
export { loopLogoSvg };
|
package/dist/viewer/theme.js
CHANGED
|
@@ -1,44 +1,67 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loopLogoSvg = void 0;
|
|
3
4
|
exports.resolveTheme = resolveTheme;
|
|
4
5
|
exports.baseCss = baseCss;
|
|
5
6
|
exports.escapeHtml = escapeHtml;
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
secondaryColor: "#1E88E5",
|
|
9
|
-
backgroundColor: "#FFFFFF",
|
|
10
|
-
textColor: "#212121",
|
|
11
|
-
fontFamily: "sans-serif",
|
|
12
|
-
layout: "standard",
|
|
13
|
-
};
|
|
7
|
+
const brand_1 = require("./brand");
|
|
8
|
+
Object.defineProperty(exports, "loopLogoSvg", { enumerable: true, get: function () { return brand_1.loopLogoSvg; } });
|
|
14
9
|
const FONT_STACKS = {
|
|
15
10
|
serif: "'Amiri', 'Noto Naskh Arabic', Georgia, serif",
|
|
16
11
|
"sans-serif": "'Tajawal', 'IBM Plex Sans Arabic', 'Segoe UI', sans-serif",
|
|
17
12
|
monospace: "'Cascadia Code', 'Courier New', monospace",
|
|
18
13
|
cursive: "'Aref Ruqaa', cursive",
|
|
19
14
|
};
|
|
15
|
+
/**
|
|
16
|
+
* يحلّ القالب النهائي: إذا حدّد المستند تصميماً مخصّصاً (template) صراحةً، يُستخدم
|
|
17
|
+
* حرفياً (تماماً كما في تطبيق Android الأصلي عند وجود template.backgroundColor/
|
|
18
|
+
* textColor/primaryColor). بخلاف ذلك تُستخدم هوية DLoF الرسمية (Copper/Moss)
|
|
19
|
+
* كافتراضية، مع دعم تلقائي للوضع الليلي حسب تفضيل نظام المستخدم.
|
|
20
|
+
*/
|
|
20
21
|
function resolveTheme(template) {
|
|
21
22
|
const t = template ?? {};
|
|
23
|
+
const custom = Boolean(t.primaryColor || t.secondaryColor || t.backgroundColor || t.textColor);
|
|
22
24
|
const fontFamily = t.fontFamily ? FONT_STACKS[t.fontFamily] ?? t.fontFamily : FONT_STACKS["sans-serif"];
|
|
23
25
|
return {
|
|
24
|
-
primaryColor: t.primaryColor ??
|
|
25
|
-
secondaryColor: t.secondaryColor ??
|
|
26
|
-
backgroundColor: t.backgroundColor ??
|
|
27
|
-
textColor: t.textColor ??
|
|
26
|
+
primaryColor: t.primaryColor ?? brand_1.BRAND_LIGHT.primary,
|
|
27
|
+
secondaryColor: t.secondaryColor ?? brand_1.BRAND_LIGHT.secondary,
|
|
28
|
+
backgroundColor: t.backgroundColor ?? brand_1.BRAND_LIGHT.background,
|
|
29
|
+
textColor: t.textColor ?? brand_1.BRAND_LIGHT.text,
|
|
28
30
|
fontFamily,
|
|
29
|
-
layout: t.layout ??
|
|
31
|
+
layout: t.layout ?? "standard",
|
|
32
|
+
custom,
|
|
30
33
|
};
|
|
31
34
|
}
|
|
32
|
-
/** يبني كتلة CSS أساسية مشتركة بين العارض والمشغّل،
|
|
35
|
+
/** يبني كتلة CSS أساسية مشتركة بين العارض والمشغّل، بهوية DLoF البصرية الرسمية ودعم RTL كامل */
|
|
33
36
|
function baseCss(theme) {
|
|
37
|
+
const darkOverride = theme.custom
|
|
38
|
+
? ""
|
|
39
|
+
: `
|
|
40
|
+
@media (prefers-color-scheme: dark) {
|
|
41
|
+
:root {
|
|
42
|
+
--primary: ${brand_1.BRAND_DARK.primary};
|
|
43
|
+
--secondary: ${brand_1.BRAND_DARK.secondary};
|
|
44
|
+
--bg: ${brand_1.BRAND_DARK.background};
|
|
45
|
+
--surface: ${brand_1.BRAND_DARK.surface};
|
|
46
|
+
--surface-variant: ${brand_1.BRAND_DARK.surfaceVariant};
|
|
47
|
+
--text: ${brand_1.BRAND_DARK.text};
|
|
48
|
+
--muted: ${brand_1.BRAND_DARK.muted};
|
|
49
|
+
--border: ${brand_1.BRAND_DARK.border};
|
|
50
|
+
}
|
|
51
|
+
}`;
|
|
34
52
|
return `
|
|
35
53
|
:root {
|
|
36
54
|
--primary: ${theme.primaryColor};
|
|
37
55
|
--secondary: ${theme.secondaryColor};
|
|
38
56
|
--bg: ${theme.backgroundColor};
|
|
57
|
+
--surface: color-mix(in srgb, ${theme.backgroundColor} 92%, #fff 8%);
|
|
58
|
+
--surface-variant: color-mix(in srgb, ${theme.backgroundColor} 85%, #000 6%);
|
|
39
59
|
--text: ${theme.textColor};
|
|
60
|
+
--muted: color-mix(in srgb, ${theme.textColor} 60%, transparent);
|
|
61
|
+
--border: color-mix(in srgb, ${theme.textColor} 90%, transparent);
|
|
40
62
|
--font: ${theme.fontFamily};
|
|
41
63
|
}
|
|
64
|
+
${darkOverride}
|
|
42
65
|
* { box-sizing: border-box; }
|
|
43
66
|
html, body {
|
|
44
67
|
margin: 0; padding: 0;
|
|
@@ -48,49 +71,88 @@ function baseCss(theme) {
|
|
|
48
71
|
direction: rtl;
|
|
49
72
|
}
|
|
50
73
|
body { min-height: 100vh; }
|
|
51
|
-
a { color: var(--
|
|
52
|
-
.app {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
74
|
+
a { color: var(--primary); }
|
|
75
|
+
.app { max-width: 900px; margin: 0 auto; padding: 0 20px 64px; }
|
|
76
|
+
|
|
77
|
+
/* ── الترويسة: شعار الحلقة + العنوان ── */
|
|
78
|
+
.dlof-header {
|
|
79
|
+
display: flex; align-items: center; gap: 14px;
|
|
80
|
+
padding: 18px 4px 14px;
|
|
81
|
+
border-bottom: 1px solid var(--border);
|
|
82
|
+
margin-bottom: 20px;
|
|
56
83
|
}
|
|
84
|
+
.dlof-header .logo-title { font-size: 1.15rem; font-weight: 800; letter-spacing: .01em; }
|
|
85
|
+
.dlof-header .logo-title .accent { color: var(--secondary); }
|
|
86
|
+
.dlof-header .logo-sub { font-size: .78rem; color: var(--muted); margin-top: 2px; }
|
|
87
|
+
|
|
57
88
|
.badge {
|
|
58
|
-
display: inline-
|
|
59
|
-
background: var(--primary);
|
|
60
|
-
|
|
61
|
-
border-radius: 999px;
|
|
62
|
-
padding: 2px 12px;
|
|
63
|
-
font-size: 12px;
|
|
89
|
+
display: inline-flex; align-items: center; gap: 4px;
|
|
90
|
+
background: var(--primary); color: #fff;
|
|
91
|
+
border-radius: 999px; padding: 3px 12px; font-size: .72rem; font-weight: 600;
|
|
64
92
|
margin-inline-end: 6px;
|
|
65
93
|
}
|
|
94
|
+
.badge.outline {
|
|
95
|
+
background: transparent; color: var(--secondary);
|
|
96
|
+
border: 1.4px solid var(--secondary);
|
|
97
|
+
}
|
|
98
|
+
.badge.ghost { background: var(--surface-variant); color: var(--text); font-weight: 500; }
|
|
99
|
+
|
|
66
100
|
.card {
|
|
67
|
-
background:
|
|
101
|
+
background: var(--surface);
|
|
102
|
+
border: 1px solid var(--border);
|
|
68
103
|
border-radius: 16px;
|
|
69
104
|
padding: 20px 22px;
|
|
70
|
-
box-shadow: 0 1px 3px rgba(0,0,0,.
|
|
105
|
+
box-shadow: 0 1px 3px rgba(0,0,0,.06);
|
|
71
106
|
margin-bottom: 18px;
|
|
72
107
|
}
|
|
73
108
|
.card.magazine { border-inline-start: 6px solid var(--primary); }
|
|
74
|
-
.card.minimal { box-shadow: none; background: transparent; padding: 8px 0; }
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
109
|
+
.card.minimal { box-shadow: none; background: transparent; padding: 8px 0; border: none; }
|
|
110
|
+
|
|
111
|
+
h1 { font-size: 1.55rem; margin: .25em 0; }
|
|
112
|
+
h2 { font-size: 1.15rem; color: var(--primary); }
|
|
113
|
+
.meta-row {
|
|
114
|
+
display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
|
|
115
|
+
font-size: .82rem; color: var(--muted); margin-bottom: 12px;
|
|
116
|
+
}
|
|
117
|
+
.tag { font-size: .8rem; color: var(--muted); margin-inline-end: 8px; }
|
|
118
|
+
.body-text { white-space: pre-wrap; line-height: 1.9; }
|
|
119
|
+
|
|
120
|
+
/* ── قائمة "الملفات" — بطاقات مطابقة لشاشة ملفاتي في تطبيق Android ── */
|
|
121
|
+
.files-section-title {
|
|
122
|
+
font-size: .78rem; font-weight: 700; color: var(--muted);
|
|
123
|
+
letter-spacing: .04em; margin: 22px 2px 10px;
|
|
84
124
|
}
|
|
125
|
+
.file-card {
|
|
126
|
+
display: flex; align-items: center; gap: 12px;
|
|
127
|
+
background: var(--surface); border: 1px solid var(--border);
|
|
128
|
+
border-radius: 14px; padding: 12px 14px; margin-bottom: 8px;
|
|
129
|
+
text-decoration: none; color: inherit;
|
|
130
|
+
}
|
|
131
|
+
.file-card .file-icon {
|
|
132
|
+
flex-shrink: 0; width: 42px; height: 42px; border-radius: 10px;
|
|
133
|
+
display: flex; align-items: center; justify-content: center; font-size: 1.25rem;
|
|
134
|
+
}
|
|
135
|
+
.file-card .file-info { flex: 1; min-width: 0; }
|
|
136
|
+
.file-card .file-name {
|
|
137
|
+
font-weight: 600; font-size: .95rem;
|
|
138
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
139
|
+
}
|
|
140
|
+
.file-card .file-meta { font-size: .76rem; color: var(--muted); margin-top: 2px; display: flex; gap: 8px; }
|
|
141
|
+
.attachment-preview { margin: 10px 0 18px; }
|
|
142
|
+
.attachment-preview img { max-width: 100%; border-radius: 12px; }
|
|
143
|
+
.attachment-preview video, .attachment-preview audio { width: 100%; border-radius: 12px; }
|
|
144
|
+
.attachment-caption { font-size: .78rem; color: var(--muted); margin-top: 4px; }
|
|
145
|
+
|
|
146
|
+
.footer-nav { display: flex; justify-content: space-between; gap: 12px; margin-top: 24px; }
|
|
85
147
|
.nav-btn {
|
|
86
|
-
flex: 1; text-align: center; padding:
|
|
148
|
+
flex: 1; text-align: center; padding: 13px 16px;
|
|
87
149
|
background: var(--primary); color: #fff; border-radius: 12px;
|
|
88
|
-
text-decoration: none; font-weight:
|
|
89
|
-
font-family: var(--font); font-size:
|
|
150
|
+
text-decoration: none; font-weight: 700; cursor: pointer; border: none;
|
|
151
|
+
font-family: var(--font); font-size: .95rem;
|
|
152
|
+
display: flex; align-items: center; justify-content: center; gap: 6px;
|
|
90
153
|
}
|
|
91
|
-
.nav-btn.secondary { background: var(--secondary); }
|
|
92
|
-
.nav-btn:disabled { opacity: .
|
|
93
|
-
.body-text { white-space: pre-wrap; line-height: 1.9; }
|
|
154
|
+
.nav-btn.secondary { background: var(--secondary); color: var(--text); }
|
|
155
|
+
.nav-btn:disabled { opacity: .35; cursor: not-allowed; }
|
|
94
156
|
`;
|
|
95
157
|
}
|
|
96
158
|
function escapeHtml(input) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface ZipEntry {
|
|
2
|
+
name: string;
|
|
3
|
+
data: Uint8Array;
|
|
4
|
+
isDirectory: boolean;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* نسخة متوافقة مع المتصفح من readZip (تعمل بنفس منطق src/zip/zipReader.ts)
|
|
8
|
+
* لكنها تستخدم fflate بدل وحدة zlib الخاصة بـ Node، وتتعامل مع Uint8Array
|
|
9
|
+
* بدل Buffer حتى تعمل داخل المتصفح مباشرة بدون أي polyfill لـ Node.
|
|
10
|
+
*/
|
|
11
|
+
export declare function readZipBrowser(buf: Uint8Array): ZipEntry[];
|
|
12
|
+
export declare function findEntry(entries: ZipEntry[], name: string): ZipEntry | undefined;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readZipBrowser = readZipBrowser;
|
|
4
|
+
exports.findEntry = findEntry;
|
|
5
|
+
const fflate_1 = require("fflate");
|
|
6
|
+
const EOCD_SIG = 0x06054b50;
|
|
7
|
+
const CDH_SIG = 0x02014b50;
|
|
8
|
+
function readUInt32LE(buf, offset) {
|
|
9
|
+
return ((buf[offset] |
|
|
10
|
+
(buf[offset + 1] << 8) |
|
|
11
|
+
(buf[offset + 2] << 16) |
|
|
12
|
+
(buf[offset + 3] << 24)) >>>
|
|
13
|
+
0);
|
|
14
|
+
}
|
|
15
|
+
function readUInt16LE(buf, offset) {
|
|
16
|
+
return buf[offset] | (buf[offset + 1] << 8);
|
|
17
|
+
}
|
|
18
|
+
function toUtf8(buf, start, end) {
|
|
19
|
+
return new TextDecoder("utf-8").decode(buf.subarray(start, end));
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* نسخة متوافقة مع المتصفح من readZip (تعمل بنفس منطق src/zip/zipReader.ts)
|
|
23
|
+
* لكنها تستخدم fflate بدل وحدة zlib الخاصة بـ Node، وتتعامل مع Uint8Array
|
|
24
|
+
* بدل Buffer حتى تعمل داخل المتصفح مباشرة بدون أي polyfill لـ Node.
|
|
25
|
+
*/
|
|
26
|
+
function readZipBrowser(buf) {
|
|
27
|
+
let eocdPos = -1;
|
|
28
|
+
const minEocd = 22;
|
|
29
|
+
for (let i = buf.length - minEocd; i >= 0 && i >= buf.length - minEocd - 65557; i--) {
|
|
30
|
+
if (readUInt32LE(buf, i) === EOCD_SIG) {
|
|
31
|
+
eocdPos = i;
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (eocdPos === -1) {
|
|
36
|
+
throw new Error("ملف ZIP غير صالح: لم يُعثر على EOCD (نهاية الفهرس المركزي)");
|
|
37
|
+
}
|
|
38
|
+
const totalEntries = readUInt16LE(buf, eocdPos + 10);
|
|
39
|
+
const cdOffset = readUInt32LE(buf, eocdPos + 16);
|
|
40
|
+
const entries = [];
|
|
41
|
+
let pos = cdOffset;
|
|
42
|
+
for (let i = 0; i < totalEntries; i++) {
|
|
43
|
+
if (readUInt32LE(buf, pos) !== CDH_SIG) {
|
|
44
|
+
throw new Error(`رأس الفهرس المركزي غير صالح عند الموضع ${pos}`);
|
|
45
|
+
}
|
|
46
|
+
const method = readUInt16LE(buf, pos + 10);
|
|
47
|
+
const compSize = readUInt32LE(buf, pos + 20);
|
|
48
|
+
const uncompSize = readUInt32LE(buf, pos + 24);
|
|
49
|
+
const nameLen = readUInt16LE(buf, pos + 28);
|
|
50
|
+
const extraLen = readUInt16LE(buf, pos + 30);
|
|
51
|
+
const commentLen = readUInt16LE(buf, pos + 32);
|
|
52
|
+
const localHeaderOffset = readUInt32LE(buf, pos + 42);
|
|
53
|
+
const name = toUtf8(buf, pos + 46, pos + 46 + nameLen);
|
|
54
|
+
entries.push(readLocalEntry(buf, localHeaderOffset, name, method, compSize, uncompSize));
|
|
55
|
+
pos += 46 + nameLen + extraLen + commentLen;
|
|
56
|
+
}
|
|
57
|
+
return entries;
|
|
58
|
+
}
|
|
59
|
+
function readLocalEntry(buf, offset, name, method, compSize, uncompSize) {
|
|
60
|
+
const nameLen = readUInt16LE(buf, offset + 26);
|
|
61
|
+
const extraLen = readUInt16LE(buf, offset + 28);
|
|
62
|
+
const dataStart = offset + 30 + nameLen + extraLen;
|
|
63
|
+
const raw = buf.subarray(dataStart, dataStart + compSize);
|
|
64
|
+
let data;
|
|
65
|
+
if (method === 0) {
|
|
66
|
+
data = raw;
|
|
67
|
+
}
|
|
68
|
+
else if (method === 8) {
|
|
69
|
+
data = (0, fflate_1.inflateSync)(raw, { size: uncompSize });
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
throw new Error(`أسلوب ضغط غير مدعوم: ${method}`);
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
name,
|
|
76
|
+
data,
|
|
77
|
+
isDirectory: name.endsWith("/"),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function findEntry(entries, name) {
|
|
81
|
+
return entries.find((e) => e.name === name || e.name === `/${name}`);
|
|
82
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vcgstudiosy-beep/dlof",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "مكتبة وأداة سطر أوامر رسمية لصيغة DLoF (Document Loop Format): تحليل وإنشاء ملفات .dlof، حزم .dlofpkg و .dlofSeries، عارض HTML، ومشغّل حلقات تفاعلي.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dlof",
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
"type": "commonjs",
|
|
18
18
|
"main": "dist/index.js",
|
|
19
19
|
"types": "dist/index.d.ts",
|
|
20
|
+
"unpkg": "dist/dlof.browser.js",
|
|
21
|
+
"jsdelivr": "dist/dlof.browser.js",
|
|
20
22
|
"bin": {
|
|
21
23
|
"dlof": "bin/dlof.js"
|
|
22
24
|
},
|
|
@@ -31,12 +33,16 @@
|
|
|
31
33
|
},
|
|
32
34
|
"scripts": {
|
|
33
35
|
"build": "tsc -p tsconfig.json",
|
|
34
|
-
"
|
|
36
|
+
"build:browser": "esbuild src/browser.ts --bundle --platform=browser --format=iife --global-name=DLOF --outfile=dist/dlof.browser.js --target=es2020 --minify",
|
|
37
|
+
"prepublishOnly": "npm run build && npm run build:browser",
|
|
35
38
|
"test": "node dist/test/run.js"
|
|
36
39
|
},
|
|
37
|
-
"dependencies": {
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"fflate": "^0.8.3"
|
|
42
|
+
},
|
|
38
43
|
"devDependencies": {
|
|
39
|
-
"
|
|
40
|
-
"
|
|
44
|
+
"@types/node": "^20.0.0",
|
|
45
|
+
"esbuild": "^0.28.1",
|
|
46
|
+
"typescript": "^5.4.0"
|
|
41
47
|
}
|
|
42
48
|
}
|