├── .eslintignore ├── .eslintrc.js ├── .github ├── jin-ss.png ├── jin-add.png ├── jin-screenshot.png └── jin-icon--color.png ├── .misc └── jin-icon--color.ai ├── .travis.yml ├── lib ├── utils │ ├── read.js │ └── write.js ├── check.js ├── export.js ├── add.js ├── destroy.js ├── edit.js ├── list.js └── Collection.js ├── config.js ├── __tests__ ├── read-write.spec.js ├── list.spec.js ├── add.spec.js └── Collection.spec.js ├── package.json ├── LICENSE ├── .gitignore ├── bin └── jin.js └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /__tests__ -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extends": "standard" 3 | } 4 | -------------------------------------------------------------------------------- /.github/jin-ss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splode/jin/HEAD/.github/jin-ss.png -------------------------------------------------------------------------------- /.github/jin-add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splode/jin/HEAD/.github/jin-add.png -------------------------------------------------------------------------------- /.github/jin-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splode/jin/HEAD/.github/jin-screenshot.png -------------------------------------------------------------------------------- /.misc/jin-icon--color.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splode/jin/HEAD/.misc/jin-icon--color.ai -------------------------------------------------------------------------------- /.github/jin-icon--color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splode/jin/HEAD/.github/jin-icon--color.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | sudo: required 3 | language: node_js 4 | node_js: 5 | - 'lts/*' 6 | before_script: 7 | - chmod u+x bin/jin.js 8 | script: 9 | - npm test 10 | branches: 11 | only: 12 | - master 13 | - dev -------------------------------------------------------------------------------- /lib/utils/read.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk') 2 | const fs = require('fs') 3 | 4 | module.exports = function read (path) { 5 | try { 6 | return JSON.parse(fs.readFileSync(path)) 7 | } catch (error) { 8 | console.log(chalk.red(error)) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/utils/write.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk') 2 | const fs = require('fs') 3 | 4 | module.exports = function write (path, data) { 5 | fs.writeFileSync(path, JSON.stringify(data), err => { 6 | if (err) { 7 | console.log(chalk.red(err)) 8 | } 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const os = require('os') 3 | 4 | const fileName = 'jin.json' 5 | const dataPath = 6 | process.env.NODE_ENV === 'test' 7 | ? path.resolve(__dirname, '__tests__', 'test.json') 8 | : path.resolve(os.homedir(), fileName) 9 | const exportPath = path.resolve(process.cwd(), fileName) 10 | 11 | module.exports = { 12 | dataPath, 13 | exportPath 14 | } 15 | -------------------------------------------------------------------------------- /lib/check.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | 3 | const { Collection } = require('./Collection') 4 | const { dataPath } = require('./../config') 5 | const write = require('./utils/write') 6 | 7 | module.exports = function () { 8 | if (!fs.existsSync(dataPath)) { 9 | const collection = new Collection() 10 | write(dataPath, collection) 11 | return false 12 | } else { 13 | return true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/export.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk') 2 | 3 | const { dataPath, exportPath } = require('./../config') 4 | const { Collection } = require('./Collection') 5 | const read = require('./utils/read') 6 | const write = require('./utils/write') 7 | 8 | module.exports = function () { 9 | const collection = new Collection(read(dataPath)) 10 | write(exportPath, collection) 11 | console.log() 12 | console.log(` ✔ Exported collection to ${chalk.yellow(exportPath)}`) 13 | console.log() 14 | } 15 | -------------------------------------------------------------------------------- /__tests__/read-write.spec.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const write = require('./../lib/utils/write') 3 | const read = require('./../lib/utils/read') 4 | const { dataPath } = require('./../config') 5 | 6 | afterAll(() => { 7 | fs.unlinkSync(dataPath) 8 | }) 9 | 10 | describe('read and write utilities', () => { 11 | test('writes "bar"', () => { 12 | const data = { 13 | foo: 'bar' 14 | } 15 | write(dataPath, data) 16 | expect(fs.existsSync(dataPath)).toBeTruthy() 17 | }) 18 | 19 | test('read "bar"', () => { 20 | const returnData = read(dataPath) 21 | expect(returnData.foo).toBe('bar') 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /__tests__/list.spec.js: -------------------------------------------------------------------------------- 1 | const execa = require('execa') 2 | 3 | describe('list actions', () => { 4 | beforeAll(() => { 5 | return execa('bin/jin.js', ['add', 'list-foo', 'bar']).then(result => result) 6 | }) 7 | 8 | test('list returns a list of notebooks', async () => { 9 | const result = await execa 10 | .stdout('bin/jin.js', ['list']) 11 | .then(result => result) 12 | expect(result).toEqual( 13 | expect.stringContaining('list-foo') 14 | ) 15 | }) 16 | 17 | test('list returns a list of notes', async () => { 18 | const result = await execa 19 | .stdout('bin/jin.js', ['list', 'list-foo']) 20 | .then(result => result) 21 | expect(result).toEqual( 22 | expect.stringContaining('bar') 23 | ) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jin-app", 3 | "version": "0.1.1", 4 | "description": "A CLI app for taking simple notes without ever leaving the terminal.", 5 | "scripts": { 6 | "test": "cross-env NODE_ENV=test jest" 7 | }, 8 | "bin": { 9 | "jin": "./bin/jin.js" 10 | }, 11 | "keywords": [ 12 | "cli", 13 | "terminal", 14 | "terminal-app", 15 | "notes", 16 | "note-taking" 17 | ], 18 | "author": "Christopher Murphy", 19 | "license": "MIT", 20 | "dependencies": { 21 | "chalk": "^2.4.2", 22 | "commander": "^2.19.0", 23 | "execa": "^1.0.0", 24 | "lodash": "^4.17.11", 25 | "moment": "^2.23.0", 26 | "prompts": "^2.0.1" 27 | }, 28 | "devDependencies": { 29 | "cross-env": "^5.2.0", 30 | "eslint": "^5.12.0", 31 | "eslint-config-standard": "^12.0.0", 32 | "eslint-plugin-import": "^2.14.0", 33 | "eslint-plugin-node": "^8.0.1", 34 | "eslint-plugin-promise": "^4.0.1", 35 | "eslint-plugin-standard": "^4.0.0", 36 | "jest": "^23.6.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Christopher Murphy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ================ Windows ===================== 2 | 3 | # Windows thumbnail cache files 4 | Thumbs.db 5 | ehthumbs.db 6 | ehthumbs_vista.db 7 | 8 | # Dump file 9 | *.stackdump 10 | 11 | # Folder config file 12 | Desktop.ini 13 | 14 | # Recycle Bin used on file shares 15 | $RECYCLE.BIN/ 16 | 17 | # Windows Installer files 18 | *.cab 19 | *.msi 20 | *.msm 21 | *.msp 22 | 23 | # Windows shortcuts 24 | *.lnk 25 | 26 | # ============== Visual Studio Code ================= 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | 33 | # ================= Mac OS ================== 34 | 35 | # General 36 | *.DS_Store 37 | .AppleDouble 38 | .LSOverride 39 | 40 | # Icon must end with two \r 41 | Icon 42 | 43 | 44 | # Thumbnails 45 | ._* 46 | 47 | # Files that might appear in the root of a volume 48 | .DocumentRevisions-V100 49 | .fseventsd 50 | .Spotlight-V100 51 | .TemporaryItems 52 | .Trashes 53 | .VolumeIcon.icns 54 | .com.apple.timemachine.donotpresent 55 | 56 | # Directories potentially created on remote AFP share 57 | .AppleDB 58 | .AppleDesktop 59 | Network Trash Folder 60 | Temporary Items 61 | .apdisk 62 | 63 | # =============== General ================== 64 | node_modules 65 | 66 | # ================ Logs ===================== 67 | logs 68 | *.log 69 | npm-debug.log* 70 | yarn-debug.log* 71 | yarn-error.log* 72 | 73 | # jin 74 | collection.json 75 | __tests__/test.json -------------------------------------------------------------------------------- /lib/add.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk') 2 | 3 | const { Note, Notebook, Collection } = require('./Collection') 4 | const { dataPath } = require('./../config') 5 | const read = require('./utils/read') 6 | const write = require('./utils/write') 7 | 8 | module.exports = { 9 | createNotebook (notebookName) { 10 | const collection = new Collection(read(dataPath)) 11 | 12 | if (!collection.getNotebook(notebookName)) { 13 | const notebook = new Notebook(notebookName) 14 | collection.addNotebook(notebook) 15 | write(dataPath, collection) 16 | 17 | console.log() 18 | console.log(` ✔ Created new notebook ${chalk.magenta(notebookName)}.`) 19 | console.log() 20 | } else { 21 | console.log() 22 | console.log(chalk.yellow(` ! Notebook ${chalk.magenta(notebookName)} already exists.`)) 23 | console.log() 24 | } 25 | }, 26 | 27 | createNote (notebookName, noteName) { 28 | const collection = new Collection(read(dataPath)) 29 | const note = new Note(noteName) 30 | let notebook = new Notebook(collection.getNotebook(notebookName)) 31 | 32 | if (!collection.getNotebook(notebookName)) { 33 | notebook = new Notebook(notebookName) 34 | notebook.addNote(note) 35 | collection.addNotebook(notebook) 36 | 37 | console.log() 38 | console.log(` ✔ Added ${chalk.green(noteName)} to new notebook ${chalk.magenta(notebookName)}.`) 39 | console.log() 40 | } else { 41 | notebook.addNote(note) 42 | collection.setNotebook(collection.getNotebookIndex(notebookName), notebook) 43 | 44 | console.log() 45 | console.log(` ✔ Added ${chalk.green(noteName)} to ${chalk.magenta(notebookName)}.`) 46 | console.log() 47 | } 48 | write(dataPath, collection) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /__tests__/add.spec.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash') 2 | const execa = require('execa') 3 | const read = require('./../lib/utils/read') 4 | const { dataPath } = require('./../config') 5 | 6 | 7 | describe('add actions', () => { 8 | test('adding a notebook "foobar" by name', async () => { 9 | const result = await execa 10 | .stdout('bin/jin.js', ['add', 'foobar']) 11 | .then(result => result) 12 | const collection = read(dataPath) 13 | const notebook = _.find(collection.notebooks, ['name', 'foobar']) 14 | expect(result).toBe('\n ✔ Created new notebook foobar.\n') 15 | expect(notebook.name).toBe('foobar') 16 | }) 17 | 18 | test('duplicate notebook "foobar" should not be created', async () => { 19 | const result = await execa 20 | .stdout('bin/jin.js', ['add', 'foobar']) 21 | .then(result => result) 22 | const collection = read(dataPath) 23 | expect(result).toBe('\n ! Notebook foobar already exists.\n') 24 | }) 25 | 26 | test('adding a note "foo" to the notebook "foobar"', async () => { 27 | const result = await execa 28 | .stdout('bin/jin.js', ['add', 'foobar', 'foo']) 29 | .then(result => result) 30 | const collection = read(dataPath) 31 | const notebook = _.find(collection.notebooks, ['name', 'foobar']) 32 | expect(result).toBe('\n ✔ Added foo to foobar.\n') 33 | expect(notebook.notes[0].note).toBe('foo') 34 | }) 35 | 36 | test('adding a note "wizzbang" to a new notebook "fizzle"', async () => { 37 | const result = await execa 38 | .stdout('bin/jin.js', ['add', 'fizzle', 'wizzbang']) 39 | .then(result => result) 40 | const collection = read(dataPath) 41 | const notebook = _.find(collection.notebooks, ['name', 'fizzle']) 42 | expect(result).toBe('\n ✔ Added wizzbang to new notebook fizzle.\n') 43 | expect(notebook.notes[0].note).toBe('wizzbang') 44 | }) 45 | }) 46 | -------------------------------------------------------------------------------- /lib/destroy.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk') 2 | 3 | const { Notebook, Collection } = require('./Collection') 4 | const { dataPath } = require('./../config') 5 | const read = require('./utils/read') 6 | const write = require('./utils/write') 7 | 8 | module.exports = { 9 | removeNoteboook (notebookName, force) { 10 | const collection = new Collection(read(dataPath)) 11 | const notebook = collection.getNotebook(notebookName) 12 | const notebookIndex = collection.getNotebookIndex(notebookName) 13 | 14 | if (notebookIndex < 0) { 15 | console.log() 16 | console.log(chalk.yellow(` ! Notebook ${chalk.magenta(notebookName)} not found.`)) 17 | console.log() 18 | } else if (notebook.notes.length > 0 && !force) { 19 | console.log() 20 | console.log(' Notebook ' + chalk.magenta(notebookName) + chalk.white(' is not empty.')) 21 | console.log() 22 | console.log(' Use the ' + chalk.green('--force') + ' flag to force remove.') 23 | console.log(' See ' + chalk.green('jin rm --help') + ' for more information.') 24 | console.log() 25 | } else if (notebook.notes.length <= 0 || force) { 26 | collection.destroyNotebook(notebookIndex) 27 | write(dataPath, collection) 28 | console.log() 29 | console.log(' ✔ Removed ' + chalk.magenta(notebookName) + ' notebook.') 30 | console.log() 31 | } 32 | }, 33 | 34 | removeNote (notebookName, index) { 35 | const collection = new Collection(read(dataPath)) 36 | const notebook = new Notebook(collection.getNotebook(notebookName)) 37 | notebook.destroyNote(index) 38 | collection.setNotebook(collection.getNotebookIndex(notebookName), notebook) 39 | write(dataPath, collection) 40 | console.log() 41 | console.log(` ✔ Removed note at index ${chalk.green(index)} from ${chalk.magenta(notebookName)} notebook.`) 42 | console.log() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/edit.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk') 2 | const prompts = require('prompts') 3 | 4 | const { Note, Notebook, Collection } = require('./Collection') 5 | const { dataPath } = require('./../config') 6 | const read = require('./utils/read') 7 | const write = require('./utils/write') 8 | 9 | module.exports = { 10 | async editNote (notebookName, noteIndex) { 11 | const collection = new Collection(read(dataPath)) 12 | const notebook = new Notebook(collection.getNotebook(notebookName)) 13 | 14 | if (!collection.getNotebook(notebookName)) { 15 | console.log() 16 | console.log( 17 | `${chalk.red(` ! Could not find a notebook named`)} ${chalk.yellow( 18 | notebookName 19 | )}${chalk.red(`.`)}` 20 | ) 21 | console.log() 22 | process.exit(1) 23 | } else if (notebook.notes.length <= 0) { 24 | console.log() 25 | console.log( 26 | `${chalk.red(` ! Notebook does not contain any notes.`)}`) 27 | console.log() 28 | process.exit(1) 29 | } 30 | 31 | const note = new Note(notebook.getNote(noteIndex)) 32 | 33 | if (!notebook.getNote(noteIndex)) { 34 | console.log() 35 | console.log( 36 | `${chalk.red(` ! Could not find a note at index`)} ${chalk.yellow(noteIndex)}`) 37 | console.log() 38 | process.exit(1) 39 | } 40 | 41 | console.log() 42 | const response = await prompts({ 43 | type: 'text', 44 | name: 'note', 45 | message: 'Edit the following note:', 46 | initial: note.note 47 | }) 48 | 49 | note.setNote(response.note) 50 | notebook.setNote(note, noteIndex) 51 | collection.setNotebook(collection.getNotebookIndex(notebookName), notebook) 52 | write(dataPath, collection) 53 | 54 | console.log() 55 | console.log( 56 | ` ✔ Replaced ${chalk.yellow(note.note)} with ${chalk.green(response.note)} in the ${chalk.magenta(notebookName)} notebook.` 57 | ) 58 | console.log() 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /bin/jin.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const pckg = require('./../package.json') 4 | const program = require('commander') 5 | 6 | const check = require('./../lib/check') 7 | const add = require('./../lib/add') 8 | const list = require('./../lib/list') 9 | const destroy = require('./../lib/destroy') 10 | const edit = require('./../lib/edit') 11 | const exp = require('./../lib/export') 12 | 13 | program.version(pckg.version, '-v, --version') 14 | program.usage(' [notebook] [note]') 15 | 16 | program 17 | .command('add [note]') 18 | .alias('a') 19 | .description( 20 | 'Add a new note to a notebook. Creates the specified notebook if it does not already exist.' 21 | ) 22 | .action((notebook, note) => { 23 | check() 24 | if (!note) { 25 | add.createNotebook(notebook) 26 | } else { 27 | add.createNote(notebook, note) 28 | } 29 | }) 30 | 31 | program 32 | .command('edit ') 33 | .alias('ed') 34 | .description('Edit the note at the given index of a notebook.') 35 | .action((notebook, index) => { 36 | check() 37 | edit.editNote(notebook, index) 38 | }) 39 | 40 | program 41 | .command('export') 42 | .alias('exp') 43 | .description('Export a copy of the notebook collection to the current directory.') 44 | .action(() => { 45 | check() 46 | exp() 47 | }) 48 | 49 | program 50 | .command('list [notebook]') 51 | .alias('ls') 52 | .option('-l, --long', 'List detailed information.') 53 | .description('List the notes for a given notebook. Lists all notebooks.') 54 | .action((notebook, cmd) => { 55 | check() 56 | if (!notebook) { 57 | list.listNotebooks(cmd) 58 | } else { 59 | list.listNotes(notebook, cmd) 60 | } 61 | }) 62 | 63 | program 64 | .command('remove [index]') 65 | .alias('rm') 66 | .option('-f, --force', 'Force delete notebook non-empty notebook.') 67 | .description('Permanently delete a note from the specified notebook. Permanently delete the specified notebook.') 68 | .action((notebook, index, cmd) => { 69 | if (!index) { 70 | destroy.removeNoteboook(notebook, cmd.force) 71 | } else { 72 | destroy.removeNote(notebook, index) 73 | } 74 | }) 75 | 76 | program.parse(process.argv) 77 | 78 | if (process.argv.length === 2) { 79 | program.outputHelp() 80 | } 81 | -------------------------------------------------------------------------------- /lib/list.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk') 2 | 3 | const { Collection } = require('./Collection') 4 | const { dataPath } = require('./../config') 5 | const read = require('./utils/read') 6 | 7 | module.exports = { 8 | listNotes (notebookName, opts) { 9 | const collection = new Collection(read(dataPath)) 10 | const notebook = collection.getNotebook(notebookName) 11 | if (!notebook) { 12 | console.log() 13 | console.log(chalk.yellow(` ! Could not find a notebook named ${chalk.magenta(notebookName)}.`)) 14 | console.log() 15 | process.exit(1) 16 | } 17 | console.log() 18 | console.log(` ${chalk.magenta(notebook.name)} Notes`) 19 | console.log(chalk.gray(' ----------------')) 20 | if (notebook.notes.length <= 0) { 21 | console.log(chalk.gray(' No notes.')) 22 | } else if (opts.long) { 23 | notebook.notes.forEach((note, i) => { 24 | let modified = note.modified 25 | if (!modified) { 26 | modified = note.created 27 | } 28 | console.log(` ${chalk.gray(i)} ${chalk.green(note.note)}`) 29 | console.log(` created ${note.created}`) 30 | console.log(` modified ${modified}`) 31 | console.log() 32 | }) 33 | } else { 34 | notebook.notes.forEach((note, i) => { 35 | console.log(` ${chalk.gray(i)} ${chalk.green(note.note)}`) 36 | }) 37 | } 38 | console.log() 39 | }, 40 | 41 | listNotebooks (opts) { 42 | const collection = read(dataPath) 43 | console.log() 44 | if (collection.notebooks.length <= 0) { 45 | console.log(chalk.yellow(' ! No notebooks found.')) 46 | console.log() 47 | console.log(` Add some notes with ${chalk.green(`jin add [note]`)}.`) 48 | } else { 49 | console.log(' Notebooks') 50 | console.log(chalk.gray(' ----------------')) 51 | if (opts.long) { 52 | collection.notebooks.forEach((notebook, i) => { 53 | let modified = notebook.modified 54 | if (!modified) { 55 | modified = notebook.created 56 | } 57 | console.log(` ${chalk.gray(i)} ${chalk.magenta(notebook.name)}`) 58 | console.log(` notes ${notebook.notes.length}`) 59 | console.log(` created ${notebook.created}`) 60 | console.log(` modified ${modified}`) 61 | console.log() 62 | }) 63 | } else { 64 | collection.notebooks.forEach((notebook, i) => { 65 | console.log(` ${chalk.gray(i)} ${chalk.magenta(notebook.name)}`) 66 | }) 67 | } 68 | } 69 | console.log() 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib/Collection.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash') 2 | const moment = require('moment') 3 | 4 | class Note { 5 | constructor (opts) { 6 | if (typeof (opts) === 'string') { 7 | this.created = moment().format('MMMM Do YYYY, h:mm:ss a') 8 | this.modified = moment().format('MMMM Do YYYY, h:mm:ss a') 9 | this.note = opts 10 | } else if (typeof (opts) === 'object') { 11 | this.created = opts.created 12 | this.modified = opts.modified 13 | this.note = opts.note 14 | } 15 | } 16 | 17 | setNote (contents) { 18 | this.note = contents 19 | this.updateModDate() 20 | } 21 | 22 | updateModDate () { 23 | this.modified = moment().format('MMMM Do YYYY, h:mm:ss a') 24 | } 25 | } 26 | 27 | class Notebook { 28 | constructor (opts) { 29 | if (typeof (opts) === 'string') { 30 | this.created = moment().format('MMMM Do YYYY, h:mm:ss a') 31 | this.modified = moment().format('MMMM Do YYYY, h:mm:ss a') 32 | this.name = opts 33 | this.notes = [] 34 | } else if (typeof (opts) === 'object') { 35 | this.created = opts.created 36 | this.modified = opts.modified 37 | this.name = opts.name 38 | this.notes = opts.notes 39 | } 40 | } 41 | 42 | addNote (note) { 43 | this.notes.push(note) 44 | this.updateModDate() 45 | } 46 | 47 | getNote (index) { 48 | return this.notes[index] 49 | } 50 | 51 | destroyNote (index) { 52 | this.updateModDate() 53 | return _.pullAt(this.notes, index) 54 | } 55 | 56 | setNote (note, index) { 57 | this.notes[index] = note 58 | this.updateModDate() 59 | } 60 | 61 | updateModDate () { 62 | this.modified = moment().format('MMMM Do YYYY, h:mm:ss a') 63 | } 64 | } 65 | 66 | class Collection { 67 | constructor (self) { 68 | this.created = self ? self.created : moment().format('MMMM Do YYYY, h:mm:ss a') 69 | this.modified = self ? self.modified : moment().format('MMMM Do YYYY, h:mm:ss a') 70 | this.notebooks = self ? self.notebooks : [] 71 | } 72 | 73 | addNotebook (notebook) { 74 | this.notebooks.push(notebook) 75 | this.updateModDate() 76 | } 77 | 78 | getNotebook (notebookName) { 79 | return _.find(this.notebooks, ['name', notebookName]) 80 | } 81 | 82 | getNotebookIndex (notebookName) { 83 | return _.findIndex(this.notebooks, ['name', notebookName]) 84 | } 85 | 86 | setNotebook (notebookIndex, notebook) { 87 | this.notebooks[notebookIndex] = notebook 88 | this.updateModDate() 89 | } 90 | 91 | updateModDate () { 92 | this.modified = moment().format('MMMM Do YYYY, h:mm:ss a') 93 | } 94 | 95 | destroyNotebook (index) { 96 | this.updateModDate() 97 | return _.pullAt(this.notebooks, index) 98 | } 99 | } 100 | 101 | module.exports = { 102 | Note, 103 | Notebook, 104 | Collection 105 | } 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | # jin 6 | 7 | *A CLI app for taking simple notes without ever leaving the terminal.* 8 | 9 | 10 | 11 | ## Overview 12 | 13 | jin allows you to take and organize simple notes without ever leaving the command line. Capture ideas, track tasks, and reference code snippets with straightforward and intuitive commands. 14 | 15 | ## Table of contents 16 | 17 | * [Overview](#overview) 18 | * [Installation](#installation) 19 | * [Usage](#usage) 20 | * [Related](#related-projects) 21 | * [License](#license) 22 | 23 | ## Installation 24 | 25 | ```bash 26 | $ npm install --global jin-app 27 | ``` 28 | 29 | ## Usage 30 | 31 | ### Commands 32 | 33 | * [`add`](#add-note) 34 | * [`list`](#list-notes) 35 | * [`edit`](#edit-note) 36 | * [`remove`](#remove-note) 37 | * [`export`](#export-notes) 38 | * [`help`](#help) 39 | 40 | 41 | ### Add Note 42 | 43 | ```bash 44 | # Add a new note to an existing notebook. 45 | # If the notebook does not exist, it will be created. 46 | 47 | $ jin add [notebook] 48 | # jin a [notebook] 49 | ``` 50 | 51 | ```bash 52 | # Create a new, empty notebook. 53 | 54 | $ jin add [notebook] 55 | # jin a [notebook] 56 | ``` 57 | 58 | > Note: a notebook name is required. 59 | 60 | #### Example 61 | 62 | ```console 63 | $ jin add nodejs "Use 'util.promisify()' to promisify a callback-style function." 64 | 65 | ✔ Added "Use 'util.promisify()' to promisify a callback-style function." to "nodejs". 66 | ``` 67 | 68 | ### List Notes 69 | 70 | ```bash 71 | # List all notes in the given notebook. 72 | # Pass the --long|-l option to get detailed information. 73 | 74 | $ jin list [options] [notebook] 75 | # jin ls [options] [notebook] 76 | ``` 77 | 78 | ```bash 79 | # List all notebooks. 80 | # Pass the --long|-l option to get detailed information. 81 | 82 | $ jin list [options] 83 | # jin ls [options] 84 | ``` 85 | 86 | #### Example 87 | 88 | ```console 89 | $ jin list nodejs 90 | 91 | nodejs Notes 92 | ---------------- 93 | 0 Use 'os.homedir()' to access the home directory. 94 | 1 Use 'util.promisify()' to promisify a callback-style function. 95 | ``` 96 | 97 | ### Edit Note 98 | 99 | ```bash 100 | # Edit the contents of a note at the given index of a given notebook. 101 | 102 | $ jin edit 103 | # jin ed 104 | ``` 105 | 106 | ### Remove Note 107 | 108 | ```bash 109 | # Remove a note at the given index of a given notebook. 110 | 111 | $ jin remove [notebook] 112 | # jin rm [notebook] 113 | ``` 114 | 115 | ```bash 116 | # Remove a given notebook. 117 | 118 | $ jin remove [notebook] 119 | # jin rm [notebook] 120 | ``` 121 | 122 | > Note: You must pass in the `--force` flag when attempting to remove a notebook that contains notes. 123 | 124 | #### Examples 125 | 126 | ```console 127 | $ jin remove nodejs 0 128 | 129 | ✔ Removed note at index 0 from nodejs notebook. 130 | ``` 131 | 132 | ```console 133 | $ jin rm --force nodejs 134 | 135 | ✔ Removed nodejs notebook. 136 | ``` 137 | 138 | 139 | 140 | 141 | 142 | ### Export Notes 143 | 144 | ```bash 145 | # Create an exports of the notes collection in the current directory. 146 | 147 | $ jin export 148 | # jin exp 149 | ``` 150 | 151 | > The notes collection is stored in the user's home directory by default. 152 | 153 | ### Help 154 | 155 | ```bash 156 | # Display general help output. 157 | 158 | $ jin --help 159 | # jin -h 160 | ``` 161 | 162 | ```bash 163 | # Display command-specific help output. 164 | 165 | $ jin [cmd] --help 166 | ``` 167 | 168 | ## Related Projects 169 | 170 | * [Dnote](https://github.com/dnote-io/cli) 171 | * [Idea](https://github.com/IonicaBizau/idea) 172 | 173 | ## License 174 | 175 | MIT © [Christopher Murphy](https://github.com/splode) 176 | 177 | [⬆ **Back to Top**](#jin) 178 | -------------------------------------------------------------------------------- /__tests__/Collection.spec.js: -------------------------------------------------------------------------------- 1 | const { Note, Notebook, Collection } = require('./../lib/Collection') 2 | 3 | const noteFromString = new Note('foo') 4 | const noteFromObject = new Note({ 5 | created: 'dated-created', 6 | modified: 'date-modified', 7 | note: 'bizbaz' 8 | }) 9 | 10 | const notebookFromString = new Notebook('notebook-from-string') 11 | const notebookFromObject = new Notebook({ 12 | created: 'date-created', 13 | modified: 'date-modified', 14 | name: 'notebook-from-object', 15 | notes: [ 16 | noteFromString 17 | ] 18 | }) 19 | 20 | const emptyCollection = new Collection() 21 | const collection = new Collection({ 22 | created: 'date-created', 23 | modified: 'date-modified', 24 | notebooks: [ 25 | notebookFromObject 26 | ] 27 | }) 28 | 29 | // note 30 | 31 | test('note is an instance of Note', () => { 32 | expect(noteFromString).toBeInstanceOf(Note) 33 | }) 34 | 35 | test('note has no undefined values', () => { 36 | expect(noteFromString.created).not.toBeUndefined() 37 | expect(noteFromString.modified).not.toBeUndefined() 38 | expect(noteFromString.note).not.toBeUndefined() 39 | }) 40 | 41 | test('Note is an object with a value of "foo"', () => { 42 | expect(noteFromString.created).not.toBeUndefined() 43 | expect(noteFromString.note).toBe('foo') 44 | }) 45 | 46 | test('note from object is an instance of Note', () => { 47 | expect(noteFromObject).toBeInstanceOf(Note) 48 | }) 49 | 50 | test('note from object has no undefined values', () => { 51 | expect(noteFromObject.created).not.toBeUndefined() 52 | expect(noteFromObject.modified).not.toBeUndefined() 53 | expect(noteFromObject.note).not.toBeUndefined() 54 | }) 55 | 56 | test('note from object is an object with a value of "bizbaz"', () => { 57 | expect(noteFromObject.created).not.toBeUndefined() 58 | expect(noteFromObject.note).toBe('bizbaz') 59 | }) 60 | 61 | test('set note contents to "fizzbang"', () => { 62 | noteFromObject.setNote('fizzbang') 63 | expect(noteFromObject.note).toBe('fizzbang') 64 | }) 65 | 66 | // notebookFromString 67 | 68 | test('notebookFromString is an instance of Notebook', () => { 69 | expect(notebookFromString).toBeInstanceOf(Notebook) 70 | }) 71 | 72 | test('notebookFromString has no undefined values', () => { 73 | expect(notebookFromString.created).not.toBeUndefined() 74 | expect(notebookFromString.modified).not.toBeUndefined() 75 | expect(notebookFromString.name).not.toBeUndefined() 76 | expect(notebookFromString.notes).not.toBeUndefined() 77 | }) 78 | 79 | // notebookFromObject 80 | 81 | test('notebookFromObject is an instance of Notebook', () => { 82 | expect(notebookFromObject).toBeInstanceOf(Notebook) 83 | }) 84 | 85 | test('notebookFromObject has no undefined values', () => { 86 | expect(notebookFromObject.created).not.toBeUndefined() 87 | expect(notebookFromObject.modified).not.toBeUndefined() 88 | expect(notebookFromObject.name).not.toBeUndefined() 89 | expect(notebookFromObject.notes).not.toBeUndefined() 90 | }) 91 | 92 | test('notebookFromObject contains a note', () => { 93 | expect(notebookFromObject.notes[0]).toMatchObject(noteFromString) 94 | }) 95 | 96 | test('get note from notebookFromObject by index', () => { 97 | expect(notebookFromObject.getNote(0)).toMatchObject(noteFromString) 98 | }) 99 | 100 | test('add a new note to notebookFromObject', () => { 101 | notebookFromObject.addNote(notebookFromObject) 102 | expect(notebookFromObject.notes.length).toBeGreaterThan(1) 103 | }) 104 | 105 | test('replace note in notebookFromObject at index', () => { 106 | notebookFromObject.setNote(new Note('bar'), 0) 107 | const updatedNote = notebookFromObject.notes[0] 108 | expect(updatedNote).toBeInstanceOf(Note) 109 | expect(updatedNote.note).toBe('bar') 110 | }) 111 | 112 | test('remove note from notebookFromObject at index', () => { 113 | notebookFromObject.destroyNote(0) 114 | expect(notebookFromObject.notes.length).toBe(1) 115 | }) 116 | 117 | // emptyCollection 118 | 119 | test('emptyCollection is an instance of Collection', () => { 120 | expect(emptyCollection).toBeInstanceOf(Collection) 121 | }) 122 | 123 | test('emptyCollection has no undefined values', () => { 124 | expect(emptyCollection.created).not.toBeUndefined() 125 | expect(emptyCollection.modified).not.toBeUndefined() 126 | expect(emptyCollection.notebooks).not.toBeUndefined() 127 | }) 128 | 129 | // collection 130 | 131 | test('collection is an instance of Collection', () => { 132 | expect(collection).toBeInstanceOf(Collection) 133 | }) 134 | 135 | test('collection has no undefined values', () => { 136 | expect(collection.created).not.toBeUndefined() 137 | expect(collection.modified).not.toBeUndefined() 138 | expect(collection.notebooks).not.toBeUndefined() 139 | }) 140 | 141 | test('collection contains a notebook', () => { 142 | expect(collection.notebooks[0]).toMatchObject(notebookFromObject) 143 | }) 144 | 145 | test('get notebook from collection by name', () => { 146 | expect(collection.getNotebook('notebook-from-object')).toBeInstanceOf(Notebook) 147 | }) 148 | 149 | test('get notebook index from collection by name', () => { 150 | expect(collection.getNotebookIndex('notebook-from-object')).toBe(0) 151 | }) 152 | 153 | test('add a new notebook to the collection', () => { 154 | const notebook = new Notebook('bamboozle') 155 | collection.addNotebook(notebook) 156 | expect(collection.getNotebookIndex(notebook.name)).not.toBeFalsy() 157 | }) 158 | 159 | test('replace a new notebook in the collection', () => { 160 | const oldNotebookName = 'bamboozle' 161 | const notebook = new Notebook('hornswaggle') 162 | collection.setNotebook(collection.getNotebookIndex(oldNotebookName), notebook) 163 | expect(collection.getNotebookIndex(notebook.name)).not.toBe(-1) 164 | expect(collection.getNotebookIndex(oldNotebookName)).toBe(-1) 165 | }) 166 | 167 | test('update modified property of collection', () => { 168 | collection.updateModDate() 169 | expect(collection.modified).not.toBeUndefined() 170 | }) 171 | 172 | test('remove notebook from collection by index', () => { 173 | collection.destroyNotebook(0) 174 | expect(collection.getNotebookIndex('notebook-from-object')).toBe(-1) 175 | }) 176 | --------------------------------------------------------------------------------