@peerasak-u/apple-notes 1.0.2 → 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/package.json +1 -1
- package/src/jxa/notes.js +141 -46
package/package.json
CHANGED
package/src/jxa/notes.js
CHANGED
|
@@ -11,44 +11,55 @@ function run(argv) {
|
|
|
11
11
|
const command = argv[0];
|
|
12
12
|
|
|
13
13
|
switch (command) {
|
|
14
|
-
case
|
|
14
|
+
case 'search':
|
|
15
15
|
if (argv.length < 2) {
|
|
16
|
-
return
|
|
16
|
+
return 'Error: search requires a query string\n' + getUsage();
|
|
17
17
|
}
|
|
18
18
|
return searchNotes(argv[1]);
|
|
19
19
|
|
|
20
|
-
case
|
|
20
|
+
case 'list':
|
|
21
21
|
if (argv.length < 2) {
|
|
22
|
-
return
|
|
22
|
+
return 'Error: list requires a search term\n' + getUsage();
|
|
23
23
|
}
|
|
24
24
|
return listNotes(argv[1]);
|
|
25
25
|
|
|
26
|
-
case
|
|
26
|
+
case 'read':
|
|
27
27
|
if (argv.length < 2) {
|
|
28
|
-
return
|
|
28
|
+
return 'Error: read requires a note identifier\n' + getUsage();
|
|
29
29
|
}
|
|
30
|
-
return readNote(argv[1], argv[2] ||
|
|
30
|
+
return readNote(argv[1], argv[2] || '');
|
|
31
31
|
|
|
32
|
-
case
|
|
32
|
+
case 'read-index':
|
|
33
33
|
if (argv.length < 3) {
|
|
34
|
-
return
|
|
34
|
+
return (
|
|
35
|
+
'Error: read-index requires search term and index\n' + getUsage()
|
|
36
|
+
);
|
|
35
37
|
}
|
|
36
38
|
return readNoteByIndex(argv[1], parseInt(argv[2], 10));
|
|
37
39
|
|
|
38
|
-
case
|
|
40
|
+
case 'recent':
|
|
39
41
|
return parseRecentArgs(argv.slice(1));
|
|
40
42
|
|
|
41
|
-
case
|
|
43
|
+
case 'create':
|
|
42
44
|
if (argv.length < 3) {
|
|
43
|
-
return
|
|
45
|
+
return 'Error: create requires title and body\n' + getUsage();
|
|
44
46
|
}
|
|
45
|
-
return createNote(argv[1], argv[2], argv[3] ||
|
|
47
|
+
return createNote(argv[1], argv[2], argv[3] || 'Notes');
|
|
46
48
|
|
|
47
|
-
case
|
|
49
|
+
case 'delete':
|
|
48
50
|
if (argv.length < 2) {
|
|
49
|
-
return
|
|
51
|
+
return 'Error: delete requires a note title\n' + getUsage();
|
|
50
52
|
}
|
|
51
|
-
return deleteNote(argv[1], argv[2] ||
|
|
53
|
+
return deleteNote(argv[1], argv[2] || '');
|
|
54
|
+
|
|
55
|
+
case 'move':
|
|
56
|
+
if (argv.length < 3) {
|
|
57
|
+
return (
|
|
58
|
+
'Error: move requires a note title and destination folder\n' +
|
|
59
|
+
getUsage()
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
return moveNote(argv[1], argv[2], argv[3] || '');
|
|
52
63
|
|
|
53
64
|
default:
|
|
54
65
|
return `Error: Unknown command '${command}'\n` + getUsage();
|
|
@@ -63,7 +74,7 @@ function run(argv) {
|
|
|
63
74
|
// JXA now passes raw HTML to/from Apple Notes
|
|
64
75
|
|
|
65
76
|
function getNotesApp() {
|
|
66
|
-
return Application(
|
|
77
|
+
return Application('Notes');
|
|
67
78
|
}
|
|
68
79
|
|
|
69
80
|
function searchNotes(query) {
|
|
@@ -96,7 +107,7 @@ function searchNotes(query) {
|
|
|
96
107
|
output += `Folder: ${r.folder}\n`;
|
|
97
108
|
output += `Modified: ${r.modified}\n`;
|
|
98
109
|
output += `Preview: ${r.preview}\n`;
|
|
99
|
-
output +=
|
|
110
|
+
output += '---\n\n';
|
|
100
111
|
}
|
|
101
112
|
|
|
102
113
|
return output;
|
|
@@ -112,7 +123,7 @@ function listNotes(query) {
|
|
|
112
123
|
const name = note.name();
|
|
113
124
|
if (name && name.toLowerCase().includes(query.toLowerCase())) {
|
|
114
125
|
const folderName = getFolderName(note);
|
|
115
|
-
const body = note.body() ||
|
|
126
|
+
const body = note.body() || '';
|
|
116
127
|
const preview = getPreview(body, 80);
|
|
117
128
|
results.push({
|
|
118
129
|
index: results.length + 1,
|
|
@@ -145,7 +156,7 @@ function readNote(identifier, folderName) {
|
|
|
145
156
|
const app = getNotesApp();
|
|
146
157
|
let matchingNotes = [];
|
|
147
158
|
|
|
148
|
-
if (folderName ===
|
|
159
|
+
if (folderName === '') {
|
|
149
160
|
// Search all notes
|
|
150
161
|
const allNotes = app.notes();
|
|
151
162
|
for (let i = 0; i < allNotes.length; i++) {
|
|
@@ -192,7 +203,7 @@ function readNote(identifier, folderName) {
|
|
|
192
203
|
|
|
193
204
|
if (matchingNotes.length === 0) {
|
|
194
205
|
let errorMsg = `Error: No note found matching '${identifier}'`;
|
|
195
|
-
if (folderName !==
|
|
206
|
+
if (folderName !== '') {
|
|
196
207
|
errorMsg += ` in folder '${folderName}'`;
|
|
197
208
|
}
|
|
198
209
|
return errorMsg;
|
|
@@ -239,7 +250,7 @@ function readNoteByIndex(query, index) {
|
|
|
239
250
|
|
|
240
251
|
function parseRecentArgs(args) {
|
|
241
252
|
let limit = 5;
|
|
242
|
-
let folderName =
|
|
253
|
+
let folderName = '';
|
|
243
254
|
|
|
244
255
|
if (args.length >= 1) {
|
|
245
256
|
const arg1 = args[0];
|
|
@@ -267,7 +278,7 @@ function getRecentNotes(limit, folderName) {
|
|
|
267
278
|
const app = getNotesApp();
|
|
268
279
|
let notes = [];
|
|
269
280
|
|
|
270
|
-
if (folderName ===
|
|
281
|
+
if (folderName === '') {
|
|
271
282
|
notes = app.notes();
|
|
272
283
|
} else {
|
|
273
284
|
const folder = findFolder(folderName);
|
|
@@ -278,8 +289,8 @@ function getRecentNotes(limit, folderName) {
|
|
|
278
289
|
}
|
|
279
290
|
|
|
280
291
|
if (notes.length === 0) {
|
|
281
|
-
let msg =
|
|
282
|
-
if (folderName !==
|
|
292
|
+
let msg = 'No notes found';
|
|
293
|
+
if (folderName !== '') {
|
|
283
294
|
msg += ` in '${folderName}' folder`;
|
|
284
295
|
}
|
|
285
296
|
return msg;
|
|
@@ -301,15 +312,15 @@ function getRecentNotes(limit, folderName) {
|
|
|
301
312
|
// Get top N
|
|
302
313
|
const count = Math.min(limit, notesWithDates.length);
|
|
303
314
|
let output = `Last ${count} note(s)`;
|
|
304
|
-
if (folderName !==
|
|
315
|
+
if (folderName !== '') {
|
|
305
316
|
output += ` from '${folderName}' folder`;
|
|
306
317
|
}
|
|
307
|
-
output +=
|
|
318
|
+
output += ':\n\n';
|
|
308
319
|
|
|
309
320
|
for (let i = 0; i < count; i++) {
|
|
310
321
|
const item = notesWithDates[i];
|
|
311
322
|
const note = item.note;
|
|
312
|
-
const body = note.body() ||
|
|
323
|
+
const body = note.body() || '';
|
|
313
324
|
const preview = getPreview(body, 100);
|
|
314
325
|
const folder = getFolderName(note) || folderName;
|
|
315
326
|
|
|
@@ -337,12 +348,12 @@ function createNote(title, body, folderName) {
|
|
|
337
348
|
folder.notes.push(newNote);
|
|
338
349
|
|
|
339
350
|
// Get the created note to return info
|
|
340
|
-
const createdNote = folder.notes().find(
|
|
351
|
+
const createdNote = folder.notes().find(n => n.name() === uniqueTitle);
|
|
341
352
|
if (!createdNote) {
|
|
342
353
|
return `Note created but could not retrieve details.\nTitle: ${uniqueTitle}\nFolder: ${folderName}`;
|
|
343
354
|
}
|
|
344
355
|
|
|
345
|
-
let output =
|
|
356
|
+
let output = 'Note created successfully!\n\n';
|
|
346
357
|
output += `Title: ${createdNote.name()}\n`;
|
|
347
358
|
output += `Folder: ${folderName}\n`;
|
|
348
359
|
output += `Created: ${createdNote.creationDate().toString()}\n`;
|
|
@@ -355,7 +366,7 @@ function deleteNote(title, folderName) {
|
|
|
355
366
|
let targetNote = null;
|
|
356
367
|
let targetFolder = null;
|
|
357
368
|
|
|
358
|
-
if (folderName ===
|
|
369
|
+
if (folderName === '') {
|
|
359
370
|
// Search all notes for exact match
|
|
360
371
|
const allNotes = app.notes();
|
|
361
372
|
for (let i = 0; i < allNotes.length; i++) {
|
|
@@ -382,7 +393,7 @@ function deleteNote(title, folderName) {
|
|
|
382
393
|
|
|
383
394
|
if (!targetNote) {
|
|
384
395
|
let errorMsg = `Error: No note found with exact title '${title}'`;
|
|
385
|
-
if (folderName !==
|
|
396
|
+
if (folderName !== '') {
|
|
386
397
|
errorMsg += ` in folder '${folderName}'`;
|
|
387
398
|
}
|
|
388
399
|
return errorMsg;
|
|
@@ -397,6 +408,78 @@ function deleteNote(title, folderName) {
|
|
|
397
408
|
return `Note deleted successfully!\n\nTitle: ${deletedTitle}\nFolder: ${deletedFolder}`;
|
|
398
409
|
}
|
|
399
410
|
|
|
411
|
+
function moveNote(title, destFolderName, sourceFolderName) {
|
|
412
|
+
const app = getNotesApp();
|
|
413
|
+
const destFolder = findFolder(destFolderName);
|
|
414
|
+
|
|
415
|
+
if (!destFolder) {
|
|
416
|
+
return `Error: Destination folder '${destFolderName}' not found`;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
let targetNote = null;
|
|
420
|
+
let sourceFolder = null;
|
|
421
|
+
|
|
422
|
+
if (sourceFolderName && sourceFolderName !== '') {
|
|
423
|
+
sourceFolder = findFolder(sourceFolderName);
|
|
424
|
+
if (!sourceFolder) {
|
|
425
|
+
return `Error: Source folder '${sourceFolderName}' not found`;
|
|
426
|
+
}
|
|
427
|
+
const folderNotes = sourceFolder.notes();
|
|
428
|
+
for (let i = 0; i < folderNotes.length; i++) {
|
|
429
|
+
const note = folderNotes[i];
|
|
430
|
+
if (note.name() === title) {
|
|
431
|
+
targetNote = note;
|
|
432
|
+
break;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
} else {
|
|
436
|
+
// Global search
|
|
437
|
+
// We need to check for ambiguity
|
|
438
|
+
const allNotes = app.notes();
|
|
439
|
+
const matches = [];
|
|
440
|
+
for (let i = 0; i < allNotes.length; i++) {
|
|
441
|
+
const note = allNotes[i];
|
|
442
|
+
if (note.name() === title) {
|
|
443
|
+
matches.push(note);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (matches.length === 0) {
|
|
448
|
+
return `Error: No note found with exact title '${title}'`;
|
|
449
|
+
} else if (matches.length > 1) {
|
|
450
|
+
let msg = `Error: Multiple notes found with title '${title}'. Please specify the source folder:\n\n`;
|
|
451
|
+
for (let i = 0; i < matches.length; i++) {
|
|
452
|
+
const note = matches[i];
|
|
453
|
+
const folder = getFolderName(note);
|
|
454
|
+
msg += `- ${title} (in '${folder}')\n`;
|
|
455
|
+
}
|
|
456
|
+
return msg;
|
|
457
|
+
} else {
|
|
458
|
+
targetNote = matches[0];
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (!targetNote) {
|
|
463
|
+
let errorMsg = `Error: No note found with exact title '${title}'`;
|
|
464
|
+
if (sourceFolderName) {
|
|
465
|
+
errorMsg += ` in folder '${sourceFolderName}'`;
|
|
466
|
+
}
|
|
467
|
+
return errorMsg;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const oldFolderName = getFolderName(targetNote);
|
|
471
|
+
|
|
472
|
+
// If already in destination, do nothing
|
|
473
|
+
if (oldFolderName === destFolder.name()) {
|
|
474
|
+
return `Note '${title}' is already in '${destFolderName}'`;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Execute move
|
|
478
|
+
app.move(targetNote, { to: destFolder });
|
|
479
|
+
|
|
480
|
+
return `Note moved successfully!\n\nTitle: ${title}\nFrom: ${oldFolderName}\nTo: ${destFolder.name()}`;
|
|
481
|
+
}
|
|
482
|
+
|
|
400
483
|
// ============================================================================
|
|
401
484
|
// Helper Functions
|
|
402
485
|
// ============================================================================
|
|
@@ -405,21 +488,31 @@ function findFolder(folderPath) {
|
|
|
405
488
|
const app = getNotesApp();
|
|
406
489
|
const defaultAccount = app.defaultAccount();
|
|
407
490
|
|
|
408
|
-
if (folderPath.includes(
|
|
491
|
+
if (folderPath.includes('/')) {
|
|
409
492
|
// Nested folder path
|
|
410
|
-
const parts = folderPath.split(
|
|
411
|
-
let currentFolder =
|
|
493
|
+
const parts = folderPath.split('/');
|
|
494
|
+
let currentFolder = null;
|
|
495
|
+
|
|
496
|
+
// Find the first folder at the account level
|
|
497
|
+
const topLevelFolders = defaultAccount.folders();
|
|
498
|
+
for (let i = 0; i < topLevelFolders.length; i++) {
|
|
499
|
+
if (topLevelFolders[i].name() === parts[0]) {
|
|
500
|
+
currentFolder = topLevelFolders[i];
|
|
501
|
+
break;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
412
504
|
|
|
413
505
|
if (!currentFolder) {
|
|
414
506
|
return null;
|
|
415
507
|
}
|
|
416
508
|
|
|
417
|
-
|
|
509
|
+
// Traverse the rest of the path
|
|
510
|
+
for (let i = 1; i < parts.length; i++) {
|
|
418
511
|
const subfolders = currentFolder.folders();
|
|
419
512
|
let found = false;
|
|
420
|
-
for (let
|
|
421
|
-
if (subfolders[
|
|
422
|
-
currentFolder = subfolders[
|
|
513
|
+
for (let j = 0; j < subfolders.length; j++) {
|
|
514
|
+
if (subfolders[j].name() === parts[i]) {
|
|
515
|
+
currentFolder = subfolders[j];
|
|
423
516
|
found = true;
|
|
424
517
|
break;
|
|
425
518
|
}
|
|
@@ -451,33 +544,33 @@ function getFolderName(note) {
|
|
|
451
544
|
} catch (e) {
|
|
452
545
|
// Container might not be accessible
|
|
453
546
|
}
|
|
454
|
-
return
|
|
547
|
+
return '';
|
|
455
548
|
}
|
|
456
549
|
|
|
457
550
|
function getPreview(body, maxLength) {
|
|
458
|
-
if (!body) return
|
|
551
|
+
if (!body) return '';
|
|
459
552
|
const length = Math.min(maxLength, body.length);
|
|
460
553
|
let preview = body.substring(0, length);
|
|
461
554
|
if (body.length > length) {
|
|
462
|
-
preview +=
|
|
555
|
+
preview += '...';
|
|
463
556
|
}
|
|
464
557
|
return preview;
|
|
465
558
|
}
|
|
466
559
|
|
|
467
560
|
function formatNoteContent(note) {
|
|
468
561
|
const title = note.name();
|
|
469
|
-
const body = note.body() ||
|
|
562
|
+
const body = note.body() || '';
|
|
470
563
|
const folder = getFolderName(note);
|
|
471
564
|
const created = note.creationDate().toString();
|
|
472
565
|
const modified = note.modificationDate().toString();
|
|
473
566
|
|
|
474
|
-
let output =
|
|
567
|
+
let output = '========================================\n';
|
|
475
568
|
output += `Title: ${title}\n`;
|
|
476
569
|
output += `Folder: ${folder}\n`;
|
|
477
570
|
output += `Created: ${created}\n`;
|
|
478
571
|
output += `Modified: ${modified}\n`;
|
|
479
|
-
output +=
|
|
480
|
-
output += body +
|
|
572
|
+
output += '========================================\n\n';
|
|
573
|
+
output += body + '\n';
|
|
481
574
|
|
|
482
575
|
return output;
|
|
483
576
|
}
|
|
@@ -513,6 +606,7 @@ function getUsage() {
|
|
|
513
606
|
apple-notes recent [count] [folder] - Get recent notes (default: 5)
|
|
514
607
|
apple-notes create <title> <body> [folder] - Create note from markdown
|
|
515
608
|
apple-notes delete <title> [folder] - Delete note by title
|
|
609
|
+
apple-notes move <title> <destination> [source] - Move note to folder
|
|
516
610
|
|
|
517
611
|
Examples:
|
|
518
612
|
apple-notes list 'meeting'
|
|
@@ -521,5 +615,6 @@ Examples:
|
|
|
521
615
|
apple-notes recent 10 'Blog'
|
|
522
616
|
apple-notes create 'Meeting Notes' '# Agenda\\n- Item 1' 'Work'
|
|
523
617
|
apple-notes delete 'Old Note' 'Archive'
|
|
618
|
+
apple-notes move 'Idea' 'Projects' 'Inbox'
|
|
524
619
|
`;
|
|
525
620
|
}
|