aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xnotes_cli (renamed from notes_cli.js)445
1 files changed, 105 insertions, 340 deletions
diff --git a/notes_cli.js b/notes_cli
index d40df6f..5db00cb 100755
--- a/notes_cli.js
+++ b/notes_cli
@@ -1,8 +1,22 @@
+#!/usr/bin/env node
const fs = require('fs')
const { homedir } = require('os')
const https = require('https')
const readline = require("readline")
+let conf, notesList = [], active = 0; scroll = 0;
+let draw = { t: 'p', v: () => ['Loading NOTES CLI...'] }; menu = 'Press [h] for help'; sort = 3
+const cl = (m, c) => console.log(c ? `\x1b[${c}m${m}\x1b[0m` : m)
+const cls = () => console.clear()
+const filesPath = `${homedir}/.local/share/notes_cli`
+const fixedStr = (s, l) => s.length < l ? s.padEnd(l) : s.substring(0, l)
+const formatDate = d => d.replace('T',' ').replace(/\..*Z/, '')
+
+const cursor = {
+ hide: () => process.stdout.write('\u001B[?25l'),
+ show: () => process.stdout.write('\u001B[?25h')
+}
+
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
@@ -15,54 +29,29 @@ rl._writeToOutput = function _writeToOutput(stringToWrite) {
rl.output.write(stringToWrite);
};
-const cursor = {
- hide: () => process.stdout.write('\u001B[?25l'),
- show: () => process.stdout.write('\u001B[?25h')
-}
-
-process.on('exit',() => {
- rl.close()
- cursor.show()
- cls()
- cl('Bye!\n\n')
-})
-
const exitWithError = (e) => {
- cls()
- draw = { t: 'p', v: () => e.map(m => `\x1b[31m${m}\x1b[0m`)}
- drawApp()
+ draw = { t: 'p', v: () => e.map(m => `\x1b[31m${m}\x1b[0m`)}; drawApp()
setTimeout(() => process.exit(1), 2000)
}
-let conf, notesList = [], active = 0; scroll = 0; draw = { t: 'p', v: () => ['Loading NOTES CLI...'] }; menu = 'Press [h] for help'; sort = 3
-const cl = (m, c) => console.log(c ? `\x1b[${c}m${m}\x1b[0m` : m)
-const cls = () => console.clear()
-const filesPath = `${homedir}/.local/share/notes_cli`
-
-const fixedStr = (s, l) => s.length < l ? s.padEnd(l) : s.substring(0, l)
-const formatDate = d => d.replace('T',' ').replace(/\..*Z/, '')
-
-/* Views */
const formatNote = note => {
const columns = process.stdout.columns
- return columns > 72
- ? `${fixedStr(note.title, columns - 45)} ${formatDate(note.created_at)} ${formatDate(note.updated_at)}`
- : fixedStr(note.title, columns - note.title.length-2)
+ return columns > 72
+ ? `${fixedStr(note.title, columns - 45)} ${formatDate(note.created_at)} ${formatDate(note.updated_at)}`
+ : fixedStr(note.title, columns - note.title.length-2)
}
const showHelp = () => {
- const prev = draw.v
- draw = { t: 'h', prev, v: () => ['[q] - Quit','[h] - Help','','[up/k] - Previous note','[down/j] - Next note',
- '[enter/o] - Open note','','[c] - Create note','[d] - Delete note',"[t] - Change note's title",'','[1] - Sort by title','[2] - Sort by creation date','[3] - Sort by modification date']};
- menu = 'Press any key to exit help'
- drawApp()
+ draw = { t: 'h', prev: draw.v, v: () => ['[q] - Quit','[h] - Help','','[up/k] - Previous note','[down/j] - Next note',
+ '[enter/o] - Open note','','[c] - Create note','[d] - Delete note',"[t] - Change note's title",'','[1] - Sort by title',
+ '[2] - Sort by creation date','[3] - Sort by modification date']};
+ menu = 'Press any key to exit help'; drawApp()
}
const setSort = (s) => {
rl.clearLine(-1)
sort = s === Math.abs(sort) ? sort * (-1) : parseInt(s)
- draw = { t: 'l', v: () => notesList.sort(sortFn).map(formatNote) }
- drawApp()
+ draw = { t: 'l', v: () => notesList.sort(sortFn).map(formatNote) }; drawApp()
}
const sortFn = (a, b) => {
@@ -75,53 +64,33 @@ const sortFn = (a, b) => {
}
const showList = (notes, status) => {
- if (status !== 200) {
- cls()
- return getEmail('Session error')
- }
-
+ if (status !== 200) { cls(); return getEmail('Session error') }
notesList = notes
- draw = { t: 'l', v: () => notes.sort(sortFn).map(formatNote) }
- drawApp()
+ draw = { t: 'l', v: () => notes.sort(sortFn).map(formatNote) }; drawApp()
}
const changeTitle = (title) => {
- const prev = draw.prev
- draw = {t: 'p', prev, v: () => ['Changing title...']}
- drawApp()
+ draw = {t: 'p', prev: draw.prev, v: () => ['Changing title...']}; drawApp()
putTitle(notesList[active], title)
}
const confirmRemoval = () => {
- const v = draw.v
- menu = '[y]es [n]o'
- draw = {t: 'c', prev: v, v: () => ['Do you want to remove note?']}
- drawApp()
+ menu = '[y]es [n]o'; draw = {t: 'c', prev: draw.v, v: () => ['Do you want to remove note?']}; drawApp()
}
const closePopup = () => {
- const v = draw.prev
- menu = 'Press [h] for help'
- draw = {t: 'l', v}
- drawApp()
+ menu = 'Press [h] for help'; draw = {t: 'l', v: draw.prev}; drawApp()
}
const getNewNoteTitle = (existing = false) => {
- const v = draw.v
rl.clearLine(-1)
- cls()
- draw = {t: 'i', prev: v, v: () => ['Please type the new note title', 'or leave empty to cancel'], noMenu: true}
- drawApp()
+ draw = {t: 'i', prev: draw.v, v: () => ['Please type the new note title', 'or leave empty to cancel'], noMenu: true}; drawApp()
cursor.show()
rl.question(' Title: ', t => {
cursor.hide()
if (t === '') {
- draw = {t: 'p', prev: v, v: () => ['Canceled']}
- drawApp()
- setTimeout(() => {
- draw = {t: 'l', v}
- drawApp()
- }, 1000)
+ draw = {t: 'p', prev: draw.v, v: () => ['Canceled']}; drawApp()
+ setTimeout(() => { draw = {t: 'l', v}; drawApp() }, 1000)
} else {
existing ? changeTitle(t) : createTmpFile(t)
}
@@ -131,10 +100,7 @@ const getNewNoteTitle = (existing = false) => {
const saveTmpFile = (note, { content }) => {
const file = `${filesPath}/${note._id}.tmp`
fs.writeFile(file, content, function(err) {
- if(err) {
- exitWithError(['Error creating tmp file'])
- }
-
+ if(err) { exitWithError(['Error creating tmp file']) }
return editTmpFile(note)
})
}
@@ -142,19 +108,11 @@ const saveTmpFile = (note, { content }) => {
const readTmpFile = (note, title) => {
const file = `${filesPath}/${title ? 'new' : note._id}.tmp`
fs.readFile(file, function(err, f){
- if (err) {
- return getEmail()
- }
-
+ if (err) { return getEmail() }
try {
const content = f.toString()
-
fs.unlink(file, () => {})
-
- if (!content) {
- return getEmail('Error saving note')
- }
-
+ if (!content) { return getEmail('Error saving note') }
title ? createNote(title, content) : putNote(note, content)
} catch (e) {
return getEmail('Session error')
@@ -163,17 +121,13 @@ const readTmpFile = (note, title) => {
}
const noteSaved = (list) => {
- draw = {t: 'p', v: () => list ? ['Note saved'] : ['Could not save note']}
- drawApp()
- fetchList()
+ draw = {t: 'p', v: () => list ? ['Note saved'] : ['Could not save note']}; drawApp(); fetchList()
}
const noteRemoved = (list) => {
- menu = 'Press [h] for help'
- draw = {t: 'p', v: () => list ? ['Note removed'] : ['Could not remove note']}
- if (list) { active-- }
- drawApp()
- fetchList()
+ menu = 'Press [h] for help'; draw = {t: 'p', v: () => list ? ['Note removed'] : ['Could not remove note']}
+ if (list) { active-- };
+ drawApp(); fetchList()
}
const createTmpFile = (title) => {
@@ -187,14 +141,9 @@ const createTmpFile = (title) => {
rl.resume()
cursor.hide()
if (e === 0) {
- draw = { t: 'p', v: () => ['Saving new note...'] }
- drawApp()
- readTmpFile(null, title)
+ draw = { t: 'p', v: () => ['Saving new note...'] }; drawApp(); readTmpFile(null, title)
} else {
- draw = { t: 'p', v: () => ['Canceled'] }
- fs.unlink(file, () => {})
- drawApp()
- fetchList()
+ draw = { t: 'p', v: () => ['Canceled'] }; fs.unlink(file, () => {}); drawApp(); fetchList()
}
});
}
@@ -210,26 +159,14 @@ const editTmpFile = (note) => {
rl.resume()
cursor.hide()
if (e === 0) {
- draw = { t: 'p', v: () => ['Saving changes...'] }
- readTmpFile(note)
+ draw = { t: 'p', v: () => ['Saving changes...'] }; readTmpFile(note)
} else {
- draw = { t: 'p', v: () => ['Changes not saved'] }
- fs.unlink(file, () => {})
- drawApp()
- fetchList()
+ draw = { t: 'p', v: () => ['Changes not saved'] }; fs.unlink(file, () => {}); drawApp(); fetchList()
}
});
}
-// =========== DRAW APP =============
-
-const sortIco = c => {
- if (c === Math.abs(sort)){
- return sort > 0 ? '▼' : '▲'
- } else {
- return ' '
- }
-}
+const sortIco = c => c === Math.abs(sort) ? sort > 0 ? '▼' : '▲' : ' '
const drawApp = () => {
const lines = process.stdout.rows
@@ -242,26 +179,14 @@ const drawApp = () => {
for (var i = 0; i < lines - 1; i++) {
const dist = t => columns > t + 2 ? [Math.floor((columns - t) / 2), Math.ceil((columns - t) / 2) - 2] : [0, 0]
switch(i){
- case 0:
- cl('╔'+'═'.repeat(columns - 2)+'╗')
- break
- case 1:
- conf?.email
+ case 0: cl('╔'+'═'.repeat(columns - 2)+'╗'); break
+ case 1: conf?.email
? cl('║'+' '.repeat(dist(conf.email.length+12)[0])+`NOTES CLI (${conf.email})`.substring(0, columns-2)+' '.repeat(dist(conf.email.length+12)[1])+'║')
- : cl('║'+' '.repeat(dist(9)[0])+`NOTES CLI`.substring(0, columns-2)+' '.repeat(dist(9)[1])+'║')
- break
- case 2:
- cl('╠'+'═'.repeat(columns - 2) +'╣')
- break
- case 3:
- cl('║'+(headers || ' '.repeat(columns-2))+'║')
- break
- case 4:
- cl('╟'+'─'.repeat(columns - 2) +'╢')
- break
- case lines - 2:
- cl('╚'+'═'.repeat(columns - 2) +'╝')
- break
+ : cl('║'+' '.repeat(dist(9)[0])+`NOTES CLI`.substring(0, columns-2)+' '.repeat(dist(9)[1])+'║'); break
+ case 2: cl('╠'+'═'.repeat(columns - 2) +'╣'); break
+ case 3: cl('║'+(headers || ' '.repeat(columns-2))+'║'); break
+ case 4: cl('╟'+'─'.repeat(columns - 2) +'╢'); break
+ case lines - 2: cl('╚'+'═'.repeat(columns - 2) +'╝'); break
default:
const max = Math.max(...(draw.v().map(el => el.length)))
if (['p','h','c','i'].includes(draw.t)) {
@@ -284,7 +209,6 @@ const drawApp = () => {
}
} else if (draw.t === 'l') {
const l = draw.v()[i-5+scroll] && draw.v()[i-5+scroll].substring(0,columns-4)
-
cl(l
? ('║'+(active===i-5+scroll?'\x1b[7m ':' ')+l+' '.repeat(columns-l.length-3)+'\x1b[0m║')
: ('║'+' '.repeat(columns - 2)+'║')
@@ -294,14 +218,11 @@ const drawApp = () => {
}
if (!draw.noMenu) {
- const activeNr = `${active+1}`
- const notesNr = `${notesList.length}`
+ const activeNr = `${active+1}`, notesNr = `${notesList.length}`
process.stdout.write(' '+menu+' '.repeat(columns-5-menu.length-activeNr.length-notesNr.length)+`[${activeNr}/${notesNr}]`)
}
}
-// ============ GET KEY =================
-
const getKey = () => {
readline.emitKeypressEvents(process.stdin);
process.stdin.setRawMode(true);
@@ -311,208 +232,99 @@ const getKey = () => {
if (draw.t === mode) {
fn()
} else if (draw.t === 'h') {
- const v = draw.prev
- draw = { t: 'l', v }
- menu = 'Press [h] for help'
- drawApp()
+ draw = { t: 'l', v: draw.prev }; menu = 'Press [h] for help'; drawApp()
} else if (draw.t === 'i') {
- } else {
- rl.clearLine(-1)
- drawApp()
- }
+ } else { rl.clearLine(-1); drawApp() }
}
switch(key.name) {
- case 'up':
- case 'k':
- run('l', () => {
- active > 0 && active--
- active < scroll && scroll--
- drawApp()
- })
- break
- case 'down':
- case 'j':
- run('l', () => {
- active < draw.v().length - 1 && active++
- active - scroll > lines - 7 && scroll++
- drawApp()
- })
- break
- case 'return':
- case 'o':
- run('l', () => {
- fetchNote(notesList[active])
- })
- break
- case 'h':
- run('l', () => {
- showHelp()
- })
- break
- case 'c':
- run('l', () => {
- getNewNoteTitle()
- })
- break
- case 't':
- run('l', () => {
- getNewNoteTitle(true)
- })
- break
- case 'd':
- run('l', () => {
- confirmRemoval()
- })
- break
- case 'y':
- run('c', () => {
- removeNote(notesList[active])
- })
- break
- case 'n':
- run('c', () => {
- closePopup()
- })
- break
- case '1':
- case '2':
- case '3':
- run('l', () => {
- setSort(parseInt(key.name))
- })
- break
- case 'q':
- run('l', () => {
- process.exit()
- })
- break
+ case 'up': case 'k': run('l', () => { active > 0 && active--; active < scroll && scroll--; drawApp() }); break
+ case 'down': case 'j': run('l', () => { active < draw.v().length - 1 && active++; active - scroll > lines - 7 && scroll++; drawApp(); }); break
+ case 'return': case 'o': run('l', () => { fetchNote(notesList[active]) }); break
+ case 'h': run('l', () => { showHelp() }); break
+ case 'c': run('l', () => { getNewNoteTitle() }); break
+ case 't': run('l', () => { getNewNoteTitle(true) }); break
+ case 'd': run('l', () => { confirmRemoval() }); break
+ case 'y': run('c', () => { removeNote(notesList[active]) }); break
+ case 'n': run('c', () => { closePopup() }); break
+ case '1': case '2': case '3': run('l', () => { setSort(parseInt(key.name)) }); break
+ case 'q': run('l', () => { process.exit() }); break
default:
if (draw.t === 'h') {
- const v = draw.prev
- draw = { t: 'l', v }
- menu = 'Press [h] for help'
- drawApp()
+ const v = draw.prev; draw = { t: 'l', v }; menu = 'Press [h] for help'; drawApp()
} if (draw.t === 'i') {
- } else {
- rl.clearLine(-1)
- drawApp()
- }
+ } else { rl.clearLine(-1); drawApp() }
}
})
}
-/* Session */
-
const setSession = (session) => {
- if (!fs.existsSync(filesPath)){
- fs.mkdirSync(filesPath, { recursive: true });
- }
-
+ if (!fs.existsSync(filesPath)) fs.mkdirSync(filesPath, { recursive: true });
fs.writeFile(`${filesPath}/session`, JSON.stringify(session), function(err) {
- if(err) {
- exitWithError(['Error writting session file'])
- }
-
+ if (err) { exitWithError(['Error writting session file']) }
getSession()
})
}
const getSession = () => {
fs.readFile(`${filesPath}/session`, function(err, f){
- if (err) {
- return getEmail()
- }
-
+ if (err) return getEmail()
try {
conf = JSON.parse(f.toString())
-
- if (!conf.session || !conf.userId || !conf.email || !conf.list) {
- return getEmail('Session error')
- }
-
+ if (!conf.session || !conf.userId || !conf.email || !conf.list) { return getEmail('Session error') }
getKey()
fetchList()
- } catch (e) {
- return getEmail('Session error')
- }
+ } catch (e) { return getEmail('Session error') }
})
}
-/* Login */
const loginMsg = (text, err) => ['Login to apps.pruss.it', '', `\x1b[31m${err}\x1b[0m`, '', ...text]
+const handleSession = (o, s, h) => {
+ if (!o || s !== 201) return getEmail('Could not log in, try again')
+ const {_id, email, isVerified, noteList} = o, session = h['set-cookie']
+ if (!_id || !email || !noteList) {
+ exitWithError(['Could not log in'])
+ } else if (!isVerified) {
+ exitWithError(['User not verified.', 'Please first verify the user using apps.pruss.it'])
+ }
+ setSession({userId: _id, email, list: noteList, session})
+}
const getPass = (e) => {
cls()
- draw = { t: 'p', v: () => loginMsg(['Please type password for', `your apps.pruss.it account`, 'or press [q] to quit'], ''), noMenu: true}
- drawApp()
+ draw = { t: 'p', v: () => loginMsg(['Please type password for', `your apps.pruss.it account`, 'or press [q] to quit'], ''), noMenu: true}; drawApp()
rl.stdoutMuted = true
rl.write()
rl.question('', p => {
- rl.pause()
- rl.stdoutMuted = false
- drawApp()
- if (p === 'q') {
- process.exit()
- }
-
- post(
- '/api/login',
- {"email": e, "password": p},
- (o, s, h) => {
- if (!o || s !== 201) {
- return getEmail('Could not log in, try again')
- }
-
- const {_id, email, isVerified, noteList} = o
- const session = h['set-cookie']
-
- if (!_id || !email || !noteList) {
- exitWithError(['Could not log in'])
- } else if (!isVerified) {
- exitWithError(['User not verified.', 'Please first verify the user using apps.pruss.it'])
- }
-
- setSession({userId: _id, email, list: noteList, session})
- })
+ rl.pause(); rl.stdoutMuted = false; drawApp()
+ if (p === 'q') process.exit()
+ post('/api/login', {"email": e, "password": p}, handleSession)
})
-
rl.history = rl.history.slice(1)
}
const getEmail = (error = '') => {
- cls()
- draw = { t: 'p', v: () => loginMsg(['Please input email for your','apps.pruss.it account or' , 'press [q] to quit'], error), noMenu: true}
- drawApp()
- cursor.show()
+ cls(); draw = { t: 'p', v: () => loginMsg(['Please input email for your','apps.pruss.it account or' , 'press [q] to quit'], error), noMenu: true};
+ drawApp(); cursor.show()
rl.question(' Email: ', e => {
- cursor.hide()
- rl.pause()
+ cursor.hide(); rl.pause()
if (e === 'q') {
process.exit()
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e)) {
- cls()
- return getEmail('Not a valid email address, try again.')
+ cls(); return getEmail('Not a valid email address, try again.')
}
-
getPass(e)
})
}
-/* API */
-
-const fetchList = () => {
- get('/api/notes', showList, conf.session)
-}
+const fetchList = () => { get('/api/notes', showList, conf.session) }
const fetchNote = note => {
- draw = { t: 'p', v: () => ['Loading...'] }
- drawApp()
+ draw = { t: 'p', v: () => ['Loading...'] }; drawApp()
get(`/api/notes/${note.noteId}`, (content) => saveTmpFile(note, content), conf.session)
}
const removeNote = note => {
- draw = { t: 'p', v: () => ['Removing note...'] }
- drawApp()
+ draw = { t: 'p', v: () => ['Removing note...'] }; drawApp()
remove(`/api/notes/${note._id}`, noteRemoved, conf.session)
}
@@ -528,33 +340,14 @@ const createNote = (title, content) => {
post('/api/notes', { title, content }, noteSaved, conf.session)
}
-/* HTTPS METHODS */
-
const get = (path, callback, cookie = '') => {
- const options = {
- hostname: 'apps.pruss.it',
- port: 443,
- path,
- method: 'GET',
- headers: {
- 'Cookie': cookie,
- },
- };
-
+ const options = { hostname: 'apps.pruss.it', port: 443, path, method: 'GET', headers: { 'Cookie': cookie } };
const req = https.request(options, res => {
let data = '';
-
- res.on('data', chunk => {
- data += chunk
- });
-
+ res.on('data', chunk => { data += chunk });
res.on('end', () => callback(JSON.parse(data), res.statusCode, res.headers));
});
-
- req.on('error', () => {
- callback(null)
- });
-
+ req.on('error', () => { callback(null) });
req.end();
}
@@ -565,28 +358,16 @@ const post = (path, data, callback, cookie = '', put = false) => {
port: 443,
path,
method: put ? 'PUT' : 'POST',
- headers: put ? {
- 'Cookie': cookie,
- } : {
- 'Content-Type': 'text/plain',
- 'Content-Length': dataString.length,
- 'Cookie': cookie,
- },
+ headers: put
+ ? { 'Cookie': cookie, }
+ : { 'Content-Type': 'text/plain', 'Content-Length': dataString.length, 'Cookie': cookie, },
timeout: 1000,
};
-
const req = https.request(options, (res) => {
let data = '';
-
- res.on('data', chunk => {
- data += chunk;
- });
-
+ res.on('data', chunk => { data += chunk });
res.on('end', () => callback(JSON.parse(data), res.statusCode, res.headers));
- }).on("error", () => {
- callback(null)
- });
-
+ }).on("error", () => { callback(null) });
req.write(dataString);
req.end();
}
@@ -594,34 +375,18 @@ const post = (path, data, callback, cookie = '', put = false) => {
const put = (...arg) => post(...arg, true)
const remove = (path, callback, cookie = '') => {
- const options = {
- hostname: 'apps.pruss.it',
- port: 443,
- path,
- method: 'DELETE',
- headers: {
- 'Cookie': cookie,
- },
- };
-
+ const options = { hostname: 'apps.pruss.it', port: 443, path, method: 'DELETE', headers: { 'Cookie': cookie } };
const req = https.request(options, res => {
let data = '';
-
- res.on('data', chunk => {
- data += chunk
- });
-
+ res.on('data', chunk => { data += chunk });
res.on('end', () => callback(JSON.parse(data), res.statusCode, res.headers));
});
-
- req.on('error', () => {
- callback(null)
- });
-
+ req.on('error', () => { callback(null) });
req.end();
}
+process.stdout.on('resize', drawApp);
+process.on('exit',() => { rl.close(); cursor.show(); cls(); cl('Bye!\n\n') })
cls()
getSession()
-process.stdout.on('resize', drawApp);
cursor.hide()