nodebb-plugin-pdf-secure 1.2.14 → 1.2.16
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/package.json +1 -1
- package/static/lib/main.js +158 -0
- package/static/viewer.html +77 -26
package/package.json
CHANGED
package/static/lib/main.js
CHANGED
|
@@ -22,6 +22,18 @@
|
|
|
22
22
|
});
|
|
23
23
|
})();
|
|
24
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
|
+
|
|
25
37
|
// Loading queue - only load one PDF at a time
|
|
26
38
|
const loadQueue = [];
|
|
27
39
|
let isLoading = false;
|
|
@@ -67,6 +79,47 @@
|
|
|
67
79
|
}
|
|
68
80
|
}
|
|
69
81
|
|
|
82
|
+
// Fullscreen toggle request from iframe viewer
|
|
83
|
+
if (event.data && event.data.type === 'pdf-secure-fullscreen-toggle') {
|
|
84
|
+
var sourceIframe = document.querySelector('.pdf-secure-iframe');
|
|
85
|
+
// Find the specific iframe that sent the message
|
|
86
|
+
document.querySelectorAll('.pdf-secure-iframe').forEach(function (f) {
|
|
87
|
+
if (f.contentWindow === event.source) sourceIframe = f;
|
|
88
|
+
});
|
|
89
|
+
if (!sourceIframe) return;
|
|
90
|
+
|
|
91
|
+
if (fullscreenApiSupported) {
|
|
92
|
+
// Native fullscreen path
|
|
93
|
+
var fsEl = document.fullscreenElement || document.webkitFullscreenElement;
|
|
94
|
+
if (fsEl) {
|
|
95
|
+
if (document.exitFullscreen) document.exitFullscreen();
|
|
96
|
+
else if (document.webkitExitFullscreen) document.webkitExitFullscreen();
|
|
97
|
+
} else {
|
|
98
|
+
if (sourceIframe.requestFullscreen) sourceIframe.requestFullscreen().catch(function () { });
|
|
99
|
+
else if (sourceIframe.webkitRequestFullscreen) sourceIframe.webkitRequestFullscreen();
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
// Simulated fullscreen path (iOS Safari / Chrome)
|
|
103
|
+
if (simulatedFullscreenIframe) {
|
|
104
|
+
exitSimulatedFullscreen();
|
|
105
|
+
} else {
|
|
106
|
+
enterSimulatedFullscreen(sourceIframe);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Fullscreen state query from iframe
|
|
112
|
+
if (event.data && event.data.type === 'pdf-secure-fullscreen-query') {
|
|
113
|
+
var fsActive = !!(document.fullscreenElement || document.webkitFullscreenElement) ||
|
|
114
|
+
(simulatedFullscreenIframe !== null);
|
|
115
|
+
if (event.source) {
|
|
116
|
+
event.source.postMessage({
|
|
117
|
+
type: 'pdf-secure-fullscreen-state',
|
|
118
|
+
isFullscreen: fsActive
|
|
119
|
+
}, event.origin);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
70
123
|
// Viewer asking for cached buffer
|
|
71
124
|
if (event.data && event.data.type === 'pdf-secure-cache-request') {
|
|
72
125
|
const { filename } = event.data;
|
|
@@ -93,6 +146,108 @@
|
|
|
93
146
|
}
|
|
94
147
|
});
|
|
95
148
|
|
|
149
|
+
// Forward fullscreen state changes to all viewer iframes
|
|
150
|
+
function notifyFullscreenChange() {
|
|
151
|
+
var fsActive = !!(document.fullscreenElement || document.webkitFullscreenElement);
|
|
152
|
+
if (fsActive) {
|
|
153
|
+
document.body.style.overscrollBehavior = 'none';
|
|
154
|
+
document.body.style.overflow = 'hidden';
|
|
155
|
+
} else {
|
|
156
|
+
document.body.style.overscrollBehavior = '';
|
|
157
|
+
document.body.style.overflow = '';
|
|
158
|
+
}
|
|
159
|
+
document.querySelectorAll('.pdf-secure-iframe').forEach(function (f) {
|
|
160
|
+
if (f.contentWindow) {
|
|
161
|
+
f.style.overscrollBehavior = 'none';
|
|
162
|
+
f.contentWindow.postMessage({
|
|
163
|
+
type: 'pdf-secure-fullscreen-state',
|
|
164
|
+
isFullscreen: fsActive
|
|
165
|
+
}, window.location.origin);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
document.addEventListener('fullscreenchange', notifyFullscreenChange);
|
|
170
|
+
document.addEventListener('webkitfullscreenchange', notifyFullscreenChange);
|
|
171
|
+
|
|
172
|
+
// Touch handler to block parent scroll during simulated fullscreen
|
|
173
|
+
function parentFullscreenTouchHandler(e) {
|
|
174
|
+
e.preventDefault();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Enter CSS simulated fullscreen (for iOS Safari/Chrome)
|
|
178
|
+
function enterSimulatedFullscreen(iframe) {
|
|
179
|
+
if (simulatedFullscreenIframe) return; // already in simulated fullscreen
|
|
180
|
+
simulatedFullscreenIframe = iframe;
|
|
181
|
+
|
|
182
|
+
// Save original iframe styles
|
|
183
|
+
iframe._savedStyle = {
|
|
184
|
+
position: iframe.style.position,
|
|
185
|
+
top: iframe.style.top,
|
|
186
|
+
left: iframe.style.left,
|
|
187
|
+
width: iframe.style.width,
|
|
188
|
+
height: iframe.style.height,
|
|
189
|
+
zIndex: iframe.style.zIndex
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// Apply fullscreen styles
|
|
193
|
+
iframe.style.position = 'fixed';
|
|
194
|
+
iframe.style.top = '0';
|
|
195
|
+
iframe.style.left = '0';
|
|
196
|
+
iframe.style.width = '100vw';
|
|
197
|
+
iframe.style.height = '100vh';
|
|
198
|
+
iframe.style.zIndex = '2147483647';
|
|
199
|
+
|
|
200
|
+
// Lock body scroll
|
|
201
|
+
savedBodyOverflow = document.body.style.overflow;
|
|
202
|
+
document.body.style.overflow = 'hidden';
|
|
203
|
+
document.body.style.overscrollBehavior = 'none';
|
|
204
|
+
|
|
205
|
+
// Block touch scroll on parent
|
|
206
|
+
document.addEventListener('touchmove', parentFullscreenTouchHandler, { passive: false });
|
|
207
|
+
|
|
208
|
+
// Notify iframe it is now fullscreen
|
|
209
|
+
if (iframe.contentWindow) {
|
|
210
|
+
iframe.contentWindow.postMessage({
|
|
211
|
+
type: 'pdf-secure-fullscreen-state',
|
|
212
|
+
isFullscreen: true
|
|
213
|
+
}, window.location.origin);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Exit CSS simulated fullscreen
|
|
218
|
+
function exitSimulatedFullscreen() {
|
|
219
|
+
if (!simulatedFullscreenIframe) return;
|
|
220
|
+
var iframe = simulatedFullscreenIframe;
|
|
221
|
+
simulatedFullscreenIframe = null;
|
|
222
|
+
|
|
223
|
+
// Restore original iframe styles
|
|
224
|
+
if (iframe._savedStyle) {
|
|
225
|
+
iframe.style.position = iframe._savedStyle.position;
|
|
226
|
+
iframe.style.top = iframe._savedStyle.top;
|
|
227
|
+
iframe.style.left = iframe._savedStyle.left;
|
|
228
|
+
iframe.style.width = iframe._savedStyle.width;
|
|
229
|
+
iframe.style.height = iframe._savedStyle.height;
|
|
230
|
+
iframe.style.zIndex = iframe._savedStyle.zIndex;
|
|
231
|
+
delete iframe._savedStyle;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Restore body scroll
|
|
235
|
+
document.body.style.overflow = savedBodyOverflow;
|
|
236
|
+
document.body.style.overscrollBehavior = '';
|
|
237
|
+
savedBodyOverflow = '';
|
|
238
|
+
|
|
239
|
+
// Remove parent touch block
|
|
240
|
+
document.removeEventListener('touchmove', parentFullscreenTouchHandler);
|
|
241
|
+
|
|
242
|
+
// Notify iframe it is no longer fullscreen
|
|
243
|
+
if (iframe.contentWindow) {
|
|
244
|
+
iframe.contentWindow.postMessage({
|
|
245
|
+
type: 'pdf-secure-fullscreen-state',
|
|
246
|
+
isFullscreen: false
|
|
247
|
+
}, window.location.origin);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
96
251
|
async function processQueue() {
|
|
97
252
|
if (isLoading || loadQueue.length === 0) return;
|
|
98
253
|
|
|
@@ -124,6 +279,8 @@
|
|
|
124
279
|
loadQueue.length = 0;
|
|
125
280
|
isLoading = false;
|
|
126
281
|
currentResolver = null;
|
|
282
|
+
// Exit simulated fullscreen on SPA navigation
|
|
283
|
+
exitSimulatedFullscreen();
|
|
127
284
|
interceptPdfLinks();
|
|
128
285
|
});
|
|
129
286
|
} catch (err) {
|
|
@@ -278,6 +435,7 @@
|
|
|
278
435
|
iframe.src = config.relative_path + '/plugins/pdf-secure/viewer?file=' + encodeURIComponent(filename);
|
|
279
436
|
iframe.setAttribute('frameborder', '0');
|
|
280
437
|
iframe.setAttribute('allowfullscreen', 'true');
|
|
438
|
+
iframe.setAttribute('allow', 'fullscreen');
|
|
281
439
|
|
|
282
440
|
// Store resolver for postMessage callback
|
|
283
441
|
currentResolver = function () {
|
package/static/viewer.html
CHANGED
|
@@ -2753,7 +2753,7 @@
|
|
|
2753
2753
|
const syntheticStart = {
|
|
2754
2754
|
currentTarget: svg,
|
|
2755
2755
|
touches: [{ clientX: touchStartX, clientY: touchStartYDraw }],
|
|
2756
|
-
preventDefault: () => {}
|
|
2756
|
+
preventDefault: () => { }
|
|
2757
2757
|
};
|
|
2758
2758
|
startDraw(syntheticStart, pageNum);
|
|
2759
2759
|
// Then immediately draw to current position for continuity
|
|
@@ -2768,7 +2768,7 @@
|
|
|
2768
2768
|
const syntheticStart = {
|
|
2769
2769
|
currentTarget: svg,
|
|
2770
2770
|
touches: [{ clientX: touchStartX, clientY: touchStartYDraw }],
|
|
2771
|
-
preventDefault: () => {}
|
|
2771
|
+
preventDefault: () => { }
|
|
2772
2772
|
};
|
|
2773
2773
|
startDraw(syntheticStart, pageNum);
|
|
2774
2774
|
stopDraw(pageNum);
|
|
@@ -4461,25 +4461,34 @@
|
|
|
4461
4461
|
// ERGONOMIC FEATURES
|
|
4462
4462
|
// ==========================================
|
|
4463
4463
|
|
|
4464
|
-
// Fullscreen
|
|
4464
|
+
// Fullscreen: track state (parent-managed when in iframe)
|
|
4465
|
+
let isFullscreen = false;
|
|
4466
|
+
const inIframe = window.self !== window.top;
|
|
4467
|
+
|
|
4465
4468
|
function toggleFullscreen() {
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4469
|
+
if (inIframe) {
|
|
4470
|
+
// In iframe: ask parent to fullscreen the iframe element
|
|
4471
|
+
// Parent manages fullscreen → more stable on tablets
|
|
4472
|
+
window.parent.postMessage({ type: 'pdf-secure-fullscreen-toggle' }, '*');
|
|
4470
4473
|
} else {
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
+
// Standalone: use local fullscreen
|
|
4475
|
+
const fsEl = document.fullscreenElement || document.webkitFullscreenElement;
|
|
4476
|
+
if (fsEl) {
|
|
4477
|
+
if (document.exitFullscreen) document.exitFullscreen();
|
|
4478
|
+
else if (document.webkitExitFullscreen) document.webkitExitFullscreen();
|
|
4479
|
+
} else {
|
|
4480
|
+
const el = document.documentElement;
|
|
4481
|
+
if (el.requestFullscreen) el.requestFullscreen().catch(() => { });
|
|
4482
|
+
else if (el.webkitRequestFullscreen) el.webkitRequestFullscreen();
|
|
4483
|
+
}
|
|
4474
4484
|
}
|
|
4475
4485
|
}
|
|
4476
4486
|
|
|
4477
|
-
// Update fullscreen button icon
|
|
4487
|
+
// Update fullscreen button icon based on state
|
|
4478
4488
|
function updateFullscreenIcon() {
|
|
4479
4489
|
const icon = document.getElementById('fullscreenIcon');
|
|
4480
4490
|
const btn = document.getElementById('fullscreenBtn');
|
|
4481
|
-
|
|
4482
|
-
if (fsEl) {
|
|
4491
|
+
if (isFullscreen) {
|
|
4483
4492
|
icon.innerHTML = '<path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"/>';
|
|
4484
4493
|
btn.classList.add('active');
|
|
4485
4494
|
} else {
|
|
@@ -4488,8 +4497,37 @@
|
|
|
4488
4497
|
}
|
|
4489
4498
|
}
|
|
4490
4499
|
|
|
4491
|
-
|
|
4492
|
-
|
|
4500
|
+
// Apply touch restrictions when entering/exiting fullscreen
|
|
4501
|
+
function applyFullscreenTouchRestrictions() {
|
|
4502
|
+
if (isFullscreen) {
|
|
4503
|
+
document.documentElement.style.touchAction = 'pan-y pinch-zoom';
|
|
4504
|
+
document.documentElement.style.overscrollBehavior = 'none';
|
|
4505
|
+
} else {
|
|
4506
|
+
document.documentElement.style.touchAction = '';
|
|
4507
|
+
document.documentElement.style.overscrollBehavior = '';
|
|
4508
|
+
}
|
|
4509
|
+
}
|
|
4510
|
+
|
|
4511
|
+
// Listen for fullscreen state from parent (iframe mode)
|
|
4512
|
+
window.addEventListener('message', (event) => {
|
|
4513
|
+
if (event.data && event.data.type === 'pdf-secure-fullscreen-state') {
|
|
4514
|
+
isFullscreen = event.data.isFullscreen;
|
|
4515
|
+
updateFullscreenIcon();
|
|
4516
|
+
applyFullscreenTouchRestrictions();
|
|
4517
|
+
}
|
|
4518
|
+
});
|
|
4519
|
+
|
|
4520
|
+
// Local fullscreen events (standalone mode)
|
|
4521
|
+
document.addEventListener('fullscreenchange', () => {
|
|
4522
|
+
isFullscreen = !!(document.fullscreenElement || document.webkitFullscreenElement);
|
|
4523
|
+
updateFullscreenIcon();
|
|
4524
|
+
applyFullscreenTouchRestrictions();
|
|
4525
|
+
});
|
|
4526
|
+
document.addEventListener('webkitfullscreenchange', () => {
|
|
4527
|
+
isFullscreen = !!(document.fullscreenElement || document.webkitFullscreenElement);
|
|
4528
|
+
updateFullscreenIcon();
|
|
4529
|
+
applyFullscreenTouchRestrictions();
|
|
4530
|
+
});
|
|
4493
4531
|
|
|
4494
4532
|
// Fullscreen button click
|
|
4495
4533
|
document.getElementById('fullscreenBtn').onclick = () => toggleFullscreen();
|
|
@@ -4781,11 +4819,21 @@
|
|
|
4781
4819
|
const touchY = e.touches[0].clientY;
|
|
4782
4820
|
const deltaY = touchStartY - touchY; // positive = scrolling down
|
|
4783
4821
|
|
|
4784
|
-
const
|
|
4785
|
-
const
|
|
4822
|
+
const BUFFER = 5; // px buffer to catch near-boundary gestures
|
|
4823
|
+
const atTop = container.scrollTop <= BUFFER;
|
|
4824
|
+
const atBottom = container.scrollTop + container.clientHeight >= container.scrollHeight - 1 - BUFFER;
|
|
4786
4825
|
|
|
4787
|
-
|
|
4788
|
-
|
|
4826
|
+
// In fullscreen, use buffer; outside fullscreen, only block at exact boundary
|
|
4827
|
+
if (isFullscreen) {
|
|
4828
|
+
if ((atTop && deltaY < 0) || (atBottom && deltaY > 0)) {
|
|
4829
|
+
e.preventDefault();
|
|
4830
|
+
}
|
|
4831
|
+
} else {
|
|
4832
|
+
const atTopExact = container.scrollTop <= 0;
|
|
4833
|
+
const atBottomExact = container.scrollTop + container.clientHeight >= container.scrollHeight - 1;
|
|
4834
|
+
if ((atTopExact && deltaY < 0) || (atBottomExact && deltaY > 0)) {
|
|
4835
|
+
e.preventDefault();
|
|
4836
|
+
}
|
|
4789
4837
|
}
|
|
4790
4838
|
touchStartY = touchY;
|
|
4791
4839
|
}, { passive: false });
|
|
@@ -4802,17 +4850,20 @@
|
|
|
4802
4850
|
document.addEventListener('touchmove', (e) => {
|
|
4803
4851
|
if (e.touches.length !== 1) return;
|
|
4804
4852
|
|
|
4805
|
-
// Only prevent when in fullscreen
|
|
4806
|
-
|
|
4807
|
-
if (!fsEl) return;
|
|
4853
|
+
// Only prevent when in fullscreen (isFullscreen works in both iframe and standalone)
|
|
4854
|
+
if (!isFullscreen) return;
|
|
4808
4855
|
|
|
4809
4856
|
const touchY = e.touches[0].clientY;
|
|
4810
4857
|
const deltaY = docTouchStartY - touchY;
|
|
4811
|
-
const atTop = container.scrollTop <= 0;
|
|
4812
|
-
const atBottom = container.scrollTop + container.clientHeight >= container.scrollHeight - 1;
|
|
4813
4858
|
|
|
4814
|
-
|
|
4815
|
-
|
|
4859
|
+
const BUFFER = 5; // px buffer to catch near-boundary gestures
|
|
4860
|
+
const atTop = container.scrollTop <= BUFFER;
|
|
4861
|
+
const atBottom = container.scrollTop + container.clientHeight >= container.scrollHeight - 1 - BUFFER;
|
|
4862
|
+
|
|
4863
|
+
// If content fits entirely (no scroll needed), block all overscroll
|
|
4864
|
+
const contentFits = container.scrollHeight <= container.clientHeight + 1;
|
|
4865
|
+
|
|
4866
|
+
if (contentFits || (atTop && deltaY < 0) || (atBottom && deltaY > 0)) {
|
|
4816
4867
|
e.preventDefault();
|
|
4817
4868
|
}
|
|
4818
4869
|
docTouchStartY = touchY;
|