tosijs-styled-editor 0.5.2 → 0.6.0
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/CHANGELOG.md +153 -2
- package/README.md +123 -32
- package/dist/changes.d.ts +48 -1
- package/dist/commands.d.ts +0 -7
- package/dist/index.js +5 -5
- package/dist/module.js +393 -168
- package/dist/selection.d.ts +104 -1
- package/dist/tosijs-styled-editor.d.ts +287 -11
- package/dist/version.d.ts +1 -1
- package/package.json +14 -4
package/dist/module.js
CHANGED
|
@@ -329,9 +329,10 @@ function wordBoundsAt(text, offset) {
|
|
|
329
329
|
end++;
|
|
330
330
|
return { start, end };
|
|
331
331
|
}
|
|
332
|
-
function stickySelectionBounds(text, anchor, head) {
|
|
332
|
+
function stickySelectionBounds(text, anchor, head, leftTheAnchorWord) {
|
|
333
333
|
const anchorWord = wordBoundsAt(text, anchor);
|
|
334
|
-
|
|
334
|
+
const stillInside = leftTheAnchorWord === undefined ? head >= anchorWord.start && head <= anchorWord.end : !leftTheAnchorWord;
|
|
335
|
+
if (stillInside) {
|
|
335
336
|
return head < anchor ? { start: head, end: anchor } : { start: anchor, end: head };
|
|
336
337
|
}
|
|
337
338
|
const headWord = wordBoundsAt(text, head);
|
|
@@ -421,6 +422,7 @@ class Selectable {
|
|
|
421
422
|
root;
|
|
422
423
|
selecting = false;
|
|
423
424
|
touchMode = false;
|
|
425
|
+
stickySelection = "touch";
|
|
424
426
|
constructor(root) {
|
|
425
427
|
this.root = root;
|
|
426
428
|
this.setup();
|
|
@@ -469,19 +471,7 @@ class Selectable {
|
|
|
469
471
|
this.lastHovered = target;
|
|
470
472
|
}
|
|
471
473
|
if (this.selecting) {
|
|
472
|
-
|
|
473
|
-
const selEnd = this.find(".sel-end");
|
|
474
|
-
if (hit && selEnd) {
|
|
475
|
-
if (!this.extendSticky(hit)) {
|
|
476
|
-
const range = document.createRange();
|
|
477
|
-
range.setStart(hit.node, hit.after ? hit.offset + 1 : hit.offset);
|
|
478
|
-
range.collapse(true);
|
|
479
|
-
range.insertNode(selEnd);
|
|
480
|
-
this.root.normalize();
|
|
481
|
-
}
|
|
482
|
-
this.extendSelection();
|
|
483
|
-
this.onBoundsChanged?.();
|
|
484
|
-
}
|
|
474
|
+
this.extendTo(evt.clientX, evt.clientY);
|
|
485
475
|
}
|
|
486
476
|
evt.preventDefault();
|
|
487
477
|
evt.stopPropagation();
|
|
@@ -524,9 +514,65 @@ class Selectable {
|
|
|
524
514
|
}
|
|
525
515
|
};
|
|
526
516
|
}
|
|
527
|
-
|
|
517
|
+
setDragAnchor(hit) {
|
|
518
|
+
const block = hit && this.topLevelAncestor(hit.node);
|
|
519
|
+
this.dragAnchor = hit && block ? {
|
|
520
|
+
block,
|
|
521
|
+
index: this.textIndexOf(block).indexOf(hit.node, hit.after ? hit.offset + 1 : hit.offset) ?? 0
|
|
522
|
+
} : null;
|
|
523
|
+
}
|
|
524
|
+
extendTo(x, y, which = "end") {
|
|
525
|
+
const hit = characterAtPoint(this.root, x, y);
|
|
526
|
+
const marker = this.find(which === "start" ? ".sel-start" : ".sel-end");
|
|
527
|
+
if (!hit || !marker)
|
|
528
|
+
return;
|
|
529
|
+
if (!this.extendSticky(hit, x, y)) {
|
|
530
|
+
const range = document.createRange();
|
|
531
|
+
range.setStart(hit.node, hit.after ? hit.offset + 1 : hit.offset);
|
|
532
|
+
range.collapse(true);
|
|
533
|
+
range.insertNode(marker);
|
|
534
|
+
this.root.normalize();
|
|
535
|
+
}
|
|
536
|
+
this.extendSelection();
|
|
537
|
+
this.onBoundsChanged?.();
|
|
538
|
+
}
|
|
539
|
+
beginBoundDrag(which) {
|
|
540
|
+
const anchorEl = this.find(which === "start" ? ".sel-end" : ".sel-start");
|
|
541
|
+
const block = anchorEl && this.topLevelAncestor(anchorEl);
|
|
542
|
+
this.selecting = 1;
|
|
543
|
+
this.touchMode = true;
|
|
544
|
+
this.dragAnchor = anchorEl && block ? { block, index: this.textOffsetOf(block, anchorEl) } : null;
|
|
545
|
+
}
|
|
546
|
+
endBoundDrag() {
|
|
547
|
+
this.selecting = false;
|
|
548
|
+
this.dragAnchor = null;
|
|
549
|
+
}
|
|
550
|
+
pointerInAnchorWord(x, y) {
|
|
528
551
|
const anchor = this.dragAnchor;
|
|
529
|
-
if (
|
|
552
|
+
if (!anchor?.word)
|
|
553
|
+
return false;
|
|
554
|
+
const index = this.textIndexOf(anchor.block);
|
|
555
|
+
const from = index.locate(anchor.word.start);
|
|
556
|
+
const to = index.locate(anchor.word.end);
|
|
557
|
+
if (!from || !to)
|
|
558
|
+
return false;
|
|
559
|
+
const range = document.createRange();
|
|
560
|
+
range.setStart(from.node, Math.min(from.offset, from.node.length));
|
|
561
|
+
range.setEnd(to.node, Math.min(to.offset, to.node.length));
|
|
562
|
+
const pad = 1;
|
|
563
|
+
for (const r of Array.from(range.getClientRects())) {
|
|
564
|
+
if (x >= r.left - pad && x <= r.right + pad && y >= r.top - pad && y <= r.bottom + pad) {
|
|
565
|
+
return true;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
return false;
|
|
569
|
+
}
|
|
570
|
+
get stickyApplies() {
|
|
571
|
+
return this.stickySelection === "always" || this.stickySelection === "touch" && this.touchMode;
|
|
572
|
+
}
|
|
573
|
+
extendSticky(hit, x, y) {
|
|
574
|
+
const anchor = this.dragAnchor;
|
|
575
|
+
if (!this.stickyApplies || this.selecting !== 1 || !anchor)
|
|
530
576
|
return false;
|
|
531
577
|
const block = this.topLevelAncestor(hit.node);
|
|
532
578
|
if (!block || block !== anchor.block)
|
|
@@ -535,7 +581,9 @@ class Selectable {
|
|
|
535
581
|
const head = index.indexOf(hit.node, hit.after ? hit.offset + 1 : hit.offset);
|
|
536
582
|
if (head === null)
|
|
537
583
|
return false;
|
|
538
|
-
|
|
584
|
+
if (!anchor.word)
|
|
585
|
+
anchor.word = wordBoundsAt(index.text, anchor.index);
|
|
586
|
+
const { start, end } = stickySelectionBounds(index.text, anchor.index, head, !this.pointerInAnchorWord(x, y));
|
|
539
587
|
const startPos = index.locate(start);
|
|
540
588
|
const endPos = index.locate(end);
|
|
541
589
|
if (!startPos || !endPos)
|
|
@@ -607,11 +655,7 @@ class Selectable {
|
|
|
607
655
|
this.extendSelection();
|
|
608
656
|
}
|
|
609
657
|
} else if (this.selecting === 1) {
|
|
610
|
-
|
|
611
|
-
this.dragAnchor = block ? {
|
|
612
|
-
block,
|
|
613
|
-
index: this.textIndexOf(block).indexOf(hit.node, hit.after ? hit.offset + 1 : hit.offset) ?? 0
|
|
614
|
-
} : null;
|
|
658
|
+
this.setDragAnchor(hit);
|
|
615
659
|
this.placeCaretAt(evt.clientX, evt.clientY);
|
|
616
660
|
} else if (this.selecting === 2) {
|
|
617
661
|
this.selectWordAt(hit.node, hit.offset);
|
|
@@ -686,8 +730,10 @@ class Selectable {
|
|
|
686
730
|
this.selecting = 1;
|
|
687
731
|
const hit = characterAtPoint(this.root, touch.clientX, touch.clientY);
|
|
688
732
|
if (hit) {
|
|
733
|
+
this.setDragAnchor(hit);
|
|
689
734
|
this.placeCaretAt(touch.clientX, touch.clientY);
|
|
690
735
|
} else if (target instanceof HTMLElement && target !== this.root) {
|
|
736
|
+
this.setDragAnchor(null);
|
|
691
737
|
this.removeBounds();
|
|
692
738
|
const bounds = this.createBounds();
|
|
693
739
|
target.appendChild(bounds);
|
|
@@ -704,17 +750,7 @@ class Selectable {
|
|
|
704
750
|
if (!target || target.closest(".not-selectable") || target.classList.contains("not-selectable")) {
|
|
705
751
|
return;
|
|
706
752
|
}
|
|
707
|
-
|
|
708
|
-
const selEnd = this.find(".sel-end");
|
|
709
|
-
if (hit && selEnd) {
|
|
710
|
-
const range = document.createRange();
|
|
711
|
-
range.setStart(hit.node, hit.after ? hit.offset + 1 : hit.offset);
|
|
712
|
-
range.collapse(true);
|
|
713
|
-
range.insertNode(selEnd);
|
|
714
|
-
this.root.normalize();
|
|
715
|
-
this.extendSelection();
|
|
716
|
-
this.onBoundsChanged?.();
|
|
717
|
-
}
|
|
753
|
+
this.extendTo(touch.clientX, touch.clientY);
|
|
718
754
|
if (evt.cancelable)
|
|
719
755
|
evt.preventDefault();
|
|
720
756
|
};
|
|
@@ -723,6 +759,7 @@ class Selectable {
|
|
|
723
759
|
this.extendSelection();
|
|
724
760
|
this.normalizeBoundsOrder();
|
|
725
761
|
this.selecting = false;
|
|
762
|
+
this.dragAnchor = null;
|
|
726
763
|
}
|
|
727
764
|
for (const child of Array.from(this.root.children)) {
|
|
728
765
|
if (!child.classList.contains("selected-block")) {
|
|
@@ -1254,6 +1291,8 @@ function footnoteKey() {
|
|
|
1254
1291
|
footnoteSeq += 1;
|
|
1255
1292
|
return `fn-${Date.now().toString(36)}-${footnoteSeq}`;
|
|
1256
1293
|
}
|
|
1294
|
+
var PLACEHOLDER = "Footnote text";
|
|
1295
|
+
var isPlaceholder = (item) => (item.textContent ?? "").trim() === PLACEHOLDER;
|
|
1257
1296
|
function renumberFootnotes(root) {
|
|
1258
1297
|
const refs = Array.from(root.querySelectorAll(`${FOOTNOTE_TAG}[data-footnote], .footnote-ref[data-footnote]`)).filter((ref) => !ref.closest(".footnotes"));
|
|
1259
1298
|
let list = root.querySelector("ol.footnotes");
|
|
@@ -1267,29 +1306,53 @@ function renumberFootnotes(root) {
|
|
|
1267
1306
|
root.appendChild(list);
|
|
1268
1307
|
}
|
|
1269
1308
|
const existing = new Map;
|
|
1309
|
+
const corrupt = [];
|
|
1270
1310
|
for (const item of Array.from(list.children)) {
|
|
1271
1311
|
const key = item.getAttribute("data-footnote");
|
|
1272
|
-
if (key)
|
|
1312
|
+
if (!key)
|
|
1313
|
+
continue;
|
|
1314
|
+
const held = existing.get(key);
|
|
1315
|
+
if (!held) {
|
|
1316
|
+
existing.set(key, item);
|
|
1317
|
+
continue;
|
|
1318
|
+
}
|
|
1319
|
+
if (isPlaceholder(held) && !isPlaceholder(item)) {
|
|
1273
1320
|
existing.set(key, item);
|
|
1321
|
+
corrupt.push(held);
|
|
1322
|
+
} else {
|
|
1323
|
+
corrupt.push(item);
|
|
1324
|
+
}
|
|
1274
1325
|
}
|
|
1275
|
-
|
|
1326
|
+
for (const duplicate of corrupt)
|
|
1327
|
+
duplicate.remove();
|
|
1328
|
+
const numbers = new Map;
|
|
1329
|
+
const placed = new Set;
|
|
1330
|
+
for (const ref of refs) {
|
|
1276
1331
|
const key = ref.getAttribute("data-footnote");
|
|
1332
|
+
let number = numbers.get(key);
|
|
1333
|
+
if (number === undefined) {
|
|
1334
|
+
number = numbers.size + 1;
|
|
1335
|
+
numbers.set(key, number);
|
|
1336
|
+
}
|
|
1277
1337
|
const link = ref.querySelector("a");
|
|
1278
1338
|
if (link) {
|
|
1279
|
-
link.textContent = String(
|
|
1339
|
+
link.textContent = String(number);
|
|
1280
1340
|
link.setAttribute("href", `#${key}`);
|
|
1281
1341
|
}
|
|
1342
|
+
if (placed.has(key))
|
|
1343
|
+
continue;
|
|
1344
|
+
placed.add(key);
|
|
1282
1345
|
let item = existing.get(key);
|
|
1283
1346
|
if (!item) {
|
|
1284
1347
|
item = document.createElement("li");
|
|
1285
1348
|
item.className = "footnote";
|
|
1286
1349
|
item.setAttribute("data-footnote", key);
|
|
1287
|
-
item.textContent =
|
|
1350
|
+
item.textContent = PLACEHOLDER;
|
|
1288
1351
|
}
|
|
1289
1352
|
item.id = key;
|
|
1290
1353
|
existing.delete(key);
|
|
1291
1354
|
list.appendChild(item);
|
|
1292
|
-
}
|
|
1355
|
+
}
|
|
1293
1356
|
for (const orphan of existing.values())
|
|
1294
1357
|
orphan.remove();
|
|
1295
1358
|
}
|
|
@@ -1470,7 +1533,7 @@ var commands = {
|
|
|
1470
1533
|
item.className = "footnote";
|
|
1471
1534
|
item.setAttribute("data-footnote", key);
|
|
1472
1535
|
item.id = key;
|
|
1473
|
-
item.textContent = text.length ? text.join(" ") :
|
|
1536
|
+
item.textContent = text.length ? text.join(" ") : PLACEHOLDER;
|
|
1474
1537
|
list.appendChild(item);
|
|
1475
1538
|
renumberFootnotes(ctx.root);
|
|
1476
1539
|
ctx.normalize();
|
|
@@ -1708,6 +1771,8 @@ import { icons as icons2 } from "tosijs-ui/icons";
|
|
|
1708
1771
|
// src/changes.ts
|
|
1709
1772
|
var INS_TAG = "tosi-ins";
|
|
1710
1773
|
var DEL_TAG = "tosi-del";
|
|
1774
|
+
var BLOCK_DELETE_ATTR = "data-block-delete";
|
|
1775
|
+
var BLOCK_INSERT_ATTR = "data-block-insert";
|
|
1711
1776
|
var changeSeq = 0;
|
|
1712
1777
|
var changeId = () => `chg-${Date.now().toString(36)}-${(changeSeq++).toString(36)}`;
|
|
1713
1778
|
|
|
@@ -1727,14 +1792,20 @@ function defineChanges() {
|
|
|
1727
1792
|
function safeAttributeValue(value) {
|
|
1728
1793
|
return value.replace(/[<>]/g, "");
|
|
1729
1794
|
}
|
|
1730
|
-
function
|
|
1731
|
-
const
|
|
1732
|
-
el.setAttribute("data-change",
|
|
1733
|
-
el.setAttribute("data-author", safeAttributeValue(author.id));
|
|
1734
|
-
if (author.name) {
|
|
1735
|
-
el.setAttribute("data-author-name", safeAttributeValue(author.name));
|
|
1736
|
-
}
|
|
1795
|
+
function stampMark(el, attribution) {
|
|
1796
|
+
const id = attribution.id ?? changeId();
|
|
1797
|
+
el.setAttribute("data-change", id);
|
|
1798
|
+
el.setAttribute("data-author", safeAttributeValue(attribution.author.id));
|
|
1799
|
+
if (attribution.author.name) {
|
|
1800
|
+
el.setAttribute("data-author-name", safeAttributeValue(attribution.author.name));
|
|
1801
|
+
}
|
|
1802
|
+
el.setAttribute("data-session", safeAttributeValue(attribution.session));
|
|
1737
1803
|
el.setAttribute("data-time", new Date().toISOString());
|
|
1804
|
+
return id;
|
|
1805
|
+
}
|
|
1806
|
+
function mark(kind, text, author, session) {
|
|
1807
|
+
const el = document.createElement(kind === "insert" ? INS_TAG : DEL_TAG);
|
|
1808
|
+
stampMark(el, { author, session });
|
|
1738
1809
|
el.textContent = text;
|
|
1739
1810
|
return el;
|
|
1740
1811
|
}
|
|
@@ -1821,7 +1892,7 @@ function diffWords(before, after) {
|
|
|
1821
1892
|
push("insert", b[j++]);
|
|
1822
1893
|
return ops;
|
|
1823
1894
|
}
|
|
1824
|
-
function applyRevision(node, revised, author) {
|
|
1895
|
+
function applyRevision(node, revised, author, session) {
|
|
1825
1896
|
const ops = diffWords(node.data, revised);
|
|
1826
1897
|
if (!ops.some((o) => o.op !== "same"))
|
|
1827
1898
|
return 0;
|
|
@@ -1832,7 +1903,7 @@ function applyRevision(node, revised, author) {
|
|
|
1832
1903
|
if (op.op === "same") {
|
|
1833
1904
|
fragment.appendChild(document.createTextNode(op.text));
|
|
1834
1905
|
} else {
|
|
1835
|
-
fragment.appendChild(mark(op.op, op.text, author));
|
|
1906
|
+
fragment.appendChild(mark(op.op, op.text, author, session));
|
|
1836
1907
|
changes++;
|
|
1837
1908
|
}
|
|
1838
1909
|
}
|
|
@@ -2245,6 +2316,35 @@ function deletableFilter(node) {
|
|
|
2245
2316
|
}
|
|
2246
2317
|
return node.nodeType !== 3 || node.textContent !== "";
|
|
2247
2318
|
}
|
|
2319
|
+
var MERGEABLE_BLOCKS = new Set([
|
|
2320
|
+
"p",
|
|
2321
|
+
"div",
|
|
2322
|
+
"h1",
|
|
2323
|
+
"h2",
|
|
2324
|
+
"h3",
|
|
2325
|
+
"h4",
|
|
2326
|
+
"h5",
|
|
2327
|
+
"h6",
|
|
2328
|
+
"blockquote",
|
|
2329
|
+
"pre",
|
|
2330
|
+
"figure",
|
|
2331
|
+
"figcaption",
|
|
2332
|
+
"address",
|
|
2333
|
+
"section",
|
|
2334
|
+
"article",
|
|
2335
|
+
"aside",
|
|
2336
|
+
"main",
|
|
2337
|
+
"header",
|
|
2338
|
+
"footer"
|
|
2339
|
+
]);
|
|
2340
|
+
var AFFORDANCE_SIZE = 44;
|
|
2341
|
+
function menuAffordanceX(startX, endX, size, maxX) {
|
|
2342
|
+
const centred = (startX + endX) / 2 - size / 2;
|
|
2343
|
+
let x = Math.max(centred, startX);
|
|
2344
|
+
if (x + size > maxX)
|
|
2345
|
+
x = startX - size * 2;
|
|
2346
|
+
return Math.max(x, 0);
|
|
2347
|
+
}
|
|
2248
2348
|
|
|
2249
2349
|
class TosijsStyledEditor extends WebComponent {
|
|
2250
2350
|
static formAssociated = true;
|
|
@@ -2491,8 +2591,8 @@ class TosijsStyledEditor extends WebComponent {
|
|
|
2491
2591
|
},
|
|
2492
2592
|
":host .touch-affordance": {
|
|
2493
2593
|
position: "absolute",
|
|
2494
|
-
width:
|
|
2495
|
-
height:
|
|
2594
|
+
width: `${AFFORDANCE_SIZE}px`,
|
|
2595
|
+
height: `${AFFORDANCE_SIZE}px`,
|
|
2496
2596
|
display: "flex",
|
|
2497
2597
|
alignItems: "center",
|
|
2498
2598
|
justifyContent: "center",
|
|
@@ -2699,7 +2799,7 @@ class TosijsStyledEditor extends WebComponent {
|
|
|
2699
2799
|
const revised = await revise(text.data);
|
|
2700
2800
|
if (typeof revised !== "string")
|
|
2701
2801
|
continue;
|
|
2702
|
-
total += applyRevision(text, revised, author);
|
|
2802
|
+
total += applyRevision(text, revised, author, this.sessionId);
|
|
2703
2803
|
}
|
|
2704
2804
|
} finally {
|
|
2705
2805
|
if (total > 0) {
|
|
@@ -2710,6 +2810,16 @@ class TosijsStyledEditor extends WebComponent {
|
|
|
2710
2810
|
return total;
|
|
2711
2811
|
}
|
|
2712
2812
|
trackChanges = false;
|
|
2813
|
+
trackStructuralEdits = false;
|
|
2814
|
+
get stickySelection() {
|
|
2815
|
+
return this.selectable?.stickySelection ?? this.pendingStickySelection;
|
|
2816
|
+
}
|
|
2817
|
+
set stickySelection(mode) {
|
|
2818
|
+
this.pendingStickySelection = mode;
|
|
2819
|
+
if (this.selectable)
|
|
2820
|
+
this.selectable.stickySelection = mode;
|
|
2821
|
+
}
|
|
2822
|
+
pendingStickySelection = "touch";
|
|
2713
2823
|
sessionId = `s-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
2714
2824
|
currentInsertion() {
|
|
2715
2825
|
const ip = this.insertionPoint();
|
|
@@ -2740,13 +2850,7 @@ class TosijsStyledEditor extends WebComponent {
|
|
|
2740
2850
|
return null;
|
|
2741
2851
|
defineChanges();
|
|
2742
2852
|
const ins = document.createElement(INS_TAG);
|
|
2743
|
-
|
|
2744
|
-
ins.setAttribute("data-author", safeAttributeValue(this.changeAuthor.id));
|
|
2745
|
-
if (this.changeAuthor.name) {
|
|
2746
|
-
ins.setAttribute("data-author-name", safeAttributeValue(this.changeAuthor.name));
|
|
2747
|
-
}
|
|
2748
|
-
ins.setAttribute("data-session", this.sessionId);
|
|
2749
|
-
ins.setAttribute("data-time", new Date().toISOString());
|
|
2853
|
+
this.stamp(ins);
|
|
2750
2854
|
const enclosing = ip.closest(INS_TAG);
|
|
2751
2855
|
if (!enclosing?.parentNode) {
|
|
2752
2856
|
ip.before(ins);
|
|
@@ -2782,19 +2886,12 @@ class TosijsStyledEditor extends WebComponent {
|
|
|
2782
2886
|
const asBlock = node.nodeType === 1 && node.parentNode === this.parts.doc ? node : null;
|
|
2783
2887
|
if (asBlock && !asBlock.firstChild)
|
|
2784
2888
|
continue;
|
|
2785
|
-
if (asBlock
|
|
2889
|
+
if (asBlock && this.isFullyStruck(asBlock))
|
|
2786
2890
|
continue;
|
|
2787
|
-
}
|
|
2788
2891
|
const del = document.createElement(DEL_TAG);
|
|
2789
|
-
|
|
2790
|
-
del.setAttribute("data-author", safeAttributeValue(this.changeAuthor.id));
|
|
2791
|
-
if (this.changeAuthor.name) {
|
|
2792
|
-
del.setAttribute("data-author-name", safeAttributeValue(this.changeAuthor.name));
|
|
2793
|
-
}
|
|
2794
|
-
del.setAttribute("data-session", this.sessionId);
|
|
2795
|
-
del.setAttribute("data-time", new Date().toISOString());
|
|
2892
|
+
this.stamp(del, id);
|
|
2796
2893
|
if (asBlock) {
|
|
2797
|
-
del.setAttribute(
|
|
2894
|
+
del.setAttribute(BLOCK_DELETE_ATTR, "");
|
|
2798
2895
|
while (asBlock.firstChild)
|
|
2799
2896
|
del.appendChild(asBlock.firstChild);
|
|
2800
2897
|
asBlock.appendChild(del);
|
|
@@ -2838,15 +2935,78 @@ class TosijsStyledEditor extends WebComponent {
|
|
|
2838
2935
|
get refusesStructuralDelete() {
|
|
2839
2936
|
return this.trackChanges;
|
|
2840
2937
|
}
|
|
2841
|
-
|
|
2842
|
-
const
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2938
|
+
rescueCaretFrom(block) {
|
|
2939
|
+
const markers = Array.from(block.querySelectorAll(".sel-start, .sel-end, .caret"));
|
|
2940
|
+
if (!markers.length)
|
|
2941
|
+
return;
|
|
2942
|
+
let top = block;
|
|
2943
|
+
while (top.parentElement && top.parentElement !== this.parts.doc) {
|
|
2944
|
+
top = top.parentElement;
|
|
2945
|
+
}
|
|
2946
|
+
const host = this.siblingBlock(top, "back") ?? this.siblingBlock(top, "forward");
|
|
2947
|
+
for (const marker of markers) {
|
|
2948
|
+
if (host)
|
|
2949
|
+
host.appendChild(marker);
|
|
2950
|
+
else
|
|
2951
|
+
this.parts.doc.appendChild(marker);
|
|
2848
2952
|
}
|
|
2849
2953
|
}
|
|
2954
|
+
isFullyStruck(block) {
|
|
2955
|
+
const probe = document.createElement("div");
|
|
2956
|
+
for (const child of Array.from(block.childNodes)) {
|
|
2957
|
+
probe.appendChild(child.cloneNode(true));
|
|
2958
|
+
}
|
|
2959
|
+
for (const el of Array.from(probe.querySelectorAll(DEL_TAG)))
|
|
2960
|
+
el.remove();
|
|
2961
|
+
for (const el of Array.from(probe.querySelectorAll(".sel-start, .sel-end, .caret"))) {
|
|
2962
|
+
el.remove();
|
|
2963
|
+
}
|
|
2964
|
+
return blockIsEmpty(probe);
|
|
2965
|
+
}
|
|
2966
|
+
resolvedClone(block) {
|
|
2967
|
+
const frag = document.createDocumentFragment();
|
|
2968
|
+
for (const child of Array.from(block.childNodes)) {
|
|
2969
|
+
frag.appendChild(child.cloneNode(true));
|
|
2970
|
+
}
|
|
2971
|
+
for (const el of Array.from(frag.querySelectorAll(".sel-start, .sel-end, .caret"))) {
|
|
2972
|
+
el.remove();
|
|
2973
|
+
}
|
|
2974
|
+
for (const del of Array.from(frag.querySelectorAll(DEL_TAG)))
|
|
2975
|
+
del.remove();
|
|
2976
|
+
for (const ins of Array.from(frag.querySelectorAll(INS_TAG)))
|
|
2977
|
+
unwrap(ins);
|
|
2978
|
+
return frag;
|
|
2979
|
+
}
|
|
2980
|
+
blockLike(model) {
|
|
2981
|
+
const block = model.cloneNode(false);
|
|
2982
|
+
block.classList.remove("selected-block", "first-block", "last-block", "selected");
|
|
2983
|
+
if (!block.getAttribute("class"))
|
|
2984
|
+
block.removeAttribute("class");
|
|
2985
|
+
return block;
|
|
2986
|
+
}
|
|
2987
|
+
canMergeBlocks(blocks) {
|
|
2988
|
+
return blocks.every((b) => !!b && MERGEABLE_BLOCKS.has(b.localName) && !b.classList.contains("editor-table"));
|
|
2989
|
+
}
|
|
2990
|
+
trackStructuralEdit(blocksOut, gestureId) {
|
|
2991
|
+
defineChanges();
|
|
2992
|
+
const id = gestureId ?? changeId();
|
|
2993
|
+
const ins = document.createElement(INS_TAG);
|
|
2994
|
+
this.stamp(ins, id);
|
|
2995
|
+
ins.setAttribute(BLOCK_INSERT_ATTR, "");
|
|
2996
|
+
const caret = this.selectable.createBounds();
|
|
2997
|
+
const parts = blocksOut.map((b) => this.resolvedClone(b));
|
|
2998
|
+
const merged = this.blockLike(blocksOut[0]);
|
|
2999
|
+
parts.forEach((frag, i) => {
|
|
3000
|
+
if (i === 1)
|
|
3001
|
+
ins.appendChild(caret);
|
|
3002
|
+
ins.appendChild(frag);
|
|
3003
|
+
});
|
|
3004
|
+
merged.appendChild(ins);
|
|
3005
|
+
this.selectable.removeBounds();
|
|
3006
|
+
this.trackDeletion(blocksOut, id);
|
|
3007
|
+
blocksOut[blocksOut.length - 1].after(merged);
|
|
3008
|
+
return { ins, caret };
|
|
3009
|
+
}
|
|
2850
3010
|
refuseStructural(reason) {
|
|
2851
3011
|
const evt = new CustomEvent("structural-edit-refused", {
|
|
2852
3012
|
bubbles: true,
|
|
@@ -2856,6 +3016,13 @@ class TosijsStyledEditor extends WebComponent {
|
|
|
2856
3016
|
});
|
|
2857
3017
|
return this.dispatchEvent(evt);
|
|
2858
3018
|
}
|
|
3019
|
+
stamp(el, id) {
|
|
3020
|
+
return stampMark(el, {
|
|
3021
|
+
id,
|
|
3022
|
+
author: this.changeAuthor,
|
|
3023
|
+
session: this.sessionId
|
|
3024
|
+
});
|
|
3025
|
+
}
|
|
2859
3026
|
get changes() {
|
|
2860
3027
|
return changesIn(this.parts.doc);
|
|
2861
3028
|
}
|
|
@@ -2863,7 +3030,7 @@ class TosijsStyledEditor extends WebComponent {
|
|
|
2863
3030
|
if (id === "")
|
|
2864
3031
|
return;
|
|
2865
3032
|
const targets = this.changes.filter((c) => !id || c.id === id);
|
|
2866
|
-
const emptied =
|
|
3033
|
+
const emptied = this.blocksOwnedByMarks(targets, "delete", BLOCK_DELETE_ATTR);
|
|
2867
3034
|
for (const change of targets)
|
|
2868
3035
|
acceptChange(change.element);
|
|
2869
3036
|
for (const block of emptied) {
|
|
@@ -2876,12 +3043,31 @@ class TosijsStyledEditor extends WebComponent {
|
|
|
2876
3043
|
this.updateUndo("new", "accept-changes");
|
|
2877
3044
|
}
|
|
2878
3045
|
}
|
|
3046
|
+
blocksOwnedByMarks(targets, kind, attr) {
|
|
3047
|
+
const blocks = targets.filter((c) => c.kind === kind && c.element.hasAttribute(attr)).map((c) => this.block(c.element)).filter((el) => el instanceof HTMLElement);
|
|
3048
|
+
return [...new Set(blocks)];
|
|
3049
|
+
}
|
|
2879
3050
|
rejectChanges(id) {
|
|
2880
3051
|
if (id === "")
|
|
2881
3052
|
return;
|
|
2882
3053
|
const targets = this.changes.filter((c) => !id || c.id === id);
|
|
3054
|
+
const proposed = this.blocksOwnedByMarks(targets, "insert", BLOCK_INSERT_ATTR);
|
|
3055
|
+
for (const change of targets) {
|
|
3056
|
+
if (change.kind !== "insert")
|
|
3057
|
+
continue;
|
|
3058
|
+
const mark = change.element;
|
|
3059
|
+
for (const marker of Array.from(mark.querySelectorAll(".sel-start, .sel-end, .caret"))) {
|
|
3060
|
+
mark.parentNode?.insertBefore(marker, mark);
|
|
3061
|
+
}
|
|
3062
|
+
}
|
|
2883
3063
|
for (const change of targets)
|
|
2884
3064
|
rejectChange(change.element);
|
|
3065
|
+
for (const block of proposed) {
|
|
3066
|
+
if (!block.isConnected || !blockIsEmpty(block))
|
|
3067
|
+
continue;
|
|
3068
|
+
this.rescueCaretFrom(block);
|
|
3069
|
+
block.remove();
|
|
3070
|
+
}
|
|
2885
3071
|
if (targets.length) {
|
|
2886
3072
|
this.normalize();
|
|
2887
3073
|
this.updateUndo("new", "reject-changes");
|
|
@@ -2987,6 +3173,7 @@ class TosijsStyledEditor extends WebComponent {
|
|
|
2987
3173
|
}
|
|
2988
3174
|
this.isInitialized = true;
|
|
2989
3175
|
this.selectable = new Selectable(doc);
|
|
3176
|
+
this.selectable.stickySelection = this.pendingStickySelection;
|
|
2990
3177
|
const firstP = doc.querySelector("p,h1,h2,h3,h4,h5,h6,div,blockquote,pre") || doc.firstElementChild;
|
|
2991
3178
|
if (firstP) {
|
|
2992
3179
|
firstP.appendChild(this.selectable.createBounds());
|
|
@@ -3627,6 +3814,42 @@ class TosijsStyledEditor extends WebComponent {
|
|
|
3627
3814
|
isolate.appendChild(ip);
|
|
3628
3815
|
return isolate;
|
|
3629
3816
|
}
|
|
3817
|
+
refuseIfUnmergeable(a, b) {
|
|
3818
|
+
if (!a || !b || a === b)
|
|
3819
|
+
return false;
|
|
3820
|
+
if (this.canMergeBlocks([a, b]))
|
|
3821
|
+
return false;
|
|
3822
|
+
this.refuseMerge();
|
|
3823
|
+
return true;
|
|
3824
|
+
}
|
|
3825
|
+
refuseMerge() {
|
|
3826
|
+
this.refuseStructural("merge-blocks-not-mergeable");
|
|
3827
|
+
}
|
|
3828
|
+
mergeBlocksRaw(earlier, later) {
|
|
3829
|
+
while (later.firstChild)
|
|
3830
|
+
earlier.appendChild(later.firstChild);
|
|
3831
|
+
later.remove();
|
|
3832
|
+
}
|
|
3833
|
+
mayTrackStructurally(reason) {
|
|
3834
|
+
if (this.trackStructuralEdits)
|
|
3835
|
+
return true;
|
|
3836
|
+
this.refuseStructural(reason);
|
|
3837
|
+
return false;
|
|
3838
|
+
}
|
|
3839
|
+
mergeBlocksTracked(earlier, later) {
|
|
3840
|
+
this.trackStructuralEdit([earlier, later]);
|
|
3841
|
+
this.normalize();
|
|
3842
|
+
this.focus();
|
|
3843
|
+
this.updateUndo("new", "merge-blocks");
|
|
3844
|
+
}
|
|
3845
|
+
siblingBlock(block, direction) {
|
|
3846
|
+
const step = (el) => direction === "back" ? el.previousElementSibling : el.nextElementSibling;
|
|
3847
|
+
let el = step(block);
|
|
3848
|
+
while (el && (el.classList.contains("not-selectable") || el.classList.contains("do-not-spanify"))) {
|
|
3849
|
+
el = step(el);
|
|
3850
|
+
}
|
|
3851
|
+
return el;
|
|
3852
|
+
}
|
|
3630
3853
|
backspace() {
|
|
3631
3854
|
if (!this.deleteSelection()) {
|
|
3632
3855
|
const ip = this.insertionPoint();
|
|
@@ -3638,25 +3861,22 @@ class TosijsStyledEditor extends WebComponent {
|
|
|
3638
3861
|
const caretBlock = this.block(ip);
|
|
3639
3862
|
const deletionBlock = this.block(node);
|
|
3640
3863
|
const crossesBlocks = !!(deletionBlock && caretBlock && deletionBlock !== caretBlock);
|
|
3641
|
-
if (this.
|
|
3864
|
+
if (this.refuseIfUnmergeable(deletionBlock, caretBlock))
|
|
3865
|
+
return;
|
|
3866
|
+
if (this.trackChanges && crossesBlocks) {
|
|
3867
|
+
if (!this.mayTrackStructurally("merge-blocks-backward"))
|
|
3868
|
+
return;
|
|
3869
|
+
this.mergeBlocksTracked(deletionBlock, caretBlock);
|
|
3642
3870
|
return;
|
|
3643
3871
|
}
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
this.normalize();
|
|
3653
|
-
if (deletionBlock && caretBlock && this.parts.doc.contains(deletionBlock) && deletionBlock !== caretBlock) {
|
|
3654
|
-
while (caretBlock.firstChild) {
|
|
3655
|
-
deletionBlock.appendChild(caretBlock.firstChild);
|
|
3656
|
-
}
|
|
3657
|
-
caretBlock.remove();
|
|
3658
|
-
}
|
|
3659
|
-
});
|
|
3872
|
+
if (crossesBlocks) {
|
|
3873
|
+
this.mergeBlocksRaw(deletionBlock, caretBlock);
|
|
3874
|
+
} else if (node.nodeType === 3 && (node.textContent || "").length > 1) {
|
|
3875
|
+
this.deleteEdgeCharacter(node, "end");
|
|
3876
|
+
} else {
|
|
3877
|
+
this.removeNode(topSingleParentAncestor(node));
|
|
3878
|
+
}
|
|
3879
|
+
this.normalize();
|
|
3660
3880
|
this.updateUndo();
|
|
3661
3881
|
}
|
|
3662
3882
|
}
|
|
@@ -3665,42 +3885,66 @@ class TosijsStyledEditor extends WebComponent {
|
|
|
3665
3885
|
const ip = this.insertionPoint();
|
|
3666
3886
|
if (!ip)
|
|
3667
3887
|
return;
|
|
3888
|
+
const emptyBlock = this.block(ip);
|
|
3889
|
+
const intoPrevious = emptyBlock && blockIsEmpty(emptyBlock) ? this.siblingBlock(emptyBlock, "back") : null;
|
|
3890
|
+
if (emptyBlock && intoPrevious) {
|
|
3891
|
+
if (this.refuseIfUnmergeable(intoPrevious, emptyBlock))
|
|
3892
|
+
return;
|
|
3893
|
+
if (this.trackChanges) {
|
|
3894
|
+
if (!this.mayTrackStructurally("merge-blocks-forward"))
|
|
3895
|
+
return;
|
|
3896
|
+
this.mergeBlocksTracked(intoPrevious, emptyBlock);
|
|
3897
|
+
} else {
|
|
3898
|
+
this.mergeBlocksRaw(intoPrevious, emptyBlock);
|
|
3899
|
+
this.normalize();
|
|
3900
|
+
this.updateUndo();
|
|
3901
|
+
}
|
|
3902
|
+
return;
|
|
3903
|
+
}
|
|
3668
3904
|
const node = this.deletionTarget(ip, this.parts.doc, "forward");
|
|
3669
3905
|
if (!node)
|
|
3670
3906
|
return;
|
|
3671
3907
|
const caretBlock = this.block(ip);
|
|
3672
3908
|
const deletionBlock = this.block(node);
|
|
3909
|
+
if (this.refuseIfUnmergeable(caretBlock, deletionBlock))
|
|
3910
|
+
return;
|
|
3673
3911
|
const crossesBlocks = !!(deletionBlock && caretBlock && deletionBlock !== caretBlock);
|
|
3674
|
-
if (this.
|
|
3912
|
+
if (this.trackChanges && crossesBlocks) {
|
|
3913
|
+
if (!this.mayTrackStructurally("merge-blocks-forward"))
|
|
3914
|
+
return;
|
|
3915
|
+
this.mergeBlocksTracked(caretBlock, deletionBlock);
|
|
3675
3916
|
return;
|
|
3676
3917
|
}
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
this.normalize();
|
|
3686
|
-
if (deletionBlock && caretBlock && this.parts.doc.contains(deletionBlock) && deletionBlock !== caretBlock) {
|
|
3687
|
-
while (deletionBlock.firstChild) {
|
|
3688
|
-
caretBlock.appendChild(deletionBlock.firstChild);
|
|
3689
|
-
}
|
|
3690
|
-
deletionBlock.remove();
|
|
3691
|
-
}
|
|
3692
|
-
});
|
|
3918
|
+
if (crossesBlocks) {
|
|
3919
|
+
this.mergeBlocksRaw(caretBlock, deletionBlock);
|
|
3920
|
+
} else if (node.nodeType === 3 && (node.textContent || "").length > 1) {
|
|
3921
|
+
this.deleteEdgeCharacter(node, "start");
|
|
3922
|
+
} else {
|
|
3923
|
+
this.removeNode(topSingleParentAncestor(node));
|
|
3924
|
+
}
|
|
3925
|
+
this.normalize();
|
|
3693
3926
|
this.updateUndo();
|
|
3694
3927
|
}
|
|
3695
3928
|
}
|
|
3696
3929
|
deleteSelection() {
|
|
3697
3930
|
const blocks = this.selectedBlocks();
|
|
3698
3931
|
const crossesBlocks = blocks.length > 1;
|
|
3699
|
-
const
|
|
3700
|
-
const
|
|
3701
|
-
|
|
3932
|
+
const ends = crossesBlocks ? [blocks[0], blocks[blocks.length - 1]] : null;
|
|
3933
|
+
const unmergeable = !!ends && !this.canMergeBlocks(ends);
|
|
3934
|
+
if (unmergeable)
|
|
3935
|
+
this.refuseMerge();
|
|
3936
|
+
if (!this.trackChanges || !crossesBlocks) {
|
|
3937
|
+
return this.runDeleteSelection(blocks, "raw");
|
|
3938
|
+
}
|
|
3939
|
+
if (unmergeable) {
|
|
3940
|
+
return this.runDeleteSelection(blocks, "none");
|
|
3941
|
+
}
|
|
3942
|
+
if (!this.mayTrackStructurally("merge-blocks-selection")) {
|
|
3943
|
+
return this.runDeleteSelection(blocks, "none");
|
|
3944
|
+
}
|
|
3945
|
+
return this.runDeleteSelection(blocks, "tracked");
|
|
3702
3946
|
}
|
|
3703
|
-
runDeleteSelection(blocks,
|
|
3947
|
+
runDeleteSelection(blocks, merge) {
|
|
3704
3948
|
let wasAnythingDeleted = false;
|
|
3705
3949
|
const gesture = this.trackChanges ? changeId() : undefined;
|
|
3706
3950
|
for (const block of blocks) {
|
|
@@ -3735,12 +3979,11 @@ class TosijsStyledEditor extends WebComponent {
|
|
|
3735
3979
|
if (blocks.length > 1) {
|
|
3736
3980
|
const firstBlock = blocks[0];
|
|
3737
3981
|
const lastBlock = blocks[blocks.length - 1];
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
firstBlock.remove();
|
|
3982
|
+
const mergeable = this.parts.doc.contains(firstBlock) && this.parts.doc.contains(lastBlock) && this.canMergeBlocks([firstBlock, lastBlock]);
|
|
3983
|
+
if (mergeable && merge === "tracked") {
|
|
3984
|
+
this.trackStructuralEdit([firstBlock, lastBlock], gesture);
|
|
3985
|
+
} else if (mergeable && merge === "raw") {
|
|
3986
|
+
this.mergeBlocksRaw(firstBlock, lastBlock);
|
|
3744
3987
|
}
|
|
3745
3988
|
this.selectable.markBounds();
|
|
3746
3989
|
this.focus();
|
|
@@ -4266,16 +4509,11 @@ class TosijsStyledEditor extends WebComponent {
|
|
|
4266
4509
|
return;
|
|
4267
4510
|
const id = changeId();
|
|
4268
4511
|
for (const mark of Array.from(marks)) {
|
|
4269
|
-
|
|
4270
|
-
mark.setAttribute("data-author", safeAttributeValue(this.changeAuthor.id));
|
|
4271
|
-
if (this.changeAuthor.name) {
|
|
4272
|
-
mark.setAttribute("data-author-name", safeAttributeValue(this.changeAuthor.name));
|
|
4273
|
-
} else {
|
|
4512
|
+
if (!this.changeAuthor.name)
|
|
4274
4513
|
mark.removeAttribute("data-author-name");
|
|
4275
|
-
|
|
4276
|
-
mark.
|
|
4277
|
-
mark.
|
|
4278
|
-
mark.removeAttribute("data-block-delete");
|
|
4514
|
+
this.stamp(mark, id);
|
|
4515
|
+
mark.removeAttribute(BLOCK_DELETE_ATTR);
|
|
4516
|
+
mark.removeAttribute(BLOCK_INSERT_ATTR);
|
|
4279
4517
|
}
|
|
4280
4518
|
}
|
|
4281
4519
|
handlePaste = (evt) => {
|
|
@@ -4440,24 +4678,14 @@ class TosijsStyledEditor extends WebComponent {
|
|
|
4440
4678
|
const startY = startRect.top - docRect.top + this.parts.doc.scrollTop;
|
|
4441
4679
|
const endX = endRect.right - docRect.left + this.parts.doc.scrollLeft;
|
|
4442
4680
|
const endY = endRect.bottom - docRect.top + this.parts.doc.scrollTop;
|
|
4443
|
-
const
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
} else {
|
|
4452
|
-
this.touchHandleStart.style.left = `${startX - 44}px`;
|
|
4453
|
-
this.touchHandleStart.style.top = `${startY - 44}px`;
|
|
4454
|
-
this.touchHandleEnd.style.left = `${endX}px`;
|
|
4455
|
-
this.touchHandleEnd.style.top = `${endY}px`;
|
|
4456
|
-
const midX = (startX + endX) / 2 - 22;
|
|
4457
|
-
const topY = Math.min(startY, endY) - 44;
|
|
4458
|
-
this.touchContextMenu.style.left = `${midX}px`;
|
|
4459
|
-
this.touchContextMenu.style.top = `${topY}px`;
|
|
4460
|
-
}
|
|
4681
|
+
const size = AFFORDANCE_SIZE;
|
|
4682
|
+
this.touchHandleStart.style.left = `${startX - size}px`;
|
|
4683
|
+
this.touchHandleStart.style.top = `${startY - size}px`;
|
|
4684
|
+
this.touchHandleEnd.style.left = `${endX}px`;
|
|
4685
|
+
this.touchHandleEnd.style.top = `${endY}px`;
|
|
4686
|
+
const maxX = this.parts.doc.clientWidth + this.parts.doc.scrollLeft;
|
|
4687
|
+
this.touchContextMenu.style.left = `${menuAffordanceX(startX, endX, size, maxX)}px`;
|
|
4688
|
+
this.touchContextMenu.style.top = `${Math.min(startY, endY) - size}px`;
|
|
4461
4689
|
}
|
|
4462
4690
|
handleAffordanceDragStart = (evt) => {
|
|
4463
4691
|
const handle = evt.currentTarget;
|
|
@@ -4482,6 +4710,7 @@ class TosijsStyledEditor extends WebComponent {
|
|
|
4482
4710
|
offsetX,
|
|
4483
4711
|
offsetY
|
|
4484
4712
|
});
|
|
4713
|
+
this.selectable.beginBoundDrag(which);
|
|
4485
4714
|
this.hideTouchMenu();
|
|
4486
4715
|
this.touchAffordances.classList.add("dragging");
|
|
4487
4716
|
evt.preventDefault();
|
|
@@ -4502,25 +4731,14 @@ class TosijsStyledEditor extends WebComponent {
|
|
|
4502
4731
|
if (!elementAtPoint || elementAtPoint.closest(".not-selectable") || elementAtPoint.classList.contains("not-selectable")) {
|
|
4503
4732
|
return;
|
|
4504
4733
|
}
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
const
|
|
4509
|
-
const
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
range.collapse(true);
|
|
4514
|
-
range.insertNode(marker);
|
|
4515
|
-
this.parts.doc.normalize();
|
|
4516
|
-
this.selectable.markBounds();
|
|
4517
|
-
const docRect = this.parts.doc.getBoundingClientRect();
|
|
4518
|
-
const handle = evt.currentTarget;
|
|
4519
|
-
const handleX = evt.clientX - docRect.left + this.parts.doc.scrollLeft;
|
|
4520
|
-
const handleY = evt.clientY - docRect.top + this.parts.doc.scrollTop;
|
|
4521
|
-
handle.style.left = `${handleX - 22}px`;
|
|
4522
|
-
handle.style.top = `${handleY - 22}px`;
|
|
4523
|
-
}
|
|
4734
|
+
this.selectable.extendTo(cursorX, cursorY, drag.target);
|
|
4735
|
+
this.selectable.markBounds();
|
|
4736
|
+
const docRect = this.parts.doc.getBoundingClientRect();
|
|
4737
|
+
const handle = evt.currentTarget;
|
|
4738
|
+
const handleX = evt.clientX - docRect.left + this.parts.doc.scrollLeft;
|
|
4739
|
+
const handleY = evt.clientY - docRect.top + this.parts.doc.scrollTop;
|
|
4740
|
+
handle.style.left = `${handleX - AFFORDANCE_SIZE / 2}px`;
|
|
4741
|
+
handle.style.top = `${handleY - AFFORDANCE_SIZE / 2}px`;
|
|
4524
4742
|
evt.preventDefault();
|
|
4525
4743
|
evt.stopPropagation();
|
|
4526
4744
|
};
|
|
@@ -4531,6 +4749,7 @@ class TosijsStyledEditor extends WebComponent {
|
|
|
4531
4749
|
handle.releasePointerCapture(evt.pointerId);
|
|
4532
4750
|
this.touchDrags.delete(evt.pointerId);
|
|
4533
4751
|
if (this.touchDrags.size === 0) {
|
|
4752
|
+
this.selectable.endBoundDrag();
|
|
4534
4753
|
this.selectable.despanify();
|
|
4535
4754
|
this.selectable.selectionChanged();
|
|
4536
4755
|
requestAnimationFrame(() => {
|
|
@@ -4734,8 +4953,11 @@ class TosijsStyledEditor extends WebComponent {
|
|
|
4734
4953
|
defineFootnote();
|
|
4735
4954
|
var tosijsStyledEditor = TosijsStyledEditor.elementCreator();
|
|
4736
4955
|
// src/version.ts
|
|
4737
|
-
var version = "0.
|
|
4956
|
+
var version = "0.6.0";
|
|
4738
4957
|
export {
|
|
4958
|
+
AFFORDANCE_SIZE,
|
|
4959
|
+
BLOCK_DELETE_ATTR,
|
|
4960
|
+
BLOCK_INSERT_ATTR,
|
|
4739
4961
|
DEL_TAG,
|
|
4740
4962
|
FOOTNOTE_TAG,
|
|
4741
4963
|
INS_TAG,
|
|
@@ -4799,6 +5021,7 @@ export {
|
|
|
4799
5021
|
localePickerWidget,
|
|
4800
5022
|
makeCSS,
|
|
4801
5023
|
makeFilter,
|
|
5024
|
+
menuAffordanceX,
|
|
4802
5025
|
minimalToolbar,
|
|
4803
5026
|
nextCell,
|
|
4804
5027
|
nextLeafNode,
|
|
@@ -4815,6 +5038,7 @@ export {
|
|
|
4815
5038
|
setColumnWidths,
|
|
4816
5039
|
siblingOrder,
|
|
4817
5040
|
spanify,
|
|
5041
|
+
stampMark,
|
|
4818
5042
|
stickySelectionBounds,
|
|
4819
5043
|
tableMenu,
|
|
4820
5044
|
tableOf,
|
|
@@ -4823,6 +5047,7 @@ export {
|
|
|
4823
5047
|
topSingleParentAncestor,
|
|
4824
5048
|
tosijsStyledEditor,
|
|
4825
5049
|
undoRedoWidgets,
|
|
5050
|
+
unwrap,
|
|
4826
5051
|
version,
|
|
4827
5052
|
wordsIn
|
|
4828
5053
|
};
|