├── .editorconfig ├── .gitattributes ├── .gitignore ├── .npmrc ├── .prettierrc.json ├── LICENSE ├── changelog.md ├── code-of-conduct.md ├── contributing.md ├── images ├── cli-help.png ├── cover.png ├── demo.gif └── flags-demo.gif ├── index.js ├── package.json ├── readme.md ├── test.js └── utils ├── cli.js ├── end.js ├── init.js ├── results.js └── switch.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # NPM # 2 | ########## 3 | # Ignore all directories called node_modules in current folder and any subfolders. 4 | node_modules/ 5 | /node_modules/ 6 | 7 | # Packages # 8 | ############ 9 | *.7z 10 | *.dmg 11 | *.gz 12 | *.bz2 13 | *.iso 14 | *.jar 15 | *.rar 16 | *.tar 17 | *.zip 18 | *.tgz 19 | *.map 20 | 21 | # Logs and databases # 22 | ###################### 23 | *.log 24 | *.sql 25 | *.env 26 | 27 | # OS generated files # 28 | ###################### 29 | **.DS_Store* 30 | ehthumbs.db 31 | Icon? 32 | Thumbs.db 33 | ._* 34 | 35 | # Vim generated files # 36 | ###################### 37 | *.un~ 38 | 39 | # SASS # 40 | ########## 41 | **/.sass-cache 42 | **/.sass-cache/* 43 | **/.map 44 | 45 | # Composer # 46 | ########## 47 | !assets/js/vendor/ 48 | wpcs/ 49 | /vendor/ 50 | 51 | # Bower # 52 | ########## 53 | assets/bower_components/* 54 | 55 | # Codekit # 56 | ########## 57 | /codekit-config.json 58 | *.codekit 59 | **.codekit-cache/* 60 | 61 | # Compiled Files and Build Dirs # 62 | ########## 63 | /README.html 64 | 65 | # PhpStrom Project Files # 66 | .idea/ 67 | library/vendors/composer 68 | assets/img/.DS_Store 69 | 70 | # Visual Studio Project Files # 71 | .vs/ 72 | 73 | # No lock files. 74 | package-lock.json 75 | yarn.lock 76 | settings.dat 77 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "none", 3 | "arrowParens": "avoid", 4 | "singleQuote": true, 5 | "printWidth": 80, 6 | "useTabs": true, 7 | "tabWidth": 4, 8 | "semi": true 9 | } 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License Copyright (c) 2020 msaaddev 2 | 3 | Permission is hereby granted, free of 4 | charge, to any person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, copy, modify, merge, 7 | publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to the 9 | following conditions: 10 | 11 | The above copyright notice and this permission notice 12 | (including the next paragraph) shall be included in all copies or substantial 13 | portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO 18 | EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 19 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | ## Changes Across Different Versions of stackoverflow-cli 2 | 3 | ### 🚀 v1.1.0 4 | 5 | - Documentation 6 | - Edge Case fix 7 | 8 | ### 🚀 v1.0.0 9 | 10 | - Search any query inside your terminal 11 | - Top 10 threads 12 | - Order the results in ascending/descending order 13 | - Sort the results via relevance/votes/activity/creation 14 | -------------------------------------------------------------------------------- /code-of-conduct.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at mrsaadirfan@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | 1. Open the issue first. 4 | 2. Fork the repository. 5 | 3. Create and switch to the new branch. New branch name convention must be like this yourUsername/newFeature, for instance, `msaaddev/pr-review` 6 | 4. Commit the changes in your forked repository. 7 | 5. Open a pull request & mention the issue number in the pull request for reference. 8 | -------------------------------------------------------------------------------- /images/cli-help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MLH-Fellowship/stackoverflow-cli/75be51d399ac5bf572f311b9dc9d53876891e753/images/cli-help.png -------------------------------------------------------------------------------- /images/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MLH-Fellowship/stackoverflow-cli/75be51d399ac5bf572f311b9dc9d53876891e753/images/cover.png -------------------------------------------------------------------------------- /images/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MLH-Fellowship/stackoverflow-cli/75be51d399ac5bf572f311b9dc9d53876891e753/images/demo.gif -------------------------------------------------------------------------------- /images/flags-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MLH-Fellowship/stackoverflow-cli/75be51d399ac5bf572f311b9dc9d53876891e753/images/flags-demo.gif -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * 5 | * Author Saad Irfan, Angelina Gasharova, Aneesh Kodali 6 | * GitHub 7 | */ 8 | 9 | const init = require('./utils/init'); 10 | const cli = require('./utils/cli'); 11 | 12 | (module.exports = async () => { 13 | let flags = []; 14 | flags = [...process.argv.slice(2)]; 15 | const question = await init(); 16 | cli(question, flags); 17 | })(); 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cli-stackoverflow", 3 | "version": "1.2.0", 4 | "description": "Work with the Stack Overflow inside your terminal", 5 | "main": "index.js", 6 | "repository": "MLH-Fellowship/stackoverflow-cli.git", 7 | "keywords": [ 8 | "stackoverflow", 9 | "stackoverflow-cli", 10 | "developer tool" 11 | ], 12 | "author": { 13 | "name": "Saad Irfan, Angelina Gasharova, Aneesh Kodali" 14 | }, 15 | "bin": { 16 | "stack": "index.js" 17 | }, 18 | "files": [ 19 | "index.js", 20 | "utils/*" 21 | ], 22 | "license": "MIT", 23 | "homepage": "MLH-Fellowship/stackoverflow-cli#readme", 24 | "scripts": { 25 | "format": "prettier --write \"./**/*.{js,json}\"", 26 | "test": "jest --detectOpenHandles" 27 | }, 28 | "dependencies": { 29 | "axios": "^0.21.0", 30 | "chalk": "^4.1.0", 31 | "cli-meow-help": "^2.0.2", 32 | "cli-welcome": "^2.2.2", 33 | "enquirer": "^2.3.6", 34 | "keypress": "^0.2.1", 35 | "log-symbols": "^4.0.0", 36 | "log-update": "^4.0.0", 37 | "meow": "^8.0.0", 38 | "node-cli-handle-error": "^1.0.0", 39 | "ora": "^5.1.0", 40 | "update-notifier": "^5.0.1" 41 | }, 42 | "devDependencies": { 43 | "jest": "^26.6.3", 44 | "prettier": "^2.2.0" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ![stackoverflow-cli](images/cover.png) 2 | 3 |
4 | Search any query on Stack Overflow without leaving your IDE 5 |
6 | version 7 | downloads 8 | license 9 |
10 | 11 | # 💥 stackoverflow-cli 12 | 13 | - Search any query inside your terminal 14 | - Top 10 threads 15 | - Order the results in ascending/descending order 16 | - Sort the results via relevance/votes/activity/creation 17 | 18 | ## ↓ INSTALL 19 | 20 | ```sh 21 | # install the cli globally 22 | npm i -g cli-stackoverflow 23 | 24 | # use it via npx 25 | npx cli-stackoverflow 26 | ``` 27 | 28 | ## ⚡️ USAGE 29 | 30 | You can run the CLI by executing following commands inside the terminal: 31 | 32 | ```sh 33 | stack 34 | ``` 35 | 36 | ### 📃 Guide 37 | 38 | - Right → key – Next thread 39 | - Left ← key – Prev thread 40 | - Up ↑ key – Next Answer 41 | - Down ↓ key – Prev Answer 42 | 43 | ![demo](images/demo.gif) 44 | 45 | ### 🏳 Flags 46 | 47 | ```sh 48 | # CLI help 49 | stack --help 50 | ``` 51 | 52 | ![help text](images/cli-help.png) 53 | 54 | ```sh 55 | # Order results in ascending order 56 | stack --asc 57 | 58 | # Sort results according to the most activity 59 | stack --activity 60 | 61 | # Sort results according to votes 62 | stack --votes 63 | 64 | # Sort results according to the creation 65 | stack --creation 66 | ``` 67 | 68 | ![flags](images/flags-demo.gif) 69 | 70 | ## 👨🏻‍💻 CONTRIBUTORS 71 | 72 | - [Saad Irfan](https://github.com/msaaddev) 73 | - [Aneesh Kodali](https://github.com/aneeshkodali) 74 | - [Angelina Gasharova](https://github.com/angelinag) 75 | 76 | ## 🔑 LICENSE 77 | 78 | - MIT 79 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | const cli = require('./utils/cli'); 2 | 3 | test('CLI function should return undefined', async () => { 4 | const cliPrompt = await cli('how to sort list in python', []); 5 | expect(typeof cliPrompt).toEqual('undefined'); 6 | }); 7 | 8 | test('Check user input for empty string without flags', async () => { 9 | const cliPrompt = await cli('', []); 10 | expect(cliPrompt).toEqual('You did not enter a question. Please enter one'); 11 | }); 12 | 13 | test('Check user input for empty string with flags', async () => { 14 | const cliPrompt = await cli('', ['--asc', '--votes']); 15 | expect(cliPrompt).toEqual('You did not enter a question. Please enter one'); 16 | }); 17 | -------------------------------------------------------------------------------- /utils/cli.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const ora = require('ora'); 3 | const results = require('./results'); 4 | const handleError = require('node-cli-handle-error'); 5 | 6 | // base url 7 | const baseUrl = 'https://api.stackexchange.com/2.2/search/advanced'; 8 | // default params 9 | const site = 'stackoverflow'; 10 | const filter = '!)rTkraPXy17fPqpx7wE5'; 11 | const pageSize = 10; 12 | 13 | /** 14 | * 15 | * @param encodedString - string to decode 16 | * @return decodedSring 17 | */ 18 | const decodeEntities = encodedString => { 19 | const translate_re = /&(nbsp|amp|quot|lt|gt);/g; 20 | const translate = { 21 | nbsp: ' ', 22 | amp: '&', 23 | quot: '"', 24 | lt: '<', 25 | gt: '>' 26 | }; 27 | return encodedString 28 | .replace(translate_re, (match, entity) => { 29 | return translate[entity]; 30 | }) 31 | .replace(/&#(\d+);/gi, (match, numStr) => { 32 | const num = parseInt(numStr, 10); 33 | return String.fromCharCode(num); 34 | }); 35 | }; 36 | 37 | /** 38 | * 39 | * @param question - user inputed question 40 | * @param flags - user provided flags 41 | */ 42 | module.exports = async (question, flags) => { 43 | // spinner 44 | const spinner = ora(`Fetching results for your query...`); 45 | 46 | // default params 47 | const order = flags.indexOf(`--asc`) >= 0 ? 'asc' : 'desc'; 48 | let sort; 49 | if (flags.indexOf(`--activity`) >= 0) { 50 | sort = `activity`; 51 | } else if (flags.indexOf(`--votes`) >= 0) { 52 | sort = `votes`; 53 | } else if (flags.indexOf(`--creation`) >= 0) { 54 | sort = `creation`; 55 | } else { 56 | sort = `relevance`; 57 | } 58 | 59 | // check if question is empty string 60 | if (question === '') { 61 | return 'You did not enter a question. Please enter one'; 62 | } 63 | 64 | // making API call 65 | try { 66 | console.log(''); 67 | spinner.start(); 68 | const { data } = await axios.get( 69 | `${baseUrl}?order=${order}&sort=${sort}&q=${question}&pageSize=${pageSize}&site=${site}&filter=${filter}` 70 | ); 71 | spinner.succeed(); 72 | console.log(''); 73 | // decode html characters to regular chars 74 | for (const [key, value] of Object.entries(data['items'])) { 75 | let item = value['body_markdown']; 76 | data['items'][key]['body_markdown'] = decodeEntities(item).split( 77 | '\r\n' 78 | ); 79 | 80 | // nullify the body for UX purposes (body prop not used) 81 | data['items'][key]['body'] = []; 82 | } 83 | let { items } = data; 84 | results(items, order, sort); 85 | } catch (err) { 86 | spinner.fail(); 87 | handleError(`Something went wrong.`, err); 88 | } 89 | }; 90 | -------------------------------------------------------------------------------- /utils/end.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | const { info } = require('log-symbols'); 3 | 4 | module.exports = () => { 5 | console.log(''); 6 | console.log( 7 | info, 8 | chalk.hex('#FAD000').inverse(' STAR '), 9 | chalk.dim('https://github.com/MLH-Fellowship/stackoverflow-cli'), 10 | '\n' 11 | ); 12 | }; 13 | -------------------------------------------------------------------------------- /utils/init.js: -------------------------------------------------------------------------------- 1 | // importing packages 2 | const welcome = require('cli-welcome'); 3 | const { Input } = require('enquirer'); 4 | const meow = require('meow'); 5 | const meowHelp = require('cli-meow-help'); 6 | const updateNotifier = require('update-notifier'); 7 | const pkgJSON = require('../package.json'); 8 | 9 | /* 10 | * 11 | * get user question 12 | */ 13 | const getInput = async () => { 14 | const prompt = new Input({ 15 | name: 'question', 16 | message: 'What is your question?' 17 | }); 18 | 19 | let answer; 20 | 21 | try { 22 | answer = await prompt.run(); 23 | } catch (error) { 24 | console.error(error); 25 | } 26 | return answer; 27 | }; 28 | 29 | /** 30 | * 31 | * generates cli help text 32 | */ 33 | const cliHelpText = () => { 34 | const commands = { 35 | none: { desc: `No commands available for ${pkgJSON.name}` } 36 | }; 37 | 38 | const flags = { 39 | asc: { 40 | desc: `Order the results in ascending order`, 41 | default: 'desc' 42 | }, 43 | activity: { 44 | desc: `Sort the results according to activity date`, 45 | default: 'relevance' 46 | }, 47 | votes: { 48 | desc: `Sort the results according to the number of votes`, 49 | default: 'relevance' 50 | }, 51 | creation: { 52 | desc: `Sort the results according to the creation date`, 53 | default: 'relevance' 54 | } 55 | }; 56 | 57 | const helpText = meowHelp({ 58 | name: `stack`, 59 | commands, 60 | flags 61 | }); 62 | 63 | meow(helpText, { flags }); 64 | }; 65 | 66 | module.exports = async () => { 67 | cliHelpText() || 68 | welcome({ 69 | title: `stackoverflow-cli`, 70 | tagLine: `by ${pkgJSON.author.name}`, 71 | description: `${pkgJSON.description}`, 72 | bgColor: `#f48024`, 73 | color: `#FFFFFF`, 74 | bold: true, 75 | clear: true, 76 | version: `${pkgJSON.version}` 77 | }); 78 | 79 | // Make sure user enters a question that's not an empty string 80 | let question = ''; 81 | while (question === '') { 82 | question = await getInput(); 83 | } 84 | 85 | // checks for CLI update 86 | updateNotifier({ pkg: pkgJSON }).notify(); 87 | 88 | return question; 89 | }; 90 | -------------------------------------------------------------------------------- /utils/results.js: -------------------------------------------------------------------------------- 1 | const switchResult = require('./switch'); 2 | 3 | /** 4 | * 5 | * @param thread - stackoverflow thread for answers 6 | * @return - array of answers of the threads 7 | */ 8 | const threadAns = thread => { 9 | const temp = []; 10 | if (typeof thread === 'undefined') { 11 | return temp; 12 | } 13 | 14 | thread.map(ans => { 15 | temp.push(ans.body_markdown); 16 | }); 17 | return temp; 18 | }; 19 | 20 | /** 21 | * 22 | * @param question - thread question 23 | * @return body - formated body 24 | */ 25 | const format = question => { 26 | let body = ''; 27 | 28 | question.map(index => { 29 | body += `${index}\n`; 30 | }); 31 | 32 | return body; 33 | }; 34 | 35 | /** 36 | * 37 | * @param results - stackoverflow threads 38 | * @param order - order of results 39 | * @param sort - sort of results 40 | */ 41 | module.exports = (results, order, sort) => { 42 | let basicInfoOfQuestions = []; 43 | 44 | results.map(result => { 45 | const infoObj = { 46 | title: typeof result.title === 'undefined' ? '' : result.title, 47 | body: format(result.body_markdown), 48 | answers: threadAns(result.answers) 49 | }; 50 | basicInfoOfQuestions.push(infoObj); 51 | }); 52 | 53 | switchResult(basicInfoOfQuestions, order, sort); 54 | }; 55 | -------------------------------------------------------------------------------- /utils/switch.js: -------------------------------------------------------------------------------- 1 | const keypress = require('keypress'); 2 | const logUpdate = require('log-update'); 3 | const chalk = require('chalk'); 4 | const { info } = require('log-symbols'); 5 | const end = require('./end'); 6 | 7 | /** 8 | * 9 | * @param index - thread number 10 | * @return string - formated string 11 | */ 12 | const formatThread = (indexOfThread, indexOfAns, thread, order, sort) => { 13 | return `${info} ${chalk.dim( 14 | `Thread #${indexOfThread + 1} | Order: ${order} | Sort By: ${sort}` 15 | )}\n\n${chalk.hex(`#14b514`).bold.inverse(` TITLE `)} ${ 16 | thread[indexOfThread].title 17 | }\n\n${chalk.hex(`#14b514`).bold.inverse(` QUESTION `)} ${ 18 | thread[indexOfThread].body 19 | }\n\n${chalk 20 | .hex(`#14b514`) 21 | .bold.inverse(` ANSWER #${indexOfAns + 1} `)} ${ 22 | thread[indexOfThread].answers[indexOfAns] 23 | }\n\n${chalk.dim( 24 | `GUIDE: \n - Press Up Arrow key to see next answer.\n - Press Down Arrow key to see previous answer. \n - Press Right Arrow key to see next thread.\n - Press Left Arrow key to see preview thread.\n - Press ESC to exit the CLI.\n` 25 | )}`; 26 | }; 27 | 28 | /** 29 | * 30 | * @param threads - That has all the data 31 | * @param order - order of results 32 | * @param sort - sort of results 33 | */ 34 | module.exports = (threads, order, sort) => { 35 | let counterOfThread = 0; 36 | let counterOfAnswer = 0; 37 | logUpdate(formatThread(counterOfThread, 0, threads, order, sort)); 38 | 39 | // switch the result back and forth from left and right arrow keys and exits with escape key 40 | keypress(process.stdin); 41 | process.stdin.on('keypress', function (ch, key) { 42 | if (key.name === 'right' && counterOfThread !== threads.length - 1) { 43 | counterOfThread++; 44 | counterOfAnswer = 0; 45 | logUpdate(formatThread(counterOfThread, 0, threads, order, sort)); 46 | } 47 | if (key.name === 'left' && counterOfThread !== 0) { 48 | counterOfThread--; 49 | counterOfAnswer = 0; 50 | logUpdate(formatThread(counterOfThread, 0, threads, order, sort)); 51 | } 52 | if ( 53 | key.name === 'up' && 54 | counterOfAnswer !== threads[counterOfThread].answers.length - 1 55 | ) { 56 | counterOfAnswer++; 57 | logUpdate( 58 | formatThread( 59 | counterOfThread, 60 | counterOfAnswer, 61 | threads, 62 | order, 63 | sort 64 | ) 65 | ); 66 | } 67 | if (key.name === 'down' && counterOfAnswer !== 0) { 68 | counterOfAnswer--; 69 | logUpdate( 70 | formatThread( 71 | counterOfThread, 72 | counterOfAnswer, 73 | threads, 74 | order, 75 | sort 76 | ) 77 | ); 78 | } 79 | if (key.name === 'escape') { 80 | end(); 81 | process.exit(); 82 | } 83 | }); 84 | 85 | process.stdin.setRawMode(true); 86 | process.stdin.resume(); 87 | }; 88 | --------------------------------------------------------------------------------