nodebb-plugin-pdf-secure 1.2.18 → 1.2.20
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/docs/plans/2026-02-24-premium-gate-design.md +52 -0
- package/docs/plans/2026-02-24-premium-gate.md +323 -0
- package/lib/pdf-handler.js +33 -0
- package/library.js +17 -6
- package/package.json +1 -1
- package/static/viewer-app.js +194 -39
- package/static/viewer.html +296 -38
package/static/viewer-app.js
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
(function () {
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
5
|
+
// Security: Capture config early and delete from window immediately
|
|
6
|
+
const _cfg = window.PDF_SECURE_CONFIG ? Object.assign({}, window.PDF_SECURE_CONFIG) : null;
|
|
7
|
+
delete window.PDF_SECURE_CONFIG;
|
|
8
|
+
|
|
5
9
|
// ============================================
|
|
6
10
|
// CANVAS EXPORT PROTECTION
|
|
7
11
|
// Block toDataURL/toBlob for PDF render canvas only
|
|
@@ -45,6 +49,9 @@
|
|
|
45
49
|
let pathSegments = [];
|
|
46
50
|
let drawRAF = null;
|
|
47
51
|
|
|
52
|
+
// Premium info (saved before config deletion for UI use)
|
|
53
|
+
let premiumInfo = null;
|
|
54
|
+
|
|
48
55
|
// Annotation persistence - stores SVG innerHTML per page
|
|
49
56
|
const annotationsStore = new Map();
|
|
50
57
|
const annotationRotations = new Map(); // tracks rotation when annotations were saved
|
|
@@ -91,8 +98,7 @@
|
|
|
91
98
|
firstPageRendered = true;
|
|
92
99
|
// Notify parent that PDF is fully rendered (for queue system)
|
|
93
100
|
if (window.parent && window.parent !== window) {
|
|
94
|
-
|
|
95
|
-
window.parent.postMessage({ type: 'pdf-secure-ready', filename: config.filename }, window.location.origin);
|
|
101
|
+
window.parent.postMessage({ type: 'pdf-secure-ready', filename: (_cfg || {}).filename }, window.location.origin);
|
|
96
102
|
console.log('[PDF-Secure] First page rendered, notifying parent');
|
|
97
103
|
}
|
|
98
104
|
}
|
|
@@ -165,14 +171,106 @@
|
|
|
165
171
|
return data.buffer;
|
|
166
172
|
}
|
|
167
173
|
|
|
174
|
+
function showPremiumLockOverlay(totalPages) {
|
|
175
|
+
const viewerEl = document.getElementById('viewer');
|
|
176
|
+
if (!viewerEl) return;
|
|
177
|
+
|
|
178
|
+
const overlay = document.createElement('div');
|
|
179
|
+
overlay.id = 'premiumLockOverlay';
|
|
180
|
+
overlay.innerHTML = `
|
|
181
|
+
<div class="premium-lock-icon">
|
|
182
|
+
<svg viewBox="0 0 24 24" width="64" height="64" fill="#ffd700">
|
|
183
|
+
<path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1s3.1 1.39 3.1 3.1v2z"/>
|
|
184
|
+
</svg>
|
|
185
|
+
</div>
|
|
186
|
+
<div class="premium-lock-pages">
|
|
187
|
+
${totalPages - 1} sayfa daha kilitli
|
|
188
|
+
</div>
|
|
189
|
+
<div class="premium-lock-message">
|
|
190
|
+
Bu icerigi goruntulemeye devam etmek icin Premium uyelik gereklidir.
|
|
191
|
+
</div>
|
|
192
|
+
<a href="https://forumtest.ieu.app/premium" target="_blank" class="premium-lock-button">
|
|
193
|
+
Premium Satin Al
|
|
194
|
+
</a>
|
|
195
|
+
<div class="premium-lock-secondary">
|
|
196
|
+
Materyal yukleyerek de Premium olabilirsiniz!
|
|
197
|
+
</div>
|
|
198
|
+
`;
|
|
199
|
+
|
|
200
|
+
viewerEl.appendChild(overlay);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ============================================
|
|
204
|
+
// PREMIUM INTEGRITY: Periodic Check (2s interval)
|
|
205
|
+
// Hides pages 2+, recreates overlay if removed, forces page 1
|
|
206
|
+
// ============================================
|
|
207
|
+
function startPeriodicCheck() {
|
|
208
|
+
setInterval(function () {
|
|
209
|
+
if (!premiumInfo || premiumInfo.isPremium) return;
|
|
210
|
+
// Hide all pages beyond page 1
|
|
211
|
+
var pages = document.querySelectorAll('#viewer .page');
|
|
212
|
+
pages.forEach(function (page, idx) {
|
|
213
|
+
if (idx > 0 && page.style.display !== 'none') {
|
|
214
|
+
page.style.display = 'none';
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
// Ensure overlay exists
|
|
218
|
+
if (!document.getElementById('premiumLockOverlay')) {
|
|
219
|
+
showPremiumLockOverlay(premiumInfo.totalPages);
|
|
220
|
+
}
|
|
221
|
+
// Force page 1 if somehow on another page
|
|
222
|
+
if (pdfViewer && pdfViewer.currentPageNumber > 1) {
|
|
223
|
+
pdfViewer.currentPageNumber = 1;
|
|
224
|
+
}
|
|
225
|
+
}, 2000);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ============================================
|
|
229
|
+
// PREMIUM INTEGRITY: MutationObserver Anti-Tampering
|
|
230
|
+
// Recreates overlay on removal, re-hides pages on style change
|
|
231
|
+
// ============================================
|
|
232
|
+
function setupAntiTampering() {
|
|
233
|
+
var viewerEl = document.getElementById('viewer');
|
|
234
|
+
if (!viewerEl) return;
|
|
235
|
+
|
|
236
|
+
// Observer 1: Watch for overlay removal (childList)
|
|
237
|
+
new MutationObserver(function (mutations) {
|
|
238
|
+
for (var i = 0; i < mutations.length; i++) {
|
|
239
|
+
var removed = mutations[i].removedNodes;
|
|
240
|
+
for (var j = 0; j < removed.length; j++) {
|
|
241
|
+
if (removed[j].id === 'premiumLockOverlay') {
|
|
242
|
+
showPremiumLockOverlay(premiumInfo.totalPages);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}).observe(viewerEl, { childList: true });
|
|
248
|
+
|
|
249
|
+
// Observer 2: Watch for page visibility tampering (attributes)
|
|
250
|
+
new MutationObserver(function (mutations) {
|
|
251
|
+
for (var i = 0; i < mutations.length; i++) {
|
|
252
|
+
var m = mutations[i];
|
|
253
|
+
if (m.type === 'attributes' && m.attributeName === 'style') {
|
|
254
|
+
var target = m.target;
|
|
255
|
+
if (target.classList && target.classList.contains('page')) {
|
|
256
|
+
var pageNum = parseInt(target.dataset.pageNumber || '0', 10);
|
|
257
|
+
if (pageNum > 1 && target.style.display !== 'none') {
|
|
258
|
+
target.style.display = 'none';
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}).observe(viewerEl, { childList: true, subtree: true, attributes: true, attributeFilter: ['style'] });
|
|
264
|
+
}
|
|
265
|
+
|
|
168
266
|
// Auto-load PDF if config is present (injected by NodeBB plugin)
|
|
169
267
|
async function autoLoadSecurePDF() {
|
|
170
|
-
if (!
|
|
268
|
+
if (!_cfg || !_cfg.filename) {
|
|
171
269
|
console.log('[PDF-Secure] No config found, showing file picker');
|
|
172
270
|
return;
|
|
173
271
|
}
|
|
174
272
|
|
|
175
|
-
const config =
|
|
273
|
+
const config = _cfg;
|
|
176
274
|
console.log('[PDF-Secure] Auto-loading:', config.filename);
|
|
177
275
|
|
|
178
276
|
// Show loading state
|
|
@@ -244,8 +342,8 @@
|
|
|
244
342
|
pdfBuffer = encodedBuffer;
|
|
245
343
|
}
|
|
246
344
|
|
|
247
|
-
// Send buffer to parent for caching
|
|
248
|
-
if (window.parent && window.parent !== window) {
|
|
345
|
+
// Send buffer to parent for caching (premium only - non-premium must not leak decoded buffer)
|
|
346
|
+
if (_cfg.isPremium !== false && window.parent && window.parent !== window) {
|
|
249
347
|
// Clone buffer for parent (we keep original)
|
|
250
348
|
const bufferCopy = pdfBuffer.slice(0);
|
|
251
349
|
window.parent.postMessage({
|
|
@@ -261,14 +359,21 @@
|
|
|
261
359
|
// Step 4: Load into viewer
|
|
262
360
|
await loadPDFFromBuffer(pdfBuffer);
|
|
263
361
|
|
|
362
|
+
// Premium Gate: Client-side page restriction for non-premium users
|
|
363
|
+
if (config.isPremium === false && pdfDoc && pdfDoc.numPages > 1) {
|
|
364
|
+
premiumInfo = Object.freeze({ isPremium: false, totalPages: pdfDoc.numPages });
|
|
365
|
+
showPremiumLockOverlay(pdfDoc.numPages);
|
|
366
|
+
startPeriodicCheck();
|
|
367
|
+
setupAntiTampering();
|
|
368
|
+
} else {
|
|
369
|
+
premiumInfo = Object.freeze({ isPremium: true, totalPages: pdfDoc ? pdfDoc.numPages : 1 });
|
|
370
|
+
}
|
|
371
|
+
|
|
264
372
|
// Step 5: Moved to pagerendered event for proper timing
|
|
265
373
|
|
|
266
374
|
// Step 6: Security - clear references to prevent extraction
|
|
267
375
|
pdfBuffer = null;
|
|
268
376
|
|
|
269
|
-
// Security: Delete config containing sensitive data (nonce, key)
|
|
270
|
-
delete window.PDF_SECURE_CONFIG;
|
|
271
|
-
|
|
272
377
|
// Security: Remove PDF.js globals to prevent console manipulation
|
|
273
378
|
delete window.pdfjsLib;
|
|
274
379
|
delete window.pdfjsViewer;
|
|
@@ -292,10 +397,9 @@
|
|
|
292
397
|
|
|
293
398
|
// Notify parent of error (prevents 60s queue hang)
|
|
294
399
|
if (window.parent && window.parent !== window) {
|
|
295
|
-
const config = window.PDF_SECURE_CONFIG || {};
|
|
296
400
|
window.parent.postMessage({
|
|
297
401
|
type: 'pdf-secure-ready',
|
|
298
|
-
filename:
|
|
402
|
+
filename: (_cfg || {}).filename,
|
|
299
403
|
error: err.message
|
|
300
404
|
}, window.location.origin);
|
|
301
405
|
}
|
|
@@ -325,10 +429,16 @@
|
|
|
325
429
|
// Create placeholder thumbnails for all pages
|
|
326
430
|
for (let i = 1; i <= pdfDoc.numPages; i++) {
|
|
327
431
|
const thumb = document.createElement('div');
|
|
328
|
-
|
|
432
|
+
const isLocked = premiumInfo && !premiumInfo.isPremium && premiumInfo.totalPages > 1 && i > 1;
|
|
433
|
+
thumb.className = 'thumbnail' + (i === 1 ? ' active' : '') + (isLocked ? ' locked' : '');
|
|
329
434
|
thumb.dataset.page = i;
|
|
330
|
-
|
|
435
|
+
if (isLocked) {
|
|
436
|
+
thumb.innerHTML = `<div class="thumbnailNum">${i}</div><div class="thumbnail-lock">🔒</div>`;
|
|
437
|
+
} else {
|
|
438
|
+
thumb.innerHTML = `<div class="thumbnailNum">${i}</div>`;
|
|
439
|
+
}
|
|
331
440
|
thumb.onclick = () => {
|
|
441
|
+
if (isLocked) return;
|
|
332
442
|
pdfViewer.currentPageNumber = i;
|
|
333
443
|
document.querySelectorAll('.thumbnail').forEach(t => t.classList.remove('active'));
|
|
334
444
|
thumb.classList.add('active');
|
|
@@ -339,7 +449,7 @@
|
|
|
339
449
|
// Lazy render thumbnails with IntersectionObserver
|
|
340
450
|
const thumbObserver = new IntersectionObserver((entries) => {
|
|
341
451
|
entries.forEach(async (entry) => {
|
|
342
|
-
if (entry.isIntersecting && !entry.target.dataset.rendered) {
|
|
452
|
+
if (entry.isIntersecting && !entry.target.dataset.rendered && !entry.target.classList.contains('locked')) {
|
|
343
453
|
entry.target.dataset.rendered = 'true';
|
|
344
454
|
const pageNum = parseInt(entry.target.dataset.page);
|
|
345
455
|
const page = await pdfDoc.getPage(pageNum);
|
|
@@ -359,10 +469,31 @@
|
|
|
359
469
|
// Events
|
|
360
470
|
eventBus.on('pagesinit', () => {
|
|
361
471
|
pdfViewer.currentScaleValue = 'page-width';
|
|
362
|
-
|
|
472
|
+
|
|
473
|
+
// Premium Gate: Hide all pages beyond page 1 immediately
|
|
474
|
+
if (premiumInfo && !premiumInfo.isPremium && premiumInfo.totalPages > 1) {
|
|
475
|
+
for (let i = 1; i < pdfViewer.pagesCount; i++) {
|
|
476
|
+
const pageView = pdfViewer.getPageView(i); // 0-indexed
|
|
477
|
+
if (pageView && pageView.div) {
|
|
478
|
+
pageView.div.style.display = 'none';
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
if (premiumInfo && !premiumInfo.isPremium && premiumInfo.totalPages > 1) {
|
|
484
|
+
document.getElementById('pageCount').textContent = `/ ${premiumInfo.totalPages}`;
|
|
485
|
+
} else {
|
|
486
|
+
document.getElementById('pageCount').textContent = `/ ${pdfViewer.pagesCount}`;
|
|
487
|
+
}
|
|
363
488
|
});
|
|
364
489
|
|
|
365
490
|
eventBus.on('pagechanging', (evt) => {
|
|
491
|
+
// Premium Gate: Block navigation beyond page 1 for non-premium users
|
|
492
|
+
if (premiumInfo && !premiumInfo.isPremium && premiumInfo.totalPages > 1 && evt.pageNumber > 1) {
|
|
493
|
+
// Force back to page 1
|
|
494
|
+
setTimeout(() => { pdfViewer.currentPageNumber = 1; }, 0);
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
366
497
|
document.getElementById('pageInput').value = evt.pageNumber;
|
|
367
498
|
// Update active thumbnail
|
|
368
499
|
document.querySelectorAll('.thumbnail').forEach(t => {
|
|
@@ -385,6 +516,15 @@
|
|
|
385
516
|
});
|
|
386
517
|
|
|
387
518
|
eventBus.on('pagerendered', (evt) => {
|
|
519
|
+
// Premium Gate: Hide pages beyond page 1 for non-premium users
|
|
520
|
+
if (premiumInfo && !premiumInfo.isPremium && premiumInfo.totalPages > 1 && evt.pageNumber > 1) {
|
|
521
|
+
const pageView = pdfViewer.getPageView(evt.pageNumber - 1);
|
|
522
|
+
if (pageView && pageView.div) {
|
|
523
|
+
pageView.div.style.display = 'none';
|
|
524
|
+
}
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
|
|
388
528
|
injectAnnotationLayer(evt.pageNumber);
|
|
389
529
|
|
|
390
530
|
// Rotation is handled natively by PDF.js via pagesRotation
|
|
@@ -393,6 +533,11 @@
|
|
|
393
533
|
// Page Navigation
|
|
394
534
|
document.getElementById('pageInput').onchange = (e) => {
|
|
395
535
|
const num = parseInt(e.target.value);
|
|
536
|
+
// Premium Gate: Block manual page input beyond page 1
|
|
537
|
+
if (premiumInfo && !premiumInfo.isPremium && premiumInfo.totalPages > 1 && num > 1) {
|
|
538
|
+
e.target.value = 1;
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
396
541
|
if (num >= 1 && num <= pdfViewer.pagesCount) {
|
|
397
542
|
pdfViewer.currentPageNumber = num;
|
|
398
543
|
}
|
|
@@ -525,6 +670,10 @@
|
|
|
525
670
|
document.getElementById('sepiaBtn').classList.contains('active'));
|
|
526
671
|
closeAllDropdowns();
|
|
527
672
|
};
|
|
673
|
+
document.getElementById('overflowFullscreen').onclick = () => {
|
|
674
|
+
toggleFullscreen();
|
|
675
|
+
closeAllDropdowns();
|
|
676
|
+
};
|
|
528
677
|
|
|
529
678
|
// Close dropdowns when clicking outside
|
|
530
679
|
document.addEventListener('click', (e) => {
|
|
@@ -613,15 +762,18 @@
|
|
|
613
762
|
highlightColor = c;
|
|
614
763
|
if (currentTool === 'highlight') currentColor = c;
|
|
615
764
|
document.getElementById('highlightWave').setAttribute('stroke', c);
|
|
765
|
+
document.getElementById('highlightColorIndicator').style.background = c;
|
|
616
766
|
});
|
|
617
767
|
setupColorPicker('drawColors', c => {
|
|
618
768
|
drawColor = c;
|
|
619
769
|
if (currentTool === 'pen') currentColor = c;
|
|
620
770
|
document.getElementById('drawWave').setAttribute('stroke', c);
|
|
771
|
+
document.getElementById('drawColorIndicator').style.background = c;
|
|
621
772
|
});
|
|
622
773
|
setupColorPicker('shapeColors', c => {
|
|
623
774
|
shapeColor = c;
|
|
624
775
|
if (currentTool === 'shape') currentColor = c;
|
|
776
|
+
document.getElementById('shapeColorIndicator').style.background = c;
|
|
625
777
|
});
|
|
626
778
|
|
|
627
779
|
// Highlighter Thickness Slider
|
|
@@ -745,13 +897,9 @@
|
|
|
745
897
|
e.preventDefault();
|
|
746
898
|
draw(e);
|
|
747
899
|
};
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
} else {
|
|
752
|
-
svg.addEventListener('touchstart', touchStartHandler, { signal });
|
|
753
|
-
svg.addEventListener('touchmove', touchMoveHandler, { signal });
|
|
754
|
-
}
|
|
900
|
+
// Always use passive:false so preventDefault() works when a tool is later activated
|
|
901
|
+
svg.addEventListener('touchstart', touchStartHandler, { passive: false, signal });
|
|
902
|
+
svg.addEventListener('touchmove', touchMoveHandler, { passive: false, signal });
|
|
755
903
|
svg.addEventListener('touchend', () => stopDraw(pageNum), { signal });
|
|
756
904
|
svg.addEventListener('touchcancel', () => stopDraw(pageNum), { signal });
|
|
757
905
|
|
|
@@ -965,7 +1113,8 @@
|
|
|
965
1113
|
// Text tool - create/edit/drag text
|
|
966
1114
|
if (currentTool === 'text') {
|
|
967
1115
|
// Check if clicked on existing text element
|
|
968
|
-
|
|
1116
|
+
// Use coords (touch-safe) instead of e.clientX which is undefined on TouchEvent
|
|
1117
|
+
const elementsUnderClick = document.elementsFromPoint(coords.clientX, coords.clientY);
|
|
969
1118
|
const existingText = elementsUnderClick.find(el => el.tagName === 'text' && el.closest('.annotationLayer'));
|
|
970
1119
|
|
|
971
1120
|
if (existingText) {
|
|
@@ -973,7 +1122,7 @@
|
|
|
973
1122
|
startTextDrag(e, existingText, svg, scaleX, pageNum);
|
|
974
1123
|
} else {
|
|
975
1124
|
// Create new text
|
|
976
|
-
showTextEditor(
|
|
1125
|
+
showTextEditor(coords.clientX, coords.clientY, svg, x, y, scaleX, pageNum);
|
|
977
1126
|
}
|
|
978
1127
|
return;
|
|
979
1128
|
}
|
|
@@ -1235,14 +1384,18 @@
|
|
|
1235
1384
|
textEl.classList.add('dragging');
|
|
1236
1385
|
hasDragged = false;
|
|
1237
1386
|
|
|
1238
|
-
|
|
1239
|
-
|
|
1387
|
+
// Touch-safe coordinate extraction
|
|
1388
|
+
const startCoords = getEventCoords(e);
|
|
1389
|
+
dragStartX = startCoords.clientX;
|
|
1390
|
+
dragStartY = startCoords.clientY;
|
|
1240
1391
|
textOriginalX = parseFloat(textEl.getAttribute('x'));
|
|
1241
1392
|
textOriginalY = parseFloat(textEl.getAttribute('y'));
|
|
1242
1393
|
|
|
1243
|
-
function
|
|
1244
|
-
|
|
1245
|
-
const
|
|
1394
|
+
function onMove(ev) {
|
|
1395
|
+
ev.preventDefault();
|
|
1396
|
+
const moveCoords = getEventCoords(ev);
|
|
1397
|
+
const dxScreen = moveCoords.clientX - dragStartX;
|
|
1398
|
+
const dyScreen = moveCoords.clientY - dragStartY;
|
|
1246
1399
|
// Convert screen delta to viewBox delta (rotation-aware)
|
|
1247
1400
|
const vbDelta = screenDeltaToViewBox(svg, dxScreen, dyScreen, textEl);
|
|
1248
1401
|
|
|
@@ -1254,29 +1407,31 @@
|
|
|
1254
1407
|
textEl.setAttribute('y', (textOriginalY + vbDelta.dy).toFixed(2));
|
|
1255
1408
|
}
|
|
1256
1409
|
|
|
1257
|
-
function
|
|
1258
|
-
document.removeEventListener('mousemove',
|
|
1259
|
-
document.removeEventListener('mouseup',
|
|
1410
|
+
function onEnd(ev) {
|
|
1411
|
+
document.removeEventListener('mousemove', onMove);
|
|
1412
|
+
document.removeEventListener('mouseup', onEnd);
|
|
1413
|
+
document.removeEventListener('touchmove', onMove);
|
|
1414
|
+
document.removeEventListener('touchend', onEnd);
|
|
1260
1415
|
textEl.classList.remove('dragging');
|
|
1261
1416
|
|
|
1262
1417
|
if (hasDragged) {
|
|
1263
1418
|
// Moved - save position
|
|
1264
1419
|
saveAnnotations(pageNum);
|
|
1265
1420
|
} else {
|
|
1266
|
-
// Not moved - short click = edit
|
|
1267
|
-
const viewBoxWidth = parseFloat(svg.dataset.viewboxWidth);
|
|
1268
|
-
const viewBoxHeight = parseFloat(svg.dataset.viewboxHeight);
|
|
1421
|
+
// Not moved - short click/tap = edit
|
|
1269
1422
|
const svgX = parseFloat(textEl.getAttribute('x'));
|
|
1270
1423
|
const svgY = parseFloat(textEl.getAttribute('y'));
|
|
1271
|
-
|
|
1272
|
-
showTextEditor(
|
|
1424
|
+
const endCoords = getEventCoords(ev);
|
|
1425
|
+
showTextEditor(endCoords.clientX, endCoords.clientY, svg, svgX, svgY, scaleX, pageNum, textEl);
|
|
1273
1426
|
}
|
|
1274
1427
|
|
|
1275
1428
|
draggedText = null;
|
|
1276
1429
|
}
|
|
1277
1430
|
|
|
1278
|
-
document.addEventListener('mousemove',
|
|
1279
|
-
document.addEventListener('mouseup',
|
|
1431
|
+
document.addEventListener('mousemove', onMove);
|
|
1432
|
+
document.addEventListener('mouseup', onEnd);
|
|
1433
|
+
document.addEventListener('touchmove', onMove, { passive: false });
|
|
1434
|
+
document.addEventListener('touchend', onEnd);
|
|
1280
1435
|
}
|
|
1281
1436
|
|
|
1282
1437
|
// Inline Text Editor
|