@peerasak-u/apple-notes 1.0.1 → 1.0.2

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 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, "![$2]($1)");
35
+ md = md.replace(/<img[^>]*src="([^"]*)"[^>]*\/?>/gi, "![]($1)");
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(/&nbsp;/g, " ");
61
+ md = md.replace(/&amp;/g, "&");
62
+ md = md.replace(/&lt;/g, "<");
63
+ md = md.replace(/&gt;/g, ">");
64
+ md = md.replace(/&quot;/g, '"');
65
+ md = md.replace(/&#39;/g, "'");
66
+ md = md.replace(/&apos;/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 = args.map(escapeShellArg).join(" ");
18
- const command = `osascript -l JavaScript "${scriptPath}" ${escapedArgs}`;
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(command, options);
25
- return result.trim();
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.1",
3
+ "version": "1.0.2",
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
- "sync-utils": "bun scripts/sync-test-utils.js",
16
- "test": "bun run sync-utils && bun test tests/*.test.js",
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
@@ -55,158 +55,13 @@ function run(argv) {
55
55
  }
56
56
  }
57
57
 
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, "![$2]($1)");
91
- md = md.replace(/<img[^>]*src="([^"]*)"[^>]*\/?>/gi, "![]($1)");
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(/&nbsp;/g, " ");
120
- md = md.replace(/&amp;/g, "&");
121
- md = md.replace(/&lt;/g, "<");
122
- md = md.replace(/&gt;/g, ">");
123
- md = md.replace(/&quot;/g, '"');
124
- md = md.replace(/&#39;/g, "'");
125
- md = md.replace(/&apos;/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
58
  // ============================================================================
207
59
  // Notes App Interaction
208
60
  // ============================================================================
209
61
 
62
+ // HTML <-> Markdown conversion is now handled in TypeScript (converter.ts)
63
+ // JXA now passes raw HTML to/from Apple Notes
64
+
210
65
  function getNotesApp() {
211
66
  return Application("Notes");
212
67
  }
@@ -226,7 +81,7 @@ function searchNotes(query) {
226
81
  title: note.name(),
227
82
  folder: folderName,
228
83
  modified: note.modificationDate().toString(),
229
- preview: htmlToMarkdown(preview),
84
+ preview: preview,
230
85
  });
231
86
  }
232
87
  }
@@ -264,7 +119,7 @@ function listNotes(query) {
264
119
  title: name,
265
120
  folder: folderName,
266
121
  modified: note.modificationDate().toString(),
267
- preview: htmlToMarkdown(preview),
122
+ preview: preview,
268
123
  });
269
124
  }
270
125
  }
@@ -461,7 +316,7 @@ function getRecentNotes(limit, folderName) {
461
316
  output += `[${i + 1}] ${note.name()}\n`;
462
317
  output += ` Folder: ${folder}\n`;
463
318
  output += ` Modified: ${item.modDate.toString()}\n`;
464
- output += ` Preview: ${htmlToMarkdown(preview)}\n\n`;
319
+ output += ` Preview: ${preview}\n\n`;
465
320
  }
466
321
 
467
322
  return output;
@@ -475,10 +330,10 @@ function createNote(title, body, folderName) {
475
330
  return `Error: Folder '${folderName}' not found`;
476
331
  }
477
332
 
478
- const htmlBody = markdownToHtml(body);
333
+ // body is now pre-converted to HTML in TypeScript
479
334
  const uniqueTitle = generateUniqueTitle(title, folder);
480
335
 
481
- const newNote = app.Note({ name: uniqueTitle, body: htmlBody });
336
+ const newNote = app.Note({ name: uniqueTitle, body: body });
482
337
  folder.notes.push(newNote);
483
338
 
484
339
  // Get the created note to return info
@@ -616,15 +471,13 @@ function formatNoteContent(note) {
616
471
  const created = note.creationDate().toString();
617
472
  const modified = note.modificationDate().toString();
618
473
 
619
- const markdownBody = htmlToMarkdown(body);
620
-
621
474
  let output = "========================================\n";
622
475
  output += `Title: ${title}\n`;
623
476
  output += `Folder: ${folder}\n`;
624
477
  output += `Created: ${created}\n`;
625
478
  output += `Modified: ${modified}\n`;
626
479
  output += "========================================\n\n";
627
- output += markdownBody + "\n";
480
+ output += body + "\n";
628
481
 
629
482
  return output;
630
483
  }