floatnote 1.0.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.
Files changed (69) hide show
  1. package/.beads/config.json +6 -0
  2. package/.beads/issues/floatnote-1.md +21 -0
  3. package/.beads/issues/floatnote-10.md +28 -0
  4. package/.beads/issues/floatnote-11.md +36 -0
  5. package/.beads/issues/floatnote-12.md +25 -0
  6. package/.beads/issues/floatnote-13.md +37 -0
  7. package/.beads/issues/floatnote-14.md +22 -0
  8. package/.beads/issues/floatnote-15.md +22 -0
  9. package/.beads/issues/floatnote-16.md +20 -0
  10. package/.beads/issues/floatnote-17.md +20 -0
  11. package/.beads/issues/floatnote-18.md +21 -0
  12. package/.beads/issues/floatnote-19.md +19 -0
  13. package/.beads/issues/floatnote-2.md +32 -0
  14. package/.beads/issues/floatnote-20.md +22 -0
  15. package/.beads/issues/floatnote-3.md +50 -0
  16. package/.beads/issues/floatnote-4.md +31 -0
  17. package/.beads/issues/floatnote-5.md +28 -0
  18. package/.beads/issues/floatnote-6.md +30 -0
  19. package/.beads/issues/floatnote-7.md +38 -0
  20. package/.beads/issues/floatnote-8.md +29 -0
  21. package/.beads/issues/floatnote-9.md +32 -0
  22. package/CLAUDE.md +61 -0
  23. package/README.md +95 -0
  24. package/bin/floatnote.js +218 -0
  25. package/coverage/base.css +224 -0
  26. package/coverage/bin/floatnote.js.html +739 -0
  27. package/coverage/bin/index.html +116 -0
  28. package/coverage/block-navigation.js +87 -0
  29. package/coverage/favicon.png +0 -0
  30. package/coverage/index.html +131 -0
  31. package/coverage/lcov-report/base.css +224 -0
  32. package/coverage/lcov-report/bin/floatnote.js.html +739 -0
  33. package/coverage/lcov-report/bin/index.html +116 -0
  34. package/coverage/lcov-report/block-navigation.js +87 -0
  35. package/coverage/lcov-report/favicon.png +0 -0
  36. package/coverage/lcov-report/index.html +131 -0
  37. package/coverage/lcov-report/prettify.css +1 -0
  38. package/coverage/lcov-report/prettify.js +2 -0
  39. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  40. package/coverage/lcov-report/sorter.js +210 -0
  41. package/coverage/lcov-report/src/index.html +146 -0
  42. package/coverage/lcov-report/src/main.js.html +1483 -0
  43. package/coverage/lcov-report/src/preload.js.html +361 -0
  44. package/coverage/lcov-report/src/renderer.js.html +8767 -0
  45. package/coverage/lcov.info +3273 -0
  46. package/coverage/prettify.css +1 -0
  47. package/coverage/prettify.js +2 -0
  48. package/coverage/sort-arrow-sprite.png +0 -0
  49. package/coverage/sorter.js +210 -0
  50. package/coverage/src/index.html +146 -0
  51. package/coverage/src/main.js.html +1483 -0
  52. package/coverage/src/preload.js.html +361 -0
  53. package/coverage/src/renderer.js.html +8767 -0
  54. package/jest.config.js +48 -0
  55. package/package.json +59 -0
  56. package/src/icon-template.png +0 -0
  57. package/src/index.html +296 -0
  58. package/src/main.js +494 -0
  59. package/src/preload.js +96 -0
  60. package/src/renderer.js +3203 -0
  61. package/src/styles.css +1448 -0
  62. package/tests/cli/floatnote.test.js +167 -0
  63. package/tests/main/main.test.js +287 -0
  64. package/tests/mocks/electron.js +126 -0
  65. package/tests/mocks/fs.js +17 -0
  66. package/tests/preload/preload.test.js +218 -0
  67. package/tests/renderer/history.test.js +234 -0
  68. package/tests/renderer/notes.test.js +262 -0
  69. package/tests/renderer/settings.test.js +178 -0
