Refactor: Improve code readability and formatting across multiple files
- Enhanced formatting in triggers.js for better readability, including consistent line breaks and indentation. - Updated utility functions in utils.js for improved clarity and organization, ensuring consistent formatting and spacing. - Refactored VNC button creation in vnc.js to enhance readability with better structured code. - Modified SQL migration script to improve readability by aligning SQL statements and enhancing the structure of the INSERT statement.
This commit is contained in:
@@ -11,7 +11,14 @@ const esbuild = require("esbuild");
|
||||
const path = require("path");
|
||||
|
||||
const entryPoint = path.resolve(__dirname, "novnc-entry.js");
|
||||
const outFile = path.resolve(__dirname, "..", "src", "js", "lib", "novnc.min.js");
|
||||
const outFile = path.resolve(
|
||||
__dirname,
|
||||
"..",
|
||||
"src",
|
||||
"js",
|
||||
"lib",
|
||||
"novnc.min.js",
|
||||
);
|
||||
|
||||
esbuild
|
||||
.build({
|
||||
@@ -26,7 +33,9 @@ esbuild
|
||||
footer: { js: "})();" },
|
||||
})
|
||||
.then(() => {
|
||||
console.log("[bundle-novnc] noVNC bundled successfully → src/js/lib/novnc.min.js");
|
||||
console.log(
|
||||
"[bundle-novnc] noVNC bundled successfully → src/js/lib/novnc.min.js",
|
||||
);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("[bundle-novnc] Failed:", err.message);
|
||||
|
||||
@@ -191,10 +191,19 @@
|
||||
}
|
||||
|
||||
@keyframes trigger-flash {
|
||||
0%, 100% { background: var(--bg-hover); }
|
||||
15% { background: rgba(239, 83, 80, 0.25); }
|
||||
30% { background: var(--bg-hover); }
|
||||
45% { background: rgba(239, 83, 80, 0.15); }
|
||||
0%,
|
||||
100% {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
15% {
|
||||
background: rgba(239, 83, 80, 0.25);
|
||||
}
|
||||
30% {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
45% {
|
||||
background: rgba(239, 83, 80, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
|
||||
+102
-99
@@ -5,150 +5,153 @@
|
||||
========================================================================== */
|
||||
|
||||
:root {
|
||||
/* -----------------------------------------------------------------------
|
||||
/* -----------------------------------------------------------------------
|
||||
Colour palette — dark charcoal theme
|
||||
----------------------------------------------------------------------- */
|
||||
--bg-base: #1c1c1c; /* deepest background */
|
||||
--bg-surface: #232323; /* primary surface (sidebar, bars) */
|
||||
--bg-elevated: #2a2a2a; /* elevated panels, cards */
|
||||
--bg-overlay: #303030; /* overlays, dropdowns */
|
||||
--bg-hover: #363636; /* hover state backgrounds */
|
||||
--bg-active: #3c3c3c; /* active / pressed state */
|
||||
--bg-input: #1e1e1e; /* input field backgrounds */
|
||||
--bg-base: #1c1c1c; /* deepest background */
|
||||
--bg-surface: #232323; /* primary surface (sidebar, bars) */
|
||||
--bg-elevated: #2a2a2a; /* elevated panels, cards */
|
||||
--bg-overlay: #303030; /* overlays, dropdowns */
|
||||
--bg-hover: #363636; /* hover state backgrounds */
|
||||
--bg-active: #3c3c3c; /* active / pressed state */
|
||||
--bg-input: #1e1e1e; /* input field backgrounds */
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
/* -----------------------------------------------------------------------
|
||||
Border colours
|
||||
----------------------------------------------------------------------- */
|
||||
--border-subtle: rgba(255, 255, 255, 0.07);
|
||||
--border-default: rgba(255, 255, 255, 0.12);
|
||||
--border-strong: rgba(255, 255, 255, 0.18);
|
||||
--border-focus: var(--accent);
|
||||
--border-subtle: rgba(255, 255, 255, 0.07);
|
||||
--border-default: rgba(255, 255, 255, 0.12);
|
||||
--border-strong: rgba(255, 255, 255, 0.18);
|
||||
--border-focus: var(--accent);
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
/* -----------------------------------------------------------------------
|
||||
Text colours
|
||||
----------------------------------------------------------------------- */
|
||||
--text-primary: #e4e4e4;
|
||||
--text-secondary: #a0a0a0;
|
||||
--text-tertiary: #707070;
|
||||
--text-disabled: #4a4a4a;
|
||||
--text-inverse: #1c1c1c;
|
||||
--sidebar-group-title: #b89d5f;
|
||||
--text-primary: #e4e4e4;
|
||||
--text-secondary: #a0a0a0;
|
||||
--text-tertiary: #707070;
|
||||
--text-disabled: #4a4a4a;
|
||||
--text-inverse: #1c1c1c;
|
||||
--sidebar-group-title: #b89d5f;
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
/* -----------------------------------------------------------------------
|
||||
Accent colours — teal / cyan
|
||||
----------------------------------------------------------------------- */
|
||||
--accent: #4ecdc4;
|
||||
--accent-hover: #45b7af;
|
||||
--accent-muted: rgba(78, 205, 196, 0.12);
|
||||
--accent-strong: rgba(78, 205, 196, 0.22);
|
||||
--accent: #4ecdc4;
|
||||
--accent-hover: #45b7af;
|
||||
--accent-muted: rgba(78, 205, 196, 0.12);
|
||||
--accent-strong: rgba(78, 205, 196, 0.22);
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
/* -----------------------------------------------------------------------
|
||||
Status colours
|
||||
----------------------------------------------------------------------- */
|
||||
--success: #66bb6a;
|
||||
--warning: #ffa726;
|
||||
--error: #ef5350;
|
||||
--info: #42a5f5;
|
||||
--success: #66bb6a;
|
||||
--warning: #ffa726;
|
||||
--error: #ef5350;
|
||||
--info: #42a5f5;
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
/* -----------------------------------------------------------------------
|
||||
Protocol badge colours
|
||||
----------------------------------------------------------------------- */
|
||||
--protocol-ssh: #4ecdc4;
|
||||
--protocol-telnet: #ffa726;
|
||||
--protocol-raw: #ab47bc;
|
||||
--protocol-github: #7c8aff;
|
||||
--protocol-vnc: #e85d4a;
|
||||
--protocol-ssh: #4ecdc4;
|
||||
--protocol-telnet: #ffa726;
|
||||
--protocol-raw: #ab47bc;
|
||||
--protocol-github: #7c8aff;
|
||||
--protocol-vnc: #e85d4a;
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
/* -----------------------------------------------------------------------
|
||||
Spacing scale (4 px base unit)
|
||||
----------------------------------------------------------------------- */
|
||||
--space-1: 4px;
|
||||
--space-2: 8px;
|
||||
--space-3: 12px;
|
||||
--space-4: 16px;
|
||||
--space-5: 20px;
|
||||
--space-6: 24px;
|
||||
--space-8: 32px;
|
||||
--space-10: 40px;
|
||||
--space-12: 48px;
|
||||
--space-16: 64px;
|
||||
--space-1: 4px;
|
||||
--space-2: 8px;
|
||||
--space-3: 12px;
|
||||
--space-4: 16px;
|
||||
--space-5: 20px;
|
||||
--space-6: 24px;
|
||||
--space-8: 32px;
|
||||
--space-10: 40px;
|
||||
--space-12: 48px;
|
||||
--space-16: 64px;
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
/* -----------------------------------------------------------------------
|
||||
Typography
|
||||
----------------------------------------------------------------------- */
|
||||
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI',
|
||||
system-ui, sans-serif;
|
||||
--font-mono: 'JetBrains Mono', 'Fira Code', 'SF Mono', Monaco,
|
||||
'Cascadia Code', monospace;
|
||||
--font-sans:
|
||||
"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui,
|
||||
sans-serif;
|
||||
--font-mono:
|
||||
"JetBrains Mono", "Fira Code", "SF Mono", Monaco, "Cascadia Code", monospace;
|
||||
|
||||
--text-xs: 11px;
|
||||
--text-sm: 12px;
|
||||
--text-base: 13px;
|
||||
--text-md: 14px;
|
||||
--text-lg: 16px;
|
||||
--text-xl: 18px;
|
||||
--text-2xl: 22px;
|
||||
--text-xs: 11px;
|
||||
--text-sm: 12px;
|
||||
--text-base: 13px;
|
||||
--text-md: 14px;
|
||||
--text-lg: 16px;
|
||||
--text-xl: 18px;
|
||||
--text-2xl: 22px;
|
||||
|
||||
--leading-tight: 1.25;
|
||||
--leading-normal: 1.5;
|
||||
--leading-relaxed: 1.625;
|
||||
--leading-tight: 1.25;
|
||||
--leading-normal: 1.5;
|
||||
--leading-relaxed: 1.625;
|
||||
|
||||
--weight-normal: 400;
|
||||
--weight-medium: 500;
|
||||
--weight-semibold: 600;
|
||||
--weight-normal: 400;
|
||||
--weight-medium: 500;
|
||||
--weight-semibold: 600;
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
/* -----------------------------------------------------------------------
|
||||
Border radius
|
||||
----------------------------------------------------------------------- */
|
||||
--radius-sm: 3px;
|
||||
--radius-md: 5px;
|
||||
--radius-lg: 8px;
|
||||
--radius-xl: 12px;
|
||||
--radius-full: 9999px;
|
||||
--radius-sm: 3px;
|
||||
--radius-md: 5px;
|
||||
--radius-lg: 8px;
|
||||
--radius-xl: 12px;
|
||||
--radius-full: 9999px;
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
/* -----------------------------------------------------------------------
|
||||
Shadows
|
||||
----------------------------------------------------------------------- */
|
||||
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.30);
|
||||
--shadow-md: 0 2px 8px rgba(0, 0, 0, 0.40);
|
||||
--shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.50);
|
||||
--shadow-xl: 0 8px 32px rgba(0, 0, 0, 0.60);
|
||||
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
|
||||
--shadow-md: 0 2px 8px rgba(0, 0, 0, 0.4);
|
||||
--shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.5);
|
||||
--shadow-xl: 0 8px 32px rgba(0, 0, 0, 0.6);
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
/* -----------------------------------------------------------------------
|
||||
Transitions
|
||||
----------------------------------------------------------------------- */
|
||||
--transition-fast: 100ms ease;
|
||||
--transition-normal: 200ms ease;
|
||||
--transition-slow: 300ms ease;
|
||||
--transition-fast: 100ms ease;
|
||||
--transition-normal: 200ms ease;
|
||||
--transition-slow: 300ms ease;
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
/* -----------------------------------------------------------------------
|
||||
Z-index layers
|
||||
----------------------------------------------------------------------- */
|
||||
--z-base: 0;
|
||||
--z-sticky: 100;
|
||||
--z-dropdown: 200;
|
||||
--z-modal-backdrop: 300;
|
||||
--z-modal: 400;
|
||||
--z-notification: 500;
|
||||
--z-tooltip: 600;
|
||||
--z-base: 0;
|
||||
--z-sticky: 100;
|
||||
--z-dropdown: 200;
|
||||
--z-modal-backdrop: 300;
|
||||
--z-modal: 400;
|
||||
--z-notification: 500;
|
||||
--z-tooltip: 600;
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
/* -----------------------------------------------------------------------
|
||||
Layout
|
||||
----------------------------------------------------------------------- */
|
||||
--sidebar-width: 260px;
|
||||
--sidebar-width-collapsed: 48px;
|
||||
--tab-bar-height: 36px;
|
||||
--toolbar-height: 40px;
|
||||
--status-bar-height: 24px;
|
||||
--sidebar-width: 260px;
|
||||
--sidebar-width-collapsed: 48px;
|
||||
--tab-bar-height: 36px;
|
||||
--toolbar-height: 40px;
|
||||
--status-bar-height: 24px;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Global input overrides — disable macOS/WebKit auto-capitalisation,
|
||||
autocorrect, and spellcheck on all text fields.
|
||||
----------------------------------------------------------------------- */
|
||||
input, textarea, select {
|
||||
autocapitalize: none;
|
||||
autocorrect: off;
|
||||
spellcheck: false;
|
||||
text-transform: none;
|
||||
input,
|
||||
textarea,
|
||||
select {
|
||||
autocapitalize: none;
|
||||
autocorrect: off;
|
||||
spellcheck: false;
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,9 @@
|
||||
border: 1px solid var(--border-subtle);
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
transition: background 0.15s, color 0.15s;
|
||||
transition:
|
||||
background 0.15s,
|
||||
color 0.15s;
|
||||
}
|
||||
|
||||
.btn-vnc-action:hover {
|
||||
|
||||
@@ -168,7 +168,9 @@
|
||||
try {
|
||||
const raw = atob(payload.data);
|
||||
this.triggers.check(payload.sessionID, raw);
|
||||
} catch (_) { /* noop */ }
|
||||
} catch (_) {
|
||||
/* noop */
|
||||
}
|
||||
});
|
||||
|
||||
// Session state transitions (connecting → connected → reconnecting → disconnected)
|
||||
@@ -529,7 +531,9 @@
|
||||
setupUI() {
|
||||
var btnEl = document.getElementById("btn-new-connection");
|
||||
this._bindClick("btn-new-connection", () => this.profileEditor.openNew());
|
||||
this._bindClick("btn-command-palette", () => this.commandPalette.toggle());
|
||||
this._bindClick("btn-command-palette", () =>
|
||||
this.commandPalette.toggle(),
|
||||
);
|
||||
this._bindClick("btn-settings", () => this.settings.open());
|
||||
this._bindClick("btn-import", () => this.importProfiles());
|
||||
this._bindClick("btn-export", () => this.exportProfiles());
|
||||
|
||||
@@ -206,14 +206,16 @@
|
||||
);
|
||||
break;
|
||||
case "vnc":
|
||||
this._renderVNCSections(profile).forEach((s) =>
|
||||
body.appendChild(s),
|
||||
);
|
||||
this._renderVNCSections(profile).forEach((s) => body.appendChild(s));
|
||||
break;
|
||||
}
|
||||
|
||||
// Terminal section (SSH and Telnet only)
|
||||
if (profile.protocol !== "raw" && profile.protocol !== "github" && profile.protocol !== "vnc") {
|
||||
if (
|
||||
profile.protocol !== "raw" &&
|
||||
profile.protocol !== "github" &&
|
||||
profile.protocol !== "vnc"
|
||||
) {
|
||||
body.appendChild(this._renderTerminalSection(profile));
|
||||
}
|
||||
|
||||
@@ -2288,7 +2290,9 @@
|
||||
if (profile.protocol === "vnc") {
|
||||
const vncHost = (profile.vnc && profile.vnc.vncHost) || profile.host;
|
||||
if (!vncHost || !vncHost.trim()) {
|
||||
errors.push("VNC Host is required (in General or VNC Server section).");
|
||||
errors.push(
|
||||
"VNC Host is required (in General or VNC Server section).",
|
||||
);
|
||||
}
|
||||
} else if (!profile.host || !profile.host.trim()) {
|
||||
errors.push("Host is required.");
|
||||
|
||||
+58
-29
@@ -63,11 +63,15 @@
|
||||
});
|
||||
|
||||
// Events
|
||||
modal.querySelector(".modal-close").addEventListener("click", () => this.close());
|
||||
modal
|
||||
.querySelector(".modal-close")
|
||||
.addEventListener("click", () => this.close());
|
||||
backdrop.addEventListener("click", (e) => {
|
||||
if (e.target === backdrop) this.close();
|
||||
});
|
||||
modal.querySelector(".sshkey-generate-btn").addEventListener("click", () => this._showGenerateForm());
|
||||
modal
|
||||
.querySelector(".sshkey-generate-btn")
|
||||
.addEventListener("click", () => this._showGenerateForm());
|
||||
}
|
||||
|
||||
async _loadKeys() {
|
||||
@@ -79,7 +83,8 @@
|
||||
try {
|
||||
const keys = await window.go.api.Service.ListSSHKeyPairs();
|
||||
if (keys.length === 0) {
|
||||
list.innerHTML = '<div class="sshkey-empty">No SSH keys found in ~/.ssh/</div>';
|
||||
list.innerHTML =
|
||||
'<div class="sshkey-empty">No SSH keys found in ~/.ssh/</div>';
|
||||
return;
|
||||
}
|
||||
list.innerHTML = "";
|
||||
@@ -95,7 +100,9 @@
|
||||
const row = createElement("div", "sshkey-row");
|
||||
|
||||
const info = createElement("div", "sshkey-info");
|
||||
const name = createElement("div", "sshkey-name", { textContent: key.name });
|
||||
const name = createElement("div", "sshkey-name", {
|
||||
textContent: key.name,
|
||||
});
|
||||
const meta = createElement("div", "sshkey-meta", {
|
||||
textContent: key.type.toUpperCase() + (key.bits ? ` ${key.bits}` : ""),
|
||||
});
|
||||
@@ -104,11 +111,15 @@
|
||||
|
||||
const actions = createElement("div", "sshkey-actions");
|
||||
|
||||
const copyBtn = createElement("button", "btn btn-xs", { textContent: "Copy Public Key" });
|
||||
const copyBtn = createElement("button", "btn btn-xs", {
|
||||
textContent: "Copy Public Key",
|
||||
});
|
||||
copyBtn.addEventListener("mousedown", (e) => e.preventDefault());
|
||||
copyBtn.addEventListener("click", () => this._copyPublicKey(key));
|
||||
|
||||
const deleteBtn = createElement("button", "btn btn-xs btn-danger", { textContent: "Delete" });
|
||||
const deleteBtn = createElement("button", "btn btn-xs btn-danger", {
|
||||
textContent: "Delete",
|
||||
});
|
||||
deleteBtn.addEventListener("mousedown", (e) => e.preventDefault());
|
||||
deleteBtn.addEventListener("click", () => this._confirmDelete(key));
|
||||
|
||||
@@ -208,28 +219,40 @@
|
||||
list.style.display = "";
|
||||
});
|
||||
|
||||
form.querySelector(".sshkey-create-btn").addEventListener("click", async () => {
|
||||
const nameInput = form.querySelector(".sshkey-name-input");
|
||||
const name = nameInput.value.trim();
|
||||
const keyType = typeSelect.value;
|
||||
const bits = parseInt(form.querySelector(".sshkey-bits-select").value, 10);
|
||||
const passphrase = form.querySelector(".sshkey-pass-input").value;
|
||||
form
|
||||
.querySelector(".sshkey-create-btn")
|
||||
.addEventListener("click", async () => {
|
||||
const nameInput = form.querySelector(".sshkey-name-input");
|
||||
const name = nameInput.value.trim();
|
||||
const keyType = typeSelect.value;
|
||||
const bits = parseInt(
|
||||
form.querySelector(".sshkey-bits-select").value,
|
||||
10,
|
||||
);
|
||||
const passphrase = form.querySelector(".sshkey-pass-input").value;
|
||||
|
||||
if (!name) {
|
||||
this.app.notifications.error("Key name is required");
|
||||
return;
|
||||
}
|
||||
if (!name) {
|
||||
this.app.notifications.error("Key name is required");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await window.go.api.Service.GenerateSSHKey(name, keyType, bits, passphrase);
|
||||
this.app.notifications.success(`Key "${name}" generated successfully`);
|
||||
form.remove();
|
||||
list.style.display = "";
|
||||
this._loadKeys();
|
||||
} catch (err) {
|
||||
this.app.notifications.error("Generation failed: " + err);
|
||||
}
|
||||
});
|
||||
try {
|
||||
await window.go.api.Service.GenerateSSHKey(
|
||||
name,
|
||||
keyType,
|
||||
bits,
|
||||
passphrase,
|
||||
);
|
||||
this.app.notifications.success(
|
||||
`Key "${name}" generated successfully`,
|
||||
);
|
||||
form.remove();
|
||||
list.style.display = "";
|
||||
this._loadKeys();
|
||||
} catch (err) {
|
||||
this.app.notifications.error("Generation failed: " + err);
|
||||
}
|
||||
});
|
||||
|
||||
form.querySelector(".sshkey-name-input").focus();
|
||||
}
|
||||
@@ -266,9 +289,15 @@
|
||||
setTimeout(() => backdrop.remove(), 200);
|
||||
resolve(val);
|
||||
};
|
||||
modal.querySelector(".modal-close").addEventListener("click", () => close(false));
|
||||
modal.querySelector(".confirm-cancel").addEventListener("click", () => close(false));
|
||||
modal.querySelector(".confirm-ok").addEventListener("click", () => close(true));
|
||||
modal
|
||||
.querySelector(".modal-close")
|
||||
.addEventListener("click", () => close(false));
|
||||
modal
|
||||
.querySelector(".confirm-cancel")
|
||||
.addEventListener("click", () => close(false));
|
||||
modal
|
||||
.querySelector(".confirm-ok")
|
||||
.addEventListener("click", () => close(true));
|
||||
backdrop.addEventListener("click", (e) => {
|
||||
if (e.target === backdrop) close(false);
|
||||
});
|
||||
|
||||
+314
-315
@@ -1,348 +1,347 @@
|
||||
(function() {
|
||||
/**
|
||||
* Nexus Terminal — terminal colour themes.
|
||||
* Provides a collection of preset xterm.js colour schemes and a manager
|
||||
* for applying them to terminal instances.
|
||||
*/
|
||||
(function () {
|
||||
/**
|
||||
* Nexus Terminal — terminal colour themes.
|
||||
* Provides a collection of preset xterm.js colour schemes and a manager
|
||||
* for applying them to terminal instances.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Preset terminal colour themes.
|
||||
* Each entry contains the full xterm.js theme object.
|
||||
*/
|
||||
const THEMES = {
|
||||
/**
|
||||
* Preset terminal colour themes.
|
||||
* Each entry contains the full xterm.js theme object.
|
||||
*/
|
||||
const THEMES = {
|
||||
charcoal: {
|
||||
label: 'Charcoal (Default)',
|
||||
theme: {
|
||||
background: '#1c1c1c',
|
||||
foreground: '#e4e4e4',
|
||||
cursor: '#4ecdc4',
|
||||
cursorAccent: '#1c1c1c',
|
||||
selectionBackground: 'rgba(78, 205, 196, 0.3)',
|
||||
selectionForeground: '#ffffff',
|
||||
black: '#1c1c1c',
|
||||
red: '#ef5350',
|
||||
green: '#66bb6a',
|
||||
yellow: '#ffa726',
|
||||
blue: '#42a5f5',
|
||||
magenta: '#ab47bc',
|
||||
cyan: '#4ecdc4',
|
||||
white: '#e4e4e4',
|
||||
brightBlack: '#555555',
|
||||
brightRed: '#ff7043',
|
||||
brightGreen: '#81c784',
|
||||
brightYellow: '#ffcc02',
|
||||
brightBlue: '#64b5f6',
|
||||
brightMagenta: '#ce93d8',
|
||||
brightCyan: '#80deea',
|
||||
brightWhite: '#ffffff',
|
||||
},
|
||||
label: "Charcoal (Default)",
|
||||
theme: {
|
||||
background: "#1c1c1c",
|
||||
foreground: "#e4e4e4",
|
||||
cursor: "#4ecdc4",
|
||||
cursorAccent: "#1c1c1c",
|
||||
selectionBackground: "rgba(78, 205, 196, 0.3)",
|
||||
selectionForeground: "#ffffff",
|
||||
black: "#1c1c1c",
|
||||
red: "#ef5350",
|
||||
green: "#66bb6a",
|
||||
yellow: "#ffa726",
|
||||
blue: "#42a5f5",
|
||||
magenta: "#ab47bc",
|
||||
cyan: "#4ecdc4",
|
||||
white: "#e4e4e4",
|
||||
brightBlack: "#555555",
|
||||
brightRed: "#ff7043",
|
||||
brightGreen: "#81c784",
|
||||
brightYellow: "#ffcc02",
|
||||
brightBlue: "#64b5f6",
|
||||
brightMagenta: "#ce93d8",
|
||||
brightCyan: "#80deea",
|
||||
brightWhite: "#ffffff",
|
||||
},
|
||||
},
|
||||
|
||||
'solarized-dark': {
|
||||
label: 'Solarized Dark',
|
||||
theme: {
|
||||
background: '#002b36',
|
||||
foreground: '#839496',
|
||||
cursor: '#93a1a1',
|
||||
cursorAccent: '#002b36',
|
||||
selectionBackground: 'rgba(147, 161, 161, 0.3)',
|
||||
selectionForeground: '#fdf6e3',
|
||||
black: '#073642',
|
||||
red: '#dc322f',
|
||||
green: '#859900',
|
||||
yellow: '#b58900',
|
||||
blue: '#268bd2',
|
||||
magenta: '#d33682',
|
||||
cyan: '#2aa198',
|
||||
white: '#eee8d5',
|
||||
brightBlack: '#586e75',
|
||||
brightRed: '#cb4b16',
|
||||
brightGreen: '#586e75',
|
||||
brightYellow: '#657b83',
|
||||
brightBlue: '#839496',
|
||||
brightMagenta: '#6c71c4',
|
||||
brightCyan: '#93a1a1',
|
||||
brightWhite: '#fdf6e3',
|
||||
},
|
||||
"solarized-dark": {
|
||||
label: "Solarized Dark",
|
||||
theme: {
|
||||
background: "#002b36",
|
||||
foreground: "#839496",
|
||||
cursor: "#93a1a1",
|
||||
cursorAccent: "#002b36",
|
||||
selectionBackground: "rgba(147, 161, 161, 0.3)",
|
||||
selectionForeground: "#fdf6e3",
|
||||
black: "#073642",
|
||||
red: "#dc322f",
|
||||
green: "#859900",
|
||||
yellow: "#b58900",
|
||||
blue: "#268bd2",
|
||||
magenta: "#d33682",
|
||||
cyan: "#2aa198",
|
||||
white: "#eee8d5",
|
||||
brightBlack: "#586e75",
|
||||
brightRed: "#cb4b16",
|
||||
brightGreen: "#586e75",
|
||||
brightYellow: "#657b83",
|
||||
brightBlue: "#839496",
|
||||
brightMagenta: "#6c71c4",
|
||||
brightCyan: "#93a1a1",
|
||||
brightWhite: "#fdf6e3",
|
||||
},
|
||||
},
|
||||
|
||||
monokai: {
|
||||
label: 'Monokai',
|
||||
theme: {
|
||||
background: '#272822',
|
||||
foreground: '#f8f8f2',
|
||||
cursor: '#f8f8f0',
|
||||
cursorAccent: '#272822',
|
||||
selectionBackground: 'rgba(73, 72, 62, 0.6)',
|
||||
selectionForeground: '#f8f8f2',
|
||||
black: '#272822',
|
||||
red: '#f92672',
|
||||
green: '#a6e22e',
|
||||
yellow: '#f4bf75',
|
||||
blue: '#66d9ef',
|
||||
magenta: '#ae81ff',
|
||||
cyan: '#a1efe4',
|
||||
white: '#f8f8f2',
|
||||
brightBlack: '#75715e',
|
||||
brightRed: '#f92672',
|
||||
brightGreen: '#a6e22e',
|
||||
brightYellow: '#f4bf75',
|
||||
brightBlue: '#66d9ef',
|
||||
brightMagenta: '#ae81ff',
|
||||
brightCyan: '#a1efe4',
|
||||
brightWhite: '#f9f8f5',
|
||||
},
|
||||
label: "Monokai",
|
||||
theme: {
|
||||
background: "#272822",
|
||||
foreground: "#f8f8f2",
|
||||
cursor: "#f8f8f0",
|
||||
cursorAccent: "#272822",
|
||||
selectionBackground: "rgba(73, 72, 62, 0.6)",
|
||||
selectionForeground: "#f8f8f2",
|
||||
black: "#272822",
|
||||
red: "#f92672",
|
||||
green: "#a6e22e",
|
||||
yellow: "#f4bf75",
|
||||
blue: "#66d9ef",
|
||||
magenta: "#ae81ff",
|
||||
cyan: "#a1efe4",
|
||||
white: "#f8f8f2",
|
||||
brightBlack: "#75715e",
|
||||
brightRed: "#f92672",
|
||||
brightGreen: "#a6e22e",
|
||||
brightYellow: "#f4bf75",
|
||||
brightBlue: "#66d9ef",
|
||||
brightMagenta: "#ae81ff",
|
||||
brightCyan: "#a1efe4",
|
||||
brightWhite: "#f9f8f5",
|
||||
},
|
||||
},
|
||||
|
||||
nord: {
|
||||
label: 'Nord',
|
||||
theme: {
|
||||
background: '#2e3440',
|
||||
foreground: '#d8dee9',
|
||||
cursor: '#d8dee9',
|
||||
cursorAccent: '#2e3440',
|
||||
selectionBackground: 'rgba(136, 192, 208, 0.3)',
|
||||
selectionForeground: '#eceff4',
|
||||
black: '#3b4252',
|
||||
red: '#bf616a',
|
||||
green: '#a3be8c',
|
||||
yellow: '#ebcb8b',
|
||||
blue: '#81a1c1',
|
||||
magenta: '#b48ead',
|
||||
cyan: '#88c0d0',
|
||||
white: '#e5e9f0',
|
||||
brightBlack: '#4c566a',
|
||||
brightRed: '#bf616a',
|
||||
brightGreen: '#a3be8c',
|
||||
brightYellow: '#ebcb8b',
|
||||
brightBlue: '#81a1c1',
|
||||
brightMagenta: '#b48ead',
|
||||
brightCyan: '#8fbcbb',
|
||||
brightWhite: '#eceff4',
|
||||
},
|
||||
label: "Nord",
|
||||
theme: {
|
||||
background: "#2e3440",
|
||||
foreground: "#d8dee9",
|
||||
cursor: "#d8dee9",
|
||||
cursorAccent: "#2e3440",
|
||||
selectionBackground: "rgba(136, 192, 208, 0.3)",
|
||||
selectionForeground: "#eceff4",
|
||||
black: "#3b4252",
|
||||
red: "#bf616a",
|
||||
green: "#a3be8c",
|
||||
yellow: "#ebcb8b",
|
||||
blue: "#81a1c1",
|
||||
magenta: "#b48ead",
|
||||
cyan: "#88c0d0",
|
||||
white: "#e5e9f0",
|
||||
brightBlack: "#4c566a",
|
||||
brightRed: "#bf616a",
|
||||
brightGreen: "#a3be8c",
|
||||
brightYellow: "#ebcb8b",
|
||||
brightBlue: "#81a1c1",
|
||||
brightMagenta: "#b48ead",
|
||||
brightCyan: "#8fbcbb",
|
||||
brightWhite: "#eceff4",
|
||||
},
|
||||
},
|
||||
|
||||
dracula: {
|
||||
label: 'Dracula',
|
||||
theme: {
|
||||
background: '#282a36',
|
||||
foreground: '#f8f8f2',
|
||||
cursor: '#f8f8f2',
|
||||
cursorAccent: '#282a36',
|
||||
selectionBackground: 'rgba(68, 71, 90, 0.6)',
|
||||
selectionForeground: '#f8f8f2',
|
||||
black: '#21222c',
|
||||
red: '#ff5555',
|
||||
green: '#50fa7b',
|
||||
yellow: '#f1fa8c',
|
||||
blue: '#bd93f9',
|
||||
magenta: '#ff79c6',
|
||||
cyan: '#8be9fd',
|
||||
white: '#f8f8f2',
|
||||
brightBlack: '#6272a4',
|
||||
brightRed: '#ff6e6e',
|
||||
brightGreen: '#69ff94',
|
||||
brightYellow: '#ffffa5',
|
||||
brightBlue: '#d6acff',
|
||||
brightMagenta: '#ff92df',
|
||||
brightCyan: '#a4ffff',
|
||||
brightWhite: '#ffffff',
|
||||
},
|
||||
label: "Dracula",
|
||||
theme: {
|
||||
background: "#282a36",
|
||||
foreground: "#f8f8f2",
|
||||
cursor: "#f8f8f2",
|
||||
cursorAccent: "#282a36",
|
||||
selectionBackground: "rgba(68, 71, 90, 0.6)",
|
||||
selectionForeground: "#f8f8f2",
|
||||
black: "#21222c",
|
||||
red: "#ff5555",
|
||||
green: "#50fa7b",
|
||||
yellow: "#f1fa8c",
|
||||
blue: "#bd93f9",
|
||||
magenta: "#ff79c6",
|
||||
cyan: "#8be9fd",
|
||||
white: "#f8f8f2",
|
||||
brightBlack: "#6272a4",
|
||||
brightRed: "#ff6e6e",
|
||||
brightGreen: "#69ff94",
|
||||
brightYellow: "#ffffa5",
|
||||
brightBlue: "#d6acff",
|
||||
brightMagenta: "#ff92df",
|
||||
brightCyan: "#a4ffff",
|
||||
brightWhite: "#ffffff",
|
||||
},
|
||||
},
|
||||
|
||||
'one-dark': {
|
||||
label: 'One Dark',
|
||||
theme: {
|
||||
background: '#282c34',
|
||||
foreground: '#abb2bf',
|
||||
cursor: '#528bff',
|
||||
cursorAccent: '#282c34',
|
||||
selectionBackground: 'rgba(82, 139, 255, 0.3)',
|
||||
selectionForeground: '#abb2bf',
|
||||
black: '#282c34',
|
||||
red: '#e06c75',
|
||||
green: '#98c379',
|
||||
yellow: '#e5c07b',
|
||||
blue: '#61afef',
|
||||
magenta: '#c678dd',
|
||||
cyan: '#56b6c2',
|
||||
white: '#abb2bf',
|
||||
brightBlack: '#5c6370',
|
||||
brightRed: '#e06c75',
|
||||
brightGreen: '#98c379',
|
||||
brightYellow: '#e5c07b',
|
||||
brightBlue: '#61afef',
|
||||
brightMagenta: '#c678dd',
|
||||
brightCyan: '#56b6c2',
|
||||
brightWhite: '#ffffff',
|
||||
},
|
||||
"one-dark": {
|
||||
label: "One Dark",
|
||||
theme: {
|
||||
background: "#282c34",
|
||||
foreground: "#abb2bf",
|
||||
cursor: "#528bff",
|
||||
cursorAccent: "#282c34",
|
||||
selectionBackground: "rgba(82, 139, 255, 0.3)",
|
||||
selectionForeground: "#abb2bf",
|
||||
black: "#282c34",
|
||||
red: "#e06c75",
|
||||
green: "#98c379",
|
||||
yellow: "#e5c07b",
|
||||
blue: "#61afef",
|
||||
magenta: "#c678dd",
|
||||
cyan: "#56b6c2",
|
||||
white: "#abb2bf",
|
||||
brightBlack: "#5c6370",
|
||||
brightRed: "#e06c75",
|
||||
brightGreen: "#98c379",
|
||||
brightYellow: "#e5c07b",
|
||||
brightBlue: "#61afef",
|
||||
brightMagenta: "#c678dd",
|
||||
brightCyan: "#56b6c2",
|
||||
brightWhite: "#ffffff",
|
||||
},
|
||||
},
|
||||
|
||||
'gruvbox-dark': {
|
||||
label: 'Gruvbox Dark',
|
||||
theme: {
|
||||
background: '#282828',
|
||||
foreground: '#ebdbb2',
|
||||
cursor: '#ebdbb2',
|
||||
cursorAccent: '#282828',
|
||||
selectionBackground: 'rgba(214, 93, 14, 0.3)',
|
||||
selectionForeground: '#ebdbb2',
|
||||
black: '#282828',
|
||||
red: '#cc241d',
|
||||
green: '#98971a',
|
||||
yellow: '#d79921',
|
||||
blue: '#458588',
|
||||
magenta: '#b16286',
|
||||
cyan: '#689d6a',
|
||||
white: '#a89984',
|
||||
brightBlack: '#928374',
|
||||
brightRed: '#fb4934',
|
||||
brightGreen: '#b8bb26',
|
||||
brightYellow: '#fabd2f',
|
||||
brightBlue: '#83a598',
|
||||
brightMagenta: '#d3869b',
|
||||
brightCyan: '#8ec07c',
|
||||
brightWhite: '#ebdbb2',
|
||||
},
|
||||
"gruvbox-dark": {
|
||||
label: "Gruvbox Dark",
|
||||
theme: {
|
||||
background: "#282828",
|
||||
foreground: "#ebdbb2",
|
||||
cursor: "#ebdbb2",
|
||||
cursorAccent: "#282828",
|
||||
selectionBackground: "rgba(214, 93, 14, 0.3)",
|
||||
selectionForeground: "#ebdbb2",
|
||||
black: "#282828",
|
||||
red: "#cc241d",
|
||||
green: "#98971a",
|
||||
yellow: "#d79921",
|
||||
blue: "#458588",
|
||||
magenta: "#b16286",
|
||||
cyan: "#689d6a",
|
||||
white: "#a89984",
|
||||
brightBlack: "#928374",
|
||||
brightRed: "#fb4934",
|
||||
brightGreen: "#b8bb26",
|
||||
brightYellow: "#fabd2f",
|
||||
brightBlue: "#83a598",
|
||||
brightMagenta: "#d3869b",
|
||||
brightCyan: "#8ec07c",
|
||||
brightWhite: "#ebdbb2",
|
||||
},
|
||||
},
|
||||
|
||||
'tokyo-night': {
|
||||
label: 'Tokyo Night',
|
||||
theme: {
|
||||
background: '#1a1b26',
|
||||
foreground: '#a9b1d6',
|
||||
cursor: '#c0caf5',
|
||||
cursorAccent: '#1a1b26',
|
||||
selectionBackground: 'rgba(41, 46, 66, 0.9)',
|
||||
selectionForeground: '#c0caf5',
|
||||
black: '#15161e',
|
||||
red: '#f7768e',
|
||||
green: '#9ece6a',
|
||||
yellow: '#e0af68',
|
||||
blue: '#7aa2f7',
|
||||
magenta: '#bb9af7',
|
||||
cyan: '#7dcfff',
|
||||
white: '#a9b1d6',
|
||||
brightBlack: '#414868',
|
||||
brightRed: '#f7768e',
|
||||
brightGreen: '#9ece6a',
|
||||
brightYellow: '#e0af68',
|
||||
brightBlue: '#7aa2f7',
|
||||
brightMagenta: '#bb9af7',
|
||||
brightCyan: '#7dcfff',
|
||||
brightWhite: '#c0caf5',
|
||||
},
|
||||
"tokyo-night": {
|
||||
label: "Tokyo Night",
|
||||
theme: {
|
||||
background: "#1a1b26",
|
||||
foreground: "#a9b1d6",
|
||||
cursor: "#c0caf5",
|
||||
cursorAccent: "#1a1b26",
|
||||
selectionBackground: "rgba(41, 46, 66, 0.9)",
|
||||
selectionForeground: "#c0caf5",
|
||||
black: "#15161e",
|
||||
red: "#f7768e",
|
||||
green: "#9ece6a",
|
||||
yellow: "#e0af68",
|
||||
blue: "#7aa2f7",
|
||||
magenta: "#bb9af7",
|
||||
cyan: "#7dcfff",
|
||||
white: "#a9b1d6",
|
||||
brightBlack: "#414868",
|
||||
brightRed: "#f7768e",
|
||||
brightGreen: "#9ece6a",
|
||||
brightYellow: "#e0af68",
|
||||
brightBlue: "#7aa2f7",
|
||||
brightMagenta: "#bb9af7",
|
||||
brightCyan: "#7dcfff",
|
||||
brightWhite: "#c0caf5",
|
||||
},
|
||||
},
|
||||
|
||||
'catppuccin-mocha': {
|
||||
label: 'Catppuccin Mocha',
|
||||
theme: {
|
||||
background: '#1e1e2e',
|
||||
foreground: '#cdd6f4',
|
||||
cursor: '#f5e0dc',
|
||||
cursorAccent: '#1e1e2e',
|
||||
selectionBackground: 'rgba(88, 91, 112, 0.5)',
|
||||
selectionForeground: '#cdd6f4',
|
||||
black: '#45475a',
|
||||
red: '#f38ba8',
|
||||
green: '#a6e3a1',
|
||||
yellow: '#f9e2af',
|
||||
blue: '#89b4fa',
|
||||
magenta: '#f5c2e7',
|
||||
cyan: '#94e2d5',
|
||||
white: '#bac2de',
|
||||
brightBlack: '#585b70',
|
||||
brightRed: '#f38ba8',
|
||||
brightGreen: '#a6e3a1',
|
||||
brightYellow: '#f9e2af',
|
||||
brightBlue: '#89b4fa',
|
||||
brightMagenta: '#f5c2e7',
|
||||
brightCyan: '#94e2d5',
|
||||
brightWhite: '#a6adc8',
|
||||
},
|
||||
"catppuccin-mocha": {
|
||||
label: "Catppuccin Mocha",
|
||||
theme: {
|
||||
background: "#1e1e2e",
|
||||
foreground: "#cdd6f4",
|
||||
cursor: "#f5e0dc",
|
||||
cursorAccent: "#1e1e2e",
|
||||
selectionBackground: "rgba(88, 91, 112, 0.5)",
|
||||
selectionForeground: "#cdd6f4",
|
||||
black: "#45475a",
|
||||
red: "#f38ba8",
|
||||
green: "#a6e3a1",
|
||||
yellow: "#f9e2af",
|
||||
blue: "#89b4fa",
|
||||
magenta: "#f5c2e7",
|
||||
cyan: "#94e2d5",
|
||||
white: "#bac2de",
|
||||
brightBlack: "#585b70",
|
||||
brightRed: "#f38ba8",
|
||||
brightGreen: "#a6e3a1",
|
||||
brightYellow: "#f9e2af",
|
||||
brightBlue: "#89b4fa",
|
||||
brightMagenta: "#f5c2e7",
|
||||
brightCyan: "#94e2d5",
|
||||
brightWhite: "#a6adc8",
|
||||
},
|
||||
},
|
||||
|
||||
'rose-pine': {
|
||||
label: 'Rosé Pine',
|
||||
theme: {
|
||||
background: '#191724',
|
||||
foreground: '#e0def4',
|
||||
cursor: '#524f67',
|
||||
cursorAccent: '#e0def4',
|
||||
selectionBackground: 'rgba(110, 106, 134, 0.4)',
|
||||
selectionForeground: '#e0def4',
|
||||
black: '#26233a',
|
||||
red: '#eb6f92',
|
||||
green: '#31748f',
|
||||
yellow: '#f6c177',
|
||||
blue: '#9ccfd8',
|
||||
magenta: '#c4a7e7',
|
||||
cyan: '#ebbcba',
|
||||
white: '#e0def4',
|
||||
brightBlack: '#6e6a86',
|
||||
brightRed: '#eb6f92',
|
||||
brightGreen: '#31748f',
|
||||
brightYellow: '#f6c177',
|
||||
brightBlue: '#9ccfd8',
|
||||
brightMagenta: '#c4a7e7',
|
||||
brightCyan: '#ebbcba',
|
||||
brightWhite: '#e0def4',
|
||||
},
|
||||
"rose-pine": {
|
||||
label: "Rosé Pine",
|
||||
theme: {
|
||||
background: "#191724",
|
||||
foreground: "#e0def4",
|
||||
cursor: "#524f67",
|
||||
cursorAccent: "#e0def4",
|
||||
selectionBackground: "rgba(110, 106, 134, 0.4)",
|
||||
selectionForeground: "#e0def4",
|
||||
black: "#26233a",
|
||||
red: "#eb6f92",
|
||||
green: "#31748f",
|
||||
yellow: "#f6c177",
|
||||
blue: "#9ccfd8",
|
||||
magenta: "#c4a7e7",
|
||||
cyan: "#ebbcba",
|
||||
white: "#e0def4",
|
||||
brightBlack: "#6e6a86",
|
||||
brightRed: "#eb6f92",
|
||||
brightGreen: "#31748f",
|
||||
brightYellow: "#f6c177",
|
||||
brightBlue: "#9ccfd8",
|
||||
brightMagenta: "#c4a7e7",
|
||||
brightCyan: "#ebbcba",
|
||||
brightWhite: "#e0def4",
|
||||
},
|
||||
},
|
||||
|
||||
'solarized-light': {
|
||||
label: 'Solarized Light',
|
||||
theme: {
|
||||
background: '#fdf6e3',
|
||||
foreground: '#657b83',
|
||||
cursor: '#586e75',
|
||||
cursorAccent: '#fdf6e3',
|
||||
selectionBackground: 'rgba(7, 54, 66, 0.2)',
|
||||
selectionForeground: '#073642',
|
||||
black: '#073642',
|
||||
red: '#dc322f',
|
||||
green: '#859900',
|
||||
yellow: '#b58900',
|
||||
blue: '#268bd2',
|
||||
magenta: '#d33682',
|
||||
cyan: '#2aa198',
|
||||
white: '#eee8d5',
|
||||
brightBlack: '#002b36',
|
||||
brightRed: '#cb4b16',
|
||||
brightGreen: '#586e75',
|
||||
brightYellow: '#657b83',
|
||||
brightBlue: '#839496',
|
||||
brightMagenta: '#6c71c4',
|
||||
brightCyan: '#93a1a1',
|
||||
brightWhite: '#fdf6e3',
|
||||
},
|
||||
"solarized-light": {
|
||||
label: "Solarized Light",
|
||||
theme: {
|
||||
background: "#fdf6e3",
|
||||
foreground: "#657b83",
|
||||
cursor: "#586e75",
|
||||
cursorAccent: "#fdf6e3",
|
||||
selectionBackground: "rgba(7, 54, 66, 0.2)",
|
||||
selectionForeground: "#073642",
|
||||
black: "#073642",
|
||||
red: "#dc322f",
|
||||
green: "#859900",
|
||||
yellow: "#b58900",
|
||||
blue: "#268bd2",
|
||||
magenta: "#d33682",
|
||||
cyan: "#2aa198",
|
||||
white: "#eee8d5",
|
||||
brightBlack: "#002b36",
|
||||
brightRed: "#cb4b16",
|
||||
brightGreen: "#586e75",
|
||||
brightYellow: "#657b83",
|
||||
brightBlue: "#839496",
|
||||
brightMagenta: "#6c71c4",
|
||||
brightCyan: "#93a1a1",
|
||||
brightWhite: "#fdf6e3",
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the theme object for the given theme name.
|
||||
* Falls back to 'charcoal' if not found.
|
||||
* @param {string} name
|
||||
* @returns {Object} xterm.js theme object
|
||||
*/
|
||||
function getTerminalTheme(name) {
|
||||
const entry = THEMES[name || 'charcoal'];
|
||||
/**
|
||||
* Returns the theme object for the given theme name.
|
||||
* Falls back to 'charcoal' if not found.
|
||||
* @param {string} name
|
||||
* @returns {Object} xterm.js theme object
|
||||
*/
|
||||
function getTerminalTheme(name) {
|
||||
const entry = THEMES[name || "charcoal"];
|
||||
return entry ? entry.theme : THEMES.charcoal.theme;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all theme names and labels for building a selector.
|
||||
* @returns {Array<{value: string, label: string}>}
|
||||
*/
|
||||
function listTerminalThemes() {
|
||||
/**
|
||||
* Returns all theme names and labels for building a selector.
|
||||
* @returns {Array<{value: string, label: string}>}
|
||||
*/
|
||||
function listTerminalThemes() {
|
||||
return Object.entries(THEMES).map(([value, entry]) => ({
|
||||
value,
|
||||
label: entry.label,
|
||||
value,
|
||||
label: entry.label,
|
||||
}));
|
||||
}
|
||||
|
||||
window.NexusTerminal = window.NexusTerminal || {};
|
||||
window.NexusTerminal.getTerminalTheme = getTerminalTheme;
|
||||
window.NexusTerminal.listTerminalThemes = listTerminalThemes;
|
||||
}
|
||||
|
||||
window.NexusTerminal = window.NexusTerminal || {};
|
||||
window.NexusTerminal.getTerminalTheme = getTerminalTheme;
|
||||
window.NexusTerminal.listTerminalThemes = listTerminalThemes;
|
||||
})();
|
||||
|
||||
+37
-16
@@ -92,7 +92,9 @@
|
||||
}
|
||||
} else {
|
||||
const haystack = rule.caseSensitive ? clean : clean.toLowerCase();
|
||||
const needle = rule.caseSensitive ? rule.pattern : rule.pattern.toLowerCase();
|
||||
const needle = rule.caseSensitive
|
||||
? rule.pattern
|
||||
: rule.pattern.toLowerCase();
|
||||
matched = haystack.includes(needle);
|
||||
}
|
||||
|
||||
@@ -101,13 +103,19 @@
|
||||
// Fire actions
|
||||
if (rule.action === "notify" || rule.action === "both") {
|
||||
const session = this.app.state.getSession(sessionId);
|
||||
const label = session ? (session.tabName || session.profileName) : sessionId;
|
||||
this.app.notifications.info(`[${label}] Trigger: ${rule.name || rule.pattern}`);
|
||||
const label = session
|
||||
? session.tabName || session.profileName
|
||||
: sessionId;
|
||||
this.app.notifications.info(
|
||||
`[${label}] Trigger: ${rule.name || rule.pattern}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (rule.action === "highlight" || rule.action === "both") {
|
||||
// Flash the tab briefly
|
||||
const tabEl = document.querySelector(`.tab[data-session-id="${sessionId}"]`);
|
||||
const tabEl = document.querySelector(
|
||||
`.tab[data-session-id="${sessionId}"]`,
|
||||
);
|
||||
if (tabEl) {
|
||||
tabEl.classList.add("tab-trigger-flash");
|
||||
setTimeout(() => tabEl.classList.remove("tab-trigger-flash"), 2000);
|
||||
@@ -186,11 +194,15 @@
|
||||
modal.classList.add("is-visible");
|
||||
});
|
||||
|
||||
modal.querySelector(".modal-close").addEventListener("click", () => this.close());
|
||||
modal
|
||||
.querySelector(".modal-close")
|
||||
.addEventListener("click", () => this.close());
|
||||
backdrop.addEventListener("click", (e) => {
|
||||
if (e.target === backdrop) this.close();
|
||||
});
|
||||
modal.querySelector(".trigger-add-btn").addEventListener("click", () => this._addRule());
|
||||
modal
|
||||
.querySelector(".trigger-add-btn")
|
||||
.addEventListener("click", () => this._addRule());
|
||||
|
||||
this._renderList();
|
||||
}
|
||||
@@ -201,7 +213,8 @@
|
||||
list.innerHTML = "";
|
||||
|
||||
if (this._rules.length === 0) {
|
||||
list.innerHTML = '<div class="trigger-empty">No trigger rules defined</div>';
|
||||
list.innerHTML =
|
||||
'<div class="trigger-empty">No trigger rules defined</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -236,11 +249,15 @@
|
||||
});
|
||||
|
||||
const actions = createElement("div", "trigger-actions");
|
||||
const editBtn = createElement("button", "btn btn-xs", { textContent: "Edit" });
|
||||
const editBtn = createElement("button", "btn btn-xs", {
|
||||
textContent: "Edit",
|
||||
});
|
||||
editBtn.addEventListener("mousedown", (e) => e.preventDefault());
|
||||
editBtn.addEventListener("click", () => this._editRule(index));
|
||||
|
||||
const delBtn = createElement("button", "btn btn-xs btn-danger", { textContent: "✕" });
|
||||
const delBtn = createElement("button", "btn btn-xs btn-danger", {
|
||||
textContent: "✕",
|
||||
});
|
||||
delBtn.addEventListener("mousedown", (e) => e.preventDefault());
|
||||
delBtn.addEventListener("click", () => {
|
||||
this._rules.splice(index, 1);
|
||||
@@ -287,7 +304,7 @@
|
||||
form.innerHTML = `
|
||||
<div class="form-group">
|
||||
<label class="form-label">Name (optional)</label>
|
||||
<input type="text" class="form-input trigger-name-input" placeholder="e.g. Error alert" value="${existing ? (existing.name || "") : ""}" />
|
||||
<input type="text" class="form-input trigger-name-input" placeholder="e.g. Error alert" value="${existing ? existing.name || "" : ""}" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Pattern</label>
|
||||
@@ -325,14 +342,18 @@
|
||||
|
||||
list.parentNode.insertBefore(form, list);
|
||||
|
||||
form.querySelector(".trigger-cancel-btn").addEventListener("click", () => {
|
||||
form.remove();
|
||||
list.style.display = "";
|
||||
if (toolbar) toolbar.style.display = "";
|
||||
});
|
||||
form
|
||||
.querySelector(".trigger-cancel-btn")
|
||||
.addEventListener("click", () => {
|
||||
form.remove();
|
||||
list.style.display = "";
|
||||
if (toolbar) toolbar.style.display = "";
|
||||
});
|
||||
|
||||
form.querySelector(".trigger-save-btn").addEventListener("click", () => {
|
||||
const pattern = form.querySelector(".trigger-pattern-input").value.trim();
|
||||
const pattern = form
|
||||
.querySelector(".trigger-pattern-input")
|
||||
.value.trim();
|
||||
if (!pattern) {
|
||||
this.app.notifications.error("Pattern is required");
|
||||
return;
|
||||
|
||||
+255
-212
@@ -1,120 +1,132 @@
|
||||
(function() {
|
||||
/**
|
||||
* Nexus Terminal — utility functions.
|
||||
* General-purpose helpers used throughout the application.
|
||||
*/
|
||||
(function () {
|
||||
/**
|
||||
* Nexus Terminal — utility functions.
|
||||
* General-purpose helpers used throughout the application.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Shorthand for document.querySelector.
|
||||
* @param {string} selector - CSS selector.
|
||||
* @returns {Element|null}
|
||||
*/
|
||||
function $(selector) {
|
||||
/**
|
||||
* Shorthand for document.querySelector.
|
||||
* @param {string} selector - CSS selector.
|
||||
* @returns {Element|null}
|
||||
*/
|
||||
function $(selector) {
|
||||
return document.querySelector(selector);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand for document.querySelectorAll.
|
||||
* @param {string} selector - CSS selector.
|
||||
* @returns {NodeListOf<Element>}
|
||||
*/
|
||||
function $$(selector) {
|
||||
/**
|
||||
* Shorthand for document.querySelectorAll.
|
||||
* @param {string} selector - CSS selector.
|
||||
* @returns {NodeListOf<Element>}
|
||||
*/
|
||||
function $$(selector) {
|
||||
return document.querySelectorAll(selector);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a DOM element with optional class name and attributes.
|
||||
* @param {string} tag - HTML tag name.
|
||||
* @param {string} [className] - Space-separated class names.
|
||||
* @param {Object} [attrs] - Attribute key/value pairs.
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
function createElement(tag, className, attrs = {}) {
|
||||
/**
|
||||
* Creates a DOM element with optional class name and attributes.
|
||||
* @param {string} tag - HTML tag name.
|
||||
* @param {string} [className] - Space-separated class names.
|
||||
* @param {Object} [attrs] - Attribute key/value pairs.
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
function createElement(tag, className, attrs = {}) {
|
||||
const el = document.createElement(tag);
|
||||
if (className) {
|
||||
el.className = className;
|
||||
el.className = className;
|
||||
}
|
||||
for (const [key, value] of Object.entries(attrs)) {
|
||||
if (key === 'textContent') {
|
||||
el.textContent = value;
|
||||
} else if (key === 'innerHTML') {
|
||||
el.innerHTML = value;
|
||||
} else if (key === 'dataset') {
|
||||
for (const [dKey, dVal] of Object.entries(value)) {
|
||||
el.dataset[dKey] = dVal;
|
||||
}
|
||||
} else if (key.startsWith('on') && typeof value === 'function') {
|
||||
el.addEventListener(key.slice(2).toLowerCase(), value);
|
||||
} else {
|
||||
el.setAttribute(key, value);
|
||||
if (key === "textContent") {
|
||||
el.textContent = value;
|
||||
} else if (key === "innerHTML") {
|
||||
el.innerHTML = value;
|
||||
} else if (key === "dataset") {
|
||||
for (const [dKey, dVal] of Object.entries(value)) {
|
||||
el.dataset[dKey] = dVal;
|
||||
}
|
||||
} else if (key.startsWith("on") && typeof value === "function") {
|
||||
el.addEventListener(key.slice(2).toLowerCase(), value);
|
||||
} else {
|
||||
el.setAttribute(key, value);
|
||||
}
|
||||
}
|
||||
return el;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes HTML special characters to prevent XSS.
|
||||
* @param {string} str - Raw string.
|
||||
* @returns {string} Escaped string safe for innerHTML.
|
||||
*/
|
||||
function escapeHtml(str) {
|
||||
if (!str) return '';
|
||||
/**
|
||||
* Escapes HTML special characters to prevent XSS.
|
||||
* @param {string} str - Raw string.
|
||||
* @returns {string} Escaped string safe for innerHTML.
|
||||
*/
|
||||
function escapeHtml(str) {
|
||||
if (!str) return "";
|
||||
const map = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'",
|
||||
};
|
||||
return str.replace(/[&<>"']/g, (ch) => map[ch]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a Date (or ISO string) into a concise timestamp.
|
||||
* @param {Date|string} date
|
||||
* @returns {string} e.g. "14:32:07" or "2 Jan 14:32".
|
||||
*/
|
||||
function formatTimestamp(date) {
|
||||
if (!date) return '';
|
||||
/**
|
||||
* Formats a Date (or ISO string) into a concise timestamp.
|
||||
* @param {Date|string} date
|
||||
* @returns {string} e.g. "14:32:07" or "2 Jan 14:32".
|
||||
*/
|
||||
function formatTimestamp(date) {
|
||||
if (!date) return "";
|
||||
const d = date instanceof Date ? date : new Date(date);
|
||||
if (isNaN(d.getTime())) return '';
|
||||
if (isNaN(d.getTime())) return "";
|
||||
|
||||
const now = new Date();
|
||||
const isToday = d.toDateString() === now.toDateString();
|
||||
const hours = String(d.getHours()).padStart(2, '0');
|
||||
const mins = String(d.getMinutes()).padStart(2, '0');
|
||||
const secs = String(d.getSeconds()).padStart(2, '0');
|
||||
const hours = String(d.getHours()).padStart(2, "0");
|
||||
const mins = String(d.getMinutes()).padStart(2, "0");
|
||||
const secs = String(d.getSeconds()).padStart(2, "0");
|
||||
|
||||
if (isToday) {
|
||||
return `${hours}:${mins}:${secs}`;
|
||||
return `${hours}:${mins}:${secs}`;
|
||||
}
|
||||
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
||||
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||
const months = [
|
||||
"Jan",
|
||||
"Feb",
|
||||
"Mar",
|
||||
"Apr",
|
||||
"May",
|
||||
"Jun",
|
||||
"Jul",
|
||||
"Aug",
|
||||
"Sep",
|
||||
"Oct",
|
||||
"Nov",
|
||||
"Dec",
|
||||
];
|
||||
return `${d.getDate()} ${months[d.getMonth()]} ${hours}:${mins}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a debounced version of the given function.
|
||||
* @param {Function} fn
|
||||
* @param {number} ms - Delay in milliseconds.
|
||||
* @returns {Function}
|
||||
*/
|
||||
function debounce(fn, ms) {
|
||||
/**
|
||||
* Returns a debounced version of the given function.
|
||||
* @param {Function} fn
|
||||
* @param {number} ms - Delay in milliseconds.
|
||||
* @returns {Function}
|
||||
*/
|
||||
function debounce(fn, ms) {
|
||||
let timer = null;
|
||||
return function (...args) {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => fn.apply(this, args), ms);
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => fn.apply(this, args), ms);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a simple fuzzy match — checks whether every character in
|
||||
* the query appears in order within the text (case-insensitive).
|
||||
* @param {string} text
|
||||
* @param {string} query
|
||||
* @returns {{ match: boolean, score: number }}
|
||||
*/
|
||||
function fuzzyMatch(text, query) {
|
||||
/**
|
||||
* Performs a simple fuzzy match — checks whether every character in
|
||||
* the query appears in order within the text (case-insensitive).
|
||||
* @param {string} text
|
||||
* @param {string} query
|
||||
* @returns {{ match: boolean, score: number }}
|
||||
*/
|
||||
function fuzzyMatch(text, query) {
|
||||
if (!query) return { match: true, score: 1 };
|
||||
const lowerText = text.toLowerCase();
|
||||
const lowerQuery = query.toLowerCase();
|
||||
@@ -124,169 +136,200 @@ function fuzzyMatch(text, query) {
|
||||
let consecutiveBonus = 0;
|
||||
|
||||
for (let qi = 0; qi < lowerQuery.length; qi++) {
|
||||
const ch = lowerQuery[qi];
|
||||
const foundAt = lowerText.indexOf(ch, textIdx);
|
||||
if (foundAt === -1) {
|
||||
return { match: false, score: 0 };
|
||||
}
|
||||
// Reward consecutive character matches and early matches
|
||||
if (foundAt === textIdx) {
|
||||
consecutiveBonus += 1;
|
||||
} else {
|
||||
consecutiveBonus = 0;
|
||||
}
|
||||
score += 1 + consecutiveBonus + (foundAt === 0 ? 2 : 0);
|
||||
textIdx = foundAt + 1;
|
||||
const ch = lowerQuery[qi];
|
||||
const foundAt = lowerText.indexOf(ch, textIdx);
|
||||
if (foundAt === -1) {
|
||||
return { match: false, score: 0 };
|
||||
}
|
||||
// Reward consecutive character matches and early matches
|
||||
if (foundAt === textIdx) {
|
||||
consecutiveBonus += 1;
|
||||
} else {
|
||||
consecutiveBonus = 0;
|
||||
}
|
||||
score += 1 + consecutiveBonus + (foundAt === 0 ? 2 : 0);
|
||||
textIdx = foundAt + 1;
|
||||
}
|
||||
|
||||
return { match: true, score };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64-encodes a string.
|
||||
* @param {string} str
|
||||
* @returns {string}
|
||||
*/
|
||||
function base64Encode(str) {
|
||||
/**
|
||||
* Base64-encodes a string.
|
||||
* @param {string} str
|
||||
* @returns {string}
|
||||
*/
|
||||
function base64Encode(str) {
|
||||
return btoa(str);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64-decodes a string.
|
||||
* @param {string} str
|
||||
* @returns {string}
|
||||
*/
|
||||
function base64Decode(str) {
|
||||
/**
|
||||
* Base64-decodes a string.
|
||||
* @param {string} str
|
||||
* @returns {string}
|
||||
*/
|
||||
function base64Decode(str) {
|
||||
return atob(str);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new UUID v4 using the browser crypto API.
|
||||
* @returns {string}
|
||||
*/
|
||||
function generateId() {
|
||||
/**
|
||||
* Generates a new UUID v4 using the browser crypto API.
|
||||
* @returns {string}
|
||||
*/
|
||||
function generateId() {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a byte count into a human-friendly string.
|
||||
* @param {number} bytes
|
||||
* @returns {string} e.g. "1.23 KB"
|
||||
*/
|
||||
function formatBytes(bytes) {
|
||||
if (bytes === 0) return '0 B';
|
||||
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
/**
|
||||
* Formats a byte count into a human-friendly string.
|
||||
* @param {number} bytes
|
||||
* @returns {string} e.g. "1.23 KB"
|
||||
*/
|
||||
function formatBytes(bytes) {
|
||||
if (bytes === 0) return "0 B";
|
||||
const units = ["B", "KB", "MB", "GB", "TB"];
|
||||
const k = 1024;
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
const value = bytes / Math.pow(k, i);
|
||||
return `${value.toFixed(i === 0 ? 0 : 2)} ${units[i]}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncates a string to the given length, appending an ellipsis if needed.
|
||||
* @param {string} str
|
||||
* @param {number} len
|
||||
* @returns {string}
|
||||
*/
|
||||
function truncate(str, len) {
|
||||
if (!str || str.length <= len) return str || '';
|
||||
return str.slice(0, len) + '…';
|
||||
}
|
||||
/**
|
||||
* Truncates a string to the given length, appending an ellipsis if needed.
|
||||
* @param {string} str
|
||||
* @param {number} len
|
||||
* @returns {string}
|
||||
*/
|
||||
function truncate(str, len) {
|
||||
if (!str || str.length <= len) return str || "";
|
||||
return str.slice(0, len) + "…";
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes any open context menus from the DOM.
|
||||
*/
|
||||
function dismissContextMenus() {
|
||||
document.querySelectorAll('.context-menu').forEach((m) => m.remove());
|
||||
}
|
||||
/**
|
||||
* Removes any open context menus from the DOM.
|
||||
*/
|
||||
function dismissContextMenus() {
|
||||
document.querySelectorAll(".context-menu").forEach((m) => m.remove());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the protocol badge colour class.
|
||||
* @param {string} protocol - 'ssh', 'telnet', 'raw', or 'github'.
|
||||
* @returns {string}
|
||||
*/
|
||||
function protocolColour(protocol) {
|
||||
/**
|
||||
* Returns the protocol badge colour class.
|
||||
* @param {string} protocol - 'ssh', 'telnet', 'raw', or 'github'.
|
||||
* @returns {string}
|
||||
*/
|
||||
function protocolColour(protocol) {
|
||||
switch (protocol) {
|
||||
case 'ssh': return 'proto-ssh';
|
||||
case 'telnet': return 'proto-telnet';
|
||||
case 'raw': return 'proto-raw';
|
||||
case 'github': return 'proto-github';
|
||||
case 'vnc': return 'proto-vnc';
|
||||
default: return '';
|
||||
case "ssh":
|
||||
return "proto-ssh";
|
||||
case "telnet":
|
||||
return "proto-telnet";
|
||||
case "raw":
|
||||
return "proto-raw";
|
||||
case "github":
|
||||
return "proto-github";
|
||||
case "vnc":
|
||||
return "proto-vnc";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a human-friendly relative time string for an ISO date.
|
||||
* @param {string} isoDate - ISO 8601 date string.
|
||||
* @returns {string} e.g. "3 hours ago", "2 days ago".
|
||||
*/
|
||||
function relativeTime(isoDate) {
|
||||
if (!isoDate) return '';
|
||||
/**
|
||||
* Returns a human-friendly relative time string for an ISO date.
|
||||
* @param {string} isoDate - ISO 8601 date string.
|
||||
* @returns {string} e.g. "3 hours ago", "2 days ago".
|
||||
*/
|
||||
function relativeTime(isoDate) {
|
||||
if (!isoDate) return "";
|
||||
const d = new Date(isoDate);
|
||||
if (isNaN(d.getTime())) return '';
|
||||
if (isNaN(d.getTime())) return "";
|
||||
|
||||
const now = Date.now();
|
||||
const diff = now - d.getTime();
|
||||
const secs = Math.floor(diff / 1000);
|
||||
if (secs < 60) return 'just now';
|
||||
if (secs < 60) return "just now";
|
||||
const mins = Math.floor(secs / 60);
|
||||
if (mins < 60) return mins === 1 ? '1 minute ago' : `${mins} minutes ago`;
|
||||
if (mins < 60) return mins === 1 ? "1 minute ago" : `${mins} minutes ago`;
|
||||
const hrs = Math.floor(mins / 60);
|
||||
if (hrs < 24) return hrs === 1 ? '1 hour ago' : `${hrs} hours ago`;
|
||||
if (hrs < 24) return hrs === 1 ? "1 hour ago" : `${hrs} hours ago`;
|
||||
const days = Math.floor(hrs / 24);
|
||||
if (days < 30) return days === 1 ? '1 day ago' : `${days} days ago`;
|
||||
if (days < 30) return days === 1 ? "1 day ago" : `${days} days ago`;
|
||||
const months = Math.floor(days / 30);
|
||||
if (months < 12) return months === 1 ? '1 month ago' : `${months} months ago`;
|
||||
if (months < 12)
|
||||
return months === 1 ? "1 month ago" : `${months} months ago`;
|
||||
const years = Math.floor(months / 12);
|
||||
return years === 1 ? '1 year ago' : `${years} years ago`;
|
||||
}
|
||||
return years === 1 ? "1 year ago" : `${years} years ago`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a human-friendly label for a session state.
|
||||
* @param {string} state
|
||||
* @returns {string}
|
||||
*/
|
||||
function stateLabel(state) {
|
||||
/**
|
||||
* Returns a human-friendly label for a session state.
|
||||
* @param {string} state
|
||||
* @returns {string}
|
||||
*/
|
||||
function stateLabel(state) {
|
||||
switch (state) {
|
||||
case 'connecting': return 'Connecting';
|
||||
case 'connected': return 'Connected';
|
||||
case 'disconnected': return 'Disconnected';
|
||||
case 'reconnecting': return 'Reconnecting';
|
||||
case 'error': return 'Error';
|
||||
default: return state || 'Unknown';
|
||||
case "connecting":
|
||||
return "Connecting";
|
||||
case "connected":
|
||||
return "Connected";
|
||||
case "disconnected":
|
||||
return "Disconnected";
|
||||
case "reconnecting":
|
||||
return "Reconnecting";
|
||||
case "error":
|
||||
return "Error";
|
||||
default:
|
||||
return state || "Unknown";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.NexusTerminal = window.NexusTerminal || {};
|
||||
Object.assign(window.NexusTerminal, { $, $$, createElement, escapeHtml, formatTimestamp, debounce, fuzzyMatch, base64Encode, base64Decode, generateId, formatBytes, truncate, dismissContextMenus, protocolColour, stateLabel, relativeTime });
|
||||
window.NexusTerminal = window.NexusTerminal || {};
|
||||
Object.assign(window.NexusTerminal, {
|
||||
$,
|
||||
$$,
|
||||
createElement,
|
||||
escapeHtml,
|
||||
formatTimestamp,
|
||||
debounce,
|
||||
fuzzyMatch,
|
||||
base64Encode,
|
||||
base64Decode,
|
||||
generateId,
|
||||
formatBytes,
|
||||
truncate,
|
||||
dismissContextMenus,
|
||||
protocolColour,
|
||||
stateLabel,
|
||||
relativeTime,
|
||||
});
|
||||
|
||||
/* ── Disable macOS/WebKit auto-capitalisation on all text inputs ───────── */
|
||||
function disableAutoCapitalise(el) {
|
||||
el.setAttribute('autocapitalize', 'none');
|
||||
el.setAttribute('autocorrect', 'off');
|
||||
el.setAttribute('spellcheck', 'false');
|
||||
}
|
||||
/* ── Disable macOS/WebKit auto-capitalisation on all text inputs ───────── */
|
||||
function disableAutoCapitalise(el) {
|
||||
el.setAttribute("autocapitalize", "none");
|
||||
el.setAttribute("autocorrect", "off");
|
||||
el.setAttribute("spellcheck", "false");
|
||||
}
|
||||
|
||||
// Apply to existing inputs
|
||||
document.querySelectorAll('input, textarea').forEach(disableAutoCapitalise);
|
||||
// Apply to existing inputs
|
||||
document.querySelectorAll("input, textarea").forEach(disableAutoCapitalise);
|
||||
|
||||
// Observe for dynamically added inputs
|
||||
var inputObserver = new MutationObserver(function(mutations) {
|
||||
// Observe for dynamically added inputs
|
||||
var inputObserver = new MutationObserver(function (mutations) {
|
||||
for (var i = 0; i < mutations.length; i++) {
|
||||
var nodes = mutations[i].addedNodes;
|
||||
for (var j = 0; j < nodes.length; j++) {
|
||||
var node = nodes[j];
|
||||
if (node.nodeType !== 1) continue;
|
||||
if (node.tagName === 'INPUT' || node.tagName === 'TEXTAREA') {
|
||||
disableAutoCapitalise(node);
|
||||
}
|
||||
if (node.querySelectorAll) {
|
||||
node.querySelectorAll('input, textarea').forEach(disableAutoCapitalise);
|
||||
}
|
||||
var nodes = mutations[i].addedNodes;
|
||||
for (var j = 0; j < nodes.length; j++) {
|
||||
var node = nodes[j];
|
||||
if (node.nodeType !== 1) continue;
|
||||
if (node.tagName === "INPUT" || node.tagName === "TEXTAREA") {
|
||||
disableAutoCapitalise(node);
|
||||
}
|
||||
if (node.querySelectorAll) {
|
||||
node
|
||||
.querySelectorAll("input, textarea")
|
||||
.forEach(disableAutoCapitalise);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
inputObserver.observe(document.body, { childList: true, subtree: true });
|
||||
|
||||
});
|
||||
inputObserver.observe(document.body, { childList: true, subtree: true });
|
||||
})();
|
||||
|
||||
+16
-8
@@ -91,16 +91,24 @@
|
||||
});
|
||||
ctrlAltDel.addEventListener("click", () => this._sendCtrlAltDel());
|
||||
|
||||
const clipboardBtn = createElement("button", "btn btn-sm btn-vnc-action", {
|
||||
textContent: "Clipboard",
|
||||
title: "Sync clipboard",
|
||||
});
|
||||
const clipboardBtn = createElement(
|
||||
"button",
|
||||
"btn btn-sm btn-vnc-action",
|
||||
{
|
||||
textContent: "Clipboard",
|
||||
title: "Sync clipboard",
|
||||
},
|
||||
);
|
||||
clipboardBtn.addEventListener("click", () => this._syncClipboard());
|
||||
|
||||
const fullscreenBtn = createElement("button", "btn btn-sm btn-vnc-action", {
|
||||
textContent: "Fullscreen",
|
||||
title: "Toggle fullscreen",
|
||||
});
|
||||
const fullscreenBtn = createElement(
|
||||
"button",
|
||||
"btn btn-sm btn-vnc-action",
|
||||
{
|
||||
textContent: "Fullscreen",
|
||||
title: "Toggle fullscreen",
|
||||
},
|
||||
);
|
||||
fullscreenBtn.addEventListener("click", () => this._toggleFullscreen());
|
||||
|
||||
this.statusEl = createElement("span", "vnc-status", {
|
||||
|
||||
Reference in New Issue
Block a user