@peerasak-u/apple-notes 1.0.1 → 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/dist/index.js +138 -4
- package/package.json +3 -4
- package/src/jxa/notes.js +149 -201
package/dist/index.js
CHANGED
|
@@ -4,8 +4,129 @@
|
|
|
4
4
|
import { execSync } from "child_process";
|
|
5
5
|
import { join, dirname } from "path";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
|
+
|
|
8
|
+
// src/converter.ts
|
|
9
|
+
function htmlToMarkdown(html) {
|
|
10
|
+
if (!html)
|
|
11
|
+
return "";
|
|
12
|
+
let md = html;
|
|
13
|
+
md = md.replace(/<div[^>]*>/gi, `
|
|
14
|
+
`);
|
|
15
|
+
md = md.replace(/<\/div>/gi, "");
|
|
16
|
+
md = md.replace(/<h1[^>]*>(.*?)<\/h1>/gi, `# $1
|
|
17
|
+
`);
|
|
18
|
+
md = md.replace(/<h2[^>]*>(.*?)<\/h2>/gi, `## $1
|
|
19
|
+
`);
|
|
20
|
+
md = md.replace(/<h3[^>]*>(.*?)<\/h3>/gi, `### $1
|
|
21
|
+
`);
|
|
22
|
+
md = md.replace(/<h4[^>]*>(.*?)<\/h4>/gi, `#### $1
|
|
23
|
+
`);
|
|
24
|
+
md = md.replace(/<h5[^>]*>(.*?)<\/h5>/gi, `##### $1
|
|
25
|
+
`);
|
|
26
|
+
md = md.replace(/<h6[^>]*>(.*?)<\/h6>/gi, `###### $1
|
|
27
|
+
`);
|
|
28
|
+
md = md.replace(/<(b|strong)[^>]*>(.*?)<\/(b|strong)>/gi, "**$2**");
|
|
29
|
+
md = md.replace(/<(i|em)[^>]*>(.*?)<\/(i|em)>/gi, "*$2*");
|
|
30
|
+
md = md.replace(/<u[^>]*>(.*?)<\/u>/gi, "_$1_");
|
|
31
|
+
md = md.replace(/<s[^>]*>(.*?)<\/s>/gi, "~~$1~~");
|
|
32
|
+
md = md.replace(/<strike[^>]*>(.*?)<\/strike>/gi, "~~$1~~");
|
|
33
|
+
md = md.replace(/<a[^>]*href="([^"]*)"[^>]*>(.*?)<\/a>/gi, "[$2]($1)");
|
|
34
|
+
md = md.replace(/<img[^>]*src="([^"]*)"[^>]*alt="([^"]*)"[^>]*\/?>/gi, "");
|
|
35
|
+
md = md.replace(/<img[^>]*src="([^"]*)"[^>]*\/?>/gi, "");
|
|
36
|
+
md = md.replace(/<ul[^>]*>/gi, `
|
|
37
|
+
`);
|
|
38
|
+
md = md.replace(/<\/ul>/gi, `
|
|
39
|
+
`);
|
|
40
|
+
md = md.replace(/<ol[^>]*>/gi, `
|
|
41
|
+
`);
|
|
42
|
+
md = md.replace(/<\/ol>/gi, `
|
|
43
|
+
`);
|
|
44
|
+
md = md.replace(/<li[^>]*>(.*?)<\/li>/gi, `- $1
|
|
45
|
+
`);
|
|
46
|
+
md = md.replace(/<br\s*\/?>/gi, `
|
|
47
|
+
`);
|
|
48
|
+
md = md.replace(/<p[^>]*>/gi, `
|
|
49
|
+
`);
|
|
50
|
+
md = md.replace(/<\/p>/gi, `
|
|
51
|
+
`);
|
|
52
|
+
md = md.replace(/<code[^>]*>(.*?)<\/code>/gi, "`$1`");
|
|
53
|
+
md = md.replace(/<pre[^>]*>(.*?)<\/pre>/gis, "```\n$1\n```\n");
|
|
54
|
+
md = md.replace(/<blockquote[^>]*>(.*?)<\/blockquote>/gis, `> $1
|
|
55
|
+
`);
|
|
56
|
+
md = md.replace(/<hr\s*\/?>/gi, `
|
|
57
|
+
---
|
|
58
|
+
`);
|
|
59
|
+
md = md.replace(/<[^>]+>/g, "");
|
|
60
|
+
md = md.replace(/ /g, " ");
|
|
61
|
+
md = md.replace(/&/g, "&");
|
|
62
|
+
md = md.replace(/</g, "<");
|
|
63
|
+
md = md.replace(/>/g, ">");
|
|
64
|
+
md = md.replace(/"/g, '"');
|
|
65
|
+
md = md.replace(/'/g, "'");
|
|
66
|
+
md = md.replace(/'/g, "'");
|
|
67
|
+
md = md.replace(/\n{3,}/g, `
|
|
68
|
+
|
|
69
|
+
`);
|
|
70
|
+
md = md.trim();
|
|
71
|
+
if (/<[a-z][\s\S]*>/i.test(md)) {
|
|
72
|
+
return `[RAW_HTML]
|
|
73
|
+
` + html;
|
|
74
|
+
}
|
|
75
|
+
return md;
|
|
76
|
+
}
|
|
77
|
+
function markdownToHtml(markdown) {
|
|
78
|
+
if (!markdown)
|
|
79
|
+
return "";
|
|
80
|
+
let html = markdown;
|
|
81
|
+
html = html.replace(/\\n/g, `
|
|
82
|
+
`);
|
|
83
|
+
html = html.replace(/\\t/g, "\t");
|
|
84
|
+
html = html.replace(/^###### (.+)$/gm, "<h6>$1</h6>");
|
|
85
|
+
html = html.replace(/^##### (.+)$/gm, "<h5>$1</h5>");
|
|
86
|
+
html = html.replace(/^#### (.+)$/gm, "<h4>$1</h4>");
|
|
87
|
+
html = html.replace(/^### (.+)$/gm, "<h3>$1</h3>");
|
|
88
|
+
html = html.replace(/^## (.+)$/gm, "<h2>$1</h2>");
|
|
89
|
+
html = html.replace(/^# (.+)$/gm, "<h1>$1</h1>");
|
|
90
|
+
html = html.replace(/\*\*\*(.+?)\*\*\*/g, "<b><i>$1</i></b>");
|
|
91
|
+
html = html.replace(/\*\*(.+?)\*\*/g, "<b>$1</b>");
|
|
92
|
+
html = html.replace(/\*(.+?)\*/g, "<i>$1</i>");
|
|
93
|
+
html = html.replace(/_(.+?)_/g, "<i>$1</i>");
|
|
94
|
+
html = html.replace(/~~(.+?)~~/g, "<s>$1</s>");
|
|
95
|
+
html = html.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1">');
|
|
96
|
+
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
|
|
97
|
+
html = html.replace(/```([\s\S]*?)```/g, "<pre>$1</pre>");
|
|
98
|
+
html = html.replace(/`([^`]+)`/g, "<code>$1</code>");
|
|
99
|
+
html = html.replace(/^> (.+)$/gm, "<blockquote>$1</blockquote>");
|
|
100
|
+
html = html.replace(/^---$/gm, "<hr>");
|
|
101
|
+
html = html.replace(/^\*\*\*$/gm, "<hr>");
|
|
102
|
+
html = html.replace(/^- (.+)$/gm, "<li>$1</li>");
|
|
103
|
+
html = html.replace(/^\* (.+)$/gm, "<li>$1</li>");
|
|
104
|
+
html = html.replace(/^\d+\. (.+)$/gm, "<li>$1</li>");
|
|
105
|
+
html = html.replace(/(<li>.*<\/li>\n?)+/g, (match) => {
|
|
106
|
+
return "<ul>" + match + "</ul>";
|
|
107
|
+
});
|
|
108
|
+
const lines = html.split(`
|
|
109
|
+
`);
|
|
110
|
+
const result = [];
|
|
111
|
+
for (let i = 0;i < lines.length; i++) {
|
|
112
|
+
const line = lines[i].trim();
|
|
113
|
+
if (line === "") {
|
|
114
|
+
result.push("<br>");
|
|
115
|
+
} else if (!/^</.test(line)) {
|
|
116
|
+
result.push("<div>" + line + "</div>");
|
|
117
|
+
} else {
|
|
118
|
+
result.push(line);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
html = result.join(`
|
|
122
|
+
`);
|
|
123
|
+
return html;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// src/executor.ts
|
|
7
127
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
8
128
|
var __dirname2 = dirname(__filename2);
|
|
129
|
+
var HTML_OUTPUT_COMMANDS = new Set(["search", "list", "read", "read-index", "recent"]);
|
|
9
130
|
function getNotesScriptPath() {
|
|
10
131
|
return join(__dirname2, "..", "src", "jxa", "notes.js");
|
|
11
132
|
}
|
|
@@ -13,16 +134,29 @@ function escapeShellArg(arg) {
|
|
|
13
134
|
return `'${arg.replace(/'/g, "'\\''")}'`;
|
|
14
135
|
}
|
|
15
136
|
function executeNotesCommand(args) {
|
|
137
|
+
const command = args[0];
|
|
138
|
+
let processedArgs = [...args];
|
|
139
|
+
if (command === "create" && args.length >= 3) {
|
|
140
|
+
const htmlBody = markdownToHtml(args[2]);
|
|
141
|
+
processedArgs = [args[0], args[1], htmlBody];
|
|
142
|
+
if (args[3]) {
|
|
143
|
+
processedArgs.push(args[3]);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
16
146
|
const scriptPath = getNotesScriptPath();
|
|
17
|
-
const escapedArgs =
|
|
18
|
-
const
|
|
147
|
+
const escapedArgs = processedArgs.map(escapeShellArg).join(" ");
|
|
148
|
+
const shellCommand = `osascript -l JavaScript "${scriptPath}" ${escapedArgs}`;
|
|
19
149
|
const options = {
|
|
20
150
|
encoding: "utf-8",
|
|
21
151
|
maxBuffer: 10 * 1024 * 1024
|
|
22
152
|
};
|
|
23
153
|
try {
|
|
24
|
-
const result = execSync(
|
|
25
|
-
|
|
154
|
+
const result = execSync(shellCommand, options);
|
|
155
|
+
const output = result.trim();
|
|
156
|
+
if (HTML_OUTPUT_COMMANDS.has(command)) {
|
|
157
|
+
return htmlToMarkdown(output);
|
|
158
|
+
}
|
|
159
|
+
return output;
|
|
26
160
|
} catch (error) {
|
|
27
161
|
if (error instanceof Error && "stderr" in error) {
|
|
28
162
|
const stderr = error.stderr;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peerasak-u/apple-notes",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "CLI tool for interacting with Apple Notes on macOS",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -12,9 +12,8 @@
|
|
|
12
12
|
],
|
|
13
13
|
"scripts": {
|
|
14
14
|
"build": "bun build src/index.ts --outdir dist --target node",
|
|
15
|
-
"
|
|
16
|
-
"test": "bun
|
|
17
|
-
"test:coverage": "bun run sync-utils && bun test --coverage tests/*.test.js",
|
|
15
|
+
"test": "bun test tests/*.test.js",
|
|
16
|
+
"test:coverage": "bun test --coverage tests/*.test.js",
|
|
18
17
|
"lint": "eslint tests/*.js",
|
|
19
18
|
"lint:fix": "eslint --fix tests/*.js",
|
|
20
19
|
"typecheck": "tsc --noEmit"
|
package/src/jxa/notes.js
CHANGED
|
@@ -11,204 +11,70 @@ 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();
|
|
55
66
|
}
|
|
56
67
|
}
|
|
57
68
|
|
|
58
|
-
// ============================================================================
|
|
59
|
-
// HTML <-> Markdown Conversion
|
|
60
|
-
// ============================================================================
|
|
61
|
-
|
|
62
|
-
function htmlToMarkdown(html) {
|
|
63
|
-
if (!html) return "";
|
|
64
|
-
|
|
65
|
-
let md = html;
|
|
66
|
-
|
|
67
|
-
// Remove Apple Notes specific wrapper divs but keep content
|
|
68
|
-
md = md.replace(/<div[^>]*>/gi, "\n");
|
|
69
|
-
md = md.replace(/<\/div>/gi, "");
|
|
70
|
-
|
|
71
|
-
// Headings
|
|
72
|
-
md = md.replace(/<h1[^>]*>(.*?)<\/h1>/gi, "# $1\n");
|
|
73
|
-
md = md.replace(/<h2[^>]*>(.*?)<\/h2>/gi, "## $1\n");
|
|
74
|
-
md = md.replace(/<h3[^>]*>(.*?)<\/h3>/gi, "### $1\n");
|
|
75
|
-
md = md.replace(/<h4[^>]*>(.*?)<\/h4>/gi, "#### $1\n");
|
|
76
|
-
md = md.replace(/<h5[^>]*>(.*?)<\/h5>/gi, "##### $1\n");
|
|
77
|
-
md = md.replace(/<h6[^>]*>(.*?)<\/h6>/gi, "###### $1\n");
|
|
78
|
-
|
|
79
|
-
// Bold and italic
|
|
80
|
-
md = md.replace(/<(b|strong)[^>]*>(.*?)<\/(b|strong)>/gi, "**$2**");
|
|
81
|
-
md = md.replace(/<(i|em)[^>]*>(.*?)<\/(i|em)>/gi, "*$2*");
|
|
82
|
-
md = md.replace(/<u[^>]*>(.*?)<\/u>/gi, "_$1_");
|
|
83
|
-
md = md.replace(/<s[^>]*>(.*?)<\/s>/gi, "~~$1~~");
|
|
84
|
-
md = md.replace(/<strike[^>]*>(.*?)<\/strike>/gi, "~~$1~~");
|
|
85
|
-
|
|
86
|
-
// Links
|
|
87
|
-
md = md.replace(/<a[^>]*href="([^"]*)"[^>]*>(.*?)<\/a>/gi, "[$2]($1)");
|
|
88
|
-
|
|
89
|
-
// Images
|
|
90
|
-
md = md.replace(/<img[^>]*src="([^"]*)"[^>]*alt="([^"]*)"[^>]*\/?>/gi, "");
|
|
91
|
-
md = md.replace(/<img[^>]*src="([^"]*)"[^>]*\/?>/gi, "");
|
|
92
|
-
|
|
93
|
-
// Lists - handle nested lists
|
|
94
|
-
md = md.replace(/<ul[^>]*>/gi, "\n");
|
|
95
|
-
md = md.replace(/<\/ul>/gi, "\n");
|
|
96
|
-
md = md.replace(/<ol[^>]*>/gi, "\n");
|
|
97
|
-
md = md.replace(/<\/ol>/gi, "\n");
|
|
98
|
-
md = md.replace(/<li[^>]*>(.*?)<\/li>/gi, "- $1\n");
|
|
99
|
-
|
|
100
|
-
// Line breaks and paragraphs
|
|
101
|
-
md = md.replace(/<br\s*\/?>/gi, "\n");
|
|
102
|
-
md = md.replace(/<p[^>]*>/gi, "\n");
|
|
103
|
-
md = md.replace(/<\/p>/gi, "\n");
|
|
104
|
-
|
|
105
|
-
// Code
|
|
106
|
-
md = md.replace(/<code[^>]*>(.*?)<\/code>/gi, "`$1`");
|
|
107
|
-
md = md.replace(/<pre[^>]*>(.*?)<\/pre>/gis, "```\n$1\n```\n");
|
|
108
|
-
|
|
109
|
-
// Blockquote
|
|
110
|
-
md = md.replace(/<blockquote[^>]*>(.*?)<\/blockquote>/gis, "> $1\n");
|
|
111
|
-
|
|
112
|
-
// Horizontal rule
|
|
113
|
-
md = md.replace(/<hr\s*\/?>/gi, "\n---\n");
|
|
114
|
-
|
|
115
|
-
// Remove remaining HTML tags
|
|
116
|
-
md = md.replace(/<[^>]+>/g, "");
|
|
117
|
-
|
|
118
|
-
// Decode HTML entities
|
|
119
|
-
md = md.replace(/ /g, " ");
|
|
120
|
-
md = md.replace(/&/g, "&");
|
|
121
|
-
md = md.replace(/</g, "<");
|
|
122
|
-
md = md.replace(/>/g, ">");
|
|
123
|
-
md = md.replace(/"/g, '"');
|
|
124
|
-
md = md.replace(/'/g, "'");
|
|
125
|
-
md = md.replace(/'/g, "'");
|
|
126
|
-
|
|
127
|
-
// Clean up multiple newlines
|
|
128
|
-
md = md.replace(/\n{3,}/g, "\n\n");
|
|
129
|
-
md = md.trim();
|
|
130
|
-
|
|
131
|
-
// Check if conversion was successful (no remaining HTML-like content)
|
|
132
|
-
if (/<[a-z][\s\S]*>/i.test(md)) {
|
|
133
|
-
return "[RAW_HTML]\n" + html;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return md;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function markdownToHtml(markdown) {
|
|
140
|
-
if (!markdown) return "";
|
|
141
|
-
|
|
142
|
-
let html = markdown;
|
|
143
|
-
|
|
144
|
-
// Unescape common escape sequences from command line
|
|
145
|
-
html = html.replace(/\\n/g, "\n");
|
|
146
|
-
html = html.replace(/\\t/g, "\t");
|
|
147
|
-
|
|
148
|
-
// Headings (must be at start of line)
|
|
149
|
-
html = html.replace(/^###### (.+)$/gm, "<h6>$1</h6>");
|
|
150
|
-
html = html.replace(/^##### (.+)$/gm, "<h5>$1</h5>");
|
|
151
|
-
html = html.replace(/^#### (.+)$/gm, "<h4>$1</h4>");
|
|
152
|
-
html = html.replace(/^### (.+)$/gm, "<h3>$1</h3>");
|
|
153
|
-
html = html.replace(/^## (.+)$/gm, "<h2>$1</h2>");
|
|
154
|
-
html = html.replace(/^# (.+)$/gm, "<h1>$1</h1>");
|
|
155
|
-
|
|
156
|
-
// Bold and italic (order matters)
|
|
157
|
-
html = html.replace(/\*\*\*(.+?)\*\*\*/g, "<b><i>$1</i></b>");
|
|
158
|
-
html = html.replace(/\*\*(.+?)\*\*/g, "<b>$1</b>");
|
|
159
|
-
html = html.replace(/\*(.+?)\*/g, "<i>$1</i>");
|
|
160
|
-
html = html.replace(/_(.+?)_/g, "<i>$1</i>");
|
|
161
|
-
html = html.replace(/~~(.+?)~~/g, "<s>$1</s>");
|
|
162
|
-
|
|
163
|
-
// Links and images
|
|
164
|
-
html = html.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1">');
|
|
165
|
-
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
|
|
166
|
-
|
|
167
|
-
// Code
|
|
168
|
-
html = html.replace(/```([\s\S]*?)```/g, "<pre>$1</pre>");
|
|
169
|
-
html = html.replace(/`([^`]+)`/g, "<code>$1</code>");
|
|
170
|
-
|
|
171
|
-
// Blockquote
|
|
172
|
-
html = html.replace(/^> (.+)$/gm, "<blockquote>$1</blockquote>");
|
|
173
|
-
|
|
174
|
-
// Horizontal rule
|
|
175
|
-
html = html.replace(/^---$/gm, "<hr>");
|
|
176
|
-
html = html.replace(/^\*\*\*$/gm, "<hr>");
|
|
177
|
-
|
|
178
|
-
// Lists (simple handling)
|
|
179
|
-
html = html.replace(/^- (.+)$/gm, "<li>$1</li>");
|
|
180
|
-
html = html.replace(/^\* (.+)$/gm, "<li>$1</li>");
|
|
181
|
-
html = html.replace(/^\d+\. (.+)$/gm, "<li>$1</li>");
|
|
182
|
-
|
|
183
|
-
// Wrap consecutive <li> in <ul>
|
|
184
|
-
html = html.replace(/(<li>.*<\/li>\n?)+/g, function (match) {
|
|
185
|
-
return "<ul>" + match + "</ul>";
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
// Paragraphs - wrap lines that aren't already HTML
|
|
189
|
-
const lines = html.split("\n");
|
|
190
|
-
const result = [];
|
|
191
|
-
for (let i = 0; i < lines.length; i++) {
|
|
192
|
-
const line = lines[i].trim();
|
|
193
|
-
if (line === "") {
|
|
194
|
-
result.push("<br>");
|
|
195
|
-
} else if (!/^</.test(line)) {
|
|
196
|
-
result.push("<div>" + line + "</div>");
|
|
197
|
-
} else {
|
|
198
|
-
result.push(line);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
html = result.join("\n");
|
|
202
|
-
|
|
203
|
-
return html;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
69
|
// ============================================================================
|
|
207
70
|
// Notes App Interaction
|
|
208
71
|
// ============================================================================
|
|
209
72
|
|
|
73
|
+
// HTML <-> Markdown conversion is now handled in TypeScript (converter.ts)
|
|
74
|
+
// JXA now passes raw HTML to/from Apple Notes
|
|
75
|
+
|
|
210
76
|
function getNotesApp() {
|
|
211
|
-
return Application(
|
|
77
|
+
return Application('Notes');
|
|
212
78
|
}
|
|
213
79
|
|
|
214
80
|
function searchNotes(query) {
|
|
@@ -226,7 +92,7 @@ function searchNotes(query) {
|
|
|
226
92
|
title: note.name(),
|
|
227
93
|
folder: folderName,
|
|
228
94
|
modified: note.modificationDate().toString(),
|
|
229
|
-
preview:
|
|
95
|
+
preview: preview,
|
|
230
96
|
});
|
|
231
97
|
}
|
|
232
98
|
}
|
|
@@ -241,7 +107,7 @@ function searchNotes(query) {
|
|
|
241
107
|
output += `Folder: ${r.folder}\n`;
|
|
242
108
|
output += `Modified: ${r.modified}\n`;
|
|
243
109
|
output += `Preview: ${r.preview}\n`;
|
|
244
|
-
output +=
|
|
110
|
+
output += '---\n\n';
|
|
245
111
|
}
|
|
246
112
|
|
|
247
113
|
return output;
|
|
@@ -257,14 +123,14 @@ function listNotes(query) {
|
|
|
257
123
|
const name = note.name();
|
|
258
124
|
if (name && name.toLowerCase().includes(query.toLowerCase())) {
|
|
259
125
|
const folderName = getFolderName(note);
|
|
260
|
-
const body = note.body() ||
|
|
126
|
+
const body = note.body() || '';
|
|
261
127
|
const preview = getPreview(body, 80);
|
|
262
128
|
results.push({
|
|
263
129
|
index: results.length + 1,
|
|
264
130
|
title: name,
|
|
265
131
|
folder: folderName,
|
|
266
132
|
modified: note.modificationDate().toString(),
|
|
267
|
-
preview:
|
|
133
|
+
preview: preview,
|
|
268
134
|
});
|
|
269
135
|
}
|
|
270
136
|
}
|
|
@@ -290,7 +156,7 @@ function readNote(identifier, folderName) {
|
|
|
290
156
|
const app = getNotesApp();
|
|
291
157
|
let matchingNotes = [];
|
|
292
158
|
|
|
293
|
-
if (folderName ===
|
|
159
|
+
if (folderName === '') {
|
|
294
160
|
// Search all notes
|
|
295
161
|
const allNotes = app.notes();
|
|
296
162
|
for (let i = 0; i < allNotes.length; i++) {
|
|
@@ -337,7 +203,7 @@ function readNote(identifier, folderName) {
|
|
|
337
203
|
|
|
338
204
|
if (matchingNotes.length === 0) {
|
|
339
205
|
let errorMsg = `Error: No note found matching '${identifier}'`;
|
|
340
|
-
if (folderName !==
|
|
206
|
+
if (folderName !== '') {
|
|
341
207
|
errorMsg += ` in folder '${folderName}'`;
|
|
342
208
|
}
|
|
343
209
|
return errorMsg;
|
|
@@ -384,7 +250,7 @@ function readNoteByIndex(query, index) {
|
|
|
384
250
|
|
|
385
251
|
function parseRecentArgs(args) {
|
|
386
252
|
let limit = 5;
|
|
387
|
-
let folderName =
|
|
253
|
+
let folderName = '';
|
|
388
254
|
|
|
389
255
|
if (args.length >= 1) {
|
|
390
256
|
const arg1 = args[0];
|
|
@@ -412,7 +278,7 @@ function getRecentNotes(limit, folderName) {
|
|
|
412
278
|
const app = getNotesApp();
|
|
413
279
|
let notes = [];
|
|
414
280
|
|
|
415
|
-
if (folderName ===
|
|
281
|
+
if (folderName === '') {
|
|
416
282
|
notes = app.notes();
|
|
417
283
|
} else {
|
|
418
284
|
const folder = findFolder(folderName);
|
|
@@ -423,8 +289,8 @@ function getRecentNotes(limit, folderName) {
|
|
|
423
289
|
}
|
|
424
290
|
|
|
425
291
|
if (notes.length === 0) {
|
|
426
|
-
let msg =
|
|
427
|
-
if (folderName !==
|
|
292
|
+
let msg = 'No notes found';
|
|
293
|
+
if (folderName !== '') {
|
|
428
294
|
msg += ` in '${folderName}' folder`;
|
|
429
295
|
}
|
|
430
296
|
return msg;
|
|
@@ -446,22 +312,22 @@ function getRecentNotes(limit, folderName) {
|
|
|
446
312
|
// Get top N
|
|
447
313
|
const count = Math.min(limit, notesWithDates.length);
|
|
448
314
|
let output = `Last ${count} note(s)`;
|
|
449
|
-
if (folderName !==
|
|
315
|
+
if (folderName !== '') {
|
|
450
316
|
output += ` from '${folderName}' folder`;
|
|
451
317
|
}
|
|
452
|
-
output +=
|
|
318
|
+
output += ':\n\n';
|
|
453
319
|
|
|
454
320
|
for (let i = 0; i < count; i++) {
|
|
455
321
|
const item = notesWithDates[i];
|
|
456
322
|
const note = item.note;
|
|
457
|
-
const body = note.body() ||
|
|
323
|
+
const body = note.body() || '';
|
|
458
324
|
const preview = getPreview(body, 100);
|
|
459
325
|
const folder = getFolderName(note) || folderName;
|
|
460
326
|
|
|
461
327
|
output += `[${i + 1}] ${note.name()}\n`;
|
|
462
328
|
output += ` Folder: ${folder}\n`;
|
|
463
329
|
output += ` Modified: ${item.modDate.toString()}\n`;
|
|
464
|
-
output += ` Preview: ${
|
|
330
|
+
output += ` Preview: ${preview}\n\n`;
|
|
465
331
|
}
|
|
466
332
|
|
|
467
333
|
return output;
|
|
@@ -475,19 +341,19 @@ function createNote(title, body, folderName) {
|
|
|
475
341
|
return `Error: Folder '${folderName}' not found`;
|
|
476
342
|
}
|
|
477
343
|
|
|
478
|
-
|
|
344
|
+
// body is now pre-converted to HTML in TypeScript
|
|
479
345
|
const uniqueTitle = generateUniqueTitle(title, folder);
|
|
480
346
|
|
|
481
|
-
const newNote = app.Note({ name: uniqueTitle, body:
|
|
347
|
+
const newNote = app.Note({ name: uniqueTitle, body: body });
|
|
482
348
|
folder.notes.push(newNote);
|
|
483
349
|
|
|
484
350
|
// Get the created note to return info
|
|
485
|
-
const createdNote = folder.notes().find(
|
|
351
|
+
const createdNote = folder.notes().find(n => n.name() === uniqueTitle);
|
|
486
352
|
if (!createdNote) {
|
|
487
353
|
return `Note created but could not retrieve details.\nTitle: ${uniqueTitle}\nFolder: ${folderName}`;
|
|
488
354
|
}
|
|
489
355
|
|
|
490
|
-
let output =
|
|
356
|
+
let output = 'Note created successfully!\n\n';
|
|
491
357
|
output += `Title: ${createdNote.name()}\n`;
|
|
492
358
|
output += `Folder: ${folderName}\n`;
|
|
493
359
|
output += `Created: ${createdNote.creationDate().toString()}\n`;
|
|
@@ -500,7 +366,7 @@ function deleteNote(title, folderName) {
|
|
|
500
366
|
let targetNote = null;
|
|
501
367
|
let targetFolder = null;
|
|
502
368
|
|
|
503
|
-
if (folderName ===
|
|
369
|
+
if (folderName === '') {
|
|
504
370
|
// Search all notes for exact match
|
|
505
371
|
const allNotes = app.notes();
|
|
506
372
|
for (let i = 0; i < allNotes.length; i++) {
|
|
@@ -527,7 +393,7 @@ function deleteNote(title, folderName) {
|
|
|
527
393
|
|
|
528
394
|
if (!targetNote) {
|
|
529
395
|
let errorMsg = `Error: No note found with exact title '${title}'`;
|
|
530
|
-
if (folderName !==
|
|
396
|
+
if (folderName !== '') {
|
|
531
397
|
errorMsg += ` in folder '${folderName}'`;
|
|
532
398
|
}
|
|
533
399
|
return errorMsg;
|
|
@@ -542,6 +408,78 @@ function deleteNote(title, folderName) {
|
|
|
542
408
|
return `Note deleted successfully!\n\nTitle: ${deletedTitle}\nFolder: ${deletedFolder}`;
|
|
543
409
|
}
|
|
544
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
|
+
|
|
545
483
|
// ============================================================================
|
|
546
484
|
// Helper Functions
|
|
547
485
|
// ============================================================================
|
|
@@ -550,21 +488,31 @@ function findFolder(folderPath) {
|
|
|
550
488
|
const app = getNotesApp();
|
|
551
489
|
const defaultAccount = app.defaultAccount();
|
|
552
490
|
|
|
553
|
-
if (folderPath.includes(
|
|
491
|
+
if (folderPath.includes('/')) {
|
|
554
492
|
// Nested folder path
|
|
555
|
-
const parts = folderPath.split(
|
|
556
|
-
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
|
+
}
|
|
557
504
|
|
|
558
505
|
if (!currentFolder) {
|
|
559
506
|
return null;
|
|
560
507
|
}
|
|
561
508
|
|
|
562
|
-
|
|
509
|
+
// Traverse the rest of the path
|
|
510
|
+
for (let i = 1; i < parts.length; i++) {
|
|
563
511
|
const subfolders = currentFolder.folders();
|
|
564
512
|
let found = false;
|
|
565
|
-
for (let
|
|
566
|
-
if (subfolders[
|
|
567
|
-
currentFolder = subfolders[
|
|
513
|
+
for (let j = 0; j < subfolders.length; j++) {
|
|
514
|
+
if (subfolders[j].name() === parts[i]) {
|
|
515
|
+
currentFolder = subfolders[j];
|
|
568
516
|
found = true;
|
|
569
517
|
break;
|
|
570
518
|
}
|
|
@@ -596,35 +544,33 @@ function getFolderName(note) {
|
|
|
596
544
|
} catch (e) {
|
|
597
545
|
// Container might not be accessible
|
|
598
546
|
}
|
|
599
|
-
return
|
|
547
|
+
return '';
|
|
600
548
|
}
|
|
601
549
|
|
|
602
550
|
function getPreview(body, maxLength) {
|
|
603
|
-
if (!body) return
|
|
551
|
+
if (!body) return '';
|
|
604
552
|
const length = Math.min(maxLength, body.length);
|
|
605
553
|
let preview = body.substring(0, length);
|
|
606
554
|
if (body.length > length) {
|
|
607
|
-
preview +=
|
|
555
|
+
preview += '...';
|
|
608
556
|
}
|
|
609
557
|
return preview;
|
|
610
558
|
}
|
|
611
559
|
|
|
612
560
|
function formatNoteContent(note) {
|
|
613
561
|
const title = note.name();
|
|
614
|
-
const body = note.body() ||
|
|
562
|
+
const body = note.body() || '';
|
|
615
563
|
const folder = getFolderName(note);
|
|
616
564
|
const created = note.creationDate().toString();
|
|
617
565
|
const modified = note.modificationDate().toString();
|
|
618
566
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
let output = "========================================\n";
|
|
567
|
+
let output = '========================================\n';
|
|
622
568
|
output += `Title: ${title}\n`;
|
|
623
569
|
output += `Folder: ${folder}\n`;
|
|
624
570
|
output += `Created: ${created}\n`;
|
|
625
571
|
output += `Modified: ${modified}\n`;
|
|
626
|
-
output +=
|
|
627
|
-
output +=
|
|
572
|
+
output += '========================================\n\n';
|
|
573
|
+
output += body + '\n';
|
|
628
574
|
|
|
629
575
|
return output;
|
|
630
576
|
}
|
|
@@ -660,6 +606,7 @@ function getUsage() {
|
|
|
660
606
|
apple-notes recent [count] [folder] - Get recent notes (default: 5)
|
|
661
607
|
apple-notes create <title> <body> [folder] - Create note from markdown
|
|
662
608
|
apple-notes delete <title> [folder] - Delete note by title
|
|
609
|
+
apple-notes move <title> <destination> [source] - Move note to folder
|
|
663
610
|
|
|
664
611
|
Examples:
|
|
665
612
|
apple-notes list 'meeting'
|
|
@@ -668,5 +615,6 @@ Examples:
|
|
|
668
615
|
apple-notes recent 10 'Blog'
|
|
669
616
|
apple-notes create 'Meeting Notes' '# Agenda\\n- Item 1' 'Work'
|
|
670
617
|
apple-notes delete 'Old Note' 'Archive'
|
|
618
|
+
apple-notes move 'Idea' 'Projects' 'Inbox'
|
|
671
619
|
`;
|
|
672
620
|
}
|