@ztwoint/z-ui 0.1.149 → 0.1.151
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.
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { useRef as
|
|
2
|
-
|
|
1
|
+
import { useRef as l, useState as U, useCallback as g, useEffect as q } from "react";
|
|
2
|
+
import { injectPdfTitle as z } from "./pdf-title-injector.js";
|
|
3
|
+
function G(r) {
|
|
3
4
|
if (typeof r == "string")
|
|
4
5
|
try {
|
|
5
6
|
const n = new URL(r, "http://localhost").pathname.split("/").pop();
|
|
@@ -8,66 +9,66 @@ function W(r) {
|
|
|
8
9
|
}
|
|
9
10
|
return r instanceof File ? r.name : "PDF Preview";
|
|
10
11
|
}
|
|
11
|
-
function
|
|
12
|
-
const { src: a, open: n, onOpenChange:
|
|
13
|
-
|
|
14
|
-
const f =
|
|
15
|
-
f.current =
|
|
16
|
-
const d =
|
|
17
|
-
d.current =
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
const [O, S] = U(!1), [
|
|
12
|
+
function K(r) {
|
|
13
|
+
const { src: a, open: n, onOpenChange: i, onError: w, onLoad: P, downloadFilename: C } = r, u = l(a);
|
|
14
|
+
u.current = a;
|
|
15
|
+
const f = l(P);
|
|
16
|
+
f.current = P;
|
|
17
|
+
const d = l(w);
|
|
18
|
+
d.current = w;
|
|
19
|
+
const L = l(C);
|
|
20
|
+
L.current = C;
|
|
21
|
+
const [O, S] = U(!1), [T, p] = U("idle"), [B, m] = U(null), [D, h] = U(null), b = l(null), v = l(null), y = n !== void 0, F = y ? n : O, o = g(() => {
|
|
21
22
|
b.current && (URL.revokeObjectURL(b.current), b.current = null);
|
|
22
|
-
}, []), R =
|
|
23
|
-
var
|
|
24
|
-
(
|
|
23
|
+
}, []), R = g(async () => {
|
|
24
|
+
var c, E, j;
|
|
25
|
+
(c = v.current) == null || c.abort();
|
|
25
26
|
const e = new AbortController();
|
|
26
|
-
v.current = e, p("loading"),
|
|
27
|
+
v.current = e, p("loading"), h(null), m(null), o();
|
|
27
28
|
try {
|
|
28
29
|
let t;
|
|
29
|
-
if (typeof
|
|
30
|
+
if (typeof u.current == "function" ? t = await u.current(e.signal) : t = u.current, e.signal.aborted) return;
|
|
30
31
|
if (typeof t == "string")
|
|
31
32
|
m(t);
|
|
32
33
|
else {
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
|
|
34
|
+
const s = L.current || (t instanceof File ? t.name : null), W = s ? await z(t, s) : t;
|
|
35
|
+
if (e.signal.aborted) return;
|
|
36
|
+
const k = URL.createObjectURL(W);
|
|
37
|
+
b.current = k, m(k);
|
|
37
38
|
}
|
|
38
39
|
p("ready"), (E = f.current) == null || E.call(f);
|
|
39
40
|
} catch (t) {
|
|
40
41
|
if (e.signal.aborted) return;
|
|
41
|
-
const
|
|
42
|
+
const s = {
|
|
42
43
|
message: t instanceof Error ? t.message : "Failed to load PDF",
|
|
43
44
|
cause: t
|
|
44
45
|
};
|
|
45
|
-
|
|
46
|
+
h(s), p("error"), (j = d.current) == null || j.call(d, s);
|
|
46
47
|
}
|
|
47
|
-
}, [o]),
|
|
48
|
+
}, [o]), I = g(
|
|
48
49
|
(e) => {
|
|
49
|
-
var
|
|
50
|
-
y || S(e),
|
|
50
|
+
var c;
|
|
51
|
+
y || S(e), i == null || i(e), e || ((c = v.current) == null || c.abort(), p("idle"), m(null), h(null), o());
|
|
51
52
|
},
|
|
52
|
-
[y,
|
|
53
|
+
[y, i, o]
|
|
53
54
|
);
|
|
54
|
-
|
|
55
|
+
q(() => (F && R(), () => {
|
|
55
56
|
var e;
|
|
56
57
|
(e = v.current) == null || e.abort(), o();
|
|
57
58
|
}), [F, R, o]);
|
|
58
|
-
const x =
|
|
59
|
+
const x = g(() => {
|
|
59
60
|
R();
|
|
60
|
-
}, [R]), A =
|
|
61
|
+
}, [R]), A = G(a);
|
|
61
62
|
return {
|
|
62
63
|
open: F,
|
|
63
|
-
handleOpenChange:
|
|
64
|
-
status:
|
|
65
|
-
resolvedUrl:
|
|
66
|
-
error:
|
|
64
|
+
handleOpenChange: I,
|
|
65
|
+
status: T,
|
|
66
|
+
resolvedUrl: B,
|
|
67
|
+
error: D,
|
|
67
68
|
retry: x,
|
|
68
69
|
resolvedTitle: A
|
|
69
70
|
};
|
|
70
71
|
}
|
|
71
72
|
export {
|
|
72
|
-
|
|
73
|
+
K as usePdfPreview
|
|
73
74
|
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Injects a /Title into a PDF's metadata via an incremental update.
|
|
3
|
+
*
|
|
4
|
+
* ## Problem
|
|
5
|
+
* When displaying a PDF via a blob URL (`blob:http://.../<uuid>`), Chrome's
|
|
6
|
+
* built-in PDF viewer (PDFium) shows the blob UUID as the document name in
|
|
7
|
+
* the toolbar — e.g. "f48f9554-646a-4...". There is no standard web API to
|
|
8
|
+
* control this; the File constructor name, URL fragments (#filename=), and
|
|
9
|
+
* Content-Disposition headers are all ignored for blob URLs.
|
|
10
|
+
*
|
|
11
|
+
* ## Solution
|
|
12
|
+
* Chrome's PDF viewer reads the `/Title` field from the PDF's Info dictionary
|
|
13
|
+
* and displays it in the toolbar when present. This function appends a new
|
|
14
|
+
* Info dictionary object containing the desired title using a PDF incremental
|
|
15
|
+
* update — the original content is never modified, only ~200 bytes are
|
|
16
|
+
* appended at the end. This keeps it fast even for large PDFs (50MB+ with
|
|
17
|
+
* heavy images), since we never re-parse or re-serialize the PDF body.
|
|
18
|
+
*
|
|
19
|
+
* ## What it covers
|
|
20
|
+
* - Chrome toolbar title: Shows the injected /Title instead of the UUID
|
|
21
|
+
* - Download button: Handled separately by the component via <a download="filename">
|
|
22
|
+
*
|
|
23
|
+
* ## What it does NOT cover
|
|
24
|
+
* - Ctrl+S "Save As" filename: Chrome still derives this from the blob URL,
|
|
25
|
+
* not the PDF metadata. Only a Service Worker + Cache API approach can fix
|
|
26
|
+
* this (see https://stackoverflow.com/q/53548182), which is too complex
|
|
27
|
+
* for this use case.
|
|
28
|
+
*
|
|
29
|
+
* ## Edge cases (falls back silently to original blob — UUID shown)
|
|
30
|
+
* - Password-protected / encrypted PDFs: The appended Info dictionary is
|
|
31
|
+
* unencrypted, which Chrome may ignore. For full encrypted PDF support,
|
|
32
|
+
* use `pdf-lib` (lazy-loaded via `await import('pdf-lib')`) which
|
|
33
|
+
* understands PDF encryption and can set metadata correctly.
|
|
34
|
+
* - PDFs with cross-reference streams (PDF 1.5+): Works if `/Size` appears
|
|
35
|
+
* in plain text within the xref stream object dictionary (most common).
|
|
36
|
+
* May fail on heavily compressed xref streams where `/Size` is not
|
|
37
|
+
* visible as plain text.
|
|
38
|
+
* - Malformed or non-PDF blobs: Regex finds no `startxref` → returns
|
|
39
|
+
* original blob unchanged.
|
|
40
|
+
*
|
|
41
|
+
* ## Performance
|
|
42
|
+
* - Reads the blob once as ArrayBuffer (already in memory from the fetch)
|
|
43
|
+
* - Decodes to string for two regex searches (`/Size`, `startxref`)
|
|
44
|
+
* - Appends ~200 bytes (new object + xref + trailer)
|
|
45
|
+
* - No re-parsing, no re-serialization of the PDF body
|
|
46
|
+
* - Significantly lighter than pdf-lib which parses + rewrites everything
|
|
47
|
+
*/
|
|
48
|
+
export declare function injectPdfTitle(blob: Blob, title: string): Promise<Blob>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
async function O(t, r) {
|
|
2
|
+
try {
|
|
3
|
+
const n = await t.arrayBuffer(), e = new Uint8Array(n), s = new TextDecoder("latin1").decode(e), i = s.lastIndexOf("startxref");
|
|
4
|
+
if (i === -1) return t;
|
|
5
|
+
const y = s.substring(i + 9).trim(), f = parseInt(y, 10);
|
|
6
|
+
if (isNaN(f)) return t;
|
|
7
|
+
const o = s.match(/\/Size\s+(\d+)/g);
|
|
8
|
+
if (!o) return t;
|
|
9
|
+
const d = o[o.length - 1].match(/\d+/);
|
|
10
|
+
if (!d) return t;
|
|
11
|
+
const l = parseInt(d[0], 10), c = l, w = l + 1, $ = F(r), p = e[e.length - 1], x = p !== 10 && p !== 13 ? `
|
|
12
|
+
` : "", S = new TextEncoder(), j = S.encode(x), h = e.length + j.length, g = `${c} 0 obj
|
|
13
|
+
<< /Title ${$} >>
|
|
14
|
+
endobj
|
|
15
|
+
`, z = S.encode(g), B = h + z.length, I = String(h).padStart(10, "0"), T = `xref
|
|
16
|
+
${c} 1
|
|
17
|
+
${I} 00000 n\r
|
|
18
|
+
`, m = `trailer
|
|
19
|
+
<< /Size ${w} /Info ${c} 0 R /Prev ${f} >>
|
|
20
|
+
startxref
|
|
21
|
+
${B}
|
|
22
|
+
%%EOF
|
|
23
|
+
`, E = x + g + T + m, u = new TextEncoder().encode(E), a = new Uint8Array(e.length + u.length);
|
|
24
|
+
return a.set(e), a.set(u, e.length), new Blob([a], { type: "application/pdf" });
|
|
25
|
+
} catch {
|
|
26
|
+
return t;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function F(t) {
|
|
30
|
+
let r = "FEFF";
|
|
31
|
+
for (let n = 0; n < t.length; n++) {
|
|
32
|
+
const e = t.charCodeAt(n);
|
|
33
|
+
r += e.toString(16).padStart(4, "0").toUpperCase();
|
|
34
|
+
}
|
|
35
|
+
return `<${r}>`;
|
|
36
|
+
}
|
|
37
|
+
export {
|
|
38
|
+
O as injectPdfTitle
|
|
39
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Injects a /Title into a PDF's metadata via an incremental update.
|
|
3
|
+
*
|
|
4
|
+
* ## Problem
|
|
5
|
+
* When displaying a PDF via a blob URL (`blob:http://.../<uuid>`), Chrome's
|
|
6
|
+
* built-in PDF viewer (PDFium) shows the blob UUID as the document name in
|
|
7
|
+
* the toolbar — e.g. "f48f9554-646a-4...". There is no standard web API to
|
|
8
|
+
* control this; the File constructor name, URL fragments (#filename=), and
|
|
9
|
+
* Content-Disposition headers are all ignored for blob URLs.
|
|
10
|
+
*
|
|
11
|
+
* ## Solution
|
|
12
|
+
* Chrome's PDF viewer reads the `/Title` field from the PDF's Info dictionary
|
|
13
|
+
* and displays it in the toolbar when present. This function appends a new
|
|
14
|
+
* Info dictionary object containing the desired title using a PDF incremental
|
|
15
|
+
* update — the original content is never modified, only ~200 bytes are
|
|
16
|
+
* appended at the end. This keeps it fast even for large PDFs (50MB+ with
|
|
17
|
+
* heavy images), since we never re-parse or re-serialize the PDF body.
|
|
18
|
+
*
|
|
19
|
+
* ## What it covers
|
|
20
|
+
* - Chrome toolbar title: Shows the injected /Title instead of the UUID
|
|
21
|
+
* - Download button: Handled separately by the component via <a download="filename">
|
|
22
|
+
*
|
|
23
|
+
* ## What it does NOT cover
|
|
24
|
+
* - Ctrl+S "Save As" filename: Chrome still derives this from the blob URL,
|
|
25
|
+
* not the PDF metadata. Only a Service Worker + Cache API approach can fix
|
|
26
|
+
* this (see https://stackoverflow.com/q/53548182), which is too complex
|
|
27
|
+
* for this use case.
|
|
28
|
+
*
|
|
29
|
+
* ## Edge cases (falls back silently to original blob — UUID shown)
|
|
30
|
+
* - Password-protected / encrypted PDFs: The appended Info dictionary is
|
|
31
|
+
* unencrypted, which Chrome may ignore. For full encrypted PDF support,
|
|
32
|
+
* use `pdf-lib` (lazy-loaded via `await import('pdf-lib')`) which
|
|
33
|
+
* understands PDF encryption and can set metadata correctly.
|
|
34
|
+
* - PDFs with cross-reference streams (PDF 1.5+): Works if `/Size` appears
|
|
35
|
+
* in plain text within the xref stream object dictionary (most common).
|
|
36
|
+
* May fail on heavily compressed xref streams where `/Size` is not
|
|
37
|
+
* visible as plain text.
|
|
38
|
+
* - Malformed or non-PDF blobs: Regex finds no `startxref` → returns
|
|
39
|
+
* original blob unchanged.
|
|
40
|
+
*
|
|
41
|
+
* ## Performance
|
|
42
|
+
* - Reads the blob once as ArrayBuffer (already in memory from the fetch)
|
|
43
|
+
* - Decodes to string for two regex searches (`/Size`, `startxref`)
|
|
44
|
+
* - Appends ~200 bytes (new object + xref + trailer)
|
|
45
|
+
* - No re-parsing, no re-serialization of the PDF body
|
|
46
|
+
* - Significantly lighter than pdf-lib which parses + rewrites everything
|
|
47
|
+
*/
|
|
48
|
+
export declare function injectPdfTitle(blob: Blob, title: string): Promise<Blob>;
|