@inditextech/weave-sdk 5.7.0-SNAPSHOT.597.1 → 5.7.0-SNAPSHOT.600.1
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/dist/sdk.js +1314 -130
- package/dist/sdk.node.js +1314 -130
- package/dist/sdk.node.stats.html +1 -1
- package/dist/sdk.stats.html +1 -1
- package/dist/types.d.ts +292 -33
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1314 -130
- package/dist/types.js.map +1 -1
- package/dist/types.stats.html +1 -1
- package/package.json +2 -2
package/dist/sdk.node.js
CHANGED
|
@@ -4774,12 +4774,39 @@ function registerKeyboardHandlers(ctx) {
|
|
|
4774
4774
|
}
|
|
4775
4775
|
}
|
|
4776
4776
|
if (e.code === "Backspace" || e.code === "Delete") {
|
|
4777
|
-
|
|
4777
|
+
queueMicrotask(() => {
|
|
4778
4778
|
ctx.removeSelectedNodes();
|
|
4779
4779
|
});
|
|
4780
4780
|
return;
|
|
4781
4781
|
}
|
|
4782
4782
|
}, { signal });
|
|
4783
|
+
const isModifier = (e) => e.key === "Control" || e.key === "Meta";
|
|
4784
|
+
const hideHover = () => ctx.getTransformerController().getHoverTransformer().nodes([]);
|
|
4785
|
+
const restoreHover = () => {
|
|
4786
|
+
const pos = stage.getPointerPosition();
|
|
4787
|
+
const shape = pos ? stage.getIntersection(pos) : null;
|
|
4788
|
+
if (!shape) return;
|
|
4789
|
+
shape.fire("pointerover", {
|
|
4790
|
+
evt: {
|
|
4791
|
+
ctrlKey: false,
|
|
4792
|
+
metaKey: false
|
|
4793
|
+
},
|
|
4794
|
+
target: shape
|
|
4795
|
+
}, true);
|
|
4796
|
+
};
|
|
4797
|
+
window.addEventListener("keydown", (e) => {
|
|
4798
|
+
if (isModifier(e)) hideHover();
|
|
4799
|
+
}, {
|
|
4800
|
+
signal,
|
|
4801
|
+
capture: true
|
|
4802
|
+
});
|
|
4803
|
+
window.addEventListener("keyup", (e) => {
|
|
4804
|
+
if (isModifier(e)) restoreHover();
|
|
4805
|
+
}, {
|
|
4806
|
+
signal,
|
|
4807
|
+
capture: true
|
|
4808
|
+
});
|
|
4809
|
+
window.addEventListener("blur", restoreHover, { signal });
|
|
4783
4810
|
stage.container().addEventListener("keyup", (e) => {
|
|
4784
4811
|
if (e.code === "Space") ctx.setSpaceKeyPressed(false);
|
|
4785
4812
|
}, { signal });
|
|
@@ -6535,7 +6562,10 @@ var WeaveNode = class {
|
|
|
6535
6562
|
}
|
|
6536
6563
|
handleMouseOver(e, node) {
|
|
6537
6564
|
const stage = this.instance.getStage();
|
|
6538
|
-
if (e.evt.ctrlKey || e.evt.metaKey)
|
|
6565
|
+
if (e.evt.ctrlKey || e.evt.metaKey) {
|
|
6566
|
+
this.hideHoverState();
|
|
6567
|
+
return false;
|
|
6568
|
+
}
|
|
6539
6569
|
const user = this.instance.getStore().getUser();
|
|
6540
6570
|
const activeAction = this.instance.getActiveAction();
|
|
6541
6571
|
const isNodeSelectionEnabled = this.getSelectionPlugin()?.isEnabled();
|
|
@@ -6582,7 +6612,10 @@ var WeaveNode = class {
|
|
|
6582
6612
|
return cancelBubble;
|
|
6583
6613
|
}
|
|
6584
6614
|
handleMouseout(e, node) {
|
|
6585
|
-
if (e.evt?.ctrlKey || e.evt?.metaKey)
|
|
6615
|
+
if (e.evt?.ctrlKey || e.evt?.metaKey) {
|
|
6616
|
+
this.hideHoverState();
|
|
6617
|
+
return false;
|
|
6618
|
+
}
|
|
6586
6619
|
if (this.instance.getInstanceRecursive(node)) this.hideHoverState();
|
|
6587
6620
|
}
|
|
6588
6621
|
create(key, props) {
|
|
@@ -9505,7 +9538,7 @@ var WeaveRegisterManager = class {
|
|
|
9505
9538
|
};
|
|
9506
9539
|
//#endregion
|
|
9507
9540
|
//#region package.json
|
|
9508
|
-
var version = "5.7.0-SNAPSHOT.
|
|
9541
|
+
var version = "5.7.0-SNAPSHOT.600.1";
|
|
9509
9542
|
//#endregion
|
|
9510
9543
|
//#region src/managers/setup.ts
|
|
9511
9544
|
var WeaveSetupManager = class {
|
|
@@ -13764,7 +13797,7 @@ var WeaveTextNode = class extends WeaveNode {
|
|
|
13764
13797
|
delete cleanedAttrs.dragBoundFunc;
|
|
13765
13798
|
if (cleanedAttrs.link) {
|
|
13766
13799
|
if (typeof cleanedAttrs.linkPreviousFill === "string") cleanedAttrs.fill = cleanedAttrs.linkPreviousFill;
|
|
13767
|
-
|
|
13800
|
+
cleanedAttrs.textDecoration = typeof cleanedAttrs.linkPreviousTextDecoration === "string" ? cleanedAttrs.linkPreviousTextDecoration : "";
|
|
13768
13801
|
}
|
|
13769
13802
|
return {
|
|
13770
13803
|
key: attrs.id ?? "",
|
|
@@ -14400,6 +14433,31 @@ var WeaveTextNode = class extends WeaveNode {
|
|
|
14400
14433
|
};
|
|
14401
14434
|
//#endregion
|
|
14402
14435
|
//#region src/nodes/text-v2/model.ts
|
|
14436
|
+
const MIN_LINE_HEIGHT = .5;
|
|
14437
|
+
/** Valid `lineHeight` mark of a run, or undefined (use the node value). */
|
|
14438
|
+
function runLineHeight(m) {
|
|
14439
|
+
const v = m?.lineHeight;
|
|
14440
|
+
return typeof v === "number" && Number.isFinite(v) && v > 0 ? Math.min(4, Math.max(MIN_LINE_HEIGHT, v)) : void 0;
|
|
14441
|
+
}
|
|
14442
|
+
/** Only absolute http(s) URLs are links (same rule as the legacy node). */
|
|
14443
|
+
function isSafeLink(value) {
|
|
14444
|
+
if (typeof value !== "string") return false;
|
|
14445
|
+
try {
|
|
14446
|
+
return ["http:", "https:"].includes(new URL(value).protocol);
|
|
14447
|
+
} catch {
|
|
14448
|
+
return false;
|
|
14449
|
+
}
|
|
14450
|
+
}
|
|
14451
|
+
/** Block fields that describe structure (not content). */
|
|
14452
|
+
function blockFields(block, withStart) {
|
|
14453
|
+
const out = {};
|
|
14454
|
+
if (!block) return out;
|
|
14455
|
+
if (block.kind) out.kind = block.kind;
|
|
14456
|
+
if (block.depth) out.depth = block.depth;
|
|
14457
|
+
if (block.align) out.align = block.align;
|
|
14458
|
+
if (withStart && block.start !== void 0) out.start = block.start;
|
|
14459
|
+
return out;
|
|
14460
|
+
}
|
|
14403
14461
|
function isEmptyMarks(m) {
|
|
14404
14462
|
return !m || Object.keys(m).length === 0;
|
|
14405
14463
|
}
|
|
@@ -14480,7 +14538,7 @@ function marksAt(runs, offset) {
|
|
|
14480
14538
|
* blocks) with plain `insert`. Marks on untouched text are preserved; the
|
|
14481
14539
|
* inserted text inherits marks of the character before `start`.
|
|
14482
14540
|
*/
|
|
14483
|
-
function replaceRange(blocks, start, end, insert) {
|
|
14541
|
+
function replaceRange(blocks, start, end, insert, insertMarks) {
|
|
14484
14542
|
const total = blocksToText(blocks).length;
|
|
14485
14543
|
start = Math.max(0, Math.min(start, total));
|
|
14486
14544
|
end = Math.max(start, Math.min(end, total));
|
|
@@ -14489,6 +14547,7 @@ function replaceRange(blocks, start, end, insert) {
|
|
|
14489
14547
|
const prefixBlocks = [];
|
|
14490
14548
|
const suffixBlocks = [];
|
|
14491
14549
|
let inheritedMarks;
|
|
14550
|
+
let startBlock;
|
|
14492
14551
|
let pos = 0;
|
|
14493
14552
|
blocks.forEach((block) => {
|
|
14494
14553
|
const len = blockText(block).length;
|
|
@@ -14501,11 +14560,13 @@ function replaceRange(blocks, start, end, insert) {
|
|
|
14501
14560
|
if (start >= bStart && start <= bEnd) {
|
|
14502
14561
|
const local = start - bStart;
|
|
14503
14562
|
before = sliceRuns(block.runs, 0, local);
|
|
14563
|
+
startBlock = block;
|
|
14504
14564
|
inheritedMarks = marksAt(block.runs, local);
|
|
14505
14565
|
}
|
|
14506
14566
|
if (end >= bStart && end <= bEnd) after = sliceRuns(block.runs, end - bStart, len);
|
|
14507
14567
|
}
|
|
14508
14568
|
});
|
|
14569
|
+
if (insertMarks !== void 0) inheritedMarks = insertMarks ?? void 0;
|
|
14509
14570
|
const lines = insert.split("\n");
|
|
14510
14571
|
const mk = (t) => t === "" ? [] : [{
|
|
14511
14572
|
t,
|
|
@@ -14516,7 +14577,10 @@ function replaceRange(blocks, start, end, insert) {
|
|
|
14516
14577
|
let runs = mk(line);
|
|
14517
14578
|
if (idx === 0) runs = [...before, ...runs];
|
|
14518
14579
|
if (idx === lines.length - 1) runs = [...runs, ...after];
|
|
14519
|
-
middle.push({
|
|
14580
|
+
middle.push({
|
|
14581
|
+
...blockFields(startBlock, idx === 0),
|
|
14582
|
+
runs: normalizeRuns(runs)
|
|
14583
|
+
});
|
|
14520
14584
|
});
|
|
14521
14585
|
return [
|
|
14522
14586
|
...prefixBlocks,
|
|
@@ -14524,8 +14588,331 @@ function replaceRange(blocks, start, end, insert) {
|
|
|
14524
14588
|
...suffixBlocks
|
|
14525
14589
|
];
|
|
14526
14590
|
}
|
|
14591
|
+
function safeBase(base) {
|
|
14592
|
+
return Number.isFinite(base) && base > 0 ? base : 1;
|
|
14593
|
+
}
|
|
14594
|
+
/** px -> stored ratio (4 decimals). */
|
|
14595
|
+
function sizeToRatio(px, base) {
|
|
14596
|
+
return Math.round(px / safeBase(base) * 1e4) / 1e4;
|
|
14597
|
+
}
|
|
14598
|
+
/** stored ratio -> px (rounded to 0.1). Invalid ratios resolve to the base. */
|
|
14599
|
+
function ratioToSize(ratio, base) {
|
|
14600
|
+
const b = safeBase(base);
|
|
14601
|
+
return Math.round((typeof ratio === "number" && ratio > 0 ? ratio : 1) * b * 10) / 10;
|
|
14602
|
+
}
|
|
14603
|
+
/** Effective ratio of a run (invalid or missing -> 1). */
|
|
14604
|
+
function runRatio(m) {
|
|
14605
|
+
const r = m?.size;
|
|
14606
|
+
return typeof r === "number" && Number.isFinite(r) && r > 0 ? r : 1;
|
|
14607
|
+
}
|
|
14608
|
+
function normalizeMarks(m) {
|
|
14609
|
+
const out = {};
|
|
14610
|
+
for (const [k, v] of Object.entries(m)) {
|
|
14611
|
+
if (v === void 0 || v === false || v === null || v === "") continue;
|
|
14612
|
+
if (k === "size" && v === 1) continue;
|
|
14613
|
+
out[k] = v;
|
|
14614
|
+
}
|
|
14615
|
+
return Object.keys(out).length === 0 ? void 0 : out;
|
|
14616
|
+
}
|
|
14617
|
+
/** Normalized marks of a run, or undefined when empty. */
|
|
14618
|
+
function cleanMarks(m) {
|
|
14619
|
+
return m ? normalizeMarks(m) : void 0;
|
|
14620
|
+
}
|
|
14621
|
+
function blockSpans(blocks) {
|
|
14622
|
+
const out = [];
|
|
14623
|
+
let pos = 0;
|
|
14624
|
+
blocks.forEach((b, index) => {
|
|
14625
|
+
const len = blockText(b).length;
|
|
14626
|
+
out.push({
|
|
14627
|
+
index,
|
|
14628
|
+
start: pos,
|
|
14629
|
+
end: pos + len
|
|
14630
|
+
});
|
|
14631
|
+
pos += len + 1;
|
|
14632
|
+
});
|
|
14633
|
+
return out;
|
|
14634
|
+
}
|
|
14635
|
+
/** Indices of blocks touched by [start, end] (collapsed range -> its block). */
|
|
14636
|
+
function blockIndicesInRange(blocks, start, end) {
|
|
14637
|
+
const out = [];
|
|
14638
|
+
for (const s of blockSpans(blocks)) if (s.end >= start && s.start <= end) {
|
|
14639
|
+
if (end > start && s.start === end && s.start !== start) continue;
|
|
14640
|
+
out.push(s.index);
|
|
14641
|
+
}
|
|
14642
|
+
return out;
|
|
14643
|
+
}
|
|
14644
|
+
/** Marks that typing at a collapsed offset would inherit. */
|
|
14645
|
+
function marksAtOffset(blocks, offset) {
|
|
14646
|
+
for (const s of blockSpans(blocks)) if (offset >= s.start && offset <= s.end) return cleanMarks(marksAt(blocks[s.index].runs, offset - s.start));
|
|
14647
|
+
}
|
|
14648
|
+
function mapRunsInRange(block, from, to, fn) {
|
|
14649
|
+
const runs = [];
|
|
14650
|
+
let pos = 0;
|
|
14651
|
+
for (const run of block.runs) {
|
|
14652
|
+
const rs = pos;
|
|
14653
|
+
const re = pos + run.t.length;
|
|
14654
|
+
pos = re;
|
|
14655
|
+
const s = Math.max(from, rs);
|
|
14656
|
+
const e = Math.min(to, re);
|
|
14657
|
+
if (s >= e) {
|
|
14658
|
+
runs.push(run);
|
|
14659
|
+
continue;
|
|
14660
|
+
}
|
|
14661
|
+
if (s > rs) runs.push({
|
|
14662
|
+
t: run.t.slice(0, s - rs),
|
|
14663
|
+
m: run.m
|
|
14664
|
+
});
|
|
14665
|
+
runs.push({
|
|
14666
|
+
t: run.t.slice(s - rs, e - rs),
|
|
14667
|
+
m: fn(run.m)
|
|
14668
|
+
});
|
|
14669
|
+
if (e < re) runs.push({
|
|
14670
|
+
t: run.t.slice(e - rs),
|
|
14671
|
+
m: run.m
|
|
14672
|
+
});
|
|
14673
|
+
}
|
|
14674
|
+
return {
|
|
14675
|
+
...block,
|
|
14676
|
+
runs: normalizeRuns(runs)
|
|
14677
|
+
};
|
|
14678
|
+
}
|
|
14679
|
+
/** Set (or remove with `undefined`) one mark over the global range. */
|
|
14680
|
+
function applyMark(blocks, start, end, key, value) {
|
|
14681
|
+
if (start >= end) return blocks;
|
|
14682
|
+
if (key === "lineHeight" && value !== void 0) {
|
|
14683
|
+
const lh = runLineHeight({ lineHeight: value });
|
|
14684
|
+
if (lh === void 0) return blocks;
|
|
14685
|
+
value = lh;
|
|
14686
|
+
}
|
|
14687
|
+
const spans = blockSpans(blocks);
|
|
14688
|
+
return blocks.map((block, i) => {
|
|
14689
|
+
const s = spans[i];
|
|
14690
|
+
if (s.end <= start || s.start >= end) return block;
|
|
14691
|
+
return mapRunsInRange(block, Math.max(start, s.start) - s.start, Math.min(end, s.end) - s.start, (m) => normalizeMarks({
|
|
14692
|
+
...m,
|
|
14693
|
+
[key]: value
|
|
14694
|
+
}));
|
|
14695
|
+
});
|
|
14696
|
+
}
|
|
14697
|
+
/** Query one mark over a range; collapsed ranges report the caret marks. */
|
|
14698
|
+
function queryMark(blocks, start, end, key, stored) {
|
|
14699
|
+
const values = [];
|
|
14700
|
+
if (start >= end) {
|
|
14701
|
+
const v = (stored ?? marksAtOffset(blocks, start))?.[key];
|
|
14702
|
+
return v === void 0 || v === false ? { state: "none" } : {
|
|
14703
|
+
state: "all",
|
|
14704
|
+
value: v
|
|
14705
|
+
};
|
|
14706
|
+
}
|
|
14707
|
+
const spans = blockSpans(blocks);
|
|
14708
|
+
blocks.forEach((block, i) => {
|
|
14709
|
+
const s = spans[i];
|
|
14710
|
+
if (s.end <= start || s.start >= end) return;
|
|
14711
|
+
const from = Math.max(start, s.start) - s.start;
|
|
14712
|
+
const to = Math.min(end, s.end) - s.start;
|
|
14713
|
+
for (const r of sliceRuns(block.runs, from, to)) {
|
|
14714
|
+
const v = r.m?.[key];
|
|
14715
|
+
values.push(v === false ? void 0 : v);
|
|
14716
|
+
}
|
|
14717
|
+
});
|
|
14718
|
+
if (values.length === 0) return { state: "none" };
|
|
14719
|
+
const set = values.filter((v) => v !== void 0);
|
|
14720
|
+
if (set.length === 0) return { state: "none" };
|
|
14721
|
+
if (set.length < values.length) return { state: "some" };
|
|
14722
|
+
return values.every((v) => v === values[0]) ? {
|
|
14723
|
+
state: "all",
|
|
14724
|
+
value: values[0]
|
|
14725
|
+
} : { state: "some" };
|
|
14726
|
+
}
|
|
14727
|
+
/** Toggle a boolean mark: removes when fully set, sets otherwise. */
|
|
14728
|
+
function toggleMark(blocks, start, end, key) {
|
|
14729
|
+
return applyMark(blocks, start, end, key, queryMark(blocks, start, end, key).state === "all" ? void 0 : true);
|
|
14730
|
+
}
|
|
14731
|
+
function mapBlocks(blocks, indices, fn) {
|
|
14732
|
+
const set = new Set(indices);
|
|
14733
|
+
return blocks.map((b, i) => set.has(i) ? fn(b) : b);
|
|
14734
|
+
}
|
|
14735
|
+
function stripList(b) {
|
|
14736
|
+
const { kind: _k, depth: _d, start: _s, ...rest } = b;
|
|
14737
|
+
return rest;
|
|
14738
|
+
}
|
|
14739
|
+
/** Toggle list `kind` on the blocks of a range (all already that kind -> off). */
|
|
14740
|
+
function toggleList(blocks, start, end, kind) {
|
|
14741
|
+
const idx = blockIndicesInRange(blocks, start, end);
|
|
14742
|
+
const allSame = idx.every((i) => blocks[i].kind === kind);
|
|
14743
|
+
return mapBlocks(blocks, idx, (b) => allSame ? stripList(b) : {
|
|
14744
|
+
...b,
|
|
14745
|
+
kind
|
|
14746
|
+
});
|
|
14747
|
+
}
|
|
14748
|
+
/** Set (or clear with `undefined`) the alignment of the blocks in a range. */
|
|
14749
|
+
function setBlockAlign(blocks, start, end, align) {
|
|
14750
|
+
return mapBlocks(blocks, blockIndicesInRange(blocks, start, end), (b) => {
|
|
14751
|
+
if (align === void 0) {
|
|
14752
|
+
const { align: _a, ...rest } = b;
|
|
14753
|
+
return rest;
|
|
14754
|
+
}
|
|
14755
|
+
return {
|
|
14756
|
+
...b,
|
|
14757
|
+
align
|
|
14758
|
+
};
|
|
14759
|
+
});
|
|
14760
|
+
}
|
|
14761
|
+
/** Increase depth of list blocks in the range (non-list blocks untouched). */
|
|
14762
|
+
function indentBlocks(blocks, start, end) {
|
|
14763
|
+
return mapBlocks(blocks, blockIndicesInRange(blocks, start, end), (b) => b.kind ? {
|
|
14764
|
+
...b,
|
|
14765
|
+
depth: (b.depth ?? 0) + 1
|
|
14766
|
+
} : b);
|
|
14767
|
+
}
|
|
14768
|
+
/** Decrease depth; at depth 0 the list marker is removed. */
|
|
14769
|
+
function outdentBlocks(blocks, start, end) {
|
|
14770
|
+
return mapBlocks(blocks, blockIndicesInRange(blocks, start, end), (b) => {
|
|
14771
|
+
if (!b.kind) return b;
|
|
14772
|
+
const d = b.depth ?? 0;
|
|
14773
|
+
if (d <= 0) return stripList(b);
|
|
14774
|
+
const next = {
|
|
14775
|
+
...b,
|
|
14776
|
+
depth: d - 1
|
|
14777
|
+
};
|
|
14778
|
+
if (d - 1 === 0) delete next.depth;
|
|
14779
|
+
return next;
|
|
14780
|
+
});
|
|
14781
|
+
}
|
|
14782
|
+
const BULLETS = [
|
|
14783
|
+
"•",
|
|
14784
|
+
"◦",
|
|
14785
|
+
"▪"
|
|
14786
|
+
];
|
|
14787
|
+
function alpha(n) {
|
|
14788
|
+
let s = "";
|
|
14789
|
+
let v = n;
|
|
14790
|
+
while (v > 0) {
|
|
14791
|
+
v--;
|
|
14792
|
+
s = String.fromCharCode(97 + v % 26) + s;
|
|
14793
|
+
v = Math.floor(v / 26);
|
|
14794
|
+
}
|
|
14795
|
+
return s;
|
|
14796
|
+
}
|
|
14797
|
+
function roman(n) {
|
|
14798
|
+
const map = [
|
|
14799
|
+
[1e3, "m"],
|
|
14800
|
+
[900, "cm"],
|
|
14801
|
+
[500, "d"],
|
|
14802
|
+
[400, "cd"],
|
|
14803
|
+
[100, "c"],
|
|
14804
|
+
[90, "xc"],
|
|
14805
|
+
[50, "l"],
|
|
14806
|
+
[40, "xl"],
|
|
14807
|
+
[10, "x"],
|
|
14808
|
+
[9, "ix"],
|
|
14809
|
+
[5, "v"],
|
|
14810
|
+
[4, "iv"],
|
|
14811
|
+
[1, "i"]
|
|
14812
|
+
];
|
|
14813
|
+
let v = n;
|
|
14814
|
+
let s = "";
|
|
14815
|
+
for (const [k, r] of map) while (v >= k) {
|
|
14816
|
+
s += r;
|
|
14817
|
+
v -= k;
|
|
14818
|
+
}
|
|
14819
|
+
return s;
|
|
14820
|
+
}
|
|
14821
|
+
/**
|
|
14822
|
+
* Marker text per block (null for plain paragraphs). Ordered numbering is
|
|
14823
|
+
* continuous across consecutive same-depth items and restarts after a
|
|
14824
|
+
* non-list block, a shallower item or a kind change; `start` seeds a run.
|
|
14825
|
+
* Markers are layout-only: they never enter `text` or offsets.
|
|
14826
|
+
*/
|
|
14827
|
+
function listMarkers(blocks) {
|
|
14828
|
+
const stack = [];
|
|
14829
|
+
return blocks.map((b) => {
|
|
14830
|
+
if (!b.kind) {
|
|
14831
|
+
stack.length = 0;
|
|
14832
|
+
return null;
|
|
14833
|
+
}
|
|
14834
|
+
const d = Math.max(0, Math.floor(b.depth ?? 0));
|
|
14835
|
+
stack.length = Math.min(stack.length, d + 1);
|
|
14836
|
+
while (stack.length < d) stack.push({
|
|
14837
|
+
kind: b.kind,
|
|
14838
|
+
n: 0
|
|
14839
|
+
});
|
|
14840
|
+
const cur = stack[d];
|
|
14841
|
+
if (!cur || cur.kind !== b.kind) stack[d] = {
|
|
14842
|
+
kind: b.kind,
|
|
14843
|
+
n: b.kind === "ol" ? Math.max(1, b.start ?? 1) : 0
|
|
14844
|
+
};
|
|
14845
|
+
else if (b.kind === "ol") cur.n += 1;
|
|
14846
|
+
if (b.kind === "ul") return BULLETS[d % 3];
|
|
14847
|
+
const n = stack[d].n;
|
|
14848
|
+
const style = d % 3;
|
|
14849
|
+
return `${style === 0 ? n : style === 1 ? alpha(n) : roman(n)}.`;
|
|
14850
|
+
});
|
|
14851
|
+
}
|
|
14852
|
+
/**
|
|
14853
|
+
* Format of the range for toolbars. A collapsed range reports the stored
|
|
14854
|
+
* marks (when given) or the marks typing would inherit.
|
|
14855
|
+
*/
|
|
14856
|
+
function getSelectionFormat(blocks, start, end, baseFontSize, stored) {
|
|
14857
|
+
const q = (key) => queryMark(blocks, start, end, key, stored);
|
|
14858
|
+
const ratios = [];
|
|
14859
|
+
if (start >= end) ratios.push(runRatio(stored ?? marksAtOffset(blocks, start)));
|
|
14860
|
+
else {
|
|
14861
|
+
const spans = blockSpans(blocks);
|
|
14862
|
+
blocks.forEach((block, i) => {
|
|
14863
|
+
const s = spans[i];
|
|
14864
|
+
if (s.end <= start || s.start >= end) return;
|
|
14865
|
+
const from = Math.max(start, s.start) - s.start;
|
|
14866
|
+
const to = Math.min(end, s.end) - s.start;
|
|
14867
|
+
for (const r of sliceRuns(block.runs, from, to)) ratios.push(runRatio(r.m));
|
|
14868
|
+
});
|
|
14869
|
+
}
|
|
14870
|
+
let size;
|
|
14871
|
+
if (ratios.length === 0 || ratios.every((r) => r === ratios[0])) {
|
|
14872
|
+
const r = ratios[0] ?? 1;
|
|
14873
|
+
size = {
|
|
14874
|
+
state: r === 1 ? "none" : "all",
|
|
14875
|
+
value: ratioToSize(r, baseFontSize)
|
|
14876
|
+
};
|
|
14877
|
+
} else size = { state: "some" };
|
|
14878
|
+
const idx = blockIndicesInRange(blocks, start, end);
|
|
14879
|
+
const kinds = new Set(idx.map((i) => blocks[i].kind ?? "none"));
|
|
14880
|
+
let list = { kind: "none" };
|
|
14881
|
+
if (kinds.size === 1) {
|
|
14882
|
+
const kind = [...kinds][0];
|
|
14883
|
+
const depths = new Set(idx.map((i) => blocks[i].depth ?? 0));
|
|
14884
|
+
list = kind === "none" ? { kind } : {
|
|
14885
|
+
kind,
|
|
14886
|
+
...depths.size === 1 ? { depth: [...depths][0] } : {}
|
|
14887
|
+
};
|
|
14888
|
+
} else if (kinds.size > 1) list = { kind: "mixed" };
|
|
14889
|
+
return {
|
|
14890
|
+
bold: q("bold"),
|
|
14891
|
+
italic: q("italic"),
|
|
14892
|
+
underline: q("underline"),
|
|
14893
|
+
strike: q("strike"),
|
|
14894
|
+
color: q("color"),
|
|
14895
|
+
family: q("family"),
|
|
14896
|
+
link: q("link"),
|
|
14897
|
+
lineHeight: q("lineHeight"),
|
|
14898
|
+
align: alignQuery(blocks, start, end),
|
|
14899
|
+
size,
|
|
14900
|
+
list
|
|
14901
|
+
};
|
|
14902
|
+
}
|
|
14903
|
+
function alignQuery(blocks, start, end) {
|
|
14904
|
+
const vals = blockIndicesInRange(blocks, start, end).map((i) => blocks[i].align);
|
|
14905
|
+
const set = vals.filter((v) => v !== void 0);
|
|
14906
|
+
if (set.length === 0) return { state: "none" };
|
|
14907
|
+
if (set.length < vals.length || set.some((v) => v !== set[0])) return { state: "some" };
|
|
14908
|
+
return {
|
|
14909
|
+
state: "all",
|
|
14910
|
+
value: set[0]
|
|
14911
|
+
};
|
|
14912
|
+
}
|
|
14527
14913
|
//#endregion
|
|
14528
14914
|
//#region src/nodes/text-v2/layout/engine.ts
|
|
14915
|
+
const DEFAULT_INDENT_STEP = 1.5;
|
|
14529
14916
|
/** Quote multi-word families like `Konva` does so the font shorthand parses. */
|
|
14530
14917
|
function normalizeFontFamily(fontFamily) {
|
|
14531
14918
|
return fontFamily.split(",").map((family) => {
|
|
@@ -14536,7 +14923,51 @@ function normalizeFontFamily(fontFamily) {
|
|
|
14536
14923
|
}).join(", ");
|
|
14537
14924
|
}
|
|
14538
14925
|
function fontString(s) {
|
|
14539
|
-
return `${s.fontStyle} ${s.fontVariant}
|
|
14926
|
+
return `${[...`${s.fontStyle} ${s.fontVariant}`.split(/\s+/).filter((token) => token !== "" && token !== "normal"), `${s.fontSize}px`].join(" ")} ${normalizeFontFamily(s.fontFamily)}`;
|
|
14927
|
+
}
|
|
14928
|
+
/** Style of a run: node style overridden by the run marks. */
|
|
14929
|
+
function runStyle(style, m) {
|
|
14930
|
+
if (!m) return style;
|
|
14931
|
+
let fontStyle = style.fontStyle;
|
|
14932
|
+
if (m.bold || m.italic) {
|
|
14933
|
+
const tokens = new Set(fontStyle.split(/\s+/).filter(Boolean));
|
|
14934
|
+
tokens.delete("normal");
|
|
14935
|
+
if (m.italic) tokens.add("italic");
|
|
14936
|
+
if (m.bold) tokens.add("bold");
|
|
14937
|
+
fontStyle = tokens.size === 0 ? "normal" : Array.from(tokens).join(" ");
|
|
14938
|
+
}
|
|
14939
|
+
return {
|
|
14940
|
+
...style,
|
|
14941
|
+
fontStyle,
|
|
14942
|
+
fontSize: style.fontSize * runRatio(m),
|
|
14943
|
+
fontFamily: typeof m.family === "string" && m.family ? m.family : style.fontFamily
|
|
14944
|
+
};
|
|
14945
|
+
}
|
|
14946
|
+
/** Pure fallback (same constants as Konva.Text when the browser has none). */
|
|
14947
|
+
const approxMetrics = (_font, size) => ({
|
|
14948
|
+
ascent: .91 * size,
|
|
14949
|
+
descent: .21 * size
|
|
14950
|
+
});
|
|
14951
|
+
const metricsCache = /* @__PURE__ */ new Map();
|
|
14952
|
+
/** Ascent/descent used for baseline placement (parity with Konva.Text). */
|
|
14953
|
+
function fontMetrics(font, size) {
|
|
14954
|
+
if (Konva.legacyTextRendering) return {
|
|
14955
|
+
ascent: 0,
|
|
14956
|
+
descent: 0
|
|
14957
|
+
};
|
|
14958
|
+
const hit = metricsCache.get(font);
|
|
14959
|
+
if (hit) return hit;
|
|
14960
|
+
const ctx = Konva.Util.createCanvasElement().getContext("2d");
|
|
14961
|
+
ctx.font = font;
|
|
14962
|
+
const m = ctx.measureText("M");
|
|
14963
|
+
const scale = size / 100;
|
|
14964
|
+
const out = {
|
|
14965
|
+
ascent: m.fontBoundingBoxAscent ?? 91 * scale,
|
|
14966
|
+
descent: m.fontBoundingBoxDescent ?? 21 * scale
|
|
14967
|
+
};
|
|
14968
|
+
if (metricsCache.size > 2e3) metricsCache.clear();
|
|
14969
|
+
metricsCache.set(font, out);
|
|
14970
|
+
return out;
|
|
14540
14971
|
}
|
|
14541
14972
|
let sharedContext = null;
|
|
14542
14973
|
/** Default measurer: works on browser, node-canvas and skia-canvas backends. */
|
|
@@ -14585,11 +15016,15 @@ function wordWrapIndex(graphemes, start, matchLen, matchWidth, maxWidth) {
|
|
|
14585
15016
|
const seg = graphemes.slice(start, start + matchLen);
|
|
14586
15017
|
return Math.max(seg.lastIndexOf(" "), seg.lastIndexOf("-")) + 1;
|
|
14587
15018
|
}
|
|
14588
|
-
function applyAlign(lines,
|
|
14589
|
-
if (align === "left") return;
|
|
15019
|
+
function applyAlign(lines, width, padding) {
|
|
14590
15020
|
for (const line of lines) {
|
|
14591
|
-
const
|
|
14592
|
-
|
|
15021
|
+
const align = line.align;
|
|
15022
|
+
if (align === "left") {
|
|
15023
|
+
line.offsetX = line.indent;
|
|
15024
|
+
continue;
|
|
15025
|
+
}
|
|
15026
|
+
const free = width - line.indent - line.width - padding * 2;
|
|
15027
|
+
line.offsetX = line.indent + (align === "center" ? free / 2 : free);
|
|
14593
15028
|
}
|
|
14594
15029
|
}
|
|
14595
15030
|
var TextLayoutEngine = class {
|
|
@@ -14599,8 +15034,10 @@ var TextLayoutEngine = class {
|
|
|
14599
15034
|
/** Entries by signature, so paragraphs that shifted index are reused. */
|
|
14600
15035
|
pool = /* @__PURE__ */ new Map();
|
|
14601
15036
|
lastParagraphsRelaid = 0;
|
|
14602
|
-
|
|
15037
|
+
metrics;
|
|
15038
|
+
constructor(measurer = canvasMeasurer, metrics = approxMetrics) {
|
|
14603
15039
|
this.measure = measurer;
|
|
15040
|
+
this.metrics = metrics;
|
|
14604
15041
|
}
|
|
14605
15042
|
/** Number of paragraphs recomputed by the last `layout()` call. */
|
|
14606
15043
|
getLastRelaidCount() {
|
|
@@ -14611,10 +15048,17 @@ var TextLayoutEngine = class {
|
|
|
14611
15048
|
this.paragraphs = [];
|
|
14612
15049
|
this.pool.clear();
|
|
14613
15050
|
}
|
|
14614
|
-
|
|
14615
|
-
|
|
14616
|
-
|
|
14617
|
-
|
|
15051
|
+
/** Text indent of a block in px (0 for plain paragraphs). */
|
|
15052
|
+
indentFor(block, style, fixedWidth, maxWidth) {
|
|
15053
|
+
if (!block.kind) return 0;
|
|
15054
|
+
const stepPx = (typeof style.indentStep === "number" && Number.isFinite(style.indentStep) && style.indentStep > 0 ? style.indentStep : DEFAULT_INDENT_STEP) * style.fontSize;
|
|
15055
|
+
const wanted = (Math.max(0, Math.floor(block.depth ?? 0)) + 1) * stepPx;
|
|
15056
|
+
if (!fixedWidth) return wanted;
|
|
15057
|
+
const limit = Math.max(0, maxWidth - 4 * style.fontSize);
|
|
15058
|
+
return Math.min(wanted, limit);
|
|
15059
|
+
}
|
|
15060
|
+
sigFor(block, style, fixedWidth, maxWidth, indent) {
|
|
15061
|
+
return `${fontString(style)}|${style.lineHeight}|${style.letterSpacing}|${style.wrap}|${fixedWidth ? maxWidth - indent : "auto"}|${JSON.stringify(block.runs)}`;
|
|
14618
15062
|
}
|
|
14619
15063
|
/**
|
|
14620
15064
|
* Pre-compute paragraph layouts for `blocks` within a time budget so a
|
|
@@ -14622,14 +15066,14 @@ var TextLayoutEngine = class {
|
|
|
14622
15066
|
* Lets callers time-slice very large content (e.g. bulk paste).
|
|
14623
15067
|
*/
|
|
14624
15068
|
warm(blocks, style, bounds, budgetMs) {
|
|
14625
|
-
const
|
|
14626
|
-
const fixedWidth = bounds.width !== void 0 && !Number.isNaN(bounds.width);
|
|
15069
|
+
const fixedWidth = isNum(bounds.width);
|
|
14627
15070
|
const maxWidth = (bounds.width ?? 0) - bounds.padding * 2;
|
|
14628
15071
|
const deadline = now() + budgetMs;
|
|
14629
15072
|
for (const block of blocks) {
|
|
14630
|
-
const
|
|
15073
|
+
const indent = this.indentFor(block, style, fixedWidth, maxWidth);
|
|
15074
|
+
const sig = this.sigFor(block, style, fixedWidth, maxWidth, indent);
|
|
14631
15075
|
if (this.pool.has(sig)) continue;
|
|
14632
|
-
this.pool.set(sig, this.layoutParagraph(block, sig,
|
|
15076
|
+
this.pool.set(sig, this.layoutParagraph(block, sig, style, fixedWidth, maxWidth - indent));
|
|
14633
15077
|
if (now() >= deadline) return false;
|
|
14634
15078
|
}
|
|
14635
15079
|
return true;
|
|
@@ -14647,35 +15091,38 @@ var TextLayoutEngine = class {
|
|
|
14647
15091
|
return w + text.length * spacing;
|
|
14648
15092
|
}
|
|
14649
15093
|
layout(blocks, style, bounds) {
|
|
14650
|
-
const font = fontString(style);
|
|
14651
15094
|
const padding = bounds.padding;
|
|
14652
15095
|
const fixedWidth = isNum(bounds.width);
|
|
14653
15096
|
const fixedHeight = isNum(bounds.height);
|
|
14654
15097
|
const maxWidth = (bounds.width ?? 0) - padding * 2;
|
|
14655
15098
|
const maxHeightPx = (bounds.height ?? 0) - padding * 2;
|
|
14656
15099
|
const lineHeightPx = style.lineHeight * style.fontSize;
|
|
14657
|
-
const
|
|
15100
|
+
const markers = listMarkers(blocks);
|
|
14658
15101
|
const prev = this.paragraphs;
|
|
14659
15102
|
const next = [];
|
|
14660
15103
|
let relaid = 0;
|
|
14661
15104
|
const lines = [];
|
|
14662
15105
|
let textWidth = 0;
|
|
14663
15106
|
let offset = 0;
|
|
15107
|
+
let contentHeight = 0;
|
|
14664
15108
|
for (let p = 0; p < blocks.length; p++) {
|
|
14665
15109
|
const block = blocks[p];
|
|
14666
|
-
const
|
|
15110
|
+
const indent = this.indentFor(block, style, fixedWidth, maxWidth);
|
|
15111
|
+
const sig = this.sigFor(block, style, fixedWidth, maxWidth, indent);
|
|
14667
15112
|
let entry = prev[p]?.sig === sig ? prev[p] : this.pool.get(sig);
|
|
14668
15113
|
if (!entry) {
|
|
14669
|
-
entry = this.layoutParagraph(block, sig,
|
|
15114
|
+
entry = this.layoutParagraph(block, sig, style, fixedWidth, maxWidth - indent);
|
|
14670
15115
|
relaid++;
|
|
14671
15116
|
}
|
|
14672
15117
|
next.push(entry);
|
|
14673
15118
|
const emitted = this.emitLines(entry, block, p, offset, lines, {
|
|
14674
|
-
font,
|
|
14675
15119
|
style,
|
|
14676
|
-
|
|
15120
|
+
indent,
|
|
15121
|
+
marker: markers[p],
|
|
15122
|
+
y: contentHeight,
|
|
14677
15123
|
maxHeightPx: fixedHeight ? maxHeightPx : void 0
|
|
14678
15124
|
});
|
|
15125
|
+
contentHeight = emitted.y;
|
|
14679
15126
|
textWidth = Math.max(textWidth, emitted.maxWidth);
|
|
14680
15127
|
if (emitted.stopped) break;
|
|
14681
15128
|
offset += blockText(block).length + 1;
|
|
@@ -14684,25 +15131,42 @@ var TextLayoutEngine = class {
|
|
|
14684
15131
|
this.pool = new Map(next.map((e) => [e.sig, e]));
|
|
14685
15132
|
this.lastParagraphsRelaid = relaid;
|
|
14686
15133
|
const width = fixedWidth ? bounds.width : textWidth + padding * 2;
|
|
14687
|
-
const height = fixedHeight ? bounds.height :
|
|
14688
|
-
applyAlign(lines,
|
|
15134
|
+
const height = fixedHeight ? bounds.height : contentHeight + padding * 2;
|
|
15135
|
+
applyAlign(lines, width, padding);
|
|
14689
15136
|
return {
|
|
14690
15137
|
lines,
|
|
14691
15138
|
textWidth,
|
|
14692
15139
|
textHeight: style.fontSize,
|
|
14693
15140
|
lineHeightPx,
|
|
15141
|
+
contentHeight,
|
|
14694
15142
|
width,
|
|
14695
15143
|
height
|
|
14696
15144
|
};
|
|
14697
15145
|
}
|
|
14698
15146
|
/** Append a paragraph's lines to `lines`; stops once a fixed height is full. */
|
|
14699
15147
|
emitLines(entry, block, paragraph, offset, lines, opts) {
|
|
14700
|
-
const {
|
|
15148
|
+
const { style, indent, maxHeightPx } = opts;
|
|
15149
|
+
let { y } = opts;
|
|
14701
15150
|
let maxWidth = 0;
|
|
15151
|
+
let first = true;
|
|
14702
15152
|
for (const pl of entry.lines) {
|
|
14703
15153
|
const text = entry.graphemes.slice(pl.startG, pl.endG).join("");
|
|
14704
|
-
const segments = this.buildSegments(entry, block, pl,
|
|
15154
|
+
const segments = this.buildSegments(entry, block, pl, style);
|
|
14705
15155
|
const startChar = entry.graphemes.slice(0, pl.startG).join("").length;
|
|
15156
|
+
let marker;
|
|
15157
|
+
if (first && opts.marker) {
|
|
15158
|
+
const m0 = block.runs[0]?.m;
|
|
15159
|
+
const rs = runStyle(style, m0);
|
|
15160
|
+
const stepPx = Math.min(indent, this.stepPx(style));
|
|
15161
|
+
marker = {
|
|
15162
|
+
text: opts.marker,
|
|
15163
|
+
x: Math.max(0, indent - stepPx),
|
|
15164
|
+
font: fontString(rs),
|
|
15165
|
+
fontSize: rs.fontSize,
|
|
15166
|
+
color: typeof m0?.color === "string" ? m0.color : void 0
|
|
15167
|
+
};
|
|
15168
|
+
}
|
|
15169
|
+
first = false;
|
|
14706
15170
|
lines.push({
|
|
14707
15171
|
paragraph,
|
|
14708
15172
|
start: offset + startChar,
|
|
@@ -14710,30 +15174,41 @@ var TextLayoutEngine = class {
|
|
|
14710
15174
|
text,
|
|
14711
15175
|
width: segments.reduce((a, s) => a + s.width, 0),
|
|
14712
15176
|
measuredWidth: pl.measuredWidth,
|
|
14713
|
-
offsetX:
|
|
14714
|
-
y
|
|
15177
|
+
offsetX: indent,
|
|
15178
|
+
y,
|
|
15179
|
+
height: pl.height,
|
|
15180
|
+
baseline: pl.baseline,
|
|
15181
|
+
indent,
|
|
15182
|
+
marker,
|
|
15183
|
+
align: block.align ?? style.align,
|
|
14715
15184
|
segments,
|
|
14716
15185
|
lastInParagraph: pl.lastInParagraph
|
|
14717
15186
|
});
|
|
14718
|
-
|
|
14719
|
-
|
|
15187
|
+
y += pl.height;
|
|
15188
|
+
maxWidth = Math.max(maxWidth, pl.measuredWidth + indent);
|
|
15189
|
+
if (maxHeightPx !== void 0 && y + pl.height > maxHeightPx) return {
|
|
14720
15190
|
stopped: true,
|
|
14721
|
-
maxWidth
|
|
15191
|
+
maxWidth,
|
|
15192
|
+
y
|
|
14722
15193
|
};
|
|
14723
15194
|
}
|
|
14724
15195
|
return {
|
|
14725
15196
|
stopped: false,
|
|
14726
|
-
maxWidth
|
|
15197
|
+
maxWidth,
|
|
15198
|
+
y
|
|
14727
15199
|
};
|
|
14728
15200
|
}
|
|
14729
|
-
|
|
15201
|
+
stepPx(style) {
|
|
15202
|
+
return (typeof style.indentStep === "number" && Number.isFinite(style.indentStep) && style.indentStep > 0 ? style.indentStep : DEFAULT_INDENT_STEP) * style.fontSize;
|
|
15203
|
+
}
|
|
15204
|
+
rangeWidth(entry, from, to, spacing) {
|
|
14730
15205
|
let width = 0;
|
|
14731
15206
|
let i = from;
|
|
14732
15207
|
while (i < to) {
|
|
14733
15208
|
const run = entry.runOf[i];
|
|
14734
15209
|
let j = i;
|
|
14735
15210
|
while (j < to && entry.runOf[j] === run) j++;
|
|
14736
|
-
width += this.measureChunk(
|
|
15211
|
+
width += this.measureChunk(entry.fonts[run], entry.graphemes.slice(i, j).join(""), spacing);
|
|
14737
15212
|
i = j;
|
|
14738
15213
|
}
|
|
14739
15214
|
return width;
|
|
@@ -14769,17 +15244,58 @@ var TextLayoutEngine = class {
|
|
|
14769
15244
|
matchWidth
|
|
14770
15245
|
};
|
|
14771
15246
|
}
|
|
14772
|
-
layoutParagraph(block, sig,
|
|
15247
|
+
layoutParagraph(block, sig, style, fixedWidth, maxWidth) {
|
|
15248
|
+
const entry = this.wrapParagraph(block, sig, style, fixedWidth, maxWidth);
|
|
15249
|
+
this.measureLines(entry, style);
|
|
15250
|
+
return entry;
|
|
15251
|
+
}
|
|
15252
|
+
/** Per-line box height and baseline from the runs on each line. */
|
|
15253
|
+
measureLines(entry, style) {
|
|
15254
|
+
const baseFont = fontString(style);
|
|
15255
|
+
for (const line of entry.lines) {
|
|
15256
|
+
let size = 0;
|
|
15257
|
+
let height = 0;
|
|
15258
|
+
let asc = 0;
|
|
15259
|
+
let desc = 0;
|
|
15260
|
+
const seen = /* @__PURE__ */ new Set();
|
|
15261
|
+
for (let g = line.startG; g < line.endG; g++) {
|
|
15262
|
+
const r = entry.runOf[g];
|
|
15263
|
+
if (seen.has(r)) continue;
|
|
15264
|
+
seen.add(r);
|
|
15265
|
+
size = Math.max(size, entry.sizes[r]);
|
|
15266
|
+
height = Math.max(height, entry.lineHeights[r] * entry.sizes[r]);
|
|
15267
|
+
const m = this.metrics(entry.fonts[r], entry.sizes[r]);
|
|
15268
|
+
asc = Math.max(asc, m.ascent);
|
|
15269
|
+
desc = Math.max(desc, m.descent);
|
|
15270
|
+
}
|
|
15271
|
+
if (seen.size === 0) {
|
|
15272
|
+
size = style.fontSize;
|
|
15273
|
+
height = style.lineHeight * size;
|
|
15274
|
+
const m = this.metrics(baseFont, style.fontSize);
|
|
15275
|
+
asc = m.ascent;
|
|
15276
|
+
desc = m.descent;
|
|
15277
|
+
}
|
|
15278
|
+
line.height = height;
|
|
15279
|
+
line.baseline = (asc - desc) / 2 + line.height / 2;
|
|
15280
|
+
}
|
|
15281
|
+
}
|
|
15282
|
+
wrapParagraph(block, sig, style, fixedWidth, maxWidth) {
|
|
14773
15283
|
const { graphemes, runOf } = flattenBlock(block);
|
|
15284
|
+
const baseFont = fontString(style);
|
|
15285
|
+
const fonts = block.runs.map((r) => r.m ? fontString(runStyle(style, r.m)) : baseFont);
|
|
15286
|
+
const sizes = block.runs.map((r) => style.fontSize * runRatio(r.m));
|
|
14774
15287
|
const entry = {
|
|
14775
15288
|
sig,
|
|
14776
15289
|
graphemes,
|
|
14777
15290
|
runOf,
|
|
15291
|
+
fonts,
|
|
15292
|
+
lineHeights: block.runs.map((r) => runLineHeight(r.m) ?? style.lineHeight),
|
|
15293
|
+
sizes,
|
|
14778
15294
|
lines: []
|
|
14779
15295
|
};
|
|
14780
15296
|
const n = graphemes.length;
|
|
14781
15297
|
const spacing = style.letterSpacing;
|
|
14782
|
-
const width = (a, b) => this.rangeWidth(entry, a, b,
|
|
15298
|
+
const width = (a, b) => this.rangeWidth(entry, a, b, spacing);
|
|
14783
15299
|
const shouldWrap = style.wrap !== "none";
|
|
14784
15300
|
const wrapAtWord = style.wrap !== "char" && shouldWrap;
|
|
14785
15301
|
const whole = width(0, n);
|
|
@@ -14788,7 +15304,9 @@ var TextLayoutEngine = class {
|
|
|
14788
15304
|
startG: 0,
|
|
14789
15305
|
endG: n,
|
|
14790
15306
|
measuredWidth: whole,
|
|
14791
|
-
lastInParagraph: true
|
|
15307
|
+
lastInParagraph: true,
|
|
15308
|
+
height: 0,
|
|
15309
|
+
baseline: 0
|
|
14792
15310
|
});
|
|
14793
15311
|
return entry;
|
|
14794
15312
|
}
|
|
@@ -14800,7 +15318,9 @@ var TextLayoutEngine = class {
|
|
|
14800
15318
|
startG: start,
|
|
14801
15319
|
endG: n,
|
|
14802
15320
|
measuredWidth: matchWidth,
|
|
14803
|
-
lastInParagraph: true
|
|
15321
|
+
lastInParagraph: true,
|
|
15322
|
+
height: 0,
|
|
15323
|
+
baseline: 0
|
|
14804
15324
|
});
|
|
14805
15325
|
return entry;
|
|
14806
15326
|
}
|
|
@@ -14816,7 +15336,9 @@ var TextLayoutEngine = class {
|
|
|
14816
15336
|
startG: start,
|
|
14817
15337
|
endG: trimTrailingSpace(graphemes, start, end),
|
|
14818
15338
|
measuredWidth: matchWidth,
|
|
14819
|
-
lastInParagraph: false
|
|
15339
|
+
lastInParagraph: false,
|
|
15340
|
+
height: 0,
|
|
15341
|
+
baseline: 0
|
|
14820
15342
|
});
|
|
14821
15343
|
start = skipSpace(graphemes, end);
|
|
14822
15344
|
}
|
|
@@ -14824,7 +15346,7 @@ var TextLayoutEngine = class {
|
|
|
14824
15346
|
if (last) last.lastInParagraph = true;
|
|
14825
15347
|
return entry;
|
|
14826
15348
|
}
|
|
14827
|
-
buildSegments(entry, block, pl,
|
|
15349
|
+
buildSegments(entry, block, pl, style) {
|
|
14828
15350
|
const segments = [];
|
|
14829
15351
|
let x = 0;
|
|
14830
15352
|
let i = pl.startG;
|
|
@@ -14833,12 +15355,16 @@ var TextLayoutEngine = class {
|
|
|
14833
15355
|
let j = i;
|
|
14834
15356
|
while (j < pl.endG && entry.runOf[j] === run) j++;
|
|
14835
15357
|
const t = entry.graphemes.slice(i, j).join("");
|
|
15358
|
+
const font = entry.fonts[run];
|
|
14836
15359
|
const width = this.measureChunk(font, t, style.letterSpacing);
|
|
14837
15360
|
segments.push({
|
|
14838
15361
|
t,
|
|
14839
15362
|
m: block.runs[run].m,
|
|
15363
|
+
run,
|
|
14840
15364
|
x,
|
|
14841
|
-
width
|
|
15365
|
+
width,
|
|
15366
|
+
font,
|
|
15367
|
+
fontSize: entry.sizes[run]
|
|
14842
15368
|
});
|
|
14843
15369
|
x += width;
|
|
14844
15370
|
i = j;
|
|
@@ -14870,12 +15396,18 @@ const LAYOUT_ATTRS = [
|
|
|
14870
15396
|
* code written against `Konva.Text` keeps working.
|
|
14871
15397
|
*/
|
|
14872
15398
|
var WeaveTextV2Shape = class extends Konva.Shape {
|
|
14873
|
-
engine = new TextLayoutEngine();
|
|
14874
|
-
naturalEngine = new TextLayoutEngine();
|
|
15399
|
+
engine = new TextLayoutEngine(canvasMeasurer, fontMetrics);
|
|
15400
|
+
naturalEngine = new TextLayoutEngine(canvasMeasurer, fontMetrics);
|
|
14875
15401
|
layoutCache = null;
|
|
14876
15402
|
blocksKey = {};
|
|
14877
15403
|
blocksCache = [];
|
|
14878
15404
|
viewport = null;
|
|
15405
|
+
indentStepValue;
|
|
15406
|
+
linkStyle = {
|
|
15407
|
+
color: "#1155ccff",
|
|
15408
|
+
hoverColor: "#3d7be0ff"
|
|
15409
|
+
};
|
|
15410
|
+
hoveredRun = null;
|
|
14879
15411
|
constructor(config) {
|
|
14880
15412
|
super(config);
|
|
14881
15413
|
this.className = "WeaveTextV2Shape";
|
|
@@ -14892,6 +15424,55 @@ var WeaveTextV2Shape = class extends Konva.Shape {
|
|
|
14892
15424
|
this._setAttr("text", value == null ? "" : String(value));
|
|
14893
15425
|
return this;
|
|
14894
15426
|
}
|
|
15427
|
+
/**
|
|
15428
|
+
* List indent per depth, as a multiple of `fontSize`. Not a node attr: it
|
|
15429
|
+
* comes from the node config and must not be serialized into the document.
|
|
15430
|
+
*/
|
|
15431
|
+
setIndentStep(step) {
|
|
15432
|
+
if (this.indentStepValue !== step) {
|
|
15433
|
+
this.indentStepValue = step;
|
|
15434
|
+
this.layoutCache = null;
|
|
15435
|
+
}
|
|
15436
|
+
return this;
|
|
15437
|
+
}
|
|
15438
|
+
/** Link run colours (from the node config; not serialized). */
|
|
15439
|
+
setLinkStyle(style) {
|
|
15440
|
+
this.linkStyle = style;
|
|
15441
|
+
return this;
|
|
15442
|
+
}
|
|
15443
|
+
/** Run under the pointer (`paragraph:run`), repainted with the hover colour. */
|
|
15444
|
+
setHoveredRun(key) {
|
|
15445
|
+
if (this.hoveredRun !== key) {
|
|
15446
|
+
this.hoveredRun = key;
|
|
15447
|
+
this.getLayer()?.batchDraw();
|
|
15448
|
+
}
|
|
15449
|
+
return this;
|
|
15450
|
+
}
|
|
15451
|
+
getHoveredRun() {
|
|
15452
|
+
return this.hoveredRun;
|
|
15453
|
+
}
|
|
15454
|
+
/**
|
|
15455
|
+
* Link run at a point in node-local coordinates (the legacy node-level
|
|
15456
|
+
* `link` wins: runs are then not links).
|
|
15457
|
+
*/
|
|
15458
|
+
linkAtPoint(point) {
|
|
15459
|
+
if (this.attrs.link) return null;
|
|
15460
|
+
const layout = this.ensureLayout();
|
|
15461
|
+
const o = this.getContentOffset();
|
|
15462
|
+
const x = point.x - o.x;
|
|
15463
|
+
const y = point.y - o.y;
|
|
15464
|
+
for (const line of layout.lines) {
|
|
15465
|
+
if (y < line.y || y >= line.y + line.height) continue;
|
|
15466
|
+
for (const seg of line.segments) {
|
|
15467
|
+
const sx = line.offsetX + seg.x;
|
|
15468
|
+
if (x >= sx && x <= sx + seg.width && isSafeLink(seg.m?.link)) return {
|
|
15469
|
+
key: `${line.paragraph}:${seg.run}`,
|
|
15470
|
+
url: seg.m.link
|
|
15471
|
+
};
|
|
15472
|
+
}
|
|
15473
|
+
}
|
|
15474
|
+
return null;
|
|
15475
|
+
}
|
|
14895
15476
|
/** Resolved blocks: stored or lazily synthesized (never written back). */
|
|
14896
15477
|
getBlocks() {
|
|
14897
15478
|
const k = this.blocksKey;
|
|
@@ -14912,6 +15493,10 @@ var WeaveTextV2Shape = class extends Konva.Shape {
|
|
|
14912
15493
|
}
|
|
14913
15494
|
/** Replace content with blocks, keeping the `text` mirror in sync. */
|
|
14914
15495
|
setBlocks(blocks) {
|
|
15496
|
+
if (!Array.isArray(blocks)) {
|
|
15497
|
+
this._setAttr("blocks", void 0);
|
|
15498
|
+
return this;
|
|
15499
|
+
}
|
|
14915
15500
|
this._setAttr("blocks", blocks);
|
|
14916
15501
|
this._setAttr("text", blocksToText(blocks));
|
|
14917
15502
|
return this;
|
|
@@ -14949,6 +15534,16 @@ var WeaveTextV2Shape = class extends Konva.Shape {
|
|
|
14949
15534
|
textDecoration(v) {
|
|
14950
15535
|
return this.accessor("textDecoration", "", v);
|
|
14951
15536
|
}
|
|
15537
|
+
/**
|
|
15538
|
+
* Always stroke under the node transform. Weave turns `strokeScaleEnabled`
|
|
15539
|
+
* off while a node is transformed; Konva then resets the canvas transform
|
|
15540
|
+
* before stroking, which only works for path shapes. `strokeText` is not a
|
|
15541
|
+
* path, so the outline was painted at local coordinates on the canvas
|
|
15542
|
+
* origin at stage scale 1 (visible over other nodes while resizing).
|
|
15543
|
+
*/
|
|
15544
|
+
getStrokeScaleEnabled() {
|
|
15545
|
+
return true;
|
|
15546
|
+
}
|
|
14952
15547
|
accessor(name, fallback, value) {
|
|
14953
15548
|
if (value === void 0) return this.attrs[name] ?? fallback;
|
|
14954
15549
|
this.setAttr(name, value);
|
|
@@ -14983,7 +15578,8 @@ var WeaveTextV2Shape = class extends Konva.Shape {
|
|
|
14983
15578
|
lineHeight: this.lineHeight(),
|
|
14984
15579
|
letterSpacing: this.letterSpacing(),
|
|
14985
15580
|
wrap: this.wrap(),
|
|
14986
|
-
align: this.align()
|
|
15581
|
+
align: this.align(),
|
|
15582
|
+
indentStep: this.indentStepValue
|
|
14987
15583
|
};
|
|
14988
15584
|
}
|
|
14989
15585
|
/** Lazy, cached layout. Size accessors and painting force it. */
|
|
@@ -15013,14 +15609,13 @@ var WeaveTextV2Shape = class extends Konva.Shape {
|
|
|
15013
15609
|
};
|
|
15014
15610
|
}
|
|
15015
15611
|
/** Rendered width of a string with this shape's font and letter spacing. */
|
|
15016
|
-
measureText(text) {
|
|
15017
|
-
return canvasMeasurer(fontString(this.style()), text) + text.length * this.letterSpacing();
|
|
15612
|
+
measureText(text, font) {
|
|
15613
|
+
return canvasMeasurer(font ?? fontString(this.style()), text) + text.length * this.letterSpacing();
|
|
15018
15614
|
}
|
|
15019
15615
|
/** Offset of the first line inside the box (padding + vertical align). */
|
|
15020
15616
|
getContentOffset() {
|
|
15021
15617
|
const padding = this.padding();
|
|
15022
|
-
const
|
|
15023
|
-
const used = layout.lines.length * layout.lineHeightPx + padding * 2;
|
|
15618
|
+
const used = this.ensureLayout().contentHeight + padding * 2;
|
|
15024
15619
|
const va = this.verticalAlign();
|
|
15025
15620
|
let alignY = 0;
|
|
15026
15621
|
if (va === "middle") alignY = (this.getHeight() - used) / 2;
|
|
@@ -15058,8 +15653,9 @@ var WeaveTextV2Shape = class extends Konva.Shape {
|
|
|
15058
15653
|
const det = m.a * m.d - m.b * m.c;
|
|
15059
15654
|
if (!det || !Number.isFinite(det)) return null;
|
|
15060
15655
|
const canvas = ctx.getCanvas();
|
|
15061
|
-
const
|
|
15062
|
-
const
|
|
15656
|
+
const backing = canvas._canvas;
|
|
15657
|
+
const w = backing?.width ?? canvas.getWidth() * canvas.getPixelRatio();
|
|
15658
|
+
const h = backing?.height ?? canvas.getHeight() * canvas.getPixelRatio();
|
|
15063
15659
|
if (!(w > 0) || !(h > 0)) return null;
|
|
15064
15660
|
let y1 = Infinity;
|
|
15065
15661
|
let y2 = -Infinity;
|
|
@@ -15083,30 +15679,22 @@ var WeaveTextV2Shape = class extends Konva.Shape {
|
|
|
15083
15679
|
const layout = self.ensureLayout();
|
|
15084
15680
|
if (layout.lines.length === 0) return;
|
|
15085
15681
|
const padding = self.padding();
|
|
15086
|
-
const lineHeightPx = layout.lineHeightPx;
|
|
15087
15682
|
let baseline = "middle";
|
|
15088
|
-
|
|
15089
|
-
if (!Konva.legacyTextRendering) {
|
|
15090
|
-
const m = self.measureMetrics();
|
|
15091
|
-
baseline = "alphabetic";
|
|
15092
|
-
translateY = (m.ascent - m.descent) / 2 + lineHeightPx / 2;
|
|
15093
|
-
}
|
|
15683
|
+
if (!Konva.legacyTextRendering) baseline = "alphabetic";
|
|
15094
15684
|
ctx.setAttr("font", fontString(self.style()));
|
|
15095
15685
|
ctx.setAttr("textBaseline", baseline);
|
|
15096
15686
|
ctx.setAttr("textAlign", "left");
|
|
15097
15687
|
const alignY = self.verticalOffset(layout);
|
|
15098
15688
|
const range = self.visibleRange(ctx);
|
|
15099
15689
|
ctx.translate(padding, alignY + padding);
|
|
15100
|
-
const
|
|
15101
|
-
|
|
15102
|
-
|
|
15103
|
-
if (range && (lineTop + lineHeightPx < range.y1 - lineHeightPx || lineTop > range.y2 + lineHeightPx)) continue;
|
|
15104
|
-
self.paintLine(ctx, layout.lines[n], translateY + n * lineHeightPx);
|
|
15690
|
+
for (const line of layout.lines) {
|
|
15691
|
+
if (range && (alignY + padding + line.y + line.height < range.y1 - line.height || alignY + padding + line.y > range.y2 + line.height)) continue;
|
|
15692
|
+
self.paintLine(ctx, line, line.y + line.baseline);
|
|
15105
15693
|
}
|
|
15106
15694
|
};
|
|
15107
15695
|
/** Vertical offset of the text block inside the box (verticalAlign). */
|
|
15108
15696
|
verticalOffset(layout) {
|
|
15109
|
-
const used = layout.
|
|
15697
|
+
const used = layout.contentHeight + this.padding() * 2;
|
|
15110
15698
|
const va = this.verticalAlign();
|
|
15111
15699
|
if (va === "middle") return (this.getHeight() - used) / 2;
|
|
15112
15700
|
if (va === "bottom") return this.getHeight() - used;
|
|
@@ -15117,7 +15705,21 @@ var WeaveTextV2Shape = class extends Konva.Shape {
|
|
|
15117
15705
|
const fontSize = this.fontSize();
|
|
15118
15706
|
ctx.save();
|
|
15119
15707
|
if (decoration.includes("underline")) this.strokeDecoration(ctx, line, y + Math.round(fontSize / 4));
|
|
15120
|
-
|
|
15708
|
+
if (line.marker) {
|
|
15709
|
+
const mk = line.marker;
|
|
15710
|
+
ctx.setAttr("font", mk.font);
|
|
15711
|
+
this.fillText(ctx, mk.text, mk.x, y, mk.color);
|
|
15712
|
+
}
|
|
15713
|
+
for (const seg of line.segments) {
|
|
15714
|
+
ctx.setAttr("font", seg.font);
|
|
15715
|
+
const x = line.offsetX + seg.x;
|
|
15716
|
+
const isLink = !this.attrs.link && isSafeLink(seg.m?.link);
|
|
15717
|
+
let color = typeof seg.m?.color === "string" ? seg.m.color : void 0;
|
|
15718
|
+
if (isLink) color = this.hoveredRun === `${line.paragraph}:${seg.run}` ? this.linkStyle.hoverColor : this.linkStyle.color;
|
|
15719
|
+
this.paintSegment(ctx, seg, x, y, color);
|
|
15720
|
+
if (seg.m?.underline || isLink) this.strokeSegment(ctx, seg, x, y + Math.round(seg.fontSize / 4), color);
|
|
15721
|
+
if (seg.m?.strike) this.strokeSegment(ctx, seg, x, y - Math.round(seg.fontSize / 4), color);
|
|
15722
|
+
}
|
|
15121
15723
|
if (decoration.includes("line-through")) this.strokeDecoration(ctx, line, y - Math.round(fontSize / 4));
|
|
15122
15724
|
ctx.restore();
|
|
15123
15725
|
}
|
|
@@ -15131,24 +15733,44 @@ var WeaveTextV2Shape = class extends Konva.Shape {
|
|
|
15131
15733
|
ctx.stroke();
|
|
15132
15734
|
ctx.restore();
|
|
15133
15735
|
}
|
|
15134
|
-
|
|
15736
|
+
strokeSegment(ctx, seg, x, y, color) {
|
|
15737
|
+
ctx.save();
|
|
15738
|
+
ctx.beginPath();
|
|
15739
|
+
ctx.moveTo(x, y);
|
|
15740
|
+
ctx.lineTo(x + Math.round(seg.width), y);
|
|
15741
|
+
ctx.lineWidth = seg.fontSize / 15;
|
|
15742
|
+
ctx.strokeStyle = color ?? this.fill();
|
|
15743
|
+
ctx.stroke();
|
|
15744
|
+
ctx.restore();
|
|
15745
|
+
}
|
|
15746
|
+
paintSegment(ctx, seg, x, y, color) {
|
|
15135
15747
|
const spacing = this.letterSpacing();
|
|
15136
15748
|
if (spacing === 0) {
|
|
15137
|
-
this.fillText(ctx,
|
|
15749
|
+
this.fillText(ctx, seg.t, x, y, color);
|
|
15138
15750
|
return;
|
|
15139
15751
|
}
|
|
15140
15752
|
let cx = x;
|
|
15141
|
-
for (const ch of Array.from(
|
|
15142
|
-
this.fillText(ctx, ch, cx, y);
|
|
15143
|
-
cx +=
|
|
15753
|
+
for (const ch of Array.from(seg.t)) {
|
|
15754
|
+
this.fillText(ctx, ch, cx, y, color);
|
|
15755
|
+
cx += canvasMeasurer(seg.font, ch) + spacing;
|
|
15144
15756
|
}
|
|
15145
15757
|
}
|
|
15146
|
-
fillText(ctx, text, x, y) {
|
|
15758
|
+
fillText(ctx, text, x, y, color) {
|
|
15147
15759
|
const self = this;
|
|
15148
15760
|
self._partialText = text;
|
|
15149
15761
|
self._partialTextX = x;
|
|
15150
15762
|
self._partialTextY = y;
|
|
15151
|
-
|
|
15763
|
+
if (color === void 0) {
|
|
15764
|
+
ctx.fillStrokeShape(this);
|
|
15765
|
+
return;
|
|
15766
|
+
}
|
|
15767
|
+
const prev = this.attrs.fill;
|
|
15768
|
+
this.attrs.fill = color;
|
|
15769
|
+
try {
|
|
15770
|
+
ctx.fillStrokeShape(this);
|
|
15771
|
+
} finally {
|
|
15772
|
+
this.attrs.fill = prev;
|
|
15773
|
+
}
|
|
15152
15774
|
}
|
|
15153
15775
|
_hitFunc = (ctx, shape) => {
|
|
15154
15776
|
ctx.beginPath();
|
|
@@ -15264,9 +15886,27 @@ function lineIndexAt(layout, offset) {
|
|
|
15264
15886
|
}
|
|
15265
15887
|
return fallback;
|
|
15266
15888
|
}
|
|
15889
|
+
/** Width of the first `k` UTF-16 units of a line, run by run. */
|
|
15890
|
+
function prefixWidth(line, k, measure) {
|
|
15891
|
+
if (k <= 0) return 0;
|
|
15892
|
+
if (line.segments.length === 0) return measure(line.text.slice(0, k));
|
|
15893
|
+
let width = 0;
|
|
15894
|
+
let pos = 0;
|
|
15895
|
+
for (const seg of line.segments) {
|
|
15896
|
+
const end = pos + seg.t.length;
|
|
15897
|
+
if (k >= end) width += seg.width;
|
|
15898
|
+
else {
|
|
15899
|
+
width += measure(seg.t.slice(0, k - pos), seg.font);
|
|
15900
|
+
break;
|
|
15901
|
+
}
|
|
15902
|
+
pos = end;
|
|
15903
|
+
if (k === end) break;
|
|
15904
|
+
}
|
|
15905
|
+
return width;
|
|
15906
|
+
}
|
|
15267
15907
|
function lineX(line, offset, measure) {
|
|
15268
15908
|
const k = Math.max(0, Math.min(offset - line.start, line.text.length));
|
|
15269
|
-
return line.offsetX + (
|
|
15909
|
+
return line.offsetX + prefixWidth(line, k, measure);
|
|
15270
15910
|
}
|
|
15271
15911
|
/** Caret rectangle in text-local coordinates (before padding/valign offset). */
|
|
15272
15912
|
function caretRect(layout, offset, measure) {
|
|
@@ -15280,16 +15920,22 @@ function caretRect(layout, offset, measure) {
|
|
|
15280
15920
|
};
|
|
15281
15921
|
return {
|
|
15282
15922
|
x: lineX(line, offset, measure),
|
|
15283
|
-
y:
|
|
15284
|
-
height:
|
|
15923
|
+
y: line.y,
|
|
15924
|
+
height: line.height,
|
|
15285
15925
|
line: i
|
|
15286
15926
|
};
|
|
15287
15927
|
}
|
|
15928
|
+
/** Index of the line containing a local y (clamped). */
|
|
15929
|
+
function lineIndexAtY(layout, y) {
|
|
15930
|
+
const lines = layout.lines;
|
|
15931
|
+
for (let i = 0; i < lines.length; i++) if (y < lines[i].y + lines[i].height) return i;
|
|
15932
|
+
return Math.max(0, lines.length - 1);
|
|
15933
|
+
}
|
|
15288
15934
|
/** Offset closest to a point in text-local coordinates. */
|
|
15289
15935
|
function offsetFromPoint(layout, point, measure) {
|
|
15290
15936
|
const lines = layout.lines;
|
|
15291
15937
|
if (lines.length === 0) return 0;
|
|
15292
|
-
return offsetInLine(lines[
|
|
15938
|
+
return offsetInLine(lines[lineIndexAtY(layout, point.y)], point.x, measure);
|
|
15293
15939
|
}
|
|
15294
15940
|
function offsetInLine(line, x, measure) {
|
|
15295
15941
|
const rel = x - line.offsetX;
|
|
@@ -15297,7 +15943,7 @@ function offsetInLine(line, x, measure) {
|
|
|
15297
15943
|
const b = boundaries(line.text, graphemeSeg);
|
|
15298
15944
|
let prevW = 0;
|
|
15299
15945
|
for (let k = 1; k < b.length; k++) {
|
|
15300
|
-
const w =
|
|
15946
|
+
const w = prefixWidth(line, b[k], measure);
|
|
15301
15947
|
if (rel < (prevW + w) / 2) return line.start + b[k - 1];
|
|
15302
15948
|
prevW = w;
|
|
15303
15949
|
}
|
|
@@ -15321,7 +15967,7 @@ function selectionRects(layout, from, to, measure) {
|
|
|
15321
15967
|
if (from > to) [from, to] = [to, from];
|
|
15322
15968
|
if (from === to) return [];
|
|
15323
15969
|
const out = [];
|
|
15324
|
-
layout.lines.forEach((line
|
|
15970
|
+
layout.lines.forEach((line) => {
|
|
15325
15971
|
const hasNewline = line.lastInParagraph;
|
|
15326
15972
|
const lineFrom = Math.max(from, line.start);
|
|
15327
15973
|
const lineTo = Math.min(to, hasNewline ? line.end + 1 : line.end);
|
|
@@ -15332,9 +15978,9 @@ function selectionRects(layout, from, to, measure) {
|
|
|
15332
15978
|
if (x2 <= x1) return;
|
|
15333
15979
|
out.push({
|
|
15334
15980
|
x: x1,
|
|
15335
|
-
y:
|
|
15981
|
+
y: line.y,
|
|
15336
15982
|
width: x2 - x1,
|
|
15337
|
-
height:
|
|
15983
|
+
height: line.height
|
|
15338
15984
|
});
|
|
15339
15985
|
});
|
|
15340
15986
|
return out;
|
|
@@ -15353,6 +15999,11 @@ var TextEditModel = class {
|
|
|
15353
15999
|
focus = 0;
|
|
15354
16000
|
/** x kept while moving vertically; reset by any other move. */
|
|
15355
16001
|
desiredX = null;
|
|
16002
|
+
/**
|
|
16003
|
+
* Marks the next typed text takes at a collapsed caret (e.g. bold toggled
|
|
16004
|
+
* with nothing selected). Cleared when the selection moves.
|
|
16005
|
+
*/
|
|
16006
|
+
storedMarks = null;
|
|
15356
16007
|
undoStack = [];
|
|
15357
16008
|
redoStack = [];
|
|
15358
16009
|
lastKind = null;
|
|
@@ -15389,12 +16040,14 @@ var TextEditModel = class {
|
|
|
15389
16040
|
const len = this.getText().length;
|
|
15390
16041
|
this.anchor = Math.max(0, Math.min(anchor, len));
|
|
15391
16042
|
this.focus = Math.max(0, Math.min(focus, len));
|
|
16043
|
+
this.storedMarks = null;
|
|
15392
16044
|
this.breakCoalescing();
|
|
15393
16045
|
}
|
|
15394
16046
|
moveTo(offset, extend = false, keepDesiredX = false) {
|
|
15395
16047
|
const len = this.getText().length;
|
|
15396
16048
|
const o = Math.max(0, Math.min(offset, len));
|
|
15397
16049
|
this.focus = o;
|
|
16050
|
+
this.storedMarks = null;
|
|
15398
16051
|
if (!extend) this.anchor = o;
|
|
15399
16052
|
if (!keepDesiredX) this.desiredX = null;
|
|
15400
16053
|
this.breakCoalescing();
|
|
@@ -15402,6 +16055,7 @@ var TextEditModel = class {
|
|
|
15402
16055
|
selectAll() {
|
|
15403
16056
|
this.anchor = 0;
|
|
15404
16057
|
this.focus = this.getText().length;
|
|
16058
|
+
this.storedMarks = null;
|
|
15405
16059
|
this.breakCoalescing();
|
|
15406
16060
|
}
|
|
15407
16061
|
breakCoalescing() {
|
|
@@ -15437,7 +16091,14 @@ var TextEditModel = class {
|
|
|
15437
16091
|
end = Math.max(start, Math.min(end, len));
|
|
15438
16092
|
if (start === end && text === "") return false;
|
|
15439
16093
|
this.record(kind, start);
|
|
15440
|
-
this.
|
|
16094
|
+
const carried = this.storedMarks ?? marksAtOffset(this.blocks, start);
|
|
16095
|
+
this.blocks = replaceRange(this.blocks, start, end, text, this.storedMarks ?? void 0);
|
|
16096
|
+
this.storedMarks = null;
|
|
16097
|
+
if (text.endsWith("\n") && carried) {
|
|
16098
|
+
const { link: _link, ...rest } = carried;
|
|
16099
|
+
const next = cleanMarks(rest);
|
|
16100
|
+
if (next && !marksAtOffset(this.blocks, start + text.length)) this.storedMarks = next;
|
|
16101
|
+
}
|
|
15441
16102
|
this.anchor = this.focus = start + text.length;
|
|
15442
16103
|
this.lastEditEnd = this.focus;
|
|
15443
16104
|
this.desiredX = null;
|
|
@@ -15477,14 +16138,102 @@ var TextEditModel = class {
|
|
|
15477
16138
|
this.restore(next);
|
|
15478
16139
|
return true;
|
|
15479
16140
|
}
|
|
16141
|
+
/** Current marks at the caret / selection start (stored marks win). */
|
|
16142
|
+
caretMarks() {
|
|
16143
|
+
return this.storedMarks ?? marksAtOffset(this.blocks, this.getSelection().start);
|
|
16144
|
+
}
|
|
16145
|
+
/** Always one new undo entry, never coalesced. */
|
|
16146
|
+
commitFormat(next) {
|
|
16147
|
+
if (JSON.stringify(next) === JSON.stringify(this.blocks)) return false;
|
|
16148
|
+
this.lastKind = null;
|
|
16149
|
+
this.record("format", -1);
|
|
16150
|
+
this.lastKind = null;
|
|
16151
|
+
this.blocks = next;
|
|
16152
|
+
return true;
|
|
16153
|
+
}
|
|
16154
|
+
/** Set (`undefined` clears) one mark on the selection or the stored marks. */
|
|
16155
|
+
setMark(key, value) {
|
|
16156
|
+
const { start, end } = this.getSelection();
|
|
16157
|
+
if (start === end) {
|
|
16158
|
+
const base = { ...this.caretMarks() ?? {} };
|
|
16159
|
+
if (value === void 0 || value === false) delete base[key];
|
|
16160
|
+
else base[key] = value;
|
|
16161
|
+
this.storedMarks = cleanMarks(base) ?? {};
|
|
16162
|
+
return true;
|
|
16163
|
+
}
|
|
16164
|
+
return this.commitFormat(applyMark(this.blocks, start, end, key, value));
|
|
16165
|
+
}
|
|
16166
|
+
/** Toggle a boolean mark: on unless the whole selection already has it. */
|
|
16167
|
+
toggleMark(key) {
|
|
16168
|
+
const { start, end } = this.getSelection();
|
|
16169
|
+
const q = queryMark(this.blocks, start, end, key, this.storedMarks);
|
|
16170
|
+
return this.setMark(key, q.state === "all" ? void 0 : true);
|
|
16171
|
+
}
|
|
16172
|
+
toggleList(kind) {
|
|
16173
|
+
const { start, end } = this.getSelection();
|
|
16174
|
+
return this.commitFormat(toggleList(this.blocks, start, end, kind));
|
|
16175
|
+
}
|
|
16176
|
+
setAlign(align) {
|
|
16177
|
+
const { start, end } = this.getSelection();
|
|
16178
|
+
return this.commitFormat(setBlockAlign(this.blocks, start, end, align));
|
|
16179
|
+
}
|
|
16180
|
+
indent() {
|
|
16181
|
+
const { start, end } = this.getSelection();
|
|
16182
|
+
return this.commitFormat(indentBlocks(this.blocks, start, end));
|
|
16183
|
+
}
|
|
16184
|
+
outdent() {
|
|
16185
|
+
const { start, end } = this.getSelection();
|
|
16186
|
+
return this.commitFormat(outdentBlocks(this.blocks, start, end));
|
|
16187
|
+
}
|
|
16188
|
+
blockAt(offset) {
|
|
16189
|
+
let pos = 0;
|
|
16190
|
+
for (let i = 0; i < this.blocks.length; i++) {
|
|
16191
|
+
const len = blockText(this.blocks[i]).length;
|
|
16192
|
+
if (offset <= pos + len) return {
|
|
16193
|
+
index: i,
|
|
16194
|
+
start: pos,
|
|
16195
|
+
length: len
|
|
16196
|
+
};
|
|
16197
|
+
pos += len + 1;
|
|
16198
|
+
}
|
|
16199
|
+
}
|
|
16200
|
+
/**
|
|
16201
|
+
* Enter in a list: an empty item exits the list; otherwise the item
|
|
16202
|
+
* continues (the block split carries `kind` and `depth`).
|
|
16203
|
+
*/
|
|
16204
|
+
enter() {
|
|
16205
|
+
const { start, end } = this.getSelection();
|
|
16206
|
+
const b = start === end ? this.blockAt(start) : void 0;
|
|
16207
|
+
if (b && this.blocks[b.index].kind && b.length === 0) return this.commitFormat(outdentToPlain(this.blocks, b.index));
|
|
16208
|
+
return this.replaceRange(start, end, "\n", "other");
|
|
16209
|
+
}
|
|
16210
|
+
/**
|
|
16211
|
+
* Backspace at the start of a list item outdents it, or removes the
|
|
16212
|
+
* marker at depth 0. Returns false when the key is not handled here.
|
|
16213
|
+
*/
|
|
16214
|
+
backspaceInList() {
|
|
16215
|
+
const { start, end } = this.getSelection();
|
|
16216
|
+
if (start !== end) return false;
|
|
16217
|
+
const b = this.blockAt(start);
|
|
16218
|
+
if (!b || b.start !== start || !this.blocks[b.index].kind) return false;
|
|
16219
|
+
return this.commitFormat(outdentBlocks(this.blocks, start, start));
|
|
16220
|
+
}
|
|
15480
16221
|
restore(s) {
|
|
15481
16222
|
this.blocks = s.blocks;
|
|
16223
|
+
this.storedMarks = null;
|
|
15482
16224
|
this.anchor = s.anchor;
|
|
15483
16225
|
this.focus = s.focus;
|
|
15484
16226
|
this.lastKind = null;
|
|
15485
16227
|
this.desiredX = null;
|
|
15486
16228
|
}
|
|
15487
16229
|
};
|
|
16230
|
+
function outdentToPlain(blocks, index) {
|
|
16231
|
+
return blocks.map((b, i) => {
|
|
16232
|
+
if (i !== index) return b;
|
|
16233
|
+
const { kind: _k, depth: _d, start: _s, ...rest } = b;
|
|
16234
|
+
return rest;
|
|
16235
|
+
});
|
|
16236
|
+
}
|
|
15488
16237
|
const SLICE_BUDGET_MS = 8;
|
|
15489
16238
|
const BLINK_MS = 530;
|
|
15490
16239
|
var TextEditSession = class {
|
|
@@ -15542,7 +16291,7 @@ var TextEditSession = class {
|
|
|
15542
16291
|
};
|
|
15543
16292
|
if (this.host.selectAllOnStart) this.model.selectAll();
|
|
15544
16293
|
this.syncTextarea();
|
|
15545
|
-
ta.focus();
|
|
16294
|
+
ta.focus({ preventScroll: true });
|
|
15546
16295
|
this.positionTextarea();
|
|
15547
16296
|
this.startBlink();
|
|
15548
16297
|
this.repaint();
|
|
@@ -15571,7 +16320,7 @@ var TextEditSession = class {
|
|
|
15571
16320
|
if (w.weaveTextEditing) delete w.weaveTextEditing[this.id];
|
|
15572
16321
|
}
|
|
15573
16322
|
focus() {
|
|
15574
|
-
this.textarea?.focus();
|
|
16323
|
+
this.textarea?.focus({ preventScroll: true });
|
|
15575
16324
|
}
|
|
15576
16325
|
createTextarea() {
|
|
15577
16326
|
const ta = this.host.container.ownerDocument.createElement("textarea");
|
|
@@ -15620,8 +16369,12 @@ var TextEditSession = class {
|
|
|
15620
16369
|
if (!ta) return;
|
|
15621
16370
|
const c = this.caretStagePoint();
|
|
15622
16371
|
const p = this.host.stageToContainer(c);
|
|
15623
|
-
|
|
15624
|
-
|
|
16372
|
+
this.host.revealCaret?.(p);
|
|
16373
|
+
const box = this.host.container;
|
|
16374
|
+
const maxX = Math.max(0, (box.clientWidth || Infinity) - 2);
|
|
16375
|
+
const maxY = Math.max(0, (box.clientHeight || Infinity) - 20);
|
|
16376
|
+
ta.style.left = `${Math.min(Math.max(p.x, 0), maxX)}px`;
|
|
16377
|
+
ta.style.top = `${Math.min(Math.max(p.y, 0), maxY)}px`;
|
|
15625
16378
|
}
|
|
15626
16379
|
caretStagePoint() {
|
|
15627
16380
|
const r = caretRect(this.target.getLayout(), this.model.focus, this.target.measure);
|
|
@@ -15653,10 +16406,41 @@ var TextEditSession = class {
|
|
|
15653
16406
|
if (e.key === "Escape") this.host.onRequestExit("escape");
|
|
15654
16407
|
else if (isUndo) this.undo();
|
|
15655
16408
|
else if (isRedo) this.redo();
|
|
16409
|
+
else if (this.handleRichKey(e, mod, key)) return true;
|
|
15656
16410
|
else if (e.key === "Tab") return true;
|
|
15657
16411
|
else return false;
|
|
15658
16412
|
return true;
|
|
15659
16413
|
}
|
|
16414
|
+
/** Format shortcuts and list key rules; only when rich text is enabled. */
|
|
16415
|
+
handleRichKey(e, mod, key) {
|
|
16416
|
+
if (!this.host.richText) return false;
|
|
16417
|
+
if (mod && !e.shiftKey && !e.altKey) {
|
|
16418
|
+
const marks = {
|
|
16419
|
+
b: "bold",
|
|
16420
|
+
i: "italic",
|
|
16421
|
+
u: "underline"
|
|
16422
|
+
};
|
|
16423
|
+
if (marks[key]) {
|
|
16424
|
+
this.toggleMark(marks[key]);
|
|
16425
|
+
return true;
|
|
16426
|
+
}
|
|
16427
|
+
}
|
|
16428
|
+
if (mod || this.composing) return false;
|
|
16429
|
+
if (e.key === "Tab") {
|
|
16430
|
+
if (e.shiftKey) this.outdent();
|
|
16431
|
+
else this.indent();
|
|
16432
|
+
return true;
|
|
16433
|
+
}
|
|
16434
|
+
if (e.key === "Enter") {
|
|
16435
|
+
if (this.model.enter()) this.afterFormat("command", true);
|
|
16436
|
+
return true;
|
|
16437
|
+
}
|
|
16438
|
+
if (e.key === "Backspace" && this.model.backspaceInList()) {
|
|
16439
|
+
this.afterFormat("command", true);
|
|
16440
|
+
return true;
|
|
16441
|
+
}
|
|
16442
|
+
return false;
|
|
16443
|
+
}
|
|
15660
16444
|
handleNavigationKey(e) {
|
|
15661
16445
|
if (e.ctrlKey || e.metaKey) return false;
|
|
15662
16446
|
if ((e.key === "ArrowUp" || e.key === "ArrowDown") && !e.altKey) {
|
|
@@ -15710,7 +16494,15 @@ var TextEditSession = class {
|
|
|
15710
16494
|
while (p < max && oldText[p] === newText[p]) p++;
|
|
15711
16495
|
let s = 0;
|
|
15712
16496
|
while (s < max - p && oldText[oldText.length - 1 - s] === newText[newText.length - 1 - s]) s++;
|
|
15713
|
-
|
|
16497
|
+
let inserted = newText.slice(p, newText.length - s);
|
|
16498
|
+
if (s + p === oldText.length && inserted !== "") {
|
|
16499
|
+
const q = ta.selectionEnd - inserted.length;
|
|
16500
|
+
if (q >= 0 && q < p && newText.slice(q, q + inserted.length) === inserted && oldText.slice(0, q) + inserted + oldText.slice(q) === newText) {
|
|
16501
|
+
s = oldText.length - q;
|
|
16502
|
+
p = q;
|
|
16503
|
+
inserted = newText.slice(p, newText.length - s);
|
|
16504
|
+
}
|
|
16505
|
+
}
|
|
15714
16506
|
const inputType = e.inputType ?? "";
|
|
15715
16507
|
const kind = this.editKindFor(inputType);
|
|
15716
16508
|
this.model.replaceRange(p, oldText.length - s, inserted, kind);
|
|
@@ -15751,6 +16543,7 @@ var TextEditSession = class {
|
|
|
15751
16543
|
if (this.model.undo()) {
|
|
15752
16544
|
this.syncTextarea();
|
|
15753
16545
|
this.afterContentChange(0, true);
|
|
16546
|
+
this.host.onFormatChange?.("undo");
|
|
15754
16547
|
}
|
|
15755
16548
|
}
|
|
15756
16549
|
redo() {
|
|
@@ -15758,8 +16551,42 @@ var TextEditSession = class {
|
|
|
15758
16551
|
if (this.model.redo()) {
|
|
15759
16552
|
this.syncTextarea();
|
|
15760
16553
|
this.afterContentChange(0, true);
|
|
16554
|
+
this.host.onFormatChange?.("redo");
|
|
15761
16555
|
}
|
|
15762
16556
|
}
|
|
16557
|
+
afterFormat(reason, content = false) {
|
|
16558
|
+
this.flushPending();
|
|
16559
|
+
this.syncTextarea();
|
|
16560
|
+
if (content) this.afterContentChange(0, true);
|
|
16561
|
+
else this.applyContent();
|
|
16562
|
+
this.afterSelectionChange();
|
|
16563
|
+
this.host.onFormatChange?.(reason);
|
|
16564
|
+
}
|
|
16565
|
+
toggleMark(key) {
|
|
16566
|
+
if (this.model.toggleMark(key)) this.afterFormat("command");
|
|
16567
|
+
}
|
|
16568
|
+
setMark(key, value) {
|
|
16569
|
+
if (this.model.setMark(key, value)) this.afterFormat("command");
|
|
16570
|
+
}
|
|
16571
|
+
toggleList(kind) {
|
|
16572
|
+
if (this.model.toggleList(kind)) this.afterFormat("command");
|
|
16573
|
+
}
|
|
16574
|
+
setAlign(align) {
|
|
16575
|
+
if (this.model.setAlign(align)) this.afterFormat("command");
|
|
16576
|
+
}
|
|
16577
|
+
indent() {
|
|
16578
|
+
if (this.model.indent()) this.afterFormat("command");
|
|
16579
|
+
}
|
|
16580
|
+
outdent() {
|
|
16581
|
+
if (this.model.outdent()) this.afterFormat("command");
|
|
16582
|
+
}
|
|
16583
|
+
getSelection() {
|
|
16584
|
+
return this.model.getSelection();
|
|
16585
|
+
}
|
|
16586
|
+
getFormat() {
|
|
16587
|
+
const { start, end } = this.model.getSelection();
|
|
16588
|
+
return getSelectionFormat(this.model.getBlocks(), start, end, this.target.getFontSize(), this.model.storedMarks);
|
|
16589
|
+
}
|
|
15763
16590
|
toLocal(e) {
|
|
15764
16591
|
const stagePt = this.host.clientToStage(e.clientX, e.clientY);
|
|
15765
16592
|
const p = this.target.getAbsoluteTransform().copy().invert().point(stagePt);
|
|
@@ -15774,12 +16601,26 @@ var TextEditSession = class {
|
|
|
15774
16601
|
const { width, height } = this.target.getSize();
|
|
15775
16602
|
return local.x + o.x >= 0 && local.y + o.y >= 0 && local.x + o.x <= width && local.y + o.y <= height;
|
|
15776
16603
|
}
|
|
16604
|
+
/**
|
|
16605
|
+
* True for pointer targets inside an element the app marked with
|
|
16606
|
+
* `data-weave-text-edit-keep` (e.g. a formatting toolbar): they neither
|
|
16607
|
+
* end the session nor, unless they are form fields, steal focus.
|
|
16608
|
+
*/
|
|
16609
|
+
isKeepTarget(e) {
|
|
16610
|
+
const t = e.target;
|
|
16611
|
+
return typeof Element !== "undefined" && t instanceof Element && !!t.closest("[data-weave-text-edit-keep]");
|
|
16612
|
+
}
|
|
15777
16613
|
onMouseDown = (e) => {
|
|
16614
|
+
if (this.isKeepTarget(e)) {
|
|
16615
|
+
if (!e.target.closest("input, textarea, select, [contenteditable=\"true\"]")) e.preventDefault();
|
|
16616
|
+
return;
|
|
16617
|
+
}
|
|
15778
16618
|
if (this.isInside(this.toLocal(e))) e.preventDefault();
|
|
15779
16619
|
};
|
|
15780
16620
|
onPointerDown = (e) => {
|
|
15781
16621
|
if (e.button !== 0 && e.pointerType === "mouse") return;
|
|
15782
16622
|
if (this.textarea && e.target === this.textarea) return;
|
|
16623
|
+
if (this.isKeepTarget(e)) return;
|
|
15783
16624
|
const local = this.toLocal(e);
|
|
15784
16625
|
if (!this.isInside(local)) {
|
|
15785
16626
|
e.stopPropagation();
|
|
@@ -15790,10 +16631,11 @@ var TextEditSession = class {
|
|
|
15790
16631
|
e.preventDefault();
|
|
15791
16632
|
const offset = offsetFromPoint(this.target.getLayout(), local, this.target.measure);
|
|
15792
16633
|
const text = this.model.getText();
|
|
15793
|
-
|
|
16634
|
+
const clicks = this.countClicks(e);
|
|
16635
|
+
if (clicks >= 3) {
|
|
15794
16636
|
const [s, en] = paragraphRangeAt(text, offset);
|
|
15795
16637
|
this.model.setSelection(s, en);
|
|
15796
|
-
} else if (
|
|
16638
|
+
} else if (clicks === 2) {
|
|
15797
16639
|
const [s, en] = wordRangeAt(text, offset);
|
|
15798
16640
|
this.model.setSelection(s, en);
|
|
15799
16641
|
} else {
|
|
@@ -15802,8 +16644,31 @@ var TextEditSession = class {
|
|
|
15802
16644
|
}
|
|
15803
16645
|
this.syncTextarea();
|
|
15804
16646
|
this.afterSelectionChange();
|
|
15805
|
-
this.textarea?.focus();
|
|
16647
|
+
this.textarea?.focus({ preventScroll: true });
|
|
15806
16648
|
};
|
|
16649
|
+
lastClick = {
|
|
16650
|
+
t: 0,
|
|
16651
|
+
x: 0,
|
|
16652
|
+
y: 0,
|
|
16653
|
+
n: 0
|
|
16654
|
+
};
|
|
16655
|
+
/**
|
|
16656
|
+
* Browsers report `detail` as 0 on pointerdown, so multi-click is counted
|
|
16657
|
+
* here (same spot, within 500 ms). A real `detail` still wins.
|
|
16658
|
+
*/
|
|
16659
|
+
countClicks(e) {
|
|
16660
|
+
const now = e.timeStamp || Date.now();
|
|
16661
|
+
const l = this.lastClick;
|
|
16662
|
+
const near = now - l.t < 500 && Math.hypot(e.clientX - l.x, e.clientY - l.y) < 5 && l.n > 0;
|
|
16663
|
+
const n = Math.max(e.detail, near ? l.n + 1 : 1);
|
|
16664
|
+
this.lastClick = {
|
|
16665
|
+
t: now,
|
|
16666
|
+
x: e.clientX,
|
|
16667
|
+
y: e.clientY,
|
|
16668
|
+
n: n >= 3 ? 0 : n
|
|
16669
|
+
};
|
|
16670
|
+
return n;
|
|
16671
|
+
}
|
|
15807
16672
|
onPointerMove = (e) => {
|
|
15808
16673
|
if (!this.dragging) return;
|
|
15809
16674
|
const local = this.toLocal(e);
|
|
@@ -15815,13 +16680,14 @@ var TextEditSession = class {
|
|
|
15815
16680
|
onPointerUp = () => {
|
|
15816
16681
|
if (!this.dragging) return;
|
|
15817
16682
|
this.dragging = false;
|
|
15818
|
-
this.textarea?.focus();
|
|
16683
|
+
this.textarea?.focus({ preventScroll: true });
|
|
15819
16684
|
};
|
|
15820
16685
|
afterSelectionChange() {
|
|
15821
16686
|
this.syncTextarea();
|
|
15822
16687
|
this.positionTextarea();
|
|
15823
16688
|
this.resetBlink();
|
|
15824
16689
|
this.repaint();
|
|
16690
|
+
this.host.onSelectionChange?.();
|
|
15825
16691
|
}
|
|
15826
16692
|
afterContentChange(insertedLength, immediate = false) {
|
|
15827
16693
|
this.flushPending();
|
|
@@ -15845,6 +16711,7 @@ var TextEditSession = class {
|
|
|
15845
16711
|
applyContent() {
|
|
15846
16712
|
this.target.setBlocks(this.model.getBlocks());
|
|
15847
16713
|
this.host.onChange?.(this.model.getBlocks());
|
|
16714
|
+
this.host.onSelectionChange?.();
|
|
15848
16715
|
this.positionTextarea();
|
|
15849
16716
|
this.target.requestRedraw();
|
|
15850
16717
|
this.repaint();
|
|
@@ -15920,15 +16787,29 @@ function host_signal(signal, abort) {
|
|
|
15920
16787
|
//#region src/nodes/text-v2/constants.ts
|
|
15921
16788
|
/** Default threshold for `onTextSizeWarning` (chars of serialized blocks). */
|
|
15922
16789
|
const WEAVE_TEXT_V2_SIZE_WARNING_THRESHOLD = 2e5;
|
|
16790
|
+
/** Default list indent per depth, as a multiple of the node `fontSize`. */
|
|
16791
|
+
const WEAVE_TEXT_V2_DEFAULT_INDENT_STEP = 1.5;
|
|
15923
16792
|
const WEAVE_TEXT_V2_SELECTION_COLOR = "rgba(66, 133, 244, 0.35)";
|
|
15924
16793
|
const WEAVE_TEXT_V2_DEFAULT_CONFIG = {
|
|
15925
16794
|
...WEAVE_TEXT_NODE_DEFAULT_CONFIG,
|
|
15926
|
-
edition: {
|
|
15927
|
-
|
|
15928
|
-
|
|
15929
|
-
|
|
15930
|
-
|
|
15931
|
-
|
|
16795
|
+
edition: {
|
|
16796
|
+
outline: {
|
|
16797
|
+
enabled: true,
|
|
16798
|
+
color: "#0074ff",
|
|
16799
|
+
width: 1,
|
|
16800
|
+
padding: 0
|
|
16801
|
+
},
|
|
16802
|
+
followPadding: {
|
|
16803
|
+
top: 40,
|
|
16804
|
+
right: 40,
|
|
16805
|
+
bottom: 40,
|
|
16806
|
+
left: 40
|
|
16807
|
+
}
|
|
16808
|
+
},
|
|
16809
|
+
richText: {
|
|
16810
|
+
enabled: false,
|
|
16811
|
+
indentStep: WEAVE_TEXT_V2_DEFAULT_INDENT_STEP
|
|
16812
|
+
},
|
|
15932
16813
|
sizeWarning: { threshold: WEAVE_TEXT_V2_SIZE_WARNING_THRESHOLD }
|
|
15933
16814
|
};
|
|
15934
16815
|
//#endregion
|
|
@@ -15956,6 +16837,7 @@ var WeaveTextV2Node = class extends WeaveNode {
|
|
|
15956
16837
|
editingNodeId = null;
|
|
15957
16838
|
session = null;
|
|
15958
16839
|
sizeWarned = false;
|
|
16840
|
+
selectionTimer;
|
|
15959
16841
|
nodeRenderedAddedRegistered = false;
|
|
15960
16842
|
keyPressHandler;
|
|
15961
16843
|
constructor(params) {
|
|
@@ -16029,6 +16911,11 @@ var WeaveTextV2Node = class extends WeaveNode {
|
|
|
16029
16911
|
linkPreviousTextDecoration
|
|
16030
16912
|
}
|
|
16031
16913
|
});
|
|
16914
|
+
text.setIndentStep(this.indentStep());
|
|
16915
|
+
text.setLinkStyle({
|
|
16916
|
+
color: this.config.link.defaultColor,
|
|
16917
|
+
hoverColor: this.config.link.hoverColor
|
|
16918
|
+
});
|
|
16032
16919
|
this.setupDefaultNodeAugmentation(text);
|
|
16033
16920
|
const defaultTransformerProperties = this.defaultGetTransformerProperties(this.config.transform);
|
|
16034
16921
|
text.getTransformerProperties = function() {
|
|
@@ -16124,7 +17011,7 @@ var WeaveTextV2Node = class extends WeaveNode {
|
|
|
16124
17011
|
this.applySize(shape);
|
|
16125
17012
|
},
|
|
16126
17013
|
getLayout: () => shape.ensureLayout(),
|
|
16127
|
-
measure: (t) => shape.measureText(t),
|
|
17014
|
+
measure: (t, f) => shape.measureText(t, f),
|
|
16128
17015
|
getAbsoluteTransform: () => shape.getAbsoluteTransform(),
|
|
16129
17016
|
getContentOffset: () => shape.getContentOffset(),
|
|
16130
17017
|
getSize: () => ({
|
|
@@ -16175,7 +17062,23 @@ var WeaveTextV2Node = class extends WeaveNode {
|
|
|
16175
17062
|
y: p.y * sy
|
|
16176
17063
|
};
|
|
16177
17064
|
},
|
|
17065
|
+
revealCaret: (p) => {
|
|
17066
|
+
const box = container;
|
|
17067
|
+
const w = box.clientWidth;
|
|
17068
|
+
const h = box.clientHeight;
|
|
17069
|
+
if (!w || !h) return;
|
|
17070
|
+
const f = this.config.edition.followPadding;
|
|
17071
|
+
const dx = p.x < f.left ? f.left - p.x : p.x > w - f.right ? w - f.right - p.x : 0;
|
|
17072
|
+
const dy = p.y < f.top ? f.top - p.y : p.y > h - f.bottom ? h - f.bottom - p.y : 0;
|
|
17073
|
+
if (!dx && !dy) return;
|
|
17074
|
+
stage.x(stage.x() + dx);
|
|
17075
|
+
stage.y(stage.y() + dy);
|
|
17076
|
+
this.instance.emitEvent("onStageMove");
|
|
17077
|
+
},
|
|
16178
17078
|
selectAllOnStart: !create,
|
|
17079
|
+
richText: this.isRich(shape),
|
|
17080
|
+
onSelectionChange: () => this.scheduleSelectionEvent(shape),
|
|
17081
|
+
onFormatChange: (reason) => this.emitFormatChange(shape, reason),
|
|
16179
17082
|
caretColor: this.config.cursor.color,
|
|
16180
17083
|
selectionColor: WEAVE_TEXT_V2_SELECTION_COLOR,
|
|
16181
17084
|
...this.config.edition.outline.enabled && {
|
|
@@ -16211,6 +17114,7 @@ var WeaveTextV2Node = class extends WeaveNode {
|
|
|
16211
17114
|
exitEditMode(shape) {
|
|
16212
17115
|
const session = this.session;
|
|
16213
17116
|
if (!session || !this.editing) return;
|
|
17117
|
+
clearTimeout(this.selectionTimer);
|
|
16214
17118
|
const { changed } = session.getResult();
|
|
16215
17119
|
session.destroy();
|
|
16216
17120
|
this.session = null;
|
|
@@ -16273,11 +17177,7 @@ var WeaveTextV2Node = class extends WeaveNode {
|
|
|
16273
17177
|
text.getLayer()?.batchDraw();
|
|
16274
17178
|
}
|
|
16275
17179
|
const layout = text.getAttrs().layout;
|
|
16276
|
-
if (this.isSelecting() && this.isNodeSelected(text) && (layout !== TEXT_LAYOUT.SMART || !isCorner()))
|
|
16277
|
-
text.width(text.width() * text.scaleX());
|
|
16278
|
-
resetScale(text);
|
|
16279
|
-
text.fontSize(text.fontSize() * text.scaleY());
|
|
16280
|
-
}
|
|
17180
|
+
if (this.isSelecting() && this.isNodeSelected(text) && (layout !== TEXT_LAYOUT.SMART || !isCorner())) resetScale(text);
|
|
16281
17181
|
text.setAttr("shouldUpdateOnTransform", false);
|
|
16282
17182
|
text.getLayer()?.batchDraw();
|
|
16283
17183
|
});
|
|
@@ -16321,6 +17221,7 @@ var WeaveTextV2Node = class extends WeaveNode {
|
|
|
16321
17221
|
};
|
|
16322
17222
|
const isLinked = Boolean(nextProps.link);
|
|
16323
17223
|
const { linkPreviousFill, linkPreviousTextDecoration } = this.linkOverrides(nextProps);
|
|
17224
|
+
shape.setIndentStep(this.indentStep());
|
|
16324
17225
|
shape.setAttrs({
|
|
16325
17226
|
...nextProps,
|
|
16326
17227
|
...this.outlineProps(),
|
|
@@ -16358,7 +17259,7 @@ var WeaveTextV2Node = class extends WeaveNode {
|
|
|
16358
17259
|
delete cleanedAttrs.dragBoundFunc;
|
|
16359
17260
|
if (cleanedAttrs.link) {
|
|
16360
17261
|
if (typeof cleanedAttrs.linkPreviousFill === "string") cleanedAttrs.fill = cleanedAttrs.linkPreviousFill;
|
|
16361
|
-
|
|
17262
|
+
cleanedAttrs.textDecoration = typeof cleanedAttrs.linkPreviousTextDecoration === "string" ? cleanedAttrs.linkPreviousTextDecoration : "";
|
|
16362
17263
|
}
|
|
16363
17264
|
return {
|
|
16364
17265
|
key: attrs.id ?? "",
|
|
@@ -16373,18 +17274,85 @@ var WeaveTextV2Node = class extends WeaveNode {
|
|
|
16373
17274
|
}
|
|
16374
17275
|
};
|
|
16375
17276
|
}
|
|
17277
|
+
linkCtxMenuGuardRegistered = false;
|
|
17278
|
+
linkHoverText = null;
|
|
17279
|
+
linkModifier = false;
|
|
17280
|
+
linkHoverRefresh = null;
|
|
17281
|
+
linkHitAt(text) {
|
|
17282
|
+
const link = text.getAttrs().link;
|
|
17283
|
+
if (link) {
|
|
17284
|
+
const p = this.localPointer(text);
|
|
17285
|
+
return p.x >= 0 && p.y >= 0 && p.x <= text.width() && p.y <= text.height() ? { url: link } : null;
|
|
17286
|
+
}
|
|
17287
|
+
return text.linkAtPoint(this.localPointer(text));
|
|
17288
|
+
}
|
|
17289
|
+
registerLinkContextMenuGuard() {
|
|
17290
|
+
if (this.linkCtxMenuGuardRegistered || this.instance.isServerSide()) return;
|
|
17291
|
+
this.linkCtxMenuGuardRegistered = true;
|
|
17292
|
+
const stage = this.instance.getStage();
|
|
17293
|
+
const signal = this.instance.getEventsController().signal;
|
|
17294
|
+
const syncModifier = (e) => {
|
|
17295
|
+
const next = e.ctrlKey || e.metaKey;
|
|
17296
|
+
if (next === this.linkModifier) return;
|
|
17297
|
+
this.linkModifier = next;
|
|
17298
|
+
this.linkHoverRefresh?.();
|
|
17299
|
+
};
|
|
17300
|
+
window.addEventListener("keydown", syncModifier, { signal });
|
|
17301
|
+
window.addEventListener("keyup", syncModifier, { signal });
|
|
17302
|
+
window.addEventListener("blur", () => {
|
|
17303
|
+
this.linkModifier = false;
|
|
17304
|
+
this.linkHoverRefresh?.();
|
|
17305
|
+
}, { signal });
|
|
17306
|
+
stage.container().addEventListener("contextmenu", (e) => {
|
|
17307
|
+
if (this.editing || !e.ctrlKey) return;
|
|
17308
|
+
const text = this.linkHoverText;
|
|
17309
|
+
if (!text) return;
|
|
17310
|
+
stage.setPointersPositions(e);
|
|
17311
|
+
const hit = this.linkHitAt(text);
|
|
17312
|
+
if (!hit) return;
|
|
17313
|
+
e.preventDefault();
|
|
17314
|
+
e.stopPropagation();
|
|
17315
|
+
this.openUrl(hit.url);
|
|
17316
|
+
}, {
|
|
17317
|
+
capture: true,
|
|
17318
|
+
signal: this.instance.getEventsController().signal
|
|
17319
|
+
});
|
|
17320
|
+
}
|
|
16376
17321
|
setupLinkBehavior(text) {
|
|
16377
|
-
|
|
16378
|
-
|
|
16379
|
-
|
|
16380
|
-
|
|
17322
|
+
this.registerLinkContextMenuGuard();
|
|
17323
|
+
const container = () => this.instance.getStage().container();
|
|
17324
|
+
const idleCursor = () => this.isNodeSelected(text) ? "grab" : "pointer";
|
|
17325
|
+
const applyHover = () => {
|
|
17326
|
+
if (this.editing) return;
|
|
17327
|
+
this.linkHoverText = text;
|
|
17328
|
+
const hit = this.linkModifier ? this.linkHitAt(text) : null;
|
|
17329
|
+
if (!text.getAttrs().link) text.setHoveredRun(hit?.key ?? null);
|
|
17330
|
+
else {
|
|
17331
|
+
text.fill(hit ? this.config.link.hoverColor : this.config.link.defaultColor);
|
|
17332
|
+
text.getLayer()?.batchDraw();
|
|
17333
|
+
}
|
|
17334
|
+
container().style.cursor = hit ? "pointer" : idleCursor();
|
|
17335
|
+
};
|
|
17336
|
+
const updateHover = (e) => {
|
|
17337
|
+
if (e?.evt) this.linkModifier = e.evt.ctrlKey || e.evt.metaKey;
|
|
17338
|
+
this.linkHoverRefresh = applyHover;
|
|
17339
|
+
applyHover();
|
|
17340
|
+
};
|
|
17341
|
+
text.handleMouseover = (e) => {
|
|
17342
|
+
updateHover(e);
|
|
16381
17343
|
};
|
|
16382
17344
|
text.handleMouseout = () => {
|
|
17345
|
+
if (this.linkHoverText === text) {
|
|
17346
|
+
this.linkHoverText = null;
|
|
17347
|
+
this.linkHoverRefresh = null;
|
|
17348
|
+
}
|
|
17349
|
+
text.setHoveredRun(null);
|
|
16383
17350
|
if (!text.getAttrs().link) return;
|
|
16384
17351
|
text.fill(this.config.link.defaultColor);
|
|
16385
17352
|
text.getLayer()?.batchDraw();
|
|
16386
17353
|
};
|
|
16387
|
-
text.
|
|
17354
|
+
text.on("pointermove", updateHover);
|
|
17355
|
+
text.defineMousePointer = () => this.linkModifier && text.getAttrs().link ? "pointer" : idleCursor();
|
|
16388
17356
|
let linkClickTimeout;
|
|
16389
17357
|
const clearLinkClickTimeout = () => {
|
|
16390
17358
|
if (linkClickTimeout) {
|
|
@@ -16398,27 +17366,41 @@ var WeaveTextV2Node = class extends WeaveNode {
|
|
|
16398
17366
|
previousDblClick();
|
|
16399
17367
|
};
|
|
16400
17368
|
text.click = ({ wasSelected, ctrlOrMetaPressed }) => {
|
|
16401
|
-
|
|
17369
|
+
const runLink = text.getAttrs().link ? null : text.linkAtPoint(this.localPointer(text));
|
|
17370
|
+
if (!text.getAttrs().link && !runLink) return;
|
|
17371
|
+
const open = () => runLink ? this.openUrl(runLink.url) : this.openTextLink(text);
|
|
16402
17372
|
if (ctrlOrMetaPressed) {
|
|
16403
17373
|
clearLinkClickTimeout();
|
|
16404
|
-
|
|
17374
|
+
open();
|
|
16405
17375
|
return;
|
|
16406
17376
|
}
|
|
16407
17377
|
if (wasSelected) {
|
|
16408
17378
|
clearLinkClickTimeout();
|
|
16409
17379
|
linkClickTimeout = setTimeout(() => {
|
|
16410
17380
|
linkClickTimeout = void 0;
|
|
16411
|
-
|
|
17381
|
+
open();
|
|
16412
17382
|
}, Konva.dblClickWindow);
|
|
16413
17383
|
}
|
|
16414
17384
|
};
|
|
16415
17385
|
}
|
|
16416
|
-
|
|
17386
|
+
localPointer(text) {
|
|
17387
|
+
return text.getRelativePointerPosition() ?? {
|
|
17388
|
+
x: -1,
|
|
17389
|
+
y: -1
|
|
17390
|
+
};
|
|
17391
|
+
}
|
|
17392
|
+
lastLinkOpen = 0;
|
|
17393
|
+
openUrl(link) {
|
|
16417
17394
|
if (this.instance.isServerSide()) return;
|
|
16418
|
-
const link = textNode.getAttrs().link;
|
|
16419
17395
|
if (typeof link !== "string" || !isValidTextLink(link)) return;
|
|
17396
|
+
const now = Date.now();
|
|
17397
|
+
if (now - this.lastLinkOpen < 500) return;
|
|
17398
|
+
this.lastLinkOpen = now;
|
|
16420
17399
|
window.open(link, "_blank", "noopener,noreferrer");
|
|
16421
17400
|
}
|
|
17401
|
+
openTextLink(textNode) {
|
|
17402
|
+
this.openUrl(textNode.getAttrs().link);
|
|
17403
|
+
}
|
|
16422
17404
|
getLink(nodeInstance) {
|
|
16423
17405
|
const link = nodeInstance.getAttrs().link;
|
|
16424
17406
|
return typeof link === "string" ? link : void 0;
|
|
@@ -16438,6 +17420,173 @@ var WeaveTextV2Node = class extends WeaveNode {
|
|
|
16438
17420
|
delete serialized.props.linkPreviousTextDecoration;
|
|
16439
17421
|
this.instance.updateNode(serialized);
|
|
16440
17422
|
}
|
|
17423
|
+
indentStep() {
|
|
17424
|
+
const step = this.config.richText.indentStep;
|
|
17425
|
+
return Number.isFinite(step) && step > 0 ? step : 1.5;
|
|
17426
|
+
}
|
|
17427
|
+
/**
|
|
17428
|
+
* Rich text is on for a node when its `richText` attribute says so;
|
|
17429
|
+
* nodes without the attribute follow `config.richText.enabled`.
|
|
17430
|
+
*/
|
|
17431
|
+
isRich(nodeInstance) {
|
|
17432
|
+
const own = nodeInstance.getAttrs().richText;
|
|
17433
|
+
return typeof own === "boolean" ? own : this.config.richText.enabled;
|
|
17434
|
+
}
|
|
17435
|
+
isRichTextEnabled(nodeInstance) {
|
|
17436
|
+
return nodeInstance ? this.isRich(nodeInstance) : this.config.richText.enabled;
|
|
17437
|
+
}
|
|
17438
|
+
activeSession(shape) {
|
|
17439
|
+
return this.editing && this.editingNodeId === shape.id() ? this.session : null;
|
|
17440
|
+
}
|
|
17441
|
+
emitFormatChange(shape, reason) {
|
|
17442
|
+
this.instance.emitEvent("onTextFormatChange", {
|
|
17443
|
+
node: shape,
|
|
17444
|
+
format: this.getSelectionFormat(shape),
|
|
17445
|
+
reason
|
|
17446
|
+
});
|
|
17447
|
+
}
|
|
17448
|
+
/** Selection events fire often while dragging: coalesce them. */
|
|
17449
|
+
scheduleSelectionEvent(shape) {
|
|
17450
|
+
if (this.selectionTimer) return;
|
|
17451
|
+
this.selectionTimer = setTimeout(() => {
|
|
17452
|
+
this.selectionTimer = void 0;
|
|
17453
|
+
const session = this.activeSession(shape);
|
|
17454
|
+
if (!session) return;
|
|
17455
|
+
const { start, end } = session.getSelection();
|
|
17456
|
+
this.instance.emitEvent("onTextSelectionChange", {
|
|
17457
|
+
node: shape,
|
|
17458
|
+
start,
|
|
17459
|
+
end,
|
|
17460
|
+
format: session.getFormat()
|
|
17461
|
+
});
|
|
17462
|
+
}, 50);
|
|
17463
|
+
}
|
|
17464
|
+
/**
|
|
17465
|
+
* Format of the active selection while editing, otherwise of `range`
|
|
17466
|
+
* (default: the whole text). Sizes are px.
|
|
17467
|
+
*/
|
|
17468
|
+
getSelectionFormat(nodeInstance, range) {
|
|
17469
|
+
const shape = nodeInstance;
|
|
17470
|
+
const session = this.activeSession(shape);
|
|
17471
|
+
if (session) return session.getFormat();
|
|
17472
|
+
const blocks = shape.getBlocks();
|
|
17473
|
+
const r = range ?? {
|
|
17474
|
+
start: 0,
|
|
17475
|
+
end: blocksToText(blocks).length
|
|
17476
|
+
};
|
|
17477
|
+
return getSelectionFormat(blocks, r.start, r.end, shape.fontSize());
|
|
17478
|
+
}
|
|
17479
|
+
/**
|
|
17480
|
+
* Run a block transform on a non-editing node: one `updateNode`, so one
|
|
17481
|
+
* Weave undo step. Returns false when rich text is disabled.
|
|
17482
|
+
*/
|
|
17483
|
+
commandOnNode(shape, range, fn) {
|
|
17484
|
+
const blocks = shape.getBlocks();
|
|
17485
|
+
const next = fn(blocks, range?.start ?? 0, range?.end ?? blocksToText(blocks).length);
|
|
17486
|
+
if (JSON.stringify(next) === JSON.stringify(blocks)) return false;
|
|
17487
|
+
shape.setBlocks(next);
|
|
17488
|
+
this.applySize(shape);
|
|
17489
|
+
this.instance.updateNode(this.serialize(shape));
|
|
17490
|
+
this.emitFormatChange(shape, "command");
|
|
17491
|
+
return true;
|
|
17492
|
+
}
|
|
17493
|
+
/**
|
|
17494
|
+
* Set a mark (`undefined` clears it). `size` is in px. While the node is
|
|
17495
|
+
* being edited it applies to the editor selection, otherwise to `range`
|
|
17496
|
+
* (default: the whole text).
|
|
17497
|
+
*/
|
|
17498
|
+
applyMark(nodeInstance, key, value, range) {
|
|
17499
|
+
const shape = nodeInstance;
|
|
17500
|
+
if (!this.isRich(shape)) return false;
|
|
17501
|
+
let v = value;
|
|
17502
|
+
if (key === "link" && v !== void 0 && !isValidTextLink(String(v))) return false;
|
|
17503
|
+
if (key === "size" && typeof value === "number") v = sizeToRatio(value, shape.fontSize());
|
|
17504
|
+
const session = this.activeSession(shape);
|
|
17505
|
+
if (session) {
|
|
17506
|
+
session.setMark(key, v);
|
|
17507
|
+
return true;
|
|
17508
|
+
}
|
|
17509
|
+
return this.commandOnNode(shape, range, (b, s, e) => applyMark(b, s, e, key, v));
|
|
17510
|
+
}
|
|
17511
|
+
/** Link a range (or the editor selection) to an http(s) URL. */
|
|
17512
|
+
setRunLink(nodeInstance, url, range) {
|
|
17513
|
+
if (typeof url !== "string" || !isValidTextLink(url)) return false;
|
|
17514
|
+
return this.applyMark(nodeInstance, "link", url, range);
|
|
17515
|
+
}
|
|
17516
|
+
removeRunLink(nodeInstance, range) {
|
|
17517
|
+
return this.applyMark(nodeInstance, "link", void 0, range);
|
|
17518
|
+
}
|
|
17519
|
+
toggleMark(nodeInstance, key, range) {
|
|
17520
|
+
const shape = nodeInstance;
|
|
17521
|
+
if (!this.isRich(shape)) return false;
|
|
17522
|
+
const session = this.activeSession(shape);
|
|
17523
|
+
if (session) {
|
|
17524
|
+
session.toggleMark(key);
|
|
17525
|
+
return true;
|
|
17526
|
+
}
|
|
17527
|
+
return this.commandOnNode(shape, range, (b, s, e) => toggleMark(b, s, e, key));
|
|
17528
|
+
}
|
|
17529
|
+
toggleList(nodeInstance, kind, range) {
|
|
17530
|
+
const shape = nodeInstance;
|
|
17531
|
+
if (!this.isRich(shape)) return false;
|
|
17532
|
+
const session = this.activeSession(shape);
|
|
17533
|
+
if (session) {
|
|
17534
|
+
session.toggleList(kind);
|
|
17535
|
+
return true;
|
|
17536
|
+
}
|
|
17537
|
+
return this.commandOnNode(shape, range, (b, s, e) => toggleList(b, s, e, kind));
|
|
17538
|
+
}
|
|
17539
|
+
/** Align every paragraph touched by the range (default: all). */
|
|
17540
|
+
setAlign(nodeInstance, align, range) {
|
|
17541
|
+
const shape = nodeInstance;
|
|
17542
|
+
if (!this.isRich(shape)) return false;
|
|
17543
|
+
if (align !== void 0 && ![
|
|
17544
|
+
"left",
|
|
17545
|
+
"center",
|
|
17546
|
+
"right"
|
|
17547
|
+
].includes(align)) return false;
|
|
17548
|
+
const session = this.activeSession(shape);
|
|
17549
|
+
if (session) {
|
|
17550
|
+
session.setAlign(align);
|
|
17551
|
+
return true;
|
|
17552
|
+
}
|
|
17553
|
+
return this.commandOnNode(shape, range, (b, s, e) => setBlockAlign(b, s, e, align));
|
|
17554
|
+
}
|
|
17555
|
+
indent(nodeInstance, range) {
|
|
17556
|
+
const shape = nodeInstance;
|
|
17557
|
+
if (!this.isRich(shape)) return false;
|
|
17558
|
+
const session = this.activeSession(shape);
|
|
17559
|
+
if (session) {
|
|
17560
|
+
session.indent();
|
|
17561
|
+
return true;
|
|
17562
|
+
}
|
|
17563
|
+
return this.commandOnNode(shape, range, indentBlocks);
|
|
17564
|
+
}
|
|
17565
|
+
outdent(nodeInstance, range) {
|
|
17566
|
+
const shape = nodeInstance;
|
|
17567
|
+
if (!this.isRich(shape)) return false;
|
|
17568
|
+
const session = this.activeSession(shape);
|
|
17569
|
+
if (session) {
|
|
17570
|
+
session.outdent();
|
|
17571
|
+
return true;
|
|
17572
|
+
}
|
|
17573
|
+
return this.commandOnNode(shape, range, outdentBlocks);
|
|
17574
|
+
}
|
|
17575
|
+
/**
|
|
17576
|
+
* Scale the whole text: sets the node `fontSize` (px); size marks are
|
|
17577
|
+
* ratios of it, so every run scales with it.
|
|
17578
|
+
*/
|
|
17579
|
+
setBaseFontSize(nodeInstance, px) {
|
|
17580
|
+
const shape = nodeInstance;
|
|
17581
|
+
if (!this.isRich(shape)) return false;
|
|
17582
|
+
if (!Number.isFinite(px) || px <= 0) return false;
|
|
17583
|
+
if (shape.fontSize() === px) return false;
|
|
17584
|
+
shape.fontSize(px);
|
|
17585
|
+
this.applySize(shape);
|
|
17586
|
+
if (!this.activeSession(shape)) this.instance.updateNode(this.serialize(shape));
|
|
17587
|
+
this.emitFormatChange(shape, "base-size");
|
|
17588
|
+
return true;
|
|
17589
|
+
}
|
|
16441
17590
|
resetSmartLayout(textNode) {
|
|
16442
17591
|
textNode.setAttr("smartFixedWidth", void 0);
|
|
16443
17592
|
textNode.width(Math.max(1, textNode.measureNaturalSize().width * 1.01));
|
|
@@ -16456,7 +17605,36 @@ var WeaveTextV2Node = class extends WeaveNode {
|
|
|
16456
17605
|
return next;
|
|
16457
17606
|
}
|
|
16458
17607
|
static getSchema() {
|
|
16459
|
-
|
|
17608
|
+
const base = WeaveTextNode.getSchema();
|
|
17609
|
+
const marks = z.object({
|
|
17610
|
+
bold: z.boolean().optional(),
|
|
17611
|
+
italic: z.boolean().optional(),
|
|
17612
|
+
underline: z.boolean().optional(),
|
|
17613
|
+
strike: z.boolean().optional(),
|
|
17614
|
+
color: z.string().optional(),
|
|
17615
|
+
size: z.number().positive().optional().describe("Ratio of the node fontSize (1 is omitted)."),
|
|
17616
|
+
family: z.string().optional(),
|
|
17617
|
+
lineHeight: z.number().positive().optional(),
|
|
17618
|
+
link: z.string().url().refine((value) => isValidTextLink(value), { message: "Link must be an absolute http:// or https:// URL." }).optional()
|
|
17619
|
+
}).passthrough();
|
|
17620
|
+
const block = z.object({
|
|
17621
|
+
runs: z.array(z.object({
|
|
17622
|
+
t: z.string(),
|
|
17623
|
+
m: marks.optional()
|
|
17624
|
+
})),
|
|
17625
|
+
kind: z.enum(["ul", "ol"]).optional(),
|
|
17626
|
+
depth: z.number().int().min(0).optional(),
|
|
17627
|
+
start: z.number().int().min(1).optional(),
|
|
17628
|
+
align: z.enum([
|
|
17629
|
+
"left",
|
|
17630
|
+
"center",
|
|
17631
|
+
"right"
|
|
17632
|
+
]).optional()
|
|
17633
|
+
});
|
|
17634
|
+
return base.extend({ props: base.shape.props.extend({
|
|
17635
|
+
richText: z.boolean().optional().describe("Enables rich text commands for this node."),
|
|
17636
|
+
blocks: z.array(block).optional().describe("Rich content: paragraphs of runs with marks and optional list fields. `text` is the derived plain mirror.")
|
|
17637
|
+
}) });
|
|
16460
17638
|
}
|
|
16461
17639
|
};
|
|
16462
17640
|
//#endregion
|
|
@@ -26978,8 +28156,16 @@ var WeaveTextToolAction = class extends WeaveAction {
|
|
|
26978
28156
|
cancelAction;
|
|
26979
28157
|
onPropsChange = void 0;
|
|
26980
28158
|
onInit = void 0;
|
|
26981
|
-
|
|
28159
|
+
toolName;
|
|
28160
|
+
initialProps;
|
|
28161
|
+
/**
|
|
28162
|
+
* `name` and `props` let an app register several text tools, e.g. a
|
|
28163
|
+
* plain one and one creating rich text nodes (`props: { richText: true }`).
|
|
28164
|
+
*/
|
|
28165
|
+
constructor(params) {
|
|
26982
28166
|
super();
|
|
28167
|
+
this.toolName = params?.name ?? "textTool";
|
|
28168
|
+
this.initialProps = params?.props ?? {};
|
|
26983
28169
|
this.initialize();
|
|
26984
28170
|
}
|
|
26985
28171
|
initialize() {
|
|
@@ -26991,7 +28177,7 @@ var WeaveTextToolAction = class extends WeaveAction {
|
|
|
26991
28177
|
this.props = this.initProps();
|
|
26992
28178
|
}
|
|
26993
28179
|
getName() {
|
|
26994
|
-
return
|
|
28180
|
+
return this.toolName;
|
|
26995
28181
|
}
|
|
26996
28182
|
initProps() {
|
|
26997
28183
|
return {
|
|
@@ -27003,7 +28189,8 @@ var WeaveTextToolAction = class extends WeaveAction {
|
|
|
27003
28189
|
align: "left",
|
|
27004
28190
|
lineHeight: 1,
|
|
27005
28191
|
verticalAlign: "top",
|
|
27006
|
-
strokeEnabled: false
|
|
28192
|
+
strokeEnabled: false,
|
|
28193
|
+
...this.initialProps
|
|
27007
28194
|
};
|
|
27008
28195
|
}
|
|
27009
28196
|
setupEvents() {
|
|
@@ -35114,15 +36301,12 @@ var WeaveNodesSnappingPlugin = class extends WeavePlugin {
|
|
|
35114
36301
|
register(instance) {
|
|
35115
36302
|
super.register(instance);
|
|
35116
36303
|
const hooks = this.instance.getHooks();
|
|
35117
|
-
|
|
35118
|
-
|
|
35119
|
-
|
|
35120
|
-
|
|
35121
|
-
})
|
|
35122
|
-
|
|
35123
|
-
let containerId = "";
|
|
35124
|
-
if (node.getAttrs().containerId) containerId = node.id();
|
|
35125
|
-
if (node && this.snappingManagerCustomGuides.isCustomGuidesVisible(containerId)) this.snappingManagerCustomGuides.renderCustomGuides(node.id());
|
|
36304
|
+
const guidedContainers = (nodes) => nodes.filter((node) => node.getAttrs().containerId && this.snappingManagerCustomGuides.isCustomGuidesVisible(node.id()));
|
|
36305
|
+
hooks.hook("weave:onTransformerDragStart", ({ nodes }) => {
|
|
36306
|
+
for (const node of guidedContainers(nodes)) this.snappingManagerCustomGuides.hideCustomGuides(node.id());
|
|
36307
|
+
});
|
|
36308
|
+
hooks.hook("weave:onTransformerDragEnd", ({ nodes }) => {
|
|
36309
|
+
for (const node of guidedContainers(nodes)) this.snappingManagerCustomGuides.renderCustomGuides(node.id());
|
|
35126
36310
|
});
|
|
35127
36311
|
return this;
|
|
35128
36312
|
}
|