mani-calc 1.1.0
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.
- package/ARCHITECTURE.md +249 -0
- package/CHANGELOG.md +69 -0
- package/COMPLETION_SUMMARY.md +304 -0
- package/CONTRIBUTING.md +110 -0
- package/EXAMPLES.md +220 -0
- package/LICENSE +21 -0
- package/OVERLAY_MODE.md +361 -0
- package/PUBLISHING_GUIDE.md +291 -0
- package/QUICKSTART.md +115 -0
- package/QUICK_PUBLISH.md +108 -0
- package/README.md +614 -0
- package/RELEASE_NOTES_v1.1.0.md +262 -0
- package/bin/cli.js +203 -0
- package/bin/overlay.js +46 -0
- package/package.json +47 -0
- package/src/core/clipboard-manager.js +40 -0
- package/src/core/history-manager.js +141 -0
- package/src/core/math-engine.js +95 -0
- package/src/core/nlp-parser.js +146 -0
- package/src/core/unit-converter.js +208 -0
- package/src/index.js +121 -0
- package/src/integration/windows-search.js +126 -0
- package/src/ui/floating-search.js +228 -0
- package/src/ui/overlay.html +254 -0
- package/test/test.js +133 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { exec } = require('child_process');
|
|
4
|
+
const { promisify } = require('util');
|
|
5
|
+
|
|
6
|
+
const execAsync = promisify(exec);
|
|
7
|
+
|
|
8
|
+
class WindowsSearchIntegration {
|
|
9
|
+
constructor(maniCalc) {
|
|
10
|
+
this.maniCalc = maniCalc;
|
|
11
|
+
this.protocolName = 'calc';
|
|
12
|
+
this.appPath = process.execPath;
|
|
13
|
+
this.scriptPath = path.join(__dirname, '..', '..', 'bin', 'cli.js');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async register() {
|
|
17
|
+
try {
|
|
18
|
+
// Register protocol handler for Windows Search
|
|
19
|
+
await this.registerProtocolHandler();
|
|
20
|
+
|
|
21
|
+
// Create search connector
|
|
22
|
+
await this.createSearchConnector();
|
|
23
|
+
|
|
24
|
+
return true;
|
|
25
|
+
} catch (error) {
|
|
26
|
+
throw new Error(`Failed to register Windows Search integration: ${error.message}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async registerProtocolHandler() {
|
|
31
|
+
// Create registry entries for custom protocol
|
|
32
|
+
const regContent = `Windows Registry Editor Version 5.00
|
|
33
|
+
|
|
34
|
+
[HKEY_CURRENT_USER\\Software\\Classes\\calc]
|
|
35
|
+
@="URL:Mani-Calc Protocol"
|
|
36
|
+
"URL Protocol"=""
|
|
37
|
+
|
|
38
|
+
[HKEY_CURRENT_USER\\Software\\Classes\\calc\\shell]
|
|
39
|
+
|
|
40
|
+
[HKEY_CURRENT_USER\\Software\\Classes\\calc\\shell\\open]
|
|
41
|
+
|
|
42
|
+
[HKEY_CURRENT_USER\\Software\\Classes\\calc\\shell\\open\\command]
|
|
43
|
+
@="\\"${process.execPath}\\" \\"${this.scriptPath}\\" \\"%1\\""
|
|
44
|
+
`;
|
|
45
|
+
|
|
46
|
+
const regFile = path.join(process.env.TEMP, 'mani-calc-protocol.reg');
|
|
47
|
+
fs.writeFileSync(regFile, regContent);
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
await execAsync(`reg import "${regFile}"`);
|
|
51
|
+
fs.unlinkSync(regFile);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.warn('Could not register protocol handler automatically. Manual registration may be required.');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async createSearchConnector() {
|
|
58
|
+
const connectorPath = path.join(
|
|
59
|
+
process.env.APPDATA,
|
|
60
|
+
'Microsoft',
|
|
61
|
+
'Windows',
|
|
62
|
+
'Libraries',
|
|
63
|
+
'ManiCalc.searchConnector-ms'
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const connectorXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
67
|
+
<searchConnectorDescription xmlns="http://schemas.microsoft.com/windows/2009/searchConnector">
|
|
68
|
+
<description>Mani-Calc - Instant Calculator</description>
|
|
69
|
+
<isSearchOnlyItem>false</isSearchOnlyItem>
|
|
70
|
+
<includeInStartMenuScope>true</includeInStartMenuScope>
|
|
71
|
+
<iconReference>shell32.dll,23</iconReference>
|
|
72
|
+
<templateInfo>
|
|
73
|
+
<folderType>{5c4f28b5-f869-4e84-8e60-f11db97c5cc7}</folderType>
|
|
74
|
+
</templateInfo>
|
|
75
|
+
<simpleLocation>
|
|
76
|
+
<url>calc:</url>
|
|
77
|
+
</simpleLocation>
|
|
78
|
+
</searchConnectorDescription>`;
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
const dir = path.dirname(connectorPath);
|
|
82
|
+
if (!fs.existsSync(dir)) {
|
|
83
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
84
|
+
}
|
|
85
|
+
fs.writeFileSync(connectorPath, connectorXml);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.warn('Could not create search connector:', error.message);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async unregister() {
|
|
92
|
+
try {
|
|
93
|
+
// Remove protocol handler
|
|
94
|
+
await execAsync('reg delete "HKEY_CURRENT_USER\\Software\\Classes\\calc" /f').catch(() => { });
|
|
95
|
+
|
|
96
|
+
// Remove search connector
|
|
97
|
+
const connectorPath = path.join(
|
|
98
|
+
process.env.APPDATA,
|
|
99
|
+
'Microsoft',
|
|
100
|
+
'Windows',
|
|
101
|
+
'Libraries',
|
|
102
|
+
'ManiCalc.searchConnector-ms'
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
if (fs.existsSync(connectorPath)) {
|
|
106
|
+
fs.unlinkSync(connectorPath);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return true;
|
|
110
|
+
} catch (error) {
|
|
111
|
+
console.error('Failed to unregister:', error);
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
parseProtocolUrl(url) {
|
|
117
|
+
// Parse calc:query format
|
|
118
|
+
const match = url.match(/^calc:(.+)$/i);
|
|
119
|
+
if (match) {
|
|
120
|
+
return decodeURIComponent(match[1]);
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
module.exports = WindowsSearchIntegration;
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
const { app, BrowserWindow, globalShortcut, ipcMain } = require('electron');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
class FloatingSearchBox {
|
|
5
|
+
constructor(maniCalc) {
|
|
6
|
+
this.maniCalc = maniCalc;
|
|
7
|
+
this.window = null;
|
|
8
|
+
this.isVisible = false;
|
|
9
|
+
this.hotkey = 'Alt+Space'; // Default hotkey
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async initialize() {
|
|
13
|
+
// Wait for Electron app to be ready
|
|
14
|
+
if (!app.isReady()) {
|
|
15
|
+
await app.whenReady();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
this.createWindow();
|
|
19
|
+
this.registerHotkey();
|
|
20
|
+
this.setupIPC();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
createWindow() {
|
|
24
|
+
this.window = new BrowserWindow({
|
|
25
|
+
width: 600,
|
|
26
|
+
height: 80,
|
|
27
|
+
frame: false,
|
|
28
|
+
transparent: true,
|
|
29
|
+
alwaysOnTop: true,
|
|
30
|
+
skipTaskbar: true,
|
|
31
|
+
resizable: false,
|
|
32
|
+
show: false,
|
|
33
|
+
webPreferences: {
|
|
34
|
+
nodeIntegration: true,
|
|
35
|
+
contextIsolation: false
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Center the window
|
|
40
|
+
const { screen } = require('electron');
|
|
41
|
+
const display = screen.getPrimaryDisplay();
|
|
42
|
+
const { width, height } = display.workAreaSize;
|
|
43
|
+
const x = Math.floor((width - 600) / 2);
|
|
44
|
+
const y = Math.floor(height * 0.3); // 30% from top
|
|
45
|
+
|
|
46
|
+
this.window.setPosition(x, y);
|
|
47
|
+
|
|
48
|
+
// Load the HTML
|
|
49
|
+
this.window.loadFile(path.join(__dirname, 'overlay.html'));
|
|
50
|
+
|
|
51
|
+
// Hide when losing focus
|
|
52
|
+
this.window.on('blur', () => {
|
|
53
|
+
if (this.isVisible) {
|
|
54
|
+
this.hide();
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
registerHotkey() {
|
|
60
|
+
globalShortcut.register(this.hotkey, () => {
|
|
61
|
+
this.toggle();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
console.log(`✓ Hotkey registered: ${this.hotkey}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
setupIPC() {
|
|
68
|
+
// Handle query from renderer
|
|
69
|
+
ipcMain.on('process-query', async (event, query) => {
|
|
70
|
+
try {
|
|
71
|
+
// Check for system commands first
|
|
72
|
+
const systemResult = await this.handleSystemCommand(query);
|
|
73
|
+
if (systemResult) {
|
|
74
|
+
event.reply('query-result', systemResult);
|
|
75
|
+
setTimeout(() => this.hide(), 1000);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Process as calculation
|
|
80
|
+
const result = await this.maniCalc.processQuery(query);
|
|
81
|
+
event.reply('query-result', result);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
event.reply('query-result', {
|
|
84
|
+
error: error.message,
|
|
85
|
+
type: 'error'
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Handle hide request
|
|
91
|
+
ipcMain.on('hide-window', () => {
|
|
92
|
+
this.hide();
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async handleSystemCommand(query) {
|
|
97
|
+
const cmd = query.toLowerCase().trim();
|
|
98
|
+
const { exec } = require('child_process');
|
|
99
|
+
const { promisify } = require('util');
|
|
100
|
+
const execAsync = promisify(exec);
|
|
101
|
+
|
|
102
|
+
const commands = {
|
|
103
|
+
'sleep': {
|
|
104
|
+
action: async () => {
|
|
105
|
+
await execAsync('rundll32.exe powrprof.dll,SetSuspendState 0,1,0');
|
|
106
|
+
return 'System going to sleep...';
|
|
107
|
+
},
|
|
108
|
+
description: 'Put computer to sleep'
|
|
109
|
+
},
|
|
110
|
+
'shutdown': {
|
|
111
|
+
action: async () => {
|
|
112
|
+
await execAsync('shutdown /s /t 0');
|
|
113
|
+
return 'Shutting down...';
|
|
114
|
+
},
|
|
115
|
+
description: 'Shutdown computer'
|
|
116
|
+
},
|
|
117
|
+
'restart': {
|
|
118
|
+
action: async () => {
|
|
119
|
+
await execAsync('shutdown /r /t 0');
|
|
120
|
+
return 'Restarting...';
|
|
121
|
+
},
|
|
122
|
+
description: 'Restart computer'
|
|
123
|
+
},
|
|
124
|
+
'lock': {
|
|
125
|
+
action: async () => {
|
|
126
|
+
await execAsync('rundll32.exe user32.dll,LockWorkStation');
|
|
127
|
+
return 'Locking computer...';
|
|
128
|
+
},
|
|
129
|
+
description: 'Lock computer'
|
|
130
|
+
},
|
|
131
|
+
'logout': {
|
|
132
|
+
action: async () => {
|
|
133
|
+
await execAsync('shutdown /l');
|
|
134
|
+
return 'Logging out...';
|
|
135
|
+
},
|
|
136
|
+
description: 'Log out current user'
|
|
137
|
+
},
|
|
138
|
+
'empty recycle bin': {
|
|
139
|
+
action: async () => {
|
|
140
|
+
await execAsync('rd /s /q %systemdrive%\\$Recycle.bin');
|
|
141
|
+
return 'Recycle bin emptied';
|
|
142
|
+
},
|
|
143
|
+
description: 'Empty recycle bin'
|
|
144
|
+
},
|
|
145
|
+
'volume up': {
|
|
146
|
+
action: async () => {
|
|
147
|
+
await execAsync('nircmd.exe changesysvolume 2000');
|
|
148
|
+
return 'Volume increased';
|
|
149
|
+
},
|
|
150
|
+
description: 'Increase volume'
|
|
151
|
+
},
|
|
152
|
+
'volume down': {
|
|
153
|
+
action: async () => {
|
|
154
|
+
await execAsync('nircmd.exe changesysvolume -2000');
|
|
155
|
+
return 'Volume decreased';
|
|
156
|
+
},
|
|
157
|
+
description: 'Decrease volume'
|
|
158
|
+
},
|
|
159
|
+
'mute': {
|
|
160
|
+
action: async () => {
|
|
161
|
+
await execAsync('nircmd.exe mutesysvolume 1');
|
|
162
|
+
return 'Volume muted';
|
|
163
|
+
},
|
|
164
|
+
description: 'Mute volume'
|
|
165
|
+
},
|
|
166
|
+
'unmute': {
|
|
167
|
+
action: async () => {
|
|
168
|
+
await execAsync('nircmd.exe mutesysvolume 0');
|
|
169
|
+
return 'Volume unmuted';
|
|
170
|
+
},
|
|
171
|
+
description: 'Unmute volume'
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
if (commands[cmd]) {
|
|
176
|
+
try {
|
|
177
|
+
const message = await commands[cmd].action();
|
|
178
|
+
return {
|
|
179
|
+
result: message,
|
|
180
|
+
type: 'system_command',
|
|
181
|
+
formatted: message
|
|
182
|
+
};
|
|
183
|
+
} catch (error) {
|
|
184
|
+
return {
|
|
185
|
+
error: `Failed to execute: ${error.message}`,
|
|
186
|
+
type: 'error'
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
show() {
|
|
195
|
+
if (this.window) {
|
|
196
|
+
this.window.show();
|
|
197
|
+
this.window.focus();
|
|
198
|
+
this.isVisible = true;
|
|
199
|
+
this.window.webContents.send('focus-input');
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
hide() {
|
|
204
|
+
if (this.window) {
|
|
205
|
+
this.window.hide();
|
|
206
|
+
this.isVisible = false;
|
|
207
|
+
this.window.webContents.send('clear-input');
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
toggle() {
|
|
212
|
+
if (this.isVisible) {
|
|
213
|
+
this.hide();
|
|
214
|
+
} else {
|
|
215
|
+
this.show();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
destroy() {
|
|
220
|
+
if (this.window) {
|
|
221
|
+
globalShortcut.unregister(this.hotkey);
|
|
222
|
+
this.window.close();
|
|
223
|
+
this.window = null;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
module.exports = FloatingSearchBox;
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Mani-Calc</title>
|
|
7
|
+
<style>
|
|
8
|
+
* {
|
|
9
|
+
margin: 0;
|
|
10
|
+
padding: 0;
|
|
11
|
+
box-sizing: border-box;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
body {
|
|
15
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
16
|
+
background: transparent;
|
|
17
|
+
-webkit-app-region: drag;
|
|
18
|
+
overflow: hidden;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.container {
|
|
22
|
+
width: 600px;
|
|
23
|
+
padding: 10px;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.search-box {
|
|
27
|
+
background: rgba(255, 255, 255, 0.95);
|
|
28
|
+
backdrop-filter: blur(20px);
|
|
29
|
+
border-radius: 12px;
|
|
30
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
|
31
|
+
overflow: hidden;
|
|
32
|
+
-webkit-app-region: no-drag;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.input-container {
|
|
36
|
+
display: flex;
|
|
37
|
+
align-items: center;
|
|
38
|
+
padding: 16px 20px;
|
|
39
|
+
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.icon {
|
|
43
|
+
font-size: 24px;
|
|
44
|
+
margin-right: 12px;
|
|
45
|
+
color: #00D9FF;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
#search-input {
|
|
49
|
+
flex: 1;
|
|
50
|
+
border: none;
|
|
51
|
+
outline: none;
|
|
52
|
+
font-size: 18px;
|
|
53
|
+
background: transparent;
|
|
54
|
+
color: #333;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
#search-input::placeholder {
|
|
58
|
+
color: #999;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.result-container {
|
|
62
|
+
padding: 12px 20px;
|
|
63
|
+
background: rgba(0, 217, 255, 0.05);
|
|
64
|
+
display: none;
|
|
65
|
+
animation: slideDown 0.2s ease;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.result-container.show {
|
|
69
|
+
display: block;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@keyframes slideDown {
|
|
73
|
+
from {
|
|
74
|
+
opacity: 0;
|
|
75
|
+
transform: translateY(-10px);
|
|
76
|
+
}
|
|
77
|
+
to {
|
|
78
|
+
opacity: 1;
|
|
79
|
+
transform: translateY(0);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.result-text {
|
|
84
|
+
font-size: 16px;
|
|
85
|
+
color: #00D9FF;
|
|
86
|
+
font-weight: 600;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.result-text.error {
|
|
90
|
+
color: #ff4444;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.result-text.success {
|
|
94
|
+
color: #00D9FF;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.result-text.system {
|
|
98
|
+
color: #ff9800;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.hint {
|
|
102
|
+
font-size: 12px;
|
|
103
|
+
color: #999;
|
|
104
|
+
margin-top: 4px;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.shortcuts {
|
|
108
|
+
padding: 8px 20px;
|
|
109
|
+
background: rgba(0, 0, 0, 0.02);
|
|
110
|
+
display: none;
|
|
111
|
+
font-size: 11px;
|
|
112
|
+
color: #666;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.shortcuts.show {
|
|
116
|
+
display: block;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.shortcut-item {
|
|
120
|
+
display: inline-block;
|
|
121
|
+
margin-right: 12px;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.key {
|
|
125
|
+
background: rgba(0, 0, 0, 0.1);
|
|
126
|
+
padding: 2px 6px;
|
|
127
|
+
border-radius: 3px;
|
|
128
|
+
font-family: monospace;
|
|
129
|
+
}
|
|
130
|
+
</style>
|
|
131
|
+
</head>
|
|
132
|
+
<body>
|
|
133
|
+
<div class="container">
|
|
134
|
+
<div class="search-box">
|
|
135
|
+
<div class="input-container">
|
|
136
|
+
<div class="icon">⚡</div>
|
|
137
|
+
<input
|
|
138
|
+
type="text"
|
|
139
|
+
id="search-input"
|
|
140
|
+
placeholder="Type calculation or command..."
|
|
141
|
+
autocomplete="off"
|
|
142
|
+
spellcheck="false"
|
|
143
|
+
>
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
<div class="result-container" id="result-container">
|
|
147
|
+
<div class="result-text" id="result-text"></div>
|
|
148
|
+
<div class="hint" id="result-hint"></div>
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
<div class="shortcuts" id="shortcuts">
|
|
152
|
+
<span class="shortcut-item"><span class="key">Enter</span> Execute</span>
|
|
153
|
+
<span class="shortcut-item"><span class="key">Esc</span> Close</span>
|
|
154
|
+
<span class="shortcut-item"><span class="key">Alt+Space</span> Toggle</span>
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
<script>
|
|
160
|
+
const { ipcRenderer } = require('electron');
|
|
161
|
+
|
|
162
|
+
const input = document.getElementById('search-input');
|
|
163
|
+
const resultContainer = document.getElementById('result-container');
|
|
164
|
+
const resultText = document.getElementById('result-text');
|
|
165
|
+
const resultHint = document.getElementById('result-hint');
|
|
166
|
+
const shortcuts = document.getElementById('shortcuts');
|
|
167
|
+
|
|
168
|
+
let debounceTimer;
|
|
169
|
+
|
|
170
|
+
// Focus input when window is shown
|
|
171
|
+
ipcRenderer.on('focus-input', () => {
|
|
172
|
+
input.focus();
|
|
173
|
+
input.select();
|
|
174
|
+
shortcuts.classList.add('show');
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Clear input when window is hidden
|
|
178
|
+
ipcRenderer.on('clear-input', () => {
|
|
179
|
+
input.value = '';
|
|
180
|
+
resultContainer.classList.remove('show');
|
|
181
|
+
shortcuts.classList.remove('show');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Handle input
|
|
185
|
+
input.addEventListener('input', (e) => {
|
|
186
|
+
const query = e.target.value.trim();
|
|
187
|
+
|
|
188
|
+
if (!query) {
|
|
189
|
+
resultContainer.classList.remove('show');
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Debounce for live preview
|
|
194
|
+
clearTimeout(debounceTimer);
|
|
195
|
+
debounceTimer = setTimeout(() => {
|
|
196
|
+
processQuery(query, true);
|
|
197
|
+
}, 300);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Handle Enter key
|
|
201
|
+
input.addEventListener('keydown', (e) => {
|
|
202
|
+
if (e.key === 'Enter') {
|
|
203
|
+
const query = input.value.trim();
|
|
204
|
+
if (query) {
|
|
205
|
+
processQuery(query, false);
|
|
206
|
+
}
|
|
207
|
+
} else if (e.key === 'Escape') {
|
|
208
|
+
ipcRenderer.send('hide-window');
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// Process query
|
|
213
|
+
function processQuery(query, isPreview) {
|
|
214
|
+
if (!isPreview) {
|
|
215
|
+
ipcRenderer.send('process-query', query);
|
|
216
|
+
} else {
|
|
217
|
+
// Show preview for calculations
|
|
218
|
+
try {
|
|
219
|
+
// Simple preview for math expressions
|
|
220
|
+
if (/^[\d\s\+\-\*\/\(\)\.\^]+$/.test(query)) {
|
|
221
|
+
showResult('Calculating...', 'Press Enter to execute', 'success');
|
|
222
|
+
}
|
|
223
|
+
} catch (error) {
|
|
224
|
+
// Ignore preview errors
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Handle result from main process
|
|
230
|
+
ipcRenderer.on('query-result', (event, result) => {
|
|
231
|
+
if (result.error) {
|
|
232
|
+
showResult(`✗ ${result.error}`, '', 'error');
|
|
233
|
+
} else if (result.type === 'system_command') {
|
|
234
|
+
showResult(`✓ ${result.formatted}`, 'Copied to clipboard', 'system');
|
|
235
|
+
} else {
|
|
236
|
+
showResult(`✓ ${result.formatted}`, 'Copied to clipboard', 'success');
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// Show result
|
|
241
|
+
function showResult(text, hint, type) {
|
|
242
|
+
resultText.textContent = text;
|
|
243
|
+
resultText.className = `result-text ${type}`;
|
|
244
|
+
resultHint.textContent = hint;
|
|
245
|
+
resultContainer.classList.add('show');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Auto-focus on load
|
|
249
|
+
setTimeout(() => {
|
|
250
|
+
input.focus();
|
|
251
|
+
}, 100);
|
|
252
|
+
</script>
|
|
253
|
+
</body>
|
|
254
|
+
</html>
|