nodebb-plugin-pdf-secure 1.0.9 → 1.1.1
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 +46 -0
- package/package.json +1 -1
- package/static/lib/main.js +43 -159
- package/static/style.less +76 -122
- package/{viewer.html → static/viewer.html} +131 -0
package/library.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fs = require('fs');
|
|
3
5
|
const meta = require.main.require('./src/meta');
|
|
4
6
|
const groups = require.main.require('./src/groups');
|
|
5
7
|
const routeHelpers = require.main.require('./src/routes/helpers');
|
|
@@ -26,6 +28,50 @@ plugin.init = async (params) => {
|
|
|
26
28
|
|
|
27
29
|
// Admin page route
|
|
28
30
|
routeHelpers.setupAdminPageRoute(router, '/admin/plugins/pdf-secure', controllers.renderAdminPage);
|
|
31
|
+
|
|
32
|
+
// Viewer page route (fullscreen Mozilla PDF.js viewer)
|
|
33
|
+
router.get('/plugins/pdf-secure/viewer', middleware.ensureLoggedIn, (req, res) => {
|
|
34
|
+
const { file } = req.query;
|
|
35
|
+
if (!file) {
|
|
36
|
+
return res.status(400).send('Missing file parameter');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Sanitize filename
|
|
40
|
+
const safeName = path.basename(file);
|
|
41
|
+
if (!safeName || !safeName.toLowerCase().endsWith('.pdf')) {
|
|
42
|
+
return res.status(400).send('Invalid file');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Serve the viewer template with security headers
|
|
46
|
+
res.set({
|
|
47
|
+
'X-Frame-Options': 'SAMEORIGIN',
|
|
48
|
+
'X-Content-Type-Options': 'nosniff',
|
|
49
|
+
'Cache-Control': 'no-store, no-cache, must-revalidate, private',
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Read and send the viewer HTML
|
|
53
|
+
const viewerPath = path.join(__dirname, 'static', 'viewer.html');
|
|
54
|
+
fs.readFile(viewerPath, 'utf8', (err, html) => {
|
|
55
|
+
if (err) {
|
|
56
|
+
return res.status(500).send('Viewer not found');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Inject the filename and config into the viewer
|
|
60
|
+
const injectedHtml = html
|
|
61
|
+
.replace('</head>', `
|
|
62
|
+
<script>
|
|
63
|
+
window.PDF_SECURE_CONFIG = {
|
|
64
|
+
filename: ${JSON.stringify(safeName)},
|
|
65
|
+
relativePath: ${JSON.stringify(req.app.get('relative_path') || '')},
|
|
66
|
+
csrfToken: ${JSON.stringify(req.csrfToken ? req.csrfToken() : '')}
|
|
67
|
+
};
|
|
68
|
+
</script>
|
|
69
|
+
</head>`)
|
|
70
|
+
.replace('id="uploadOverlay">', 'id="uploadOverlay" class="hidden">');
|
|
71
|
+
|
|
72
|
+
res.type('html').send(injectedHtml);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
29
75
|
};
|
|
30
76
|
|
|
31
77
|
plugin.addRoutes = async ({ router, middleware, helpers }) => {
|
package/package.json
CHANGED
package/static/lib/main.js
CHANGED
|
@@ -2,22 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
console.log('[PDF-Secure] main.js loaded');
|
|
4
4
|
|
|
5
|
-
//
|
|
6
|
-
(function () {
|
|
7
|
-
var base = (window.config ? config.relative_path : '') + '/plugins/nodebb-plugin-pdf-secure/static/lib';
|
|
8
|
-
var s = document.createElement('script');
|
|
9
|
-
s.type = 'module';
|
|
10
|
-
s.textContent =
|
|
11
|
-
'import { getDocument, GlobalWorkerOptions } from "' + base + '/pdf.min.mjs";\n' +
|
|
12
|
-
'GlobalWorkerOptions.workerSrc = "' + base + '/pdf.worker.min.mjs";\n' +
|
|
13
|
-
'window.__pdfSecureLib = { getDocument: getDocument };\n' +
|
|
14
|
-
'window.dispatchEvent(new CustomEvent("pdf-secure-lib-ready"));\n' +
|
|
15
|
-
'console.log("[PDF-Secure] PDF.js library ready");';
|
|
16
|
-
document.head.appendChild(s);
|
|
17
|
-
console.log('[PDF-Secure] PDF.js module injected');
|
|
18
|
-
})();
|
|
19
|
-
|
|
20
|
-
// Main plugin logic
|
|
5
|
+
// Main plugin logic - PDF links become inline embedded viewers
|
|
21
6
|
(async function () {
|
|
22
7
|
try {
|
|
23
8
|
var hooks = await app.require('hooks');
|
|
@@ -34,7 +19,7 @@ console.log('[PDF-Secure] main.js loaded');
|
|
|
34
19
|
function interceptPdfLinks() {
|
|
35
20
|
var postContents = document.querySelectorAll('[component="post/content"]');
|
|
36
21
|
|
|
37
|
-
postContents.forEach(function (content
|
|
22
|
+
postContents.forEach(function (content) {
|
|
38
23
|
var pdfLinks = content.querySelectorAll('a[href$=".pdf"], a[href$=".PDF"]');
|
|
39
24
|
|
|
40
25
|
pdfLinks.forEach(function (link) {
|
|
@@ -44,156 +29,55 @@ console.log('[PDF-Secure] main.js loaded');
|
|
|
44
29
|
var href = link.getAttribute('href');
|
|
45
30
|
var parts = href.split('/');
|
|
46
31
|
var filename = parts[parts.length - 1];
|
|
47
|
-
console.log('[PDF-Secure]
|
|
32
|
+
console.log('[PDF-Secure] Embedding:', filename);
|
|
48
33
|
|
|
34
|
+
// Create inline viewer container
|
|
49
35
|
var container = document.createElement('div');
|
|
50
|
-
container.className = 'pdf-secure-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
'</div>' +
|
|
36
|
+
container.className = 'pdf-secure-embed';
|
|
37
|
+
|
|
38
|
+
// Header with filename
|
|
39
|
+
var header = document.createElement('div');
|
|
40
|
+
header.className = 'pdf-secure-embed-header';
|
|
41
|
+
header.innerHTML =
|
|
42
|
+
'<div class="pdf-secure-embed-title">' +
|
|
43
|
+
'<svg viewBox="0 0 24 24"><path d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z" fill="currentColor"/></svg>' +
|
|
44
|
+
'<span>' + escapeHtml(link.textContent || filename) + '</span>' +
|
|
60
45
|
'</div>' +
|
|
61
|
-
'<div class="pdf-secure-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
'</div>' +
|
|
66
|
-
'<div class="pdf-secure-error"></div>' +
|
|
67
|
-
'<div class="pdf-secure-pages"></div>' +
|
|
46
|
+
'<div class="pdf-secure-embed-actions">' +
|
|
47
|
+
'<button class="pdf-secure-fullscreen-btn" title="Tam Ekran">' +
|
|
48
|
+
'<svg viewBox="0 0 24 24"><path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z" fill="currentColor"/></svg>' +
|
|
49
|
+
'</button>' +
|
|
68
50
|
'</div>';
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
loadingEl.style.display = 'none';
|
|
94
|
-
errorEl.style.display = 'flex';
|
|
95
|
-
errorEl.textContent = msg;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
try {
|
|
99
|
-
// 1. Nonce
|
|
100
|
-
var nonceRes = await fetch(
|
|
101
|
-
config.relative_path + '/api/v3/plugins/pdf-secure/nonce?file=' + encodeURIComponent(filename),
|
|
102
|
-
{ credentials: 'same-origin', headers: { 'x-csrf-token': config.csrf_token } }
|
|
103
|
-
);
|
|
104
|
-
if (!nonceRes.ok) {
|
|
105
|
-
showError(nonceRes.status === 401 ? 'Log in to view this PDF.' : 'Failed to load PDF (' + nonceRes.status + ')');
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
var result = await nonceRes.json();
|
|
109
|
-
var nonce = result.response.nonce;
|
|
110
|
-
console.log('[PDF-Secure] Nonce OK:', nonce);
|
|
111
|
-
|
|
112
|
-
// 2. PDF binary
|
|
113
|
-
var pdfRes = await fetch(
|
|
114
|
-
config.relative_path + '/api/v3/plugins/pdf-secure/pdf-data?nonce=' + encodeURIComponent(nonce),
|
|
115
|
-
{ credentials: 'same-origin' }
|
|
116
|
-
);
|
|
117
|
-
if (!pdfRes.ok) { showError('Failed to load PDF data (' + pdfRes.status + ')'); return; }
|
|
118
|
-
var pdfArrayBuffer = await pdfRes.arrayBuffer();
|
|
119
|
-
console.log('[PDF-Secure] PDF loaded:', pdfArrayBuffer.byteLength, 'bytes');
|
|
120
|
-
|
|
121
|
-
// 3. PDF.js
|
|
122
|
-
var lib = await getPdfLib();
|
|
123
|
-
if (!lib || !lib.getDocument) { showError('PDF.js library failed to load.'); return; }
|
|
124
|
-
|
|
125
|
-
var pdfDoc = await lib.getDocument({ data: new Uint8Array(pdfArrayBuffer) }).promise;
|
|
126
|
-
var totalPages = pdfDoc.numPages;
|
|
127
|
-
console.log('[PDF-Secure] Pages:', totalPages);
|
|
128
|
-
|
|
129
|
-
loadingEl.style.display = 'none';
|
|
130
|
-
pagecountEl.textContent = totalPages + ' page' + (totalPages > 1 ? 's' : '');
|
|
131
|
-
|
|
132
|
-
// Security
|
|
133
|
-
container.addEventListener('contextmenu', function (e) { e.preventDefault(); });
|
|
134
|
-
container.addEventListener('dragstart', function (e) { e.preventDefault(); });
|
|
135
|
-
container.addEventListener('selectstart', function (e) { e.preventDefault(); });
|
|
136
|
-
|
|
137
|
-
// 4. Render ALL pages
|
|
138
|
-
var viewerArea = container.querySelector('.pdf-secure-viewer-area');
|
|
139
|
-
var pageCanvases = [];
|
|
140
|
-
|
|
141
|
-
for (var i = 1; i <= totalPages; i++) {
|
|
142
|
-
var pageWrapper = document.createElement('div');
|
|
143
|
-
pageWrapper.className = 'pdf-secure-page';
|
|
144
|
-
var canvas = document.createElement('canvas');
|
|
145
|
-
pageWrapper.appendChild(canvas);
|
|
146
|
-
pagesEl.appendChild(pageWrapper);
|
|
147
|
-
pageCanvases.push({ canvas: canvas, pageNum: i, rendered: false });
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Render visible pages (IntersectionObserver for performance)
|
|
151
|
-
var observer = new IntersectionObserver(function (entries) {
|
|
152
|
-
entries.forEach(function (entry) {
|
|
153
|
-
if (entry.isIntersecting) {
|
|
154
|
-
var idx = parseInt(entry.target.dataset.pageIdx, 10);
|
|
155
|
-
var item = pageCanvases[idx];
|
|
156
|
-
if (item && !item.rendered) {
|
|
157
|
-
item.rendered = true;
|
|
158
|
-
renderPage(pdfDoc, item.canvas, item.pageNum, viewerArea.clientWidth);
|
|
159
|
-
}
|
|
51
|
+
container.appendChild(header);
|
|
52
|
+
|
|
53
|
+
// Iframe for viewer
|
|
54
|
+
var iframeWrapper = document.createElement('div');
|
|
55
|
+
iframeWrapper.className = 'pdf-secure-embed-body';
|
|
56
|
+
|
|
57
|
+
var iframe = document.createElement('iframe');
|
|
58
|
+
iframe.className = 'pdf-secure-iframe';
|
|
59
|
+
iframe.src = config.relative_path + '/plugins/pdf-secure/viewer?file=' + encodeURIComponent(filename);
|
|
60
|
+
iframe.setAttribute('frameborder', '0');
|
|
61
|
+
iframe.setAttribute('allowfullscreen', 'true');
|
|
62
|
+
iframe.setAttribute('loading', 'lazy');
|
|
63
|
+
|
|
64
|
+
iframeWrapper.appendChild(iframe);
|
|
65
|
+
container.appendChild(iframeWrapper);
|
|
66
|
+
|
|
67
|
+
// Fullscreen button handler
|
|
68
|
+
header.querySelector('.pdf-secure-fullscreen-btn').addEventListener('click', function () {
|
|
69
|
+
if (iframe.requestFullscreen) {
|
|
70
|
+
iframe.requestFullscreen();
|
|
71
|
+
} else if (iframe.webkitRequestFullscreen) {
|
|
72
|
+
iframe.webkitRequestFullscreen();
|
|
73
|
+
} else if (iframe.msRequestFullscreen) {
|
|
74
|
+
iframe.msRequestFullscreen();
|
|
160
75
|
}
|
|
161
76
|
});
|
|
162
|
-
}, { root: viewerArea, rootMargin: '200px' });
|
|
163
77
|
|
|
164
|
-
|
|
165
|
-
item.canvas.parentElement.dataset.pageIdx = idx;
|
|
166
|
-
observer.observe(item.canvas.parentElement);
|
|
78
|
+
link.replaceWith(container);
|
|
167
79
|
});
|
|
168
|
-
|
|
169
|
-
console.log('[PDF-Secure] DONE:', filename);
|
|
170
|
-
} catch (err) {
|
|
171
|
-
console.error('[PDF-Secure] CATCH:', err);
|
|
172
|
-
showError('Failed to load PDF.');
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
async function renderPage(pdfDoc, canvas, pageNum, containerWidth) {
|
|
177
|
-
try {
|
|
178
|
-
var page = await pdfDoc.getPage(pageNum);
|
|
179
|
-
var vp = page.getViewport({ scale: 1 });
|
|
180
|
-
// Use container width minus padding for scale
|
|
181
|
-
var availableWidth = containerWidth - 40;
|
|
182
|
-
var scale = Math.min(availableWidth / vp.width, 2.5);
|
|
183
|
-
var scaled = page.getViewport({ scale: scale });
|
|
184
|
-
|
|
185
|
-
canvas.width = scaled.width;
|
|
186
|
-
canvas.height = scaled.height;
|
|
187
|
-
|
|
188
|
-
await page.render({
|
|
189
|
-
canvasContext: canvas.getContext('2d'),
|
|
190
|
-
viewport: scaled,
|
|
191
|
-
}).promise;
|
|
192
|
-
|
|
193
|
-
console.log('[PDF-Secure] Rendered page', pageNum);
|
|
194
|
-
} catch (err) {
|
|
195
|
-
console.error('[PDF-Secure] Render error page ' + pageNum + ':', err);
|
|
196
|
-
}
|
|
80
|
+
});
|
|
197
81
|
}
|
|
198
82
|
|
|
199
83
|
function escapeHtml(str) {
|
package/static/style.less
CHANGED
|
@@ -1,169 +1,123 @@
|
|
|
1
|
-
/* PDF Secure Viewer — Inline
|
|
1
|
+
/* PDF Secure Viewer — Inline Embed Styles for NodeBB */
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
/* Embedded PDF Container */
|
|
4
|
+
.pdf-secure-embed {
|
|
5
|
+
margin: 16px 0;
|
|
6
|
+
border-radius: 12px;
|
|
5
7
|
overflow: hidden;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
-webkit-user-select: none;
|
|
10
|
-
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.25);
|
|
11
|
-
border: 1px solid #404040;
|
|
8
|
+
background: #1f1f1f;
|
|
9
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
10
|
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25);
|
|
12
11
|
}
|
|
13
12
|
|
|
14
|
-
/*
|
|
15
|
-
.pdf-secure-
|
|
13
|
+
/* Header */
|
|
14
|
+
.pdf-secure-embed-header {
|
|
16
15
|
display: flex;
|
|
17
16
|
align-items: center;
|
|
18
17
|
justify-content: space-between;
|
|
19
|
-
padding:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
border-bottom: 1px solid #404040;
|
|
23
|
-
color: #fff;
|
|
24
|
-
font-family: "Segoe UI", system-ui, -apple-system, sans-serif;
|
|
25
|
-
font-size: 13px;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
.pdf-secure-toolbar-left {
|
|
29
|
-
display: flex;
|
|
30
|
-
align-items: center;
|
|
31
|
-
gap: 8px;
|
|
32
|
-
min-width: 0;
|
|
18
|
+
padding: 10px 16px;
|
|
19
|
+
background: linear-gradient(135deg, #2d2d2d 0%, #252525 100%);
|
|
20
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
|
33
21
|
}
|
|
34
22
|
|
|
35
|
-
.pdf-secure-
|
|
23
|
+
.pdf-secure-embed-title {
|
|
36
24
|
display: flex;
|
|
37
25
|
align-items: center;
|
|
38
|
-
gap:
|
|
39
|
-
|
|
26
|
+
gap: 10px;
|
|
27
|
+
color: #fff;
|
|
28
|
+
font-size: 14px;
|
|
29
|
+
font-weight: 500;
|
|
40
30
|
}
|
|
41
31
|
|
|
42
|
-
.pdf-secure-
|
|
43
|
-
width:
|
|
44
|
-
height:
|
|
45
|
-
|
|
32
|
+
.pdf-secure-embed-title svg {
|
|
33
|
+
width: 20px;
|
|
34
|
+
height: 20px;
|
|
35
|
+
fill: #e81224;
|
|
46
36
|
flex-shrink: 0;
|
|
47
37
|
}
|
|
48
38
|
|
|
49
|
-
.pdf-secure-
|
|
39
|
+
.pdf-secure-embed-title span {
|
|
50
40
|
white-space: nowrap;
|
|
51
41
|
overflow: hidden;
|
|
52
42
|
text-overflow: ellipsis;
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
.pdf-secure-pagecount {
|
|
57
|
-
color: #a0a0a0;
|
|
58
|
-
font-size: 12px;
|
|
59
|
-
white-space: nowrap;
|
|
43
|
+
max-width: 400px;
|
|
60
44
|
}
|
|
61
45
|
|
|
62
|
-
|
|
63
|
-
.pdf-secure-viewer-area {
|
|
64
|
-
background: #525659;
|
|
65
|
-
max-height: 700px;
|
|
66
|
-
overflow-y: auto;
|
|
67
|
-
overflow-x: hidden;
|
|
68
|
-
position: relative;
|
|
69
|
-
|
|
70
|
-
/* Scrollbar styling */
|
|
71
|
-
&::-webkit-scrollbar {
|
|
72
|
-
width: 8px;
|
|
73
|
-
}
|
|
74
|
-
&::-webkit-scrollbar-track {
|
|
75
|
-
background: #2d2d2d;
|
|
76
|
-
}
|
|
77
|
-
&::-webkit-scrollbar-thumb {
|
|
78
|
-
background: #3d3d3d;
|
|
79
|
-
border-radius: 4px;
|
|
80
|
-
}
|
|
81
|
-
&::-webkit-scrollbar-thumb:hover {
|
|
82
|
-
background: #555;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/* Each page — matches viewer.html .pdfViewer .page */
|
|
87
|
-
.pdf-secure-page {
|
|
88
|
-
margin: 8px auto;
|
|
89
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
|
46
|
+
.pdf-secure-embed-actions {
|
|
90
47
|
display: flex;
|
|
91
|
-
|
|
92
|
-
width: fit-content;
|
|
93
|
-
|
|
94
|
-
canvas {
|
|
95
|
-
display: block;
|
|
96
|
-
max-width: 100%;
|
|
97
|
-
pointer-events: none;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/* Pages container */
|
|
102
|
-
.pdf-secure-pages {
|
|
103
|
-
padding: 4px 0;
|
|
48
|
+
gap: 8px;
|
|
104
49
|
}
|
|
105
50
|
|
|
106
|
-
|
|
107
|
-
.pdf-secure-loading {
|
|
51
|
+
.pdf-secure-fullscreen-btn {
|
|
108
52
|
display: flex;
|
|
109
|
-
flex-direction: column;
|
|
110
53
|
align-items: center;
|
|
111
54
|
justify-content: center;
|
|
112
|
-
gap: 12px;
|
|
113
|
-
min-height: 300px;
|
|
114
|
-
color: #a0a0a0;
|
|
115
|
-
font-size: 14px;
|
|
116
|
-
font-family: "Segoe UI", system-ui, sans-serif;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
.pdf-secure-spinner {
|
|
120
55
|
width: 32px;
|
|
121
56
|
height: 32px;
|
|
122
|
-
|
|
123
|
-
border
|
|
124
|
-
border-radius:
|
|
125
|
-
|
|
57
|
+
background: rgba(255, 255, 255, 0.08);
|
|
58
|
+
border: none;
|
|
59
|
+
border-radius: 6px;
|
|
60
|
+
cursor: pointer;
|
|
61
|
+
transition: all 0.2s;
|
|
126
62
|
}
|
|
127
63
|
|
|
128
|
-
|
|
129
|
-
|
|
64
|
+
.pdf-secure-fullscreen-btn:hover {
|
|
65
|
+
background: rgba(255, 255, 255, 0.15);
|
|
130
66
|
}
|
|
131
67
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
justify-content: center;
|
|
137
|
-
min-height: 200px;
|
|
138
|
-
color: #e81224;
|
|
139
|
-
font-size: 14px;
|
|
140
|
-
text-align: center;
|
|
141
|
-
padding: 20px;
|
|
142
|
-
font-family: "Segoe UI", system-ui, sans-serif;
|
|
68
|
+
.pdf-secure-fullscreen-btn svg {
|
|
69
|
+
width: 18px;
|
|
70
|
+
height: 18px;
|
|
71
|
+
fill: #fff;
|
|
143
72
|
}
|
|
144
73
|
|
|
145
|
-
/*
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
74
|
+
/* Viewer Body */
|
|
75
|
+
.pdf-secure-embed-body {
|
|
76
|
+
position: relative;
|
|
77
|
+
width: 100%;
|
|
78
|
+
height: 600px;
|
|
79
|
+
background: #525659;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.pdf-secure-iframe {
|
|
83
|
+
width: 100%;
|
|
84
|
+
height: 100%;
|
|
85
|
+
border: none;
|
|
86
|
+
display: block;
|
|
150
87
|
}
|
|
151
88
|
|
|
152
89
|
/* Responsive */
|
|
153
90
|
@media (max-width: 768px) {
|
|
154
|
-
.pdf-secure-
|
|
155
|
-
|
|
91
|
+
.pdf-secure-embed-body {
|
|
92
|
+
height: 450px;
|
|
156
93
|
}
|
|
157
94
|
|
|
158
|
-
.pdf-secure-
|
|
159
|
-
|
|
160
|
-
padding: 0 8px;
|
|
161
|
-
font-size: 12px;
|
|
95
|
+
.pdf-secure-embed-title span {
|
|
96
|
+
max-width: 200px;
|
|
162
97
|
}
|
|
163
98
|
}
|
|
164
99
|
|
|
165
100
|
@media (max-width: 480px) {
|
|
166
|
-
.pdf-secure-
|
|
167
|
-
|
|
101
|
+
.pdf-secure-embed-body {
|
|
102
|
+
height: 350px;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.pdf-secure-embed-header {
|
|
106
|
+
padding: 8px 12px;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.pdf-secure-embed-title {
|
|
110
|
+
font-size: 13px;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.pdf-secure-embed-title span {
|
|
114
|
+
max-width: 150px;
|
|
168
115
|
}
|
|
169
116
|
}
|
|
117
|
+
|
|
118
|
+
/* Anti-print */
|
|
119
|
+
@media print {
|
|
120
|
+
.pdf-secure-embed {
|
|
121
|
+
display: none !important;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -40,6 +40,53 @@
|
|
|
40
40
|
font-size: 14px;
|
|
41
41
|
overflow: hidden;
|
|
42
42
|
color: var(--text-primary);
|
|
43
|
+
/* Security: prevent text selection globally */
|
|
44
|
+
-webkit-user-select: none;
|
|
45
|
+
-moz-user-select: none;
|
|
46
|
+
-ms-user-select: none;
|
|
47
|
+
user-select: none;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* Print Protection - hide everything when printing */
|
|
51
|
+
@media print {
|
|
52
|
+
|
|
53
|
+
html,
|
|
54
|
+
body,
|
|
55
|
+
#viewerContainer,
|
|
56
|
+
#viewer,
|
|
57
|
+
.pdfViewer,
|
|
58
|
+
.page {
|
|
59
|
+
display: none !important;
|
|
60
|
+
visibility: hidden !important;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
body::before {
|
|
64
|
+
content: 'Bu içeriğin yazdırılması engellenmiştir.' !important;
|
|
65
|
+
display: block !important;
|
|
66
|
+
font-size: 24px;
|
|
67
|
+
padding: 50px;
|
|
68
|
+
text-align: center;
|
|
69
|
+
color: #666;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* Loading Spinner Animation */
|
|
74
|
+
@keyframes spin {
|
|
75
|
+
from {
|
|
76
|
+
transform: rotate(0deg);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
to {
|
|
80
|
+
transform: rotate(360deg);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.spin {
|
|
85
|
+
animation: spin 1s linear infinite;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.dropzone svg.spin {
|
|
89
|
+
fill: var(--accent);
|
|
43
90
|
}
|
|
44
91
|
|
|
45
92
|
/* Toolbar - Edge Style */
|
|
@@ -1357,6 +1404,90 @@
|
|
|
1357
1404
|
generateThumbnails();
|
|
1358
1405
|
}
|
|
1359
1406
|
|
|
1407
|
+
// Load PDF from ArrayBuffer (for secure nonce-based loading)
|
|
1408
|
+
async function loadPDFFromBuffer(arrayBuffer) {
|
|
1409
|
+
uploadOverlay.classList.add('hidden');
|
|
1410
|
+
|
|
1411
|
+
pdfDoc = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
|
|
1412
|
+
|
|
1413
|
+
pdfViewer.setDocument(pdfDoc);
|
|
1414
|
+
linkService.setDocument(pdfDoc);
|
|
1415
|
+
|
|
1416
|
+
['zoomIn', 'zoomOut', 'pageInput', 'rotateLeft', 'rotateRight'].forEach(id => {
|
|
1417
|
+
document.getElementById(id).disabled = false;
|
|
1418
|
+
});
|
|
1419
|
+
|
|
1420
|
+
generateThumbnails();
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
// Auto-load PDF if config is present (injected by NodeBB plugin)
|
|
1424
|
+
async function autoLoadSecurePDF() {
|
|
1425
|
+
if (!window.PDF_SECURE_CONFIG || !window.PDF_SECURE_CONFIG.filename) {
|
|
1426
|
+
console.log('[PDF-Secure] No config found, showing file picker');
|
|
1427
|
+
return;
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
const config = window.PDF_SECURE_CONFIG;
|
|
1431
|
+
console.log('[PDF-Secure] Auto-loading:', config.filename);
|
|
1432
|
+
|
|
1433
|
+
// Show loading state
|
|
1434
|
+
const dropzone = document.getElementById('dropzone');
|
|
1435
|
+
if (dropzone) {
|
|
1436
|
+
dropzone.innerHTML = `
|
|
1437
|
+
<svg viewBox="0 0 24 24" class="spin">
|
|
1438
|
+
<path d="M12 4V2A10 10 0 0 0 2 12h2a8 8 0 0 1 8-8z" />
|
|
1439
|
+
</svg>
|
|
1440
|
+
<h2>PDF Yükleniyor...</h2>
|
|
1441
|
+
<p>${config.filename}</p>
|
|
1442
|
+
`;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
try {
|
|
1446
|
+
// Step 1: Get nonce
|
|
1447
|
+
const nonceUrl = config.relativePath + '/api/v3/plugins/pdf-secure/nonce?file=' + encodeURIComponent(config.filename);
|
|
1448
|
+
const nonceRes = await fetch(nonceUrl, {
|
|
1449
|
+
credentials: 'same-origin',
|
|
1450
|
+
headers: { 'x-csrf-token': config.csrfToken }
|
|
1451
|
+
});
|
|
1452
|
+
|
|
1453
|
+
if (!nonceRes.ok) {
|
|
1454
|
+
throw new Error(nonceRes.status === 401 ? 'Giriş yapmanız gerekiyor' : 'Nonce alınamadı');
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
const nonceData = await nonceRes.json();
|
|
1458
|
+
const nonce = nonceData.response.nonce;
|
|
1459
|
+
|
|
1460
|
+
// Step 2: Fetch PDF binary
|
|
1461
|
+
const pdfUrl = config.relativePath + '/api/v3/plugins/pdf-secure/pdf-data?nonce=' + encodeURIComponent(nonce);
|
|
1462
|
+
const pdfRes = await fetch(pdfUrl, { credentials: 'same-origin' });
|
|
1463
|
+
|
|
1464
|
+
if (!pdfRes.ok) {
|
|
1465
|
+
throw new Error('PDF yüklenemedi (' + pdfRes.status + ')');
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
const pdfBuffer = await pdfRes.arrayBuffer();
|
|
1469
|
+
console.log('[PDF-Secure] PDF loaded:', pdfBuffer.byteLength, 'bytes');
|
|
1470
|
+
|
|
1471
|
+
// Load into viewer
|
|
1472
|
+
await loadPDFFromBuffer(pdfBuffer);
|
|
1473
|
+
|
|
1474
|
+
} catch (err) {
|
|
1475
|
+
console.error('[PDF-Secure] Auto-load error:', err);
|
|
1476
|
+
if (dropzone) {
|
|
1477
|
+
dropzone.innerHTML = `
|
|
1478
|
+
<svg viewBox="0 0 24 24" style="fill: #e81224;">
|
|
1479
|
+
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/>
|
|
1480
|
+
</svg>
|
|
1481
|
+
<h2>Hata</h2>
|
|
1482
|
+
<p>${err.message}</p>
|
|
1483
|
+
`;
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
// Run auto-load on page ready
|
|
1489
|
+
autoLoadSecurePDF();
|
|
1490
|
+
|
|
1360
1491
|
// Generate Thumbnails
|
|
1361
1492
|
async function generateThumbnails() {
|
|
1362
1493
|
thumbnailContainer.innerHTML = '';
|