mirror of
https://github.com/NuSkooler/enigma-bbs.git
synced 2025-06-07 13:15:28 +02:00
MANY changes around message listing / viewing
* If messageList is used, alwasy require items to contain areaTag * Standardize messageList a bit - still WIP, needs cleaned up * Lof of changes around area/conf tracking in relation to messages and message listings * Work for message searching * Clean up of various code, much to do...
This commit is contained in:
parent
cc2ee9c586
commit
837326e15a
9 changed files with 329 additions and 146 deletions
76
core/fse.js
76
core/fse.js
|
@ -104,6 +104,7 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
||||||
this.editorMode = config.editorMode;
|
this.editorMode = config.editorMode;
|
||||||
|
|
||||||
if(config.messageAreaTag) {
|
if(config.messageAreaTag) {
|
||||||
|
// :TODO: swtich to this.config.messageAreaTag so we can follow Object.assign pattern for config/extraArgs
|
||||||
this.messageAreaTag = config.messageAreaTag;
|
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;
|
this.isReady = false;
|
||||||
|
|
||||||
if(_.has(options, 'extraArgs.message')) {
|
if(_.has(options, 'extraArgs.message')) {
|
||||||
|
@ -342,49 +346,56 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
||||||
return cb(null);
|
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) {
|
setMessage(message) {
|
||||||
this.message = message;
|
this.message = message;
|
||||||
|
|
||||||
updateMessageAreaLastReadId(
|
this.updateLastReadId( () => {
|
||||||
this.client.user.userId, this.messageAreaTag, this.message.messageId, () => {
|
if(this.isReady) {
|
||||||
|
this.initHeaderViewMode();
|
||||||
|
this.initFooterViewMode();
|
||||||
|
|
||||||
if(this.isReady) {
|
const bodyMessageView = this.viewControllers.body.getView(MciViewIds.body.message);
|
||||||
this.initHeaderViewMode();
|
let msg = this.message.message;
|
||||||
this.initFooterViewMode();
|
|
||||||
|
|
||||||
const bodyMessageView = this.viewControllers.body.getView(MciViewIds.body.message);
|
if(bodyMessageView && _.has(this, 'message.message')) {
|
||||||
let msg = this.message.message;
|
//
|
||||||
|
// We handle ANSI messages differently than standard messages -- this is required as
|
||||||
if(bodyMessageView && _.has(this, 'message.message')) {
|
// 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
|
// Find tearline - we want to color it differently.
|
||||||
// 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)) {
|
const tearLinePos = this.message.getTearLinePosition(msg);
|
||||||
//
|
|
||||||
// Find tearline - we want to color it differently.
|
|
||||||
//
|
|
||||||
const tearLinePos = this.message.getTearLinePosition(msg);
|
|
||||||
|
|
||||||
if(tearLinePos > -1) {
|
if(tearLinePos > -1) {
|
||||||
msg = insert(msg, tearLinePos, bodyMessageView.getSGRFor('text'));
|
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
getMessage(cb) {
|
||||||
|
@ -816,6 +827,9 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
||||||
this.setHeaderText(MciViewIds.header.msgTotal, this.messageTotal.toString());
|
this.setHeaderText(MciViewIds.header.msgTotal, this.messageTotal.toString());
|
||||||
|
|
||||||
this.updateCustomViewTextsWithFilter('header', MciViewIds.header.customRangeStart, this.getHeaderFormatObj());
|
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() {
|
initHeaderReplyEditMode() {
|
||||||
|
|
|
@ -117,7 +117,7 @@ module.exports = class MenuStack {
|
||||||
};
|
};
|
||||||
|
|
||||||
if(_.isObject(options)) {
|
if(_.isObject(options)) {
|
||||||
loadOpts.extraArgs = options.extraArgs;
|
loadOpts.extraArgs = options.extraArgs || _.get(options, 'formData.value');
|
||||||
loadOpts.lastMenuResult = options.lastMenuResult;
|
loadOpts.lastMenuResult = options.lastMenuResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -87,6 +87,12 @@ const FTN_PROPERTY_NAMES = {
|
||||||
FtnSeenBy : 'ftn_seen_by', // http://ftsc.org/docs/fts-0004.001
|
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 {
|
module.exports = class Message {
|
||||||
constructor(
|
constructor(
|
||||||
{
|
{
|
||||||
|
@ -189,6 +195,16 @@ module.exports = class Message {
|
||||||
return uuidParse.unparse(createNamedUUID(ENIGMA_MESSAGE_UUID_NAMESPACE, Buffer.concat( [ areaTag, modTimestamp, subject, body ] )));
|
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:
|
Find message IDs or UUIDs by filter. Available filters/options:
|
||||||
|
|
||||||
|
@ -199,11 +215,10 @@ module.exports = class Message {
|
||||||
filter.replyToMesageId
|
filter.replyToMesageId
|
||||||
filter.newerThanTimestamp
|
filter.newerThanTimestamp
|
||||||
filter.newerThanMessageId
|
filter.newerThanMessageId
|
||||||
*filter.confTag - all area tags in confTag
|
filter.areaTag - note if you want by conf, send in all areas for a conf
|
||||||
filter.areaTag
|
|
||||||
*filter.metaTuples - {category, name, value}
|
*filter.metaTuples - {category, name, value}
|
||||||
|
|
||||||
*filter.terms - FTS search
|
filter.terms - FTS search
|
||||||
|
|
||||||
filter.sort = modTimestamp | messageId
|
filter.sort = modTimestamp | messageId
|
||||||
filter.order = ascending | (descending)
|
filter.order = ascending | (descending)
|
||||||
|
@ -223,7 +238,13 @@ module.exports = class Message {
|
||||||
filter.resultType = filter.resultType || 'id';
|
filter.resultType = filter.resultType || 'id';
|
||||||
filter.extraFields = filter.extraFields || [];
|
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)) {
|
if(moment.isMoment(filter.newerThanTimestamp)) {
|
||||||
filter.newerThanTimestamp = getISOTimestampString(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}
|
WHERE meta_category = "System" AND meta_name = "${Message.SystemMetaNames.LocalToUserID}" AND meta_value = ${filter.privateTagUserId}
|
||||||
)`);
|
)`);
|
||||||
} else {
|
} else {
|
||||||
let areaTags = [];
|
if(filter.areaTag && filter.areaTag.length > 0) {
|
||||||
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(Array.isArray(filter.areaTag)) {
|
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(', ');
|
const areaList = filter.areaTag.map(t => `"${t}"`).join(', ');
|
||||||
appendWhereClause(`m.area_tag IN(${areaList})`);
|
appendWhereClause(`m.area_tag IN(${areaList})`);
|
||||||
} else {
|
} else if(_.isString(filter.areaTag)) {
|
||||||
appendWhereClause(`m.area_tag = "${areaTags[0]}"`);
|
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) {
|
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}`);
|
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}`;
|
sql += `${sqlWhere} ${sqlOrderBy}`;
|
||||||
|
|
||||||
if(_.isNumber(filter.limit)) {
|
if(_.isNumber(filter.limit)) {
|
||||||
|
@ -332,9 +355,12 @@ module.exports = class Message {
|
||||||
} else {
|
} else {
|
||||||
const matches = [];
|
const matches = [];
|
||||||
const extra = filter.extraFields.length > 0;
|
const extra = filter.extraFields.length > 0;
|
||||||
|
|
||||||
|
const rowConv = 'messageList' === filter.resultType ? Message.getMessageFromRow : row => row;
|
||||||
|
|
||||||
msgDb.each(sql, (err, row) => {
|
msgDb.each(sql, (err, row) => {
|
||||||
if(_.isObject(row)) {
|
if(_.isObject(row)) {
|
||||||
matches.push(extra ? row : row[field]);
|
matches.push(extra ? rowConv(row) : row[field]);
|
||||||
}
|
}
|
||||||
}, err => {
|
}, err => {
|
||||||
return cb(err, matches);
|
return cb(err, matches);
|
||||||
|
|
|
@ -8,13 +8,11 @@ const Message = require('./message.js');
|
||||||
const Log = require('./logger.js').log;
|
const Log = require('./logger.js').log;
|
||||||
const msgNetRecord = require('./msg_network.js').recordMessage;
|
const msgNetRecord = require('./msg_network.js').recordMessage;
|
||||||
const sortAreasOrConfs = require('./conf_area_util.js').sortAreasOrConfs;
|
const sortAreasOrConfs = require('./conf_area_util.js').sortAreasOrConfs;
|
||||||
const { getISOTimestampString } = require('./database.js');
|
|
||||||
|
|
||||||
// deps
|
// deps
|
||||||
const async = require('async');
|
const async = require('async');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
const moment = require('moment');
|
|
||||||
|
|
||||||
exports.getAvailableMessageConferences = getAvailableMessageConferences;
|
exports.getAvailableMessageConferences = getAvailableMessageConferences;
|
||||||
exports.getSortedAvailMessageConferences = getSortedAvailMessageConferences;
|
exports.getSortedAvailMessageConferences = getSortedAvailMessageConferences;
|
||||||
|
@ -169,6 +167,7 @@ function getMessageConfTagByAreaTag(areaTag) {
|
||||||
function getMessageAreaByTag(areaTag, optionalConfTag) {
|
function getMessageAreaByTag(areaTag, optionalConfTag) {
|
||||||
const confs = Config.messageConferences;
|
const confs = Config.messageConferences;
|
||||||
|
|
||||||
|
// :TODO: this could be cached
|
||||||
if(_.isString(optionalConfTag)) {
|
if(_.isString(optionalConfTag)) {
|
||||||
if(_.has(confs, [ optionalConfTag, 'areas', areaTag ])) {
|
if(_.has(confs, [ optionalConfTag, 'areas', areaTag ])) {
|
||||||
return confs[optionalConfTag].areas[areaTag];
|
return confs[optionalConfTag].areas[areaTag];
|
||||||
|
@ -311,18 +310,6 @@ function changeMessageArea(client, areaTag, cb) {
|
||||||
changeMessageAreaWithOptions(client, areaTag, { persist : true }, 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) {
|
function getNewMessageCountInAreaForUser(userId, areaTag, cb) {
|
||||||
getMessageAreaLastReadId(userId, areaTag, (err, lastMessageId) => {
|
getMessageAreaLastReadId(userId, areaTag, (err, lastMessageId) => {
|
||||||
lastMessageId = lastMessageId || 0;
|
lastMessageId = lastMessageId || 0;
|
||||||
|
@ -349,45 +336,33 @@ function getNewMessagesInAreaForUser(userId, areaTag, cb) {
|
||||||
|
|
||||||
const filter = {
|
const filter = {
|
||||||
areaTag,
|
areaTag,
|
||||||
|
resultType : 'messageList',
|
||||||
newerThanMessageId : lastMessageId,
|
newerThanMessageId : lastMessageId,
|
||||||
sort : 'messageId',
|
sort : 'messageId',
|
||||||
order : 'ascending',
|
order : 'ascending',
|
||||||
extraFields : [ 'message_uuid', 'reply_to_message_id', 'to_user_name', 'from_user_name', 'subject', 'modified_timestamp' ],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if(Message.isPrivateAreaTag(areaTag)) {
|
if(Message.isPrivateAreaTag(areaTag)) {
|
||||||
filter.privateTagUserId = userId;
|
filter.privateTagUserId = userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
Message.findMessages(filter, (err, messages) => {
|
return Message.findMessages(filter, cb);
|
||||||
if(err) {
|
|
||||||
return cb(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
return cb(null, messages.map(msg => getMessageFromRow(msg)));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMessageListForArea(client, areaTag, cb) {
|
function getMessageListForArea(client, areaTag, cb) {
|
||||||
const filter = {
|
const filter = {
|
||||||
areaTag,
|
areaTag,
|
||||||
|
resultType : 'messageList',
|
||||||
sort : 'messageId',
|
sort : 'messageId',
|
||||||
order : 'ascending',
|
order : 'ascending',
|
||||||
extraFields : [ 'message_uuid', 'reply_to_message_id', 'to_user_name', 'from_user_name', 'subject', 'modified_timestamp' ],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if(Message.isPrivateAreaTag(areaTag)) {
|
if(Message.isPrivateAreaTag(areaTag)) {
|
||||||
filter.privateTagUserId = client.user.userId;
|
filter.privateTagUserId = client.user.userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
Message.findMessages(filter, (err, messages) => {
|
return Message.findMessages(filter, cb);
|
||||||
if(err) {
|
|
||||||
return cb(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
return cb(null, messages.map(msg => getMessageFromRow(msg)));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMessageIdNewerThanTimestampByArea(areaTag, newerThanTimestamp, cb) {
|
function getMessageIdNewerThanTimestampByArea(areaTag, newerThanTimestamp, cb) {
|
||||||
|
|
148
core/message_base_search.js
Normal file
148
core/message_base_search.js
Normal file
|
@ -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
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
|
@ -2,22 +2,25 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const messageArea = require('../core/message_area.js');
|
const messageArea = require('../core/message_area.js');
|
||||||
|
const { get } = require('lodash');
|
||||||
|
|
||||||
|
|
||||||
exports.MessageAreaConfTempSwitcher = Sup => class extends Sup {
|
exports.MessageAreaConfTempSwitcher = Sup => class extends Sup {
|
||||||
|
|
||||||
tempMessageConfAndAreaSwitch(messageAreaTag) {
|
tempMessageConfAndAreaSwitch(messageAreaTag, recordPrevious = true) {
|
||||||
messageAreaTag = messageAreaTag || this.messageAreaTag;
|
messageAreaTag = messageAreaTag || get(this, 'config.messageAreaTag', this.messageAreaTag);
|
||||||
if(!messageAreaTag) {
|
if(!messageAreaTag) {
|
||||||
return; // nothing to do!
|
return; // nothing to do!
|
||||||
}
|
}
|
||||||
|
|
||||||
this.prevMessageConfAndArea = {
|
if(recordPrevious) {
|
||||||
confTag : this.client.user.properties.message_conf_tag,
|
this.prevMessageConfAndArea = {
|
||||||
areaTag : this.client.user.properties.message_area_tag,
|
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');
|
this.client.log.warn( { messageAreaTag : messageArea }, 'Failed to perform temporary message area/conf switch');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,10 @@ exports.getModule = class AreaViewFSEModule extends FullScreenEditorModule {
|
||||||
this.messageIndex = this.messageIndex || 0;
|
this.messageIndex = this.messageIndex || 0;
|
||||||
this.messageTotal = this.messageList.length;
|
this.messageTotal = this.messageList.length;
|
||||||
|
|
||||||
|
if(this.messageList.length > 0) {
|
||||||
|
this.messageAreaTag = this.messageList[this.messageIndex].areaTag;
|
||||||
|
}
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
// assign *additional* menuMethods
|
// assign *additional* menuMethods
|
||||||
|
@ -39,6 +43,9 @@ exports.getModule = class AreaViewFSEModule extends FullScreenEditorModule {
|
||||||
if(self.messageIndex + 1 < self.messageList.length) {
|
if(self.messageIndex + 1 < self.messageList.length) {
|
||||||
self.messageIndex++;
|
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);
|
return self.loadMessageByUuid(self.messageList[self.messageIndex].messageUuid, cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,6 +62,9 @@ exports.getModule = class AreaViewFSEModule extends FullScreenEditorModule {
|
||||||
if(self.messageIndex > 0) {
|
if(self.messageIndex > 0) {
|
||||||
self.messageIndex--;
|
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);
|
return self.loadMessageByUuid(self.messageList[self.messageIndex].messageUuid, cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
112
core/msg_list.js
112
core/msg_list.js
|
@ -35,8 +35,8 @@ exports.moduleInfo = {
|
||||||
author : 'NuSkooler',
|
author : 'NuSkooler',
|
||||||
};
|
};
|
||||||
|
|
||||||
const MCICodesIDs = {
|
const MciViewIds = {
|
||||||
MsgList : 1, // VM1
|
msgList : 1, // VM1
|
||||||
MsgInfo1 : 2, // TL2
|
MsgInfo1 : 2, // TL2
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -44,71 +44,68 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
const self = this;
|
// :TODO: consider this pattern in base MenuModule - clean up code all over
|
||||||
const config = this.menuConfig.config;
|
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);
|
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 = {
|
this.menuMethods = {
|
||||||
selectMessage : function(formData, extraArgs, cb) {
|
selectMessage : (formData, extraArgs, cb) => {
|
||||||
if(1 === formData.submitId) {
|
if(MciViewIds.msgList === formData.submitId) {
|
||||||
self.initialFocusIndex = formData.value.message;
|
this.initialFocusIndex = formData.value.message;
|
||||||
|
|
||||||
const modOpts = {
|
const modOpts = {
|
||||||
extraArgs : {
|
extraArgs : {
|
||||||
messageAreaTag : self.messageAreaTag,
|
messageAreaTag : this.getSelectedAreaTag(formData.value.message),// this.config.messageAreaTag,
|
||||||
messageList : self.messageList,
|
messageList : this.config.messageList,
|
||||||
messageIndex : formData.value.message,
|
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
|
// 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
|
// due to the size of |messageList|. See https://github.com/trentm/node-bunyan/issues/189
|
||||||
//
|
//
|
||||||
|
const self = this;
|
||||||
modOpts.extraArgs.toJSON = function() {
|
modOpts.extraArgs.toJSON = function() {
|
||||||
const logMsgList = (this.messageList.length <= 4) ?
|
const logMsgList = (self.config.messageList.length <= 4) ?
|
||||||
this.messageList :
|
self.config.messageList :
|
||||||
this.messageList.slice(0, 2).concat(this.messageList.slice(-2));
|
self.config.messageList.slice(0, 2).concat(self.config.messageList.slice(-2));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
// note |this| is scope of toJSON()!
|
||||||
messageAreaTag : this.messageAreaTag,
|
messageAreaTag : this.messageAreaTag,
|
||||||
apprevMessageList : logMsgList,
|
apprevMessageList : logMsgList,
|
||||||
messageCount : this.messageList.length,
|
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 {
|
} else {
|
||||||
return cb(null);
|
return cb(null);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
fullExit : function(formData, extraArgs, cb) {
|
fullExit : (formData, extraArgs, cb) => {
|
||||||
self.menuResult = { fullExit : true };
|
this.menuResult = { fullExit : true };
|
||||||
return self.prevMenu(cb);
|
return this.prevMenu(cb);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getSelectedAreaTag(listIndex) {
|
||||||
|
return this.config.messageList[listIndex].areaTag || this.config.messageAreaTag;
|
||||||
|
}
|
||||||
|
|
||||||
enter() {
|
enter() {
|
||||||
if(this.lastMessageReachedExit) {
|
if(this.lastMessageReachedExit) {
|
||||||
return this.prevMenu();
|
return this.prevMenu();
|
||||||
|
@ -118,12 +115,16 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
|
||||||
|
|
||||||
//
|
//
|
||||||
// Config can specify |messageAreaTag| else it comes from
|
// 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) {
|
if(!Array.isArray(this.config.messageList)) {
|
||||||
this.tempMessageConfAndAreaSwitch(this.messageAreaTag);
|
if(this.config.messageAreaTag) {
|
||||||
} else {
|
this.tempMessageConfAndAreaSwitch(this.config.messageAreaTag);
|
||||||
this.messageAreaTag = this.client.user.properties.message_area_tag;
|
} 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
|
// Config can supply messages else we'll need to populate the list now
|
||||||
//
|
//
|
||||||
if(_.isArray(self.messageList)) {
|
if(_.isArray(self.config.messageList)) {
|
||||||
return callback(0 === self.messageList.length ? new Error('No messages in area') : null);
|
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) {
|
if(!msgList || 0 === msgList.length) {
|
||||||
return callback(new Error('No messages in area'));
|
return callback(new Error('No messages in area'));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.messageList = msgList;
|
self.config.messageList = msgList;
|
||||||
return callback(err);
|
return callback(err);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function getLastReadMesageId(callback) {
|
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;
|
self.lastReadId = lastReadId || 0;
|
||||||
return callback(null); // ignore any errors, e.g. missing value
|
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
|
const regIndicator = new Array(newIndicator.length + 1).join(' '); // fill with space to avoid draw issues
|
||||||
|
|
||||||
let msgNum = 1;
|
let msgNum = 1;
|
||||||
self.messageList.forEach( (listItem, index) => {
|
self.config.messageList.forEach( (listItem, index) => {
|
||||||
listItem.msgNum = msgNum++;
|
listItem.msgNum = msgNum++;
|
||||||
listItem.ts = moment(listItem.modTimestamp).format(dateTimeFormat);
|
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) {
|
if(_.isUndefined(self.initialFocusIndex) && listItem.messageId > self.lastReadId) {
|
||||||
self.initialFocusIndex = index;
|
self.initialFocusIndex = index;
|
||||||
|
@ -192,7 +200,7 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
|
||||||
return callback(null);
|
return callback(null);
|
||||||
},
|
},
|
||||||
function populateList(callback) {
|
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 listFormat = self.menuConfig.config.listFormat || '{msgNum} - {subject} - {toUserName}';
|
||||||
const focusListFormat = self.menuConfig.config.focusListFormat || listFormat; // :TODO: default change color here
|
const focusListFormat = self.menuConfig.config.focusListFormat || listFormat; // :TODO: default change color here
|
||||||
const messageInfo1Format = self.menuConfig.config.messageInfo1Format || '{msgNumSelected} / {msgNumTotal}';
|
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
|
// :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
|
// 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);
|
return stringFormat(listFormat, listEntry);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
msgListView.setFocusItems(_.map(self.messageList, listEntry => {
|
msgListView.setFocusItems(_.map(self.config.messageList, listEntry => {
|
||||||
return stringFormat(focusListFormat, listEntry);
|
return stringFormat(focusListFormat, listEntry);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
msgListView.on('index update', idx => {
|
msgListView.on('index update', idx => {
|
||||||
self.setViewText(
|
self.setViewText(
|
||||||
'allViews',
|
'allViews',
|
||||||
MCICodesIDs.MsgInfo1,
|
MciViewIds.msgInfo1,
|
||||||
stringFormat(messageInfo1Format, { msgNumSelected : (idx + 1), msgNumTotal : self.messageList.length } ));
|
stringFormat(messageInfo1Format, { msgNumSelected : (idx + 1), msgNumTotal : self.config.messageList.length } ));
|
||||||
});
|
});
|
||||||
|
|
||||||
if(self.initialFocusIndex > 0) {
|
if(self.initialFocusIndex > 0) {
|
||||||
|
@ -228,8 +236,8 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
|
||||||
const messageInfo1Format = self.menuConfig.config.messageInfo1Format || '{msgNumSelected} / {msgNumTotal}';
|
const messageInfo1Format = self.menuConfig.config.messageInfo1Format || '{msgNumSelected} / {msgNumTotal}';
|
||||||
self.setViewText(
|
self.setViewText(
|
||||||
'allViews',
|
'allViews',
|
||||||
MCICodesIDs.MsgInfo1,
|
MciViewIds.msgInfo1,
|
||||||
stringFormat(messageInfo1Format, { msgNumSelected : self.initialFocusIndex + 1, msgNumTotal : self.messageList.length } ));
|
stringFormat(messageInfo1Format, { msgNumSelected : self.initialFocusIndex + 1, msgNumTotal : self.config.messageList.length } ));
|
||||||
return callback(null);
|
return callback(null);
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -178,8 +178,7 @@ VerticalMenuView.prototype.onKeyPress = function(ch, key) {
|
||||||
|
|
||||||
VerticalMenuView.prototype.getData = function() {
|
VerticalMenuView.prototype.getData = function() {
|
||||||
const item = this.getItem(this.focusedItemIndex);
|
const item = this.getItem(this.focusedItemIndex);
|
||||||
return item.data ? item.data : this.focusedItemIndex;
|
return _.isString(item.data) ? item.data : this.focusedItemIndex;
|
||||||
//return this.focusedItemIndex;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
VerticalMenuView.prototype.setItems = function(items) {
|
VerticalMenuView.prototype.setItems = function(items) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue