diff --git a/core/fse.js b/core/fse.js index de8fb1f9..c0135919 100644 --- a/core/fse.js +++ b/core/fse.js @@ -104,6 +104,7 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul this.editorMode = config.editorMode; if(config.messageAreaTag) { + // :TODO: swtich to this.config.messageAreaTag so we can follow Object.assign pattern for config/extraArgs this.messageAreaTag = config.messageAreaTag; } @@ -127,6 +128,9 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul } } + this.noUpdateLastReadId = _.get(options, 'extraArgs.noUpdateLastReadId', config.noUpdateLastReadId) || false; + console.log(this.noUpdateLastReadId); + this.isReady = false; if(_.has(options, 'extraArgs.message')) { @@ -342,49 +346,56 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul return cb(null); } + updateLastReadId(cb) { + if(this.noUpdateLastReadId) { + return cb(null); + } + + return updateMessageAreaLastReadId( + this.client.user.userId, this.messageAreaTag, this.message.messageId, cb + ); + } + setMessage(message) { this.message = message; - updateMessageAreaLastReadId( - this.client.user.userId, this.messageAreaTag, this.message.messageId, () => { + this.updateLastReadId( () => { + if(this.isReady) { + this.initHeaderViewMode(); + this.initFooterViewMode(); - if(this.isReady) { - this.initHeaderViewMode(); - this.initFooterViewMode(); + const bodyMessageView = this.viewControllers.body.getView(MciViewIds.body.message); + let msg = this.message.message; - const bodyMessageView = this.viewControllers.body.getView(MciViewIds.body.message); - let msg = this.message.message; - - if(bodyMessageView && _.has(this, 'message.message')) { + if(bodyMessageView && _.has(this, 'message.message')) { + // + // We handle ANSI messages differently than standard messages -- this is required as + // we don't want to do things like word wrap ANSI, but instead, trust that it's formatted + // how the author wanted it + // + if(isAnsi(msg)) { // - // We handle ANSI messages differently than standard messages -- this is required as - // we don't want to do things like word wrap ANSI, but instead, trust that it's formatted - // how the author wanted it + // Find tearline - we want to color it differently. // - if(isAnsi(msg)) { - // - // Find tearline - we want to color it differently. - // - const tearLinePos = this.message.getTearLinePosition(msg); + const tearLinePos = this.message.getTearLinePosition(msg); - if(tearLinePos > -1) { - msg = insert(msg, tearLinePos, bodyMessageView.getSGRFor('text')); - } - - bodyMessageView.setAnsi( - msg.replace(/\r?\n/g, '\r\n'), // messages are stored with CRLF -> LF - { - prepped : false, - forceLineTerm : true, - } - ); - } else { - bodyMessageView.setText(cleanControlCodes(msg)); + if(tearLinePos > -1) { + msg = insert(msg, tearLinePos, bodyMessageView.getSGRFor('text')); } + + bodyMessageView.setAnsi( + msg.replace(/\r?\n/g, '\r\n'), // messages are stored with CRLF -> LF + { + prepped : false, + forceLineTerm : true, + } + ); + } else { + bodyMessageView.setText(cleanControlCodes(msg)); } } } - ); + }); } getMessage(cb) { @@ -816,6 +827,9 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul this.setHeaderText(MciViewIds.header.msgTotal, this.messageTotal.toString()); this.updateCustomViewTextsWithFilter('header', MciViewIds.header.customRangeStart, this.getHeaderFormatObj()); + + // if we changed conf/area we need to update any related standard MCI view + this.refreshPredefinedMciViewsByCode('header', [ 'MA', 'MC', 'ML', 'CM' ] ); } initHeaderReplyEditMode() { diff --git a/core/menu_stack.js b/core/menu_stack.js index 26e88cc5..073dece5 100644 --- a/core/menu_stack.js +++ b/core/menu_stack.js @@ -117,7 +117,7 @@ module.exports = class MenuStack { }; if(_.isObject(options)) { - loadOpts.extraArgs = options.extraArgs; + loadOpts.extraArgs = options.extraArgs || _.get(options, 'formData.value'); loadOpts.lastMenuResult = options.lastMenuResult; } diff --git a/core/message.js b/core/message.js index 6c47a934..f7f980f7 100644 --- a/core/message.js +++ b/core/message.js @@ -87,6 +87,12 @@ const FTN_PROPERTY_NAMES = { FtnSeenBy : 'ftn_seen_by', // http://ftsc.org/docs/fts-0004.001 }; +// :TODO: this is a ugly hack due to bad variable names - clean it up & just _.camelCase(k)! +const MESSAGE_ROW_MAP = { + reply_to_message_id : 'replyToMsgId', + modified_timestamp : 'modTimestamp' +}; + module.exports = class Message { constructor( { @@ -189,6 +195,16 @@ module.exports = class Message { return uuidParse.unparse(createNamedUUID(ENIGMA_MESSAGE_UUID_NAMESPACE, Buffer.concat( [ areaTag, modTimestamp, subject, body ] ))); } + static getMessageFromRow(row) { + const msg = {}; + _.each(row, (v, k) => { + // :TODO: see notes around MESSAGE_ROW_MAP -- clean this up so we can just _camelCase()! + k = MESSAGE_ROW_MAP[k] || _.camelCase(k); + msg[k] = v; + }); + return msg; + } + /* Find message IDs or UUIDs by filter. Available filters/options: @@ -199,11 +215,10 @@ module.exports = class Message { filter.replyToMesageId filter.newerThanTimestamp filter.newerThanMessageId - *filter.confTag - all area tags in confTag - filter.areaTag + filter.areaTag - note if you want by conf, send in all areas for a conf *filter.metaTuples - {category, name, value} - *filter.terms - FTS search + filter.terms - FTS search filter.sort = modTimestamp | messageId filter.order = ascending | (descending) @@ -223,7 +238,13 @@ module.exports = class Message { filter.resultType = filter.resultType || 'id'; filter.extraFields = filter.extraFields || []; - const field = 'id' === filter.resultType ? 'message_id' : 'message_uuid'; + if('messageList' === filter.resultType) { + filter.extraFields = _.uniq(filter.extraFields.concat( + [ 'area_tag', 'message_uuid', 'reply_to_message_id', 'to_user_name', 'from_user_name', 'subject', 'modified_timestamp' ] + )); + } + + const field = 'uuid' === filter.resultType ? 'message_uuid' : 'message_id'; if(moment.isMoment(filter.newerThanTimestamp)) { filter.newerThanTimestamp = getISOTimestampString(filter.newerThanTimestamp); @@ -280,32 +301,23 @@ module.exports = class Message { WHERE meta_category = "System" AND meta_name = "${Message.SystemMetaNames.LocalToUserID}" AND meta_value = ${filter.privateTagUserId} )`); } else { - let areaTags = []; - if(filter.confTag && filter.confTag.length > 0) { - // :TODO: grab areas from conf -> add to areaTags[] - } - - if(areaTags.length > 0 || filter.areaTag && filter.areaTag.length > 0) { + if(filter.areaTag && filter.areaTag.length > 0) { if(Array.isArray(filter.areaTag)) { - areaTags = areaTags.concat(filter.areaTag); - } else if(_.isString(filter.areaTag)) { - areaTags.push(filter.areaTag); - } - - areaTags = _.uniq(areaTags); // remove any dupes - - if(areaTags.length > 1) { const areaList = filter.areaTag.map(t => `"${t}"`).join(', '); appendWhereClause(`m.area_tag IN(${areaList})`); - } else { - appendWhereClause(`m.area_tag = "${areaTags[0]}"`); + } else if(_.isString(filter.areaTag)) { + appendWhereClause(`m.area_tag = "${filter.areaTag}"`); } } } - [ 'toUserName', 'fromUserName', 'replyToMessageId' ].forEach(field => { + if(_.isNumber(filter.replyToMessageId)) { + appendWhereClause(`m.reply_to_message_id=${filter.replyToMessageId}`); + } + + [ 'toUserName', 'fromUserName' ].forEach(field => { if(_.isString(filter[field]) && filter[field].length > 0) { - appendWhereClause(`m.${_.snakeCase(field)} = "${sanatizeString(filter[field])}"`); + appendWhereClause(`m.${_.snakeCase(field)} LIKE "${sanatizeString(filter[field])}"`); } }); @@ -317,6 +329,17 @@ module.exports = class Message { appendWhereClause(`m.message_id > ${filter.newerThanMessageId}`); } + if(filter.terms && filter.terms.length > 0) { + // note the ':' in MATCH expr., see https://www.sqlite.org/cvstrac/wiki?p=FullTextIndex + appendWhereClause( + `m.message_id IN ( + SELECT rowid + FROM message_fts + WHERE message_fts MATCH ":${sanatizeString(filter.terms)}" + )` + ); + } + sql += `${sqlWhere} ${sqlOrderBy}`; if(_.isNumber(filter.limit)) { @@ -332,9 +355,12 @@ module.exports = class Message { } else { const matches = []; const extra = filter.extraFields.length > 0; + + const rowConv = 'messageList' === filter.resultType ? Message.getMessageFromRow : row => row; + msgDb.each(sql, (err, row) => { if(_.isObject(row)) { - matches.push(extra ? row : row[field]); + matches.push(extra ? rowConv(row) : row[field]); } }, err => { return cb(err, matches); diff --git a/core/message_area.js b/core/message_area.js index fb068e13..985da046 100644 --- a/core/message_area.js +++ b/core/message_area.js @@ -8,13 +8,11 @@ const Message = require('./message.js'); const Log = require('./logger.js').log; const msgNetRecord = require('./msg_network.js').recordMessage; const sortAreasOrConfs = require('./conf_area_util.js').sortAreasOrConfs; -const { getISOTimestampString } = require('./database.js'); // deps const async = require('async'); const _ = require('lodash'); const assert = require('assert'); -const moment = require('moment'); exports.getAvailableMessageConferences = getAvailableMessageConferences; exports.getSortedAvailMessageConferences = getSortedAvailMessageConferences; @@ -169,6 +167,7 @@ function getMessageConfTagByAreaTag(areaTag) { function getMessageAreaByTag(areaTag, optionalConfTag) { const confs = Config.messageConferences; + // :TODO: this could be cached if(_.isString(optionalConfTag)) { if(_.has(confs, [ optionalConfTag, 'areas', areaTag ])) { return confs[optionalConfTag].areas[areaTag]; @@ -311,18 +310,6 @@ function changeMessageArea(client, areaTag, cb) { changeMessageAreaWithOptions(client, areaTag, { persist : true }, cb); } -function getMessageFromRow(row) { - return { - messageId : row.message_id, - messageUuid : row.message_uuid, - replyToMsgId : row.reply_to_message_id, - toUserName : row.to_user_name, - fromUserName : row.from_user_name, - subject : row.subject, - modTimestamp : row.modified_timestamp, - }; -} - function getNewMessageCountInAreaForUser(userId, areaTag, cb) { getMessageAreaLastReadId(userId, areaTag, (err, lastMessageId) => { lastMessageId = lastMessageId || 0; @@ -349,45 +336,33 @@ function getNewMessagesInAreaForUser(userId, areaTag, cb) { const filter = { areaTag, + resultType : 'messageList', newerThanMessageId : lastMessageId, sort : 'messageId', order : 'ascending', - extraFields : [ 'message_uuid', 'reply_to_message_id', 'to_user_name', 'from_user_name', 'subject', 'modified_timestamp' ], }; if(Message.isPrivateAreaTag(areaTag)) { filter.privateTagUserId = userId; } - Message.findMessages(filter, (err, messages) => { - if(err) { - return cb(err); - } - - return cb(null, messages.map(msg => getMessageFromRow(msg))); - }); + return Message.findMessages(filter, cb); }); } function getMessageListForArea(client, areaTag, cb) { const filter = { areaTag, + resultType : 'messageList', sort : 'messageId', order : 'ascending', - extraFields : [ 'message_uuid', 'reply_to_message_id', 'to_user_name', 'from_user_name', 'subject', 'modified_timestamp' ], }; if(Message.isPrivateAreaTag(areaTag)) { filter.privateTagUserId = client.user.userId; } - Message.findMessages(filter, (err, messages) => { - if(err) { - return cb(err); - } - - return cb(null, messages.map(msg => getMessageFromRow(msg))); - }); + return Message.findMessages(filter, cb); } function getMessageIdNewerThanTimestampByArea(areaTag, newerThanTimestamp, cb) { diff --git a/core/message_base_search.js b/core/message_base_search.js new file mode 100644 index 00000000..b0259490 --- /dev/null +++ b/core/message_base_search.js @@ -0,0 +1,148 @@ +/* jslint node: true */ +'use strict'; + +// ENiGMA½ +const MenuModule = require('./menu_module.js').MenuModule; +const { + getSortedAvailMessageConferences, + getAvailableMessageAreasByConfTag, + getSortedAvailMessageAreasByConfTag, +} = require('./message_area.js'); +const Errors = require('./enig_error.js').Errors; +const Message = require('./message.js'); + +// deps +const _ = require('lodash'); + +exports.moduleInfo = { + name : 'Message Base Search', + desc : 'Module for quickly searching the message base', + author : 'NuSkooler', +}; + +const MciViewIds = { + search : { + searchTerms : 1, + search : 2, + conf : 3, + area : 4, + to : 5, + from : 6, + advSearch : 7, + } +}; + +exports.getModule = class MessageBaseSearch extends MenuModule { + constructor(options) { + super(options); + + this.menuMethods = { + search : (formData, extraArgs, cb) => { + return this.searchNow(formData, cb); + } + }; + } + + mciReady(mciData, cb) { + super.mciReady(mciData, err => { + if(err) { + return cb(err); + } + + this.prepViewController('search', 0, { mciMap : mciData.menu }, (err, vc) => { + if(err) { + return cb(err); + } + + const confView = vc.getView(MciViewIds.search.conf); + const areaView = vc.getView(MciViewIds.search.area); + + if(!confView || !areaView) { + return cb(Errors.DoesNotExist('Missing one or more required views')); + } + + const availConfs = [ { text : '-ALL-', data : '' } ].concat( + getSortedAvailMessageConferences(this.client).map(conf => Object.assign(conf, { text : conf.conf.name, data : conf.confTag } )) || [] + ); + + let availAreas = [ { text : '-ALL-', data : '' } ]; // note: will populate if conf changes from ALL + + confView.setItems(availConfs); + areaView.setItems(availAreas); + + confView.setFocusItemIndex(0); + areaView.setFocusItemIndex(0); + + confView.on('index update', idx => { + availAreas = [ { text : '-ALL-', data : '' } ].concat( + getSortedAvailMessageAreasByConfTag(availConfs[idx].confTag, { client : this.client }).map( + area => Object.assign(area, { text : area.area.name, data : area.areaTag } ) + ) + ); + areaView.setItems(availAreas); + areaView.setFocusItemIndex(0); + }); + + vc.switchFocus(MciViewIds.search.searchTerms); + return cb(null); + }); + }); + } + + searchNow(formData, cb) { + const isAdvanced = formData.submitId === MciViewIds.search.advSearch; + const value = formData.value; + + const filter = { + resultType : 'messageList', + sort : 'modTimestamp', + terms : value.searchTerms, + //extraFields : [ 'area_tag', 'message_uuid', 'reply_to_message_id', 'to_user_name', 'from_user_name', 'subject', 'modified_timestamp' ], + limit : 2048, // :TODO: best way to handle this? we should probably let the user know if some results are returned + }; + + if(isAdvanced) { + filter.toUserName = value.toUserName; + filter.fromUserName = value.fromUserName; + + if(value.confTag && !value.areaTag) { + // areaTag may be a string or array of strings + // getAvailableMessageAreasByConfTag() returns a obj - we only need tags + filter.areaTag = _.map( + getAvailableMessageAreasByConfTag(value.confTag, { client : this.client } ), + (area, areaTag) => areaTag + ); + } else if(value.areaTag) { + filter.areaTag = value.areaTag; // specific conf + area + } + } + + Message.findMessages(filter, (err, messageList) => { + if(err) { + return cb(err); + } + + if(0 === messageList.length) { + return this.gotoMenu( + this.menuConfig.config.noResultsMenu || 'messageSearchNoResults', + { menuFlags : [ 'popParent' ] }, + cb + ); + } + + const menuOpts = { + extraArgs : { + messageList, + noUpdateLastReadId : true + }, + menuFlags : [ 'popParent' ], + }; + + return this.gotoMenu( + this.menuConfig.config.messageListMenu || 'messageAreaMessageList', + menuOpts, + cb + ); + }); + } +}; diff --git a/core/mod_mixins.js b/core/mod_mixins.js index 48546825..c830813a 100644 --- a/core/mod_mixins.js +++ b/core/mod_mixins.js @@ -2,22 +2,25 @@ 'use strict'; const messageArea = require('../core/message_area.js'); +const { get } = require('lodash'); exports.MessageAreaConfTempSwitcher = Sup => class extends Sup { - tempMessageConfAndAreaSwitch(messageAreaTag) { - messageAreaTag = messageAreaTag || this.messageAreaTag; + tempMessageConfAndAreaSwitch(messageAreaTag, recordPrevious = true) { + messageAreaTag = messageAreaTag || get(this, 'config.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(recordPrevious) { + 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)) { + if(!messageArea.tempChangeMessageConfAndArea(this.client, messageAreaTag)) { this.client.log.warn( { messageAreaTag : messageArea }, 'Failed to perform temporary message area/conf switch'); } } diff --git a/core/msg_area_view_fse.js b/core/msg_area_view_fse.js index 0f25c63f..7452d9d2 100644 --- a/core/msg_area_view_fse.js +++ b/core/msg_area_view_fse.js @@ -31,6 +31,10 @@ exports.getModule = class AreaViewFSEModule extends FullScreenEditorModule { this.messageIndex = this.messageIndex || 0; this.messageTotal = this.messageList.length; + if(this.messageList.length > 0) { + this.messageAreaTag = this.messageList[this.messageIndex].areaTag; + } + const self = this; // assign *additional* menuMethods @@ -39,6 +43,9 @@ exports.getModule = class AreaViewFSEModule extends FullScreenEditorModule { if(self.messageIndex + 1 < self.messageList.length) { self.messageIndex++; + this.messageAreaTag = this.messageList[this.messageIndex].areaTag; + this.tempMessageConfAndAreaSwitch(this.messageAreaTag, false); // false=don't record prev; we want what we entered the module with + return self.loadMessageByUuid(self.messageList[self.messageIndex].messageUuid, cb); } @@ -55,6 +62,9 @@ exports.getModule = class AreaViewFSEModule extends FullScreenEditorModule { if(self.messageIndex > 0) { self.messageIndex--; + this.messageAreaTag = this.messageList[this.messageIndex].areaTag; + this.tempMessageConfAndAreaSwitch(this.messageAreaTag, false); // false=don't record prev; we want what we entered the module with + return self.loadMessageByUuid(self.messageList[self.messageIndex].messageUuid, cb); } diff --git a/core/msg_list.js b/core/msg_list.js index 72ee20f5..d48c1588 100644 --- a/core/msg_list.js +++ b/core/msg_list.js @@ -35,8 +35,8 @@ exports.moduleInfo = { author : 'NuSkooler', }; -const MCICodesIDs = { - MsgList : 1, // VM1 +const MciViewIds = { + msgList : 1, // VM1 MsgInfo1 : 2, // TL2 }; @@ -44,71 +44,68 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher( constructor(options) { super(options); - const self = this; - const config = this.menuConfig.config; + // :TODO: consider this pattern in base MenuModule - clean up code all over + this.config = Object.assign({}, _.get(options, 'menuConfig.config'), options.extraArgs); - this.messageAreaTag = config.messageAreaTag; + // :TODO: Ugg, this is needed for MessageAreaConfTempSwitcher, which wants |this.messageAreaTag| explicitly + //this.messageAreaTag = this.config.messageAreaTag; this.lastMessageReachedExit = _.get(options, 'lastMenuResult.lastMessageReached', false); - 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; - } - } - this.menuMethods = { - selectMessage : function(formData, extraArgs, cb) { - if(1 === formData.submitId) { - self.initialFocusIndex = formData.value.message; + selectMessage : (formData, extraArgs, cb) => { + if(MciViewIds.msgList === formData.submitId) { + this.initialFocusIndex = formData.value.message; const modOpts = { extraArgs : { - messageAreaTag : self.messageAreaTag, - messageList : self.messageList, + messageAreaTag : this.getSelectedAreaTag(formData.value.message),// this.config.messageAreaTag, + messageList : this.config.messageList, messageIndex : formData.value.message, - lastMessageNextExit : true, + lastMessageNextExit : true, } }; + if(_.isBoolean(this.config.noUpdateLastReadId)) { + modOpts.extraArgs.noUpdateLastReadId = this.config.noUpdateLastReadId; + } + // // 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 // + const self = this; modOpts.extraArgs.toJSON = function() { - const logMsgList = (this.messageList.length <= 4) ? - this.messageList : - this.messageList.slice(0, 2).concat(this.messageList.slice(-2)); + const logMsgList = (self.config.messageList.length <= 4) ? + self.config.messageList : + self.config.messageList.slice(0, 2).concat(self.config.messageList.slice(-2)); return { + // note |this| is scope of toJSON()! messageAreaTag : this.messageAreaTag, apprevMessageList : logMsgList, messageCount : this.messageList.length, - messageIndex : formData.value.message, + messageIndex : this.messageIndex, }; }; - return self.gotoMenu(config.menuViewPost || 'messageAreaViewPost', modOpts, cb); + return this.gotoMenu(this.config.menuViewPost || 'messageAreaViewPost', modOpts, cb); } else { return cb(null); } }, - fullExit : function(formData, extraArgs, cb) { - self.menuResult = { fullExit : true }; - return self.prevMenu(cb); + fullExit : (formData, extraArgs, cb) => { + this.menuResult = { fullExit : true }; + return this.prevMenu(cb); } }; } + getSelectedAreaTag(listIndex) { + return this.config.messageList[listIndex].areaTag || this.config.messageAreaTag; + } + enter() { if(this.lastMessageReachedExit) { return this.prevMenu(); @@ -118,12 +115,16 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher( // // Config can specify |messageAreaTag| else it comes from - // the user's current area + // the user's current area. If |messageList| is supplied, + // each item is expected to contain |areaTag|, so we use that + // instead in those cases. // - if(this.messageAreaTag) { - this.tempMessageConfAndAreaSwitch(this.messageAreaTag); - } else { - this.messageAreaTag = this.client.user.properties.message_area_tag; + if(!Array.isArray(this.config.messageList)) { + if(this.config.messageAreaTag) { + this.tempMessageConfAndAreaSwitch(this.config.messageAreaTag); + } else { + this.config.messageAreaTag = this.client.user.properties.message_area_tag; + } } } @@ -155,21 +156,27 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher( // // 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); + if(_.isArray(self.config.messageList)) { + return callback(0 === self.config.messageList.length ? new Error('No messages in area') : null); } - messageArea.getMessageListForArea(self.client, self.messageAreaTag, function msgs(err, msgList) { + messageArea.getMessageListForArea(self.client, self.config.messageAreaTag, function msgs(err, msgList) { if(!msgList || 0 === msgList.length) { return callback(new Error('No messages in area')); } - self.messageList = msgList; + self.config.messageList = msgList; return callback(err); }); }, function getLastReadMesageId(callback) { - messageArea.getMessageAreaLastReadId(self.client.user.userId, self.messageAreaTag, function lastRead(err, lastReadId) { + // messageList entries can contain |isNew| if they want to be considered new + if(Array.isArray(self.config.messageList)) { + self.lastReadId = 0; + return callback(null); + } + + messageArea.getMessageAreaLastReadId(self.client.user.userId, self.config.messageAreaTag, function lastRead(err, lastReadId) { self.lastReadId = lastReadId || 0; return callback(null); // ignore any errors, e.g. missing value }); @@ -180,10 +187,11 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher( const regIndicator = new Array(newIndicator.length + 1).join(' '); // fill with space to avoid draw issues let msgNum = 1; - self.messageList.forEach( (listItem, index) => { + self.config.messageList.forEach( (listItem, index) => { listItem.msgNum = msgNum++; listItem.ts = moment(listItem.modTimestamp).format(dateTimeFormat); - listItem.newIndicator = listItem.messageId > self.lastReadId ? newIndicator : regIndicator; + const isNew = _.isBoolean(listItem.isNew) ? listItem.isNew : listItem.messageId > self.lastReadId; + listItem.newIndicator = isNew ? newIndicator : regIndicator; if(_.isUndefined(self.initialFocusIndex) && listItem.messageId > self.lastReadId) { self.initialFocusIndex = index; @@ -192,7 +200,7 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher( return callback(null); }, function populateList(callback) { - const msgListView = vc.getView(MCICodesIDs.MsgList); + const msgListView = vc.getView(MciViewIds.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}'; @@ -200,19 +208,19 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher( // :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 => { + msgListView.setItems(_.map(self.config.messageList, listEntry => { return stringFormat(listFormat, listEntry); })); - msgListView.setFocusItems(_.map(self.messageList, listEntry => { + msgListView.setFocusItems(_.map(self.config.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 } )); + MciViewIds.msgInfo1, + stringFormat(messageInfo1Format, { msgNumSelected : (idx + 1), msgNumTotal : self.config.messageList.length } )); }); if(self.initialFocusIndex > 0) { @@ -228,8 +236,8 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher( const messageInfo1Format = self.menuConfig.config.messageInfo1Format || '{msgNumSelected} / {msgNumTotal}'; self.setViewText( 'allViews', - MCICodesIDs.MsgInfo1, - stringFormat(messageInfo1Format, { msgNumSelected : self.initialFocusIndex + 1, msgNumTotal : self.messageList.length } )); + MciViewIds.msgInfo1, + stringFormat(messageInfo1Format, { msgNumSelected : self.initialFocusIndex + 1, msgNumTotal : self.config.messageList.length } )); return callback(null); }, ], diff --git a/core/vertical_menu_view.js b/core/vertical_menu_view.js index 6f083193..423ffc00 100644 --- a/core/vertical_menu_view.js +++ b/core/vertical_menu_view.js @@ -178,8 +178,7 @@ VerticalMenuView.prototype.onKeyPress = function(ch, key) { VerticalMenuView.prototype.getData = function() { const item = this.getItem(this.focusedItemIndex); - return item.data ? item.data : this.focusedItemIndex; - //return this.focusedItemIndex; + return _.isString(item.data) ? item.data : this.focusedItemIndex; }; VerticalMenuView.prototype.setItems = function(items) {