nodebb-plugin-pdf-secure2 1.2.38 → 1.3.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.
@@ -1,468 +1,468 @@
1
- 'use strict';
2
-
3
- // Main plugin logic - PDF links become inline embedded viewers with lazy loading + queue
4
- (async function () {
5
- // ============================================
6
- // PDF.js PRELOAD - Cache CDN assets before iframe loads
7
- // ============================================
8
- (function preloadPdfJs() {
9
- const preloads = [
10
- { href: 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js', as: 'script' },
11
- { href: 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf_viewer.min.css', as: 'style' }
12
- ];
13
- preloads.forEach(({ href, as }) => {
14
- if (!document.querySelector(`link[href="${href}"]`)) {
15
- const link = document.createElement('link');
16
- link.rel = 'preload';
17
- link.href = href;
18
- link.as = as;
19
- link.crossOrigin = 'anonymous';
20
- document.head.appendChild(link);
21
- }
22
- });
23
- })();
24
-
25
- // Fullscreen API support detection
26
- // On touch devices (tablets, mobiles), always use CSS simulated fullscreen.
27
- // Native fullscreen has an unblockable browser "scroll-to-exit" gesture on touch devices.
28
- // Desktop keeps native fullscreen for the proper OS-level experience.
29
- var isTouchDevice = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0);
30
- var fullscreenApiSupported = !isTouchDevice && !!(
31
- document.documentElement.requestFullscreen ||
32
- document.documentElement.webkitRequestFullscreen
33
- );
34
- var simulatedFullscreenIframe = null;
35
- var savedBodyOverflow = '';
36
-
37
- // Loading queue - only load one PDF at a time
38
- const loadQueue = [];
39
- let isLoading = false;
40
- let currentResolver = null;
41
-
42
- // ============================================
43
- // SPA MEMORY CACHE - Cache decoded PDF buffers
44
- // ============================================
45
- const pdfBufferCache = new Map(); // filename -> ArrayBuffer
46
- const CACHE_MAX_SIZE = 5; // ~50MB limit (avg 10MB per PDF)
47
- let currentLoadingFilename = null;
48
-
49
- function setCachedBuffer(filename, buffer) {
50
- // Evict oldest if cache is full
51
- if (pdfBufferCache.size >= CACHE_MAX_SIZE) {
52
- const firstKey = pdfBufferCache.keys().next().value;
53
- pdfBufferCache.delete(firstKey);
54
- }
55
- pdfBufferCache.set(filename, buffer);
56
- }
57
-
58
- // Listen for postMessage from iframe
59
- window.addEventListener('message', function (event) {
60
- // Security: Only accept messages from same origin
61
- if (event.origin !== window.location.origin) return;
62
-
63
- // PDF ready - resolve queue
64
- if (event.data && event.data.type === 'pdf-secure-ready') {
65
- if (currentResolver) {
66
- currentResolver();
67
- currentResolver = null;
68
- }
69
- }
70
-
71
- // PDF buffer from viewer - cache it
72
- if (event.data && event.data.type === 'pdf-secure-buffer') {
73
- const { filename, buffer } = event.data;
74
- if (filename && buffer) {
75
- setCachedBuffer(filename, buffer);
76
- }
77
- }
78
-
79
- // Fullscreen toggle request from iframe viewer
80
- if (event.data && event.data.type === 'pdf-secure-fullscreen-toggle') {
81
- var sourceIframe = document.querySelector('.pdf-secure-iframe');
82
- // Find the specific iframe that sent the message
83
- document.querySelectorAll('.pdf-secure-iframe').forEach(function (f) {
84
- if (f.contentWindow === event.source) sourceIframe = f;
85
- });
86
- if (!sourceIframe) return;
87
-
88
- if (fullscreenApiSupported) {
89
- // Native fullscreen path
90
- var fsEl = document.fullscreenElement || document.webkitFullscreenElement;
91
- if (fsEl) {
92
- if (document.exitFullscreen) document.exitFullscreen();
93
- else if (document.webkitExitFullscreen) document.webkitExitFullscreen();
94
- } else {
95
- if (sourceIframe.requestFullscreen) sourceIframe.requestFullscreen().catch(function () { });
96
- else if (sourceIframe.webkitRequestFullscreen) sourceIframe.webkitRequestFullscreen();
97
- }
98
- } else {
99
- // Simulated fullscreen path (iOS Safari / Chrome)
100
- if (simulatedFullscreenIframe) {
101
- exitSimulatedFullscreen();
102
- } else {
103
- enterSimulatedFullscreen(sourceIframe);
104
- }
105
- }
106
- }
107
-
108
- // Fullscreen state query from iframe
109
- if (event.data && event.data.type === 'pdf-secure-fullscreen-query') {
110
- var fsActive = !!(document.fullscreenElement || document.webkitFullscreenElement) ||
111
- (simulatedFullscreenIframe !== null);
112
- if (event.source) {
113
- event.source.postMessage({
114
- type: 'pdf-secure-fullscreen-state',
115
- isFullscreen: fsActive
116
- }, event.origin);
117
- }
118
- }
119
-
120
- // Viewer asking for cached buffer
121
- if (event.data && event.data.type === 'pdf-secure-cache-request') {
122
- const { filename } = event.data;
123
- const cached = pdfBufferCache.get(filename);
124
- if (cached && event.source) {
125
- // Send cached buffer to viewer (transferable for 0-copy)
126
- // Clone once: keep original in cache, transfer the copy
127
- const copy = cached.slice(0);
128
- event.source.postMessage({
129
- type: 'pdf-secure-cache-response',
130
- filename: filename,
131
- buffer: copy
132
- }, event.origin, [copy]);
133
- } else if (event.source) {
134
- // No cache, viewer will fetch normally
135
- event.source.postMessage({
136
- type: 'pdf-secure-cache-response',
137
- filename: filename,
138
- buffer: null
139
- }, event.origin);
140
- }
141
- }
142
- });
143
-
144
- // Forward fullscreen state changes to all viewer iframes
145
- function notifyFullscreenChange() {
146
- var fsActive = !!(document.fullscreenElement || document.webkitFullscreenElement);
147
- if (fsActive) {
148
- document.body.style.overscrollBehavior = 'none';
149
- document.body.style.overflow = 'hidden';
150
- } else {
151
- document.body.style.overscrollBehavior = '';
152
- document.body.style.overflow = '';
153
- }
154
- document.querySelectorAll('.pdf-secure-iframe').forEach(function (f) {
155
- if (f.contentWindow) {
156
- f.style.overscrollBehavior = 'none';
157
- f.contentWindow.postMessage({
158
- type: 'pdf-secure-fullscreen-state',
159
- isFullscreen: fsActive
160
- }, window.location.origin);
161
- }
162
- });
163
- }
164
- document.addEventListener('fullscreenchange', notifyFullscreenChange);
165
- document.addEventListener('webkitfullscreenchange', notifyFullscreenChange);
166
-
167
- // Touch handler to block parent scroll during simulated fullscreen
168
- function parentFullscreenTouchHandler(e) {
169
- e.preventDefault();
170
- }
171
-
172
- // Enter CSS simulated fullscreen (for iOS Safari/Chrome)
173
- function enterSimulatedFullscreen(iframe) {
174
- if (simulatedFullscreenIframe) return; // already in simulated fullscreen
175
- simulatedFullscreenIframe = iframe;
176
-
177
- // Save original iframe styles
178
- iframe._savedStyle = {
179
- position: iframe.style.position,
180
- top: iframe.style.top,
181
- left: iframe.style.left,
182
- width: iframe.style.width,
183
- height: iframe.style.height,
184
- zIndex: iframe.style.zIndex
185
- };
186
-
187
- // Apply fullscreen styles
188
- iframe.style.position = 'fixed';
189
- iframe.style.top = '0';
190
- iframe.style.left = '0';
191
- iframe.style.width = '100vw';
192
- iframe.style.width = '100dvw'; // Override: dynamic viewport (excludes browser chrome)
193
- iframe.style.height = '100vh';
194
- iframe.style.height = '100dvh'; // Override: dynamic viewport (excludes address bar on mobile)
195
- iframe.style.zIndex = '2147483647';
196
-
197
- // Lock body scroll
198
- savedBodyOverflow = document.body.style.overflow;
199
- document.body.style.overflow = 'hidden';
200
- document.body.style.overscrollBehavior = 'none';
201
-
202
- // Block touch scroll on parent
203
- document.addEventListener('touchmove', parentFullscreenTouchHandler, { passive: false });
204
-
205
- // Notify iframe it is now fullscreen
206
- if (iframe.contentWindow) {
207
- iframe.contentWindow.postMessage({
208
- type: 'pdf-secure-fullscreen-state',
209
- isFullscreen: true
210
- }, window.location.origin);
211
- }
212
- }
213
-
214
- // Exit CSS simulated fullscreen
215
- function exitSimulatedFullscreen() {
216
- if (!simulatedFullscreenIframe) return;
217
- var iframe = simulatedFullscreenIframe;
218
- simulatedFullscreenIframe = null;
219
-
220
- // Restore original iframe styles
221
- if (iframe._savedStyle) {
222
- iframe.style.position = iframe._savedStyle.position;
223
- iframe.style.top = iframe._savedStyle.top;
224
- iframe.style.left = iframe._savedStyle.left;
225
- iframe.style.width = iframe._savedStyle.width;
226
- iframe.style.height = iframe._savedStyle.height;
227
- iframe.style.zIndex = iframe._savedStyle.zIndex;
228
- delete iframe._savedStyle;
229
- }
230
-
231
- // Restore body scroll
232
- document.body.style.overflow = savedBodyOverflow;
233
- document.body.style.overscrollBehavior = '';
234
- savedBodyOverflow = '';
235
-
236
- // Remove parent touch block
237
- document.removeEventListener('touchmove', parentFullscreenTouchHandler);
238
-
239
- // Notify iframe it is no longer fullscreen
240
- if (iframe.contentWindow) {
241
- iframe.contentWindow.postMessage({
242
- type: 'pdf-secure-fullscreen-state',
243
- isFullscreen: false
244
- }, window.location.origin);
245
- }
246
- }
247
-
248
- async function processQueue() {
249
- if (isLoading || loadQueue.length === 0) return;
250
-
251
- isLoading = true;
252
- const { wrapper, filename, placeholder } = loadQueue.shift();
253
-
254
- try {
255
- await loadPdfIframe(wrapper, filename, placeholder);
256
- } catch (err) {
257
- }
258
-
259
- isLoading = false;
260
-
261
- // Small delay between loads
262
- setTimeout(processQueue, 200);
263
- }
264
-
265
- function queuePdfLoad(wrapper, filename, placeholder) {
266
- loadQueue.push({ wrapper, filename, placeholder });
267
- processQueue();
268
- }
269
-
270
- try {
271
- var hooks = await app.require('hooks');
272
-
273
- hooks.on('action:ajaxify.end', function () {
274
- // Clear queue on page change
275
- loadQueue.length = 0;
276
- isLoading = false;
277
- currentResolver = null;
278
- // Exit simulated fullscreen on SPA navigation
279
- exitSimulatedFullscreen();
280
- interceptPdfLinks();
281
- });
282
- } catch (err) {
283
- }
284
-
285
- function interceptPdfLinks() {
286
- var postContents = document.querySelectorAll('[component="post/content"]');
287
-
288
- postContents.forEach(function (content) {
289
- // NEW: Detect server-rendered secure placeholders (hides URL from source)
290
- var placeholders = content.querySelectorAll('.pdf-secure-placeholder');
291
- placeholders.forEach(function (placeholder) {
292
- if (placeholder.dataset.pdfSecureProcessed) return;
293
- placeholder.dataset.pdfSecureProcessed = 'true';
294
-
295
- var filename = placeholder.dataset.filename;
296
- var displayName = placeholder.querySelector('span')?.textContent || filename;
297
-
298
- createPdfViewer(placeholder, filename, displayName);
299
- });
300
-
301
- // FALLBACK: Detect old-style PDF links (for backwards compatibility)
302
- var pdfLinks = content.querySelectorAll('a[href$=".pdf"], a[href$=".PDF"]');
303
- pdfLinks.forEach(function (link) {
304
- if (link.dataset.pdfSecure) return;
305
- link.dataset.pdfSecure = 'true';
306
-
307
- var href = link.getAttribute('href');
308
- var parts = href.split('/');
309
- var filename = parts[parts.length - 1];
310
- var displayName = link.textContent || filename;
311
-
312
- createPdfViewer(link, filename, displayName);
313
- });
314
- });
315
- }
316
-
317
- function createPdfViewer(targetElement, filename, displayName) {
318
-
319
- // Create container
320
- var container = document.createElement('div');
321
- container.className = 'pdf-secure-embed';
322
- container.style.cssText = 'margin:16px 0;border-radius:12px;overflow:hidden;background:#1f1f1f;border:1px solid rgba(255,255,255,0.1);box-shadow:0 4px 20px rgba(0,0,0,0.25);';
323
-
324
- // Header
325
- var header = document.createElement('div');
326
- header.className = 'pdf-secure-embed-header';
327
- header.style.cssText = 'display:flex;align-items:center;justify-content:space-between;padding:10px 16px;background:linear-gradient(135deg,#2d2d2d 0%,#252525 100%);border-bottom:1px solid rgba(255,255,255,0.08);';
328
-
329
- var title = document.createElement('div');
330
- title.className = 'pdf-secure-embed-title';
331
- title.style.cssText = 'display:flex;align-items:center;gap:10px;color:#fff;font-size:14px;font-weight:500;';
332
-
333
- var icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
334
- icon.setAttribute('viewBox', '0 0 24 24');
335
- icon.style.cssText = 'width:20px;height:20px;min-width:20px;max-width:20px;fill:#e81224;flex-shrink:0;';
336
- icon.innerHTML = '<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"/>';
337
-
338
- var nameSpan = document.createElement('span');
339
- nameSpan.style.cssText = 'white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:400px;';
340
- try { nameSpan.textContent = decodeURIComponent(displayName); }
341
- catch (e) { nameSpan.textContent = displayName; }
342
-
343
- title.appendChild(icon);
344
- title.appendChild(nameSpan);
345
-
346
- header.appendChild(title);
347
- container.appendChild(header);
348
-
349
- // Body with loading placeholder
350
- var iframeWrapper = document.createElement('div');
351
- iframeWrapper.className = 'pdf-secure-embed-body';
352
- iframeWrapper.style.cssText = 'position:relative;width:100%;height:600px;background:#525659;';
353
-
354
- // Loading placeholder - ALWAYS VISIBLE until PDF ready (z-index: 10)
355
- var loadingPlaceholder = document.createElement('div');
356
- loadingPlaceholder.className = 'pdf-loading-placeholder';
357
- loadingPlaceholder.style.cssText = 'position:absolute;top:0;left:0;right:0;bottom:0;display:flex;flex-direction:column;align-items:center;justify-content:center;background:#2d2d2d;color:#fff;gap:16px;z-index:10;transition:opacity 0.3s;';
358
- loadingPlaceholder.innerHTML = `
359
- <svg viewBox="0 0 24 24" style="width:48px;height:48px;fill:#555;">
360
- <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"/>
361
- </svg>
362
- <div class="pdf-loading-text" style="font-size:14px;color:#a0a0a0;">Sırada bekliyor...</div>
363
- <style>@keyframes spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}</style>
364
- `;
365
- iframeWrapper.appendChild(loadingPlaceholder);
366
-
367
- container.appendChild(iframeWrapper);
368
-
369
- targetElement.replaceWith(container);
370
-
371
- // LAZY LOADING with Intersection Observer + Queue
372
- // Smart loading: only loads PDFs that are actually visible
373
- var queueEntry = null; // Track if this PDF is in queue
374
- var observer = new IntersectionObserver(function (entries) {
375
- entries.forEach(function (entry) {
376
- if (entry.isIntersecting) {
377
- // Update placeholder to show loading state
378
- var textEl = loadingPlaceholder.querySelector('.pdf-loading-text');
379
- if (textEl) textEl.textContent = 'PDF Yükleniyor...';
380
-
381
- var svgEl = loadingPlaceholder.querySelector('svg');
382
- if (svgEl) {
383
- svgEl.style.fill = '#0078d4';
384
- svgEl.style.animation = 'spin 1s linear infinite';
385
- svgEl.innerHTML = '<path d="M12 4V2A10 10 0 0 0 2 12h2a8 8 0 0 1 8-8z"/>';
386
- }
387
-
388
- // Add to queue (if not already)
389
- if (!queueEntry) {
390
- queueEntry = { wrapper: iframeWrapper, filename, placeholder: loadingPlaceholder };
391
- loadQueue.push(queueEntry);
392
- processQueue();
393
- }
394
- } else {
395
- // LEFT viewport - remove from queue if waiting
396
- if (queueEntry && loadQueue.includes(queueEntry)) {
397
- var idx = loadQueue.indexOf(queueEntry);
398
- if (idx > -1) {
399
- loadQueue.splice(idx, 1);
400
-
401
- // Reset placeholder to waiting state
402
- var textEl = loadingPlaceholder.querySelector('.pdf-loading-text');
403
- if (textEl) textEl.textContent = 'Sırada bekliyor...';
404
- var svgEl = loadingPlaceholder.querySelector('svg');
405
- if (svgEl) {
406
- svgEl.style.fill = '#555';
407
- svgEl.style.animation = 'none';
408
- svgEl.innerHTML = '<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"/>';
409
- }
410
- }
411
- queueEntry = null;
412
- }
413
- }
414
- });
415
- }, {
416
- rootMargin: '0px', // Only trigger when actually visible
417
- threshold: 0
418
- });
419
-
420
- observer.observe(container);
421
- }
422
-
423
- function loadPdfIframe(wrapper, filename, placeholder) {
424
- return new Promise((resolve, reject) => {
425
- // Create iframe HIDDEN (z-index: 1, under placeholder)
426
- var iframe = document.createElement('iframe');
427
- iframe.className = 'pdf-secure-iframe';
428
- iframe.style.cssText = 'position:absolute;top:0;left:0;width:100%;height:100%;border:none;z-index:1;';
429
- iframe.src = config.relative_path + '/plugins/pdf-secure/viewer?file=' + encodeURIComponent(filename);
430
- iframe.setAttribute('frameborder', '0');
431
- iframe.setAttribute('allowfullscreen', 'true');
432
- iframe.setAttribute('allow', 'fullscreen');
433
-
434
- // Store resolver for postMessage callback
435
- currentResolver = function () {
436
- // Fade out placeholder, show iframe
437
- if (placeholder) {
438
- placeholder.style.opacity = '0';
439
- setTimeout(function () {
440
- if (placeholder.parentNode) {
441
- placeholder.remove();
442
- }
443
- }, 300);
444
- }
445
- resolve();
446
- };
447
-
448
- iframe.onerror = function () {
449
- currentResolver = null;
450
- if (placeholder) {
451
- var textEl = placeholder.querySelector('.pdf-loading-text');
452
- if (textEl) textEl.textContent = 'Yükleme hatası!';
453
- }
454
- reject(new Error('Failed to load iframe'));
455
- };
456
-
457
- wrapper.appendChild(iframe);
458
-
459
- // Timeout fallback (60 seconds for large PDFs)
460
- setTimeout(function () {
461
- if (currentResolver) {
462
- currentResolver();
463
- currentResolver = null;
464
- }
465
- }, 60000);
466
- });
467
- }
468
- })();
1
+ 'use strict';
2
+
3
+ // Main plugin logic - PDF links become inline embedded viewers with lazy loading + queue
4
+ (async function () {
5
+ // ============================================
6
+ // PDF.js PRELOAD - Cache CDN assets before iframe loads
7
+ // ============================================
8
+ (function preloadPdfJs() {
9
+ const preloads = [
10
+ { href: 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js', as: 'script' },
11
+ { href: 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf_viewer.min.css', as: 'style' }
12
+ ];
13
+ preloads.forEach(({ href, as }) => {
14
+ if (!document.querySelector(`link[href="${href}"]`)) {
15
+ const link = document.createElement('link');
16
+ link.rel = 'preload';
17
+ link.href = href;
18
+ link.as = as;
19
+ link.crossOrigin = 'anonymous';
20
+ document.head.appendChild(link);
21
+ }
22
+ });
23
+ })();
24
+
25
+ // Fullscreen API support detection
26
+ // On touch devices (tablets, mobiles), always use CSS simulated fullscreen.
27
+ // Native fullscreen has an unblockable browser "scroll-to-exit" gesture on touch devices.
28
+ // Desktop keeps native fullscreen for the proper OS-level experience.
29
+ var isTouchDevice = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0);
30
+ var fullscreenApiSupported = !isTouchDevice && !!(
31
+ document.documentElement.requestFullscreen ||
32
+ document.documentElement.webkitRequestFullscreen
33
+ );
34
+ var simulatedFullscreenIframe = null;
35
+ var savedBodyOverflow = '';
36
+
37
+ // Loading queue - only load one PDF at a time
38
+ const loadQueue = [];
39
+ let isLoading = false;
40
+ let currentResolver = null;
41
+
42
+ // ============================================
43
+ // SPA MEMORY CACHE - Cache decoded PDF buffers
44
+ // ============================================
45
+ const pdfBufferCache = new Map(); // filename -> ArrayBuffer
46
+ const CACHE_MAX_SIZE = 5; // ~50MB limit (avg 10MB per PDF)
47
+ let currentLoadingFilename = null;
48
+
49
+ function setCachedBuffer(filename, buffer) {
50
+ // Evict oldest if cache is full
51
+ if (pdfBufferCache.size >= CACHE_MAX_SIZE) {
52
+ const firstKey = pdfBufferCache.keys().next().value;
53
+ pdfBufferCache.delete(firstKey);
54
+ }
55
+ pdfBufferCache.set(filename, buffer);
56
+ }
57
+
58
+ // Listen for postMessage from iframe
59
+ window.addEventListener('message', function (event) {
60
+ // Security: Only accept messages from same origin
61
+ if (event.origin !== window.location.origin) return;
62
+
63
+ // PDF ready - resolve queue
64
+ if (event.data && event.data.type === 'pdf-secure-ready') {
65
+ if (currentResolver) {
66
+ currentResolver();
67
+ currentResolver = null;
68
+ }
69
+ }
70
+
71
+ // PDF buffer from viewer - cache it
72
+ if (event.data && event.data.type === 'pdf-secure-buffer') {
73
+ const { filename, buffer } = event.data;
74
+ if (filename && buffer) {
75
+ setCachedBuffer(filename, buffer);
76
+ }
77
+ }
78
+
79
+ // Fullscreen toggle request from iframe viewer
80
+ if (event.data && event.data.type === 'pdf-secure-fullscreen-toggle') {
81
+ var sourceIframe = document.querySelector('.pdf-secure-iframe');
82
+ // Find the specific iframe that sent the message
83
+ document.querySelectorAll('.pdf-secure-iframe').forEach(function (f) {
84
+ if (f.contentWindow === event.source) sourceIframe = f;
85
+ });
86
+ if (!sourceIframe) return;
87
+
88
+ if (fullscreenApiSupported) {
89
+ // Native fullscreen path
90
+ var fsEl = document.fullscreenElement || document.webkitFullscreenElement;
91
+ if (fsEl) {
92
+ if (document.exitFullscreen) document.exitFullscreen();
93
+ else if (document.webkitExitFullscreen) document.webkitExitFullscreen();
94
+ } else {
95
+ if (sourceIframe.requestFullscreen) sourceIframe.requestFullscreen().catch(function () { });
96
+ else if (sourceIframe.webkitRequestFullscreen) sourceIframe.webkitRequestFullscreen();
97
+ }
98
+ } else {
99
+ // Simulated fullscreen path (iOS Safari / Chrome)
100
+ if (simulatedFullscreenIframe) {
101
+ exitSimulatedFullscreen();
102
+ } else {
103
+ enterSimulatedFullscreen(sourceIframe);
104
+ }
105
+ }
106
+ }
107
+
108
+ // Fullscreen state query from iframe
109
+ if (event.data && event.data.type === 'pdf-secure-fullscreen-query') {
110
+ var fsActive = !!(document.fullscreenElement || document.webkitFullscreenElement) ||
111
+ (simulatedFullscreenIframe !== null);
112
+ if (event.source) {
113
+ event.source.postMessage({
114
+ type: 'pdf-secure-fullscreen-state',
115
+ isFullscreen: fsActive
116
+ }, event.origin);
117
+ }
118
+ }
119
+
120
+ // Viewer asking for cached buffer
121
+ if (event.data && event.data.type === 'pdf-secure-cache-request') {
122
+ const { filename } = event.data;
123
+ const cached = pdfBufferCache.get(filename);
124
+ if (cached && event.source) {
125
+ // Send cached buffer to viewer (transferable for 0-copy)
126
+ // Clone once: keep original in cache, transfer the copy
127
+ const copy = cached.slice(0);
128
+ event.source.postMessage({
129
+ type: 'pdf-secure-cache-response',
130
+ filename: filename,
131
+ buffer: copy
132
+ }, event.origin, [copy]);
133
+ } else if (event.source) {
134
+ // No cache, viewer will fetch normally
135
+ event.source.postMessage({
136
+ type: 'pdf-secure-cache-response',
137
+ filename: filename,
138
+ buffer: null
139
+ }, event.origin);
140
+ }
141
+ }
142
+ });
143
+
144
+ // Forward fullscreen state changes to all viewer iframes
145
+ function notifyFullscreenChange() {
146
+ var fsActive = !!(document.fullscreenElement || document.webkitFullscreenElement);
147
+ if (fsActive) {
148
+ document.body.style.overscrollBehavior = 'none';
149
+ document.body.style.overflow = 'hidden';
150
+ } else {
151
+ document.body.style.overscrollBehavior = '';
152
+ document.body.style.overflow = '';
153
+ }
154
+ document.querySelectorAll('.pdf-secure-iframe').forEach(function (f) {
155
+ if (f.contentWindow) {
156
+ f.style.overscrollBehavior = 'none';
157
+ f.contentWindow.postMessage({
158
+ type: 'pdf-secure-fullscreen-state',
159
+ isFullscreen: fsActive
160
+ }, window.location.origin);
161
+ }
162
+ });
163
+ }
164
+ document.addEventListener('fullscreenchange', notifyFullscreenChange);
165
+ document.addEventListener('webkitfullscreenchange', notifyFullscreenChange);
166
+
167
+ // Touch handler to block parent scroll during simulated fullscreen
168
+ function parentFullscreenTouchHandler(e) {
169
+ e.preventDefault();
170
+ }
171
+
172
+ // Enter CSS simulated fullscreen (for iOS Safari/Chrome)
173
+ function enterSimulatedFullscreen(iframe) {
174
+ if (simulatedFullscreenIframe) return; // already in simulated fullscreen
175
+ simulatedFullscreenIframe = iframe;
176
+
177
+ // Save original iframe styles
178
+ iframe._savedStyle = {
179
+ position: iframe.style.position,
180
+ top: iframe.style.top,
181
+ left: iframe.style.left,
182
+ width: iframe.style.width,
183
+ height: iframe.style.height,
184
+ zIndex: iframe.style.zIndex
185
+ };
186
+
187
+ // Apply fullscreen styles
188
+ iframe.style.position = 'fixed';
189
+ iframe.style.top = '0';
190
+ iframe.style.left = '0';
191
+ iframe.style.width = '100vw';
192
+ iframe.style.width = '100dvw'; // Override: dynamic viewport (excludes browser chrome)
193
+ iframe.style.height = '100vh';
194
+ iframe.style.height = '100dvh'; // Override: dynamic viewport (excludes address bar on mobile)
195
+ iframe.style.zIndex = '2147483647';
196
+
197
+ // Lock body scroll
198
+ savedBodyOverflow = document.body.style.overflow;
199
+ document.body.style.overflow = 'hidden';
200
+ document.body.style.overscrollBehavior = 'none';
201
+
202
+ // Block touch scroll on parent
203
+ document.addEventListener('touchmove', parentFullscreenTouchHandler, { passive: false });
204
+
205
+ // Notify iframe it is now fullscreen
206
+ if (iframe.contentWindow) {
207
+ iframe.contentWindow.postMessage({
208
+ type: 'pdf-secure-fullscreen-state',
209
+ isFullscreen: true
210
+ }, window.location.origin);
211
+ }
212
+ }
213
+
214
+ // Exit CSS simulated fullscreen
215
+ function exitSimulatedFullscreen() {
216
+ if (!simulatedFullscreenIframe) return;
217
+ var iframe = simulatedFullscreenIframe;
218
+ simulatedFullscreenIframe = null;
219
+
220
+ // Restore original iframe styles
221
+ if (iframe._savedStyle) {
222
+ iframe.style.position = iframe._savedStyle.position;
223
+ iframe.style.top = iframe._savedStyle.top;
224
+ iframe.style.left = iframe._savedStyle.left;
225
+ iframe.style.width = iframe._savedStyle.width;
226
+ iframe.style.height = iframe._savedStyle.height;
227
+ iframe.style.zIndex = iframe._savedStyle.zIndex;
228
+ delete iframe._savedStyle;
229
+ }
230
+
231
+ // Restore body scroll
232
+ document.body.style.overflow = savedBodyOverflow;
233
+ document.body.style.overscrollBehavior = '';
234
+ savedBodyOverflow = '';
235
+
236
+ // Remove parent touch block
237
+ document.removeEventListener('touchmove', parentFullscreenTouchHandler);
238
+
239
+ // Notify iframe it is no longer fullscreen
240
+ if (iframe.contentWindow) {
241
+ iframe.contentWindow.postMessage({
242
+ type: 'pdf-secure-fullscreen-state',
243
+ isFullscreen: false
244
+ }, window.location.origin);
245
+ }
246
+ }
247
+
248
+ async function processQueue() {
249
+ if (isLoading || loadQueue.length === 0) return;
250
+
251
+ isLoading = true;
252
+ const { wrapper, filename, placeholder } = loadQueue.shift();
253
+
254
+ try {
255
+ await loadPdfIframe(wrapper, filename, placeholder);
256
+ } catch (err) {
257
+ }
258
+
259
+ isLoading = false;
260
+
261
+ // Small delay between loads
262
+ setTimeout(processQueue, 200);
263
+ }
264
+
265
+ function queuePdfLoad(wrapper, filename, placeholder) {
266
+ loadQueue.push({ wrapper, filename, placeholder });
267
+ processQueue();
268
+ }
269
+
270
+ try {
271
+ var hooks = await app.require('hooks');
272
+
273
+ hooks.on('action:ajaxify.end', function () {
274
+ // Clear queue on page change
275
+ loadQueue.length = 0;
276
+ isLoading = false;
277
+ currentResolver = null;
278
+ // Exit simulated fullscreen on SPA navigation
279
+ exitSimulatedFullscreen();
280
+ interceptPdfLinks();
281
+ });
282
+ } catch (err) {
283
+ }
284
+
285
+ function interceptPdfLinks() {
286
+ var postContents = document.querySelectorAll('[component="post/content"]');
287
+
288
+ postContents.forEach(function (content) {
289
+ // NEW: Detect server-rendered secure placeholders (hides URL from source)
290
+ var placeholders = content.querySelectorAll('.pdf-secure-placeholder');
291
+ placeholders.forEach(function (placeholder) {
292
+ if (placeholder.dataset.pdfSecureProcessed) return;
293
+ placeholder.dataset.pdfSecureProcessed = 'true';
294
+
295
+ var filename = placeholder.dataset.filename;
296
+ var displayName = placeholder.querySelector('span')?.textContent || filename;
297
+
298
+ createPdfViewer(placeholder, filename, displayName);
299
+ });
300
+
301
+ // FALLBACK: Detect old-style PDF links (for backwards compatibility)
302
+ var pdfLinks = content.querySelectorAll('a[href$=".pdf"], a[href$=".PDF"]');
303
+ pdfLinks.forEach(function (link) {
304
+ if (link.dataset.pdfSecure) return;
305
+ link.dataset.pdfSecure = 'true';
306
+
307
+ var href = link.getAttribute('href');
308
+ var parts = href.split('/');
309
+ var filename = parts[parts.length - 1];
310
+ var displayName = link.textContent || filename;
311
+
312
+ createPdfViewer(link, filename, displayName);
313
+ });
314
+ });
315
+ }
316
+
317
+ function createPdfViewer(targetElement, filename, displayName) {
318
+
319
+ // Create container
320
+ var container = document.createElement('div');
321
+ container.className = 'pdf-secure-embed';
322
+ container.style.cssText = 'margin:16px 0;border-radius:12px;overflow:hidden;background:#1f1f1f;border:1px solid rgba(255,255,255,0.1);box-shadow:0 4px 20px rgba(0,0,0,0.25);';
323
+
324
+ // Header
325
+ var header = document.createElement('div');
326
+ header.className = 'pdf-secure-embed-header';
327
+ header.style.cssText = 'display:flex;align-items:center;justify-content:space-between;padding:10px 16px;background:linear-gradient(135deg,#2d2d2d 0%,#252525 100%);border-bottom:1px solid rgba(255,255,255,0.08);';
328
+
329
+ var title = document.createElement('div');
330
+ title.className = 'pdf-secure-embed-title';
331
+ title.style.cssText = 'display:flex;align-items:center;gap:10px;color:#fff;font-size:14px;font-weight:500;';
332
+
333
+ var icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
334
+ icon.setAttribute('viewBox', '0 0 24 24');
335
+ icon.style.cssText = 'width:20px;height:20px;min-width:20px;max-width:20px;fill:#e81224;flex-shrink:0;';
336
+ icon.innerHTML = '<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"/>';
337
+
338
+ var nameSpan = document.createElement('span');
339
+ nameSpan.style.cssText = 'white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:400px;';
340
+ try { nameSpan.textContent = decodeURIComponent(displayName); }
341
+ catch (e) { nameSpan.textContent = displayName; }
342
+
343
+ title.appendChild(icon);
344
+ title.appendChild(nameSpan);
345
+
346
+ header.appendChild(title);
347
+ container.appendChild(header);
348
+
349
+ // Body with loading placeholder
350
+ var iframeWrapper = document.createElement('div');
351
+ iframeWrapper.className = 'pdf-secure-embed-body';
352
+ iframeWrapper.style.cssText = 'position:relative;width:100%;height:600px;background:#525659;';
353
+
354
+ // Loading placeholder - ALWAYS VISIBLE until PDF ready (z-index: 10)
355
+ var loadingPlaceholder = document.createElement('div');
356
+ loadingPlaceholder.className = 'pdf-loading-placeholder';
357
+ loadingPlaceholder.style.cssText = 'position:absolute;top:0;left:0;right:0;bottom:0;display:flex;flex-direction:column;align-items:center;justify-content:center;background:#2d2d2d;color:#fff;gap:16px;z-index:10;transition:opacity 0.3s;';
358
+ loadingPlaceholder.innerHTML = `
359
+ <svg viewBox="0 0 24 24" style="width:48px;height:48px;fill:#555;">
360
+ <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"/>
361
+ </svg>
362
+ <div class="pdf-loading-text" style="font-size:14px;color:#a0a0a0;">Sırada bekliyor...</div>
363
+ <style>@keyframes spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}</style>
364
+ `;
365
+ iframeWrapper.appendChild(loadingPlaceholder);
366
+
367
+ container.appendChild(iframeWrapper);
368
+
369
+ targetElement.replaceWith(container);
370
+
371
+ // LAZY LOADING with Intersection Observer + Queue
372
+ // Smart loading: only loads PDFs that are actually visible
373
+ var queueEntry = null; // Track if this PDF is in queue
374
+ var observer = new IntersectionObserver(function (entries) {
375
+ entries.forEach(function (entry) {
376
+ if (entry.isIntersecting) {
377
+ // Update placeholder to show loading state
378
+ var textEl = loadingPlaceholder.querySelector('.pdf-loading-text');
379
+ if (textEl) textEl.textContent = 'PDF Yükleniyor...';
380
+
381
+ var svgEl = loadingPlaceholder.querySelector('svg');
382
+ if (svgEl) {
383
+ svgEl.style.fill = '#0078d4';
384
+ svgEl.style.animation = 'spin 1s linear infinite';
385
+ svgEl.innerHTML = '<path d="M12 4V2A10 10 0 0 0 2 12h2a8 8 0 0 1 8-8z"/>';
386
+ }
387
+
388
+ // Add to queue (if not already)
389
+ if (!queueEntry) {
390
+ queueEntry = { wrapper: iframeWrapper, filename, placeholder: loadingPlaceholder };
391
+ loadQueue.push(queueEntry);
392
+ processQueue();
393
+ }
394
+ } else {
395
+ // LEFT viewport - remove from queue if waiting
396
+ if (queueEntry && loadQueue.includes(queueEntry)) {
397
+ var idx = loadQueue.indexOf(queueEntry);
398
+ if (idx > -1) {
399
+ loadQueue.splice(idx, 1);
400
+
401
+ // Reset placeholder to waiting state
402
+ var textEl = loadingPlaceholder.querySelector('.pdf-loading-text');
403
+ if (textEl) textEl.textContent = 'Sırada bekliyor...';
404
+ var svgEl = loadingPlaceholder.querySelector('svg');
405
+ if (svgEl) {
406
+ svgEl.style.fill = '#555';
407
+ svgEl.style.animation = 'none';
408
+ svgEl.innerHTML = '<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"/>';
409
+ }
410
+ }
411
+ queueEntry = null;
412
+ }
413
+ }
414
+ });
415
+ }, {
416
+ rootMargin: '0px', // Only trigger when actually visible
417
+ threshold: 0
418
+ });
419
+
420
+ observer.observe(container);
421
+ }
422
+
423
+ function loadPdfIframe(wrapper, filename, placeholder) {
424
+ return new Promise((resolve, reject) => {
425
+ // Create iframe HIDDEN (z-index: 1, under placeholder)
426
+ var iframe = document.createElement('iframe');
427
+ iframe.className = 'pdf-secure-iframe';
428
+ iframe.style.cssText = 'position:absolute;top:0;left:0;width:100%;height:100%;border:none;z-index:1;';
429
+ iframe.src = config.relative_path + '/plugins/pdf-secure/viewer?file=' + encodeURIComponent(filename);
430
+ iframe.setAttribute('frameborder', '0');
431
+ iframe.setAttribute('allowfullscreen', 'true');
432
+ iframe.setAttribute('allow', 'fullscreen');
433
+
434
+ // Store resolver for postMessage callback
435
+ currentResolver = function () {
436
+ // Fade out placeholder, show iframe
437
+ if (placeholder) {
438
+ placeholder.style.opacity = '0';
439
+ setTimeout(function () {
440
+ if (placeholder.parentNode) {
441
+ placeholder.remove();
442
+ }
443
+ }, 300);
444
+ }
445
+ resolve();
446
+ };
447
+
448
+ iframe.onerror = function () {
449
+ currentResolver = null;
450
+ if (placeholder) {
451
+ var textEl = placeholder.querySelector('.pdf-loading-text');
452
+ if (textEl) textEl.textContent = 'Yükleme hatası!';
453
+ }
454
+ reject(new Error('Failed to load iframe'));
455
+ };
456
+
457
+ wrapper.appendChild(iframe);
458
+
459
+ // Timeout fallback (60 seconds for large PDFs)
460
+ setTimeout(function () {
461
+ if (currentResolver) {
462
+ currentResolver();
463
+ currentResolver = null;
464
+ }
465
+ }, 60000);
466
+ });
467
+ }
468
+ })();