@guidelytech/guide-runtime 0.5.0 → 0.6.2
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 +1336 -0
- package/dist/capture.cjs.map +1 -0
- package/dist/capture.d.cts +184 -0
- package/dist/capture.d.ts +184 -0
- package/dist/capture.js +857 -0
- package/dist/capture.js.map +1 -0
- package/dist/{chunk-SLBUGXNH.js → chunk-4FKYF6OB.js} +196 -23
- package/dist/chunk-4FKYF6OB.js.map +1 -0
- package/dist/{chunk-SGFC66YD.js → chunk-4UW6YF44.js} +150 -8
- package/dist/chunk-4UW6YF44.js.map +1 -0
- package/dist/chunk-7DOGZKWD.js +322 -0
- package/dist/chunk-7DOGZKWD.js.map +1 -0
- package/dist/chunk-GUSKGNNR.js +301 -0
- package/dist/chunk-GUSKGNNR.js.map +1 -0
- package/dist/index.cjs +192 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -2
- package/dist/index.d.ts +12 -2
- package/dist/index.js +29 -561
- package/dist/index.js.map +1 -1
- package/dist/messenger.cjs +825 -57
- package/dist/messenger.cjs.map +1 -1
- package/dist/messenger.d.cts +61 -3
- package/dist/messenger.d.ts +61 -3
- package/dist/messenger.js +662 -45
- package/dist/messenger.js.map +1 -1
- package/package.json +7 -2
- package/dist/chunk-SGFC66YD.js.map +0 -1
- package/dist/chunk-SLBUGXNH.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/capture-styles.ts","../src/capture-dom.ts","../src/capture-screen.ts","../src/capture.ts"],"sourcesContent":["/** How-to capture toolbar styles, injected into the Guidely shadow root when capture starts. */\nexport const CAPTURE_STYLES = `\n.g-cap{position:fixed;left:50%;bottom:16px;transform:translateX(-50%);z-index:calc(var(--g-z) + 10);pointer-events:auto;display:flex;flex-direction:column;align-items:center;gap:6px;max-width:calc(100vw - 32px);font:500 13px/1.4 var(--g-font);color:#fff}\n.g-cap-row{display:flex;align-items:center;flex-wrap:wrap;gap:8px;padding:6px 6px 6px 14px;background:#111827;border-radius:999px;box-shadow:0 6px 24px rgba(0,0,0,.28)}\n.g-cap-dot{position:relative;flex:none;width:10px;height:10px;border-radius:999px;background:#9ca3af}\n.g-cap[data-state=\"recording\"] .g-cap-dot{background:#ef4444}\n.g-cap[data-state=\"recording\"] .g-cap-dot::after{content:\"\";position:absolute;inset:0;border-radius:inherit;background:#ef4444;animation:g-pulse 1.4s ease-out infinite}\n.g-cap-status{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:min(340px,60vw)}\n.g-cap .g-btn{min-height:28px;padding:3px 12px;font-size:13px;border-radius:999px}\n.g-cap .g-btn-secondary{background:rgba(255,255,255,.14);color:#fff;border-color:transparent}\n.g-cap .g-btn-secondary:hover{background:rgba(255,255,255,.22)}\n.g-cap .g-btn-ghost{color:#d1d5db}\n.g-cap .g-btn-ghost:hover{background:rgba(255,255,255,.1);color:#fff}\n.g-cap .g-cap-warn{background:#f59e0b;color:#111827}\n.g-cap-note{max-width:min(420px,calc(100vw - 32px));padding:6px 12px;border-radius:10px;background:#7c2d12;box-shadow:0 6px 24px rgba(0,0,0,.28)}\n.g-cap-count{display:none;font-variant-numeric:tabular-nums}\n.g-cap[data-compact] .g-cap-row{gap:6px;padding:5px 10px;opacity:.85}\n.g-cap[data-compact] .g-cap-row>:not(.g-cap-dot):not(.g-cap-count):not(.g-cap-warn){display:none}\n.g-cap[data-compact] .g-cap-count{display:inline}\n.g-cap[data-captured] .g-cap-dot,.g-cap[data-captured] .g-cap-dot::after{background:#22c55e}\n`;\n","import type { CapturedElement, NormalizedRect } from \"@guidelytech/shared\";\nimport { normalizeText } from \"./selectors\";\n\n/**\n * Pure DOM helpers for how-to capture: which element an interaction is about, how to name it for\n * people, which fields and on-screen text are sensitive, and where things sit in a screenshot.\n */\n\n/** Elements a click is \"about\" when it lands on them or inside them. */\nexport const CAPTURE_INTERACTIVE_SELECTOR =\n \"a,button,input,select,textarea,label,summary,[role=button],[role=link],[role=tab],[role=menuitem],[role=menuitemcheckbox],[role=menuitemradio],[role=option],[role=checkbox],[role=radio],[role=switch],[role=combobox],[contenteditable]:not([contenteditable=false])\";\n\n/** Always-redacted elements (on top of the project's mask selectors). */\nexport const REDACT_SELECTOR = \"[data-guidely-redact]\";\n\nconst TEXT_INPUT_TYPES = new Set([\"\", \"text\", \"email\", \"search\", \"tel\", \"url\", \"number\", \"password\", \"date\", \"datetime-local\", \"month\", \"week\", \"time\"]);\n\nfunction closestSafe(el: Element, selector: string): Element | null {\n if (!selector) return null;\n try {\n return el.closest(selector);\n } catch {\n return null;\n }\n}\n\nfunction inputType(el: Element): string {\n return el.tagName === \"INPUT\" ? (el.getAttribute(\"type\") || \"\").toLowerCase() : \"\";\n}\n\nfunction isEditableHost(el: Element): boolean {\n const v = el.getAttribute(\"contenteditable\");\n return v != null && v.toLowerCase() !== \"false\";\n}\n\n/** Fields whose typed text becomes a `type` step (instead of a click). */\nexport function isTextEntry(el: Element): boolean {\n if (el.tagName === \"TEXTAREA\") return true;\n if (el.tagName === \"INPUT\") return TEXT_INPUT_TYPES.has(inputType(el));\n return isEditableHost(el) || !!closestSafe(el, \"[contenteditable]:not([contenteditable=false])\");\n}\n\n/** The element a pointer interaction is about: the closest interactive ancestor, else the target itself. */\nexport function actionTarget(target: Element): Element {\n const hit = closestSafe(target, CAPTURE_INTERACTIVE_SELECTOR) ?? target;\n // A contenteditable descendant belongs to its editing host.\n if (!isEditableHost(hit) && isTextEntry(hit) && hit.tagName !== \"INPUT\" && hit.tagName !== \"TEXTAREA\") return closestSafe(hit, \"[contenteditable]:not([contenteditable=false])\") ?? hit;\n return hit;\n}\n\n/** The control a `<label>` is for (clicking the label acts on it), if any. */\nexport function labelControl(el: Element): Element | null {\n if (el.tagName !== \"LABEL\") return null;\n const label = el as HTMLLabelElement;\n if (label.control) return label.control;\n const forId = label.getAttribute(\"for\");\n if (forId) return label.ownerDocument.getElementById(forId);\n return label.querySelector(\"input,select,textarea\");\n}\n\n/** Is the element (or an ancestor) covered by the mask selectors or `[data-guidely-redact]`? */\nexport function isMaskedElement(el: Element, maskSelector: string): boolean {\n return !!closestSafe(el, REDACT_SELECTOR) || !!closestSafe(el, maskSelector);\n}\n\nconst SENSITIVE_NAME = /pass|card|cvv|cvc|ssn|secret|token|otp/i;\nconst SENSITIVE_AUTOCOMPLETE = /cc-|one-time-code|current-password|new-password/i;\n\n/** Fields whose value is never recorded and whose pixels are redacted from screenshots. */\nexport function isSensitiveField(el: Element, maskSelector = \"\"): boolean {\n if (inputType(el) === \"password\") return true;\n if (SENSITIVE_AUTOCOMPLETE.test(el.getAttribute(\"autocomplete\") || \"\")) return true;\n if (/^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName) && (SENSITIVE_NAME.test(el.getAttribute(\"name\") || \"\") || SENSITIVE_NAME.test(el.id || \"\"))) return true;\n return isMaskedElement(el, maskSelector);\n}\n\n// ---------------------------------------------------------------------------\n// Sensitive text patterns\n// ---------------------------------------------------------------------------\n\nconst EMAIL = /[A-Z0-9._%+-]+@[A-Z0-9-]+(?:\\.[A-Z0-9-]+)*\\.[A-Z]{2,}/gi;\n/** A run of digit groups (optionally `+cc` and `(area)`) separated by single spaces, dots or dashes. */\nconst NUMBER_RUN = /(?:\\+\\d{1,3}[ .-]?)?(?:\\(\\d{2,4}\\)[ .-]?)?\\d+(?:[ .-]\\d+)*/g;\n\nexport type SensitiveNumberKind = \"card\" | \"ssn\" | \"phone\";\n\n/**\n * Classifies a number run: card (13–19 digits in groups of 4+), US SSN (3-2-4) or phone\n * (10–13 digits written with a country code, area-code parentheses or 3+ groups ending in 4 digits).\n * Dates, times, prices, versions and IP addresses do not match.\n */\nexport function classifyNumber(run: string): SensitiveNumberKind | null {\n const groups = run.match(/\\d+/g) ?? [];\n const lens = groups.map((g) => g.length);\n const digits = lens.reduce((a, b) => a + b, 0);\n const special = /[+(.]/.test(run);\n if (lens.length === 3 && lens[0] === 3 && lens[1] === 2 && lens[2] === 4 && /^\\d{3}[ -]\\d{2}[ -]\\d{4}$/.test(run)) return \"ssn\";\n if (digits >= 13 && digits <= 19 && !special && lens.every((l) => l >= 4)) return \"card\";\n if (digits >= 10 && digits <= 13 && (/[+(]/.test(run) || (lens.length >= 3 && lens[lens.length - 1] === 4 && lens.every((l) => l >= 2) && !run.includes(\".\")))) return \"phone\";\n return null;\n}\n\n/** Character ranges `[start, end)` of emails, card numbers, SSNs and phone numbers in `text`. */\nexport function findSensitiveText(text: string): Array<[number, number]> {\n const out: Array<[number, number]> = [];\n if (text.length < 7) return out;\n if (text.includes(\"@\")) {\n EMAIL.lastIndex = 0;\n for (let m = EMAIL.exec(text); m && out.length < 200; m = EMAIL.exec(text)) out.push([m.index, m.index + m[0].length]);\n }\n if (/\\d{3}/.test(text)) {\n NUMBER_RUN.lastIndex = 0;\n for (let m = NUMBER_RUN.exec(text); m && out.length < 200; m = NUMBER_RUN.exec(text)) {\n if (m[0].length >= 9 && classifyNumber(m[0])) out.push([m.index, m.index + m[0].length]);\n }\n }\n return out;\n}\n\n/** Typed values are recorded verbatim unless they contain something like a card number or SSN. */\nexport function looksLikeSecretValue(value: string): boolean {\n NUMBER_RUN.lastIndex = 0;\n for (let m = NUMBER_RUN.exec(value); m; m = NUMBER_RUN.exec(value)) {\n const kind = classifyNumber(m[0]);\n if (kind === \"card\" || kind === \"ssn\") return true;\n }\n return false;\n}\n\n// ---------------------------------------------------------------------------\n// Element description\n// ---------------------------------------------------------------------------\n\nconst ROLE_MAP: Record<string, string> = {\n button: \"button\",\n link: \"link\",\n tab: \"tab\",\n menuitem: \"menuitem\",\n menuitemcheckbox: \"menuitem\",\n menuitemradio: \"menuitem\",\n checkbox: \"checkbox\",\n radio: \"radio\",\n switch: \"switch\",\n option: \"option\",\n combobox: \"dropdown\",\n listbox: \"dropdown\",\n textbox: \"field\",\n searchbox: \"field\",\n spinbutton: \"field\",\n};\n\n/** Normalised role: button, link, tab, menuitem, checkbox, radio, switch, field, dropdown, option or element. */\nexport function captureRole(el: Element): string {\n const explicit = (el.getAttribute(\"role\") || \"\").trim().split(/\\s+/)[0]!.toLowerCase();\n if (ROLE_MAP[explicit]) return ROLE_MAP[explicit]!;\n const tag = el.tagName.toLowerCase();\n if (tag === \"a\") return el.hasAttribute(\"href\") ? \"link\" : \"element\";\n if (tag === \"button\" || tag === \"summary\") return \"button\";\n if (tag === \"select\") return \"dropdown\";\n if (tag === \"option\") return \"option\";\n if (tag === \"textarea\") return \"field\";\n if (tag === \"input\") {\n const t = inputType(el);\n if (t === \"checkbox\") return \"checkbox\";\n if (t === \"radio\") return \"radio\";\n if (t === \"button\" || t === \"submit\" || t === \"reset\" || t === \"image\") return \"button\";\n return \"field\";\n }\n if (isEditableHost(el)) return \"field\";\n return \"element\";\n}\n\nconst clip = (s: string, max = 80) => (s.length > max ? `${s.slice(0, max - 1).trimEnd()}…` : s);\n\nfunction textOf(el: Element): string {\n return normalizeText((el as HTMLElement).innerText ?? el.textContent);\n}\n\nfunction labelledBy(el: Element): string {\n const ids = (el.getAttribute(\"aria-labelledby\") || \"\").split(/\\s+/).filter(Boolean);\n return normalizeText(ids.map((id) => el.ownerDocument.getElementById(id)).filter((x): x is HTMLElement => !!x).map(textOf).join(\" \"));\n}\n\nfunction associatedLabel(el: Element): string {\n const labels = (el as HTMLInputElement).labels;\n if (labels && labels.length) return normalizeText(Array.from(labels).map(textOf).join(\" \"));\n if (el.id) {\n try {\n const l = el.ownerDocument.querySelector(`label[for=\"${el.id.replace(/[\"\\\\]/g, \"\\\\$&\")}\"]`);\n if (l) return textOf(l);\n } catch {\n /* ignore */\n }\n }\n const wrap = el.parentElement?.closest(\"label\");\n return wrap ? textOf(wrap) : \"\";\n}\n\n/**\n * Visible name of an element for people: aria-label, aria-labelledby, its `<label>`, its text\n * (not for fields, whose value is user data), placeholder, alt, title or name. Max 80 characters.\n */\nexport function captureLabel(el: Element): string {\n const attr = (name: string) => normalizeText(el.getAttribute(name));\n const tag = el.tagName;\n const field = tag === \"INPUT\" || tag === \"TEXTAREA\" || tag === \"SELECT\" || isEditableHost(el);\n const candidates: Array<() => string> = [\n () => attr(\"aria-label\"),\n () => labelledBy(el),\n () => (/^(INPUT|TEXTAREA|SELECT|METER|PROGRESS|OUTPUT)$/.test(tag) ? associatedLabel(el) : \"\"),\n () => (tag === \"INPUT\" && /^(button|submit|reset)$/.test(inputType(el)) ? normalizeText((el as HTMLInputElement).value) : \"\"),\n () => (field ? \"\" : textOf(el)),\n () => attr(\"placeholder\"),\n () => attr(\"alt\"),\n () => normalizeText(el.querySelector?.(\"img[alt]\")?.getAttribute(\"alt\")),\n () => attr(\"title\"),\n () => normalizeText(el.querySelector?.(\"svg title\")?.textContent),\n () => attr(\"name\"),\n ];\n for (const c of candidates) {\n const v = c();\n if (v) return clip(v);\n }\n return \"\";\n}\n\n/** People-facing description of the element; masked elements get no label (never their text). */\nexport function describeCapturedElement(el: Element, maskSelector = \"\"): CapturedElement {\n return { label: isMaskedElement(el, maskSelector) ? \"\" : captureLabel(el), role: captureRole(el), tag: el.tagName.toLowerCase() };\n}\n\n/** The value a `type` step records: trimmed, at most 200 characters. */\nexport function fieldValue(el: Element): string {\n const raw = el.tagName === \"INPUT\" || el.tagName === \"TEXTAREA\" ? (el as HTMLInputElement).value : textOf(el);\n return (raw || \"\").trim().slice(0, 200);\n}\n\n/** Text of the selected option(s) of a `<select>`. */\nexport function selectedOptionText(el: HTMLSelectElement): string {\n return normalizeText(Array.from(el.selectedOptions ?? []).map((o) => o.text || o.value).join(\", \")).slice(0, 200);\n}\n\n// ---------------------------------------------------------------------------\n// Geometry\n// ---------------------------------------------------------------------------\n\nexport interface Box {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nconst clamp01 = (n: number) => Math.min(1, Math.max(0, n));\nconst round = (n: number) => Math.round(n * 10000) / 10000;\n\n/**\n * Maps a viewport rect (CSS px) to fractions of a screenshot of that viewport, padded by `pad`\n * CSS px and clamped to the frame. Returns null when nothing of it is on screen.\n */\nexport function normalizeRect(rect: Box, viewport: { width: number; height: number }, pad = 4): NormalizedRect | null {\n if (!viewport.width || !viewport.height || rect.width <= 0 || rect.height <= 0) return null;\n const x0 = clamp01((rect.x - pad) / viewport.width);\n const y0 = clamp01((rect.y - pad) / viewport.height);\n const x1 = clamp01((rect.x + rect.width + pad) / viewport.width);\n const y1 = clamp01((rect.y + rect.height + pad) / viewport.height);\n if (x1 <= x0 || y1 <= y0) return null;\n return { x: round(x0), y: round(y0), width: round(x1 - x0), height: round(y1 - y0) };\n}\n\n/** Scales a viewport rect (CSS px) to frame pixels, clipped to the frame. */\nexport function toFrameRect(rect: Box, scaleX: number, scaleY: number, frame: { width: number; height: number }, pad = 0): Box | null {\n const x0 = Math.max(0, Math.floor((rect.x - pad) * scaleX));\n const y0 = Math.max(0, Math.floor((rect.y - pad) * scaleY));\n const x1 = Math.min(frame.width, Math.ceil((rect.x + rect.width + pad) * scaleX));\n const y1 = Math.min(frame.height, Math.ceil((rect.y + rect.height + pad) * scaleY));\n return x1 > x0 && y1 > y0 ? { x: x0, y: y0, width: x1 - x0, height: y1 - y0 } : null;\n}\n\nfunction intersectsViewport(r: Box, vw: number, vh: number): boolean {\n return r.width > 0 && r.height > 0 && r.x < vw && r.y < vh && r.x + r.width > 0 && r.y + r.height > 0;\n}\n\nconst SKIP_TEXT_PARENTS = /^(SCRIPT|STYLE|NOSCRIPT|TEMPLATE|TEXTAREA|OPTION)$/;\n\nexport interface RedactionOptions {\n maskSelector?: string;\n /** Stop after this many text nodes (keeps the synchronous scan cheap on huge pages). */\n maxTextNodes?: number;\n}\n\n/**\n * Viewport rects (CSS px) to blur in a screenshot: password and other sensitive fields, masked and\n * `[data-guidely-redact]` elements, field values and visible text that look like emails, card\n * numbers, SSNs or phone numbers. Runs synchronously so it matches the frame grabbed with it.\n */\nexport function collectRedactions(doc: Document, options: RedactionOptions = {}): Box[] {\n const win = doc.defaultView;\n const vw = win?.innerWidth ?? 0;\n const vh = win?.innerHeight ?? 0;\n const out: Box[] = [];\n const push = (r: Box) => {\n if (intersectsViewport(r, vw, vh)) out.push({ x: r.x, y: r.y, width: r.width, height: r.height });\n };\n const root = doc.body;\n if (!root) return out;\n const mask = options.maskSelector || \"\";\n const blocks = new Set<Element>();\n try {\n root.querySelectorAll(mask ? `${REDACT_SELECTOR},${mask}` : REDACT_SELECTOR).forEach((el) => blocks.add(el));\n } catch {\n root.querySelectorAll(REDACT_SELECTOR).forEach((el) => blocks.add(el));\n }\n root.querySelectorAll(\"input,textarea,select,[contenteditable]:not([contenteditable=false])\").forEach((el) => {\n if (blocks.has(el)) return;\n const t = inputType(el);\n if (t === \"hidden\" || t === \"checkbox\" || t === \"radio\" || t === \"button\" || t === \"submit\" || t === \"reset\") return;\n if (isSensitiveField(el, mask)) blocks.add(el);\n else if ((el.tagName === \"INPUT\" || el.tagName === \"TEXTAREA\") && findSensitiveText((el as HTMLInputElement).value || \"\").length) blocks.add(el);\n });\n blocks.forEach((el) => push(el.getBoundingClientRect()));\n\n const max = options.maxTextNodes ?? 5000;\n const walker = doc.createTreeWalker(root, 4 /* NodeFilter.SHOW_TEXT */);\n let range: Range | null = null;\n let seen = 0;\n for (let node = walker.nextNode(); node && seen < max; node = walker.nextNode()) {\n const text = node.nodeValue || \"\";\n if (text.length < 7) continue;\n seen++;\n const parent = node.parentElement;\n if (!parent || SKIP_TEXT_PARENTS.test(parent.tagName)) continue;\n const hits = findSensitiveText(text);\n if (!hits.length) continue;\n // Text inside an element that is already blurred as a whole needs no second pass.\n if (Array.from(blocks).some((b) => b.contains(parent))) continue;\n range ??= doc.createRange();\n for (const [start, end] of hits) {\n try {\n range.setStart(node, start);\n range.setEnd(node, end);\n Array.from(range.getClientRects()).forEach(push);\n } catch {\n /* node changed under us */\n }\n }\n }\n return out;\n}\n","import { toFrameRect, type Box } from \"./capture-dom\";\n\n/**\n * Screenshots for how-to capture: a tab-capture stream (`getDisplayMedia`) played into a hidden\n * video, frames grabbed synchronously into a canvas, sensitive regions blurred into the pixels and\n * the result encoded — all in the browser, so raw pixels of redacted regions never leave it.\n */\n\nexport type ScreenResult = { ok: true; screen: ScreenSource } | { ok: false; reason: \"unsupported\" | \"denied\" | \"wrong-surface\" | \"error\" };\n\nexport interface ScreenSource {\n /** True while the stream delivers frames. */\n live(): boolean;\n /** Called once when the user stops sharing (or the stream ends otherwise). */\n onEnded(cb: () => void): void;\n /** Draws the current frame into a new canvas (synchronously). Null when no frame is available. */\n grab(): HTMLCanvasElement | null;\n /** Does the current frame show the page as it is after `changedAt` (a `performance.now()` time)? */\n freshSince(changedAt: number): boolean;\n /** Resolves when the next frame arrives (or after `timeoutMs`). */\n nextFrame(timeoutMs?: number): Promise<void>;\n stop(): void;\n}\n\n/** Can this browser capture the tab at all? */\nexport function canCaptureScreen(): boolean {\n return typeof navigator !== \"undefined\" && !!navigator.mediaDevices && typeof navigator.mediaDevices.getDisplayMedia === \"function\";\n}\n\n/**\n * Asks the user to share this tab (must run from a user gesture) and plays the stream into a\n * hidden `<video>` appended to `container`. Rejects windows and screens: their frames do not\n * match the page's viewport, so highlights and redactions would land in the wrong place.\n */\nexport async function requestTabCapture(container: Node, doc: Document = document): Promise<ScreenResult> {\n if (!canCaptureScreen()) return { ok: false, reason: \"unsupported\" };\n let stream: MediaStream;\n try {\n stream = await navigator.mediaDevices.getDisplayMedia({\n video: { displaySurface: \"browser\", frameRate: 5 },\n audio: false,\n preferCurrentTab: true,\n selfBrowserSurface: \"include\",\n surfaceSwitching: \"exclude\",\n } as DisplayMediaStreamOptions);\n } catch (err) {\n return { ok: false, reason: (err as { name?: string })?.name === \"NotAllowedError\" ? \"denied\" : \"error\" };\n }\n const track = stream.getVideoTracks()[0];\n if (!track) {\n stream.getTracks().forEach((t) => t.stop());\n return { ok: false, reason: \"error\" };\n }\n const surface = (track.getSettings?.() as { displaySurface?: string } | undefined)?.displaySurface;\n if (surface && surface !== \"browser\") {\n stream.getTracks().forEach((t) => t.stop());\n return { ok: false, reason: \"wrong-surface\" };\n }\n const video = doc.createElement(\"video\");\n video.muted = true;\n video.playsInline = true;\n video.setAttribute(\"aria-hidden\", \"true\");\n // Kept rendered (not display:none) so browsers keep decoding frames.\n video.style.cssText = \"position:fixed;left:0;top:0;width:2px;height:2px;opacity:0;pointer-events:none\";\n try {\n video.srcObject = stream;\n container.appendChild(video);\n } catch {\n stream.getTracks().forEach((t) => t.stop());\n return { ok: false, reason: \"error\" };\n }\n try {\n await video.play();\n if (!video.videoWidth) await new Promise<void>((resolve) => {\n const done = () => resolve();\n video.addEventListener(\"loadeddata\", done, { once: true });\n setTimeout(done, 1500);\n });\n } catch {\n /* frames may still arrive; grab() returns null until they do */\n }\n let ended = false;\n const listeners: Array<() => void> = [];\n // Frame timing: `requestVideoFrameCallback` where supported, else assume one frame period (5 fps) of lag.\n type FrameVideo = HTMLVideoElement & { requestVideoFrameCallback?: (cb: (now: number) => void) => number };\n const fv = video as FrameVideo;\n const rvfc = typeof fv.requestVideoFrameCallback === \"function\";\n let frameAt = 0;\n let waiters: Array<() => void> = [];\n const onFrame = (now: number) => {\n frameAt = now;\n waiters.splice(0).forEach((w) => w());\n if (!ended) fv.requestVideoFrameCallback!(onFrame);\n };\n if (rvfc) fv.requestVideoFrameCallback!(onFrame);\n const end = () => {\n if (ended) return;\n ended = true;\n stream.getTracks().forEach((t) => t.stop());\n video.srcObject = null;\n video.remove();\n listeners.splice(0).forEach((cb) => cb());\n };\n track.addEventListener(\"ended\", end);\n return {\n ok: true,\n screen: {\n live: () => !ended && track.readyState === \"live\",\n onEnded: (cb) => (ended ? cb() : listeners.push(cb)),\n grab: () => (ended ? null : grabFrame(video, doc)),\n freshSince: (changedAt) => (rvfc ? frameAt > changedAt : performance.now() - changedAt > 250),\n nextFrame: (timeoutMs = 500) =>\n new Promise<void>((resolve) => {\n const t = setTimeout(done, rvfc ? timeoutMs : 250);\n function done() {\n clearTimeout(t);\n waiters = waiters.filter((w) => w !== done);\n resolve();\n }\n if (rvfc) waiters.push(done);\n }),\n stop: () => {\n listeners.length = 0;\n end();\n },\n },\n };\n}\n\n/** Copies the video's current frame into a new canvas. */\nexport function grabFrame(video: HTMLVideoElement, doc: Document = document): HTMLCanvasElement | null {\n const w = video.videoWidth;\n const h = video.videoHeight;\n if (!w || !h) return null;\n const canvas = doc.createElement(\"canvas\");\n canvas.width = w;\n canvas.height = h;\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return null;\n ctx.drawImage(video, 0, 0, w, h);\n return canvas;\n}\n\n/**\n * Blurs viewport rects (CSS px) into the canvas by averaging each region down to a few samples per\n * line of text and scaling it back up, which leaves nothing legible to recover. Returns the number\n * of regions blurred.\n */\nexport function redactCanvas(canvas: HTMLCanvasElement, rects: Box[], viewport: { width: number; height: number }): number {\n const ctx = canvas.getContext(\"2d\");\n if (!ctx || !rects.length || !viewport.width || !viewport.height) return 0;\n const sx = canvas.width / viewport.width;\n const sy = canvas.height / viewport.height;\n const tmp = canvas.ownerDocument.createElement(\"canvas\");\n const tctx = tmp.getContext(\"2d\");\n if (!tctx) return 0;\n let count = 0;\n for (const rect of rects) {\n const r = toFrameRect(rect, sx, sy, canvas, 4);\n if (!r) continue;\n // One sample per ~line height (at least 16 CSS px) in both directions.\n const block = Math.max(16 * sy, Math.min(r.height, 48 * sy));\n tmp.width = Math.max(1, Math.round(r.width / block));\n tmp.height = Math.max(1, Math.round(r.height / block));\n tctx.imageSmoothingEnabled = true;\n tctx.drawImage(canvas, r.x, r.y, r.width, r.height, 0, 0, tmp.width, tmp.height);\n ctx.imageSmoothingEnabled = true;\n ctx.drawImage(tmp, 0, 0, tmp.width, tmp.height, r.x, r.y, r.width, r.height);\n // A light tint so readers can tell the region was hidden on purpose.\n ctx.fillStyle = \"rgba(120,120,120,.28)\";\n ctx.fillRect(r.x, r.y, r.width, r.height);\n count++;\n }\n return count;\n}\n\nfunction scaled(canvas: HTMLCanvasElement, maxWidth: number): HTMLCanvasElement {\n if (canvas.width <= maxWidth) return canvas;\n const out = canvas.ownerDocument.createElement(\"canvas\");\n out.width = maxWidth;\n out.height = Math.round((canvas.height * maxWidth) / canvas.width);\n const ctx = out.getContext(\"2d\");\n if (!ctx) return canvas;\n ctx.imageSmoothingQuality = \"high\";\n ctx.drawImage(canvas, 0, 0, out.width, out.height);\n return out;\n}\n\nconst toBlob = (canvas: HTMLCanvasElement, type: string, quality: number) =>\n new Promise<Blob | null>((resolve) => {\n try {\n canvas.toBlob((b) => resolve(b), type, quality);\n } catch {\n resolve(null);\n }\n });\n\n/** Encodes as WebP (JPEG where WebP encoding is unsupported), at most `maxWidth` wide and `maxBytes` big. */\nexport async function encodeScreenshot(canvas: HTMLCanvasElement, options: { maxWidth?: number; maxBytes?: number } = {}): Promise<Blob | null> {\n const src = scaled(canvas, options.maxWidth ?? 2560);\n const maxBytes = options.maxBytes ?? 8 * 1024 * 1024;\n let blob = await toBlob(src, \"image/webp\", 0.85);\n // Browsers without a WebP encoder silently return PNG.\n if (!blob || blob.type !== \"image/webp\") blob = await toBlob(src, \"image/jpeg\", 0.85);\n if (blob && blob.size > maxBytes) blob = await toBlob(src, \"image/jpeg\", 0.6);\n return blob && blob.size <= maxBytes ? blob : null;\n}\n\n/** Synchronous JPEG data URL (≤ 1600 px wide), for persisting an unsent screenshot while the page unloads. */\nexport function screenshotDataUrl(canvas: HTMLCanvasElement): string | null {\n try {\n return scaled(canvas, 1600).toDataURL(\"image/jpeg\", 0.7);\n } catch {\n return null;\n }\n}\n","import type { CaptureStepInput, HowToAction } from \"@guidelytech/shared\";\nimport { CAPTURE_STYLES } from \"./capture-styles\";\nimport {\n actionTarget,\n collectRedactions,\n describeCapturedElement,\n fieldValue,\n isMaskedElement,\n isSensitiveField,\n isTextEntry,\n labelControl,\n looksLikeSecretValue,\n normalizeRect,\n selectedOptionText,\n} from \"./capture-dom\";\nimport { canCaptureScreen, redactCanvas, requestTabCapture, type ScreenSource } from \"./capture-screen\";\nimport { createScope } from \"./host\";\nimport { describeElement, isGuidelyElement } from \"./selectors\";\n\nexport * from \"./capture-dom\";\nexport { canCaptureScreen, encodeScreenshot, grabFrame, redactCanvas, requestTabCapture, screenshotDataUrl } from \"./capture-screen\";\nexport type { ScreenResult, ScreenSource } from \"./capture-screen\";\n\n/** A captured step before the SDK gives it a `clientId`. */\nexport type CaptureStepDraft = Omit<CaptureStepInput, \"clientId\">;\n\nexport interface CapturedStep {\n step: CaptureStepDraft;\n /**\n * The screenshot, already redacted (encode it with `encodeScreenshot`); null without screenshots.\n * A promise when the current frame predates a scroll or resize: it resolves with the next frame,\n * after `step.highlight` and `step.redactions` were updated to match it.\n */\n frame: HTMLCanvasElement | Promise<HTMLCanvasElement | null> | null;\n}\n\nexport interface CaptureOptions {\n /** How-to title shown in the toolbar before recording starts. */\n title: string;\n /** Steps already recorded (a recording continues across full page loads). */\n stepCount?: number;\n /** False when the server cannot store screenshots: record steps only. Default true. */\n screenshots?: boolean;\n /**\n * Continue a recording after a full page load: start recording straight away. `navigate` adds a\n * step for the new page; `screenshots` offers \"Screenshots paused — Resume\" (the stream is gone).\n */\n resume?: { navigate: boolean; screenshots: boolean };\n /** Comma-joined CSS selectors whose text and pixels are never captured. */\n maskSelector?: string;\n onStep(step: CapturedStep): void;\n /** Recording started; `screenshots` is whether the user chose to take screenshots. */\n onStart?(info: { screenshots: boolean }): void;\n onFinish(): void;\n onCancel(): void;\n doc?: Document;\n}\n\nexport interface CaptureSession {\n /** Shows \"Saving…\" (and stops recording) while queued uploads finish. */\n setSaving(saving: boolean): void;\n isRecording(): boolean;\n stop(): void;\n}\n\n/** A repeat press on the same element within this window is a double click, not a new step. */\nexport const DOUBLE_CLICK_MS = 400;\n/** URL changes this soon after an interaction were caused by it (no separate navigate step). */\nexport const NAV_AFTER_ACTION_MS = 1500;\n/** Wait for the page to settle before capturing a navigate step. */\nexport const NAV_SETTLE_MS = 600;\n\ntype Phase = \"ready\" | \"recording\" | \"paused\" | \"saving\";\n\nconst NOTICES = {\n \"wrong-surface\": \"Share this tab (not a window or the whole screen) so screenshots match the page.\",\n denied: \"Screen sharing was cancelled. Start again, or record without screenshots.\",\n error: \"Screen capture could not start. Try again, or record without screenshots.\",\n unsupported: \"This browser cannot capture screenshots; steps are recorded without them.\",\n lost: \"Screenshots paused: sharing stopped or the page reloaded.\",\n} as const;\n\nfunction button(doc: Document, text: string, cls: string, attr: string): HTMLButtonElement {\n const b = doc.createElement(\"button\");\n b.type = \"button\";\n b.className = `g-btn ${cls}`;\n b.textContent = text;\n b.setAttribute(attr, \"\");\n return b;\n}\n\nfunction eventTarget(e: Event): Element | null {\n const path = typeof e.composedPath === \"function\" ? e.composedPath() : [];\n const first = (path[0] ?? e.target) as Node | null;\n if (!first) return null;\n return first.nodeType === 1 ? (first as Element) : first.parentElement;\n}\n\n/** The focused element inside a shadow-DOM subtree, if any. */\nfunction deepActiveElementIn(el: Element): Element | null {\n const root = el.getRootNode() as Document | ShadowRoot;\n return root.activeElement ?? null;\n}\n\nfunction deepActiveElement(doc: Document): Element | null {\n let el = doc.activeElement;\n while (el?.shadowRoot?.activeElement) el = el.shadowRoot.activeElement;\n return el;\n}\n\n/**\n * Starts how-to capture: a Shadow-DOM toolbar (bottom centre, a small pill while recording) and passive capture-phase\n * listeners that turn clicks, typing, selections and navigations into steps. The app keeps\n * receiving every event; nothing is prevented.\n */\nexport function startCapture(options: CaptureOptions): CaptureSession {\n const doc = options.doc ?? document;\n const win = doc.defaultView ?? window;\n const mask = options.maskSelector ?? \"\";\n const shotsAllowed = options.screenshots !== false;\n const scope = createScope(null, { theme: \"light\", animation: \"fade\" }, doc);\n const style = doc.createElement(\"style\");\n style.textContent = CAPTURE_STYLES;\n scope.el.appendChild(style);\n\n const bar = doc.createElement(\"div\");\n bar.className = \"g-cap\";\n bar.setAttribute(\"data-guidely-capture-bar\", \"\");\n bar.setAttribute(\"role\", \"toolbar\");\n bar.setAttribute(\"aria-label\", \"Guidely how-to recorder\");\n const note = doc.createElement(\"div\");\n note.className = \"g-cap-note\";\n note.setAttribute(\"data-guidely-capture-notice\", \"\");\n note.hidden = true;\n const row = doc.createElement(\"div\");\n row.className = \"g-cap-row\";\n const dot = doc.createElement(\"span\");\n dot.className = \"g-cap-dot\";\n const status = doc.createElement(\"span\");\n status.className = \"g-cap-status\";\n status.setAttribute(\"aria-live\", \"polite\");\n const count_ = doc.createElement(\"span\");\n count_.className = \"g-cap-count\";\n count_.setAttribute(\"aria-hidden\", \"true\");\n const start = button(doc, \"Start recording\", \"g-btn-primary\", \"data-guidely-capture-start\");\n const noShots = button(doc, \"Record without screenshots\", \"g-btn-secondary\", \"data-guidely-capture-no-screenshots\");\n const resumeShots = button(doc, \"Screenshots paused — Resume\", \"g-cap-warn\", \"data-guidely-capture-resume-screenshots\");\n const pause = button(doc, \"Pause\", \"g-btn-secondary\", \"data-guidely-capture-pause\");\n const done = button(doc, \"Done\", \"g-btn-primary\", \"data-guidely-capture-done\");\n const cancel = button(doc, \"Cancel\", \"g-btn-ghost\", \"data-guidely-capture-cancel\");\n row.append(dot, count_, status, start, noShots, resumeShots, pause, done, cancel);\n bar.append(note, row);\n scope.el.appendChild(bar);\n\n let phase: Phase = \"ready\";\n let count = options.stepCount ?? 0;\n let screen: ScreenSource | null = null;\n /** The user chose screenshots (so a lost stream shows \"Resume\"). */\n let wantShots = false;\n let requesting = false;\n let stopped = false;\n let lastAction = 0;\n /** `performance.now()` of the last scroll or resize: frames older than that do not match the DOM. */\n let movedAt = -1;\n let lastPress: { el: Element; at: number } | null = null;\n let lastUrl = win.location.href;\n let navTimer: ReturnType<typeof setTimeout> | null = null;\n let pulseTimer: ReturnType<typeof setTimeout> | null = null;\n /** `performance.now()` of the last keystroke in a field: a `type` step needs a frame that shows it. */\n let typedAt = -1;\n /**\n * While recording, the bar is a small pill (dot and step count) unless the pointer or focus is on\n * it, so screenshots show as little of it as possible.\n */\n let expanded = false; /** Field value at focus (or when last recorded), so each edit is recorded once. */\n const baseline = new WeakMap<Element, string>();\n\n const recording = () => phase === \"recording\" && !stopped;\n const steps = (n: number) => `${n} step${n === 1 ? \"\" : \"s\"}`;\n\n const render = () => {\n bar.setAttribute(\"data-state\", phase);\n bar.toggleAttribute(\"data-compact\", phase === \"recording\" && !expanded);\n const ready = phase === \"ready\";\n const active = phase === \"recording\" || phase === \"paused\";\n start.hidden = !ready;\n start.disabled = requesting;\n noShots.hidden = !ready || !shotsAllowed || !canCaptureScreen();\n noShots.disabled = requesting;\n resumeShots.hidden = !active || !wantShots || !!screen?.live();\n resumeShots.disabled = requesting;\n pause.hidden = !active;\n pause.textContent = phase === \"paused\" ? \"Resume\" : \"Pause\";\n done.hidden = !active;\n cancel.hidden = phase === \"saving\";\n if (ready) status.textContent = requesting ? \"Choose this tab to share…\" : `Ready to record “${options.title}”`;\n else if (phase === \"saving\") status.textContent = \"Saving steps…\";\n else status.textContent = `${phase === \"paused\" ? \"Paused\" : \"Recording\"} · ${steps(count)}`;\n count_.textContent = String(count);\n };\n\n const showNotice = (text: string | null) => {\n note.textContent = text ?? \"\";\n note.hidden = !text;\n };\n\n /** Briefly turns the dot green: a step was captured. */\n const pulse = () => {\n bar.setAttribute(\"data-captured\", \"\");\n if (pulseTimer) clearTimeout(pulseTimer);\n pulseTimer = setTimeout(() => bar.removeAttribute(\"data-captured\"), 700);\n };\n\n /** Grabs the frame, blurs sensitive regions and sets the highlight; all from the same moment's layout. */\n const shoot = (step: CaptureStepDraft, highlightEl: Element | null): HTMLCanvasElement | null => {\n const frame = screen?.live() ? screen.grab() : null;\n if (!frame) return null;\n const viewport = { width: win.innerWidth, height: win.innerHeight };\n try {\n step.redactions = redactCanvas(frame, collectRedactions(doc, { maskSelector: mask }), viewport);\n } catch {\n // Never upload a frame that could not be redacted.\n return null;\n }\n const highlight = highlightEl?.isConnected ? normalizeRect(highlightEl.getBoundingClientRect(), viewport) : null;\n if (highlight) step.highlight = highlight;\n else delete step.highlight;\n return frame;\n };\n\n /** Captures one step. The frame is grabbed synchronously, before the app reacts to the event. */\n const record = (action: HowToAction, el: Element | null, extra: { value?: string; valueRedacted?: boolean; highlightEl?: Element | null } = {}) => {\n const step: CaptureStepDraft = { action, url: win.location.href, pageTitle: (doc.title || \"\").slice(0, 300), redactions: 0, occurredAt: new Date().toISOString() };\n let highlightEl: Element | null = null;\n if (el) {\n const masked = isMaskedElement(el, mask);\n step.element = describeCapturedElement(el, mask);\n step.selectors = describeElement(el).selectors.filter((s) => !masked || s.kind !== \"text\");\n const box = extra.highlightEl?.getBoundingClientRect();\n highlightEl = extra.highlightEl && box && (box.width || box.height) ? extra.highlightEl : el;\n }\n let frame: CapturedStep[\"frame\"] = null;\n if (screen?.live()) {\n const s = screen;\n // A frame older than the last scroll shows content where it no longer is (redactions would miss it), and one\n // older than the last keystroke may not show the typed text yet: use the next one. A click keeps the current\n // frame, which shows the page before the app reacted.\n const changedAt = action === \"type\" || action === \"select\" ? Math.max(movedAt, typedAt) : movedAt;\n if (s.freshSince(changedAt)) frame = shoot(step, highlightEl);\n else frame = s.nextFrame().then(() => shoot(step, highlightEl));\n }\n if (extra.value !== undefined) step.value = extra.value;\n if (extra.valueRedacted) step.valueRedacted = true;\n if (action !== \"navigate\") lastAction = Date.now();\n count++;\n render();\n pulse();\n options.onStep({ step, frame });\n };\n\n /** Records a `type` step when the field's value changed since focus (or since it was last recorded). */\n const flushField = (el: Element) => {\n if (!recording() || !isTextEntry(el)) return;\n const host = el.tagName === \"INPUT\" || el.tagName === \"TEXTAREA\" ? el : actionTarget(el);\n const value = fieldValue(host);\n if (value === (baseline.get(host) ?? \"\")) return;\n baseline.set(host, value);\n if (!value) return;\n if (isSensitiveField(host, mask) || looksLikeSecretValue(value)) record(\"type\", host, { valueRedacted: true });\n else record(\"type\", host, { value });\n };\n\n const onPointerDown = (e: PointerEvent) => {\n if (!recording() || e.button !== 0) return;\n const target = eventTarget(e);\n if (!target || isGuidelyElement(target)) return;\n // Typing into the focused field happened before this click: record it first.\n const active = deepActiveElement(doc);\n if (active && active !== target && !active.contains(target) && isTextEntry(active)) flushField(active);\n let el = actionTarget(target);\n let highlightEl: Element | null = null;\n const control = labelControl(el);\n if (control) {\n if (isTextEntry(control) || control.tagName === \"SELECT\") return; // the label only focuses the field\n highlightEl = el;\n el = control;\n }\n if (isTextEntry(el) || el.tagName === \"SELECT\" || el.closest(\"select\")) return; // `change` records these\n if ((el as HTMLButtonElement).disabled) return;\n const now = Date.now();\n if (lastPress && lastPress.el === el && now - lastPress.at < DOUBLE_CLICK_MS) {\n lastPress.at = now;\n return;\n }\n lastPress = { el, at: now };\n record(\"click\", el, { highlightEl });\n };\n\n const onChange = (e: Event) => {\n if (!recording()) return;\n const el = eventTarget(e);\n if (!el || isGuidelyElement(el)) return;\n if (el.tagName === \"SELECT\") {\n const sensitive = isSensitiveField(el, mask);\n const value = sensitive ? \"\" : selectedOptionText(el as HTMLSelectElement);\n record(\"select\", el, sensitive ? { valueRedacted: true } : value ? { value } : {});\n return;\n }\n const type = el.tagName === \"INPUT\" ? (el.getAttribute(\"type\") || \"\").toLowerCase() : \"\";\n if (type === \"checkbox\" || type === \"radio\") {\n // Toggled with the keyboard (no pointer press on it or its label just now).\n const pressed = lastPress && Date.now() - lastPress.at < 1000 && lastPress.el === el;\n if (!pressed) record(\"click\", el);\n return;\n }\n if (isTextEntry(el)) flushField(el);\n };\n\n const onFocusIn = (e: FocusEvent) => {\n const el = eventTarget(e);\n if (el && !isGuidelyElement(el) && isTextEntry(el)) {\n const host = el.tagName === \"INPUT\" || el.tagName === \"TEXTAREA\" ? el : actionTarget(el);\n baseline.set(host, fieldValue(host));\n }\n };\n\n const onFocusOut = (e: FocusEvent) => {\n const el = eventTarget(e);\n // Inputs report edits through `change`; contenteditable regions only through blur.\n if (el && el.tagName !== \"INPUT\" && el.tagName !== \"TEXTAREA\" && isTextEntry(el)) flushField(el);\n };\n\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key !== \"Enter\" || e.isComposing) return;\n const el = eventTarget(e);\n if (el && (el.tagName === \"INPUT\" || ((e.metaKey || e.ctrlKey) && isTextEntry(el)))) flushField(el);\n };\n\n const checkUrl = () => {\n const url = win.location.href;\n if (url === lastUrl) return;\n lastUrl = url;\n if (!recording() || Date.now() - lastAction < NAV_AFTER_ACTION_MS) return;\n if (navTimer) clearTimeout(navTimer);\n navTimer = setTimeout(() => {\n navTimer = null;\n if (recording()) record(\"navigate\", null);\n }, NAV_SETTLE_MS);\n };\n const poll = setInterval(checkUrl, 300);\n\n const onMove = () => {\n movedAt = performance.now();\n };\n const onInput = (e: Event) => {\n const el = eventTarget(e);\n if (el && !isGuidelyElement(el)) typedAt = performance.now();\n };\n const expand = (open: boolean) => {\n if (expanded === open) return;\n expanded = open;\n render();\n };\n const onBarEnter = () => expand(true);\n const onBarLeave = () => expand(bar.contains(deepActiveElementIn(bar)));\n const onBarFocusOut = (e: FocusEvent) => expand(!!e.relatedTarget && bar.contains(e.relatedTarget as Node));\n bar.addEventListener(\"pointerenter\", onBarEnter);\n bar.addEventListener(\"pointerleave\", onBarLeave);\n bar.addEventListener(\"focusin\", onBarEnter);\n bar.addEventListener(\"focusout\", onBarFocusOut);\n\n const listen = { capture: true, passive: true } as const;\n doc.addEventListener(\"scroll\", onMove, listen);\n win.addEventListener(\"resize\", onMove);\n doc.addEventListener(\"pointerdown\", onPointerDown, listen);\n doc.addEventListener(\"change\", onChange, listen);\n doc.addEventListener(\"input\", onInput, listen);\n doc.addEventListener(\"focusin\", onFocusIn, listen);\n doc.addEventListener(\"focusout\", onFocusOut, listen);\n doc.addEventListener(\"keydown\", onKeyDown, listen);\n win.addEventListener(\"popstate\", checkUrl);\n win.addEventListener(\"hashchange\", checkUrl);\n\n const onLost = () => {\n screen = null;\n if (!stopped && wantShots) showNotice(NOTICES.lost);\n render();\n };\n\n /** Asks for the tab stream (needs the click that called it). */\n const requestScreen = async (): Promise<boolean> => {\n requesting = true;\n showNotice(null);\n render();\n const res = await requestTabCapture(scope.el, doc);\n requesting = false;\n if (stopped) {\n if (res.ok) res.screen.stop();\n return false;\n }\n if (res.ok) {\n screen = res.screen;\n screen.onEnded(onLost);\n } else showNotice(NOTICES[res.reason]);\n render();\n return res.ok;\n };\n\n const begin = (shots: boolean) => {\n wantShots = shots;\n phase = \"recording\";\n // Collapse to the pill even though the pointer is still on the Start button, and take the first\n // screenshot from a frame drawn after that.\n expanded = false;\n movedAt = performance.now();\n primeActive();\n lastUrl = win.location.href;\n render();\n options.onStart?.({ screenshots: shots });\n record(\"navigate\", null);\n };\n\n start.addEventListener(\"click\", () => {\n if (requesting) return;\n if (!shotsAllowed || !canCaptureScreen()) {\n if (shotsAllowed) showNotice(NOTICES.unsupported);\n return begin(false);\n }\n void requestScreen().then((ok) => {\n if (ok) begin(true);\n });\n });\n noShots.addEventListener(\"click\", () => {\n showNotice(null);\n begin(false);\n });\n resumeShots.addEventListener(\"click\", () => {\n if (!requesting) void requestScreen();\n });\n pause.addEventListener(\"click\", () => {\n if (phase === \"recording\") {\n flushActive();\n phase = \"paused\";\n } else if (phase === \"paused\") {\n phase = \"recording\";\n lastUrl = win.location.href;\n }\n render();\n });\n /** Typing still in the focused field (Safari does not move focus to a clicked button). */\n const flushActive = () => {\n const active = deepActiveElement(doc);\n if (active && !isGuidelyElement(active) && isTextEntry(active)) flushField(active);\n };\n const primeActive = () => {\n const active = deepActiveElement(doc);\n if (active && !isGuidelyElement(active) && isTextEntry(active)) baseline.set(active, fieldValue(active));\n };\n\n done.addEventListener(\"click\", () => {\n if (phase === \"saving\") return;\n flushActive();\n options.onFinish();\n });\n cancel.addEventListener(\"click\", () => {\n stop();\n options.onCancel();\n });\n\n const stop = () => {\n if (stopped) return;\n stopped = true;\n clearInterval(poll);\n if (navTimer) clearTimeout(navTimer);\n if (pulseTimer) clearTimeout(pulseTimer);\n doc.removeEventListener(\"scroll\", onMove, listen);\n win.removeEventListener(\"resize\", onMove);\n doc.removeEventListener(\"pointerdown\", onPointerDown, listen);\n doc.removeEventListener(\"change\", onChange, listen);\n doc.removeEventListener(\"input\", onInput, listen);\n doc.removeEventListener(\"focusin\", onFocusIn, listen);\n doc.removeEventListener(\"focusout\", onFocusOut, listen);\n doc.removeEventListener(\"keydown\", onKeyDown, listen);\n win.removeEventListener(\"popstate\", checkUrl);\n win.removeEventListener(\"hashchange\", checkUrl);\n screen?.stop();\n screen = null;\n scope.destroy();\n };\n\n if (options.resume) {\n wantShots = shotsAllowed && options.resume.screenshots && canCaptureScreen();\n phase = \"recording\";\n if (wantShots) showNotice(NOTICES.lost);\n primeActive();\n render();\n if (options.resume.navigate) record(\"navigate\", null);\n } else render();\n\n return {\n setSaving: (saving) => {\n if (stopped) return;\n phase = saving ? \"saving\" : \"recording\";\n render();\n },\n isRecording: recording,\n stop,\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import {
|
|
2
|
+
prefersReducedMotion
|
|
3
|
+
} from "./chunk-7DOGZKWD.js";
|
|
4
4
|
|
|
5
5
|
// src/sanitize.ts
|
|
6
|
+
import { EMBED_IFRAME_ATTRS, VIDEO_ATTRS, isAllowedEmbedSrc, isSafeMediaUrl } from "@guidelytech/shared/runtime";
|
|
6
7
|
var ALLOWED_TAGS = /* @__PURE__ */ new Set(["p", "br", "strong", "b", "em", "i", "u", "a", "ul", "ol", "li", "img", "h1", "h2", "h3", "h4", "code", "pre", "blockquote", "span", "div", "hr", "button"]);
|
|
7
|
-
var DROP_TAGS = /* @__PURE__ */ new Set(["script", "style", "
|
|
8
|
-
var ALLOWED_ATTRS = /* @__PURE__ */ new Set(["href", "src", "alt", "title", "target", "rel", "class", "data-g-action", "data-g-value"]);
|
|
8
|
+
var DROP_TAGS = /* @__PURE__ */ new Set(["script", "style", "object", "embed", "form", "input", "textarea", "select", "link", "meta", "base", "template", "svg", "math", "frame", "frameset", "noscript"]);
|
|
9
|
+
var ALLOWED_ATTRS = /* @__PURE__ */ new Set(["href", "src", "alt", "title", "target", "rel", "class", "width", "height", "data-g-action", "data-g-value"]);
|
|
9
10
|
function isSafeUrl(value, attr, tag) {
|
|
10
11
|
const v = value.trim().replace(/[\u0000-\u001f\s]/g, "").toLowerCase();
|
|
11
12
|
if (!v) return false;
|
|
@@ -32,10 +33,32 @@ function sanitizeElement(el2) {
|
|
|
32
33
|
}
|
|
33
34
|
if (tag === "button") el2.setAttribute("type", "button");
|
|
34
35
|
if (tag === "img") {
|
|
36
|
+
for (const dim of ["width", "height"]) if (el2.hasAttribute(dim) && !/^\d{1,5}$/.test(el2.getAttribute(dim))) el2.removeAttribute(dim);
|
|
35
37
|
el2.setAttribute("loading", "lazy");
|
|
36
38
|
if (!el2.getAttribute("alt")) el2.setAttribute("alt", "");
|
|
37
39
|
}
|
|
38
40
|
}
|
|
41
|
+
function rebuildMedia(el2) {
|
|
42
|
+
const doc = el2.ownerDocument;
|
|
43
|
+
const tag = el2.tagName.toLowerCase();
|
|
44
|
+
const src = el2.getAttribute("src");
|
|
45
|
+
let out;
|
|
46
|
+
if (tag === "iframe") {
|
|
47
|
+
if (!isAllowedEmbedSrc(src)) return null;
|
|
48
|
+
out = doc.createElement("iframe");
|
|
49
|
+
out.setAttribute("src", src);
|
|
50
|
+
out.setAttribute("title", el2.getAttribute("title") || "Embedded video");
|
|
51
|
+
for (const [k, v] of Object.entries(EMBED_IFRAME_ATTRS)) out.setAttribute(k, v);
|
|
52
|
+
} else {
|
|
53
|
+
if (!isSafeMediaUrl(src)) return null;
|
|
54
|
+
out = doc.createElement("video");
|
|
55
|
+
out.setAttribute("src", src);
|
|
56
|
+
const poster = el2.getAttribute("poster");
|
|
57
|
+
if (isSafeMediaUrl(poster)) out.setAttribute("poster", poster);
|
|
58
|
+
for (const [k, v] of Object.entries(VIDEO_ATTRS)) out.setAttribute(k, v);
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
39
62
|
function walk(node) {
|
|
40
63
|
const children = Array.from(node.childNodes);
|
|
41
64
|
for (const child of children) {
|
|
@@ -50,6 +73,12 @@ function walk(node) {
|
|
|
50
73
|
node.removeChild(el2);
|
|
51
74
|
continue;
|
|
52
75
|
}
|
|
76
|
+
if (tag === "iframe" || tag === "video") {
|
|
77
|
+
const media = rebuildMedia(el2);
|
|
78
|
+
if (media) node.replaceChild(media, el2);
|
|
79
|
+
else node.removeChild(el2);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
53
82
|
if (!ALLOWED_TAGS.has(tag)) {
|
|
54
83
|
while (el2.firstChild) node.insertBefore(el2.firstChild, el2);
|
|
55
84
|
node.removeChild(el2);
|
|
@@ -77,7 +106,138 @@ function setSanitizedHtml(el2, html) {
|
|
|
77
106
|
el2.appendChild(sanitizeToFragment(html, el2.ownerDocument));
|
|
78
107
|
}
|
|
79
108
|
|
|
109
|
+
// src/pdf-viewer.ts
|
|
110
|
+
import { FILE_LINK_CLASS, PDF_BLOCK_CLASS, PDF_PAGES_CLASS } from "@guidelytech/shared/runtime";
|
|
111
|
+
function enhancePdfViewers(root, doc = root.ownerDocument) {
|
|
112
|
+
for (const block of Array.from(root.querySelectorAll(`div.${PDF_BLOCK_CLASS}`))) {
|
|
113
|
+
const link = block.querySelector(`a.${FILE_LINK_CLASS}`);
|
|
114
|
+
const strip = block.querySelector(`.${PDF_PAGES_CLASS}`);
|
|
115
|
+
if (!link || !strip || block.hasAttribute("data-g-ready")) continue;
|
|
116
|
+
for (const img of Array.from(strip.querySelectorAll("img:not([src])"))) img.remove();
|
|
117
|
+
const pages = Array.from(strip.querySelectorAll("img"));
|
|
118
|
+
if (!pages.length) {
|
|
119
|
+
strip.remove();
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
block.setAttribute("data-g-ready", "");
|
|
123
|
+
const name = (link.textContent || "").trim() || "PDF";
|
|
124
|
+
block.setAttribute("role", "group");
|
|
125
|
+
block.setAttribute("aria-label", `${name}, ${pages.length} ${pages.length === 1 ? "page" : "pages"}`);
|
|
126
|
+
link.setAttribute("aria-label", `Read ${name} full screen`);
|
|
127
|
+
link.addEventListener("click", (e) => {
|
|
128
|
+
e.preventDefault();
|
|
129
|
+
openReader(doc, block, link, pages, 0);
|
|
130
|
+
});
|
|
131
|
+
const viewport = doc.createElement("div");
|
|
132
|
+
viewport.className = "g-pdf-viewport";
|
|
133
|
+
strip.replaceWith(viewport);
|
|
134
|
+
viewport.appendChild(strip);
|
|
135
|
+
strip.tabIndex = 0;
|
|
136
|
+
strip.setAttribute("aria-label", `${name} pages`);
|
|
137
|
+
const counter = doc.createElement("span");
|
|
138
|
+
counter.className = "g-pdf-count";
|
|
139
|
+
counter.setAttribute("aria-live", "polite");
|
|
140
|
+
const prev = navButton(doc, "prev", "Previous page", "\u2039");
|
|
141
|
+
const next = navButton(doc, "next", "Next page", "\u203A");
|
|
142
|
+
viewport.append(prev, next, counter);
|
|
143
|
+
const stride = () => Math.max(1, pages.length > 1 ? pages[1].offsetLeft - pages[0].offsetLeft : strip.clientWidth);
|
|
144
|
+
const current = () => Math.round(strip.scrollLeft / stride());
|
|
145
|
+
const go = (i) => strip.scrollTo({ left: Math.max(0, Math.min(pages.length - 1, i)) * stride(), behavior: prefersReducedMotion() ? "auto" : "smooth" });
|
|
146
|
+
const update = () => {
|
|
147
|
+
const i = Math.min(pages.length - 1, current());
|
|
148
|
+
counter.textContent = `${i + 1} / ${pages.length}`;
|
|
149
|
+
prev.disabled = i === 0;
|
|
150
|
+
next.disabled = i >= pages.length - 1;
|
|
151
|
+
};
|
|
152
|
+
prev.addEventListener("click", () => go(current() - 1));
|
|
153
|
+
next.addEventListener("click", () => go(current() + 1));
|
|
154
|
+
strip.addEventListener("scroll", update, { passive: true });
|
|
155
|
+
strip.addEventListener("keydown", (e) => {
|
|
156
|
+
if (e.key === "ArrowRight" || e.key === "ArrowLeft") {
|
|
157
|
+
e.preventDefault();
|
|
158
|
+
go(current() + (e.key === "ArrowRight" ? 1 : -1));
|
|
159
|
+
} else if (e.key === "Enter") openReader(doc, block, link, pages, current());
|
|
160
|
+
});
|
|
161
|
+
pages.forEach((img, i) => img.addEventListener("click", () => openReader(doc, block, link, pages, i)));
|
|
162
|
+
if (pages.length === 1) viewport.classList.add("g-pdf-single");
|
|
163
|
+
update();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function navButton(doc, cls, label, text) {
|
|
167
|
+
const b = doc.createElement("button");
|
|
168
|
+
b.type = "button";
|
|
169
|
+
b.className = `g-pdf-nav g-pdf-${cls}`;
|
|
170
|
+
b.setAttribute("aria-label", label);
|
|
171
|
+
b.textContent = text;
|
|
172
|
+
return b;
|
|
173
|
+
}
|
|
174
|
+
function openReader(doc, block, link, pages, start) {
|
|
175
|
+
const host = block.closest(".g-scope") ?? doc.body;
|
|
176
|
+
const returnFocus = block.getRootNode().activeElement;
|
|
177
|
+
const name = (link.textContent || "").trim() || "PDF";
|
|
178
|
+
const reader = doc.createElement("div");
|
|
179
|
+
reader.className = "g-pdf-reader";
|
|
180
|
+
reader.setAttribute("role", "dialog");
|
|
181
|
+
reader.setAttribute("aria-modal", "true");
|
|
182
|
+
reader.setAttribute("aria-label", name);
|
|
183
|
+
const bar = doc.createElement("div");
|
|
184
|
+
bar.className = "g-pdf-bar";
|
|
185
|
+
const title = doc.createElement("span");
|
|
186
|
+
title.className = "g-pdf-title";
|
|
187
|
+
title.textContent = name;
|
|
188
|
+
const original = doc.createElement("a");
|
|
189
|
+
original.className = "g-btn g-btn-secondary";
|
|
190
|
+
original.href = link.href;
|
|
191
|
+
original.target = "_blank";
|
|
192
|
+
original.rel = "noopener noreferrer";
|
|
193
|
+
original.textContent = "Open PDF \u2197";
|
|
194
|
+
const close = doc.createElement("button");
|
|
195
|
+
close.type = "button";
|
|
196
|
+
close.className = "g-close g-pdf-close";
|
|
197
|
+
close.setAttribute("aria-label", "Close");
|
|
198
|
+
close.textContent = "\xD7";
|
|
199
|
+
bar.append(title, original, close);
|
|
200
|
+
const scroller = doc.createElement("div");
|
|
201
|
+
scroller.className = "g-pdf-scroll";
|
|
202
|
+
scroller.tabIndex = 0;
|
|
203
|
+
for (const page of pages) {
|
|
204
|
+
const img = doc.createElement("img");
|
|
205
|
+
img.src = page.src;
|
|
206
|
+
img.alt = page.alt;
|
|
207
|
+
img.decoding = "async";
|
|
208
|
+
for (const dim of ["width", "height"]) if (page.hasAttribute(dim)) img.setAttribute(dim, page.getAttribute(dim));
|
|
209
|
+
scroller.appendChild(img);
|
|
210
|
+
}
|
|
211
|
+
reader.append(bar, scroller);
|
|
212
|
+
const onKey = (e) => {
|
|
213
|
+
if (e.key === "Escape") {
|
|
214
|
+
e.stopPropagation();
|
|
215
|
+
done();
|
|
216
|
+
} else if (e.key === "Tab") {
|
|
217
|
+
const items = [original, close, scroller];
|
|
218
|
+
const at = items.indexOf(reader.getRootNode().activeElement);
|
|
219
|
+
e.preventDefault();
|
|
220
|
+
items[(at + (e.shiftKey ? items.length - 1 : 1)) % items.length].focus();
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
const done = () => {
|
|
224
|
+
reader.remove();
|
|
225
|
+
returnFocus?.focus?.({ preventScroll: true });
|
|
226
|
+
};
|
|
227
|
+
close.addEventListener("click", done);
|
|
228
|
+
reader.addEventListener("keydown", onKey);
|
|
229
|
+
host.appendChild(reader);
|
|
230
|
+
close.focus();
|
|
231
|
+
const target = scroller.children[start];
|
|
232
|
+
if (target && start > 0) {
|
|
233
|
+
const land = () => scroller.scrollTop = target.offsetTop - 12;
|
|
234
|
+
requestAnimationFrame(land);
|
|
235
|
+
if (!target.hasAttribute("height")) for (const img of Array.from(scroller.children).slice(0, start)) img.addEventListener("load", land, { once: true });
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
80
239
|
// src/help-view.ts
|
|
240
|
+
import { isArticleListed, resourceCenterSections } from "@guidelytech/shared/runtime";
|
|
81
241
|
function el(doc, tag, cls, text) {
|
|
82
242
|
const e = doc.createElement(tag);
|
|
83
243
|
if (cls) e.className = cls;
|
|
@@ -149,13 +309,19 @@ function createHelpView(options) {
|
|
|
149
309
|
const title = el(doc, "h3", "g-title", article.title);
|
|
150
310
|
const content = el(doc, "div", "g-body");
|
|
151
311
|
if (loading) content.appendChild(el(doc, "div", "g-rc-empty", "Loading\u2026"));
|
|
152
|
-
else if (article.body)
|
|
153
|
-
|
|
312
|
+
else if (article.body) {
|
|
313
|
+
setSanitizedHtml(content, article.body);
|
|
314
|
+
enhancePdfViewers(content, doc);
|
|
315
|
+
} else content.appendChild(el(doc, "div", "g-rc-empty", "This article couldn't be loaded."));
|
|
154
316
|
wrap.append(back, title, content);
|
|
155
317
|
body.appendChild(wrap);
|
|
156
318
|
back.focus?.({ preventScroll: true });
|
|
157
319
|
};
|
|
158
|
-
const section = (label
|
|
320
|
+
const section = (label, depth = 0) => {
|
|
321
|
+
const s = body.appendChild(el(doc, "div", "g-rc-section", label));
|
|
322
|
+
if (depth) s.dataset.depth = String(depth);
|
|
323
|
+
return s;
|
|
324
|
+
};
|
|
159
325
|
const articleButton = (a) => {
|
|
160
326
|
const b = listItem(doc, "\u{1F4C4}", a.title, a.summary);
|
|
161
327
|
b.addEventListener("click", () => openArticle(a));
|
|
@@ -165,16 +331,30 @@ function createHelpView(options) {
|
|
|
165
331
|
body.textContent = "";
|
|
166
332
|
if (active) return renderArticle(active);
|
|
167
333
|
const data = options.data();
|
|
334
|
+
const member = options.audienceIds?.() ?? [];
|
|
335
|
+
const sections = resourceCenterSections(data.collections, data.articles, member);
|
|
168
336
|
const q = query.trim().toLowerCase();
|
|
337
|
+
body.append(...options.extra?.(q) ?? []);
|
|
169
338
|
if (q) {
|
|
170
|
-
const
|
|
339
|
+
const visible = sections.flatMap((s) => s.articles);
|
|
340
|
+
const local = visible.filter((a) => a.title.toLowerCase().includes(q) || a.summary.toLowerCase().includes(q));
|
|
171
341
|
const seen = new Set(local.map((a) => a.id));
|
|
172
|
-
const
|
|
342
|
+
const hidden = new Set(data.articles.map((a) => a.id).filter((id) => !visible.some((v) => v.id === id)));
|
|
343
|
+
const merged = [...local, ...serverResults.filter((a) => !seen.has(a.id) && !hidden.has(a.id) && isArticleListed(data.collections, a, member))];
|
|
173
344
|
section(`Results (${merged.length})`);
|
|
174
345
|
if (!merged.length) body.appendChild(el(doc, "div", "g-rc-empty", "No articles match your search."));
|
|
175
346
|
merged.forEach(articleButton);
|
|
176
347
|
return;
|
|
177
348
|
}
|
|
349
|
+
if (data.suggested?.items.length) {
|
|
350
|
+
section(data.suggested.title);
|
|
351
|
+
const glyph = { guide: "\u25B6", howto: "\u2630", article: "\u{1F4C4}" };
|
|
352
|
+
data.suggested.items.forEach((item) => {
|
|
353
|
+
const b = listItem(doc, glyph[item.kind], item.title, item.summary);
|
|
354
|
+
b.addEventListener("click", item.run);
|
|
355
|
+
body.appendChild(b);
|
|
356
|
+
});
|
|
357
|
+
}
|
|
178
358
|
if (data.recommended?.length) {
|
|
179
359
|
section("Recommended");
|
|
180
360
|
data.recommended.forEach((g) => {
|
|
@@ -191,17 +371,9 @@ function createHelpView(options) {
|
|
|
191
371
|
body.appendChild(b);
|
|
192
372
|
});
|
|
193
373
|
}
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const articles = data.articles.filter((a) => a.collectionId === c.id);
|
|
198
|
-
if (!articles.length) return;
|
|
199
|
-
section(c.name);
|
|
200
|
-
articles.forEach(articleButton);
|
|
201
|
-
});
|
|
202
|
-
if (uncollected.length) {
|
|
203
|
-
section("Articles");
|
|
204
|
-
uncollected.forEach(articleButton);
|
|
374
|
+
for (const s of sections) {
|
|
375
|
+
section(s.collection ? s.path.join(" \u203A ") : "Articles", s.depth);
|
|
376
|
+
s.articles.forEach(articleButton);
|
|
205
377
|
}
|
|
206
378
|
if (data.faqs?.length) {
|
|
207
379
|
section("FAQ");
|
|
@@ -283,11 +455,12 @@ function createHelpView(options) {
|
|
|
283
455
|
}
|
|
284
456
|
|
|
285
457
|
export {
|
|
286
|
-
__publicField,
|
|
287
458
|
sanitizeToFragment,
|
|
288
459
|
sanitizeHtml,
|
|
289
460
|
setSanitizedHtml,
|
|
461
|
+
enhancePdfViewers,
|
|
290
462
|
el,
|
|
463
|
+
listItem,
|
|
291
464
|
createHelpView
|
|
292
465
|
};
|
|
293
|
-
//# sourceMappingURL=chunk-
|
|
466
|
+
//# sourceMappingURL=chunk-4FKYF6OB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/sanitize.ts","../src/pdf-viewer.ts","../src/help-view.ts"],"sourcesContent":["import { EMBED_IFRAME_ATTRS, VIDEO_ATTRS, isAllowedEmbedSrc, isSafeMediaUrl } from \"@guidelytech/shared/runtime\";\n\nconst ALLOWED_TAGS = new Set([\"p\", \"br\", \"strong\", \"b\", \"em\", \"i\", \"u\", \"a\", \"ul\", \"ol\", \"li\", \"img\", \"h1\", \"h2\", \"h3\", \"h4\", \"code\", \"pre\", \"blockquote\", \"span\", \"div\", \"hr\", \"button\"]);\nconst DROP_TAGS = new Set([\"script\", \"style\", \"object\", \"embed\", \"form\", \"input\", \"textarea\", \"select\", \"link\", \"meta\", \"base\", \"template\", \"svg\", \"math\", \"frame\", \"frameset\", \"noscript\"]);\nconst ALLOWED_ATTRS = new Set([\"href\", \"src\", \"alt\", \"title\", \"target\", \"rel\", \"class\", \"width\", \"height\", \"data-g-action\", \"data-g-value\"]);\nfunction isSafeUrl(value: string, attr: string, tag: string): boolean {\n // eslint-disable-next-line no-control-regex\n const v = value.trim().replace(/[\\u0000-\\u001f\\s]/g, \"\").toLowerCase();\n if (!v) return false;\n const m = /^([a-z][a-z0-9+.-]*):/.exec(v);\n if (!m) return true; // relative URL\n const scheme = m[1];\n if (scheme === \"data\") return tag === \"img\" && attr === \"src\" && v.startsWith(\"data:image/\");\n return scheme === \"http\" || scheme === \"https\" || scheme === \"mailto\" || scheme === \"tel\";\n}\n\nfunction sanitizeElement(el: Element): void {\n const tag = el.tagName.toLowerCase();\n for (const attr of Array.from(el.attributes)) {\n const name = attr.name.toLowerCase();\n if (name.startsWith(\"on\") || !ALLOWED_ATTRS.has(name)) {\n el.removeAttribute(attr.name);\n continue;\n }\n if ((name === \"href\" || name === \"src\") && !isSafeUrl(attr.value, name, tag)) el.removeAttribute(attr.name);\n }\n if (tag === \"a\") {\n const target = el.getAttribute(\"target\");\n if (target && target !== \"_blank\" && target !== \"_self\") el.removeAttribute(\"target\");\n if (el.getAttribute(\"target\") === \"_blank\") el.setAttribute(\"rel\", \"noopener noreferrer\");\n }\n if (tag === \"button\") el.setAttribute(\"type\", \"button\");\n if (tag === \"img\") {\n for (const dim of [\"width\", \"height\"]) if (el.hasAttribute(dim) && !/^\\d{1,5}$/.test(el.getAttribute(dim)!)) el.removeAttribute(dim);\n el.setAttribute(\"loading\", \"lazy\");\n if (!el.getAttribute(\"alt\")) el.setAttribute(\"alt\", \"\");\n }\n}\n\n/**\n * Media elements are rebuilt from scratch rather than filtered: an `<iframe>` only for a canonical\n * YouTube/Vimeo/Loom/Wistia embed, a `<video>` only for an absolute http(s) file, each with fixed\n * attributes. Returns null when the element must go.\n */\nfunction rebuildMedia(el: Element): Element | null {\n const doc = el.ownerDocument;\n const tag = el.tagName.toLowerCase();\n const src = el.getAttribute(\"src\");\n let out: Element;\n if (tag === \"iframe\") {\n if (!isAllowedEmbedSrc(src)) return null;\n out = doc.createElement(\"iframe\");\n out.setAttribute(\"src\", src!);\n out.setAttribute(\"title\", el.getAttribute(\"title\") || \"Embedded video\");\n for (const [k, v] of Object.entries(EMBED_IFRAME_ATTRS)) out.setAttribute(k, v);\n } else {\n if (!isSafeMediaUrl(src)) return null;\n out = doc.createElement(\"video\");\n out.setAttribute(\"src\", src!);\n const poster = el.getAttribute(\"poster\");\n if (isSafeMediaUrl(poster)) out.setAttribute(\"poster\", poster!);\n for (const [k, v] of Object.entries(VIDEO_ATTRS)) out.setAttribute(k, v);\n }\n return out;\n}\n\nfunction walk(node: Node): void {\n const children = Array.from(node.childNodes);\n for (const child of children) {\n if (child.nodeType === 8 /* comment */) {\n node.removeChild(child);\n continue;\n }\n if (child.nodeType !== 1) continue;\n const el = child as Element;\n const tag = el.tagName.toLowerCase();\n if (DROP_TAGS.has(tag)) {\n node.removeChild(el);\n continue;\n }\n if (tag === \"iframe\" || tag === \"video\") {\n const media = rebuildMedia(el);\n if (media) node.replaceChild(media, el);\n else node.removeChild(el);\n continue;\n }\n if (!ALLOWED_TAGS.has(tag)) {\n // Unwrap: keep text/children, drop the element itself.\n while (el.firstChild) node.insertBefore(el.firstChild, el);\n node.removeChild(el);\n // Re-walk the parent since children moved up.\n walk(node);\n return;\n }\n sanitizeElement(el);\n walk(el);\n }\n}\n\n/**\n * Parses HTML into a sanitized DocumentFragment using a strict allowlist. No external\n * dependency: a `<template>` element parses inertly and the tree is walked element by element.\n */\nexport function sanitizeToFragment(html: string, doc: Document = document): DocumentFragment {\n const tpl = doc.createElement(\"template\");\n tpl.innerHTML = html || \"\";\n walk(tpl.content);\n return tpl.content;\n}\n\n/** Returns sanitized HTML as a string. */\nexport function sanitizeHtml(html: string, doc: Document = document): string {\n const frag = sanitizeToFragment(html, doc);\n const div = doc.createElement(\"div\");\n div.appendChild(frag);\n return div.innerHTML;\n}\n\n/** Replaces the content of `el` with the sanitized HTML. */\nexport function setSanitizedHtml(el: Element, html: string): void {\n el.textContent = \"\";\n el.appendChild(sanitizeToFragment(html, el.ownerDocument));\n}\n","import { FILE_LINK_CLASS, PDF_BLOCK_CLASS, PDF_PAGES_CLASS } from \"@guidelytech/shared/runtime\";\nimport { prefersReducedMotion } from \"./host\";\n\n/**\n * Turns the PDFs in a sanitized article body (`div.gd-pdf`: the PDF link plus its pages as images)\n * into inline viewers: pages swipe sideways with a page counter and previous/next buttons, and the\n * header or a tap on a page opens a full-screen reader inside the widget, so visitors read the\n * document without downloading it. The original PDF stays one tap away in the reader. Without this\n * (or without script) the CSS alone still shows a scrollable strip of pages.\n */\nexport function enhancePdfViewers(root: Element, doc: Document = root.ownerDocument): void {\n for (const block of Array.from(root.querySelectorAll(`div.${PDF_BLOCK_CLASS}`))) {\n const link = block.querySelector<HTMLAnchorElement>(`a.${FILE_LINK_CLASS}`);\n const strip = block.querySelector<HTMLElement>(`.${PDF_PAGES_CLASS}`);\n if (!link || !strip || block.hasAttribute(\"data-g-ready\")) continue;\n // The sanitizer strips unsafe sources; those images aren't pages.\n for (const img of Array.from(strip.querySelectorAll(\"img:not([src])\"))) img.remove();\n const pages = Array.from(strip.querySelectorAll(\"img\"));\n if (!pages.length) {\n strip.remove();\n continue;\n }\n block.setAttribute(\"data-g-ready\", \"\");\n const name = (link.textContent || \"\").trim() || \"PDF\";\n block.setAttribute(\"role\", \"group\");\n block.setAttribute(\"aria-label\", `${name}, ${pages.length} ${pages.length === 1 ? \"page\" : \"pages\"}`);\n link.setAttribute(\"aria-label\", `Read ${name} full screen`);\n link.addEventListener(\"click\", (e) => {\n e.preventDefault();\n openReader(doc, block, link, pages, 0);\n });\n\n const viewport = doc.createElement(\"div\");\n viewport.className = \"g-pdf-viewport\";\n strip.replaceWith(viewport);\n viewport.appendChild(strip);\n strip.tabIndex = 0;\n strip.setAttribute(\"aria-label\", `${name} pages`);\n\n const counter = doc.createElement(\"span\");\n counter.className = \"g-pdf-count\";\n counter.setAttribute(\"aria-live\", \"polite\");\n const prev = navButton(doc, \"prev\", \"Previous page\", \"‹\");\n const next = navButton(doc, \"next\", \"Next page\", \"›\");\n viewport.append(prev, next, counter);\n\n // Distance between page starts: one page width plus the gap.\n const stride = () => Math.max(1, pages.length > 1 ? pages[1]!.offsetLeft - pages[0]!.offsetLeft : strip.clientWidth);\n const current = () => Math.round(strip.scrollLeft / stride());\n const go = (i: number) => strip.scrollTo({ left: Math.max(0, Math.min(pages.length - 1, i)) * stride(), behavior: prefersReducedMotion() ? \"auto\" : \"smooth\" });\n const update = () => {\n const i = Math.min(pages.length - 1, current());\n counter.textContent = `${i + 1} / ${pages.length}`;\n prev.disabled = i === 0;\n next.disabled = i >= pages.length - 1;\n };\n prev.addEventListener(\"click\", () => go(current() - 1));\n next.addEventListener(\"click\", () => go(current() + 1));\n strip.addEventListener(\"scroll\", update, { passive: true });\n strip.addEventListener(\"keydown\", (e) => {\n if (e.key === \"ArrowRight\" || e.key === \"ArrowLeft\") {\n e.preventDefault();\n go(current() + (e.key === \"ArrowRight\" ? 1 : -1));\n } else if (e.key === \"Enter\") openReader(doc, block, link, pages, current());\n });\n pages.forEach((img, i) => img.addEventListener(\"click\", () => openReader(doc, block, link, pages, i)));\n if (pages.length === 1) viewport.classList.add(\"g-pdf-single\");\n update();\n }\n}\n\nfunction navButton(doc: Document, cls: string, label: string, text: string) {\n const b = doc.createElement(\"button\");\n b.type = \"button\";\n b.className = `g-pdf-nav g-pdf-${cls}`;\n b.setAttribute(\"aria-label\", label);\n b.textContent = text;\n return b;\n}\n\n/** Full-screen reader: every page stacked vertically, the original PDF linked, Escape to close. */\nfunction openReader(doc: Document, block: Element, link: HTMLAnchorElement, pages: HTMLImageElement[], start: number) {\n const host = (block.closest(\".g-scope\") as HTMLElement | null) ?? doc.body;\n // Inside the shadow root, `document.activeElement` is only the host.\n const returnFocus = (block.getRootNode() as Document | ShadowRoot).activeElement as HTMLElement | null;\n const name = (link.textContent || \"\").trim() || \"PDF\";\n\n const reader = doc.createElement(\"div\");\n reader.className = \"g-pdf-reader\";\n reader.setAttribute(\"role\", \"dialog\");\n reader.setAttribute(\"aria-modal\", \"true\");\n reader.setAttribute(\"aria-label\", name);\n const bar = doc.createElement(\"div\");\n bar.className = \"g-pdf-bar\";\n const title = doc.createElement(\"span\");\n title.className = \"g-pdf-title\";\n title.textContent = name;\n const original = doc.createElement(\"a\");\n original.className = \"g-btn g-btn-secondary\";\n original.href = link.href;\n original.target = \"_blank\";\n original.rel = \"noopener noreferrer\";\n original.textContent = \"Open PDF ↗\";\n const close = doc.createElement(\"button\");\n close.type = \"button\";\n close.className = \"g-close g-pdf-close\";\n close.setAttribute(\"aria-label\", \"Close\");\n close.textContent = \"×\";\n bar.append(title, original, close);\n\n const scroller = doc.createElement(\"div\");\n scroller.className = \"g-pdf-scroll\";\n scroller.tabIndex = 0;\n for (const page of pages) {\n const img = doc.createElement(\"img\");\n img.src = page.src;\n img.alt = page.alt;\n img.decoding = \"async\";\n // Sizes up front so every page has its height before loading, and opening at page N lands on it.\n for (const dim of [\"width\", \"height\"]) if (page.hasAttribute(dim)) img.setAttribute(dim, page.getAttribute(dim)!);\n scroller.appendChild(img);\n }\n reader.append(bar, scroller);\n\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") {\n e.stopPropagation();\n done();\n } else if (e.key === \"Tab\") {\n // Keep focus inside the reader.\n const items: HTMLElement[] = [original, close, scroller];\n const at = items.indexOf((reader.getRootNode() as Document | ShadowRoot).activeElement as HTMLElement);\n e.preventDefault();\n items[(at + (e.shiftKey ? items.length - 1 : 1)) % items.length]!.focus();\n }\n };\n const done = () => {\n reader.remove();\n returnFocus?.focus?.({ preventScroll: true });\n };\n close.addEventListener(\"click\", done);\n reader.addEventListener(\"keydown\", onKey);\n host.appendChild(reader);\n close.focus();\n const target = scroller.children[start] as HTMLImageElement | undefined;\n if (target && start > 0) {\n const land = () => (scroller.scrollTop = target.offsetTop - 12);\n requestAnimationFrame(land);\n // Pages without stored sizes only get their height once the pages above have loaded.\n if (!target.hasAttribute(\"height\")) for (const img of Array.from(scroller.children).slice(0, start)) img.addEventListener(\"load\", land, { once: true });\n }\n}\n","import type { ResourceCenterArticle, ResourceCenterConfig } from \"@guidelytech/shared\";\nimport { isArticleListed, resourceCenterSections } from \"@guidelytech/shared/runtime\";\nimport { enhancePdfViewers } from \"./pdf-viewer\";\nimport { setSanitizedHtml } from \"./sanitize\";\n\nexport interface HelpGuideRef {\n id: string;\n key: string;\n name: string;\n kind: string;\n}\n\n/** A page-aware suggestion at the top of the help view (the messenger works out what and how it opens). */\nexport interface HelpSuggestion {\n kind: \"guide\" | \"howto\" | \"article\";\n title: string;\n summary?: string;\n run: () => void;\n}\n\n/** What the help view lists. Article bodies may be missing (`bodyOmitted` or summaries only): they load on open. */\nexport interface HelpData {\n collections: ResourceCenterConfig[\"collections\"];\n articles: Array<Omit<ResourceCenterArticle, \"body\"> & { body?: string }>;\n /** Shown first, under its own title (\"For the Invoices page\"). */\n suggested?: { title: string; items: HelpSuggestion[] };\n recommended?: HelpGuideRef[];\n checklists?: HelpGuideRef[];\n faqs?: ResourceCenterConfig[\"faqs\"];\n links?: ResourceCenterConfig[\"links\"];\n}\n\nexport interface HelpViewOptions {\n doc: Document;\n data: () => HelpData;\n /** Server search. Results are merged with the client-side title/summary filter. */\n search?: (q: string) => Promise<ResourceCenterArticle[]>;\n /** The visitor's audiences; articles and collections targeted at others aren't listed. */\n audienceIds?: () => ReadonlySet<string> | readonly string[];\n /** Loads an article body that isn't in `data()`. */\n loadArticle?: (id: string) => Promise<ResourceCenterArticle | null>;\n onStartGuide?: (guideId: string) => void;\n onOpenChecklist?: (guideId: string) => void;\n onArticleView?: (article: ResourceCenterArticle) => void;\n /** Extra items above the list (`query` empty) or above the article results (the messenger's how-tos). */\n extra?: (query: string) => Node[];\n}\n\nexport interface HelpView {\n /** Search box + scrollable body. */\n el: HTMLElement;\n render(): void;\n /** Back to the article list and clear the search. */\n reset(): void;\n openArticle(id: string): void;\n focusSearch(): void;\n setQuery(q: string): void;\n destroy(): void;\n}\n\nexport function el<K extends keyof HTMLElementTagNameMap>(doc: Document, tag: K, cls?: string, text?: string): HTMLElementTagNameMap[K] {\n const e = doc.createElement(tag);\n if (cls) e.className = cls;\n if (text != null) e.textContent = text;\n return e;\n}\n\nexport function listItem(doc: Document, icon: string, title: string, summary?: string): HTMLButtonElement {\n const b = el(doc, \"button\", \"g-rc-item\");\n b.type = \"button\";\n const i = el(doc, \"span\", \"g-rc-icon\", icon);\n i.setAttribute(\"aria-hidden\", \"true\");\n const text = el(doc, \"span\");\n text.appendChild(el(doc, \"div\", \"g-rc-title\", title));\n if (summary) text.appendChild(el(doc, \"div\", \"g-rc-sum\", summary));\n b.append(i, text);\n return b;\n}\n\ntype ListedArticle = HelpData[\"articles\"][number];\n\n/** Article browser shared by the resource center and the messenger Help tab. */\nexport function createHelpView(options: HelpViewOptions): HelpView {\n const { doc } = options;\n let query = \"\";\n let serverResults: ResourceCenterArticle[] = [];\n let searchSeq = 0;\n let openSeq = 0;\n let active: { article: ListedArticle; loading: boolean } | null = null;\n\n const root = el(doc, \"div\", \"g-help\");\n const searchWrap = el(doc, \"div\", \"g-rc-search\");\n const input = el(doc, \"input\", \"g-input\");\n input.type = \"search\";\n input.placeholder = \"Search help articles…\";\n input.setAttribute(\"aria-label\", \"Search help articles\");\n searchWrap.appendChild(input);\n const body = el(doc, \"div\", \"g-panel-body\");\n root.append(searchWrap, body);\n\n const hasBody = (a: ListedArticle): a is ResourceCenterArticle => typeof a.body === \"string\" && !a.bodyOmitted;\n\n const openArticle = (article: ListedArticle) => {\n if (article.externalUrl) {\n window.open(article.externalUrl, \"_blank\", \"noopener,noreferrer\");\n if (hasBody(article)) options.onArticleView?.(article);\n return;\n }\n const seq = ++openSeq;\n if (hasBody(article) || !options.loadArticle) {\n active = { article, loading: false };\n if (hasBody(article)) options.onArticleView?.(article);\n return render();\n }\n active = { article, loading: true };\n render();\n options\n .loadArticle(article.id)\n .then((full) => {\n if (seq !== openSeq || !active) return;\n active = { article: full ?? { ...article, body: \"\" }, loading: false };\n if (full) options.onArticleView?.(full);\n render();\n })\n .catch(() => {\n if (seq !== openSeq || !active) return;\n active = { article: { ...article, body: \"\" }, loading: false };\n render();\n });\n };\n\n const renderArticle = ({ article, loading }: NonNullable<typeof active>) => {\n const wrap = el(doc, \"div\", \"g-rc-article\");\n const back = el(doc, \"button\", \"g-btn g-btn-link g-rc-back\", \"← Back\");\n back.type = \"button\";\n back.addEventListener(\"click\", () => {\n openSeq++;\n active = null;\n render();\n });\n const title = el(doc, \"h3\", \"g-title\", article.title);\n const content = el(doc, \"div\", \"g-body\");\n if (loading) content.appendChild(el(doc, \"div\", \"g-rc-empty\", \"Loading…\"));\n else if (article.body) {\n setSanitizedHtml(content, article.body);\n enhancePdfViewers(content, doc);\n }\n else content.appendChild(el(doc, \"div\", \"g-rc-empty\", \"This article couldn't be loaded.\"));\n wrap.append(back, title, content);\n body.appendChild(wrap);\n back.focus?.({ preventScroll: true });\n };\n\n const section = (label: string, depth = 0) => {\n const s = body.appendChild(el(doc, \"div\", \"g-rc-section\", label));\n if (depth) s.dataset.depth = String(depth);\n return s;\n };\n const articleButton = (a: ListedArticle) => {\n const b = listItem(doc, \"📄\", a.title, a.summary);\n b.addEventListener(\"click\", () => openArticle(a));\n body.appendChild(b);\n };\n\n function render() {\n body.textContent = \"\";\n if (active) return renderArticle(active);\n const data = options.data();\n const member = options.audienceIds?.() ?? [];\n const sections = resourceCenterSections(data.collections, data.articles, member);\n const q = query.trim().toLowerCase();\n body.append(...(options.extra?.(q) ?? []));\n if (q) {\n const visible = sections.flatMap((s) => s.articles);\n const local = visible.filter((a) => a.title.toLowerCase().includes(q) || a.summary.toLowerCase().includes(q));\n const seen = new Set(local.map((a) => a.id));\n // Server search covers every published article: keep only the ones listed for this visitor.\n const hidden = new Set(data.articles.map((a) => a.id).filter((id) => !visible.some((v) => v.id === id)));\n const merged: ListedArticle[] = [...local, ...serverResults.filter((a) => !seen.has(a.id) && !hidden.has(a.id) && isArticleListed(data.collections, a, member))];\n section(`Results (${merged.length})`);\n if (!merged.length) body.appendChild(el(doc, \"div\", \"g-rc-empty\", \"No articles match your search.\"));\n merged.forEach(articleButton);\n return;\n }\n if (data.suggested?.items.length) {\n section(data.suggested.title);\n const glyph = { guide: \"▶\", howto: \"☰\", article: \"📄\" } as const;\n data.suggested.items.forEach((item) => {\n const b = listItem(doc, glyph[item.kind], item.title, item.summary);\n b.addEventListener(\"click\", item.run);\n body.appendChild(b);\n });\n }\n if (data.recommended?.length) {\n section(\"Recommended\");\n data.recommended.forEach((g) => {\n const b = listItem(doc, \"▶\", g.name, \"Start guide\");\n b.addEventListener(\"click\", () => options.onStartGuide?.(g.id));\n body.appendChild(b);\n });\n }\n if (data.checklists?.length) {\n section(\"Checklists\");\n data.checklists.forEach((g) => {\n const b = listItem(doc, \"☑\", g.name, \"Open checklist\");\n b.addEventListener(\"click\", () => options.onOpenChecklist?.(g.id));\n body.appendChild(b);\n });\n }\n // Nested collections read as \"Billing › Invoices\", depth first.\n for (const s of sections) {\n section(s.collection ? s.path.join(\" › \") : \"Articles\", s.depth);\n s.articles.forEach(articleButton);\n }\n if (data.faqs?.length) {\n section(\"FAQ\");\n data.faqs.forEach((f) => {\n const b = listItem(doc, \"?\", f.question);\n b.setAttribute(\"aria-expanded\", \"false\");\n const a = el(doc, \"div\", \"g-faq-a\");\n setSanitizedHtml(a, f.answer);\n a.hidden = true;\n b.addEventListener(\"click\", () => {\n a.hidden = !a.hidden;\n b.setAttribute(\"aria-expanded\", String(!a.hidden));\n });\n body.append(b, a);\n });\n }\n if (data.links?.length) {\n section(\"Links\");\n data.links.forEach((l) => {\n const a = el(doc, \"a\", \"g-rc-item\");\n a.href = l.url;\n a.target = \"_blank\";\n a.rel = \"noopener noreferrer\";\n const i = el(doc, \"span\", \"g-rc-icon\", \"↗\");\n i.setAttribute(\"aria-hidden\", \"true\");\n a.append(i, el(doc, \"span\", \"g-rc-title\", l.label));\n body.appendChild(a);\n });\n }\n if (!body.childNodes.length) body.appendChild(el(doc, \"div\", \"g-rc-empty\", \"Nothing here yet.\"));\n }\n\n let debounce: ReturnType<typeof setTimeout> | null = null;\n const onInput = () => {\n query = input.value;\n openSeq++;\n active = null;\n render();\n if (debounce) clearTimeout(debounce);\n if (!options.search || !query.trim()) {\n serverResults = [];\n return;\n }\n const seq = ++searchSeq;\n debounce = setTimeout(() => {\n options\n .search?.(query.trim())\n .then((results) => {\n if (seq !== searchSeq) return;\n serverResults = Array.isArray(results) ? results : [];\n render();\n })\n .catch(() => {});\n }, 250);\n };\n input.addEventListener(\"input\", onInput);\n\n return {\n el: root,\n render,\n reset() {\n openSeq++;\n active = null;\n query = \"\";\n input.value = \"\";\n serverResults = [];\n },\n openArticle(id: string) {\n const listed = options.data().articles.find((a) => a.id === id) ?? { id, slug: id, title: \"Article\", summary: \"\", updatedAt: \"\", bodyOmitted: true };\n openArticle(listed);\n },\n focusSearch: () => input.focus?.({ preventScroll: true }),\n setQuery(q: string) {\n input.value = q;\n onInput();\n },\n destroy() {\n if (debounce) clearTimeout(debounce);\n input.removeEventListener(\"input\", onInput);\n root.remove();\n },\n };\n}\n"],"mappings":"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","names":["el"]}
|