floatnote 1.0.0 → 1.0.6
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/bin/floatnote.js +16 -0
- package/package.json +13 -5
- package/.beads/config.json +0 -6
- package/.beads/issues/floatnote-1.md +0 -21
- package/.beads/issues/floatnote-10.md +0 -28
- package/.beads/issues/floatnote-11.md +0 -36
- package/.beads/issues/floatnote-12.md +0 -25
- package/.beads/issues/floatnote-13.md +0 -37
- package/.beads/issues/floatnote-14.md +0 -22
- package/.beads/issues/floatnote-15.md +0 -22
- package/.beads/issues/floatnote-16.md +0 -20
- package/.beads/issues/floatnote-17.md +0 -20
- package/.beads/issues/floatnote-18.md +0 -21
- package/.beads/issues/floatnote-19.md +0 -19
- package/.beads/issues/floatnote-2.md +0 -32
- package/.beads/issues/floatnote-20.md +0 -22
- package/.beads/issues/floatnote-3.md +0 -50
- package/.beads/issues/floatnote-4.md +0 -31
- package/.beads/issues/floatnote-5.md +0 -28
- package/.beads/issues/floatnote-6.md +0 -30
- package/.beads/issues/floatnote-7.md +0 -38
- package/.beads/issues/floatnote-8.md +0 -29
- package/.beads/issues/floatnote-9.md +0 -32
- package/CLAUDE.md +0 -61
- package/coverage/base.css +0 -224
- package/coverage/bin/floatnote.js.html +0 -739
- package/coverage/bin/index.html +0 -116
- package/coverage/block-navigation.js +0 -87
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +0 -131
- package/coverage/lcov-report/base.css +0 -224
- package/coverage/lcov-report/bin/floatnote.js.html +0 -739
- package/coverage/lcov-report/bin/index.html +0 -116
- package/coverage/lcov-report/block-navigation.js +0 -87
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +0 -131
- package/coverage/lcov-report/prettify.css +0 -1
- package/coverage/lcov-report/prettify.js +0 -2
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +0 -210
- package/coverage/lcov-report/src/index.html +0 -146
- package/coverage/lcov-report/src/main.js.html +0 -1483
- package/coverage/lcov-report/src/preload.js.html +0 -361
- package/coverage/lcov-report/src/renderer.js.html +0 -8767
- package/coverage/lcov.info +0 -3273
- package/coverage/prettify.css +0 -1
- package/coverage/prettify.js +0 -2
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +0 -210
- package/coverage/src/index.html +0 -146
- package/coverage/src/main.js.html +0 -1483
- package/coverage/src/preload.js.html +0 -361
- package/coverage/src/renderer.js.html +0 -8767
- package/jest.config.js +0 -48
- package/src/icon-template.png +0 -0
- package/src/index.html +0 -296
- package/src/main.js +0 -494
- package/src/preload.js +0 -96
- package/src/renderer.js +0 -3203
- package/src/styles.css +0 -1448
- package/tests/cli/floatnote.test.js +0 -167
- package/tests/main/main.test.js +0 -287
- package/tests/mocks/electron.js +0 -126
- package/tests/mocks/fs.js +0 -17
- package/tests/preload/preload.test.js +0 -218
- package/tests/renderer/history.test.js +0 -234
- package/tests/renderer/notes.test.js +0 -262
- package/tests/renderer/settings.test.js +0 -178
package/src/main.js
DELETED
|
@@ -1,494 +0,0 @@
|
|
|
1
|
-
const { app, BrowserWindow, globalShortcut, dialog, ipcMain, screen, Tray, Menu, nativeImage, shell } = require('electron');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
|
|
5
|
-
// Data storage path
|
|
6
|
-
const userDataPath = app.getPath('userData');
|
|
7
|
-
const dataFilePath = path.join(userDataPath, 'floatnote-data.json');
|
|
8
|
-
|
|
9
|
-
// Ensure only one instance of the app runs
|
|
10
|
-
const gotTheLock = app.requestSingleInstanceLock();
|
|
11
|
-
if (!gotTheLock) {
|
|
12
|
-
app.exit(0);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// Single window reference
|
|
16
|
-
let mainWindow = null;
|
|
17
|
-
let tray = null;
|
|
18
|
-
|
|
19
|
-
// Handle second instance attempt - show existing window
|
|
20
|
-
app.on('second-instance', () => {
|
|
21
|
-
if (mainWindow) {
|
|
22
|
-
if (mainWindow.isMinimized()) mainWindow.restore();
|
|
23
|
-
mainWindow.show();
|
|
24
|
-
mainWindow.focus();
|
|
25
|
-
}
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
// Track if we're currently creating a window to prevent race conditions
|
|
29
|
-
let isCreatingWindow = false;
|
|
30
|
-
|
|
31
|
-
function createWindow(options = {}) {
|
|
32
|
-
// If window already exists, just show it
|
|
33
|
-
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
34
|
-
mainWindow.show();
|
|
35
|
-
mainWindow.focus();
|
|
36
|
-
return mainWindow;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Prevent race condition - if already creating, don't create another
|
|
40
|
-
if (isCreatingWindow) {
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
isCreatingWindow = true;
|
|
44
|
-
|
|
45
|
-
// Close any stray windows (safety check)
|
|
46
|
-
BrowserWindow.getAllWindows().forEach(win => {
|
|
47
|
-
if (win !== mainWindow) {
|
|
48
|
-
win.destroy();
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
// Get the display where the cursor is (or primary display)
|
|
53
|
-
const cursorPoint = screen.getCursorScreenPoint();
|
|
54
|
-
const currentDisplay = screen.getDisplayNearestPoint(cursorPoint);
|
|
55
|
-
const { width: screenWidth, height: screenHeight } = currentDisplay.workAreaSize;
|
|
56
|
-
const { x: displayX, y: displayY } = currentDisplay.workArea;
|
|
57
|
-
|
|
58
|
-
// Default: 30% width, full height, anchored to right
|
|
59
|
-
const windowWidth = Math.round(screenWidth * 0.3);
|
|
60
|
-
const windowHeight = screenHeight;
|
|
61
|
-
|
|
62
|
-
// Position on the right edge of the current display
|
|
63
|
-
const x = options.x ?? (displayX + screenWidth - windowWidth);
|
|
64
|
-
const y = options.y ?? displayY;
|
|
65
|
-
|
|
66
|
-
const win = new BrowserWindow({
|
|
67
|
-
width: windowWidth,
|
|
68
|
-
height: windowHeight,
|
|
69
|
-
x: x,
|
|
70
|
-
y: y,
|
|
71
|
-
transparent: true,
|
|
72
|
-
backgroundColor: '#00000000',
|
|
73
|
-
frame: false,
|
|
74
|
-
titleBarStyle: 'customButtonsOnHover',
|
|
75
|
-
trafficLightPosition: { x: -100, y: -100 },
|
|
76
|
-
alwaysOnTop: true,
|
|
77
|
-
hasShadow: false,
|
|
78
|
-
resizable: true,
|
|
79
|
-
movable: true,
|
|
80
|
-
skipTaskbar: false,
|
|
81
|
-
// macOS vibrancy settings for blur effect
|
|
82
|
-
vibrancy: null, // Start with no vibrancy, set dynamically
|
|
83
|
-
visualEffectState: 'active',
|
|
84
|
-
webPreferences: {
|
|
85
|
-
nodeIntegration: false,
|
|
86
|
-
contextIsolation: true,
|
|
87
|
-
preload: path.join(__dirname, 'preload.js')
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
// Ensure window stays on top at all times
|
|
92
|
-
win.setAlwaysOnTop(true, 'floating', 1);
|
|
93
|
-
win.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true });
|
|
94
|
-
|
|
95
|
-
win.loadFile(path.join(__dirname, 'index.html'));
|
|
96
|
-
|
|
97
|
-
// Track focus for yellow glow effect
|
|
98
|
-
win.on('focus', () => {
|
|
99
|
-
win.webContents.send('window-focus', true);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
win.on('blur', () => {
|
|
103
|
-
win.webContents.send('window-focus', false);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
// Handle close confirmation
|
|
107
|
-
win.on('close', (e) => {
|
|
108
|
-
e.preventDefault();
|
|
109
|
-
|
|
110
|
-
dialog.showMessageBox(win, {
|
|
111
|
-
type: 'warning',
|
|
112
|
-
buttons: ['Close', 'Cancel'],
|
|
113
|
-
defaultId: 1,
|
|
114
|
-
title: 'Close Floatnote?',
|
|
115
|
-
message: 'Are you sure you want to close Floatnote?',
|
|
116
|
-
detail: 'Any unsaved content will be lost.'
|
|
117
|
-
}).then(result => {
|
|
118
|
-
if (result.response === 0) {
|
|
119
|
-
mainWindow = null;
|
|
120
|
-
win.destroy();
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
// Ensure cleanup when window is destroyed
|
|
126
|
-
win.on('closed', () => {
|
|
127
|
-
mainWindow = null;
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
mainWindow = win;
|
|
131
|
-
isCreatingWindow = false;
|
|
132
|
-
return win;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
app.whenReady().then(() => {
|
|
136
|
-
// Set dock icon for macOS app switcher
|
|
137
|
-
if (process.platform === 'darwin' && app.dock) {
|
|
138
|
-
// Create a 512x512 dock icon (simple "G" icon)
|
|
139
|
-
const dockIconBase64 = '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';
|
|
140
|
-
const dockIcon = nativeImage.createFromDataURL(`data:image/png;base64,${dockIconBase64}`);
|
|
141
|
-
app.dock.setIcon(dockIcon);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Create menu bar tray icon
|
|
145
|
-
createTray();
|
|
146
|
-
|
|
147
|
-
// Create the window
|
|
148
|
-
createWindow();
|
|
149
|
-
|
|
150
|
-
// Register global shortcut to toggle Floatnote (Cmd+Shift+G)
|
|
151
|
-
globalShortcut.register('CommandOrControl+Shift+G', () => {
|
|
152
|
-
toggleFloatnote();
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
// Alternative quick toggle: Option+Space (like Spotlight)
|
|
156
|
-
globalShortcut.register('Alt+Space', () => {
|
|
157
|
-
toggleFloatnote();
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
function createTray() {
|
|
164
|
-
// Create a 16x16 template icon embedded as base64
|
|
165
|
-
// This is a simple "G" letter icon that works as a macOS template image
|
|
166
|
-
const iconBase64 = 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAhGVYSWZNTQAqAAAACAAFARIAAwAAAAEAAQAAARoABQAAAAEAAABKARsABQAAAAEAAABSASgAAwAAAAEAAgAAh2kABAAAAAEAAABaAAAAAAAAAEgAAAABAAAASAAAAAEAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAEKADAAQAAAABAAAAEAAAAABBPTzcAAAACXBIWXMAAAsTAAALEwEAmpwYAAACamlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNi4wLjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgICAgICAgICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iPgogICAgICAgICA8dGlmZjpPcmllbnRhdGlvbj4xPC90aWZmOk9yaWVudGF0aW9uPgogICAgICAgICA8ZXhpZjpQaXhlbFhEaW1lbnNpb24+MTY8L2V4aWY6UGl4ZWxYRGltZW5zaW9uPgogICAgICAgICA8ZXhpZjpQaXhlbFlEaW1lbnNpb24+MTY8L2V4aWY6UGl4ZWxZRGltZW5zaW9uPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KXwjvyAAAALNJREFUOBFjZGBg+M+ABjCYGP4z/GdgUGBkYDjPwMDwH0XOf4b/TP8ZGM4zMjD8h8oBOU9AgMxMBrDAf4b/jAz/kZ3AwPD/PyMDw38kJwAF/zOC+CCJf0BBRkYGhv8MDIwMDAwgJ/xnAgr8Z2D4z8Dw//9/BkZGRob/ICdAxRj+MzAygBQCJRgYGP6D+EB5kPP/MzD8R3ECUAwiBhYEOYGRkeE/0GAGBgYgG+oloCAA7jIp5c7VUCUAAAAASUVORK5CYII=';
|
|
167
|
-
|
|
168
|
-
const icon = nativeImage.createFromDataURL(`data:image/png;base64,${iconBase64}`);
|
|
169
|
-
icon.setTemplateImage(true);
|
|
170
|
-
|
|
171
|
-
tray = new Tray(icon);
|
|
172
|
-
tray.setToolTip('Floatnote');
|
|
173
|
-
|
|
174
|
-
// Create context menu for right-click
|
|
175
|
-
const contextMenu = Menu.buildFromTemplate([
|
|
176
|
-
{
|
|
177
|
-
label: 'Show Floatnote',
|
|
178
|
-
click: () => {
|
|
179
|
-
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
180
|
-
mainWindow.show();
|
|
181
|
-
mainWindow.focus();
|
|
182
|
-
} else {
|
|
183
|
-
createWindow();
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
},
|
|
187
|
-
{
|
|
188
|
-
label: 'Hide Floatnote',
|
|
189
|
-
click: () => {
|
|
190
|
-
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
191
|
-
mainWindow.hide();
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
},
|
|
195
|
-
{ type: 'separator' },
|
|
196
|
-
{
|
|
197
|
-
label: 'Shortcuts',
|
|
198
|
-
submenu: [
|
|
199
|
-
{ label: 'Toggle: ⌘⇧G', enabled: false },
|
|
200
|
-
{ label: 'Quick Toggle: ⌥Space', enabled: false },
|
|
201
|
-
{ label: 'Quick Toggle: ⌃`', enabled: false },
|
|
202
|
-
{ label: 'Settings: ⌘,', enabled: false },
|
|
203
|
-
{ label: 'Close: ⌘W', enabled: false },
|
|
204
|
-
{ type: 'separator' },
|
|
205
|
-
{ label: 'Undo: ⌘Z', enabled: false },
|
|
206
|
-
{ label: 'Redo: ⌘⇧Z', enabled: false },
|
|
207
|
-
{ type: 'separator' },
|
|
208
|
-
{ label: 'Select Mode: V', enabled: false },
|
|
209
|
-
{ label: 'Draw Mode: B', enabled: false },
|
|
210
|
-
{ label: 'Text Mode: T', enabled: false },
|
|
211
|
-
{ type: 'separator' },
|
|
212
|
-
{ label: 'Copy: ⌘C', enabled: false },
|
|
213
|
-
{ label: 'Paste: ⌘V', enabled: false },
|
|
214
|
-
{ label: 'Delete: D', enabled: false },
|
|
215
|
-
{ type: 'separator' },
|
|
216
|
-
{ label: 'Zoom In: ⌘+', enabled: false },
|
|
217
|
-
{ label: 'Zoom Out: ⌘-', enabled: false },
|
|
218
|
-
{ label: 'Reset Zoom: ⌘0', enabled: false }
|
|
219
|
-
]
|
|
220
|
-
},
|
|
221
|
-
{ type: 'separator' },
|
|
222
|
-
{
|
|
223
|
-
label: 'About Floatnote',
|
|
224
|
-
click: () => {
|
|
225
|
-
dialog.showMessageBox({
|
|
226
|
-
type: 'info',
|
|
227
|
-
title: 'About Floatnote',
|
|
228
|
-
message: 'Floatnote',
|
|
229
|
-
detail: 'A transparent drawing and note-taking overlay for macOS.\n\nVersion 1.0.0'
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
},
|
|
233
|
-
{ type: 'separator' },
|
|
234
|
-
{
|
|
235
|
-
label: 'Quit Floatnote',
|
|
236
|
-
accelerator: 'CommandOrControl+Q',
|
|
237
|
-
click: () => app.quit()
|
|
238
|
-
}
|
|
239
|
-
]);
|
|
240
|
-
|
|
241
|
-
// Left-click: Show/focus the window
|
|
242
|
-
tray.on('click', () => {
|
|
243
|
-
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
244
|
-
if (mainWindow.isVisible()) {
|
|
245
|
-
mainWindow.focus();
|
|
246
|
-
} else {
|
|
247
|
-
mainWindow.show();
|
|
248
|
-
mainWindow.focus();
|
|
249
|
-
}
|
|
250
|
-
} else {
|
|
251
|
-
createWindow();
|
|
252
|
-
}
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
// Right-click: Show context menu
|
|
256
|
-
tray.on('right-click', () => {
|
|
257
|
-
tray.popUpContextMenu(contextMenu);
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function toggleFloatnote() {
|
|
262
|
-
if (!mainWindow || mainWindow.isDestroyed()) {
|
|
263
|
-
createWindow();
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
if (mainWindow.isVisible()) {
|
|
268
|
-
mainWindow.hide();
|
|
269
|
-
} else {
|
|
270
|
-
mainWindow.show();
|
|
271
|
-
mainWindow.focus();
|
|
272
|
-
// Notify renderer that window was shown via toggle
|
|
273
|
-
mainWindow.webContents.send('window-toggled-open');
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// Handle Cmd+W from renderer
|
|
278
|
-
ipcMain.on('close-window', (event) => {
|
|
279
|
-
const win = BrowserWindow.fromWebContents(event.sender);
|
|
280
|
-
if (win) {
|
|
281
|
-
win.close();
|
|
282
|
-
}
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
// Handle hide window (no confirmation, just hide)
|
|
286
|
-
ipcMain.on('hide-window', (event) => {
|
|
287
|
-
const win = BrowserWindow.fromWebContents(event.sender);
|
|
288
|
-
if (win) {
|
|
289
|
-
win.hide();
|
|
290
|
-
}
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
// Handle pin toggle from renderer
|
|
294
|
-
ipcMain.on('set-pinned', (event, pinned) => {
|
|
295
|
-
const win = BrowserWindow.fromWebContents(event.sender);
|
|
296
|
-
if (win) {
|
|
297
|
-
win.setAlwaysOnTop(pinned, 'floating', 1);
|
|
298
|
-
}
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
// Handle background mode change from renderer
|
|
302
|
-
// Uses macOS native vibrancy for blur effect
|
|
303
|
-
ipcMain.on('set-background-mode', (event, mode) => {
|
|
304
|
-
const win = BrowserWindow.fromWebContents(event.sender);
|
|
305
|
-
if (!win) return;
|
|
306
|
-
|
|
307
|
-
// Set vibrancy based on mode
|
|
308
|
-
if (mode === 'blur') {
|
|
309
|
-
// Use macOS native vibrancy for blur effect
|
|
310
|
-
win.setVibrancy('under-window');
|
|
311
|
-
} else {
|
|
312
|
-
// Remove vibrancy for transparent or dark modes
|
|
313
|
-
win.setVibrancy(null);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// Notify renderer of the mode change
|
|
317
|
-
win.webContents.send('background-mode-changed', mode);
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
// Handle window size change from renderer
|
|
321
|
-
ipcMain.on('set-window-size', (event, size) => {
|
|
322
|
-
const win = BrowserWindow.fromWebContents(event.sender);
|
|
323
|
-
if (!win) return;
|
|
324
|
-
|
|
325
|
-
const cursorPoint = screen.getCursorScreenPoint();
|
|
326
|
-
const currentDisplay = screen.getDisplayNearestPoint(cursorPoint);
|
|
327
|
-
const { width: screenWidth, height: screenHeight } = currentDisplay.workAreaSize;
|
|
328
|
-
const { x: displayX, y: displayY } = currentDisplay.workArea;
|
|
329
|
-
|
|
330
|
-
let newWidth, newHeight, newX, newY;
|
|
331
|
-
|
|
332
|
-
switch (size) {
|
|
333
|
-
case 'sm':
|
|
334
|
-
newWidth = Math.round(screenWidth * 0.33);
|
|
335
|
-
newHeight = Math.round(screenHeight * 0.5);
|
|
336
|
-
newX = displayX + screenWidth - newWidth;
|
|
337
|
-
newY = displayY + screenHeight - newHeight;
|
|
338
|
-
break;
|
|
339
|
-
case 'md':
|
|
340
|
-
newWidth = Math.round(screenWidth * 0.33);
|
|
341
|
-
newHeight = screenHeight;
|
|
342
|
-
newX = displayX + screenWidth - newWidth;
|
|
343
|
-
newY = displayY;
|
|
344
|
-
break;
|
|
345
|
-
case 'lg':
|
|
346
|
-
newWidth = screenWidth;
|
|
347
|
-
newHeight = screenHeight;
|
|
348
|
-
newX = displayX;
|
|
349
|
-
newY = displayY;
|
|
350
|
-
break;
|
|
351
|
-
default:
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
win.setBounds({ x: newX, y: newY, width: newWidth, height: newHeight });
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
app.on('window-all-closed', () => {
|
|
359
|
-
// Don't quit on macOS
|
|
360
|
-
if (process.platform !== 'darwin') {
|
|
361
|
-
app.quit();
|
|
362
|
-
}
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
app.on('will-quit', () => {
|
|
366
|
-
globalShortcut.unregisterAll();
|
|
367
|
-
});
|
|
368
|
-
|
|
369
|
-
// Handle horizontal resize from left edge
|
|
370
|
-
ipcMain.on('resize-window-left', (event, deltaX) => {
|
|
371
|
-
const win = BrowserWindow.fromWebContents(event.sender);
|
|
372
|
-
if (!win) return;
|
|
373
|
-
|
|
374
|
-
const bounds = win.getBounds();
|
|
375
|
-
const minWidth = 200;
|
|
376
|
-
const newWidth = bounds.width - deltaX;
|
|
377
|
-
|
|
378
|
-
if (newWidth >= minWidth) {
|
|
379
|
-
win.setBounds({
|
|
380
|
-
x: bounds.x + deltaX,
|
|
381
|
-
y: bounds.y,
|
|
382
|
-
width: newWidth,
|
|
383
|
-
height: bounds.height
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
// Data persistence handlers
|
|
389
|
-
ipcMain.handle('save-data', async (event, data) => {
|
|
390
|
-
try {
|
|
391
|
-
fs.writeFileSync(dataFilePath, JSON.stringify(data, null, 2));
|
|
392
|
-
return { success: true };
|
|
393
|
-
} catch (error) {
|
|
394
|
-
console.error('Failed to save data:', error);
|
|
395
|
-
return { success: false, error: error.message };
|
|
396
|
-
}
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
ipcMain.handle('load-data', async () => {
|
|
400
|
-
try {
|
|
401
|
-
if (fs.existsSync(dataFilePath)) {
|
|
402
|
-
const data = fs.readFileSync(dataFilePath, 'utf-8');
|
|
403
|
-
return { success: true, data: JSON.parse(data) };
|
|
404
|
-
}
|
|
405
|
-
return { success: true, data: null };
|
|
406
|
-
} catch (error) {
|
|
407
|
-
console.error('Failed to load data:', error);
|
|
408
|
-
return { success: false, error: error.message };
|
|
409
|
-
}
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
// Floatnote folder path
|
|
413
|
-
const floatnoteFolder = path.join(app.getPath('home'), '.floatnote');
|
|
414
|
-
|
|
415
|
-
// Export note to ~/.floatnote folder
|
|
416
|
-
ipcMain.handle('export-to-floatnote', async (event, noteData) => {
|
|
417
|
-
try {
|
|
418
|
-
console.log('export-to-floatnote called, saving to:', floatnoteFolder);
|
|
419
|
-
// Create folder if it doesn't exist
|
|
420
|
-
if (!fs.existsSync(floatnoteFolder)) {
|
|
421
|
-
console.log('Creating folder:', floatnoteFolder);
|
|
422
|
-
fs.mkdirSync(floatnoteFolder, { recursive: true });
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
const filename = `note-${noteData.id}.json`;
|
|
426
|
-
const filePath = path.join(floatnoteFolder, filename);
|
|
427
|
-
console.log('Writing file:', filePath);
|
|
428
|
-
fs.writeFileSync(filePath, JSON.stringify(noteData, null, 2));
|
|
429
|
-
console.log('File written successfully');
|
|
430
|
-
return { success: true, path: filePath };
|
|
431
|
-
} catch (error) {
|
|
432
|
-
console.error('Failed to export note:', error);
|
|
433
|
-
return { success: false, error: error.message };
|
|
434
|
-
}
|
|
435
|
-
});
|
|
436
|
-
|
|
437
|
-
// Open ~/.floatnote folder in Finder
|
|
438
|
-
ipcMain.handle('open-floatnote-folder', async () => {
|
|
439
|
-
try {
|
|
440
|
-
// Create folder if it doesn't exist
|
|
441
|
-
if (!fs.existsSync(floatnoteFolder)) {
|
|
442
|
-
fs.mkdirSync(floatnoteFolder, { recursive: true });
|
|
443
|
-
}
|
|
444
|
-
await shell.openPath(floatnoteFolder);
|
|
445
|
-
return { success: true };
|
|
446
|
-
} catch (error) {
|
|
447
|
-
console.error('Failed to open folder:', error);
|
|
448
|
-
return { success: false, error: error.message };
|
|
449
|
-
}
|
|
450
|
-
});
|
|
451
|
-
|
|
452
|
-
// Open file in default application
|
|
453
|
-
ipcMain.on('open-file', (event, filePath) => {
|
|
454
|
-
if (filePath && fs.existsSync(filePath)) {
|
|
455
|
-
shell.openPath(filePath);
|
|
456
|
-
}
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
// Export note as PNG with save dialog
|
|
460
|
-
ipcMain.handle('export-png', async (event, imageDataUrl) => {
|
|
461
|
-
try {
|
|
462
|
-
const win = BrowserWindow.fromWebContents(event.sender);
|
|
463
|
-
|
|
464
|
-
// Temporarily disable always-on-top so dialog appears in front
|
|
465
|
-
if (win) {
|
|
466
|
-
win.setAlwaysOnTop(false);
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
const result = await dialog.showSaveDialog(win, {
|
|
470
|
-
title: 'Export Note as PNG',
|
|
471
|
-
defaultPath: `floatnote-${Date.now()}.png`,
|
|
472
|
-
filters: [
|
|
473
|
-
{ name: 'PNG Images', extensions: ['png'] }
|
|
474
|
-
]
|
|
475
|
-
});
|
|
476
|
-
|
|
477
|
-
// Re-enable always-on-top
|
|
478
|
-
if (win) {
|
|
479
|
-
win.setAlwaysOnTop(true, 'floating', 1);
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
if (result.canceled || !result.filePath) {
|
|
483
|
-
return { success: false, canceled: true };
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
// Remove data URL prefix and write file
|
|
487
|
-
const base64Data = imageDataUrl.replace(/^data:image\/png;base64,/, '');
|
|
488
|
-
fs.writeFileSync(result.filePath, Buffer.from(base64Data, 'base64'));
|
|
489
|
-
return { success: true, path: result.filePath };
|
|
490
|
-
} catch (error) {
|
|
491
|
-
console.error('Failed to export PNG:', error);
|
|
492
|
-
return { success: false, error: error.message };
|
|
493
|
-
}
|
|
494
|
-
});
|
package/src/preload.js
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
const { contextBridge, ipcRenderer, clipboard, nativeImage } = require('electron');
|
|
2
|
-
|
|
3
|
-
contextBridge.exposeInMainWorld('glassboard', {
|
|
4
|
-
onFocusChange: (callback) => {
|
|
5
|
-
ipcRenderer.on('window-focus', (event, focused) => callback(focused));
|
|
6
|
-
},
|
|
7
|
-
onBackgroundModeChange: (callback) => {
|
|
8
|
-
ipcRenderer.on('background-mode-changed', (event, mode) => callback(mode));
|
|
9
|
-
},
|
|
10
|
-
onWindowToggledOpen: (callback) => {
|
|
11
|
-
ipcRenderer.on('window-toggled-open', () => callback());
|
|
12
|
-
},
|
|
13
|
-
closeWindow: () => {
|
|
14
|
-
ipcRenderer.send('close-window');
|
|
15
|
-
},
|
|
16
|
-
hideWindow: () => {
|
|
17
|
-
ipcRenderer.send('hide-window');
|
|
18
|
-
},
|
|
19
|
-
setPinned: (pinned) => {
|
|
20
|
-
ipcRenderer.send('set-pinned', pinned);
|
|
21
|
-
},
|
|
22
|
-
setWindowSize: (size) => {
|
|
23
|
-
ipcRenderer.send('set-window-size', size);
|
|
24
|
-
},
|
|
25
|
-
setBackgroundMode: (mode) => {
|
|
26
|
-
ipcRenderer.send('set-background-mode', mode);
|
|
27
|
-
},
|
|
28
|
-
// Clipboard access
|
|
29
|
-
getClipboardContent: () => {
|
|
30
|
-
const formats = clipboard.availableFormats();
|
|
31
|
-
const hasImage = formats.some(f => f.includes('image'));
|
|
32
|
-
const hasText = formats.some(f => f.includes('text'));
|
|
33
|
-
|
|
34
|
-
if (hasImage) {
|
|
35
|
-
const image = clipboard.readImage();
|
|
36
|
-
if (!image.isEmpty()) {
|
|
37
|
-
const size = image.getSize();
|
|
38
|
-
return {
|
|
39
|
-
type: 'image',
|
|
40
|
-
dataUrl: image.toDataURL(),
|
|
41
|
-
width: size.width,
|
|
42
|
-
height: size.height
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (hasText) {
|
|
48
|
-
const text = clipboard.readText();
|
|
49
|
-
if (text && text.trim()) {
|
|
50
|
-
return {
|
|
51
|
-
type: 'text',
|
|
52
|
-
content: text
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return null;
|
|
58
|
-
},
|
|
59
|
-
readClipboardImage: () => {
|
|
60
|
-
const image = clipboard.readImage();
|
|
61
|
-
if (!image.isEmpty()) {
|
|
62
|
-
return image.toDataURL();
|
|
63
|
-
}
|
|
64
|
-
return null;
|
|
65
|
-
},
|
|
66
|
-
readClipboardText: () => {
|
|
67
|
-
return clipboard.readText();
|
|
68
|
-
},
|
|
69
|
-
// Data persistence
|
|
70
|
-
saveData: (data) => {
|
|
71
|
-
return ipcRenderer.invoke('save-data', data);
|
|
72
|
-
},
|
|
73
|
-
loadData: () => {
|
|
74
|
-
return ipcRenderer.invoke('load-data');
|
|
75
|
-
},
|
|
76
|
-
// Window resize
|
|
77
|
-
resizeWindowLeft: (deltaX) => {
|
|
78
|
-
ipcRenderer.send('resize-window-left', deltaX);
|
|
79
|
-
},
|
|
80
|
-
// Export note to ~/.floatnote folder
|
|
81
|
-
exportToFloatnote: (noteData) => {
|
|
82
|
-
return ipcRenderer.invoke('export-to-floatnote', noteData);
|
|
83
|
-
},
|
|
84
|
-
// Open ~/.floatnote folder in Finder
|
|
85
|
-
openFloatnoteFolder: () => {
|
|
86
|
-
return ipcRenderer.invoke('open-floatnote-folder');
|
|
87
|
-
},
|
|
88
|
-
// Export note as PNG
|
|
89
|
-
exportPNG: (imageDataUrl) => {
|
|
90
|
-
return ipcRenderer.invoke('export-png', imageDataUrl);
|
|
91
|
-
},
|
|
92
|
-
// Open file in default application
|
|
93
|
-
openFile: (filePath) => {
|
|
94
|
-
ipcRenderer.send('open-file', filePath);
|
|
95
|
-
}
|
|
96
|
-
});
|