From 99ab60bf771a8738e9ddd5dcd5c8fe458b24533d Mon Sep 17 00:00:00 2001 From: Bryan Ashby Date: Wed, 25 Jan 2017 22:18:05 -0700 Subject: [PATCH] * Convert MenuModule to ES6 style class * Convert modules that are MenuModule subclasses to ES6 style classes * Convert mixins to ES6 style * Various cleanup --- core/door_party.js | 31 +- core/enig_error.js | 1 + core/fse.js | 723 ++++++++++++++--------------- core/menu_module.js | 721 ++++++++++++++-------------- core/mod_mixins.js | 41 +- core/new_scan.js | 193 ++++---- core/standard_menu.js | 39 +- core/user_config.js | 361 +++++++------- mods/abracadabra.js | 101 ++-- mods/art_pool.js | 33 -- mods/bbs_link.js | 42 +- mods/bbs_list.js | 278 ++++++----- mods/erc_client.js | 5 +- mods/file_base_download_manager.js | 3 - mods/last_callers.js | 185 ++++---- mods/msg_area_list.js | 243 +++++----- mods/msg_area_post_fse.js | 105 ++--- mods/msg_area_view_fse.js | 191 ++++---- mods/msg_conf_list.js | 237 +++++----- mods/msg_list.js | 398 ++++++++-------- mods/nua.js | 209 +++++---- mods/onelinerz.js | 182 ++++---- mods/telnet_bridge.js | 27 +- mods/user_list.js | 165 ++++--- mods/whos_online.js | 113 ++--- package.json | 5 +- 26 files changed, 2214 insertions(+), 2418 deletions(-) delete mode 100644 mods/art_pool.js diff --git a/core/door_party.js b/core/door_party.js index d782c07e..1766f5ff 100644 --- a/core/door_party.js +++ b/core/door_party.js @@ -10,28 +10,26 @@ const async = require('async'); const _ = require('lodash'); const SSHClient = require('ssh2').Client; -exports.getModule = DoorPartyModule; - exports.moduleInfo = { name : 'DoorParty', desc : 'DoorParty Access Module', author : 'NuSkooler', }; +exports.getModule = class DoorPartyModule extends MenuModule { + constructor(options) { + super(options); -function DoorPartyModule(options) { - MenuModule.call(this, options); + // establish defaults + this.config = options.menuConfig.config; + this.config.host = this.config.host || 'dp.throwbackbbs.com'; + this.config.sshPort = this.config.sshPort || 2022; + this.config.rloginPort = this.config.rloginPort || 513; + } - const self = this; - - // establish defaults - this.config = options.menuConfig.config; - this.config.host = this.config.host || 'dp.throwbackbbs.com'; - this.config.sshPort = this.config.sshPort || 2022; - this.config.rloginPort = this.config.rloginPort || 513; - - this.initSequence = function() { + initSequence() { let clientTerminated; + const self = this; async.series( [ @@ -125,8 +123,5 @@ function DoorPartyModule(options) { } } ); - }; - -} - -require('util').inherits(DoorPartyModule, MenuModule); \ No newline at end of file + } +}; diff --git a/core/enig_error.js b/core/enig_error.js index 98e3cb50..0378ee00 100644 --- a/core/enig_error.js +++ b/core/enig_error.js @@ -28,4 +28,5 @@ exports.Errors = { AccessDenied : (reason, reasonCode) => new EnigError('Access denied', -32003, reason, reasonCode), Invalid : (reason, reasonCode) => new EnigError('Invalid', -32004, reason, reasonCode), ExternalProcess : (reason, reasonCode) => new EnigError('External process error', -32005, reason, reasonCode), + MissingConfig : (reason, reasonCode) => new EnigError('Missing configuration', -32006, reason, reasonCode), }; diff --git a/core/fse.js b/core/fse.js index 414c10ef..d7da12ed 100644 --- a/core/fse.js +++ b/core/fse.js @@ -12,6 +12,7 @@ const getUserIdAndName = require('./user.js').getUserIdAndName; const cleanControlCodes = require('./string_util.js').cleanControlCodes; const StatLog = require('./stat_log.js'); const stringFormat = require('./string_format.js'); +const MessageAreaConfTempSwitcher = require('./mod_mixins.js').MessageAreaConfTempSwitcher; // deps const async = require('async'); @@ -19,12 +20,6 @@ const assert = require('assert'); const _ = require('lodash'); const moment = require('moment'); -exports.FullScreenEditorModule = FullScreenEditorModule; - -// :TODO: clean this up: - -exports.getModule = FullScreenEditorModule; - exports.moduleInfo = { name : 'Full Screen Editor (FSE)', desc : 'A full screen editor/viewer', @@ -65,7 +60,7 @@ exports.moduleInfo = { */ -var MCICodeIds = { +const MciCodeIds = { ViewModeHeader : { From : 1, To : 2, @@ -97,72 +92,192 @@ var MCICodeIds = { }, }; -function FullScreenEditorModule(options) { - MenuModule.call(this, options); +// :TODO: convert code in this class to newer styles, conventions, etc. There is a lot of experimental stuff here that has better (DRY) alternatives - var self = this; - var config = this.menuConfig.config; +exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModule extends MessageAreaConfTempSwitcher(MenuModule) { - // - // menuConfig.config: - // editorType : email | area - // editorMode : view | edit | quote - // - // menuConfig.config or extraArgs - // messageAreaTag - // messageIndex / messageTotal - // toUserId - // - this.editorType = config.editorType; - this.editorMode = config.editorMode; - - if(config.messageAreaTag) { - this.messageAreaTag = config.messageAreaTag; - } - - this.messageIndex = config.messageIndex || 0; - this.messageTotal = config.messageTotal || 0; - this.toUserId = config.toUserId || 0; + constructor(options) { + super(options); - // extraArgs can override some config - if(_.isObject(options.extraArgs)) { - if(options.extraArgs.messageAreaTag) { - this.messageAreaTag = options.extraArgs.messageAreaTag; + const self = this; + const config = this.menuConfig.config; + + // + // menuConfig.config: + // editorType : email | area + // editorMode : view | edit | quote + // + // menuConfig.config or extraArgs + // messageAreaTag + // messageIndex / messageTotal + // toUserId + // + this.editorType = config.editorType; + this.editorMode = config.editorMode; + + if(config.messageAreaTag) { + this.messageAreaTag = config.messageAreaTag; } - if(options.extraArgs.messageIndex) { - this.messageIndex = options.extraArgs.messageIndex; + + this.messageIndex = config.messageIndex || 0; + this.messageTotal = config.messageTotal || 0; + this.toUserId = config.toUserId || 0; + + // extraArgs can override some config + if(_.isObject(options.extraArgs)) { + if(options.extraArgs.messageAreaTag) { + this.messageAreaTag = options.extraArgs.messageAreaTag; + } + if(options.extraArgs.messageIndex) { + this.messageIndex = options.extraArgs.messageIndex; + } + if(options.extraArgs.messageTotal) { + this.messageTotal = options.extraArgs.messageTotal; + } + if(options.extraArgs.toUserId) { + this.toUserId = options.extraArgs.toUserId; + } } - if(options.extraArgs.messageTotal) { - this.messageTotal = options.extraArgs.messageTotal; - } - if(options.extraArgs.toUserId) { - this.toUserId = options.extraArgs.toUserId; + + this.isReady = false; + + if(_.has(options, 'extraArgs.message')) { + this.setMessage(options.extraArgs.message); + } else if(_.has(options, 'extraArgs.replyToMessage')) { + this.replyToMessage = options.extraArgs.replyToMessage; } + + this.menuMethods = { + // + // Validation stuff + // + viewValidationListener : function(err, cb) { + var errMsgView = self.viewControllers.header.getView(MciCodeIds.ReplyEditModeHeader.ErrorMsg); + var newFocusViewId; + if(errMsgView) { + if(err) { + errMsgView.setText(err.message); + + if(MciCodeIds.ViewModeHeader.Subject === err.view.getId()) { + // :TODO: for "area" mode, should probably just bail if this is emtpy (e.g. cancel) + } + } else { + errMsgView.clearText(); + } + } + cb(newFocusViewId); + }, + + headerSubmit : function(formData, extraArgs, cb) { + self.switchToBody(); + return cb(null); + }, + editModeEscPressed : function(formData, extraArgs, cb) { + self.footerMode = 'editor' === self.footerMode ? 'editorMenu' : 'editor'; + + self.switchFooter(function next(err) { + if(err) { + // :TODO:... what now? + console.log(err) + } else { + switch(self.footerMode) { + case 'editor' : + if(!_.isUndefined(self.viewControllers.footerEditorMenu)) { + //self.viewControllers.footerEditorMenu.setFocus(false); + self.viewControllers.footerEditorMenu.detachClientEvents(); + } + self.viewControllers.body.switchFocus(1); + self.observeEditorEvents(); + break; + + case 'editorMenu' : + self.viewControllers.body.setFocus(false); + self.viewControllers.footerEditorMenu.switchFocus(1); + break; + + default : throw new Error('Unexpected mode'); + } + } + + return cb(null); + }); + }, + editModeMenuQuote : function(formData, extraArgs, cb) { + self.viewControllers.footerEditorMenu.setFocus(false); + self.displayQuoteBuilder(); + return cb(null); + }, + appendQuoteEntry: function(formData, extraArgs, cb) { + // :TODO: Dont' use magic # ID's here + var quoteMsgView = self.viewControllers.quoteBuilder.getView(1); + + if(self.newQuoteBlock) { + self.newQuoteBlock = false; + quoteMsgView.addText(self.getQuoteByHeader()); + } + + var quoteText = self.viewControllers.quoteBuilder.getView(3).getItem(formData.value.quote); + quoteMsgView.addText(quoteText); + + // + // If this is *not* the last item, advance. Otherwise, do nothing as we + // don't want to jump back to the top and repeat already quoted lines + // + var quoteListView = self.viewControllers.quoteBuilder.getView(3); + if(quoteListView.getData() !== quoteListView.getCount() - 1) { + quoteListView.focusNext(); + } else { + self.quoteBuilderFinalize(); + } + + return cb(null); + }, + quoteBuilderEscPressed : function(formData, extraArgs, cb) { + self.quoteBuilderFinalize(); + return cb(null); + }, + /* + replyDiscard : function(formData, extraArgs) { + // :TODO: need to prompt yes/no + // :TODO: @method for fallback would be better + self.prevMenu(); + }, + */ + editModeMenuHelp : function(formData, extraArgs, cb) { + self.viewControllers.footerEditorMenu.setFocus(false); + return self.displayHelp(cb); + }, + /////////////////////////////////////////////////////////////////////// + // View Mode + /////////////////////////////////////////////////////////////////////// + viewModeMenuHelp : function(formData, extraArgs, cb) { + self.viewControllers.footerView.setFocus(false); + return self.displayHelp(cb); + } + }; } - this.isReady = false; + isEditMode() { + return 'edit' === this.editorMode; + } - this.isEditMode = function() { - return 'edit' === self.editorMode; - }; - - this.isViewMode = function() { - return 'view' === self.editorMode; - }; + isViewMode() { + return 'view' === this.editorMode; + } - this.isLocalEmail = function() { - return Message.WellKnownAreaTags.Private === self.messageAreaTag; - }; + isLocalEmail() { + return Message.WellKnownAreaTags.Private === this.messageAreaTag; + } - this.isReply = function() { - return !_.isUndefined(self.replyToMessage); - }; + isReply() { + return !_.isUndefined(this.replyToMessage); + } - this.getFooterName = function() { - return 'footer' + _.capitalize(self.footerMode); // e.g. 'footerEditor', 'footerEditorMenu', ... - }; + getFooterName() { + return 'footer' + _.capitalize(this.footerMode); // e.g. 'footerEditor', 'footerEditorMenu', ... + } - this.getFormId = function(name) { + getFormId(name) { return { header : 0, body : 1, @@ -173,27 +288,13 @@ function FullScreenEditorModule(options) { help : 50, }[name]; - }; - - /*ViewModeHeader : { - From : 1, - To : 2, - Subject : 3, - - DateTime : 5, - MsgNum : 6, - MsgTotal : 7, - ViewCount : 8, - HashTags : 9, - MessageID : 10, - ReplyToMsgID : 11 - },*/ + } // :TODO: convert to something like this for all view acces: - this.getHeaderViews = function() { - var vc = self.viewControllers.header; + getHeaderViews() { + var vc = this.viewControllers.header; - if(self.isViewMode()) { + if(this.isViewMode()) { return { from : vc.getView(1), to : vc.getView(2), @@ -205,61 +306,55 @@ function FullScreenEditorModule(options) { }; } - }; + } - this.setInitialFooterMode = function() { - switch(self.editorMode) { - case 'edit' : self.footerMode = 'editor'; break; - case 'view' : self.footerMode = 'view'; break; + setInitialFooterMode() { + switch(this.editorMode) { + case 'edit' : this.footerMode = 'editor'; break; + case 'view' : this.footerMode = 'view'; break; } - }; + } - this.buildMessage = function() { - var headerValues = self.viewControllers.header.getFormData().value; + buildMessage() { + var headerValues = this.viewControllers.header.getFormData().value; var msgOpts = { - areaTag : self.messageAreaTag, + areaTag : this.messageAreaTag, toUserName : headerValues.to, fromUserName : headerValues.from, subject : headerValues.subject, - message : self.viewControllers.body.getFormData().value.message, + message : this.viewControllers.body.getFormData().value.message, }; - if(self.isReply()) { - msgOpts.replyToMsgId = self.replyToMessage.messageId; + if(this.isReply()) { + msgOpts.replyToMsgId = this.replyToMessage.messageId; } - self.message = new Message(msgOpts); - }; + this.message = new Message(msgOpts); + } - /* - this.setBodyMessageViewText = function() { - self.bodyMessageView.setText(cleanControlCodes(self.message.message)); - }; - */ - - this.setMessage = function(message) { - self.message = message; + setMessage(message) { + this.message = message; updateMessageAreaLastReadId( - self.client.user.userId, self.messageAreaTag, self.message.messageId, () => { + this.client.user.userId, this.messageAreaTag, this.message.messageId, () => { - if(self.isReady) { - self.initHeaderViewMode(); - self.initFooterViewMode(); + if(this.isReady) { + this.initHeaderViewMode(); + this.initFooterViewMode(); - var bodyMessageView = self.viewControllers.body.getView(1); - if(bodyMessageView && _.has(self, 'message.message')) { - //self.setBodyMessageViewText(); - bodyMessageView.setText(cleanControlCodes(self.message.message)); - //bodyMessageView.redraw(); + var bodyMessageView = this.viewControllers.body.getView(1); + if(bodyMessageView && _.has(this, 'message.message')) { + bodyMessageView.setText(cleanControlCodes(this.message.message)); } } } ); - }; + } + + getMessage(cb) { + const self = this; - this.getMessage = function(cb) { async.series( [ function buildIfNecessary(callback) { @@ -295,24 +390,22 @@ function FullScreenEditorModule(options) { cb(err, self.message); } ); - }; + } - this.updateUserStats = function(cb) { + updateUserStats(cb) { if(Message.isPrivateAreaTag(this.message.areaTag)) { if(cb) { - return cb(null); + cb(null); } + return; // don't inc stats for private messages } - StatLog.incrementUserStat( - self.client.user, - 'post_count', - 1, - cb - ); - }; + return StatLog.incrementUserStat(this.client.user, 'post_count', 1, cb); + } + + redrawFooter(options, cb) { + const self = this; - this.redrawFooter = function(options, cb) { async.waterfall( [ function moveToFooterPosition(callback) { @@ -354,10 +447,11 @@ function FullScreenEditorModule(options) { cb(err, artData); } ); - }; + } - this.redrawScreen = function(cb) { + redrawScreen(cb) { var comps = [ 'header', 'body' ]; + const self = this; var art = self.menuConfig.config.art; self.client.term.rawWrite(ansi.resetScreen()); @@ -398,43 +492,44 @@ function FullScreenEditorModule(options) { cb(err); } ); - }; + } + switchFooter(cb) { + var footerName = this.getFooterName(); - this.switchFooter = function(cb) { - var footerName = self.getFooterName(); - - self.redrawFooter( { footerName : footerName, clear : true }, function artDisplayed(err, artData) { + this.redrawFooter( { footerName : footerName, clear : true }, (err, artData) => { if(err) { cb(err); return; } - var formId = self.getFormId(footerName); + var formId = this.getFormId(footerName); - if(_.isUndefined(self.viewControllers[footerName])) { + if(_.isUndefined(this.viewControllers[footerName])) { var menuLoadOpts = { - callingMenu : self, + callingMenu : this, formId : formId, mciMap : artData.mciMap }; - self.addViewController( + this.addViewController( footerName, - new ViewController( { client : self.client, formId : formId } ) - ).loadFromMenuConfig(menuLoadOpts, function footerReady(err) { + new ViewController( { client : this.client, formId : formId } ) + ).loadFromMenuConfig(menuLoadOpts, err => { cb(err); }); } else { - self.viewControllers[footerName].redrawAll(); + this.viewControllers[footerName].redrawAll(); cb(null); } }); - }; + } - this.initSequence = function() { + initSequence() { var mciData = { }; + const self = this; var art = self.menuConfig.config.art; + assert(_.isObject(art)); async.series( @@ -488,10 +583,10 @@ function FullScreenEditorModule(options) { } } ); - }; + } - this.createInitialViews = function(mciData, cb) { - + createInitialViews(mciData, cb) { + const self = this; var menuLoadOpts = { callingMenu : self }; async.series( @@ -596,11 +691,11 @@ function FullScreenEditorModule(options) { cb(err); } ); - }; + } - this.mciReadyHandler = function(mciData, cb) { + mciReadyHandler(mciData, cb) { - self.createInitialViews(mciData, function viewsCreated(err) { + this.createInitialViews(mciData, err => { // :TODO: Can probably be replaced with @systemMethod:validateUserNameExists when the framework is in // place - if this is for existing usernames else validate spec @@ -620,103 +715,94 @@ function FullScreenEditorModule(options) { cb(err); }); - }; + } - this.updateEditModePosition = function(pos) { - if(self.isEditMode()) { - var posView = self.viewControllers.footerEditor.getView(1); + updateEditModePosition(pos) { + if(this.isEditMode()) { + var posView = this.viewControllers.footerEditor.getView(1); if(posView) { - self.client.term.rawWrite(ansi.savePos()); + this.client.term.rawWrite(ansi.savePos()); + // :TODO: Use new formatting techniques here, e.g. state.cursorPositionRow, cursorPositionCol and cursorPositionFormat posView.setText(_.padLeft(String(pos.row + 1), 2, '0') + ',' + _.padLeft(String(pos.col + 1), 2, '0')); - self.client.term.rawWrite(ansi.restorePos()); + this.client.term.rawWrite(ansi.restorePos()); } } - }; + } - this.updateTextEditMode = function(mode) { - if(self.isEditMode()) { - var modeView = self.viewControllers.footerEditor.getView(2); + updateTextEditMode(mode) { + if(this.isEditMode()) { + var modeView = this.viewControllers.footerEditor.getView(2); if(modeView) { - self.client.term.rawWrite(ansi.savePos()); + this.client.term.rawWrite(ansi.savePos()); modeView.setText('insert' === mode ? 'INS' : 'OVR'); - self.client.term.rawWrite(ansi.restorePos()); + this.client.term.rawWrite(ansi.restorePos()); } } - }; + } - this.setHeaderText = function(id, text) { - var v = self.viewControllers.header.getView(id); - if(v) { - v.setText(text); - } - }; + setHeaderText(id, text) { + this.setViewText('header', id, text); + } - this.initHeaderViewMode = function() { - assert(_.isObject(self.message)); + initHeaderViewMode() { + assert(_.isObject(this.message)); - self.setHeaderText(MCICodeIds.ViewModeHeader.From, self.message.fromUserName); - self.setHeaderText(MCICodeIds.ViewModeHeader.To, self.message.toUserName); - self.setHeaderText(MCICodeIds.ViewModeHeader.Subject, self.message.subject); - self.setHeaderText(MCICodeIds.ViewModeHeader.DateTime, moment(self.message.modTimestamp).format(self.client.currentTheme.helpers.getDateTimeFormat())); - self.setHeaderText(MCICodeIds.ViewModeHeader.MsgNum, (self.messageIndex + 1).toString()); - self.setHeaderText(MCICodeIds.ViewModeHeader.MsgTotal, self.messageTotal.toString()); - self.setHeaderText(MCICodeIds.ViewModeHeader.ViewCount, self.message.viewCount); - self.setHeaderText(MCICodeIds.ViewModeHeader.HashTags, 'TODO hash tags'); - self.setHeaderText(MCICodeIds.ViewModeHeader.MessageID, self.message.messageId); - self.setHeaderText(MCICodeIds.ViewModeHeader.ReplyToMsgID, self.message.replyToMessageId); - }; + this.setHeaderText(MciCodeIds.ViewModeHeader.From, this.message.fromUserName); + this.setHeaderText(MciCodeIds.ViewModeHeader.To, this.message.toUserName); + this.setHeaderText(MciCodeIds.ViewModeHeader.Subject, this.message.subject); + this.setHeaderText(MciCodeIds.ViewModeHeader.DateTime, moment(this.message.modTimestamp).format(this.client.currentTheme.helpers.getDateTimeFormat())); + this.setHeaderText(MciCodeIds.ViewModeHeader.MsgNum, (this.messageIndex + 1).toString()); + this.setHeaderText(MciCodeIds.ViewModeHeader.MsgTotal, this.messageTotal.toString()); + this.setHeaderText(MciCodeIds.ViewModeHeader.ViewCount, this.message.viewCount); + this.setHeaderText(MciCodeIds.ViewModeHeader.HashTags, 'TODO hash tags'); + this.setHeaderText(MciCodeIds.ViewModeHeader.MessageID, this.message.messageId); + this.setHeaderText(MciCodeIds.ViewModeHeader.ReplyToMsgID, this.message.replyToMessageId); + } - this.initHeaderReplyEditMode = function() { - assert(_.isObject(self.replyToMessage)); + initHeaderReplyEditMode() { + assert(_.isObject(this.replyToMessage)); - self.setHeaderText(MCICodeIds.ReplyEditModeHeader.To, self.replyToMessage.fromUserName); + this.setHeaderText(MciCodeIds.ReplyEditModeHeader.To, this.replyToMessage.fromUserName); // // We want to prefix the subject with "RE: " only if it's not already // that way -- avoid RE: RE: RE: RE: ... // - let newSubj = self.replyToMessage.subject; + let newSubj = this.replyToMessage.subject; if(false === /^RE:\s+/i.test(newSubj)) { newSubj = `RE: ${newSubj}`; } - self.setHeaderText(MCICodeIds.ReplyEditModeHeader.Subject, newSubj); - }; + this.setHeaderText(MciCodeIds.ReplyEditModeHeader.Subject, newSubj); + } - this.initFooterViewMode = function() { - - function setFooterText(id, text) { - var v = self.viewControllers.footerView.getView(id); - if(v) { - v.setText(text); - } - } + initFooterViewMode() { + this.setViewText('footerView', MciCodeIds.ViewModeFooter.MsgNum, (this.messageIndex + 1).toString() ); + this.setViewText('footerView', MciCodeIds.ViewModeFooter.MsgTotal, this.messageTotal.toString() ); + } - setFooterText(MCICodeIds.ViewModeFooter.MsgNum, (self.messageIndex + 1).toString()); - setFooterText(MCICodeIds.ViewModeFooter.MsgTotal, self.messageTotal.toString()); - }; - - this.displayHelp = function(cb) { - self.client.term.rawWrite(ansi.resetScreen()); + displayHelp(cb) { + this.client.term.rawWrite(ansi.resetScreen()); theme.displayThemeArt( - { name : self.menuConfig.config.art.help, client : self.client }, + { name : this.menuConfig.config.art.help, client : this.client }, () => { - self.client.waitForKeyPress( () => { - self.redrawScreen( () => { - self.viewControllers[self.getFooterName()].setFocus(true); + this.client.waitForKeyPress( () => { + this.redrawScreen( () => { + this.viewControllers[this.getFooterName()].setFocus(true); return cb(null); }); }); } ); - }; + } - this.displayQuoteBuilder = function() { + displayQuoteBuilder() { // // Clear body area // - self.newQuoteBlock = true; + this.newQuoteBlock = true; + const self = this; async.waterfall( [ @@ -772,19 +858,19 @@ function FullScreenEditorModule(options) { } } ); - }; + } - this.observeEditorEvents = function() { - var bodyView = self.viewControllers.body.getView(1); + observeEditorEvents() { + const bodyView = this.viewControllers.body.getView(1); - bodyView.on('edit position', function cursorPosUpdate(pos) { - self.updateEditModePosition(pos); + bodyView.on('edit position', pos => { + this.updateEditModePosition(pos); }); - bodyView.on('text edit mode', function textEditMode(mode) { - self.updateTextEditMode(mode); + bodyView.on('text edit mode', mode => { + this.updateTextEditMode(mode); }); - }; + } /* this.observeViewPosition = function() { @@ -794,43 +880,43 @@ function FullScreenEditorModule(options) { }; */ - this.switchToHeader = function() { - self.viewControllers.body.setFocus(false); - self.viewControllers.header.switchFocus(2); // to + switchToHeader() { + this.viewControllers.body.setFocus(false); + this.viewControllers.header.switchFocus(2); // to + } + + switchToBody() { + this.viewControllers.header.setFocus(false); + this.viewControllers.body.switchFocus(1); + + this.observeEditorEvents(); }; - this.switchToBody = function() { - self.viewControllers.header.setFocus(false); - self.viewControllers.body.switchFocus(1); + switchToFooter() { + this.viewControllers.header.setFocus(false); + this.viewControllers.body.setFocus(false); - self.observeEditorEvents(); - }; + this.viewControllers[this.getFooterName()].switchFocus(1); // HM1 + } - this.switchToFooter = function() { - self.viewControllers.header.setFocus(false); - self.viewControllers.body.setFocus(false); - - self.viewControllers[self.getFooterName()].switchFocus(1); // HM1 - }; - - this.switchFromQuoteBuilderToBody = function() { - self.viewControllers.quoteBuilder.setFocus(false); - var body = self.viewControllers.body.getView(1); + switchFromQuoteBuilderToBody() { + this.viewControllers.quoteBuilder.setFocus(false); + var body = this.viewControllers.body.getView(1); body.redraw(); - self.viewControllers.body.switchFocus(1); + this.viewControllers.body.switchFocus(1); // :TODO: create method (DRY) - self.updateTextEditMode(body.getTextEditMode()); - self.updateEditModePosition(body.getEditPosition()); + this.updateTextEditMode(body.getTextEditMode()); + this.updateEditModePosition(body.getEditPosition()); - self.observeEditorEvents(); - }; + this.observeEditorEvents(); + } - this.quoteBuilderFinalize = function() { + quoteBuilderFinalize() { // :TODO: fix magic #'s - var quoteMsgView = self.viewControllers.quoteBuilder.getView(1); - var msgView = self.viewControllers.body.getView(1); + var quoteMsgView = this.viewControllers.quoteBuilder.getView(1); + var msgView = this.viewControllers.body.getView(1); var quoteLines = quoteMsgView.getData(); @@ -841,164 +927,43 @@ function FullScreenEditorModule(options) { quoteMsgView.setText(''); - var footerName = self.getFooterName(); + this.footerMode = 'editor'; - self.footerMode = 'editor'; - - self.switchFooter(function switched(err) { - self.switchFromQuoteBuilderToBody(); + this.switchFooter( () => { + this.switchFromQuoteBuilderToBody(); }); - }; + } - this.getQuoteByHeader = function() { + getQuoteByHeader() { let quoteFormat = this.menuConfig.config.quoteFormats; + if(Array.isArray(quoteFormat)) { quoteFormat = quoteFormat[ Math.floor(Math.random() * quoteFormat.length) ]; } else if(!_.isString(quoteFormat)) { quoteFormat = 'On {dateTime} {userName} said...'; } - const dtFormat = this.menuConfig.config.quoteDateTimeFormat || self.client.currentTheme.helpers.getDateTimeFormat(); + const dtFormat = this.menuConfig.config.quoteDateTimeFormat || this.client.currentTheme.helpers.getDateTimeFormat(); return stringFormat(quoteFormat, { - dateTime : moment(self.replyToMessage.modTimestamp).format(dtFormat), - userName : self.replyToMessage.fromUserName, + dateTime : moment(this.replyToMessage.modTimestamp).format(dtFormat), + userName : this.replyToMessage.fromUserName, }); - }; + } - this.menuMethods = { - // - // Validation stuff - // - viewValidationListener : function(err, cb) { - var errMsgView = self.viewControllers.header.getView(MCICodeIds.ReplyEditModeHeader.ErrorMsg); - var newFocusViewId; - if(errMsgView) { - if(err) { - errMsgView.setText(err.message); - - if(MCICodeIds.ViewModeHeader.Subject === err.view.getId()) { - // :TODO: for "area" mode, should probably just bail if this is emtpy (e.g. cancel) - } - } else { - errMsgView.clearText(); - } - } - cb(newFocusViewId); - }, - - headerSubmit : function(formData, extraArgs, cb) { - self.switchToBody(); - return cb(null); - }, - editModeEscPressed : function(formData, extraArgs, cb) { - self.footerMode = 'editor' === self.footerMode ? 'editorMenu' : 'editor'; - - self.switchFooter(function next(err) { - if(err) { - // :TODO:... what now? - console.log(err) - } else { - switch(self.footerMode) { - case 'editor' : - if(!_.isUndefined(self.viewControllers.footerEditorMenu)) { - //self.viewControllers.footerEditorMenu.setFocus(false); - self.viewControllers.footerEditorMenu.detachClientEvents(); - } - self.viewControllers.body.switchFocus(1); - self.observeEditorEvents(); - break; - - case 'editorMenu' : - self.viewControllers.body.setFocus(false); - self.viewControllers.footerEditorMenu.switchFocus(1); - break; - - default : throw new Error('Unexpected mode'); - } - } - - return cb(null); - }); - }, - editModeMenuQuote : function(formData, extraArgs, cb) { - self.viewControllers.footerEditorMenu.setFocus(false); - self.displayQuoteBuilder(); - return cb(null); - }, - appendQuoteEntry: function(formData, extraArgs, cb) { - // :TODO: Dont' use magic # ID's here - var quoteMsgView = self.viewControllers.quoteBuilder.getView(1); - - if(self.newQuoteBlock) { - self.newQuoteBlock = false; - quoteMsgView.addText(self.getQuoteByHeader()); - } - - var quoteText = self.viewControllers.quoteBuilder.getView(3).getItem(formData.value.quote); - quoteMsgView.addText(quoteText); - - // - // If this is *not* the last item, advance. Otherwise, do nothing as we - // don't want to jump back to the top and repeat already quoted lines - // - var quoteListView = self.viewControllers.quoteBuilder.getView(3); - if(quoteListView.getData() !== quoteListView.getCount() - 1) { - quoteListView.focusNext(); - } else { - self.quoteBuilderFinalize(); - } - - return cb(null); - }, - quoteBuilderEscPressed : function(formData, extraArgs, cb) { - self.quoteBuilderFinalize(); - return cb(null); - }, - /* - replyDiscard : function(formData, extraArgs) { - // :TODO: need to prompt yes/no - // :TODO: @method for fallback would be better - self.prevMenu(); - }, - */ - editModeMenuHelp : function(formData, extraArgs, cb) { - self.viewControllers.footerEditorMenu.setFocus(false); - return self.displayHelp(cb); - }, - /////////////////////////////////////////////////////////////////////// - // View Mode - /////////////////////////////////////////////////////////////////////// - viewModeMenuHelp : function(formData, extraArgs, cb) { - self.viewControllers.footerView.setFocus(false); - return self.displayHelp(cb); + enter() { + if(this.messageAreaTag) { + this.tempMessageConfAndAreaSwitch(this.messageAreaTag); } - }; - if(_.has(options, 'extraArgs.message')) { - this.setMessage(options.extraArgs.message); - } else if(_.has(options, 'extraArgs.replyToMessage')) { - this.replyToMessage = options.extraArgs.replyToMessage; - } -} - -require('util').inherits(FullScreenEditorModule, MenuModule); - -require('./mod_mixins.js').MessageAreaConfTempSwitcher.call(FullScreenEditorModule.prototype); - -FullScreenEditorModule.prototype.enter = function() { - - if(this.messageAreaTag) { - this.tempMessageConfAndAreaSwitch(this.messageAreaTag); + super.enter(); } - FullScreenEditorModule.super_.prototype.enter.call(this); -}; + leave() { + this.tempMessageConfAndAreaRestore(); + super.leave(); + } -FullScreenEditorModule.prototype.leave = function() { - this.tempMessageConfAndAreaRestore(); - FullScreenEditorModule.super_.prototype.leave.call(this); -}; - -FullScreenEditorModule.prototype.mciReady = function(mciData, cb) { - this.mciReadyHandler(mciData, cb); + mciReady(mciData, cb) { + return this.mciReadyHandler(mciData, cb); + } }; diff --git a/core/menu_module.js b/core/menu_module.js index 6cc81e4e..95356f90 100644 --- a/core/menu_module.js +++ b/core/menu_module.js @@ -9,432 +9,393 @@ const menuUtil = require('./menu_util.js'); const Config = require('./config.js').config; const stringFormat = require('../core/string_format.js'); const MultiLineEditTextView = require('../core/multi_line_edit_text_view.js').MultiLineEditTextView; +const Errors = require('../core/enig_error.js').Errors; // deps -const async = require('async'); -const assert = require('assert'); -const _ = require('lodash'); +const async = require('async'); +const assert = require('assert'); +const _ = require('lodash'); -exports.MenuModule = MenuModule; +exports.MenuModule = class MenuModule extends PluginModule { + + constructor(options) { + super(options); -// :TODO: some of this is a bit off... should pause after finishedLoading() + this.menuName = options.menuName; + this.menuConfig = options.menuConfig; + this.client = options.client; + this.menuConfig.options = options.menuConfig.options || {}; + this.menuMethods = {}; // methods called from @method's + this.menuConfig.config = this.menuConfig.config || {}; + + this.cls = _.isBoolean(this.menuConfig.options.cls) ? this.menuConfig.options.cls : Config.menus.cls; -function MenuModule(options) { - PluginModule.call(this, options); + this.viewControllers = {}; + } - var self = this; - this.menuName = options.menuName; - this.menuConfig = options.menuConfig; - this.client = options.client; - - // :TODO: this and the line below with .config creates empty ({}) objects in the theme -- - // ...which we really should not do. If they aren't there already, don't use 'em. - this.menuConfig.options = options.menuConfig.options || {}; - this.menuMethods = {}; // methods called from @method's + enter() { + this.initSequence(); + } - this.cls = _.isBoolean(this.menuConfig.options.cls) ? - this.menuConfig.options.cls : - Config.menus.cls; + leave() { + this.detachViewControllers(); + } - this.menuConfig.config = this.menuConfig.config || {}; + initSequence() { + const self = this; + const mciData = {}; + let pausePosition; - this.initViewControllers(); + async.series( + [ + function beforeDisplayArt(callback) { + self.beforeArt(callback); + }, + function displayMenuArt(callback) { + if(!_.isString(self.menuConfig.art)) { + return callback(null); + } - this.shouldPause = function() { - return 'end' === self.menuConfig.options.pause || true === self.menuConfig.options.pause; - }; + self.displayAsset( + self.menuConfig.art, + self.menuConfig.options, + (err, artData) => { + if(err) { + self.client.log.trace('Could not display art', { art : self.menuConfig.art, reason : err.message } ); + } else { + mciData.menu = artData.mciMap; + } - this.hasNextTimeout = function() { - return _.isNumber(self.menuConfig.options.nextTimeout); - }; + return callback(null); // any errors are non-fatal + } + ); + }, + function moveToPromptLocation(callback) { + if(self.menuConfig.prompt) { + // :TODO: fetch and move cursor to prompt location, if supplied. See notes/etc. on placements + } - this.autoNextMenu = function(cb) { - function goNext() { - if(_.isString(self.menuConfig.next) || _.isArray(self.menuConfig.next)) { + return callback(null); + }, + function displayPromptArt(callback) { + if(!_.isString(self.menuConfig.prompt)) { + return callback(null); + } + + if(!_.isObject(self.menuConfig.promptConfig)) { + return callback(Errors.MissingConfig('Prompt specified but no "promptConfig" block found')); + } + + self.displayAsset( + self.menuConfig.promptConfig.art, + self.menuConfig.options, + (err, artData) => { + if(artData) { + mciData.prompt = artData.mciMap; + } + return callback(err); // pass err here; prompts *must* have art + } + ); + }, + function recordCursorPosition(callback) { + if(!self.shouldPause()) { + return callback(null); // cursor position not needed + } + + self.client.once('cursor position report', pos => { + pausePosition = { row : pos[0], col : 1 }; + self.client.log.trace('After art position recorded', { position : pausePosition } ); + return callback(null); + }); + + self.client.term.rawWrite(ansi.queryPos()); + }, + function afterArtDisplayed(callback) { + return self.mciReady(mciData, callback); + }, + function displayPauseIfRequested(callback) { + if(!self.shouldPause()) { + return callback(null); + } + + return self.pausePrompt(pausePosition, callback); + }, + function finishAndNext(callback) { + self.finishedLoading(); + return self.autoNextMenu(callback); + } + ], + err => { + if(err) { + self.client.log.warn('Error during init sequence', { error : err.message } ); + + return self.prevMenu( () => { /* dummy */ } ); + } + } + ); + } + + beforeArt(cb) { + if(_.isNumber(this.menuConfig.options.baudRate)) { + // :TODO: some terminals not supporting cterm style emulated baud rate end up displaying a broken ESC sequence or a single "r" here + this.client.term.rawWrite(ansi.setEmulatedBaudRate(this.menuConfig.options.baudRate)); + } + + if(this.cls) { + this.client.term.rawWrite(ansi.resetScreen()); + } + + return cb(null); + } + + mciReady(mciData, cb) { + // available for sub-classes + return cb(null); + } + + finishedLoading() { + // nothing in base + } + + getSaveState() { + // nothing in base + } + + restoreSavedState(/*savedState*/) { + // nothing in base + } + + getMenuResult() { + // nothing in base + } + + nextMenu(cb) { + if(!this.haveNext()) { + return this.prevMenu(cb); // no next, go to prev + } + + return this.client.menuStack.next(cb); + } + + prevMenu(cb) { + return this.client.menuStack.prev(cb); + } + + gotoMenu(name, options, cb) { + return this.client.menuStack.goto(name, options, cb); + } + + addViewController(name, vc) { + assert(!this.viewControllers[name], `ViewController by the name of "${name}" already exists!`); + + this.viewControllers[name] = vc; + return vc; + } + + detachViewControllers() { + Object.keys(this.viewControllers).forEach( name => { + this.viewControllers[name].detachClientEvents(); + }); + } + + shouldPause() { + return ('end' === this.menuConfig.options.pause || true === this.menuConfig.options.pause); + } + + hasNextTimeout() { + return _.isNumber(this.menuConfig.options.nextTimeout); + } + + haveNext() { + return (_.isString(this.menuConfig.next) || _.isArray(this.menuConfig.next)); + } + + autoNextMenu(cb) { + const self = this; + + function gotoNextMenu() { + if(self.haveNext()) { return menuUtil.handleNext(self.client, self.menuConfig.next, {}, cb); } else { return self.prevMenu(cb); } } - if(_.has(self.menuConfig, 'runtime.autoNext') && true === self.menuConfig.runtime.autoNext) { - /* - If 'next' is supplied, we'll use it. Otherwise, utlize fallback which - may be explicit (supplied) or non-explicit (previous menu) - - 'next' may be a simple asset, or a object with next.asset and - extrArgs - - next: assetSpec - - -or- - - next: { - asset: assetSpec - extraArgs: ... - } - */ - if(self.hasNextTimeout()) { + if(_.has(this.menuConfig, 'runtime.autoNext') && true === this.menuConfig.runtime.autoNext) { + if(this.hasNextTimeout()) { setTimeout( () => { - return goNext(); + return gotoNextMenu(); }, this.menuConfig.options.nextTimeout); } else { - goNext(); + return gotoNextMenu(); } } - }; + } - this.haveNext = function() { - return (_.isString(this.menuConfig.next) || _.isArray(this.menuConfig.next)); - }; -} + standardMCIReadyHandler(mciData, cb) { + // + // A quick rundown: + // * We may have mciData.menu, mciData.prompt, or both. + // * Prompt form is favored over menu form if both are present. + // * Standard/prefdefined MCI entries must load both (e.g. %BN is expected to resolve) + // + const self = this; -require('util').inherits(MenuModule, PluginModule); + async.series( + [ + function addViewControllers(callback) { + _.forEach(mciData, (mciMap, name) => { + assert('menu' === name || 'prompt' === name); + self.addViewController(name, new ViewController( { client : self.client } ) ); + }); -require('./mod_mixins.js').ViewControllerManagement.call(MenuModule.prototype); - - -MenuModule.prototype.enter = function() { - this.initSequence(); -}; - -MenuModule.prototype.initSequence = function() { - var mciData = { }; - const self = this; - - async.series( - [ - function beforeDisplayArt(callback) { - self.beforeArt(callback); - }, - function displayMenuArt(callback) { - if(_.isString(self.menuConfig.art)) { - theme.displayThemedAsset( - self.menuConfig.art, - self.client, - self.menuConfig.options, // can include .font, .trailingLF, etc. - function displayed(err, artData) { - if(err) { - self.client.log.trace( { art : self.menuConfig.art, error : err.message }, 'Could not display art'); - } else { - mciData.menu = artData.mciMap; - } - callback(null); // non-fatal - } - ); - } else { - callback(null); - } - }, - function moveToPromptLocation(callback) { - if(self.menuConfig.prompt) { - // :TODO: fetch and move cursor to prompt location, if supplied. See notes/etc. on placements - } - - callback(null); - }, - function displayPromptArt(callback) { - if(_.isString(self.menuConfig.prompt)) { - // If a prompt is specified, we need the configuration - if(!_.isObject(self.menuConfig.promptConfig)) { - callback(new Error('Prompt specified but configuraiton not found!')); - return; + return callback(null); + }, + function createMenu(callback) { + if(!self.viewControllers.menu) { + return callback(null); } - // Prompts *must* have art. If it's missing it's an error - // :TODO: allow inline prompts in the future, e.g. @inline:memberName -> { "memberName" : { "text" : "stuff", ... } } - var promptConfig = self.menuConfig.promptConfig; - theme.displayThemedAsset( - promptConfig.art, - self.client, - self.menuConfig.options, // can include .font, .trailingLF, etc. - function displayed(err, artData) { - if(!err) { - mciData.prompt = artData.mciMap; - } - callback(err); - }); - } else { - callback(null); - } - }, - function recordCursorPosition(callback) { - if(self.shouldPause()) { - self.client.once('cursor position report', function cpr(pos) { - self.afterArtPos = pos; - self.client.log.trace( { position : pos }, 'After art position recorded'); - callback(null); - }); - self.client.term.write(ansi.queryPos()); - } else { - callback(null); - } - }, - function afterArtDisplayed(callback) { - self.mciReady(mciData, callback); - }, - function displayPauseIfRequested(callback) { - if(self.shouldPause()) { - self.client.term.write(ansi.goto(self.afterArtPos[0], 1)); - - // :TODO: really need a client.term.pause() that uses the correct art/etc. - // :TODO: Use MenuModule.pausePrompt() - theme.displayThemedPause( { client : self.client }, function keyPressed() { - callback(null); - }); - } else { - callback(null); - } - }, - function finishAndNext(callback) { - self.finishedLoading(); - - self.autoNextMenu(callback); - } - ], - function complete(err) { - if(err) { - console.log(err) - // :TODO: what to do exactly????? - return self.prevMenu( () => { - // dummy - }); - } - } - ); -}; - -MenuModule.prototype.getSaveState = function() { - // nothing in base -}; - -MenuModule.prototype.restoreSavedState = function(/*savedState*/) { - // nothing in base -}; - -MenuModule.prototype.nextMenu = function(cb) { - // - // If we don't actually have |next|, we'll go previous - // - if(!this.haveNext()) { - return this.prevMenu(cb); - } - - this.client.menuStack.next(cb); -}; - -MenuModule.prototype.prevMenu = function(cb) { - this.client.menuStack.prev(cb); -}; - -MenuModule.prototype.gotoMenu = function(name, options, cb) { - this.client.menuStack.goto(name, options, cb); -}; - -MenuModule.prototype.popAndGotoMenu = function(name, options, cb) { - this.client.menuStack.pop(); - this.client.menuStack.goto(name, options, cb); -}; - -MenuModule.prototype.leave = function() { - this.detachViewControllers(); -}; - -MenuModule.prototype.beforeArt = function(cb) { - // - // Set emulated baud rate - note that some terminals will display - // part of the ESC sequence here (generally a single 'r') if they - // do not support cterm style baud rates - // - if(_.isNumber(this.menuConfig.options.baudRate)) { - this.client.term.write(ansi.setEmulatedBaudRate(this.menuConfig.options.baudRate)); - } - - if(this.cls) { - this.client.term.write(ansi.resetScreen()); - } - - return cb(null); -}; - -MenuModule.prototype.mciReady = function(mciData, cb) { - // Reserved for sub classes - cb(null); -}; - -MenuModule.prototype.standardMCIReadyHandler = function(mciData, cb) { - // - // A quick rundown: - // * We may have mciData.menu, mciData.prompt, or both. - // * Prompt form is favored over menu form if both are present. - // * Standard/prefdefined MCI entries must load both (e.g. %BN is expected to resolve) - // - var self = this; - - async.series( - [ - function addViewControllers(callback) { - _.forEach(mciData, function entry(mciMap, name) { - assert('menu' === name || 'prompt' === name); - self.addViewController(name, new ViewController( { client : self.client } )); - }); - callback(null); - }, - function createMenu(callback) { - if(self.viewControllers.menu) { - var menuLoadOpts = { + const menuLoadOpts = { mciMap : mciData.menu, callingMenu : self, withoutForm : _.isObject(mciData.prompt), }; - self.viewControllers.menu.loadFromMenuConfig(menuLoadOpts, function menuLoaded(err) { - callback(err); + self.viewControllers.menu.loadFromMenuConfig(menuLoadOpts, err => { + return callback(err); }); - } else { - callback(null); - } - }, - function createPrompt(callback) { - if(self.viewControllers.prompt) { - var promptLoadOpts = { + }, + function createPrompt(callback) { + if(!self.viewControllers.prompt) { + return callback(null); + } + + const promptLoadOpts = { callingMenu : self, mciMap : mciData.prompt, }; - self.viewControllers.prompt.loadFromPromptConfig(promptLoadOpts, function promptLoaded(err) { - callback(err); + self.viewControllers.prompt.loadFromPromptConfig(promptLoadOpts, err => { + return callback(err); }); - } else { - callback(null); } - } - ], - function complete(err) { - cb(err); - } - ); -}; - -MenuModule.prototype.finishedLoading = function() { -}; - -MenuModule.prototype.getMenuResult = function() { - // nothing in base -}; - -MenuModule.prototype.displayAsset = function(name, options, cb) { - - if(_.isFunction(options)) { - cb = options; - options = {}; - } - - if(options.clearScreen) { - this.client.term.rawWrite(ansi.clearScreen()); - } - - return theme.displayThemedAsset( - name, - this.client, - Object.assign( { font : this.menuConfig.config.font }, options ), - (err, artData) => { - if(cb) { - return cb(err, artData); - } - } - ); - -}; - -MenuModule.prototype.prepViewController = function(name, formId, artData, cb) { - - if(_.isUndefined(this.viewControllers[name])) { - const vcOpts = { - client : this.client, - formId : formId, - }; - - const vc = this.addViewController(name, new ViewController(vcOpts)); - - const loadOpts = { - callingMenu : this, - mciMap : artData.mciMap, - formId : formId, - }; - - return vc.loadFromMenuConfig(loadOpts, cb); - } - - this.viewControllers[name].setFocus(true); - return cb(null); -}; - - -MenuModule.prototype.prepViewControllerWithArt = function(name, formId, options, cb) { - this.displayAsset( - this.menuConfig.config.art[name], - options, - (err, artData) => { - if(err) { + ], + err => { return cb(err); } + ); + } - return this.prepViewController(name, formId, artData, cb); + displayAsset(name, options, cb) { + if(_.isFunction(options)) { + cb = options; + options = {}; } - ); -}; -MenuModule.prototype.pausePrompt = function(position, cb) { - if(!cb && _.isFunction(position)) { - cb = position; - position = null; - } - - if(position) { - position.x = position.row || position.x || 1; - position.y = position.col || position.y || 1; - - this.client.term.rawWrite(ansi.goto(position.x, position.y)); - } - - theme.displayThemedPause( { client : this.client }, cb); -}; - -MenuModule.prototype.setViewText = function(formName, mciId, text, appendMultiline) { - const view = this.viewControllers[formName].getView(mciId); - if(!view) { - return; - } - - if(appendMultiline && (view instanceof MultiLineEditTextView)) { - view.addText(text); - } else { - view.setText(text); - } -}; - -MenuModule.prototype.updateCustomViewTextsWithFilter = function(formName, startId, fmtObj, options) { - options = options || {}; - - let textView; - let customMciId = startId; - const config = this.menuConfig.config; - - while( (textView = this.viewControllers[formName].getView(customMciId)) ) { - const key = `${formName}InfoFormat${customMciId}`; // e.g. "mainInfoFormat10" - const format = config[key]; - - if(format && (!options.filter || options.filter.find(f => format.indexOf(f) > - 1))) { - const text = stringFormat(format, fmtObj); - - if(options.appendMultiLine && (textView instanceof MultiLineEditTextView)) { - textView.addText(text); - } else { - textView.setText(text); + if(options.clearScreen) { + this.client.term.rawWrite(ansi.clearScreen()); + } + + return theme.displayThemedAsset( + name, + this.client, + Object.assign( { font : this.menuConfig.config.font }, options ), + (err, artData) => { + if(cb) { + return cb(err, artData); + } } + ); + } + + prepViewController(name, formId, artData, cb) { + if(_.isUndefined(this.viewControllers[name])) { + const vcOpts = { + client : this.client, + formId : formId, + }; + + const vc = this.addViewController(name, new ViewController(vcOpts)); + + const loadOpts = { + callingMenu : this, + mciMap : artData.mciMap, + formId : formId, + }; + + return vc.loadFromMenuConfig(loadOpts, cb); } - ++customMciId; + this.viewControllers[name].setFocus(true); + return cb(null); } -}; \ No newline at end of file + + prepViewControllerWithArt(name, formId, options, cb) { + this.displayAsset( + this.menuConfig.config.art[name], + options, + (err, artData) => { + if(err) { + return cb(err); + } + + return this.prepViewController(name, formId, artData, cb); + } + ); + } + + pausePrompt(position, cb) { + if(!cb && _.isFunction(position)) { + cb = position; + position = null; + } + + if(position) { + position.x = position.row || position.x || 1; + position.y = position.col || position.y || 1; + + this.client.term.rawWrite(ansi.goto(position.x, position.y)); + } + + return theme.displayThemedPause( { client : this.client }, cb); + } + + setViewText(formName, mciId, text, appendMultiLine) { + const view = this.viewControllers[formName].getView(mciId); + if(!view) { + return; + } + + if(appendMultiLine && (view instanceof MultiLineEditTextView)) { + view.addText(text); + } else { + view.setText(text); + } + } + + updateCustomViewTextsWithFilter(formName, startId, fmtObj, options) { + options = options || {}; + + let textView; + let customMciId = startId; + const config = this.menuConfig.config; + + while( (textView = this.viewControllers[formName].getView(customMciId)) ) { + const key = `${formName}InfoFormat${customMciId}`; // e.g. "mainInfoFormat10" + const format = config[key]; + + if(format && (!options.filter || options.filter.find(f => format.indexOf(f) > - 1))) { + const text = stringFormat(format, fmtObj); + + if(options.appendMultiLine && (textView instanceof MultiLineEditTextView)) { + textView.addText(text); + } else { + textView.setText(text); + } + } + + ++customMciId; + } + } +}; diff --git a/core/mod_mixins.js b/core/mod_mixins.js index 00ec452b..291e0cc9 100644 --- a/core/mod_mixins.js +++ b/core/mod_mixins.js @@ -1,56 +1,31 @@ /* jslint node: true */ 'use strict'; -const messageArea = require('../core/message_area.js'); +const messageArea = require('../core/message_area.js'); -// deps -const assert = require('assert'); -// -// A simple mixin for View Controller management -// -exports.ViewControllerManagement = function() { - this.initViewControllers = function() { - this.viewControllers = {}; - }; - - this.detachViewControllers = function() { - var self = this; - Object.keys(this.viewControllers).forEach(function vc(name) { - self.viewControllers[name].detachClientEvents(); - }); - }; - - this.addViewController = function(name, vc) { - assert(this.viewControllers, 'initViewControllers() has not been called!'); - assert(!this.viewControllers[name], 'ViewController by the name of \'' + name + '\' already exists!'); - - this.viewControllers[name] = vc; - return vc; - }; -}; - -exports.MessageAreaConfTempSwitcher = function() { +exports.MessageAreaConfTempSwitcher = Sup => class extends Sup { - this.tempMessageConfAndAreaSwitch = function(messageAreaTag) { + tempMessageConfAndAreaSwitch(messageAreaTag) { messageAreaTag = messageAreaTag || this.messageAreaTag; if(!messageAreaTag) { return; // nothing to do! } + this.prevMessageConfAndArea = { confTag : this.client.user.properties.message_conf_tag, areaTag : this.client.user.properties.message_area_tag, }; + if(!messageArea.tempChangeMessageConfAndArea(this.client, this.messageAreaTag)) { this.client.log.warn( { messageAreaTag : messageArea }, 'Failed to perform temporary message area/conf switch'); } - }; + } - this.tempMessageConfAndAreaRestore = function() { + tempMessageConfAndAreaRestore() { if(this.prevMessageConfAndArea) { this.client.user.properties.message_conf_tag = this.prevMessageConfAndArea.confTag; this.client.user.properties.message_area_tag = this.prevMessageConfAndArea.areaTag; } - }; - + } }; diff --git a/core/new_scan.js b/core/new_scan.js index f38196e4..abcc43b2 100644 --- a/core/new_scan.js +++ b/core/new_scan.js @@ -17,8 +17,6 @@ exports.moduleInfo = { author : 'NuSkooler', }; -exports.getModule = NewScanModule; - /* * :TODO: * * User configurable new scan: Area selection (avail from messages area) (sep module) @@ -27,48 +25,45 @@ exports.getModule = NewScanModule; */ -var MciCodeIds = { +const MciCodeIds = { ScanStatusLabel : 1, // TL1 ScanStatusList : 2, // VM2 (appends) }; -function NewScanModule(options) { - MenuModule.call(this, options); +exports.getModule = class NewScanModule extends MenuModule { + constructor(options) { + super(options); - var self = this; - var config = this.menuConfig.config; + this.newScanFullExit = _.has(options, 'lastMenuResult.fullExit') ? options.lastMenuResult.fullExit : false; - this.newScanFullExit = _.has(options, 'lastMenuResult.fullExit') ? options.lastMenuResult.fullExit : false; + this.currentStep = 'messageConferences'; + this.currentScanAux = {}; - this.currentStep = 'messageConferences'; - this.currentScanAux = {}; + // :TODO: Make this conf/area specific: + const config = this.menuConfig.config; + this.scanStartFmt = config.scanStartFmt || 'Scanning {confName} - {areaName}...'; + this.scanFinishNoneFmt = config.scanFinishNoneFmt || 'Nothing new'; + this.scanFinishNewFmt = config.scanFinishNewFmt || '{count} entries found'; + this.scanCompleteMsg = config.scanCompleteMsg || 'Finished newscan'; + } - // :TODO: Make this conf/area specific: - this.scanStartFmt = config.scanStartFmt || 'Scanning {confName} - {areaName}...'; - this.scanFinishNoneFmt = config.scanFinishNoneFmt || 'Nothing new'; - this.scanFinishNewFmt = config.scanFinishNewFmt || '{count} entries found'; - this.scanCompleteMsg = config.scanCompleteMsg || 'Finished newscan'; - - this.updateScanStatus = function(statusText) { - var vc = self.viewControllers.allViews; - - var view = vc.getView(MciCodeIds.ScanStatusLabel); - if(view) { - view.setText(statusText); - } + updateScanStatus(statusText) { + this.setViewText('allViews', MciCodeIds.ScanStatusLabel, statusText); + /* view = vc.getView(MciCodeIds.ScanStatusList); // :TODO: MenuView needs appendItem() if(view) { } - }; + */ + } - this.newScanMessageConference = function(cb) { + newScanMessageConference(cb) { // lazy init - if(!self.sortedMessageConfs) { + if(!this.sortedMessageConfs) { const getAvailOpts = { includeSystemInternal : true }; // find new private messages, bulletins, etc. - self.sortedMessageConfs = _.map(msgArea.getAvailableMessageConferences(self.client, getAvailOpts), (v, k) => { + this.sortedMessageConfs = _.map(msgArea.getAvailableMessageConferences(this.client, getAvailOpts), (v, k) => { return { confTag : k, conf : v, @@ -80,7 +75,7 @@ function NewScanModule(options) { // always come first such that we display private mails/etc. before // other conferences & areas // - self.sortedMessageConfs.sort((a, b) => { + this.sortedMessageConfs.sort((a, b) => { if('system_internal' === a.confTag) { return -1; } else { @@ -88,11 +83,12 @@ function NewScanModule(options) { } }); - self.currentScanAux.conf = self.currentScanAux.conf || 0; - self.currentScanAux.area = self.currentScanAux.area || 0; + this.currentScanAux.conf = this.currentScanAux.conf || 0; + this.currentScanAux.area = this.currentScanAux.area || 0; } - const currentConf = self.sortedMessageConfs[self.currentScanAux.conf]; + const currentConf = this.sortedMessageConfs[this.currentScanAux.conf]; + const self = this; async.series( [ @@ -113,19 +109,22 @@ function NewScanModule(options) { }); } ], - cb + err => { + return cb(err); + } ); - }; + } - this.newScanMessageArea = function(conf, cb) { + newScanMessageArea(conf, cb) { // :TODO: it would be nice to cache this - must be done by conf! - const sortedAreas = msgArea.getSortedAvailMessageAreasByConfTag(conf.confTag, { client : self.client } ); - const currentArea = sortedAreas[self.currentScanAux.area]; + const sortedAreas = msgArea.getSortedAvailMessageAreasByConfTag(conf.confTag, { client : this.client } ); + const currentArea = sortedAreas[this.currentScanAux.area]; // // Scan and update index until we find something. If results are found, // we'll goto the list module & show them. // + const self = this; async.waterfall( [ function checkAndUpdateIndex(callback) { @@ -165,73 +164,73 @@ function NewScanModule(options) { } }; - return self.gotoMenu(config.newScanMessageList || 'newScanMessageList', nextModuleOpts); + return self.gotoMenu(self.menuConfig.config.newScanMessageList || 'newScanMessageList', nextModuleOpts); } ], - cb // no more areas + err => { + return cb(err); + } ); - }; - -} - -require('util').inherits(NewScanModule, MenuModule); - -NewScanModule.prototype.getSaveState = function() { - return { - currentStep : this.currentStep, - currentScanAux : this.currentScanAux, - }; -}; - -NewScanModule.prototype.restoreSavedState = function(savedState) { - this.currentStep = savedState.currentStep; - this.currentScanAux = savedState.currentScanAux; -}; - -NewScanModule.prototype.mciReady = function(mciData, cb) { - - if(this.newScanFullExit) { - // user has canceled the entire scan @ message list view - return cb(null); } + getSaveState() { + return { + currentStep : this.currentStep, + currentScanAux : this.currentScanAux, + }; + } - var self = this; - var vc = self.viewControllers.allViews = new ViewController( { client : self.client } ); + restoreSavedState(savedState) { + this.currentStep = savedState.currentStep; + this.currentScanAux = savedState.currentScanAux; + } - // :TODO: display scan step/etc. - - async.series( - [ - function callParentMciReady(callback) { - NewScanModule.super_.prototype.mciReady.call(self, mciData, callback); - }, - function loadFromConfig(callback) { - const loadOpts = { - callingMenu : self, - mciMap : mciData.menu, - noInput : true, - }; - - vc.loadFromMenuConfig(loadOpts, callback); - }, - function performCurrentStepScan(callback) { - switch(self.currentStep) { - case 'messageConferences' : - self.newScanMessageConference( () => { - callback(null); // finished - }); - break; - - default : return callback(null); - } - } - ], - function complete(err) { - if(err) { - self.client.log.error( { error : err.toString() }, 'Error during new scan'); - } - cb(err); + mciReady(mciData, cb) { + if(this.newScanFullExit) { + // user has canceled the entire scan @ message list view + return cb(null); } - ); + + super.mciReady(mciData, err => { + if(err) { + return cb(err); + } + + const self = this; + const vc = self.viewControllers.allViews = new ViewController( { client : self.client } ); + + // :TODO: display scan step/etc. + + async.series( + [ + function loadFromConfig(callback) { + const loadOpts = { + callingMenu : self, + mciMap : mciData.menu, + noInput : true, + }; + + vc.loadFromMenuConfig(loadOpts, callback); + }, + function performCurrentStepScan(callback) { + switch(self.currentStep) { + case 'messageConferences' : + self.newScanMessageConference( () => { + callback(null); // finished + }); + break; + + default : return callback(null); + } + } + ], + err => { + if(err) { + self.client.log.error( { error : err.toString() }, 'Error during new scan'); + } + return cb(err); + } + ); + }); + } }; diff --git a/core/standard_menu.js b/core/standard_menu.js index 4d4b8819..ddaebff3 100644 --- a/core/standard_menu.js +++ b/core/standard_menu.js @@ -1,9 +1,7 @@ /* jslint node: true */ 'use strict'; -var MenuModule = require('./menu_module.js').MenuModule; - -exports.getModule = StandardMenuModule; +const MenuModule = require('./menu_module.js').MenuModule; exports.moduleInfo = { name : 'Standard Menu Module', @@ -11,30 +9,19 @@ exports.moduleInfo = { author : 'NuSkooler', }; -function StandardMenuModule(menuConfig) { - MenuModule.call(this, menuConfig); -} +exports.getModule = class StandardMenuModule extends MenuModule { + constructor(options) { + super(options); + } -require('util').inherits(StandardMenuModule, MenuModule); + mciReady(mciData, cb) { + super.mciReady(mciData, err => { + if(err) { + return cb(err); + } - -StandardMenuModule.prototype.enter = function() { - StandardMenuModule.super_.prototype.enter.call(this); -}; - -StandardMenuModule.prototype.beforeArt = function(cb) { - StandardMenuModule.super_.prototype.beforeArt.call(this, cb); -}; - -StandardMenuModule.prototype.mciReady = function(mciData, cb) { - var self = this; - - StandardMenuModule.super_.prototype.mciReady.call(this, mciData, function mciReadyComplete(err) { - if(err) { - cb(err); - } else { // we do this so other modules can be both customized and still perform standard tasks - StandardMenuModule.super_.prototype.standardMCIReadyHandler.call(self, mciData, cb); - } - }); + return this.standardMCIReadyHandler(mciData, cb); + }); + } }; diff --git a/core/user_config.js b/core/user_config.js index 901ec80f..432cdade 100644 --- a/core/user_config.js +++ b/core/user_config.js @@ -1,17 +1,15 @@ /* jslint node: true */ 'use strict'; -var MenuModule = require('./menu_module.js').MenuModule; -var ViewController = require('./view_controller.js').ViewController; -var theme = require('./theme.js'); -var sysValidate = require('./system_view_validate.js'); +const MenuModule = require('./menu_module.js').MenuModule; +const ViewController = require('./view_controller.js').ViewController; +const theme = require('./theme.js'); +const sysValidate = require('./system_view_validate.js'); -var async = require('async'); -var assert = require('assert'); -var _ = require('lodash'); -var moment = require('moment'); - -exports.getModule = UserConfigModule; +const async = require('async'); +const assert = require('assert'); +const _ = require('lodash'); +const moment = require('moment'); exports.moduleInfo = { name : 'User Configuration', @@ -19,7 +17,7 @@ exports.moduleInfo = { author : 'NuSkooler', }; -var MciCodeIds = { +const MciCodeIds = { RealName : 1, BirthDate : 2, Sex : 3, @@ -37,192 +35,187 @@ var MciCodeIds = { SaveCancel : 25, }; -function UserConfigModule(options) { - MenuModule.call(this, options); +exports.getModule = class UserConfigModule extends MenuModule { + constructor(options) { + super(options); - var self = this; - - self.getView = function(viewId) { - return self.viewControllers.menu.getView(viewId); - }; + const self = this; - self.setViewText = function(viewId, text) { - var v = self.getView(viewId); - if(v) { - v.setText(text); - } - }; - - this.menuMethods = { - // - // Validation support - // - validateEmailAvail : function(data, cb) { + this.menuMethods = { // - // If nothing changed, we know it's OK + // Validation support // - if(self.client.user.properties.email_address.toLowerCase() === data.toLowerCase()) { - return cb(null); - } - - // Otherwise we can use the standard system method - return sysValidate.validateEmailAvail(data, cb); - }, - - validatePassword : function(data, cb) { - // - // Blank is OK - this means we won't be changing it - // - if(!data || 0 === data.length) { - return cb(null); - } - - // Otherwise we can use the standard system method - return sysValidate.validatePasswordSpec(data, cb); - }, - - validatePassConfirmMatch : function(data, cb) { - var passwordView = self.getView(MciCodeIds.Password); - cb(passwordView.getData() === data ? null : new Error('Passwords do not match')); - }, - - viewValidationListener : function(err, cb) { - var errMsgView = self.getView(MciCodeIds.ErrorMsg); - var newFocusId; - if(errMsgView) { - if(err) { - errMsgView.setText(err.message); - - if(err.view.getId() === MciCodeIds.PassConfirm) { - newFocusId = MciCodeIds.Password; - var passwordView = self.getView(MciCodeIds.Password); - passwordView.clearText(); - err.view.clearText(); - } - } else { - errMsgView.clearText(); - } - } - cb(newFocusId); - }, - - // - // Handlers - // - saveChanges : function(formData, extraArgs, cb) { - assert(formData.value.password === formData.value.passwordConfirm); - - const newProperties = { - real_name : formData.value.realName, - birthdate : new Date(Date.parse(formData.value.birthdate)).toISOString(), - sex : formData.value.sex, - location : formData.value.location, - affiliation : formData.value.affils, - email_address : formData.value.email, - web_address : formData.value.web, - term_height : formData.value.termHeight.toString(), - theme_id : self.availThemeInfo[formData.value.theme].themeId, - }; - - // runtime set theme - theme.setClientTheme(self.client, newProperties.theme_id); - - // persist all changes - self.client.user.persistProperties(newProperties, err => { - if(err) { - self.client.log.warn( { error : err.toString() }, 'Failed persisting updated properties'); - // :TODO: warn end user! - return self.prevMenu(cb); - } + validateEmailAvail : function(data, cb) { // - // New password if it's not empty + // If nothing changed, we know it's OK // - self.client.log.info('User updated properties'); - - if(formData.value.password.length > 0) { - self.client.user.setNewAuthCredentials(formData.value.password, err => { - if(err) { - self.client.log.error( { err : err }, 'Failed storing new authentication credentials'); - } else { - self.client.log.info('User changed authentication credentials'); - } - return self.prevMenu(cb); - }); - } else { - return self.prevMenu(cb); + if(self.client.user.properties.email_address.toLowerCase() === data.toLowerCase()) { + return cb(null); } - }); - }, - }; -} - -require('util').inherits(UserConfigModule, MenuModule); - -UserConfigModule.prototype.mciReady = function(mciData, cb) { - var self = this; - var vc = self.viewControllers.menu = new ViewController( { client : self.client} ); - - var currentThemeIdIndex = 0; - - async.series( - [ - function callParentMciReady(callback) { - UserConfigModule.super_.prototype.mciReady.call(self, mciData, callback); - }, - function loadFromConfig(callback) { - vc.loadFromMenuConfig( { callingMenu : self, mciMap : mciData.menu }, callback); - }, - function prepareAvailableThemes(callback) { - self.availThemeInfo = _.sortBy(_.map(theme.getAvailableThemes(), function makeThemeInfo(t, themeId) { - return { - themeId : themeId, - name : t.info.name, - author : t.info.author, - desc : _.isString(t.info.desc) ? t.info.desc : '', - group : _.isString(t.info.group) ? t.info.group : '', - }; - }), 'name'); - currentThemeIdIndex = _.findIndex(self.availThemeInfo, function cmp(ti) { - return ti.themeId === self.client.user.properties.theme_id; - }); - - callback(null); + // Otherwise we can use the standard system method + return sysValidate.validateEmailAvail(data, cb); }, - function populateViews(callback) { - var user = self.client.user; - - self.setViewText(MciCodeIds.RealName, user.properties.real_name); - self.setViewText(MciCodeIds.BirthDate, moment(user.properties.birthdate).format('YYYYMMDD')); - self.setViewText(MciCodeIds.Sex, user.properties.sex); - self.setViewText(MciCodeIds.Loc, user.properties.location); - self.setViewText(MciCodeIds.Affils, user.properties.affiliation); - self.setViewText(MciCodeIds.Email, user.properties.email_address); - self.setViewText(MciCodeIds.Web, user.properties.web_address); - self.setViewText(MciCodeIds.TermHeight, user.properties.term_height.toString()); + + validatePassword : function(data, cb) { + // + // Blank is OK - this means we won't be changing it + // + if(!data || 0 === data.length) { + return cb(null); + } + + // Otherwise we can use the standard system method + return sysValidate.validatePasswordSpec(data, cb); + }, + + validatePassConfirmMatch : function(data, cb) { + var passwordView = self.getView(MciCodeIds.Password); + cb(passwordView.getData() === data ? null : new Error('Passwords do not match')); + }, + + viewValidationListener : function(err, cb) { + var errMsgView = self.getView(MciCodeIds.ErrorMsg); + var newFocusId; + if(errMsgView) { + if(err) { + errMsgView.setText(err.message); - - var themeView = self.getView(MciCodeIds.Theme); - if(themeView) { - themeView.setItems(_.map(self.availThemeInfo, 'name')); - themeView.setFocusItemIndex(currentThemeIdIndex); + if(err.view.getId() === MciCodeIds.PassConfirm) { + newFocusId = MciCodeIds.Password; + var passwordView = self.getView(MciCodeIds.Password); + passwordView.clearText(); + err.view.clearText(); + } + } else { + errMsgView.clearText(); + } } + cb(newFocusId); + }, + + // + // Handlers + // + saveChanges : function(formData, extraArgs, cb) { + assert(formData.value.password === formData.value.passwordConfirm); - var realNameView = self.getView(MciCodeIds.RealName); - if(realNameView) { - realNameView.setFocus(true); // :TODO: HACK! menu.hjson sets focus, but manual population above breaks this. Needs a real fix! - } + const newProperties = { + real_name : formData.value.realName, + birthdate : new Date(Date.parse(formData.value.birthdate)).toISOString(), + sex : formData.value.sex, + location : formData.value.location, + affiliation : formData.value.affils, + email_address : formData.value.email, + web_address : formData.value.web, + term_height : formData.value.termHeight.toString(), + theme_id : self.availThemeInfo[formData.value.theme].themeId, + }; - callback(null); - } - ], - function complete(err) { + // runtime set theme + theme.setClientTheme(self.client, newProperties.theme_id); + + // persist all changes + self.client.user.persistProperties(newProperties, err => { + if(err) { + self.client.log.warn( { error : err.toString() }, 'Failed persisting updated properties'); + // :TODO: warn end user! + return self.prevMenu(cb); + } + // + // New password if it's not empty + // + self.client.log.info('User updated properties'); + + if(formData.value.password.length > 0) { + self.client.user.setNewAuthCredentials(formData.value.password, err => { + if(err) { + self.client.log.error( { err : err }, 'Failed storing new authentication credentials'); + } else { + self.client.log.info('User changed authentication credentials'); + } + return self.prevMenu(cb); + }); + } else { + return self.prevMenu(cb); + } + }); + }, + }; + } + + getView(viewId) { + return this.viewControllers.menu.getView(viewId); + } + + mciReady(mciData, cb) { + super.mciReady(mciData, err => { if(err) { - self.client.log.warn( { error : err.toString() }, 'User configuration failed to init'); - self.prevMenu(); - } else { - cb(null); + return cb(err); } - } - ); + + const self = this; + const vc = self.viewControllers.menu = new ViewController( { client : self.client} ); + let currentThemeIdIndex = 0; + + async.series( + [ + function loadFromConfig(callback) { + vc.loadFromMenuConfig( { callingMenu : self, mciMap : mciData.menu }, callback); + }, + function prepareAvailableThemes(callback) { + self.availThemeInfo = _.sortBy(_.map(theme.getAvailableThemes(), function makeThemeInfo(t, themeId) { + return { + themeId : themeId, + name : t.info.name, + author : t.info.author, + desc : _.isString(t.info.desc) ? t.info.desc : '', + group : _.isString(t.info.group) ? t.info.group : '', + }; + }), 'name'); + + currentThemeIdIndex = _.findIndex(self.availThemeInfo, function cmp(ti) { + return ti.themeId === self.client.user.properties.theme_id; + }); + + callback(null); + }, + function populateViews(callback) { + var user = self.client.user; + + self.setViewText('menu', MciCodeIds.RealName, user.properties.real_name); + self.setViewText('menu', MciCodeIds.BirthDate, moment(user.properties.birthdate).format('YYYYMMDD')); + self.setViewText('menu', MciCodeIds.Sex, user.properties.sex); + self.setViewText('menu', MciCodeIds.Loc, user.properties.location); + self.setViewText('menu', MciCodeIds.Affils, user.properties.affiliation); + self.setViewText('menu', MciCodeIds.Email, user.properties.email_address); + self.setViewText('menu', MciCodeIds.Web, user.properties.web_address); + self.setViewText('menu', MciCodeIds.TermHeight, user.properties.term_height.toString()); + + + var themeView = self.getView(MciCodeIds.Theme); + if(themeView) { + themeView.setItems(_.map(self.availThemeInfo, 'name')); + themeView.setFocusItemIndex(currentThemeIdIndex); + } + + var realNameView = self.getView(MciCodeIds.RealName); + if(realNameView) { + realNameView.setFocus(true); // :TODO: HACK! menu.hjson sets focus, but manual population above breaks this. Needs a real fix! + } + + callback(null); + } + ], + function complete(err) { + if(err) { + self.client.log.warn( { error : err.toString() }, 'User configuration failed to init'); + self.prevMenu(); + } else { + cb(null); + } + } + ); + }); + } }; diff --git a/mods/abracadabra.js b/mods/abracadabra.js index 08b3f2c2..4b9afdbf 100644 --- a/mods/abracadabra.js +++ b/mods/abracadabra.js @@ -1,23 +1,21 @@ /* jslint node: true */ 'use strict'; -let MenuModule = require('../core/menu_module.js').MenuModule; -let DropFile = require('../core/dropfile.js').DropFile; -let door = require('../core/door.js'); -let theme = require('../core/theme.js'); -let ansi = require('../core/ansi_term.js'); +const MenuModule = require('../core/menu_module.js').MenuModule; +const DropFile = require('../core/dropfile.js').DropFile; +const door = require('../core/door.js'); +const theme = require('../core/theme.js'); +const ansi = require('../core/ansi_term.js'); -let async = require('async'); -let assert = require('assert'); -let paths = require('path'); -let _ = require('lodash'); -let mkdirs = require('fs-extra').mkdirs; +const async = require('async'); +const assert = require('assert'); +const paths = require('path'); +const _ = require('lodash'); +const mkdirs = require('fs-extra').mkdirs; // :TODO: This should really be a system module... needs a little work to allow for such -exports.getModule = AbracadabraModule; - -let activeDoorNodeInstances = {}; +const activeDoorNodeInstances = {}; exports.moduleInfo = { name : 'Abracadabra', @@ -60,20 +58,20 @@ exports.moduleInfo = { :TODO: See Mystic & others for other arg options that we may need to support */ -function AbracadabraModule(options) { - MenuModule.call(this, options); - let self = this; +exports.getModule = class AbracadabraModule extends MenuModule { + constructor(options) { + super(options); - this.config = options.menuConfig.config; + this.config = options.menuConfig.config; + // :TODO: MenuModule.validateConfig(cb) -- validate config section gracefully instead of asserts! -- { key : type, key2 : type2, ... } + assert(_.isString(this.config.name, 'Config \'name\' is required')); + assert(_.isString(this.config.dropFileType, 'Config \'dropFileType\' is required')); + assert(_.isString(this.config.cmd, 'Config \'cmd\' is required')); - // :TODO: MenuModule.validateConfig(cb) -- validate config section gracefully instead of asserts! - assert(_.isString(this.config.name, 'Config \'name\' is required')); - assert(_.isString(this.config.dropFileType, 'Config \'dropFileType\' is required')); - assert(_.isString(this.config.cmd, 'Config \'cmd\' is required')); - - this.config.nodeMax = this.config.nodeMax || 0; - this.config.args = this.config.args || []; + this.config.nodeMax = this.config.nodeMax || 0; + this.config.args = this.config.args || []; + } /* :TODO: @@ -82,7 +80,9 @@ function AbracadabraModule(options) { * Font support ala all other menus... or does this just work? */ - this.initSequence = function() { + initSequence() { + const self = this; + async.series( [ function validateNodeCount(callback) { @@ -148,54 +148,51 @@ function AbracadabraModule(options) { } } ); - }; + } - this.runDoor = function() { + runDoor() { const exeInfo = { - cmd : self.config.cmd, - args : self.config.args, - io : self.config.io || 'stdio', - encoding : self.config.encoding || self.client.term.outputEncoding, - dropFile : self.dropFile.fileName, - node : self.client.node, - //inhSocket : self.client.output._handle.fd, + cmd : this.config.cmd, + args : this.config.args, + io : this.config.io || 'stdio', + encoding : this.config.encoding || this.client.term.outputEncoding, + dropFile : this.dropFile.fileName, + node : this.client.node, + //inhSocket : this.client.output._handle.fd, }; - const doorInstance = new door.Door(self.client, exeInfo); + const doorInstance = new door.Door(this.client, exeInfo); doorInstance.once('finished', () => { // // Try to clean up various settings such as scroll regions that may // have been set within the door // - self.client.term.rawWrite( + this.client.term.rawWrite( ansi.normal() + - ansi.goto(self.client.term.termHeight, self.client.term.termWidth) + + ansi.goto(this.client.term.termHeight, this.client.term.termWidth) + ansi.setScrollRegion() + - ansi.goto(self.client.term.termHeight, 0) + + ansi.goto(this.client.term.termHeight, 0) + '\r\n\r\n' ); - self.prevMenu(); + this.prevMenu(); }); - self.client.term.write(ansi.resetScreen()); + this.client.term.write(ansi.resetScreen()); doorInstance.run(); - }; -} + } -require('util').inherits(AbracadabraModule, MenuModule); + leave() { + super.leave(); + if(!this.lastError) { + activeDoorNodeInstances[this.config.name] -= 1; + } + } -AbracadabraModule.prototype.leave = function() { - AbracadabraModule.super_.prototype.leave.call(this); - - if(!this.lastError) { - activeDoorNodeInstances[this.config.name] -= 1; + finishedLoading() { + this.runDoor(); } }; - -AbracadabraModule.prototype.finishedLoading = function() { - this.runDoor(); -}; \ No newline at end of file diff --git a/mods/art_pool.js b/mods/art_pool.js deleted file mode 100644 index 8b0020fd..00000000 --- a/mods/art_pool.js +++ /dev/null @@ -1,33 +0,0 @@ -/* jslint node: true */ -'use strict'; - -var MenuModule = require('../core/menu_module.js').MenuModule; - - -exports.getModule = ArtPoolModule; - -exports.moduleInfo = { - name : 'Art Pool', - desc : 'Display art from a pool of options', - author : 'NuSkooler', -}; - -function ArtPoolModule(options) { - MenuModule.call(this, options); - - var config = this.menuConfig.config; - - // - // :TODO: General idea - // * Break up some of MenuModule initSequence's calls into methods - // * initSequence here basically has general "clear", "next", etc. as per normal - // * Display art -> ooptinal pause -> display more if requested, etc. - // * Finally exit & move on as per normal - -} - -require('util').inherits(ArtPoolModule, MenuModule); - -MessageAreaModule.prototype.mciReady = function(mciData, cb) { - this.standardMCIReadyHandler(mciData, cb); -}; diff --git a/mods/bbs_link.js b/mods/bbs_link.js index 92bab4e8..0cf0a5db 100644 --- a/mods/bbs_link.js +++ b/mods/bbs_link.js @@ -36,28 +36,26 @@ const packageJson = require('../package.json'); // :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 = BBSLinkModule; - exports.moduleInfo = { name : 'BBSLink', desc : 'BBSLink Access Module', author : 'NuSkooler', }; +exports.getModule = class BBSLinkModule extends MenuModule { + constructor(options) { + super(options); -function BBSLinkModule(options) { - MenuModule.call(this, options); + this.config = options.menuConfig.config; + this.config.host = this.config.host || 'games.bbslink.net'; + this.config.port = this.config.port || 23; + } - var self = this; - this.config = options.menuConfig.config; - - this.config.host = this.config.host || 'games.bbslink.net'; - this.config.port = this.config.port || 23; - - this.initSequence = function() { - var token; - var randomKey; - var clientTerminated; + initSequence() { + let token; + let randomKey; + let clientTerminated; + const self = this; async.series( [ @@ -180,17 +178,17 @@ function BBSLinkModule(options) { } } ); - }; + } - this.simpleHttpRequest = function(path, headers, cb) { - var getOpts = { + simpleHttpRequest(path, headers, cb) { + const getOpts = { host : this.config.host, path : path, headers : headers, }; - var req = http.get(getOpts, function response(resp) { - var data = ''; + const req = http.get(getOpts, function response(resp) { + let data = ''; resp.on('data', function chunk(c) { data += c; @@ -205,7 +203,5 @@ function BBSLinkModule(options) { req.on('error', function reqErr(err) { cb(err); }); - }; -} - -require('util').inherits(BBSLinkModule, MenuModule); \ No newline at end of file + } +}; diff --git a/mods/bbs_list.js b/mods/bbs_list.js index 9b37b24e..43ff3135 100644 --- a/mods/bbs_list.js +++ b/mods/bbs_list.js @@ -17,17 +17,13 @@ const _ = require('lodash'); // :TODO: add notes field -exports.getModule = BBSListModule; - -const moduleInfo = { +const moduleInfo = exports.moduleInfo = { name : 'BBS List', desc : 'List of other BBSes', author : 'Andrew Pamment', packageName : 'com.magickabbs.enigma.bbslist' }; -exports.moduleInfo = moduleInfo; - const MciViewIds = { view : { BBSList : 1, @@ -69,13 +65,106 @@ const SELECTED_MCI_NAME_TO_ENTRY = { SelectedBBSNotes : 'notes', }; -function BBSListModule(options) { - MenuModule.call(this, options); +exports.getModule = class BBSListModule extends MenuModule { + constructor(options) { + super(options); - const self = this; - const config = this.menuConfig.config; + const self = this; + this.menuMethods = { + // + // Validators + // + viewValidationListener : function(err, cb) { + const errMsgView = self.viewControllers.add.getView(MciViewIds.add.Error); + if(errMsgView) { + if(err) { + errMsgView.setText(err.message); + } else { + errMsgView.clearText(); + } + } - this.initSequence = function() { + return cb(null); + }, + + // + // Key & submit handlers + // + addBBS : function(formData, extraArgs, cb) { + self.displayAddScreen(cb); + }, + deleteBBS : function(formData, extraArgs, cb) { + const entriesView = self.viewControllers.view.getView(MciViewIds.view.BBSList); + + if(self.entries[self.selectedBBS].submitterUserId !== self.client.user.userId && !self.client.user.isSysOp()) { + // must be owner or +op + return cb(null); + } + + const entry = self.entries[self.selectedBBS]; + if(!entry) { + return cb(null); + } + + self.database.run( + `DELETE FROM bbs_list + WHERE id=?;`, + [ entry.id ], + err => { + if (err) { + self.client.log.error( { err : err }, 'Error deleting from BBS list'); + } else { + self.entries.splice(self.selectedBBS, 1); + + self.setEntries(entriesView); + + if(self.entries.length > 0) { + entriesView.focusPrevious(); + } + + self.viewControllers.view.redrawAll(); + } + + return cb(null); + } + ); + }, + submitBBS : function(formData, extraArgs, cb) { + + let ok = true; + [ 'BBSName', 'Sysop', 'Telnet' ].forEach( mciName => { + if('' === self.viewControllers.add.getView(MciViewIds.add[mciName]).getData()) { + ok = false; + } + }); + if(!ok) { + // validators should prevent this! + return cb(null); + } + + self.database.run( + `INSERT INTO bbs_list (bbs_name, sysop, telnet, www, location, software, submitter_user_id, notes) + VALUES(?, ?, ?, ?, ?, ?, ?, ?);`, + [ formData.value.name, formData.value.sysop, formData.value.telnet, formData.value.www, formData.value.location, formData.value.software, self.client.user.userId, formData.value.notes ], + err => { + if(err) { + self.client.log.error( { err : err }, 'Error adding to BBS list'); + } + + self.clearAddForm(); + self.displayBBSList(true, cb); + } + ); + }, + cancelSubmit : function(formData, extraArgs, cb) { + self.clearAddForm(); + self.displayBBSList(true, cb); + } + }; + } + + initSequence() { + const self = this; async.series( [ function beforeDisplayArt(callback) { @@ -92,39 +181,42 @@ function BBSListModule(options) { self.finishedLoading(); } ); - }; + } - this.drawSelectedEntry = function(entry) { + drawSelectedEntry(entry) { if(!entry) { Object.keys(SELECTED_MCI_NAME_TO_ENTRY).forEach(mciName => { - self.setViewText(MciViewIds.view[mciName], ''); + this.setViewText('view', MciViewIds.view[mciName], ''); }); } else { - const youSubmittedFormat = config.youSubmittedFormat || '{submitter} (You!)'; + const youSubmittedFormat = this.menuConfig.youSubmittedFormat || '{submitter} (You!)'; Object.keys(SELECTED_MCI_NAME_TO_ENTRY).forEach(mciName => { const t = entry[SELECTED_MCI_NAME_TO_ENTRY[mciName]]; if(MciViewIds.view[mciName]) { - if('SelectedBBSSubmitter' == mciName && entry.submitterUserId == self.client.user.userId) { - self.setViewText(MciViewIds.view.SelectedBBSSubmitter, stringFormat(youSubmittedFormat, entry)); + if('SelectedBBSSubmitter' == mciName && entry.submitterUserId == this.client.user.userId) { + this.setViewText('view',MciViewIds.view.SelectedBBSSubmitter, stringFormat(youSubmittedFormat, entry)); } else { - self.setViewText(MciViewIds.view[mciName], t); + this.setViewText('view',MciViewIds.view[mciName], t); } } }); } - }; + } - this.setEntries = function(entriesView) { + setEntries(entriesView) { + const config = this.menuConfig.config; const listFormat = config.listFormat || '{bbsName}'; const focusListFormat = config.focusListFormat || '{bbsName}'; - entriesView.setItems(self.entries.map( e => stringFormat(listFormat, e) ) ); - entriesView.setFocusItems(self.entries.map( e => stringFormat(focusListFormat, e) ) ); - }; + entriesView.setItems(this.entries.map( e => stringFormat(listFormat, e) ) ); + entriesView.setFocusItems(this.entries.map( e => stringFormat(focusListFormat, e) ) ); + } + + displayBBSList(clearScreen, cb) { + const self = this; - this.displayBBSList = function(clearScreen, cb) { async.waterfall( [ function clearAndDisplayArt(callback) { @@ -135,7 +227,7 @@ function BBSListModule(options) { self.client.term.rawWrite(ansi.resetScreen()); } theme.displayThemedAsset( - config.art.entries, + self.menuConfig.config.art.entries, self.client, { font : self.menuConfig.font, trailingLF : false }, (err, artData) => { @@ -238,9 +330,11 @@ function BBSListModule(options) { } } ); - }; + } + + displayAddScreen(cb) { + const self = this; - this.displayAddScreen = function(cb) { async.waterfall( [ function clearAndDisplayArt(callback) { @@ -248,7 +342,7 @@ function BBSListModule(options) { self.client.term.rawWrite(ansi.resetScreen()); theme.displayThemedAsset( - config.art.add, + self.menuConfig.config.art.add, self.client, { font : self.menuConfig.font }, (err, artData) => { @@ -284,117 +378,17 @@ function BBSListModule(options) { } } ); - }; + } - this.clearAddForm = function() { + clearAddForm() { [ 'BBSName', 'Sysop', 'Telnet', 'Www', 'Location', 'Software', 'Error', 'Notes' ].forEach( mciName => { - const v = self.viewControllers.add.getView(MciViewIds.add[mciName]); - if(v) { - v.setText(''); - } + this.setViewText('add', MciViewIds.add[mciName], ''); }); - }; + } - this.menuMethods = { - // - // Validators - // - viewValidationListener : function(err, cb) { - const errMsgView = self.viewControllers.add.getView(MciViewIds.add.Error); - if(errMsgView) { - if(err) { - errMsgView.setText(err.message); - } else { - errMsgView.clearText(); - } - } + initDatabase(cb) { + const self = this; - return cb(null); - }, - - // - // Key & submit handlers - // - addBBS : function(formData, extraArgs, cb) { - self.displayAddScreen(cb); - }, - deleteBBS : function(formData, extraArgs, cb) { - const entriesView = self.viewControllers.view.getView(MciViewIds.view.BBSList); - - if(self.entries[self.selectedBBS].submitterUserId !== self.client.user.userId && !self.client.user.isSysOp()) { - // must be owner or +op - return cb(null); - } - - const entry = self.entries[self.selectedBBS]; - if(!entry) { - return cb(null); - } - - self.database.run( - `DELETE FROM bbs_list - WHERE id=?;`, - [ entry.id ], - err => { - if (err) { - self.client.log.error( { err : err }, 'Error deleting from BBS list'); - } else { - self.entries.splice(self.selectedBBS, 1); - - self.setEntries(entriesView); - - if(self.entries.length > 0) { - entriesView.focusPrevious(); - } - - self.viewControllers.view.redrawAll(); - } - - return cb(null); - } - ); - }, - submitBBS : function(formData, extraArgs, cb) { - - let ok = true; - [ 'BBSName', 'Sysop', 'Telnet' ].forEach( mciName => { - if('' === self.viewControllers.add.getView(MciViewIds.add[mciName]).getData()) { - ok = false; - } - }); - if(!ok) { - // validators should prevent this! - return cb(null); - } - - self.database.run( - `INSERT INTO bbs_list (bbs_name, sysop, telnet, www, location, software, submitter_user_id, notes) - VALUES(?, ?, ?, ?, ?, ?, ?, ?);`, - [ formData.value.name, formData.value.sysop, formData.value.telnet, formData.value.www, formData.value.location, formData.value.software, self.client.user.userId, formData.value.notes ], - err => { - if(err) { - self.client.log.error( { err : err }, 'Error adding to BBS list'); - } - - self.clearAddForm(); - self.displayBBSList(true, cb); - } - ); - }, - cancelSubmit : function(formData, extraArgs, cb) { - self.clearAddForm(); - self.displayBBSList(true, cb); - } - }; - - this.setViewText = function(id, text) { - var v = self.viewControllers.view.getView(id); - if(v) { - v.setText(text); - } - }; - - this.initDatabase = function(cb) { async.series( [ function openDatabase(callback) { @@ -422,15 +416,15 @@ function BBSListModule(options) { callback(null); } ], - cb + err => { + return cb(err); + } ); - }; -} + } -require('util').inherits(BBSListModule, MenuModule); - -BBSListModule.prototype.beforeArt = function(cb) { - BBSListModule.super_.prototype.beforeArt.call(this, err => { - return err ? cb(err) : this.initDatabase(cb); - }); + beforeArt(cb) { + super.beforeArt(err => { + return err ? cb(err) : this.initDatabase(cb); + }); + } }; diff --git a/mods/erc_client.js b/mods/erc_client.js index dd0f9494..02b42ad5 100644 --- a/mods/erc_client.js +++ b/mods/erc_client.js @@ -1,7 +1,7 @@ /* jslint node: true */ 'use strict'; -var MenuModule = require('../core/menu_module.js').MenuModule; +const MenuModule = require('../core/menu_module.js').MenuModule; const stringFormat = require('../core/string_format.js'); // deps @@ -33,8 +33,9 @@ var MciViewIds = { InputArea : 3, }; +// :TODO: needs converted to ES6 MenuModule subclass function ErcClientModule(options) { - MenuModule.call(this, options); + MenuModule.prototype.ctorShim.call(this, options); const self = this; this.config = options.menuConfig.config; diff --git a/mods/file_base_download_manager.js b/mods/file_base_download_manager.js index 77b6609c..2bd166bf 100644 --- a/mods/file_base_download_manager.js +++ b/mods/file_base_download_manager.js @@ -80,10 +80,7 @@ exports.getModule = class FileBaseDownloadQueueManager extends MenuModule { } // Simply an empty D/L queue: Present a specialized "empty queue" page - // :TODO: This technique can be applied in many areas of the code; probablly need a better name than 'popAndGotoMenu' though - // ...actually, the option to not append to the stack would be better here return this.gotoMenu(this.menuConfig.config.emptyQueueMenu || 'fileBaseDownloadManagerEmptyQueue'); - //return this.popAndGotoMenu(this.menuConfig.config.emptyQueueMenu || 'fileBaseDownloadManagerEmptyQueue'); } const self = this; diff --git a/mods/last_callers.js b/mods/last_callers.js index 36a682a2..bf05fc35 100644 --- a/mods/last_callers.js +++ b/mods/last_callers.js @@ -32,111 +32,112 @@ exports.moduleInfo = { packageName : 'codes.l33t.enigma.lastcallers' // :TODO: concept idea for mods }; -exports.getModule = LastCallersModule; - -var MciCodeIds = { +const MciCodeIds = { CallerList : 1, }; -function LastCallersModule(options) { - MenuModule.call(this, options); -} +exports.getModule = class LastCallersModule extends MenuModule { + constructor(options) { + super(options); + } -require('util').inherits(LastCallersModule, MenuModule); + mciReady(mciData, cb) { + super.mciReady(mciData, err => { + if(err) { + return cb(err); + } -LastCallersModule.prototype.mciReady = function(mciData, cb) { - const self = this; - const vc = self.viewControllers.allViews = new ViewController( { client : self.client } ); + const self = this; + const vc = self.viewControllers.allViews = new ViewController( { client : self.client } ); - let loginHistory; - let callersView; + let loginHistory; + let callersView; - async.series( - [ - function callParentMciReady(callback) { - LastCallersModule.super_.prototype.mciReady.call(self, mciData, callback); - }, - function loadFromConfig(callback) { - const loadOpts = { - callingMenu : self, - mciMap : mciData.menu, - noInput : true, - }; + async.series( + [ + function loadFromConfig(callback) { + const loadOpts = { + callingMenu : self, + mciMap : mciData.menu, + noInput : true, + }; - vc.loadFromMenuConfig(loadOpts, callback); - }, - function fetchHistory(callback) { - callersView = vc.getView(MciCodeIds.CallerList); + vc.loadFromMenuConfig(loadOpts, callback); + }, + function fetchHistory(callback) { + callersView = vc.getView(MciCodeIds.CallerList); - // fetch up - StatLog.getSystemLogEntries('user_login_history', StatLog.Order.TimestampDesc, 200, (err, lh) => { - loginHistory = lh; + // fetch up + StatLog.getSystemLogEntries('user_login_history', StatLog.Order.TimestampDesc, 200, (err, lh) => { + loginHistory = lh; - if(self.menuConfig.config.hideSysOpLogin) { - const noOpLoginHistory = loginHistory.filter(lh => { - return false === isRootUserId(parseInt(lh.log_value)); // log_value=userId - }); + if(self.menuConfig.config.hideSysOpLogin) { + const noOpLoginHistory = loginHistory.filter(lh => { + return false === isRootUserId(parseInt(lh.log_value)); // log_value=userId + }); - // - // If we have enough items to display, or hideSysOpLogin is set to 'always', - // then set loginHistory to our filtered list. Else, we'll leave it be. - // - if(noOpLoginHistory.length >= callersView.dimens.height || 'always' === self.menuConfig.config.hideSysOpLogin) { - loginHistory = noOpLoginHistory; - } - } - - // - // Finally, we need to trim up the list to the needed size - // - loginHistory = loginHistory.slice(0, callersView.dimens.height); - - return callback(err); - }); - }, - function getUserNamesAndProperties(callback) { - const getPropOpts = { - names : [ 'location', 'affiliation' ] - }; - - const dateTimeFormat = self.menuConfig.config.dateTimeFormat || 'ddd MMM DD'; - - async.each( - loginHistory, - (item, next) => { - item.userId = parseInt(item.log_value); - item.ts = moment(item.timestamp).format(dateTimeFormat); - - getUserName(item.userId, (err, userName) => { - item.userName = userName; - getPropOpts.userId = item.userId; - - loadProperties(getPropOpts, (err, props) => { - if(!err) { - item.location = props.location; - item.affiliation = item.affils = props.affiliation; - } - return next(); - }); + // + // If we have enough items to display, or hideSysOpLogin is set to 'always', + // then set loginHistory to our filtered list. Else, we'll leave it be. + // + if(noOpLoginHistory.length >= callersView.dimens.height || 'always' === self.menuConfig.config.hideSysOpLogin) { + loginHistory = noOpLoginHistory; + } + } + + // + // Finally, we need to trim up the list to the needed size + // + loginHistory = loginHistory.slice(0, callersView.dimens.height); + + return callback(err); }); }, - callback - ); - }, - function populateList(callback) { - const listFormat = self.menuConfig.config.listFormat || '{userName} - {location} - {affiliation} - {ts}'; + function getUserNamesAndProperties(callback) { + const getPropOpts = { + names : [ 'location', 'affiliation' ] + }; - callersView.setItems(_.map(loginHistory, ce => stringFormat(listFormat, ce) ) ); + const dateTimeFormat = self.menuConfig.config.dateTimeFormat || 'ddd MMM DD'; - callersView.redraw(); - return callback(null); - } - ], - (err) => { - if(err) { - self.client.log.error( { error : err.toString() }, 'Error loading last callers'); - } - cb(err); - } - ); + async.each( + loginHistory, + (item, next) => { + item.userId = parseInt(item.log_value); + item.ts = moment(item.timestamp).format(dateTimeFormat); + + getUserName(item.userId, (err, userName) => { + item.userName = userName; + getPropOpts.userId = item.userId; + + loadProperties(getPropOpts, (err, props) => { + if(!err) { + item.location = props.location; + item.affiliation = item.affils = props.affiliation; + } + return next(); + }); + }); + }, + callback + ); + }, + function populateList(callback) { + const listFormat = self.menuConfig.config.listFormat || '{userName} - {location} - {affiliation} - {ts}'; + + callersView.setItems(_.map(loginHistory, ce => stringFormat(listFormat, ce) ) ); + + callersView.redraw(); + return callback(null); + } + ], + (err) => { + if(err) { + self.client.log.error( { error : err.toString() }, 'Error loading last callers'); + } + cb(err); + } + ); + }); + } }; diff --git a/mods/msg_area_list.js b/mods/msg_area_list.js index c9ad5afd..cb8d14bb 100644 --- a/mods/msg_area_list.js +++ b/mods/msg_area_list.js @@ -14,8 +14,6 @@ const stringFormat = require('../core/string_format.js'); const async = require('async'); const _ = require('lodash'); -exports.getModule = MessageAreaListModule; - exports.moduleInfo = { name : 'Message Area List', desc : 'Module for listing / choosing message areas', @@ -36,153 +34,146 @@ exports.moduleInfo = { |TI Current time */ -const MCICodesIDs = { +const MciViewIds = { AreaList : 1, SelAreaInfo1 : 2, SelAreaInfo2 : 3, }; -function MessageAreaListModule(options) { - MenuModule.call(this, options); +exports.getModule = class MessageAreaListModule extends MenuModule { + constructor(options) { + super(options); - var self = this; + this.messageAreas = messageArea.getSortedAvailMessageAreasByConfTag( + this.client.user.properties.message_conf_tag, + { client : this.client } + ); - this.messageAreas = messageArea.getSortedAvailMessageAreasByConfTag( - self.client.user.properties.message_conf_tag, - { client : self.client } - ); + const self = this; + this.menuMethods = { + changeArea : function(formData, extraArgs, cb) { + if(1 === formData.submitId) { + let area = self.messageAreas[formData.value.area]; + const areaTag = area.areaTag; + area = area.area; // what we want is actually embedded - this.prevMenuOnTimeout = function(timeout, cb) { - setTimeout( () => { - self.prevMenu(cb); - }, timeout); - }; + messageArea.changeMessageArea(self.client, areaTag, err => { + if(err) { + self.client.term.pipeWrite(`\n|00Cannot change area: ${err.message}\n`); - this.menuMethods = { - changeArea : function(formData, extraArgs, cb) { - if(1 === formData.submitId) { - let area = self.messageAreas[formData.value.area]; - const areaTag = area.areaTag; - area = area.area; // what we want is actually embedded + self.prevMenuOnTimeout(1000, cb); + } else { + if(_.isString(area.art)) { + const dispOptions = { + client : self.client, + name : area.art, + }; - messageArea.changeMessageArea(self.client, areaTag, err => { - if(err) { - self.client.term.pipeWrite(`\n|00Cannot change area: ${err.message}\n`); + self.client.term.rawWrite(resetScreen()); - self.prevMenuOnTimeout(1000, cb); - } else { - if(_.isString(area.art)) { - const dispOptions = { - client : self.client, - name : area.art, - }; - - self.client.term.rawWrite(resetScreen()); - - displayThemeArt(dispOptions, () => { - // pause by default, unless explicitly told not to - if(_.has(area, 'options.pause') && false === area.options.pause) { - return self.prevMenuOnTimeout(1000, cb); - } else { - // :TODO: Use MenuModule.pausePrompt() - displayThemedPause( { client : self.client }, () => { - return self.prevMenu(cb); - }); - } - }); - } else { - return self.prevMenu(cb); + displayThemeArt(dispOptions, () => { + // pause by default, unless explicitly told not to + if(_.has(area, 'options.pause') && false === area.options.pause) { + return self.prevMenuOnTimeout(1000, cb); + } else { + // :TODO: Use MenuModule.pausePrompt() + displayThemedPause( { client : self.client }, () => { + return self.prevMenu(cb); + }); + } + }); + } else { + return self.prevMenu(cb); + } } - } - }); - } else { - return cb(null); + }); + } else { + return cb(null); + } } - } - }; + }; + } - this.setViewText = function(id, text) { - const v = self.viewControllers.areaList.getView(id); - if(v) { - v.setText(text); - } - }; + prevMenuOnTimeout(timeout, cb) { + setTimeout( () => { + return this.prevMenu(cb); + }, timeout); + } - this.updateGeneralAreaInfoViews = function(areaIndex) { + updateGeneralAreaInfoViews(areaIndex) { + // :TODO: these concepts have been replaced with the {someKey} style formatting - update me! /* experimental: not yet avail const areaInfo = self.messageAreas[areaIndex]; - [ MCICodesIDs.SelAreaInfo1, MCICodesIDs.SelAreaInfo2 ].forEach(mciId => { + [ MciViewIds.SelAreaInfo1, MciViewIds.SelAreaInfo2 ].forEach(mciId => { const v = self.viewControllers.areaList.getView(mciId); if(v) { v.setFormatObject(areaInfo.area); } }); */ - }; + } -} - -require('util').inherits(MessageAreaListModule, MenuModule); - -MessageAreaListModule.prototype.mciReady = function(mciData, cb) { - const self = this; - const vc = self.viewControllers.areaList = new ViewController( { client : self.client } ); - - async.series( - [ - function callParentMciReady(callback) { - MessageAreaListModule.super_.prototype.mciReady.call(this, mciData, function parentMciReady(err) { - callback(err); - }); - }, - function loadFromConfig(callback) { - const loadOpts = { - callingMenu : self, - mciMap : mciData.menu, - formId : 0, - }; - - vc.loadFromMenuConfig(loadOpts, function startingViewReady(err) { - callback(err); - }); - }, - function populateAreaListView(callback) { - const listFormat = self.menuConfig.config.listFormat || '{index} ) - {name}'; - const focusListFormat = self.menuConfig.config.focusListFormat || listFormat; - - const areaListView = vc.getView(MCICodesIDs.AreaList); - let i = 1; - areaListView.setItems(_.map(self.messageAreas, v => { - return stringFormat(listFormat, { - index : i++, - areaTag : v.area.areaTag, - name : v.area.name, - desc : v.area.desc, - }); - })); - - i = 1; - areaListView.setFocusItems(_.map(self.messageAreas, v => { - return stringFormat(focusListFormat, { - index : i++, - areaTag : v.area.areaTag, - name : v.area.name, - desc : v.area.desc, - }); - })); - - areaListView.on('index update', areaIndex => { - self.updateGeneralAreaInfoViews(areaIndex); - }); - - areaListView.redraw(); - - callback(null); + mciReady(mciData, cb) { + super.mciReady(mciData, err => { + if(err) { + return cb(err); } - ], - function complete(err) { - return cb(err); - } - ); -}; \ No newline at end of file + + const self = this; + const vc = self.viewControllers.areaList = new ViewController( { client : self.client } ); + + async.series( + [ + function loadFromConfig(callback) { + const loadOpts = { + callingMenu : self, + mciMap : mciData.menu, + formId : 0, + }; + + vc.loadFromMenuConfig(loadOpts, function startingViewReady(err) { + callback(err); + }); + }, + function populateAreaListView(callback) { + const listFormat = self.menuConfig.config.listFormat || '{index} ) - {name}'; + const focusListFormat = self.menuConfig.config.focusListFormat || listFormat; + + const areaListView = vc.getView(MciViewIds.AreaList); + let i = 1; + areaListView.setItems(_.map(self.messageAreas, v => { + return stringFormat(listFormat, { + index : i++, + areaTag : v.area.areaTag, + name : v.area.name, + desc : v.area.desc, + }); + })); + + i = 1; + areaListView.setFocusItems(_.map(self.messageAreas, v => { + return stringFormat(focusListFormat, { + index : i++, + areaTag : v.area.areaTag, + name : v.area.name, + desc : v.area.desc, + }); + })); + + areaListView.on('index update', areaIndex => { + self.updateGeneralAreaInfoViews(areaIndex); + }); + + areaListView.redraw(); + + callback(null); + } + ], + function complete(err) { + return cb(err); + } + ); + }); + } +}; diff --git a/mods/msg_area_post_fse.js b/mods/msg_area_post_fse.js index 8a515895..21b5d068 100644 --- a/mods/msg_area_post_fse.js +++ b/mods/msg_area_post_fse.js @@ -1,15 +1,11 @@ /* jslint node: true */ 'use strict'; -let FullScreenEditorModule = require('../core/fse.js').FullScreenEditorModule; -//var Message = require('../core/message.js').Message; -let persistMessage = require('../core/message_area.js').persistMessage; -let user = require('../core/user.js'); +const FullScreenEditorModule = require('../core/fse.js').FullScreenEditorModule; +const persistMessage = require('../core/message_area.js').persistMessage; -let _ = require('lodash'); -let async = require('async'); - -exports.getModule = AreaPostFSEModule; +const _ = require('lodash'); +const async = require('async'); exports.moduleInfo = { name : 'Message Area Post', @@ -17,56 +13,55 @@ exports.moduleInfo = { author : 'NuSkooler', }; -function AreaPostFSEModule(options) { - FullScreenEditorModule.call(this, options); +exports.getModule = class AreaPostFSEModule extends FullScreenEditorModule { + constructor(options) { + super(options); - var self = this; + const self = this; - // we're posting, so always start with 'edit' mode - this.editorMode = 'edit'; + // we're posting, so always start with 'edit' mode + this.editorMode = 'edit'; - this.menuMethods.editModeMenuSave = function(formData, extraArgs, cb) { + this.menuMethods.editModeMenuSave = function(formData, extraArgs, cb) { - var msg; - async.series( - [ - function getMessageObject(callback) { - self.getMessage(function gotMsg(err, msgObj) { - msg = msgObj; - return callback(err); - }); - }, - function saveMessage(callback) { - return persistMessage(msg, callback); - }, - function updateStats(callback) { - self.updateUserStats(callback); + var msg; + async.series( + [ + function getMessageObject(callback) { + self.getMessage(function gotMsg(err, msgObj) { + msg = msgObj; + return callback(err); + }); + }, + function saveMessage(callback) { + return persistMessage(msg, callback); + }, + function updateStats(callback) { + self.updateUserStats(callback); + } + ], + function complete(err) { + if(err) { + // :TODO:... sooooo now what? + } else { + // note: not logging 'from' here as it's part of client.log.xxxx() + self.client.log.info( + { to : msg.toUserName, subject : msg.subject, uuid : msg.uuid }, + 'Message persisted' + ); + } + + return self.nextMenu(cb); } - ], - function complete(err) { - if(err) { - // :TODO:... sooooo now what? - } else { - // note: not logging 'from' here as it's part of client.log.xxxx() - self.client.log.info( - { to : msg.toUserName, subject : msg.subject, uuid : msg.uuid }, - 'Message persisted' - ); - } - - return self.nextMenu(cb); - } - ); - }; -} - -require('util').inherits(AreaPostFSEModule, FullScreenEditorModule); - -AreaPostFSEModule.prototype.enter = function() { - - if(_.isString(this.client.user.properties.message_area_tag) && !_.isString(this.messageAreaTag)) { - this.messageAreaTag = this.client.user.properties.message_area_tag; + ); + }; } - - AreaPostFSEModule.super_.prototype.enter.call(this); -}; + + enter() { + if(_.isString(this.client.user.properties.message_area_tag) && !_.isString(this.messageAreaTag)) { + this.messageAreaTag = this.client.user.properties.message_area_tag; + } + + super.enter(); + } +}; \ No newline at end of file diff --git a/mods/msg_area_view_fse.js b/mods/msg_area_view_fse.js index a82433b2..51794399 100644 --- a/mods/msg_area_view_fse.js +++ b/mods/msg_area_view_fse.js @@ -8,122 +8,117 @@ const Message = require('../core/message.js'); // deps const _ = require('lodash'); -exports.getModule = AreaViewFSEModule; - exports.moduleInfo = { name : 'Message Area View', desc : 'Module for viewing an area message', author : 'NuSkooler', }; -function AreaViewFSEModule(options) { - FullScreenEditorModule.call(this, options); +exports.getModule = class AreaViewFSEModule extends FullScreenEditorModule { + constructor(options) { + super(options); - const self = this; + this.editorType = 'area'; + this.editorMode = 'view'; - this.editorType = 'area'; - this.editorMode = 'view'; + if(_.isObject(options.extraArgs)) { + this.messageList = options.extraArgs.messageList; + this.messageIndex = options.extraArgs.messageIndex; + } - if(_.isObject(options.extraArgs)) { - this.messageList = options.extraArgs.messageList; - this.messageIndex = options.extraArgs.messageIndex; + this.messageList = this.messageList || []; + this.messageIndex = this.messageIndex || 0; + this.messageTotal = this.messageList.length; + + const self = this; + + this.menuMethods = { + nextMessage : (formData, extraArgs, cb) => { + if(self.messageIndex + 1 < self.messageList.length) { + self.messageIndex++; + + return self.loadMessageByUuid(self.messageList[self.messageIndex].messageUuid, cb); + } + + return cb(null); + }, + + prevMessage : (formData, extraArgs, cb) => { + if(self.messageIndex > 0) { + self.messageIndex--; + + return self.loadMessageByUuid(self.messageList[self.messageIndex].messageUuid, cb); + } + + return cb(null); + }, + + movementKeyPressed : (formData, extraArgs, cb) => { + const bodyView = self.viewControllers.body.getView(1); // :TODO: use const here vs magic # + + // :TODO: Create methods for up/down vs using keyPressXXXXX + switch(formData.key.name) { + case 'down arrow' : bodyView.scrollDocumentUp(); break; + case 'up arrow' : bodyView.scrollDocumentDown(); break; + case 'page up' : bodyView.keyPressPageUp(); break; + case 'page down' : bodyView.keyPressPageDown(); break; + } + + // :TODO: need to stop down/page down if doing so would push the last + // visible page off the screen at all .... this should be handled by MLTEV though... + + return cb(null); + }, + + replyMessage : (formData, extraArgs, cb) => { + if(_.isString(extraArgs.menu)) { + const modOpts = { + extraArgs : { + messageAreaTag : self.messageAreaTag, + replyToMessage : self.message, + } + }; + + return self.gotoMenu(extraArgs.menu, modOpts, cb); + } + + self.client.log(extraArgs, 'Missing extraArgs.menu'); + return cb(null); + } + }; } - this.messageList = this.messageList || []; - this.messageIndex = this.messageIndex || 0; - this.messageTotal = this.messageList.length; - this.menuMethods.nextMessage = function(formData, extraArgs, cb) { - if(self.messageIndex + 1 < self.messageList.length) { - self.messageIndex++; - - return self.loadMessageByUuid(self.messageList[self.messageIndex].messageUuid, cb); - } - - return cb(null); - }; - - this.menuMethods.prevMessage = function(formData, extraArgs, cb) { - if(self.messageIndex > 0) { - self.messageIndex--; - - return self.loadMessageByUuid(self.messageList[self.messageIndex].messageUuid, cb); - } - - return cb(null); - }; - - this.menuMethods.movementKeyPressed = function(formData, extraArgs, cb) { - const bodyView = self.viewControllers.body.getView(1); // :TODO: use const here vs magic # - - // :TODO: Create methods for up/down vs using keyPressXXXXX - switch(formData.key.name) { - case 'down arrow' : bodyView.scrollDocumentUp(); break; - case 'up arrow' : bodyView.scrollDocumentDown(); break; - case 'page up' : bodyView.keyPressPageUp(); break; - case 'page down' : bodyView.keyPressPageDown(); break; - } - - // :TODO: need to stop down/page down if doing so would push the last - // visible page off the screen at all .... this should be handled by MLTEV though... - - return cb(null); - - }; - - this.menuMethods.replyMessage = function(formData, extraArgs, cb) { - if(_.isString(extraArgs.menu)) { - const modOpts = { - extraArgs : { - messageAreaTag : self.messageAreaTag, - replyToMessage : self.message, - } - }; - - return self.gotoMenu(extraArgs.menu, modOpts, cb); - } - - self.client.log(extraArgs, 'Missing extraArgs.menu'); - return cb(null); - }; - - this.loadMessageByUuid = function(uuid, cb) { + loadMessageByUuid(uuid, cb) { const msg = new Message(); - msg.load( { uuid : uuid, user : self.client.user }, () => { - self.setMessage(msg); + msg.load( { uuid : uuid, user : this.client.user }, () => { + this.setMessage(msg); + if(cb) { return cb(null); } }); - }; -} + } -require('util').inherits(AreaViewFSEModule, FullScreenEditorModule); - -AreaViewFSEModule.prototype.finishedLoading = function() { - if(this.messageList.length) { + finishedLoading() { this.loadMessageByUuid(this.messageList[this.messageIndex].messageUuid); } -}; - -AreaViewFSEModule.prototype.getSaveState = function() { - AreaViewFSEModule.super_.prototype.getSaveState.call(this); - - return { - messageList : this.messageList, - messageIndex : this.messageIndex, - messageTotal : this.messageList.length, - }; -}; - -AreaViewFSEModule.prototype.restoreSavedState = function(savedState) { - AreaViewFSEModule.super_.prototype.restoreSavedState.call(this, savedState); - - this.messageList = savedState.messageList; - this.messageIndex = savedState.messageIndex; - this.messageTotal = savedState.messageTotal; -}; - -AreaViewFSEModule.prototype.getMenuResult = function() { - return this.messageIndex; + + getSaveState() { + return { + messageList : this.messageList, + messageIndex : this.messageIndex, + messageTotal : this.messageList.length, + }; + } + + restoreSavedState(savedState) { + this.messageList = savedState.messageList; + this.messageIndex = savedState.messageIndex; + this.messageTotal = savedState.messageTotal; + } + + getMenuResult() { + return this.messageIndex; + } }; diff --git a/mods/msg_conf_list.js b/mods/msg_conf_list.js index e813a154..0d6b6202 100644 --- a/mods/msg_conf_list.js +++ b/mods/msg_conf_list.js @@ -14,15 +14,13 @@ const stringFormat = require('../core/string_format.js'); const async = require('async'); const _ = require('lodash'); -exports.getModule = MessageConfListModule; - exports.moduleInfo = { name : 'Message Conference List', desc : 'Module for listing / choosing message conferences', author : 'NuSkooler', }; -const MCICodeIDs = { +const MciViewIds = { ConfList : 1, // :TODO: @@ -30,128 +28,123 @@ const MCICodeIDs = { // }; -function MessageConfListModule(options) { - MenuModule.call(this, options); +exports.getModule = class MessageConfListModule extends MenuModule { + constructor(options) { + super(options); - var self = this; + this.messageConfs = messageArea.getSortedAvailMessageConferences(this.client); + const self = this; + + this.menuMethods = { + changeConference : function(formData, extraArgs, cb) { + if(1 === formData.submitId) { + let conf = self.messageConfs[formData.value.conf]; + const confTag = conf.confTag; + conf = conf.conf; // what we want is embedded - this.messageConfs = messageArea.getSortedAvailMessageConferences(self.client); + messageArea.changeMessageConference(self.client, confTag, err => { + if(err) { + self.client.term.pipeWrite(`\n|00Cannot change conference: ${err.message}\n`); - this.prevMenuOnTimeout = function(timeout, cb) { - setTimeout( () => { - self.prevMenu(cb); - }, timeout); - }; - - this.menuMethods = { - changeConference : function(formData, extraArgs, cb) { - if(1 === formData.submitId) { - let conf = self.messageConfs[formData.value.conf]; - const confTag = conf.confTag; - conf = conf.conf; // what we want is embedded - - messageArea.changeMessageConference(self.client, confTag, err => { - if(err) { - self.client.term.pipeWrite(`\n|00Cannot change conference: ${err.message}\n`); - - setTimeout( () => { - return self.prevMenu(cb); - }, 1000); - } else { - if(_.isString(conf.art)) { - const dispOptions = { - client : self.client, - name : conf.art, - }; - - self.client.term.rawWrite(resetScreen()); - - displayThemeArt(dispOptions, () => { - // pause by default, unless explicitly told not to - if(_.has(conf, 'options.pause') && false === conf.options.pause) { - return self.prevMenuOnTimeout(1000, cb); - } else { - // :TODO: Use MenuModule.pausePrompt() - displayThemedPause( { client : self.client }, () => { - return self.prevMenu(cb); - }); - } - }); + setTimeout( () => { + return self.prevMenu(cb); + }, 1000); } else { - return self.prevMenu(cb); + if(_.isString(conf.art)) { + const dispOptions = { + client : self.client, + name : conf.art, + }; + + self.client.term.rawWrite(resetScreen()); + + displayThemeArt(dispOptions, () => { + // pause by default, unless explicitly told not to + if(_.has(conf, 'options.pause') && false === conf.options.pause) { + return self.prevMenuOnTimeout(1000, cb); + } else { + // :TODO: Use MenuModule.pausePrompt() + displayThemedPause( { client : self.client }, () => { + return self.prevMenu(cb); + }); + } + }); + } else { + return self.prevMenu(cb); + } } + }); + } else { + return cb(null); + } + } + }; + } + + prevMenuOnTimeout(timeout, cb) { + setTimeout( () => { + return this.prevMenu(cb); + }, timeout); + } + + mciReady(mciData, cb) { + super.mciReady(mciData, err => { + if(err) { + return cb(err); + } + + const self = this; + const vc = self.viewControllers.areaList = new ViewController( { client : self.client } ); + + async.series( + [ + function loadFromConfig(callback) { + let loadOpts = { + callingMenu : self, + mciMap : mciData.menu, + formId : 0, + }; + + vc.loadFromMenuConfig(loadOpts, callback); + }, + function populateConfListView(callback) { + const listFormat = self.menuConfig.config.listFormat || '{index} ) - {name}'; + const focusListFormat = self.menuConfig.config.focusListFormat || listFormat; + + const confListView = vc.getView(MciViewIds.ConfList); + let i = 1; + confListView.setItems(_.map(self.messageConfs, v => { + return stringFormat(listFormat, { + index : i++, + confTag : v.conf.confTag, + name : v.conf.name, + desc : v.conf.desc, + }); + })); + + i = 1; + confListView.setFocusItems(_.map(self.messageConfs, v => { + return stringFormat(focusListFormat, { + index : i++, + confTag : v.conf.confTag, + name : v.conf.name, + desc : v.conf.desc, + }); + })); + + confListView.redraw(); + + callback(null); + }, + function populateTextViews(callback) { + // :TODO: populate other avail MCI, e.g. current conf name + callback(null); } - }); - } else { - return cb(null); - } - } - }; - - this.setViewText = function(id, text) { - const v = self.viewControllers.areaList.getView(id); - if(v) { - v.setText(text); - } - }; -} - -require('util').inherits(MessageConfListModule, MenuModule); - -MessageConfListModule.prototype.mciReady = function(mciData, cb) { - var self = this; - const vc = self.viewControllers.areaList = new ViewController( { client : self.client } ); - - async.series( - [ - function callParentMciReady(callback) { - MessageConfListModule.super_.prototype.mciReady.call(this, mciData, callback); - }, - function loadFromConfig(callback) { - let loadOpts = { - callingMenu : self, - mciMap : mciData.menu, - formId : 0, - }; - - vc.loadFromMenuConfig(loadOpts, callback); - }, - function populateConfListView(callback) { - const listFormat = self.menuConfig.config.listFormat || '{index} ) - {name}'; - const focusListFormat = self.menuConfig.config.focusListFormat || listFormat; - - const confListView = vc.getView(MCICodeIDs.ConfList); - let i = 1; - confListView.setItems(_.map(self.messageConfs, v => { - return stringFormat(listFormat, { - index : i++, - confTag : v.conf.confTag, - name : v.conf.name, - desc : v.conf.desc, - }); - })); - - i = 1; - confListView.setFocusItems(_.map(self.messageConfs, v => { - return stringFormat(focusListFormat, { - index : i++, - confTag : v.conf.confTag, - name : v.conf.name, - desc : v.conf.desc, - }); - })); - - confListView.redraw(); - - callback(null); - }, - function populateTextViews(callback) { - // :TODO: populate other avail MCI, e.g. current conf name - callback(null); - } - ], - function complete(err) { - cb(err); - } - ); -}; \ No newline at end of file + ], + function complete(err) { + cb(err); + } + ); + }); + } +}; diff --git a/mods/msg_list.js b/mods/msg_list.js index dc82d437..498d8976 100644 --- a/mods/msg_list.js +++ b/mods/msg_list.js @@ -2,10 +2,11 @@ 'use strict'; // ENiGMA½ -const MenuModule = require('../core/menu_module.js').MenuModule; -const ViewController = require('../core/view_controller.js').ViewController; -const messageArea = require('../core/message_area.js'); -const stringFormat = require('../core/string_format.js'); +const MenuModule = require('../core/menu_module.js').MenuModule; +const ViewController = require('../core/view_controller.js').ViewController; +const messageArea = require('../core/message_area.js'); +const stringFormat = require('../core/string_format.js'); +const MessageAreaConfTempSwitcher = require('../core/mod_mixins.js').MessageAreaConfTempSwitcher; // deps const async = require('async'); @@ -28,8 +29,6 @@ const moment = require('moment'); TL2 : Message info 1: { msgNumSelected, msgNumTotal } */ -exports.getModule = MessageListModule; - exports.moduleInfo = { name : 'Message List', desc : 'Module for listing/browsing available messages', @@ -41,218 +40,213 @@ const MCICodesIDs = { MsgInfo1 : 2, // TL2 }; -function MessageListModule(options) { - MenuModule.call(this, options); +exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(MenuModule) { + constructor(options) { + super(options); - const self = this; - const config = this.menuConfig.config; + const self = this; + const config = this.menuConfig.config; - this.messageAreaTag = config.messageAreaTag; + this.messageAreaTag = config.messageAreaTag; - if(options.extraArgs) { - // - // |extraArgs| can override |messageAreaTag| provided by config - // as well as supply a pre-defined message list - // - if(options.extraArgs.messageAreaTag) { - this.messageAreaTag = options.extraArgs.messageAreaTag; + if(options.extraArgs) { + // + // |extraArgs| can override |messageAreaTag| provided by config + // as well as supply a pre-defined message list + // + if(options.extraArgs.messageAreaTag) { + this.messageAreaTag = options.extraArgs.messageAreaTag; + } + + if(options.extraArgs.messageList) { + this.messageList = options.extraArgs.messageList; + } } - if(options.extraArgs.messageList) { - this.messageList = options.extraArgs.messageList; - } - } + this.menuMethods = { + selectMessage : function(formData, extraArgs, cb) { + if(1 === formData.submitId) { + self.initialFocusIndex = formData.value.message; - this.menuMethods = { - selectMessage : function(formData, extraArgs, cb) { - if(1 === formData.submitId) { - self.initialFocusIndex = formData.value.message; - - const modOpts = { - extraArgs : { - messageAreaTag : self.messageAreaTag, - messageList : self.messageList, - messageIndex : formData.value.message, - } - }; - - // - // Provide a serializer so we don't dump *huge* bits of information to the log - // due to the size of |messageList|. See https://github.com/trentm/node-bunyan/issues/189 - // - modOpts.extraArgs.toJSON = function() { - const logMsgList = (this.messageList.length <= 4) ? - this.messageList : - this.messageList.slice(0, 2).concat(this.messageList.slice(-2)); - - return { - messageAreaTag : this.messageAreaTag, - apprevMessageList : logMsgList, - messageCount : this.messageList.length, - messageIndex : formData.value.message, + const modOpts = { + extraArgs : { + messageAreaTag : self.messageAreaTag, + messageList : self.messageList, + messageIndex : formData.value.message, + } }; - }; - return self.gotoMenu(config.menuViewPost || 'messageAreaViewPost', modOpts, cb); - } else { - return cb(null); - } - }, + // + // Provide a serializer so we don't dump *huge* bits of information to the log + // due to the size of |messageList|. See https://github.com/trentm/node-bunyan/issues/189 + // + modOpts.extraArgs.toJSON = function() { + const logMsgList = (this.messageList.length <= 4) ? + this.messageList : + this.messageList.slice(0, 2).concat(this.messageList.slice(-2)); - fullExit : function(formData, extraArgs, cb) { - self.menuResult = { fullExit : true }; - return self.prevMenu(cb); - } - }; + return { + messageAreaTag : this.messageAreaTag, + apprevMessageList : logMsgList, + messageCount : this.messageList.length, + messageIndex : formData.value.message, + }; + }; - this.setViewText = function(id, text) { - const v = self.viewControllers.allViews.getView(id); - if(v) { - v.setText(text); - } - }; -} - -require('util').inherits(MessageListModule, MenuModule); - -require('../core/mod_mixins.js').MessageAreaConfTempSwitcher.call(MessageListModule.prototype); - -MessageListModule.prototype.enter = function() { - MessageListModule.super_.prototype.enter.call(this); - - // - // Config can specify |messageAreaTag| else it comes from - // the user's current area - // - if(this.messageAreaTag) { - this.tempMessageConfAndAreaSwitch(this.messageAreaTag); - } else { - this.messageAreaTag = this.client.user.properties.message_area_tag; - } -}; - -MessageListModule.prototype.leave = function() { - this.tempMessageConfAndAreaRestore(); - - MessageListModule.super_.prototype.leave.call(this); -}; - -MessageListModule.prototype.mciReady = function(mciData, cb) { - const self = this; - const vc = self.viewControllers.allViews = new ViewController( { client : self.client } ); - - async.series( - [ - function callParentMciReady(callback) { - MessageListModule.super_.prototype.mciReady.call(self, mciData, callback); - }, - function loadFromConfig(callback) { - const loadOpts = { - callingMenu : self, - mciMap : mciData.menu - }; - - return vc.loadFromMenuConfig(loadOpts, callback); - }, - function fetchMessagesInArea(callback) { - // - // Config can supply messages else we'll need to populate the list now - // - if(_.isArray(self.messageList)) { - return callback(0 === self.messageList.length ? new Error('No messages in area') : null); - } - - messageArea.getMessageListForArea( { client : self.client }, self.messageAreaTag, function msgs(err, msgList) { - if(!msgList || 0 === msgList.length) { - return callback(new Error('No messages in area')); - } - - self.messageList = msgList; - return callback(err); - }); - }, - function getLastReadMesageId(callback) { - messageArea.getMessageAreaLastReadId(self.client.user.userId, self.messageAreaTag, function lastRead(err, lastReadId) { - self.lastReadId = lastReadId || 0; - return callback(null); // ignore any errors, e.g. missing value - }); - }, - function updateMessageListObjects(callback) { - const dateTimeFormat = self.menuConfig.config.dateTimeFormat || 'ddd MMM Do'; - const newIndicator = self.menuConfig.config.newIndicator || '*'; - const regIndicator = new Array(newIndicator.length + 1).join(' '); // fill with space to avoid draw issues - - let msgNum = 1; - self.messageList.forEach( (listItem, index) => { - listItem.msgNum = msgNum++; - listItem.ts = moment(listItem.modTimestamp).format(dateTimeFormat); - listItem.newIndicator = listItem.messageId > self.lastReadId ? newIndicator : regIndicator; - - if(_.isUndefined(self.initialFocusIndex) && listItem.messageId > self.lastReadId) { - self.initialFocusIndex = index; - } - }); - return callback(null); - }, - function populateList(callback) { - const msgListView = vc.getView(MCICodesIDs.MsgList); - const listFormat = self.menuConfig.config.listFormat || '{msgNum} - {subject} - {toUserName}'; - const focusListFormat = self.menuConfig.config.focusListFormat || listFormat; // :TODO: default change color here - const messageInfo1Format = self.menuConfig.config.messageInfo1Format || '{msgNumSelected} / {msgNumTotal}'; - - // :TODO: This can take a very long time to load large lists. What we need is to implement the "owner draw" concept in - // which items are requested (e.g. their format at least) *as-needed* vs trying to get the format for all of them at once - - msgListView.setItems(_.map(self.messageList, listEntry => { - return stringFormat(listFormat, listEntry); - })); - - msgListView.setFocusItems(_.map(self.messageList, listEntry => { - return stringFormat(focusListFormat, listEntry); - })); - - msgListView.on('index update', idx => { - self.setViewText( - MCICodesIDs.MsgInfo1, - stringFormat(messageInfo1Format, { msgNumSelected : (idx + 1), msgNumTotal : self.messageList.length } )); - }); - - if(self.initialFocusIndex > 0) { - // note: causes redraw() - msgListView.setFocusItemIndex(self.initialFocusIndex); + return self.gotoMenu(config.menuViewPost || 'messageAreaViewPost', modOpts, cb); } else { - msgListView.redraw(); + return cb(null); } + }, - return callback(null); - }, - function drawOtherViews(callback) { - const messageInfo1Format = self.menuConfig.config.messageInfo1Format || '{msgNumSelected} / {msgNumTotal}'; - self.setViewText( - MCICodesIDs.MsgInfo1, - stringFormat(messageInfo1Format, { msgNumSelected : self.initialFocusIndex + 1, msgNumTotal : self.messageList.length } )); - return callback(null); - }, - ], - err => { - if(err) { - self.client.log.error( { error : err.message }, 'Error loading message list'); + fullExit : function(formData, extraArgs, cb) { + self.menuResult = { fullExit : true }; + return self.prevMenu(cb); } - return cb(err); + }; + } + + enter() { + super.enter(); + + // + // Config can specify |messageAreaTag| else it comes from + // the user's current area + // + if(this.messageAreaTag) { + this.tempMessageConfAndAreaSwitch(this.messageAreaTag); + } else { + this.messageAreaTag = this.client.user.properties.message_area_tag; } - ); -}; + } -MessageListModule.prototype.getSaveState = function() { - return { initialFocusIndex : this.initialFocusIndex }; -}; + leave() { + this.tempMessageConfAndAreaRestore(); + super.leave(); + } -MessageListModule.prototype.restoreSavedState = function(savedState) { - if(savedState) { - this.initialFocusIndex = savedState.initialFocusIndex; + mciReady(mciData, cb) { + super.mciReady(mciData, err => { + if(err) { + return cb(err); + } + + const self = this; + const vc = self.viewControllers.allViews = new ViewController( { client : self.client } ); + + async.series( + [ + function loadFromConfig(callback) { + const loadOpts = { + callingMenu : self, + mciMap : mciData.menu + }; + + return vc.loadFromMenuConfig(loadOpts, callback); + }, + function fetchMessagesInArea(callback) { + // + // Config can supply messages else we'll need to populate the list now + // + if(_.isArray(self.messageList)) { + return callback(0 === self.messageList.length ? new Error('No messages in area') : null); + } + + messageArea.getMessageListForArea( { client : self.client }, self.messageAreaTag, function msgs(err, msgList) { + if(!msgList || 0 === msgList.length) { + return callback(new Error('No messages in area')); + } + + self.messageList = msgList; + return callback(err); + }); + }, + function getLastReadMesageId(callback) { + messageArea.getMessageAreaLastReadId(self.client.user.userId, self.messageAreaTag, function lastRead(err, lastReadId) { + self.lastReadId = lastReadId || 0; + return callback(null); // ignore any errors, e.g. missing value + }); + }, + function updateMessageListObjects(callback) { + const dateTimeFormat = self.menuConfig.config.dateTimeFormat || 'ddd MMM Do'; + const newIndicator = self.menuConfig.config.newIndicator || '*'; + const regIndicator = new Array(newIndicator.length + 1).join(' '); // fill with space to avoid draw issues + + let msgNum = 1; + self.messageList.forEach( (listItem, index) => { + listItem.msgNum = msgNum++; + listItem.ts = moment(listItem.modTimestamp).format(dateTimeFormat); + listItem.newIndicator = listItem.messageId > self.lastReadId ? newIndicator : regIndicator; + + if(_.isUndefined(self.initialFocusIndex) && listItem.messageId > self.lastReadId) { + self.initialFocusIndex = index; + } + }); + return callback(null); + }, + function populateList(callback) { + const msgListView = vc.getView(MCICodesIDs.MsgList); + const listFormat = self.menuConfig.config.listFormat || '{msgNum} - {subject} - {toUserName}'; + const focusListFormat = self.menuConfig.config.focusListFormat || listFormat; // :TODO: default change color here + const messageInfo1Format = self.menuConfig.config.messageInfo1Format || '{msgNumSelected} / {msgNumTotal}'; + + // :TODO: This can take a very long time to load large lists. What we need is to implement the "owner draw" concept in + // which items are requested (e.g. their format at least) *as-needed* vs trying to get the format for all of them at once + + msgListView.setItems(_.map(self.messageList, listEntry => { + return stringFormat(listFormat, listEntry); + })); + + msgListView.setFocusItems(_.map(self.messageList, listEntry => { + return stringFormat(focusListFormat, listEntry); + })); + + msgListView.on('index update', idx => { + self.setViewText( + 'allViews', + MCICodesIDs.MsgInfo1, + stringFormat(messageInfo1Format, { msgNumSelected : (idx + 1), msgNumTotal : self.messageList.length } )); + }); + + if(self.initialFocusIndex > 0) { + // note: causes redraw() + msgListView.setFocusItemIndex(self.initialFocusIndex); + } else { + msgListView.redraw(); + } + + return callback(null); + }, + function drawOtherViews(callback) { + const messageInfo1Format = self.menuConfig.config.messageInfo1Format || '{msgNumSelected} / {msgNumTotal}'; + self.setViewText( + 'allViews', + MCICodesIDs.MsgInfo1, + stringFormat(messageInfo1Format, { msgNumSelected : self.initialFocusIndex + 1, msgNumTotal : self.messageList.length } )); + return callback(null); + }, + ], + err => { + if(err) { + self.client.log.error( { error : err.message }, 'Error loading message list'); + } + return cb(err); + } + ); + }); + } + + getSaveState() { + return { initialFocusIndex : this.initialFocusIndex }; + } + + restoreSavedState(savedState) { + if(savedState) { + this.initialFocusIndex = savedState.initialFocusIndex; + } + } + + getMenuResult() { + return this.menuResult; } }; - -MessageListModule.prototype.getMenuResult = function() { - return this.menuResult; -}; diff --git a/mods/nua.js b/mods/nua.js index 14ef4803..31658eb3 100644 --- a/mods/nua.js +++ b/mods/nua.js @@ -9,8 +9,6 @@ const login = require('../core/system_menu_method.js').login; const Config = require('../core/config.js').config; const messageArea = require('../core/message_area.js'); -exports.getModule = NewUserAppModule; - exports.moduleInfo = { name : 'NUA', desc : 'New User Application', @@ -23,123 +21,124 @@ const MciViewIds = { errMsg : 11, }; -function NewUserAppModule(options) { - MenuModule.call(this, options); +exports.getModule = class NewUserAppModule extends MenuModule { + + constructor(options) { + super(options); + + const self = this; - const self = this; + this.menuMethods = { + // + // Validation stuff + // + validatePassConfirmMatch : function(data, cb) { + const passwordView = self.viewControllers.menu.getView(MciViewIds.password); + return cb(passwordView.getData() === data ? null : new Error('Passwords do not match')); + }, - this.menuMethods = { - // - // Validation stuff - // - validatePassConfirmMatch : function(data, cb) { - const passwordView = self.viewControllers.menu.getView(MciViewIds.password); - return cb(passwordView.getData() === data ? null : new Error('Passwords do not match')); - }, + viewValidationListener : function(err, cb) { + const errMsgView = self.viewControllers.menu.getView(MciViewIds.errMsg); + let newFocusId; + + if(err) { + errMsgView.setText(err.message); + err.view.clearText(); - viewValidationListener : function(err, cb) { - const errMsgView = self.viewControllers.menu.getView(MciViewIds.errMsg); - let newFocusId; - - if(err) { - errMsgView.setText(err.message); - err.view.clearText(); - - if(err.view.getId() === MciViewIds.confirm) { - newFocusId = MciViewIds.password; - self.viewControllers.menu.getView(MciViewIds.password).clearText(); + if(err.view.getId() === MciViewIds.confirm) { + newFocusId = MciViewIds.password; + self.viewControllers.menu.getView(MciViewIds.password).clearText(); + } + } else { + errMsgView.clearText(); } - } else { - errMsgView.clearText(); - } - return cb(newFocusId); - }, + return cb(newFocusId); + }, - // - // Submit handlers - // - submitApplication : function(formData, extraArgs, cb) { - const newUser = new user.User(); - - newUser.username = formData.value.username; - // - // We have to disable ACS checks for initial default areas as the user is not yet ready - // - let confTag = messageArea.getDefaultMessageConferenceTag(self.client, true); // true=disableAcsCheck - let areaTag = messageArea.getDefaultMessageAreaTagByConfTag(self.client, confTag, true); // true=disableAcsCheck + // Submit handlers + // + submitApplication : function(formData, extraArgs, cb) { + const newUser = new user.User(); - // can't store undefined! - confTag = confTag || ''; - areaTag = areaTag || ''; - - newUser.properties = { - real_name : formData.value.realName, - birthdate : new Date(Date.parse(formData.value.birthdate)).toISOString(), // :TODO: Use moment & explicit ISO string format - sex : formData.value.sex, - location : formData.value.location, - affiliation : formData.value.affils, - email_address : formData.value.email, - web_address : formData.value.web, - account_created : new Date().toISOString(), // :TODO: Use moment & explicit ISO string format - - message_conf_tag : confTag, - message_area_tag : areaTag, + newUser.username = formData.value.username; - term_height : self.client.term.termHeight, - term_width : self.client.term.termWidth, + // + // We have to disable ACS checks for initial default areas as the user is not yet ready + // + let confTag = messageArea.getDefaultMessageConferenceTag(self.client, true); // true=disableAcsCheck + let areaTag = messageArea.getDefaultMessageAreaTagByConfTag(self.client, confTag, true); // true=disableAcsCheck - // :TODO: Other defaults - // :TODO: should probably have a place to create defaults/etc. - }; + // can't store undefined! + confTag = confTag || ''; + areaTag = areaTag || ''; + + newUser.properties = { + real_name : formData.value.realName, + birthdate : new Date(Date.parse(formData.value.birthdate)).toISOString(), // :TODO: Use moment & explicit ISO string format + sex : formData.value.sex, + location : formData.value.location, + affiliation : formData.value.affils, + email_address : formData.value.email, + web_address : formData.value.web, + account_created : new Date().toISOString(), // :TODO: Use moment & explicit ISO string format + + message_conf_tag : confTag, + message_area_tag : areaTag, - if('*' === Config.defaults.theme) { - newUser.properties.theme_id = theme.getRandomTheme(); - } else { - newUser.properties.theme_id = Config.defaults.theme; - } - - // :TODO: User.create() should validate email uniqueness! - newUser.create( { password : formData.value.password }, err => { - if(err) { - self.client.log.info( { error : err, username : formData.value.username }, 'New user creation failed'); + term_height : self.client.term.termHeight, + term_width : self.client.term.termWidth, - self.gotoMenu(extraArgs.error, err => { - if(err) { - return self.prevMenu(cb); - } - return cb(null); - }); + // :TODO: Other defaults + // :TODO: should probably have a place to create defaults/etc. + }; + + if('*' === Config.defaults.theme) { + newUser.properties.theme_id = theme.getRandomTheme(); } else { - self.client.log.info( { username : formData.value.username, userId : newUser.userId }, 'New user created'); - - // Cache SysOp information now - // :TODO: Similar to bbs.js. DRY - if(newUser.isSysOp()) { - Config.general.sysOp = { - username : formData.value.username, - properties : newUser.properties, - }; - } - - if(user.User.AccountStatus.inactive === self.client.user.properties.account_status) { - return self.gotoMenu(extraArgs.inactive, cb); - } else { - // - // If active now, we need to call login() to authenticate - // - return login(self, formData, extraArgs, cb); - } + newUser.properties.theme_id = Config.defaults.theme; } - }); - }, - }; -} + + // :TODO: User.create() should validate email uniqueness! + newUser.create( { password : formData.value.password }, err => { + if(err) { + self.client.log.info( { error : err, username : formData.value.username }, 'New user creation failed'); -require('util').inherits(NewUserAppModule, MenuModule); + self.gotoMenu(extraArgs.error, err => { + if(err) { + return self.prevMenu(cb); + } + return cb(null); + }); + } else { + self.client.log.info( { username : formData.value.username, userId : newUser.userId }, 'New user created'); -NewUserAppModule.prototype.mciReady = function(mciData, cb) { - this.standardMCIReadyHandler(mciData, cb); -}; \ No newline at end of file + // Cache SysOp information now + // :TODO: Similar to bbs.js. DRY + if(newUser.isSysOp()) { + Config.general.sysOp = { + username : formData.value.username, + properties : newUser.properties, + }; + } + + if(user.User.AccountStatus.inactive === self.client.user.properties.account_status) { + return self.gotoMenu(extraArgs.inactive, cb); + } else { + // + // If active now, we need to call login() to authenticate + // + return login(self, formData, extraArgs, cb); + } + } + }); + }, + }; + } + + mciReady(mciData, cb) { + return this.standardMCIReadyHandler(mciData, cb); + } +}; diff --git a/mods/onelinerz.js b/mods/onelinerz.js index a00685f2..335c25ce 100644 --- a/mods/onelinerz.js +++ b/mods/onelinerz.js @@ -31,9 +31,7 @@ exports.moduleInfo = { packageName : 'codes.l33t.enigma.onelinerz', }; -exports.getModule = OnelinerzModule; - -const MciCodeIds = { +const MciViewIds = { ViewForm : { Entries : 1, AddPrompt : 2, @@ -50,20 +48,52 @@ const FormIds = { Add : 1, }; -function OnelinerzModule(options) { - MenuModule.call(this, options); +exports.getModule = class OnelinerzModule extends MenuModule { + constructor(options) { + super(options); - const self = this; - const config = this.menuConfig.config; + const self = this; - this.initSequence = function() { + this.menuMethods = { + viewAddScreen : function(formData, extraArgs, cb) { + return self.displayAddScreen(cb); + }, + + addEntry : function(formData, extraArgs, cb) { + if(_.isString(formData.value.oneliner) && formData.value.oneliner.length > 0) { + 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(); + return self.displayViewScreen(true, cb); // true=cls + }); + + } else { + // empty message - treat as if cancel was hit + return self.displayViewScreen(true, cb); // true=cls + } + }, + + cancelAdd : function(formData, extraArgs, cb) { + self.clearAddForm(); + return self.displayViewScreen(true, cb); // true=cls + } + }; + } + + initSequence() { + const self = this; async.series( [ function beforeDisplayArt(callback) { - self.beforeArt(callback); + return self.beforeArt(callback); }, function display(callback) { - self.displayViewScreen(false, callback); + return self.displayViewScreen(false, callback); } ], err => { @@ -73,9 +103,11 @@ function OnelinerzModule(options) { self.finishedLoading(); } ); - }; + } + + displayViewScreen(clearScreen, cb) { + const self = this; - this.displayViewScreen = function(clearScreen, cb) { async.waterfall( [ function clearAndDisplayArt(callback) { @@ -88,7 +120,7 @@ function OnelinerzModule(options) { } theme.displayThemedAsset( - config.art.entries, + self.menuConfig.config.art.entries, self.client, { font : self.menuConfig.font, trailingLF : false }, (err, artData) => { @@ -112,12 +144,12 @@ function OnelinerzModule(options) { return vc.loadFromMenuConfig(loadOpts, callback); } else { self.viewControllers.view.setFocus(true); - self.viewControllers.view.getView(MciCodeIds.ViewForm.AddPrompt).redraw(); + self.viewControllers.view.getView(MciViewIds.ViewForm.AddPrompt).redraw(); return callback(null); } }, function fetchEntries(callback) { - const entriesView = self.viewControllers.view.getView(MciCodeIds.ViewForm.Entries); + const entriesView = self.viewControllers.view.getView(MciViewIds.ViewForm.Entries); const limit = entriesView.dimens.height; let entries = []; @@ -142,8 +174,8 @@ function OnelinerzModule(options) { ); }, function populateEntries(entriesView, entries, callback) { - const listFormat = config.listFormat || '{username}@{ts}: {oneliner}';// :TODO: should be userName to be consistent - const tsFormat = config.timestampFormat || 'ddd h:mma'; + const listFormat = self.menuConfig.config.listFormat || '{username}@{ts}: {oneliner}';// :TODO: should be userName to be consistent + const tsFormat = self.menuConfig.config.timestampFormat || 'ddd h:mma'; entriesView.setItems(entries.map( e => { return stringFormat(listFormat, { @@ -159,7 +191,7 @@ function OnelinerzModule(options) { return callback(null); }, function finalPrep(callback) { - const promptView = self.viewControllers.view.getView(MciCodeIds.ViewForm.AddPrompt); + const promptView = self.viewControllers.view.getView(MciViewIds.ViewForm.AddPrompt); promptView.setFocusItemIndex(1); // default to NO return callback(null); } @@ -170,9 +202,11 @@ function OnelinerzModule(options) { } } ); - }; + } + + displayAddScreen(cb) { + const self = this; - this.displayAddScreen = function(cb) { async.waterfall( [ function clearAndDisplayArt(callback) { @@ -180,7 +214,7 @@ function OnelinerzModule(options) { self.client.term.rawWrite(ansi.resetScreen()); theme.displayThemedAsset( - config.art.add, + self.menuConfig.config.art.add, self.client, { font : self.menuConfig.font }, (err, artData) => { @@ -205,7 +239,7 @@ function OnelinerzModule(options) { } else { self.viewControllers.add.setFocus(true); self.viewControllers.add.redrawAll(); - self.viewControllers.add.switchFocus(MciCodeIds.AddForm.NewEntry); + self.viewControllers.add.switchFocus(MciViewIds.AddForm.NewEntry); return callback(null); } } @@ -216,80 +250,50 @@ function OnelinerzModule(options) { } } ); - }; + } - this.clearAddForm = function() { - const newEntryView = self.viewControllers.add.getView(MciCodeIds.AddForm.NewEntry); - const previewView = self.viewControllers.add.getView(MciCodeIds.AddForm.EntryPreview); + clearAddForm() { + this.setViewText('add', MciViewIds.AddForm.NewEntry, ''); + this.setViewText('add', MciViewIds.AddForm.EntryPreview, ''); + } - newEntryView.setText(''); - - // preview is optional - if(previewView) { - previewView.setText(''); - } - }; + initDatabase(cb) { + const self = this; - this.menuMethods = { - viewAddScreen : function(formData, extraArgs, cb) { - return self.displayAddScreen(cb); - }, - - addEntry : function(formData, extraArgs, cb) { - if(_.isString(formData.value.oneliner) && formData.value.oneliner.length > 0) { - 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(); - return self.displayViewScreen(true, cb); // true=cls - }); - - } else { - // empty message - treat as if cancel was hit - return self.displayViewScreen(true, cb); // true=cls - } - }, - - cancelAdd : function(formData, extraArgs, cb) { - self.clearAddForm(); - return self.displayViewScreen(true, cb); // true=cls - } - }; - - this.initDatabase = function(cb) { async.series( [ function openDatabase(callback) { self.db = new sqlite3.Database( getModDatabasePath(exports.moduleInfo), - callback + err => { + return callback(err); + } ); }, 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 - )` - ); + 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 + );` + , + err => { + return callback(err); }); - callback(null); } ], - cb + err => { + return cb(err); + } ); - }; + } - this.storeNewOneliner = function(oneliner, cb) { - const ts = moment().format('YYYY-MM-DDTHH:mm:ss.SSSZ'); + storeNewOneliner(oneliner, cb) { + const self = this; + const ts = moment().format('YYYY-MM-DDTHH:mm:ss.SSSZ'); async.series( [ @@ -315,15 +319,15 @@ function OnelinerzModule(options) { ); } ], - cb + err => { + return cb(err); + } ); - }; -} + } -require('util').inherits(OnelinerzModule, MenuModule); - -OnelinerzModule.prototype.beforeArt = function(cb) { - OnelinerzModule.super_.prototype.beforeArt.call(this, err => { - return err ? cb(err) : this.initDatabase(cb); - }); + beforeArt(cb) { + super.beforeArt(err => { + return err ? cb(err) : this.initDatabase(cb); + }); + } }; diff --git a/mods/telnet_bridge.js b/mods/telnet_bridge.js index a8a2d9d3..b70b4589 100644 --- a/mods/telnet_bridge.js +++ b/mods/telnet_bridge.js @@ -27,9 +27,6 @@ const buffers = require('buffers'); */ // :TODO: ENH: Support nodeMax and tooManyArt - -exports.getModule = TelnetBridgeModule; - exports.moduleInfo = { name : 'Telnet Bridge', desc : 'Connect to other Telnet Systems', @@ -123,18 +120,18 @@ class TelnetClientConnection extends EventEmitter { } +exports.getModule = class TelnetBridgeModule extends MenuModule { + constructor(options) { + super(options); -function TelnetBridgeModule(options) { - MenuModule.call(this, options); - - const self = this; - this.config = options.menuConfig.config; + this.config = options.menuConfig.config; + // defaults + this.config.port = this.config.port || 23; + } - // defaults - this.config.port = this.config.port || 23; - - this.initSequence = function() { + initSequence() { let clientTerminated; + const self = this; async.series( [ @@ -195,7 +192,5 @@ function TelnetBridgeModule(options) { } } ); - }; -} - -require('util').inherits(TelnetBridgeModule, MenuModule); + } +}; diff --git a/mods/user_list.js b/mods/user_list.js index 8401a182..07c27965 100644 --- a/mods/user_list.js +++ b/mods/user_list.js @@ -1,15 +1,14 @@ /* jslint node: true */ 'use strict'; -var MenuModule = require('../core/menu_module.js').MenuModule; -//var userDb = require('../core/database.js').dbs.user; -var getUserList = require('../core/user.js').getUserList; -var ViewController = require('../core/view_controller.js').ViewController; +const MenuModule = require('../core/menu_module.js').MenuModule; +const getUserList = require('../core/user.js').getUserList; +const ViewController = require('../core/view_controller.js').ViewController; const stringFormat = require('../core/string_format.js'); -var moment = require('moment'); -var async = require('async'); -var _ = require('lodash'); +const moment = require('moment'); +const async = require('async'); +const _ = require('lodash'); /* Available listFormat/focusListFormat object members: @@ -29,85 +28,85 @@ exports.moduleInfo = { author : 'NuSkooler', }; -exports.getModule = UserListModule; - -var MciCodeIds = { +const MciViewIds = { UserList : 1, }; -function UserListModule(options) { - MenuModule.call(this, options); -} +exports.getModule = class UserListModule extends MenuModule { + constructor(options) { + super(options); + } -require('util').inherits(UserListModule, MenuModule); - -UserListModule.prototype.mciReady = function(mciData, cb) { - var self = this; - var vc = self.viewControllers.allViews = new ViewController( { client : self.client } ); - - var userList = []; - - var USER_LIST_OPTS = { - properties : [ 'location', 'affiliation', 'last_login_timestamp' ], - }; - - async.series( - [ - // :TODO: These two functions repeated all over -- need DRY - function callParentMciReady(callback) { - UserListModule.super_.prototype.mciReady.call(self, mciData, callback); - }, - function loadFromConfig(callback) { - var loadOpts = { - callingMenu : self, - mciMap : mciData.menu, - }; - - vc.loadFromMenuConfig(loadOpts, callback); - }, - function fetchUserList(callback) { - // :TODO: Currently fetching all users - probably always OK, but this could be paged - getUserList(USER_LIST_OPTS, function got(err, ul) { - userList = ul; - callback(err); - }); - }, - function populateList(callback) { - var userListView = vc.getView(MciCodeIds.UserList); - - var listFormat = self.menuConfig.config.listFormat || '{userName} - {affils}'; - var focusListFormat = self.menuConfig.config.focusListFormat || listFormat; // :TODO: default changed color! - var dateTimeFormat = self.menuConfig.config.dateTimeFormat || 'ddd MMM DD'; - - function getUserFmtObj(ue) { - return { - userId : ue.userId, - userName : ue.userName, - affils : ue.affiliation, - location : ue.location, - // :TODO: the rest! - note : ue.note || '', - lastLoginTs : moment(ue.last_login_timestamp).format(dateTimeFormat), - }; - } - - userListView.setItems(_.map(userList, function formatUserEntry(ue) { - return stringFormat(listFormat, getUserFmtObj(ue)); - })); - - userListView.setFocusItems(_.map(userList, function formatUserEntry(ue) { - return stringFormat(focusListFormat, getUserFmtObj(ue)); - })); - - userListView.redraw(); - callback(null); - } - ], - function complete(err) { + mciReady(mciData, cb) { + super.mciReady(mciData, err => { if(err) { - self.client.log.error( { error : err.toString() }, 'Error loading user list'); + return cb(err); } - cb(err); - } - ); -}; \ No newline at end of file + + const self = this; + const vc = self.viewControllers.allViews = new ViewController( { client : self.client } ); + + let userList = []; + + const USER_LIST_OPTS = { + properties : [ 'location', 'affiliation', 'last_login_timestamp' ], + }; + + async.series( + [ + function loadFromConfig(callback) { + var loadOpts = { + callingMenu : self, + mciMap : mciData.menu, + }; + + vc.loadFromMenuConfig(loadOpts, callback); + }, + function fetchUserList(callback) { + // :TODO: Currently fetching all users - probably always OK, but this could be paged + getUserList(USER_LIST_OPTS, function got(err, ul) { + userList = ul; + callback(err); + }); + }, + function populateList(callback) { + var userListView = vc.getView(MciViewIds.UserList); + + var listFormat = self.menuConfig.config.listFormat || '{userName} - {affils}'; + var focusListFormat = self.menuConfig.config.focusListFormat || listFormat; // :TODO: default changed color! + var dateTimeFormat = self.menuConfig.config.dateTimeFormat || 'ddd MMM DD'; + + function getUserFmtObj(ue) { + return { + userId : ue.userId, + userName : ue.userName, + affils : ue.affiliation, + location : ue.location, + // :TODO: the rest! + note : ue.note || '', + lastLoginTs : moment(ue.last_login_timestamp).format(dateTimeFormat), + }; + } + + userListView.setItems(_.map(userList, function formatUserEntry(ue) { + return stringFormat(listFormat, getUserFmtObj(ue)); + })); + + userListView.setFocusItems(_.map(userList, function formatUserEntry(ue) { + return stringFormat(focusListFormat, getUserFmtObj(ue)); + })); + + userListView.redraw(); + callback(null); + } + ], + function complete(err) { + if(err) { + self.client.log.error( { error : err.toString() }, 'Error loading user list'); + } + cb(err); + } + ); + }); + } +}; diff --git a/mods/whos_online.js b/mods/whos_online.js index 770528f0..8d38646d 100644 --- a/mods/whos_online.js +++ b/mods/whos_online.js @@ -18,66 +18,67 @@ exports.moduleInfo = { packageName : 'codes.l33t.enigma.whosonline' }; -exports.getModule = WhosOnlineModule; - -const MciCodeIds = { +const MciViewIds = { OnlineList : 1, }; -function WhosOnlineModule(options) { - MenuModule.call(this, options); -} +exports.getModule = class WhosOnlineModule extends MenuModule { + constructor(options) { + super(options); + } -require('util').inherits(WhosOnlineModule, MenuModule); - -WhosOnlineModule.prototype.mciReady = function(mciData, cb) { - const self = this; - const vc = self.viewControllers.allViews = new ViewController( { client : self.client } ); - - async.series( - [ - function callParentMciReady(callback) { - return WhosOnlineModule.super_.prototype.mciReady.call(self, mciData, callback); - }, - function loadFromConfig(callback) { - const loadOpts = { - callingMenu : self, - mciMap : mciData.menu, - noInput : true, - }; - - return vc.loadFromMenuConfig(loadOpts, callback); - }, - function populateList(callback) { - const onlineListView = vc.getView(MciCodeIds.OnlineList); - const listFormat = self.menuConfig.config.listFormat || '{node} - {userName} - {action} - {timeOn}'; - const nonAuthUser = self.menuConfig.config.nonAuthUser || 'Logging In'; - const otherUnknown = self.menuConfig.config.otherUnknown || 'N/A'; - const onlineList = getActiveNodeList(self.menuConfig.config.authUsersOnly).slice(0, onlineListView.height); - - onlineListView.setItems(_.map(onlineList, oe => { - if(oe.authenticated) { - oe.timeOn = _.capitalize(oe.timeOn.humanize()); - } else { - [ 'realName', 'location', 'affils', 'timeOn' ].forEach(m => { - oe[m] = otherUnknown; - }); - oe.userName = nonAuthUser; - } - return stringFormat(listFormat, oe); - })); - - onlineListView.focusItems = onlineListView.items; - onlineListView.redraw(); - - return callback(null); - } - ], - function complete(err) { + mciReady(mciData, cb) { + super.mciReady(mciData, err => { if(err) { - self.client.log.error( { error : err.message }, 'Error loading who\'s online'); + return cb(err); } - return cb(err); - } - ); + + const self = this; + const vc = self.viewControllers.allViews = new ViewController( { client : self.client } ); + + async.series( + [ + function loadFromConfig(callback) { + const loadOpts = { + callingMenu : self, + mciMap : mciData.menu, + noInput : true, + }; + + return vc.loadFromMenuConfig(loadOpts, callback); + }, + function populateList(callback) { + const onlineListView = vc.getView(MciViewIds.OnlineList); + const listFormat = self.menuConfig.config.listFormat || '{node} - {userName} - {action} - {timeOn}'; + const nonAuthUser = self.menuConfig.config.nonAuthUser || 'Logging In'; + const otherUnknown = self.menuConfig.config.otherUnknown || 'N/A'; + const onlineList = getActiveNodeList(self.menuConfig.config.authUsersOnly).slice(0, onlineListView.height); + + onlineListView.setItems(_.map(onlineList, oe => { + if(oe.authenticated) { + oe.timeOn = _.capitalize(oe.timeOn.humanize()); + } else { + [ 'realName', 'location', 'affils', 'timeOn' ].forEach(m => { + oe[m] = otherUnknown; + }); + oe.userName = nonAuthUser; + } + return stringFormat(listFormat, oe); + })); + + onlineListView.focusItems = onlineListView.items; + onlineListView.redraw(); + + return callback(null); + } + ], + function complete(err) { + if(err) { + self.client.log.error( { error : err.message }, 'Error loading who\'s online'); + } + return cb(err); + } + ); + }); + } }; diff --git a/package.json b/package.json index 8fc24eff..902c34e5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "enigma-bbs", - "version": "0.0.2-alpha", + "version": "0.0.3-alpha", "description": "ENiGMA½ Bulletin Board System", "author": "Bryan Ashby ", "license": "BSD-2-Clause", @@ -14,7 +14,8 @@ }, "keywords": [ "bbs", - "telnet" + "telnet", + "retro" ], "dependencies": { "async": "^1.5.1",