nodebb-plugin-pdf-secure 1.2.13 → 1.2.14
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/viewer.html +191 -17
package/package.json
CHANGED
package/static/viewer.html
CHANGED
|
@@ -52,8 +52,8 @@
|
|
|
52
52
|
-moz-user-select: none;
|
|
53
53
|
-ms-user-select: none;
|
|
54
54
|
user-select: none;
|
|
55
|
-
/* Prevent scroll chaining
|
|
56
|
-
overscroll-behavior:
|
|
55
|
+
/* Prevent scroll chaining and browser overscroll gestures (e.g. pull-to-exit-fullscreen) */
|
|
56
|
+
overscroll-behavior: none;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
/* Print Protection - hide everything when printing */
|
|
@@ -626,8 +626,8 @@
|
|
|
626
626
|
overflow: auto;
|
|
627
627
|
background: #525659;
|
|
628
628
|
z-index: 1;
|
|
629
|
-
/* Prevent scroll chaining
|
|
630
|
-
overscroll-behavior:
|
|
629
|
+
/* Prevent scroll chaining and overscroll gestures on touch devices */
|
|
630
|
+
overscroll-behavior: none;
|
|
631
631
|
-webkit-overflow-scrolling: touch;
|
|
632
632
|
}
|
|
633
633
|
|
|
@@ -1532,7 +1532,7 @@
|
|
|
1532
1532
|
|
|
1533
1533
|
/* Let our JS handle pinch-to-zoom, but allow browser pan-y for scroll */
|
|
1534
1534
|
#viewerContainer {
|
|
1535
|
-
touch-action:
|
|
1535
|
+
touch-action: manipulation;
|
|
1536
1536
|
}
|
|
1537
1537
|
}
|
|
1538
1538
|
</style>
|
|
@@ -2253,8 +2253,10 @@
|
|
|
2253
2253
|
}
|
|
2254
2254
|
|
|
2255
2255
|
// Events
|
|
2256
|
+
let initialScale = 1;
|
|
2256
2257
|
eventBus.on('pagesinit', () => {
|
|
2257
2258
|
pdfViewer.currentScaleValue = 'page-width';
|
|
2259
|
+
initialScale = pdfViewer.currentScale;
|
|
2258
2260
|
document.getElementById('pageCount').textContent = `/ ${pdfViewer.pagesCount}`;
|
|
2259
2261
|
});
|
|
2260
2262
|
|
|
@@ -2698,17 +2700,89 @@
|
|
|
2698
2700
|
svg.addEventListener('mouseleave', () => stopDraw(pageNum), { signal });
|
|
2699
2701
|
|
|
2700
2702
|
// Touch support for tablets
|
|
2703
|
+
// Uses direction detection: predominantly vertical = scroll, otherwise = draw
|
|
2704
|
+
let touchDrawDecided = false;
|
|
2705
|
+
let touchDrawing = false;
|
|
2706
|
+
let touchStartX = 0;
|
|
2707
|
+
let touchStartYDraw = 0;
|
|
2708
|
+
|
|
2701
2709
|
svg.addEventListener('touchstart', (e) => {
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2710
|
+
if (!currentTool || e.touches.length !== 1) return;
|
|
2711
|
+
|
|
2712
|
+
// Eraser and select need immediate response
|
|
2713
|
+
if (currentTool === 'eraser' || currentTool === 'select') {
|
|
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;
|
|
2724
|
+
touchStartX = e.touches[0].clientX;
|
|
2725
|
+
touchStartYDraw = e.touches[0].clientY;
|
|
2705
2726
|
}, { passive: false, signal });
|
|
2727
|
+
|
|
2706
2728
|
svg.addEventListener('touchmove', (e) => {
|
|
2707
|
-
if (currentTool
|
|
2708
|
-
|
|
2729
|
+
if (!currentTool || e.touches.length !== 1) return;
|
|
2730
|
+
|
|
2731
|
+
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
|
+
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
|
+
draw(e);
|
|
2761
|
+
touchDrawing = true;
|
|
2762
|
+
}
|
|
2709
2763
|
}, { passive: false, signal });
|
|
2710
|
-
|
|
2711
|
-
svg.addEventListener('
|
|
2764
|
+
|
|
2765
|
+
svg.addEventListener('touchend', (e) => {
|
|
2766
|
+
if (!touchDrawDecided && currentTool && currentTool !== 'eraser' && currentTool !== 'select') {
|
|
2767
|
+
// Tap without moving > 10px → draw a dot at touch position
|
|
2768
|
+
const syntheticStart = {
|
|
2769
|
+
currentTarget: svg,
|
|
2770
|
+
touches: [{ clientX: touchStartX, clientY: touchStartYDraw }],
|
|
2771
|
+
preventDefault: () => {}
|
|
2772
|
+
};
|
|
2773
|
+
startDraw(syntheticStart, pageNum);
|
|
2774
|
+
stopDraw(pageNum);
|
|
2775
|
+
} else if (touchDrawing) {
|
|
2776
|
+
stopDraw(pageNum);
|
|
2777
|
+
}
|
|
2778
|
+
touchDrawDecided = false;
|
|
2779
|
+
touchDrawing = false;
|
|
2780
|
+
}, { signal });
|
|
2781
|
+
svg.addEventListener('touchcancel', () => {
|
|
2782
|
+
if (touchDrawing) stopDraw(pageNum);
|
|
2783
|
+
touchDrawDecided = false;
|
|
2784
|
+
touchDrawing = false;
|
|
2785
|
+
}, { signal });
|
|
2712
2786
|
|
|
2713
2787
|
svg.classList.toggle('active', annotationMode);
|
|
2714
2788
|
}
|
|
@@ -2904,7 +2978,7 @@
|
|
|
2904
2978
|
// Save current state to undo stack with rotation
|
|
2905
2979
|
if (!undoStacks.has(pageNum)) undoStacks.set(pageNum, []);
|
|
2906
2980
|
const stack = undoStacks.get(pageNum);
|
|
2907
|
-
stack.push({ html: svg
|
|
2981
|
+
stack.push({ html: getCleanSvgInnerHTML(svg), rotation: pdfViewer.pagesRotation || 0 });
|
|
2908
2982
|
if (stack.length > MAX_HISTORY) stack.shift();
|
|
2909
2983
|
|
|
2910
2984
|
// Clear redo stack
|
|
@@ -4395,7 +4469,7 @@
|
|
|
4395
4469
|
else if (document.webkitExitFullscreen) document.webkitExitFullscreen();
|
|
4396
4470
|
} else {
|
|
4397
4471
|
const el = document.documentElement;
|
|
4398
|
-
if (el.requestFullscreen) el.requestFullscreen().catch(() => {});
|
|
4472
|
+
if (el.requestFullscreen) el.requestFullscreen().catch(() => { });
|
|
4399
4473
|
else if (el.webkitRequestFullscreen) el.webkitRequestFullscreen();
|
|
4400
4474
|
}
|
|
4401
4475
|
}
|
|
@@ -4541,6 +4615,7 @@
|
|
|
4541
4615
|
if (e.touches.length === 2) {
|
|
4542
4616
|
// Cancel any active drawing and clean up
|
|
4543
4617
|
if (isDrawing && currentDrawingPage) {
|
|
4618
|
+
const savePage = currentDrawingPage;
|
|
4544
4619
|
if (currentPath && currentPath.parentNode) currentPath.remove();
|
|
4545
4620
|
currentPath = null;
|
|
4546
4621
|
pathSegments = [];
|
|
@@ -4548,6 +4623,7 @@
|
|
|
4548
4623
|
isDrawing = false;
|
|
4549
4624
|
currentSvg = null;
|
|
4550
4625
|
currentDrawingPage = null;
|
|
4626
|
+
saveAnnotations(savePage);
|
|
4551
4627
|
}
|
|
4552
4628
|
isPinching = true;
|
|
4553
4629
|
pinchStartDistance = getTouchDistance(e.touches);
|
|
@@ -4584,19 +4660,38 @@
|
|
|
4584
4660
|
}
|
|
4585
4661
|
}, { passive: false });
|
|
4586
4662
|
|
|
4663
|
+
let pinchJustEnded = false;
|
|
4664
|
+
|
|
4587
4665
|
container.addEventListener('touchend', (e) => {
|
|
4588
4666
|
if (isPinching && e.touches.length < 2) {
|
|
4589
4667
|
isPinching = false;
|
|
4590
4668
|
|
|
4669
|
+
// Prevent double-tap false positive right after pinch ends
|
|
4670
|
+
pinchJustEnded = true;
|
|
4671
|
+
setTimeout(() => { pinchJustEnded = false; }, 400);
|
|
4672
|
+
|
|
4591
4673
|
// Remove CSS transform
|
|
4592
4674
|
viewerDiv.style.transform = '';
|
|
4593
4675
|
viewerDiv.style.willChange = '';
|
|
4594
4676
|
viewerDiv.style.transformOrigin = '';
|
|
4595
4677
|
|
|
4596
|
-
// Apply actual scale
|
|
4678
|
+
// Apply actual scale with scroll position preservation
|
|
4597
4679
|
const finalScale = Math.min(Math.max(pinchStartScale * pinchVisualRatio, 0.5), 5.0);
|
|
4598
4680
|
if (Math.abs(finalScale - pdfViewer.currentScale) > 0.01) {
|
|
4681
|
+
const scrollRatio = finalScale / pdfViewer.currentScale;
|
|
4682
|
+
const midRect = container.getBoundingClientRect();
|
|
4683
|
+
const viewCenterX = midRect.width / 2;
|
|
4684
|
+
const viewCenterY = midRect.height / 2;
|
|
4685
|
+
const oldScrollLeft = container.scrollLeft;
|
|
4686
|
+
const oldScrollTop = container.scrollTop;
|
|
4687
|
+
|
|
4599
4688
|
pdfViewer.currentScale = finalScale;
|
|
4689
|
+
|
|
4690
|
+
// Adjust scroll after PDF.js re-render to keep view centered
|
|
4691
|
+
requestAnimationFrame(() => {
|
|
4692
|
+
container.scrollLeft = (oldScrollLeft + viewCenterX) * scrollRatio - viewCenterX;
|
|
4693
|
+
container.scrollTop = (oldScrollTop + viewCenterY) * scrollRatio - viewCenterY;
|
|
4694
|
+
});
|
|
4600
4695
|
}
|
|
4601
4696
|
pinchVisualRatio = 1;
|
|
4602
4697
|
}
|
|
@@ -4612,6 +4707,54 @@
|
|
|
4612
4707
|
}
|
|
4613
4708
|
});
|
|
4614
4709
|
|
|
4710
|
+
// ==========================================
|
|
4711
|
+
// DOUBLE-TAP TO ZOOM (Tablet)
|
|
4712
|
+
// ==========================================
|
|
4713
|
+
let lastTapTime = 0;
|
|
4714
|
+
let lastTapX = 0;
|
|
4715
|
+
let lastTapY = 0;
|
|
4716
|
+
|
|
4717
|
+
container.addEventListener('touchend', (e) => {
|
|
4718
|
+
// Skip during pinch, multi-touch, annotation mode, or right after pinch ends
|
|
4719
|
+
if (isPinching || e.touches.length !== 0 || pinchJustEnded) return;
|
|
4720
|
+
if (annotationMode && currentTool) return;
|
|
4721
|
+
|
|
4722
|
+
const now = Date.now();
|
|
4723
|
+
const touch = e.changedTouches[0];
|
|
4724
|
+
const dx = touch.clientX - lastTapX;
|
|
4725
|
+
const dy = touch.clientY - lastTapY;
|
|
4726
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
4727
|
+
|
|
4728
|
+
if (now - lastTapTime < 300 && dist < 30) {
|
|
4729
|
+
// Double-tap detected
|
|
4730
|
+
e.preventDefault();
|
|
4731
|
+
|
|
4732
|
+
if (pdfViewer.currentScaleValue === 'page-width' ||
|
|
4733
|
+
Math.abs(pdfViewer.currentScale - initialScale) < 0.01) {
|
|
4734
|
+
// Zoom in 2x, centered on tap point
|
|
4735
|
+
const rect = container.getBoundingClientRect();
|
|
4736
|
+
const tapX = touch.clientX - rect.left;
|
|
4737
|
+
const tapY = touch.clientY - rect.top;
|
|
4738
|
+
|
|
4739
|
+
pdfViewer.currentScale = initialScale * 2;
|
|
4740
|
+
|
|
4741
|
+
// Scroll to keep tap point centered
|
|
4742
|
+
container.scrollLeft = (tapX + container.scrollLeft) * 2 - tapX;
|
|
4743
|
+
container.scrollTop = (tapY + container.scrollTop) * 2 - tapY;
|
|
4744
|
+
} else {
|
|
4745
|
+
// Reset to page-width
|
|
4746
|
+
pdfViewer.currentScaleValue = 'page-width';
|
|
4747
|
+
}
|
|
4748
|
+
|
|
4749
|
+
lastTapTime = 0;
|
|
4750
|
+
return;
|
|
4751
|
+
}
|
|
4752
|
+
|
|
4753
|
+
lastTapTime = now;
|
|
4754
|
+
lastTapX = touch.clientX;
|
|
4755
|
+
lastTapY = touch.clientY;
|
|
4756
|
+
});
|
|
4757
|
+
|
|
4615
4758
|
// ==========================================
|
|
4616
4759
|
// TOUCH SCROLL BOUNDARY (Prevent exit on tablet)
|
|
4617
4760
|
// ==========================================
|
|
@@ -4620,6 +4763,9 @@
|
|
|
4620
4763
|
(function initTouchScrollBoundary() {
|
|
4621
4764
|
if (!isTouch()) return;
|
|
4622
4765
|
|
|
4766
|
+
// Ensure html element also blocks overscroll
|
|
4767
|
+
document.documentElement.style.overscrollBehavior = 'none';
|
|
4768
|
+
|
|
4623
4769
|
let touchStartY = 0;
|
|
4624
4770
|
|
|
4625
4771
|
container.addEventListener('touchstart', (e) => {
|
|
@@ -4628,8 +4774,8 @@
|
|
|
4628
4774
|
}
|
|
4629
4775
|
}, { passive: true });
|
|
4630
4776
|
|
|
4777
|
+
// Container-level boundary prevention
|
|
4631
4778
|
container.addEventListener('touchmove', (e) => {
|
|
4632
|
-
// Skip if pinching
|
|
4633
4779
|
if (isPinching || e.touches.length !== 1) return;
|
|
4634
4780
|
|
|
4635
4781
|
const touchY = e.touches[0].clientY;
|
|
@@ -4638,11 +4784,39 @@
|
|
|
4638
4784
|
const atTop = container.scrollTop <= 0;
|
|
4639
4785
|
const atBottom = container.scrollTop + container.clientHeight >= container.scrollHeight - 1;
|
|
4640
4786
|
|
|
4641
|
-
// Prevent overscroll at boundaries
|
|
4642
4787
|
if ((atTop && deltaY < 0) || (atBottom && deltaY > 0)) {
|
|
4643
4788
|
e.preventDefault();
|
|
4644
4789
|
}
|
|
4790
|
+
touchStartY = touchY;
|
|
4645
4791
|
}, { passive: false });
|
|
4792
|
+
|
|
4793
|
+
// Document-level capture handler: catch overscroll gestures
|
|
4794
|
+
// that escape the container (e.g. browser pull-to-exit-fullscreen)
|
|
4795
|
+
let docTouchStartY = 0;
|
|
4796
|
+
document.addEventListener('touchstart', (e) => {
|
|
4797
|
+
if (e.touches.length === 1) {
|
|
4798
|
+
docTouchStartY = e.touches[0].clientY;
|
|
4799
|
+
}
|
|
4800
|
+
}, { passive: true, capture: true });
|
|
4801
|
+
|
|
4802
|
+
document.addEventListener('touchmove', (e) => {
|
|
4803
|
+
if (e.touches.length !== 1) return;
|
|
4804
|
+
|
|
4805
|
+
// Only prevent when in fullscreen
|
|
4806
|
+
const fsEl = document.fullscreenElement || document.webkitFullscreenElement;
|
|
4807
|
+
if (!fsEl) return;
|
|
4808
|
+
|
|
4809
|
+
const touchY = e.touches[0].clientY;
|
|
4810
|
+
const deltaY = docTouchStartY - touchY;
|
|
4811
|
+
const atTop = container.scrollTop <= 0;
|
|
4812
|
+
const atBottom = container.scrollTop + container.clientHeight >= container.scrollHeight - 1;
|
|
4813
|
+
|
|
4814
|
+
// Block pull-down at top and pull-up at bottom in fullscreen
|
|
4815
|
+
if ((atTop && deltaY < 0) || (atBottom && deltaY > 0)) {
|
|
4816
|
+
e.preventDefault();
|
|
4817
|
+
}
|
|
4818
|
+
docTouchStartY = touchY;
|
|
4819
|
+
}, { passive: false, capture: true });
|
|
4646
4820
|
})();
|
|
4647
4821
|
|
|
4648
4822
|
// ==========================================
|