@relevaince/document-viewer 0.1.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.
@@ -0,0 +1,75 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ /**
4
+ * Bounding box in normalised coordinates (0–1 range),
5
+ * origin at top-left of the page.
6
+ */
7
+ type BBox = {
8
+ x1: number;
9
+ y1: number;
10
+ x2: number;
11
+ y2: number;
12
+ };
13
+ /**
14
+ * Describes *what* to show and optionally *where* to focus.
15
+ *
16
+ * Consumers pass a `ViewerTarget` to `<DocumentViewer>` each time
17
+ * the user clicks a citation.
18
+ */
19
+ type ViewerTarget = {
20
+ /** Public URL of the document (PDF / DOCX). */
21
+ url: string;
22
+ /** Explicit file type — auto-detected from URL extension when omitted. */
23
+ fileType?: "pdf" | "docx";
24
+ /** 1-indexed page to scroll to. */
25
+ page?: number;
26
+ /** Bounding box to highlight (normalised 0–1 coords). */
27
+ bbox?: BBox;
28
+ /** Cited text snippet — for future text-search highlighting. */
29
+ text?: string;
30
+ /** Display title shown in the viewer header. */
31
+ title?: string;
32
+ };
33
+
34
+ type DocumentViewerProps = {
35
+ /** The document + focus target. Pass `null` to hide the viewer. */
36
+ target: ViewerTarget | null;
37
+ /** Override the default pdfjs web-worker URL. */
38
+ pdfWorkerUrl?: string;
39
+ /** Custom loading indicator. */
40
+ loadingComponent?: React.ReactNode;
41
+ /** Custom error state. */
42
+ errorComponent?: React.ReactNode;
43
+ /** Fires when the document begins loading. */
44
+ onLoadStart?: () => void;
45
+ /** Fires when the document has finished loading. */
46
+ onLoadSuccess?: () => void;
47
+ /** Fires when the document fails to load. */
48
+ onLoadError?: (err: Error) => void;
49
+ /** Optional className for the outermost wrapper. */
50
+ className?: string;
51
+ };
52
+
53
+ /**
54
+ * Top-level component that routes to the correct renderer
55
+ * based on the target's file type.
56
+ *
57
+ * ```tsx
58
+ * <DocumentViewer
59
+ * target={{
60
+ * url: "https://example.com/report.pdf",
61
+ * page: 3,
62
+ * bbox: { x1: 0.1, y1: 0.2, x2: 0.5, y2: 0.3 },
63
+ * }}
64
+ * />
65
+ * ```
66
+ */
67
+ declare function DocumentViewer({ target, pdfWorkerUrl, loadingComponent, errorComponent, onLoadStart, onLoadSuccess, onLoadError, className, }: DocumentViewerProps): react_jsx_runtime.JSX.Element | null;
68
+
69
+ /**
70
+ * Infer document type from URL extension.
71
+ * Strips query-strings and fragments before matching.
72
+ */
73
+ declare function getFileType(url: string): "pdf" | "docx" | "unknown";
74
+
75
+ export { type BBox, DocumentViewer, type DocumentViewerProps, type ViewerTarget, getFileType };
@@ -0,0 +1,75 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ /**
4
+ * Bounding box in normalised coordinates (0–1 range),
5
+ * origin at top-left of the page.
6
+ */
7
+ type BBox = {
8
+ x1: number;
9
+ y1: number;
10
+ x2: number;
11
+ y2: number;
12
+ };
13
+ /**
14
+ * Describes *what* to show and optionally *where* to focus.
15
+ *
16
+ * Consumers pass a `ViewerTarget` to `<DocumentViewer>` each time
17
+ * the user clicks a citation.
18
+ */
19
+ type ViewerTarget = {
20
+ /** Public URL of the document (PDF / DOCX). */
21
+ url: string;
22
+ /** Explicit file type — auto-detected from URL extension when omitted. */
23
+ fileType?: "pdf" | "docx";
24
+ /** 1-indexed page to scroll to. */
25
+ page?: number;
26
+ /** Bounding box to highlight (normalised 0–1 coords). */
27
+ bbox?: BBox;
28
+ /** Cited text snippet — for future text-search highlighting. */
29
+ text?: string;
30
+ /** Display title shown in the viewer header. */
31
+ title?: string;
32
+ };
33
+
34
+ type DocumentViewerProps = {
35
+ /** The document + focus target. Pass `null` to hide the viewer. */
36
+ target: ViewerTarget | null;
37
+ /** Override the default pdfjs web-worker URL. */
38
+ pdfWorkerUrl?: string;
39
+ /** Custom loading indicator. */
40
+ loadingComponent?: React.ReactNode;
41
+ /** Custom error state. */
42
+ errorComponent?: React.ReactNode;
43
+ /** Fires when the document begins loading. */
44
+ onLoadStart?: () => void;
45
+ /** Fires when the document has finished loading. */
46
+ onLoadSuccess?: () => void;
47
+ /** Fires when the document fails to load. */
48
+ onLoadError?: (err: Error) => void;
49
+ /** Optional className for the outermost wrapper. */
50
+ className?: string;
51
+ };
52
+
53
+ /**
54
+ * Top-level component that routes to the correct renderer
55
+ * based on the target's file type.
56
+ *
57
+ * ```tsx
58
+ * <DocumentViewer
59
+ * target={{
60
+ * url: "https://example.com/report.pdf",
61
+ * page: 3,
62
+ * bbox: { x1: 0.1, y1: 0.2, x2: 0.5, y2: 0.3 },
63
+ * }}
64
+ * />
65
+ * ```
66
+ */
67
+ declare function DocumentViewer({ target, pdfWorkerUrl, loadingComponent, errorComponent, onLoadStart, onLoadSuccess, onLoadError, className, }: DocumentViewerProps): react_jsx_runtime.JSX.Element | null;
68
+
69
+ /**
70
+ * Infer document type from URL extension.
71
+ * Strips query-strings and fragments before matching.
72
+ */
73
+ declare function getFileType(url: string): "pdf" | "docx" | "unknown";
74
+
75
+ export { type BBox, DocumentViewer, type DocumentViewerProps, type ViewerTarget, getFileType };
package/dist/index.js ADDED
@@ -0,0 +1,248 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ DocumentViewer: () => DocumentViewer,
24
+ getFileType: () => getFileType
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+
28
+ // src/utils/getFileType.ts
29
+ function getFileType(url) {
30
+ const clean = url.split("?")[0].split("#")[0].toLowerCase();
31
+ if (clean.endsWith(".pdf")) return "pdf";
32
+ if (clean.endsWith(".docx") || clean.endsWith(".doc")) return "docx";
33
+ return "unknown";
34
+ }
35
+
36
+ // src/components/renderers/PdfRenderer.tsx
37
+ var import_react = require("react");
38
+ var import_react_pdf_highlighter_extended = require("react-pdf-highlighter-extended");
39
+ var import_jsx_runtime = require("react/jsx-runtime");
40
+ var CITATION_ID = "citation-highlight";
41
+ var DEFAULT_WORKER_URL = "https://unpkg.com/pdfjs-dist@4.4.168/build/pdf.worker.min.mjs";
42
+ function bboxToHighlight(target) {
43
+ if (!target.bbox) return null;
44
+ const page = target.page ?? 1;
45
+ const { x1, y1, x2, y2 } = target.bbox;
46
+ const scaledRect = {
47
+ x1,
48
+ y1,
49
+ x2,
50
+ y2,
51
+ width: 1,
52
+ height: 1,
53
+ pageNumber: page
54
+ };
55
+ const position = {
56
+ boundingRect: scaledRect,
57
+ rects: [scaledRect]
58
+ };
59
+ return {
60
+ id: CITATION_ID,
61
+ type: "area",
62
+ position
63
+ };
64
+ }
65
+ function CitationHighlightRenderer() {
66
+ const { highlight, isScrolledTo } = (0, import_react_pdf_highlighter_extended.useHighlightContainerContext)();
67
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
68
+ import_react_pdf_highlighter_extended.AreaHighlight,
69
+ {
70
+ highlight,
71
+ isScrolledTo,
72
+ style: {
73
+ background: isScrolledTo ? "rgba(255, 171, 0, 0.35)" : "rgba(255, 171, 0, 0.2)",
74
+ border: "1.5px solid rgba(255, 171, 0, 0.6)",
75
+ borderRadius: 2,
76
+ transition: "background 200ms ease"
77
+ }
78
+ }
79
+ );
80
+ }
81
+ function PdfRenderer({
82
+ target,
83
+ workerUrl,
84
+ loadingComponent,
85
+ errorComponent,
86
+ onLoadSuccess,
87
+ onLoadError
88
+ }) {
89
+ const highlighterRef = (0, import_react.useRef)(null);
90
+ const loadNotifiedRef = (0, import_react.useRef)(false);
91
+ const highlights = (0, import_react.useMemo)(() => {
92
+ const h = bboxToHighlight(target);
93
+ return h ? [h] : [];
94
+ }, [target]);
95
+ (0, import_react.useEffect)(() => {
96
+ if (highlights.length === 0) return;
97
+ const timer = setTimeout(() => {
98
+ highlighterRef.current?.scrollToHighlight(highlights[0]);
99
+ }, 400);
100
+ return () => clearTimeout(timer);
101
+ }, [highlights]);
102
+ const handleUtilsRef = (0, import_react.useCallback)((utils) => {
103
+ highlighterRef.current = utils;
104
+ }, []);
105
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { width: "100%", height: "100%", position: "relative" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
106
+ import_react_pdf_highlighter_extended.PdfLoader,
107
+ {
108
+ document: target.url,
109
+ workerSrc: workerUrl ?? DEFAULT_WORKER_URL,
110
+ beforeLoad: () => loadingComponent ?? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
111
+ "div",
112
+ {
113
+ style: {
114
+ display: "flex",
115
+ alignItems: "center",
116
+ justifyContent: "center",
117
+ width: "100%",
118
+ height: "100%",
119
+ color: "#888",
120
+ fontSize: 14,
121
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
122
+ },
123
+ children: "Loading PDF\u2026"
124
+ }
125
+ ),
126
+ errorMessage: (error) => {
127
+ onLoadError?.(error);
128
+ return errorComponent ?? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
129
+ "div",
130
+ {
131
+ style: {
132
+ display: "flex",
133
+ alignItems: "center",
134
+ justifyContent: "center",
135
+ width: "100%",
136
+ height: "100%",
137
+ color: "#ef4444",
138
+ fontSize: 14,
139
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
140
+ },
141
+ children: "Failed to load PDF"
142
+ }
143
+ );
144
+ },
145
+ children: (pdfDocument) => {
146
+ if (!loadNotifiedRef.current) {
147
+ loadNotifiedRef.current = true;
148
+ onLoadSuccess?.();
149
+ }
150
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
151
+ import_react_pdf_highlighter_extended.PdfHighlighter,
152
+ {
153
+ pdfDocument,
154
+ highlights,
155
+ utilsRef: handleUtilsRef,
156
+ style: {
157
+ width: "100%",
158
+ height: "100%"
159
+ },
160
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CitationHighlightRenderer, {})
161
+ }
162
+ );
163
+ }
164
+ }
165
+ ) });
166
+ }
167
+
168
+ // src/components/renderers/DocxRenderer.tsx
169
+ var import_jsx_runtime2 = require("react/jsx-runtime");
170
+ function DocxRenderer({ target }) {
171
+ const officeUrl = `https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(
172
+ target.url
173
+ )}`;
174
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
175
+ "iframe",
176
+ {
177
+ src: officeUrl,
178
+ title: target.title ?? "Document preview",
179
+ style: {
180
+ width: "100%",
181
+ height: "100%",
182
+ border: "none",
183
+ borderRadius: 6,
184
+ background: "#fff"
185
+ }
186
+ }
187
+ );
188
+ }
189
+
190
+ // src/components/DocumentViewer.tsx
191
+ var import_jsx_runtime3 = require("react/jsx-runtime");
192
+ function DocumentViewer({
193
+ target,
194
+ pdfWorkerUrl,
195
+ loadingComponent,
196
+ errorComponent,
197
+ onLoadStart,
198
+ onLoadSuccess,
199
+ onLoadError,
200
+ className
201
+ }) {
202
+ if (!target) return null;
203
+ const type = target.fileType ?? getFileType(target.url);
204
+ const wrapperStyle = {
205
+ width: "100%",
206
+ height: "100%",
207
+ position: "relative",
208
+ overflow: "hidden",
209
+ borderRadius: 8,
210
+ background: "#fafafa"
211
+ };
212
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className, style: wrapperStyle, children: [
213
+ type === "pdf" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
214
+ PdfRenderer,
215
+ {
216
+ target,
217
+ workerUrl: pdfWorkerUrl,
218
+ loadingComponent,
219
+ errorComponent,
220
+ onLoadSuccess,
221
+ onLoadError
222
+ }
223
+ ),
224
+ type === "docx" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DocxRenderer, { target }),
225
+ type === "unknown" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
226
+ "div",
227
+ {
228
+ style: {
229
+ display: "flex",
230
+ alignItems: "center",
231
+ justifyContent: "center",
232
+ width: "100%",
233
+ height: "100%",
234
+ color: "#888",
235
+ fontSize: 14,
236
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
237
+ },
238
+ children: "Unsupported file type"
239
+ }
240
+ )
241
+ ] });
242
+ }
243
+ // Annotate the CommonJS export names for ESM import in node:
244
+ 0 && (module.exports = {
245
+ DocumentViewer,
246
+ getFileType
247
+ });
248
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/utils/getFileType.ts","../src/components/renderers/PdfRenderer.tsx","../src/components/renderers/DocxRenderer.tsx","../src/components/DocumentViewer.tsx"],"sourcesContent":["/* ------------------------------------------------------------------ */\n/* Components */\n/* ------------------------------------------------------------------ */\nexport { DocumentViewer } from \"./components/DocumentViewer\";\n\n/* ------------------------------------------------------------------ */\n/* Types */\n/* ------------------------------------------------------------------ */\nexport type { ViewerTarget, BBox } from \"./types/ViewerTarget\";\nexport type { DocumentViewerProps } from \"./types/DocumentViewerProps\";\n\n/* ------------------------------------------------------------------ */\n/* Utilities */\n/* ------------------------------------------------------------------ */\nexport { getFileType } from \"./utils/getFileType\";\n","/**\n * Infer document type from URL extension.\n * Strips query-strings and fragments before matching.\n */\nexport function getFileType(url: string): \"pdf\" | \"docx\" | \"unknown\" {\n const clean = url.split(\"?\")[0].split(\"#\")[0].toLowerCase();\n\n if (clean.endsWith(\".pdf\")) return \"pdf\";\n if (clean.endsWith(\".docx\") || clean.endsWith(\".doc\")) return \"docx\";\n\n return \"unknown\";\n}\n","import React, { useRef, useEffect, useMemo, useCallback } from \"react\";\nimport {\n PdfLoader,\n PdfHighlighter,\n AreaHighlight,\n useHighlightContainerContext,\n} from \"react-pdf-highlighter-extended\";\nimport type {\n Highlight,\n ScaledPosition,\n PdfHighlighterUtils,\n} from \"react-pdf-highlighter-extended\";\nimport type { ViewerTarget } from \"../../types/ViewerTarget\";\n\n/* ------------------------------------------------------------------ */\n/* Constants */\n/* ------------------------------------------------------------------ */\n\nconst CITATION_ID = \"citation-highlight\";\n\nconst DEFAULT_WORKER_URL =\n \"https://unpkg.com/pdfjs-dist@4.4.168/build/pdf.worker.min.mjs\";\n\n/* ------------------------------------------------------------------ */\n/* BBox → Highlight conversion */\n/* ------------------------------------------------------------------ */\n\n/**\n * Convert our normalised (0-1) BBox into the Scaled coordinate system\n * used by react-pdf-highlighter-extended.\n *\n * The library internally computes:\n * viewportX = (viewportWidth × scaled.x1) / scaled.width\n *\n * So `scaled.x1 / scaled.width` must equal the normalised value.\n * We simply store the 0-1 value directly and set width / height to 1.\n */\nfunction bboxToHighlight(target: ViewerTarget): Highlight | null {\n if (!target.bbox) return null;\n\n const page = target.page ?? 1;\n const { x1, y1, x2, y2 } = target.bbox;\n\n const scaledRect = {\n x1,\n y1,\n x2,\n y2,\n width: 1,\n height: 1,\n pageNumber: page,\n };\n\n const position: ScaledPosition = {\n boundingRect: scaledRect,\n rects: [scaledRect],\n };\n\n return {\n id: CITATION_ID,\n type: \"area\",\n position,\n };\n}\n\n/* ------------------------------------------------------------------ */\n/* Highlight child renderer */\n/* ------------------------------------------------------------------ */\n\n/**\n * Rendered once per highlight by PdfHighlighter's internal loop.\n * Uses context to access the highlight's viewport position.\n */\nfunction CitationHighlightRenderer() {\n const { highlight, isScrolledTo } = useHighlightContainerContext();\n\n return (\n <AreaHighlight\n highlight={highlight}\n isScrolledTo={isScrolledTo}\n style={{\n background: isScrolledTo\n ? \"rgba(255, 171, 0, 0.35)\"\n : \"rgba(255, 171, 0, 0.2)\",\n border: \"1.5px solid rgba(255, 171, 0, 0.6)\",\n borderRadius: 2,\n transition: \"background 200ms ease\",\n }}\n />\n );\n}\n\n/* ------------------------------------------------------------------ */\n/* PdfRenderer */\n/* ------------------------------------------------------------------ */\n\ninterface PdfRendererProps {\n target: ViewerTarget;\n workerUrl?: string;\n loadingComponent?: React.ReactNode;\n errorComponent?: React.ReactNode;\n onLoadSuccess?: () => void;\n onLoadError?: (err: Error) => void;\n}\n\nexport function PdfRenderer({\n target,\n workerUrl,\n loadingComponent,\n errorComponent,\n onLoadSuccess,\n onLoadError,\n}: PdfRendererProps) {\n const highlighterRef = useRef<PdfHighlighterUtils | null>(null);\n const loadNotifiedRef = useRef(false);\n\n /* Build highlight array from target bbox */\n const highlights = useMemo(() => {\n const h = bboxToHighlight(target);\n return h ? [h] : [];\n }, [target]);\n\n /* Scroll to the citation highlight once the viewer is ready */\n useEffect(() => {\n if (highlights.length === 0) return;\n\n const timer = setTimeout(() => {\n highlighterRef.current?.scrollToHighlight(highlights[0]);\n }, 400);\n\n return () => clearTimeout(timer);\n }, [highlights]);\n\n /* Stable callback so we don't re-trigger PdfHighlighter renders */\n const handleUtilsRef = useCallback((utils: PdfHighlighterUtils) => {\n highlighterRef.current = utils;\n }, []);\n\n return (\n <div style={{ width: \"100%\", height: \"100%\", position: \"relative\" }}>\n <PdfLoader\n document={target.url}\n workerSrc={workerUrl ?? DEFAULT_WORKER_URL}\n beforeLoad={() =>\n loadingComponent ?? (\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n width: \"100%\",\n height: \"100%\",\n color: \"#888\",\n fontSize: 14,\n fontFamily:\n '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\n }}\n >\n Loading PDF…\n </div>\n )\n }\n errorMessage={(error) => {\n onLoadError?.(error);\n return (\n errorComponent ?? (\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n width: \"100%\",\n height: \"100%\",\n color: \"#ef4444\",\n fontSize: 14,\n fontFamily:\n '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\n }}\n >\n Failed to load PDF\n </div>\n )\n );\n }}\n >\n {(pdfDocument) => {\n /* Notify once */\n if (!loadNotifiedRef.current) {\n loadNotifiedRef.current = true;\n onLoadSuccess?.();\n }\n\n return (\n <PdfHighlighter\n pdfDocument={pdfDocument}\n highlights={highlights}\n utilsRef={handleUtilsRef}\n style={{\n width: \"100%\",\n height: \"100%\",\n }}\n >\n <CitationHighlightRenderer />\n </PdfHighlighter>\n );\n }}\n </PdfLoader>\n </div>\n );\n}\n","import React from \"react\";\nimport type { ViewerTarget } from \"../../types/ViewerTarget\";\n\n/**\n * Renders DOCX / DOC files via the free Microsoft Office Online embed viewer.\n * The document URL must be publicly accessible.\n */\nexport function DocxRenderer({ target }: { target: ViewerTarget }) {\n const officeUrl = `https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(\n target.url\n )}`;\n\n return (\n <iframe\n src={officeUrl}\n title={target.title ?? \"Document preview\"}\n style={{\n width: \"100%\",\n height: \"100%\",\n border: \"none\",\n borderRadius: 6,\n background: \"#fff\",\n }}\n />\n );\n}\n","import React from \"react\";\nimport type { DocumentViewerProps } from \"../types/DocumentViewerProps\";\nimport { getFileType } from \"../utils/getFileType\";\nimport { PdfRenderer } from \"./renderers/PdfRenderer\";\nimport { DocxRenderer } from \"./renderers/DocxRenderer\";\n\n/**\n * Top-level component that routes to the correct renderer\n * based on the target's file type.\n *\n * ```tsx\n * <DocumentViewer\n * target={{\n * url: \"https://example.com/report.pdf\",\n * page: 3,\n * bbox: { x1: 0.1, y1: 0.2, x2: 0.5, y2: 0.3 },\n * }}\n * />\n * ```\n */\nexport function DocumentViewer({\n target,\n pdfWorkerUrl,\n loadingComponent,\n errorComponent,\n onLoadStart,\n onLoadSuccess,\n onLoadError,\n className,\n}: DocumentViewerProps) {\n if (!target) return null;\n\n const type = target.fileType ?? getFileType(target.url);\n\n const wrapperStyle: React.CSSProperties = {\n width: \"100%\",\n height: \"100%\",\n position: \"relative\",\n overflow: \"hidden\",\n borderRadius: 8,\n background: \"#fafafa\",\n };\n\n return (\n <div className={className} style={wrapperStyle}>\n {type === \"pdf\" && (\n <PdfRenderer\n target={target}\n workerUrl={pdfWorkerUrl}\n loadingComponent={loadingComponent}\n errorComponent={errorComponent}\n onLoadSuccess={onLoadSuccess}\n onLoadError={onLoadError}\n />\n )}\n\n {type === \"docx\" && <DocxRenderer target={target} />}\n\n {type === \"unknown\" && (\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n width: \"100%\",\n height: \"100%\",\n color: \"#888\",\n fontSize: 14,\n fontFamily:\n '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\n }}\n >\n Unsupported file type\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":["import_jsx_runtime","import_jsx_runtime"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,225 @@
1
+ // src/utils/getFileType.ts
2
+ function getFileType(url) {
3
+ const clean = url.split("?")[0].split("#")[0].toLowerCase();
4
+ if (clean.endsWith(".pdf")) return "pdf";
5
+ if (clean.endsWith(".docx") || clean.endsWith(".doc")) return "docx";
6
+ return "unknown";
7
+ }
8
+
9
+ // src/components/renderers/PdfRenderer.tsx
10
+ import { useRef, useEffect, useMemo, useCallback } from "react";
11
+ import {
12
+ PdfLoader,
13
+ PdfHighlighter,
14
+ AreaHighlight,
15
+ useHighlightContainerContext
16
+ } from "react-pdf-highlighter-extended";
17
+ import { jsx } from "react/jsx-runtime";
18
+ var CITATION_ID = "citation-highlight";
19
+ var DEFAULT_WORKER_URL = "https://unpkg.com/pdfjs-dist@4.4.168/build/pdf.worker.min.mjs";
20
+ function bboxToHighlight(target) {
21
+ if (!target.bbox) return null;
22
+ const page = target.page ?? 1;
23
+ const { x1, y1, x2, y2 } = target.bbox;
24
+ const scaledRect = {
25
+ x1,
26
+ y1,
27
+ x2,
28
+ y2,
29
+ width: 1,
30
+ height: 1,
31
+ pageNumber: page
32
+ };
33
+ const position = {
34
+ boundingRect: scaledRect,
35
+ rects: [scaledRect]
36
+ };
37
+ return {
38
+ id: CITATION_ID,
39
+ type: "area",
40
+ position
41
+ };
42
+ }
43
+ function CitationHighlightRenderer() {
44
+ const { highlight, isScrolledTo } = useHighlightContainerContext();
45
+ return /* @__PURE__ */ jsx(
46
+ AreaHighlight,
47
+ {
48
+ highlight,
49
+ isScrolledTo,
50
+ style: {
51
+ background: isScrolledTo ? "rgba(255, 171, 0, 0.35)" : "rgba(255, 171, 0, 0.2)",
52
+ border: "1.5px solid rgba(255, 171, 0, 0.6)",
53
+ borderRadius: 2,
54
+ transition: "background 200ms ease"
55
+ }
56
+ }
57
+ );
58
+ }
59
+ function PdfRenderer({
60
+ target,
61
+ workerUrl,
62
+ loadingComponent,
63
+ errorComponent,
64
+ onLoadSuccess,
65
+ onLoadError
66
+ }) {
67
+ const highlighterRef = useRef(null);
68
+ const loadNotifiedRef = useRef(false);
69
+ const highlights = useMemo(() => {
70
+ const h = bboxToHighlight(target);
71
+ return h ? [h] : [];
72
+ }, [target]);
73
+ useEffect(() => {
74
+ if (highlights.length === 0) return;
75
+ const timer = setTimeout(() => {
76
+ highlighterRef.current?.scrollToHighlight(highlights[0]);
77
+ }, 400);
78
+ return () => clearTimeout(timer);
79
+ }, [highlights]);
80
+ const handleUtilsRef = useCallback((utils) => {
81
+ highlighterRef.current = utils;
82
+ }, []);
83
+ return /* @__PURE__ */ jsx("div", { style: { width: "100%", height: "100%", position: "relative" }, children: /* @__PURE__ */ jsx(
84
+ PdfLoader,
85
+ {
86
+ document: target.url,
87
+ workerSrc: workerUrl ?? DEFAULT_WORKER_URL,
88
+ beforeLoad: () => loadingComponent ?? /* @__PURE__ */ jsx(
89
+ "div",
90
+ {
91
+ style: {
92
+ display: "flex",
93
+ alignItems: "center",
94
+ justifyContent: "center",
95
+ width: "100%",
96
+ height: "100%",
97
+ color: "#888",
98
+ fontSize: 14,
99
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
100
+ },
101
+ children: "Loading PDF\u2026"
102
+ }
103
+ ),
104
+ errorMessage: (error) => {
105
+ onLoadError?.(error);
106
+ return errorComponent ?? /* @__PURE__ */ jsx(
107
+ "div",
108
+ {
109
+ style: {
110
+ display: "flex",
111
+ alignItems: "center",
112
+ justifyContent: "center",
113
+ width: "100%",
114
+ height: "100%",
115
+ color: "#ef4444",
116
+ fontSize: 14,
117
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
118
+ },
119
+ children: "Failed to load PDF"
120
+ }
121
+ );
122
+ },
123
+ children: (pdfDocument) => {
124
+ if (!loadNotifiedRef.current) {
125
+ loadNotifiedRef.current = true;
126
+ onLoadSuccess?.();
127
+ }
128
+ return /* @__PURE__ */ jsx(
129
+ PdfHighlighter,
130
+ {
131
+ pdfDocument,
132
+ highlights,
133
+ utilsRef: handleUtilsRef,
134
+ style: {
135
+ width: "100%",
136
+ height: "100%"
137
+ },
138
+ children: /* @__PURE__ */ jsx(CitationHighlightRenderer, {})
139
+ }
140
+ );
141
+ }
142
+ }
143
+ ) });
144
+ }
145
+
146
+ // src/components/renderers/DocxRenderer.tsx
147
+ import { jsx as jsx2 } from "react/jsx-runtime";
148
+ function DocxRenderer({ target }) {
149
+ const officeUrl = `https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(
150
+ target.url
151
+ )}`;
152
+ return /* @__PURE__ */ jsx2(
153
+ "iframe",
154
+ {
155
+ src: officeUrl,
156
+ title: target.title ?? "Document preview",
157
+ style: {
158
+ width: "100%",
159
+ height: "100%",
160
+ border: "none",
161
+ borderRadius: 6,
162
+ background: "#fff"
163
+ }
164
+ }
165
+ );
166
+ }
167
+
168
+ // src/components/DocumentViewer.tsx
169
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
170
+ function DocumentViewer({
171
+ target,
172
+ pdfWorkerUrl,
173
+ loadingComponent,
174
+ errorComponent,
175
+ onLoadStart,
176
+ onLoadSuccess,
177
+ onLoadError,
178
+ className
179
+ }) {
180
+ if (!target) return null;
181
+ const type = target.fileType ?? getFileType(target.url);
182
+ const wrapperStyle = {
183
+ width: "100%",
184
+ height: "100%",
185
+ position: "relative",
186
+ overflow: "hidden",
187
+ borderRadius: 8,
188
+ background: "#fafafa"
189
+ };
190
+ return /* @__PURE__ */ jsxs("div", { className, style: wrapperStyle, children: [
191
+ type === "pdf" && /* @__PURE__ */ jsx3(
192
+ PdfRenderer,
193
+ {
194
+ target,
195
+ workerUrl: pdfWorkerUrl,
196
+ loadingComponent,
197
+ errorComponent,
198
+ onLoadSuccess,
199
+ onLoadError
200
+ }
201
+ ),
202
+ type === "docx" && /* @__PURE__ */ jsx3(DocxRenderer, { target }),
203
+ type === "unknown" && /* @__PURE__ */ jsx3(
204
+ "div",
205
+ {
206
+ style: {
207
+ display: "flex",
208
+ alignItems: "center",
209
+ justifyContent: "center",
210
+ width: "100%",
211
+ height: "100%",
212
+ color: "#888",
213
+ fontSize: 14,
214
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
215
+ },
216
+ children: "Unsupported file type"
217
+ }
218
+ )
219
+ ] });
220
+ }
221
+ export {
222
+ DocumentViewer,
223
+ getFileType
224
+ };
225
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/getFileType.ts","../src/components/renderers/PdfRenderer.tsx","../src/components/renderers/DocxRenderer.tsx","../src/components/DocumentViewer.tsx"],"sourcesContent":["/**\n * Infer document type from URL extension.\n * Strips query-strings and fragments before matching.\n */\nexport function getFileType(url: string): \"pdf\" | \"docx\" | \"unknown\" {\n const clean = url.split(\"?\")[0].split(\"#\")[0].toLowerCase();\n\n if (clean.endsWith(\".pdf\")) return \"pdf\";\n if (clean.endsWith(\".docx\") || clean.endsWith(\".doc\")) return \"docx\";\n\n return \"unknown\";\n}\n","import React, { useRef, useEffect, useMemo, useCallback } from \"react\";\nimport {\n PdfLoader,\n PdfHighlighter,\n AreaHighlight,\n useHighlightContainerContext,\n} from \"react-pdf-highlighter-extended\";\nimport type {\n Highlight,\n ScaledPosition,\n PdfHighlighterUtils,\n} from \"react-pdf-highlighter-extended\";\nimport type { ViewerTarget } from \"../../types/ViewerTarget\";\n\n/* ------------------------------------------------------------------ */\n/* Constants */\n/* ------------------------------------------------------------------ */\n\nconst CITATION_ID = \"citation-highlight\";\n\nconst DEFAULT_WORKER_URL =\n \"https://unpkg.com/pdfjs-dist@4.4.168/build/pdf.worker.min.mjs\";\n\n/* ------------------------------------------------------------------ */\n/* BBox → Highlight conversion */\n/* ------------------------------------------------------------------ */\n\n/**\n * Convert our normalised (0-1) BBox into the Scaled coordinate system\n * used by react-pdf-highlighter-extended.\n *\n * The library internally computes:\n * viewportX = (viewportWidth × scaled.x1) / scaled.width\n *\n * So `scaled.x1 / scaled.width` must equal the normalised value.\n * We simply store the 0-1 value directly and set width / height to 1.\n */\nfunction bboxToHighlight(target: ViewerTarget): Highlight | null {\n if (!target.bbox) return null;\n\n const page = target.page ?? 1;\n const { x1, y1, x2, y2 } = target.bbox;\n\n const scaledRect = {\n x1,\n y1,\n x2,\n y2,\n width: 1,\n height: 1,\n pageNumber: page,\n };\n\n const position: ScaledPosition = {\n boundingRect: scaledRect,\n rects: [scaledRect],\n };\n\n return {\n id: CITATION_ID,\n type: \"area\",\n position,\n };\n}\n\n/* ------------------------------------------------------------------ */\n/* Highlight child renderer */\n/* ------------------------------------------------------------------ */\n\n/**\n * Rendered once per highlight by PdfHighlighter's internal loop.\n * Uses context to access the highlight's viewport position.\n */\nfunction CitationHighlightRenderer() {\n const { highlight, isScrolledTo } = useHighlightContainerContext();\n\n return (\n <AreaHighlight\n highlight={highlight}\n isScrolledTo={isScrolledTo}\n style={{\n background: isScrolledTo\n ? \"rgba(255, 171, 0, 0.35)\"\n : \"rgba(255, 171, 0, 0.2)\",\n border: \"1.5px solid rgba(255, 171, 0, 0.6)\",\n borderRadius: 2,\n transition: \"background 200ms ease\",\n }}\n />\n );\n}\n\n/* ------------------------------------------------------------------ */\n/* PdfRenderer */\n/* ------------------------------------------------------------------ */\n\ninterface PdfRendererProps {\n target: ViewerTarget;\n workerUrl?: string;\n loadingComponent?: React.ReactNode;\n errorComponent?: React.ReactNode;\n onLoadSuccess?: () => void;\n onLoadError?: (err: Error) => void;\n}\n\nexport function PdfRenderer({\n target,\n workerUrl,\n loadingComponent,\n errorComponent,\n onLoadSuccess,\n onLoadError,\n}: PdfRendererProps) {\n const highlighterRef = useRef<PdfHighlighterUtils | null>(null);\n const loadNotifiedRef = useRef(false);\n\n /* Build highlight array from target bbox */\n const highlights = useMemo(() => {\n const h = bboxToHighlight(target);\n return h ? [h] : [];\n }, [target]);\n\n /* Scroll to the citation highlight once the viewer is ready */\n useEffect(() => {\n if (highlights.length === 0) return;\n\n const timer = setTimeout(() => {\n highlighterRef.current?.scrollToHighlight(highlights[0]);\n }, 400);\n\n return () => clearTimeout(timer);\n }, [highlights]);\n\n /* Stable callback so we don't re-trigger PdfHighlighter renders */\n const handleUtilsRef = useCallback((utils: PdfHighlighterUtils) => {\n highlighterRef.current = utils;\n }, []);\n\n return (\n <div style={{ width: \"100%\", height: \"100%\", position: \"relative\" }}>\n <PdfLoader\n document={target.url}\n workerSrc={workerUrl ?? DEFAULT_WORKER_URL}\n beforeLoad={() =>\n loadingComponent ?? (\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n width: \"100%\",\n height: \"100%\",\n color: \"#888\",\n fontSize: 14,\n fontFamily:\n '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\n }}\n >\n Loading PDF…\n </div>\n )\n }\n errorMessage={(error) => {\n onLoadError?.(error);\n return (\n errorComponent ?? (\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n width: \"100%\",\n height: \"100%\",\n color: \"#ef4444\",\n fontSize: 14,\n fontFamily:\n '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\n }}\n >\n Failed to load PDF\n </div>\n )\n );\n }}\n >\n {(pdfDocument) => {\n /* Notify once */\n if (!loadNotifiedRef.current) {\n loadNotifiedRef.current = true;\n onLoadSuccess?.();\n }\n\n return (\n <PdfHighlighter\n pdfDocument={pdfDocument}\n highlights={highlights}\n utilsRef={handleUtilsRef}\n style={{\n width: \"100%\",\n height: \"100%\",\n }}\n >\n <CitationHighlightRenderer />\n </PdfHighlighter>\n );\n }}\n </PdfLoader>\n </div>\n );\n}\n","import React from \"react\";\nimport type { ViewerTarget } from \"../../types/ViewerTarget\";\n\n/**\n * Renders DOCX / DOC files via the free Microsoft Office Online embed viewer.\n * The document URL must be publicly accessible.\n */\nexport function DocxRenderer({ target }: { target: ViewerTarget }) {\n const officeUrl = `https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(\n target.url\n )}`;\n\n return (\n <iframe\n src={officeUrl}\n title={target.title ?? \"Document preview\"}\n style={{\n width: \"100%\",\n height: \"100%\",\n border: \"none\",\n borderRadius: 6,\n background: \"#fff\",\n }}\n />\n );\n}\n","import React from \"react\";\nimport type { DocumentViewerProps } from \"../types/DocumentViewerProps\";\nimport { getFileType } from \"../utils/getFileType\";\nimport { PdfRenderer } from \"./renderers/PdfRenderer\";\nimport { DocxRenderer } from \"./renderers/DocxRenderer\";\n\n/**\n * Top-level component that routes to the correct renderer\n * based on the target's file type.\n *\n * ```tsx\n * <DocumentViewer\n * target={{\n * url: \"https://example.com/report.pdf\",\n * page: 3,\n * bbox: { x1: 0.1, y1: 0.2, x2: 0.5, y2: 0.3 },\n * }}\n * />\n * ```\n */\nexport function DocumentViewer({\n target,\n pdfWorkerUrl,\n loadingComponent,\n errorComponent,\n onLoadStart,\n onLoadSuccess,\n onLoadError,\n className,\n}: DocumentViewerProps) {\n if (!target) return null;\n\n const type = target.fileType ?? getFileType(target.url);\n\n const wrapperStyle: React.CSSProperties = {\n width: \"100%\",\n height: \"100%\",\n position: \"relative\",\n overflow: \"hidden\",\n borderRadius: 8,\n background: \"#fafafa\",\n };\n\n return (\n <div className={className} style={wrapperStyle}>\n {type === \"pdf\" && (\n <PdfRenderer\n target={target}\n workerUrl={pdfWorkerUrl}\n loadingComponent={loadingComponent}\n errorComponent={errorComponent}\n onLoadSuccess={onLoadSuccess}\n onLoadError={onLoadError}\n />\n )}\n\n {type === \"docx\" && <DocxRenderer target={target} />}\n\n {type === \"unknown\" && (\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n width: \"100%\",\n height: \"100%\",\n color: \"#888\",\n fontSize: 14,\n fontFamily:\n '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\n }}\n >\n Unsupported file type\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":["jsx","jsx"]}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@relevaince/document-viewer",
3
+ "version": "0.1.0",
4
+ "description": "Citation-driven document viewer for Relevaince",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsup",
20
+ "dev": "tsup --watch"
21
+ },
22
+ "peerDependencies": {
23
+ "react": ">=18",
24
+ "react-dom": ">=18"
25
+ },
26
+ "dependencies": {
27
+ "pdfjs-dist": "^4.4.168",
28
+ "react-pdf-highlighter-extended": "^8.1.0"
29
+ },
30
+ "devDependencies": {
31
+ "@types/react": "^18.3.28",
32
+ "@types/react-dom": "^18.3.7",
33
+ "tsup": "^8.5.1",
34
+ "typescript": "^5.9.3"
35
+ },
36
+ "keywords": [
37
+ "document-viewer",
38
+ "pdf",
39
+ "docx",
40
+ "citation",
41
+ "react"
42
+ ],
43
+ "author": "Relevaince",
44
+ "license": "MIT",
45
+ "publishConfig": {
46
+ "access": "public"
47
+ },
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "https://github.com/relevaince/document-viewer"
51
+ }
52
+ }