nodebb-plugin-pdf-secure 1.0.5 → 1.0.6
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/library.js +2 -6
- package/package.json +1 -1
- package/static/lib/main.js +11 -141
- package/static/lib/viewer.js +137 -80
package/library.js
CHANGED
|
@@ -43,12 +43,8 @@ plugin.addRoutes = async ({ router, middleware, helpers }) => {
|
|
|
43
43
|
return helpers.formatApiResponse(400, res, new Error('Invalid file'));
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
//
|
|
47
|
-
const
|
|
48
|
-
const premiumGroup = settings.premiumGroup || 'Premium';
|
|
49
|
-
|
|
50
|
-
// Check if user is in premium group
|
|
51
|
-
const isPremium = await groups.isMember(req.uid, premiumGroup);
|
|
46
|
+
// Premium check disabled for testing — everyone gets full PDF
|
|
47
|
+
const isPremium = true;
|
|
52
48
|
|
|
53
49
|
const nonce = nonceStore.generate(req.uid, safeName, isPremium);
|
|
54
50
|
|
package/package.json
CHANGED
package/static/lib/main.js
CHANGED
|
@@ -1,144 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
});
|
|
15
|
-
} catch (err) {
|
|
16
|
-
console.error('[PDF-Secure] Failed to initialize:', err);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function interceptPdfLinks() {
|
|
20
|
-
const postContents = document.querySelectorAll('[component="post/content"]');
|
|
21
|
-
console.log('[PDF-Secure] Found ' + postContents.length + ' post content areas');
|
|
22
|
-
|
|
23
|
-
postContents.forEach((content, idx) => {
|
|
24
|
-
const pdfLinks = content.querySelectorAll('a[href$=".pdf"], a[href$=".PDF"]');
|
|
25
|
-
console.log('[PDF-Secure] Post #' + idx + ': found ' + pdfLinks.length + ' PDF links');
|
|
26
|
-
|
|
27
|
-
pdfLinks.forEach((link) => {
|
|
28
|
-
if (link.dataset.pdfSecure) {
|
|
29
|
-
console.log('[PDF-Secure] Skipping already processed link:', link.href);
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
link.dataset.pdfSecure = 'true';
|
|
33
|
-
|
|
34
|
-
const href = link.getAttribute('href');
|
|
35
|
-
const parts = href.split('/');
|
|
36
|
-
const filename = parts[parts.length - 1];
|
|
37
|
-
console.log('[PDF-Secure] Processing link -> filename:', filename, 'href:', href);
|
|
38
|
-
|
|
39
|
-
// Create inline viewer container
|
|
40
|
-
const container = document.createElement('div');
|
|
41
|
-
container.className = 'pdf-secure-inline';
|
|
42
|
-
container.innerHTML =
|
|
43
|
-
'<div class="pdf-secure-inline-header">' +
|
|
44
|
-
'<i class="fa fa-file-pdf-o"></i> ' +
|
|
45
|
-
'<span class="pdf-secure-filename">' + escapeHtml(link.textContent || filename) + '</span>' +
|
|
46
|
-
'</div>' +
|
|
47
|
-
'<div class="pdf-secure-inline-body">' +
|
|
48
|
-
'<div class="pdf-secure-loading">Loading PDF...</div>' +
|
|
49
|
-
'<div class="pdf-secure-error"></div>' +
|
|
50
|
-
'<canvas class="pdf-secure-canvas"></canvas>' +
|
|
51
|
-
'</div>' +
|
|
52
|
-
'<div class="pdf-secure-inline-footer">' +
|
|
53
|
-
'<button class="pdf-secure-prev" disabled>‹ Prev</button>' +
|
|
54
|
-
'<span class="pdf-secure-page-info"></span>' +
|
|
55
|
-
'<button class="pdf-secure-next" disabled>Next ›</button>' +
|
|
56
|
-
'</div>' +
|
|
57
|
-
'<div class="pdf-secure-premium-banner">' +
|
|
58
|
-
'This is a preview (page 1 only). Upgrade to Premium for full access.' +
|
|
59
|
-
'</div>';
|
|
60
|
-
|
|
61
|
-
link.replaceWith(container);
|
|
62
|
-
console.log('[PDF-Secure] Replaced link with inline container for:', filename);
|
|
63
|
-
|
|
64
|
-
// Auto-load PDF immediately
|
|
65
|
-
loadPdf(container, filename);
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async function loadPdf(container, filename) {
|
|
71
|
-
var loadingEl = container.querySelector('.pdf-secure-loading');
|
|
72
|
-
var errorEl = container.querySelector('.pdf-secure-error');
|
|
73
|
-
|
|
74
|
-
function showError(msg) {
|
|
75
|
-
console.error('[PDF-Secure] ERROR for ' + filename + ':', msg);
|
|
76
|
-
loadingEl.style.display = 'none';
|
|
77
|
-
errorEl.style.display = 'flex';
|
|
78
|
-
errorEl.textContent = msg;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
try {
|
|
82
|
-
// Step 1: Fetch nonce
|
|
83
|
-
var nonceUrl = config.relative_path + '/api/v3/plugins/pdf-secure/nonce?file=' + encodeURIComponent(filename);
|
|
84
|
-
console.log('[PDF-Secure] Step 1 - Fetching nonce from:', nonceUrl);
|
|
85
|
-
|
|
86
|
-
var nonceRes = await fetch(nonceUrl, {
|
|
87
|
-
credentials: 'same-origin',
|
|
88
|
-
headers: { 'x-csrf-token': config.csrf_token },
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
console.log('[PDF-Secure] Step 1 - Nonce response status:', nonceRes.status);
|
|
92
|
-
|
|
93
|
-
if (!nonceRes.ok) {
|
|
94
|
-
showError(nonceRes.status === 401 ? 'Log in to view this PDF.' : 'Failed to load PDF. Status: ' + nonceRes.status);
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
var result = await nonceRes.json();
|
|
99
|
-
console.log('[PDF-Secure] Step 1 - Nonce result:', JSON.stringify(result));
|
|
100
|
-
|
|
101
|
-
var nonce = result.response.nonce;
|
|
102
|
-
var isPremium = result.response.isPremium;
|
|
103
|
-
console.log('[PDF-Secure] Step 1 - nonce:', nonce, 'isPremium:', isPremium);
|
|
104
|
-
|
|
105
|
-
// Step 2: Fetch PDF binary
|
|
106
|
-
var pdfUrl = config.relative_path + '/api/v3/plugins/pdf-secure/pdf-data?nonce=' + encodeURIComponent(nonce);
|
|
107
|
-
console.log('[PDF-Secure] Step 2 - Fetching PDF binary from:', pdfUrl);
|
|
108
|
-
|
|
109
|
-
var pdfRes = await fetch(pdfUrl, { credentials: 'same-origin' });
|
|
110
|
-
console.log('[PDF-Secure] Step 2 - PDF response status:', pdfRes.status, 'content-type:', pdfRes.headers.get('content-type'));
|
|
111
|
-
|
|
112
|
-
if (!pdfRes.ok) {
|
|
113
|
-
showError('Failed to load PDF data. Status: ' + pdfRes.status);
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
var pdfArrayBuffer = await pdfRes.arrayBuffer();
|
|
118
|
-
console.log('[PDF-Secure] Step 2 - PDF binary loaded, size:', pdfArrayBuffer.byteLength, 'bytes');
|
|
119
|
-
|
|
120
|
-
// Step 3: Dynamic import viewer.js
|
|
121
|
-
var viewerUrl = config.relative_path + '/plugins/nodebb-plugin-pdf-secure/static/lib/viewer.js';
|
|
122
|
-
console.log('[PDF-Secure] Step 3 - Importing viewer from:', viewerUrl);
|
|
123
|
-
|
|
124
|
-
var viewer = await import(viewerUrl);
|
|
125
|
-
console.log('[PDF-Secure] Step 3 - Viewer module loaded, exports:', Object.keys(viewer));
|
|
126
|
-
|
|
127
|
-
// Step 4: Initialize inline viewer
|
|
128
|
-
console.log('[PDF-Secure] Step 4 - Calling initInlineViewer...');
|
|
129
|
-
await viewer.initInlineViewer(container, pdfArrayBuffer, isPremium);
|
|
130
|
-
console.log('[PDF-Secure] Step 4 - initInlineViewer completed successfully for:', filename);
|
|
131
|
-
|
|
132
|
-
} catch (err) {
|
|
133
|
-
console.error('[PDF-Secure] CATCH error for ' + filename + ':', err);
|
|
134
|
-
console.error('[PDF-Secure] Error stack:', err.stack);
|
|
135
|
-
showError('Failed to load PDF.');
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function escapeHtml(str) {
|
|
140
|
-
var div = document.createElement('div');
|
|
141
|
-
div.textContent = str;
|
|
142
|
-
return div.innerHTML;
|
|
143
|
-
}
|
|
3
|
+
// Bootstrapper: NodeBB bundles this into nodebb.min.js (AMD),
|
|
4
|
+
// so dynamic import() won't work here. Instead, inject a real
|
|
5
|
+
// ES module script that can import pdf.min.mjs natively.
|
|
6
|
+
(function () {
|
|
7
|
+
console.log('[PDF-Secure] main.js bootstrapper loaded');
|
|
8
|
+
var s = document.createElement('script');
|
|
9
|
+
s.type = 'module';
|
|
10
|
+
s.src = (window.config ? config.relative_path : '') +
|
|
11
|
+
'/plugins/nodebb-plugin-pdf-secure/static/lib/viewer.js';
|
|
12
|
+
document.head.appendChild(s);
|
|
13
|
+
console.log('[PDF-Secure] viewer.js module script injected');
|
|
144
14
|
})();
|
package/static/lib/viewer.js
CHANGED
|
@@ -1,112 +1,163 @@
|
|
|
1
|
-
console.log('[PDF-Secure][Viewer] viewer.js module loading...');
|
|
2
|
-
|
|
3
1
|
import { getDocument, GlobalWorkerOptions } from './pdf.min.mjs';
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
GlobalWorkerOptions.workerSrc
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
console.log('[PDF-Secure][Viewer]
|
|
3
|
+
GlobalWorkerOptions.workerSrc = new URL('./pdf.worker.min.mjs', import.meta.url).href;
|
|
4
|
+
console.log('[PDF-Secure][Viewer] ES module loaded, worker:', GlobalWorkerOptions.workerSrc);
|
|
5
|
+
|
|
6
|
+
// Listen for NodeBB SPA navigations (jQuery event)
|
|
7
|
+
$(window).on('action:ajaxify.end', function () {
|
|
8
|
+
console.log('[PDF-Secure][Viewer] action:ajaxify.end fired');
|
|
9
|
+
interceptPdfLinks();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
// Also run immediately for the current page
|
|
13
|
+
console.log('[PDF-Secure][Viewer] Running initial interceptPdfLinks...');
|
|
14
|
+
interceptPdfLinks();
|
|
15
|
+
|
|
16
|
+
function interceptPdfLinks() {
|
|
17
|
+
var postContents = document.querySelectorAll('[component="post/content"]');
|
|
18
|
+
console.log('[PDF-Secure][Viewer] Found ' + postContents.length + ' post areas');
|
|
19
|
+
|
|
20
|
+
postContents.forEach(function (content, idx) {
|
|
21
|
+
var pdfLinks = content.querySelectorAll('a[href$=".pdf"], a[href$=".PDF"]');
|
|
22
|
+
console.log('[PDF-Secure][Viewer] Post #' + idx + ': ' + pdfLinks.length + ' PDF links');
|
|
23
|
+
|
|
24
|
+
pdfLinks.forEach(function (link) {
|
|
25
|
+
if (link.dataset.pdfSecure) return;
|
|
26
|
+
link.dataset.pdfSecure = 'true';
|
|
27
|
+
|
|
28
|
+
var href = link.getAttribute('href');
|
|
29
|
+
var parts = href.split('/');
|
|
30
|
+
var filename = parts[parts.length - 1];
|
|
31
|
+
console.log('[PDF-Secure][Viewer] Processing:', filename);
|
|
32
|
+
|
|
33
|
+
var container = document.createElement('div');
|
|
34
|
+
container.className = 'pdf-secure-inline';
|
|
35
|
+
container.innerHTML =
|
|
36
|
+
'<div class="pdf-secure-inline-header">' +
|
|
37
|
+
'<i class="fa fa-file-pdf-o"></i> ' +
|
|
38
|
+
'<span class="pdf-secure-filename">' + escapeHtml(link.textContent || filename) + '</span>' +
|
|
39
|
+
'</div>' +
|
|
40
|
+
'<div class="pdf-secure-inline-body">' +
|
|
41
|
+
'<div class="pdf-secure-loading">Loading PDF...</div>' +
|
|
42
|
+
'<div class="pdf-secure-error"></div>' +
|
|
43
|
+
'<canvas class="pdf-secure-canvas"></canvas>' +
|
|
44
|
+
'</div>' +
|
|
45
|
+
'<div class="pdf-secure-inline-footer">' +
|
|
46
|
+
'<button class="pdf-secure-prev" disabled>‹ Prev</button>' +
|
|
47
|
+
'<span class="pdf-secure-page-info"></span>' +
|
|
48
|
+
'<button class="pdf-secure-next" disabled>Next ›</button>' +
|
|
49
|
+
'</div>';
|
|
50
|
+
|
|
51
|
+
link.replaceWith(container);
|
|
52
|
+
console.log('[PDF-Secure][Viewer] Container created for:', filename);
|
|
53
|
+
|
|
54
|
+
loadPdf(container, filename);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}
|
|
11
58
|
|
|
12
|
-
|
|
13
|
-
var ctx = canvas.getContext('2d');
|
|
59
|
+
async function loadPdf(container, filename) {
|
|
14
60
|
var loadingEl = container.querySelector('.pdf-secure-loading');
|
|
15
61
|
var errorEl = container.querySelector('.pdf-secure-error');
|
|
62
|
+
var canvas = container.querySelector('.pdf-secure-canvas');
|
|
16
63
|
var footer = container.querySelector('.pdf-secure-inline-footer');
|
|
17
64
|
var prevBtn = container.querySelector('.pdf-secure-prev');
|
|
18
65
|
var nextBtn = container.querySelector('.pdf-secure-next');
|
|
19
66
|
var pageInfo = container.querySelector('.pdf-secure-page-info');
|
|
20
|
-
var premiumBanner = container.querySelector('.pdf-secure-premium-banner');
|
|
21
67
|
var bodyEl = container.querySelector('.pdf-secure-inline-body');
|
|
22
68
|
|
|
23
|
-
console.log('[PDF-Secure][Viewer] DOM elements found:', {
|
|
24
|
-
canvas: !!canvas,
|
|
25
|
-
ctx: !!ctx,
|
|
26
|
-
loadingEl: !!loadingEl,
|
|
27
|
-
errorEl: !!errorEl,
|
|
28
|
-
footer: !!footer,
|
|
29
|
-
prevBtn: !!prevBtn,
|
|
30
|
-
nextBtn: !!nextBtn,
|
|
31
|
-
pageInfo: !!pageInfo,
|
|
32
|
-
premiumBanner: !!premiumBanner,
|
|
33
|
-
bodyEl: !!bodyEl,
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
var pdfDoc = null;
|
|
37
|
-
var currentPage = 1;
|
|
38
|
-
var totalPages = 0;
|
|
39
|
-
var rendering = false;
|
|
40
|
-
|
|
41
|
-
// Security: scoped to container
|
|
42
|
-
container.addEventListener('contextmenu', function (e) { e.preventDefault(); });
|
|
43
|
-
container.addEventListener('dragstart', function (e) { e.preventDefault(); });
|
|
44
|
-
container.addEventListener('selectstart', function (e) { e.preventDefault(); });
|
|
45
|
-
|
|
46
69
|
function showError(msg) {
|
|
47
|
-
console.error('[PDF-Secure][Viewer]
|
|
70
|
+
console.error('[PDF-Secure][Viewer] ERROR ' + filename + ':', msg);
|
|
48
71
|
loadingEl.style.display = 'none';
|
|
49
72
|
canvas.style.display = 'none';
|
|
50
73
|
errorEl.style.display = 'flex';
|
|
51
74
|
errorEl.textContent = msg;
|
|
52
75
|
}
|
|
53
76
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
77
|
+
try {
|
|
78
|
+
// Step 1: Fetch nonce
|
|
79
|
+
var nonceUrl = config.relative_path + '/api/v3/plugins/pdf-secure/nonce?file=' + encodeURIComponent(filename);
|
|
80
|
+
console.log('[PDF-Secure][Viewer] Step 1 - Nonce request:', nonceUrl);
|
|
81
|
+
|
|
82
|
+
var nonceRes = await fetch(nonceUrl, {
|
|
83
|
+
credentials: 'same-origin',
|
|
84
|
+
headers: { 'x-csrf-token': config.csrf_token },
|
|
85
|
+
});
|
|
86
|
+
console.log('[PDF-Secure][Viewer] Step 1 - Status:', nonceRes.status);
|
|
87
|
+
|
|
88
|
+
if (!nonceRes.ok) {
|
|
89
|
+
showError(nonceRes.status === 401 ? 'Log in to view this PDF.' : 'Failed to load PDF (' + nonceRes.status + ')');
|
|
57
90
|
return;
|
|
58
91
|
}
|
|
59
|
-
rendering = true;
|
|
60
|
-
console.log('[PDF-Secure][Viewer] renderPage(' + pageNum + ') started');
|
|
61
92
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
console.log('[PDF-Secure][Viewer] Container width for scaling:', containerWidth);
|
|
93
|
+
var result = await nonceRes.json();
|
|
94
|
+
var nonce = result.response.nonce;
|
|
95
|
+
console.log('[PDF-Secure][Viewer] Step 1 - Nonce:', nonce);
|
|
66
96
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
97
|
+
// Step 2: Fetch PDF binary
|
|
98
|
+
var pdfUrl = config.relative_path + '/api/v3/plugins/pdf-secure/pdf-data?nonce=' + encodeURIComponent(nonce);
|
|
99
|
+
console.log('[PDF-Secure][Viewer] Step 2 - PDF request:', pdfUrl);
|
|
70
100
|
|
|
71
|
-
|
|
101
|
+
var pdfRes = await fetch(pdfUrl, { credentials: 'same-origin' });
|
|
102
|
+
console.log('[PDF-Secure][Viewer] Step 2 - Status:', pdfRes.status);
|
|
72
103
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
await page.render({ canvasContext: ctx, viewport: scaledViewport }).promise;
|
|
77
|
-
|
|
78
|
-
currentPage = pageNum;
|
|
79
|
-
pageInfo.textContent = currentPage + ' / ' + totalPages;
|
|
80
|
-
prevBtn.disabled = currentPage <= 1;
|
|
81
|
-
nextBtn.disabled = currentPage >= totalPages;
|
|
82
|
-
|
|
83
|
-
console.log('[PDF-Secure][Viewer] renderPage(' + pageNum + ') completed successfully');
|
|
84
|
-
} catch (err) {
|
|
85
|
-
console.error('[PDF-Secure][Viewer] renderPage error:', err);
|
|
86
|
-
showError('Error rendering page.');
|
|
104
|
+
if (!pdfRes.ok) {
|
|
105
|
+
showError('Failed to load PDF data (' + pdfRes.status + ')');
|
|
106
|
+
return;
|
|
87
107
|
}
|
|
88
|
-
rendering = false;
|
|
89
|
-
}
|
|
90
108
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
109
|
+
var pdfArrayBuffer = await pdfRes.arrayBuffer();
|
|
110
|
+
console.log('[PDF-Secure][Viewer] Step 2 - PDF loaded:', pdfArrayBuffer.byteLength, 'bytes');
|
|
111
|
+
|
|
112
|
+
// Step 3: Render PDF
|
|
113
|
+
console.log('[PDF-Secure][Viewer] Step 3 - Rendering...');
|
|
114
|
+
var pdfDoc = await getDocument({ data: new Uint8Array(pdfArrayBuffer) }).promise;
|
|
115
|
+
var totalPages = pdfDoc.numPages;
|
|
116
|
+
console.log('[PDF-Secure][Viewer] Step 3 - Pages:', totalPages);
|
|
98
117
|
|
|
99
118
|
loadingEl.style.display = 'none';
|
|
100
119
|
canvas.style.display = 'block';
|
|
101
120
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
121
|
+
// Security: scoped to container
|
|
122
|
+
container.addEventListener('contextmenu', function (e) { e.preventDefault(); });
|
|
123
|
+
container.addEventListener('dragstart', function (e) { e.preventDefault(); });
|
|
124
|
+
container.addEventListener('selectstart', function (e) { e.preventDefault(); });
|
|
125
|
+
|
|
126
|
+
var ctx = canvas.getContext('2d');
|
|
127
|
+
var currentPage = 1;
|
|
128
|
+
var rendering = false;
|
|
129
|
+
|
|
130
|
+
async function renderPage(pageNum) {
|
|
131
|
+
if (rendering) return;
|
|
132
|
+
rendering = true;
|
|
133
|
+
console.log('[PDF-Secure][Viewer] renderPage(' + pageNum + ')');
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
var page = await pdfDoc.getPage(pageNum);
|
|
137
|
+
var containerWidth = bodyEl.clientWidth - 20;
|
|
138
|
+
var vp = page.getViewport({ scale: 1 });
|
|
139
|
+
var scale = Math.min(containerWidth / vp.width, 2.0);
|
|
140
|
+
var scaled = page.getViewport({ scale: scale });
|
|
141
|
+
|
|
142
|
+
canvas.width = scaled.width;
|
|
143
|
+
canvas.height = scaled.height;
|
|
144
|
+
|
|
145
|
+
await page.render({ canvasContext: ctx, viewport: scaled }).promise;
|
|
146
|
+
|
|
147
|
+
currentPage = pageNum;
|
|
148
|
+
pageInfo.textContent = currentPage + ' / ' + totalPages;
|
|
149
|
+
prevBtn.disabled = currentPage <= 1;
|
|
150
|
+
nextBtn.disabled = currentPage >= totalPages;
|
|
151
|
+
console.log('[PDF-Secure][Viewer] renderPage(' + pageNum + ') done, canvas:', scaled.width + 'x' + scaled.height);
|
|
152
|
+
} catch (err) {
|
|
153
|
+
console.error('[PDF-Secure][Viewer] Render error:', err);
|
|
154
|
+
showError('Error rendering page.');
|
|
155
|
+
}
|
|
156
|
+
rendering = false;
|
|
105
157
|
}
|
|
106
158
|
|
|
107
159
|
await renderPage(1);
|
|
108
160
|
|
|
109
|
-
// Show navigation if multi-page
|
|
110
161
|
if (totalPages > 1) {
|
|
111
162
|
footer.style.display = 'flex';
|
|
112
163
|
prevBtn.addEventListener('click', function () {
|
|
@@ -115,13 +166,19 @@ export async function initInlineViewer(container, pdfArrayBuffer, isPremium) {
|
|
|
115
166
|
nextBtn.addEventListener('click', function () {
|
|
116
167
|
if (currentPage < totalPages) renderPage(currentPage + 1);
|
|
117
168
|
});
|
|
118
|
-
console.log('[PDF-Secure][Viewer] Navigation enabled
|
|
119
|
-
} else {
|
|
120
|
-
console.log('[PDF-Secure][Viewer] Single page PDF, navigation hidden');
|
|
169
|
+
console.log('[PDF-Secure][Viewer] Navigation enabled (' + totalPages + ' pages)');
|
|
121
170
|
}
|
|
171
|
+
|
|
172
|
+
console.log('[PDF-Secure][Viewer] DONE for:', filename);
|
|
122
173
|
} catch (err) {
|
|
123
|
-
console.error('[PDF-Secure][Viewer]
|
|
124
|
-
console.error('[PDF-Secure][Viewer]
|
|
174
|
+
console.error('[PDF-Secure][Viewer] CATCH:', err);
|
|
175
|
+
console.error('[PDF-Secure][Viewer] Stack:', err.stack);
|
|
125
176
|
showError('Failed to load PDF.');
|
|
126
177
|
}
|
|
127
178
|
}
|
|
179
|
+
|
|
180
|
+
function escapeHtml(str) {
|
|
181
|
+
var d = document.createElement('div');
|
|
182
|
+
d.textContent = str;
|
|
183
|
+
return d.innerHTML;
|
|
184
|
+
}
|