@reticlehq/browser 3.2.0 → 3.3.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/actions/actions-dom.d.ts +1 -1
- package/dist/actions/actions-dom.js +5 -4
- package/dist/actions/actions.d.ts +0 -1
- package/dist/actions/actions.js +4 -2
- package/dist/actions/value-input.js +14 -5
- package/dist/connect-options.d.ts +1 -0
- package/dist/dom/addressing/source.d.ts +2 -1
- package/dist/dom/dom-ignore.d.ts +1 -0
- package/dist/dom/dom-ignore.js +13 -0
- package/dist/dom/query.js +6 -4
- package/dist/dom/snapshot.d.ts +1 -1
- package/dist/observers/console.js +38 -13
- package/dist/observers/dom.js +3 -1
- package/dist/observers/download.js +3 -2
- package/dist/observers/error-repeats.d.ts +18 -0
- package/dist/observers/error-repeats.js +41 -0
- package/dist/observers/field.d.ts +2 -0
- package/dist/observers/field.js +86 -0
- package/dist/observers/frames.js +3 -4
- package/dist/observers/install-all.js +2 -0
- package/dist/observers/net-detail/network-body.d.ts +2 -0
- package/dist/observers/net-detail/network-body.js +24 -7
- package/dist/observers/route.js +4 -4
- package/dist/observers/state.js +18 -8
- package/dist/presenter/carousel/carousel.d.ts +13 -0
- package/dist/presenter/carousel/carousel.js +126 -0
- package/dist/presenter/carousel/offer-card.d.ts +21 -0
- package/dist/presenter/carousel/offer-card.js +55 -0
- package/dist/presenter/carousel/panel-slides.d.ts +7 -0
- package/dist/presenter/carousel/panel-slides.js +10 -0
- package/dist/presenter/carousel/talk-card.d.ts +8 -0
- package/dist/presenter/carousel/talk-card.js +20 -0
- package/dist/presenter/chrome/presenter-log.d.ts +2 -1
- package/dist/presenter/chrome/presenter-log.js +14 -3
- package/dist/presenter/chrome/presenter-report-copy.d.ts +0 -1
- package/dist/presenter/chrome/presenter-report-copy.js +1 -1
- package/dist/presenter/hud-telemetry.d.ts +3 -0
- package/dist/presenter/hud-telemetry.js +87 -0
- package/dist/presenter/presenter-account.js +1 -1
- package/dist/presenter/presenter-config.d.ts +5 -1
- package/dist/presenter/presenter-config.js +4 -1
- package/dist/presenter/presenter-controls.d.ts +1 -0
- package/dist/presenter/presenter-controls.js +12 -1
- package/dist/presenter/presenter-report.js +6 -6
- package/dist/presenter/presenter-settings-styles.js +2 -1
- package/dist/presenter/presenter-settings.d.ts +2 -4
- package/dist/presenter/presenter-settings.js +13 -4
- package/dist/presenter/presenter-shell-styles.js +4 -0
- package/dist/presenter/presenter-shell.d.ts +4 -1
- package/dist/presenter/presenter-shell.js +49 -11
- package/dist/presenter/presenter-styles.js +5 -1
- package/dist/presenter/presenter-workspace.d.ts +0 -7
- package/dist/presenter/presenter-workspace.js +7 -7
- package/dist/presenter/presenter.js +43 -10
- package/dist/presenter/tour/tour.d.ts +3 -0
- package/dist/presenter/tour/tour.js +5 -0
- package/dist/recorder/recorder.js +25 -22
- package/dist/reticle-inject.d.ts +2 -0
- package/dist/reticle-inject.js +1337 -0
- package/dist/reticle.js +6 -0
- package/dist/review/annotator.d.ts +3 -0
- package/dist/review/annotator.js +37 -37
- package/dist/review/marks-for-agent.d.ts +9 -0
- package/dist/review/marks-for-agent.js +12 -0
- package/dist/util/captured-method.d.ts +0 -1
- package/dist/util/captured-method.js +1 -1
- package/package.json +8 -4
- package/dist/presenter/presenter-offer.d.ts +0 -25
- package/dist/presenter/presenter-offer.js +0 -87
package/dist/reticle.js
CHANGED
|
@@ -10,6 +10,7 @@ import { declaredChannels } from './registry/declared-channels.js';
|
|
|
10
10
|
import { declaredCommands, declaredPlatform } from './registry/declared-platform.js';
|
|
11
11
|
import { registerCapabilities, setCapabilitiesListener, hasCapabilities, } from './registry/capabilities.js';
|
|
12
12
|
import { installAllObservers, runTeardowns } from './observers/install-all.js';
|
|
13
|
+
import { setNetworkBodyMaxChars } from './observers/net-detail/network-body.js';
|
|
13
14
|
import { installOverlay } from './presenter/chrome/overlay.js';
|
|
14
15
|
import { LOG_KIND, LOG_RESULT } from './presenter/chrome/log-kinds.js';
|
|
15
16
|
import { actionVerb } from './presenter/chrome/presenter-verbs.js';
|
|
@@ -187,6 +188,9 @@ export class Reticle {
|
|
|
187
188
|
});
|
|
188
189
|
setCapabilitiesListener(() => this.#transport?.reannounce());
|
|
189
190
|
setPresenterVisible(true === options.exposePresenter);
|
|
191
|
+
if (options.networkBodyMaxChars !== undefined) {
|
|
192
|
+
setNetworkBodyMaxChars(options.networkBodyMaxChars);
|
|
193
|
+
}
|
|
190
194
|
const emit = this.#emit;
|
|
191
195
|
this.#captureBodies = true === options.captureNetworkBodies;
|
|
192
196
|
this.#sourceMapping =
|
|
@@ -248,6 +252,7 @@ export class Reticle {
|
|
|
248
252
|
panelOptions.onControl = (intent) => this.#emit(EventType.HUMAN_CONTROL, intent.text !== undefined
|
|
249
253
|
? { kind: intent.kind, text: intent.text }
|
|
250
254
|
: { kind: intent.kind });
|
|
255
|
+
panelOptions.onHudUse = (use) => this.#emit(EventType.HUD_USED, { ...use });
|
|
251
256
|
const panel = new Presenter(panelOptions);
|
|
252
257
|
this.#presenter = panel;
|
|
253
258
|
panel.mount();
|
|
@@ -263,6 +268,7 @@ export class Reticle {
|
|
|
263
268
|
projectId: reticleParamsFromSearch(window.location.search).projectId ?? window.location.origin,
|
|
264
269
|
isDriving: () => PresenterMode.IDLE !== panel.mode,
|
|
265
270
|
search: window.location.search,
|
|
271
|
+
navigator,
|
|
266
272
|
copy: (text) => navigator.clipboard?.writeText(text).then(() => true, () => false) ?? false,
|
|
267
273
|
select: (element) => {
|
|
268
274
|
const selection = window.getSelection();
|
|
@@ -16,7 +16,9 @@ export interface AnnotatorDeps {
|
|
|
16
16
|
export interface AnnotatorChrome {
|
|
17
17
|
markersBtn?: HTMLElement;
|
|
18
18
|
clearBtn?: HTMLElement;
|
|
19
|
+
copyRow?: HTMLElement;
|
|
19
20
|
countEl?: HTMLElement;
|
|
21
|
+
onCount?: (count: number) => void;
|
|
20
22
|
}
|
|
21
23
|
export declare class Annotator {
|
|
22
24
|
#private;
|
|
@@ -29,6 +31,7 @@ export declare class Annotator {
|
|
|
29
31
|
attachChrome(chrome: AnnotatorChrome): void;
|
|
30
32
|
destroy(): void;
|
|
31
33
|
toggle(on?: boolean): void;
|
|
34
|
+
agentPrompt(pageUrl: string): string;
|
|
32
35
|
clearAll(): void;
|
|
33
36
|
syncAnchors(): void;
|
|
34
37
|
}
|
package/dist/review/annotator.js
CHANGED
|
@@ -2,6 +2,7 @@ import { isSyntheticInput } from '../actions/synthetic/synthetic-input.js';
|
|
|
2
2
|
import { EventType } from '@reticlehq/core';
|
|
3
3
|
import { isReticleUi, isReticleOverlay } from '../dom/dom-ignore.js';
|
|
4
4
|
import { resolveMarkAnchor } from './mark-anchor.js';
|
|
5
|
+
import { marksCountText, marksForAgent } from './marks-for-agent.js';
|
|
5
6
|
import { nativeSetTimeout, nativeClearTimeout } from '../timers/native/native-timers.js';
|
|
6
7
|
import { ANNOTATOR_CSS, ANNOTATOR_ROOT_HTML, MARK_CANCEL, MARK_PENDING_GLYPH, MARK_PLACEHOLDER, MARK_SUBMIT, } from './annotator-styles.js';
|
|
7
8
|
const HIGHLIGHT_REST_MS = 130;
|
|
@@ -33,13 +34,11 @@ export class Annotator {
|
|
|
33
34
|
#pendingTarget;
|
|
34
35
|
#markersBtn;
|
|
35
36
|
#clearBtn;
|
|
37
|
+
#copyRow;
|
|
38
|
+
#onChromeCount;
|
|
36
39
|
#countEl;
|
|
37
40
|
#accent;
|
|
38
|
-
#
|
|
39
|
-
#onKeydown;
|
|
40
|
-
#onMove;
|
|
41
|
-
#onScroll;
|
|
42
|
-
#onResize;
|
|
41
|
+
#ac;
|
|
43
42
|
#mo;
|
|
44
43
|
constructor(deps) {
|
|
45
44
|
this.#emit = deps.emit;
|
|
@@ -69,16 +68,23 @@ export class Annotator {
|
|
|
69
68
|
this.#hi = root.querySelector(sel('hi')) ?? undefined;
|
|
70
69
|
this.#hiLabel = root.querySelector(sel('hilabel')) ?? undefined;
|
|
71
70
|
this.#selBox = root.querySelector(sel('sel')) ?? undefined;
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
71
|
+
const ac = new AbortController();
|
|
72
|
+
this.#ac = ac;
|
|
73
|
+
document.addEventListener('click', (ev) => this.#handleClick(ev), {
|
|
74
|
+
capture: true,
|
|
75
|
+
signal: ac.signal,
|
|
76
|
+
});
|
|
77
|
+
document.addEventListener('mousemove', (ev) => this.#scheduleMove(ev), {
|
|
78
|
+
passive: true,
|
|
79
|
+
capture: true,
|
|
80
|
+
signal: ac.signal,
|
|
81
|
+
});
|
|
82
|
+
document.addEventListener('keydown', (ev) => this.#handleKey(ev), { signal: ac.signal });
|
|
83
|
+
window.addEventListener('scroll', () => this.#reposition(), {
|
|
84
|
+
capture: true,
|
|
85
|
+
signal: ac.signal,
|
|
86
|
+
});
|
|
87
|
+
window.addEventListener('resize', () => this.#reposition(), { signal: ac.signal });
|
|
82
88
|
this.#mo = new MutationObserver(() => this.syncAnchors());
|
|
83
89
|
this.#mo.observe(document.documentElement, { childList: true, subtree: true });
|
|
84
90
|
if (undefined !== this.#accent)
|
|
@@ -94,6 +100,8 @@ export class Annotator {
|
|
|
94
100
|
attachChrome(chrome) {
|
|
95
101
|
this.#markersBtn = chrome.markersBtn;
|
|
96
102
|
this.#clearBtn = chrome.clearBtn;
|
|
103
|
+
this.#copyRow = chrome.copyRow;
|
|
104
|
+
this.#onChromeCount = chrome.onCount;
|
|
97
105
|
this.#countEl = chrome.countEl;
|
|
98
106
|
this.#markersBtn?.addEventListener('click', (e) => {
|
|
99
107
|
e.stopPropagation();
|
|
@@ -106,26 +114,8 @@ export class Annotator {
|
|
|
106
114
|
this.#syncChrome();
|
|
107
115
|
}
|
|
108
116
|
destroy() {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
this.#onClick = undefined;
|
|
112
|
-
}
|
|
113
|
-
if (this.#onKeydown !== undefined) {
|
|
114
|
-
document.removeEventListener('keydown', this.#onKeydown);
|
|
115
|
-
this.#onKeydown = undefined;
|
|
116
|
-
}
|
|
117
|
-
if (this.#onMove !== undefined) {
|
|
118
|
-
document.removeEventListener('mousemove', this.#onMove, { capture: true });
|
|
119
|
-
this.#onMove = undefined;
|
|
120
|
-
}
|
|
121
|
-
if (this.#onScroll !== undefined) {
|
|
122
|
-
window.removeEventListener('scroll', this.#onScroll, true);
|
|
123
|
-
this.#onScroll = undefined;
|
|
124
|
-
}
|
|
125
|
-
if (this.#onResize !== undefined) {
|
|
126
|
-
window.removeEventListener('resize', this.#onResize);
|
|
127
|
-
this.#onResize = undefined;
|
|
128
|
-
}
|
|
117
|
+
this.#ac?.abort();
|
|
118
|
+
this.#ac = undefined;
|
|
129
119
|
this.#mo?.disconnect();
|
|
130
120
|
this.#mo = undefined;
|
|
131
121
|
if (this.#hiTimer !== undefined)
|
|
@@ -151,6 +141,9 @@ export class Annotator {
|
|
|
151
141
|
}
|
|
152
142
|
this.#syncChrome();
|
|
153
143
|
}
|
|
144
|
+
agentPrompt(pageUrl) {
|
|
145
|
+
return marksForAgent(this.#marks, pageUrl);
|
|
146
|
+
}
|
|
154
147
|
clearAll() {
|
|
155
148
|
this.#closePopover();
|
|
156
149
|
for (const mark of this.#marks)
|
|
@@ -193,6 +186,13 @@ export class Annotator {
|
|
|
193
186
|
this.#countEl.hidden = 0 === n;
|
|
194
187
|
}
|
|
195
188
|
this.#clearBtn?.toggleAttribute('disabled', 0 === n);
|
|
189
|
+
this.#onChromeCount?.(n);
|
|
190
|
+
if (this.#copyRow !== undefined) {
|
|
191
|
+
this.#copyRow.hidden = 0 === n;
|
|
192
|
+
const text = this.#copyRow.querySelector('[data-reticle-marks-text]');
|
|
193
|
+
if (text !== null)
|
|
194
|
+
text.textContent = marksCountText(n);
|
|
195
|
+
}
|
|
196
196
|
this.#markersBtn?.toggleAttribute('disabled', 0 === n);
|
|
197
197
|
}
|
|
198
198
|
#handleKey(ev) {
|
|
@@ -344,8 +344,8 @@ export class Annotator {
|
|
|
344
344
|
pop.innerHTML = `<div class="reticle-mark-where"></div>
|
|
345
345
|
<textarea rows="2" placeholder="${MARK_PLACEHOLDER}"></textarea>
|
|
346
346
|
<div class="reticle-mark-row">
|
|
347
|
-
<button type="button" data-cancel>${MARK_CANCEL}</button>
|
|
348
|
-
<button type="button" data-send disabled>${MARK_SUBMIT}</button>
|
|
347
|
+
<button type="button" data-cancel data-reticle-mark-cancel>${MARK_CANCEL}</button>
|
|
348
|
+
<button type="button" data-send data-reticle-mark-send disabled>${MARK_SUBMIT}</button>
|
|
349
349
|
</div>`;
|
|
350
350
|
const whereEl = pop.querySelector('.reticle-mark-where');
|
|
351
351
|
if (whereEl !== null)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface MarkForAgent {
|
|
2
|
+
note: string;
|
|
3
|
+
label: string;
|
|
4
|
+
anchor: string;
|
|
5
|
+
route: string;
|
|
6
|
+
source?: string | undefined;
|
|
7
|
+
}
|
|
8
|
+
export declare function marksCountText(n: number): string;
|
|
9
|
+
export declare function marksForAgent(marks: readonly MarkForAgent[], pageUrl: string): string;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function marksCountText(n) {
|
|
2
|
+
return `${String(n)} ${1 === n ? 'note' : 'notes'} on this page`;
|
|
3
|
+
}
|
|
4
|
+
export function marksForAgent(marks, pageUrl) {
|
|
5
|
+
const n = marks.length;
|
|
6
|
+
const head = `I marked ${String(n)} ${1 === n ? 'issue' : 'issues'} on ${pageUrl} with Reticle. Fix each one, then verify it in the running app:`;
|
|
7
|
+
const rows = marks.map((m, i) => {
|
|
8
|
+
const where = m.source === undefined ? m.anchor : `${m.anchor}, ${m.source}`;
|
|
9
|
+
return `${String(i + 1)}. ${m.note}\n ${m.label} (${where}) on ${m.route}`;
|
|
10
|
+
});
|
|
11
|
+
return [head, '', ...rows].join('\n');
|
|
12
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reticlehq/browser",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"description": "Reticle browser SDK — installs observers, builds semantic snapshots, executes actions, talks to the bridge.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -10,10 +10,14 @@
|
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
11
11
|
"default": "./dist/index.js"
|
|
12
12
|
},
|
|
13
|
-
"./package.json": "./package.json"
|
|
13
|
+
"./package.json": "./package.json",
|
|
14
|
+
"./inject": {
|
|
15
|
+
"types": "./dist/reticle-inject.d.ts",
|
|
16
|
+
"default": "./dist/reticle-inject.js"
|
|
17
|
+
}
|
|
14
18
|
},
|
|
15
19
|
"dependencies": {
|
|
16
|
-
"@reticlehq/core": "3.
|
|
20
|
+
"@reticlehq/core": "3.3.0"
|
|
17
21
|
},
|
|
18
22
|
"devDependencies": {
|
|
19
23
|
"@iconify-json/heroicons-outline": "^1.2.1",
|
|
@@ -60,7 +64,7 @@
|
|
|
60
64
|
"node": ">=20.0.0"
|
|
61
65
|
},
|
|
62
66
|
"scripts": {
|
|
63
|
-
"build": "node scripts/embed-heroicons.mjs && tsc -b && node ../../../scripts/alias-dist.mjs",
|
|
67
|
+
"build": "node scripts/embed-heroicons.mjs && tsc -b && node ../../../scripts/alias-dist.mjs && node scripts/bundle-inject.mjs",
|
|
64
68
|
"typecheck": "tsc -b",
|
|
65
69
|
"lint": "eslint src",
|
|
66
70
|
"test:unit": "vitest run src --passWithNoTests",
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
export declare const OFFER_ATTR = "data-reticle-offer";
|
|
2
|
-
export declare const OFFER_SLOT_ATTR = "data-reticle-offer-slot";
|
|
3
|
-
export declare const OFFER_CLAIM_ATTR = "data-reticle-offer-claim";
|
|
4
|
-
export declare const OFFER_DISMISS_ATTR = "data-reticle-offer-dismiss";
|
|
5
|
-
export declare const OFFER_DISMISSED_KEY = "reticle.harnessOffer.dismissed";
|
|
6
|
-
export declare const OFFER_TEXT: {
|
|
7
|
-
readonly HEADLINE: "Reticle drives your app for you";
|
|
8
|
-
readonly BODY: "The harness explores your app, proves what works, and saves every journey as a test that replays for free. Built on Jev — no model API key needed.";
|
|
9
|
-
readonly FREE: "Free for 3 months";
|
|
10
|
-
readonly CLAIM: "Claim now";
|
|
11
|
-
readonly DISMISS: "Not now";
|
|
12
|
-
readonly claimed: (days: number) => string;
|
|
13
|
-
readonly ending: (days: number) => string;
|
|
14
|
-
};
|
|
15
|
-
export declare function offerDismissed(storage: Pick<Storage, 'getItem'> | undefined): boolean;
|
|
16
|
-
export declare function dismissOffer(storage: Pick<Storage, 'setItem'> | undefined): void;
|
|
17
|
-
export interface OfferState {
|
|
18
|
-
claimed: boolean;
|
|
19
|
-
daysRemaining?: number | undefined;
|
|
20
|
-
claimUrl?: string | undefined;
|
|
21
|
-
eligible?: boolean | undefined;
|
|
22
|
-
}
|
|
23
|
-
export declare function offerHtml(offer: OfferState | undefined, dismissed: boolean): string;
|
|
24
|
-
export declare function paintOffer(root: ParentNode, offer: OfferState | undefined, storage: Pick<Storage, 'getItem' | 'setItem'> | undefined): void;
|
|
25
|
-
export declare const OFFER_CSS = "\n.reticle-offer{margin:8px 10px;padding:10px 12px;border:1px solid var(--reticle-line,#2a2f3a);border-radius:10px;background:var(--reticle-surface-inset,rgba(255,255,255,.03));}\n.reticle-offer-head{display:flex;align-items:center;justify-content:space-between;gap:8px;}\n.reticle-offer-title{font-weight:600;font-size:12px;}\n.reticle-offer-free{font-size:10px;padding:2px 6px;border-radius:999px;background:var(--reticle-accent,#4f7cff);color:#fff;white-space:nowrap;}\n.reticle-offer-body{margin:6px 0 8px;font-size:11px;line-height:1.45;opacity:.8;}\n.reticle-offer-actions{display:flex;align-items:center;gap:8px;}\n.reticle-offer-claim{font-size:11px;font-weight:600;padding:4px 10px;border-radius:6px;background:var(--reticle-accent,#4f7cff);color:#fff;text-decoration:none;}\n.reticle-offer-dismiss{font-size:11px;padding:4px 8px;border-radius:6px;background:none;border:0;color:inherit;opacity:.6;cursor:pointer;}\n.reticle-offer-dismiss:hover{opacity:1;}\n.reticle-offer-claimed{margin:6px 10px;font-size:10px;opacity:.6;}\n";
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
export const OFFER_ATTR = 'data-reticle-offer';
|
|
2
|
-
export const OFFER_SLOT_ATTR = 'data-reticle-offer-slot';
|
|
3
|
-
export const OFFER_CLAIM_ATTR = 'data-reticle-offer-claim';
|
|
4
|
-
export const OFFER_DISMISS_ATTR = 'data-reticle-offer-dismiss';
|
|
5
|
-
export const OFFER_DISMISSED_KEY = 'reticle.harnessOffer.dismissed';
|
|
6
|
-
export const OFFER_TEXT = {
|
|
7
|
-
HEADLINE: 'Reticle drives your app for you',
|
|
8
|
-
BODY: 'The harness explores your app, proves what works, and saves every journey as a test that replays for free. Built on Jev — no model API key needed.',
|
|
9
|
-
FREE: 'Free for 3 months',
|
|
10
|
-
CLAIM: 'Claim now',
|
|
11
|
-
DISMISS: 'Not now',
|
|
12
|
-
claimed: (days) => `Harness active — ${String(days)} days left`,
|
|
13
|
-
ending: (days) => 1 >= days
|
|
14
|
-
? 'Harness free period ends today'
|
|
15
|
-
: `Harness free period ends in ${String(days)} days`,
|
|
16
|
-
};
|
|
17
|
-
const ENDING_SOON_DAYS = 14;
|
|
18
|
-
export function offerDismissed(storage) {
|
|
19
|
-
try {
|
|
20
|
-
return '1' === storage?.getItem(OFFER_DISMISSED_KEY);
|
|
21
|
-
}
|
|
22
|
-
catch {
|
|
23
|
-
return false;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
export function dismissOffer(storage) {
|
|
27
|
-
try {
|
|
28
|
-
storage?.setItem(OFFER_DISMISSED_KEY, '1');
|
|
29
|
-
}
|
|
30
|
-
catch {
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
const escape = (text) => text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
34
|
-
export function offerHtml(offer, dismissed) {
|
|
35
|
-
if (offer === undefined)
|
|
36
|
-
return '';
|
|
37
|
-
if (offer.claimed) {
|
|
38
|
-
const days = offer.daysRemaining;
|
|
39
|
-
if (days === undefined || 0 > days)
|
|
40
|
-
return '';
|
|
41
|
-
const text = ENDING_SOON_DAYS >= days ? OFFER_TEXT.ending(days) : OFFER_TEXT.claimed(days);
|
|
42
|
-
return `<div ${OFFER_ATTR} class="reticle-offer reticle-offer-claimed">${escape(text)}</div>`;
|
|
43
|
-
}
|
|
44
|
-
if (false === offer.eligible)
|
|
45
|
-
return '';
|
|
46
|
-
if (dismissed)
|
|
47
|
-
return '';
|
|
48
|
-
const url = offer.claimUrl;
|
|
49
|
-
if (url === undefined || 0 === url.length)
|
|
50
|
-
return '';
|
|
51
|
-
return `<div ${OFFER_ATTR} class="reticle-offer" role="note">
|
|
52
|
-
<div class="reticle-offer-head">
|
|
53
|
-
<span class="reticle-offer-title">${escape(OFFER_TEXT.HEADLINE)}</span>
|
|
54
|
-
<span class="reticle-offer-free">${escape(OFFER_TEXT.FREE)}</span>
|
|
55
|
-
</div>
|
|
56
|
-
<p class="reticle-offer-body">${escape(OFFER_TEXT.BODY)}</p>
|
|
57
|
-
<div class="reticle-offer-actions">
|
|
58
|
-
<a ${OFFER_CLAIM_ATTR} class="reticle-offer-claim" href="${escape(url)}" target="_blank" rel="noopener noreferrer">${escape(OFFER_TEXT.CLAIM)}</a>
|
|
59
|
-
<button type="button" ${OFFER_DISMISS_ATTR} class="reticle-offer-dismiss">${escape(OFFER_TEXT.DISMISS)}</button>
|
|
60
|
-
</div>
|
|
61
|
-
</div>`;
|
|
62
|
-
}
|
|
63
|
-
export function paintOffer(root, offer, storage) {
|
|
64
|
-
const slot = root.querySelector(`[${OFFER_SLOT_ATTR}]`);
|
|
65
|
-
if (!(slot instanceof HTMLElement))
|
|
66
|
-
return;
|
|
67
|
-
slot.innerHTML = offerHtml(offer, offerDismissed(storage));
|
|
68
|
-
const dismiss = slot.querySelector(`[${OFFER_DISMISS_ATTR}]`);
|
|
69
|
-
dismiss?.addEventListener('click', (event) => {
|
|
70
|
-
event.preventDefault();
|
|
71
|
-
event.stopPropagation();
|
|
72
|
-
dismissOffer(storage);
|
|
73
|
-
slot.innerHTML = '';
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
export const OFFER_CSS = `
|
|
77
|
-
.reticle-offer{margin:8px 10px;padding:10px 12px;border:1px solid var(--reticle-line,#2a2f3a);border-radius:10px;background:var(--reticle-surface-inset,rgba(255,255,255,.03));}
|
|
78
|
-
.reticle-offer-head{display:flex;align-items:center;justify-content:space-between;gap:8px;}
|
|
79
|
-
.reticle-offer-title{font-weight:600;font-size:12px;}
|
|
80
|
-
.reticle-offer-free{font-size:10px;padding:2px 6px;border-radius:999px;background:var(--reticle-accent,#4f7cff);color:#fff;white-space:nowrap;}
|
|
81
|
-
.reticle-offer-body{margin:6px 0 8px;font-size:11px;line-height:1.45;opacity:.8;}
|
|
82
|
-
.reticle-offer-actions{display:flex;align-items:center;gap:8px;}
|
|
83
|
-
.reticle-offer-claim{font-size:11px;font-weight:600;padding:4px 10px;border-radius:6px;background:var(--reticle-accent,#4f7cff);color:#fff;text-decoration:none;}
|
|
84
|
-
.reticle-offer-dismiss{font-size:11px;padding:4px 8px;border-radius:6px;background:none;border:0;color:inherit;opacity:.6;cursor:pointer;}
|
|
85
|
-
.reticle-offer-dismiss:hover{opacity:1;}
|
|
86
|
-
.reticle-offer-claimed{margin:6px 10px;font-size:10px;opacity:.6;}
|
|
87
|
-
`;
|