diff --git a/core/fse.js b/core/fse.js index 50755aae..a2b20f07 100644 --- a/core/fse.js +++ b/core/fse.js @@ -446,6 +446,7 @@ function FullScreenEditorModule(options) { case 'view' : self.switchToFooter(); + //self.observeViewPosition(); break; } @@ -558,7 +559,7 @@ function FullScreenEditorModule(options) { function helpDisplayed(err, artData) { self.client.waitForKeyPress(function keyPress(ch, key) { self.redrawScreen(); - self.viewControllers.footerEditorMenu.setFocus(true); + self.viewControllers[self.getFooterName()].setFocus(true); }); } ); @@ -636,6 +637,14 @@ function FullScreenEditorModule(options) { }); }; + /* + this.observeViewPosition = function() { + self.viewControllers.body.getView(1).on('edit position', function positionUpdate(pos) { + console.log(pos.percent + ' / ' + pos.below) + }); + }; + */ + this.switchToHeader = function() { self.viewControllers.body.setFocus(false); self.viewControllers.header.switchFocus(2); // to @@ -765,6 +774,10 @@ function FullScreenEditorModule(options) { // MLTEV won't get key events -- we need to handle them all here? // ...up/down, page up/page down... both should go by pages // ...Next/Prev/Etc. here + }, + viewModeMenuHelp : function(formData, extraArgs) { + self.viewControllers.footerView.setFocus(false); + self.displayHelp(); } }; diff --git a/core/menu_util.js b/core/menu_util.js index 087af3b3..9f471fc9 100644 --- a/core/menu_util.js +++ b/core/menu_util.js @@ -201,6 +201,8 @@ function handleAction(client, formData, conf) { var currentModule = client.currentMenuModule; if(_.isFunction(currentModule.menuMethods[actionAsset.asset])) { currentModule.menuMethods[actionAsset.asset](formData, conf.extraArgs); + } else { + client.log.warn( { method : actionAsset.asset }, 'Method does not exist in module'); } } } diff --git a/core/message.js b/core/message.js index b539d91e..f2f14e27 100644 --- a/core/message.js +++ b/core/message.js @@ -244,6 +244,7 @@ Message.prototype.persist = function(cb) { ); }; +// :TODO: Update this to use a FTN module, e.g. ftn.getQuotePrefix(name) Message.prototype.getFTNQuotePrefix = function(source) { source = source || 'fromUserName'; @@ -275,22 +276,29 @@ Message.prototype.getQuoteLines = function(width, options) { var quoteLines = []; var origLines = this.message + .trim() .replace(/\b/g, '') .split(/\r\n|[\n\v\f\r\x85\u2028\u2029]/g); - var wrapOpts = { - width : width, - tabHandling : 'expand', - tabWidth : 4, - }; - - var quotePrefix; + var quotePrefix = ''; // we need this init even if blank if(options.includePrefix) { quotePrefix = ' ' + this.getFTNQuotePrefix(options.prefixSource || 'fromUserName') + '> '; } + var wrapOpts = { + width : width - quotePrefix.length, + tabHandling : 'expand', + tabWidth : 4, + }; + + function addPrefix(l) { + return quotePrefix + l; + } + + var wrapped; for(var i = 0; i < origLines.length; ++i) { - Array.prototype.push.apply(quoteLines, wordWrapText(quotePrefix + origLines[i], wrapOpts).wrapped); + wrapped = wordWrapText(origLines[i], wrapOpts).wrapped; + Array.prototype.push.apply(quoteLines, _.map(wrapped, addPrefix)); } return quoteLines; diff --git a/core/multi_line_edit_text_view.js b/core/multi_line_edit_text_view.js index bb89f7e6..d54a453f 100644 --- a/core/multi_line_edit_text_view.js +++ b/core/multi_line_edit_text_view.js @@ -365,7 +365,7 @@ function MultiLineEditTextView(options) { self.updateTextWordWrap(index); self.redrawRows(self.cursorPos.row, self.dimens.height); - self.moveClientCusorToCursorPos(); + self.moveClientCursorToCursorPos(); } else if('delete line' === operation) { // // Delete a visible line. Note that this is *not* the "physical" line, or @@ -401,7 +401,7 @@ function MultiLineEditTextView(options) { if(isLastLine) { self.cursorEndOfPreviousLine(); } else { - self.moveClientCusorToCursorPos(); + self.moveClientCursorToCursorPos(); } } }; @@ -438,7 +438,7 @@ function MultiLineEditTextView(options) { self.cursorPos.col += cursorOffset; self.client.term.rawWrite(ansi.right(cursorOffset)); } else { - self.moveClientCusorToCursorPos(); + self.moveClientCursorToCursorPos(); } } else { // @@ -561,7 +561,7 @@ function MultiLineEditTextView(options) { }; }; - this.moveClientCusorToCursorPos = function() { + this.moveClientCursorToCursorPos = function() { var absPos = self.getAbsolutePosition(self.cursorPos.row, self.cursorPos.col); self.client.term.rawWrite(ansi.goto(absPos.row, absPos.col)); }; @@ -669,14 +669,14 @@ function MultiLineEditTextView(options) { } else { self.cursorPos.col = 0; } - self.moveClientCusorToCursorPos(); + self.moveClientCursorToCursorPos(); self.emitEditPosition(); }; this.keyPressEnd = function() { self.cursorPos.col = self.getTextEndOfLineColumn(); - self.moveClientCusorToCursorPos(); + self.moveClientCursorToCursorPos(); self.emitEditPosition(); }; @@ -687,7 +687,7 @@ function MultiLineEditTextView(options) { self.adjustCursorIfPastEndOfLine(true); } else { self.cursorPos.row = 0; - self.moveClientCusorToCursorPos(); // :TODO: ajust if eol, etc. + self.moveClientCursorToCursorPos(); // :TODO: ajust if eol, etc. } self.emitEditPosition(); @@ -817,7 +817,7 @@ function MultiLineEditTextView(options) { } if(forceUpdate) { - self.moveClientCusorToCursorPos(); + self.moveClientCursorToCursorPos(); } }; @@ -874,7 +874,7 @@ function MultiLineEditTextView(options) { self.cursorPos = { row : 0, col : 0 }; self.redraw(); - self.moveClientCusorToCursorPos(); + self.moveClientCursorToCursorPos(); }; this.cursorEndOfDocument = function() { @@ -883,7 +883,7 @@ function MultiLineEditTextView(options) { self.cursorPos.col = self.getTextEndOfLineColumn(); self.redraw(); - self.moveClientCusorToCursorPos(); + self.moveClientCursorToCursorPos(); }; this.cursorBeginOfNextLine = function() { @@ -984,7 +984,7 @@ MultiLineEditTextView.prototype.redraw = function() { MultiLineEditTextView.prototype.setFocus = function(focused) { this.client.term.rawWrite(this.getSGRFor('text')); - this.moveClientCusorToCursorPos(); + this.moveClientCursorToCursorPos(); MultiLineEditTextView.super_.prototype.setFocus.call(this, focused); }; @@ -1036,12 +1036,6 @@ var HANDLED_SPECIAL_KEYS = [ 'delete line', ]; -/* -var EDIT_MODE_KEYS = [ - 'line feed', 'insert', 'tab', 'backspace', 'del', 'delete line' -]; -*/ - var PREVIEW_MODE_KEYS = [ 'up', 'down', 'page up', 'page down' ]; @@ -1078,6 +1072,13 @@ MultiLineEditTextView.prototype.getTextEditMode = function() { }; MultiLineEditTextView.prototype.getEditPosition = function() { - return { row : this.getTextLinesIndex(this.cursorPos.row), col : this.cursorPos.col } + var currentIndex = this.getTextLinesIndex() + 1; + + return { + row : this.getTextLinesIndex(this.cursorPos.row), + col : this.cursorPos.col, + percent : Math.floor(((currentIndex / this.textLines.length) * 100)), + below : this.getRemainingLinesBelowRow(), + }; }; diff --git a/core/view_controller.js b/core/view_controller.js index b62f96b5..b6ae89bf 100644 --- a/core/view_controller.js +++ b/core/view_controller.js @@ -42,21 +42,20 @@ function ViewController(options) { // Process key presses treating form submit mapped keys special. // Everything else is forwarded on to the focused View, if any. // - if(key) { - var actionForKey = self.actionKeyMap[key.name] - if(actionForKey) { - if(_.isNumber(actionForKey.viewId)) { - // - // Key works on behalf of a view -- switch focus & submit - // - self.switchFocus(actionForKey.viewId); - self.submitForm(key); - } else if(_.isString(actionForKey.action)) { - menuUtil.handleAction( - self.client, - { key : key }, // formData - actionForKey); // action block - } + var actionForKey = key ? self.actionKeyMap[key.name] : self.actionKeyMap[ch]; + //var actionForKey = self.actionKeyMap[key.name] || self.actionKeyMap[ch]; + if(actionForKey) { + if(_.isNumber(actionForKey.viewId)) { + // + // Key works on behalf of a view -- switch focus & submit + // + self.switchFocus(actionForKey.viewId); + self.submitForm(key); + } else if(_.isString(actionForKey.action)) { + menuUtil.handleAction( + self.client, + { ch : ch, key : key }, // formData + actionForKey); // action block } } @@ -298,6 +297,21 @@ function ViewController(options) { if(!options.detached) { this.attachClientEvents(); } + + this.setViewFocusWithEvents = function(view, focused) { + if(!view || !view.acceptsFocus) { + return; + } + + if(focused) { + self.switchFocusEvent('return', view); + self.focusedView = view; + } else { + self.switchFocusEvent('leave', view); + } + + view.setFocus(focused); + }; } util.inherits(ViewController, events.EventEmitter); @@ -358,23 +372,19 @@ ViewController.prototype.setFocus = function(focused) { } else { this.detachClientEvents(); } + + // :TODO: without this, setFocus(false) is broken (doens't call focus events); with it, FSE menus break!! +// this.setViewFocusWithEvents(this.focusedView, focused); }; ViewController.prototype.switchFocus = function(id) { this.setFocus(true); // ensure events are attached - if(this.focusedView && this.focusedView.acceptsFocus) { - this.switchFocusEvent('leave', this.focusedView); - this.focusedView.setFocus(false); - } + // remove from old + this.setViewFocusWithEvents(this.focusedView, false); - var view = this.getView(id); - if(view && view.acceptsFocus) { - this.switchFocusEvent('return', view); - - this.focusedView = view; - this.focusedView.setFocus(true); - } + // set to new + this.setViewFocusWithEvents(this.getView(id), true); }; ViewController.prototype.nextFocus = function() { @@ -624,7 +634,7 @@ ViewController.prototype.loadFromMenuConfig = function(options, cb) { callback(null); return; } - + formConfig.actionKeys.forEach(function akEntry(ak) { // // * 'keys' must be present and be an array of key names diff --git a/mods/menu.hjson b/mods/menu.hjson index 446a537b..ddd942f1 100644 --- a/mods/menu.hjson +++ b/mods/menu.hjson @@ -439,7 +439,7 @@ footerEdit: demo_fse_netmail_footer_edit.ans footerEditMenu: demo_fse_netmail_footer_edit_menu.ans footerView: MSGVFTR - help: demo_fse_netmail_help.ans + help: MSGVHLP }, editorMode: view editorType: area @@ -502,34 +502,48 @@ } } }, - "4" : { - "mci" : { - "HM1" : { - // (P)rev/(N)ext/Post/(R)eply/(Q)uit/(?)Help - // (#)Jump/(L)Index (msg list)/Last - "items" : [ "Prev", "Next", "Reply", "Quit", "Help" ] + 4: { + mci: { + HM1: { + // :TODO: (#)Jump/(L)Index (msg list)/Last + items: [ + // (P)revious + Prev + // (N)ext + Next + // (R)reply + Reply + // (Q)uit (ESC) + Quit + // (?)Help + Help + ] } }, "submit" : { "*" : [ { - "value" : { "1" : 0 }, - "action" : "@method:prevMessage" - }, + value: { 1: 0 } + action: @method:prevMessage" + } { - "value" : { "1" : 1 }, - "action" : "@method:nextMessage" - }, + value: { 1: 1 } + action: @method:nextMessage + } { value: { 1: 2 } action: @method:replyMessage extraArgs: { menu: messageAreaReplyPost } - }, + } { - "value" : { "1" : 3 }, - "action" : "@menu:messageArea" + value: { 1: 3 } + action: @menu:messageArea + } + { + value: { 1: 4 } + action: @method:viewModeMenuHelp } ] }, @@ -549,11 +563,6 @@ keys: [ "n", "shift + n" ] action: @method:nextMessage } - { - keys: [ "escape", "q", "shift + q" ] - action: @menu:messageArea - } - // :TODO: why the fuck is 'r' not working but 'n' for example does? { keys: [ "r", "shift + r" ] action: @method:replyMessage @@ -561,6 +570,14 @@ menu: messageAreaReplyPost } } + { + keys: [ "escape", "q", "shift + q" ] + action: @menu:messageArea + } + { + keys: [ "?" ] + action: @method:viewModeMenuHelp + } { "keys" : [ "down arrow", "up arrow", "page up", "page down" ], "action" : "@method:movementKeyPressed" @@ -578,8 +595,7 @@ quote: MSGQUOT footerEditor: MSGEFTR footerEditorMenu: MSGEMFT - // :TODO: fix help - help: demo_fse_netmail_help.ans + help: MSGEHLP } editorMode: edit editorType: area @@ -602,9 +618,10 @@ argName: subject maxLength: 72 submit: true + textOverflow: ... } TL4: { - // :TODO: this is for RE: line + // :TODO: this is for RE: line (NYI) width: 27 textOverflow: ... } @@ -667,10 +684,27 @@ keys: [ "escape" ] action: @method:editModeEscPressed } + { + keys: [ "s", "shift + s" ] + action: @method:replySave + } + { + keys: [ "d", "shift + d" ] + action: @method:replyDiscard + } + { + keys: [ "q", "shift + q" ] + action: @method:editModeMenuQuote + } + { + keys: [ "?" ] + action: @method:editModeMenuHelp + } ] } } + // Quote builder 5: { mci: { MT1: { @@ -713,7 +747,7 @@ body: MSGBODY footerEditor: MSGEFTR footerEditorMenu: MSGEMFT - help: demo_fse_netmail_help.ans + help: MSGEHLP }, editorMode: edit editorType: area diff --git a/mods/msg_area_post_fse.js b/mods/msg_area_post_fse.js index 6a43b6ad..99a9ee67 100644 --- a/mods/msg_area_post_fse.js +++ b/mods/msg_area_post_fse.js @@ -70,6 +70,11 @@ AreaPostFSEModule.prototype.enter = function(client) { AreaPostFSEModule.prototype.validateToUserName = function(un, cb) { var self = this; + if(!un) { + cb(new Error('Username must be supplied!')); + return; + } + if(!self.isLocalEmail()) { cb(null); return; diff --git a/mods/msg_area_view_fse.js b/mods/msg_area_view_fse.js index e209a279..15eace0f 100644 --- a/mods/msg_area_view_fse.js +++ b/mods/msg_area_view_fse.js @@ -60,8 +60,12 @@ function AreaViewFSEModule(options) { case 'down arrow' : bodyView.scrollDocumentUp(); break; case 'up arrow' : bodyView.scrollDocumentDown(); break; case 'page up' : bodyView.keyPressPageUp(); break; - case 'page down' : bodyView.keyPressPageDown(); 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.menuMethods.replyMessage = function(formData, extraArgs) { diff --git a/mods/themes/NU-MAYA/MSGEHLP.ANS b/mods/themes/NU-MAYA/MSGEHLP.ANS new file mode 100644 index 00000000..121250ca Binary files /dev/null and b/mods/themes/NU-MAYA/MSGEHLP.ANS differ diff --git a/mods/themes/NU-MAYA/MSGQUOT.ANS b/mods/themes/NU-MAYA/MSGQUOT.ANS index 16d1be42..f7d62a3d 100644 Binary files a/mods/themes/NU-MAYA/MSGQUOT.ANS and b/mods/themes/NU-MAYA/MSGQUOT.ANS differ diff --git a/mods/themes/NU-MAYA/MSGVHLP.ANS b/mods/themes/NU-MAYA/MSGVHLP.ANS new file mode 100644 index 00000000..5fed6314 Binary files /dev/null and b/mods/themes/NU-MAYA/MSGVHLP.ANS differ