saveinme 1.0.0 → 1.3.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/README.md +46 -4
- package/bin/saveinme.js +76 -13
- package/package.json +3 -2
- package/src/clipboard.js +42 -0
- package/src/display.js +85 -24
- package/src/editor.js +157 -15
- package/src/store.js +19 -0
package/README.md
CHANGED
|
@@ -4,19 +4,33 @@
|
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
|
+
To install globally from npm:
|
|
8
|
+
```bash
|
|
9
|
+
npm install -g saveinme
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Or for local development:
|
|
7
13
|
```bash
|
|
8
14
|
cd path/to/saveinme
|
|
9
15
|
npm install
|
|
10
|
-
npm link
|
|
16
|
+
npm link
|
|
11
17
|
```
|
|
12
18
|
|
|
19
|
+
> **Note:** Once installed, both the **`saveinme`** and **`sim`** commands are registered globally and can be used interchangeably in any terminal window!
|
|
20
|
+
|
|
13
21
|
## Commands
|
|
14
22
|
|
|
23
|
+
> Both `saveinme` and `sim` can be used interchangeably!
|
|
24
|
+
|
|
15
25
|
| Command | What it does |
|
|
16
26
|
|---|---|
|
|
17
|
-
| `saveinme` | List all your saved notes |
|
|
18
|
-
| `saveinme -
|
|
19
|
-
| `saveinme -
|
|
27
|
+
| `saveinme` or `sim` | List all your saved notes |
|
|
28
|
+
| `saveinme -c` | Create a new note or edit an existing one |
|
|
29
|
+
| `saveinme -c <title>` | Jump straight to editing/creating a note by title |
|
|
30
|
+
| `saveinme -c <title> -n <notebook>` | Create/edit a note inside a specific notebook |
|
|
31
|
+
| `saveinme -cl` | Instantly save system clipboard content as a note |
|
|
32
|
+
| `saveinme -cl <title>` | Save clipboard content directly with a specific title |
|
|
33
|
+
| `saveinme -t <notebook>` | Set the default target notebook for new notes |
|
|
20
34
|
| `saveinme -v <title>` | View a note in full |
|
|
21
35
|
| `saveinme -d <title>` | Delete a note |
|
|
22
36
|
| `saveinme -p <title>` | Toggle pin (pinned notes appear at the top) |
|
|
@@ -24,6 +38,34 @@ npm link # makes `saveinme` available globally in any terminal
|
|
|
24
38
|
| `saveinme --path` | Show where your notes are stored |
|
|
25
39
|
| `saveinme -h` | Help |
|
|
26
40
|
|
|
41
|
+
## Notebook Target & Command Piping
|
|
42
|
+
|
|
43
|
+
### Default Notebook Target
|
|
44
|
+
You can lock the default notebook (category) where your notes go:
|
|
45
|
+
```bash
|
|
46
|
+
sim -t "Work"
|
|
47
|
+
```
|
|
48
|
+
Once set, any new note created via `sim -c` or `sim -cl` will default to the `"Work"` notebook automatically.
|
|
49
|
+
* View current target: `sim -t`
|
|
50
|
+
* Clear default target: `sim -t --clear`
|
|
51
|
+
|
|
52
|
+
### Override Notebook
|
|
53
|
+
Send a note to a specific notebook at any time using the `-n` or `--notebook` flag at the end of your command:
|
|
54
|
+
```bash
|
|
55
|
+
sim -c "Todo List" -n "Personal"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Stdin Piping
|
|
59
|
+
You can pipe the standard output of any command directly into a note!
|
|
60
|
+
```bash
|
|
61
|
+
# Save search results directly to a note
|
|
62
|
+
grep -rn "TODO" src/ | sim -c "Code TODOs"
|
|
63
|
+
|
|
64
|
+
# Save wget/curl responses instantly
|
|
65
|
+
curl https://api.github.com/zen | sim -c "Zen Quote" -n "Quotes"
|
|
66
|
+
```
|
|
67
|
+
The content will be saved instantly with the specified title and target notebook. If no title is specified, it will auto-generate one based on the first line.
|
|
68
|
+
|
|
27
69
|
## What gets saved per note
|
|
28
70
|
|
|
29
71
|
- **Title** — a short name for the note
|
package/bin/saveinme.js
CHANGED
|
@@ -6,6 +6,8 @@ import {
|
|
|
6
6
|
togglePin,
|
|
7
7
|
searchNotes,
|
|
8
8
|
getDataPath,
|
|
9
|
+
getDefaultNotebook,
|
|
10
|
+
setDefaultNotebook,
|
|
9
11
|
} from '../src/store.js';
|
|
10
12
|
|
|
11
13
|
import {
|
|
@@ -17,14 +19,53 @@ import {
|
|
|
17
19
|
showHelp,
|
|
18
20
|
} from '../src/display.js';
|
|
19
21
|
|
|
20
|
-
import { openEditor } from '../src/editor.js';
|
|
22
|
+
import { openEditor, saveFromClipboard, savePipedInput } from '../src/editor.js';
|
|
21
23
|
|
|
22
24
|
// ─── Parse args ───────────────────────────────────────────────────────────────
|
|
23
25
|
|
|
24
26
|
const argv = process.argv.slice(2);
|
|
27
|
+
|
|
28
|
+
// Extract --notebook or -n
|
|
29
|
+
let notebookOverride = null;
|
|
30
|
+
const nIdx = argv.findIndex(arg => arg === '-n' || arg === '--notebook');
|
|
31
|
+
if (nIdx !== -1 && nIdx + 1 < argv.length) {
|
|
32
|
+
notebookOverride = argv[nIdx + 1];
|
|
33
|
+
argv.splice(nIdx, 2); // remove from arguments
|
|
34
|
+
}
|
|
35
|
+
|
|
25
36
|
const flag = argv[0];
|
|
26
37
|
const rest = argv.slice(1).join(' ').trim();
|
|
27
38
|
|
|
39
|
+
// ─── Stdin Piping Support ──────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
const creationFlags = [undefined, '-c', '--create', '-s', '--save'];
|
|
42
|
+
const isPiped = !process.stdin.isTTY && creationFlags.includes(flag);
|
|
43
|
+
|
|
44
|
+
if (isPiped) {
|
|
45
|
+
// Read piped input
|
|
46
|
+
let inputData = '';
|
|
47
|
+
process.stdin.setEncoding('utf8');
|
|
48
|
+
process.stdin.on('data', chunk => {
|
|
49
|
+
inputData += chunk;
|
|
50
|
+
});
|
|
51
|
+
process.stdin.on('end', async () => {
|
|
52
|
+
// Treat as note creation
|
|
53
|
+
const titleArg = (flag === '-c' || flag === '--create' || flag === '-s' || flag === '--save') ? rest : (flag || '');
|
|
54
|
+
await savePipedInput(inputData, titleArg, notebookOverride);
|
|
55
|
+
process.exit(0);
|
|
56
|
+
});
|
|
57
|
+
} else {
|
|
58
|
+
main().catch(err => {
|
|
59
|
+
// Suppress Ctrl+C noise from inquirer
|
|
60
|
+
if (err?.name === 'ExitPromptError' || err?.message === 'cancelled') {
|
|
61
|
+
process.stdout.write('\n');
|
|
62
|
+
process.exit(0);
|
|
63
|
+
}
|
|
64
|
+
console.error(err);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
28
69
|
// ─── Main ─────────────────────────────────────────────────────────────────────
|
|
29
70
|
|
|
30
71
|
async function main() {
|
|
@@ -37,10 +78,42 @@ async function main() {
|
|
|
37
78
|
break;
|
|
38
79
|
}
|
|
39
80
|
|
|
40
|
-
// ──
|
|
81
|
+
// ── Create / Edit ──────────────────────────────────────────────────────
|
|
82
|
+
case '-c':
|
|
83
|
+
case '--create':
|
|
41
84
|
case '-s':
|
|
42
85
|
case '--save': {
|
|
43
|
-
await openEditor(rest || undefined);
|
|
86
|
+
await openEditor(rest || undefined, notebookOverride);
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ── Paste from clipboard ───────────────────────────────────────────────
|
|
91
|
+
case '-cl':
|
|
92
|
+
case '--clip':
|
|
93
|
+
case '--paste': {
|
|
94
|
+
await saveFromClipboard(rest || undefined, notebookOverride);
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ── Target default notebook ────────────────────────────────────────────
|
|
99
|
+
case '-t':
|
|
100
|
+
case '--target': {
|
|
101
|
+
if (rest) {
|
|
102
|
+
if (rest === '--clear' || rest === 'clear') {
|
|
103
|
+
setDefaultNotebook('');
|
|
104
|
+
showSuccess('Default notebook cleared.');
|
|
105
|
+
} else {
|
|
106
|
+
setDefaultNotebook(rest);
|
|
107
|
+
showSuccess(`Target notebook set to "${rest}".\n New notes will default to this notebook.`);
|
|
108
|
+
}
|
|
109
|
+
} else {
|
|
110
|
+
const current = getDefaultNotebook();
|
|
111
|
+
if (current) {
|
|
112
|
+
showInfo(`Current default notebook is "${current}".\n Change it with: sim -t <notebook>\n Clear it with: sim -t --clear`);
|
|
113
|
+
} else {
|
|
114
|
+
showInfo('No default notebook set. New notes will be uncategorized.\n Set one with: sim -t <notebook>');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
44
117
|
break;
|
|
45
118
|
}
|
|
46
119
|
|
|
@@ -115,13 +188,3 @@ async function main() {
|
|
|
115
188
|
}
|
|
116
189
|
}
|
|
117
190
|
}
|
|
118
|
-
|
|
119
|
-
main().catch(err => {
|
|
120
|
-
// Suppress Ctrl+C / forced-exit noise from inquirer
|
|
121
|
-
if (err?.name === 'ExitPromptError' || err?.message === 'cancelled') {
|
|
122
|
-
process.stdout.write('\n');
|
|
123
|
-
process.exit(0);
|
|
124
|
-
}
|
|
125
|
-
console.error(err);
|
|
126
|
-
process.exit(1);
|
|
127
|
-
});
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "saveinme",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.2",
|
|
4
4
|
"description": "Your personal terminal notebook — save anything, permanently.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./bin/saveinme.js",
|
|
7
7
|
"bin": {
|
|
8
|
-
"saveinme": "bin/saveinme.js"
|
|
8
|
+
"saveinme": "bin/saveinme.js",
|
|
9
|
+
"sim": "bin/saveinme.js"
|
|
9
10
|
},
|
|
10
11
|
"files": [
|
|
11
12
|
"bin/",
|
package/src/clipboard.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { platform } from 'os';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Reads text from the system clipboard cross-platform without external package dependencies.
|
|
6
|
+
* Supports Windows, macOS, and Linux (via xclip/xsel).
|
|
7
|
+
* @returns {string} The text from the clipboard, or empty string if failed/empty.
|
|
8
|
+
*/
|
|
9
|
+
export function getClipboardText() {
|
|
10
|
+
const currentPlatform = platform();
|
|
11
|
+
try {
|
|
12
|
+
if (currentPlatform === 'win32') {
|
|
13
|
+
// Get-Clipboard returns string/array. We use raw output formatting.
|
|
14
|
+
const stdout = execSync('powershell.exe -NoProfile -Command "Get-Clipboard"', {
|
|
15
|
+
encoding: 'utf8',
|
|
16
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
17
|
+
});
|
|
18
|
+
return stdout || '';
|
|
19
|
+
} else if (currentPlatform === 'darwin') {
|
|
20
|
+
return execSync('pbpaste', {
|
|
21
|
+
encoding: 'utf8',
|
|
22
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
23
|
+
}) || '';
|
|
24
|
+
} else {
|
|
25
|
+
// Linux/BSD
|
|
26
|
+
try {
|
|
27
|
+
return execSync('xclip -selection clipboard -o', {
|
|
28
|
+
encoding: 'utf8',
|
|
29
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
30
|
+
}) || '';
|
|
31
|
+
} catch {
|
|
32
|
+
return execSync('xsel -ob', {
|
|
33
|
+
encoding: 'utf8',
|
|
34
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
35
|
+
}) || '';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
} catch (e) {
|
|
39
|
+
// If clipboard tools are not installed or command fails
|
|
40
|
+
return '';
|
|
41
|
+
}
|
|
42
|
+
}
|
package/src/display.js
CHANGED
|
@@ -135,7 +135,8 @@ export function showNotesList(notes, heading = null) {
|
|
|
135
135
|
console.log(
|
|
136
136
|
chalk.dim(
|
|
137
137
|
`\n ${notes.length} note${notes.length !== 1 ? 's' : ''} saved` +
|
|
138
|
-
` · saveinme -
|
|
138
|
+
` · saveinme -c create` +
|
|
139
|
+
` · saveinme -cl paste clip` +
|
|
139
140
|
` · saveinme -v <title> view` +
|
|
140
141
|
` · saveinme -f <term> search` +
|
|
141
142
|
` · saveinme -h help\n`
|
|
@@ -154,7 +155,7 @@ export function showNote(note) {
|
|
|
154
155
|
? note.tags.map(t => chalk.magentaBright(`#${t}`)).join(' ')
|
|
155
156
|
: null,
|
|
156
157
|
note.priority
|
|
157
|
-
?
|
|
158
|
+
? (PRIORITY_COLOR[note.priority] ?? chalk.white)(`(●) ${note.priority}`)
|
|
158
159
|
: null,
|
|
159
160
|
note.pinned ? chalk.yellow('📌 pinned') : null,
|
|
160
161
|
].filter(Boolean).join(' ');
|
|
@@ -230,28 +231,88 @@ export function showInfo(msg) {
|
|
|
230
231
|
export function showHelp() {
|
|
231
232
|
showHeader();
|
|
232
233
|
console.log(`
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
${chalk.bold.
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
${chalk.
|
|
234
|
+
================================================================================
|
|
235
|
+
🚀 ${chalk.bold.yellow('COMMANDS CHEAT SHEET (QUICK OVERVIEW)')}
|
|
236
|
+
================================================================================
|
|
237
|
+
${chalk.dim(' * Note: You can use "saveinme" or the shortcut "sim" interchangeably *')}
|
|
238
|
+
|
|
239
|
+
${chalk.bold.white('sim')} List all saved notes (pinned first)
|
|
240
|
+
${chalk.bold.white('sim -c')} [${chalk.italic('title')}] Create/edit a note interactively
|
|
241
|
+
${chalk.bold.white('sim -cl')} [${chalk.italic('title')}] Instantly save text from your clipboard
|
|
242
|
+
${chalk.bold.white('sim -t')} [${chalk.italic('notebook')}] Set default target notebook (folder)
|
|
243
|
+
${chalk.bold.white('sim -v')} ${chalk.italic('<title>')} View a note's full content
|
|
244
|
+
${chalk.bold.white('sim -d')} ${chalk.italic('<title>')} Delete a note permanently
|
|
245
|
+
${chalk.bold.white('sim -p')} ${chalk.italic('<title>')} Toggle pin status (stays at top)
|
|
246
|
+
${chalk.bold.white('sim -f')} ${chalk.italic('<term>')} Search title, content, category, or tags
|
|
247
|
+
${chalk.bold.white('sim --path')} Show database folder path on disk
|
|
248
|
+
${chalk.bold.white('sim -h')} Show this detailed help manual
|
|
249
|
+
|
|
250
|
+
================================================================================
|
|
251
|
+
📖 ${chalk.bold.yellow('DETAILED EXPLANATIONS & EXAMPLES')}
|
|
252
|
+
================================================================================
|
|
253
|
+
|
|
254
|
+
${chalk.bold.cyan('1. Creating and Editing Notes')}
|
|
255
|
+
↳ Running ${chalk.white('sim -c')} starts the interactive editor. You'll be asked for:
|
|
256
|
+
- Title
|
|
257
|
+
- Content (write line-by-line. Type ${chalk.green(':save')} on a new line to finish)
|
|
258
|
+
- Details (optional: tags, priority, pin status)
|
|
259
|
+
|
|
260
|
+
${chalk.yellow('Example (Create):')}
|
|
261
|
+
$ sim -c
|
|
262
|
+
${chalk.yellow('Example (Directly create/edit by title):')}
|
|
263
|
+
$ sim -c "My Todo List"
|
|
264
|
+
|
|
265
|
+
${chalk.bold.cyan('2. Clipboard Notes')}
|
|
266
|
+
↳ Automatically copies your clipboard text and saves it ${chalk.bold.green('instantly')} with ${chalk.bold.red('zero prompts')}.
|
|
267
|
+
- Target: Default target notebook, unless overridden with ${chalk.white('-n')}.
|
|
268
|
+
- Title: Uses custom title if provided, otherwise defaults to:
|
|
269
|
+
${chalk.cyan('Clipboard Note - YYYY-MM-DD HH_MM_SS')}
|
|
270
|
+
|
|
271
|
+
${chalk.yellow('Example (Save clipboard instantly):')}
|
|
272
|
+
$ sim -cl
|
|
273
|
+
${chalk.yellow('Example (Save clipboard with custom title):')}
|
|
274
|
+
$ sim -cl "important log snippet"
|
|
275
|
+
|
|
276
|
+
${chalk.bold.cyan('3. Default Target Notebooks')}
|
|
277
|
+
↳ Targets allow you to automatically group notes by category (like folder routing).
|
|
278
|
+
- Set active notebook target: ${chalk.white('sim -t "<name>"')}
|
|
279
|
+
- View current active target: ${chalk.white('sim -t')}
|
|
280
|
+
- Clear target default: ${chalk.white('sim -t --clear')}
|
|
281
|
+
|
|
282
|
+
${chalk.yellow('Example (Set target to "Work"):')}
|
|
283
|
+
$ sim -t "Work"
|
|
284
|
+
$ sim -c "meeting summary" ${chalk.dim('↳ Automatically saved inside notebook "Work"')}
|
|
285
|
+
${chalk.yellow('Example (Override target for single command using -n):')}
|
|
286
|
+
$ sim -c "groceries" -n "Personal" ${chalk.dim('↳ Bypasses default target, saves to "Personal"')}
|
|
287
|
+
|
|
288
|
+
${chalk.bold.cyan('4. Piping Command Outputs (Advanced)')}
|
|
289
|
+
↳ Pipe stdout of any command directly into a note. Saves ${chalk.bold.green('instantly')} with ${chalk.bold.red('zero prompts')}!
|
|
290
|
+
- Target: Default target notebook, unless overridden with ${chalk.white('-n')}.
|
|
291
|
+
- Title: Uses custom title if provided, otherwise defaults to:
|
|
292
|
+
${chalk.cyan('Piped Note - YYYY-MM-DD HH_MM_SS')}
|
|
293
|
+
|
|
294
|
+
${chalk.yellow('Example (Save curl/wget responses instantly):')}
|
|
295
|
+
$ curl https://api.github.com/zen | sim -c "Zen Quote" -n "Inspiration"
|
|
296
|
+
${chalk.yellow('Example (Save tail/history list instantly):')}
|
|
297
|
+
$ history | tail -n 15 | sim -c
|
|
298
|
+
${chalk.yellow('Example (Log grep search queries instantly):')}
|
|
299
|
+
$ grep -rn "TODO" src/ | sim -c "Project TODOs"
|
|
300
|
+
|
|
301
|
+
================================================================================
|
|
302
|
+
✏️ ${chalk.bold.yellow('IN-TERMINAL EDITOR COMMANDS')}
|
|
303
|
+
================================================================================
|
|
304
|
+
While typing multi-line text inside the terminal editor, type these on a new line:
|
|
305
|
+
${chalk.green(':save')} Save changes and exit
|
|
306
|
+
${chalk.red(':cancel')} Discard all changes
|
|
307
|
+
${chalk.cyan(':keep')} Keep existing note text unchanged (only when editing)
|
|
308
|
+
|
|
309
|
+
================================================================================
|
|
310
|
+
💾 ${chalk.bold.yellow('BACKUP & LOCAL DATABASE')}
|
|
311
|
+
================================================================================
|
|
312
|
+
Your database JSON files are stored permanently under:
|
|
313
|
+
Notes database: ${chalk.cyan('~/.saveinme/notes.json')}
|
|
314
|
+
Config settings: ${chalk.cyan('~/.saveinme/config.json')}
|
|
315
|
+
================================================================================
|
|
255
316
|
`);
|
|
256
317
|
}
|
|
257
318
|
|
package/src/editor.js
CHANGED
|
@@ -1,8 +1,121 @@
|
|
|
1
1
|
import { input, select, confirm } from '@inquirer/prompts';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
|
-
import { getAllNotes, saveNote, getNoteByTitle } from './store.js';
|
|
4
|
-
import { showHeader, showSuccess, showError } from './display.js';
|
|
3
|
+
import { getAllNotes, saveNote, getNoteByTitle, getDefaultNotebook } from './store.js';
|
|
4
|
+
import { showHeader, showSuccess, showError, showInfo } from './display.js';
|
|
5
5
|
import { multilineEditor } from './multiline.js';
|
|
6
|
+
import { getClipboardText } from './clipboard.js';
|
|
7
|
+
|
|
8
|
+
// Helper for generating unique, readable default titles
|
|
9
|
+
function getTimestamp() {
|
|
10
|
+
const now = new Date();
|
|
11
|
+
const yyyy = now.getFullYear();
|
|
12
|
+
const mm = String(now.getMonth() + 1).padStart(2, '0');
|
|
13
|
+
const dd = String(now.getDate()).padStart(2, '0');
|
|
14
|
+
const hh = String(now.getHours()).padStart(2, '0');
|
|
15
|
+
const min = String(now.getMinutes()).padStart(2, '0');
|
|
16
|
+
const ss = String(now.getSeconds()).padStart(2, '0');
|
|
17
|
+
return `${yyyy}-${mm}-${dd} ${hh}_${min}_${ss}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ─── Save from clipboard directly ─────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Reads content directly from system clipboard and saves it as a note.
|
|
24
|
+
* Saves instantly with zero prompts.
|
|
25
|
+
* @param {string|undefined} titleArg Optional title passed via CLI
|
|
26
|
+
* @param {string|null} notebookOverride Target notebook overriding the default setting
|
|
27
|
+
*/
|
|
28
|
+
export async function saveFromClipboard(titleArg, notebookOverride) {
|
|
29
|
+
showHeader();
|
|
30
|
+
console.log('');
|
|
31
|
+
|
|
32
|
+
const clipboardContent = getClipboardText().trim();
|
|
33
|
+
|
|
34
|
+
if (!clipboardContent) {
|
|
35
|
+
showError('Clipboard is empty or could not be read.');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Show a preview of the clipboard text
|
|
40
|
+
console.log(chalk.bold.cyanBright('📋 Clipboard Content Detected:'));
|
|
41
|
+
const lines = clipboardContent.split('\n');
|
|
42
|
+
const previewLines = lines.slice(0, 5);
|
|
43
|
+
console.log(chalk.dim(' ┌──────────────────────────────────────────────────'));
|
|
44
|
+
previewLines.forEach(line => {
|
|
45
|
+
console.log(chalk.dim(` │ ${line.slice(0, 70)}${line.length > 70 ? '...' : ''}`));
|
|
46
|
+
});
|
|
47
|
+
if (lines.length > 5) {
|
|
48
|
+
console.log(chalk.dim(` │ ... and ${lines.length - 5} more lines`));
|
|
49
|
+
}
|
|
50
|
+
console.log(chalk.dim(' └──────────────────────────────────────────────────\n'));
|
|
51
|
+
|
|
52
|
+
// Resolve title
|
|
53
|
+
const title = (titleArg && titleArg.trim()) || `Clipboard Note - ${getTimestamp()}`;
|
|
54
|
+
const existingNote = getNoteByTitle(title);
|
|
55
|
+
|
|
56
|
+
// Resolve notebook target
|
|
57
|
+
const defaultNotebook = getDefaultNotebook();
|
|
58
|
+
const category = (notebookOverride !== null) ? notebookOverride.trim() : (existingNote?.category ?? defaultNotebook);
|
|
59
|
+
|
|
60
|
+
saveNote({
|
|
61
|
+
id: existingNote?.id, // updates if title matches exactly
|
|
62
|
+
title: title.trim(),
|
|
63
|
+
content: clipboardContent,
|
|
64
|
+
category: category.trim(),
|
|
65
|
+
tags: existingNote?.tags ?? [],
|
|
66
|
+
priority: existingNote?.priority ?? 'medium',
|
|
67
|
+
pinned: existingNote?.pinned ?? false,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const wc = clipboardContent.split(/\s+/).filter(Boolean).length;
|
|
71
|
+
showSuccess(
|
|
72
|
+
`Clipboard note "${title.trim()}" saved!` +
|
|
73
|
+
(category ? ` [Notebook: ${category.trim()}]` : '') +
|
|
74
|
+
chalk.dim(` (${wc} words)`)
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ─── Save piped stdin directly ────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Saves piped/redirected input directly into notes.
|
|
82
|
+
* Saves instantly with zero prompts.
|
|
83
|
+
* @param {string} inputContent The text read from stdin
|
|
84
|
+
* @param {string|undefined} titleArg The title passed as command line argument
|
|
85
|
+
* @param {string|null} notebookOverride Optional target notebook passed via CLI
|
|
86
|
+
*/
|
|
87
|
+
export async function savePipedInput(inputContent, titleArg, notebookOverride) {
|
|
88
|
+
const content = inputContent.trim();
|
|
89
|
+
if (!content) {
|
|
90
|
+
showError('Standard input is empty.');
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Resolve notebook target
|
|
95
|
+
const defaultNotebook = getDefaultNotebook();
|
|
96
|
+
const category = (notebookOverride !== null) ? notebookOverride.trim() : defaultNotebook;
|
|
97
|
+
|
|
98
|
+
// Resolve title
|
|
99
|
+
const title = (titleArg && titleArg.trim()) || `Piped Note - ${getTimestamp()}`;
|
|
100
|
+
const existingNote = getNoteByTitle(title);
|
|
101
|
+
|
|
102
|
+
saveNote({
|
|
103
|
+
id: existingNote?.id,
|
|
104
|
+
title: title.trim(),
|
|
105
|
+
content: content,
|
|
106
|
+
category: category,
|
|
107
|
+
tags: existingNote?.tags ?? [],
|
|
108
|
+
priority: existingNote?.priority ?? 'medium',
|
|
109
|
+
pinned: existingNote?.pinned ?? false,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const wc = content.split(/\s+/).filter(Boolean).length;
|
|
113
|
+
showSuccess(
|
|
114
|
+
`Saved piped note "${title.trim()}"` +
|
|
115
|
+
(category ? ` to notebook "${category}"` : '') +
|
|
116
|
+
chalk.dim(` (${wc} words)`)
|
|
117
|
+
);
|
|
118
|
+
}
|
|
6
119
|
|
|
7
120
|
// ─── Main editor entry point ──────────────────────────────────────────────────
|
|
8
121
|
|
|
@@ -10,8 +123,9 @@ import { multilineEditor } from './multiline.js';
|
|
|
10
123
|
* Opens the interactive note editor.
|
|
11
124
|
* Flow: Title → Content → (optional details) → Save
|
|
12
125
|
* @param {string|undefined} titleArg If provided, jump straight to editing that note.
|
|
126
|
+
* @param {string|null} notebookOverride Optional target notebook passed via CLI
|
|
13
127
|
*/
|
|
14
|
-
export async function openEditor(titleArg) {
|
|
128
|
+
export async function openEditor(titleArg, notebookOverride) {
|
|
15
129
|
showHeader();
|
|
16
130
|
console.log('');
|
|
17
131
|
|
|
@@ -59,16 +173,41 @@ export async function openEditor(titleArg) {
|
|
|
59
173
|
|
|
60
174
|
console.log('');
|
|
61
175
|
|
|
176
|
+
// Check if they want to paste from clipboard if clipboard is not empty
|
|
177
|
+
const clipText = getClipboardText().trim();
|
|
178
|
+
let content = null;
|
|
179
|
+
let useClipboard = false;
|
|
180
|
+
|
|
181
|
+
if (clipText && !existingNote) {
|
|
182
|
+
const preview = clipText.slice(0, 45) + (clipText.length > 45 ? '...' : '');
|
|
183
|
+
const choice = await select({
|
|
184
|
+
message: 'How would you like to write the content?',
|
|
185
|
+
choices: [
|
|
186
|
+
{ name: `✍️ Write in terminal editor`, value: 'manual' },
|
|
187
|
+
{ name: `📋 Paste from Clipboard: "${preview}"`, value: 'clip' },
|
|
188
|
+
],
|
|
189
|
+
});
|
|
190
|
+
if (choice === 'clip') {
|
|
191
|
+
content = clipText;
|
|
192
|
+
useClipboard = true;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
62
196
|
// ── 1. Title ──────────────────────────────────────────────────────────────
|
|
197
|
+
const firstLine = clipText.split('\n')[0].trim().slice(0, 40);
|
|
198
|
+
const defaultTitle = existingNote?.title ?? (titleArg || (useClipboard && firstLine.length > 3 ? firstLine : ''));
|
|
199
|
+
|
|
63
200
|
const title = await input({
|
|
64
201
|
message: 'Title:',
|
|
65
|
-
default:
|
|
202
|
+
default: defaultTitle,
|
|
66
203
|
validate: v => v.trim().length > 0 || 'Title cannot be empty.',
|
|
67
204
|
});
|
|
68
205
|
|
|
69
|
-
// ── 2. Content (straight to the editor)
|
|
70
|
-
|
|
71
|
-
|
|
206
|
+
// ── 2. Content (straight to the editor if not clipboard) ───────────────────
|
|
207
|
+
if (!useClipboard) {
|
|
208
|
+
console.log('');
|
|
209
|
+
content = await multilineEditor(existingNote?.content ?? '');
|
|
210
|
+
}
|
|
72
211
|
|
|
73
212
|
if (content === null) {
|
|
74
213
|
showError('Note discarded.');
|
|
@@ -92,8 +231,10 @@ export async function openEditor(titleArg) {
|
|
|
92
231
|
default: false,
|
|
93
232
|
});
|
|
94
233
|
|
|
234
|
+
// Target notebook/category default
|
|
235
|
+
const defaultNotebook = getDefaultNotebook();
|
|
236
|
+
let category = notebookOverride !== null ? notebookOverride : (existingNote?.category ?? defaultNotebook);
|
|
95
237
|
let tags = existingNote?.tags ?? [];
|
|
96
|
-
let category = existingNote?.category ?? '';
|
|
97
238
|
let priority = existingNote?.priority ?? 'medium';
|
|
98
239
|
let pinned = existingNote?.pinned ?? false;
|
|
99
240
|
|
|
@@ -104,13 +245,13 @@ export async function openEditor(titleArg) {
|
|
|
104
245
|
const existingCats = [...new Set(notes.map(n => n.category).filter(Boolean))];
|
|
105
246
|
if (existingCats.length > 0) {
|
|
106
247
|
const catChoice = await select({
|
|
107
|
-
message: 'Category:',
|
|
248
|
+
message: 'Category (Notebook):',
|
|
108
249
|
choices: [
|
|
109
250
|
...existingCats.map(c => ({ name: c, value: c })),
|
|
110
251
|
{ name: chalk.cyan('+ New category…'), value: '__new__' },
|
|
111
252
|
{ name: chalk.dim('— None'), value: '' },
|
|
112
253
|
],
|
|
113
|
-
default:
|
|
254
|
+
default: category || undefined,
|
|
114
255
|
});
|
|
115
256
|
category =
|
|
116
257
|
catChoice === '__new__'
|
|
@@ -118,15 +259,15 @@ export async function openEditor(titleArg) {
|
|
|
118
259
|
: catChoice;
|
|
119
260
|
} else {
|
|
120
261
|
category = await input({
|
|
121
|
-
message: 'Category (optional):',
|
|
122
|
-
default:
|
|
262
|
+
message: 'Category (Notebook) (optional):',
|
|
263
|
+
default: category,
|
|
123
264
|
});
|
|
124
265
|
}
|
|
125
266
|
|
|
126
267
|
// Tags
|
|
127
268
|
const tagsRaw = await input({
|
|
128
269
|
message: 'Tags (comma-separated):',
|
|
129
|
-
default:
|
|
270
|
+
default: tags.join(', '),
|
|
130
271
|
});
|
|
131
272
|
tags = tagsRaw
|
|
132
273
|
.split(',')
|
|
@@ -141,13 +282,13 @@ export async function openEditor(titleArg) {
|
|
|
141
282
|
{ name: chalk.yellow('🟡 Medium'), value: 'medium' },
|
|
142
283
|
{ name: chalk.red('🔴 High'), value: 'high' },
|
|
143
284
|
],
|
|
144
|
-
default:
|
|
285
|
+
default: priority,
|
|
145
286
|
});
|
|
146
287
|
|
|
147
288
|
// Pin
|
|
148
289
|
pinned = await confirm({
|
|
149
290
|
message: 'Pin this note?',
|
|
150
|
-
default:
|
|
291
|
+
default: pinned,
|
|
151
292
|
});
|
|
152
293
|
}
|
|
153
294
|
|
|
@@ -165,6 +306,7 @@ export async function openEditor(titleArg) {
|
|
|
165
306
|
const wc = finalContent.trim().split(/\s+/).filter(Boolean).length;
|
|
166
307
|
showSuccess(
|
|
167
308
|
`Note "${title.trim()}" saved!` +
|
|
309
|
+
(category ? ` [Notebook: ${category.trim()}]` : '') +
|
|
168
310
|
chalk.dim(` (${wc} words)`)
|
|
169
311
|
);
|
|
170
312
|
}
|
package/src/store.js
CHANGED
|
@@ -105,6 +105,25 @@ export function searchNotes(term) {
|
|
|
105
105
|
);
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
const CONFIG_FILE = join(DATA_DIR, 'config.json');
|
|
109
|
+
|
|
108
110
|
export function getDataPath() {
|
|
109
111
|
return DATA_FILE;
|
|
110
112
|
}
|
|
113
|
+
|
|
114
|
+
export function getDefaultNotebook() {
|
|
115
|
+
ensureDir();
|
|
116
|
+
if (!existsSync(CONFIG_FILE)) return '';
|
|
117
|
+
try {
|
|
118
|
+
const config = JSON.parse(readFileSync(CONFIG_FILE, 'utf-8'));
|
|
119
|
+
return config.defaultNotebook ?? '';
|
|
120
|
+
} catch {
|
|
121
|
+
return '';
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function setDefaultNotebook(name) {
|
|
126
|
+
ensureDir();
|
|
127
|
+
const config = { defaultNotebook: name.trim() };
|
|
128
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8');
|
|
129
|
+
}
|