nodebb-plugin-pdf-secure 1.2.17 → 1.2.19
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 +2 -0
- package/static/viewer-app.js +75 -51
- package/static/viewer.css +12 -0
- package/static/viewer.html +109 -26
package/package.json
CHANGED
package/static/lib/main.js
CHANGED
|
@@ -194,7 +194,9 @@
|
|
|
194
194
|
iframe.style.top = '0';
|
|
195
195
|
iframe.style.left = '0';
|
|
196
196
|
iframe.style.width = '100vw';
|
|
197
|
+
iframe.style.width = '100dvw'; // Override: dynamic viewport (excludes browser chrome)
|
|
197
198
|
iframe.style.height = '100vh';
|
|
199
|
+
iframe.style.height = '100dvh'; // Override: dynamic viewport (excludes address bar on mobile)
|
|
198
200
|
iframe.style.zIndex = '2147483647';
|
|
199
201
|
|
|
200
202
|
// Lock body scroll
|
package/static/viewer-app.js
CHANGED
|
@@ -525,6 +525,10 @@
|
|
|
525
525
|
document.getElementById('sepiaBtn').classList.contains('active'));
|
|
526
526
|
closeAllDropdowns();
|
|
527
527
|
};
|
|
528
|
+
document.getElementById('overflowFullscreen').onclick = () => {
|
|
529
|
+
toggleFullscreen();
|
|
530
|
+
closeAllDropdowns();
|
|
531
|
+
};
|
|
528
532
|
|
|
529
533
|
// Close dropdowns when clicking outside
|
|
530
534
|
document.addEventListener('click', (e) => {
|
|
@@ -613,15 +617,18 @@
|
|
|
613
617
|
highlightColor = c;
|
|
614
618
|
if (currentTool === 'highlight') currentColor = c;
|
|
615
619
|
document.getElementById('highlightWave').setAttribute('stroke', c);
|
|
620
|
+
document.getElementById('highlightColorIndicator').style.background = c;
|
|
616
621
|
});
|
|
617
622
|
setupColorPicker('drawColors', c => {
|
|
618
623
|
drawColor = c;
|
|
619
624
|
if (currentTool === 'pen') currentColor = c;
|
|
620
625
|
document.getElementById('drawWave').setAttribute('stroke', c);
|
|
626
|
+
document.getElementById('drawColorIndicator').style.background = c;
|
|
621
627
|
});
|
|
622
628
|
setupColorPicker('shapeColors', c => {
|
|
623
629
|
shapeColor = c;
|
|
624
630
|
if (currentTool === 'shape') currentColor = c;
|
|
631
|
+
document.getElementById('shapeColorIndicator').style.background = c;
|
|
625
632
|
});
|
|
626
633
|
|
|
627
634
|
// Highlighter Thickness Slider
|
|
@@ -745,13 +752,9 @@
|
|
|
745
752
|
e.preventDefault();
|
|
746
753
|
draw(e);
|
|
747
754
|
};
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
} else {
|
|
752
|
-
svg.addEventListener('touchstart', touchStartHandler, { signal });
|
|
753
|
-
svg.addEventListener('touchmove', touchMoveHandler, { signal });
|
|
754
|
-
}
|
|
755
|
+
// Always use passive:false so preventDefault() works when a tool is later activated
|
|
756
|
+
svg.addEventListener('touchstart', touchStartHandler, { passive: false, signal });
|
|
757
|
+
svg.addEventListener('touchmove', touchMoveHandler, { passive: false, signal });
|
|
755
758
|
svg.addEventListener('touchend', () => stopDraw(pageNum), { signal });
|
|
756
759
|
svg.addEventListener('touchcancel', () => stopDraw(pageNum), { signal });
|
|
757
760
|
|
|
@@ -965,7 +968,8 @@
|
|
|
965
968
|
// Text tool - create/edit/drag text
|
|
966
969
|
if (currentTool === 'text') {
|
|
967
970
|
// Check if clicked on existing text element
|
|
968
|
-
|
|
971
|
+
// Use coords (touch-safe) instead of e.clientX which is undefined on TouchEvent
|
|
972
|
+
const elementsUnderClick = document.elementsFromPoint(coords.clientX, coords.clientY);
|
|
969
973
|
const existingText = elementsUnderClick.find(el => el.tagName === 'text' && el.closest('.annotationLayer'));
|
|
970
974
|
|
|
971
975
|
if (existingText) {
|
|
@@ -973,7 +977,7 @@
|
|
|
973
977
|
startTextDrag(e, existingText, svg, scaleX, pageNum);
|
|
974
978
|
} else {
|
|
975
979
|
// Create new text
|
|
976
|
-
showTextEditor(
|
|
980
|
+
showTextEditor(coords.clientX, coords.clientY, svg, x, y, scaleX, pageNum);
|
|
977
981
|
}
|
|
978
982
|
return;
|
|
979
983
|
}
|
|
@@ -1235,14 +1239,18 @@
|
|
|
1235
1239
|
textEl.classList.add('dragging');
|
|
1236
1240
|
hasDragged = false;
|
|
1237
1241
|
|
|
1238
|
-
|
|
1239
|
-
|
|
1242
|
+
// Touch-safe coordinate extraction
|
|
1243
|
+
const startCoords = getEventCoords(e);
|
|
1244
|
+
dragStartX = startCoords.clientX;
|
|
1245
|
+
dragStartY = startCoords.clientY;
|
|
1240
1246
|
textOriginalX = parseFloat(textEl.getAttribute('x'));
|
|
1241
1247
|
textOriginalY = parseFloat(textEl.getAttribute('y'));
|
|
1242
1248
|
|
|
1243
|
-
function
|
|
1244
|
-
|
|
1245
|
-
const
|
|
1249
|
+
function onMove(ev) {
|
|
1250
|
+
ev.preventDefault();
|
|
1251
|
+
const moveCoords = getEventCoords(ev);
|
|
1252
|
+
const dxScreen = moveCoords.clientX - dragStartX;
|
|
1253
|
+
const dyScreen = moveCoords.clientY - dragStartY;
|
|
1246
1254
|
// Convert screen delta to viewBox delta (rotation-aware)
|
|
1247
1255
|
const vbDelta = screenDeltaToViewBox(svg, dxScreen, dyScreen, textEl);
|
|
1248
1256
|
|
|
@@ -1254,29 +1262,31 @@
|
|
|
1254
1262
|
textEl.setAttribute('y', (textOriginalY + vbDelta.dy).toFixed(2));
|
|
1255
1263
|
}
|
|
1256
1264
|
|
|
1257
|
-
function
|
|
1258
|
-
document.removeEventListener('mousemove',
|
|
1259
|
-
document.removeEventListener('mouseup',
|
|
1265
|
+
function onEnd(ev) {
|
|
1266
|
+
document.removeEventListener('mousemove', onMove);
|
|
1267
|
+
document.removeEventListener('mouseup', onEnd);
|
|
1268
|
+
document.removeEventListener('touchmove', onMove);
|
|
1269
|
+
document.removeEventListener('touchend', onEnd);
|
|
1260
1270
|
textEl.classList.remove('dragging');
|
|
1261
1271
|
|
|
1262
1272
|
if (hasDragged) {
|
|
1263
1273
|
// Moved - save position
|
|
1264
1274
|
saveAnnotations(pageNum);
|
|
1265
1275
|
} else {
|
|
1266
|
-
// Not moved - short click = edit
|
|
1267
|
-
const viewBoxWidth = parseFloat(svg.dataset.viewboxWidth);
|
|
1268
|
-
const viewBoxHeight = parseFloat(svg.dataset.viewboxHeight);
|
|
1276
|
+
// Not moved - short click/tap = edit
|
|
1269
1277
|
const svgX = parseFloat(textEl.getAttribute('x'));
|
|
1270
1278
|
const svgY = parseFloat(textEl.getAttribute('y'));
|
|
1271
|
-
|
|
1272
|
-
showTextEditor(
|
|
1279
|
+
const endCoords = getEventCoords(ev);
|
|
1280
|
+
showTextEditor(endCoords.clientX, endCoords.clientY, svg, svgX, svgY, scaleX, pageNum, textEl);
|
|
1273
1281
|
}
|
|
1274
1282
|
|
|
1275
1283
|
draggedText = null;
|
|
1276
1284
|
}
|
|
1277
1285
|
|
|
1278
|
-
document.addEventListener('mousemove',
|
|
1279
|
-
document.addEventListener('mouseup',
|
|
1286
|
+
document.addEventListener('mousemove', onMove);
|
|
1287
|
+
document.addEventListener('mouseup', onEnd);
|
|
1288
|
+
document.addEventListener('touchmove', onMove, { passive: false });
|
|
1289
|
+
document.addEventListener('touchend', onEnd);
|
|
1280
1290
|
}
|
|
1281
1291
|
|
|
1282
1292
|
// Inline Text Editor
|
|
@@ -2417,12 +2427,22 @@
|
|
|
2417
2427
|
// ERGONOMIC FEATURES
|
|
2418
2428
|
// ==========================================
|
|
2419
2429
|
|
|
2420
|
-
|
|
2430
|
+
|
|
2431
|
+
// Fullscreen state (tracks both native and simulated fullscreen)
|
|
2432
|
+
let isFullscreen = false;
|
|
2433
|
+
|
|
2434
|
+
// Fullscreen toggle function — delegates to parent iframe handler via postMessage
|
|
2421
2435
|
function toggleFullscreen() {
|
|
2422
|
-
if (
|
|
2423
|
-
|
|
2436
|
+
if (window.self !== window.top) {
|
|
2437
|
+
// Inside iframe: ask parent to handle fullscreen
|
|
2438
|
+
window.parent.postMessage({ type: 'pdf-secure-fullscreen-toggle' }, '*');
|
|
2424
2439
|
} else {
|
|
2425
|
-
|
|
2440
|
+
// Standalone mode: use native fullscreen
|
|
2441
|
+
if (document.fullscreenElement) {
|
|
2442
|
+
document.exitFullscreen();
|
|
2443
|
+
} else {
|
|
2444
|
+
document.documentElement.requestFullscreen().catch(() => { });
|
|
2445
|
+
}
|
|
2426
2446
|
}
|
|
2427
2447
|
}
|
|
2428
2448
|
|
|
@@ -2430,7 +2450,7 @@
|
|
|
2430
2450
|
function updateFullscreenIcon() {
|
|
2431
2451
|
const icon = document.getElementById('fullscreenIcon');
|
|
2432
2452
|
const btn = document.getElementById('fullscreenBtn');
|
|
2433
|
-
if (
|
|
2453
|
+
if (isFullscreen) {
|
|
2434
2454
|
icon.innerHTML = '<path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"/>';
|
|
2435
2455
|
btn.classList.add('active');
|
|
2436
2456
|
} else {
|
|
@@ -2439,32 +2459,36 @@
|
|
|
2439
2459
|
}
|
|
2440
2460
|
}
|
|
2441
2461
|
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2462
|
+
// Apply fullscreen CSS class and touch restrictions
|
|
2463
|
+
function applyFullscreenTouchRestrictions() {
|
|
2464
|
+
document.body.classList.toggle('viewer-fullscreen', isFullscreen);
|
|
2465
|
+
if (isFullscreen) {
|
|
2466
|
+
document.documentElement.style.touchAction = 'pan-y pinch-zoom';
|
|
2467
|
+
document.documentElement.style.overscrollBehavior = 'none';
|
|
2468
|
+
} else {
|
|
2469
|
+
document.documentElement.style.touchAction = '';
|
|
2470
|
+
document.documentElement.style.overscrollBehavior = '';
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2446
2473
|
|
|
2447
|
-
//
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2474
|
+
// Listen for fullscreen state from parent (simulated fullscreen)
|
|
2475
|
+
window.addEventListener('message', (event) => {
|
|
2476
|
+
if (event.data && event.data.type === 'pdf-secure-fullscreen-state') {
|
|
2477
|
+
isFullscreen = event.data.isFullscreen;
|
|
2478
|
+
updateFullscreenIcon();
|
|
2479
|
+
applyFullscreenTouchRestrictions();
|
|
2453
2480
|
}
|
|
2454
|
-
lastClickTime = now;
|
|
2455
2481
|
});
|
|
2456
2482
|
|
|
2457
|
-
//
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
container.addEventListener('touchstart', autoFullscreen, { once: true });
|
|
2467
|
-
}
|
|
2483
|
+
// Local fullscreen events (standalone mode)
|
|
2484
|
+
document.addEventListener('fullscreenchange', () => {
|
|
2485
|
+
isFullscreen = !!(document.fullscreenElement || document.webkitFullscreenElement);
|
|
2486
|
+
updateFullscreenIcon();
|
|
2487
|
+
applyFullscreenTouchRestrictions();
|
|
2488
|
+
});
|
|
2489
|
+
|
|
2490
|
+
document.getElementById('fullscreenBtn').onclick = () => toggleFullscreen();
|
|
2491
|
+
|
|
2468
2492
|
|
|
2469
2493
|
// Mouse wheel zoom with Ctrl (debounced, clamped 0.5x-5x)
|
|
2470
2494
|
let zoomTimeout;
|
package/static/viewer.css
CHANGED
|
@@ -1482,3 +1482,15 @@ body {
|
|
|
1482
1482
|
height: 56px;
|
|
1483
1483
|
}
|
|
1484
1484
|
}
|
|
1485
|
+
|
|
1486
|
+
/* ==========================================
|
|
1487
|
+
FULLSCREEN STATE (simulated fullscreen on mobile)
|
|
1488
|
+
========================================== */
|
|
1489
|
+
/* Ensure bottom toolbar is visible and viewer container makes room for it */
|
|
1490
|
+
body.viewer-fullscreen #bottomToolbar {
|
|
1491
|
+
display: block;
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
body.viewer-fullscreen #viewerContainer {
|
|
1495
|
+
bottom: calc(var(--bottom-bar-height) + var(--safe-area-bottom));
|
|
1496
|
+
}
|
package/static/viewer.html
CHANGED
|
@@ -401,6 +401,18 @@
|
|
|
401
401
|
border-radius: 4px 0 0 4px;
|
|
402
402
|
}
|
|
403
403
|
|
|
404
|
+
/* Color indicator bar under tool button */
|
|
405
|
+
.toolColorIndicator {
|
|
406
|
+
position: absolute;
|
|
407
|
+
bottom: 2px;
|
|
408
|
+
left: 6px;
|
|
409
|
+
right: calc(20px + 6px);
|
|
410
|
+
height: 3px;
|
|
411
|
+
border-radius: 2px;
|
|
412
|
+
pointer-events: none;
|
|
413
|
+
transition: background 0.15s;
|
|
414
|
+
}
|
|
415
|
+
|
|
404
416
|
.dropdownArrow {
|
|
405
417
|
width: 20px;
|
|
406
418
|
height: 36px;
|
|
@@ -1178,12 +1190,17 @@
|
|
|
1178
1190
|
z-index: 100;
|
|
1179
1191
|
padding: 0 8px;
|
|
1180
1192
|
padding-bottom: var(--safe-area-bottom);
|
|
1193
|
+
/* Flex layout: scrollable tools + fixed fullscreen button */
|
|
1194
|
+
align-items: center;
|
|
1195
|
+
gap: 0;
|
|
1181
1196
|
}
|
|
1182
1197
|
|
|
1183
1198
|
.bottomToolbarInner {
|
|
1184
1199
|
display: flex;
|
|
1185
1200
|
align-items: center;
|
|
1186
1201
|
gap: 2px;
|
|
1202
|
+
flex: 1;
|
|
1203
|
+
min-width: 0;
|
|
1187
1204
|
height: var(--bottom-bar-height);
|
|
1188
1205
|
overflow-x: auto;
|
|
1189
1206
|
overflow-y: hidden;
|
|
@@ -1196,6 +1213,14 @@
|
|
|
1196
1213
|
display: none;
|
|
1197
1214
|
}
|
|
1198
1215
|
|
|
1216
|
+
/* Fullscreen button pinned to right side of bottom toolbar */
|
|
1217
|
+
.bottomFullscreenBtn {
|
|
1218
|
+
flex-shrink: 0;
|
|
1219
|
+
margin-left: 4px;
|
|
1220
|
+
border-left: 1px solid var(--border-color);
|
|
1221
|
+
padding-left: 4px;
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1199
1224
|
/* Dropdown backdrop overlay */
|
|
1200
1225
|
#dropdownBackdrop {
|
|
1201
1226
|
display: none;
|
|
@@ -1248,7 +1273,7 @@
|
|
|
1248
1273
|
|
|
1249
1274
|
/* Show bottom toolbar */
|
|
1250
1275
|
#bottomToolbar {
|
|
1251
|
-
display:
|
|
1276
|
+
display: flex;
|
|
1252
1277
|
}
|
|
1253
1278
|
|
|
1254
1279
|
/* Viewer container adjusted for mobile toolbars */
|
|
@@ -1353,6 +1378,18 @@
|
|
|
1353
1378
|
}
|
|
1354
1379
|
}
|
|
1355
1380
|
|
|
1381
|
+
/* ==========================================
|
|
1382
|
+
FULLSCREEN STATE (simulated fullscreen on mobile)
|
|
1383
|
+
========================================== */
|
|
1384
|
+
/* Ensure bottom toolbar is visible and viewer container makes room for it */
|
|
1385
|
+
body.viewer-fullscreen #bottomToolbar {
|
|
1386
|
+
display: flex;
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
body.viewer-fullscreen #viewerContainer {
|
|
1390
|
+
bottom: calc(var(--bottom-bar-height) + var(--safe-area-bottom));
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1356
1393
|
/* ==========================================
|
|
1357
1394
|
TABLET BREAKPOINT (600px - 1024px)
|
|
1358
1395
|
========================================== */
|
|
@@ -1418,7 +1455,7 @@
|
|
|
1418
1455
|
|
|
1419
1456
|
/* Show bottom toolbar */
|
|
1420
1457
|
#bottomToolbar {
|
|
1421
|
-
display:
|
|
1458
|
+
display: flex;
|
|
1422
1459
|
}
|
|
1423
1460
|
|
|
1424
1461
|
/* Viewer container adjusted for both toolbars */
|
|
@@ -1561,6 +1598,7 @@
|
|
|
1561
1598
|
<path d="M3 21h18v-2H3v2zM5 16h14l-3-10H8l-3 10zM9 8h6l1.5 5h-9L9 8z" opacity="0.7" />
|
|
1562
1599
|
</svg>
|
|
1563
1600
|
</button>
|
|
1601
|
+
<div class="toolColorIndicator" id="highlightColorIndicator" style="background:#fff100"></div>
|
|
1564
1602
|
<button class="dropdownArrow" id="highlightArrow">
|
|
1565
1603
|
<svg viewBox="0 0 24 24">
|
|
1566
1604
|
<path d="M7 10l5 5 5-5z" />
|
|
@@ -1606,6 +1644,7 @@
|
|
|
1606
1644
|
d="M20.71 4.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83zM3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25z" />
|
|
1607
1645
|
</svg>
|
|
1608
1646
|
</button>
|
|
1647
|
+
<div class="toolColorIndicator" id="drawColorIndicator" style="background:#e81224"></div>
|
|
1609
1648
|
<button class="dropdownArrow" id="drawArrow">
|
|
1610
1649
|
<svg viewBox="0 0 24 24">
|
|
1611
1650
|
<path d="M7 10l5 5 5-5z" />
|
|
@@ -1709,6 +1748,7 @@
|
|
|
1709
1748
|
<path d="M3 3h8v8H3V3zm10 0h8v8h-8V3zM3 13h8v8H3v-8zm13 0a5 5 0 110 10 5 5 0 010-10z" />
|
|
1710
1749
|
</svg>
|
|
1711
1750
|
</button>
|
|
1751
|
+
<div class="toolColorIndicator" id="shapeColorIndicator" style="background:#e81224"></div>
|
|
1712
1752
|
<button class="dropdownArrow" id="shapesArrow">
|
|
1713
1753
|
<svg viewBox="0 0 24 24">
|
|
1714
1754
|
<path d="M7 10l5 5 5-5z" />
|
|
@@ -1851,6 +1891,13 @@
|
|
|
1851
1891
|
</svg>
|
|
1852
1892
|
<span>Okuma Modu</span>
|
|
1853
1893
|
</button>
|
|
1894
|
+
<div class="overflowDivider"></div>
|
|
1895
|
+
<button class="overflowItem" id="overflowFullscreen">
|
|
1896
|
+
<svg viewBox="0 0 24 24">
|
|
1897
|
+
<path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z" />
|
|
1898
|
+
</svg>
|
|
1899
|
+
<span>Tam Ekran</span>
|
|
1900
|
+
</button>
|
|
1854
1901
|
</div>
|
|
1855
1902
|
</div>
|
|
1856
1903
|
</div>
|
|
@@ -1861,11 +1908,16 @@
|
|
|
1861
1908
|
</div>
|
|
1862
1909
|
</div>
|
|
1863
1910
|
|
|
1864
|
-
<!-- Bottom Toolbar (Mobile
|
|
1911
|
+
<!-- Bottom Toolbar (Mobile/Tablet Portrait) -->
|
|
1865
1912
|
<div id="bottomToolbar">
|
|
1866
1913
|
<div class="bottomToolbarInner" id="bottomToolbarInner">
|
|
1867
1914
|
<!-- Annotation tool buttons will be moved here on mobile via JS -->
|
|
1868
1915
|
</div>
|
|
1916
|
+
<button class="toolbarBtn bottomFullscreenBtn" id="bottomFullscreenBtn" title="Tam Ekran">
|
|
1917
|
+
<svg viewBox="0 0 24 24">
|
|
1918
|
+
<path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z" />
|
|
1919
|
+
</svg>
|
|
1920
|
+
</button>
|
|
1869
1921
|
</div>
|
|
1870
1922
|
|
|
1871
1923
|
<!-- Dropdown Backdrop (Mobile) -->
|
|
@@ -2417,7 +2469,10 @@
|
|
|
2417
2469
|
dropdown.addEventListener('touchstart', (e) => {
|
|
2418
2470
|
const rect = dropdown.getBoundingClientRect();
|
|
2419
2471
|
const touchY = e.touches[0].clientY;
|
|
2420
|
-
|
|
2472
|
+
// Only start swipe-to-dismiss from the handle area (top ~40px)
|
|
2473
|
+
// Don't intercept touches on interactive content (color dots, sliders, shape buttons)
|
|
2474
|
+
const isInteractive = e.target.closest('.colorDot, .colorGrid, .thicknessSlider, .shapeBtn, .shapeGrid, input, .strokePreview, .overflowItem');
|
|
2475
|
+
if (touchY - rect.top > 40 || isInteractive) return;
|
|
2421
2476
|
startY = touchY;
|
|
2422
2477
|
currentY = startY;
|
|
2423
2478
|
isDragging = true;
|
|
@@ -2475,6 +2530,10 @@
|
|
|
2475
2530
|
document.getElementById('sepiaBtn').classList.contains('active'));
|
|
2476
2531
|
closeAllDropdowns();
|
|
2477
2532
|
};
|
|
2533
|
+
document.getElementById('overflowFullscreen').onclick = () => {
|
|
2534
|
+
toggleFullscreen();
|
|
2535
|
+
closeAllDropdowns();
|
|
2536
|
+
};
|
|
2478
2537
|
|
|
2479
2538
|
// Close dropdowns when clicking outside
|
|
2480
2539
|
document.addEventListener('click', (e) => {
|
|
@@ -2557,8 +2616,9 @@
|
|
|
2557
2616
|
dot.classList.add('active');
|
|
2558
2617
|
highlightColor = dot.dataset.color;
|
|
2559
2618
|
if (currentTool === 'highlight') currentColor = highlightColor;
|
|
2560
|
-
// Update preview
|
|
2619
|
+
// Update preview and color indicator
|
|
2561
2620
|
document.getElementById('highlightWave').setAttribute('stroke', highlightColor);
|
|
2621
|
+
document.getElementById('highlightColorIndicator').style.background = highlightColor;
|
|
2562
2622
|
};
|
|
2563
2623
|
});
|
|
2564
2624
|
|
|
@@ -2570,8 +2630,9 @@
|
|
|
2570
2630
|
dot.classList.add('active');
|
|
2571
2631
|
drawColor = dot.dataset.color;
|
|
2572
2632
|
if (currentTool === 'pen') currentColor = drawColor;
|
|
2573
|
-
// Update preview
|
|
2633
|
+
// Update preview and color indicator
|
|
2574
2634
|
document.getElementById('drawWave').setAttribute('stroke', drawColor);
|
|
2635
|
+
document.getElementById('drawColorIndicator').style.background = drawColor;
|
|
2575
2636
|
};
|
|
2576
2637
|
});
|
|
2577
2638
|
|
|
@@ -2609,6 +2670,8 @@
|
|
|
2609
2670
|
dot.classList.add('active');
|
|
2610
2671
|
shapeColor = dot.dataset.color;
|
|
2611
2672
|
if (currentTool === 'shape') currentColor = shapeColor;
|
|
2673
|
+
// Update color indicator
|
|
2674
|
+
document.getElementById('shapeColorIndicator').style.background = shapeColor;
|
|
2612
2675
|
};
|
|
2613
2676
|
});
|
|
2614
2677
|
|
|
@@ -2993,7 +3056,8 @@
|
|
|
2993
3056
|
// Text tool - create/edit/drag text
|
|
2994
3057
|
if (currentTool === 'text') {
|
|
2995
3058
|
// Check if clicked on existing text element
|
|
2996
|
-
|
|
3059
|
+
// Use coords (touch-safe) instead of e.clientX which is undefined on TouchEvent
|
|
3060
|
+
const elementsUnderClick = document.elementsFromPoint(coords.clientX, coords.clientY);
|
|
2997
3061
|
const existingText = elementsUnderClick.find(el => el.tagName === 'text' && el.closest('.annotationLayer'));
|
|
2998
3062
|
|
|
2999
3063
|
if (existingText) {
|
|
@@ -3001,7 +3065,7 @@
|
|
|
3001
3065
|
startTextDrag(e, existingText, svg, scaleX, pageNum);
|
|
3002
3066
|
} else {
|
|
3003
3067
|
// Create new text
|
|
3004
|
-
showTextEditor(
|
|
3068
|
+
showTextEditor(coords.clientX, coords.clientY, svg, x, y, scaleX, pageNum);
|
|
3005
3069
|
}
|
|
3006
3070
|
return;
|
|
3007
3071
|
}
|
|
@@ -3271,14 +3335,18 @@
|
|
|
3271
3335
|
textEl.classList.add('dragging');
|
|
3272
3336
|
hasDragged = false;
|
|
3273
3337
|
|
|
3274
|
-
|
|
3275
|
-
|
|
3338
|
+
// Touch-safe coordinate extraction
|
|
3339
|
+
const startCoords = getEventCoords(e);
|
|
3340
|
+
dragStartX = startCoords.clientX;
|
|
3341
|
+
dragStartY = startCoords.clientY;
|
|
3276
3342
|
textOriginalX = parseFloat(textEl.getAttribute('x'));
|
|
3277
3343
|
textOriginalY = parseFloat(textEl.getAttribute('y'));
|
|
3278
3344
|
|
|
3279
|
-
function
|
|
3280
|
-
|
|
3281
|
-
const
|
|
3345
|
+
function onMove(ev) {
|
|
3346
|
+
ev.preventDefault();
|
|
3347
|
+
const moveCoords = getEventCoords(ev);
|
|
3348
|
+
const dxScreen = moveCoords.clientX - dragStartX;
|
|
3349
|
+
const dyScreen = moveCoords.clientY - dragStartY;
|
|
3282
3350
|
// Convert screen delta to viewBox delta (rotation-aware)
|
|
3283
3351
|
const vbDelta = screenDeltaToViewBox(svg, dxScreen, dyScreen);
|
|
3284
3352
|
|
|
@@ -3290,29 +3358,31 @@
|
|
|
3290
3358
|
textEl.setAttribute('y', (textOriginalY + vbDelta.dy).toFixed(2));
|
|
3291
3359
|
}
|
|
3292
3360
|
|
|
3293
|
-
function
|
|
3294
|
-
document.removeEventListener('mousemove',
|
|
3295
|
-
document.removeEventListener('mouseup',
|
|
3361
|
+
function onEnd(ev) {
|
|
3362
|
+
document.removeEventListener('mousemove', onMove);
|
|
3363
|
+
document.removeEventListener('mouseup', onEnd);
|
|
3364
|
+
document.removeEventListener('touchmove', onMove);
|
|
3365
|
+
document.removeEventListener('touchend', onEnd);
|
|
3296
3366
|
textEl.classList.remove('dragging');
|
|
3297
3367
|
|
|
3298
3368
|
if (hasDragged) {
|
|
3299
3369
|
// Moved - save position
|
|
3300
3370
|
saveAnnotations(pageNum);
|
|
3301
3371
|
} else {
|
|
3302
|
-
// Not moved - short click = edit
|
|
3303
|
-
const viewBoxWidth = parseFloat(svg.dataset.viewboxWidth);
|
|
3304
|
-
const viewBoxHeight = parseFloat(svg.dataset.viewboxHeight);
|
|
3372
|
+
// Not moved - short click/tap = edit
|
|
3305
3373
|
const svgX = parseFloat(textEl.getAttribute('x'));
|
|
3306
3374
|
const svgY = parseFloat(textEl.getAttribute('y'));
|
|
3307
|
-
|
|
3308
|
-
showTextEditor(
|
|
3375
|
+
const endCoords = getEventCoords(ev);
|
|
3376
|
+
showTextEditor(endCoords.clientX, endCoords.clientY, svg, svgX, svgY, scaleX, pageNum, textEl);
|
|
3309
3377
|
}
|
|
3310
3378
|
|
|
3311
3379
|
draggedText = null;
|
|
3312
3380
|
}
|
|
3313
3381
|
|
|
3314
|
-
document.addEventListener('mousemove',
|
|
3315
|
-
document.addEventListener('mouseup',
|
|
3382
|
+
document.addEventListener('mousemove', onMove);
|
|
3383
|
+
document.addEventListener('mouseup', onEnd);
|
|
3384
|
+
document.addEventListener('touchmove', onMove, { passive: false });
|
|
3385
|
+
document.addEventListener('touchend', onEnd);
|
|
3316
3386
|
}
|
|
3317
3387
|
|
|
3318
3388
|
// Inline Text Editor
|
|
@@ -4452,17 +4522,29 @@
|
|
|
4452
4522
|
function updateFullscreenIcon() {
|
|
4453
4523
|
const icon = document.getElementById('fullscreenIcon');
|
|
4454
4524
|
const btn = document.getElementById('fullscreenBtn');
|
|
4525
|
+
const bottomBtn = document.getElementById('bottomFullscreenBtn');
|
|
4526
|
+
const exitPath = '<path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"/>';
|
|
4527
|
+
const enterPath = '<path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/>';
|
|
4455
4528
|
if (isFullscreen) {
|
|
4456
|
-
icon.innerHTML =
|
|
4529
|
+
icon.innerHTML = exitPath;
|
|
4457
4530
|
btn.classList.add('active');
|
|
4531
|
+
if (bottomBtn) {
|
|
4532
|
+
bottomBtn.querySelector('svg').innerHTML = exitPath;
|
|
4533
|
+
bottomBtn.classList.add('active');
|
|
4534
|
+
}
|
|
4458
4535
|
} else {
|
|
4459
|
-
icon.innerHTML =
|
|
4536
|
+
icon.innerHTML = enterPath;
|
|
4460
4537
|
btn.classList.remove('active');
|
|
4538
|
+
if (bottomBtn) {
|
|
4539
|
+
bottomBtn.querySelector('svg').innerHTML = enterPath;
|
|
4540
|
+
bottomBtn.classList.remove('active');
|
|
4541
|
+
}
|
|
4461
4542
|
}
|
|
4462
4543
|
}
|
|
4463
4544
|
|
|
4464
4545
|
// Apply touch restrictions when entering/exiting fullscreen
|
|
4465
4546
|
function applyFullscreenTouchRestrictions() {
|
|
4547
|
+
document.body.classList.toggle('viewer-fullscreen', isFullscreen);
|
|
4466
4548
|
if (isFullscreen) {
|
|
4467
4549
|
document.documentElement.style.touchAction = 'pan-y pinch-zoom';
|
|
4468
4550
|
document.documentElement.style.overscrollBehavior = 'none';
|
|
@@ -4493,8 +4575,9 @@
|
|
|
4493
4575
|
applyFullscreenTouchRestrictions();
|
|
4494
4576
|
});
|
|
4495
4577
|
|
|
4496
|
-
// Fullscreen button click
|
|
4578
|
+
// Fullscreen button click (top toolbar + bottom toolbar)
|
|
4497
4579
|
document.getElementById('fullscreenBtn').onclick = () => toggleFullscreen();
|
|
4580
|
+
document.getElementById('bottomFullscreenBtn').onclick = () => toggleFullscreen();
|
|
4498
4581
|
|
|
4499
4582
|
|
|
4500
4583
|
|