nodebb-plugin-pdf-secure 1.2.18 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-pdf-secure",
3
- "version": "1.2.18",
3
+ "version": "1.2.19",
4
4
  "description": "Secure PDF viewer plugin for NodeBB - prevents downloading, enables canvas-only rendering with Premium group support",
5
5
  "main": "library.js",
6
6
  "repository": {
@@ -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
- if (annotationMode) {
749
- svg.addEventListener('touchstart', touchStartHandler, { passive: false, signal });
750
- svg.addEventListener('touchmove', touchMoveHandler, { passive: false, signal });
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
- const elementsUnderClick = document.elementsFromPoint(e.clientX, e.clientY);
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(e.clientX, e.clientY, svg, x, y, scaleX, pageNum);
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
- dragStartX = e.clientX;
1239
- dragStartY = e.clientY;
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 onMouseMove(ev) {
1244
- const dxScreen = ev.clientX - dragStartX;
1245
- const dyScreen = ev.clientY - dragStartY;
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 onMouseUp(ev) {
1258
- document.removeEventListener('mousemove', onMouseMove);
1259
- document.removeEventListener('mouseup', onMouseUp);
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
- // Note: showTextEditor needs scaleX for font scaling logic, which we still have from arguments
1272
- showTextEditor(ev.clientX, ev.clientY, svg, svgX, svgY, scaleX, pageNum, textEl);
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', onMouseMove);
1279
- document.addEventListener('mouseup', onMouseUp);
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
@@ -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: block;
1276
+ display: flex;
1252
1277
  }
1253
1278
 
1254
1279
  /* Viewer container adjusted for mobile toolbars */
@@ -1358,7 +1383,7 @@
1358
1383
  ========================================== */
1359
1384
  /* Ensure bottom toolbar is visible and viewer container makes room for it */
1360
1385
  body.viewer-fullscreen #bottomToolbar {
1361
- display: block;
1386
+ display: flex;
1362
1387
  }
1363
1388
 
1364
1389
  body.viewer-fullscreen #viewerContainer {
@@ -1430,7 +1455,7 @@
1430
1455
 
1431
1456
  /* Show bottom toolbar */
1432
1457
  #bottomToolbar {
1433
- display: block;
1458
+ display: flex;
1434
1459
  }
1435
1460
 
1436
1461
  /* Viewer container adjusted for both toolbars */
@@ -1573,6 +1598,7 @@
1573
1598
  <path d="M3 21h18v-2H3v2zM5 16h14l-3-10H8l-3 10zM9 8h6l1.5 5h-9L9 8z" opacity="0.7" />
1574
1599
  </svg>
1575
1600
  </button>
1601
+ <div class="toolColorIndicator" id="highlightColorIndicator" style="background:#fff100"></div>
1576
1602
  <button class="dropdownArrow" id="highlightArrow">
1577
1603
  <svg viewBox="0 0 24 24">
1578
1604
  <path d="M7 10l5 5 5-5z" />
@@ -1618,6 +1644,7 @@
1618
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" />
1619
1645
  </svg>
1620
1646
  </button>
1647
+ <div class="toolColorIndicator" id="drawColorIndicator" style="background:#e81224"></div>
1621
1648
  <button class="dropdownArrow" id="drawArrow">
1622
1649
  <svg viewBox="0 0 24 24">
1623
1650
  <path d="M7 10l5 5 5-5z" />
@@ -1721,6 +1748,7 @@
1721
1748
  <path d="M3 3h8v8H3V3zm10 0h8v8h-8V3zM3 13h8v8H3v-8zm13 0a5 5 0 110 10 5 5 0 010-10z" />
1722
1749
  </svg>
1723
1750
  </button>
1751
+ <div class="toolColorIndicator" id="shapeColorIndicator" style="background:#e81224"></div>
1724
1752
  <button class="dropdownArrow" id="shapesArrow">
1725
1753
  <svg viewBox="0 0 24 24">
1726
1754
  <path d="M7 10l5 5 5-5z" />
@@ -1863,6 +1891,13 @@
1863
1891
  </svg>
1864
1892
  <span>Okuma Modu</span>
1865
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>
1866
1901
  </div>
1867
1902
  </div>
1868
1903
  </div>
@@ -1873,11 +1908,16 @@
1873
1908
  </div>
1874
1909
  </div>
1875
1910
 
1876
- <!-- Bottom Toolbar (Mobile Only) -->
1911
+ <!-- Bottom Toolbar (Mobile/Tablet Portrait) -->
1877
1912
  <div id="bottomToolbar">
1878
1913
  <div class="bottomToolbarInner" id="bottomToolbarInner">
1879
1914
  <!-- Annotation tool buttons will be moved here on mobile via JS -->
1880
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>
1881
1921
  </div>
1882
1922
 
1883
1923
  <!-- Dropdown Backdrop (Mobile) -->
@@ -2429,7 +2469,10 @@
2429
2469
  dropdown.addEventListener('touchstart', (e) => {
2430
2470
  const rect = dropdown.getBoundingClientRect();
2431
2471
  const touchY = e.touches[0].clientY;
2432
- if (touchY - rect.top > 40 && dropdown.scrollTop > 0) return;
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;
2433
2476
  startY = touchY;
2434
2477
  currentY = startY;
2435
2478
  isDragging = true;
@@ -2487,6 +2530,10 @@
2487
2530
  document.getElementById('sepiaBtn').classList.contains('active'));
2488
2531
  closeAllDropdowns();
2489
2532
  };
2533
+ document.getElementById('overflowFullscreen').onclick = () => {
2534
+ toggleFullscreen();
2535
+ closeAllDropdowns();
2536
+ };
2490
2537
 
2491
2538
  // Close dropdowns when clicking outside
2492
2539
  document.addEventListener('click', (e) => {
@@ -2569,8 +2616,9 @@
2569
2616
  dot.classList.add('active');
2570
2617
  highlightColor = dot.dataset.color;
2571
2618
  if (currentTool === 'highlight') currentColor = highlightColor;
2572
- // Update preview
2619
+ // Update preview and color indicator
2573
2620
  document.getElementById('highlightWave').setAttribute('stroke', highlightColor);
2621
+ document.getElementById('highlightColorIndicator').style.background = highlightColor;
2574
2622
  };
2575
2623
  });
