From 8f1301d64747aacf7f6a3a36c39f49f8c8cefd66 Mon Sep 17 00:00:00 2001 From: Bryan Ashby Date: Sat, 25 Jun 2016 22:36:40 -0600 Subject: [PATCH 01/23] ES6 + misc cleanup --- core/asset.js | 82 ++++++++++++++++++++++----------------------------- 1 file changed, 35 insertions(+), 47 deletions(-) diff --git a/core/asset.js b/core/asset.js index 063c115b..ae74e07b 100644 --- a/core/asset.js +++ b/core/asset.js @@ -1,10 +1,12 @@ /* jslint node: true */ 'use strict'; -var Config = require('./config.js').config; +// ENiGMA½ +const Config = require('./config.js').config; -var _ = require('lodash'); -var assert = require('assert'); +// deps +const _ = require('lodash'); +const assert = require('assert'); exports.parseAsset = parseAsset; exports.getAssetWithShorthand = getAssetWithShorthand; @@ -17,19 +19,20 @@ const ALL_ASSETS = [ 'art', 'menu', 'method', + 'module', 'systemMethod', 'systemModule', 'prompt', 'config', ]; -var ASSET_RE = new RegExp('\\@(' + ALL_ASSETS.join('|') + ')\\:([\\w\\d\\.]*)(?:\\/([\\w\\d\\_]+))*'); +const ASSET_RE = new RegExp('\\@(' + ALL_ASSETS.join('|') + ')\\:([\\w\\d\\.]*)(?:\\/([\\w\\d\\_]+))*'); function parseAsset(s) { - var m = ASSET_RE.exec(s); + const m = ASSET_RE.exec(s); if(m) { - var result = { type : m[1] }; + let result = { type : m[1] }; if(m[3]) { result.location = m[2]; @@ -48,7 +51,7 @@ function getAssetWithShorthand(spec, defaultType) { } if('@' === spec[0]) { - var asset = parseAsset(spec); + const asset = parseAsset(spec); assert(_.isString(asset.type)); return asset; @@ -56,63 +59,48 @@ function getAssetWithShorthand(spec, defaultType) { return { type : defaultType, asset : spec, - } - } -} - -// :TODO: Convert these to getAssetWithShorthand() -function getArtAsset(art) { - if(!_.isString(art)) { - return null; - } - - if('@' === art[0]) { - var artAsset = parseAsset(art); - assert('art' === artAsset.type || 'method' === artAsset.type); - - return artAsset; - } else { - return { - type : 'art', - asset : art, }; } } -function getModuleAsset(module) { - if(!_.isString(module)) { +function getArtAsset(spec) { + const asset = getAssetWithShorthand(spec, 'art'); + + if(!asset) { return null; } - if('@' === module[0]) { - var modAsset = parseAsset(module); - assert('module' === modAsset.type || 'systemModule' === modAsset.type); - - return modAsset; - } else { - return { - type : 'module', - asset : module, - } - } + assert( ['art', 'method' ].indexOf(asset.type) > -1); + return asset; } -function resolveConfigAsset(from) { - var asset = parseAsset(from); +function getModuleAsset(spec) { + const asset = getAssetWithShorthand(spec, 'module'); + + if(!asset) { + return null; + } + + assert( ['module', 'systemModule' ].indexOf(asset.type) > -1); + return asset; +} + +function resolveConfigAsset(spec) { + const asset = parseAsset(spec); if(asset) { assert('config' === asset.type); - var path = asset.asset.split('.'); - var conf = Config; - for(var i = 0; i < path.length; ++i) { + const path = asset.asset.split('.'); + let conf = Config; + for(let i = 0; i < path.length; ++i) { if(_.isUndefined(conf[path[i]])) { - return from; + return spec; } conf = conf[path[i]]; } return conf; } else { - return from; + return spec; } } @@ -122,4 +110,4 @@ function getViewPropertyAsset(src) { } return parseAsset(src); -}; +} From 57180da398a78ce014090e4afe8c4512dce6e599 Mon Sep 17 00:00:00 2001 From: Bryan Ashby Date: Sat, 25 Jun 2016 22:37:02 -0600 Subject: [PATCH 02/23] Mods DB location --- core/config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/core/config.js b/core/config.js index 4436004a..eac3dc53 100644 --- a/core/config.js +++ b/core/config.js @@ -180,6 +180,7 @@ function getDefaultConfig() { themes : paths.join(__dirname, './../mods/themes/'), logs : paths.join(__dirname, './../logs/'), // :TODO: set up based on system, e.g. /var/logs/enigmabbs or such db : paths.join(__dirname, './../db/'), + modsDb : paths.join(__dirname, './../db/mods/'), dropFiles : paths.join(__dirname, './../dropfiles/'), // + "/node/ misc : paths.join(__dirname, './../misc/'), }, From a5505ea7182f466764ee3346376f20e903af0348 Mon Sep 17 00:00:00 2001 From: Bryan Ashby Date: Sat, 25 Jun 2016 22:37:28 -0600 Subject: [PATCH 03/23] Add getModDatabasePath() for modules --- core/database.js | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/core/database.js b/core/database.js index 5029d7e1..a3385509 100644 --- a/core/database.js +++ b/core/database.js @@ -7,15 +7,42 @@ var sqlite3 = require('sqlite3'); var paths = require('path'); var async = require('async'); +const _ = require('lodash'); +const assert = require('assert'); + // database handles var dbs = {}; +exports.getModDatabasePath = getModDatabasePath; exports.initializeDatabases = initializeDatabases; exports.dbs = dbs; function getDatabasePath(name) { - return paths.join(conf.config.paths.db, name + '.sqlite3'); + return paths.join(conf.config.paths.db, `${name}.sqlite3`); +} + +function getModDatabasePath(moduleInfo, suffix) { + // + // Mods that use a database are stored in Config.paths.modsDb (e.g. enigma-bbs/db/mods) + // We expect that moduleInfo defines packageName which will be the base of the modules + // filename. An optional suffix may be supplied as well. + // + const HOST_RE = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/; + + assert(_.isObject(moduleInfo)); + assert(_.isString(moduleInfo.packageName), 'moduleInfo must define "packageName"!'); + + let full = moduleInfo.packageName; + if(suffix) { + full += `.${suffix}`; + } + + assert( + (full.split('.').length > 1 && HOST_RE.test(full)), + 'packageName must follow Reverse Domain Name Notation - https://en.wikipedia.org/wiki/Reverse_domain_name_notation'); + + return paths.join(conf.config.paths.modsDb, `${full}.sqlite3`); } function initializeDatabases(cb) { From 0f0e674f974ab17aa78825cddd42448e0a321926 Mon Sep 17 00:00:00 2001 From: Bryan Ashby Date: Sat, 25 Jun 2016 22:43:12 -0600 Subject: [PATCH 04/23] Add nextMenu() @systemMethod, code cleanup, ES6, etc. --- core/system_menu_method.js | 49 ++++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/core/system_menu_method.js b/core/system_menu_method.js index 0299c00e..72621122 100644 --- a/core/system_menu_method.js +++ b/core/system_menu_method.js @@ -1,25 +1,23 @@ /* jslint node: true */ 'use strict'; -var theme = require('./theme.js'); -var removeClient = require('./client_connections.js').removeClient; -var ansi = require('./ansi_term.js'); -var userDb = require('./database.js').dbs.user; -var sysProp = require('./system_property.js'); -var userLogin = require('./user_login.js').userLogin; +// ENiGMA½ +const removeClient = require('./client_connections.js').removeClient; +const ansiNormal = require('./ansi_term.js').normal; +const userLogin = require('./user_login.js').userLogin; -var async = require('async'); -var _ = require('lodash'); -var iconv = require('iconv-lite'); +// deps +const _ = require('lodash'); +const iconv = require('iconv-lite'); exports.login = login; exports.logoff = logoff; exports.prevMenu = prevMenu; +exports.nextMenu = nextMenu; -function login(callingMenu, formData, extraArgs) { - var client = callingMenu.client; +function login(callingMenu, formData) { - userLogin(callingMenu.client, formData.value.username, formData.value.password, function authResult(err) { + userLogin(callingMenu.client, formData.value.username, formData.value.password, err => { if(err) { // login failure if(err.existingConn && _.has(callingMenu, 'menuConfig.config.tooNodeMenu')) { @@ -36,32 +34,41 @@ function login(callingMenu, formData, extraArgs) { }); } -function logoff(callingMenu, formData, extraArgs) { +function logoff(callingMenu) { // // Simple logoff. Note that recording of @ logoff properties/stats // occurs elsewhere! // - var client = callingMenu.client; + const client = callingMenu.client; - setTimeout(function timeout() { + setTimeout( () => { // // For giggles... // client.term.write( - ansi.normal() + '\n' + + ansiNormal() + '\n' + iconv.decode(require('crypto').randomBytes(Math.floor(Math.random() * 65) + 20), client.term.outputEncoding) + - 'NO CARRIER', null, function written() { + 'NO CARRIER', null, () => { // after data is written, disconnect & remove the client - removeClient(client); - }); + return removeClient(client); + } + ); }, 500); } -function prevMenu(callingMenu, formData, extraArgs) { - callingMenu.prevMenu(function result(err) { +function prevMenu(callingMenu) { + callingMenu.prevMenu( err => { if(err) { callingMenu.client.log.error( { error : err.toString() }, 'Error attempting to fallback!'); } }); } + +function nextMenu(callingMenu) { + callingMenu.nextMenu( err => { + if(err) { + callingMenu.client.log.error( { error : err.toString() }, 'Error attempting to go to next menu!'); + } + }); +} From 0ea0f62b2c2eab6740d34821539abcf38b56bdd5 Mon Sep 17 00:00:00 2001 From: Bryan Ashby Date: Sat, 25 Jun 2016 22:45:49 -0600 Subject: [PATCH 05/23] Telnet Bridge module by apam! Note that there are currently issues connecting to some boards, namely: Other enigma boards! :D Likely needs some telnet negotiations acting as a *client*. WIP! --- mods/telnet_bridge.js | 115 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 mods/telnet_bridge.js diff --git a/mods/telnet_bridge.js b/mods/telnet_bridge.js new file mode 100644 index 00000000..6f7f4366 --- /dev/null +++ b/mods/telnet_bridge.js @@ -0,0 +1,115 @@ +/* jslint node: true */ +'use strict'; + +const MenuModule = require('../core/menu_module.js').MenuModule; +const resetScreen = require('../core/ansi_term.js').resetScreen; + +const async = require('async'); +const _ = require('lodash'); +const net = require('net'); + +/* + Expected configuration block: + + { + module: telnet_bridge + ... + config: { + host: somehost.net + port: 23 + } + } +*/ + +// :TODO: BUG: When a client disconnects, it's not handled very well -- the log is spammed with tons of errors +// :TODO: ENH: Support nodeMax and tooManyArt + +exports.getModule = TelnetBridgeModule; + +exports.moduleInfo = { + name : 'Telnet Bridge', + desc : 'Connect to other Telnet Systems', + author : 'Andrew Pamment', +}; + + +function TelnetBridgeModule(options) { + MenuModule.call(this, options); + + const self = this; + this.config = options.menuConfig.config; + + this.initSequence = function() { + let clientTerminated; + + async.series( + [ + function validateConfig(callback) { + if(_.isString(self.config.host) && + _.isNumber(self.config.port)) + { + callback(null); + } else { + callback(new Error('Configuration is missing required option(s)')); + } + }, + function createTelnetBridge(callback) { + const connectOpts = { + port : self.config.port, + host : self.config.host, + }; + + let clientTerminated; + + self.client.term.write(resetScreen()); + self.client.term.write(` Connecting to ${connectOpts.host}, please wait...\n`); + + let bridgeConnection = net.createConnection(connectOpts, () => { + self.client.log.info(connectOpts, 'Telnet bridge connection established'); + + self.client.term.output.pipe(bridgeConnection); + + self.client.once('end', () => { + self.client.log.info('Connection ended. Terminating connection'); + clientTerminated = true; + return bridgeConnection.end(); + }); + }); + + const restorePipe = function() { + self.client.term.output.unpipe(bridgeConnection); + self.client.term.output.resume(); + }; + + bridgeConnection.on('data', data => { + // pass along + // :TODO: just pipe this as well + return self.client.term.rawWrite(data); + }); + + bridgeConnection.once('end', () => { + restorePipe(); + return callback(clientTerminated ? new Error('Client connection terminated') : null); + }); + + bridgeConnection.once('error', err => { + self.client.log.info(`Telnet bridge connection error: ${err.message}`); + restorePipe(); + return callback(err); + }); + } + ], + err => { + if(err) { + self.client.log.warn( { error : err.message }, 'Telnet connection error'); + } + + if(!clientTerminated) { + self.prevMenu(); + } + } + ); + }; +} + +require('util').inherits(TelnetBridgeModule, MenuModule); From 3955d5539fb47b7d92ac52fb5a812fe515a0cce9 Mon Sep 17 00:00:00 2001 From: Bryan Ashby Date: Sun, 26 Jun 2016 21:23:36 -0600 Subject: [PATCH 06/23] Comment out disabling of VT100 line wrapping for now - causes too much trouble with actual esc seq in place --- core/connect.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/connect.js b/core/connect.js index 67ab1c1f..2698fd44 100644 --- a/core/connect.js +++ b/core/connect.js @@ -72,7 +72,7 @@ function ansiQueryTermSizeIfNeeded(client, cb) { function prepareTerminal(term) { term.rawWrite(ansi.normal()); - term.rawWrite(ansi.disableVT100LineWrapping()); + //term.rawWrite(ansi.disableVT100LineWrapping()); // :TODO: set xterm stuff -- see x84/others } From b6cada6f3c8e94d4daefb68b9c2a661ec7f2f10f Mon Sep 17 00:00:00 2001 From: Bryan Ashby Date: Sun, 26 Jun 2016 21:23:59 -0600 Subject: [PATCH 07/23] Various ES6, TODO and code cleanup --- core/ansi_term.js | 110 ++++++++++++++++++++++++---------------------- 1 file changed, 57 insertions(+), 53 deletions(-) diff --git a/core/ansi_term.js b/core/ansi_term.js index 28a3f20f..c7d32016 100644 --- a/core/ansi_term.js +++ b/core/ansi_term.js @@ -12,9 +12,13 @@ // * http://www.inwap.com/pdp10/ansicode.txt // -const assert = require('assert'); +// ENiGMA½ const miscUtil = require('./misc_util.js'); +// deps +const assert = require('assert'); +const _ = require('lodash'); + exports.getFGColorValue = getFGColorValue; exports.getBGColorValue = getBGColorValue; exports.sgr = sgr; @@ -35,9 +39,9 @@ exports.setEmulatedBaudRate = setEmulatedBaudRate; // See also // https://github.com/TooTallNate/ansi.js/blob/master/lib/ansi.js -var ESC_CSI = '\u001b['; +const ESC_CSI = '\u001b['; -var CONTROL = { +const CONTROL = { up : 'A', down : 'B', @@ -124,7 +128,7 @@ var CONTROL = { // Select Graphics Rendition // See http://cvs.synchro.net/cgi-bin/viewcvs.cgi/*checkout*/src/conio/cterm.txt // -var SGRValues = { +const SGRValues = { reset : 0, bold : 1, dim : 2, @@ -180,7 +184,7 @@ function getBGColorValue(name) { // // See https://github.com/protomouse/synchronet/blob/master/src/conio/cterm.txt // -var SYNCTERM_FONT_AND_ENCODING_TABLE = [ +const SYNCTERM_FONT_AND_ENCODING_TABLE = [ 'cp437', 'cp1251', 'koi8_r', @@ -233,7 +237,7 @@ var SYNCTERM_FONT_AND_ENCODING_TABLE = [ // This table contains lowercased entries with any spaces // replaced with '_' for lookup purposes. // -var FONT_ALIAS_TO_SYNCTERM_MAP = { +const FONT_ALIAS_TO_SYNCTERM_MAP = { 'cp437' : 'cp437', 'ibm_vga' : 'cp437', 'ibmpc' : 'cp437', @@ -267,8 +271,8 @@ var FONT_ALIAS_TO_SYNCTERM_MAP = { 'amiga_p0t-noodle' : 'pot_noodle', 'mo_soul' : 'mo_soul', - 'mosoul' : 'mo_soul', - 'mO\'sOul' : 'mo_soul', + 'mosoul' : 'mo_soul', + 'mO\'sOul' : 'mo_soul', 'amiga_microknight' : 'microknight', 'amiga_microknight+' : 'microknight_plus', @@ -280,13 +284,13 @@ var FONT_ALIAS_TO_SYNCTERM_MAP = { }; function setSyncTERMFont(name, fontPage) { - var p1 = miscUtil.valueWithDefault(fontPage, 0); + const p1 = miscUtil.valueWithDefault(fontPage, 0); assert(p1 >= 0 && p1 <= 3); - var p2 = SYNCTERM_FONT_AND_ENCODING_TABLE.indexOf(name); + const p2 = SYNCTERM_FONT_AND_ENCODING_TABLE.indexOf(name); if(p2 > -1) { - return ESC_CSI + p1 + ';' + p2 + ' D'; + return `${ESC_CSI}${p1};${p2} D`; } return ''; @@ -296,20 +300,20 @@ function getSyncTERMFontFromAlias(alias) { return FONT_ALIAS_TO_SYNCTERM_MAP[alias.toLowerCase().replace(/ /g, '_')]; } -var DEC_CURSOR_STYLE = { - 'blinking block' : 0, - 'default' : 1, - 'steady block' : 2, +const DEC_CURSOR_STYLE = { + 'blinking block' : 0, + 'default' : 1, + 'steady block' : 2, 'blinking underline' : 3, - 'steady underline' : 4, - 'blinking bar' : 5, - 'steady bar' : 6, + 'steady underline' : 4, + 'blinking bar' : 5, + 'steady bar' : 6, }; function setCursorStyle(cursorStyle) { - var ps = DEC_CURSOR_STYLE[cursorStyle]; + const ps = DEC_CURSOR_STYLE[cursorStyle]; if(ps) { - return ESC_CSI + ps + ' q'; + return `${ESC_CSI}${ps} q`; } return ''; @@ -317,24 +321,24 @@ function setCursorStyle(cursorStyle) { // Create methods such as up(), nextLine(),... Object.keys(CONTROL).forEach(function onControlName(name) { - var code = CONTROL[name]; + const code = CONTROL[name]; exports[name] = function() { - var c = code; + let c = code; if(arguments.length > 0) { // arguments are array like -- we want an array c = Array.prototype.slice.call(arguments).map(Math.round).join(';') + code; } - return ESC_CSI + c; + return `${ESC_CSI}${c}`; }; }); // Create various color methods such as white(), yellowBG(), reset(), ... -Object.keys(SGRValues).forEach(function onSgrName(name) { - var code = SGRValues[name]; +Object.keys(SGRValues).forEach( name => { + const code = SGRValues[name]; exports[name] = function() { - return ESC_CSI + code + 'm'; + return `${ESC_CSI}${code}m`; }; }); @@ -347,28 +351,19 @@ function sgr() { if(arguments.length <= 0) { return ''; } - - var result = ''; - // :TODO: this method needs a lot of cleanup! + let result = []; + const args = Array.isArray(arguments[0]) ? arguments[0] : arguments; - var args = Array.isArray(arguments[0]) ? arguments[0] : arguments; - for(var i = 0; i < args.length; i++) { - if(typeof args[i] === 'string') { - if(args[i] in SGRValues) { - if(result.length > 0) { - result += ';'; - } - result += SGRValues[args[i]]; - } - } else if(typeof args[i] === 'number') { - if(result.length > 0) { - result += ';'; - } - result += args[i]; + args.forEach(arg => { + if(_.isString(arg) && arg in SGRValues) { + result.push(SGRValues[arg]); + } else if(_.isNumber(arg)) { + result.push(arg); } - } - return ESC_CSI + result + 'm'; + }); + + return `${ESC_CSI}${result.join(';')}m`; } // @@ -376,10 +371,10 @@ function sgr() { // to a ANSI SGR sequence. // function getSGRFromGraphicRendition(graphicRendition, initialReset) { - var sgrSeq = []; + let sgrSeq = []; + let styleCount = 0; - var styleCount = 0; - [ 'intensity', 'underline', 'blink', 'negative', 'invisible' ].forEach(function style(s) { + [ 'intensity', 'underline', 'blink', 'negative', 'invisible' ].forEach( s => { if(graphicRendition[s]) { sgrSeq.push(graphicRendition[s]); ++styleCount; @@ -414,11 +409,11 @@ function clearScreen() { } function resetScreen() { - return exports.reset() + exports.eraseData(2) + exports.goHome(); + return `${exports.reset()}${exports.eraseData(2)}${exports.goHome()}`; } function normal() { - return sgr(['normal', 'reset']); + return sgr( [ 'normal', 'reset' ] ); } function goHome() { @@ -452,14 +447,23 @@ function deleteLine(count) { */ // -// See http://www.termsys.demon.co.uk/vtANSI_BBS.htm +// Disable auto line wraping @ termWidth +// +// See: +// http://stjarnhimlen.se/snippets/vt100.txt +// https://github.com/protomouse/synchronet/blob/master/src/conio/cterm.txt +// +// WARNING: +// * Not honored by all clients +// * If it is honored, ANSI's that rely on this (e.g. do not have \r\n endings +// and use term width -- generally 80 columns -- will display garbled! // function disableVT100LineWrapping() { - return ESC_CSI + '7l'; + return `${ESC_CSI}?7l`; } function setEmulatedBaudRate(rate) { - var speed = { + const speed = { unlimited : 0, off : 0, 0 : 0, From be6af161ec19776680618f5f27903d2ffa6b602c Mon Sep 17 00:00:00 2001 From: Andrew Pamment Date: Mon, 27 Jun 2016 17:29:17 +1000 Subject: [PATCH 08/23] Added ERC Module --- core/multi_line_edit_text_view.js | 90 +++++++++------ mods/art/erc.ans | Bin 0 -> 376 bytes mods/erc_client.js | 184 ++++++++++++++++++++++++++++++ mods/menu.hjson | 102 +++++++++++++---- 4 files changed, 318 insertions(+), 58 deletions(-) create mode 100644 mods/art/erc.ans create mode 100644 mods/erc_client.js diff --git a/core/multi_line_edit_text_view.js b/core/multi_line_edit_text_view.js index 63c5e20b..c06a1f2c 100644 --- a/core/multi_line_edit_text_view.js +++ b/core/multi_line_edit_text_view.js @@ -34,7 +34,7 @@ var _ = require('lodash'); // // Editors - BBS // * https://github.com/M-griffin/Enthral/blob/master/src/msg_fse.cpp -// +// // // Editors - Other // * http://joe-editor.sourceforge.net/ @@ -55,12 +55,12 @@ var _ = require('lodash'); // // To-Do -// +// // * Index pos % for emit scroll events // * Some of this shoudl be async'd where there is lots of processing (e.g. word wrap) // * Fix backspace when col=0 (e.g. bs to prev line) // * Add back word delete -// * +// * var SPECIAL_KEY_MAP_DEFAULT = { @@ -114,6 +114,11 @@ function MultiLineEditTextView(options) { this.topVisibleIndex = 0; this.mode = options.mode || 'edit'; // edit | preview | read-only + if (this.mode == 'edit') { + this.autoScroll = options.autoScroll || 'true'; + } else { + this.autoScroll = options.autoScroll || 'false'; + } // // cursorPos represents zero-based row, col positions // within the editor itself @@ -179,7 +184,7 @@ function MultiLineEditTextView(options) { this.eraseRows = function(startRow, endRow) { self.client.term.rawWrite(self.getSGRFor('text') + ansi.hideCursor()); - + var absPos = self.getAbsolutePosition(startRow, 0); var absPosEnd = self.getAbsolutePosition(endRow, 0); var eraseFiller = new Array(self.dimens.width).join(' '); @@ -216,7 +221,7 @@ function MultiLineEditTextView(options) { if(!_.isNumber(index)) { index = self.getTextLinesIndex(); } - return self.textLines[index].text.replace(/\t/g, ' '); + return self.textLines[index].text.replace(/\t/g, ' '); }; this.getText = function(index) { @@ -266,19 +271,19 @@ function MultiLineEditTextView(options) { } return lines; }; - + this.getOutputText = function(startIndex, endIndex, eolMarker) { let lines = self.getTextLines(startIndex, endIndex); let text = ''; var re = new RegExp('\\t{1,' + (self.tabWidth) + '}', 'g'); - + lines.forEach(line => { text += line.text.replace(re, '\t'); if(eolMarker && line.eol) { text += eolMarker; - } + } }); - + return text; } @@ -302,7 +307,7 @@ function MultiLineEditTextView(options) { /* this.editTextAtPosition = function(editAction, text, index, col) { switch(editAction) { - case 'insert' : + case 'insert' : self.insertCharactersInText(text, index, col); break; @@ -329,7 +334,7 @@ function MultiLineEditTextView(options) { newLines[newLines.length - 1].eol = true; Array.prototype.splice.apply( - self.textLines, + self.textLines, [ index, (nextEolIndex - index) + 1 ].concat(newLines)); return wrapped.firstWrapRange; @@ -337,7 +342,7 @@ function MultiLineEditTextView(options) { this.removeCharactersFromText = function(index, col, operation, count) { if('right' === operation) { - self.textLines[index].text = + self.textLines[index].text = self.textLines[index].text.slice(col, count) + self.textLines[index].text.slice(col + count); @@ -354,11 +359,11 @@ function MultiLineEditTextView(options) { } else if ('backspace' === operation) { // :TODO: method for splicing text self.textLines[index].text = - self.textLines[index].text.slice(0, col - (count - 1)) + + self.textLines[index].text.slice(0, col - (count - 1)) + self.textLines[index].text.slice(col + 1); self.cursorPos.col -= (count - 1); - + self.updateTextWordWrap(index); self.redrawRows(self.cursorPos.row, self.dimens.height); @@ -405,9 +410,9 @@ function MultiLineEditTextView(options) { this.insertCharactersInText = function(c, index, col) { self.textLines[index].text = [ - self.textLines[index].text.slice(0, col), - c, - self.textLines[index].text.slice(col) + self.textLines[index].text.slice(0, col), + c, + self.textLines[index].text.slice(col) ].join(''); //self.cursorPos.col++; @@ -443,13 +448,13 @@ function MultiLineEditTextView(options) { // absPos = self.getAbsolutePosition(self.cursorPos.row, self.cursorPos.col); self.client.term.write( - ansi.hideCursor() + - self.getSGRFor('text') + + ansi.hideCursor() + + self.getSGRFor('text') + self.getRenderText(index).slice(self.cursorPos.col - c.length) + ansi.goto(absPos.row, absPos.col) + ansi.showCursor(), false ); - } + } }; this.getRemainingTabWidth = function(col) { @@ -541,7 +546,7 @@ function MultiLineEditTextView(options) { .split(/\r\n|[\n\v\f\r\x85\u2028\u2029]/g); var wrapped; - + for(var i = 0; i < text.length; ++i) { wrapped = self.wordWrapSingleLine( text[i], // input @@ -556,7 +561,7 @@ function MultiLineEditTextView(options) { }; this.getAbsolutePosition = function(row, col) { - return { + return { row : self.position.row + row, col : self.position.col + col, }; @@ -610,7 +615,7 @@ function MultiLineEditTextView(options) { this.keyPressDown = function() { var lastVisibleRow = Math.min( - self.dimens.height, + self.dimens.height, (self.textLines.length - self.topVisibleIndex)) - 1; if(self.cursorPos.row < lastVisibleRow) { @@ -714,7 +719,7 @@ function MultiLineEditTextView(options) { var nextEolIndex = self.getNextEndOfLineIndex(index); var text = self.getContiguousText(index, nextEolIndex); var newLines = self.wordWrapSingleLine(text.slice(self.cursorPos.col), 'tabsIntact').wrapped; - + newLines.unshift( { text : text.slice(0, self.cursorPos.col), eol : true } ); for(var i = 1; i < newLines.length; ++i) { newLines[i] = { text : newLines[i] }; @@ -722,7 +727,7 @@ function MultiLineEditTextView(options) { newLines[newLines.length - 1].eol = true; Array.prototype.splice.apply( - self.textLines, + self.textLines, [ index, (nextEolIndex - index) + 1 ].concat(newLines)); // redraw from current row to end of visible area @@ -844,9 +849,9 @@ function MultiLineEditTextView(options) { self.client.term.rawWrite(ansi.left(move)); break; - case 'up' : + case 'up' : case 'down' : - // + // // Jump to the tabstop nearest the cursor // var newCol = self.tabStops.reduce(function r(prev, curr) { @@ -890,7 +895,7 @@ function MultiLineEditTextView(options) { this.cursorBeginOfNextLine = function() { // e.g. when scrolling right past eol var linesBelow = self.getRemainingLinesBelowRow(); - + if(linesBelow > 0) { var lastVisibleRow = Math.min(self.dimens.height, self.textLines.length) - 1; if(self.cursorPos.row < lastVisibleRow) { @@ -1007,9 +1012,9 @@ MultiLineEditTextView.prototype.setText = function(text) { MultiLineEditTextView.prototype.addText = function(text) { this.insertRawText(text); - if(this.isEditMode()) { + if(this.autoScroll) { this.cursorEndOfDocument(); - } else if(this.isPreviewMode()) { + } else { this.cursorStartOfDocument(); } }; @@ -1027,7 +1032,7 @@ MultiLineEditTextView.prototype.setPropertyValue = function(propName, value) { }; var HANDLED_SPECIAL_KEYS = [ - 'up', 'down', 'left', 'right', + 'up', 'down', 'left', 'right', 'home', 'end', 'page up', 'page down', 'line feed', @@ -1045,7 +1050,7 @@ MultiLineEditTextView.prototype.onKeyPress = function(ch, key) { var self = this; var handled; - if(key) { + if(key) { HANDLED_SPECIAL_KEYS.forEach(function aKey(specialKey) { if(self.isKeyMapped(specialKey, key.name)) { @@ -1068,6 +1073,22 @@ MultiLineEditTextView.prototype.onKeyPress = function(ch, key) { } }; +MultiLineEditTextView.prototype.scrollUp = function() { + this.scrollDocumentUp(); +} + +MultiLineEditTextView.prototype.scrollDown = function() { + this.scrollDocumentDown(); +} + +MultiLineEditTextView.prototype.deleteLine = function(line) { + this.textLines.splice(line, 1); +} + +MultiLineEditTextView.prototype.getLineCount = function() { + return this.textLines.length; +} + MultiLineEditTextView.prototype.getTextEditMode = function() { return this.overtypeMode ? 'overtype' : 'insert'; }; @@ -1075,11 +1096,10 @@ MultiLineEditTextView.prototype.getTextEditMode = function() { MultiLineEditTextView.prototype.getEditPosition = function() { var currentIndex = this.getTextLinesIndex() + 1; - return { - row : this.getTextLinesIndex(this.cursorPos.row), + return { + row : this.getTextLinesIndex(this.cursorPos.row), col : this.cursorPos.col, percent : Math.floor(((currentIndex / this.textLines.length) * 100)), below : this.getRemainingLinesBelowRow(), }; }; - diff --git a/mods/art/erc.ans b/mods/art/erc.ans new file mode 100644 index 0000000000000000000000000000000000000000..d2f336d2255ffac5d5eed3b240d40fe2d6e267ba GIT binary patch literal 376 zcmb1+Hn27^ur@Z$y-j5x9c^r$tLhtK$b}66Wr0>oN1K>h8yn=N1|=&vXC#&=IOk-h z=9MVu7nWw0D3s(YfN3jDpgMBgC>?ER4RnN^G|-bk>l7ePat$%&k_vVVb#^r{P@o<# jGB7kVFf%gaWn^GrWDH=CU;qPQPbXi6Fn31?4^9FA1c{gz literal 0 HcmV?d00001 diff --git a/mods/erc_client.js b/mods/erc_client.js new file mode 100644 index 00000000..61e2881e --- /dev/null +++ b/mods/erc_client.js @@ -0,0 +1,184 @@ +/* jslint node: true */ +'use strict'; +var MenuModule = require('../core/menu_module.js').MenuModule; + +const async = require('async'); +const _ = require('lodash'); +const net = require('net'); + +const packageJson = require('../package.json'); + +/* + Expected configuration block: + + ercClient: { + art: erc + module: erc_client + config: { + host: 192.168.1.171 + port: 5001 + bbsTag: SUPER + } + + form: { + 0: { + mci: { + MT1: { + width: 79 + height: 21 + mode: preview + autoScroll: true + } + ET3: { + autoScale: false + width: 77 + argName: chattxt + focus: true + submit: true + } + } + + submit: { + *: [ + { + value: { chattxt: null } + action: @method:processInput + } + ] + } + actionKeys: [ + { + keys: [ "tab" ] + } + { + keys: [ "up arrow" ] + action: @method:scrollDown + } + { + keys: [ "down arrow" ] + action: @method:scrollUp + } + ] + } + } + } +*/ + +exports.getModule = ErcClientModule; + +exports.moduleInfo = { + name : 'ENiGMA Relay Chat Client', + desc : 'Chat with other ENiGMA BBSes', + author : 'Andrew Pamment', +}; + +var MciViewIds = { + chatDisplay : 1, + inputArea : 3, +}; + +function ErcClientModule(options) { + MenuModule.call(this, options); + + var self = this; + this.config = options.menuConfig.config; + this.chatConnection = null; + this.finishedLoading = function() { + async.series( + [ + function validateConfig(callback) { + if(_.isString(self.config.host) && + _.isNumber(self.config.port) && + _.isString(self.config.bbsTag)) + { + callback(null); + } else { + callback(new Error('Configuration is missing required option(s)')); + } + }, + function connectToServer(callback) { + const connectOpts = { + port : self.config.port, + host : self.config.host, + }; + + + var chatMessageView = self.viewControllers.menu.getView(MciViewIds.chatDisplay); + chatMessageView.setText("Connecting to server..."); + chatMessageView.redraw(); + self.viewControllers.menu.switchFocus(MciViewIds.inputArea); + self.chatConnection = net.createConnection(connectOpts.port, connectOpts.host); + + self.chatConnection.on('data', data => { + var chatMessageView = self.viewControllers.menu.getView(MciViewIds.chatDisplay); + + if (data.toString().substring(0, 12) == "ERCHANDSHAKE") { + self.chatConnection.write("ERCMAGIC|" + self.config.bbsTag + "|" + self.client.user.username + "\r\n"); + } else { + chatMessageView.addText(data.toString()); + if (chatMessageView.getLineCount() > 30) { + chatMessageView.deleteLine(0); + chatMessageView.scrollDown(); + } + chatMessageView.redraw(); + self.viewControllers.menu.switchFocus(MciViewIds.inputArea); + } + }); + + self.chatConnection.once('end', () => { + return callback(null); + }); + + self.chatConnection.once('error', err => { + self.client.log.info(`Telnet bridge connection error: ${err.message}`); + }); + } + ], + err => { + if(err) { + self.client.log.warn( { error : err.message }, 'Telnet connection error'); + } + + self.prevMenu(); + } + ); + }; + + + this.menuMethods = { + processInput : function(data, cb) { + let chatInput = self.viewControllers.menu.getView(MciViewIds.inputArea); + let chatData = chatInput.getData(); + if (chatData[0] === '/') { + if (chatData[1] === 'q' || chatInput[1] === 'Q') { + self.chatConnection.end(); + } + } else { + self.chatConnection.write(chatData + "\r\n"); + chatInput.clearText(); + } + }, + scrollUp : function(data, cb) { + let chatInput = self.viewControllers.menu.getView(MciViewIds.inputArea); + let chatMessageView = self.viewControllers.menu.getView(MciViewIds.chatDisplay); + chatMessageView.scrollUp(); + chatMessageView.redraw(); + chatInput.setFocus(true); + }, + scrollDown : function(data, cb) { + let chatInput = self.viewControllers.menu.getView(MciViewIds.inputArea); + let chatMessageView = self.viewControllers.menu.getView(MciViewIds.chatDisplay); + chatMessageView.scrollDown(); + chatMessageView.redraw(); + chatInput.setFocus(true); + } + }; + + +} + +require('util').inherits(ErcClientModule, MenuModule); + +ErcClientModule.prototype.mciReady = function(mciData, cb) { + this.standardMCIReadyHandler(mciData, cb); +}; diff --git a/mods/menu.hjson b/mods/menu.hjson index 5618f111..00e67b72 100644 --- a/mods/menu.hjson +++ b/mods/menu.hjson @@ -1,4 +1,4 @@ -{ +{ /* ENiGMA½ Menu Configuration @@ -29,7 +29,7 @@ // // Another SSH specialization: If the user logs in with a new user - // name (e.g. "new", "apply", ...) they will be directed to the + // name (e.g. "new", "apply", ...) they will be directed to the // application process. // sshConnectedNewUser: { @@ -157,7 +157,7 @@ } } - logoff: { + logoff: { art: LOGOFF desc: Logging Off next: @systemMethod:logoff @@ -264,7 +264,7 @@ action: @systemMethod:prevMenu } ] - } + } } } @@ -361,7 +361,7 @@ action: @systemMethod:prevMenu } ] - } + } } } @@ -375,10 +375,10 @@ status: Feedback to SysOp module: msg_area_post_fse next: [ - { + { acs: AS2 next: fullLoginSequenceLoginArt - } + } { next: newUserInactiveDone } @@ -510,7 +510,7 @@ module: last_callers art: LASTCALL options: { pause: true } - next: fullLoginSequenceWhosOnline + next: fullLoginSequenceWhosOnline } fullLoginSequenceWhosOnline: { desc: Who's Online @@ -644,6 +644,10 @@ value: { command: "K" } action: @menu:mainMenuFeedbackToSysOp } + { + value: { command: "CHAT"} + action: @menu:ercClient + } { value: 1 action: @menu:mainMenu @@ -665,7 +669,7 @@ mainMenuUserStats: { desc: User Stats art: STATUS - options: { pause: true } + options: { pause: true } } mainMenuSystemStats: { desc: System Stats @@ -907,6 +911,58 @@ } } + ercClient: { + art: erc + module: erc_client + config: { + host: 192.168.1.171 + port: 5001 + bbsTag: SUPER + } + + form: { + 0: { + mci: { + MT1: { + width: 79 + height: 21 + mode: preview + autoScroll: true + } + ET3: { + autoScale: false + width: 77 + argName: chattxt + focus: true + submit: true + } + } + + submit: { + *: [ + { + value: { chattxt: null } + action: @method:processInput + } + ] + } + actionKeys: [ + { + keys: [ "tab" ] + } + { + keys: [ "up arrow" ] + action: @method:scrollDown + } + { + keys: [ "down arrow" ] + action: @method:scrollUp + } + ] + } + } + } + /////////////////////////////////////////////////////////////////////// // Doors Menu /////////////////////////////////////////////////////////////////////// @@ -948,12 +1004,12 @@ doorPimpWars: { desc: Playing PimpWars - module: abracadabra + module: abracadabra config: { name: PimpWars dropFileType: DORINFO cmd: /home/nuskooler/DOS/scripts/pimpwars.sh - args: [ + args: [ "{node}", "{dropFile}", "{srvPort}", @@ -966,12 +1022,12 @@ doorDarkLands: { desc: Playing Dark Lands - module: abracadabra + module: abracadabra config: { name: DARKLANDS dropFileType: DOOR cmd: /home/nuskooler/dev/enigma-bbs/doors/darklands/start.sh - args: [ + args: [ "{node}", "{dropFile}", "{srvPort}", @@ -981,7 +1037,7 @@ io: socket } } - + doorLORD: { desc: Playing L.O.R.D. module: abracadabra @@ -1056,7 +1112,7 @@ { value: 1 action: @menu:messageArea - } + } ] } @@ -1244,7 +1300,7 @@ { keys: [ "n", "shift + n" ] action: @method:nextMessage - } + } { keys: [ "r", "shift + r" ] action: @method:replyMessage @@ -1259,7 +1315,7 @@ { keys: [ "?" ] action: @method:viewModeMenuHelp - } + } { keys: [ "down arrow", "up arrow", "page up", "page down" ] action: @method:movementKeyPressed @@ -1295,7 +1351,7 @@ validate: @systemMethod:validateNonEmpty } ET3: { - argName: subject + argName: subject maxLength: 72 submit: true validate: @systemMethod:validateNonEmpty @@ -1395,7 +1451,7 @@ width: 79 height: 4 argName: quote - } + } } submit: { @@ -1552,7 +1608,7 @@ "mci" : { "VM1" : { "items" : [ - "Single Line Text Editing Views", + "Single Line Text Editing Views", "Spinner & Toggle Views", "Mask Edit Views", "Multi Line Text Editor", @@ -1735,7 +1791,7 @@ "form" : { "0" : { "BTMT" : { - "mci" : { + "mci" : { "MT1" : { "width" : 70, "height" : 17, @@ -2019,6 +2075,6 @@ } } } - } + } } -} \ No newline at end of file +} From 8be75d47137b7c32e32ae2a52451f53d45ff8605 Mon Sep 17 00:00:00 2001 From: Bryan Ashby Date: Mon, 27 Jun 2016 22:34:19 -0600 Subject: [PATCH 09/23] Remove commented out deleteLine method --- core/ansi_term.js | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/core/ansi_term.js b/core/ansi_term.js index c7d32016..c0bf37e8 100644 --- a/core/ansi_term.js +++ b/core/ansi_term.js @@ -27,7 +27,6 @@ exports.clearScreen = clearScreen; exports.resetScreen = resetScreen; exports.normal = normal; exports.goHome = goHome; -//exports.deleteLine = deleteLine; exports.disableVT100LineWrapping = disableVT100LineWrapping; exports.setSyncTERMFont = setSyncTERMFont; exports.getSyncTERMFontFromAlias = getSyncTERMFontFromAlias; @@ -420,32 +419,6 @@ function goHome() { return exports.goto(); // no params = home = 1,1 } -// -// Delete line(s) -// This method acts like ESC[ p1 M but should work -// for all terminals via using eraseLine and movement -// -/* -function deleteLine(count) { - count = count || 1; - - console.log(exports.eraseLine) - var seq = exports.eraseLine(2); // 2 = entire line - var i; - for(i = 1; i < count; ++i) { - seq += - '\n' + // down a line - exports.eraseLine(2); // erase it - } - - // now, move back up any we lines we went down - if(count > 1) { - seq += exports.up(count - 1); - } - return seq; -} -*/ - // // Disable auto line wraping @ termWidth // From 478a8a994a3a2a37e1f08129cbbab66e3923ca51 Mon Sep 17 00:00:00 2001 From: Bryan Ashby Date: Mon, 27 Jun 2016 22:35:13 -0600 Subject: [PATCH 10/23] * Various const, let vs var, etc. cleanup * Some DRY * Allow user formatting (e.g. in theme.hjson) * Misc cleanup & formatting --- mods/erc_client.js | 206 +++++++++++++++++++++------------------------ 1 file changed, 97 insertions(+), 109 deletions(-) diff --git a/mods/erc_client.js b/mods/erc_client.js index 61e2881e..3c759159 100644 --- a/mods/erc_client.js +++ b/mods/erc_client.js @@ -1,67 +1,22 @@ /* jslint node: true */ 'use strict'; -var MenuModule = require('../core/menu_module.js').MenuModule; +var MenuModule = require('../core/menu_module.js').MenuModule; + +// deps const async = require('async'); const _ = require('lodash'); const net = require('net'); -const packageJson = require('../package.json'); - /* - Expected configuration block: + Expected configuration block example: - ercClient: { - art: erc - module: erc_client - config: { - host: 192.168.1.171 - port: 5001 - bbsTag: SUPER - } - - form: { - 0: { - mci: { - MT1: { - width: 79 - height: 21 - mode: preview - autoScroll: true - } - ET3: { - autoScale: false - width: 77 - argName: chattxt - focus: true - submit: true - } - } - - submit: { - *: [ - { - value: { chattxt: null } - action: @method:processInput - } - ] - } - actionKeys: [ - { - keys: [ "tab" ] - } - { - keys: [ "up arrow" ] - action: @method:scrollDown - } - { - keys: [ "down arrow" ] - action: @method:scrollUp - } - ] - } - } + config: { + host: 192.168.1.171 + port: 5001 + bbsTag: SOME_TAG } + */ exports.getModule = ErcClientModule; @@ -73,55 +28,81 @@ exports.moduleInfo = { }; var MciViewIds = { - chatDisplay : 1, - inputArea : 3, + ChatDisplay : 1, + InputArea : 3, }; function ErcClientModule(options) { - MenuModule.call(this, options); + MenuModule.call(this, options); - var self = this; - this.config = options.menuConfig.config; - this.chatConnection = null; - this.finishedLoading = function() { - async.series( + const self = this; + this.config = options.menuConfig.config; + + this.chatEntryFormat = this.config.chatEntryFormat || '[{bbsTag}] {userName}: {message}'; + this.systemEntryFormat = this.config.systemEntryFormat || '[*SYSTEM*] {message}'; + + this.finishedLoading = function() { + async.waterfall( [ function validateConfig(callback) { if(_.isString(self.config.host) && _.isNumber(self.config.port) && _.isString(self.config.bbsTag)) { - callback(null); + return callback(null); } else { - callback(new Error('Configuration is missing required option(s)')); + return callback(new Error('Configuration is missing required option(s)')); } }, - function connectToServer(callback) { - const connectOpts = { + function connectToServer(callback) { + const connectOpts = { port : self.config.port, host : self.config.host, }; - - var chatMessageView = self.viewControllers.menu.getView(MciViewIds.chatDisplay); - chatMessageView.setText("Connecting to server..."); + const chatMessageView = self.viewControllers.menu.getView(MciViewIds.ChatDisplay); + + chatMessageView.setText('Connecting to server...'); chatMessageView.redraw(); - self.viewControllers.menu.switchFocus(MciViewIds.inputArea); + + self.viewControllers.menu.switchFocus(MciViewIds.InputArea); + self.chatConnection = net.createConnection(connectOpts.port, connectOpts.host); self.chatConnection.on('data', data => { - var chatMessageView = self.viewControllers.menu.getView(MciViewIds.chatDisplay); + data = data.toString(); - if (data.toString().substring(0, 12) == "ERCHANDSHAKE") { - self.chatConnection.write("ERCMAGIC|" + self.config.bbsTag + "|" + self.client.user.username + "\r\n"); - } else { - chatMessageView.addText(data.toString()); - if (chatMessageView.getLineCount() > 30) { + if(data.startsWith('ERCHANDSHAKE')) { + self.chatConnection.write(`ERCMAGIC|${self.config.bbsTag}|${self.client.user.username}\r\n`); + } else if(data.startsWith('{')) { + try { + data = JSON.parse(data); + } catch(e) { + return self.client.log.warn( { error : e.message }, 'ERC: Error parsing ERC data from server'); + } + + let text; + try { + if(data.userName) { + // user message + text = self.chatEntryFormat.format(data); + } else { + // system message + text = self.systemEntryFormat.format(data); + } + } catch(e) { + return self.client.log.warn( { error : e.message }, 'ERC: chatEntryFormat error'); + } + + chatMessageView.addText(text); + + if(chatMessageView.getLineCount() > 30) { // :TODO: should probably be ChatDisplay.height? chatMessageView.deleteLine(0); chatMessageView.scrollDown(); } + chatMessageView.redraw(); - self.viewControllers.menu.switchFocus(MciViewIds.inputArea); + self.viewControllers.menu.switchFocus(MciViewIds.InputArea); } }); @@ -130,13 +111,13 @@ function ErcClientModule(options) { }); self.chatConnection.once('error', err => { - self.client.log.info(`Telnet bridge connection error: ${err.message}`); + self.client.log.info(`ERC connection error: ${err.message}`); }); - } + } ], err => { if(err) { - self.client.log.warn( { error : err.message }, 'Telnet connection error'); + self.client.log.warn( { error : err.message }, 'ERC error'); } self.prevMenu(); @@ -144,37 +125,44 @@ function ErcClientModule(options) { ); }; + this.scrollHandler = function(keyName) { + const inputAreaView = self.viewControllers.menu.getView(MciViewIds.InputArea); + const chatDisplayView = self.viewControllers.menu.getView(MciViewIds.ChatDisplay); - this.menuMethods = { - processInput : function(data, cb) { - let chatInput = self.viewControllers.menu.getView(MciViewIds.inputArea); - let chatData = chatInput.getData(); - if (chatData[0] === '/') { - if (chatData[1] === 'q' || chatInput[1] === 'Q') { - self.chatConnection.end(); - } - } else { - self.chatConnection.write(chatData + "\r\n"); - chatInput.clearText(); - } - }, - scrollUp : function(data, cb) { - let chatInput = self.viewControllers.menu.getView(MciViewIds.inputArea); - let chatMessageView = self.viewControllers.menu.getView(MciViewIds.chatDisplay); - chatMessageView.scrollUp(); - chatMessageView.redraw(); - chatInput.setFocus(true); - }, - scrollDown : function(data, cb) { - let chatInput = self.viewControllers.menu.getView(MciViewIds.inputArea); - let chatMessageView = self.viewControllers.menu.getView(MciViewIds.chatDisplay); - chatMessageView.scrollDown(); - chatMessageView.redraw(); - chatInput.setFocus(true); + if('up arrow' === keyName) { + chatDisplayView.scrollUp(); + } else { + chatDisplayView.scrollDown(); + } + + chatDisplayView.redraw(); + inputAreaView.setFocus(true); + }; + + + this.menuMethods = { + inputAreaSubmit : function() { + const inputAreaView = self.viewControllers.menu.getView(MciViewIds.InputArea); + const inputData = inputAreaView.getData(); + + if('/quit' === inputData.toLowerCase()) { + self.chatConnection.end(); + } else { + try { + self.chatConnection.write(`${inputData}\r\n`); + } catch(e) { + self.client.log.warn( { error : e.message }, 'ERC error'); + } + inputAreaView.clearText(); } - }; - - + }, + scrollUp : function(formData) { + self.scrollHandler(formData.key.name); + }, + scrollDown : function(formData) { + self.scrollHandler(formData.key.name); + } + }; } require('util').inherits(ErcClientModule, MenuModule); From e82ec724e15bd989f8a4b97a09eb43c8ba46f671 Mon Sep 17 00:00:00 2001 From: Bryan Ashby Date: Mon, 27 Jun 2016 23:01:31 -0600 Subject: [PATCH 11/23] * Fix some issues with autoScroll * Misc cleanup --- core/multi_line_edit_text_view.js | 34 +++++++++++++++---------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/core/multi_line_edit_text_view.js b/core/multi_line_edit_text_view.js index c06a1f2c..c9c5ceb2 100644 --- a/core/multi_line_edit_text_view.js +++ b/core/multi_line_edit_text_view.js @@ -1,15 +1,14 @@ /* jslint node: true */ 'use strict'; -var View = require('./view.js').View; -var miscUtil = require('./misc_util.js'); -var strUtil = require('./string_util.js'); -var ansi = require('./ansi_term.js'); -var colorCodes = require('./color_codes.js'); -var wordWrapText = require('./word_wrap.js').wordWrapText; +const View = require('./view.js').View; +const strUtil = require('./string_util.js'); +const ansi = require('./ansi_term.js'); +const colorCodes = require('./color_codes.js'); +const wordWrapText = require('./word_wrap.js').wordWrapText; -var assert = require('assert'); -var _ = require('lodash'); +const assert = require('assert'); +const _ = require('lodash'); // :TODO: Determine CTRL-* keys for various things // See http://www.bbsdocumentary.com/library/PROGRAMS/GRAPHICS/ANSI/bansi.txt @@ -114,10 +113,10 @@ function MultiLineEditTextView(options) { this.topVisibleIndex = 0; this.mode = options.mode || 'edit'; // edit | preview | read-only - if (this.mode == 'edit') { - this.autoScroll = options.autoScroll || 'true'; + if ('preview' === this.mode) { + this.autoScroll = options.autoScroll || true; } else { - this.autoScroll = options.autoScroll || 'false'; + this.autoScroll = options.autoScroll || false; } // // cursorPos represents zero-based row, col positions @@ -1012,7 +1011,7 @@ MultiLineEditTextView.prototype.setText = function(text) { MultiLineEditTextView.prototype.addText = function(text) { this.insertRawText(text); - if(this.autoScroll) { + if(this.isEditMode() || this.autoScroll) { this.cursorEndOfDocument(); } else { this.cursorStartOfDocument(); @@ -1025,7 +1024,8 @@ MultiLineEditTextView.prototype.getData = function() { MultiLineEditTextView.prototype.setPropertyValue = function(propName, value) { switch(propName) { - case 'mode' : this.mode = value; break; + case 'mode' : this.mode = value; break; + case 'autoScroll' : this.autoScroll = value; break; } MultiLineEditTextView.super_.prototype.setPropertyValue.call(this, propName, value); @@ -1075,19 +1075,19 @@ MultiLineEditTextView.prototype.onKeyPress = function(ch, key) { MultiLineEditTextView.prototype.scrollUp = function() { this.scrollDocumentUp(); -} +}; MultiLineEditTextView.prototype.scrollDown = function() { this.scrollDocumentDown(); -} +}; MultiLineEditTextView.prototype.deleteLine = function(line) { this.textLines.splice(line, 1); -} +}; MultiLineEditTextView.prototype.getLineCount = function() { return this.textLines.length; -} +}; MultiLineEditTextView.prototype.getTextEditMode = function() { return this.overtypeMode ? 'overtype' : 'insert'; From dc8a06331bc52707bef3245b330000f1cd9eb1ab Mon Sep 17 00:00:00 2001 From: Bryan Ashby Date: Mon, 27 Jun 2016 23:01:57 -0600 Subject: [PATCH 12/23] Fix connection issue --- mods/erc_client.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mods/erc_client.js b/mods/erc_client.js index 3c759159..cb30f2d0 100644 --- a/mods/erc_client.js +++ b/mods/erc_client.js @@ -67,6 +67,7 @@ function ErcClientModule(options) { self.viewControllers.menu.switchFocus(MciViewIds.InputArea); + // :TODO: Track actual client->enig connection for optional prevMenu @ final CB self.chatConnection = net.createConnection(connectOpts.port, connectOpts.host); self.chatConnection.on('data', data => { @@ -112,6 +113,7 @@ function ErcClientModule(options) { self.chatConnection.once('error', err => { self.client.log.info(`ERC connection error: ${err.message}`); + return callback(new Error('Failed connecting to ERC server!')); }); } ], From a3b4568a76ca559e70e615b247e9d816facec149 Mon Sep 17 00:00:00 2001 From: Bryan Ashby Date: Mon, 27 Jun 2016 23:07:59 -0600 Subject: [PATCH 13/23] Fix issue parsing args in sgr() introduced recently --- core/ansi_term.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/ansi_term.js b/core/ansi_term.js index c0bf37e8..869768b9 100644 --- a/core/ansi_term.js +++ b/core/ansi_term.js @@ -354,13 +354,14 @@ function sgr() { let result = []; const args = Array.isArray(arguments[0]) ? arguments[0] : arguments; - args.forEach(arg => { + for(let i = 0; i < args.length; ++i) { + const arg = args[i]; if(_.isString(arg) && arg in SGRValues) { result.push(SGRValues[arg]); } else if(_.isNumber(arg)) { result.push(arg); } - }); + } return `${ESC_CSI}${result.join(';')}m`; } From 5fc458e65ee8075edb1b339253f541ba230b59f8 Mon Sep 17 00:00:00 2001 From: Bryan Ashby Date: Tue, 28 Jun 2016 22:38:29 -0600 Subject: [PATCH 14/23] Onelinerz WIP, telnet bridge, ERC update --- mods/menu.hjson | 96 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 87 insertions(+), 9 deletions(-) diff --git a/mods/menu.hjson b/mods/menu.hjson index 00e67b72..f7637274 100644 --- a/mods/menu.hjson +++ b/mods/menu.hjson @@ -75,7 +75,8 @@ login: { art: USERLOG - next: fullLoginSequenceLoginArt + //next: fullLoginSequenceLoginArt + next: loginOnelinerz config: { tooNodeMenu: loginAttemptTooNode } @@ -112,6 +113,67 @@ } } + loginOnelinerz: { + desc: Viewing Onelinerz + module: onelinerz + next: fullLoginSequenceLoginArt + options: { + cls: true + } + config: { + art: { + entries: ONELINER + add: ONEADD + } + } + form: { + 0: { + mci: { + VM1: { + focus: false + } + TM2: { + argName: wantAdd + items: [ "hell yeah!", "nah" ] + submit: true + focus: true + } + } + submit: { + *: [ + { + value: { wantAdd: 0 } + action: @method:addPromptYes + } + { + value: { wantAdd: null } + action: @systemMethod:nextMenu + } + ] + } + }, + 1: { + mci: { + ET1: { + focus: true + // :TODO: fix this length + maxLength: 30 + width: 30 + argName: oneliner + } + TL2: { + width: 20 + } + TM3: { + argName: wantAdd + items: [ "add", "cancel" ] + submit: true + } + } + } + } + } + loginAttemptTooNode: { art: TOONODE options: { @@ -915,10 +977,10 @@ art: erc module: erc_client config: { - host: 192.168.1.171 - port: 5001 - bbsTag: SUPER - } + host: localhost + port: 5001 + bbsTag: CHANGEME + } form: { 0: { @@ -932,7 +994,7 @@ ET3: { autoScale: false width: 77 - argName: chattxt + argName: inputArea focus: true submit: true } @@ -941,9 +1003,9 @@ submit: { *: [ { - value: { chattxt: null } - action: @method:processInput - } + value: { inputArea: null } + action: @method:inputAreaSubmit + } ] } actionKeys: [ @@ -999,6 +1061,10 @@ value: { command: "DP" } action: @menu:doorParty } + { + value: { command: "HL" } + action: @menu:telnetBridgeHappyLand + } ] } @@ -1076,6 +1142,18 @@ bbsTag: XX } } + + telnetBridgeHappyLand: { + desc: Connected to HappyLand BBS + module: telnet_bridge + config: { + host: andrew.homeunix.org + port: 2023 + //host: agency.bbs.geek.nz + //port: 23 + } + } + /////////////////////////////////////////////////////////////////////// // Message Area Menu /////////////////////////////////////////////////////////////////////// From e22f0d37ceaf05208225475e5d9dff18dcc88318 Mon Sep 17 00:00:00 2001 From: Bryan Ashby Date: Tue, 28 Jun 2016 22:41:50 -0600 Subject: [PATCH 15/23] Onelinerz WIP --- mods/onelinerz.js | 300 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 300 insertions(+) create mode 100644 mods/onelinerz.js diff --git a/mods/onelinerz.js b/mods/onelinerz.js new file mode 100644 index 00000000..8399afa4 --- /dev/null +++ b/mods/onelinerz.js @@ -0,0 +1,300 @@ +/* jslint node: true */ +'use strict'; + +// ENiGMA½ +const MenuModule = require('../core/menu_module.js').MenuModule; +const getModDatabasePath = require('../core/database.js').getModDatabasePath; +const ViewController = require('../core/view_controller.js').ViewController; +const theme = require('../core/theme.js'); +const ansi = require('../core/ansi_term.js'); + +// deps +const sqlite3 = require('sqlite3'); +const async = require('async'); +const _ = require('lodash'); + +exports.moduleInfo = { + name : 'Onelinerz', + desc : 'Standard local onelinerz', + author : 'NuSkooler', + packageName : 'codes.l33t.enigma.onelinerz', +}; + +exports.getModule = OnelinerzModule; + +const MciCodeIds = { + ViewForm : { + Entries : 1, + AddPrompt : 2, + }, + AddForm : { + + } +}; + +const FormIds = { + View : 0, + Add : 1, +}; + +function OnelinerzModule(options) { + MenuModule.call(this, options); + + const self = this; + const config = this.menuConfig.config; + + this.initSequence = function() { + async.series( + [ + function beforeDisplayArt(callback) { + self.beforeArt(callback); + }, + function display(callback) { + self.displayViewScreen(false, callback); + } + ], + err => { + if(err) { + // :TODO: Handle me -- initSequence() should really take a completion callback + } + self.finishedLoading(); + } + ); + }; + + this.displayViewScreen = function(clearScreen, cb) { + async.waterfall( + [ + function clearAndDisplayArt(callback) { + if(self.viewControllers.add) { + self.viewControllers.add.setFocus(false); + } + + if(clearScreen) { + self.client.term.rawWrite(ansi.resetScreen()); + } + + theme.displayThemedAsset( + config.art.entries, + self.client, + { font : self.menuConfig.font, trailingLF : false }, + (err, artData) => { + return callback(err, artData); + } + ); + }, + function initOrRedrawViewController(artData, callback) { + if(_.isUndefined(self.viewControllers.add)) { + const vc = self.addViewController( + 'view', + new ViewController( { client : self.client, formId : FormIds.View } ) + ); + + const loadOpts = { + callingMenu : self, + mciMap : artData.mciMap, + formId : FormIds.View, + }; + + return vc.loadFromMenuConfig(loadOpts, callback); + } else { + self.viewControllers.view.setFocus(true); + self.viewControllers.view.getView(MciCodeIds.ViewForm.AddPrompt).redraw(); + return callback(null); + } + }, + function fetchEntries(callback) { + const entriesView = self.viewControllers.view.getView(MciCodeIds.ViewForm.Entries); + const limit = entriesView.dimens.height; + let entries = []; + + self.db.each( + `SELECT user_id, user_name, oneliner, timestamp + FROM onelinerz + LIMIT ${limit};`, + (err, row) => { + if(!err) { + entries.push(row); + } + }, + err => { + return callback(err, entriesView, entries); + } + ); + }, + function populateEntries(entriesView, entries, callback) { + const listFormat = config.listFormat || '{username}: {oneliner}'; + + // :TODO: remove meh: + entries = [ + { user_id : 1, user_name : 'NuSkooler', oneliner : 'Boojahhhh!!!', timestamp : '2016-06-04' } + ] + + entriesView.setItems(entries.map( e => { + return listFormat.format( { + userId : e.user_id, + username : e.user_name, + oneliner : e.oneliner, + ts : e.timestamp, + } ); + })); + + entriesView.focusItems = entriesView.items; // :TODO: this is a hack + entriesView.redraw(); + + return callback(null); + } + ], + cb + ); + }; + + this.displayAddScreen = function(cb) { + async.waterfall( + [ + function clearAndDisplayArt(callback) { + self.viewControllers.view.setFocus(false); + self.client.term.rawWrite(ansi.resetScreen()); + + theme.displayThemedAsset( + config.art.add, + self.client, + { font : self.menuConfig.font }, + (err, artData) => { + return callback(err, artData); + } + ); + }, + function initOrRedrawViewController(artData, callback) { + if(_.isUndefined(self.viewControllers.add)) { + const vc = self.addViewController( + 'add', + new ViewController( { client : self.client, formId : FormIds.Add } ) + ); + + const loadOpts = { + callingMenu : self, + mciMap : artData.mciMap, + formId : FormIds.Add, + }; + + return vc.loadFromMenuConfig(loadOpts, callback); + } else { + self.viewControllers.add.redrawAll(); + return callback(null); + } + } + ], + cb + ); + }; + + this.menuMethods = { + addPromptYes : function(formData, extraArgs) { + self.displayAddScreen(err => { + + }); + } + }; + + this.initDatabase = function(cb) { + async.series( + [ + function openDatabase(callback) { + self.db = new sqlite3.Database( + getModDatabasePath(exports.moduleInfo), + callback + ); + }, + function createTables(callback) { + self.db.serialize( () => { + self.db.run( + `CREATE TABLE IF NOT EXISTS onelinerz ( + id INTEGER PRIMARY KEY, + user_id INTEGER_NOT NULL, + user_name VARCHAR NOT NULL, + oneliner VARCHAR NOT NULL, + timestamp DATETIME NOT NULL + )` + ); + }); + callback(null); + } + ], + cb + ); + }; +} + +require('util').inherits(OnelinerzModule, MenuModule); + +OnelinerzModule.prototype.beforeArt = function(cb) { + OnelinerzModule.super_.prototype.beforeArt.call(this, err => { + return err ? cb(err) : this.initDatabase(cb); + }); +}; + +/* +OnelinerzModule.prototype.mciReady = function(mciData, cb) { + const self = this; + const vc = self.addViewController( + 'entries', + new ViewController( { client : self.client, formId : FormIds.AddPrompt } ) + ); + + let entries = []; + let entriesView; + + async.series( + [ + function loadFromConfig(callback) { + const loadOpts = { + callingMenu : self, + mciMap : mciData.entries.mciMap, + }; + + vc.loadFromMenuConfig(loadOpts, callback); + }, + function fetchEntries(callback) { + entriesView = vc.getView(MciCodeIds.ViewScreen.Entries); + const limit = entriesView.dimens.height; + + self.db.each( + `SELECT user_id, user_name, oneliner, timestamp + FROM onelinerz + LIMIT ${limit};`, + (err, row) => { + if(!err) { + entries.push(row); + } + }, + callback + ); + }, + function populateEntries(callback) { + const listFormat = self.menuConfig.config.listFormat || '{username}: {oneliner}'; + + // :TODO: remove meh: + entries = [ + { user_id : 1, user_name : 'NuSkooler', oneliner : 'Boojahhhh!!!', timestamp : '2016-06-04' } + ] + + entriesView.setItems(entries.map( e => { + return listFormat.format( { + userId : e.user_id, + username : e.user_name, + oneliner : e.oneliner, + ts : e.timestamp, + } ); + })); + + entriesView.focusItems = entriesView.items; // :TODO: this is a hack + entriesView.redraw(); + + return callback(null); + } + ], + cb + ); +}; +*/ From 8567692238580aa0fa52b32790caab0a78fb6f29 Mon Sep 17 00:00:00 2001 From: Bryan Ashby Date: Tue, 28 Jun 2016 22:43:05 -0600 Subject: [PATCH 16/23] Temp onelinerz art --- mods/art/ONEADD.ANS | Bin 0 -> 237 bytes mods/art/ONELINER.ANS | Bin 0 -> 4268 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 mods/art/ONEADD.ANS create mode 100644 mods/art/ONELINER.ANS diff --git a/mods/art/ONEADD.ANS b/mods/art/ONEADD.ANS new file mode 100644 index 0000000000000000000000000000000000000000..046e6855f6e7a03c43faf17f7b06bc63c4e55b62 GIT binary patch literal 237 zcmb1+Hn27^ur@Z&<$?j}XbWe@loW+Th5WqKoXot`A}cPCn5t`tp>(uiE{Ns=iWuaA zL<5Ra%Q92Tp-Mu0jDRvGCeErMzQ#bs)+VO85T-#cmsGH0sI#kqfdch_k%6I^ftis- cH3I_!BVzyqKad6jVNWMtg)nzV2oFvI0PZO%kpKVy literal 0 HcmV?d00001 diff --git a/mods/art/ONELINER.ANS b/mods/art/ONELINER.ANS new file mode 100644 index 0000000000000000000000000000000000000000..cdaa35005bbc8a0f714f303f340c84fe3d753956 GIT binary patch literal 4268 zcmb`KKaV3t5XJY>jgW}I$t0J1cKk=k0Xj|y5uAhs9DyT(lR&xw37y2(^7-quz4J|0 z{NAhTp7Cyq1ADr=`qisfRXy$1aedgV55vQFSsnYs=HW2x#|zIf*R77jcAWInr-gr6 z?Z>HsHc%+;BwHM=Ax^2_$>Q2r_jkSsO8lf>6!{L8_z33ch>ZnX^tq+- zWGg#b4yB%(mMf+@}@tjcCva~r+lRLh@(urMc8(G8v0s$(UaO5Nl#w?bAX;?kMs z1zS{w0W>lV{T$k)@V40Qh@$>n(HqxR7$BhALFH71P}Nx(`nJwRsjIo7zV*#sYOYeb zc2i}e=r1Z75)2mY5NUQGJ|MMZ-Etw5FUJ|Mb;L`QeFFI$4u!SkV~Gde47H+I#6iR+ z$qm?uuw-*(!?L_nJ{o)+Xp~i6s~0O7 zvrt9M8gSfFb5@~N%F6=D?nIlXxqp+W+yMga(|U?j)BV^}ubMq(Imz1?_L zrDIR|w?>b1!)LTxA1fOPI^KOuHxfj+=QNnHLBxPsUtP^7kgG*nF&QB4Mk9@`F zG9`%u%xFo1qgK8|$Ahfr5JHIx-KZ@BFxlpE3bJffI*lw5=fM=`Wt@$cBFun{+E&J0 z2M*IC*P*bo!h>5#)V#z2vA}g)v*TPBcOI#;iXY+Jc)g)dZo(vEtFdx=&K)|?!VzcH zRa!68nrG>t&M+%2dPG#}lllyPBuBBMka@0--@Hv8x1bp50IM8*|K}3U3)@$5t+iF= zXi+WtXWl8n-8T3ua9NM5;}(C!keheDK4Szs*Gkb_hd3(hGuJN$C$Ymyxg4_nF_h>o zMF9^&7N8dP%=CTux<%CeNr-WV$XKI*4@6Vhjj!Lo>l0~b)?f!WRqVVycqi)yjClx%xtVn3-TEwHCYZ$(8bvTWR#1BD`JZ=(yKM9#Dpk5X64dbg^%( z=v`cOYZ!HvvjH{gAsdB|kmV^c*I?SoQ`klmxiI Date: Tue, 28 Jun 2016 23:06:35 -0600 Subject: [PATCH 17/23] Add callback to beforeArt --- core/menu_module.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/menu_module.js b/core/menu_module.js index e454ff58..03f7e2d6 100644 --- a/core/menu_module.js +++ b/core/menu_module.js @@ -46,8 +46,7 @@ function MenuModule(options) { async.series( [ function beforeDisplayArt(callback) { - self.beforeArt(); - callback(null); + self.beforeArt(callback); }, function displayMenuArt(callback) { if(_.isString(self.menuConfig.art)) { @@ -247,7 +246,7 @@ MenuModule.prototype.leave = function() { this.detachViewControllers(); }; -MenuModule.prototype.beforeArt = function() { +MenuModule.prototype.beforeArt = function(cb) { if(this.cls) { this.client.term.write(ansi.resetScreen()); } @@ -255,6 +254,8 @@ MenuModule.prototype.beforeArt = function() { if(_.isNumber(this.menuConfig.options.baudRate)) { this.client.term.write(ansi.setEmulatedBaudRate(this.menuConfig.options.baudRate)); } + + return cb(null); }; MenuModule.prototype.mciReady = function(mciData, cb) { From 9e6af7f19b2a18bbf24c7eb08101c006239e060f Mon Sep 17 00:00:00 2001 From: Bryan Ashby Date: Tue, 28 Jun 2016 23:07:15 -0600 Subject: [PATCH 18/23] Use new beforeArt --- core/fse.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/fse.js b/core/fse.js index 7592a11e..b1455e5d 100644 --- a/core/fse.js +++ b/core/fse.js @@ -317,7 +317,7 @@ function FullScreenEditorModule(options) { // in NetRunner: self.client.term.rawWrite(ansi.reset() + ansi.deleteLine(3)); - self.client.term.rawWrite(ansi.reset() + ansi.eraseLine(2)) + self.client.term.rawWrite(ansi.reset() + ansi.eraseLine(2)); } callback(null); }, @@ -424,8 +424,7 @@ function FullScreenEditorModule(options) { async.series( [ function beforeDisplayArt(callback) { - self.beforeArt(); - callback(null); + self.beforeArt(callback); }, function displayHeaderAndBodyArt(callback) { assert(_.isString(art.header)); From d5c806031f6c7f85362c6336bba2f1d40a929070 Mon Sep 17 00:00:00 2001 From: Bryan Ashby Date: Tue, 28 Jun 2016 23:07:33 -0600 Subject: [PATCH 19/23] Use new beforeArt --- core/standard_menu.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/standard_menu.js b/core/standard_menu.js index 9848acb5..4d4b8819 100644 --- a/core/standard_menu.js +++ b/core/standard_menu.js @@ -22,8 +22,8 @@ StandardMenuModule.prototype.enter = function() { StandardMenuModule.super_.prototype.enter.call(this); }; -StandardMenuModule.prototype.beforeArt = function() { - StandardMenuModule.super_.prototype.beforeArt.call(this); +StandardMenuModule.prototype.beforeArt = function(cb) { + StandardMenuModule.super_.prototype.beforeArt.call(this, cb); }; StandardMenuModule.prototype.mciReady = function(mciData, cb) { From 9bb08c3d54bc70f7d790f02a0ebddc8b14531b6a Mon Sep 17 00:00:00 2001 From: Bryan Ashby Date: Tue, 28 Jun 2016 23:53:30 -0600 Subject: [PATCH 20/23] Onelinerz mostly functional, but still WIP. Need to trim DB, display proper count, and add preview. Checking in for testing. --- mods/menu.hjson | 28 ++++++--- mods/onelinerz.js | 142 +++++++++++++++++++++------------------------- 2 files changed, 87 insertions(+), 83 deletions(-) diff --git a/mods/menu.hjson b/mods/menu.hjson index f7637274..eaed5f0a 100644 --- a/mods/menu.hjson +++ b/mods/menu.hjson @@ -75,8 +75,8 @@ login: { art: USERLOG - //next: fullLoginSequenceLoginArt - next: loginOnelinerz + next: fullLoginSequenceLoginArt + //next: loginOnelinerz config: { tooNodeMenu: loginAttemptTooNode } @@ -131,9 +131,10 @@ mci: { VM1: { focus: false + height: 10 } TM2: { - argName: wantAdd + argName: addOrExit items: [ "hell yeah!", "nah" ] submit: true focus: true @@ -142,11 +143,11 @@ submit: { *: [ { - value: { wantAdd: 0 } - action: @method:addPromptYes + value: { addOrExit: 0 } + action: @method:viewAddScreen } { - value: { wantAdd: null } + value: { addOrExit: null } action: @systemMethod:nextMenu } ] @@ -165,11 +166,24 @@ width: 20 } TM3: { - argName: wantAdd + argName: addOrCancel items: [ "add", "cancel" ] submit: true } } + + submit: { + *: [ + { + value: { addOrCancel: 0 } + action: @method:addEntry + } + { + value: { addOrCancel: 1 } + action: @method:cancelAdd + } + ] + } } } } diff --git a/mods/onelinerz.js b/mods/onelinerz.js index 8399afa4..a6036c61 100644 --- a/mods/onelinerz.js +++ b/mods/onelinerz.js @@ -12,6 +12,7 @@ const ansi = require('../core/ansi_term.js'); const sqlite3 = require('sqlite3'); const async = require('async'); const _ = require('lodash'); +const moment = require('moment'); exports.moduleInfo = { name : 'Onelinerz', @@ -28,7 +29,9 @@ const MciCodeIds = { AddPrompt : 2, }, AddForm : { - + NewEntry : 1, + EntryPreview : 2, + AddPrompt : 3, } }; @@ -114,6 +117,7 @@ function OnelinerzModule(options) { LIMIT ${limit};`, (err, row) => { if(!err) { + row.timestamp = moment(row.timestamp); // convert -> moment entries.push(row); } }, @@ -126,16 +130,16 @@ function OnelinerzModule(options) { const listFormat = config.listFormat || '{username}: {oneliner}'; // :TODO: remove meh: - entries = [ - { user_id : 1, user_name : 'NuSkooler', oneliner : 'Boojahhhh!!!', timestamp : '2016-06-04' } - ] + //entries = [ + // { user_id : 1, user_name : 'NuSkooler', oneliner : 'Boojahhhh!!!', timestamp : '2016-06-04' } + //] entriesView.setItems(entries.map( e => { return listFormat.format( { userId : e.user_id, username : e.user_name, oneliner : e.oneliner, - ts : e.timestamp, + ts : e.timestamp.toString(), // :TODO: allow custom TS formatting - see e.g. last_callers.js } ); })); @@ -145,7 +149,11 @@ function OnelinerzModule(options) { return callback(null); } ], - cb + err => { + if(cb) { + return cb(err); + } + } ); }; @@ -180,20 +188,52 @@ function OnelinerzModule(options) { return vc.loadFromMenuConfig(loadOpts, callback); } else { + self.viewControllers.add.setFocus(true); self.viewControllers.add.redrawAll(); + self.viewControllers.add.switchFocus(MciCodeIds.AddForm.NewEntry); return callback(null); } } ], - cb + err => { + if(cb) { + return cb(err); + } + } ); }; - this.menuMethods = { - addPromptYes : function(formData, extraArgs) { - self.displayAddScreen(err => { + this.clearAddForm = function() { + const newEntryView = self.viewControllers.add.getView(MciCodeIds.AddForm.NewEntry); + const previewView = self.viewControllers.add.getView(MciCodeIds.AddForm.EntryPreview); - }); + newEntryView.setText(''); + previewView.setText(''); + }; + + this.menuMethods = { + viewAddScreen : function() { + self.displayAddScreen(); + }, + + addEntry : function(formData) { + if(_.isString(formData.value.oneliner) && formData.value.oneliner.length > 0) { + const oneliner = formData.value.oneliner.trim(); // remove any trailing ws + + self.storeNewOneliner(oneliner, err => { + self.clearAddForm(); + self.displayViewScreen(true); // true=cls + }); + + } else { + // empty message - treat as if cancel was hit + self.displayViewScreen(true); // true=cls + } + }, + + cancelAdd : function() { + self.clearAddForm(); + self.displayViewScreen(true); // true=cls } }; @@ -224,6 +264,21 @@ function OnelinerzModule(options) { cb ); }; + + this.storeNewOneliner = function(oneliner, cb) { + const ts = moment().format('YYYY-MM-DDTHH:mm:ss.SSSZ'); + + // :TODO: Keep max of N (e.g. 25) & change retrieval to show most recent N (height) + + self.db.run( + `INSERT INTO onelinerz (user_id, user_name, oneliner, timestamp) + VALUES (?, ?, ?, ?);`, + [ self.client.user.userId, self.client.user.username, oneliner, ts ], + err => { + return cb(err); + } + ); + }; } require('util').inherits(OnelinerzModule, MenuModule); @@ -233,68 +288,3 @@ OnelinerzModule.prototype.beforeArt = function(cb) { return err ? cb(err) : this.initDatabase(cb); }); }; - -/* -OnelinerzModule.prototype.mciReady = function(mciData, cb) { - const self = this; - const vc = self.addViewController( - 'entries', - new ViewController( { client : self.client, formId : FormIds.AddPrompt } ) - ); - - let entries = []; - let entriesView; - - async.series( - [ - function loadFromConfig(callback) { - const loadOpts = { - callingMenu : self, - mciMap : mciData.entries.mciMap, - }; - - vc.loadFromMenuConfig(loadOpts, callback); - }, - function fetchEntries(callback) { - entriesView = vc.getView(MciCodeIds.ViewScreen.Entries); - const limit = entriesView.dimens.height; - - self.db.each( - `SELECT user_id, user_name, oneliner, timestamp - FROM onelinerz - LIMIT ${limit};`, - (err, row) => { - if(!err) { - entries.push(row); - } - }, - callback - ); - }, - function populateEntries(callback) { - const listFormat = self.menuConfig.config.listFormat || '{username}: {oneliner}'; - - // :TODO: remove meh: - entries = [ - { user_id : 1, user_name : 'NuSkooler', oneliner : 'Boojahhhh!!!', timestamp : '2016-06-04' } - ] - - entriesView.setItems(entries.map( e => { - return listFormat.format( { - userId : e.user_id, - username : e.user_name, - oneliner : e.oneliner, - ts : e.timestamp, - } ); - })); - - entriesView.focusItems = entriesView.items; // :TODO: this is a hack - entriesView.redraw(); - - return callback(null); - } - ], - cb - ); -}; -*/ From 96348d5ed84bf4dc0a8e00c615eca0d2560a5809 Mon Sep 17 00:00:00 2001 From: Bryan Ashby Date: Wed, 29 Jun 2016 23:23:06 -0600 Subject: [PATCH 21/23] * Onelinerz nearly complete - only really need preview MCI implemented * Added luciano_blocktronics styled onelinerz - need same style for add oneliner still --- mods/menu.hjson | 20 ++++-- mods/onelinerz.js | 61 ++++++++++++------ mods/themes/luciano_blocktronics/ONELINER.ANS | Bin 0 -> 1819 bytes mods/themes/luciano_blocktronics/theme.hjson | 29 +++++++++ 4 files changed, 85 insertions(+), 25 deletions(-) create mode 100644 mods/themes/luciano_blocktronics/ONELINER.ANS diff --git a/mods/menu.hjson b/mods/menu.hjson index eaed5f0a..2356a937 100644 --- a/mods/menu.hjson +++ b/mods/menu.hjson @@ -135,7 +135,7 @@ } TM2: { argName: addOrExit - items: [ "hell yeah!", "nah" ] + items: [ "yeah!", "nah" ] submit: true focus: true } @@ -152,18 +152,22 @@ } ] } + actionKeys: [ + { + keys: [ "escape" ] + action: @systemMethod:nextMenu + } + ] }, 1: { mci: { ET1: { focus: true - // :TODO: fix this length - maxLength: 30 - width: 30 + maxLength: 70 argName: oneliner } TL2: { - width: 20 + width: 60 } TM3: { argName: addOrCancel @@ -184,6 +188,12 @@ } ] } + actionKeys: [ + { + keys: [ "escape" ] + action: @method:cancelAdd + } + ] } } } diff --git a/mods/onelinerz.js b/mods/onelinerz.js index a6036c61..74d912cf 100644 --- a/mods/onelinerz.js +++ b/mods/onelinerz.js @@ -112,9 +112,14 @@ function OnelinerzModule(options) { let entries = []; self.db.each( - `SELECT user_id, user_name, oneliner, timestamp - FROM onelinerz - LIMIT ${limit};`, + `SELECT * + FROM ( + SELECT * + FROM onelinerz + ORDER BY timestamp DESC + LIMIT ${limit} + ) + ORDER BY timestamp ASC;`, (err, row) => { if(!err) { row.timestamp = moment(row.timestamp); // convert -> moment @@ -127,19 +132,15 @@ function OnelinerzModule(options) { ); }, function populateEntries(entriesView, entries, callback) { - const listFormat = config.listFormat || '{username}: {oneliner}'; - - // :TODO: remove meh: - //entries = [ - // { user_id : 1, user_name : 'NuSkooler', oneliner : 'Boojahhhh!!!', timestamp : '2016-06-04' } - //] + const listFormat = config.listFormat || '{username}@{ts}: {oneliner}';// :TODO: should be userName to be consistent + const tsFormat = config.timestampFormat || 'ddd h:mma'; entriesView.setItems(entries.map( e => { return listFormat.format( { userId : e.user_id, username : e.user_name, oneliner : e.oneliner, - ts : e.timestamp.toString(), // :TODO: allow custom TS formatting - see e.g. last_callers.js + ts : e.timestamp.format(tsFormat), } ); })); @@ -221,6 +222,10 @@ function OnelinerzModule(options) { const oneliner = formData.value.oneliner.trim(); // remove any trailing ws self.storeNewOneliner(oneliner, err => { + if(err) { + self.client.log.warn( { error : err.message }, 'Failed saving oneliner'); + } + self.clearAddForm(); self.displayViewScreen(true); // true=cls }); @@ -268,16 +273,32 @@ function OnelinerzModule(options) { this.storeNewOneliner = function(oneliner, cb) { const ts = moment().format('YYYY-MM-DDTHH:mm:ss.SSSZ'); - // :TODO: Keep max of N (e.g. 25) & change retrieval to show most recent N (height) - - self.db.run( - `INSERT INTO onelinerz (user_id, user_name, oneliner, timestamp) - VALUES (?, ?, ?, ?);`, - [ self.client.user.userId, self.client.user.username, oneliner, ts ], - err => { - return cb(err); - } - ); + async.series( + [ + function addRec(callback) { + self.db.run( + `INSERT INTO onelinerz (user_id, user_name, oneliner, timestamp) + VALUES (?, ?, ?, ?);`, + [ self.client.user.userId, self.client.user.username, oneliner, ts ], + callback + ); + }, + function removeOld(callback) { + // keep 25 max most recent items - remove the older ones + self.db.run( + `DELETE FROM onelinerz + WHERE id IN ( + SELECT id + FROM onelinerz + ORDER BY id DESC + LIMIT -1 OFFSET 25 + );`, + callback + ); + } + ], + cb + ); }; } diff --git a/mods/themes/luciano_blocktronics/ONELINER.ANS b/mods/themes/luciano_blocktronics/ONELINER.ANS new file mode 100644 index 0000000000000000000000000000000000000000..b494dc7b085ed7cad5cac3b51d6d64aac9b517fb GIT binary patch literal 1819 zcmb`IL2uJQ5QWnVhg>;lK5k+OFzxTC#kOZPm*$ zxl@`FewZWXwpS>wdO`^D9f<~xz$8;IfV^r*n%1KQ6$Bx+gF&T8o(LvjBa>fA z!$yTnF{UXp$I&C?i>t@*<`ec#a>lK>7#PXNU=vQ&wjX#2i8T;`k*7FQaVJ_+(er`k(fqYI%GtX^+cv;ScT4{B!fmY#wWHGN{0P=l;xn z5IXnxxCNB&5jw+anKh9!1G|2R!8+` Date: Thu, 30 Jun 2016 22:30:46 -0600 Subject: [PATCH 22/23] * Theme onelinerz * Make preview optional (will do later - need to work on bugs!) * Add to default menu.hjson --- mods/menu.hjson | 265 +++++++++++++------ mods/onelinerz.js | 6 +- mods/themes/luciano_blocktronics/theme.hjson | 26 +- 3 files changed, 208 insertions(+), 89 deletions(-) diff --git a/mods/menu.hjson b/mods/menu.hjson index 2356a937..1ed12a98 100644 --- a/mods/menu.hjson +++ b/mods/menu.hjson @@ -76,7 +76,6 @@ login: { art: USERLOG next: fullLoginSequenceLoginArt - //next: loginOnelinerz config: { tooNodeMenu: loginAttemptTooNode } @@ -113,91 +112,6 @@ } } - loginOnelinerz: { - desc: Viewing Onelinerz - module: onelinerz - next: fullLoginSequenceLoginArt - options: { - cls: true - } - config: { - art: { - entries: ONELINER - add: ONEADD - } - } - form: { - 0: { - mci: { - VM1: { - focus: false - height: 10 - } - TM2: { - argName: addOrExit - items: [ "yeah!", "nah" ] - submit: true - focus: true - } - } - submit: { - *: [ - { - value: { addOrExit: 0 } - action: @method:viewAddScreen - } - { - value: { addOrExit: null } - action: @systemMethod:nextMenu - } - ] - } - actionKeys: [ - { - keys: [ "escape" ] - action: @systemMethod:nextMenu - } - ] - }, - 1: { - mci: { - ET1: { - focus: true - maxLength: 70 - argName: oneliner - } - TL2: { - width: 60 - } - TM3: { - argName: addOrCancel - items: [ "add", "cancel" ] - submit: true - } - } - - submit: { - *: [ - { - value: { addOrCancel: 0 } - action: @method:addEntry - } - { - value: { addOrCancel: 1 } - action: @method:cancelAdd - } - ] - } - actionKeys: [ - { - keys: [ "escape" ] - action: @method:cancelAdd - } - ] - } - } - } - loginAttemptTooNode: { art: TOONODE options: { @@ -603,9 +517,96 @@ module: whos_online art: WHOSON options: { pause: true } - next: fullLoginSequenceNewScanConfirm + next: fullLoginSequenceOnelinerz } + fullLoginSequenceOnelinerz: { + desc: Viewing Onelinerz + module: onelinerz + next: fullLoginSequenceNewScanConfirm + options: { + cls: true + } + config: { + art: { + entries: ONELINER + add: ONEADD + } + } + form: { + 0: { + mci: { + VM1: { + focus: false + height: 10 + } + TM2: { + argName: addOrExit + items: [ "yeah!", "nah" ] + "hotKeys" : { "Y" : 0, "N" : 1, "Q" : 1 } + submit: true + focus: true + } + } + submit: { + *: [ + { + value: { addOrExit: 0 } + action: @method:viewAddScreen + } + { + value: { addOrExit: null } + action: @systemMethod:nextMenu + } + ] + } + actionKeys: [ + { + keys: [ "escape" ] + action: @systemMethod:nextMenu + } + ] + }, + 1: { + mci: { + ET1: { + focus: true + maxLength: 70 + argName: oneliner + } + TL2: { + width: 60 + } + TM3: { + argName: addOrCancel + items: [ "add", "cancel" ] + "hotKeys" : { "A" : 0, "C" : 1, "Q" : 1 } + submit: true + } + } + + submit: { + *: [ + { + value: { addOrCancel: 0 } + action: @method:addEntry + } + { + value: { addOrCancel: 1 } + action: @method:cancelAdd + } + ] + } + actionKeys: [ + { + keys: [ "escape" ] + action: @method:cancelAdd + } + ] + } + } + } + fullLoginSequenceNewScanConfirm: { desc: Logging In prompt: loginGlobalNewScan @@ -730,6 +731,10 @@ value: { command: "K" } action: @menu:mainMenuFeedbackToSysOp } + { + value: { command: "O" } + action: @menu:mainMenuOnelinerz + } { value: { command: "CHAT"} action: @menu:ercClient @@ -997,6 +1002,92 @@ } } + mainMenuOnelinerz: { + desc: Viewing Onelinerz + module: onelinerz + options: { + cls: true + } + config: { + art: { + entries: ONELINER + add: ONEADD + } + } + form: { + 0: { + mci: { + VM1: { + focus: false + height: 10 + } + TM2: { + argName: addOrExit + items: [ "yeah!", "nah" ] + "hotKeys" : { "Y" : 0, "N" : 1, "Q" : 1 } + submit: true + focus: true + } + } + submit: { + *: [ + { + value: { addOrExit: 0 } + action: @method:viewAddScreen + } + { + value: { addOrExit: null } + action: @systemMethod:nextMenu + } + ] + } + actionKeys: [ + { + keys: [ "escape" ] + action: @systemMethod:nextMenu + } + ] + }, + 1: { + mci: { + ET1: { + focus: true + maxLength: 70 + argName: oneliner + } + TL2: { + width: 60 + } + TM3: { + argName: addOrCancel + items: [ "add", "cancel" ] + "hotKeys" : { "A" : 0, "C" : 1, "Q" : 1 } + submit: true + } + } + + submit: { + *: [ + { + value: { addOrCancel: 0 } + action: @method:addEntry + } + { + value: { addOrCancel: 1 } + action: @method:cancelAdd + } + ] + } + actionKeys: [ + { + keys: [ "escape" ] + action: @method:cancelAdd + } + ] + } + } + } + ercClient: { art: erc module: erc_client diff --git a/mods/onelinerz.js b/mods/onelinerz.js index 74d912cf..80bd301e 100644 --- a/mods/onelinerz.js +++ b/mods/onelinerz.js @@ -209,7 +209,11 @@ function OnelinerzModule(options) { const previewView = self.viewControllers.add.getView(MciCodeIds.AddForm.EntryPreview); newEntryView.setText(''); - previewView.setText(''); + + // preview is optional + if(previewView) { + previewView.setText(''); + } }; this.menuMethods = { diff --git a/mods/themes/luciano_blocktronics/theme.hjson b/mods/themes/luciano_blocktronics/theme.hjson index 98f16cb4..ac359ac8 100644 --- a/mods/themes/luciano_blocktronics/theme.hjson +++ b/mods/themes/luciano_blocktronics/theme.hjson @@ -84,7 +84,7 @@ } } - loginOnelinerz: { + fullLoginSequenceOnelinerz: { config: { listFormat: "|00|11{username:<12}|08: |03{oneliner:<59.58}" } @@ -180,6 +180,30 @@ } } + mainMenuOnelinerz: { + // :TODO: Need way to just duplicate entry here & in menu.hjson, e.g. use: someName + must supply next/etc. in menu + config: { + listFormat: "|00|11{username:<12}|08: |03{oneliner:<59.58}" + } + 0: { + mci: { + VM1: { height: 10 } + TM2: { + focusTextStyle: first lower + } + } + } + 1: { + mci: { + ET1: { width: 60 } + TL2: { width: 60 } + TM3: { + focusTextStyle: first lower + } + } + } + } + messageAreaMessageList: { config: { listFormat: "|00|15{msgNum:>4} |03{subj:<29.29} |11{from:<20.20} |03{ts} |01|31{newIndicator}" From 0591f364dc6ed2bb6b4312c3e6bb7bcd4c2abff7 Mon Sep 17 00:00:00 2001 From: Bryan Ashby Date: Thu, 30 Jun 2016 22:31:32 -0600 Subject: [PATCH 23/23] Add onelinerz to menu --- mods/themes/luciano_blocktronics/MMENU.ANS | Bin 3346 -> 3360 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/mods/themes/luciano_blocktronics/MMENU.ANS b/mods/themes/luciano_blocktronics/MMENU.ANS index 35950215c7a622303bd4999c22260f96f18a8532..0fc985de1350b23136bf6748537f30f58614a866 100644 GIT binary patch delta 60 zcmbOvwLoga32tU1Bj?F?x%HI{4V|T<4RiAqq@xY2jm>iN^HOs%^HPhdq@#_^bETt= Q%qH{k++*B2`7Mts06>ouQUCw| delta 46 ycmZ1=HA!m232sqSXX$8Tvs~$DLt_xjJXZk-CZFckXEL#vEW>k;arxxmJgNXYA`P