nodebb-plugin-pdf-secure 1.0.9 → 1.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.
- package/library.js +46 -0
- package/package.json +1 -1
- package/static/lib/main.js +21 -157
- package/static/style.less +90 -127
- 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 open in fullscreen viewer
|
|
21
6
|
(async function () {
|
|
22
7
|
try {
|
|
23
8
|
var hooks = await app.require('hooks');
|
|
@@ -46,156 +31,35 @@ console.log('[PDF-Secure] main.js loaded');
|
|
|
46
31
|
var filename = parts[parts.length - 1];
|
|
47
32
|
console.log('[PDF-Secure] Processing:', filename);
|
|
48
33
|
|
|
34
|
+
// Create PDF preview card with "View PDF" button
|
|
49
35
|
var container = document.createElement('div');
|
|
50
|
-
container.className = 'pdf-secure-
|
|
36
|
+
container.className = 'pdf-secure-card';
|
|
51
37
|
container.innerHTML =
|
|
52
|
-
'<div class="pdf-secure-
|
|
53
|
-
|
|
54
|
-
'<svg class="pdf-secure-icon" 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>' +
|
|
55
|
-
'<span class="pdf-secure-filename">' + escapeHtml(link.textContent || filename) + '</span>' +
|
|
56
|
-
'</div>' +
|
|
57
|
-
'<div class="pdf-secure-toolbar-right">' +
|
|
58
|
-
'<span class="pdf-secure-pagecount"></span>' +
|
|
59
|
-
'</div>' +
|
|
38
|
+
'<div class="pdf-secure-card-icon">' +
|
|
39
|
+
'<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>' +
|
|
60
40
|
'</div>' +
|
|
61
|
-
'<div class="pdf-secure-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
'</
|
|
41
|
+
'<div class="pdf-secure-card-info">' +
|
|
42
|
+
'<span class="pdf-secure-card-name">' + escapeHtml(link.textContent || filename) + '</span>' +
|
|
43
|
+
'<span class="pdf-secure-card-type">PDF Dosyası</span>' +
|
|
44
|
+
'</div>' +
|
|
45
|
+
'<button class="pdf-secure-card-btn" data-filename="' + escapeHtml(filename) + '">' +
|
|
46
|
+
'<svg viewBox="0 0 24 24"><path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z" fill="currentColor"/></svg>' +
|
|
47
|
+
'<span>Görüntüle</span>' +
|
|
48
|
+
'</button>';
|
|
49
|
+
|
|
50
|
+
// Click handler - open viewer in new tab
|
|
51
|
+
container.querySelector('.pdf-secure-card-btn').addEventListener('click', function (e) {
|
|
52
|
+
e.preventDefault();
|
|
53
|
+
var fname = this.dataset.filename;
|
|
54
|
+
var viewerUrl = config.relative_path + '/plugins/pdf-secure/viewer?file=' + encodeURIComponent(fname);
|
|
55
|
+
window.open(viewerUrl, '_blank', 'noopener,noreferrer');
|
|
56
|
+
});
|
|
69
57
|
|
|
70
58
|
link.replaceWith(container);
|
|
71
|
-
loadPdf(container, filename);
|
|
72
59
|
});
|
|
73
60
|
});
|
|
74
61
|
}
|
|
75
62
|
|
|
76
|
-
function getPdfLib() {
|
|
77
|
-
if (window.__pdfSecureLib) return Promise.resolve(window.__pdfSecureLib);
|
|
78
|
-
return new Promise(function (resolve) {
|
|
79
|
-
window.addEventListener('pdf-secure-lib-ready', function () {
|
|
80
|
-
resolve(window.__pdfSecureLib);
|
|
81
|
-
}, { once: true });
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
async function loadPdf(container, filename) {
|
|
86
|
-
var loadingEl = container.querySelector('.pdf-secure-loading');
|
|
87
|
-
var errorEl = container.querySelector('.pdf-secure-error');
|
|
88
|
-
var pagesEl = container.querySelector('.pdf-secure-pages');
|
|
89
|
-
var pagecountEl = container.querySelector('.pdf-secure-pagecount');
|
|
90
|
-
|
|
91
|
-
function showError(msg) {
|
|
92
|
-
console.error('[PDF-Secure] ERROR ' + filename + ':', msg);
|
|
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
|
-
}
|
|
160
|
-
}
|
|
161
|
-
});
|
|
162
|
-
}, { root: viewerArea, rootMargin: '200px' });
|
|
163
|
-
|
|
164
|
-
pageCanvases.forEach(function (item, idx) {
|
|
165
|
-
item.canvas.parentElement.dataset.pageIdx = idx;
|
|
166
|
-
observer.observe(item.canvas.parentElement);
|
|
167
|
-
});
|
|
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
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
63
|
function escapeHtml(str) {
|
|
200
64
|
var d = document.createElement('div');
|
|
201
65
|
d.textContent = str;
|
package/static/style.less
CHANGED
|
@@ -1,169 +1,132 @@
|
|
|
1
|
-
/* PDF Secure Viewer —
|
|
1
|
+
/* PDF Secure Viewer — Card Styles for NodeBB */
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
overflow: hidden;
|
|
6
|
-
margin: 12px 0;
|
|
7
|
-
max-width: 100%;
|
|
8
|
-
user-select: none;
|
|
9
|
-
-webkit-user-select: none;
|
|
10
|
-
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.25);
|
|
11
|
-
border: 1px solid #404040;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/* Toolbar — matches viewer.html */
|
|
15
|
-
.pdf-secure-toolbar {
|
|
3
|
+
/* PDF Card - shown in posts */
|
|
4
|
+
.pdf-secure-card {
|
|
16
5
|
display: flex;
|
|
17
6
|
align-items: center;
|
|
18
|
-
|
|
19
|
-
padding:
|
|
20
|
-
|
|
21
|
-
background: #
|
|
22
|
-
border
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
7
|
+
gap: 12px;
|
|
8
|
+
padding: 12px 16px;
|
|
9
|
+
margin: 12px 0;
|
|
10
|
+
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
|
11
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
12
|
+
border-radius: 12px;
|
|
13
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
|
14
|
+
transition: transform 0.2s, box-shadow 0.2s;
|
|
26
15
|
}
|
|
27
16
|
|
|
28
|
-
.pdf-secure-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
gap: 8px;
|
|
32
|
-
min-width: 0;
|
|
17
|
+
.pdf-secure-card:hover {
|
|
18
|
+
transform: translateY(-2px);
|
|
19
|
+
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.25);
|
|
33
20
|
}
|
|
34
21
|
|
|
35
|
-
.pdf-secure-
|
|
22
|
+
.pdf-secure-card-icon {
|
|
36
23
|
display: flex;
|
|
37
24
|
align-items: center;
|
|
38
|
-
|
|
25
|
+
justify-content: center;
|
|
26
|
+
width: 48px;
|
|
27
|
+
height: 48px;
|
|
28
|
+
background: linear-gradient(135deg, #e81224 0%, #c0392b 100%);
|
|
29
|
+
border-radius: 10px;
|
|
39
30
|
flex-shrink: 0;
|
|
40
31
|
}
|
|
41
32
|
|
|
42
|
-
.pdf-secure-icon {
|
|
43
|
-
width:
|
|
44
|
-
height:
|
|
45
|
-
|
|
46
|
-
flex-shrink: 0;
|
|
33
|
+
.pdf-secure-card-icon svg {
|
|
34
|
+
width: 26px;
|
|
35
|
+
height: 26px;
|
|
36
|
+
fill: #fff;
|
|
47
37
|
}
|
|
48
38
|
|
|
49
|
-
.pdf-secure-
|
|
39
|
+
.pdf-secure-card-info {
|
|
40
|
+
display: flex;
|
|
41
|
+
flex-direction: column;
|
|
42
|
+
gap: 2px;
|
|
43
|
+
flex: 1;
|
|
44
|
+
min-width: 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.pdf-secure-card-name {
|
|
48
|
+
font-size: 14px;
|
|
49
|
+
font-weight: 600;
|
|
50
|
+
color: #fff;
|
|
50
51
|
white-space: nowrap;
|
|
51
52
|
overflow: hidden;
|
|
52
53
|
text-overflow: ellipsis;
|
|
53
|
-
font-weight: 500;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
.pdf-secure-
|
|
57
|
-
color: #a0a0a0;
|
|
56
|
+
.pdf-secure-card-type {
|
|
58
57
|
font-size: 12px;
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/* Viewer area — matches viewer.html #viewerContainer */
|
|
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
|
-
}
|
|
58
|
+
color: rgba(255, 255, 255, 0.5);
|
|
84
59
|
}
|
|
85
60
|
|
|
86
|
-
|
|
87
|
-
.pdf-secure-page {
|
|
88
|
-
margin: 8px auto;
|
|
89
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
|
61
|
+
.pdf-secure-card-btn {
|
|
90
62
|
display: flex;
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
63
|
+
align-items: center;
|
|
64
|
+
gap: 6px;
|
|
65
|
+
padding: 10px 18px;
|
|
66
|
+
background: linear-gradient(135deg, #0078d4 0%, #0066b8 100%);
|
|
67
|
+
border: none;
|
|
68
|
+
border-radius: 8px;
|
|
69
|
+
color: #fff;
|
|
70
|
+
font-size: 13px;
|
|
71
|
+
font-weight: 600;
|
|
72
|
+
cursor: pointer;
|
|
73
|
+
transition: all 0.2s;
|
|
74
|
+
flex-shrink: 0;
|
|
99
75
|
}
|
|
100
76
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
77
|
+
.pdf-secure-card-btn:hover {
|
|
78
|
+
background: linear-gradient(135deg, #1a86dc 0%, #0078d4 100%);
|
|
79
|
+
transform: scale(1.02);
|
|
104
80
|
}
|
|
105
81
|
|
|
106
|
-
|
|
107
|
-
.
|
|
108
|
-
display: flex;
|
|
109
|
-
flex-direction: column;
|
|
110
|
-
align-items: center;
|
|
111
|
-
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;
|
|
82
|
+
.pdf-secure-card-btn:active {
|
|
83
|
+
transform: scale(0.98);
|
|
117
84
|
}
|
|
118
85
|
|
|
119
|
-
.pdf-secure-
|
|
120
|
-
width:
|
|
121
|
-
height:
|
|
122
|
-
|
|
123
|
-
border-top-color: #0078d4;
|
|
124
|
-
border-radius: 50%;
|
|
125
|
-
animation: pdf-secure-spin 0.8s linear infinite;
|
|
86
|
+
.pdf-secure-card-btn svg {
|
|
87
|
+
width: 18px;
|
|
88
|
+
height: 18px;
|
|
89
|
+
fill: currentColor;
|
|
126
90
|
}
|
|
127
91
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
92
|
+
/* Mobile responsiveness */
|
|
93
|
+
@media (max-width: 480px) {
|
|
94
|
+
.pdf-secure-card {
|
|
95
|
+
padding: 10px 12px;
|
|
96
|
+
gap: 10px;
|
|
97
|
+
}
|
|
131
98
|
|
|
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;
|
|
143
|
-
}
|
|
99
|
+
.pdf-secure-card-icon {
|
|
100
|
+
width: 40px;
|
|
101
|
+
height: 40px;
|
|
102
|
+
}
|
|
144
103
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
display: none !important;
|
|
104
|
+
.pdf-secure-card-icon svg {
|
|
105
|
+
width: 22px;
|
|
106
|
+
height: 22px;
|
|
149
107
|
}
|
|
150
|
-
}
|
|
151
108
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
.pdf-secure-viewer-area {
|
|
155
|
-
max-height: 500px;
|
|
109
|
+
.pdf-secure-card-name {
|
|
110
|
+
font-size: 13px;
|
|
156
111
|
}
|
|
157
112
|
|
|
158
|
-
.pdf-secure-
|
|
159
|
-
|
|
160
|
-
padding: 0 8px;
|
|
113
|
+
.pdf-secure-card-btn {
|
|
114
|
+
padding: 8px 14px;
|
|
161
115
|
font-size: 12px;
|
|
162
116
|
}
|
|
163
|
-
}
|
|
164
117
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
118
|
+
.pdf-secure-card-btn span {
|
|
119
|
+
display: none;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.pdf-secure-card-btn svg {
|
|
123
|
+
margin: 0;
|
|
168
124
|
}
|
|
169
125
|
}
|
|
126
|
+
|
|
127
|
+
/* Anti-print protection */
|
|
128
|
+
@media print {
|
|
129
|
+
.pdf-secure-card {
|
|
130
|
+
display: none !important;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -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 = '';
|