nodebb-plugin-pdf-secure2 1.2.30

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,184 @@
1
+ import { getDocument, GlobalWorkerOptions } from './pdf.min.mjs';
2
+
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>&#8249; Prev</button>' +
47
+ '<span class="pdf-secure-page-info"></span>' +
48
+ '<button class="pdf-secure-next" disabled>Next &#8250;</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
+ }
58
+
59
+ async function loadPdf(container, filename) {
60
+ var loadingEl = container.querySelector('.pdf-secure-loading');
61
+ var errorEl = container.querySelector('.pdf-secure-error');
62
+ var canvas = container.querySelector('.pdf-secure-canvas');
63
+ var footer = container.querySelector('.pdf-secure-inline-footer');
64
+ var prevBtn = container.querySelector('.pdf-secure-prev');
65
+ var nextBtn = container.querySelector('.pdf-secure-next');
66
+ var pageInfo = container.querySelector('.pdf-secure-page-info');
67
+ var bodyEl = container.querySelector('.pdf-secure-inline-body');
68
+
69
+ function showError(msg) {
70
+ console.error('[PDF-Secure][Viewer] ERROR ' + filename + ':', msg);
71
+ loadingEl.style.display = 'none';
72
+ canvas.style.display = 'none';
73
+ errorEl.style.display = 'flex';
74
+ errorEl.textContent = msg;
75
+ }
76
+
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 + ')');
90
+ return;
91
+ }
92
+
93
+ var result = await nonceRes.json();
94
+ var nonce = result.response.nonce;
95
+ console.log('[PDF-Secure][Viewer] Step 1 - Nonce:', nonce);
96
+
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);
100
+
101
+ var pdfRes = await fetch(pdfUrl, { credentials: 'same-origin' });
102
+ console.log('[PDF-Secure][Viewer] Step 2 - Status:', pdfRes.status);
103
+
104
+ if (!pdfRes.ok) {
105
+ showError('Failed to load PDF data (' + pdfRes.status + ')');
106
+ return;
107
+ }
108
+
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);
117
+
118
+ loadingEl.style.display = 'none';
119
+ canvas.style.display = 'block';
120
+
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;
157
+ }
158
+
159
+ await renderPage(1);
160
+
161
+ if (totalPages > 1) {
162
+ footer.style.display = 'flex';
163
+ prevBtn.addEventListener('click', function () {
164
+ if (currentPage > 1) renderPage(currentPage - 1);
165
+ });
166
+ nextBtn.addEventListener('click', function () {
167
+ if (currentPage < totalPages) renderPage(currentPage + 1);
168
+ });
169
+ console.log('[PDF-Secure][Viewer] Navigation enabled (' + totalPages + ' pages)');
170
+ }
171
+
172
+ console.log('[PDF-Secure][Viewer] DONE for:', filename);
173
+ } catch (err) {
174
+ console.error('[PDF-Secure][Viewer] CATCH:', err);
175
+ console.error('[PDF-Secure][Viewer] Stack:', err.stack);
176
+ showError('Failed to load PDF.');
177
+ }
178
+ }
179
+
180
+ function escapeHtml(str) {
181
+ var d = document.createElement('div');
182
+ d.textContent = str;
183
+ return d.innerHTML;
184
+ }
@@ -0,0 +1,142 @@
1
+ /* PDF Secure Viewer — Inline Embed Styles for NodeBB */
2
+
3
+ /* AGGRESSIVE: Hide NodeBB's large file preview SVG icons */
4
+ [component="post/content"]>p>svg,
5
+ [component="post/content"]>svg,
6
+ [component="post/content"] svg[viewBox="0 0 24 24"]:not(.pdf-secure-embed svg) {
7
+ display: none !important;
8
+ }
9
+
10
+ /* Hide any standalone large SVG that looks like a file icon */
11
+ [component="post/content"]>p:has(svg[viewBox]) {
12
+
13
+ /* If paragraph only contains an SVG, hide the whole paragraph */
14
+ &:not(:has(.pdf-secure-embed)):not(:has(a)):not(:has(span)):not(:has(img)) {
15
+ display: none !important;
16
+ }
17
+ }
18
+
19
+ /* Embedded PDF Container */
20
+ .pdf-secure-embed {
21
+ margin: 16px 0;
22
+ border-radius: 12px;
23
+ overflow: hidden;
24
+ background: #1f1f1f;
25
+ border: 1px solid rgba(255, 255, 255, 0.1);
26
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25);
27
+ }
28
+
29
+ /* Header */
30
+ .pdf-secure-embed-header {
31
+ display: flex;
32
+ align-items: center;
33
+ justify-content: space-between;
34
+ padding: 10px 16px;
35
+ background: linear-gradient(135deg, #2d2d2d 0%, #252525 100%);
36
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
37
+ }
38
+
39
+ .pdf-secure-embed-title {
40
+ display: flex;
41
+ align-items: center;
42
+ gap: 10px;
43
+ color: #fff;
44
+ font-size: 14px;
45
+ font-weight: 500;
46
+ }
47
+
48
+ .pdf-secure-embed-title svg {
49
+ width: 20px;
50
+ height: 20px;
51
+ fill: #e81224;
52
+ flex-shrink: 0;
53
+ /* Make sure our header icon is visible */
54
+ display: inline-block !important;
55
+ }
56
+
57
+ .pdf-secure-embed-title span {
58
+ white-space: nowrap;
59
+ overflow: hidden;
60
+ text-overflow: ellipsis;
61
+ max-width: 400px;
62
+ }
63
+
64
+ .pdf-secure-embed-actions {
65
+ display: flex;
66
+ gap: 8px;
67
+ }
68
+
69
+ .pdf-secure-fullscreen-btn {
70
+ display: flex;
71
+ align-items: center;
72
+ justify-content: center;
73
+ width: 32px;
74
+ height: 32px;
75
+ background: rgba(255, 255, 255, 0.08);
76
+ border: none;
77
+ border-radius: 6px;
78
+ cursor: pointer;
79
+ transition: all 0.2s;
80
+ }
81
+
82
+ .pdf-secure-fullscreen-btn:hover {
83
+ background: rgba(255, 255, 255, 0.15);
84
+ }
85
+
86
+ .pdf-secure-fullscreen-btn svg {
87
+ width: 18px;
88
+ height: 18px;
89
+ fill: #fff;
90
+ display: inline-block !important;
91
+ }
92
+
93
+ /* Viewer Body */
94
+ .pdf-secure-embed-body {
95
+ position: relative;
96
+ width: 100%;
97
+ height: 600px;
98
+ background: #525659;
99
+ }
100
+
101
+ .pdf-secure-iframe {
102
+ width: 100%;
103
+ height: 100%;
104
+ border: none;
105
+ display: block;
106
+ }
107
+
108
+ /* Responsive */
109
+ @media (max-width: 768px) {
110
+ .pdf-secure-embed-body {
111
+ height: 450px;
112
+ }
113
+
114
+ .pdf-secure-embed-title span {
115
+ max-width: 200px;
116
+ }
117
+ }
118
+
119
+ @media (max-width: 480px) {
120
+ .pdf-secure-embed-body {
121
+ height: 350px;
122
+ }
123
+
124
+ .pdf-secure-embed-header {
125
+ padding: 8px 12px;
126
+ }
127
+
128
+ .pdf-secure-embed-title {
129
+ font-size: 13px;
130
+ }
131
+
132
+ .pdf-secure-embed-title span {
133
+ max-width: 150px;
134
+ }
135
+ }
136
+
137
+ /* Anti-print */
138
+ @media print {
139
+ .pdf-secure-embed {
140
+ display: none !important;
141
+ }
142
+ }
@@ -0,0 +1,43 @@
1
+ <div class="acp-page-container">
2
+ <!-- IMPORT admin/partials/settings/header.tpl -->
3
+
4
+ <div class="row m-0">
5
+ <div id="spy-container" class="col-12 col-md-8 px-0 mb-4" tabindex="0">
6
+ <form role="form" class="pdf-secure-settings">
7
+ <div class="mb-4">
8
+ <h5 class="fw-bold tracking-tight settings-header">PDF Secure Viewer Settings</h5>
9
+
10
+ <p class="lead">
11
+ Configure the secure PDF viewer plugin settings below.
12
+ </p>
13
+
14
+ <div class="mb-3">
15
+ <label class="form-label" for="premiumGroup">Premium Group Name</label>
16
+ <input type="text" id="premiumGroup" name="premiumGroup" title="Premium Group Name" class="form-control" placeholder="Premium" value="Premium">
17
+ <div class="form-text">Users in this group can view full PDFs with all tools. Others can only see the first page.</div>
18
+ </div>
19
+
20
+ <div class="mb-3">
21
+ <label class="form-label" for="vipGroup">VIP Group Name</label>
22
+ <input type="text" id="vipGroup" name="vipGroup" title="VIP Group Name" class="form-control" placeholder="VIP" value="VIP">
23
+ <div class="form-text">Users in this group get all Premium features plus VIP badge and early access to new features.</div>
24
+ </div>
25
+
26
+ <div class="mb-3">
27
+ <label class="form-label" for="liteGroup">Lite Group Name</label>
28
+ <input type="text" id="liteGroup" name="liteGroup" title="Lite Group Name" class="form-control" placeholder="Lite" value="Lite">
29
+ <div class="form-text">Users in this group can view full PDFs but only with zoom and fullscreen. No annotations, sidebar, or other tools.</div>
30
+ </div>
31
+
32
+ <div class="form-check form-switch mb-3">
33
+ <input type="checkbox" class="form-check-input" id="watermarkEnabled" name="watermarkEnabled">
34
+ <label for="watermarkEnabled" class="form-check-label">Enable Watermark</label>
35
+ <div class="form-text">Show a watermark overlay on PDF pages.</div>
36
+ </div>
37
+ </div>
38
+ </form>
39
+ </div>
40
+
41
+ <!-- IMPORT admin/partials/settings/toc.tpl -->
42
+ </div>
43
+ </div>