@arraypress/extension-kit 1.9.0 → 1.11.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/README.md +145 -18
- package/bin/extension-kit.js +2 -1
- package/package.json +1 -1
- package/src/ai/host.d.ts +183 -0
- package/src/ai/host.js +225 -0
- package/src/ai/model.d.ts +2 -1
- package/src/ai/model.js +10 -2
- package/src/build/index.d.ts +2 -1
- package/src/build/index.js +2 -1
- package/src/build/store-shots.d.ts +177 -0
- package/src/build/store-shots.js +173 -0
- package/src/build/test-build.d.ts +13 -1
- package/src/build/test-build.js +41 -2
- package/src/build/vendor.d.ts +29 -8
- package/src/build/vendor.js +136 -40
- package/src/content/elements.d.ts +50 -0
- package/src/content/elements.js +88 -0
- package/src/content/highlight.d.ts +63 -0
- package/src/content/highlight.js +117 -0
- package/src/content/overlay.d.ts +31 -6
- package/src/content/overlay.js +56 -8
- package/src/content/page-toast.d.ts +26 -0
- package/src/content/page-toast.js +72 -0
- package/src/content/slices.d.ts +3 -1
- package/src/content/slices.js +29 -14
- package/src/content/watch.d.ts +4 -2
- package/src/content/watch.js +17 -3
- package/src/dom/copy.d.ts +36 -0
- package/src/dom/copy.js +84 -0
- package/src/dom/focus.d.ts +11 -0
- package/src/dom/focus.js +15 -0
- package/src/dom/i18n.d.ts +17 -0
- package/src/dom/i18n.js +15 -0
- package/src/dom/make.d.ts +53 -0
- package/src/dom/make.js +71 -0
- package/src/frame/window.d.ts +8 -1
- package/src/frame/window.js +11 -8
- package/src/index.d.ts +31 -6
- package/src/index.js +24 -6
- package/src/panel/show-on-page.d.ts +82 -0
- package/src/panel/show-on-page.js +83 -0
- package/src/scripting/call.d.ts +29 -9
- package/src/scripting/call.js +35 -11
- package/src/scripting/context-menus.d.ts +45 -0
- package/src/scripting/context-menus.js +53 -0
- package/src/scripting/registration.d.ts +2 -1
- package/src/scripting/registration.js +2 -1
- package/src/scripting/tab.d.ts +80 -0
- package/src/scripting/tab.js +89 -0
- package/src/settings/saver.js +16 -6
- package/src/settings/storage.d.ts +33 -2
- package/src/settings/storage.js +73 -3
- package/src/settings/store.d.ts +17 -0
- package/src/settings/store.js +87 -20
- package/src/sites/sites.d.ts +6 -1
- package/src/sites/sites.js +20 -3
- package/src/styles/chrome-ui.css +63 -30
- package/src/testing/e2e.d.ts +35 -4
- package/src/testing/e2e.js +67 -5
- package/src/testing/fake-model.d.ts +9 -1
- package/src/testing/fake-model.js +44 -23
- package/src/testing/fake-storage.d.ts +41 -13
- package/src/testing/fake-storage.js +163 -51
- package/src/testing/index.d.ts +8 -2
- package/src/testing/index.js +8 -2
- package/src/testing/live-run.d.ts +151 -0
- package/src/testing/live-run.js +206 -0
- package/src/testing/messages.d.ts +7 -3
- package/src/testing/messages.js +50 -9
- package/src/testing/package.d.ts +56 -7
- package/src/testing/package.js +57 -18
- package/src/testing/pages.d.ts +114 -0
- package/src/testing/pages.js +109 -0
- package/src/testing/prompt.d.ts +4 -2
- package/src/testing/prompt.js +7 -3
- package/src/time/pool.d.ts +2 -1
- package/src/time/pool.js +4 -2
- package/src/toolbar/theme.d.ts +10 -2
- package/src/toolbar/theme.js +12 -4
- package/src/ui/menu.d.ts +13 -3
- package/src/ui/menu.js +31 -11
- package/src/ui/model-row.js +5 -1
- package/src/ui/notice.d.ts +58 -0
- package/src/ui/notice.js +43 -0
- package/src/ui/site-list.js +12 -3
- package/src/ui/text-dialog.d.ts +3 -2
- package/src/ui/text-dialog.js +10 -3
- package/src/ui/toast.js +2 -0
package/src/dom/i18n.d.ts
CHANGED
|
@@ -45,3 +45,20 @@ export declare function translator(i18n: Pick<I18nApi, 'getMessage'>): (key: str
|
|
|
45
45
|
* @returns {void}
|
|
46
46
|
*/
|
|
47
47
|
export declare function localize(root: ParentNode, i18n: I18nApi): void;
|
|
48
|
+
/**
|
|
49
|
+
* A count in words, its number written as the person's language writes numbers: one message for
|
|
50
|
+
* one, another (with the number as `$1`) for any other count.
|
|
51
|
+
*
|
|
52
|
+
* @param {number} count - How many.
|
|
53
|
+
* @param {string} one - The message for one (`"1 colour"`).
|
|
54
|
+
* @param {string} many - The message for others (`"$COUNT$ colours"`, its placeholder `$1`).
|
|
55
|
+
* @param {{t: (key: string, substitutions?: string | string[]) => string, lang: string}} words - The
|
|
56
|
+
* page's message lookup and the UI language.
|
|
57
|
+
* @returns {string} The words.
|
|
58
|
+
* @example
|
|
59
|
+
* counted(1200, 'panelColoursOne', 'panelColours', { t, lang: 'en-GB' }); // '1,200 colours'
|
|
60
|
+
*/
|
|
61
|
+
export declare const counted: (count: number, one: string, many: string, { t, lang }: {
|
|
62
|
+
t: (key: string, substitutions?: string | string[]) => string;
|
|
63
|
+
lang: string;
|
|
64
|
+
}) => string;
|
package/src/dom/i18n.js
CHANGED
|
@@ -60,3 +60,18 @@ export function localize(root, i18n) {
|
|
|
60
60
|
}
|
|
61
61
|
if ('documentElement' in root) /** @type {Document} */ (root).documentElement.lang = i18n.getUILanguage();
|
|
62
62
|
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* A count in words, its number written as the person's language writes numbers: one message for
|
|
66
|
+
* one, another (with the number as `$1`) for any other count.
|
|
67
|
+
*
|
|
68
|
+
* @param {number} count - How many.
|
|
69
|
+
* @param {string} one - The message for one (`"1 colour"`).
|
|
70
|
+
* @param {string} many - The message for others (`"$COUNT$ colours"`, its placeholder `$1`).
|
|
71
|
+
* @param {{t: (key: string, substitutions?: string | string[]) => string, lang: string}} words - The
|
|
72
|
+
* page's message lookup and the UI language.
|
|
73
|
+
* @returns {string} The words.
|
|
74
|
+
* @example
|
|
75
|
+
* counted(1200, 'panelColoursOne', 'panelColours', { t, lang: 'en-GB' }); // '1,200 colours'
|
|
76
|
+
*/
|
|
77
|
+
export const counted = (count, one, many, { t, lang }) => (count === 1 ? t(one) : t(many, [count.toLocaleString(lang)]));
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Making a page's elements in a line: an element with its class and text, an
|
|
3
|
+
* icon from the page's SVG sprite, and Chrome's 24px icon button with its
|
|
4
|
+
* tooltip. For extension pages and content-script overlays alike (pass the
|
|
5
|
+
* document the element belongs to).
|
|
6
|
+
*
|
|
7
|
+
* @module dom/make
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* An element with a class and text.
|
|
11
|
+
*
|
|
12
|
+
* @template {keyof HTMLElementTagNameMap} K
|
|
13
|
+
* @param {K} tag - Its tag.
|
|
14
|
+
* @param {string} [className] - Its class.
|
|
15
|
+
* @param {string} [text] - Its text.
|
|
16
|
+
* @param {Document} [doc] - Its document. Default `document`.
|
|
17
|
+
* @returns {HTMLElementTagNameMap[K]} It.
|
|
18
|
+
* @example
|
|
19
|
+
* make('span', 'meta', '12 colours');
|
|
20
|
+
*/
|
|
21
|
+
export declare function make<K extends keyof HTMLElementTagNameMap>(tag: K, className?: string, text?: string, doc?: Document): HTMLElementTagNameMap[K];
|
|
22
|
+
/**
|
|
23
|
+
* An icon from the page's SVG sprite (`<symbol id="i-copy">`), hidden from screen readers.
|
|
24
|
+
*
|
|
25
|
+
* @param {string} id - The symbol's id, without `#`.
|
|
26
|
+
* @param {string} [className] - More classes beside `icon`.
|
|
27
|
+
* @param {Document} [doc] - Its document. Default `document`.
|
|
28
|
+
* @returns {SVGSVGElement} The icon.
|
|
29
|
+
* @example
|
|
30
|
+
* button.append(spriteIcon('i-copy'));
|
|
31
|
+
*/
|
|
32
|
+
export declare function spriteIcon(id: string, className?: string, doc?: Document): SVGSVGElement;
|
|
33
|
+
/**
|
|
34
|
+
* Chrome's icon button (chrome-ui.css `.icon-button`): an icon, its label as tooltip and for
|
|
35
|
+
* screen readers.
|
|
36
|
+
*
|
|
37
|
+
* @param {object} options
|
|
38
|
+
* @param {string} options.icon - The sprite symbol's id.
|
|
39
|
+
* @param {string} options.label - What it does.
|
|
40
|
+
* @param {() => void} options.onClick - Doing it.
|
|
41
|
+
* @param {string} [options.className] - More classes.
|
|
42
|
+
* @param {Document} [options.doc] - Its document. Default `document`.
|
|
43
|
+
* @returns {HTMLButtonElement} The button.
|
|
44
|
+
* @example
|
|
45
|
+
* iconButton({ icon: 'i-copy', label: 'Copy', onClick: () => copy(hex) });
|
|
46
|
+
*/
|
|
47
|
+
export declare function iconButton({ icon, label, onClick, className, doc }: {
|
|
48
|
+
icon: string;
|
|
49
|
+
label: string;
|
|
50
|
+
onClick: () => void;
|
|
51
|
+
className?: string;
|
|
52
|
+
doc?: Document;
|
|
53
|
+
}): HTMLButtonElement;
|
package/src/dom/make.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Making a page's elements in a line: an element with its class and text, an
|
|
3
|
+
* icon from the page's SVG sprite, and Chrome's 24px icon button with its
|
|
4
|
+
* tooltip. For extension pages and content-script overlays alike (pass the
|
|
5
|
+
* document the element belongs to).
|
|
6
|
+
*
|
|
7
|
+
* @module dom/make
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* An element with a class and text.
|
|
12
|
+
*
|
|
13
|
+
* @template {keyof HTMLElementTagNameMap} K
|
|
14
|
+
* @param {K} tag - Its tag.
|
|
15
|
+
* @param {string} [className] - Its class.
|
|
16
|
+
* @param {string} [text] - Its text.
|
|
17
|
+
* @param {Document} [doc] - Its document. Default `document`.
|
|
18
|
+
* @returns {HTMLElementTagNameMap[K]} It.
|
|
19
|
+
* @example
|
|
20
|
+
* make('span', 'meta', '12 colours');
|
|
21
|
+
*/
|
|
22
|
+
export function make(tag, className = '', text = '', doc = document) {
|
|
23
|
+
const element = doc.createElement(tag);
|
|
24
|
+
if (className) element.className = className;
|
|
25
|
+
if (text) element.textContent = text;
|
|
26
|
+
return element;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* An icon from the page's SVG sprite (`<symbol id="i-copy">`), hidden from screen readers.
|
|
31
|
+
*
|
|
32
|
+
* @param {string} id - The symbol's id, without `#`.
|
|
33
|
+
* @param {string} [className] - More classes beside `icon`.
|
|
34
|
+
* @param {Document} [doc] - Its document. Default `document`.
|
|
35
|
+
* @returns {SVGSVGElement} The icon.
|
|
36
|
+
* @example
|
|
37
|
+
* button.append(spriteIcon('i-copy'));
|
|
38
|
+
*/
|
|
39
|
+
export function spriteIcon(id, className = '', doc = document) {
|
|
40
|
+
const svg = doc.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
41
|
+
svg.setAttribute('class', `icon ${className}`.trim());
|
|
42
|
+
svg.setAttribute('aria-hidden', 'true');
|
|
43
|
+
const use = doc.createElementNS('http://www.w3.org/2000/svg', 'use');
|
|
44
|
+
use.setAttribute('href', `#${id}`);
|
|
45
|
+
svg.append(use);
|
|
46
|
+
return svg;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Chrome's icon button (chrome-ui.css `.icon-button`): an icon, its label as tooltip and for
|
|
51
|
+
* screen readers.
|
|
52
|
+
*
|
|
53
|
+
* @param {object} options
|
|
54
|
+
* @param {string} options.icon - The sprite symbol's id.
|
|
55
|
+
* @param {string} options.label - What it does.
|
|
56
|
+
* @param {() => void} options.onClick - Doing it.
|
|
57
|
+
* @param {string} [options.className] - More classes.
|
|
58
|
+
* @param {Document} [options.doc] - Its document. Default `document`.
|
|
59
|
+
* @returns {HTMLButtonElement} The button.
|
|
60
|
+
* @example
|
|
61
|
+
* iconButton({ icon: 'i-copy', label: 'Copy', onClick: () => copy(hex) });
|
|
62
|
+
*/
|
|
63
|
+
export function iconButton({ icon, label, onClick, className = '', doc = document }) {
|
|
64
|
+
const button = make('button', `icon-button ${className}`.trim(), '', doc);
|
|
65
|
+
button.type = 'button';
|
|
66
|
+
button.title = label;
|
|
67
|
+
button.setAttribute('aria-label', label);
|
|
68
|
+
button.append(spriteIcon(icon, '', doc));
|
|
69
|
+
button.addEventListener('click', onClick);
|
|
70
|
+
return button;
|
|
71
|
+
}
|
package/src/frame/window.d.ts
CHANGED
|
@@ -27,6 +27,13 @@ export declare const MAC_WINDOW: Readonly<{
|
|
|
27
27
|
}>;
|
|
28
28
|
/** The window's default width (px): the store screenshot's. */
|
|
29
29
|
export declare const WINDOW_W = 1152;
|
|
30
|
+
/**
|
|
31
|
+
* Text made safe for the drawn windows' HTML (and the store screenshots').
|
|
32
|
+
*
|
|
33
|
+
* @param {unknown} s - Text.
|
|
34
|
+
* @returns {string} It, escaped for HTML text and attributes.
|
|
35
|
+
*/
|
|
36
|
+
export declare const esc: (s: unknown) => string;
|
|
30
37
|
/**
|
|
31
38
|
* A letter favicon for made-up sites (no real brands).
|
|
32
39
|
*
|
|
@@ -104,7 +111,7 @@ export type Tab = {
|
|
|
104
111
|
*/
|
|
105
112
|
title: string;
|
|
106
113
|
/**
|
|
107
|
-
* Its favicon's URL.
|
|
114
|
+
* Its favicon's URL (escaped into the markup: an `&` in it is fine).
|
|
108
115
|
*/
|
|
109
116
|
icon: string;
|
|
110
117
|
/**
|
package/src/frame/window.js
CHANGED
|
@@ -26,10 +26,12 @@ export const MAC_WINDOW = Object.freeze({ titleBar: 32, radius: 16 });
|
|
|
26
26
|
export const WINDOW_W = 1152;
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
+
* Text made safe for the drawn windows' HTML (and the store screenshots').
|
|
30
|
+
*
|
|
29
31
|
* @param {unknown} s - Text.
|
|
30
32
|
* @returns {string} It, escaped for HTML text and attributes.
|
|
31
33
|
*/
|
|
32
|
-
const esc = (s) => String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
34
|
+
export const esc = (s) => String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
33
35
|
|
|
34
36
|
/** Material icon paths (24×24). */
|
|
35
37
|
const P = {
|
|
@@ -66,7 +68,7 @@ const icon = (name, size = 20) =>
|
|
|
66
68
|
* @returns {string} A data: URL of the SVG.
|
|
67
69
|
*/
|
|
68
70
|
export const letterIcon = (letter, bg, fg = '#fff') =>
|
|
69
|
-
`data:image/svg+xml,${encodeURIComponent(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><rect width="16" height="16" rx="4" fill="${bg}"/><text x="8" y="12" font-family="-apple-system,Helvetica,Arial" font-size="11" font-weight="700" text-anchor="middle" fill="${fg}">${letter}</text></svg>`)}`;
|
|
71
|
+
`data:image/svg+xml,${encodeURIComponent(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><rect width="16" height="16" rx="4" fill="${esc(bg)}"/><text x="8" y="12" font-family="-apple-system,Helvetica,Arial" font-size="11" font-weight="700" text-anchor="middle" fill="${esc(fg)}">${esc(letter)}</text></svg>`)}`;
|
|
70
72
|
|
|
71
73
|
/**
|
|
72
74
|
* A window's shadow: soft and dropped below it, as macOS draws one (px; `blur` as CSS's and a
|
|
@@ -138,7 +140,7 @@ const trafficLights = ({ left, top, size, gap }) =>
|
|
|
138
140
|
*
|
|
139
141
|
* @typedef {object} Tab
|
|
140
142
|
* @property {string} title Its title.
|
|
141
|
-
* @property {string} icon Its favicon's URL.
|
|
143
|
+
* @property {string} icon Its favicon's URL (escaped into the markup: an `&` in it is fine).
|
|
142
144
|
* @property {boolean} [active] The selected tab.
|
|
143
145
|
* @property {boolean} [audible] Playing sound.
|
|
144
146
|
* @property {boolean} [muted] Muted.
|
|
@@ -158,7 +160,7 @@ function tabLayout(tabs, w) {
|
|
|
158
160
|
const right = 60;
|
|
159
161
|
const max = 240;
|
|
160
162
|
const avail = w - left - right - 36;
|
|
161
|
-
const width = Math.min(max, Math.floor(avail / tabs.length));
|
|
163
|
+
const width = Math.min(max, Math.floor(avail / Math.max(1, tabs.length)));
|
|
162
164
|
return tabs.map((_, i) => ({ x: left + i * width, width }));
|
|
163
165
|
}
|
|
164
166
|
|
|
@@ -183,15 +185,16 @@ export function windowChrome({ tabs, url, urlHost, theme = 'light', w = WINDOW_W
|
|
|
183
185
|
const alert = tab.muted ? icon('muted', 16) : tab.audible ? icon('sound', 16) : '';
|
|
184
186
|
const showClose = tab.active || width >= 120;
|
|
185
187
|
return `<div class="tab${tab.active ? ' active' : ''}" style="left:${x}px;width:${width}px">
|
|
186
|
-
<img class="fav" src="${tab.icon}" width="16" height="16" alt=""/>
|
|
188
|
+
<img class="fav" src="${esc(tab.icon)}" width="16" height="16" alt=""/>
|
|
187
189
|
<span class="title">${esc(tab.title)}</span>
|
|
188
190
|
${alert ? `<span class="alert">${alert}</span>` : ''}
|
|
189
191
|
${showClose ? `<span class="x">${icon('close', 14)}</span>` : ''}
|
|
190
192
|
</div>${sepAfter ? `<span class="sep" style="left:${x + width - 1}px"></span>` : ''}`;
|
|
191
193
|
}).join('');
|
|
192
|
-
|
|
193
|
-
const
|
|
194
|
-
const
|
|
194
|
+
// After the last tab, or where the first would be in a window drawn without any.
|
|
195
|
+
const last = layout.at(-1);
|
|
196
|
+
const plusX = last ? last.x + last.width + 4 : 84;
|
|
197
|
+
const extIcons = ext.map((src) => `<img src="${esc(src)}" width="16" height="16" alt="" style="margin:0 9px"/>`).join('');
|
|
195
198
|
return `
|
|
196
199
|
<div class="strip">
|
|
197
200
|
${trafficLights({ left: 16, top: 15, size: 12, gap: 8 })}
|
package/src/index.d.ts
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
* service worker and content scripts load, Chrome's page components (the ⋮
|
|
4
4
|
* menu, the text dialog, the toast with Undo, a settings card's site list,
|
|
5
5
|
* the built-in AI row), Chrome's window drawn around a capture, per-site lists' sites, Chrome's on-device model,
|
|
6
|
-
* a content script's slices, change watcher and
|
|
7
|
-
*
|
|
6
|
+
* a content script's slices, change watcher, overlay and “Show on page” boxes, a module's
|
|
7
|
+
* function called in a page or every frame of a tab, a tab's side panel opened from a click,
|
|
8
|
+
* copying (plain text and HTML), and making elements and icon buttons in a line. Every function takes the `chrome`
|
|
8
9
|
* APIs it uses as arguments, so tests run the real logic against fakes.
|
|
9
10
|
*
|
|
10
11
|
* Build-time helpers are in `@arraypress/extension-kit/build`, test helpers
|
|
@@ -15,20 +16,29 @@
|
|
|
15
16
|
*/
|
|
16
17
|
export { githubAboutLinks, isPlaceholder } from './about/links.js';
|
|
17
18
|
export { MODEL_MESSAGES, chromeLanguageModel, createModelSession, describeModel, downloadModel, modelAvailability, } from './ai/model.js';
|
|
19
|
+
export { HOST_GRACE_MS, MODEL_HOST, createHostedModelSession, serveModelOffscreen } from './ai/host.js';
|
|
18
20
|
export { setShortcuts } from './commands/shortcuts.js';
|
|
19
21
|
export { BLOCK_CHARS, SLICE_MS, blocksOf, eachInSlices, inSlices } from './content/slices.js';
|
|
22
|
+
export { MAX_ELEMENTS, otherSiteFrames, renderedElements, shadowRootOf } from './content/elements.js';
|
|
23
|
+
export { HIGHLIGHT_CSS, MAX_BOXES, createHighlighter } from './content/highlight.js';
|
|
20
24
|
export { pageOverlay } from './content/overlay.js';
|
|
25
|
+
export { PAGE_TOAST_MS, showPageToast } from './content/page-toast.js';
|
|
21
26
|
export { MAX_WAIT_MS, SETTLE_MS, watchChanges } from './content/watch.js';
|
|
22
|
-
export {
|
|
27
|
+
export { copyFormats } from './dom/copy.js';
|
|
28
|
+
export { focusedElement, focusedIn } from './dom/focus.js';
|
|
29
|
+
export { iconButton, make, spriteIcon } from './dom/make.js';
|
|
23
30
|
export { MAC_WINDOW, THEMES, WINDOW_BARS, WINDOW_W, letterIcon, macWindowSvg, windowChrome, windowChromeSvg } from './frame/window.js';
|
|
24
|
-
export { localize, translator } from './dom/i18n.js';
|
|
31
|
+
export { counted, localize, translator } from './dom/i18n.js';
|
|
25
32
|
export { byId, setAboutLinks, statusFlasher, valueRow } from './dom/page.js';
|
|
26
33
|
export { setUpPage } from './dom/setup.js';
|
|
34
|
+
export { createShowOnPage } from './panel/show-on-page.js';
|
|
27
35
|
export { openSidePanel, sidePanelPath } from './panel/side-panel.js';
|
|
28
|
-
export { callInPage, importAndCall } from './scripting/call.js';
|
|
36
|
+
export { CALL_FAILED, callInPage, importAndCall } from './scripting/call.js';
|
|
37
|
+
export { contextMenuBuilder } from './scripting/context-menus.js';
|
|
29
38
|
export { contentScriptSync } from './scripting/registration.js';
|
|
39
|
+
export { callInTab, isBrowserPage } from './scripting/tab.js';
|
|
30
40
|
export { createSettingsSaver } from './settings/saver.js';
|
|
31
|
-
export { MAX_SYNC_ITEM_BYTES } from './settings/storage.js';
|
|
41
|
+
export { MAX_SYNC_ITEM_BYTES, syncItemBytes } from './settings/storage.js';
|
|
32
42
|
export { createSettingsStore } from './settings/store.js';
|
|
33
43
|
export { MAX_HOST_LENGTH, hostPatterns, onSite, originPattern, siteHost, siteLabel, siteOrigin, withSite, withSiteReplaced, withoutSite, } from './sites/sites.js';
|
|
34
44
|
export { eachLimited } from './time/pool.js';
|
|
@@ -40,6 +50,7 @@ export { MENU_EDGE, createMenu, isInside, menuPosition, moreActionsButton } from
|
|
|
40
50
|
export { createModelRow } from './ui/model-row.js';
|
|
41
51
|
export { SITE_LIST_MESSAGES, SITE_LIST_WORDS, createSiteList } from './ui/site-list.js';
|
|
42
52
|
export { askFields, askText, checkText, fieldIds } from './ui/text-dialog.js';
|
|
53
|
+
export { noticeCard } from './ui/notice.js';
|
|
43
54
|
export { TOAST_MS, createToast } from './ui/toast.js';
|
|
44
55
|
export type Availability = import('./ai/model.js').Availability;
|
|
45
56
|
export type InitialPrompt = import('./ai/model.js').InitialPrompt;
|
|
@@ -50,7 +61,11 @@ export type ModelAnswer = import('./ai/model.js').ModelAnswer;
|
|
|
50
61
|
export type ModelSession = import('./ai/model.js').ModelSession;
|
|
51
62
|
export type ModelStatus = import('./ai/model.js').ModelStatus;
|
|
52
63
|
export type SliceOptions = import('./content/slices.js').SliceOptions;
|
|
64
|
+
export type Highlighter = import('./content/highlight.js').Highlighter;
|
|
65
|
+
export type ClearReason = import('./content/highlight.js').ClearReason;
|
|
66
|
+
export type ShowOnPage = import('./panel/show-on-page.js').ShowOnPage;
|
|
53
67
|
export type PageOverlay = import('./content/overlay.js').PageOverlay;
|
|
68
|
+
export type ClipboardFormats = import('./dom/copy.js').ClipboardFormats;
|
|
54
69
|
export type WatchHandlers = import('./content/watch.js').WatchHandlers;
|
|
55
70
|
export type WindowTab = import('./frame/window.js').Tab;
|
|
56
71
|
export type WindowTheme = import('./frame/window.js').Theme;
|
|
@@ -58,6 +73,8 @@ export type WindowShadow = import('./frame/window.js').WindowShadow;
|
|
|
58
73
|
export type ModelRow = import('./ui/model-row.js').ModelRow;
|
|
59
74
|
export type ContentScript = import('./scripting/registration.js').ContentScript;
|
|
60
75
|
export type FrameResult = import('./scripting/call.js').FrameResult<unknown>;
|
|
76
|
+
export type TabReading = import('./scripting/tab.js').TabReading<unknown>;
|
|
77
|
+
export type TabEdges = import('./scripting/tab.js').TabEdges;
|
|
61
78
|
export type ActionMenu = import('./ui/menu.js').ActionMenu;
|
|
62
79
|
export type MenuEntry = import('./ui/menu.js').MenuEntry;
|
|
63
80
|
export type MenuItem = import('./ui/menu.js').MenuItem;
|
|
@@ -66,6 +83,7 @@ export type SiteList = import('./ui/site-list.js').SiteList;
|
|
|
66
83
|
export type FieldRequest = import('./ui/text-dialog.js').FieldRequest;
|
|
67
84
|
export type FieldsRequest = import('./ui/text-dialog.js').FieldsRequest;
|
|
68
85
|
export type TextRequest = import('./ui/text-dialog.js').TextRequest;
|
|
86
|
+
export type Notice = import('./ui/notice.js').Notice;
|
|
69
87
|
export type Toast = import('./ui/toast.js').Toast;
|
|
70
88
|
/**
|
|
71
89
|
* The page helpers' types, for a page's JSDoc.
|
|
@@ -79,7 +97,11 @@ export type Toast = import('./ui/toast.js').Toast;
|
|
|
79
97
|
* @typedef {import('./ai/model.js').ModelSession} ModelSession
|
|
80
98
|
* @typedef {import('./ai/model.js').ModelStatus} ModelStatus
|
|
81
99
|
* @typedef {import('./content/slices.js').SliceOptions} SliceOptions
|
|
100
|
+
* @typedef {import('./content/highlight.js').Highlighter} Highlighter
|
|
101
|
+
* @typedef {import('./content/highlight.js').ClearReason} ClearReason
|
|
102
|
+
* @typedef {import('./panel/show-on-page.js').ShowOnPage} ShowOnPage
|
|
82
103
|
* @typedef {import('./content/overlay.js').PageOverlay} PageOverlay
|
|
104
|
+
* @typedef {import('./dom/copy.js').ClipboardFormats} ClipboardFormats
|
|
83
105
|
* @typedef {import('./content/watch.js').WatchHandlers} WatchHandlers
|
|
84
106
|
* @typedef {import('./frame/window.js').Tab} WindowTab
|
|
85
107
|
* @typedef {import('./frame/window.js').Theme} WindowTheme
|
|
@@ -87,6 +109,8 @@ export type Toast = import('./ui/toast.js').Toast;
|
|
|
87
109
|
* @typedef {import('./ui/model-row.js').ModelRow} ModelRow
|
|
88
110
|
* @typedef {import('./scripting/registration.js').ContentScript} ContentScript
|
|
89
111
|
* @typedef {import('./scripting/call.js').FrameResult<unknown>} FrameResult
|
|
112
|
+
* @typedef {import('./scripting/tab.js').TabReading<unknown>} TabReading
|
|
113
|
+
* @typedef {import('./scripting/tab.js').TabEdges} TabEdges
|
|
90
114
|
* @typedef {import('./ui/menu.js').ActionMenu} ActionMenu
|
|
91
115
|
* @typedef {import('./ui/menu.js').MenuEntry} MenuEntry
|
|
92
116
|
* @typedef {import('./ui/menu.js').MenuItem} MenuItem
|
|
@@ -95,5 +119,6 @@ export type Toast = import('./ui/toast.js').Toast;
|
|
|
95
119
|
* @typedef {import('./ui/text-dialog.js').FieldRequest} FieldRequest
|
|
96
120
|
* @typedef {import('./ui/text-dialog.js').FieldsRequest} FieldsRequest
|
|
97
121
|
* @typedef {import('./ui/text-dialog.js').TextRequest} TextRequest
|
|
122
|
+
* @typedef {import('./ui/notice.js').Notice} Notice
|
|
98
123
|
* @typedef {import('./ui/toast.js').Toast} Toast
|
|
99
124
|
*/
|
package/src/index.js
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
* service worker and content scripts load, Chrome's page components (the ⋮
|
|
4
4
|
* menu, the text dialog, the toast with Undo, a settings card's site list,
|
|
5
5
|
* the built-in AI row), Chrome's window drawn around a capture, per-site lists' sites, Chrome's on-device model,
|
|
6
|
-
* a content script's slices, change watcher and
|
|
7
|
-
*
|
|
6
|
+
* a content script's slices, change watcher, overlay and “Show on page” boxes, a module's
|
|
7
|
+
* function called in a page or every frame of a tab, a tab's side panel opened from a click,
|
|
8
|
+
* copying (plain text and HTML), and making elements and icon buttons in a line. Every function takes the `chrome`
|
|
8
9
|
* APIs it uses as arguments, so tests run the real logic against fakes.
|
|
9
10
|
*
|
|
10
11
|
* Build-time helpers are in `@arraypress/extension-kit/build`, test helpers
|
|
@@ -18,20 +19,29 @@ export { githubAboutLinks, isPlaceholder } from './about/links.js';
|
|
|
18
19
|
export {
|
|
19
20
|
MODEL_MESSAGES, chromeLanguageModel, createModelSession, describeModel, downloadModel, modelAvailability,
|
|
20
21
|
} from './ai/model.js';
|
|
22
|
+
export { HOST_GRACE_MS, MODEL_HOST, createHostedModelSession, serveModelOffscreen } from './ai/host.js';
|
|
21
23
|
export { setShortcuts } from './commands/shortcuts.js';
|
|
22
24
|
export { BLOCK_CHARS, SLICE_MS, blocksOf, eachInSlices, inSlices } from './content/slices.js';
|
|
25
|
+
export { MAX_ELEMENTS, otherSiteFrames, renderedElements, shadowRootOf } from './content/elements.js';
|
|
26
|
+
export { HIGHLIGHT_CSS, MAX_BOXES, createHighlighter } from './content/highlight.js';
|
|
23
27
|
export { pageOverlay } from './content/overlay.js';
|
|
28
|
+
export { PAGE_TOAST_MS, showPageToast } from './content/page-toast.js';
|
|
24
29
|
export { MAX_WAIT_MS, SETTLE_MS, watchChanges } from './content/watch.js';
|
|
25
|
-
export {
|
|
30
|
+
export { copyFormats } from './dom/copy.js';
|
|
31
|
+
export { focusedElement, focusedIn } from './dom/focus.js';
|
|
32
|
+
export { iconButton, make, spriteIcon } from './dom/make.js';
|
|
26
33
|
export { MAC_WINDOW, THEMES, WINDOW_BARS, WINDOW_W, letterIcon, macWindowSvg, windowChrome, windowChromeSvg } from './frame/window.js';
|
|
27
|
-
export { localize, translator } from './dom/i18n.js';
|
|
34
|
+
export { counted, localize, translator } from './dom/i18n.js';
|
|
28
35
|
export { byId, setAboutLinks, statusFlasher, valueRow } from './dom/page.js';
|
|
29
36
|
export { setUpPage } from './dom/setup.js';
|
|
37
|
+
export { createShowOnPage } from './panel/show-on-page.js';
|
|
30
38
|
export { openSidePanel, sidePanelPath } from './panel/side-panel.js';
|
|
31
|
-
export { callInPage, importAndCall } from './scripting/call.js';
|
|
39
|
+
export { CALL_FAILED, callInPage, importAndCall } from './scripting/call.js';
|
|
40
|
+
export { contextMenuBuilder } from './scripting/context-menus.js';
|
|
32
41
|
export { contentScriptSync } from './scripting/registration.js';
|
|
42
|
+
export { callInTab, isBrowserPage } from './scripting/tab.js';
|
|
33
43
|
export { createSettingsSaver } from './settings/saver.js';
|
|
34
|
-
export { MAX_SYNC_ITEM_BYTES } from './settings/storage.js';
|
|
44
|
+
export { MAX_SYNC_ITEM_BYTES, syncItemBytes } from './settings/storage.js';
|
|
35
45
|
export { createSettingsStore } from './settings/store.js';
|
|
36
46
|
export {
|
|
37
47
|
MAX_HOST_LENGTH, hostPatterns, onSite, originPattern, siteHost, siteLabel, siteOrigin, withSite, withSiteReplaced, withoutSite,
|
|
@@ -47,6 +57,7 @@ export { MENU_EDGE, createMenu, isInside, menuPosition, moreActionsButton } from
|
|
|
47
57
|
export { createModelRow } from './ui/model-row.js';
|
|
48
58
|
export { SITE_LIST_MESSAGES, SITE_LIST_WORDS, createSiteList } from './ui/site-list.js';
|
|
49
59
|
export { askFields, askText, checkText, fieldIds } from './ui/text-dialog.js';
|
|
60
|
+
export { noticeCard } from './ui/notice.js';
|
|
50
61
|
export { TOAST_MS, createToast } from './ui/toast.js';
|
|
51
62
|
|
|
52
63
|
/**
|
|
@@ -61,7 +72,11 @@ export { TOAST_MS, createToast } from './ui/toast.js';
|
|
|
61
72
|
* @typedef {import('./ai/model.js').ModelSession} ModelSession
|
|
62
73
|
* @typedef {import('./ai/model.js').ModelStatus} ModelStatus
|
|
63
74
|
* @typedef {import('./content/slices.js').SliceOptions} SliceOptions
|
|
75
|
+
* @typedef {import('./content/highlight.js').Highlighter} Highlighter
|
|
76
|
+
* @typedef {import('./content/highlight.js').ClearReason} ClearReason
|
|
77
|
+
* @typedef {import('./panel/show-on-page.js').ShowOnPage} ShowOnPage
|
|
64
78
|
* @typedef {import('./content/overlay.js').PageOverlay} PageOverlay
|
|
79
|
+
* @typedef {import('./dom/copy.js').ClipboardFormats} ClipboardFormats
|
|
65
80
|
* @typedef {import('./content/watch.js').WatchHandlers} WatchHandlers
|
|
66
81
|
* @typedef {import('./frame/window.js').Tab} WindowTab
|
|
67
82
|
* @typedef {import('./frame/window.js').Theme} WindowTheme
|
|
@@ -69,6 +84,8 @@ export { TOAST_MS, createToast } from './ui/toast.js';
|
|
|
69
84
|
* @typedef {import('./ui/model-row.js').ModelRow} ModelRow
|
|
70
85
|
* @typedef {import('./scripting/registration.js').ContentScript} ContentScript
|
|
71
86
|
* @typedef {import('./scripting/call.js').FrameResult<unknown>} FrameResult
|
|
87
|
+
* @typedef {import('./scripting/tab.js').TabReading<unknown>} TabReading
|
|
88
|
+
* @typedef {import('./scripting/tab.js').TabEdges} TabEdges
|
|
72
89
|
* @typedef {import('./ui/menu.js').ActionMenu} ActionMenu
|
|
73
90
|
* @typedef {import('./ui/menu.js').MenuEntry} MenuEntry
|
|
74
91
|
* @typedef {import('./ui/menu.js').MenuItem} MenuItem
|
|
@@ -77,5 +94,6 @@ export { TOAST_MS, createToast } from './ui/toast.js';
|
|
|
77
94
|
* @typedef {import('./ui/text-dialog.js').FieldRequest} FieldRequest
|
|
78
95
|
* @typedef {import('./ui/text-dialog.js').FieldsRequest} FieldsRequest
|
|
79
96
|
* @typedef {import('./ui/text-dialog.js').TextRequest} TextRequest
|
|
97
|
+
* @typedef {import('./ui/notice.js').Notice} Notice
|
|
80
98
|
* @typedef {import('./ui/toast.js').Toast} Toast
|
|
81
99
|
*/
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A side panel's “Show on page”: which of the panel's items (a font, a
|
|
3
|
+
* colour) is boxed on the page, kept true whatever ends it. A click on the
|
|
4
|
+
* item's button shows it, a second click takes it away, another item's
|
|
5
|
+
* replaces it; a rescan or the panel closing clears the page's boxes
|
|
6
|
+
* (`reset`); and when the page clears them itself (Esc there, or the page
|
|
7
|
+
* left: the highlighter's `onClear` reasons `'escape'` and `'pagehide'`,
|
|
8
|
+
* sent to the panel as a message) the panel's button is released without
|
|
9
|
+
* calling the page back (`cleared`).
|
|
10
|
+
*
|
|
11
|
+
* @module panel/show-on-page
|
|
12
|
+
*/
|
|
13
|
+
export type ShowOnPageSetup = {
|
|
14
|
+
/**
|
|
15
|
+
* Boxes these on
|
|
16
|
+
* the page (the panel's own call into the tab's frames), scrolling the first into view in
|
|
17
|
+
* `revealFrame`; null takes the boxes away.
|
|
18
|
+
*/
|
|
19
|
+
show: (ids: string[] | null, revealFrame?: number) => Promise<unknown>;
|
|
20
|
+
/**
|
|
21
|
+
* Hears which item is shown now (press its
|
|
22
|
+
* button, release the others).
|
|
23
|
+
*/
|
|
24
|
+
onChange: (shown: string | null) => void;
|
|
25
|
+
};
|
|
26
|
+
export type ShowOnPage = {
|
|
27
|
+
/**
|
|
28
|
+
* The item shown, or null.
|
|
29
|
+
*/
|
|
30
|
+
shown: () => string | null;
|
|
31
|
+
/**
|
|
32
|
+
* Show the item
|
|
33
|
+
* `id` (the page's `ids`), or take it away if it's the one shown. Rejects when the page can't be
|
|
34
|
+
* reached, with nothing shown.
|
|
35
|
+
*/
|
|
36
|
+
toggle: (id: string, ids: string[], revealFrame?: number) => Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* A rescan started, or the panel is closing: take the page's boxes
|
|
39
|
+
* away if any are shown. The call to the page starts at once (a closing panel still gets it out)
|
|
40
|
+
* and a failure is ignored (the tab is gone).
|
|
41
|
+
*/
|
|
42
|
+
reset: () => Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* The page says its boxes went: nothing is shown, and the page isn't called.
|
|
45
|
+
*/
|
|
46
|
+
cleared: () => void;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* @typedef {object} ShowOnPageSetup
|
|
50
|
+
* @property {(ids: string[] | null, revealFrame?: number) => Promise<unknown>} show Boxes these on
|
|
51
|
+
* the page (the panel's own call into the tab's frames), scrolling the first into view in
|
|
52
|
+
* `revealFrame`; null takes the boxes away.
|
|
53
|
+
* @property {(shown: string | null) => void} onChange Hears which item is shown now (press its
|
|
54
|
+
* button, release the others).
|
|
55
|
+
*/
|
|
56
|
+
/**
|
|
57
|
+
* @typedef {object} ShowOnPage
|
|
58
|
+
* @property {() => string | null} shown The item shown, or null.
|
|
59
|
+
* @property {(id: string, ids: string[], revealFrame?: number) => Promise<void>} toggle Show the item
|
|
60
|
+
* `id` (the page's `ids`), or take it away if it's the one shown. Rejects when the page can't be
|
|
61
|
+
* reached, with nothing shown.
|
|
62
|
+
* @property {() => Promise<void>} reset A rescan started, or the panel is closing: take the page's boxes
|
|
63
|
+
* away if any are shown. The call to the page starts at once (a closing panel still gets it out)
|
|
64
|
+
* and a failure is ignored (the tab is gone).
|
|
65
|
+
* @property {() => void} cleared The page says its boxes went: nothing is shown, and the page isn't called.
|
|
66
|
+
*/
|
|
67
|
+
/**
|
|
68
|
+
* A panel's “Show on page” state.
|
|
69
|
+
*
|
|
70
|
+
* @param {ShowOnPageSetup} setup - How to box items on the page, and who hears what's shown.
|
|
71
|
+
* @returns {ShowOnPage} The controller.
|
|
72
|
+
* @example
|
|
73
|
+
* const onPage = createShowOnPage({
|
|
74
|
+
* show: (ids, frame) => callPage('highlight', [ids, frame]),
|
|
75
|
+
* onChange: (shown) => { for (const button of buttons) button.setAttribute('aria-pressed', String(button.dataset.id === shown)); },
|
|
76
|
+
* });
|
|
77
|
+
* button.addEventListener('click', () => onPage.toggle(font.id, [font.id], font.frameId));
|
|
78
|
+
* chrome.runtime.onMessage.addListener((message) => { if (message.type === 'boxes-cleared') onPage.cleared(); });
|
|
79
|
+
* addEventListener('pagehide', () => onPage.reset());
|
|
80
|
+
* await onPage.reset(); // before a rescan
|
|
81
|
+
*/
|
|
82
|
+
export declare function createShowOnPage({ show, onChange }: ShowOnPageSetup): ShowOnPage;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A side panel's “Show on page”: which of the panel's items (a font, a
|
|
3
|
+
* colour) is boxed on the page, kept true whatever ends it. A click on the
|
|
4
|
+
* item's button shows it, a second click takes it away, another item's
|
|
5
|
+
* replaces it; a rescan or the panel closing clears the page's boxes
|
|
6
|
+
* (`reset`); and when the page clears them itself (Esc there, or the page
|
|
7
|
+
* left: the highlighter's `onClear` reasons `'escape'` and `'pagehide'`,
|
|
8
|
+
* sent to the panel as a message) the panel's button is released without
|
|
9
|
+
* calling the page back (`cleared`).
|
|
10
|
+
*
|
|
11
|
+
* @module panel/show-on-page
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {object} ShowOnPageSetup
|
|
16
|
+
* @property {(ids: string[] | null, revealFrame?: number) => Promise<unknown>} show Boxes these on
|
|
17
|
+
* the page (the panel's own call into the tab's frames), scrolling the first into view in
|
|
18
|
+
* `revealFrame`; null takes the boxes away.
|
|
19
|
+
* @property {(shown: string | null) => void} onChange Hears which item is shown now (press its
|
|
20
|
+
* button, release the others).
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {object} ShowOnPage
|
|
25
|
+
* @property {() => string | null} shown The item shown, or null.
|
|
26
|
+
* @property {(id: string, ids: string[], revealFrame?: number) => Promise<void>} toggle Show the item
|
|
27
|
+
* `id` (the page's `ids`), or take it away if it's the one shown. Rejects when the page can't be
|
|
28
|
+
* reached, with nothing shown.
|
|
29
|
+
* @property {() => Promise<void>} reset A rescan started, or the panel is closing: take the page's boxes
|
|
30
|
+
* away if any are shown. The call to the page starts at once (a closing panel still gets it out)
|
|
31
|
+
* and a failure is ignored (the tab is gone).
|
|
32
|
+
* @property {() => void} cleared The page says its boxes went: nothing is shown, and the page isn't called.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A panel's “Show on page” state.
|
|
37
|
+
*
|
|
38
|
+
* @param {ShowOnPageSetup} setup - How to box items on the page, and who hears what's shown.
|
|
39
|
+
* @returns {ShowOnPage} The controller.
|
|
40
|
+
* @example
|
|
41
|
+
* const onPage = createShowOnPage({
|
|
42
|
+
* show: (ids, frame) => callPage('highlight', [ids, frame]),
|
|
43
|
+
* onChange: (shown) => { for (const button of buttons) button.setAttribute('aria-pressed', String(button.dataset.id === shown)); },
|
|
44
|
+
* });
|
|
45
|
+
* button.addEventListener('click', () => onPage.toggle(font.id, [font.id], font.frameId));
|
|
46
|
+
* chrome.runtime.onMessage.addListener((message) => { if (message.type === 'boxes-cleared') onPage.cleared(); });
|
|
47
|
+
* addEventListener('pagehide', () => onPage.reset());
|
|
48
|
+
* await onPage.reset(); // before a rescan
|
|
49
|
+
*/
|
|
50
|
+
export function createShowOnPage({ show, onChange }) {
|
|
51
|
+
/** @type {string | null} */
|
|
52
|
+
let shown = null;
|
|
53
|
+
/** @param {string | null} id */
|
|
54
|
+
const set = (id) => {
|
|
55
|
+
if (shown === id) return;
|
|
56
|
+
shown = id;
|
|
57
|
+
onChange(id);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
shown: () => shown,
|
|
62
|
+
async toggle(id, ids, revealFrame) {
|
|
63
|
+
if (shown === id) {
|
|
64
|
+
set(null);
|
|
65
|
+
await show(null);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
set(id);
|
|
69
|
+
try {
|
|
70
|
+
await show(ids, revealFrame);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
if (shown === id) set(null);
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
async reset() {
|
|
77
|
+
if (shown === null) return;
|
|
78
|
+
set(null);
|
|
79
|
+
await show(null).catch(() => {});
|
|
80
|
+
},
|
|
81
|
+
cleared: () => set(null),
|
|
82
|
+
};
|
|
83
|
+
}
|