koneck 2.38.0 → 2.38.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui-client.d.ts","sourceRoot":"","sources":["../../src/web/ui-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,IAAI,MAAM,
|
|
1
|
+
{"version":3,"file":"ui-client.d.ts","sourceRoot":"","sources":["../../src/web/ui-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,IAAI,MAAM,CA29FrC"}
|
package/dist/web/ui-client.js
CHANGED
|
@@ -724,6 +724,117 @@ function renderTreeInto(host, entries, depth) {
|
|
|
724
724
|
if (!entries.length) host.appendChild(el('div', 'shead', 'empty'));
|
|
725
725
|
}
|
|
726
726
|
|
|
727
|
+
/* ── colouring code ──────────────────────────────────────────────────────────────────────── */
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* Syntax colouring, as one pass over the text.
|
|
731
|
+
*
|
|
732
|
+
* Not a parser. A parser for a dozen languages is a project, and what makes code readable is
|
|
733
|
+
* separating the four things the eye needs first: what is prose (comments), what is data (strings
|
|
734
|
+
* and numbers), what is structure (keywords), and what is a name being used. A tokeniser that gets
|
|
735
|
+
* those right at a glance beats a parser that gets everything right next week.
|
|
736
|
+
*
|
|
737
|
+
* One regular expression, its alternatives ordered so the greedy ones win: a keyword inside a
|
|
738
|
+
* string is part of the string, and a string inside a comment is part of the comment. Getting that
|
|
739
|
+
* order wrong is how one apostrophe in a comment colours the next forty lines as text.
|
|
740
|
+
*/
|
|
741
|
+
const KEYWORDS = {
|
|
742
|
+
js: 'abstract as async await break case catch class const continue debugger declare default delete do else enum export extends finally for from function get if implements import in infer instanceof interface is keyof let new of package private protected public readonly return satisfies set static super switch symbol this throw try type typeof var void while with yield',
|
|
743
|
+
py: 'and as assert async await break class continue def del elif else except finally for from global if import in is lambda nonlocal not or pass raise return try while with yield match case',
|
|
744
|
+
go: 'break case chan const continue default defer else fallthrough for func go goto if import interface map package range return select struct switch type var',
|
|
745
|
+
rs: 'as async await break const continue crate dyn else enum extern fn for if impl in let loop match mod move mut pub ref return self static struct super trait type unsafe use where while',
|
|
746
|
+
sh: 'if then else elif fi for while do done case esac function return local export source alias unset trap exit',
|
|
747
|
+
css: 'important media supports keyframes import charset font-face root',
|
|
748
|
+
sql: 'select from where insert into values update set delete create table alter drop join left right inner outer on group by order having limit offset union as and or not null primary key foreign references index view',
|
|
749
|
+
};
|
|
750
|
+
const LITERALS = 'true false null undefined None True False nil NaN Infinity self this super void';
|
|
751
|
+
|
|
752
|
+
/** Which vocabulary a file is written in. */
|
|
753
|
+
function langOf(name) {
|
|
754
|
+
const ext = (String(name).match(/\.([a-z0-9]+)$/i) || [, ''])[1].toLowerCase();
|
|
755
|
+
if (/^(ts|tsx|js|jsx|mjs|cjs|mts|cts|json|jsonc)$/.test(ext)) return 'js';
|
|
756
|
+
if (/^(py|pyi)$/.test(ext)) return 'py';
|
|
757
|
+
if (ext === 'go') return 'go';
|
|
758
|
+
if (ext === 'rs') return 'rs';
|
|
759
|
+
if (/^(sh|bash|zsh|env)$/.test(ext)) return 'sh';
|
|
760
|
+
if (/^(dockerfile|makefile)/i.test(String(name).split('/').pop() || '')) return 'sh';
|
|
761
|
+
if (/^(css|scss|less)$/.test(ext)) return 'css';
|
|
762
|
+
if (ext === 'sql') return 'sql';
|
|
763
|
+
if (/^(html|htm|xml|svg|vue|svelte)$/.test(ext)) return 'html';
|
|
764
|
+
if (/^(yml|yaml|toml|ini|conf)$/.test(ext)) return 'yaml';
|
|
765
|
+
return 'plain';
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
const escHtml = (t) => t.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
769
|
+
|
|
770
|
+
/** The pattern for a language, built once and kept. */
|
|
771
|
+
const PATTERNS = {};
|
|
772
|
+
function patternFor(lang) {
|
|
773
|
+
if (PATTERNS[lang]) return PATTERNS[lang];
|
|
774
|
+
const hash = /^(py|sh|yaml)$/.test(lang);
|
|
775
|
+
// A backtick, built from its code point: a literal one would end the template this whole script
|
|
776
|
+
// lives in.
|
|
777
|
+
const T = String.fromCharCode(96);
|
|
778
|
+
const parts = [
|
|
779
|
+
// Comments first, longest form first, so nothing inside one is read again.
|
|
780
|
+
'(?<com>/\\*[\\s\\S]*?\\*/' + (hash ? '|#[^\\n]*' : '') + '|//[^\\n]*|<!--[\\s\\S]*?-->)',
|
|
781
|
+
// Then strings: double, single, Python triple, and template literals.
|
|
782
|
+
'(?<str>"""[\\s\\S]*?"""' +
|
|
783
|
+
"|'''[\\s\\S]*?'''" +
|
|
784
|
+
'|"(?:\\\\.|[^"\\\\\\n])*"' +
|
|
785
|
+
"|'(?:\\\\.|[^'\\\\\\n])*'" +
|
|
786
|
+
'|' + T + '(?:\\\\.|[^' + T + '\\\\])*' + T + ')',
|
|
787
|
+
'(?<num>\\b(?:0[xXbBoO][0-9a-fA-F_]+|\\d[\\d_]*(?:\\.[\\d_]+)?(?:[eE][+-]?\\d+)?)\\b)',
|
|
788
|
+
// A name followed by an opening bracket is being called.
|
|
789
|
+
'(?<fun>\\b[A-Za-z_$][\\w$]*(?=\\s*\\())',
|
|
790
|
+
// A capitalised name is a type or a class, near enough to be worth colouring as one.
|
|
791
|
+
'(?<typ>\\b[A-Z][A-Za-z0-9_$]*\\b)',
|
|
792
|
+
'(?<wrd>\\b[A-Za-z_$][\\w$-]*\\b)',
|
|
793
|
+
'(?<pun>[{}()\\[\\];:,.<>=+\\-*/%!?&|^~@#]+)',
|
|
794
|
+
];
|
|
795
|
+
const re = new RegExp(parts.join('|'), 'g');
|
|
796
|
+
const words = new Set(((KEYWORDS[lang] || '') + ' ' + LITERALS).split(/\s+/).filter(Boolean));
|
|
797
|
+
PATTERNS[lang] = { re: re, words: words };
|
|
798
|
+
return PATTERNS[lang];
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/**
|
|
802
|
+
* The text as coloured HTML.
|
|
803
|
+
*
|
|
804
|
+
* Line structure is preserved exactly. The caller lays this behind a textarea, and one swallowed
|
|
805
|
+
* newline would shift every colour below it onto the wrong line.
|
|
806
|
+
*/
|
|
807
|
+
function colourise(text, filename) {
|
|
808
|
+
const lang = langOf(filename);
|
|
809
|
+
if (lang === 'plain') return escHtml(text);
|
|
810
|
+
const spec = patternFor(lang);
|
|
811
|
+
const re = spec.re, words = spec.words;
|
|
812
|
+
let out = '';
|
|
813
|
+
let last = 0;
|
|
814
|
+
re.lastIndex = 0;
|
|
815
|
+
let m;
|
|
816
|
+
while ((m = re.exec(text)) !== null) {
|
|
817
|
+
if (m.index > last) out += escHtml(text.slice(last, m.index));
|
|
818
|
+
const g = m.groups || {};
|
|
819
|
+
const raw = m[0];
|
|
820
|
+
let cls = null;
|
|
821
|
+
if (g.com !== undefined) cls = 'tk-com';
|
|
822
|
+
else if (g.str !== undefined) cls = 'tk-str';
|
|
823
|
+
else if (g.num !== undefined) cls = 'tk-num';
|
|
824
|
+
else if (g.fun !== undefined) cls = words.has(raw) ? 'tk-key' : 'tk-fun';
|
|
825
|
+
else if (g.typ !== undefined) cls = words.has(raw) ? 'tk-key' : 'tk-typ';
|
|
826
|
+
else if (g.wrd !== undefined) cls = words.has(raw) ? 'tk-key' : null;
|
|
827
|
+
else if (g.pun !== undefined) cls = 'tk-pun';
|
|
828
|
+
out += cls ? '<span class="' + cls + '">' + escHtml(raw) + '</span>' : escHtml(raw);
|
|
829
|
+
last = m.index + raw.length;
|
|
830
|
+
// A zero-length match would spin for ever. Nothing here should make one, but a hung tab is
|
|
831
|
+
// unrecoverable and the guard costs nothing.
|
|
832
|
+
if (raw.length === 0) re.lastIndex++;
|
|
833
|
+
}
|
|
834
|
+
out += escHtml(text.slice(last));
|
|
835
|
+
return out;
|
|
836
|
+
}
|
|
837
|
+
|
|
727
838
|
/**
|
|
728
839
|
* A file, open and editable.
|
|
729
840
|
*
|
|
@@ -774,48 +885,110 @@ async function openFile(rel) {
|
|
|
774
885
|
const redo = el('button', 'ebtn', 'redo');
|
|
775
886
|
const save = el('button', 'ebtn save', 'save');
|
|
776
887
|
const revert = el('button', 'ebtn', 'revert');
|
|
777
|
-
const status = el('span', 'est', data.truncated ? 'read only
|
|
888
|
+
const status = el('span', 'est', data.truncated ? 'read only - opened truncated' : 'saved');
|
|
778
889
|
status.id = 'edstate';
|
|
779
890
|
for (const b of [undo, redo, save, revert]) b.type = 'button';
|
|
780
891
|
bar.appendChild(undo); bar.appendChild(redo); bar.appendChild(save); bar.appendChild(revert);
|
|
781
892
|
bar.appendChild(status);
|
|
782
893
|
host.appendChild(bar);
|
|
783
894
|
|
|
895
|
+
/*
|
|
896
|
+
* Three columns that must agree: numbers, colours, and the text you actually type into.
|
|
897
|
+
*
|
|
898
|
+
* The textarea is on top with transparent text and a visible caret; the coloured copy is a pre
|
|
899
|
+
* directly behind it; the numbers are a third column. All three share one font, size, line
|
|
900
|
+
* height and padding, and the textarea's scroll drives the other two — a pixel of difference
|
|
901
|
+
* anywhere and the colours sit on the wrong characters.
|
|
902
|
+
*/
|
|
903
|
+
const ed = el('div', 'ed');
|
|
904
|
+
const gut = el('div', 'egut');
|
|
905
|
+
const stack = el('div', 'estack');
|
|
906
|
+
const hl = document.createElement('pre');
|
|
907
|
+
hl.className = 'ehl';
|
|
908
|
+
hl.setAttribute('aria-hidden', 'true');
|
|
784
909
|
const area = document.createElement('textarea');
|
|
785
910
|
area.className = 'edit';
|
|
786
911
|
area.spellcheck = false;
|
|
787
912
|
area.value = data.text;
|
|
788
|
-
// A truncated file must never be saved: the editor only ever had the first part of it.
|
|
789
913
|
area.readOnly = data.truncated === true;
|
|
790
914
|
save.disabled = data.truncated === true;
|
|
791
|
-
|
|
915
|
+
stack.appendChild(hl);
|
|
916
|
+
stack.appendChild(area);
|
|
917
|
+
ed.appendChild(gut);
|
|
918
|
+
ed.appendChild(stack);
|
|
919
|
+
host.appendChild(ed);
|
|
920
|
+
|
|
921
|
+
/** Redraws the colours and the numbers for whatever the textarea now holds. */
|
|
922
|
+
const repaint = () => {
|
|
923
|
+
const text = area.value;
|
|
924
|
+
hl.innerHTML = colourise(text, data.path);
|
|
925
|
+
const count = text.split('\n').length;
|
|
926
|
+
// Rebuilt only when the count changes: typing inside a line must not rebuild a 900-row column
|
|
927
|
+
// on every keystroke.
|
|
928
|
+
if (gut.dataset.count !== String(count)) {
|
|
929
|
+
const numbers = [];
|
|
930
|
+
for (let i = 1; i <= count; i++) numbers.push(i);
|
|
931
|
+
gut.textContent = numbers.join('\n');
|
|
932
|
+
gut.dataset.count = String(count);
|
|
933
|
+
}
|
|
934
|
+
};
|
|
935
|
+
repaint();
|
|
936
|
+
|
|
937
|
+
// Colouring a large file on every keystroke is wasted work nobody sees; a frame's delay is
|
|
938
|
+
// invisible and keeps typing smooth in a 20,000-character file.
|
|
939
|
+
let pending = null;
|
|
940
|
+
const repaintSoon = () => {
|
|
941
|
+
if (pending) return;
|
|
942
|
+
pending = requestAnimationFrame(() => { pending = null; repaint(); });
|
|
943
|
+
};
|
|
792
944
|
|
|
945
|
+
const sync = () => {
|
|
946
|
+
hl.scrollTop = area.scrollTop;
|
|
947
|
+
hl.scrollLeft = area.scrollLeft;
|
|
948
|
+
gut.scrollTop = area.scrollTop;
|
|
949
|
+
};
|
|
950
|
+
area.addEventListener('scroll', sync);
|
|
793
951
|
area.addEventListener('input', () => {
|
|
794
952
|
editing.dirty = area.value !== editing.text;
|
|
795
953
|
editState(editing.dirty ? 'unsaved' : 'saved', editing.dirty ? 'dirty' : '');
|
|
954
|
+
repaintSoon();
|
|
955
|
+
sync();
|
|
796
956
|
});
|
|
797
|
-
|
|
957
|
+
|
|
958
|
+
// The browser's own undo, so every shortcut a person already knows works — including the ones
|
|
959
|
+
// nobody thinks about, and undo history that survives switching tabs.
|
|
798
960
|
undo.onclick = () => { area.focus(); document.execCommand('undo'); area.dispatchEvent(new Event('input')); };
|
|
799
961
|
redo.onclick = () => { area.focus(); document.execCommand('redo'); area.dispatchEvent(new Event('input')); };
|
|
800
962
|
revert.onclick = () => {
|
|
801
963
|
area.value = editing.text;
|
|
802
964
|
editing.dirty = false;
|
|
803
965
|
editState('saved');
|
|
966
|
+
repaint();
|
|
804
967
|
};
|
|
805
968
|
|
|
969
|
+
// Tab inserts a tab rather than leaving the editor, which is what it means in code.
|
|
970
|
+
area.addEventListener('keydown', (ev) => {
|
|
971
|
+
if (ev.key === 'Tab' && !ev.ctrlKey && !ev.metaKey) {
|
|
972
|
+
ev.preventDefault();
|
|
973
|
+
const at = area.selectionStart, to = area.selectionEnd;
|
|
974
|
+
area.setRangeText(' ', at, to, 'end');
|
|
975
|
+
area.dispatchEvent(new Event('input'));
|
|
976
|
+
}
|
|
977
|
+
});
|
|
978
|
+
|
|
806
979
|
const doSave = async () => {
|
|
807
980
|
if (area.readOnly) return;
|
|
808
|
-
editState('saving
|
|
981
|
+
editState('saving...');
|
|
809
982
|
let out;
|
|
810
983
|
try {
|
|
811
984
|
out = await api('/api/file', { method: 'POST', body: JSON.stringify({
|
|
812
985
|
path: editing.path, text: area.value, size: editing.size, mtimeMs: editing.mtimeMs }) });
|
|
813
986
|
} catch (e) {
|
|
814
987
|
// A refusal because the agent wrote the file while it was open is the one worth acting on,
|
|
815
|
-
// so it comes with the way through rather than
|
|
988
|
+
// so it comes with the way through rather than only a message.
|
|
816
989
|
editState(e.message.slice(0, 90), 'bad');
|
|
817
|
-
if (e.status === 409) {
|
|
818
|
-
const again = el('button', 'ebtn', 'reload from disk');
|
|
990
|
+
if (e.status === 409 && !bar.querySelector('.reload')) {
|
|
991
|
+
const again = el('button', 'ebtn reload', 'reload from disk');
|
|
819
992
|
again.type = 'button';
|
|
820
993
|
again.onclick = () => openFile(rel);
|
|
821
994
|
bar.insertBefore(again, status);
|
|
@@ -1945,6 +2118,10 @@ const abort = () => api('/api/abort', { method: 'POST',
|
|
|
1945
2118
|
$('stop').onclick = abort;
|
|
1946
2119
|
$('livestop').onclick = abort;
|
|
1947
2120
|
$('tab-chat').onclick = () => setView('chat');
|
|
2121
|
+
// Wired one by one, and these two were added to the tab bar and to setView but never here — so
|
|
2122
|
+
// they were painted, highlighted on hover, and did nothing at all when clicked.
|
|
2123
|
+
$('tab-terminal').onclick = () => setView('terminal');
|
|
2124
|
+
$('tab-preview').onclick = () => setView('preview');
|
|
1948
2125
|
$('tab-files').onclick = () => setView('files');
|
|
1949
2126
|
$('tab-search').onclick = () => setView('search');
|
|
1950
2127
|
$('tab-review').onclick = () => setView('review');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui-client.js","sourceRoot":"","sources":["../../src/web/ui-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,YAAY;IAC1B,OAAO,MAAM,CAAC,GAAG,CAAA
|
|
1
|
+
{"version":3,"file":"ui-client.js","sourceRoot":"","sources":["../../src/web/ui-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,YAAY;IAC1B,OAAO,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAy9FlB,CAAC;AACF,CAAC"}
|
package/dist/web/ui-css.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui-css.d.ts","sourceRoot":"","sources":["../../src/web/ui-css.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,wBAAgB,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"ui-css.d.ts","sourceRoot":"","sources":["../../src/web/ui-css.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,wBAAgB,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAqxB3C"}
|
package/dist/web/ui-css.js
CHANGED
|
@@ -22,6 +22,9 @@ export function css(markUrl) {
|
|
|
22
22
|
--add-bg:#e4f6ea; --add-ink:#0d5c37; --del-bg:#fdeaea; --del-ink:#8f1c22;
|
|
23
23
|
/* The region a tool is reading, and the exact text it matched inside that region. */
|
|
24
24
|
--attn-bg:#e8f4fb; --attn-hit:#bde4f7;
|
|
25
|
+
/* Syntax. Light theme. */
|
|
26
|
+
--syn-com:#6a7d8c; --syn-str:#0a6b4f; --syn-num:#9a4b00; --syn-key:#8a2be2;
|
|
27
|
+
--syn-typ:#0b6ca8; --syn-fun:#0a5ea8; --syn-pun:#6b7785;
|
|
25
28
|
--shadow:0 1px 2px rgba(15,28,34,.07), 0 8px 24px rgba(15,28,34,.05);
|
|
26
29
|
--radius:10px; --mono:ui-monospace,'SF Mono',Menlo,Consolas,'DejaVu Sans Mono',monospace;
|
|
27
30
|
--mark:url("${markUrl}");
|
|
@@ -34,7 +37,11 @@ export function css(markUrl) {
|
|
|
34
37
|
--violet:#b39cfb;
|
|
35
38
|
--add-bg:#0f2a1e; --add-ink:#7ee0a7; --del-bg:#2b1416; --del-ink:#f4a3a6;
|
|
36
39
|
--attn-bg:#10222c; --attn-hit:#1c3f52;
|
|
40
|
+
--syn-com:#6b8296; --syn-str:#7ee0a7; --syn-num:#f0b166; --syn-key:#c792ea;
|
|
41
|
+
--syn-typ:#63c8f0; --syn-fun:#82aaff; --syn-pun:#7a8899;
|
|
37
42
|
--attn-bg:#10222c; --attn-hit:#1c3f52;
|
|
43
|
+
--syn-com:#6b8296; --syn-str:#7ee0a7; --syn-num:#f0b166; --syn-key:#c792ea;
|
|
44
|
+
--syn-typ:#63c8f0; --syn-fun:#82aaff; --syn-pun:#7a8899;
|
|
38
45
|
--shadow:0 1px 2px rgba(0,0,0,.4), 0 8px 24px rgba(0,0,0,.3);
|
|
39
46
|
}
|
|
40
47
|
}
|
|
@@ -264,7 +271,9 @@ header{height:47px;flex:0 0 47px;border-bottom:1px solid var(--line);display:fle
|
|
|
264
271
|
.empty{text-align:center;padding:12vh 0;color:var(--dim);font-size:13px}
|
|
265
272
|
|
|
266
273
|
/* ── file tree ───────────────────────────────────────────── */
|
|
267
|
-
.
|
|
274
|
+
/* Like #chatview: the tab itself does not scroll, so the panes inside it can. */
|
|
275
|
+
#filesview{overflow:hidden;display:flex}
|
|
276
|
+
.split{display:flex;flex:1;min-height:0;min-width:0}
|
|
268
277
|
.tree{width:300px;flex:0 0 300px;border-right:1px solid var(--line);overflow-y:auto;padding:8px 0}
|
|
269
278
|
.tnode{display:flex;align-items:center;gap:6px;padding:3px 12px;width:100%;text-align:left;
|
|
270
279
|
font-size:12.5px;font-family:var(--mono);color:var(--muted);white-space:nowrap}
|
|
@@ -273,7 +282,7 @@ header{height:47px;flex:0 0 47px;border-bottom:1px solid var(--line);display:fle
|
|
|
273
282
|
.tnode .ic{width:13px;flex:0 0 13px;color:var(--dim);text-align:center}
|
|
274
283
|
.tnode.d .ic{color:var(--cyan)}
|
|
275
284
|
.tnode .sz{margin-left:auto;font-size:10.5px;color:var(--dim)}
|
|
276
|
-
.fileview{flex:1;min-width:0;overflow:
|
|
285
|
+
.fileview{flex:1;min-width:0;min-height:0;overflow:hidden;display:flex;flex-direction:column}
|
|
277
286
|
.fvhead{position:sticky;top:0;background:var(--panel);border-bottom:1px solid var(--line);
|
|
278
287
|
padding:8px 14px;font-family:var(--mono);font-size:12px;display:flex;gap:12px;align-items:center}
|
|
279
288
|
.fvhead .fsz{color:var(--dim);font-size:11px}
|
|
@@ -360,7 +369,16 @@ footer[hidden]{display:none}
|
|
|
360
369
|
background:var(--panel-2);border-radius:var(--radius);padding:9px 10px;
|
|
361
370
|
transition:border-color .14s,box-shadow .14s}
|
|
362
371
|
.cin:focus-within{border-color:var(--cyan);box-shadow:0 0 0 3px rgba(77,214,232,.13)}
|
|
363
|
-
|
|
372
|
+
/*
|
|
373
|
+
* The composer's textarea, scoped to the composer.
|
|
374
|
+
*
|
|
375
|
+
* This was a bare element selector, so its max-height applied to every textarea on the page —
|
|
376
|
+
* including the file editor, which is why a 21K file was shown 200 pixels tall with 570 pixels of
|
|
377
|
+
* empty panel under it. A broad element selector setting a property that a later class does not
|
|
378
|
+
* happen to override is the same trap as [hidden]: measured 766px of panel, 200px of editor, and
|
|
379
|
+
* flex-grow reading 1 the whole time.
|
|
380
|
+
*/
|
|
381
|
+
.cin textarea{flex:1;background:none;border:0;outline:0;resize:none;max-height:200px;
|
|
364
382
|
line-height:1.5;font-size:14px}
|
|
365
383
|
textarea::placeholder{color:var(--dim)}
|
|
366
384
|
.send{width:31px;height:31px;flex:0 0 31px;border-radius:50%;
|
|
@@ -724,10 +742,42 @@ body[data-tint="soft"]{filter:contrast(.92) brightness(1.04)}
|
|
|
724
742
|
.edbar .est{margin-left:auto;color:var(--dim);white-space:nowrap}
|
|
725
743
|
.edbar .est.dirty{color:var(--amber)}
|
|
726
744
|
.edbar .est.bad{color:var(--red)}
|
|
727
|
-
/*
|
|
728
|
-
.
|
|
729
|
-
|
|
730
|
-
|
|
745
|
+
/*
|
|
746
|
+
* An editor that is coloured and numbered, and still a real textarea.
|
|
747
|
+
*
|
|
748
|
+
* The textarea sits on top with transparent text and a visible caret; the coloured copy is a <pre>
|
|
749
|
+
* directly behind it, and the line numbers are a third column. All three share the same font,
|
|
750
|
+
* size, line height and padding, and the textarea's scroll drives the other two — if any of those
|
|
751
|
+
* differ by a pixel the colours drift off the characters they belong to.
|
|
752
|
+
*
|
|
753
|
+
* Done this way so undo and redo stay the browser's own. A contenteditable div with syntax colours
|
|
754
|
+
* would look the same and break every shortcut a person already knows.
|
|
755
|
+
*/
|
|
756
|
+
.ed{flex:1;display:flex;min-height:0;background:var(--bg)}
|
|
757
|
+
.ed .egut{flex:0 0 auto;overflow:hidden;padding:12px 10px 12px 14px;text-align:right;
|
|
758
|
+
color:var(--dim);background:var(--panel);border-right:1px solid var(--line);user-select:none;
|
|
759
|
+
font-family:var(--mono);font-size:12.5px;line-height:1.5;font-variant-numeric:tabular-nums;
|
|
760
|
+
white-space:pre}
|
|
761
|
+
.estack{position:relative;flex:1;min-width:0}
|
|
762
|
+
.ehl,.edit{position:absolute;inset:0;margin:0;padding:12px 14px;border:0;
|
|
763
|
+
font-family:var(--mono);font-size:12.5px;line-height:1.5;tab-size:2;
|
|
764
|
+
white-space:pre;overflow:auto;word-break:normal;overflow-wrap:normal}
|
|
765
|
+
.ehl{pointer-events:none;color:var(--ink)}
|
|
766
|
+
/* Transparent text over the coloured copy, with the caret and the selection still visible. */
|
|
767
|
+
.edit{background:transparent;color:transparent;caret-color:var(--cyan);resize:none;outline:none;
|
|
768
|
+
max-height:none}
|
|
769
|
+
.edit::selection{background:var(--attn-hit);color:transparent}
|
|
770
|
+
|
|
771
|
+
/* The colours. Chosen to read at 12.5px in both themes rather than to look like a screenshot. */
|
|
772
|
+
.tk-com{color:var(--syn-com);font-style:italic}
|
|
773
|
+
.tk-str{color:var(--syn-str)}
|
|
774
|
+
.tk-num{color:var(--syn-num)}
|
|
775
|
+
.tk-key{color:var(--syn-key)}
|
|
776
|
+
.tk-typ{color:var(--syn-typ)}
|
|
777
|
+
.tk-fun{color:var(--syn-fun)}
|
|
778
|
+
.tk-pun{color:var(--syn-pun)}
|
|
779
|
+
.tk-tag{color:var(--syn-key)}
|
|
780
|
+
.tk-att{color:var(--syn-fun)}
|
|
731
781
|
|
|
732
782
|
/* Carrying on from a transcript, offered in the bar that says it is read only. */
|
|
733
783
|
.readonly .rgap{flex:1}
|
package/dist/web/ui-css.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui-css.js","sourceRoot":"","sources":["../../src/web/ui-css.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,GAAG,CAAC,OAAe;IACjC,OAAO
|
|
1
|
+
{"version":3,"file":"ui-css.js","sourceRoot":"","sources":["../../src/web/ui-css.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,GAAG,CAAC,OAAe;IACjC,OAAO;;;;;;;;;;;;;;;gBAeO,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAowBtB,CAAC;AACF,CAAC"}
|