nodebb-plugin-pdf-secure 1.2.15 → 1.2.17
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 +119 -7
- package/static/viewer.html +58 -55
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;
|
|
@@ -76,19 +88,30 @@
|
|
|
76
88
|
});
|
|
77
89
|
if (!sourceIframe) return;
|
|
78
90
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
+
}
|
|
83
101
|
} else {
|
|
84
|
-
|
|
85
|
-
|
|
102
|
+
// Simulated fullscreen path (iOS Safari / Chrome)
|
|
103
|
+
if (simulatedFullscreenIframe) {
|
|
104
|
+
exitSimulatedFullscreen();
|
|
105
|
+
} else {
|
|
106
|
+
enterSimulatedFullscreen(sourceIframe);
|
|
107
|
+
}
|
|
86
108
|
}
|
|
87
109
|
}
|
|
88
110
|
|
|
89
111
|
// Fullscreen state query from iframe
|
|
90
112
|
if (event.data && event.data.type === 'pdf-secure-fullscreen-query') {
|
|
91
|
-
var fsActive = !!(document.fullscreenElement || document.webkitFullscreenElement)
|
|
113
|
+
var fsActive = !!(document.fullscreenElement || document.webkitFullscreenElement) ||
|
|
114
|
+
(simulatedFullscreenIframe !== null);
|
|
92
115
|
if (event.source) {
|
|
93
116
|
event.source.postMessage({
|
|
94
117
|
type: 'pdf-secure-fullscreen-state',
|
|
@@ -126,8 +149,16 @@
|
|
|
126
149
|
// Forward fullscreen state changes to all viewer iframes
|
|
127
150
|
function notifyFullscreenChange() {
|
|
128
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
|
+
}
|
|
129
159
|
document.querySelectorAll('.pdf-secure-iframe').forEach(function (f) {
|
|
130
160
|
if (f.contentWindow) {
|
|
161
|
+
f.style.overscrollBehavior = 'none';
|
|
131
162
|
f.contentWindow.postMessage({
|
|
132
163
|
type: 'pdf-secure-fullscreen-state',
|
|
133
164
|
isFullscreen: fsActive
|
|
@@ -138,6 +169,85 @@
|
|
|
138
169
|
document.addEventListener('fullscreenchange', notifyFullscreenChange);
|
|
139
170
|
document.addEventListener('webkitfullscreenchange', notifyFullscreenChange);
|
|
140
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
|
+
|
|
141
251
|
async function processQueue() {
|
|
142
252
|
if (isLoading || loadQueue.length === 0) return;
|
|
143
253
|
|
|
@@ -169,6 +279,8 @@
|
|
|
169
279
|
loadQueue.length = 0;
|
|
170
280
|
isLoading = false;
|
|
171
281
|
currentResolver = null;
|
|
282
|
+
// Exit simulated fullscreen on SPA navigation
|
|
283
|
+
exitSimulatedFullscreen();
|
|
172
284
|
interceptPdfLinks();
|
|
173
285
|
});
|
|
174
286
|
} catch (err) {
|
package/static/viewer.html
CHANGED
|
@@ -2708,72 +2708,36 @@
|
|
|
2708
2708
|
|
|
2709
2709
|
svg.addEventListener('touchstart', (e) => {
|
|
2710
2710
|
if (!currentTool || e.touches.length !== 1) return;
|
|
2711
|
-
|
|
2712
|
-
//
|
|
2713
|
-
|
|
2714
|
-
e.preventDefault();
|
|
2715
|
-
startDraw(e, pageNum);
|
|
2716
|
-
touchDrawing = true;
|
|
2717
|
-
touchDrawDecided = true;
|
|
2718
|
-
return;
|
|
2719
|
-
}
|
|
2720
|
-
|
|
2721
|
-
// For pen/highlight/shape: wait to decide scroll vs draw
|
|
2722
|
-
touchDrawDecided = false;
|
|
2723
|
-
touchDrawing = false;
|
|
2711
|
+
// In annotation mode, always draw immediately — no direction detection.
|
|
2712
|
+
// Pan and pinch-zoom are blocked at the container level when a tool is active.
|
|
2713
|
+
e.preventDefault();
|
|
2724
2714
|
touchStartX = e.touches[0].clientX;
|
|
2725
2715
|
touchStartYDraw = e.touches[0].clientY;
|
|
2716
|
+
startDraw(e, pageNum);
|
|
2717
|
+
touchDrawing = true;
|
|
2718
|
+
touchDrawDecided = true;
|
|
2726
2719
|
}, { passive: false, signal });
|
|
2727
2720
|
|
|
2728
2721
|
svg.addEventListener('touchmove', (e) => {
|
|
2729
2722
|
if (!currentTool || e.touches.length !== 1) return;
|
|
2730
|
-
|
|
2731
2723
|
if (touchDrawing) {
|
|
2732
|
-
// Already decided: drawing
|
|
2733
|
-
e.preventDefault();
|
|
2734
|
-
draw(e);
|
|
2735
|
-
return;
|
|
2736
|
-
}
|
|
2737
|
-
|
|
2738
|
-
if (touchDrawDecided) return; // decided scroll, let it through
|
|
2739
|
-
|
|
2740
|
-
const dx = e.touches[0].clientX - touchStartX;
|
|
2741
|
-
const dy = e.touches[0].clientY - touchStartYDraw;
|
|
2742
|
-
|
|
2743
|
-
if (Math.abs(dx) + Math.abs(dy) > 10) {
|
|
2744
|
-
touchDrawDecided = true;
|
|
2745
|
-
|
|
2746
|
-
if (Math.abs(dy) > Math.abs(dx) * 2) {
|
|
2747
|
-
// Predominantly vertical → scroll (don't prevent default)
|
|
2748
|
-
return;
|
|
2749
|
-
}
|
|
2750
|
-
// Horizontal or diagonal → drawing
|
|
2751
2724
|
e.preventDefault();
|
|
2752
|
-
// Start draw from ORIGINAL touch position (not current 10px-offset position)
|
|
2753
|
-
const syntheticStart = {
|
|
2754
|
-
currentTarget: svg,
|
|
2755
|
-
touches: [{ clientX: touchStartX, clientY: touchStartYDraw }],
|
|
2756
|
-
preventDefault: () => {}
|
|
2757
|
-
};
|
|
2758
|
-
startDraw(syntheticStart, pageNum);
|
|
2759
|
-
// Then immediately draw to current position for continuity
|
|
2760
2725
|
draw(e);
|
|
2761
|
-
touchDrawing = true;
|
|
2762
2726
|
}
|
|
2763
2727
|
}, { passive: false, signal });
|
|
2764
2728
|
|
|
2765
2729
|
svg.addEventListener('touchend', (e) => {
|
|
2766
|
-
if (
|
|
2767
|
-
|
|
2730
|
+
if (touchDrawing) {
|
|
2731
|
+
stopDraw(pageNum);
|
|
2732
|
+
} else if (currentTool && currentTool !== 'eraser' && currentTool !== 'select') {
|
|
2733
|
+
// Tap without moving → draw a dot at touch position
|
|
2768
2734
|
const syntheticStart = {
|
|
2769
2735
|
currentTarget: svg,
|
|
2770
2736
|
touches: [{ clientX: touchStartX, clientY: touchStartYDraw }],
|
|
2771
|
-
preventDefault: () => {}
|
|
2737
|
+
preventDefault: () => { }
|
|
2772
2738
|
};
|
|
2773
2739
|
startDraw(syntheticStart, pageNum);
|
|
2774
2740
|
stopDraw(pageNum);
|
|
2775
|
-
} else if (touchDrawing) {
|
|
2776
|
-
stopDraw(pageNum);
|
|
2777
2741
|
}
|
|
2778
2742
|
touchDrawDecided = false;
|
|
2779
2743
|
touchDrawing = false;
|
|
@@ -4497,11 +4461,23 @@
|
|
|
4497
4461
|
}
|
|
4498
4462
|
}
|
|
4499
4463
|
|
|
4464
|
+
// Apply touch restrictions when entering/exiting fullscreen
|
|
4465
|
+
function applyFullscreenTouchRestrictions() {
|
|
4466
|
+
if (isFullscreen) {
|
|
4467
|
+
document.documentElement.style.touchAction = 'pan-y pinch-zoom';
|
|
4468
|
+
document.documentElement.style.overscrollBehavior = 'none';
|
|
4469
|
+
} else {
|
|
4470
|
+
document.documentElement.style.touchAction = '';
|
|
4471
|
+
document.documentElement.style.overscrollBehavior = '';
|
|
4472
|
+
}
|
|
4473
|
+
}
|
|
4474
|
+
|
|
4500
4475
|
// Listen for fullscreen state from parent (iframe mode)
|
|
4501
4476
|
window.addEventListener('message', (event) => {
|
|
4502
4477
|
if (event.data && event.data.type === 'pdf-secure-fullscreen-state') {
|
|
4503
4478
|
isFullscreen = event.data.isFullscreen;
|
|
4504
4479
|
updateFullscreenIcon();
|
|
4480
|
+
applyFullscreenTouchRestrictions();
|
|
4505
4481
|
}
|
|
4506
4482
|
});
|
|
4507
4483
|
|
|
@@ -4509,10 +4485,12 @@
|
|
|
4509
4485
|
document.addEventListener('fullscreenchange', () => {
|
|
4510
4486
|
isFullscreen = !!(document.fullscreenElement || document.webkitFullscreenElement);
|
|
4511
4487
|
updateFullscreenIcon();
|
|
4488
|
+
applyFullscreenTouchRestrictions();
|
|
4512
4489
|
});
|
|
4513
4490
|
document.addEventListener('webkitfullscreenchange', () => {
|
|
4514
4491
|
isFullscreen = !!(document.fullscreenElement || document.webkitFullscreenElement);
|
|
4515
4492
|
updateFullscreenIcon();
|
|
4493
|
+
applyFullscreenTouchRestrictions();
|
|
4516
4494
|
});
|
|
4517
4495
|
|
|
4518
4496
|
// Fullscreen button click
|
|
@@ -4637,6 +4615,11 @@
|
|
|
4637
4615
|
|
|
4638
4616
|
container.addEventListener('touchstart', (e) => {
|
|
4639
4617
|
if (e.touches.length === 2) {
|
|
4618
|
+
// Block pinch-zoom when an annotation tool is active
|
|
4619
|
+
if (annotationMode && currentTool) {
|
|
4620
|
+
e.preventDefault();
|
|
4621
|
+
return;
|
|
4622
|
+
}
|
|
4640
4623
|
// Cancel any active drawing and clean up
|
|
4641
4624
|
if (isDrawing && currentDrawingPage) {
|
|
4642
4625
|
const savePage = currentDrawingPage;
|
|
@@ -4802,14 +4785,30 @@
|
|
|
4802
4785
|
container.addEventListener('touchmove', (e) => {
|
|
4803
4786
|
if (isPinching || e.touches.length !== 1) return;
|
|
4804
4787
|
|
|
4788
|
+
// Block all container scroll when an annotation tool is active
|
|
4789
|
+
if (annotationMode && currentTool) {
|
|
4790
|
+
e.preventDefault();
|
|
4791
|
+
return;
|
|
4792
|
+
}
|
|
4793
|
+
|
|
4805
4794
|
const touchY = e.touches[0].clientY;
|
|
4806
4795
|
const deltaY = touchStartY - touchY; // positive = scrolling down
|
|
4807
4796
|
|
|
4808
|
-
const
|
|
4809
|
-
const
|
|
4797
|
+
const BUFFER = 5; // px buffer to catch near-boundary gestures
|
|
4798
|
+
const atTop = container.scrollTop <= BUFFER;
|
|
4799
|
+
const atBottom = container.scrollTop + container.clientHeight >= container.scrollHeight - 1 - BUFFER;
|
|
4810
4800
|
|
|
4811
|
-
|
|
4812
|
-
|
|
4801
|
+
// In fullscreen, use buffer; outside fullscreen, only block at exact boundary
|
|
4802
|
+
if (isFullscreen) {
|
|
4803
|
+
if ((atTop && deltaY < 0) || (atBottom && deltaY > 0)) {
|
|
4804
|
+
e.preventDefault();
|
|
4805
|
+
}
|
|
4806
|
+
} else {
|
|
4807
|
+
const atTopExact = container.scrollTop <= 0;
|
|
4808
|
+
const atBottomExact = container.scrollTop + container.clientHeight >= container.scrollHeight - 1;
|
|
4809
|
+
if ((atTopExact && deltaY < 0) || (atBottomExact && deltaY > 0)) {
|
|
4810
|
+
e.preventDefault();
|
|
4811
|
+
}
|
|
4813
4812
|
}
|
|
4814
4813
|
touchStartY = touchY;
|
|
4815
4814
|
}, { passive: false });
|
|
@@ -4831,11 +4830,15 @@
|
|
|
4831
4830
|
|
|
4832
4831
|
const touchY = e.touches[0].clientY;
|
|
4833
4832
|
const deltaY = docTouchStartY - touchY;
|
|
4834
|
-
const atTop = container.scrollTop <= 0;
|
|
4835
|
-
const atBottom = container.scrollTop + container.clientHeight >= container.scrollHeight - 1;
|
|
4836
4833
|
|
|
4837
|
-
|
|
4838
|
-
|
|
4834
|
+
const BUFFER = 5; // px buffer to catch near-boundary gestures
|
|
4835
|
+
const atTop = container.scrollTop <= BUFFER;
|
|
4836
|
+
const atBottom = container.scrollTop + container.clientHeight >= container.scrollHeight - 1 - BUFFER;
|
|
4837
|
+
|
|
4838
|
+
// If content fits entirely (no scroll needed), block all overscroll
|
|
4839
|
+
const contentFits = container.scrollHeight <= container.clientHeight + 1;
|
|
4840
|
+
|
|
4841
|
+
if (contentFits || (atTop && deltaY < 0) || (atBottom && deltaY > 0)) {
|
|
4839
4842
|
e.preventDefault();
|
|
4840
4843
|
}
|
|
4841
4844
|
docTouchStartY = touchY;
|