package/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # Floatnote
2
+
3
+ Transparent always-on-top drawing and note-taking overlay for macOS.
4
+
5
+ ![Floatnote](https://img.shields.io/npm/v/floatnote) ![License](https://img.shields.io/npm/l/floatnote)
6
+
7
+ ## Installation
8
+
9
+ ### Using npx (recommended)
10
+
11
+ ```bash
12
+ npx floatnote
13
+ ```
14
+
15
+ This will automatically download and launch the latest version of Floatnote.
16
+
17
+ ### Using Homebrew
18
+
19
+ ```bash
20
+ brew tap josmanvis/floatnote
21
+ brew install --cask floatnote
22
+ ```
23
+
24
+ ### Manual Download
25
+
26
+ Download the latest release from [GitHub Releases](https://github.com/josmanvis/floatnote/releases).
27
+
28
+ ## Features
29
+
30
+ - **Transparent Overlay** - Draw on top of any application
31
+ - **Always on Top** - Never lose sight of your annotations
32
+ - **Drawing Tools** - Multiple colors and stroke sizes
33
+ - **Text Annotations** - Add text notes anywhere
34
+ - **Multi-note Support** - Create and navigate between multiple notes
35
+ - **Gesture Support** - Pinch to zoom, two-finger pan, rotate
36
+ - **Smart Paste** - Paste images directly from clipboard
37
+ - **Data Persistence** - Your notes are automatically saved
38
+
39
+ ## Keyboard Shortcuts
40
+
41
+ | Action | Shortcut |
42
+ |--------|----------|
43
+ | Toggle Floatnote | `Cmd+Shift+G` |
44
+ | Quick Toggle | `Alt+Space` or `Ctrl+\`` |
45
+ | Settings | `Cmd+,` |
46
+ | Previous Note | `[` |
47
+ | Next Note | `]` |
48
+ | Undo | `Cmd+Z` |
49
+ | Redo | `Cmd+Shift+Z` |
50
+ | Select Mode | `V` |
51
+ | Draw Mode | `B` |
52
+ | Text Mode | `T` |
53
+ | Select All | `Cmd+A` |
54
+ | Delete | `D` |
55
+ | Zoom In | `Cmd++` |
56
+ | Zoom Out | `Cmd+-` |
57
+ | Reset Zoom | `Cmd+0` |
58
+
59
+ ## CLI Options
60
+
61
+ ```bash
62
+ floatnote [options]
63
+
64
+ Options:
65
+ -v, --version Show version number
66
+ -h, --help Show help message
67
+ --update Force update to latest version
68
+ --uninstall Remove Floatnote from your system
69
+ ```
70
+
71
+ ## System Requirements
72
+
73
+ - macOS 10.13 or later
74
+ - Node.js 16+ (for npx installation)
75
+
76
+ ## Development
77
+
78
+ ```bash
79
+ # Clone the repository
80
+ git clone https://github.com/josmanvis/floatnote.git
81
+ cd floatnote
82
+
83
+ # Install dependencies
84
+ npm install
85
+
86
+ # Run in development mode
87
+ npm run dev
88
+
89
+ # Build for distribution
90
+ npm run build
91
+ ```
92
+
93
+ ## License
94
+
95
+ MIT
@@ -0,0 +1,218 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { spawn } = require('child_process');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const https = require('https');
7
+ const { createWriteStream, mkdirSync, existsSync, rmSync, unlinkSync, readFileSync, writeFileSync } = require('fs');
8
+ const { execSync } = require('child_process');
9
+
10
+ const APP_NAME = 'Floatnote';
11
+ const GITHUB_REPO = 'josmanvis/floatnote';
12
+ const APP_DIR = path.join(process.env.HOME, '.floatnote');
13
+ const APP_PATH = path.join(APP_DIR, `${APP_NAME}.app`);
14
+ const VERSION_FILE = path.join(APP_DIR, 'version');
15
+
16
+ function log(message) {
17
+ console.log(`[floatnote] ${message}`);
18
+ }
19
+
20
+ async function getLatestRelease() {
21
+ return new Promise((resolve, reject) => {
22
+ const options = {
23
+ hostname: 'api.github.com',
24
+ path: `/repos/${GITHUB_REPO}/releases/latest`,
25
+ headers: { 'User-Agent': 'floatnote-cli' }
26
+ };
27
+
28
+ https.get(options, (res) => {
29
+ if (res.statusCode === 404) {
30
+ reject(new Error('No releases found. Please wait for the first release to be published.'));
31
+ return;
32
+ }
33
+ let data = '';
34
+ res.on('data', chunk => data += chunk);
35
+ res.on('end', () => {
36
+ try {
37
+ resolve(JSON.parse(data));
38
+ } catch (e) {
39
+ reject(new Error('Failed to parse release data'));
40
+ }
41
+ });
42
+ res.on('error', reject);
43
+ }).on('error', reject);
44
+ });
45
+ }
46
+
47
+ async function downloadFile(url, dest, onProgress) {
48
+ return new Promise((resolve, reject) => {
49
+ const makeRequest = (requestUrl) => {
50
+ const parsedUrl = new URL(requestUrl);
51
+ const options = {
52
+ hostname: parsedUrl.hostname,
53
+ path: parsedUrl.pathname + parsedUrl.search,
54
+ headers: { 'User-Agent': 'floatnote-cli' }
55
+ };
56
+
57
+ https.get(options, (res) => {
58
+ // Handle redirects
59
+ if (res.statusCode === 302 || res.statusCode === 301) {
60
+ makeRequest(res.headers.location);
61
+ return;
62
+ }
63
+
64
+ if (res.statusCode !== 200) {
65
+ reject(new Error(`Download failed with status ${res.statusCode}`));
66
+ return;
67
+ }
68
+
69
+ const totalSize = parseInt(res.headers['content-length'], 10);
70
+ let downloadedSize = 0;
71
+
72
+ const file = createWriteStream(dest);
73
+ res.on('data', (chunk) => {
74
+ downloadedSize += chunk.length;
75
+ if (onProgress && totalSize) {
76
+ const percent = Math.round((downloadedSize / totalSize) * 100);
77
+ onProgress(percent, downloadedSize, totalSize);
78
+ }
79
+ });
80
+ res.pipe(file);
81
+ file.on('finish', () => {
82
+ file.close();
83
+ resolve();
84
+ });
85
+ file.on('error', (err) => {
86
+ unlinkSync(dest);
87
+ reject(err);
88
+ });
89
+ }).on('error', reject);
90
+ };
91
+
92
+ makeRequest(url);
93
+ });
94
+ }
95
+
96
+ function formatBytes(bytes) {
97
+ if (bytes < 1024) return bytes + ' B';
98
+ if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
99
+ return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
100
+ }
101
+
102
+ async function main() {
103
+ // Handle command line arguments
104
+ const args = process.argv.slice(2);
105
+ if (args.includes('--version') || args.includes('-v')) {
106
+ const pkg = require('../package.json');
107
+ console.log(`floatnote v${pkg.version}`);
108
+ return;
109
+ }
110
+ if (args.includes('--help') || args.includes('-h')) {
111
+ console.log(`
112
+ Floatnote - Transparent drawing overlay for macOS
113
+
114
+ Usage: floatnote [options]
115
+
116
+ Options:
117
+ -v, --version Show version number
118
+ -h, --help Show this help message
119
+ --update Force update to latest version
120
+ --uninstall Remove Floatnote from your system
121
+
122
+ Keyboard shortcuts (when app is running):
123
+ Cmd+Shift+G Toggle Floatnote
124
+ Alt+Space Quick toggle
125
+ Ctrl+\` Quick toggle
126
+ Cmd+, Settings
127
+ [ / ] Navigate notes
128
+ `);
129
+ return;
130
+ }
131
+
132
+ if (args.includes('--uninstall')) {
133
+ if (existsSync(APP_DIR)) {
134
+ rmSync(APP_DIR, { recursive: true });
135
+ log('Floatnote has been uninstalled.');
136
+ } else {
137
+ log('Floatnote is not installed.');
138
+ }
139
+ return;
140
+ }
141
+
142
+ // Create app directory
143
+ if (!existsSync(APP_DIR)) {
144
+ mkdirSync(APP_DIR, { recursive: true });
145
+ }
146
+
147
+ const forceUpdate = args.includes('--update');
148
+ let currentVersion = existsSync(VERSION_FILE) ? readFileSync(VERSION_FILE, 'utf8').trim() : null;
149
+
150
+ // Check for updates
151
+ try {
152
+ log('Checking for updates...');
153
+ const release = await getLatestRelease();
154
+
155
+ if (forceUpdate || !currentVersion || currentVersion !== release.tag_name || !existsSync(APP_PATH)) {
156
+ log(`Downloading Floatnote ${release.tag_name}...`);
157
+
158
+ // Find the zip asset for macOS
159
+ const zipAsset = release.assets.find(a =>
160
+ a.name.includes('mac') && a.name.endsWith('.zip') ||
161
+ a.name === `${APP_NAME}-${release.tag_name.replace('v', '')}-mac.zip` ||
162
+ a.name === `${APP_NAME}.zip` ||
163
+ a.name.endsWith('.zip')
164
+ );
165
+
166
+ if (!zipAsset) {
167
+ throw new Error('No macOS build found in the latest release.');
168
+ }
169
+
170
+ const zipPath = path.join(APP_DIR, 'floatnote.zip');
171
+
172
+ // Download with progress
173
+ let lastPercent = 0;
174
+ await downloadFile(zipAsset.browser_download_url, zipPath, (percent, downloaded, total) => {
175
+ if (percent !== lastPercent && percent % 10 === 0) {
176
+ log(`Progress: ${percent}% (${formatBytes(downloaded)} / ${formatBytes(total)})`);
177
+ lastPercent = percent;
178
+ }
179
+ });
180
+
181
+ // Extract
182
+ log('Extracting...');
183
+ if (existsSync(APP_PATH)) {
184
+ rmSync(APP_PATH, { recursive: true });
185
+ }
186
+ execSync(`unzip -q -o "${zipPath}" -d "${APP_DIR}"`, { stdio: 'pipe' });
187
+ unlinkSync(zipPath);
188
+
189
+ // Save version
190
+ writeFileSync(VERSION_FILE, release.tag_name);
191
+ log(`Floatnote ${release.tag_name} installed successfully!`);
192
+ } else {
193
+ log(`Floatnote ${currentVersion} is up to date.`);
194
+ }
195
+ } catch (err) {
196
+ if (!existsSync(APP_PATH)) {
197
+ console.error(`Error: ${err.message}`);
198
+ console.error('Make sure the GitHub repository has published releases.');
199
+ process.exit(1);
200
+ }
201
+ // If app exists, we can still launch it
202
+ log(`Could not check for updates: ${err.message}`);
203
+ }
204
+
205
+ // Launch the app
206
+ if (existsSync(APP_PATH)) {
207
+ log('Launching Floatnote...');
208
+ spawn('open', [APP_PATH], { detached: true, stdio: 'ignore' }).unref();
209
+ } else {
210
+ console.error('Error: Floatnote app not found. Please try running with --update flag.');
211
+ process.exit(1);
212
+ }
213
+ }
214
+
215
+ main().catch(err => {
216
+ console.error('Error:', err.message);
217
+ process.exit(1);
218
+ });
@@ -0,0 +1,224 @@
1
+ body, html {
2
+ margin:0; padding: 0;
3
+ height: 100%;
4
+ }
5
+ body {
6
+ font-family: Helvetica Neue, Helvetica, Arial;
7
+ font-size: 14px;
8
+ color:#333;
9
+ }
10
+ .small { font-size: 12px; }
11
+ *, *:after, *:before {
12
+ -webkit-box-sizing:border-box;
13
+ -moz-box-sizing:border-box;
14
+ box-sizing:border-box;
15
+ }
16
+ h1 { font-size: 20px; margin: 0;}
17
+ h2 { font-size: 14px; }
18
+ pre {
19
+ font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace;
20
+ margin: 0;
21
+ padding: 0;
22
+ -moz-tab-size: 2;
23
+ -o-tab-size: 2;
24
+ tab-size: 2;
25
+ }
26
+ a { color:#0074D9; text-decoration:none; }
27
+ a:hover { text-decoration:underline; }
28
+ .strong { font-weight: bold; }
29
+ .space-top1 { padding: 10px 0 0 0; }
30
+ .pad2y { padding: 20px 0; }
31
+ .pad1y { padding: 10px 0; }
32
+ .pad2x { padding: 0 20px; }
33
+ .pad2 { padding: 20px; }
34
+ .pad1 { padding: 10px; }
35
+ .space-left2 { padding-left:55px; }
36
+ .space-right2 { padding-right:20px; }
37
+ .center { text-align:center; }
38
+ .clearfix { display:block; }
39
+ .clearfix:after {
40
+ content:'';
41
+ display:block;
42
+ height:0;
43
+ clear:both;
44
+ visibility:hidden;
45
+ }
46
+ .fl { float: left; }
47
+ @media only screen and (max-width:640px) {
48
+ .col3 { width:100%; max-width:100%; }
49
+ .hide-mobile { display:none!important; }
50
+ }
51
+
52
+ .quiet {
53
+ color: #7f7f7f;
54
+ color: rgba(0,0,0,0.5);
55
+ }
56
+ .quiet a { opacity: 0.7; }
57
+
58
+ .fraction {
59
+ font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
60
+ font-size: 10px;
61
+ color: #555;
62
+ background: #E8E8E8;
63
+ padding: 4px 5px;
64
+ border-radius: 3px;
65
+ vertical-align: middle;
66
+ }
67
+
68
+ div.path a:link, div.path a:visited { color: #333; }
69
+ table.coverage {
70
+ border-collapse: collapse;
71
+ margin: 10px 0 0 0;
72
+ padding: 0;
73
+ }
74
+
75
+ table.coverage td {
76
+ margin: 0;
77
+ padding: 0;
78
+ vertical-align: top;
79
+ }
80
+ table.coverage td.line-count {
81
+ text-align: right;
82
+ padding: 0 5px 0 20px;
83
+ }
84
+ table.coverage td.line-coverage {
85
+ text-align: right;
86
+ padding-right: 10px;
87
+ min-width:20px;
88
+ }
89
+
90
+ table.coverage td span.cline-any {
91
+ display: inline-block;
92
+ padding: 0 5px;
93
+ width: 100%;
94
+ }
95
+ .missing-if-branch {
96
+ display: inline-block;
97
+ margin-right: 5px;
98
+ border-radius: 3px;
99
+ position: relative;
100
+ padding: 0 4px;
101
+ background: #333;
102
+ color: yellow;
103
+ }
104
+
105
+ .skip-if-branch {
106
+ display: none;
107
+ margin-right: 10px;
108
+ position: relative;
109
+ padding: 0 4px;
110
+ background: #ccc;
111
+ color: white;
112
+ }
113
+ .missing-if-branch .typ, .skip-if-branch .typ {
114
+ color: inherit !important;
115
+ }
116
+ .coverage-summary {
117
+ border-collapse: collapse;
118
+ width: 100%;
119
+ }
120
+ .coverage-summary tr { border-bottom: 1px solid #bbb; }
121
+ .keyline-all { border: 1px solid #ddd; }
122
+ .coverage-summary td, .coverage-summary th { padding: 10px; }
123
+ .coverage-summary tbody { border: 1px solid #bbb; }
124
+ .coverage-summary td { border-right: 1px solid #bbb; }
125
+ .coverage-summary td:last-child { border-right: none; }
126
+ .coverage-summary th {
127
+ text-align: left;
128
+ font-weight: normal;
129
+ white-space: nowrap;
130
+ }
131
+ .coverage-summary th.file { border-right: none !important; }
132
+ .coverage-summary th.pct { }
133
+ .coverage-summary th.pic,
134
+ .coverage-summary th.abs,
135
+ .coverage-summary td.pct,
136
+ .coverage-summary td.abs { text-align: right; }
137
+ .coverage-summary td.file { white-space: nowrap; }
138
+ .coverage-summary td.pic { min-width: 120px !important; }
139
+ .coverage-summary tfoot td { }
140
+
141
+ .coverage-summary .sorter {
142
+ height: 10px;
143
+ width: 7px;
144
+ display: inline-block;
145
+ margin-left: 0.5em;
146
+ background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent;
147
+ }
148
+ .coverage-summary .sorted .sorter {
149
+ background-position: 0 -20px;
150
+ }
151
+ .coverage-summary .sorted-desc .sorter {
152
+ background-position: 0 -10px;
153
+ }
154
+ .status-line { height: 10px; }
155
+ /* yellow */
156
+ .cbranch-no { background: yellow !important; color: #111; }
157
+ /* dark red */
158
+ .red.solid, .status-line.low, .low .cover-fill { background:#C21F39 }
159
+ .low .chart { border:1px solid #C21F39 }
160
+ .highlighted,
161
+ .highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{
162
+ background: #C21F39 !important;
163
+ }
164
+ /* medium red */
165
+ .cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE }
166
+ /* light red */
167
+ .low, .cline-no { background:#FCE1E5 }
168
+ /* light green */
169
+ .high, .cline-yes { background:rgb(230,245,208) }
170
+ /* medium green */
171
+ .cstat-yes { background:rgb(161,215,106) }
172
+ /* dark green */
173
+ .status-line.high, .high .cover-fill { background:rgb(77,146,33) }
174
+ .high .chart { border:1px solid rgb(77,146,33) }
175
+ /* dark yellow (gold) */
176
+ .status-line.medium, .medium .cover-fill { background: #f9cd0b; }
177
+ .medium .chart { border:1px solid #f9cd0b; }
178
+ /* light yellow */
179
+ .medium { background: #fff4c2; }
180
+
181
+ .cstat-skip { background: #ddd; color: #111; }
182
+ .fstat-skip { background: #ddd; color: #111 !important; }
183
+ .cbranch-skip { background: #ddd !important; color: #111; }
184
+
185
+ span.cline-neutral { background: #eaeaea; }
186
+
187
+ .coverage-summary td.empty {
188
+ opacity: .5;
189
+ padding-top: 4px;
190
+ padding-bottom: 4px;
191
+ line-height: 1;
192
+ color: #888;
193
+ }
194
+
195
+ .cover-fill, .cover-empty {
196
+ display:inline-block;
197
+ height: 12px;
198
+ }
199
+ .chart {
200
+ line-height: 0;
201
+ }
202
+ .cover-empty {
203
+ background: white;
204
+ }
205
+ .cover-full {
206
+ border-right: none !important;
207
+ }
208
+ pre.prettyprint {
209
+ border: none !important;
210
+ padding: 0 !important;
211
+ margin: 0 !important;
212
+ }
213
+ .com { color: #999 !important; }
214
+ .ignore-none { color: #999; font-weight: normal; }
215
+
216
+ .wrapper {
217
+ min-height: 100%;
218
+ height: auto !important;
219
+ height: 100%;
220
+ margin: 0 auto -48px;
221
+ }
222
+ .footer, .push {
223
+ height: 48px;
224
+ }