2576
2624
 
@@ -2582,8 +2630,9 @@
2582
2630
  dot.classList.add('active');
2583
2631
  drawColor = dot.dataset.color;
2584
2632
  if (currentTool === 'pen') currentColor = drawColor;
2585
- // Update preview
2633
+ // Update preview and color indicator
2586
2634
  document.getElementById('drawWave').setAttribute('stroke', drawColor);
2635
+ document.getElementById('drawColorIndicator').style.background = drawColor;
2587
2636
  };
2588
2637
  });
2589
2638
 
@@ -2621,6 +2670,8 @@
2621
2670
  dot.classList.add('active');
2622
2671
  shapeColor = dot.dataset.color;
2623
2672
  if (currentTool === 'shape') currentColor = shapeColor;
2673
+ // Update color indicator
2674
+ document.getElementById('shapeColorIndicator').style.background = shapeColor;
2624
2675
  };
2625
2676
  });
2626
2677
 
@@ -3005,7 +3056,8 @@
3005
3056
  // Text tool - create/edit/drag text
3006
3057
  if (currentTool === 'text') {
3007
3058
  // Check if clicked on existing text element
3008
- const elementsUnderClick = document.elementsFromPoint(e.clientX, e.clientY);
3059
+ // Use coords (touch-safe) instead of e.clientX which is undefined on TouchEvent
3060
+ const elementsUnderClick = document.elementsFromPoint(coords.clientX, coords.clientY);
3009
3061
  const existingText = elementsUnderClick.find(el => el.tagName === 'text' && el.closest('.annotationLayer'));
3010
3062
 
3011
3063
  if (existingText) {
@@ -3013,7 +3065,7 @@
3013
3065
  startTextDrag(e, existingText, svg, scaleX, pageNum);
3014
3066
  } else {
3015
3067
  // Create new text
3016
- showTextEditor(e.clientX, e.clientY, svg, x, y, scaleX, pageNum);
3068
+ showTextEditor(coords.clientX, coords.clientY, svg, x, y, scaleX, pageNum);
3017
3069
  }
3018
3070
  return;
3019
3071
  }
@@ -3283,14 +3335,18 @@
3283
3335
  textEl.classList.add('dragging');
3284
3336
  hasDragged = false;
3285
3337
 
3286
- dragStartX = e.clientX;
3287
- dragStartY = e.clientY;
3338
+ // Touch-safe coordinate extraction
3339
+ const startCoords = getEventCoords(e);
3340
+ dragStartX = startCoords.clientX;
3341
+ dragStartY = startCoords.clientY;
3288
3342
  textOriginalX = parseFloat(textEl.getAttribute('x'));
3289
3343
  textOriginalY = parseFloat(textEl.getAttribute('y'));
