@guidelytech/guide-runtime 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/capture.cjs +8 -0
- package/dist/capture.cjs.map +1 -1
- package/dist/capture.js +2 -2
- package/dist/chunk-KJRXCITD.js +515 -0
- package/dist/chunk-KJRXCITD.js.map +1 -0
- package/dist/chunk-PG7CMBHD.js +330 -0
- package/dist/chunk-PG7CMBHD.js.map +1 -0
- package/dist/chunk-VNJPNGZ4.js +301 -0
- package/dist/chunk-VNJPNGZ4.js.map +1 -0
- package/dist/index.cjs +138 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +74 -10
- package/dist/index.js.map +1 -1
- package/dist/messenger.cjs +586 -35
- package/dist/messenger.cjs.map +1 -1
- package/dist/messenger.d.cts +21 -1
- package/dist/messenger.d.ts +21 -1
- package/dist/messenger.js +527 -23
- package/dist/messenger.js.map +1 -1
- package/package.json +5 -5
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__publicField
|
|
3
|
+
} from "./chunk-PG7CMBHD.js";
|
|
4
|
+
|
|
5
|
+
// src/selectors.ts
|
|
6
|
+
import { matchUrlPattern } from "@guidelytech/shared/runtime";
|
|
7
|
+
function urlMatches(pattern, url) {
|
|
8
|
+
if (!pattern) return true;
|
|
9
|
+
if (matchUrlPattern(pattern, url)) return true;
|
|
10
|
+
try {
|
|
11
|
+
const u = new URL(url, "http://localhost");
|
|
12
|
+
if (!pattern.includes("?") && !pattern.includes("#")) return matchUrlPattern(pattern, u.pathname) || matchUrlPattern(pattern, u.origin + u.pathname);
|
|
13
|
+
} catch {
|
|
14
|
+
}
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
var TEXT_CANDIDATES = "button, a, [role='button'], [role='link'], [role='tab'], [role='menuitem'], h1, h2, h3, h4, h5, h6, label, summary";
|
|
18
|
+
var HOST_ID = "guidely-root";
|
|
19
|
+
function docOf(root) {
|
|
20
|
+
return root.nodeType === 9 ? root : root.ownerDocument;
|
|
21
|
+
}
|
|
22
|
+
function hasLayout(doc) {
|
|
23
|
+
const de = doc.documentElement;
|
|
24
|
+
if (!de) return false;
|
|
25
|
+
const r = de.getBoundingClientRect();
|
|
26
|
+
return r.width > 0 || r.height > 0;
|
|
27
|
+
}
|
|
28
|
+
function normalizeText(text) {
|
|
29
|
+
return (text || "").replace(/\s+/g, " ").trim();
|
|
30
|
+
}
|
|
31
|
+
function isVisible(el) {
|
|
32
|
+
if (!el.isConnected) return false;
|
|
33
|
+
const doc = el.ownerDocument;
|
|
34
|
+
const win = doc.defaultView;
|
|
35
|
+
let cur = el;
|
|
36
|
+
while (cur && cur !== doc.documentElement) {
|
|
37
|
+
if (cur.hidden) return false;
|
|
38
|
+
if (win) {
|
|
39
|
+
const cs = win.getComputedStyle(cur);
|
|
40
|
+
if (cs.display === "none" || cs.visibility === "hidden") return false;
|
|
41
|
+
}
|
|
42
|
+
cur = cur.parentElement ?? cur.getRootNode().host ?? null;
|
|
43
|
+
}
|
|
44
|
+
if (hasLayout(doc)) {
|
|
45
|
+
const r = el.getBoundingClientRect();
|
|
46
|
+
if (r.width === 0 && r.height === 0) return false;
|
|
47
|
+
}
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
function isGuidelyElement(el) {
|
|
51
|
+
if (!el) return false;
|
|
52
|
+
const root = el.getRootNode();
|
|
53
|
+
if (root instanceof ShadowRoot && root.host.id === HOST_ID) return true;
|
|
54
|
+
return el.id === HOST_ID || !!el.closest?.(`#${HOST_ID}`);
|
|
55
|
+
}
|
|
56
|
+
function safeQueryAll(root, selector) {
|
|
57
|
+
try {
|
|
58
|
+
return Array.from(root.querySelectorAll(selector));
|
|
59
|
+
} catch {
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function escapeCss(value) {
|
|
64
|
+
const g = globalThis;
|
|
65
|
+
if (g.CSS && typeof g.CSS.escape === "function") return g.CSS.escape(value);
|
|
66
|
+
return value.replace(/["\\]/g, "\\$&");
|
|
67
|
+
}
|
|
68
|
+
function queryXPath(root, expr) {
|
|
69
|
+
const doc = docOf(root);
|
|
70
|
+
const d = doc;
|
|
71
|
+
if (typeof d.evaluate !== "function") return [];
|
|
72
|
+
try {
|
|
73
|
+
const result = d.evaluate(expr, root, null, 7, null);
|
|
74
|
+
const out = [];
|
|
75
|
+
for (let i = 0; i < result.snapshotLength; i++) {
|
|
76
|
+
const n = result.snapshotItem(i);
|
|
77
|
+
if (n && n.nodeType === 1) out.push(n);
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
} catch {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function queryText(root, text) {
|
|
85
|
+
const needle = normalizeText(text).toLowerCase();
|
|
86
|
+
if (!needle) return [];
|
|
87
|
+
const candidates = safeQueryAll(root, TEXT_CANDIDATES);
|
|
88
|
+
const exact = candidates.filter((c) => normalizeText(c.textContent).toLowerCase() === needle);
|
|
89
|
+
if (exact.length) return exact;
|
|
90
|
+
return candidates.filter((c) => normalizeText(c.textContent).toLowerCase().includes(needle));
|
|
91
|
+
}
|
|
92
|
+
function querySelectorStrategy(strategy, root) {
|
|
93
|
+
const value = strategy.value.trim();
|
|
94
|
+
if (!value) return [];
|
|
95
|
+
switch (strategy.kind) {
|
|
96
|
+
case "id": {
|
|
97
|
+
const id = value.startsWith("#") ? value.slice(1) : value;
|
|
98
|
+
return safeQueryAll(root, `#${escapeCss(id)}`);
|
|
99
|
+
}
|
|
100
|
+
case "guidely-id": {
|
|
101
|
+
const id = value.startsWith("#") ? value.slice(1) : value;
|
|
102
|
+
return safeQueryAll(root, attrSelector("data-guidely-id", id));
|
|
103
|
+
}
|
|
104
|
+
case "data-attr":
|
|
105
|
+
case "aria":
|
|
106
|
+
case "css":
|
|
107
|
+
return safeQueryAll(root, value.startsWith("[") || /^[a-z*#.[]/i.test(value) ? value : `[${value}]`);
|
|
108
|
+
case "text":
|
|
109
|
+
return queryText(root, value);
|
|
110
|
+
case "xpath":
|
|
111
|
+
return queryXPath(root, value);
|
|
112
|
+
default:
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function resolveTarget(target, root = document) {
|
|
117
|
+
const selectors = Array.isArray(target) ? target : target.selectors;
|
|
118
|
+
for (const strategy of selectors) {
|
|
119
|
+
const matches = querySelectorStrategy(strategy, root).filter((el) => !isGuidelyElement(el) && isVisible(el));
|
|
120
|
+
if (matches.length === 1) return matches[0];
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
var TargetTimeoutError = class extends Error {
|
|
125
|
+
constructor(message = "Target element not found before timeout") {
|
|
126
|
+
super(message);
|
|
127
|
+
__publicField(this, "name", "TargetTimeoutError");
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
function waitForTarget(target, options = {}) {
|
|
131
|
+
const timeoutMs = options.timeoutMs ?? 5e3;
|
|
132
|
+
const root = options.root ?? document;
|
|
133
|
+
const pollMs = Math.min(250, Math.max(16, options.pollMs ?? 250));
|
|
134
|
+
return new Promise((resolve, reject) => {
|
|
135
|
+
const immediate = resolveTarget(target, root);
|
|
136
|
+
if (immediate) return resolve(immediate);
|
|
137
|
+
if (options.signal?.aborted) return reject(new DOMException("Aborted", "AbortError"));
|
|
138
|
+
let done = false;
|
|
139
|
+
let observer = null;
|
|
140
|
+
let timer = null;
|
|
141
|
+
let timeout = null;
|
|
142
|
+
const cleanup = () => {
|
|
143
|
+
done = true;
|
|
144
|
+
observer?.disconnect();
|
|
145
|
+
if (timer) clearInterval(timer);
|
|
146
|
+
if (timeout) clearTimeout(timeout);
|
|
147
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
148
|
+
};
|
|
149
|
+
const check = () => {
|
|
150
|
+
if (done) return;
|
|
151
|
+
const el = resolveTarget(target, root);
|
|
152
|
+
if (el) {
|
|
153
|
+
cleanup();
|
|
154
|
+
resolve(el);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
const onAbort = () => {
|
|
158
|
+
cleanup();
|
|
159
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
160
|
+
};
|
|
161
|
+
options.signal?.addEventListener("abort", onAbort);
|
|
162
|
+
const observeRoot = root.nodeType === 9 ? root.documentElement : root;
|
|
163
|
+
if (typeof MutationObserver !== "undefined" && observeRoot) {
|
|
164
|
+
observer = new MutationObserver(check);
|
|
165
|
+
observer.observe(observeRoot, { childList: true, subtree: true, attributes: true, attributeFilter: ["class", "style", "hidden", "id", "data-tour", "data-guidely-id", "aria-label"] });
|
|
166
|
+
}
|
|
167
|
+
timer = setInterval(check, pollMs);
|
|
168
|
+
if (timeoutMs > 0) {
|
|
169
|
+
timeout = setTimeout(() => {
|
|
170
|
+
cleanup();
|
|
171
|
+
reject(new TargetTimeoutError());
|
|
172
|
+
}, timeoutMs);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
var GENERATED_ID = /(\d{4,}|:r[0-9a-z]+:|^radix-|^headlessui-|^mui-|^ember\d|^react-|^\w*-\d+$|__|^[a-f0-9]{8,}$|^svelte-)/i;
|
|
177
|
+
var GENERATED_CLASS = /(^css-|^sc-|^jss\d|^svelte-|^emotion-|^chakra-|^Mui|^_|[0-9a-f]{6,}|^\w{1,2}$|^[a-z]+-\d+$|[:[\]/])/;
|
|
178
|
+
var PREFERRED_DATA_ATTRS = ["data-testid", "data-tour", "data-test", "data-test-id", "data-cy", "data-qa", "data-id", "data-name", "data-guidely"];
|
|
179
|
+
var IGNORED_DATA_ATTRS = /^data-(guidely-id|state|v-|reactid|react|radix|headlessui|svelte|hydrated|orientation|highlighted|focus|key|index|rbd|dnd|rk-|sveltekit)/i;
|
|
180
|
+
function isStableId(id) {
|
|
181
|
+
return !!id && /^[A-Za-z][\w-]*$/.test(id) && !GENERATED_ID.test(id);
|
|
182
|
+
}
|
|
183
|
+
function isStableClass(cls) {
|
|
184
|
+
return !!cls && !GENERATED_CLASS.test(cls) && cls.length <= 40;
|
|
185
|
+
}
|
|
186
|
+
function uniqueIn(doc, selector, el) {
|
|
187
|
+
const all = safeQueryAll(doc, selector);
|
|
188
|
+
return all.length === 1 && all[0] === el;
|
|
189
|
+
}
|
|
190
|
+
function attrSelector(name, value) {
|
|
191
|
+
return `[${name}='${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}']`;
|
|
192
|
+
}
|
|
193
|
+
function cssPath(el, doc) {
|
|
194
|
+
const segments = [];
|
|
195
|
+
let cur = el;
|
|
196
|
+
while (cur && cur.nodeType === 1 && cur !== doc.body && cur !== doc.documentElement) {
|
|
197
|
+
const tag = cur.tagName.toLowerCase();
|
|
198
|
+
if (isStableId(cur.id)) {
|
|
199
|
+
segments.unshift(`#${cur.id}`);
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
let seg = tag;
|
|
203
|
+
const parent = cur.parentElement;
|
|
204
|
+
if (parent) {
|
|
205
|
+
const sameTag = Array.from(parent.children).filter((c) => c.tagName === cur.tagName);
|
|
206
|
+
if (sameTag.length > 1) {
|
|
207
|
+
const stableCls = Array.from(cur.classList).find((c) => isStableClass(c) && sameTag.filter((s) => s.classList.contains(c)).length === 1);
|
|
208
|
+
seg += stableCls ? `.${escapeCss(stableCls)}` : `:nth-of-type(${sameTag.indexOf(cur) + 1})`;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
segments.unshift(seg);
|
|
212
|
+
const candidate = segments.join(" > ");
|
|
213
|
+
if (uniqueIn(doc, candidate, el)) return candidate;
|
|
214
|
+
cur = parent;
|
|
215
|
+
}
|
|
216
|
+
const full = segments.join(" > ");
|
|
217
|
+
return uniqueIn(doc, full, el) ? full : null;
|
|
218
|
+
}
|
|
219
|
+
function textSnapshotOf(el) {
|
|
220
|
+
const he = el;
|
|
221
|
+
const own = normalizeText(he.innerText ?? el.textContent);
|
|
222
|
+
const alt = normalizeText(el.getAttribute("aria-label") || el.getAttribute("title") || el.getAttribute("placeholder") || el.getAttribute("alt") || el.value);
|
|
223
|
+
return (own || alt).slice(0, 200);
|
|
224
|
+
}
|
|
225
|
+
function generateSelectors(el) {
|
|
226
|
+
const doc = el.ownerDocument;
|
|
227
|
+
const out = [];
|
|
228
|
+
const seen = /* @__PURE__ */ new Set();
|
|
229
|
+
const push = (s) => {
|
|
230
|
+
const k = `${s.kind}:${s.value}`;
|
|
231
|
+
if (!seen.has(k)) {
|
|
232
|
+
seen.add(k);
|
|
233
|
+
out.push(s);
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
const tag = el.tagName.toLowerCase();
|
|
237
|
+
const guidelyId = el.getAttribute("data-guidely-id");
|
|
238
|
+
if (guidelyId && uniqueIn(doc, attrSelector("data-guidely-id", guidelyId), el)) push({ kind: "guidely-id", value: guidelyId, label: `Guidely id ${guidelyId}` });
|
|
239
|
+
if (isStableId(el.id) && uniqueIn(doc, `#${escapeCss(el.id)}`, el)) push({ kind: "id", value: `#${el.id}`, label: `id ${el.id}` });
|
|
240
|
+
const dataAttrs = Array.from(el.attributes).filter((a) => a.name.startsWith("data-") && !IGNORED_DATA_ATTRS.test(a.name) && a.value && a.value.length <= 120 && !/\d{5,}/.test(a.value));
|
|
241
|
+
dataAttrs.sort((a, b) => {
|
|
242
|
+
const ia = PREFERRED_DATA_ATTRS.indexOf(a.name);
|
|
243
|
+
const ib = PREFERRED_DATA_ATTRS.indexOf(b.name);
|
|
244
|
+
return (ia === -1 ? 99 : ia) - (ib === -1 ? 99 : ib);
|
|
245
|
+
});
|
|
246
|
+
for (const a of dataAttrs) {
|
|
247
|
+
const sel = attrSelector(a.name, a.value);
|
|
248
|
+
if (uniqueIn(doc, sel, el)) push({ kind: "data-attr", value: sel, label: `${a.name}=${a.value}` });
|
|
249
|
+
else if (uniqueIn(doc, tag + sel, el)) push({ kind: "data-attr", value: tag + sel, label: `${a.name}=${a.value}` });
|
|
250
|
+
}
|
|
251
|
+
const aria = el.getAttribute("aria-label");
|
|
252
|
+
if (aria && aria.length <= 120) {
|
|
253
|
+
const sel = attrSelector("aria-label", aria);
|
|
254
|
+
if (uniqueIn(doc, sel, el)) push({ kind: "aria", value: sel, label: aria });
|
|
255
|
+
else if (uniqueIn(doc, tag + sel, el)) push({ kind: "aria", value: tag + sel, label: aria });
|
|
256
|
+
}
|
|
257
|
+
const name = el.getAttribute("name");
|
|
258
|
+
if (name && uniqueIn(doc, tag + attrSelector("name", name), el)) push({ kind: "css", value: tag + attrSelector("name", name), label: `${tag} name=${name}` });
|
|
259
|
+
const role = el.getAttribute("role");
|
|
260
|
+
if (role) {
|
|
261
|
+
const title = el.getAttribute("title") || el.getAttribute("aria-labelledby");
|
|
262
|
+
const sel = title ? `[role='${role}']${attrSelector(el.getAttribute("title") ? "title" : "aria-labelledby", title)}` : `[role='${role}']`;
|
|
263
|
+
if (uniqueIn(doc, sel, el)) push({ kind: "css", value: sel, label: `role ${role}` });
|
|
264
|
+
}
|
|
265
|
+
if (tag === "a") {
|
|
266
|
+
const href = el.getAttribute("href");
|
|
267
|
+
if (href && href.length <= 200 && uniqueIn(doc, `a${attrSelector("href", href)}`, el)) push({ kind: "css", value: `a${attrSelector("href", href)}`, label: `link ${href}` });
|
|
268
|
+
}
|
|
269
|
+
const path = cssPath(el, doc);
|
|
270
|
+
if (path) push({ kind: "css", value: path, label: "css path" });
|
|
271
|
+
if (/^(button|a|h[1-6]|label|summary)$/.test(tag) || role === "button" || role === "link" || role === "tab" || role === "menuitem") {
|
|
272
|
+
const text = normalizeText(el.innerText ?? el.textContent);
|
|
273
|
+
if (text && text.length <= 80) push({ kind: "text", value: text, label: `text "${text}"` });
|
|
274
|
+
}
|
|
275
|
+
return out.slice(0, 10);
|
|
276
|
+
}
|
|
277
|
+
function describeElement(el) {
|
|
278
|
+
const r = el.getBoundingClientRect();
|
|
279
|
+
return {
|
|
280
|
+
selectors: generateSelectors(el),
|
|
281
|
+
textSnapshot: textSnapshotOf(el),
|
|
282
|
+
rect: { x: r.x, y: r.y, width: r.width, height: r.height },
|
|
283
|
+
tag: el.tagName.toLowerCase()
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export {
|
|
288
|
+
urlMatches,
|
|
289
|
+
normalizeText,
|
|
290
|
+
isVisible,
|
|
291
|
+
isGuidelyElement,
|
|
292
|
+
querySelectorStrategy,
|
|
293
|
+
resolveTarget,
|
|
294
|
+
TargetTimeoutError,
|
|
295
|
+
waitForTarget,
|
|
296
|
+
isStableId,
|
|
297
|
+
textSnapshotOf,
|
|
298
|
+
generateSelectors,
|
|
299
|
+
describeElement
|
|
300
|
+
};
|
|
301
|
+
//# sourceMappingURL=chunk-VNJPNGZ4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/selectors.ts"],"sourcesContent":["import { matchUrlPattern } from \"@guidelytech/shared/runtime\";\nimport type { SelectorStrategy, StepTarget } from \"@guidelytech/shared\";\n\n/**\n * URL matching used for `step.url`, `urlPatterns` and `trigger.urlPattern`: a pattern matches the\n * full URL, the path+query+hash, or the bare path (so `/deals` also matches `/deals?x=1`).\n */\nexport function urlMatches(pattern: string | undefined | null, url: string): boolean {\n if (!pattern) return true;\n if (matchUrlPattern(pattern, url)) return true;\n try {\n const u = new URL(url, \"http://localhost\");\n if (!pattern.includes(\"?\") && !pattern.includes(\"#\")) return matchUrlPattern(pattern, u.pathname) || matchUrlPattern(pattern, u.origin + u.pathname);\n } catch {\n /* ignore */\n }\n return false;\n}\n\nexport type QueryRoot = Document | DocumentFragment | Element;\n\nconst TEXT_CANDIDATES =\n \"button, a, [role='button'], [role='link'], [role='tab'], [role='menuitem'], h1, h2, h3, h4, h5, h6, label, summary\";\n\nconst HOST_ID = \"guidely-root\";\n\nfunction docOf(root: QueryRoot): Document {\n return root.nodeType === 9 ? (root as Document) : (root.ownerDocument as Document);\n}\n\n/** True when the environment has a layout engine (real browsers). happy-dom/jsdom report zero sizes. */\nfunction hasLayout(doc: Document): boolean {\n const de = doc.documentElement;\n if (!de) return false;\n const r = de.getBoundingClientRect();\n return r.width > 0 || r.height > 0;\n}\n\n/** Normalises whitespace in a text snippet. */\nexport function normalizeText(text: string | null | undefined): string {\n return (text || \"\").replace(/\\s+/g, \" \").trim();\n}\n\n/** Is the element rendered (not hidden by `hidden`, `display:none`, `visibility:hidden`, or zero size)? */\nexport function isVisible(el: Element): boolean {\n if (!el.isConnected) return false;\n const doc = el.ownerDocument;\n const win = doc.defaultView;\n let cur: Element | null = el;\n while (cur && cur !== doc.documentElement) {\n if ((cur as HTMLElement).hidden) return false;\n if (win) {\n const cs = win.getComputedStyle(cur);\n if (cs.display === \"none\" || cs.visibility === \"hidden\") return false;\n }\n cur = cur.parentElement ?? ((cur.getRootNode() as ShadowRoot).host as Element | undefined) ?? null;\n }\n if (hasLayout(doc)) {\n const r = el.getBoundingClientRect();\n if (r.width === 0 && r.height === 0) return false;\n }\n return true;\n}\n\n/** Elements that belong to Guidely's own UI are never valid targets. */\nexport function isGuidelyElement(el: Element | null): boolean {\n if (!el) return false;\n const root = el.getRootNode();\n if (root instanceof ShadowRoot && root.host.id === HOST_ID) return true;\n return el.id === HOST_ID || !!el.closest?.(`#${HOST_ID}`);\n}\n\nfunction safeQueryAll(root: QueryRoot, selector: string): Element[] {\n try {\n return Array.from(root.querySelectorAll(selector));\n } catch {\n return [];\n }\n}\n\nfunction escapeCss(value: string): string {\n const g = globalThis as { CSS?: { escape?: (v: string) => string } };\n if (g.CSS && typeof g.CSS.escape === \"function\") return g.CSS.escape(value);\n return value.replace(/[\"\\\\]/g, \"\\\\$&\");\n}\n\nfunction queryXPath(root: QueryRoot, expr: string): Element[] {\n const doc = docOf(root);\n const d = doc as Document & { evaluate?: Document[\"evaluate\"] };\n if (typeof d.evaluate !== \"function\") return [];\n try {\n const result = d.evaluate(expr, root, null, 7 /* ORDERED_NODE_SNAPSHOT_TYPE */, null);\n const out: Element[] = [];\n for (let i = 0; i < result.snapshotLength; i++) {\n const n = result.snapshotItem(i);\n if (n && n.nodeType === 1) out.push(n as Element);\n }\n return out;\n } catch {\n return [];\n }\n}\n\nfunction queryText(root: QueryRoot, text: string): Element[] {\n const needle = normalizeText(text).toLowerCase();\n if (!needle) return [];\n const candidates = safeQueryAll(root, TEXT_CANDIDATES);\n const exact = candidates.filter((c) => normalizeText(c.textContent).toLowerCase() === needle);\n if (exact.length) return exact;\n return candidates.filter((c) => normalizeText(c.textContent).toLowerCase().includes(needle));\n}\n\n/** Resolves every element matching one strategy (visible or not). */\nexport function querySelectorStrategy(strategy: SelectorStrategy, root: QueryRoot): Element[] {\n const value = strategy.value.trim();\n if (!value) return [];\n switch (strategy.kind) {\n case \"id\": {\n const id = value.startsWith(\"#\") ? value.slice(1) : value;\n return safeQueryAll(root, `#${escapeCss(id)}`);\n }\n case \"guidely-id\": {\n const id = value.startsWith(\"#\") ? value.slice(1) : value;\n return safeQueryAll(root, attrSelector(\"data-guidely-id\", id));\n }\n case \"data-attr\":\n case \"aria\":\n case \"css\":\n return safeQueryAll(root, value.startsWith(\"[\") || /^[a-z*#.[]/i.test(value) ? value : `[${value}]`);\n case \"text\":\n return queryText(root, value);\n case \"xpath\":\n return queryXPath(root, value);\n default:\n return [];\n }\n}\n\n/**\n * Resolves a step target by trying each strategy in order. A strategy wins only when it\n * resolves to exactly one *visible* element; otherwise the next strategy is tried.\n */\nexport function resolveTarget(target: Pick<StepTarget, \"selectors\"> | SelectorStrategy[], root: QueryRoot = document): Element | null {\n const selectors = Array.isArray(target) ? target : target.selectors;\n for (const strategy of selectors) {\n const matches = querySelectorStrategy(strategy, root).filter((el) => !isGuidelyElement(el) && isVisible(el));\n if (matches.length === 1) return matches[0] as Element;\n }\n return null;\n}\n\nexport interface WaitForTargetOptions {\n /** Give up after this many milliseconds (default 5000). */\n timeoutMs?: number;\n /** Abort early. */\n signal?: AbortSignal;\n root?: QueryRoot;\n /** Polling fallback interval (default 250ms, which is also the maximum). */\n pollMs?: number;\n}\n\nexport class TargetTimeoutError extends Error {\n override name = \"TargetTimeoutError\";\n constructor(message = \"Target element not found before timeout\") {\n super(message);\n }\n}\n\n/**\n * Waits until `resolveTarget` finds an element. Uses a MutationObserver plus a polling fallback\n * (at most every 250ms) so visibility changes without DOM mutations are also detected.\n */\nexport function waitForTarget(target: Pick<StepTarget, \"selectors\"> | SelectorStrategy[], options: WaitForTargetOptions = {}): Promise<Element> {\n const timeoutMs = options.timeoutMs ?? 5000;\n const root = options.root ?? document;\n const pollMs = Math.min(250, Math.max(16, options.pollMs ?? 250));\n return new Promise<Element>((resolve, reject) => {\n const immediate = resolveTarget(target, root);\n if (immediate) return resolve(immediate);\n if (options.signal?.aborted) return reject(new DOMException(\"Aborted\", \"AbortError\"));\n let done = false;\n let observer: MutationObserver | null = null;\n let timer: ReturnType<typeof setInterval> | null = null;\n let timeout: ReturnType<typeof setTimeout> | null = null;\n const cleanup = () => {\n done = true;\n observer?.disconnect();\n if (timer) clearInterval(timer);\n if (timeout) clearTimeout(timeout);\n options.signal?.removeEventListener(\"abort\", onAbort);\n };\n const check = () => {\n if (done) return;\n const el = resolveTarget(target, root);\n if (el) {\n cleanup();\n resolve(el);\n }\n };\n const onAbort = () => {\n cleanup();\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n };\n options.signal?.addEventListener(\"abort\", onAbort);\n const observeRoot = root.nodeType === 9 ? (root as Document).documentElement : (root as Element);\n if (typeof MutationObserver !== \"undefined\" && observeRoot) {\n observer = new MutationObserver(check);\n observer.observe(observeRoot, { childList: true, subtree: true, attributes: true, attributeFilter: [\"class\", \"style\", \"hidden\", \"id\", \"data-tour\", \"data-guidely-id\", \"aria-label\"] });\n }\n timer = setInterval(check, pollMs);\n if (timeoutMs > 0) {\n timeout = setTimeout(() => {\n cleanup();\n reject(new TargetTimeoutError());\n }, timeoutMs);\n }\n });\n}\n\n// ---------------------------------------------------------------------------\n// Selector generation (used by the element picker)\n// ---------------------------------------------------------------------------\n\nconst GENERATED_ID = /(\\d{4,}|:r[0-9a-z]+:|^radix-|^headlessui-|^mui-|^ember\\d|^react-|^\\w*-\\d+$|__|^[a-f0-9]{8,}$|^svelte-)/i;\nconst GENERATED_CLASS = /(^css-|^sc-|^jss\\d|^svelte-|^emotion-|^chakra-|^Mui|^_|[0-9a-f]{6,}|^\\w{1,2}$|^[a-z]+-\\d+$|[:[\\]/])/;\nconst PREFERRED_DATA_ATTRS = [\"data-testid\", \"data-tour\", \"data-test\", \"data-test-id\", \"data-cy\", \"data-qa\", \"data-id\", \"data-name\", \"data-guidely\"];\nconst IGNORED_DATA_ATTRS = /^data-(guidely-id|state|v-|reactid|react|radix|headlessui|svelte|hydrated|orientation|highlighted|focus|key|index|rbd|dnd|rk-|sveltekit)/i;\n\n/** Is this id stable enough to be used as a selector (not generated by a framework)? */\nexport function isStableId(id: string): boolean {\n return !!id && /^[A-Za-z][\\w-]*$/.test(id) && !GENERATED_ID.test(id);\n}\n\nfunction isStableClass(cls: string): boolean {\n return !!cls && !GENERATED_CLASS.test(cls) && cls.length <= 40;\n}\n\nfunction uniqueIn(doc: Document, selector: string, el: Element): boolean {\n const all = safeQueryAll(doc, selector);\n return all.length === 1 && all[0] === el;\n}\n\nfunction attrSelector(name: string, value: string): string {\n return `[${name}='${value.replace(/\\\\/g, \"\\\\\\\\\").replace(/'/g, \"\\\\'\")}']`;\n}\n\nfunction cssPath(el: Element, doc: Document): string | null {\n const segments: string[] = [];\n let cur: Element | null = el;\n while (cur && cur.nodeType === 1 && cur !== doc.body && cur !== doc.documentElement) {\n const tag = cur.tagName.toLowerCase();\n if (isStableId(cur.id)) {\n segments.unshift(`#${cur.id}`);\n break;\n }\n let seg = tag;\n const parent: Element | null = cur.parentElement;\n if (parent) {\n const sameTag = Array.from(parent.children).filter((c) => c.tagName === cur!.tagName);\n if (sameTag.length > 1) {\n const stableCls = Array.from(cur.classList).find((c) => isStableClass(c) && sameTag.filter((s) => s.classList.contains(c)).length === 1);\n seg += stableCls ? `.${escapeCss(stableCls)}` : `:nth-of-type(${sameTag.indexOf(cur) + 1})`;\n }\n }\n segments.unshift(seg);\n const candidate = segments.join(\" > \");\n if (uniqueIn(doc, candidate, el)) return candidate;\n cur = parent;\n }\n const full = segments.join(\" > \");\n return uniqueIn(doc, full, el) ? full : null;\n}\n\n/** Short human-readable text for an element, trimmed to 200 characters. */\nexport function textSnapshotOf(el: Element): string {\n const he = el as HTMLElement;\n const own = normalizeText(he.innerText ?? el.textContent);\n const alt = normalizeText(el.getAttribute(\"aria-label\") || el.getAttribute(\"title\") || el.getAttribute(\"placeholder\") || el.getAttribute(\"alt\") || (el as HTMLInputElement).value);\n return (own || alt).slice(0, 200);\n}\n\n/**\n * Generates an ordered list of selector strategies for an element, most robust first:\n * stable id → data-* attributes → aria-label → role/name → short unique CSS path → visible text.\n */\nexport function generateSelectors(el: Element): SelectorStrategy[] {\n const doc = el.ownerDocument;\n const out: SelectorStrategy[] = [];\n const seen = new Set<string>();\n const push = (s: SelectorStrategy) => {\n const k = `${s.kind}:${s.value}`;\n if (!seen.has(k)) {\n seen.add(k);\n out.push(s);\n }\n };\n const tag = el.tagName.toLowerCase();\n\n // A Guidely id is shared with the mobile apps, so it is the most portable target there is.\n const guidelyId = el.getAttribute(\"data-guidely-id\");\n if (guidelyId && uniqueIn(doc, attrSelector(\"data-guidely-id\", guidelyId), el)) push({ kind: \"guidely-id\", value: guidelyId, label: `Guidely id ${guidelyId}` });\n if (isStableId(el.id) && uniqueIn(doc, `#${escapeCss(el.id)}`, el)) push({ kind: \"id\", value: `#${el.id}`, label: `id ${el.id}` });\n\n const dataAttrs = Array.from(el.attributes).filter((a) => a.name.startsWith(\"data-\") && !IGNORED_DATA_ATTRS.test(a.name) && a.value && a.value.length <= 120 && !/\\d{5,}/.test(a.value));\n dataAttrs.sort((a, b) => {\n const ia = PREFERRED_DATA_ATTRS.indexOf(a.name);\n const ib = PREFERRED_DATA_ATTRS.indexOf(b.name);\n return (ia === -1 ? 99 : ia) - (ib === -1 ? 99 : ib);\n });\n for (const a of dataAttrs) {\n const sel = attrSelector(a.name, a.value);\n if (uniqueIn(doc, sel, el)) push({ kind: \"data-attr\", value: sel, label: `${a.name}=${a.value}` });\n else if (uniqueIn(doc, tag + sel, el)) push({ kind: \"data-attr\", value: tag + sel, label: `${a.name}=${a.value}` });\n }\n\n const aria = el.getAttribute(\"aria-label\");\n if (aria && aria.length <= 120) {\n const sel = attrSelector(\"aria-label\", aria);\n if (uniqueIn(doc, sel, el)) push({ kind: \"aria\", value: sel, label: aria });\n else if (uniqueIn(doc, tag + sel, el)) push({ kind: \"aria\", value: tag + sel, label: aria });\n }\n\n const name = el.getAttribute(\"name\");\n if (name && uniqueIn(doc, tag + attrSelector(\"name\", name), el)) push({ kind: \"css\", value: tag + attrSelector(\"name\", name), label: `${tag} name=${name}` });\n const role = el.getAttribute(\"role\");\n if (role) {\n const title = el.getAttribute(\"title\") || el.getAttribute(\"aria-labelledby\");\n const sel = title ? `[role='${role}']${attrSelector(el.getAttribute(\"title\") ? \"title\" : \"aria-labelledby\", title)}` : `[role='${role}']`;\n if (uniqueIn(doc, sel, el)) push({ kind: \"css\", value: sel, label: `role ${role}` });\n }\n if (tag === \"a\") {\n const href = el.getAttribute(\"href\");\n if (href && href.length <= 200 && uniqueIn(doc, `a${attrSelector(\"href\", href)}`, el)) push({ kind: \"css\", value: `a${attrSelector(\"href\", href)}`, label: `link ${href}` });\n }\n\n const path = cssPath(el, doc);\n if (path) push({ kind: \"css\", value: path, label: \"css path\" });\n\n if (/^(button|a|h[1-6]|label|summary)$/.test(tag) || role === \"button\" || role === \"link\" || role === \"tab\" || role === \"menuitem\") {\n const text = normalizeText((el as HTMLElement).innerText ?? el.textContent);\n if (text && text.length <= 80) push({ kind: \"text\", value: text, label: `text \"${text}\"` });\n }\n return out.slice(0, 10);\n}\n\nexport interface ElementDescription {\n selectors: SelectorStrategy[];\n textSnapshot: string;\n rect: { x: number; y: number; width: number; height: number };\n tag: string;\n}\n\n/** Everything the authoring picker needs for one element. */\nexport function describeElement(el: Element): ElementDescription {\n const r = el.getBoundingClientRect();\n return {\n selectors: generateSelectors(el),\n textSnapshot: textSnapshotOf(el),\n rect: { x: r.x, y: r.y, width: r.width, height: r.height },\n tag: el.tagName.toLowerCase(),\n };\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.cjs
CHANGED
|
@@ -618,7 +618,15 @@ var STYLES = `
|
|
|
618
618
|
.g-rc-item .g-rc-title{font-weight:500}
|
|
619
619
|
.g-rc-item .g-rc-sum{font-size:12px;color:var(--g-muted);margin-top:2px}
|
|
620
620
|
.g-rc-item .g-rc-icon{flex:none;width:20px;text-align:center;color:var(--g-primary)}
|
|
621
|
+
.g-rc-row{display:flex;align-items:center;gap:6px;padding-right:16px}
|
|
622
|
+
.g-rc-row>.g-rc-item{flex:1;min-width:0;padding-right:4px}
|
|
623
|
+
.g-rc-row:hover{background:var(--g-subtle)}
|
|
624
|
+
.g-rc-row:hover>.g-rc-item{background:transparent}
|
|
625
|
+
.g-rc-showme{flex:none;min-height:32px;padding:4px 10px;font-size:12px;white-space:nowrap}
|
|
626
|
+
.g-help-showme{margin:4px 0 12px}
|
|
621
627
|
.g-rc-article{padding:16px 20px}
|
|
628
|
+
.g-resource-center .g-panel{transition:width .2s ease,max-height .2s ease}
|
|
629
|
+
.g-resource-center .g-panel.g-panel-reading{width:min(720px,calc(100vw - 32px));max-height:calc(100vh - 100px)}
|
|
622
630
|
.g-rc-back{margin-bottom:8px}
|
|
623
631
|
.g-rc-empty{padding:24px 20px;color:var(--g-muted);text-align:center}
|
|
624
632
|
.g-faq-a{padding:0 20px 12px 50px;font-size:13px;color:var(--g-muted)}
|
|
@@ -1363,6 +1371,17 @@ function isAnchoredStep(step) {
|
|
|
1363
1371
|
var import_runtime5 = require("@guidelytech/shared/runtime");
|
|
1364
1372
|
function noop() {
|
|
1365
1373
|
}
|
|
1374
|
+
var escapeHtml = (s) => s.replace(/[&<>"']/g, (c) => `&#${c.charCodeAt(0)};`);
|
|
1375
|
+
function navigableUrl(pattern) {
|
|
1376
|
+
if (!pattern || /[*?#\s]/.test(pattern) || pattern.includes("/:")) return null;
|
|
1377
|
+
if (pattern.startsWith("/") && !pattern.startsWith("//")) return pattern;
|
|
1378
|
+
try {
|
|
1379
|
+
const u = new URL(pattern);
|
|
1380
|
+
if (typeof location !== "undefined" && u.origin === location.origin) return u.pathname;
|
|
1381
|
+
} catch {
|
|
1382
|
+
}
|
|
1383
|
+
return null;
|
|
1384
|
+
}
|
|
1366
1385
|
function runGuide(guide, options = {}) {
|
|
1367
1386
|
const doc = options.doc ?? (options.container?.ownerDocument || document);
|
|
1368
1387
|
const steps = guide.config.steps;
|
|
@@ -1507,15 +1526,15 @@ function runGuide(guide, options = {}) {
|
|
|
1507
1526
|
onError(err);
|
|
1508
1527
|
}
|
|
1509
1528
|
};
|
|
1510
|
-
const setupAdvance = (step) => {
|
|
1529
|
+
const setupAdvance = (step, target) => {
|
|
1511
1530
|
const modes = step.advanceOn === "any" ? ["element_click", "element_visible", "event", "delay"] : [step.advanceOn];
|
|
1512
1531
|
for (const mode of modes) {
|
|
1513
|
-
if (mode === "element_click" && step.advanceValue) {
|
|
1532
|
+
if (mode === "element_click" && (step.advanceValue || target)) {
|
|
1514
1533
|
const selector = step.advanceValue;
|
|
1515
1534
|
const handler = (e) => {
|
|
1516
1535
|
const t = e.target;
|
|
1517
1536
|
try {
|
|
1518
|
-
if (t && typeof t.closest === "function" && t.closest(selector)) advance();
|
|
1537
|
+
if (selector ? t && typeof t.closest === "function" && t.closest(selector) : t && target?.contains(t)) advance();
|
|
1519
1538
|
} catch {
|
|
1520
1539
|
}
|
|
1521
1540
|
};
|
|
@@ -1551,10 +1570,11 @@ function runGuide(guide, options = {}) {
|
|
|
1551
1570
|
const render = (step, target, asModal = false) => {
|
|
1552
1571
|
if (ended) return;
|
|
1553
1572
|
status = "running";
|
|
1573
|
+
const shown = !target && step.advanceOn === "element_click" && !step.advanceValue && !step.buttons.length ? { ...step, buttons: [{ id: "g-next", label: index + 1 >= total ? "Done" : "Next", action: index + 1 >= total ? "complete" : "next", variant: "primary" }] } : step;
|
|
1554
1574
|
try {
|
|
1555
1575
|
view = renderStep({
|
|
1556
1576
|
scope: getScope(),
|
|
1557
|
-
step,
|
|
1577
|
+
step: shown,
|
|
1558
1578
|
index,
|
|
1559
1579
|
total,
|
|
1560
1580
|
canGoBack: history.length > 0 || index > 0,
|
|
@@ -1576,7 +1596,43 @@ function runGuide(guide, options = {}) {
|
|
|
1576
1596
|
}
|
|
1577
1597
|
emit("step_viewed", step, index, asModal ? { fallback: "show_as_modal" } : void 0);
|
|
1578
1598
|
announce(step.title ? `${step.title}. Step ${index + 1} of ${total}` : `Step ${index + 1} of ${total}`);
|
|
1579
|
-
setupAdvance(step);
|
|
1599
|
+
setupAdvance(step, target);
|
|
1600
|
+
};
|
|
1601
|
+
const showUrlPrompt = (step) => {
|
|
1602
|
+
if (ended || status !== "waiting_url" || view) return;
|
|
1603
|
+
const href = navigableUrl(step.url);
|
|
1604
|
+
const where = (step.url ?? "").replace(/^https?:\/\/[^/]+/i, "") || "another page";
|
|
1605
|
+
const prompt = {
|
|
1606
|
+
...step,
|
|
1607
|
+
type: "modal",
|
|
1608
|
+
title: "Continue on another page",
|
|
1609
|
+
body: `<p>The next step${step.title ? `, <strong>${escapeHtml(step.title)}</strong>,` : ""} is on <code>${escapeHtml(where)}</code>.</p>`,
|
|
1610
|
+
imageUrl: "",
|
|
1611
|
+
buttons: [
|
|
1612
|
+
...href ? [{ id: "g-go", label: "Take me there", action: "url", value: href, variant: "primary" }] : [],
|
|
1613
|
+
{ id: "g-exit", label: "Exit", action: "dismiss", variant: href ? "secondary" : "primary" }
|
|
1614
|
+
]
|
|
1615
|
+
};
|
|
1616
|
+
try {
|
|
1617
|
+
view = renderStep({
|
|
1618
|
+
scope: getScope(),
|
|
1619
|
+
step: prompt,
|
|
1620
|
+
index,
|
|
1621
|
+
total,
|
|
1622
|
+
canGoBack: false,
|
|
1623
|
+
target: null,
|
|
1624
|
+
asModal: true,
|
|
1625
|
+
onButton: (b) => {
|
|
1626
|
+
if (b.action !== "url" || !b.value) return end("dismissed");
|
|
1627
|
+
if (options.onNavigate) options.onNavigate(b.value);
|
|
1628
|
+
else location.assign(b.value);
|
|
1629
|
+
},
|
|
1630
|
+
onClose: () => end("dismissed"),
|
|
1631
|
+
announce
|
|
1632
|
+
});
|
|
1633
|
+
} catch (err) {
|
|
1634
|
+
onError(err);
|
|
1635
|
+
}
|
|
1580
1636
|
};
|
|
1581
1637
|
const applyFallback = (step, direction) => {
|
|
1582
1638
|
const fallback = step.target?.fallback ?? "skip";
|
|
@@ -1602,9 +1658,18 @@ function runGuide(guide, options = {}) {
|
|
|
1602
1658
|
if (nextIndex < 0) return;
|
|
1603
1659
|
return showStep(nextIndex, direction);
|
|
1604
1660
|
}
|
|
1661
|
+
try {
|
|
1662
|
+
options.onPosition?.(step.id);
|
|
1663
|
+
} catch (err) {
|
|
1664
|
+
onError(err);
|
|
1665
|
+
}
|
|
1605
1666
|
if (step.url && !urlMatches(step.url, currentUrl())) {
|
|
1606
1667
|
status = "waiting_url";
|
|
1607
1668
|
pendingIndex = i;
|
|
1669
|
+
if (options.promptForUrl) {
|
|
1670
|
+
const t = setTimeout(() => showUrlPrompt(step), options.urlPromptDelayMs ?? 1200);
|
|
1671
|
+
stepCleanups.push(() => clearTimeout(t));
|
|
1672
|
+
}
|
|
1608
1673
|
return;
|
|
1609
1674
|
}
|
|
1610
1675
|
pendingIndex = -1;
|
|
@@ -2102,6 +2167,38 @@ function renderSurvey(options) {
|
|
|
2102
2167
|
|
|
2103
2168
|
// src/help-view.ts
|
|
2104
2169
|
var import_runtime8 = require("@guidelytech/shared/runtime");
|
|
2170
|
+
var SHOW_ME_KINDS = /* @__PURE__ */ new Set(["flow", "modal", "tooltip", "announcement"]);
|
|
2171
|
+
function startableGuide(guides, keyOrId) {
|
|
2172
|
+
if (!keyOrId || !guides) return null;
|
|
2173
|
+
return guides.find((g) => (g.key === keyOrId || g.id === keyOrId) && SHOW_ME_KINDS.has(g.kind)) ?? null;
|
|
2174
|
+
}
|
|
2175
|
+
function resultRow(doc, icon, title, summary, onOpen, showMe) {
|
|
2176
|
+
const item = listItem(doc, icon, title, summary);
|
|
2177
|
+
item.addEventListener("click", onOpen);
|
|
2178
|
+
if (!showMe) return item;
|
|
2179
|
+
const row = el(doc, "div", "g-rc-row");
|
|
2180
|
+
const b = el(doc, "button", "g-btn g-btn-primary g-rc-showme", "\u25B6 Show me");
|
|
2181
|
+
b.type = "button";
|
|
2182
|
+
b.setAttribute("aria-label", `Show me: ${title}`);
|
|
2183
|
+
b.addEventListener("click", showMe);
|
|
2184
|
+
row.append(item, b);
|
|
2185
|
+
return row;
|
|
2186
|
+
}
|
|
2187
|
+
function enhanceShowMe(root, doc, canStart, start) {
|
|
2188
|
+
for (const a of Array.from(root.querySelectorAll(`a[href^="${import_runtime8.SHOW_ME_HREF_PREFIX}"]`))) {
|
|
2189
|
+
const key = (0, import_runtime8.showMeKeyFromHref)(a.getAttribute("href"));
|
|
2190
|
+
const holder = a.closest("p.gd-show-me") ?? a;
|
|
2191
|
+
if (!key || !canStart(key)) {
|
|
2192
|
+
if (holder !== a) holder.remove();
|
|
2193
|
+
else a.replaceWith(doc.createTextNode(a.textContent ?? ""));
|
|
2194
|
+
continue;
|
|
2195
|
+
}
|
|
2196
|
+
const b = el(doc, "button", "g-btn g-btn-primary g-help-showme", `\u25B6 ${(a.textContent ?? "").trim() || "Show me"}`);
|
|
2197
|
+
b.type = "button";
|
|
2198
|
+
b.addEventListener("click", () => start(key));
|
|
2199
|
+
a.replaceWith(b);
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2105
2202
|
function el(doc, tag, cls, text) {
|
|
2106
2203
|
const e = doc.createElement(tag);
|
|
2107
2204
|
if (cls) e.className = cls;
|
|
@@ -2126,6 +2223,11 @@ function createHelpView(options) {
|
|
|
2126
2223
|
let searchSeq = 0;
|
|
2127
2224
|
let openSeq = 0;
|
|
2128
2225
|
let active = null;
|
|
2226
|
+
const setActive = (next) => {
|
|
2227
|
+
const was = !!active;
|
|
2228
|
+
active = next;
|
|
2229
|
+
if (was !== !!next) options.onArticleChange?.(!!next);
|
|
2230
|
+
};
|
|
2129
2231
|
const root = el(doc, "div", "g-help");
|
|
2130
2232
|
const searchWrap = el(doc, "div", "g-rc-search");
|
|
2131
2233
|
const input = el(doc, "input", "g-input");
|
|
@@ -2136,6 +2238,8 @@ function createHelpView(options) {
|
|
|
2136
2238
|
const body = el(doc, "div", "g-panel-body");
|
|
2137
2239
|
root.append(searchWrap, body);
|
|
2138
2240
|
const hasBody = (a) => typeof a.body === "string" && !a.bodyOmitted;
|
|
2241
|
+
const canShowMe = (key) => !!options.onShowMe && !!startableGuide(options.guides?.(), key);
|
|
2242
|
+
const showMe = (key) => options.onShowMe?.(startableGuide(options.guides?.(), key)?.key ?? key);
|
|
2139
2243
|
const openArticle = (article) => {
|
|
2140
2244
|
if (article.externalUrl) {
|
|
2141
2245
|
window.open(article.externalUrl, "_blank", "noopener,noreferrer");
|
|
@@ -2144,20 +2248,20 @@ function createHelpView(options) {
|
|
|
2144
2248
|
}
|
|
2145
2249
|
const seq = ++openSeq;
|
|
2146
2250
|
if (hasBody(article) || !options.loadArticle) {
|
|
2147
|
-
|
|
2251
|
+
setActive({ article, loading: false });
|
|
2148
2252
|
if (hasBody(article)) options.onArticleView?.(article);
|
|
2149
2253
|
return render();
|
|
2150
2254
|
}
|
|
2151
|
-
|
|
2255
|
+
setActive({ article, loading: true });
|
|
2152
2256
|
render();
|
|
2153
2257
|
options.loadArticle(article.id).then((full) => {
|
|
2154
2258
|
if (seq !== openSeq || !active) return;
|
|
2155
|
-
|
|
2259
|
+
setActive({ article: full ?? { ...article, body: "" }, loading: false });
|
|
2156
2260
|
if (full) options.onArticleView?.(full);
|
|
2157
2261
|
render();
|
|
2158
2262
|
}).catch(() => {
|
|
2159
2263
|
if (seq !== openSeq || !active) return;
|
|
2160
|
-
|
|
2264
|
+
setActive({ article: { ...article, body: "" }, loading: false });
|
|
2161
2265
|
render();
|
|
2162
2266
|
});
|
|
2163
2267
|
};
|
|
@@ -2167,7 +2271,7 @@ function createHelpView(options) {
|
|
|
2167
2271
|
back.type = "button";
|
|
2168
2272
|
back.addEventListener("click", () => {
|
|
2169
2273
|
openSeq++;
|
|
2170
|
-
|
|
2274
|
+
setActive(null);
|
|
2171
2275
|
render();
|
|
2172
2276
|
});
|
|
2173
2277
|
const title = el(doc, "h3", "g-title", article.title);
|
|
@@ -2176,6 +2280,7 @@ function createHelpView(options) {
|
|
|
2176
2280
|
else if (article.body) {
|
|
2177
2281
|
setSanitizedHtml(content, article.body);
|
|
2178
2282
|
enhancePdfViewers(content, doc);
|
|
2283
|
+
enhanceShowMe(content, doc, canShowMe, showMe);
|
|
2179
2284
|
} else content.appendChild(el(doc, "div", "g-rc-empty", "This article couldn't be loaded."));
|
|
2180
2285
|
wrap.append(back, title, content);
|
|
2181
2286
|
body.appendChild(wrap);
|
|
@@ -2187,9 +2292,9 @@ function createHelpView(options) {
|
|
|
2187
2292
|
return s;
|
|
2188
2293
|
};
|
|
2189
2294
|
const articleButton = (a) => {
|
|
2190
|
-
const
|
|
2191
|
-
|
|
2192
|
-
body.appendChild(
|
|
2295
|
+
const key = a.walkthroughs?.find(canShowMe);
|
|
2296
|
+
const summary = a.hasVideo ? a.summary ? `Video \xB7 ${a.summary}` : "Video" : a.summary;
|
|
2297
|
+
body.appendChild(resultRow(doc, a.hasVideo ? "\u25B6" : "\u{1F4C4}", a.title, summary, () => openArticle(a), key ? () => showMe(key) : void 0));
|
|
2193
2298
|
};
|
|
2194
2299
|
function render() {
|
|
2195
2300
|
body.textContent = "";
|
|
@@ -2198,10 +2303,17 @@ function createHelpView(options) {
|
|
|
2198
2303
|
const member = options.audienceIds?.() ?? [];
|
|
2199
2304
|
const sections = (0, import_runtime8.resourceCenterSections)(data.collections, data.articles, member);
|
|
2200
2305
|
const q = query.trim().toLowerCase();
|
|
2306
|
+
if (q) {
|
|
2307
|
+
const walkthroughs = (data.recommended ?? []).filter((g) => (0, import_runtime8.matchesHelpQuery)(g.name, q) && startableGuide(options.guides?.() ?? data.recommended, g.id));
|
|
2308
|
+
if (walkthroughs.length) {
|
|
2309
|
+
section(`Walkthroughs (${walkthroughs.length})`);
|
|
2310
|
+
for (const g of walkthroughs) body.appendChild(resultRow(doc, "\u25B6", g.name, "Show me, step by step", () => options.onStartGuide?.(g.id)));
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2201
2313
|
body.append(...options.extra?.(q) ?? []);
|
|
2202
2314
|
if (q) {
|
|
2203
2315
|
const visible = sections.flatMap((s) => s.articles);
|
|
2204
|
-
const local = visible.filter((a) => a.title
|
|
2316
|
+
const local = visible.filter((a) => (0, import_runtime8.matchesHelpQuery)(`${a.title} ${a.summary}`, q));
|
|
2205
2317
|
const seen = new Set(local.map((a) => a.id));
|
|
2206
2318
|
const hidden = new Set(data.articles.map((a) => a.id).filter((id) => !visible.some((v) => v.id === id)));
|
|
2207
2319
|
const merged = [...local, ...serverResults.filter((a) => !seen.has(a.id) && !hidden.has(a.id) && (0, import_runtime8.isArticleListed)(data.collections, a, member))];
|
|
@@ -2273,7 +2385,7 @@ function createHelpView(options) {
|
|
|
2273
2385
|
const onInput = () => {
|
|
2274
2386
|
query = input.value;
|
|
2275
2387
|
openSeq++;
|
|
2276
|
-
|
|
2388
|
+
setActive(null);
|
|
2277
2389
|
render();
|
|
2278
2390
|
if (debounce) clearTimeout(debounce);
|
|
2279
2391
|
if (!options.search || !query.trim()) {
|
|
@@ -2296,7 +2408,7 @@ function createHelpView(options) {
|
|
|
2296
2408
|
render,
|
|
2297
2409
|
reset() {
|
|
2298
2410
|
openSeq++;
|
|
2299
|
-
|
|
2411
|
+
setActive(null);
|
|
2300
2412
|
query = "";
|
|
2301
2413
|
input.value = "";
|
|
2302
2414
|
serverResults = [];
|
|
@@ -2305,6 +2417,7 @@ function createHelpView(options) {
|
|
|
2305
2417
|
const listed = options.data().articles.find((a) => a.id === id) ?? { id, slug: id, title: "Article", summary: "", updatedAt: "", bodyOmitted: true };
|
|
2306
2418
|
openArticle(listed);
|
|
2307
2419
|
},
|
|
2420
|
+
isArticleOpen: () => !!active,
|
|
2308
2421
|
focusSearch: () => input.focus?.({ preventScroll: true }),
|
|
2309
2422
|
setQuery(q) {
|
|
2310
2423
|
input.value = q;
|
|
@@ -2356,7 +2469,14 @@ function renderResourceCenter(options) {
|
|
|
2356
2469
|
loadArticle: options.loadArticle,
|
|
2357
2470
|
onStartGuide: options.onStartGuide,
|
|
2358
2471
|
onOpenChecklist: options.onOpenChecklist,
|
|
2359
|
-
onArticleView: options.onArticleView
|
|
2472
|
+
onArticleView: options.onArticleView,
|
|
2473
|
+
guides: () => guides,
|
|
2474
|
+
onShowMe: (key) => {
|
|
2475
|
+
setOpen(false);
|
|
2476
|
+
options.onStartGuide?.(key);
|
|
2477
|
+
},
|
|
2478
|
+
// Articles read in a larger panel.
|
|
2479
|
+
onArticleChange: (reading) => panel.classList.toggle("g-panel-reading", reading)
|
|
2360
2480
|
});
|
|
2361
2481
|
panel.append(head, help.el);
|
|
2362
2482
|
root.append(launcher, panel);
|
|
@@ -2542,7 +2662,7 @@ function previewStep(step, theme, options = {}) {
|
|
|
2542
2662
|
name: "Preview",
|
|
2543
2663
|
kind: "flow",
|
|
2544
2664
|
version: 0,
|
|
2545
|
-
config: { steps: [previewStepConfig], targeting: { id: "root", op: "and", rules: [] }, trigger: { type: "manual", delayMs: 0 }, frequency: { mode: "every_time", cooldownHours: 0, priority: 0, mutuallyExclusiveWith: [], reshowAfterDismiss: true }, appearance: options.appearance ?? { theme: "auto", animation: "fade" }, urlPatterns: [], platforms: ["web"] }
|
|
2665
|
+
config: { steps: [previewStepConfig], visibility: "everyone", targeting: { id: "root", op: "and", rules: [] }, trigger: { type: "manual", delayMs: 0 }, frequency: { mode: "every_time", cooldownHours: 0, priority: 0, mutuallyExclusiveWith: [], reshowAfterDismiss: true }, appearance: options.appearance ?? { theme: "auto", animation: "fade" }, urlPatterns: [], platforms: ["web"] }
|
|
2546
2666
|
};
|
|
2547
2667
|
const controller = runGuide(guide, { theme, doc: options.doc, onEnd: () => options.onEnd?.(), onNavigate: () => {
|
|
2548
2668
|
}, onAction: () => {
|