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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-pdf-secure",
3
- "version": "1.0.9",
3
+ "version": "1.1.0",
4
4
  "description": "Secure PDF viewer plugin for NodeBB - prevents downloading, enables canvas-only rendering with Premium group support",
5
5
  "main": "library.js",
6
6
  "repository": {
@@ -2,22 +2,7 @@
2
2
 
3
3
  console.log('[PDF-Secure] main.js loaded');
4
4
 
5
- // Load PDF.js via inline <script type="module"> expose globally
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-inline';
36
+ container.className = 'pdf-secure-card';
51
37
  container.innerHTML =
52
- '<div class="pdf-secure-toolbar">' +
53
- '<div class="pdf-secure-toolbar-left">' +
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-viewer-area">' +
62
- '<div class="pdf-secure-loading">' +
63
- '<div class="pdf-secure-spinner"></div>' +
64
- '<span>Loading PDF...</span>' +
65
- '</div>' +
66
- '<div class="pdf-secure-error"></div>' +
67
- '<div class="pdf-secure-pages"></div>' +
68
- '</div>';
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 — Inline Preview (Edge-style dark theme) */
1
+ /* PDF Secure Viewer — Card Styles for NodeBB */
2
2
 
3
- .pdf-secure-inline {
4
- border-radius: 8px;
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
- justify-content: space-between;
19
- padding: 0 12px;
20
- height: 42px;
21
- background: #2d2d2d;
22
- border-bottom: 1px solid #404040;
23
- color: #fff;
24
- font-family: "Segoe UI", system-ui, -apple-system, sans-serif;
25
- font-size: 13px;
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-toolbar-left {
29
- display: flex;
30
- align-items: center;
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-toolbar-right {
22
+ .pdf-secure-card-icon {
36
23
  display: flex;
37
24
  align-items: center;
38
- gap: 8px;
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: 18px;
44
- height: 18px;
45
- color: #a0a0a0;
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-filename {
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-pagecount {
57
- color: #a0a0a0;
56
+ .pdf-secure-card-type {
58
57
  font-size: 12px;
59
- white-space: nowrap;
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
- /* 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);
61
+ .pdf-secure-card-btn {
90
62
  display: flex;
91
- justify-content: center;
92
- width: fit-content;
93
-
94
- canvas {
95
- display: block;
96
- max-width: 100%;
97
- pointer-events: none;
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
- /* Pages container */
102
- .pdf-secure-pages {
103
- padding: 4px 0;
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
- /* Loading spinner */
107
- .pdf-secure-loading {
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-spinner {
120
- width: 32px;
121
- height: 32px;
122
- border: 3px solid #3d3d3d;
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
- @keyframes pdf-secure-spin {
129
- to { transform: rotate(360deg); }
130
- }
92
+ /* Mobile responsiveness */
93
+ @media (max-width: 480px) {
94
+ .pdf-secure-card {
95
+ padding: 10px 12px;
96
+ gap: 10px;
97
+ }
131
98
 
132
- /* Error */
133
- .pdf-secure-error {
134
- display: none;
135
- align-items: center;
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
- /* Anti-print */
146
- @media print {
147
- .pdf-secure-inline {
148
- display: none !important;
104
+ .pdf-secure-card-icon svg {
105
+ width: 22px;
106
+ height: 22px;
149
107
  }
150
- }
151
108
 
152
- /* Responsive */
153
- @media (max-width: 768px) {
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-toolbar {
159
- height: 38px;
160
- padding: 0 8px;
113
+ .pdf-secure-card-btn {
114
+ padding: 8px 14px;
161
115
  font-size: 12px;
162
116
  }
163
- }
164
117
 
165
- @media (max-width: 480px) {
166
- .pdf-secure-viewer-area {
167
- max-height: 400px;
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 = '';