aboutsummaryrefslogtreecommitdiffstats
path: root/notes_cli.js
diff options
context:
space:
mode:
authorGravatar piotrruss <mail@pruss.it> 2022-05-08 23:45:01 +0100
committerGravatar piotrruss <mail@pruss.it> 2022-05-08 23:46:56 +0100
commit1254372229cd7435f0b201fdd991f8c30449c9e2 (patch)
tree488d4dda4269c11f3c3460a3d0e602ad56b9d188 /notes_cli.js
parenteb38e05f691fe0a4b722b96311fb045d77d31ce9 (diff)
downloadnotes_cli-1254372229cd7435f0b201fdd991f8c30449c9e2.tar.gz
notes_cli-1254372229cd7435f0b201fdd991f8c30449c9e2.tar.bz2
notes_cli-1254372229cd7435f0b201fdd991f8c30449c9e2.zip
compact to shell script
Diffstat (limited to 'notes_cli.js')
-rwxr-xr-xnotes_cli.js627
1 files changed, 0 insertions, 627 deletions
diff --git a/notes_cli.js b/notes_cli.js
deleted file mode 100755
index d40df6f..0000000
--- a/notes_cli.js
+++ /dev/null
@@ -1,627 +0,0 @@
-const fs = require('fs')
-const { homedir } = require('os')
-const https = require('https')
-const readline = require("readline")
-
-const rl = readline.createInterface({
- input: process.stdin,
- output: process.stdout
-})
-
-rl._writeToOutput = function _writeToOutput(stringToWrite) {
- if (rl.stdoutMuted)
- rl.output.write("\x1B[2K\x1B[200D Password: "+('*'.repeat(rl.line.length)))
- else
- 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()
- 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)
-}
-
-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()
-}
-
-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()
-}
-
-const sortFn = (a, b) => {
- const d = sort > 0 ? 1 : -1
- switch (Math.abs(sort)) {
- case 1: return d * a.title.localeCompare(b.title)
- case 2: return d * (new Date(b.created_at) - new Date(a.created_at))
- case 3: return d * (new Date(b.updated_at) - new Date(a.updated_at))
- }
-}
-
-const showList = (notes, status) => {
- if (status !== 200) {
- cls()
- return getEmail('Session error')
- }
-
- notesList = notes
- 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()
- 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()
-}
-
-const closePopup = () => {
- const v = draw.prev
- menu = 'Press [h] for help'
- draw = {t: 'l', v}
- 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()
- 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)
- } else {
- existing ? changeTitle(t) : createTmpFile(t)
- }
- })
-}
-
-const saveTmpFile = (note, { content }) => {
- const file = `${filesPath}/${note._id}.tmp`
- fs.writeFile(file, content, function(err) {
- if(err) {
- exitWithError(['Error creating tmp file'])
- }
-
- return editTmpFile(note)
- })
-}
-
-const readTmpFile = (note, title) => {
- const file = `${filesPath}/${title ? 'new' : note._id}.tmp`
- fs.readFile(file, function(err, f){
- if (err) {
- return getEmail()
- }
-
- try {
- const content = f.toString()
-
- fs.unlink(file, () => {})
-
- if (!content) {
- return getEmail('Error saving note')
- }
-
- title ? createNote(title, content) : putNote(note, content)
- } catch (e) {
- return getEmail('Session error')
- }
- })
-}
-
-const noteSaved = (list) => {
- 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()
-}
-
-const createTmpFile = (title) => {
- const file = `${filesPath}/new.tmp`
- const child_process = require('child_process')
- const editor = process.env.EDITOR || 'vi';
- cursor.show()
- const child = child_process.spawn(editor, [file], { stdio: 'inherit' });
- rl.pause()
- child.on('exit', function (e) {
- rl.resume()
- cursor.hide()
- if (e === 0) {
- draw = { t: 'p', v: () => ['Saving new note...'] }
- drawApp()
- readTmpFile(null, title)
- } else {
- draw = { t: 'p', v: () => ['Canceled'] }
- fs.unlink(file, () => {})
- drawApp()
- fetchList()
- }
- });
-}
-
-const editTmpFile = (note) => {
- const file = `${filesPath}/${note._id}.tmp`
- const child_process = require('child_process')
- const editor = process.env.EDITOR || 'vi';
- cursor.show()
- const child = child_process.spawn(editor, [file], { stdio: 'inherit' });
- rl.pause()
- child.on('exit', function (e) {
- rl.resume()
- cursor.hide()
- if (e === 0) {
- draw = { t: 'p', v: () => ['Saving changes...'] }
- readTmpFile(note)
- } else {
- 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 drawApp = () => {
- const lines = process.stdout.rows
- const columns = process.stdout.columns
- const headers = columns > 72
- ? `${fixedStr(' Title '+sortIco(1), columns - 44)} Created at ${sortIco(2)}${' '.repeat(8)} Updated at ${sortIco(3)}${' '.repeat(8)}`
- : ` Title ${' '.repeat(columns - 9)}`
-
- cls()
- 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
- ? 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
- default:
- const max = Math.max(...(draw.v().map(el => el.length)))
- if (['p','h','c','i'].includes(draw.t)) {
- if (i === (Math.floor((lines - draw.v().length) / 2) - 2)){
- cl('║'+' '.repeat(dist(max+4)[0])+'┌'+'─'.repeat(max+2)+'┐'+' '.repeat(dist(max+4)[1])+'║')
- } else if (i === (Math.floor((lines + draw.v().length) / 2) + 1)) {
- cl('║'+' '.repeat(dist(max+4)[0])+'└'+'─'.repeat(max+2)+'┘'+' '.repeat(dist(max+4)[1])+'║')
- } else if (i === (Math.floor((lines - draw.v().length) / 2) - 1) || i === (Math.floor((lines + draw.v().length) / 2))) {
- cl('║'+' '.repeat(dist(max+4)[0])+'│'+' '.repeat(max+2)+'│'+' '.repeat(dist(max+4)[1])+'║')
- } else if (i < (Math.floor((lines - draw.v().length) / 2) - 1) || (i > (Math.floor((lines + draw.v().length) / 2)))) {
- cl('║'+' '.repeat(columns - 2)+'║')
- } else {
- const n = i - (Math.floor((lines - draw.v().length) / 2))
- const t = draw.v()[n]
- const tLength = t.indexOf('\x1b') > -1 ? t.length - 9 : t.length
- const s = [Math.floor((max+2-tLength) / 2), Math.ceil((max+2-tLength) / 2)]
- draw.t === 'h'
- ? cl('║'+' '.repeat(dist(max+4)[0])+'│'+' '+t+' '.repeat(s[0]+s[1]-1)+'│'+' '.repeat(dist(max+4)[1])+'║')
- : cl('║'+' '.repeat(dist(max+4)[0])+'│'+' '.repeat(s[0])+t+' '.repeat(s[1])+'│'+' '.repeat(dist(max+4)[1])+'║')
- }
- } 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)+'║')
- )
- }
- }
- }
-
- if (!draw.noMenu) {
- const activeNr = `${active+1}`
- const 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);
- process.stdin.on('keypress', (_, key) => {
- const lines = process.stdout.rows
- const run = (mode, fn) => {
- if (draw.t === mode) {
- fn()
- } else if (draw.t === 'h') {
- const v = draw.prev
- draw = { t: 'l', v }
- menu = 'Press [h] for help'
- drawApp()
- } else if (draw.t === 'i') {
- } 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
- default:
- if (draw.t === 'h') {
- const v = draw.prev
- draw = { t: 'l', v }
- menu = 'Press [h] for help'
- drawApp()
- } if (draw.t === 'i') {
- } else {
- rl.clearLine(-1)
- drawApp()
- }
- }
- })
-}
-
-/* Session */
-
-const setSession = (session) => {
- 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'])
- }
-
- getSession()
- })
-}
-
-const getSession = () => {
- fs.readFile(`${filesPath}/session`, function(err, f){
- if (err) {
- return getEmail()
- }
-
- try {
- conf = JSON.parse(f.toString())
-
- if (!conf.session || !conf.userId || !conf.email || !conf.list) {
- return getEmail('Session error')
- }
-
- getKey()
- fetchList()
- } catch (e) {
- return getEmail('Session error')
- }
- })
-}
-
-/* Login */
-const loginMsg = (text, err) => ['Login to apps.pruss.it', '', `\x1b[31m${err}\x1b[0m`, '', ...text]
-
-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()
- 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.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()
- rl.question(' Email: ', e => {
- 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.')
- }
-
- getPass(e)
- })
-}
-
-/* API */
-
-const fetchList = () => {
- get('/api/notes', showList, conf.session)
-}
-
-const fetchNote = note => {
- 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()
- remove(`/api/notes/${note._id}`, noteRemoved, conf.session)
-}
-
-const putNote = ({ _id, noteId }, content) => {
- put(`/api/notes/${_id}`, { noteId, content }, noteSaved, conf.session)
-}
-
-const putTitle = ({ _id, noteId }, title) => {
- put(`/api/notes/${_id}`, { noteId, title }, noteSaved, conf.session)
-}
-
-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 req = https.request(options, res => {
- let data = '';
-
- res.on('data', chunk => {
- data += chunk
- });
-
- res.on('end', () => callback(JSON.parse(data), res.statusCode, res.headers));
- });
-
- req.on('error', () => {
- callback(null)
- });
-
- req.end();
-}
-
-const post = (path, data, callback, cookie = '', put = false) => {
- const dataString = JSON.stringify(data)
- const options = {
- hostname: 'apps.pruss.it',
- port: 443,
- path,
- method: put ? 'PUT' : 'POST',
- 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('end', () => callback(JSON.parse(data), res.statusCode, res.headers));
- }).on("error", () => {
- callback(null)
- });
-
- req.write(dataString);
- req.end();
-}
-
-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 req = https.request(options, res => {
- let data = '';
-
- res.on('data', chunk => {
- data += chunk
- });
-
- res.on('end', () => callback(JSON.parse(data), res.statusCode, res.headers));
- });
-
- req.on('error', () => {
- callback(null)
- });
-
- req.end();
-}
-
-cls()
-getSession()
-process.stdout.on('resize', drawApp);
-cursor.hide()