3290
3344
 
3291
- function onMouseMove(ev) {
3292
- const dxScreen = ev.clientX - dragStartX;
3293
- const dyScreen = ev.clientY - dragStartY;
3345
+ function onMove(ev) {
3346
+ ev.preventDefault();
3347
+ const moveCoords = getEventCoords(ev);
3348
+ const dxScreen = moveCoords.clientX - dragStartX;
3349
+ const dyScreen = moveCoords.clientY - dragStartY;
3294
3350
  // Convert screen delta to viewBox delta (rotation-aware)
3295
3351
  const vbDelta = screenDeltaToViewBox(svg, dxScreen, dyScreen);
3296
3352
 
@@ -3302,29 +3358,31 @@
3302
3358
  textEl.setAttribute('y', (textOriginalY + vbDelta.dy).toFixed(2));
3303
3359
  }
3304
3360
 
3305
- function onMouseUp(ev) {
3306
- document.removeEventListener('mousemove', onMouseMove);
3307
- document.removeEventListener('mouseup', onMouseUp);
3361
+ function onEnd(ev) {
3362
+ document.removeEventListener('mousemove', onMove);
3363
+ document.removeEventListener('mouseup', onEnd);
3364
+ document.removeEventListener('touchmove', onMove);
3365
+ document.removeEventListener('touchend', onEnd);
3308
3366
  textEl.classList.remove('dragging');
3309
3367
 
3310
3368
  if (hasDragged) {
3311
3369
  // Moved - save position
3312
3370
  saveAnnotations(pageNum);
3313
3371
  } else {
3314
- // Not moved - short click = edit
3315
- const viewBoxWidth = parseFloat(svg.dataset.viewboxWidth);
3316
- const viewBoxHeight = parseFloat(svg.dataset.viewboxHeight);
3372
+ // Not moved - short click/tap = edit
3317
3373
  const svgX = parseFloat(textEl.getAttribute('x'));
3318
3374
  const svgY = parseFloat(textEl.getAttribute('y'));
3319
- // Note: showTextEditor needs scaleX for font scaling logic, which we still have from arguments
3320
- showTextEditor(ev.clientX, ev.clientY, svg, svgX, svgY, scaleX, pageNum, textEl);
3375
+ const endCoords = getEventCoords(ev);
3376
+ showTextEditor(endCoords.clientX, endCoords.clientY, svg, svgX, svgY, scaleX, pageNum, textEl);
3321
3377
  }
3322
3378
 
3323
3379
  draggedText = null;
3324
3380
  }
3325
3381
 
3326
- document.addEventListener('mousemove', onMouseMove);
3327
- document.addEventListener('mouseup', onMouseUp);
3382
+ document.addEventListener('mousemove', onMove);
3383
+ document.addEventListener('mouseup', onEnd);
3384
+ document.addEventListener('touchmove', onMove, { passive: false });
3385
+ document.addEventListener('touchend', onEnd);
3328
3386
  }
3329
3387
 
3330
3388
  // Inline Text Editor
@@ -4464,12 +4522,23 @@
4464
4522
  function updateFullscreenIcon() {
4465
4523
  const icon = document.getElementById('fullscreenIcon');
4466
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"/>';
4467
4528
  if (isFullscreen) {
4468
- icon.innerHTML = '<path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"/>';
4529
+ icon.innerHTML = exitPath;
4469
4530
  btn.classList.add('active');
4531
+ if (bottomBtn) {
4532
+ bottomBtn.querySelector('svg').innerHTML = exitPath;
4533
+ bottomBtn.classList.add('active');
4534
+ }
4470
4535
  } else {
4471
- icon.innerHTML = '<path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/>';
4536
+ icon.innerHTML = enterPath;
4472
4537
  btn.classList.remove('active');
4538
+ if (bottomBtn) {
4539
+ bottomBtn.querySelector('svg').innerHTML = enterPath;
4540
+ bottomBtn.classList.remove('active');
4541
+ }
4473
4542
  }
4474
4543
  }
4475
4544
 
@@ -4506,8 +4575,9 @@
4506
4575
  applyFullscreenTouchRestrictions();
4507
4576
  });
4508
4577
 
4509
- // Fullscreen button click
4578
+ // Fullscreen button click (top toolbar + bottom toolbar)
4510
4579
  document.getElementById('fullscreenBtn').onclick = () => toggleFullscreen();
4580
+ document.getElementById('bottomFullscreenBtn').onclick = () => toggleFullscreen();
4511
4581
 
4512
4582
 
4513
4583