Alasvetovalikko kpn-terminaalin autocompletioniin
TAB avaa dropdown-valikon käytettävissä olevista vaihtoehdoista: - Nuolilla (ylös/alas) navigointi - Enter tai TAB valitsee korostetun vaihtoehdon - Esc sulkee valikon - Klikkaus valitsee suoraan - Yksi vaihtoehto → täydennetään suoraan ilman valikkoa Valikko näyttää kontekstin mukaan: alikomennot, mallit/agentit tai esimerkkiprompteja. Sulkeutuu automaattisesti kun klikataan muualle. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1105,10 +1105,11 @@
|
||||
</div>
|
||||
<div class="terminal-panel" id="agent-terminal" style="margin-top:0;border-top:none;border-radius:0">
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;background:#010409;border:1px solid var(--border-color);border-top:none;border-radius:0 0 6px 6px;padding:8px 12px;font-family:'Courier New',monospace;font-size:14px">
|
||||
<div style="position:relative;display:flex;align-items:center;background:#010409;border:1px solid var(--border-color);border-top:none;border-radius:0 0 6px 6px;padding:8px 12px;font-family:'Courier New',monospace;font-size:14px">
|
||||
<span style="color:#d29922;margin-right:8px;flex-shrink:0">$</span>
|
||||
<input id="term-input" type="text" placeholder="kpn run coder "kirjoita hello world"" spellcheck="false"
|
||||
<input id="term-input" type="text" placeholder="kpn run coder "kirjoita hello world"" spellcheck="false" autocomplete="off"
|
||||
style="flex:1;background:transparent;border:none;outline:none;color:var(--success-color);font-family:inherit;font-size:inherit">
|
||||
<div id="term-dropdown" style="display:none;position:absolute;bottom:100%;left:30px;background:#161b22;border:1px solid #30363d;border-radius:6px;max-height:200px;overflow-y:auto;font-size:13px;min-width:200px;z-index:100;box-shadow:0 4px 12px rgba(0,0,0,0.4)"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /panel-agents -->
|
||||
@@ -1982,13 +1983,105 @@
|
||||
return false;
|
||||
}
|
||||
|
||||
// Dropdown-autocompletionin tila
|
||||
const dropdown = document.getElementById('term-dropdown');
|
||||
let dropdownItems = [];
|
||||
let dropdownIdx = -1;
|
||||
let dropdownPrefix = ''; // Inputin alku joka säilyy valinnan yhteydessä
|
||||
|
||||
function getCandidates(val) {
|
||||
const words = val.trimEnd().split(/\s+/);
|
||||
for (let depth = words.length; depth >= 1; depth--) {
|
||||
const prefix = words.slice(0, depth).join(' ');
|
||||
const partial = words[depth] || '';
|
||||
// Esimerkkipromptit
|
||||
if (kpnExamples[prefix] && !partial) {
|
||||
return { items: kpnExamples[prefix], prefix: prefix + ' ' };
|
||||
}
|
||||
// Komennot
|
||||
const candidates = kpnCommands[prefix];
|
||||
if (candidates) {
|
||||
const matches = partial ? candidates.filter(c => c.startsWith(partial)) : candidates;
|
||||
if (matches.length > 0) {
|
||||
return { items: matches, prefix: prefix + ' ' };
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!val.trim()) return { items: kpnCommands['kpn'] || [], prefix: 'kpn ' };
|
||||
return { items: [], prefix: val };
|
||||
}
|
||||
|
||||
function showDropdown(items, prefix) {
|
||||
if (!dropdown || items.length === 0) { hideDropdown(); return; }
|
||||
dropdownItems = items;
|
||||
dropdownPrefix = prefix;
|
||||
dropdownIdx = -1;
|
||||
dropdown.innerHTML = items.map((item, i) =>
|
||||
`<div class="term-dd-item" data-idx="${i}" style="padding:6px 12px;cursor:pointer;color:#c9d1d9;white-space:nowrap;border-bottom:1px solid #21262d">${esc(item)}</div>`
|
||||
).join('');
|
||||
dropdown.style.display = 'block';
|
||||
|
||||
// Klikkaus-handlerit
|
||||
dropdown.querySelectorAll('.term-dd-item').forEach(el => {
|
||||
el.addEventListener('mouseenter', () => highlightDropdown(parseInt(el.dataset.idx)));
|
||||
el.addEventListener('click', () => { selectDropdown(); termInput.focus(); });
|
||||
});
|
||||
}
|
||||
|
||||
function hideDropdown() {
|
||||
if (dropdown) { dropdown.style.display = 'none'; dropdown.innerHTML = ''; }
|
||||
dropdownItems = [];
|
||||
dropdownIdx = -1;
|
||||
}
|
||||
|
||||
function highlightDropdown(idx) {
|
||||
dropdownIdx = idx;
|
||||
dropdown.querySelectorAll('.term-dd-item').forEach((el, i) => {
|
||||
el.style.background = i === idx ? '#30363d' : 'transparent';
|
||||
el.style.color = i === idx ? '#58a6ff' : '#c9d1d9';
|
||||
});
|
||||
// Varmistetaan näkyvyys
|
||||
const active = dropdown.children[idx];
|
||||
if (active) active.scrollIntoView({ block: 'nearest' });
|
||||
}
|
||||
|
||||
function selectDropdown() {
|
||||
if (dropdownIdx >= 0 && dropdownIdx < dropdownItems.length) {
|
||||
termInput.value = dropdownPrefix + dropdownItems[dropdownIdx] + (dropdownItems[dropdownIdx].startsWith('"') ? '' : ' ');
|
||||
}
|
||||
hideDropdown();
|
||||
}
|
||||
|
||||
termInput?.addEventListener('keydown', (e) => {
|
||||
// Dropdown auki: nuolet navigoi, Enter/Tab valitsee, Esc sulkee
|
||||
if (dropdown && dropdown.style.display === 'block') {
|
||||
if (e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
highlightDropdown(Math.min(dropdownIdx + 1, dropdownItems.length - 1));
|
||||
return;
|
||||
}
|
||||
if (e.key === 'ArrowUp') {
|
||||
e.preventDefault();
|
||||
highlightDropdown(Math.max(dropdownIdx - 1, 0));
|
||||
return;
|
||||
}
|
||||
if ((e.key === 'Enter' || e.key === 'Tab') && dropdownIdx >= 0) {
|
||||
e.preventDefault();
|
||||
selectDropdown();
|
||||
return;
|
||||
}
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
hideDropdown();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (e.key === 'Tab' && e.shiftKey) {
|
||||
// Shift-TAB: poista viimeinen sana (tai lainausmerkkien sisältö)
|
||||
e.preventDefault();
|
||||
hideDropdown();
|
||||
const val = termInput.value.trimEnd();
|
||||
if (!val) return;
|
||||
// Jos päättyy lainausmerkkeihin, poista koko lainattu osa
|
||||
const quoteMatch = val.match(/^(.+\s)".*"?$|^(.+\s)'.*'?$/);
|
||||
if (quoteMatch) {
|
||||
termInput.value = (quoteMatch[1] || quoteMatch[2]).trimEnd() + ' ';
|
||||
@@ -1998,18 +2091,26 @@
|
||||
}
|
||||
} else if (e.key === 'Tab') {
|
||||
e.preventDefault();
|
||||
tabComplete(termInput);
|
||||
// Näytä dropdown tai täydennä jos vain yksi vaihtoehto
|
||||
const { items, prefix } = getCandidates(termInput.value);
|
||||
if (items.length === 1) {
|
||||
termInput.value = prefix + items[0] + (items[0].startsWith('"') ? '' : ' ');
|
||||
hideDropdown();
|
||||
} else if (items.length > 1) {
|
||||
showDropdown(items, prefix);
|
||||
}
|
||||
} else if (e.key === 'Enter') {
|
||||
hideDropdown();
|
||||
const cmd = termInput.value.trim();
|
||||
if (cmd) termExec(cmd);
|
||||
termInput.value = '';
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
} else if (e.key === 'ArrowUp' && !dropdown?.style.display?.includes('block')) {
|
||||
e.preventDefault();
|
||||
if (termHistIdx < termHistory.length - 1) {
|
||||
termHistIdx++;
|
||||
termInput.value = termHistory[termHistIdx];
|
||||
}
|
||||
} else if (e.key === 'ArrowDown') {
|
||||
} else if (e.key === 'ArrowDown' && !dropdown?.style.display?.includes('block')) {
|
||||
e.preventDefault();
|
||||
if (termHistIdx > 0) {
|
||||
termHistIdx--;
|
||||
@@ -2021,6 +2122,11 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Suljetaan dropdown kun klikataan muualle
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!termInput?.contains(e.target) && !dropdown?.contains(e.target)) hideDropdown();
|
||||
});
|
||||
|
||||
// Klikkaa terminaalipaneelia → fokusoi input
|
||||
termPanel?.addEventListener('click', () => termInput?.focus());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user