···6868});
69697070async function render() {
7171- // Outer container
7272- let panel = document.createElement('div');
7373- panel.id = 'cmdPanel';
7474- panel.classList.add('cmdPanel');
7575-7676- await css(panel, {
7777- //border: '1px solid black',
7878- display: 'flex',
7979- flexDirection: 'column',
8080- justifyContent: 'center',
8181- margin: '0',
8282- padding: '0',
8383- height: '3rem',
8484- width: '20rem',
8585- paddingLeft: '1rem',
8686- paddingRight: '1rem',
7171+ // Get the command input element and results container
7272+ const commandInput = document.getElementById('command-input');
7373+ const resultsContainer = document.getElementById('results');
7474+7575+ // Set placeholder and focus the input
7676+ commandInput.placeholder = 'Start typing...';
7777+ commandInput.focus();
7878+7979+ // Add event listeners to the input
8080+ commandInput.addEventListener('keyup', onKeyup);
8181+ commandInput.addEventListener('keydown', (e) => {
8282+ // Allow arrows, tab, escape
8383+ if (!['ArrowUp', 'ArrowDown', 'Tab', 'Escape', 'Enter'].includes(e.key)) {
8484+ return; // Don't prevent default for normal typing
8585+ }
8686+ e.preventDefault(); // Prevent default for special keys
8787 });
8888-8989- // Where text is shown
9090- let input = document.createElement('div');
9191- input.id = 'cmdInput';
9292-9393- await css(input, {
9494- //border: '1px solid black',
9595- //overflow: 'hidden',
9696- whiteSpace: 'nowrap',
9797- fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif',
9898- fontSize: 'large'
8888+8989+ // Make sure the input stays focused
9090+ window.addEventListener('blur', () => {
9191+ setTimeout(() => commandInput.focus(), 10);
9992 });
100100-101101- panel.appendChild(input);
102102-103103- document.body.appendChild(panel);
104104-105105- updateInputUI('Start typing...')
106106-107107- // add event listeners
108108- document.addEventListener('keyup', onKeyup, true);
109109- document.addEventListener('keypress', onKeyDummyStop, true);
110110- document.addEventListener('keydown', onKeyDummyStop, true);
111111- document.addEventListener('input', onKeyDummyStop, true);
9393+9494+ window.addEventListener('focus', () => {
9595+ commandInput.focus();
9696+ });
9797+9898+ // Automatically focus the input when the window loads
9999+ setTimeout(() => {
100100+ commandInput.focus();
101101+ }, 100);
112102}
113103114104render();
···201191async function onKeyup(e) {
202192 // flag for logging
203193 const r = true;
204204-205205- e.preventDefault();
194194+195195+ // Get the command input element and results container
196196+ const commandInput = document.getElementById('command-input');
197197+ const resultsContainer = document.getElementById('results');
198198+199199+ // Use the input value as the typed text
200200+ state.typed = commandInput.value;
206201207202 if (isModifier(e)) {
208203 return;
209204 }
210205211211- r || console.log('onKeyup', e.key, e.which)
212212- r || console.log('hasModifier', hasModifier(e), 'isModifier', isModifier(e), 'isIgnorable', isIgnorable(e));
213213-214214- e.preventDefault();
215215-216206 // if user pressed escape, go away
217207 if (e.key == 'Escape' && !hasModifier(e)) {
218218- r || console.log('onKeyUp: escape!');
219208 await shutdown();
209209+ return;
220210 }
221211222212 // if user pressed return, attempt to execute command
223223- else if (e.key == 'Enter' && !hasModifier(e)) {
224224- r || console.log('onKeyUp: enter!', state.typed);
213213+ if (e.key == 'Enter' && !hasModifier(e)) {
225214 let name = state.matches[state.matchIndex];
226226- if (Object.keys(state.commands).indexOf(name) > -1) {
227227- //await shutdown();
215215+ if (name && Object.keys(state.commands).indexOf(name) > -1) {
228216 execute(name, state.typed);
229217 state.lastExecuted = name;
230218 updateMatchCount(name);
231219 updateMatchFeedback(state.typed, name);
220220+ commandInput.value = '';
232221 state.typed = '';
222222+ resultsContainer.innerHTML = '';
233223 }
224224+ return;
234225 }
235226236236- // attempt to complete typed characters to a command
237237- // or do other modifications based on user typed keys
238238- else if (!hasModifier(e) && !isModifier(e) && !isIgnorable(e)) {
239239- r || console.log('LEGIT... no modifier, is not a modifier and not ignorable')
240240-241241- // correct on backspace
242242- if (e.key == 'Backspace') {
243243- r || console.log('back', state.typed);
244244- if (state.typed.length > 0) {
245245- r || console.log('back, no typed tho');
246246- state.typed = state.typed.substring(0, state.typed.length - 1);
247247- }
248248- }
249249- // otherwise add typed character to buffer
250250- else {
251251- r || console.log('updating', e.key);
252252- state.typed += e.key
253253- }
254254-255255- // search, and update UI
256256- state.matches = findMatchingCommands(state.typed);
257257- if (state.matches.length) {
258258- r || console.log('matches!', state.matches);
259259- updateInputUI(state.typed, state.matches[0]);
260260- state.matchIndex = 0;
261261- }
262262- else {
263263- r || console.log('no matches for ', state.typed);
264264- updateInputUI(state.typed);
265265- }
227227+ // Handle up/down arrows for navigation
228228+ if (e.key == 'ArrowUp' && state.matchIndex > 0) {
229229+ state.matchIndex--;
230230+ updateResultsUI();
231231+ return;
266232 }
267233268268- // if up arrow and currently visible command is not first, select one previous
269269- else if (e.key == 'ArrowUp' && state.matchIndex) {
270270- r || console.log('onKeyUp: arrow up!');
271271- updateInputUI(state.typed, state.matches[--state.matchIndex]);
234234+ if (e.key == 'ArrowDown' && state.matchIndex + 1 < state.matches.length) {
235235+ state.matchIndex++;
236236+ updateResultsUI();
237237+ return;
272238 }
273239274274- // if down arrow and there are more matches, select the next one
275275- else if (e.key == 'ArrowDown' && state.matchIndex + 1 < state.matches.length) {
276276- r || console.log('onKeyUp: arrow down!');
277277- updateInputUI(state.typed, state.matches[++state.matchIndex]);
240240+ // Handle tab for autocompletion
241241+ if (e.key == 'Tab' && state.matches && state.matches.length > 0) {
242242+ commandInput.value = state.matches[state.matchIndex];
243243+ state.typed = state.matches[state.matchIndex];
244244+ return;
278245 }
279246280280- // Old behavior on tab:
281281- // tab -> shift to next result
282282- // shift + tab -> shift to previous result
283283- // New behavior on tab:
284284- // autocomplete to the matched command
285285- // which allows easy adding onto a command name
286286- // without having to type all the same visible text
287287- else if (e.key == 'Tab' && state.matches) {
288288- r || console.log('onKeyUp: tab!');
289289- state.typed = state.matches[state.matchIndex]
290290- updateInputUI(state.typed, state.matches[state.matchIndex]);
291291- //if (e.shiftKey && matchIndex)
292292- // updateInputUI(state.typed, state.matches[--state.matchIndex]);
293293- //else if (state.matchIndex + 1 < state.matches.length)
294294- // updateInputUI(state.typed, state.matches[++state.matchIndex]);
247247+ // Update matches based on typed text
248248+ state.matches = findMatchingCommands(state.typed);
249249+ state.matchIndex = 0;
250250+251251+ // Update the results UI
252252+ updateResultsUI();
253253+}
254254+255255+function updateResultsUI() {
256256+ const resultsContainer = document.getElementById('results');
257257+ resultsContainer.innerHTML = '';
258258+259259+ if (state.matches.length === 0) {
260260+ return;
295261 }
262262+263263+ // Create and append result items
264264+ state.matches.forEach((match, index) => {
265265+ const item = document.createElement('div');
266266+ item.className = 'command-item';
267267+ if (index === state.matchIndex) {
268268+ item.classList.add('selected');
269269+ }
270270+ item.textContent = match;
271271+272272+ // Add click handler
273273+ item.addEventListener('click', () => {
274274+ state.matchIndex = index;
275275+ execute(match, state.typed);
276276+ state.lastExecuted = match;
277277+ updateMatchCount(match);
278278+ updateMatchFeedback(state.typed, match);
279279+ document.getElementById('command-input').value = '';
280280+ state.typed = '';
281281+ resultsContainer.innerHTML = '';
282282+ });
283283+284284+ resultsContainer.appendChild(item);
285285+ });
296286}
297287298288function hasModifier(e) {
···333323 }
334324}
335325336336-function updateInputUI(typed, completed) {
337337- const r = true;
338338- r || console.log('updateInputUI', typed, completed);
339339- let str = ''
340340- if (completed) {
341341- str = generateUnderlined(typed, completed);
342342- }
343343- // no match
344344- else if (typed) {
345345- str = typed;
346346- }
347347-348348- let parser = new DOMParser();
349349- let doc = parser.parseFromString(str, 'text/html');
350350-351351- let input = document.querySelector('#cmdInput');
352352- if (input && input.firstElementChild)
353353- input.removeChild(input.firstElementChild);
354354- input.appendChild(doc.firstElementChild);
355355-356356- /*
357357- let parent = input.parentNode;
358358- state.matches.forEach(match => {
359359- let node = document.createElement('div')
360360- node.innerText = match
361361- parent.appendChild(node)
362362- });
363363- */
364364-}
365365-366366-// typed text, inline matching suggestion
367367-function generateUnderlined(typed, match) {
368368- const r = 1;
369369- r || console.log('generateUnderlined', typed, match);
370370- // user already matched a commmand and added params
371371- /*
372372- if (match.length > typed.length &&
373373- match.indexOf(typed) === 0) {
374374- return match;
375375- }
376376- */
377377-378378- if (typed.length == 0 || match.length == 0)
379379- return typed;
380380-381381- // look for typed within match
382382- var startIndex = match.toLowerCase().indexOf(typed.toLowerCase());
383383- if (startIndex == -1) {
384384- // otherwise look for match in typed
385385- // (why would this happen?!)
386386- startIndex = match.toLowerCase().indexOf(typed.toLowerCase());
387387- if (startIndex == -1) {
388388- return typed;
389389- }
390390- }
391391-392392- var endIndex = startIndex + match.length;
393393- r || console.log('startIndex', startIndex, 'endIndex', endIndex);
394394- var str = ''
395395-396396- // substring is empty
397397- if (!match) {
398398- r || console.log('no suggestion, so no underline')
399399- str = '<span>' + typed + '</span>'
400400- }
401401- // occurs at beginning
402402- else if (startIndex === 0) {
403403- r || console.log('start');
404404- str = '<span style="text-decoration: underline;">' + typed + '</span>' +
405405- '<span style="color: #6E6E6E;">' + match.substring(typed.length) + '</span>';
406406- }
407407- // occurs in middle
408408- else if (startIndex > 0) {
409409- r || console.log('middle');
410410- str = "<span style='color: #6E6E6E;'>" + match.substring(0, startIndex) + "</span>" +
411411- "<span style='text-decoration: underline;'>" + match.substring(startIndex, startIndex + typed.length) + "</span>" +
412412- "<span style='color: #6E6E6E;'>" + match.substring(startIndex + typed.length) + "</span>";
413413- }
414414- // occurs at the end
415415- else {
416416- r || console.log('end');
417417- str = "<span class='completed'>" + typed.substring(0, startIndex) + "</span>" +
418418- "<span class='typed'>" + match + "</span>";
419419- }
420420- return str;
421421-}
326326+// These functions are replaced by the new updateResultsUI function that
327327+// works with the actual HTML input field instead of custom rendering
+13-4
app/groups/index.js
···11import { id, labels, schemas, storageKeys, defaults } from './config.js';
22-import { openStore, openWindow } from "../utils.js";
22+import { openStore } from "../utils.js";
33+import windows from "../windows.js";
34import api from '../api.js';
4556console.log('background', labels.name);
···1617 const width = 800;
17181819 const params = {
1919- address,
2020 key: address,
2121 height,
2222- width
2222+ width,
2323+ // Not using modal so window stays open when clicking elsewhere
2424+ modal: false
2325 };
24262525- openWindow(address, params);
2727+ // Use the window creation API
2828+ windows.createWindow(address, params)
2929+ .then(window => {
3030+ console.log('Groups window opened:', window);
3131+ })
3232+ .catch(error => {
3333+ console.error('Failed to open groups window:', error);
3434+ });
2635};
27362837const initShortcut = shortcut => {
+43-28
app/index.js
···11import appConfig from './config.js';
22-import { openStore, openWindow } from "./utils.js";
22+import { openStore } from "./utils.js";
33+import windowManager from "./windows.js"; // Renamed to avoid naming collision
34import api from './api.js';
45import fc from './features.js';
56···21222223let _settingsWin = null;
23242424-const openSettingsWindow = (prefs) => {
2525+const openSettingsWindow = async (prefs) => {
2526 console.log('openSettingsWindow()');
26272727- /*
2828- // TODO: fuck, have to call main process to do this
2929- if (_settingsWin) {
3030- console.log('win exists, focusing');
3131- _settingsWin.focus();
3232- console.log('focused');
3333- return;
3434- }
3535- */
3636-3728 // Get screen dimensions from window object
3829 const screenWidth = window.screen.availWidth;
3930 const screenHeight = window.screen.availHeight;
···46374738 const params = {
4839 debug,
4949- address: settingsAddress,
5040 key: settingsAddress,
5141 transparent: true,
5242 height,
5353- width
4343+ width,
4444+ // Settings window should stay open when clicking elsewhere, so not modal
4545+ modal: false
5446 };
55475656- console.log('opening settings window', params);
5757- _settingsWin = openWindow(settingsAddress, params);
5858- console.log('opened settings window', _settingsWin);
4848+ console.log('Opening settings window with params:', params);
4949+5050+ try {
5151+ // Use the window creation API from windows.js
5252+ const windowController = await windowManager.createWindow(settingsAddress, params);
5353+5454+ console.log('Settings window opened successfully with controller:', windowController);
5555+ _settingsWin = windowController;
5656+5757+ // Focus the window to bring it to front
5858+ await windowController.focus();
5959+ } catch (error) {
6060+ console.error('Failed to open settings window:', error);
6161+ }
5962};
60636164const initSettingsShortcut = (prefs) => {
···110113const prefs = () => store.get(storageKeys.PREFS);
111114const features = () => store.get(storageKeys.ITEMS);
112115113113-const init = () => {
116116+const init = async () => {
114117 console.log('init');
115118116119 const p = prefs();
···127130 api.subscribe('open', msg => {
128131 // eg from the tray icon.
129132 if (msg.address && msg.address == settingsAddress) {
130130- openSettingsWindow(p);
133133+ openSettingsWindow(p).catch(err => {
134134+ console.error('Error opening settings window from open event:', err);
135135+ });
131136 }
132137 });
133138139139+ // Open settings window on startup if configured
134140 if (p.startupFeature == settingsAddress) {
135135- openSettingsWindow(p);
141141+ try {
142142+ await openSettingsWindow(p);
143143+ } catch (error) {
144144+ console.error('Error opening startup settings window:', error);
145145+ }
136146 }
137147138148 // feature enable/disable
···163173 //features.forEach(initIframeFeature);
164174165175 /*
176176+ // Example of using the new windows.js API:
166177 const addy = 'http://localhost';
167178 const params = {
168179 debug,
169169- address: addy,
170180 key: addy,
171181 height: 300,
172182 width: 300
173183 };
174184175175- const w = openWindow(addy, params);
176176-177177- api.subscribe('onWindowOpened', msg => {
178178- api.modifyWindow(params.key, {
179179- hide: true
185185+ windowManager.createWindow(addy, params)
186186+ .then(windowController => {
187187+ // Can use windowController to interact with the window
188188+ windowController.hide();
189189+ })
190190+ .catch(error => {
191191+ console.error('Error opening example window:', error);
180192 });
181181- });
182193 */
183194};
184195185185-window.addEventListener('load', init);
196196+window.addEventListener('load', () => {
197197+ init().catch(error => {
198198+ console.error('Error during application initialization:', error);
199199+ });
200200+});
186201187202/*
188203const odiff = (a, b) => Object.entries(b).reduce((c, [k, v]) => Object.assign(c, a[k] ? {} : { [k]: v }), {});
+18-5
app/peeks/index.js
···11import { id, labels, schemas, storageKeys, defaults } from './config.js';
22-import { openStore, openWindow } from "../utils.js";
22+import { openStore } from "../utils.js";
33+import windows from "../windows.js";
34import api from '../api.js';
4556console.log('background', labels.name);
···1011const store = openStore(id, defaults, clear /* clear storage */);
11121213const executeItem = (item) => {
1313- console.log('executeItem:slide', item);
1414+ console.log('executeItem:peek', item);
1415 const height = item.height || 600;
1516 const width = item.width || 800;
16171718 const params = {
1819 // browserwindow
1919- address: item.address,
2020 height,
2121 width,
22222323 // peek
2424 feature: labels.name,
2525 keepLive: item.keepLive || false,
2626- persistState: item.persistState || false
2626+ persistState: item.persistState || false,
2727+2828+ // Create a unique key for this peek using its address
2929+ key: `peek:${item.address}`,
3030+3131+ // Use modal behavior (closes on escape/blur)
3232+ modal: true
2733 };
28342929- openWindow(item.address, params);
3535+ // Use the modal window API for peeks
3636+ windows.openModalWindow(item.address, params)
3737+ .then(result => {
3838+ console.log('Peek window opened:', result);
3939+ })
4040+ .catch(error => {
4141+ console.error('Failed to open peek window:', error);
4242+ });
3043};
31443245const initItems = (prefs, items) => {
+20-4
app/scripts/index.js
···11import { id, labels, schemas, storageKeys, defaults } from './config.js';
22-import { openStore, openWindow } from "../utils.js";
22+import { openStore } from "../utils.js";
33+import windows from "../windows.js";
34import api from '../api.js';
4556console.log('background', labels.name);
···2122 `;
22232324 const params = {
2424- address: script.address,
2525 show: false,
2626 script: {
2727 script: str,
2828 domEvent: 'dom-ready',
2929 closeOnCompletion: true,
3030- }
3030+ },
3131+ // Make script windows hidden and auto-close
3232+ modal: false
3133 };
32343333- openWindow(script.address, params);
3535+ // For script windows, we use createWindow for more control
3636+ windows.createWindow(script.address, params)
3737+ .then(window => {
3838+ console.log('Script window opened and running');
3939+4040+ // Auto-close after execution
4141+ setTimeout(() => {
4242+ window.close().catch(err => {
4343+ console.error('Error closing script window:', err);
4444+ });
4545+ }, 5000); // Give it 5 seconds to execute
4646+ })
4747+ .catch(error => {
4848+ console.error('Failed to open script window:', error);
4949+ });
3450};
35513652const initItems = (prefs, items) => {
+1-1
app/slides/index.js
···11import { id, labels, schemas, storageKeys, defaults } from './config.js';
22-import { openStore, openWindow } from "../utils.js";
22+import { openStore } from "../utils.js";
33import windows from "../windows.js";
44import api from '../api.js';
55
+3-30
app/utils.js
···5151 return store;
5252};
53535454-const flattenObj = o => Object.keys(o).map(k => {
5555- // Make sure boolean values are properly converted to strings
5656- if (typeof o[k] === 'boolean') {
5757- return `${k}=${o[k]}`;
5858- }
5959- // For numbers and strings, just convert directly
6060- else {
6161- return `${k}=${o[k]}`;
6262- }
6363-}).join(',');
6464-6565-const openWindow = (address, params) => {
6666- const target = params.hasOwnProperty('key') ? params.key : '_blank';
6767-6868- // Log parameters to help with debugging
6969- console.log('openWindow called with params:', params);
7070-7171- if (window.app && window.app.window) {
7272- // Use the IPC window API if available (this goes through main process)
7373- console.log('Using window.app.window.open API');
7474- return window.app.window.open(address, params);
7575- } else {
7676- // Fall back to regular window.open if API not available
7777- console.log('Using regular window.open', flattenObj(params));
7878- return window.open(address, target, flattenObj(params));
7979- }
8080-};
5454+// Removed openWindow - use windows.js instead
5555+// The flattenObj helper is now private - it's only needed for window.open
81568257export {
8383- flattenObj,
8484- openStore,
8585- openWindow
5858+ openStore
8659};
+7-18
app/windows.js
···11-import { openStore, openWindow } from "./utils.js";
11+import { openStore } from "./utils.js";
22import api from './api.js';
33import fc from './features.js';
44···20202121 console.log('Opening modal window with params:', params);
22222323- // Prefer using the IPC API directly
2323+ // Always use the IPC API
2424 if (api.window && api.window.open) {
2525 return api.window.open(address, params);
2626 } else {
2727- return openWindow(address, params);
2727+ console.error('API window.open not available');
2828+ throw new Error('API window.open not available. Cannot open window.');
2829 }
2930};
3031···39404041 let windowId;
41424242- // Prefer using the IPC API directly
4343+ // Always use the IPC API
4344 if (api.window && api.window.open) {
4445 const result = await api.window.open(address, params);
4546 if (result.success) {
···4950 throw new Error(`Failed to open window: ${result.error}`);
5051 }
5152 } else {
5252- // Fallback to regular window.open
5353- const win = openWindow(address, params);
5454- return {
5555- window: win,
5656- close: () => {
5757- if (win) win.close();
5858- },
5959- hide: () => {
6060- if (win) win.close();
6161- },
6262- show: () => {
6363- // Can't re-open with this method
6464- }
6565- };
5353+ console.error('API window.open not available');
5454+ throw new Error('API window.open not available. Cannot open window.');
6655 }
67566857 // Return an API for the window