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.
Files changed (68) hide show
  1. package/bin/floatnote.js +16 -0
  2. package/package.json +13 -5
  3. package/.beads/config.json +0 -6
  4. package/.beads/issues/floatnote-1.md +0 -21
  5. package/.beads/issues/floatnote-10.md +0 -28
  6. package/.beads/issues/floatnote-11.md +0 -36
  7. package/.beads/issues/floatnote-12.md +0 -25
  8. package/.beads/issues/floatnote-13.md +0 -37
  9. package/.beads/issues/floatnote-14.md +0 -22
  10. package/.beads/issues/floatnote-15.md +0 -22
  11. package/.beads/issues/floatnote-16.md +0 -20
  12. package/.beads/issues/floatnote-17.md +0 -20
  13. package/.beads/issues/floatnote-18.md +0 -21
  14. package/.beads/issues/floatnote-19.md +0 -19
  15. package/.beads/issues/floatnote-2.md +0 -32
  16. package/.beads/issues/floatnote-20.md +0 -22
  17. package/.beads/issues/floatnote-3.md +0 -50
  18. package/.beads/issues/floatnote-4.md +0 -31
  19. package/.beads/issues/floatnote-5.md +0 -28
  20. package/.beads/issues/floatnote-6.md +0 -30
  21. package/.beads/issues/floatnote-7.md +0 -38
  22. package/.beads/issues/floatnote-8.md +0 -29
  23. package/.beads/issues/floatnote-9.md +0 -32
  24. package/CLAUDE.md +0 -61
  25. package/coverage/base.css +0 -224
  26. package/coverage/bin/floatnote.js.html +0 -739
  27. package/coverage/bin/index.html +0 -116
  28. package/coverage/block-navigation.js +0 -87
  29. package/coverage/favicon.png +0 -0
  30. package/coverage/index.html +0 -131
  31. package/coverage/lcov-report/base.css +0 -224
  32. package/coverage/lcov-report/bin/floatnote.js.html +0 -739
  33. package/coverage/lcov-report/bin/index.html +0 -116
  34. package/coverage/lcov-report/block-navigation.js +0 -87
  35. package/coverage/lcov-report/favicon.png +0 -0
  36. package/coverage/lcov-report/index.html +0 -131
  37. package/coverage/lcov-report/prettify.css +0 -1
  38. package/coverage/lcov-report/prettify.js +0 -2
  39. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  40. package/coverage/lcov-report/sorter.js +0 -210
  41. package/coverage/lcov-report/src/index.html +0 -146
  42. package/coverage/lcov-report/src/main.js.html +0 -1483
  43. package/coverage/lcov-report/src/preload.js.html +0 -361
  44. package/coverage/lcov-report/src/renderer.js.html +0 -8767
  45. package/coverage/lcov.info +0 -3273
  46. package/coverage/prettify.css +0 -1
  47. package/coverage/prettify.js +0 -2
  48. package/coverage/sort-arrow-sprite.png +0 -0
  49. package/coverage/sorter.js +0 -210
  50. package/coverage/src/index.html +0 -146
  51. package/coverage/src/main.js.html +0 -1483
  52. package/coverage/src/preload.js.html +0 -361
  53. package/coverage/src/renderer.js.html +0 -8767
  54. package/jest.config.js +0 -48
  55. package/src/icon-template.png +0 -0
  56. package/src/index.html +0 -296
  57. package/src/main.js +0 -494
  58. package/src/preload.js +0 -96
  59. package/src/renderer.js +0 -3203
  60. package/src/styles.css +0 -1448
  61. package/tests/cli/floatnote.test.js +0 -167
  62. package/tests/main/main.test.js +0 -287
  63. package/tests/mocks/electron.js +0 -126
  64. package/tests/mocks/fs.js +0 -17
  65. package/tests/preload/preload.test.js +0 -218
  66. package/tests/renderer/history.test.js +0 -234
  67. package/tests/renderer/notes.test.js +0 -262
  68. 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
- });