overplay 4.0.0 → 4.1.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/CHANGELOG.md +9 -0
- package/LICENSE +3 -0
- package/README.md +6 -3
- package/dist/overplay.esm.js +69 -21
- package/dist/overplay.esm.js.map +1 -1
- package/dist/overplay.iife.js +1 -1
- package/dist/overplay.iife.js.map +1 -1
- package/dist/overplay.umd.js +1 -1
- package/dist/overplay.umd.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 4.1.0 — 2026-10-17
|
|
4
|
+
|
|
5
|
+
- New default shortcut to open and close comments: **Alt+C** (**Option+C** on Mac). It works on any keyboard layout. Alt+Shift+C still works, but on Windows Alt+Shift can switch the keyboard language. Settings shows Option on Mac. Set `keyboardShortcut` to keep another key.
|
|
6
|
+
- The update notice in the sidebar footer now offers a prompt for your coding agent: Show Prompt and Copy Prompt. The prompt updates the package, reads the release notes, clears the bundler cache (Vite, Next.js, Nuxt and others) and restarts the dev server. A version bump alone often kept serving the old widget.
|
|
7
|
+
- The update notice no longer shows for the script tag, which loads the server's copy and updates by itself.
|
|
8
|
+
- Dashboard: on the Projects page each team is a panel with its name, project count and New Project button. Project tiles use the brand blue.
|
|
9
|
+
- Website: the MCP section shows a short chat in which a coding agent reads yesterday's comments, fixes one and replies.
|
|
10
|
+
- The public source moves to [github.com/artemsvit/Overplay-Community](https://github.com/artemsvit/Overplay-Community): the widget and the Community server. Links on the site and in the package point there.
|
|
11
|
+
|
|
3
12
|
## 4.0.0 — 2026-10-17
|
|
4
13
|
|
|
5
14
|
The package and code now use the Overplay name everywhere. This release has breaking changes.
|
package/LICENSE
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
+
This license covers the Overplay widget (the `overplay` npm package) and every file outside
|
|
4
|
+
`service/`. The service in `service/` uses the Functional Source License: see service/LICENSE.md.
|
|
5
|
+
|
|
3
6
|
Copyright (c) 2026 Overplay contributors
|
|
4
7
|
|
|
5
8
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
12
|
<a href="https://www.npmjs.com/package/overplay"><img src="https://img.shields.io/npm/v/overplay?color=575ce5" alt="npm version"></a>
|
|
13
|
-
<a href="./LICENSE"><img src="https://img.shields.io/badge/license-MIT-575ce5" alt="MIT license"></a>
|
|
13
|
+
<a href="./LICENSE"><img src="https://img.shields.io/badge/widget-MIT-575ce5" alt="Widget: MIT license"></a> <a href="./service/LICENSE.md"><img src="https://img.shields.io/badge/service-FSL--1.1--MIT-575ce5" alt="Service: FSL-1.1-MIT license"></a>
|
|
14
14
|
<a href="https://overplay.app/"><img src="https://img.shields.io/badge/cloud-overplay.app-575ce5" alt="Cloud"></a>
|
|
15
15
|
</p>
|
|
16
16
|
|
|
@@ -110,7 +110,7 @@ Read the [self-hosting guide](docs/SELF_HOSTING.md).
|
|
|
110
110
|
|
|
111
111
|
| Key | Action |
|
|
112
112
|
| --- | --- |
|
|
113
|
-
| <kbd>Alt</kbd> + <kbd>
|
|
113
|
+
| <kbd>Alt</kbd> + <kbd>C</kbd> (<kbd>Option</kbd> + <kbd>C</kbd> on Mac) | Open or close the widget |
|
|
114
114
|
| <kbd>Esc</kbd> | Cancel |
|
|
115
115
|
| <kbd>Enter</kbd> | Place a pin on the focused element |
|
|
116
116
|
|
|
@@ -132,7 +132,10 @@ The hosted service (Cloudflare Worker + D1) is in [`service/`](service/README.md
|
|
|
132
132
|
|
|
133
133
|
## License
|
|
134
134
|
|
|
135
|
-
[MIT](./LICENSE)
|
|
135
|
+
- **Widget** (the `overplay` npm package: `src/`, `dist/`): [MIT](./LICENSE). Use it on any site, free.
|
|
136
|
+
- **Service** (Cloud and Community: everything in `service/`): [Functional Source License 1.1, MIT Future License](./service/LICENSE.md) (FSL-1.1-MIT). You may run Community for free, for your own company and your clients, and change it. You may not offer it as a competing paid or hosted product. Two years after each release, that release becomes MIT.
|
|
137
|
+
|
|
138
|
+
Code published before this change keeps the MIT license it was published under.
|
|
136
139
|
|
|
137
140
|
## Connect a coding agent
|
|
138
141
|
|
package/dist/overplay.esm.js
CHANGED
|
@@ -421,6 +421,10 @@ function matchesShortcut(event, shortcut) {
|
|
|
421
421
|
const code = /^[a-z]$/.test(shortcut.key) ? `Key${shortcut.key.toUpperCase()}` : /^\d$/.test(shortcut.key) ? `Digit${shortcut.key}` : "";
|
|
422
422
|
return event.key.toLowerCase() === shortcut.key || !!code && event.code === code;
|
|
423
423
|
}
|
|
424
|
+
function shortcutLabel(value, mac) {
|
|
425
|
+
const names = mac ? { alt: "Option", option: "Option", meta: "Cmd", cmd: "Cmd", ctrl: "Control", control: "Control" } : { alt: "Alt", option: "Alt", meta: "Meta", cmd: "Meta", ctrl: "Ctrl", control: "Ctrl" };
|
|
426
|
+
return value.toLowerCase().split("+").map((part) => part.trim()).filter(Boolean).map((part) => names[part] ?? (part.length === 1 ? part.toUpperCase() : part[0].toUpperCase() + part.slice(1))).join(" + ");
|
|
427
|
+
}
|
|
424
428
|
function newId() {
|
|
425
429
|
if (typeof crypto.randomUUID === "function") return crypto.randomUUID();
|
|
426
430
|
const bytes = crypto.getRandomValues(new Uint8Array(16));
|
|
@@ -470,10 +474,26 @@ function newActivity(comments, seen, selfId) {
|
|
|
470
474
|
function sameComments(a, b) {
|
|
471
475
|
return a.length === b.length && JSON.stringify(a) === JSON.stringify(b);
|
|
472
476
|
}
|
|
473
|
-
const version = "4.
|
|
477
|
+
const version = "4.1.0";
|
|
474
478
|
const VERSION = version;
|
|
475
479
|
const PACKAGE = "overplay";
|
|
476
|
-
|
|
480
|
+
function updatePrompt(current, latest) {
|
|
481
|
+
return `Update the Overplay widget in this project from version ${current} to ${latest}.
|
|
482
|
+
|
|
483
|
+
Steps:
|
|
484
|
+
1. Find the package manager (look for package-lock.json, pnpm-lock.yaml, yarn.lock or bun.lockb) and update the package "${PACKAGE}" to ${latest}:
|
|
485
|
+
npm install ${PACKAGE}@${latest} (or: pnpm add ${PACKAGE}@${latest}, yarn add ${PACKAGE}@${latest}, bun add ${PACKAGE}@${latest})
|
|
486
|
+
2. Read the release notes from ${current} to ${latest}: https://github.com/artemsvit/Overplay-Community/blob/main/CHANGELOG.md
|
|
487
|
+
If a release lists breaking changes, update our code to match. Keep serverUrl, projectId and the other options as they are.
|
|
488
|
+
3. Stop the dev server and clear the bundler cache, so the old widget is not served again:
|
|
489
|
+
- Vite: delete node_modules/.vite
|
|
490
|
+
- Next.js: delete .next
|
|
491
|
+
- Nuxt: delete .nuxt and node_modules/.cache
|
|
492
|
+
- Other tools (webpack, Parcel, Astro, SvelteKit): delete node_modules/.cache, .parcel-cache, .astro or .svelte-kit if they exist.
|
|
493
|
+
4. Start the dev server again and tell me to hard-reload the browser (Cmd+Shift+R or Ctrl+Shift+R).
|
|
494
|
+
5. Run the project's type check and build, and fix any errors.
|
|
495
|
+
6. When you finish, list the files you changed. The update is done when the widget no longer shows "Version ${latest} is available".`;
|
|
496
|
+
}
|
|
477
497
|
function isNewer(latest, current) {
|
|
478
498
|
const parse = (v) => {
|
|
479
499
|
var _a;
|
|
@@ -572,6 +592,8 @@ const paths = {
|
|
|
572
592
|
arrow: "M12 19V5m-6 6 6-6 6 6",
|
|
573
593
|
refresh: "M20.5 12a8.5 8.5 0 1 1-2.6-6.1L20.5 8.5M20.5 3.5v5h-5",
|
|
574
594
|
open: "M7 17 17 7M9 7h8v8",
|
|
595
|
+
copy: "M9 9h11v11H9zM5 15H4V4h11v1",
|
|
596
|
+
sparkle: "M10 3.5c.6 3.9 2.6 5.9 6.5 6.5-3.9.6-5.9 2.6-6.5 6.5-.6-3.9-2.6-5.9-6.5-6.5 3.9-.6 5.9-2.6 6.5-6.5ZM18 14.5c.3 1.9 1.1 2.7 3 3-1.9.3-2.7 1.1-3 3-.3-1.9-1.1-2.7-3-3 1.9-.3 2.7-1.1 3-3Z",
|
|
575
597
|
trash: "M3 6h18M9 6V4h6v2M5 6l1 14h12l1-14M10 10v6m4-6v6",
|
|
576
598
|
pin: "M12 21s7-6 7-13A7 7 0 0 0 5 8c0 7 7 13 7 13ZM12 6v4M10 8h4",
|
|
577
599
|
search: "m21 21-5-5M18 10a8 8 0 1 1-16 0 8 8 0 0 1 16 0Z",
|
|
@@ -671,6 +693,10 @@ const tokens = {
|
|
|
671
693
|
warning: "#745320",
|
|
672
694
|
"warning-soft": "#fff8e8",
|
|
673
695
|
"warning-line": "#ebd59f",
|
|
696
|
+
// AI: a light violet that marks AI features, such as the AI Install prompt. Never for buttons.
|
|
697
|
+
ai: "#6d4fe0",
|
|
698
|
+
"ai-soft": "#f6f3ff",
|
|
699
|
+
"ai-line": "#e3dbff",
|
|
674
700
|
// Shape. Buttons and chips are pills; cards and windows have soft corners; big page panels have 26px.
|
|
675
701
|
"radius-sm": "6px",
|
|
676
702
|
radius: "10px",
|
|
@@ -710,6 +736,9 @@ const darkTokens = {
|
|
|
710
736
|
warning: "#efc56e",
|
|
711
737
|
"warning-soft": "#2f2718",
|
|
712
738
|
"warning-line": "#57472a",
|
|
739
|
+
ai: "#a994ff",
|
|
740
|
+
"ai-soft": "#1d1a2c",
|
|
741
|
+
"ai-line": "#3b3263",
|
|
713
742
|
"shadow-sm": "0 0 0 1px #ffffff14,0 1px 2px #00000066",
|
|
714
743
|
shadow: "0 1px 3px #00000066,0 16px 40px -12px #000000b3"
|
|
715
744
|
};
|
|
@@ -801,7 +830,7 @@ const styles = `
|
|
|
801
830
|
.anchor-status .quiet{display:inline-flex;align-items:center;gap:6px;margin-top:10px;padding:6px 11px 6px 9px;font-size:13px;font-weight:600}.anchor-status .quiet svg{width:15px;height:15px;flex-shrink:0}
|
|
802
831
|
@media(max-width:600px){.panel,.panel.left{inset:0;width:auto;border:0}.panel.placing{visibility:hidden}.panel.behind{visibility:hidden}.launcher{right:16px;bottom:16px}.notice,.notice.beside-panel,.notice.beside-panel.left{left:50%;bottom:24px;width:calc(100vw - 32px);font-size:13px}.compose-hint{display:none}.compose-bar{justify-content:flex-end}.toasts,.toasts.left,.toasts.beside-panel,.toasts.beside-panel.left{left:16px;right:16px;width:auto;max-width:none;bottom:82px}.toasts.top,.toasts.beside-panel.top{top:82px;bottom:auto}.toasts.beside-panel{display:none}.head{padding-top:18px}}
|
|
803
832
|
.popover.moving{visibility:hidden;pointer-events:none}
|
|
804
|
-
.update-bar{display:grid;grid-template-columns:30px 1fr auto;align-items:start;gap:4px 12px;padding:14px 12px 16px 20px;background:var(--cw-accent-soft);border-top-color:var(--cw-accent-line)}.update-icon{display:grid;place-items:center;width:30px;height:30px;border-radius:9px;background:var(--cw-surface);color:var(--cw-accent);box-shadow:var(--cw-shadow-sm)}.update-icon svg{width:16px;height:16px}.update-text{display:flex;flex-direction:column;gap:1px;min-width:0;color:var(--cw-muted);font-size:12.5px;line-height:1.45}.update-text strong{color:var(--cw-ink);font-size:13px;font-weight:650}.update-
|
|
833
|
+
.update-bar{display:grid;grid-template-columns:30px 1fr auto;align-items:start;gap:4px 12px;padding:14px 12px 16px 20px;background:var(--cw-accent-soft);border-top-color:var(--cw-accent-line)}.update-icon{display:grid;place-items:center;width:30px;height:30px;border-radius:9px;background:var(--cw-surface);color:var(--cw-accent);box-shadow:var(--cw-shadow-sm)}.update-icon svg{width:16px;height:16px}.update-text{display:flex;flex-direction:column;gap:1px;min-width:0;color:var(--cw-muted);font-size:12.5px;line-height:1.45}.update-text strong{color:var(--cw-ink);font-size:13px;font-weight:650}.update-actions{grid-column:2/-1;display:flex;flex-wrap:wrap;gap:8px;margin:8px 8px 0 0}.update-actions .quiet{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;font-size:13px;font-weight:600;border-radius:var(--cw-radius-pill)}.update-actions svg{width:14px;height:14px}.update-toggle svg{color:var(--cw-ai)}.update-prompt{grid-column:1/-1;margin:10px 8px 0 0;max-height:220px;overflow:auto;padding:10px 12px;border:1px solid var(--cw-ai-line);border-radius:10px;background:linear-gradient(135deg,var(--cw-ai-soft),var(--cw-surface));color:var(--cw-ink);font:11.5px/1.55 ui-monospace,SFMono-Regular,Menlo,monospace;white-space:pre-wrap;overflow-wrap:anywhere;user-select:text;-webkit-user-select:text}.update-prompt:focus-visible{outline:2px solid var(--cw-accent);outline-offset:2px}
|
|
805
834
|
@media(max-width:600px){.search input{font-size:16px}.select{max-width:100%}}
|
|
806
835
|
@media(prefers-reduced-motion:reduce){*,*::before,*::after{animation:none!important;transition:none!important;scroll-behavior:auto!important}}
|
|
807
836
|
`;
|
|
@@ -1667,6 +1696,7 @@ class Overlay {
|
|
|
1667
1696
|
__publicField(this, "settingsCache", null);
|
|
1668
1697
|
/** A newer npm version, found when the sidebar first opens. See checkForUpdate(). */
|
|
1669
1698
|
__publicField(this, "update", null);
|
|
1699
|
+
__publicField(this, "updatePromptOpen", false);
|
|
1670
1700
|
__publicField(this, "updateChecked", false);
|
|
1671
1701
|
/** The page margin we replaced to make room for the sidebar, restored when it closes. */
|
|
1672
1702
|
__publicField(this, "pushed", null);
|
|
@@ -1884,13 +1914,13 @@ class Overlay {
|
|
|
1884
1914
|
});
|
|
1885
1915
|
});
|
|
1886
1916
|
var _a, _b, _c;
|
|
1887
|
-
this.config = { primaryColor: "#0068f9", position: "bottom-right", keyboardShortcut: "alt+
|
|
1917
|
+
this.config = { primaryColor: "#0068f9", position: "bottom-right", keyboardShortcut: "alt+c", enableKeyboardShortcuts: true, ...config };
|
|
1888
1918
|
if (!/^#[0-9a-f]{3}([0-9a-f]{3})?$/i.test(this.config.primaryColor ?? "")) {
|
|
1889
1919
|
throw new Error("primaryColor must be a 3- or 6-digit hex color.");
|
|
1890
1920
|
}
|
|
1891
1921
|
if (this.config.theme !== void 0 && !THEMES.includes(this.config.theme)) throw new Error('theme must be "light", "dark" or "system".');
|
|
1892
1922
|
this.shortcut = parseShortcut(this.config.keyboardShortcut ?? "");
|
|
1893
|
-
if (this.config.enableKeyboardShortcuts && !this.shortcut) throw new Error('keyboardShortcut must be a key such as "alt+
|
|
1923
|
+
if (this.config.enableKeyboardShortcuts && !this.shortcut) throw new Error('keyboardShortcut must be a key such as "alt+c".');
|
|
1894
1924
|
const project = config.projectId || location.hostname || "default";
|
|
1895
1925
|
this.identityKey = `overplay:${project}:user`;
|
|
1896
1926
|
this.launcherKey = `overplay:${project}:launcher`;
|
|
@@ -2727,7 +2757,9 @@ ${this.currentPage()}`;
|
|
|
2727
2757
|
* Only development hosts check by default: that is where the person reading the notice can run npm.
|
|
2728
2758
|
*/
|
|
2729
2759
|
checkForUpdate() {
|
|
2730
|
-
|
|
2760
|
+
var _a;
|
|
2761
|
+
const scriptTag = typeof ((_a = globalThis.Overplay) == null ? void 0 : _a.init) === "function";
|
|
2762
|
+
if (this.updateChecked || !(this.config.updateNotice ?? (developmentHost(location.hostname) && !scriptTag))) return;
|
|
2731
2763
|
this.updateChecked = true;
|
|
2732
2764
|
void latestVersion().then((latest) => {
|
|
2733
2765
|
if (this.destroyed || !latest || !isNewer(latest, VERSION)) return;
|
|
@@ -2740,19 +2772,24 @@ ${this.currentPage()}`;
|
|
|
2740
2772
|
});
|
|
2741
2773
|
}
|
|
2742
2774
|
async copyUpdate() {
|
|
2775
|
+
const prompt = updatePrompt(VERSION, this.update ?? "latest");
|
|
2743
2776
|
let copied = false;
|
|
2744
2777
|
try {
|
|
2745
|
-
await navigator.clipboard.writeText(
|
|
2778
|
+
await navigator.clipboard.writeText(prompt);
|
|
2746
2779
|
copied = true;
|
|
2747
2780
|
} catch {
|
|
2748
2781
|
}
|
|
2782
|
+
if (!copied) {
|
|
2783
|
+
this.updatePromptOpen = true;
|
|
2784
|
+
this.render();
|
|
2785
|
+
}
|
|
2749
2786
|
this.toast({
|
|
2750
2787
|
kind: "comment",
|
|
2751
2788
|
commentId: "",
|
|
2752
|
-
author: copied ?
|
|
2753
|
-
text: copied ?
|
|
2789
|
+
author: copied ? "Update prompt copied" : "Copy the prompt below",
|
|
2790
|
+
text: copied ? "Paste it into your coding agent. It updates the package, clears the cache and restarts the dev server." : "Select the prompt text and copy it."
|
|
2754
2791
|
}, true);
|
|
2755
|
-
this.announce(copied ? "Update
|
|
2792
|
+
this.announce(copied ? "Update prompt copied." : "Select the prompt text to copy it.");
|
|
2756
2793
|
}
|
|
2757
2794
|
/** "Later" hides the notice until the next version comes out. */
|
|
2758
2795
|
dismissUpdate() {
|
|
@@ -3468,6 +3505,10 @@ ${this.currentPage()}`;
|
|
|
3468
3505
|
return empty;
|
|
3469
3506
|
}
|
|
3470
3507
|
/** A new npm version is the one thing left at the bottom. The account and settings live in the header. */
|
|
3508
|
+
/**
|
|
3509
|
+
* The update notice: an AI prompt that updates the package, clears the bundler cache and restarts the dev
|
|
3510
|
+
* server. A version bump alone often keeps serving the old widget.
|
|
3511
|
+
*/
|
|
3471
3512
|
footer() {
|
|
3472
3513
|
if (!this.update) return null;
|
|
3473
3514
|
const footer = element("div", "footer update-bar");
|
|
@@ -3475,16 +3516,24 @@ ${this.currentPage()}`;
|
|
|
3475
3516
|
const mark = element("span", "update-icon");
|
|
3476
3517
|
mark.append(icon("refresh"));
|
|
3477
3518
|
const text = element("div", "update-text");
|
|
3478
|
-
text.append(element("strong", "", `Version ${this.update} is available`), element("span", "", `You have ${VERSION}.
|
|
3479
|
-
const
|
|
3480
|
-
const
|
|
3481
|
-
|
|
3482
|
-
|
|
3519
|
+
text.append(element("strong", "", `Version ${this.update} is available`), element("span", "", `You have ${VERSION}. Give this prompt to your coding agent to update.`));
|
|
3520
|
+
const actions = element("div", "update-actions");
|
|
3521
|
+
const show = button(this.updatePromptOpen ? "Hide Prompt" : "Show Prompt", "quiet update-toggle", () => {
|
|
3522
|
+
this.updatePromptOpen = !this.updatePromptOpen;
|
|
3523
|
+
this.render();
|
|
3524
|
+
}, "sparkle");
|
|
3525
|
+
show.setAttribute("aria-expanded", String(this.updatePromptOpen));
|
|
3526
|
+
const copy = button("Copy Prompt", "quiet update", () => {
|
|
3483
3527
|
void this.copyUpdate();
|
|
3484
|
-
});
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3528
|
+
}, "copy");
|
|
3529
|
+
actions.append(show, copy);
|
|
3530
|
+
footer.append(mark, text, button("Hide until the next version", "icon-button", () => this.dismissUpdate(), "close"), actions);
|
|
3531
|
+
if (this.updatePromptOpen) {
|
|
3532
|
+
const prompt = element("pre", "update-prompt", updatePrompt(VERSION, this.update));
|
|
3533
|
+
prompt.tabIndex = 0;
|
|
3534
|
+
prompt.setAttribute("aria-label", "Update prompt for your coding agent");
|
|
3535
|
+
footer.append(prompt);
|
|
3536
|
+
}
|
|
3488
3537
|
return footer;
|
|
3489
3538
|
}
|
|
3490
3539
|
messageHeader(name, timestamp, url, via, words2 = []) {
|
|
@@ -3637,7 +3686,6 @@ ${this.currentPage()}`;
|
|
|
3637
3686
|
}
|
|
3638
3687
|
/** Display choices for this project. Each person has their own, saved in this browser. */
|
|
3639
3688
|
settingsView() {
|
|
3640
|
-
var _a;
|
|
3641
3689
|
const view = this.view("Settings", "settings");
|
|
3642
3690
|
view.append(this.detailHeading("Settings", "Your choices for this project. Other people keep their own."));
|
|
3643
3691
|
const current = this.settings, body = element("div", "settings-body");
|
|
@@ -3744,7 +3792,7 @@ ${this.currentPage()}`;
|
|
|
3744
3792
|
row(panel, "Push the page aside", "The open panel does not cover the page.", toggle("pushContent", "Push the page aside"));
|
|
3745
3793
|
const alerts = section("Notifications and keys");
|
|
3746
3794
|
row(alerts, "New activity", "Show a short notice when others comment or reply.", toggle("notifications", "Show new activity"));
|
|
3747
|
-
const keys = (
|
|
3795
|
+
const keys = shortcutLabel(this.config.keyboardShortcut ?? "", /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent));
|
|
3748
3796
|
row(alerts, "Keyboard shortcut", this.shortcut ? `${keys} opens and closes comments.` : "This site has no shortcut.", toggle("shortcut", "Keyboard shortcut", !this.shortcut));
|
|
3749
3797
|
const foot = element("div", "settings-foot");
|
|
3750
3798
|
foot.append(element("span", "", "Saved in this browser"), button("Reset to Defaults", "quiet", () => this.resetSettings()));
|