captide
Version:
Get millions of financial documents into your AI app π
881 lines (864 loc) β’ 58.5 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var React = require('react');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
function _interopNamespace(e) {
if (e && e.__esModule) return e;
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n["default"] = e;
return Object.freeze(n);
}
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
var __assign = function() {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
function __generator(thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
}
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
// Initial state for the context
var initialState = {
document: null,
isLoading: false,
isOpen: false,
zoomLevel: 0.7
};
var DocumentViewerContext = React.createContext(undefined);
DocumentViewerContext.displayName = 'DocumentViewerContext';
var DocumentViewerProvider = function (_a) {
var children = _a.children, providedFetchFn = _a.fetchDocumentFn;
var _b = React.useState(initialState), state = _b[0], setState = _b[1];
var fetchDocumentFnRef = React.useRef(null);
React.useEffect(function () {
if (providedFetchFn) {
fetchDocumentFnRef.current = providedFetchFn;
}
}, [providedFetchFn]);
var updateDocumentViewer = React.useCallback(function (updates) {
setState(function (prev) { return (__assign(__assign({}, prev), updates)); });
}, []);
var setDocument = React.useCallback(function (document) {
setState(function (prev) { return (__assign(__assign({}, prev), { document: document })); });
}, []);
var setFetchDocumentFn = React.useCallback(function (fn) {
fetchDocumentFnRef.current = fn;
}, []);
var openViewer = React.useCallback(function () {
setState(function (prev) { return (__assign(__assign({}, prev), { isOpen: true })); });
}, []);
var closeViewer = React.useCallback(function () {
setState(function (prev) { return (__assign(__assign({}, prev), { isOpen: false, document: null, pageNumber: undefined, citationSnippet: undefined })); });
}, []);
var loadDocument = React.useCallback(function (documentId, pageNumber, citationSnippet, legacyElementId) { return __awaiter(void 0, void 0, void 0, function () {
var effectivePageNumber, lastFourChars, pageNum, fetchFn, response, documentWithId_1;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
effectivePageNumber = pageNumber;
if (!effectivePageNumber && legacyElementId) {
lastFourChars = legacyElementId.slice(-4);
pageNum = parseInt(lastFourChars, 10);
if (!isNaN(pageNum)) {
effectivePageNumber = pageNum + 1; // Convert 0-based to 1-based
}
}
setState(function (prev) { return (__assign(__assign({}, prev), { isLoading: true, document: null, isOpen: true, pageNumber: effectivePageNumber, citationSnippet: citationSnippet })); });
fetchFn = fetchDocumentFnRef.current || providedFetchFn;
if (!fetchFn) {
setState(function (prev) { return (__assign(__assign({}, prev), { isLoading: false })); });
throw new Error('No fetchDocumentFn provided. Cannot load document.');
}
_b.label = 1;
case 1:
_b.trys.push([1, 3, , 4]);
return [4 /*yield*/, fetchFn(documentId)];
case 2:
response = _b.sent();
documentWithId_1 = __assign(__assign({}, response), { documentId: documentId });
setState(function (prev) { return (__assign(__assign({}, prev), { document: documentWithId_1, isLoading: false })); });
return [3 /*break*/, 4];
case 3:
_b.sent();
setState(function (prev) { return (__assign(__assign({}, prev), { isLoading: false })); });
return [3 /*break*/, 4];
case 4: return [2 /*return*/];
}
});
}); }, [providedFetchFn]);
var setZoomLevel = React.useCallback(function (level) {
setState(function (prev) { return (__assign(__assign({}, prev), { zoomLevel: level })); });
}, []);
var zoomIn = React.useCallback(function () {
setState(function (prev) { return (__assign(__assign({}, prev), { zoomLevel: prev.zoomLevel + 0.1 })); });
}, []);
var zoomOut = React.useCallback(function () {
setState(function (prev) { return (__assign(__assign({}, prev), { zoomLevel: Math.max(0.1, prev.zoomLevel - 0.1) })); });
}, []);
var resetZoom = React.useCallback(function () {
setState(function (prev) { return (__assign(__assign({}, prev), { zoomLevel: 1.0 })); });
}, []);
var contextValue = __assign(__assign({}, state), { updateDocumentViewer: updateDocumentViewer, setDocument: setDocument, loadDocument: loadDocument, setFetchDocumentFn: setFetchDocumentFn, openViewer: openViewer, closeViewer: closeViewer, setZoomLevel: setZoomLevel, zoomIn: zoomIn, zoomOut: zoomOut, resetZoom: resetZoom });
return (React__default["default"].createElement(DocumentViewerContext.Provider, { value: contextValue }, children));
};
var useDocumentViewer = function () {
var context = React.useContext(DocumentViewerContext);
if (!context) {
throw new Error('useDocumentViewer must be used within a DocumentViewerProvider');
}
return context;
};
/**
* PDF Highlighting Utility
*
* Provides functionality to find and highlight text in PDF documents using PDF.js
*/
/**
* Sleep helper for retry/backoff flows
*/
var sleep = function (ms) { return new Promise(function (resolve) { return setTimeout(resolve, ms); }); };
/**
* Wait until a page view is ready (its container div and viewport exist).
* Polls until ready or timeout.
*/
var waitForPageReady = function (pdfViewerInstance, pageNumber, timeoutMs, pollIntervalMs) {
if (timeoutMs === void 0) { timeoutMs = 2000; }
if (pollIntervalMs === void 0) { pollIntervalMs = 50; }
return __awaiter(void 0, void 0, void 0, function () {
var start, targetIndex, pageView;
var _a;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
start = Date.now();
targetIndex = Math.max(0, pageNumber - 1);
_b.label = 1;
case 1:
if (!(Date.now() - start < timeoutMs)) return [3 /*break*/, 3];
try {
pageView = (_a = pdfViewerInstance === null || pdfViewerInstance === void 0 ? void 0 : pdfViewerInstance.getPageView) === null || _a === void 0 ? void 0 : _a.call(pdfViewerInstance, targetIndex);
if ((pageView === null || pageView === void 0 ? void 0 : pageView.div) && (pageView === null || pageView === void 0 ? void 0 : pageView.viewport)) {
return [2 /*return*/, pageView];
}
}
catch (_) {
// ignore and retry
}
return [4 /*yield*/, sleep(pollIntervalMs)];
case 2:
_b.sent();
return [3 /*break*/, 1];
case 3: return [2 /*return*/, null];
}
});
});
};
/**
* Normalize text for comparison (remove spaces, dollar signs, make lowercase)
*/
var normalizeText = function (str) {
return str
.toLowerCase()
// normalize curly/typographic double quotes to ASCII "
.replace(/[ββββ«»βΉβΊοΌ]/g, '"')
// remove whitespace, $, apostrophes, parentheses/brackets
.replace(/[\s$'ββββΒ΄Κ»ΚΌΚ½ΚΎΚΏΛΛΛΛ΄ΥκοΌ()\[\]{}β¨β©βΉβΊΒ«Β»γγγγγγγγγγγγβ¨β©βͺβ«β¬β]+/g, '');
};
/**
* Extract text content from a PDF page
*/
var extractTextFromPage = function (pageView) { return __awaiter(void 0, void 0, void 0, function () {
var textContent, textItems, fullText;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 2, , 3]);
if (!(pageView === null || pageView === void 0 ? void 0 : pageView.pdfPage))
return [2 /*return*/, null];
return [4 /*yield*/, pageView.pdfPage.getTextContent()];
case 1:
textContent = _a.sent();
textItems = textContent.items.map(function (item) { return item.str; });
fullText = textItems.join(' ');
return [2 /*return*/, { textItems: textItems, fullText: fullText, textContent: textContent }];
case 2:
_a.sent();
return [2 /*return*/, null];
case 3: return [2 /*return*/];
}
});
}); };
/**
* Find text in PDF and return matching text items with coordinates
*/
var findTextInPDF = function (searchText, pdfViewerInstance, targetPage) { return __awaiter(void 0, void 0, void 0, function () {
var normalizedSearchText, effectiveSearchText, pagesToSearch, _i, pagesToSearch_1, pageNum, pageView, pageTextData, normalizedPageText, matchIndex, matchingTextItems, normalizedPos, i, textItem, normalizedItemText, itemStart, itemEnd;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!searchText || !pdfViewerInstance || !pdfViewerInstance.pagesCount)
return [2 /*return*/, null];
normalizedSearchText = normalizeText(searchText);
effectiveSearchText = normalizedSearchText.length > 50 ? normalizedSearchText.slice(0, 50) : normalizedSearchText;
pagesToSearch = targetPage
? [targetPage]
: Array.from({ length: pdfViewerInstance.pagesCount }, function (_, i) { return i + 1; });
_i = 0, pagesToSearch_1 = pagesToSearch;
_a.label = 1;
case 1:
if (!(_i < pagesToSearch_1.length)) return [3 /*break*/, 4];
pageNum = pagesToSearch_1[_i];
pageView = pdfViewerInstance.getPageView(pageNum - 1);
return [4 /*yield*/, extractTextFromPage(pageView)];
case 2:
pageTextData = _a.sent();
if (!pageTextData)
return [3 /*break*/, 3];
normalizedPageText = normalizeText(pageTextData.fullText);
matchIndex = normalizedPageText.indexOf(effectiveSearchText);
if (matchIndex !== -1) {
matchingTextItems = [];
normalizedPos = 0;
for (i = 0; i < pageTextData.textContent.items.length; i++) {
textItem = pageTextData.textContent.items[i];
normalizedItemText = normalizeText(textItem.str);
itemStart = normalizedPos;
itemEnd = normalizedPos + normalizedItemText.length;
// Check if this text item overlaps with our match
if (itemStart < matchIndex + normalizedSearchText.length && itemEnd > matchIndex) {
matchingTextItems.push({
index: i,
item: textItem,
start: Math.max(itemStart, matchIndex),
end: Math.min(itemEnd, matchIndex + normalizedSearchText.length)
});
}
normalizedPos += normalizedItemText.length;
}
if (matchingTextItems.length > 0) {
return [2 /*return*/, {
page: pageNum,
textItems: matchingTextItems,
textContent: pageTextData.textContent
}];
}
}
_a.label = 3;
case 3:
_i++;
return [3 /*break*/, 1];
case 4: return [2 /*return*/, null];
}
});
}); };
/**
* Create a rectangle highlight overlay on a PDF page
*/
var createRectangleHighlight = function (_a) {
var searchText = _a.searchText, pdfViewerInstance = _a.pdfViewerInstance, targetPage = _a.targetPage, _b = _a.currentHighlight, currentHighlight = _b === void 0 ? null : _b, _c = _a.forceRecreate, forceRecreate = _c === void 0 ? false : _c, _d = _a.shouldNavigateOnMatch, shouldNavigateOnMatch = _d === void 0 ? true : _d;
return __awaiter(void 0, void 0, void 0, function () {
var maxAttempts, retryDelayMs, attempt, result, pageNumber, confirmedResult;
var _e;
return __generator(this, function (_f) {
switch (_f.label) {
case 0:
if (!searchText || !pdfViewerInstance || !pdfViewerInstance.pagesCount)
return [2 /*return*/, null];
// Check if we already have a highlight for the same text and page
if (currentHighlight &&
currentHighlight.text === searchText &&
currentHighlight.page === (targetPage || pdfViewerInstance.currentPageNumber) &&
// Ensure the element is still attached; otherwise, recreate
(((_e = currentHighlight.element) === null || _e === void 0 ? void 0 : _e.isConnected) === true) &&
!forceRecreate) {
return [2 /*return*/, currentHighlight];
}
maxAttempts = 20;
retryDelayMs = 100;
attempt = 0;
result = null;
_f.label = 1;
case 1:
if (!(attempt < maxAttempts)) return [3 /*break*/, 4];
return [4 /*yield*/, findTextInPDF(searchText, pdfViewerInstance, targetPage)];
case 2:
result = _f.sent();
if (result)
return [3 /*break*/, 4];
return [4 /*yield*/, sleep(retryDelayMs)];
case 3:
_f.sent();
attempt += 1;
return [3 /*break*/, 1];
case 4:
if (!result)
return [2 /*return*/, null];
// Don't auto-navigate when user is scrolling around
if (shouldNavigateOnMatch && targetPage && result.page !== pdfViewerInstance.currentPageNumber) {
try {
pageNumber = Number(result.page);
if (pageNumber >= 1 && pageNumber <= pdfViewerInstance.pagesCount) {
pdfViewerInstance.currentPageNumber = pageNumber;
}
}
catch (err) {
console.warn('Failed to navigate to page for highlighting:', result.page, err);
}
}
confirmedResult = result;
return [2 /*return*/, new Promise(function (resolve) {
(function () { return __awaiter(void 0, void 0, void 0, function () {
var currentPageView, pageDiv, minX, minY, maxX, maxY, hasValidCoordinates, _i, _a, matchItem, item, x, y, width_1, height_1, viewport, _b, x1, y1, _c, x2, y2, highlightElement, width, height, top, left, padding, offsetX, offsetY, existingHighlight, highlight;
return __generator(this, function (_d) {
switch (_d.label) {
case 0: return [4 /*yield*/, waitForPageReady(pdfViewerInstance, confirmedResult.page)];
case 1:
currentPageView = _d.sent();
if (!currentPageView) {
resolve(null);
return [2 /*return*/];
}
pageDiv = currentPageView.div;
minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
hasValidCoordinates = false;
for (_i = 0, _a = confirmedResult.textItems; _i < _a.length; _i++) {
matchItem = _a[_i];
item = matchItem.item;
if (item.transform && item.width && item.height) {
x = item.transform[4];
y = item.transform[5];
width_1 = item.width;
height_1 = item.height;
minX = Math.min(minX, x);
minY = Math.min(minY, y);
maxX = Math.max(maxX, x + width_1);
maxY = Math.max(maxY, y + height_1);
hasValidCoordinates = true;
}
}
if (!hasValidCoordinates) {
resolve(null);
return [2 /*return*/];
}
viewport = currentPageView.viewport;
_b = viewport.convertToViewportPoint(minX, minY), x1 = _b[0], y1 = _b[1];
_c = viewport.convertToViewportPoint(maxX, maxY), x2 = _c[0], y2 = _c[1];
highlightElement = document.createElement('div');
highlightElement.className = 'pdf-rectangle-highlight';
width = Math.abs(x2 - x1);
height = Math.abs(y2 - y1);
top = Math.min(y1, y2);
left = Math.min(x1, x2);
padding = Math.min(2, width * 0.1, height * 0.1);
offsetX = 0;
offsetY = -Math.min(2, height * 0.1);
highlightElement.style.left = "".concat(left - padding - offsetX, "px");
highlightElement.style.top = "".concat(top - padding - offsetY, "px");
highlightElement.style.width = "".concat(width + padding * 2, "px");
highlightElement.style.height = "".concat(height + padding * 2, "px");
existingHighlight = document.querySelector('.pdf-rectangle-highlight');
if (existingHighlight) {
existingHighlight.remove();
}
// Add to page
pageDiv.style.position = 'relative';
pageDiv.appendChild(highlightElement);
highlight = {
element: highlightElement,
page: confirmedResult.page,
text: searchText
};
resolve(highlight);
return [2 /*return*/];
}
});
}); })();
})];
}
});
});
};
/**
* Remove a highlight element from the DOM
*/
var removeHighlight = function (highlight) {
if (highlight === null || highlight === void 0 ? void 0 : highlight.element) {
highlight.element.remove();
}
};
var Loader = function (_a) {
var _b = _a.className, className = _b === void 0 ? '' : _b, style = _a.style;
return (React__default["default"].createElement("div", { className: "flex flex-col items-center justify-center w-full h-full ".concat(className), style: style },
React__default["default"].createElement("div", { style: {
width: '40px',
height: '40px',
border: '3px solid #f3f3f3',
borderTop: '3px solid #475569',
borderRadius: '50%',
animation: 'spin 1s linear infinite'
} }),
React__default["default"].createElement("style", null, "\n @keyframes spin {\n 0% { transform: rotate(0deg); }\n 100% { transform: rotate(360deg); }\n }\n ")));
};
/**
* A simple icon-only download button component
*/
var DownloadButton = function (_a) {
var onClick = _a.onClick, _b = _a.style, style = _b === void 0 ? {} : _b, _c = _a.className, className = _c === void 0 ? '' : _c;
var buttonStyle = __assign({ width: '32px', height: '32px', padding: '8px', borderRadius: '4px', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', backgroundColor: 'rgba(255, 255, 255, 0.9)', color: '#475569', border: '1px solid rgba(203, 213, 225, 0.5)', boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)', transition: 'background-color 0.2s ease' }, style);
// Handle hover states
var handleMouseOver = function (e) {
e.currentTarget.style.backgroundColor = '#f8fafc';
};
var handleMouseOut = function (e) {
e.currentTarget.style.backgroundColor = 'rgba(255, 255, 255, 0.9)';
};
return (React__default["default"].createElement("button", { onClick: onClick, style: buttonStyle, className: className, onMouseOver: handleMouseOver, onMouseOut: handleMouseOut, title: "Download PDF" },
React__default["default"].createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" },
React__default["default"].createElement("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
React__default["default"].createElement("polyline", { points: "7 10 12 15 17 10" }),
React__default["default"].createElement("line", { x1: "12", y1: "15", x2: "12", y2: "3" }))));
};
// Simple placeholder for SSR
var PDFPlaceholder = function (_a) {
var _b = _a.className, className = _b === void 0 ? 'w-full h-full' : _b, style = _a.style;
return (React__default["default"].createElement("div", { className: className, style: __assign(__assign({}, style), { display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '20px', color: '#666' }) }, "PDF viewer loading..."));
};
var DocumentViewer = function (_a) {
var _b = _a.className, className = _b === void 0 ? 'w-full h-full' : _b, style = _a.style;
var _c = useDocumentViewer(), pdfDocument = _c.document, isLoading = _c.isLoading, zoomLevel = _c.zoomLevel, zoomIn = _c.zoomIn, zoomOut = _c.zoomOut, resetZoom = _c.resetZoom, pageNumber = _c.pageNumber, citationSnippet = _c.citationSnippet;
var containerRef = React.useRef(null);
var viewerContainerRef = React.useRef(null);
var _d = React.useState(null), error = _d[0], setError = _d[1];
var _e = React.useState(0), numPages = _e[0], setNumPages = _e[1];
var _f = React.useState(1), currentPage = _f[0], setCurrentPage = _f[1];
var _g = React.useState(false), pdfJsLoaded = _g[0], setPdfJsLoaded = _g[1];
var _h = React.useState(null), viewer = _h[0], setViewer = _h[1];
var _j = React.useState(null), currentHighlight = _j[0], setCurrentHighlight = _j[1];
// Only run in browser
var isBrowser = typeof window !== 'undefined';
// Use pageNumber directly (backwards compatibility handled in context)
var effectivePageNumber = pageNumber;
// Add highlighting styles
if (typeof window !== 'undefined' && !document.getElementById('pdf-rectangle-highlight-style')) {
var style_1 = document.createElement('style');
style_1.id = 'pdf-rectangle-highlight-style';
style_1.textContent = "\n .pdf-rectangle-highlight {\n position: absolute !important;\n background: var(--captidejs-highlight-bg, rgba(255, 235, 59, 0.3)) !important;\n border: var(--captidejs-highlight-border, 2px solid #fdcb6e) !important;\n border-radius: var(--captidejs-highlight-radius, 3px) !important;\n pointer-events: none !important;\n z-index: var(--captidejs-highlight-z, 1000) !important;\n box-shadow: var(--captidejs-highlight-shadow, 0 2px 8px rgba(253, 203, 110, 0.3)) !important;\n }\n ";
document.head.appendChild(style_1);
}
// Remove current highlight
var removeCurrentHighlight = React.useCallback(function () {
if (currentHighlight) {
removeHighlight(currentHighlight);
setCurrentHighlight(null);
}
}, [currentHighlight]);
// Handle download functionality
var handleDownload = function () {
if (!(pdfDocument === null || pdfDocument === void 0 ? void 0 : pdfDocument.originalFileUrl))
return;
fetch(pdfDocument.originalFileUrl)
.then(function (response) {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.blob();
})
.then(function (blob) {
var blobUrl = window.URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = blobUrl;
var filename = 'document.pdf';
try {
var urlObj = new URL(pdfDocument.originalFileUrl);
var pathParts = urlObj.pathname.split('/');
var potentialFilename = pathParts[pathParts.length - 1];
if (potentialFilename && potentialFilename.includes('.pdf')) {
filename = decodeURIComponent(potentialFilename.split('?')[0]);
}
}
catch (e) {
// Fall back to default name
}
a.download = filename;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(blobUrl);
document.body.removeChild(a);
})
.catch(function (error) {
window.open(pdfDocument.originalFileUrl, '_blank');
});
};
// Handle keyboard shortcuts
React.useEffect(function () {
var el = containerRef.current;
if (!el)
return;
var handler = function (e) {
if (e.ctrlKey || e.metaKey) {
switch (e.key) {
case '=':
case '+':
e.preventDefault();
zoomIn();
break;
case '-':
e.preventDefault();
zoomOut();
break;
case '0':
e.preventDefault();
resetZoom();
break;
}
}
};
el.addEventListener('keydown', handler);
return function () { return el.removeEventListener('keydown', handler); };
}, [zoomIn, zoomOut, resetZoom]);
// Handle wheel zoom
React.useEffect(function () {
var el = containerRef.current;
if (!el)
return;
var handler = function (e) {
if (e.ctrlKey) {
e.preventDefault();
e.deltaY < 0 ? zoomIn() : zoomOut();
}
};
el.addEventListener('wheel', handler, { passive: false });
return function () { return el.removeEventListener('wheel', handler); };
}, [zoomIn, zoomOut]);
// Initialize PDF.js in browser
React.useEffect(function () {
if (!isBrowser)
return;
var mounted = true;
var loadPdfJs = function () { return __awaiter(void 0, void 0, void 0, function () {
var pdfjsLib, GlobalWorkerOptions, workerUrl, preloadWorker, link, customStyles, err_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 3, , 4]);
return [4 /*yield*/, Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require('pdfjs-dist')); })];
case 1:
pdfjsLib = _a.sent();
return [4 /*yield*/, Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require('pdfjs-dist')); })];
case 2:
GlobalWorkerOptions = (_a.sent()).GlobalWorkerOptions;
workerUrl = "https://cdn.jsdelivr.net/npm/pdfjs-dist@".concat(pdfjsLib.version, "/build/pdf.worker.min.mjs");
GlobalWorkerOptions.workerSrc = workerUrl;
// Pre-fetch the worker
try {
preloadWorker = document.createElement('link');
preloadWorker.rel = 'preload';
preloadWorker.as = 'script';
preloadWorker.href = workerUrl;
document.head.appendChild(preloadWorker);
}
catch (e) {
// Ignore preload errors
}
// Load viewer CSS if not already loaded
if (!document.getElementById('pdfjs-viewer-styles')) {
link = document.createElement('link');
link.id = 'pdfjs-viewer-styles';
link.rel = 'stylesheet';
link.href = "https://cdn.jsdelivr.net/npm/pdfjs-dist@".concat(pdfjsLib.version, "/web/pdf_viewer.css");
document.head.appendChild(link);
customStyles = document.createElement('style');
customStyles.id = 'pdf-custom-styles';
customStyles.textContent = "\n .pdf-container {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n overflow: auto;\n }\n .pdfViewer .page {\n margin: var(--captidejs-page-margin, 15px auto);\n box-shadow: var(--captidejs-page-shadow, 0 2px 5px rgba(0, 0, 0, 0.2));\n border: var(--captidejs-page-border, none);\n border-width: var(--captidejs-page-border-width, 0);\n }\n .pdfViewer .page.highlighted {\n box-shadow: var(--captidejs-page-highlight-shadow, 0 0 15px 5px rgba(255, 235, 59, 0.5));\n }\n /*\n PDF.js renders visible content to a canvas and selection/search highlights via an HTML text layer.\n Some apps/global CSS can accidentally offset the text layer; keep it anchored to the page.\n */\n .pdfViewer .textLayer {\n top: 0px !important;\n left: 0px !important;\n }\n /* Ensure text spans keep PDF.js expected positioning/metrics (defensive against global CSS). */\n .pdfViewer .textLayer > span {\n transform-origin: 0% 0% !important;\n line-height: 1 !important;\n }\n /* Override PDF.js search highlight colors to yellow */\n .pdfViewer .textLayer .highlight {\n background-color: rgba(255, 235, 59, 0.3) !important;\n color: inherit !important;\n }\n .pdfViewer .textLayer .highlight.selected {\n background-color: rgba(255, 235, 59, 0.5) !important;\n }\n ";
document.head.appendChild(customStyles);
}
if (mounted) {
setPdfJsLoaded(true);
}
return [3 /*break*/, 4];
case 3:
err_1 = _a.sent();
if (mounted) {
setError("Failed to load PDF.js: ".concat(err_1 instanceof Error ? err_1.message : String(err_1)));
}
return [3 /*break*/, 4];
case 4: return [2 /*return*/];
}
});
}); };
loadPdfJs();
return function () {
mounted = false;
};
}, [isBrowser]);
// Load and render PDF when document changes and PDF.js is loaded
React.useEffect(function () {
if (!isBrowser || !(pdfDocument === null || pdfDocument === void 0 ? void 0 : pdfDocument.originalFileUrl) || !pdfJsLoaded || !viewerContainerRef.current)
return;
var mounted = true;
var pdfViewerInstance = null;
var pdfDocumentInstance = null;
var eventBusInstance = null;
var loadAndRenderPdf = function () { return __awaiter(void 0, void 0, void 0, function () {
var pdfjsLib, viewerModule, viewerContainer, viewerElement, pdfLinkService, loadingTask, err_2;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 4, , 5]);
setError(null);
return [4 /*yield*/, Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require('pdfjs-dist')); })];
case 1:
pdfjsLib = _a.sent();
return [4 /*yield*/, Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require('pdfjs-dist/web/pdf_viewer.mjs')); })];
case 2:
viewerModule = _a.sent();
if (!mounted || !viewerContainerRef.current)
return [2 /*return*/];
// Clear previous viewer
viewerContainerRef.current.innerHTML = '';
viewerContainer = document.createElement('div');
viewerContainer.className = 'pdf-container';
viewerElement = document.createElement('div');
viewerElement.className = 'pdfViewer';
viewerContainer.appendChild(viewerElement);
viewerContainerRef.current.appendChild(viewerContainer);
// Create event bus
eventBusInstance = new viewerModule.EventBus();
pdfLinkService = new viewerModule.PDFLinkService({
eventBus: eventBusInstance,
});
// Create viewer
pdfViewerInstance = new viewerModule.PDFViewer({
container: viewerContainer,
viewer: viewerElement,
eventBus: eventBusInstance,
linkService: pdfLinkService,
// Use the non-enhanced text layer mode; it tends to be more reliable across PDFs and CSS environments.
textLayerMode: 1,
removePageBorders: false,
});
pdfLinkService.setViewer(pdfViewerInstance);
// Set up event listeners
eventBusInstance.on('pagesinit', function () {
// Viewer is ready once pages are initialized
setNumPages((pdfViewerInstance === null || pdfViewerInstance === void 0 ? void 0 : pdfViewerInstance.pagesCount) || (pdfDocumentInstance === null || pdfDocumentInstance === void 0 ? void 0 : pdfDocumentInstance.numPages) || 0);
setViewer(pdfViewerInstance);
// Set initial zoom level
if (pdfViewerInstance && zoomLevel !== undefined) {
if (typeof zoomLevel === 'string') {
pdfViewerInstance.currentScaleValue = zoomLevel;
}
else {
pdfViewerInstance.currentScale = zoomLevel;
}
}
// Navigate to specific page if pageNumber is provided
if (typeof effectivePageNumber === 'number' && pdfViewerInstance && mounted) {
var pageNum_1 = effectivePageNumber;
if (pageNum_1 >= 1 && pageNum_1 <= pdfViewerInstance.pagesCount) {
try {
pdfViewerInstance.currentPageNumber = Number(pageNum_1);
// Highlight the page after a short delay
setTimeout(function () {
var _a;
if (mounted && pdfViewerInstance) {
var pageDiv = (_a = pdfViewerInstance.getPageView(pageNum_1 - 1)) === null || _a === void 0 ? void 0 : _a.div;
if (pageDiv) {
pageDiv.classList.add('highlighted');
pageDiv.scrollIntoView({
behavior: 'smooth',
block: 'center'
});
}
}
}, 200);
}
catch (err) {
console.warn('Failed to navigate to page:', pageNum_1, err);
}
}
}
});
eventBusInstance.on('pagechanging', function (evt) {
if (mounted) {
var pageNumber_1 = parseInt(evt.pageNumber, 10) || 1;
setCurrentPage(pageNumber_1);
}
});
loadingTask = pdfjsLib.getDocument({
url: pdfDocument.originalFileUrl,
withCredentials: false,
cMapUrl: "https://cdn.jsdelivr.net/npm/pdfjs-dist@".concat(pdfjsLib.version, "/cmaps/"),
cMapPacked: true,
// Provide standard font data; missing font metrics are a common cause of misaligned text layer selection/highlights.
standardFontDataUrl: "https://cdn.jsdelivr.net/npm/pdfjs-dist@".concat(pdfjsLib.version, "/standard_fonts/"),
});
return [4 /*yield*/, loadingTask.promise];
case 3:
pdfDocumentInstance = _a.sent();
if (!mounted || !pdfViewerInstance)
return [2 /*return*/];
// Set the document in the viewer
pdfViewerInstance.setDocument(pdfDocumentInstance);
pdfLinkService.setDocument(pdfDocumentInstance);
// Set zoom after document is loaded - with delay to ensure pages are ready
if (pdfViewerInstance && zoomLevel !== undefined && pdfViewerInstance.pagesCount > 0) {
setTimeout(function () {
if (pdfViewerInstance && mounted && pdfViewerInstance.pagesCount > 0) {
try {
if (typeof zoomLevel === 'string') {
pdfViewerInstance.currentScaleValue = zoomLevel;
}
else {
pdfViewerInstance.currentScale = zoomLevel;
}
}
catch (err) {
// Silently handle zoom setting errors
console.warn('Failed to set zoom level:', err);
}
}
}, 100);
}
return [3 /*break*/, 5];
case 4:
err_2 = _a.sent();
if (mounted) {
setError("Failed to load or render PDF: ".concat(err_2 instanceof Error ? err_2.message : String(err_2)));
}
return [3 /*break*/, 5];
case 5: return [2 /*return*/];
}
});
}); };
loadAndRenderPdf();
return function () {
mounted = false;
// Clean up
if (eventBusInstance) {
eventBusInstance.off('pagesinit');
eventBusInstance.off('pagechanging');
}
if (pdfDocumentInstance) {
pdfDocumentInstance.destroy();
}
};
}, [pdfDocument === null || pdfDocument === void 0 ? void 0 : pdfDocument.originalFileUrl, pdfJsLoaded, isBrowser, effectivePageNumber]);
// Handle text highlighting when citationSnippet changes
React.useEffect(function () {
if (citationSnippet && viewer && !isLoading) {
// Check if we already have a highlight for this exact text and it's still connected
if (currentHighlight &&
currentHighlight.text === citationSnippet &&
currentHighlight.element.isConnected) {
return;
}
// If highlight exists but is disconnected, remove it first
if (currentHighlight && !currentHighlight.element.isConnected) {
removeCurrentHighlight();
}
var targetPage_1;
if (typeof effectivePageNumber === 'number') {
targetPage_1 = effectivePageNumber;
}
(function () { return __awaiter(void 0, void 0, void 0, function () {
var newHighlight;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, createRectangleHighlight({
searchText: citationSnippet,
pdfViewerInstance: viewer,
targetPage: targetPage_1,
currentHighlight: currentHighlight,
})];
case 1:
newHighlight = _a.sent();
if (newHighlight) {
removeCurrentHighlight();
setCurrentHighlight(newHighlight);
}
return [2 /*return*/];
}
});
}); })();
}
}, [citationSnippet, viewer, isLoading, effectivePageNumber, currentHighlight, removeCurrentHighlight]);
// Clean up highlight when citationSnippet becomes null
React.useEffect(function () {
if (!citationSnippet && currentHighlight) {
removeCurrentHighlight();
}
}, [citationSnippet, currentHighlight, removeCurrentHighlight]);
// Handle pageNumber changes after viewer is loaded
React.useEffect(function () {
if (typeof effectivePageNumber === 'number' && viewer && !isLoading && numPages > 0) {
var pageNum_2 = effectivePageNumber;
// Validate page number is within bounds
if (pageNum_2 >= 1 && pageNum_2 <= viewer.pagesCount) {
try {
view