taskninja 1.0.4 → 1.1.1

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 CHANGED
@@ -1,21 +1,25 @@
1
-
2
1
  # TaskNinja
3
2
 
4
- **Version:** 1.0.4
5
- **Description:** A simple CLI To-Do Application to manage your tasks directly from the terminal.
3
+ **Version:** 1.1.0
4
+ **Description:** A simple CLI To-Do Application to manage your tasks directly from the terminal. Includes colored tables, search, soft delete, undo, and interactive sort.
6
5
 
7
6
  ---
8
7
 
9
8
  ## Table of Contents
10
- - [Installation](#installation)
11
- - [Running the CLI](#running-the-cli)
12
- - [Commands](#commands)
13
- - [Add Task](#add-task)
14
- - [List Tasks](#list-tasks)
15
- - [Update Task](#update-task)
16
- - [Delete Task](#delete-task)
17
- - [Task Fields & Allowed Values](#task-fields--allowed-values)
18
- - [Examples](#examples)
9
+
10
+ * [Installation](#installation)
11
+ * [Running the CLI](#running-the-cli)
12
+ * [Commands](#commands)
13
+
14
+ * [Add Task](#add-task)
15
+ * [List Tasks](#list-tasks)
16
+ * [Update Task](#update-task)
17
+ * [Delete Task](#delete-task)
18
+ * [Undo Delete Task](#undo-delete-task)
19
+ * [Search Tasks](#search-tasks)
20
+ * [Sort Tasks](#sort-tasks)
21
+ * [Task Fields & Allowed Values](#task-fields--allowed-values)
22
+ * [Examples](#examples)
19
23
 
20
24
  ---
21
25
 
@@ -30,24 +34,19 @@ npm install -g taskninja
30
34
  ```
31
35
 
32
36
  or use it instantly with:
33
- ```bash
34
- npx taskninja
35
- ```
36
37
 
37
- and then use it:
38
38
  ```bash
39
+ npx taskninja
39
40
  npx taskninja <command>
40
41
  ```
41
42
 
42
- After installation, you can run the CLI using the `dotask` or `taskninja` or `tn` command (as defined in `package.json`).
43
-
44
43
  Clone the repository and install dependencies:
45
44
 
46
45
  ```bash
47
46
  git clone <your-repo-url>
48
47
  cd taskninja
49
48
  npm install
50
- ````
49
+ ```
51
50
 
52
51
  ---
53
52
 
@@ -65,7 +64,7 @@ taskninja <command>
65
64
  tn <command>
66
65
  ```
67
66
 
68
- Replace `<command>` with any of the available commands: `add`, `list`, `update`, `delete`.
67
+ Replace `<command>` with any of the available commands: `add`, `list`, `update`, `delete`, `undo`, `search`, `sort`.
69
68
 
70
69
  ---
71
70
 
@@ -77,28 +76,30 @@ Replace `<command>` with any of the available commands: `add`, `list`, `update`,
77
76
  **Description:** Add a new task interactively.
78
77
 
79
78
  ```bash
80
- node app.js add
81
- # or
82
79
  tn a
83
80
  ```
84
81
 
85
82
  **Prompts:**
86
83
 
87
84
  1. **Task Title:** Enter any text (cannot be empty).
85
+
88
86
  2. **Task Status:** Select from allowed values using arrows:
89
87
 
90
88
  * todo
91
89
  * in-progress
92
90
  * done
93
- 3. **Task Priority:** Select from allowed values using arrows:
91
+
92
+ 3. **Task Priority:** Select from allowed values:
94
93
 
95
94
  * low
96
95
  * medium
97
96
  * high
97
+
98
98
  4. **Due Date:** Enter in `YYYY-MM-DD` format. Invalid dates will show a validation error.
99
+
99
100
  5. **Description:** Optional text.
100
101
 
101
- **Expected Behavior:**
102
+ **Behavior:**
102
103
 
103
104
  * Task is saved in `todos.json`.
104
105
  * Confirmation message: `Task added successfully!`
@@ -111,25 +112,19 @@ tn a
111
112
  **Description:** List all tasks, optionally filtered by status.
112
113
 
113
114
  ```bash
114
- node app.js list
115
- # or
116
115
  tn ls
117
- ```
118
-
119
- **Optional Status Filter:**
120
-
121
- ```bash
122
- node app.js list --status todo
116
+ tn ls --status todo
123
117
  ```
124
118
 
125
119
  **Behavior:**
126
120
 
127
- * Displays tasks in a table with columns:
121
+ * Displays tasks in a **colored table** using chalk:
128
122
 
129
123
  | # | ID | Title | Status | Priority | DueDate | Description |
130
124
  | - | -- | ----- | ------ | -------- | ------- | ----------- |
131
125
 
132
- * Numbered index (`#`) starts from 1.
126
+ * Status colors: `todo` blue, `in-progress` → yellow, `done` → green
127
+ * Priority colors: `low` → green, `medium` → yellow, `high` → red
133
128
 
134
129
  ---
135
130
 
@@ -139,46 +134,127 @@ node app.js list --status todo
139
134
  **Description:** Update an existing task by selecting its ID.
140
135
 
141
136
  ```bash
142
- node app.js update
143
- # or
144
137
  tn up
145
138
  ```
146
139
 
147
140
  **Steps:**
148
141
 
149
- 1. Select task by ID from a list (use arrows).
142
+ 1. Select task by ID from a list.
150
143
  2. Confirm which fields to change (title, status, priority, due date, description).
151
- 3. Enter new values where applicable (status/priority selections use arrows).
144
+ 3. Enter new values where applicable.
152
145
 
153
146
  **Behavior:**
154
147
 
155
148
  * Only fields selected for change are updated.
156
149
  * Confirmation message: `Task updated successfully!`
157
- * Shows updated task table.
150
+ * Shows updated **colored task table**.
158
151
 
159
152
  ---
160
153
 
161
154
  ### 4. Delete Task
162
155
 
163
156
  **Alias:** `del`
164
- **Description:** Delete a task by selecting its ID.
157
+ **Description:** Soft delete a task by selecting its ID.
165
158
 
166
159
  ```bash
167
- node app.js delete
168
- # or
169
160
  tn del
170
161
  ```
171
162
 
172
163
  **Steps:**
173
164
 
174
- 1. Select task by ID from a list (use arrows).
165
+ 1. Select task by ID from a list.
175
166
  2. Confirm deletion.
176
167
 
177
168
  **Behavior:**
178
169
 
179
- * Task is removed from `todos.json`.
180
- * Confirmation message: `Task deleted successfully!`
181
- * Shows updated task table.
170
+ * Task is removed from `todos.json` **but saved in `deleted-todos.json`**.
171
+ * Confirmation: `Task deleted successfully!`
172
+ * You can restore it using the `undo` command.
173
+
174
+ ---
175
+
176
+ ### 5. Undo Delete Task
177
+
178
+ **Alias:** `un`
179
+ **Description:** Restore the last deleted task.
180
+
181
+ ```bash
182
+ tn un
183
+ ```
184
+
185
+ **Behavior:**
186
+
187
+ * Restores the last deleted task from `deleted-todos.json`.
188
+ * Confirmation: `Last deleted task restored successfully!, (Task name: taskName)`
189
+
190
+ ---
191
+
192
+ Here’s the **Search Tasks** section in the README updated in English after your CLI changes:
193
+
194
+ ---
195
+
196
+ ### 6. Search Tasks
197
+
198
+ **Alias:** `sr`
199
+ **Description:** Search tasks by keyword in title or description.
200
+
201
+ ```bash
202
+ # Search interactively
203
+ tn search
204
+
205
+ # Or search directly using --find
206
+ tn search --find "meeting"
207
+ ```
208
+
209
+ **Behavior:**
210
+
211
+ * If you run `tn search` without a keyword, the CLI will ask you:
212
+
213
+ 1. Enter the keyword you want to search for.
214
+ 2. Choose where to search: in the title, description, or both.
215
+
216
+ * If you use `--find <keyword>` directly, the CLI will return all tasks containing that keyword in the title or description.
217
+
218
+ **Example:**
219
+
220
+ ```bash
221
+ tn search
222
+ ```
223
+
224
+ ```
225
+ Enter the keyword you want to search for: meeting
226
+ Where do you want to search? (Use arrow keys)
227
+ 1. title
228
+ 2. description
229
+ 3. both
230
+ ```
231
+
232
+ ```bash
233
+ tn search --find "meeting"
234
+ ```
235
+
236
+ ┌─┬────┬────────────┬─────────────┬─────────┬────────────┬───────────────────┐
237
+ │#│ ID │ Title │ Status │ Priority│ DueDate │ Description │
238
+ ├─┼────┼────────────┼─────────────┼─────────┼────────────┼───────────────────┤
239
+ │1│ 3 │ Team meeting│ todo │ medium │ 2026-02-01 │ Discuss project │
240
+ └─┴────┴────────────┴─────────────┴─────────┴────────────┴───────────────────┘
241
+
242
+ ---
243
+
244
+ ### 7. Sort Tasks
245
+
246
+ **Alias:** `so`
247
+ **Description:** Sort tasks interactively by due date, priority, or status.
248
+
249
+ ```bash
250
+ tn so
251
+ ```
252
+
253
+ **Behavior:**
254
+
255
+ * Prompts: `Sort tasks by:` → select from `dueDate`, `priority`, `status`.
256
+ * Displays **sorted colored table**.
257
+ * Can also pass `--by` option: `tn so --by priority`
182
258
 
183
259
  ---
184
260
 
@@ -230,20 +306,77 @@ tn ls
230
306
  tn up
231
307
  ```
232
308
 
233
- * Select task ID → confirm fields → update values
234
- * Shows updated table.
235
-
236
309
  **Deleting a Task:**
237
310
 
238
311
  ```bash
239
312
  tn del
240
313
  ```
241
314
 
242
- * Select task ID → confirm deletion → updated table shown
315
+ **Undo Last Deleted Task:**
316
+
317
+ ```bash
318
+ tn un
319
+ ```
320
+
321
+ **Search Tasks:**
322
+
323
+ ```bash
324
+ tn sr groceries
325
+ ```
326
+
327
+ **Sort Tasks:**
328
+
329
+ ```bash
330
+ tn so
331
+ ```
332
+
333
+ * Select `dueDate`, `priority`, or `status` interactively
334
+ * Or use `--by` option: `tn so --by priority`
243
335
 
244
336
  ---
245
337
 
246
338
  ## Notes
247
339
 
248
- * All selection prompts (`status`, `priority`, task selection) use arrow keys in terminal.
249
- * Task table always shows numbered index (`#`) starting from 1 for easy reference.
340
+ * All selection prompts use **arrow keys** in terminal.
341
+ * Task table always shows **numbered index (`#`) starting from 1**.
342
+ * Status and priority are displayed in **colors** for better readability.
343
+
344
+ ---
345
+
346
+ ---
347
+
348
+ ## Screenshots / Demo
349
+
350
+ **Adding a Task:**
351
+
352
+ ![Add Task](images/add.png "Adding a Task in TaskNinja CLI")
353
+
354
+ **Listing Tasks:**
355
+
356
+ ![List Tasks](images/list.png "Listing Tasks in TaskNinja CLI")
357
+
358
+ **Updating a Task:**
359
+
360
+ ![Update Task](images/update.png "Updating a Task")
361
+
362
+ **Deleting a Task:**
363
+
364
+ ![Delete Task](images/delete.png "Deleting a Task")
365
+ ![Delete Task](images/delete2.png "Deleting a Task")
366
+
367
+ **Restore Last-Deleted Task:**
368
+
369
+ ![Undo](images/undo.png "Restore Last-deleted task")
370
+
371
+ **Search / Sort Tasks:**
372
+
373
+ ![Search Tasks](images/search.png "Searching Tasks")
374
+ ![Sort Tasks](images/sort.png "Sorting Tasks")
375
+ ![Sort Tasks-2](images/sort2.png "Sorting Tasks")
376
+
377
+ **Mark as Done:**
378
+
379
+ ![Mark Task As Done](images/done.png "Mark as Done In one step")
380
+ ![Mark Task As Done](images/done2.png "Mark as Done In one step")
381
+
382
+ ---
package/app.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #! /usr/bin/env node
2
2
  /**
3
+ * TASKNINJA -
3
4
  * Task Manager CLI Application
4
5
  * This application allows users to manage their tasks via command line interface.
5
6
  * It supports adding, listing, and removing tasks.
@@ -8,29 +9,71 @@
8
9
  * - inquirer: For interactive prompts
9
10
  * - fs: For file system operations
10
11
  * Author: Mohamed Bakr
11
- * Date: June 2024
12
- * Version: 1.0.0
12
+ * Date: January 2024
13
+ * Version: 1.1.1
13
14
  */
14
15
 
15
16
  // for using commands in terminal
16
17
  import { Command } from "commander";
17
18
  // for interactive command line prompts
18
19
  import inquirer from "inquirer";
20
+ // supporting colors in table forms
21
+ import Table from 'cli-table3';
22
+ // for colored text
23
+ import chalk from 'chalk';
19
24
 
20
25
  // assigning Commander to a variable
21
26
  const program = new Command();
22
27
 
23
28
  // importing validators and allowed values
24
- import { validateDueDate, ALLOWED_PRIORITIES, ALLOWED_STATUSES } from "./validators.js";
29
+ import { validateDueDate, ALLOWED_PRIORITIES, ALLOWED_STATUSES } from "./utils/validators.js";
25
30
  // importing task service functions
26
- import { loadTasks, saveTasks, getNextId } from "./taskService.js";
31
+ import { loadTasks, saveTasks, getNextId, saveDeletedTask, loadDeletedTask, clearDeletedTask } from "./utils/taskService.js";
27
32
 
28
33
 
34
+ // helper function to display tasks in colored table
35
+ const displayTasks = (tasks) => {
36
+ if (!tasks.length) {
37
+ console.log(chalk.yellow("No tasks to display."));
38
+ return;
39
+ }
40
+
41
+ const table = new Table({
42
+ head: [
43
+ chalk.cyanBright('#'),
44
+ chalk.cyanBright('ID'),
45
+ chalk.cyanBright('Title'),
46
+ chalk.cyanBright('Status'),
47
+ chalk.cyanBright('Priority'),
48
+ chalk.cyanBright('DueDate'),
49
+ chalk.cyanBright('Description')
50
+ ],
51
+ colWidths: [4, 4, 30, 12, 10, 12, 60]
52
+ });
53
+
54
+ tasks.forEach((task, index) => {
55
+ table.push([
56
+ index + 1,
57
+ task.id,
58
+ task.title,
59
+ task.status === "done" ? chalk.green(task.status)
60
+ : task.status === "in-progress" ? chalk.yellow(task.status)
61
+ : chalk.blue(task.status),
62
+ task.priority === "high" ? chalk.red(task.priority)
63
+ : task.priority === "medium" ? chalk.yellow(task.priority)
64
+ : chalk.green(task.priority),
65
+ task.dueDate,
66
+ task.description || ''
67
+ ]);
68
+ });
69
+
70
+ console.log(table.toString());
71
+ };
29
72
  // setting up
30
73
  program
31
- .name("todo-cli")
74
+ .name("taskninja")
32
75
  .description("A simple CLI application to manage your tasks")
33
- .version("1.0.4");
76
+ .version("1.1.0");
34
77
 
35
78
  // use command 'add' with title + status + priority + dueDate + description and action
36
79
  program
@@ -76,7 +119,7 @@ program
76
119
  type: 'input',
77
120
  name: 'description',
78
121
  message : 'Task Description (optional):'
79
- }
122
+ },
80
123
  ]);
81
124
  // load existing tasks
82
125
  const tasks = await loadTasks();
@@ -94,7 +137,7 @@ program
94
137
  tasks.push(newTask);
95
138
  // save updated tasks to file
96
139
  await saveTasks(tasks);
97
- console.log("Task added successfully!");
140
+ console.log(chalk.green('Task added successfully!'));
98
141
  });
99
142
 
100
143
 
@@ -109,30 +152,130 @@ program
109
152
 
110
153
  if (options.status) {
111
154
  if (!ALLOWED_STATUSES.includes(options.status)){
112
- console.error(`Invalid status filter. Allowed statuses are: ${ALLOWED_STATUSES.join(", ")}`);
113
- process.exit(1);
155
+ console.log(chalk.red(`Invalid status filter. Allowed statuses are: ${ALLOWED_STATUSES.join(", ")}`));
156
+ return;
114
157
  }
115
158
  tasks = tasks.filter(task => task.status === options.status);
116
159
  }
117
- if (tasks.length === 0) {
118
- console.log('No tasks found.');
160
+ // display all tasks in table format
161
+ displayTasks(tasks);
162
+ });
163
+
164
+
165
+
166
+ // use command 'search' to find tasks by keyword in title or description
167
+ program
168
+ .command('search [keyword]')
169
+ .alias('sr')
170
+ .option('-f, --find <keyword>', 'Keyword to search in title or description')
171
+ .description('Search tasks by keyword in title or description')
172
+ .action(async (keyword, options) => {
173
+ const tasks = await loadTasks();
174
+ let searchTerm = keyword || options.find;
175
+
176
+ if (!searchTerm) {
177
+ const answers = await inquirer.prompt([
178
+ {
179
+ type: 'input',
180
+ name: 'term',
181
+ message: 'Enter the keyword you want to search for:',
182
+ validate: input => input ? true : 'Keyword cannot be empty!'
183
+ },
184
+ {
185
+ type: 'rawlist',
186
+ name: 'field',
187
+ message: 'Where do you want to search?',
188
+ choices: ['title', 'description', 'both'],
189
+ default: 'both'
190
+ }
191
+ ]);
192
+ searchTerm = answers.term;
193
+ const field = answers.field;
194
+
195
+ const founded = tasks.filter(task => {
196
+ if (field === 'title') return task.title.toLowerCase().includes(searchTerm.toLowerCase());
197
+ if (field === 'description') return task.description.toLowerCase().includes(searchTerm.toLowerCase());
198
+ return task.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
199
+ task.description.toLowerCase().includes(searchTerm.toLowerCase());
200
+ });
201
+
202
+ if (founded.length === 0) {
203
+ console.log(chalk.red('No task matched your search!'));
204
+ return;
205
+ }
206
+
207
+ displayTasks(founded);
119
208
  return;
120
209
  }
121
- // map all tasks to displayable objects
122
- const tableData = tasks.map((task, index) => ({
123
- '#': index + 1,
124
- ID: task.id,
125
- Title: task.title,
126
- Status: task.status,
127
- Priority: task.priority,
128
- DueDate: task.dueDate,
129
- Description: task.description || ''
130
- }));
131
210
 
132
- // display all tasks in table format
133
- console.table(tableData);
211
+ const founded = tasks.filter(task =>
212
+ task.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
213
+ task.description.toLowerCase().includes(searchTerm.toLowerCase())
214
+ );
215
+
216
+ if (founded.length === 0) {
217
+ console.log(chalk.red('No task matched your search!'));
218
+ return;
219
+ }
220
+
221
+ displayTasks(founded);
134
222
  });
135
223
 
224
+
225
+
226
+
227
+ // use command 'sort' to sort tasks by due date, priority, or status
228
+ program
229
+ .command('sort')
230
+ .alias('so')
231
+ .option('--by <criteria>', 'Sort tasks by criteria (dueDate, priority, status)')
232
+ .description('Sort tasks by due date, priority, or status')
233
+ .action( async (options) => {
234
+
235
+ const tasks = await loadTasks();
236
+ let criteria = options.by;
237
+
238
+ if (!criteria) {
239
+ const answer = await inquirer.prompt([
240
+ {
241
+ type: 'rawlist',
242
+ name: 'criteria',
243
+ message: 'Sort tasks by:',
244
+ choices: ['dueDate', 'priority', 'status']
245
+ }
246
+ ]);
247
+ criteria = answer.criteria;
248
+ }
249
+
250
+
251
+ if (!['dueDate', 'priority', 'status'].includes(criteria)) {
252
+ console.log(chalk.red('Invalid sort criteria. Use --by with dueDate, priority, or status.'));
253
+ return;
254
+ }
255
+ // sorting logic
256
+ const sortedTasks = [...tasks];
257
+
258
+ switch (criteria) {
259
+ case 'dueDate' || 'duedate' || 'DueDate' || 'Duedate' || 'due date' || 'Due date' || 'due-date':
260
+ sortedTasks.sort((a, b) => new Date(a.dueDate) - new Date(b.dueDate));
261
+ break;
262
+ case 'priority' || 'Priority':
263
+ const priorityOrder = { 'high': 1, 'medium': 2, 'low': 3 };
264
+ sortedTasks.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
265
+ break;
266
+ case 'status' || 'Status':
267
+ const statusOrder = { 'todo': 1, 'in-progress': 2, 'done': 3 };
268
+ sortedTasks.sort((a, b) => statusOrder[a.priority] - statusOrder[b.priority]);
269
+ break;
270
+ default:
271
+ console.log(chalk.red('Invalid sort criteria. Use dueDate, priority, or status.'));
272
+ return;
273
+ }
274
+
275
+ displayTasks(sortedTasks);
276
+ });
277
+
278
+
136
279
  // use command 'update' with task ID and action
137
280
  program
138
281
  .command('update')
@@ -141,7 +284,7 @@ program
141
284
  .action(async () =>{
142
285
  const tasks = await loadTasks();
143
286
  if (tasks.length === 0) {
144
- console.log('No tasks found to update.');
287
+ console.log(chalk.red('No tasks found to update.'));
145
288
  return;
146
289
  }
147
290
 
@@ -157,7 +300,7 @@ program
157
300
  // find the task to update
158
301
  const task = tasks.find(t => t.id === Number(id));
159
302
  if (!task) {
160
- console.error('Task not found!');
303
+ console.log(chalk.red('Task not found!'));
161
304
  return;
162
305
  }
163
306
 
@@ -262,28 +405,58 @@ program
262
405
  ].some(Boolean);
263
406
 
264
407
  if (!hasChanges) {
265
- console.log('No changes were made.');
408
+ console.log(chalk.yellow('No changes were made.'));
266
409
  }else {
267
410
  // save updated tasks to file
268
411
  await saveTasks(tasks);
269
- console.log('Task updated successfully!');
412
+ console.log(chalk.green('Task updated successfully!'));
270
413
  }
271
414
 
272
- const tableData = tasks.map((task, index) => ({
273
- '#': index + 1,
274
- ID: task.id,
275
- Title: task.title,
276
- Status: task.status,
277
- Priority: task.priority,
278
- DueDate: task.dueDate,
279
- Description: task.description || ''
280
- }));
415
+ displayTasks(tasks);
416
+ });
281
417
 
282
- // display all tasks in table format
283
- console.table(tableData);
418
+
419
+ // use command 'done' with task ID instead of 'update' + confirm to mark task as done
420
+ program
421
+ .command('done')
422
+ .description('Mark a task as done by ==> ID <==')
423
+ .action(async () => {
424
+ const tasks = await loadTasks();
425
+ if (tasks.length === 0) {
426
+ console.log(chalk.magenta('Congratulations! All tasks are already done.'));
427
+ return;
428
+ }
429
+
430
+ const activeTasks = tasks.filter(t => t.status !== 'done');
431
+ if (activeTasks.length === 0) {
432
+ console.log(chalk.magenta('Congratulations! All tasks are already done.'));
433
+ return;
434
+ }
435
+
436
+ const { id } = await inquirer.prompt([
437
+ {
438
+ type: 'rawlist',
439
+ name: 'id',
440
+ message: 'Select the task to mark as done:',
441
+ choices: activeTasks.map(t => (
442
+ {
443
+ name: `${t.id}: ${t.title}[Current Status: ${t.status}]`,
444
+ value: t.id
445
+ }
446
+ ))
447
+ }
448
+ ]);
449
+
450
+ const task = tasks.find(t => t.id === Number(id));
451
+ task.status = 'done';
452
+ await saveTasks(tasks);
453
+ console.log(chalk.green('Task marked as done successfully!'));
454
+
455
+ displayTasks(tasks);
284
456
  });
285
457
 
286
458
 
459
+
287
460
  // use command 'delete' with task ID and action
288
461
  program
289
462
  .command('delete')
@@ -292,7 +465,7 @@ program
292
465
  .action(async () => {
293
466
  const tasks = await loadTasks();
294
467
  if (tasks.length === 0) {
295
- console.log('No tasks found to delete.');
468
+ console.log(chalk.yellow('No tasks found to delete.'));
296
469
  return;
297
470
  }
298
471
 
@@ -314,27 +487,44 @@ program
314
487
  }
315
488
  ]);
316
489
  if (!confirm) {
317
- console.log('Task deletion cancelled.');
490
+ console.log(chalk.yellow('Task deletion cancelled.'));
318
491
  return;
319
492
  }
320
-
493
+ const taskToDelete = tasks.find(t => t.id === Number(id));
494
+ // save deleted task for undo functionality
495
+ await saveDeletedTask(taskToDelete);
496
+ // filter out the deleted task
321
497
  const newTasks = tasks.filter(t => t.id !== Number(id));
498
+
322
499
  await saveTasks(newTasks);
323
- console.log('Task deleted successfully!');
324
-
325
- const tableData = newTasks.map((task, index) => ({
326
- '#': index + 1,
327
- ID: task.id,
328
- Title: task.title,
329
- Status: task.status,
330
- Priority: task.priority,
331
- DueDate: task.dueDate,
332
- Description: task.description || ''
333
- }));
500
+ console.log(chalk.green('Task deleted successfully!'));
501
+ console.log(chalk.cyan('You can undo this action by using the `undo` command.'));
334
502
 
335
- // display all tasks in table format
336
- console.table(tableData);
503
+ displayTasks(newTasks);
337
504
  });
338
505
 
506
+ // use command 'undo' to restore last deleted task
507
+ program
508
+ .command('undo')
509
+ .alias('un')
510
+ .description('Undo the last deleted task')
511
+ .action( async () => {
512
+ const lastDeletedTask = await loadDeletedTask();
513
+ if (!lastDeletedTask) {
514
+ console.log(chalk.yellow('No deleted task to restore.'));
515
+ return;
516
+ }
517
+
518
+ const tasks = await loadTasks();
519
+ tasks.push(lastDeletedTask);
520
+ tasks.sort((a, b) => a.id - b.id); // keep tasks sorted by ID
521
+ await saveTasks(tasks);
522
+ await clearDeletedTask();
523
+
524
+ console.log(chalk.green(`Last deleted task restored successfully!, (Task name: ${lastDeletedTask.title})`));
525
+ });
526
+
527
+
528
+
339
529
  // parse command line arguments
340
530
  program.parse(process.argv);
package/images/add.png ADDED
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,22 +1,35 @@
1
1
  {
2
2
  "name": "taskninja",
3
- "version": "1.0.4",
3
+ "version": "1.1.1",
4
4
  "description": "a simple CLI application with JS as a (To-Do Application)",
5
5
  "main": "app.js",
6
- "type" : "module",
6
+ "type": "module",
7
7
  "scripts": {
8
8
  "test": "echo \"Error: no test specified\" && exit 1",
9
9
  "start": "node app.js",
10
10
  "dev": "node app.js"
11
11
  },
12
- "bin" : {
12
+ "bin": {
13
13
  "dotask": "./app.js",
14
14
  "taskninja": "./app.js",
15
15
  "tn": "./app.js"
16
16
  },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/MoBMoCaffeine/taskninja--todo-CLI-app-.git"
20
+ },
21
+ "keywords": [
22
+ "cli",
23
+ "todo",
24
+ "task-manager",
25
+ "javascript",
26
+ "node"
27
+ ],
17
28
  "author": "Moahmed Bakr",
18
29
  "license": "ISC",
19
30
  "dependencies": {
31
+ "chalk": "^5.6.2",
32
+ "cli-table3": "^0.6.5",
20
33
  "commander": "^14.0.2",
21
34
  "inquirer": "^13.2.1"
22
35
  }
@@ -0,0 +1,63 @@
1
+ // for file system operations
2
+ import fs from "fs/promises";
3
+
4
+
5
+ // file to store tasks
6
+ const TASKS_FILE = "./todos.json";
7
+
8
+ // file to temporarily store deleted tasks for undo functionality
9
+ export const DELETED_TASKS_FILE = "./deleted_todos.json";
10
+
11
+
12
+ /**
13
+ * @description this section responsible to store tasks in todo.json
14
+ */
15
+ // function to load tasks from file
16
+ export const loadTasks = async () => {
17
+ try{
18
+ const data = await fs.readFile(TASKS_FILE, 'utf8');
19
+ return JSON.parse(data);
20
+ } catch (error) {
21
+ if (error.code === 'ENOENT') return [];
22
+ throw error;
23
+ }
24
+ };
25
+ // function to save tasks to file
26
+ export const saveTasks = async (tasks) => {
27
+ await fs.writeFile(TASKS_FILE, JSON.stringify(tasks, null, 2));
28
+ };
29
+ // get next task ID
30
+ export const getNextId = (tasks) => {
31
+ return tasks.length > 0 ? Math.max(...tasks.map(task => task.id)) + 1 : 1;
32
+ };
33
+
34
+ /**
35
+ * @description this section responsible to store deleted tasks in deleted_todos.json
36
+ * for undo functionality
37
+ */
38
+ // to save deleted last-deleted task
39
+ export const saveDeletedTask = async (task) => {
40
+ await fs.writeFile(DELETED_TASKS_FILE, JSON.stringify(task, null, 2));
41
+ }
42
+
43
+ // to load deleted last-deleted task
44
+ export const loadDeletedTask = async () => {
45
+ try {
46
+ const data = await fs.readFile(DELETED_TASKS_FILE, 'utf8');
47
+ return JSON.parse(data);
48
+ } catch (error) {
49
+ if (error.code === 'ENOENT') return null;
50
+ throw error;
51
+ }
52
+ };
53
+
54
+ // to clear deleted tasks file after undo
55
+ export const clearDeletedTask = async () => {
56
+ try {
57
+ await fs.unlink(DELETED_TASKS_FILE);
58
+ } catch (error) {
59
+ if (error.code !== 'ENOENT') {
60
+ throw error;
61
+ }
62
+ }
63
+ };
package/taskService.js DELETED
@@ -1,25 +0,0 @@
1
- // for file system operations
2
- import fs from "fs/promises";
3
-
4
-
5
- // file to store tasks
6
- const TASKS_FILE = "./todos.json";
7
-
8
- // function to load tasks from file
9
- export const loadTasks = async () => {
10
- try{
11
- const data = await fs.readFile(TASKS_FILE, 'utf8');
12
- return JSON.parse(data);
13
- } catch (error) {
14
- if (error.code === 'ENOENT') return [];
15
- throw error;
16
- }
17
- };
18
- // function to save tasks to file
19
- export const saveTasks = async (tasks) => {
20
- await fs.writeFile(TASKS_FILE, JSON.stringify(tasks, null, 2));
21
- };
22
- // get next task ID
23
- export const getNextId = (tasks) => {
24
- return tasks.length > 0 ? Math.max(...tasks.map(task => task.id)) + 1 : 1;
25
- };
package/todos.json DELETED
@@ -1 +0,0 @@
1
- []
File without changes