Changeset 0.17.2 (#77)
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
========================================== */
|
||||
|
||||
:root {
|
||||
/* ── Core palette ────────────────────────── */
|
||||
/* ── Core palette (Dark — default) ────────── */
|
||||
--bg: #0e0e10;
|
||||
--bg-surface: #18181b;
|
||||
--bg-raised: #222227;
|
||||
@@ -60,6 +60,44 @@
|
||||
--banner-fg: transparent;
|
||||
}
|
||||
|
||||
/* ── Light Theme ─────────────────────────────── */
|
||||
[data-theme="light"] {
|
||||
--bg: #ffffff;
|
||||
--bg-surface: #f8f9fa;
|
||||
--bg-raised: #f0f1f3;
|
||||
--bg-hover: #e8e9ec;
|
||||
--border: #d5d7db;
|
||||
--border-light: #c0c3c9;
|
||||
--text: #1a1a2e;
|
||||
--text-2: #555770;
|
||||
--text-3: #8b8da3;
|
||||
--accent: #4a7cdb;
|
||||
--accent-hover: #3968c4;
|
||||
--accent-dim: rgba(74,124,219,0.10);
|
||||
--danger: #dc2626;
|
||||
--success: #16a34a;
|
||||
--warning: #ca8a04;
|
||||
--purple: #7c3aed;
|
||||
--purple-dim: rgba(124,58,237,0.08);
|
||||
|
||||
--text-on-color: #fff;
|
||||
--text-on-warning: #000;
|
||||
|
||||
--accent-light: #2563eb;
|
||||
--danger-light: #dc2626;
|
||||
--success-light: #16a34a;
|
||||
--warning-light: #ca8a04;
|
||||
|
||||
--danger-dim: rgba(220,38,38,0.10);
|
||||
--success-dim: rgba(22,163,74,0.10);
|
||||
--warning-dim: rgba(202,138,4,0.10);
|
||||
|
||||
--overlay: rgba(0,0,0,0.35);
|
||||
--glass: rgba(0,0,0,0.02);
|
||||
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
html, body { height: 100%; }
|
||||
body { font-family: var(--font); background: var(--bg); color: var(--text); overflow: hidden; font-size: 14px; }
|
||||
@@ -912,6 +950,27 @@ a:hover { text-decoration: underline; }
|
||||
.input-wrap textarea:focus { outline: none; }
|
||||
.input-wrap textarea::placeholder { color: var(--text-3); }
|
||||
|
||||
/* CM6 chat input — match textarea layout within .input-wrap */
|
||||
#messageInputWrap {
|
||||
flex: 1; min-width: 0;
|
||||
}
|
||||
#messageInputWrap .cm-editor {
|
||||
background: transparent;
|
||||
padding: 12px 0 12px 8px;
|
||||
max-height: 200px;
|
||||
font-family: var(--font);
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
#messageInputWrap .cm-editor.cm-focused { outline: none; }
|
||||
#messageInputWrap .cm-scroller { overflow-y: auto; }
|
||||
|
||||
/* CM6 extension editor containers */
|
||||
.ext-edit-form .cm-editor {
|
||||
height: 100%;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.input-actions { display: flex; align-items: center; gap: 2px; padding: 6px 8px; }
|
||||
|
||||
/* Left-side toolbar: attach, tools, KB — single container handles alignment */
|
||||
@@ -1276,6 +1335,25 @@ select option { background: var(--bg-surface); color: var(--text); }
|
||||
.range-labels { display: flex; justify-content: space-between; font-size: 11px; color: var(--text-3); }
|
||||
.checkbox-label { display: flex; align-items: center; gap: 8px; font-size: 14px; cursor: pointer; margin: 0.5rem 0; color: var(--text-2); }
|
||||
|
||||
/* ── Theme Toggle ────────────────────────── */
|
||||
.theme-toggle {
|
||||
display: flex; gap: 4px; padding: 3px;
|
||||
background: var(--bg); border: 1px solid var(--border);
|
||||
border-radius: var(--radius); width: fit-content;
|
||||
}
|
||||
.theme-btn {
|
||||
display: flex; align-items: center; gap: 6px;
|
||||
padding: 6px 14px; border-radius: calc(var(--radius) - 2px);
|
||||
border: none; background: none; color: var(--text-3);
|
||||
font-family: var(--font); font-size: 13px; font-weight: 500;
|
||||
cursor: pointer; transition: all var(--transition);
|
||||
}
|
||||
.theme-btn:hover { color: var(--text); background: var(--bg-hover); }
|
||||
.theme-btn.active {
|
||||
color: var(--text); background: var(--bg-raised);
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
/* ── Modal ────────────────────────────────── */
|
||||
|
||||
.modal-overlay {
|
||||
|
||||
54
src/editor/build.mjs
Normal file
54
src/editor/build.mjs
Normal file
@@ -0,0 +1,54 @@
|
||||
// ==========================================
|
||||
// Chat Switchboard — CM6 Build Script
|
||||
// ==========================================
|
||||
// Bundles CodeMirror 6 into a single IIFE that
|
||||
// exposes window.CM. Run via scripts/build-editor.sh
|
||||
// or directly: node build.mjs [--outdir=path]
|
||||
//
|
||||
// Output:
|
||||
// codemirror.bundle.js (~295KB min, ~90KB gzip)
|
||||
// codemirror.bundle.css (~5-10KB)
|
||||
// ==========================================
|
||||
|
||||
import { build } from 'esbuild';
|
||||
import { resolve, dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
// Parse --outdir=<path> from argv
|
||||
let outdir = resolve(__dirname, 'dist');
|
||||
for (const arg of process.argv.slice(2)) {
|
||||
if (arg.startsWith('--outdir=')) {
|
||||
outdir = resolve(arg.slice('--outdir='.length));
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await build({
|
||||
entryPoints: [resolve(__dirname, 'index.mjs')],
|
||||
bundle: true,
|
||||
minify: true,
|
||||
format: 'iife',
|
||||
target: ['es2020'],
|
||||
outfile: resolve(outdir, 'codemirror.bundle.js'),
|
||||
// Metafile for optional size analysis
|
||||
metafile: true,
|
||||
// Log level
|
||||
logLevel: 'info',
|
||||
});
|
||||
|
||||
// Print bundle size summary
|
||||
if (result.metafile) {
|
||||
const outputs = result.metafile.outputs;
|
||||
for (const [file, info] of Object.entries(outputs)) {
|
||||
const kb = (info.bytes / 1024).toFixed(1);
|
||||
console.log(` ${file}: ${kb} KB`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`✅ CM6 bundle → ${outdir}`);
|
||||
} catch (err) {
|
||||
console.error('❌ CM6 build failed:', err.message);
|
||||
process.exit(1);
|
||||
}
|
||||
310
src/editor/chat-input.mjs
Normal file
310
src/editor/chat-input.mjs
Normal file
@@ -0,0 +1,310 @@
|
||||
// ==========================================
|
||||
// Chat Switchboard — CM6 Chat Input Factory
|
||||
// ==========================================
|
||||
// Minimal markdown editor for the chat message
|
||||
// input. No line numbers, no gutter. Enter sends,
|
||||
// Shift+Enter inserts newline. Auto-growing height.
|
||||
//
|
||||
// Usage:
|
||||
// const editor = CM.chatInput(container, {
|
||||
// placeholder: 'Send a message...',
|
||||
// onSubmit: (text) => sendMessage(text),
|
||||
// onChange: (text) => updateInputTokens(text),
|
||||
// maxHeight: 200,
|
||||
// darkMode: true,
|
||||
// });
|
||||
//
|
||||
// editor.getValue()
|
||||
// editor.setValue(str)
|
||||
// editor.focus()
|
||||
// editor.destroy()
|
||||
// ==========================================
|
||||
|
||||
import { EditorView, keymap, drawSelection, dropCursor,
|
||||
highlightSpecialChars, placeholder as placeholderExt,
|
||||
ViewPlugin, Decoration } from '@codemirror/view';
|
||||
import { EditorState, Compartment, RangeSetBuilder } from '@codemirror/state';
|
||||
import { defaultKeymap, history, historyKeymap } from '@codemirror/commands';
|
||||
import { closeBrackets, closeBracketsKeymap } from '@codemirror/autocomplete';
|
||||
import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
|
||||
import { syntaxHighlighting, defaultHighlightStyle, syntaxTree } from '@codemirror/language';
|
||||
|
||||
import { chatInputTheme } from './theme.mjs';
|
||||
|
||||
// ── Auto-Height Extension ───────────────────
|
||||
|
||||
/**
|
||||
* Extension that auto-grows the editor height to fit content,
|
||||
* up to maxHeight pixels. Mirrors the existing textarea behavior:
|
||||
* this.style.height = 'auto';
|
||||
* this.style.height = Math.min(this.scrollHeight, 200) + 'px';
|
||||
*/
|
||||
function autoHeight(maxHeight = 200) {
|
||||
return EditorView.updateListener.of((update) => {
|
||||
if (update.docChanged || update.geometryChanged) {
|
||||
const dom = update.view.dom;
|
||||
dom.style.height = 'auto';
|
||||
const scrollH = update.view.contentDOM.scrollHeight;
|
||||
// Account for .cm-editor padding (12px top + 12px bottom from wrapper CSS)
|
||||
const padV = 24;
|
||||
const targetH = Math.min(scrollH + padV, maxHeight);
|
||||
dom.style.height = targetH + 'px';
|
||||
dom.style.overflowY = scrollH + padV > maxHeight ? 'auto' : 'hidden';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ── Fenced Code Block Decorations ───────────
|
||||
//
|
||||
// Scans the markdown syntax tree for FencedCode nodes
|
||||
// and applies line decorations so they render as visual
|
||||
// code blocks (dark background, monospace, rounded corners)
|
||||
// inside the chat input — matching the claude.ai UX.
|
||||
|
||||
const codeBlockDeco = ViewPlugin.fromClass(class {
|
||||
decorations;
|
||||
|
||||
constructor(view) {
|
||||
this.decorations = this.buildDecorations(view);
|
||||
}
|
||||
|
||||
update(update) {
|
||||
// Rebuild when content changes, viewport shifts, or the parser
|
||||
// finishes an incremental parse (tree reference changes)
|
||||
if (update.docChanged || update.viewportChanged ||
|
||||
syntaxTree(update.startState) !== syntaxTree(update.state)) {
|
||||
this.decorations = this.buildDecorations(update.view);
|
||||
}
|
||||
}
|
||||
|
||||
buildDecorations(view) {
|
||||
const builder = new RangeSetBuilder();
|
||||
const tree = syntaxTree(view.state);
|
||||
const doc = view.state.doc;
|
||||
|
||||
tree.iterate({
|
||||
enter(node) {
|
||||
if (node.name !== 'FencedCode') return;
|
||||
|
||||
const startLine = doc.lineAt(node.from);
|
||||
const endLine = doc.lineAt(node.to);
|
||||
|
||||
for (let ln = startLine.number; ln <= endLine.number; ln++) {
|
||||
const line = doc.line(ln);
|
||||
let cls;
|
||||
|
||||
if (ln === startLine.number) {
|
||||
cls = 'cm-codeblock cm-codeblock-open';
|
||||
} else if (ln === endLine.number) {
|
||||
cls = 'cm-codeblock cm-codeblock-close';
|
||||
} else {
|
||||
cls = 'cm-codeblock cm-codeblock-mid';
|
||||
}
|
||||
|
||||
// Single-line code block (just ```)
|
||||
if (startLine.number === endLine.number) {
|
||||
cls = 'cm-codeblock cm-codeblock-open cm-codeblock-close';
|
||||
}
|
||||
|
||||
builder.add(line.from, line.from, Decoration.line({ class: cls }));
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return builder.finish();
|
||||
}
|
||||
}, {
|
||||
decorations: (v) => v.decorations,
|
||||
});
|
||||
|
||||
|
||||
// ── Code Block Shortcuts ────────────────────
|
||||
|
||||
/**
|
||||
* Input handler: triple backtick → fenced code block.
|
||||
* Triggers when user types the third ` completing ```
|
||||
* at the start of a line (possibly preceded only by whitespace/text).
|
||||
*/
|
||||
function tripleBacktickHandler() {
|
||||
return EditorView.inputHandler.of((view, from, to, text) => {
|
||||
if (text !== '`') return false;
|
||||
|
||||
const line = view.state.doc.lineAt(from);
|
||||
const textBeforeCursor = view.state.doc.sliceString(line.from, from);
|
||||
|
||||
// Match `` at end of line (completing ```) — only at line start
|
||||
if (/^``$/.test(textBeforeCursor.trimStart()) && textBeforeCursor.trimStart() === '``') {
|
||||
const leadingWhitespace = textBeforeCursor.match(/^(\s*)/)[1];
|
||||
const block = leadingWhitespace + '```\n' + leadingWhitespace + '\n' + leadingWhitespace + '```';
|
||||
view.dispatch({
|
||||
changes: { from: line.from, to, insert: block },
|
||||
selection: { anchor: line.from + leadingWhitespace.length + 4 },
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Keybinding: Ctrl/Cmd+E wraps selection in inline code backticks,
|
||||
* or inserts a pair and places cursor between them.
|
||||
*/
|
||||
function inlineCodeKeymap() {
|
||||
return keymap.of([{
|
||||
key: 'Mod-e',
|
||||
run: (view) => {
|
||||
const { from, to } = view.state.selection.main;
|
||||
if (from !== to) {
|
||||
const text = view.state.doc.sliceString(from, to);
|
||||
view.dispatch({
|
||||
changes: { from, to, insert: '`' + text + '`' },
|
||||
selection: { anchor: from + 1, head: to + 1 },
|
||||
});
|
||||
} else {
|
||||
view.dispatch({
|
||||
changes: { from, to: from, insert: '``' },
|
||||
selection: { anchor: from + 1 },
|
||||
});
|
||||
}
|
||||
return true;
|
||||
},
|
||||
}]);
|
||||
}
|
||||
|
||||
// ── Chat Input Factory ──────────────────────
|
||||
|
||||
/**
|
||||
* Create a chat message input with markdown highlighting.
|
||||
*
|
||||
* @param {HTMLElement} target - Container element
|
||||
* @param {Object} opts
|
||||
* @param {string} [opts.placeholder='Send a message...']
|
||||
* @param {Function} [opts.onSubmit] - Called with text on Enter (without Shift)
|
||||
* @param {Function} [opts.onChange] - Called with text on every edit
|
||||
* @param {number} [opts.maxHeight=200] - Max height in pixels before scrolling
|
||||
* @param {boolean} [opts.darkMode] - Auto-detected from body if omitted
|
||||
* @returns {{ getValue, setValue, focus, getView, destroy }}
|
||||
*/
|
||||
export function chatInput(target, opts = {}) {
|
||||
const {
|
||||
placeholder: placeholderText = 'Send a message...',
|
||||
onSubmit = null,
|
||||
onChange = null,
|
||||
maxHeight = 200,
|
||||
darkMode = document.documentElement.getAttribute('data-theme') !== 'light',
|
||||
} = opts;
|
||||
|
||||
// Build extensions
|
||||
const extensions = [
|
||||
// Markdown highlighting
|
||||
markdown({ base: markdownLanguage }),
|
||||
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
|
||||
|
||||
// Code block shortcuts
|
||||
tripleBacktickHandler(), // ``` → fenced code block
|
||||
inlineCodeKeymap(), // Ctrl/Cmd+E → inline code
|
||||
|
||||
// Code block visual decorations (WYSIWYG style)
|
||||
codeBlockDeco,
|
||||
|
||||
// Base editing
|
||||
highlightSpecialChars(),
|
||||
history(),
|
||||
drawSelection(),
|
||||
dropCursor(),
|
||||
closeBrackets(),
|
||||
|
||||
// Theme — minimal chrome, no gutters
|
||||
chatInputTheme,
|
||||
|
||||
// Placeholder
|
||||
placeholderExt(placeholderText),
|
||||
|
||||
// Auto-growing height
|
||||
autoHeight(maxHeight),
|
||||
|
||||
// Keymaps — chat-specific: Enter sends, Shift+Enter newline
|
||||
keymap.of([
|
||||
// Enter to submit (must be before defaultKeymap)
|
||||
{
|
||||
key: 'Enter',
|
||||
run: (view) => {
|
||||
if (onSubmit) {
|
||||
const text = view.state.doc.toString();
|
||||
onSubmit(text);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
},
|
||||
// Shift+Enter to insert newline
|
||||
{
|
||||
key: 'Shift-Enter',
|
||||
run: (view) => {
|
||||
view.dispatch(
|
||||
view.state.replaceSelection('\n')
|
||||
);
|
||||
return true;
|
||||
},
|
||||
},
|
||||
...closeBracketsKeymap,
|
||||
...defaultKeymap,
|
||||
...historyKeymap,
|
||||
]),
|
||||
];
|
||||
|
||||
// Change listener
|
||||
if (onChange) {
|
||||
extensions.push(EditorView.updateListener.of((update) => {
|
||||
if (update.docChanged) {
|
||||
onChange(update.state.doc.toString());
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
// Enable spell check and standard text input attributes
|
||||
extensions.push(
|
||||
EditorView.contentAttributes.of({
|
||||
spellcheck: 'true',
|
||||
autocapitalize: 'sentences',
|
||||
autocorrect: 'on',
|
||||
})
|
||||
);
|
||||
|
||||
// Single-line display mode: wrap long lines
|
||||
extensions.push(EditorView.lineWrapping);
|
||||
|
||||
const view = new EditorView({
|
||||
state: EditorState.create({ doc: '', extensions }),
|
||||
parent: target,
|
||||
});
|
||||
|
||||
// ── Public API ──────────────────────────
|
||||
|
||||
return {
|
||||
getValue() {
|
||||
return view.state.doc.toString();
|
||||
},
|
||||
|
||||
setValue(text) {
|
||||
view.dispatch({
|
||||
changes: { from: 0, to: view.state.doc.length, insert: text || '' },
|
||||
});
|
||||
},
|
||||
|
||||
focus() {
|
||||
view.focus();
|
||||
},
|
||||
|
||||
/** Access the underlying EditorView */
|
||||
getView() {
|
||||
return view;
|
||||
},
|
||||
|
||||
/** Clean up */
|
||||
destroy() {
|
||||
view.destroy();
|
||||
},
|
||||
};
|
||||
}
|
||||
241
src/editor/code-editor.mjs
Normal file
241
src/editor/code-editor.mjs
Normal file
@@ -0,0 +1,241 @@
|
||||
// ==========================================
|
||||
// Chat Switchboard — CM6 Code Editor Factory
|
||||
// ==========================================
|
||||
// Full-featured code editor: line numbers, bracket
|
||||
// matching, search/replace, auto-indent, syntax
|
||||
// highlighting. Used for extension editing and
|
||||
// future code surfaces.
|
||||
//
|
||||
// Usage:
|
||||
// const editor = CM.codeEditor(container, {
|
||||
// language: 'javascript',
|
||||
// value: 'const x = 1;',
|
||||
// lineNumbers: true,
|
||||
// darkMode: true,
|
||||
// readOnly: false,
|
||||
// keymap: 'standard', // 'standard' | 'vim' | 'emacs'
|
||||
// onChange: (value) => {},
|
||||
// });
|
||||
//
|
||||
// editor.getValue()
|
||||
// editor.setValue(str)
|
||||
// editor.focus()
|
||||
// editor.destroy()
|
||||
// ==========================================
|
||||
|
||||
import { EditorView, keymap, lineNumbers, highlightActiveLine,
|
||||
highlightActiveLineGutter, drawSelection, dropCursor,
|
||||
rectangularSelection, crosshairCursor,
|
||||
highlightSpecialChars, placeholder as placeholderExt } from '@codemirror/view';
|
||||
import { EditorState, Compartment } from '@codemirror/state';
|
||||
import { defaultKeymap, indentWithTab, history, historyKeymap,
|
||||
undo, redo } from '@codemirror/commands';
|
||||
import { bracketMatching, indentOnInput, foldGutter, foldKeymap,
|
||||
syntaxHighlighting, defaultHighlightStyle,
|
||||
HighlightStyle } from '@codemirror/language';
|
||||
import { highlightSelectionMatches, searchKeymap } from '@codemirror/search';
|
||||
import { autocompletion, completionKeymap, closeBrackets,
|
||||
closeBracketsKeymap } from '@codemirror/autocomplete';
|
||||
import { oneDark } from '@codemirror/theme-one-dark';
|
||||
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { json } from '@codemirror/lang-json';
|
||||
import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
|
||||
import { sql } from '@codemirror/lang-sql';
|
||||
import { html } from '@codemirror/lang-html';
|
||||
import { css } from '@codemirror/lang-css';
|
||||
import { yaml } from '@codemirror/lang-yaml';
|
||||
import { go } from '@codemirror/lang-go';
|
||||
import { python } from '@codemirror/lang-python';
|
||||
import { rust } from '@codemirror/lang-rust';
|
||||
|
||||
import { vim } from '@replit/codemirror-vim';
|
||||
import { emacs } from '@replit/codemirror-emacs';
|
||||
|
||||
import { switchboardTheme } from './theme.mjs';
|
||||
|
||||
// ── Language Registry ────────────────────────
|
||||
|
||||
const LANGUAGES = {
|
||||
javascript, js: javascript,
|
||||
json,
|
||||
markdown, md: markdown,
|
||||
sql,
|
||||
html,
|
||||
css,
|
||||
yaml, yml: yaml,
|
||||
go,
|
||||
python, py: python,
|
||||
rust, rs: rust,
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolve a language name to a CM6 LanguageSupport instance.
|
||||
* Returns null if the language isn't bundled.
|
||||
*/
|
||||
function resolveLanguage(name) {
|
||||
if (!name) return null;
|
||||
const factory = LANGUAGES[name.toLowerCase()];
|
||||
return factory ? factory() : null;
|
||||
}
|
||||
|
||||
// ── Code Editor Factory ─────────────────────
|
||||
|
||||
/**
|
||||
* Create a full-featured code editor.
|
||||
*
|
||||
* @param {HTMLElement} target - Container element (editor replaces its content)
|
||||
* @param {Object} opts
|
||||
* @param {string} [opts.language] - Language mode name
|
||||
* @param {string} [opts.value=''] - Initial content
|
||||
* @param {boolean} [opts.lineNumbers=true]
|
||||
* @param {boolean} [opts.darkMode] - Auto-detected from body if omitted
|
||||
* @param {boolean} [opts.readOnly=false]
|
||||
* @param {string} [opts.keymap='standard'] - 'standard' | 'vim' | 'emacs'
|
||||
* @param {string} [opts.placeholder] - Placeholder text
|
||||
* @param {Function} [opts.onChange] - Called with new value on every edit
|
||||
* @returns {{ getValue, setValue, focus, getView, destroy }}
|
||||
*/
|
||||
export function codeEditor(target, opts = {}) {
|
||||
const {
|
||||
language = null,
|
||||
value = '',
|
||||
lineNumbers: showLineNumbers = true,
|
||||
darkMode = document.documentElement.getAttribute('data-theme') !== 'light',
|
||||
readOnly = false,
|
||||
keymap: keymapMode = 'standard',
|
||||
placeholder: placeholderText = '',
|
||||
onChange = null,
|
||||
} = opts;
|
||||
|
||||
// Compartments for reconfigurable extensions
|
||||
const langCompartment = new Compartment();
|
||||
const keymapCompartment = new Compartment();
|
||||
const readOnlyCompartment = new Compartment();
|
||||
const themeCompartment = new Compartment();
|
||||
|
||||
// Build extensions list
|
||||
const extensions = [
|
||||
// Base editing
|
||||
highlightSpecialChars(),
|
||||
history(),
|
||||
drawSelection(),
|
||||
dropCursor(),
|
||||
EditorState.allowMultipleSelections.of(true),
|
||||
indentOnInput(),
|
||||
bracketMatching(),
|
||||
closeBrackets(),
|
||||
autocompletion(),
|
||||
rectangularSelection(),
|
||||
crosshairCursor(),
|
||||
highlightActiveLine(),
|
||||
highlightSelectionMatches(),
|
||||
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
|
||||
|
||||
// Keymaps
|
||||
keymap.of([
|
||||
...closeBracketsKeymap,
|
||||
...defaultKeymap,
|
||||
...searchKeymap,
|
||||
...historyKeymap,
|
||||
...foldKeymap,
|
||||
...completionKeymap,
|
||||
indentWithTab,
|
||||
]),
|
||||
|
||||
// Theme
|
||||
switchboardTheme,
|
||||
themeCompartment.of(darkMode ? [oneDark] : []),
|
||||
|
||||
// Configurable compartments
|
||||
langCompartment.of(resolveLanguage(language) || []),
|
||||
keymapCompartment.of(_resolveKeymapExt(keymapMode)),
|
||||
readOnlyCompartment.of(EditorState.readOnly.of(readOnly)),
|
||||
|
||||
// Optional features
|
||||
...(showLineNumbers ? [lineNumbers(), highlightActiveLineGutter(), foldGutter()] : []),
|
||||
...(placeholderText ? [placeholderExt(placeholderText)] : []),
|
||||
];
|
||||
|
||||
// Change listener
|
||||
if (onChange) {
|
||||
extensions.push(EditorView.updateListener.of((update) => {
|
||||
if (update.docChanged) {
|
||||
onChange(update.state.doc.toString());
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
// Create the editor
|
||||
const view = new EditorView({
|
||||
state: EditorState.create({ doc: value, extensions }),
|
||||
parent: target,
|
||||
});
|
||||
|
||||
// ── Public API ──────────────────────────
|
||||
|
||||
return {
|
||||
getValue() {
|
||||
return view.state.doc.toString();
|
||||
},
|
||||
|
||||
setValue(text) {
|
||||
view.dispatch({
|
||||
changes: { from: 0, to: view.state.doc.length, insert: text },
|
||||
});
|
||||
},
|
||||
|
||||
focus() {
|
||||
view.focus();
|
||||
},
|
||||
|
||||
/** Access the underlying EditorView for advanced use */
|
||||
getView() {
|
||||
return view;
|
||||
},
|
||||
|
||||
/** Switch language mode at runtime */
|
||||
setLanguage(name) {
|
||||
const lang = resolveLanguage(name);
|
||||
view.dispatch({
|
||||
effects: langCompartment.reconfigure(lang || []),
|
||||
});
|
||||
},
|
||||
|
||||
/** Switch keymap mode at runtime */
|
||||
setKeymap(mode) {
|
||||
view.dispatch({
|
||||
effects: keymapCompartment.reconfigure(_resolveKeymapExt(mode)),
|
||||
});
|
||||
},
|
||||
|
||||
/** Toggle read-only state */
|
||||
setReadOnly(ro) {
|
||||
view.dispatch({
|
||||
effects: readOnlyCompartment.reconfigure(
|
||||
EditorState.readOnly.of(ro)
|
||||
),
|
||||
});
|
||||
},
|
||||
|
||||
/** Toggle dark/light mode (controls oneDark syntax theme) */
|
||||
setDarkMode(dark) {
|
||||
view.dispatch({
|
||||
effects: themeCompartment.reconfigure(dark ? [oneDark] : []),
|
||||
});
|
||||
},
|
||||
|
||||
/** Clean up — removes the editor from the DOM */
|
||||
destroy() {
|
||||
view.destroy();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// ── Keybinding Resolution ───────────────────
|
||||
|
||||
function _resolveKeymapExt(mode) {
|
||||
if (mode === 'vim') return vim();
|
||||
if (mode === 'emacs') return emacs();
|
||||
return [];
|
||||
}
|
||||
61
src/editor/index.mjs
Normal file
61
src/editor/index.mjs
Normal file
@@ -0,0 +1,61 @@
|
||||
// ==========================================
|
||||
// Chat Switchboard — CM6 Bundle Entrypoint
|
||||
// ==========================================
|
||||
// esbuild compiles this into an IIFE that sets
|
||||
// window.CM with factory functions and utilities.
|
||||
//
|
||||
// Consumers:
|
||||
// if (window.CM) {
|
||||
// const editor = CM.chatInput(el, opts);
|
||||
// const code = CM.codeEditor(el, opts);
|
||||
// }
|
||||
// ==========================================
|
||||
|
||||
import { EditorView } from '@codemirror/view';
|
||||
import { EditorState } from '@codemirror/state';
|
||||
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { json } from '@codemirror/lang-json';
|
||||
import { markdown } from '@codemirror/lang-markdown';
|
||||
import { sql } from '@codemirror/lang-sql';
|
||||
import { html } from '@codemirror/lang-html';
|
||||
import { css } from '@codemirror/lang-css';
|
||||
import { yaml } from '@codemirror/lang-yaml';
|
||||
import { go } from '@codemirror/lang-go';
|
||||
import { python } from '@codemirror/lang-python';
|
||||
import { rust } from '@codemirror/lang-rust';
|
||||
|
||||
import { vim } from '@replit/codemirror-vim';
|
||||
import { emacs } from '@replit/codemirror-emacs';
|
||||
|
||||
import { codeEditor } from './code-editor.mjs';
|
||||
import { chatInput } from './chat-input.mjs';
|
||||
|
||||
// ── Expose on window ────────────────────────
|
||||
|
||||
window.CM = {
|
||||
// Version (injected by the build script or matched to app version)
|
||||
version: '0.17.2',
|
||||
|
||||
// Low-level access (for advanced use / debug console)
|
||||
EditorView,
|
||||
EditorState,
|
||||
|
||||
// Factory: full-featured code editor
|
||||
// CM.codeEditor(container, { language, value, lineNumbers, darkMode, keymap, onChange })
|
||||
codeEditor,
|
||||
|
||||
// Factory: chat message input (markdown mode, minimal chrome)
|
||||
// CM.chatInput(container, { placeholder, onSubmit, onChange, maxHeight, darkMode })
|
||||
chatInput,
|
||||
|
||||
// Bundled language modes (for reference / dynamic use)
|
||||
languages: {
|
||||
javascript, json, markdown, sql, html, css, yaml, go, python, rust,
|
||||
},
|
||||
|
||||
// Keybinding modes (for reference / settings UI)
|
||||
keybindings: { vim, emacs },
|
||||
};
|
||||
|
||||
console.log('[CM6] CodeMirror bundle loaded (v' + window.CM.version + ')');
|
||||
917
src/editor/package-lock.json
generated
Normal file
917
src/editor/package-lock.json
generated
Normal file
@@ -0,0 +1,917 @@
|
||||
{
|
||||
"name": "editor",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6",
|
||||
"@codemirror/commands": "^6",
|
||||
"@codemirror/lang-css": "^6",
|
||||
"@codemirror/lang-go": "^6",
|
||||
"@codemirror/lang-html": "^6",
|
||||
"@codemirror/lang-javascript": "^6",
|
||||
"@codemirror/lang-json": "^6",
|
||||
"@codemirror/lang-markdown": "^6",
|
||||
"@codemirror/lang-python": "^6",
|
||||
"@codemirror/lang-rust": "^6",
|
||||
"@codemirror/lang-sql": "^6",
|
||||
"@codemirror/lang-yaml": "^6",
|
||||
"@codemirror/language": "^6",
|
||||
"@codemirror/search": "^6",
|
||||
"@codemirror/state": "^6",
|
||||
"@codemirror/theme-one-dark": "^6",
|
||||
"@codemirror/view": "^6",
|
||||
"@replit/codemirror-emacs": "^6",
|
||||
"@replit/codemirror-vim": "^6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"esbuild": "^0.25.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/autocomplete": {
|
||||
"version": "6.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.0.tgz",
|
||||
"integrity": "sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.17.0",
|
||||
"@lezer/common": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/commands": {
|
||||
"version": "6.10.2",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.2.tgz",
|
||||
"integrity": "sha512-vvX1fsih9HledO1c9zdotZYUZnE4xV0m6i3m25s5DIfXofuprk6cRcLUZvSk3CASUbwjQX21tOGbkY2BH8TpnQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.4.0",
|
||||
"@codemirror/view": "^6.27.0",
|
||||
"@lezer/common": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-css": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz",
|
||||
"integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@lezer/common": "^1.0.2",
|
||||
"@lezer/css": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-go": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-go/-/lang-go-6.0.1.tgz",
|
||||
"integrity": "sha512-7fNvbyNylvqCphW9HD6WFnRpcDjr+KXX/FgqXy5H5ZS0eC5edDljukm/yNgYkwTsgp2busdod50AOTIy6Jikfg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/language": "^6.6.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@lezer/common": "^1.0.0",
|
||||
"@lezer/go": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-html": {
|
||||
"version": "6.4.11",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.11.tgz",
|
||||
"integrity": "sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/lang-css": "^6.0.0",
|
||||
"@codemirror/lang-javascript": "^6.0.0",
|
||||
"@codemirror/language": "^6.4.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.17.0",
|
||||
"@lezer/common": "^1.0.0",
|
||||
"@lezer/css": "^1.1.0",
|
||||
"@lezer/html": "^1.3.12"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-javascript": {
|
||||
"version": "6.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.4.tgz",
|
||||
"integrity": "sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/language": "^6.6.0",
|
||||
"@codemirror/lint": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.17.0",
|
||||
"@lezer/common": "^1.0.0",
|
||||
"@lezer/javascript": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-json": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.2.tgz",
|
||||
"integrity": "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@lezer/json": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-markdown": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-6.5.0.tgz",
|
||||
"integrity": "sha512-0K40bZ35jpHya6FriukbgaleaqzBLZfOh7HuzqbMxBXkbYMJDxfF39c23xOgxFezR+3G+tR2/Mup+Xk865OMvw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.7.1",
|
||||
"@codemirror/lang-html": "^6.0.0",
|
||||
"@codemirror/language": "^6.3.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
"@lezer/common": "^1.2.1",
|
||||
"@lezer/markdown": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-python": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-python/-/lang-python-6.2.1.tgz",
|
||||
"integrity": "sha512-IRjC8RUBhn9mGR9ywecNhB51yePWCGgvHfY1lWN/Mrp3cKuHr0isDKia+9HnvhiWNnMpbGhWrkhuWOc09exRyw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.3.2",
|
||||
"@codemirror/language": "^6.8.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@lezer/common": "^1.2.1",
|
||||
"@lezer/python": "^1.1.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-rust": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-rust/-/lang-rust-6.0.2.tgz",
|
||||
"integrity": "sha512-EZaGjCUegtiU7kSMvOfEZpaCReowEf3yNidYu7+vfuGTm9ow4mthAparY5hisJqOHmJowVH3Upu+eJlUji6qqA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@lezer/rust": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-sql": {
|
||||
"version": "6.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-sql/-/lang-sql-6.10.0.tgz",
|
||||
"integrity": "sha512-6ayPkEd/yRw0XKBx5uAiToSgGECo/GY2NoJIHXIIQh1EVwLuKoU8BP/qK0qH5NLXAbtJRLuT73hx7P9X34iO4w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-yaml": {
|
||||
"version": "6.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-yaml/-/lang-yaml-6.1.2.tgz",
|
||||
"integrity": "sha512-dxrfG8w5Ce/QbT7YID7mWZFKhdhsaTNOYjOkSIMt1qmC4VQnXSDSYVHHHn8k6kJUfIhtLo8t1JJgltlxWdsITw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.2.0",
|
||||
"@lezer/lr": "^1.0.0",
|
||||
"@lezer/yaml": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/language": {
|
||||
"version": "6.12.2",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.2.tgz",
|
||||
"integrity": "sha512-jEPmz2nGGDxhRTg3lTpzmIyGKxz3Gp3SJES4b0nAuE5SWQoKdT5GoQ69cwMmFd+wvFUhYirtDTr0/DRHpQAyWg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.23.0",
|
||||
"@lezer/common": "^1.5.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0",
|
||||
"style-mod": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lint": {
|
||||
"version": "6.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.4.tgz",
|
||||
"integrity": "sha512-ABc9vJ8DEmvOWuH26P3i8FpMWPQkduD9Rvba5iwb6O3hxASgclm3T3krGo8NASXkHCidz6b++LWlzWIUfEPSWw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.35.0",
|
||||
"crelt": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/search": {
|
||||
"version": "6.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.6.0.tgz",
|
||||
"integrity": "sha512-koFuNXcDvyyotWcgOnZGmY7LZqEOXZaaxD/j6n18TCLx2/9HieZJ5H6hs1g8FiRxBD0DNfs0nXn17g872RmYdw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.37.0",
|
||||
"crelt": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/state": {
|
||||
"version": "6.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.4.tgz",
|
||||
"integrity": "sha512-8y7xqG/hpB53l25CIoit9/ngxdfoG+fx+V3SHBrinnhOtLvKHRyAJJuHzkWrR4YXXLX8eXBsejgAAxHUOdW1yw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@marijn/find-cluster-break": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/theme-one-dark": {
|
||||
"version": "6.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz",
|
||||
"integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
"@lezer/highlight": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/view": {
|
||||
"version": "6.39.15",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.39.15.tgz",
|
||||
"integrity": "sha512-aCWjgweIIXLBHh7bY6cACvXuyrZ0xGafjQ2VInjp4RM4gMfscK5uESiNdrH0pE+e1lZr2B4ONGsjchl2KsKZzg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.5.0",
|
||||
"crelt": "^1.0.6",
|
||||
"style-mod": "^4.1.0",
|
||||
"w3c-keyname": "^2.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
|
||||
"integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
|
||||
"integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
|
||||
"integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
|
||||
"integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
|
||||
"integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
|
||||
"integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
|
||||
"integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
|
||||
"integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
|
||||
"integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
|
||||
"integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
|
||||
"integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
|
||||
"integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
|
||||
"integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
|
||||
"integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
|
||||
"integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
|
||||
"integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
|
||||
"integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
|
||||
"integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
|
||||
"integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
|
||||
"integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
|
||||
"integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openharmony-arm64": {
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
|
||||
"integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openharmony"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
|
||||
"integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
|
||||
"integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
|
||||
"integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
|
||||
"integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/common": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.1.tgz",
|
||||
"integrity": "sha512-6YRVG9vBkaY7p1IVxL4s44n5nUnaNnGM2/AckNgYOnxTG2kWh1vR8BMxPseWPjRNpb5VtXnMpeYAEAADoRV1Iw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@lezer/css": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.1.tgz",
|
||||
"integrity": "sha512-PYAKeUVBo3HFThruRyp/iK91SwiZJnzXh8QzkQlwijB5y+N5iB28+iLk78o2zmKqqV0uolNhCwFqB8LA7b0Svg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/go": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/go/-/go-1.0.1.tgz",
|
||||
"integrity": "sha512-xToRsYxwsgJNHTgNdStpcvmbVuKxTapV0dM0wey1geMMRc9aggoVyKgzYp41D2/vVOx+Ii4hmE206kvxIXBVXQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/highlight": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz",
|
||||
"integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/html": {
|
||||
"version": "1.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.13.tgz",
|
||||
"integrity": "sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/javascript": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz",
|
||||
"integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.1.3",
|
||||
"@lezer/lr": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/json": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.3.tgz",
|
||||
"integrity": "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/lr": {
|
||||
"version": "1.4.8",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.8.tgz",
|
||||
"integrity": "sha512-bPWa0Pgx69ylNlMlPvBPryqeLYQjyJjqPx+Aupm5zydLIF3NE+6MMLT8Yi23Bd9cif9VS00aUebn+6fDIGBcDA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/markdown": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.6.3.tgz",
|
||||
"integrity": "sha512-jpGm5Ps+XErS+xA4urw7ogEGkeZOahVQF21Z6oECF0sj+2liwZopd2+I8uH5I/vZsRuuze3OxBREIANLf6KKUw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.5.0",
|
||||
"@lezer/highlight": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/python": {
|
||||
"version": "1.1.18",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/python/-/python-1.1.18.tgz",
|
||||
"integrity": "sha512-31FiUrU7z9+d/ElGQLJFXl+dKOdx0jALlP3KEOsGTex8mvj+SoE1FgItcHWK/axkxCHGUSpqIHt6JAWfWu9Rhg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/rust": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/rust/-/rust-1.0.2.tgz",
|
||||
"integrity": "sha512-Lz5sIPBdF2FUXcWeCu1//ojFAZqzTQNRga0aYv6dYXqJqPfMdCAI0NzajWUd4Xijj1IKJLtjoXRPMvTKWBcqKg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/yaml": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/yaml/-/yaml-1.0.4.tgz",
|
||||
"integrity": "sha512-2lrrHqxalACEbxIbsjhqGpSW8kWpUKuY6RHgnSAFZa6qK62wvnPxA8hGOwOoDbwHcOFs5M4o27mjGu+P7TvBmw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@marijn/find-cluster-break": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
|
||||
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@replit/codemirror-emacs": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@replit/codemirror-emacs/-/codemirror-emacs-6.1.0.tgz",
|
||||
"integrity": "sha512-74DITnht6Cs6sHg02PQ169IKb1XgtyhI9sLD0JeOFco6Ds18PT+dkD8+DgXBDokne9UIFKsBbKPnpFRAz60/Lw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.2",
|
||||
"@codemirror/commands": "^6.0.0",
|
||||
"@codemirror/search": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.1",
|
||||
"@codemirror/view": "^6.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@replit/codemirror-vim": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@replit/codemirror-vim/-/codemirror-vim-6.3.0.tgz",
|
||||
"integrity": "sha512-aTx931ULAMuJx6xLf7KQDOL7CxD+Sa05FktTDrtLaSy53uj01ll3Zf17JdKsriER248oS55GBzg0CfCTjEneAQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@codemirror/commands": "6.x.x",
|
||||
"@codemirror/language": "6.x.x",
|
||||
"@codemirror/search": "6.x.x",
|
||||
"@codemirror/state": "6.x.x",
|
||||
"@codemirror/view": "6.x.x"
|
||||
}
|
||||
},
|
||||
"node_modules/crelt": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
|
||||
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
|
||||
"integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.25.12",
|
||||
"@esbuild/android-arm": "0.25.12",
|
||||
"@esbuild/android-arm64": "0.25.12",
|
||||
"@esbuild/android-x64": "0.25.12",
|
||||
"@esbuild/darwin-arm64": "0.25.12",
|
||||
"@esbuild/darwin-x64": "0.25.12",
|
||||
"@esbuild/freebsd-arm64": "0.25.12",
|
||||
"@esbuild/freebsd-x64": "0.25.12",
|
||||
"@esbuild/linux-arm": "0.25.12",
|
||||
"@esbuild/linux-arm64": "0.25.12",
|
||||
"@esbuild/linux-ia32": "0.25.12",
|
||||
"@esbuild/linux-loong64": "0.25.12",
|
||||
"@esbuild/linux-mips64el": "0.25.12",
|
||||
"@esbuild/linux-ppc64": "0.25.12",
|
||||
"@esbuild/linux-riscv64": "0.25.12",
|
||||
"@esbuild/linux-s390x": "0.25.12",
|
||||
"@esbuild/linux-x64": "0.25.12",
|
||||
"@esbuild/netbsd-arm64": "0.25.12",
|
||||
"@esbuild/netbsd-x64": "0.25.12",
|
||||
"@esbuild/openbsd-arm64": "0.25.12",
|
||||
"@esbuild/openbsd-x64": "0.25.12",
|
||||
"@esbuild/openharmony-arm64": "0.25.12",
|
||||
"@esbuild/sunos-x64": "0.25.12",
|
||||
"@esbuild/win32-arm64": "0.25.12",
|
||||
"@esbuild/win32-ia32": "0.25.12",
|
||||
"@esbuild/win32-x64": "0.25.12"
|
||||
}
|
||||
},
|
||||
"node_modules/style-mod": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz",
|
||||
"integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/w3c-keyname": {
|
||||
"version": "2.2.8",
|
||||
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
||||
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
28
src/editor/package.json
Normal file
28
src/editor/package.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"esbuild": "^0.25.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6",
|
||||
"@codemirror/commands": "^6",
|
||||
"@codemirror/lang-css": "^6",
|
||||
"@codemirror/lang-go": "^6",
|
||||
"@codemirror/lang-html": "^6",
|
||||
"@codemirror/lang-javascript": "^6",
|
||||
"@codemirror/lang-json": "^6",
|
||||
"@codemirror/lang-markdown": "^6",
|
||||
"@codemirror/lang-python": "^6",
|
||||
"@codemirror/lang-rust": "^6",
|
||||
"@codemirror/lang-sql": "^6",
|
||||
"@codemirror/lang-yaml": "^6",
|
||||
"@codemirror/language": "^6",
|
||||
"@codemirror/search": "^6",
|
||||
"@codemirror/state": "^6",
|
||||
"@codemirror/theme-one-dark": "^6",
|
||||
"@codemirror/view": "^6",
|
||||
"@replit/codemirror-vim": "^6",
|
||||
"@replit/codemirror-emacs": "^6"
|
||||
}
|
||||
}
|
||||
186
src/editor/theme.mjs
Normal file
186
src/editor/theme.mjs
Normal file
@@ -0,0 +1,186 @@
|
||||
// ==========================================
|
||||
// Chat Switchboard — CM6 Theme
|
||||
// ==========================================
|
||||
// Maps the app's CSS custom properties to CM6
|
||||
// theme slots. Works in both light and dark mode.
|
||||
//
|
||||
// The theme is applied via EditorView.theme()
|
||||
// which generates scoped CSS classes. Dark mode
|
||||
// detection uses body.dark-theme (existing pattern).
|
||||
// ==========================================
|
||||
|
||||
import { EditorView } from '@codemirror/view';
|
||||
|
||||
/**
|
||||
* Switchboard base theme — uses CSS variables so it
|
||||
* adapts automatically when the theme toggle fires.
|
||||
*/
|
||||
export const switchboardTheme = EditorView.theme({
|
||||
'&': {
|
||||
backgroundColor: 'var(--bg-surface, var(--bg))',
|
||||
color: 'var(--text)',
|
||||
fontSize: '14px',
|
||||
},
|
||||
'.cm-content': {
|
||||
fontFamily: 'var(--mono, monospace)',
|
||||
caretColor: 'var(--accent)',
|
||||
padding: '4px 0',
|
||||
},
|
||||
'&.cm-focused': {
|
||||
outline: 'none',
|
||||
},
|
||||
'.cm-cursor, .cm-dropCursor': {
|
||||
borderLeftColor: 'var(--accent)',
|
||||
borderLeftWidth: '2px',
|
||||
},
|
||||
'&.cm-focused .cm-selectionBackground, .cm-selectionBackground': {
|
||||
backgroundColor: 'var(--accent-muted, rgba(99, 102, 241, 0.2))',
|
||||
},
|
||||
'.cm-activeLine': {
|
||||
backgroundColor: 'var(--bg-hover, rgba(255, 255, 255, 0.03))',
|
||||
},
|
||||
'.cm-gutters': {
|
||||
backgroundColor: 'var(--bg-2, var(--bg))',
|
||||
color: 'var(--text-3, #666)',
|
||||
borderRight: '1px solid var(--border)',
|
||||
minWidth: '40px',
|
||||
},
|
||||
'.cm-activeLineGutter': {
|
||||
backgroundColor: 'var(--bg-hover, rgba(255, 255, 255, 0.05))',
|
||||
color: 'var(--text-2)',
|
||||
},
|
||||
'.cm-foldPlaceholder': {
|
||||
backgroundColor: 'var(--bg-2)',
|
||||
color: 'var(--text-3)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: '3px',
|
||||
padding: '0 4px',
|
||||
},
|
||||
// Search panel
|
||||
'.cm-panels': {
|
||||
backgroundColor: 'var(--bg-2)',
|
||||
color: 'var(--text)',
|
||||
borderBottom: '1px solid var(--border)',
|
||||
},
|
||||
'.cm-panels.cm-panels-top': {
|
||||
borderBottom: '1px solid var(--border)',
|
||||
},
|
||||
'.cm-panels.cm-panels-bottom': {
|
||||
borderTop: '1px solid var(--border)',
|
||||
},
|
||||
'.cm-search input, .cm-search button': {
|
||||
backgroundColor: 'var(--bg-surface)',
|
||||
color: 'var(--text)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--radius, 4px)',
|
||||
fontSize: '13px',
|
||||
},
|
||||
'.cm-search input:focus': {
|
||||
borderColor: 'var(--accent)',
|
||||
outline: 'none',
|
||||
},
|
||||
// Autocomplete
|
||||
'.cm-tooltip': {
|
||||
backgroundColor: 'var(--bg-2)',
|
||||
color: 'var(--text)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--radius, 4px)',
|
||||
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.3)',
|
||||
},
|
||||
'.cm-tooltip-autocomplete > ul > li': {
|
||||
padding: '4px 8px',
|
||||
},
|
||||
'.cm-tooltip-autocomplete > ul > li[aria-selected]': {
|
||||
backgroundColor: 'var(--accent)',
|
||||
color: '#fff',
|
||||
},
|
||||
// Matching brackets
|
||||
'.cm-matchingBracket': {
|
||||
backgroundColor: 'var(--accent-muted, rgba(99, 102, 241, 0.3))',
|
||||
outline: '1px solid var(--accent)',
|
||||
},
|
||||
'.cm-nonmatchingBracket': {
|
||||
color: 'var(--danger, #e74c3c)',
|
||||
},
|
||||
// Scrollbar
|
||||
'.cm-scroller': {
|
||||
fontFamily: 'var(--mono, monospace)',
|
||||
overflow: 'auto',
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Minimal theme for the chat input — removes gutters,
|
||||
* line numbers, and active line highlighting.
|
||||
*/
|
||||
export const chatInputTheme = EditorView.theme({
|
||||
'&': {
|
||||
backgroundColor: 'transparent',
|
||||
color: 'var(--text)',
|
||||
fontSize: 'var(--msg-font, 14px)',
|
||||
},
|
||||
'.cm-content': {
|
||||
fontFamily: 'var(--font, system-ui, sans-serif)',
|
||||
caretColor: 'var(--text)',
|
||||
padding: '0',
|
||||
minHeight: '20px',
|
||||
},
|
||||
'&.cm-focused': {
|
||||
outline: 'none',
|
||||
},
|
||||
// Cursor: high-contrast, always visible in both themes
|
||||
'.cm-cursor, .cm-dropCursor': {
|
||||
borderLeftColor: 'var(--text)',
|
||||
borderLeftWidth: '2px',
|
||||
},
|
||||
'&.cm-focused .cm-selectionBackground, .cm-selectionBackground': {
|
||||
backgroundColor: 'var(--accent-dim, rgba(99, 102, 241, 0.2))',
|
||||
},
|
||||
'.cm-line': {
|
||||
padding: '0',
|
||||
},
|
||||
'.cm-scroller': {
|
||||
fontFamily: 'var(--font, system-ui, sans-serif)',
|
||||
overflow: 'auto',
|
||||
},
|
||||
// Hide scrollbar but keep scrollable for auto-grow
|
||||
'.cm-scroller::-webkit-scrollbar': {
|
||||
display: 'none',
|
||||
},
|
||||
// Placeholder — vertically aligned with first line of input
|
||||
'.cm-placeholder': {
|
||||
color: 'var(--text-3, #888)',
|
||||
fontStyle: 'normal',
|
||||
lineHeight: 'inherit',
|
||||
pointerEvents: 'none',
|
||||
},
|
||||
// Tooltip/autocomplete inherits from base
|
||||
'.cm-tooltip': {
|
||||
backgroundColor: 'var(--bg-2)',
|
||||
color: 'var(--text)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--radius, 4px)',
|
||||
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.3)',
|
||||
},
|
||||
// ── Fenced code block decorations (WYSIWYG) ──
|
||||
// Lines inside fenced code blocks get a visual container
|
||||
'.cm-codeblock': {
|
||||
backgroundColor: 'var(--bg-surface, #18181b)',
|
||||
fontFamily: 'var(--mono, monospace)',
|
||||
fontSize: '13px',
|
||||
padding: '0 10px',
|
||||
borderLeft: '2px solid var(--accent, #6c9fff)',
|
||||
},
|
||||
'.cm-codeblock.cm-codeblock-open': {
|
||||
borderTopLeftRadius: 'var(--radius, 6px)',
|
||||
borderTopRightRadius: 'var(--radius, 6px)',
|
||||
paddingTop: '6px',
|
||||
color: 'var(--text-3, #6b6b7b)',
|
||||
},
|
||||
'.cm-codeblock.cm-codeblock-close': {
|
||||
borderBottomLeftRadius: 'var(--radius, 6px)',
|
||||
borderBottomRightRadius: 'var(--radius, 6px)',
|
||||
paddingBottom: '6px',
|
||||
color: 'var(--text-3, #6b6b7b)',
|
||||
},
|
||||
});
|
||||
@@ -172,7 +172,9 @@
|
||||
<div class="popup-menu kb-popup" id="kbPopup"></div>
|
||||
</div>
|
||||
</div>
|
||||
<textarea id="messageInput" placeholder="Send a message..." rows="1"></textarea>
|
||||
<div id="messageInputWrap">
|
||||
<textarea id="messageInput" placeholder="Send a message..." rows="1"></textarea>
|
||||
</div>
|
||||
<div class="input-actions">
|
||||
<button class="action-btn stop-btn" id="stopBtn" title="Stop">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="6" width="12" height="12" rx="2"/></svg>
|
||||
@@ -409,6 +411,26 @@
|
||||
</div>
|
||||
<!-- Appearance Tab -->
|
||||
<div class="settings-tab-content" id="settingsAppearanceTab" style="display:none">
|
||||
<section class="settings-section">
|
||||
<h3>Theme</h3>
|
||||
<div class="form-group">
|
||||
<label>Mode</label>
|
||||
<div class="theme-toggle" id="themeToggle">
|
||||
<button class="theme-btn" data-theme="light" title="Light">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>
|
||||
Light
|
||||
</button>
|
||||
<button class="theme-btn" data-theme="dark" title="Dark">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
|
||||
Dark
|
||||
</button>
|
||||
<button class="theme-btn" data-theme="system" title="System">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
|
||||
System
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="settings-section">
|
||||
<h3>Display</h3>
|
||||
<div class="form-group">
|
||||
@@ -422,6 +444,18 @@
|
||||
<div class="range-labels"><span>12px</span><span>20px</span></div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="settings-section">
|
||||
<h3>Editor</h3>
|
||||
<div class="form-group">
|
||||
<label>Keybindings</label>
|
||||
<div class="theme-toggle" id="keymapToggle">
|
||||
<button class="theme-btn" data-keymap="standard" title="Standard keybindings">Standard</button>
|
||||
<button class="theme-btn" data-keymap="vim" title="Vim keybindings">Vim</button>
|
||||
<button class="theme-btn" data-keymap="emacs" title="Emacs keybindings">Emacs</button>
|
||||
</div>
|
||||
<span class="form-hint">Applies to code editors only — chat input always uses standard.</span>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<!-- Providers Tab -->
|
||||
<div class="settings-tab-content" id="settingsProvidersTab" style="display:none">
|
||||
@@ -984,6 +1018,7 @@
|
||||
<!-- Vendor libs -->
|
||||
<script src="vendor/marked.min.js" onerror="this.onerror=null;this.src='https://cdnjs.cloudflare.com/ajax/libs/marked/16.3.0/lib/marked.umd.min.js'"></script>
|
||||
<script src="vendor/purify.min.js" onerror="this.onerror=null;this.src='https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.2.4/purify.min.js'"></script>
|
||||
<script src="vendor/codemirror/codemirror.bundle.js?v=%%APP_VERSION%%" onerror="console.warn('[CM6] Bundle not available — falling back to textarea')"></script>
|
||||
|
||||
<script src="js/debug.js?v=%%APP_VERSION%%"></script>
|
||||
<script src="js/events.js?v=%%APP_VERSION%%"></script>
|
||||
|
||||
@@ -693,9 +693,40 @@ async function deleteAdminExtension(id, name) {
|
||||
} catch (e) { UI.toast(e.message, 'error'); }
|
||||
}
|
||||
|
||||
// Track CM6 editor instances per extension edit form
|
||||
var _extEditors = {};
|
||||
|
||||
// Listen for theme changes to update CM6 editors
|
||||
if (typeof Events !== 'undefined') {
|
||||
Events.on('theme.changed', (data) => {
|
||||
const isDark = data.resolved === 'dark';
|
||||
for (const editors of Object.values(_extEditors)) {
|
||||
editors.manifest?.setDarkMode(isDark);
|
||||
editors.script?.setDarkMode(isDark);
|
||||
}
|
||||
});
|
||||
|
||||
// Listen for keymap changes to update CM6 editors in real time
|
||||
Events.on('keymap.changed', (data) => {
|
||||
const mode = data.mode || 'standard';
|
||||
for (const editors of Object.values(_extEditors)) {
|
||||
editors.manifest?.setKeymap(mode);
|
||||
editors.script?.setKeymap(mode);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function editAdminExtension(id) {
|
||||
// Close any existing edit form
|
||||
document.querySelectorAll('.ext-edit-form').forEach(el => el.remove());
|
||||
// Close any existing edit form (and destroy CM6 instances)
|
||||
document.querySelectorAll('.ext-edit-form').forEach(el => {
|
||||
const eid = el.dataset.extId;
|
||||
if (_extEditors[eid]) {
|
||||
_extEditors[eid].manifest?.destroy();
|
||||
_extEditors[eid].script?.destroy();
|
||||
delete _extEditors[eid];
|
||||
}
|
||||
el.remove();
|
||||
});
|
||||
|
||||
const ext = (UI._adminExtensions || []).find(e => e.id === id);
|
||||
if (!ext) return UI.toast('Extension not found', 'error');
|
||||
@@ -716,6 +747,15 @@ function editAdminExtension(id) {
|
||||
</div>`
|
||||
: '';
|
||||
|
||||
// Use container divs instead of textareas when CM6 is available
|
||||
const useCM6 = !!window.CM?.codeEditor;
|
||||
const manifestEditor = useCM6
|
||||
? `<div id="extEdit-manifest-${id}" style="width:100%;min-height:180px;border:1px solid var(--border);border-radius:var(--radius,4px);overflow:hidden"></div>`
|
||||
: `<textarea id="extEdit-manifest-${id}" rows="8" style="width:100%;font-family:var(--mono);font-size:12px;tab-size:2">${esc(manifestJSON)}</textarea>`;
|
||||
const scriptEditor = useCM6
|
||||
? `<div id="extEdit-script-${id}" style="width:100%;min-height:320px;border:1px solid var(--border);border-radius:var(--radius,4px);overflow:hidden"></div>`
|
||||
: `<textarea id="extEdit-script-${id}" rows="16" style="width:100%;font-family:var(--mono);font-size:12px;tab-size:2;white-space:pre">${esc(script)}</textarea>`;
|
||||
|
||||
const formHTML = `
|
||||
<div class="ext-edit-form" data-ext-id="${id}" style="border-top:1px solid var(--border);padding:12px 0;margin-top:8px">
|
||||
${systemWarning}
|
||||
@@ -729,14 +769,14 @@ function editAdminExtension(id) {
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom:8px">
|
||||
<label style="font-size:12px;font-weight:600">Manifest JSON <span class="text-muted">(without _script)</span></label>
|
||||
<textarea id="extEdit-manifest-${id}" rows="8" style="width:100%;font-family:var(--mono);font-size:12px;tab-size:2">${esc(manifestJSON)}</textarea>
|
||||
${manifestEditor}
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom:8px">
|
||||
<label style="font-size:12px;font-weight:600">Script <span class="text-muted">(${script.length.toLocaleString()} chars)</span></label>
|
||||
<textarea id="extEdit-script-${id}" rows="16" style="width:100%;font-family:var(--mono);font-size:12px;tab-size:2;white-space:pre">${esc(script)}</textarea>
|
||||
${scriptEditor}
|
||||
</div>
|
||||
<div style="display:flex;gap:8px;justify-content:flex-end">
|
||||
<button class="btn-small" onclick="this.closest('.ext-edit-form').remove()">Cancel</button>
|
||||
<button class="btn-small" onclick="_closeExtEditForm('${id}')">Cancel</button>
|
||||
<button class="btn-small btn-primary" onclick="saveAdminExtension('${id}')">Save Changes</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -748,42 +788,80 @@ function editAdminExtension(id) {
|
||||
if (row.querySelector(`[onclick*="editAdminExtension('${id}')"]`)) {
|
||||
row.insertAdjacentHTML('afterend', formHTML);
|
||||
|
||||
// Tab key inserts tab in script textarea
|
||||
const scriptEl = document.getElementById(`extEdit-script-${id}`);
|
||||
if (scriptEl) {
|
||||
scriptEl.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Tab') {
|
||||
e.preventDefault();
|
||||
const start = scriptEl.selectionStart;
|
||||
const end = scriptEl.selectionEnd;
|
||||
scriptEl.value = scriptEl.value.substring(0, start) + ' ' + scriptEl.value.substring(end);
|
||||
scriptEl.selectionStart = scriptEl.selectionEnd = start + 4;
|
||||
}
|
||||
});
|
||||
if (useCM6) {
|
||||
// Get user keybinding preference
|
||||
const prefs = JSON.parse(localStorage.getItem('cs-appearance') || '{}');
|
||||
const keymapMode = prefs.editorKeymap || 'standard';
|
||||
|
||||
// Initialize CM6 editors
|
||||
_extEditors[id] = {
|
||||
manifest: CM.codeEditor(document.getElementById(`extEdit-manifest-${id}`), {
|
||||
language: 'json',
|
||||
value: manifestJSON,
|
||||
lineNumbers: true,
|
||||
keymap: keymapMode,
|
||||
}),
|
||||
script: CM.codeEditor(document.getElementById(`extEdit-script-${id}`), {
|
||||
language: 'javascript',
|
||||
value: script,
|
||||
lineNumbers: true,
|
||||
keymap: keymapMode,
|
||||
}),
|
||||
};
|
||||
} else {
|
||||
// Fallback: Tab key inserts tab in script textarea
|
||||
const scriptEl = document.getElementById(`extEdit-script-${id}`);
|
||||
if (scriptEl) {
|
||||
scriptEl.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Tab') {
|
||||
e.preventDefault();
|
||||
const start = scriptEl.selectionStart;
|
||||
const end = scriptEl.selectionEnd;
|
||||
scriptEl.value = scriptEl.value.substring(0, start) + ' ' + scriptEl.value.substring(end);
|
||||
scriptEl.selectionStart = scriptEl.selectionEnd = start + 4;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _closeExtEditForm(id) {
|
||||
if (_extEditors[id]) {
|
||||
_extEditors[id].manifest?.destroy();
|
||||
_extEditors[id].script?.destroy();
|
||||
delete _extEditors[id];
|
||||
}
|
||||
document.querySelector(`.ext-edit-form[data-ext-id="${id}"]`)?.remove();
|
||||
}
|
||||
|
||||
async function saveAdminExtension(id) {
|
||||
const nameEl = document.getElementById(`extEdit-name-${id}`);
|
||||
const descEl = document.getElementById(`extEdit-desc-${id}`);
|
||||
const manifestEl = document.getElementById(`extEdit-manifest-${id}`);
|
||||
const scriptEl = document.getElementById(`extEdit-script-${id}`);
|
||||
if (!nameEl || !manifestEl || !scriptEl) return;
|
||||
|
||||
// Read from CM6 if available, otherwise from textarea
|
||||
const editors = _extEditors[id];
|
||||
const manifestText = editors?.manifest
|
||||
? editors.manifest.getValue()
|
||||
: document.getElementById(`extEdit-manifest-${id}`)?.value;
|
||||
const scriptText = editors?.script
|
||||
? editors.script.getValue()
|
||||
: document.getElementById(`extEdit-script-${id}`)?.value;
|
||||
|
||||
if (!nameEl || manifestText == null || scriptText == null) return;
|
||||
|
||||
// Parse manifest and merge _script back in
|
||||
let manifest;
|
||||
try {
|
||||
manifest = JSON.parse(manifestEl.value.trim() || '{}');
|
||||
manifest = JSON.parse((manifestText || '').trim() || '{}');
|
||||
} catch (e) {
|
||||
return UI.toast('Invalid manifest JSON: ' + e.message, 'error');
|
||||
}
|
||||
|
||||
const script = scriptEl.value;
|
||||
if (script.trim()) {
|
||||
manifest._script = script;
|
||||
if (scriptText.trim()) {
|
||||
manifest._script = scriptText;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -793,6 +871,7 @@ async function saveAdminExtension(id) {
|
||||
manifest: manifest,
|
||||
});
|
||||
UI.toast('Extension updated — reload page to apply changes');
|
||||
_closeExtEditForm(id);
|
||||
await UI.loadAdminExtensions();
|
||||
} catch (e) {
|
||||
UI.toast(e.message, 'error');
|
||||
|
||||
@@ -721,7 +721,6 @@ function initAttachments() {
|
||||
function _initAttachmentListeners() {
|
||||
const attachBtn = document.getElementById('attachBtn');
|
||||
const fileInput = document.getElementById('fileInput');
|
||||
const messageInput = document.getElementById('messageInput');
|
||||
const chatArea = document.getElementById('chatMessages');
|
||||
const lightbox = document.getElementById('lightbox');
|
||||
|
||||
@@ -741,8 +740,12 @@ function _initAttachmentListeners() {
|
||||
}
|
||||
|
||||
// ── Smart Paste ─────────────────────────
|
||||
if (messageInput) {
|
||||
messageInput.addEventListener('paste', (e) => {
|
||||
// Attach to the active input element (CM6 contentDOM or textarea)
|
||||
const pasteTarget = (typeof ChatInput !== 'undefined' && ChatInput.getDom())
|
||||
? ChatInput.getDom()
|
||||
: document.getElementById('messageInput');
|
||||
if (pasteTarget) {
|
||||
pasteTarget.addEventListener('paste', (e) => {
|
||||
if (!App.storageConfigured) return; // fall through to normal paste
|
||||
|
||||
const items = [...(e.clipboardData?.items || [])];
|
||||
|
||||
@@ -4,6 +4,75 @@
|
||||
// Chat management, send, stream, regenerate, edit, branch,
|
||||
// summarize, per-chat model persistence.
|
||||
|
||||
// ── Chat Input Abstraction ──────────────────
|
||||
// Wraps CM6 editor (when available) or fallback textarea.
|
||||
// All code that reads/writes the message input goes through this.
|
||||
|
||||
const ChatInput = {
|
||||
_editor: null, // CM6 instance, set during init
|
||||
_textarea: null, // fallback textarea element
|
||||
|
||||
getValue() {
|
||||
if (this._editor) return this._editor.getValue();
|
||||
return this._textarea?.value || '';
|
||||
},
|
||||
|
||||
setValue(text) {
|
||||
if (this._editor) {
|
||||
this._editor.setValue(text || '');
|
||||
} else if (this._textarea) {
|
||||
this._textarea.value = text || '';
|
||||
this._textarea.style.height = 'auto';
|
||||
}
|
||||
},
|
||||
|
||||
focus() {
|
||||
if (this._editor) this._editor.focus();
|
||||
else this._textarea?.focus();
|
||||
},
|
||||
|
||||
/** Get the DOM element (for paste listeners, etc.) */
|
||||
getDom() {
|
||||
if (this._editor) return this._editor.getView().contentDOM;
|
||||
return this._textarea;
|
||||
},
|
||||
|
||||
/** Get the wrapper DOM element (for resize, etc.) */
|
||||
getWrapDom() {
|
||||
if (this._editor) return this._editor.getView().dom;
|
||||
return this._textarea;
|
||||
},
|
||||
|
||||
/** Initialize — try CM6, fall back to textarea */
|
||||
init() {
|
||||
this._textarea = document.getElementById('messageInput');
|
||||
const wrap = document.getElementById('messageInputWrap');
|
||||
|
||||
if (window.CM?.chatInput && wrap) {
|
||||
// Hide the textarea, create CM6 editor in its place
|
||||
this._textarea.style.display = 'none';
|
||||
this._editor = CM.chatInput(wrap, {
|
||||
placeholder: 'Send a message...',
|
||||
onSubmit: () => sendMessage(),
|
||||
onChange: () => updateInputTokens(),
|
||||
maxHeight: 200,
|
||||
});
|
||||
DebugLog?.push?.('chat', '[CM6] Chat input initialized');
|
||||
} else {
|
||||
// Fallback: textarea with manual event handling
|
||||
DebugLog?.push?.('chat', '[CM6] Not available — using textarea fallback');
|
||||
this._textarea.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); }
|
||||
});
|
||||
this._textarea.addEventListener('input', function() {
|
||||
this.style.height = 'auto';
|
||||
this.style.height = Math.min(this.scrollHeight, 200) + 'px';
|
||||
updateInputTokens();
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// ── Summarize & Continue ──────────────────
|
||||
|
||||
async function summarizeAndContinue() {
|
||||
@@ -266,7 +335,7 @@ async function newChat() {
|
||||
Tokens._warningDismissed = false;
|
||||
updateContextWarning(); updateChatTokenCount();
|
||||
updateInputTokens();
|
||||
document.getElementById('messageInput').focus();
|
||||
ChatInput.focus();
|
||||
if (window.innerWidth <= 768) {
|
||||
document.getElementById('sidebar').classList.add('collapsed');
|
||||
const ov = document.getElementById('sidebarOverlay');
|
||||
@@ -377,15 +446,13 @@ function startRenameChat(chatId) {
|
||||
// ── Send Message ─────────────────────────────
|
||||
|
||||
async function sendMessage() {
|
||||
const input = document.getElementById('messageInput');
|
||||
const text = input.value.trim();
|
||||
const text = ChatInput.getValue().trim();
|
||||
const hasAttachments = hasStagedAttachments();
|
||||
|
||||
// Need text or attachments, not generating, not blocked by uploads
|
||||
if ((!text && !hasAttachments) || App.isGenerating || isSendBlocked()) return;
|
||||
|
||||
input.value = '';
|
||||
input.style.height = 'auto';
|
||||
ChatInput.setValue('');
|
||||
|
||||
// Snapshot attachment IDs before clearing staged state
|
||||
const attachmentIds = getStagedAttachmentIds();
|
||||
@@ -749,16 +816,8 @@ function _initChatListeners() {
|
||||
UI.toast(`Loaded ${visible} model${visible !== 1 ? 's' : ''}`, 'success');
|
||||
});
|
||||
|
||||
// Input
|
||||
const input = document.getElementById('messageInput');
|
||||
input.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); }
|
||||
});
|
||||
input.addEventListener('input', function() {
|
||||
this.style.height = 'auto';
|
||||
this.style.height = Math.min(this.scrollHeight, 200) + 'px';
|
||||
updateInputTokens();
|
||||
});
|
||||
// Input — CM6 or textarea fallback
|
||||
ChatInput.init();
|
||||
|
||||
// Close modals on overlay click
|
||||
document.querySelectorAll('.modal-overlay').forEach(overlay => {
|
||||
|
||||
@@ -272,6 +272,11 @@ const DebugLog = {
|
||||
snap.extensions = Extensions.debug();
|
||||
}
|
||||
|
||||
// CM6 editor state
|
||||
snap.cm6 = window.CM
|
||||
? { version: window.CM.version, languages: Object.keys(window.CM.languages || {}) }
|
||||
: { available: false };
|
||||
|
||||
return snap;
|
||||
},
|
||||
|
||||
|
||||
@@ -69,8 +69,7 @@ function updateInputTokens() {
|
||||
const el = document.getElementById('inputTokenCount');
|
||||
if (!el) return;
|
||||
|
||||
const input = document.getElementById('messageInput');
|
||||
const inputText = input?.value || '';
|
||||
const inputText = (typeof ChatInput !== 'undefined') ? ChatInput.getValue() : '';
|
||||
const inputTokens = Tokens.estimate(inputText);
|
||||
|
||||
if (!inputText.trim()) {
|
||||
|
||||
@@ -11,17 +11,30 @@ Object.assign(UI, {
|
||||
const prefs = JSON.parse(localStorage.getItem('cs-appearance') || '{}');
|
||||
const scale = prefs.scale || 100;
|
||||
const msgFont = prefs.msgFont || 14;
|
||||
const theme = prefs.theme || 'dark';
|
||||
const keymap = prefs.editorKeymap || 'standard';
|
||||
|
||||
const scaleEl = document.getElementById('settingsScale');
|
||||
const msgFontEl = document.getElementById('settingsMsgFont');
|
||||
if (scaleEl) { scaleEl.value = scale; document.getElementById('scaleValue').textContent = scale + '%'; }
|
||||
if (msgFontEl) { msgFontEl.value = msgFont; document.getElementById('msgFontValue').textContent = msgFont + 'px'; }
|
||||
|
||||
// Highlight active theme button
|
||||
document.querySelectorAll('#themeToggle .theme-btn').forEach(btn => {
|
||||
btn.classList.toggle('active', btn.dataset.theme === theme);
|
||||
});
|
||||
|
||||
// Highlight active keymap button
|
||||
document.querySelectorAll('#keymapToggle .theme-btn').forEach(btn => {
|
||||
btn.classList.toggle('active', btn.dataset.keymap === keymap);
|
||||
});
|
||||
},
|
||||
|
||||
initAppearance() {
|
||||
// Load saved prefs on startup
|
||||
const prefs = JSON.parse(localStorage.getItem('cs-appearance') || '{}');
|
||||
UI.applyAppearance(prefs.scale || 100, prefs.msgFont || 14);
|
||||
UI.applyTheme(prefs.theme || 'dark');
|
||||
|
||||
// Live preview on slider change
|
||||
const scaleEl = document.getElementById('settingsScale');
|
||||
@@ -36,6 +49,70 @@ Object.assign(UI, {
|
||||
document.getElementById('msgFontValue').textContent = v + 'px';
|
||||
UI.applyAppearance(parseInt(scaleEl?.value || 100), v);
|
||||
});
|
||||
|
||||
// Theme toggle buttons
|
||||
document.querySelectorAll('#themeToggle .theme-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const theme = btn.dataset.theme;
|
||||
UI.applyTheme(theme);
|
||||
document.querySelectorAll('#themeToggle .theme-btn').forEach(b =>
|
||||
b.classList.toggle('active', b === btn)
|
||||
);
|
||||
const p = JSON.parse(localStorage.getItem('cs-appearance') || '{}');
|
||||
p.theme = theme;
|
||||
localStorage.setItem('cs-appearance', JSON.stringify(p));
|
||||
});
|
||||
});
|
||||
|
||||
// Editor keymap toggle buttons
|
||||
document.querySelectorAll('#keymapToggle .theme-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const mode = btn.dataset.keymap;
|
||||
document.querySelectorAll('#keymapToggle .theme-btn').forEach(b =>
|
||||
b.classList.toggle('active', b === btn)
|
||||
);
|
||||
// Persist
|
||||
const p = JSON.parse(localStorage.getItem('cs-appearance') || '{}');
|
||||
p.editorKeymap = mode;
|
||||
localStorage.setItem('cs-appearance', JSON.stringify(p));
|
||||
// Notify open editors
|
||||
if (typeof Events !== 'undefined' && Events.emit) {
|
||||
Events.emit('keymap.changed', { mode });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// System theme change listener (for "system" mode)
|
||||
UI._systemThemeQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
UI._systemThemeQuery.addEventListener('change', () => {
|
||||
const p = JSON.parse(localStorage.getItem('cs-appearance') || '{}');
|
||||
if (p.theme === 'system') UI.applyTheme('system');
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Apply theme to the document.
|
||||
* @param {'light'|'dark'|'system'} mode
|
||||
*/
|
||||
applyTheme(mode) {
|
||||
let resolved = mode;
|
||||
if (mode === 'system') {
|
||||
resolved = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||
}
|
||||
if (resolved === 'light') {
|
||||
document.documentElement.setAttribute('data-theme', 'light');
|
||||
} else {
|
||||
document.documentElement.removeAttribute('data-theme');
|
||||
}
|
||||
// Publish event for CM6 editors and extensions
|
||||
if (typeof Events !== 'undefined' && Events.emit) {
|
||||
Events.emit('theme.changed', { mode, resolved });
|
||||
}
|
||||
},
|
||||
|
||||
/** Get the currently resolved theme ('dark' or 'light') */
|
||||
getResolvedTheme() {
|
||||
return document.documentElement.getAttribute('data-theme') === 'light' ? 'light' : 'dark';
|
||||
},
|
||||
|
||||
applyAppearance(scale, msgFont) {
|
||||
|
||||
@@ -68,11 +68,12 @@ self.addEventListener('activate', (event) => {
|
||||
self.addEventListener('fetch', (event) => {
|
||||
const url = new URL(event.request.url);
|
||||
|
||||
// Never cache API calls, WebSocket upgrades, branding, or extension assets
|
||||
// Never cache API calls, WebSocket upgrades, branding, extensions, or CM6 bundle
|
||||
if (url.pathname.includes('/api/') ||
|
||||
url.pathname.includes('/ws') ||
|
||||
url.pathname.includes('/branding/') ||
|
||||
url.pathname.includes('/extensions/') ||
|
||||
url.pathname.includes('/vendor/codemirror/') ||
|
||||
event.request.method !== 'GET') {
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user