@unhingged/vizu-core 0.1.2 → 0.1.3
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/auto.cjs +663 -1850
- package/dist/auto.cjs.map +1 -1
- package/dist/auto.js +289 -57
- package/dist/auto.js.map +1 -1
- package/dist/index.cjs +662 -1849
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +0 -39
- package/dist/index.d.ts +0 -39
- package/dist/index.js +289 -57
- package/dist/index.js.map +1 -1
- package/dist/vizu.min.js +55 -234
- package/dist/vizu.min.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-OMIFOSQ2.js +0 -295
- package/dist/chunk-OMIFOSQ2.js.map +0 -1
- package/dist/live-collab-BVLNJ5QI.js +0 -1098
- package/dist/live-collab-BVLNJ5QI.js.map +0 -1
package/dist/auto.js
CHANGED
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
findByFingerprint,
|
|
3
|
-
findElementByFingerprint,
|
|
4
|
-
fingerprint,
|
|
5
|
-
fingerprintKey,
|
|
6
|
-
fingerprintLabel
|
|
7
|
-
} from "./chunk-OMIFOSQ2.js";
|
|
8
|
-
|
|
9
1
|
// src/types.ts
|
|
10
2
|
var SCHEMA_VERSION = 2;
|
|
11
3
|
|
|
@@ -365,18 +357,6 @@ var CloudStorageAdapter = class {
|
|
|
365
357
|
async preflightAuth() {
|
|
366
358
|
await this.resolveToken();
|
|
367
359
|
}
|
|
368
|
-
/**
|
|
369
|
-
* Synchronous accessor for the currently cached bearer token, used
|
|
370
|
-
* by sibling modules that need to authenticate against the same
|
|
371
|
-
* backend (live-collab cross-origin polling). Returns null when no
|
|
372
|
-
* valid token is cached — callers fall back to credentials:'include'
|
|
373
|
-
* for same-origin or just disable themselves.
|
|
374
|
-
*/
|
|
375
|
-
getCachedAuthToken() {
|
|
376
|
-
if (!this.cachedToken) return null;
|
|
377
|
-
if (this.isExpired(this.cachedToken)) return null;
|
|
378
|
-
return this.cachedToken.token;
|
|
379
|
-
}
|
|
380
360
|
/* ─── StorageAdapter v2 ───────────────────────────────────────────── */
|
|
381
361
|
async load(_namespace) {
|
|
382
362
|
const out = [];
|
|
@@ -692,10 +672,297 @@ function apiErr(status, body) {
|
|
|
692
672
|
return err;
|
|
693
673
|
}
|
|
694
674
|
|
|
675
|
+
// src/fingerprint.ts
|
|
676
|
+
var TEXT_SNIPPET_MAX = 80;
|
|
677
|
+
var ANCESTOR_DEPTH = 4;
|
|
678
|
+
var ANCESTOR_MAX_SHIFT = 3;
|
|
679
|
+
var TEXT_RATIO_THRESHOLD = 0.75;
|
|
680
|
+
var CLASS_JACCARD_THRESHOLD = 0.6;
|
|
681
|
+
function buildSelector(el) {
|
|
682
|
+
const parts = [];
|
|
683
|
+
let current = el;
|
|
684
|
+
let depth = 0;
|
|
685
|
+
while (current && current !== document.documentElement && depth < 8) {
|
|
686
|
+
let part = current.tagName.toLowerCase();
|
|
687
|
+
if (current.id) {
|
|
688
|
+
parts.unshift("#" + CSS.escape(current.id));
|
|
689
|
+
break;
|
|
690
|
+
}
|
|
691
|
+
const cls = Array.from(current.classList).filter((c) => !c.startsWith("vz-") && !c.startsWith("vizu-")).slice(0, 2);
|
|
692
|
+
if (cls.length) part += "." + cls.map((c) => CSS.escape(c)).join(".");
|
|
693
|
+
const parent = current.parentElement;
|
|
694
|
+
if (parent) {
|
|
695
|
+
const sameTag = Array.from(parent.children).filter((c) => c.tagName === current.tagName);
|
|
696
|
+
if (sameTag.length > 1) {
|
|
697
|
+
const idx = sameTag.indexOf(current);
|
|
698
|
+
part += ":nth-of-type(" + (idx + 1) + ")";
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
parts.unshift(part);
|
|
702
|
+
current = parent;
|
|
703
|
+
depth++;
|
|
704
|
+
}
|
|
705
|
+
return parts.join(" > ");
|
|
706
|
+
}
|
|
707
|
+
function fingerprint(el) {
|
|
708
|
+
const text = el.innerText || el.textContent || "";
|
|
709
|
+
const parent = el.parentElement;
|
|
710
|
+
let siblingIndex = 0;
|
|
711
|
+
if (parent) {
|
|
712
|
+
siblingIndex = Array.from(parent.children).indexOf(el);
|
|
713
|
+
}
|
|
714
|
+
return {
|
|
715
|
+
selector: buildSelector(el),
|
|
716
|
+
parentSelector: parent ? buildSelector(parent) : "",
|
|
717
|
+
tagName: el.tagName,
|
|
718
|
+
textSnippet: text.trim().slice(0, TEXT_SNIPPET_MAX),
|
|
719
|
+
siblingIndex,
|
|
720
|
+
attributes: {
|
|
721
|
+
id: el.id || void 0,
|
|
722
|
+
classList: Array.from(el.classList).filter((c) => !c.startsWith("vz-") && !c.startsWith("vizu-")),
|
|
723
|
+
role: el.getAttribute("role") || void 0,
|
|
724
|
+
ariaLabel: el.getAttribute("aria-label") || void 0,
|
|
725
|
+
dataKey: el.getAttribute("data-vizu-key") || void 0
|
|
726
|
+
},
|
|
727
|
+
ancestorChain: captureAncestorChain(el, ANCESTOR_DEPTH),
|
|
728
|
+
algorithmVersion: 2
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
function captureAncestorChain(el, depth) {
|
|
732
|
+
const steps = [];
|
|
733
|
+
let current = el.parentElement;
|
|
734
|
+
while (current && steps.length < depth) {
|
|
735
|
+
const parent = current.parentElement;
|
|
736
|
+
let nthOfType = 1;
|
|
737
|
+
if (parent) {
|
|
738
|
+
const sameTag = Array.from(parent.children).filter((c) => c.tagName === current.tagName);
|
|
739
|
+
nthOfType = sameTag.indexOf(current) + 1;
|
|
740
|
+
}
|
|
741
|
+
steps.push({ tag: current.tagName, nthOfType });
|
|
742
|
+
current = parent;
|
|
743
|
+
}
|
|
744
|
+
return steps.length > 0 ? { steps } : void 0;
|
|
745
|
+
}
|
|
746
|
+
function findByFingerprint(fp, root = document) {
|
|
747
|
+
if (fp.attributes.dataKey) {
|
|
748
|
+
try {
|
|
749
|
+
const byKey = root.querySelector(`[data-vizu-key="${CSS.escape(fp.attributes.dataKey)}"]`);
|
|
750
|
+
if (byKey) return { element: byKey, confidence: "exact" };
|
|
751
|
+
} catch {
|
|
752
|
+
}
|
|
753
|
+
return { element: null, confidence: "orphaned" };
|
|
754
|
+
}
|
|
755
|
+
if (fp.attributes.id) {
|
|
756
|
+
try {
|
|
757
|
+
const byId = root.querySelector("#" + CSS.escape(fp.attributes.id));
|
|
758
|
+
if (byId) return { element: byId, confidence: "exact" };
|
|
759
|
+
} catch {
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
const candidates = [];
|
|
763
|
+
const classes = fp.attributes.classList;
|
|
764
|
+
if (classes && classes.length > 0) {
|
|
765
|
+
const m = findByClassSignature(root, fp.tagName, classes);
|
|
766
|
+
if (m?.element) candidates.push({ el: m.element, rung: "class" });
|
|
767
|
+
}
|
|
768
|
+
try {
|
|
769
|
+
const bySelector = root.querySelector(fp.selector);
|
|
770
|
+
if (bySelector) candidates.push({ el: bySelector, rung: "selector" });
|
|
771
|
+
} catch {
|
|
772
|
+
}
|
|
773
|
+
if (fp.ancestorChain && fp.ancestorChain.steps.length > 0) {
|
|
774
|
+
const m = findByAncestorChain(root, fp.tagName, fp.ancestorChain.steps);
|
|
775
|
+
if (m?.element) candidates.push({ el: m.element, rung: "chain" });
|
|
776
|
+
} else {
|
|
777
|
+
try {
|
|
778
|
+
const parent = fp.parentSelector ? root.querySelector(fp.parentSelector) : root;
|
|
779
|
+
if (parent) {
|
|
780
|
+
const candidate = parent.children[fp.siblingIndex];
|
|
781
|
+
if (candidate && candidate.tagName === fp.tagName) {
|
|
782
|
+
candidates.push({ el: candidate, rung: "chain" });
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
} catch {
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
if (fp.textSnippet) {
|
|
789
|
+
const m = findByTextSnippet(root, fp.tagName, fp.textSnippet);
|
|
790
|
+
if (m?.element) candidates.push({ el: m.element, rung: "text" });
|
|
791
|
+
}
|
|
792
|
+
if (candidates.length === 0) {
|
|
793
|
+
return { element: null, confidence: "orphaned" };
|
|
794
|
+
}
|
|
795
|
+
const votes = /* @__PURE__ */ new Map();
|
|
796
|
+
for (const c of candidates) {
|
|
797
|
+
let s = votes.get(c.el);
|
|
798
|
+
if (!s) {
|
|
799
|
+
s = /* @__PURE__ */ new Set();
|
|
800
|
+
votes.set(c.el, s);
|
|
801
|
+
}
|
|
802
|
+
s.add(c.rung);
|
|
803
|
+
}
|
|
804
|
+
let best = null;
|
|
805
|
+
for (const [el, rungs] of votes) {
|
|
806
|
+
if (!best || rungs.size > best.rungs.size) best = { el, rungs };
|
|
807
|
+
}
|
|
808
|
+
if (!best) return { element: null, confidence: "orphaned" };
|
|
809
|
+
if (best.rungs.size >= 3) return { element: best.el, confidence: "likely" };
|
|
810
|
+
if (best.rungs.size >= 2) return { element: best.el, confidence: "drifted" };
|
|
811
|
+
if (best.rungs.has("class") || best.rungs.has("chain")) {
|
|
812
|
+
return { element: best.el, confidence: "drifted" };
|
|
813
|
+
}
|
|
814
|
+
return { element: null, confidence: "orphaned" };
|
|
815
|
+
}
|
|
816
|
+
function findByClassSignature(root, tagName, needle) {
|
|
817
|
+
const needleSet = new Set(needle.filter((c) => !c.startsWith("vz-") && !c.startsWith("vizu-")));
|
|
818
|
+
if (needleSet.size === 0) return null;
|
|
819
|
+
const all = root.querySelectorAll(tagName.toLowerCase());
|
|
820
|
+
let best = null;
|
|
821
|
+
let secondScore = 0;
|
|
822
|
+
for (const el of all) {
|
|
823
|
+
const haystack = new Set(
|
|
824
|
+
Array.from(el.classList).filter((c) => !c.startsWith("vz-") && !c.startsWith("vizu-"))
|
|
825
|
+
);
|
|
826
|
+
if (haystack.size === 0) continue;
|
|
827
|
+
const score = jaccardSimilarity(needleSet, haystack);
|
|
828
|
+
if (score < CLASS_JACCARD_THRESHOLD) continue;
|
|
829
|
+
if (!best || score > best.score) {
|
|
830
|
+
secondScore = best?.score ?? 0;
|
|
831
|
+
best = { el, score };
|
|
832
|
+
} else if (score > secondScore) {
|
|
833
|
+
secondScore = score;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
if (!best) return null;
|
|
837
|
+
if (best.score - secondScore < 0.1) return null;
|
|
838
|
+
return { element: best.el, confidence: "likely" };
|
|
839
|
+
}
|
|
840
|
+
function findByAncestorChain(root, tagName, steps) {
|
|
841
|
+
const all = root.querySelectorAll(tagName.toLowerCase());
|
|
842
|
+
let best = null;
|
|
843
|
+
for (const el of all) {
|
|
844
|
+
const ancestors = collectAncestorSteps(el, steps.length + ANCESTOR_MAX_SHIFT);
|
|
845
|
+
const score = scoreChainAgainstAncestors(ancestors, steps, ANCESTOR_MAX_SHIFT);
|
|
846
|
+
if (score < 2) continue;
|
|
847
|
+
if (!best || score > best.score) best = { el, score };
|
|
848
|
+
}
|
|
849
|
+
if (!best) return null;
|
|
850
|
+
return {
|
|
851
|
+
element: best.el,
|
|
852
|
+
confidence: best.score === steps.length ? "likely" : "drifted"
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
function collectAncestorSteps(el, limit) {
|
|
856
|
+
const steps = [];
|
|
857
|
+
let current = el.parentElement;
|
|
858
|
+
while (current && steps.length < limit) {
|
|
859
|
+
const parent = current.parentElement;
|
|
860
|
+
let nth = 1;
|
|
861
|
+
if (parent) {
|
|
862
|
+
const sameTag = Array.from(parent.children).filter((c) => c.tagName === current.tagName);
|
|
863
|
+
nth = sameTag.indexOf(current) + 1;
|
|
864
|
+
}
|
|
865
|
+
steps.push({ tag: current.tagName, nthOfType: nth });
|
|
866
|
+
current = parent;
|
|
867
|
+
}
|
|
868
|
+
return steps;
|
|
869
|
+
}
|
|
870
|
+
function findByTextSnippet(root, tagName, snippet) {
|
|
871
|
+
const needle = normalizeText(snippet);
|
|
872
|
+
if (!needle) return null;
|
|
873
|
+
const all = root.querySelectorAll(tagName.toLowerCase());
|
|
874
|
+
let best = null;
|
|
875
|
+
for (const el of all) {
|
|
876
|
+
const raw = el.innerText || el.textContent || "";
|
|
877
|
+
const candidate = normalizeText(raw.slice(0, TEXT_SNIPPET_MAX * 2));
|
|
878
|
+
if (!candidate) continue;
|
|
879
|
+
const score = levenshteinRatio(needle, candidate);
|
|
880
|
+
if (score < TEXT_RATIO_THRESHOLD) continue;
|
|
881
|
+
if (!best || score > best.score) {
|
|
882
|
+
best = { el, score };
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
return best ? { element: best.el, confidence: "drifted" } : null;
|
|
886
|
+
}
|
|
887
|
+
function normalizeText(input) {
|
|
888
|
+
return input.toLowerCase().replace(/\s+/g, " ").trim();
|
|
889
|
+
}
|
|
890
|
+
function levenshteinRatio(a, b) {
|
|
891
|
+
if (a === b) return 1;
|
|
892
|
+
if (a.length === 0 || b.length === 0) return a.length === b.length ? 1 : 0;
|
|
893
|
+
const maxLen = Math.max(a.length, b.length);
|
|
894
|
+
const distance = levenshteinDistance(a, b);
|
|
895
|
+
return 1 - distance / maxLen;
|
|
896
|
+
}
|
|
897
|
+
function levenshteinDistance(a, b) {
|
|
898
|
+
if (a.length > b.length) {
|
|
899
|
+
const tmp = a;
|
|
900
|
+
a = b;
|
|
901
|
+
b = tmp;
|
|
902
|
+
}
|
|
903
|
+
const prev = new Array(a.length + 1);
|
|
904
|
+
const curr = new Array(a.length + 1);
|
|
905
|
+
for (let i = 0; i <= a.length; i++) prev[i] = i;
|
|
906
|
+
for (let j = 1; j <= b.length; j++) {
|
|
907
|
+
curr[0] = j;
|
|
908
|
+
for (let i = 1; i <= a.length; i++) {
|
|
909
|
+
const cost = a.charCodeAt(i - 1) === b.charCodeAt(j - 1) ? 0 : 1;
|
|
910
|
+
curr[i] = Math.min(
|
|
911
|
+
curr[i - 1] + 1,
|
|
912
|
+
// insert
|
|
913
|
+
prev[i] + 1,
|
|
914
|
+
// delete
|
|
915
|
+
prev[i - 1] + cost
|
|
916
|
+
// substitute
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
for (let i = 0; i <= a.length; i++) prev[i] = curr[i];
|
|
920
|
+
}
|
|
921
|
+
return prev[a.length];
|
|
922
|
+
}
|
|
923
|
+
function scoreChainAgainstAncestors(actual, captured, maxShift) {
|
|
924
|
+
let best = 0;
|
|
925
|
+
for (let offset = 0; offset <= maxShift; offset++) {
|
|
926
|
+
let score = 0;
|
|
927
|
+
for (let i = 0; i < captured.length; i++) {
|
|
928
|
+
const a = actual[i + offset];
|
|
929
|
+
if (!a) break;
|
|
930
|
+
if (a.tag === captured[i].tag && a.nthOfType === captured[i].nthOfType) {
|
|
931
|
+
score++;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
if (score > best) best = score;
|
|
935
|
+
}
|
|
936
|
+
return best;
|
|
937
|
+
}
|
|
938
|
+
function jaccardSimilarity(a, b) {
|
|
939
|
+
if (a.size === 0 && b.size === 0) return 1;
|
|
940
|
+
let intersection = 0;
|
|
941
|
+
for (const item of a) if (b.has(item)) intersection++;
|
|
942
|
+
const union = a.size + b.size - intersection;
|
|
943
|
+
return union === 0 ? 0 : intersection / union;
|
|
944
|
+
}
|
|
945
|
+
function findElementByFingerprint(fp, root) {
|
|
946
|
+
return findByFingerprint(fp, root).element;
|
|
947
|
+
}
|
|
948
|
+
function fingerprintKey(fp) {
|
|
949
|
+
return fp.selector + "|" + fp.textSnippet;
|
|
950
|
+
}
|
|
951
|
+
function fingerprintLabel(fp) {
|
|
952
|
+
const tag = fp.tagName.toLowerCase();
|
|
953
|
+
if (fp.textSnippet) {
|
|
954
|
+
const snippet = fp.textSnippet.length > 24 ? fp.textSnippet.slice(0, 24) + "\u2026" : fp.textSnippet;
|
|
955
|
+
return tag + ": " + snippet;
|
|
956
|
+
}
|
|
957
|
+
if (fp.attributes.id) return tag + "#" + fp.attributes.id;
|
|
958
|
+
if (fp.attributes.classList && fp.attributes.classList.length) return tag + "." + fp.attributes.classList[0];
|
|
959
|
+
return tag;
|
|
960
|
+
}
|
|
961
|
+
|
|
695
962
|
// src/highlighter.ts
|
|
696
963
|
var IGNORE_SELECTOR = "[data-vizu-root], [data-vizu-ignore], script, style, link, meta, head";
|
|
697
964
|
var Highlighter = class {
|
|
698
|
-
constructor(root, extraIgnore, onClick
|
|
965
|
+
constructor(root, extraIgnore, onClick) {
|
|
699
966
|
this.current = null;
|
|
700
967
|
this.active = false;
|
|
701
968
|
this.paused = false;
|
|
@@ -730,7 +997,6 @@ var Highlighter = class {
|
|
|
730
997
|
this.root = root;
|
|
731
998
|
this.extraIgnore = extraIgnore;
|
|
732
999
|
this.onClick = onClick;
|
|
733
|
-
this.onCurrentChange = onCurrentChange ?? null;
|
|
734
1000
|
this.overlay = document.createElement("div");
|
|
735
1001
|
this.overlay.className = "vz-highlight";
|
|
736
1002
|
this.overlay.style.display = "none";
|
|
@@ -739,7 +1005,6 @@ var Highlighter = class {
|
|
|
739
1005
|
setCurrent(el) {
|
|
740
1006
|
if (this.current === el) return;
|
|
741
1007
|
this.current = el;
|
|
742
|
-
this.onCurrentChange?.(el);
|
|
743
1008
|
}
|
|
744
1009
|
start() {
|
|
745
1010
|
if (this.active) return;
|
|
@@ -2784,8 +3049,6 @@ var Vizu = class {
|
|
|
2784
3049
|
this.selfHealedThisSession = /* @__PURE__ */ new Set();
|
|
2785
3050
|
this.actions = [];
|
|
2786
3051
|
this.user = null;
|
|
2787
|
-
/** Live-collab module — non-null only when `opts.liveblocks` and `opts.cloud` are both set AND the plan carries `live_collab`. Stays null otherwise. */
|
|
2788
|
-
this.liveCollab = null;
|
|
2789
3052
|
this.enabled = false;
|
|
2790
3053
|
this.rafScheduled = false;
|
|
2791
3054
|
this.bus = new EventBus();
|
|
@@ -2857,31 +3120,6 @@ var Vizu = class {
|
|
|
2857
3120
|
if (options.actions) this.actions = [...options.actions];
|
|
2858
3121
|
if (options.onCommentAdded) this.bus.on("comment:added", (p) => options.onCommentAdded(p.comment));
|
|
2859
3122
|
if (options.onCommentRemoved) this.bus.on("comment:removed", (p) => options.onCommentRemoved(p.id));
|
|
2860
|
-
if (options.cloud) {
|
|
2861
|
-
const cloud = options.cloud;
|
|
2862
|
-
const apiUrl = (cloud.apiUrl ?? "https://vizu.unhingged.com").replace(/\/$/, "");
|
|
2863
|
-
void import("./live-collab-BVLNJ5QI.js").then(({ LiveCollab }) => {
|
|
2864
|
-
this.liveCollab = new LiveCollab({
|
|
2865
|
-
workspaceSlug: cloud.workspace,
|
|
2866
|
-
apiUrl,
|
|
2867
|
-
getAuthHeader: async () => {
|
|
2868
|
-
if (typeof window === "undefined") return null;
|
|
2869
|
-
try {
|
|
2870
|
-
if (new URL(apiUrl).origin === window.location.origin) return null;
|
|
2871
|
-
} catch {
|
|
2872
|
-
return null;
|
|
2873
|
-
}
|
|
2874
|
-
const adapter = this.storage;
|
|
2875
|
-
if (adapter instanceof CloudStorageAdapter) {
|
|
2876
|
-
const token = adapter.getCachedAuthToken();
|
|
2877
|
-
if (token) return { Authorization: `Bearer ${token}` };
|
|
2878
|
-
}
|
|
2879
|
-
return null;
|
|
2880
|
-
}
|
|
2881
|
-
});
|
|
2882
|
-
void this.liveCollab.start();
|
|
2883
|
-
});
|
|
2884
|
-
}
|
|
2885
3123
|
if (typeof window !== "undefined") {
|
|
2886
3124
|
window.addEventListener("keydown", this.onKeyDown);
|
|
2887
3125
|
void this.loadComments().then(() => this.subscribeStorage());
|
|
@@ -2939,8 +3177,6 @@ var Vizu = class {
|
|
|
2939
3177
|
this.unsubscribeStorage?.();
|
|
2940
3178
|
this.unsubscribeStorage = null;
|
|
2941
3179
|
this.selfHealedThisSession.clear();
|
|
2942
|
-
this.liveCollab?.destroy();
|
|
2943
|
-
this.liveCollab = null;
|
|
2944
3180
|
this.bus.clear();
|
|
2945
3181
|
}
|
|
2946
3182
|
/* ===== Action API ===== */
|
|
@@ -3262,11 +3498,7 @@ var Vizu = class {
|
|
|
3262
3498
|
this.highlighter = new Highlighter(
|
|
3263
3499
|
this.root,
|
|
3264
3500
|
this.opts.ignoreSelectors ?? [],
|
|
3265
|
-
(el, e) => this.onElementClick(el, e)
|
|
3266
|
-
(el) => {
|
|
3267
|
-
if (!this.liveCollab) return;
|
|
3268
|
-
this.liveCollab.setLocalSelection(el ? fingerprint(el) : null);
|
|
3269
|
-
}
|
|
3501
|
+
(el, e) => this.onElementClick(el, e)
|
|
3270
3502
|
);
|
|
3271
3503
|
this.highlighter.start();
|
|
3272
3504
|
this.renderAllMarkers();
|