* ENiGMA now require Node.js 6.x LTS+
* Bump version to 0.0.4-alpha * Update package dependencies * Use modified node-buffers that does not use deprecated Array.get() * Update lodash dependency to 4.x & convert to new methods/etc. * Better 'noHistory' support for menu stack * Fix bug in download queue init * Misc code cleanup
This commit is contained in:
parent
1fc9fc1c90
commit
12d4c158c4
|
@ -226,7 +226,7 @@ function ANSIEscapeParser(options) {
|
||||||
|
|
||||||
self.lastMciCode = fullMciCode;
|
self.lastMciCode = fullMciCode;
|
||||||
|
|
||||||
self.graphicRenditionForErase = _.clone(self.graphicRendition, true);
|
self.graphicRenditionForErase = _.clone(self.graphicRendition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
/* jslint node: true */
|
/* jslint node: true */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var miscUtil = require('./misc_util.js');
|
// ENiGMA½
|
||||||
|
const miscUtil = require('./misc_util.js');
|
||||||
|
|
||||||
var fs = require('fs');
|
// deps
|
||||||
var paths = require('path');
|
const fs = require('fs');
|
||||||
var async = require('async');
|
const paths = require('path');
|
||||||
var _ = require('lodash');
|
const async = require('async');
|
||||||
var hjson = require('hjson');
|
const _ = require('lodash');
|
||||||
var assert = require('assert');
|
const hjson = require('hjson');
|
||||||
|
const assert = require('assert');
|
||||||
|
|
||||||
exports.init = init;
|
exports.init = init;
|
||||||
exports.getDefaultPath = getDefaultPath;
|
exports.getDefaultPath = getDefaultPath;
|
||||||
|
|
||||||
function hasMessageConferenceAndArea(config) {
|
function hasMessageConferenceAndArea(config) {
|
||||||
assert(_.isObject(config.messageConferences)); // we create one ourself!
|
assert(_.isObject(config.messageConferences)); // we create one ourself!
|
||||||
|
@ -43,38 +45,45 @@ function init(configPath, cb) {
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
function loadUserConfig(callback) {
|
function loadUserConfig(callback) {
|
||||||
if(_.isString(configPath)) {
|
if(!_.isString(configPath)) {
|
||||||
fs.readFile(configPath, { encoding : 'utf8' }, function configData(err, data) {
|
return callback(null, { } );
|
||||||
if(err) {
|
|
||||||
callback(err);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
var configJson = hjson.parse(data);
|
|
||||||
callback(null, configJson);
|
|
||||||
} catch(e) {
|
|
||||||
callback(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
callback(null, { } );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fs.readFile(configPath, { encoding : 'utf8' }, (err, configData) => {
|
||||||
|
if(err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
let configJson;
|
||||||
|
try {
|
||||||
|
configJson = hjson.parse(configData);
|
||||||
|
} catch(e) {
|
||||||
|
return callback(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return callback(null, configJson);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
function mergeWithDefaultConfig(configJson, callback) {
|
function mergeWithDefaultConfig(configJson, callback) {
|
||||||
var mergedConfig = _.merge(getDefaultConfig(), configJson, function mergeCustomizer(conf1, conf2) {
|
|
||||||
// Arrays should always concat
|
const mergedConfig = _.mergeWith(
|
||||||
if(_.isArray(conf1)) {
|
getDefaultConfig(),
|
||||||
// :TODO: look for collisions & override dupes
|
configJson, (conf1, conf2) => {
|
||||||
return conf1.concat(conf2);
|
// Arrays should always concat
|
||||||
|
if(_.isArray(conf1)) {
|
||||||
|
// :TODO: look for collisions & override dupes
|
||||||
|
return conf1.concat(conf2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
callback(null, mergedConfig);
|
return callback(null, mergedConfig);
|
||||||
},
|
},
|
||||||
function validate(mergedConfig, callback) {
|
function validate(mergedConfig, callback) {
|
||||||
//
|
//
|
||||||
// Various sections must now exist in config
|
// Various sections must now exist in config
|
||||||
//
|
//
|
||||||
|
// :TODO: Logic is broken here:
|
||||||
if(hasMessageConferenceAndArea(mergedConfig)) {
|
if(hasMessageConferenceAndArea(mergedConfig)) {
|
||||||
var msgAreasErr = new Error('Please create at least one message conference and area!');
|
var msgAreasErr = new Error('Please create at least one message conference and area!');
|
||||||
msgAreasErr.code = 'EBADCONFIG';
|
msgAreasErr.code = 'EBADCONFIG';
|
||||||
|
@ -92,7 +101,7 @@ function init(configPath, cb) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDefaultPath() {
|
function getDefaultPath() {
|
||||||
var base = miscUtil.resolvePath('~/');
|
const base = miscUtil.resolvePath('~/');
|
||||||
if(base) {
|
if(base) {
|
||||||
// e.g. /home/users/joeuser/.config/enigma-bbs/config.hjson
|
// e.g. /home/users/joeuser/.config/enigma-bbs/config.hjson
|
||||||
return paths.join(base, '.config', 'enigma-bbs', 'config.hjson');
|
return paths.join(base, '.config', 'enigma-bbs', 'config.hjson');
|
||||||
|
|
|
@ -8,7 +8,11 @@ module.exports = class DownloadQueue {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
|
|
||||||
if(!Array.isArray(this.client.user.downloadQueue)) {
|
if(!Array.isArray(this.client.user.downloadQueue)) {
|
||||||
this.loadFromProperty(client);
|
if(this.client.user.properties.dl_queue) {
|
||||||
|
this.loadFromProperty(this.client.user.properties.dl_queue);
|
||||||
|
} else {
|
||||||
|
this.client.user.downloadQueue = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,3 +30,10 @@ exports.Errors = {
|
||||||
ExternalProcess : (reason, reasonCode) => new EnigError('External process error', -32005, reason, reasonCode),
|
ExternalProcess : (reason, reasonCode) => new EnigError('External process error', -32005, reason, reasonCode),
|
||||||
MissingConfig : (reason, reasonCode) => new EnigError('Missing configuration', -32006, reason, reasonCode),
|
MissingConfig : (reason, reasonCode) => new EnigError('Missing configuration', -32006, reason, reasonCode),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.ErrorReasons = {
|
||||||
|
AlreadyThere : 'ALREADYTHERE',
|
||||||
|
InvalidNextMenu : 'BADNEXT',
|
||||||
|
NoPreviousMenu : 'NOPREV',
|
||||||
|
NoConditionMatch : 'NOCONDMATCH',
|
||||||
|
};
|
|
@ -45,7 +45,7 @@ function getAvailableFileAreas(client, options) {
|
||||||
// perform ACS check per conf & omit internal if desired
|
// perform ACS check per conf & omit internal if desired
|
||||||
const allAreas = _.map(Config.fileBase.areas, (areaInfo, areaTag) => Object.assign(areaInfo, { areaTag : areaTag } ));
|
const allAreas = _.map(Config.fileBase.areas, (areaInfo, areaTag) => Object.assign(areaInfo, { areaTag : areaTag } ));
|
||||||
|
|
||||||
return _.omit(allAreas, areaInfo => {
|
return _.omitBy(allAreas, areaInfo => {
|
||||||
if(!options.includeSystemInternal && isInternalArea(areaInfo.areaTag)) {
|
if(!options.includeSystemInternal && isInternalArea(areaInfo.areaTag)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -274,7 +274,7 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
||||||
}
|
}
|
||||||
|
|
||||||
getFooterName() {
|
getFooterName() {
|
||||||
return 'footer' + _.capitalize(this.footerMode); // e.g. 'footerEditor', 'footerEditorMenu', ...
|
return 'footer' + _.upperFirst(this.footerMode); // e.g. 'footerEditor', 'footerEditorMenu', ...
|
||||||
}
|
}
|
||||||
|
|
||||||
getFormId(name) {
|
getFormId(name) {
|
||||||
|
@ -431,9 +431,9 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
||||||
callback(null);
|
callback(null);
|
||||||
},
|
},
|
||||||
function displayFooterArt(callback) {
|
function displayFooterArt(callback) {
|
||||||
var footerArt = self.menuConfig.config.art[options.footerName];
|
const footerArt = self.menuConfig.config.art[options.footerName];
|
||||||
|
|
||||||
theme.displayThemedAsset(
|
or theme.displayThemedAsset(
|
||||||
footerArt,
|
footerArt,
|
||||||
self.client,
|
self.client,
|
||||||
{ font : self.menuConfig.font },
|
{ font : self.menuConfig.font },
|
||||||
|
@ -723,7 +723,7 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
||||||
if(posView) {
|
if(posView) {
|
||||||
this.client.term.rawWrite(ansi.savePos());
|
this.client.term.rawWrite(ansi.savePos());
|
||||||
// :TODO: Use new formatting techniques here, e.g. state.cursorPositionRow, cursorPositionCol and cursorPositionFormat
|
// :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'));
|
posView.setText(_.padStart(String(pos.row + 1), 2, '0') + ',' + _.padEnd(String(pos.col + 1), 2, '0'));
|
||||||
this.client.term.rawWrite(ansi.restorePos());
|
this.client.term.rawWrite(ansi.restorePos());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,7 +104,7 @@ exports.MenuModule = class MenuModule extends PluginModule {
|
||||||
|
|
||||||
self.client.once('cursor position report', pos => {
|
self.client.once('cursor position report', pos => {
|
||||||
pausePosition = { row : pos[0], col : 1 };
|
pausePosition = { row : pos[0], col : 1 };
|
||||||
self.client.log.trace('After art position recorded', { position : pausePosition } );
|
self.client.log.trace('After art position recorded', pausePosition );
|
||||||
return callback(null);
|
return callback(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -133,37 +133,34 @@ module.exports = class MenuStack {
|
||||||
currentModuleInfo.savedState = currentModuleInfo.instance.getSaveState();
|
currentModuleInfo.savedState = currentModuleInfo.instance.getSaveState();
|
||||||
|
|
||||||
currentModuleInfo.instance.leave();
|
currentModuleInfo.instance.leave();
|
||||||
|
|
||||||
|
if(modInst.menuConfig.options.menuFlags.includes('noHistory')) {
|
||||||
|
this.pop().instance.leave(); // leave & remove current
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const noHistory = modInst.menuConfig.options.menuFlags.indexOf('noHistory') > -1;
|
self.push({
|
||||||
|
name : name,
|
||||||
const stackToLog = _.map(self.stack, stackEntry => stackEntry.name);
|
instance : modInst,
|
||||||
|
extraArgs : loadOpts.extraArgs,
|
||||||
if(!noHistory) {
|
});
|
||||||
self.push({
|
|
||||||
name : name,
|
|
||||||
instance : modInst,
|
|
||||||
extraArgs : loadOpts.extraArgs,
|
|
||||||
});
|
|
||||||
|
|
||||||
stackToLog.push(name);
|
|
||||||
} else {
|
|
||||||
stackToLog.push(`${name} (noHistory)`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// restore previous state if requested
|
// restore previous state if requested
|
||||||
if(options && options.savedState) {
|
if(options && options.savedState) {
|
||||||
modInst.restoreSavedState(options.savedState);
|
modInst.restoreSavedState(options.savedState);
|
||||||
}
|
}
|
||||||
|
|
||||||
modInst.enter();
|
const stackEntries = self.stack.map(stackEntry => {
|
||||||
|
let name = stackEntry.name;
|
||||||
|
if(stackEntry.instance.menuConfig.options.menuFlags.length > 0) {
|
||||||
|
name += ` (${stackEntry.instance.menuConfig.options.menuFlags.join(', ')})`;
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
});
|
||||||
|
|
||||||
self.client.log.trace(
|
self.client.log.trace( { stack : stackEntries }, 'Updated menu stack' );
|
||||||
{
|
|
||||||
stack : stackToLog
|
modInst.enter();
|
||||||
},
|
|
||||||
'Updated menu stack'
|
|
||||||
);
|
|
||||||
|
|
||||||
if(cb) {
|
if(cb) {
|
||||||
cb(null);
|
cb(null);
|
||||||
|
|
|
@ -94,18 +94,20 @@ function loadMenu(options, cb) {
|
||||||
{ moduleName : modData.name, extraArgs : options.extraArgs, config : modData.config, info : modData.mod.modInfo },
|
{ moduleName : modData.name, extraArgs : options.extraArgs, config : modData.config, info : modData.mod.modInfo },
|
||||||
'Creating menu module instance');
|
'Creating menu module instance');
|
||||||
|
|
||||||
|
let moduleInstance;
|
||||||
try {
|
try {
|
||||||
const moduleInstance = new modData.mod.getModule({
|
moduleInstance = new modData.mod.getModule({
|
||||||
menuName : options.name,
|
menuName : options.name,
|
||||||
menuConfig : modData.config,
|
menuConfig : modData.config,
|
||||||
extraArgs : options.extraArgs,
|
extraArgs : options.extraArgs,
|
||||||
client : options.client,
|
client : options.client,
|
||||||
lastMenuResult : options.lastMenuResult,
|
lastMenuResult : options.lastMenuResult,
|
||||||
});
|
});
|
||||||
return callback(null, moduleInstance);
|
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
return callback(e);
|
return callback(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return callback(null, moduleInstance);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
(err, modInst) => {
|
(err, modInst) => {
|
||||||
|
@ -127,8 +129,8 @@ function getFormConfigByIDAndMap(menuConfig, formId, mciMap, cb) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var formForId = menuConfig.form[formId];
|
const formForId = menuConfig.form[formId];
|
||||||
var mciReqKey = _.filter(_.pluck(_.sortBy(mciMap, 'code'), 'code'), function(mci) {
|
const mciReqKey = _.filter(_.map(_.sortBy(mciMap, 'code'), 'code'), (mci) => {
|
||||||
return MCIViewFactory.UserViewCodes.indexOf(mci) > -1;
|
return MCIViewFactory.UserViewCodes.indexOf(mci) > -1;
|
||||||
}).join('');
|
}).join('');
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ function getAvailableMessageConferences(client, options) {
|
||||||
options = options || { includeSystemInternal : false };
|
options = options || { includeSystemInternal : false };
|
||||||
|
|
||||||
// perform ACS check per conf & omit system_internal if desired
|
// perform ACS check per conf & omit system_internal if desired
|
||||||
return _.omit(Config.messageConferences, (conf, confTag) => {
|
return _.omitBy(Config.messageConferences, (conf, confTag) => {
|
||||||
if(!options.includeSystemInternal && 'system_internal' === confTag) {
|
if(!options.includeSystemInternal && 'system_internal' === confTag) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,7 @@ function getAvailableMessageAreasByConfTag(confTag, options) {
|
||||||
return areas;
|
return areas;
|
||||||
} else {
|
} else {
|
||||||
// perform ACS check per area
|
// perform ACS check per area
|
||||||
return _.omit(areas, area => {
|
return _.omitBy(areas, area => {
|
||||||
return !options.client.acs.hasMessageAreaRead(area);
|
return !options.client.acs.hasMessageAreaRead(area);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,7 +86,7 @@ function loadModulesForCategory(category, iterator, complete) {
|
||||||
});
|
});
|
||||||
|
|
||||||
async.each(jsModules, (file, next) => {
|
async.each(jsModules, (file, next) => {
|
||||||
+ loadModule(paths.basename(file, '.js'), category, (err, mod) => {
|
loadModule(paths.basename(file, '.js'), category, (err, mod) => {
|
||||||
iterator(err, mod);
|
iterator(err, mod);
|
||||||
return next();
|
return next();
|
||||||
});
|
});
|
||||||
|
|
303
core/theme.js
303
core/theme.js
|
@ -1,21 +1,20 @@
|
||||||
/* jslint node: true */
|
/* jslint node: true */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var Config = require('./config.js').config;
|
const Config = require('./config.js').config;
|
||||||
var art = require('./art.js');
|
const art = require('./art.js');
|
||||||
var ansi = require('./ansi_term.js');
|
const ansi = require('./ansi_term.js');
|
||||||
var miscUtil = require('./misc_util.js');
|
const Log = require('./logger.js').log;
|
||||||
var Log = require('./logger.js').log;
|
const configCache = require('./config_cache.js');
|
||||||
var configCache = require('./config_cache.js');
|
const getFullConfig = require('./config_util.js').getFullConfig;
|
||||||
var getFullConfig = require('./config_util.js').getFullConfig;
|
const asset = require('./asset.js');
|
||||||
var asset = require('./asset.js');
|
const ViewController = require('./view_controller.js').ViewController;
|
||||||
var ViewController = require('./view_controller.js').ViewController;
|
|
||||||
|
|
||||||
var fs = require('fs');
|
const fs = require('fs');
|
||||||
var paths = require('path');
|
const paths = require('path');
|
||||||
var async = require('async');
|
const async = require('async');
|
||||||
var _ = require('lodash');
|
const _ = require('lodash');
|
||||||
var assert = require('assert');
|
const assert = require('assert');
|
||||||
|
|
||||||
exports.getThemeArt = getThemeArt;
|
exports.getThemeArt = getThemeArt;
|
||||||
exports.getAvailableThemes = getAvailableThemes;
|
exports.getAvailableThemes = getAvailableThemes;
|
||||||
|
@ -100,10 +99,10 @@ function loadTheme(themeID, cb) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var availableThemes = {};
|
const availableThemes = {};
|
||||||
|
|
||||||
var IMMUTABLE_MCI_PROPERTIES = [
|
const IMMUTABLE_MCI_PROPERTIES = [
|
||||||
'maxLength', 'argName', 'submit', 'validate'
|
'maxLength', 'argName', 'submit', 'validate'
|
||||||
];
|
];
|
||||||
|
|
||||||
function getMergedTheme(menuConfig, promptConfig, theme) {
|
function getMergedTheme(menuConfig, promptConfig, theme) {
|
||||||
|
@ -119,44 +118,44 @@ function getMergedTheme(menuConfig, promptConfig, theme) {
|
||||||
//
|
//
|
||||||
var mergedTheme = _.cloneDeep(menuConfig);
|
var mergedTheme = _.cloneDeep(menuConfig);
|
||||||
|
|
||||||
if(_.isObject(promptConfig.prompts)) {
|
if(_.isObject(promptConfig.prompts)) {
|
||||||
mergedTheme.prompts = _.cloneDeep(promptConfig.prompts);
|
mergedTheme.prompts = _.cloneDeep(promptConfig.prompts);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Add in data we won't be altering directly from the theme
|
// Add in data we won't be altering directly from the theme
|
||||||
//
|
//
|
||||||
mergedTheme.info = theme.info;
|
mergedTheme.info = theme.info;
|
||||||
mergedTheme.helpers = theme.helpers;
|
mergedTheme.helpers = theme.helpers;
|
||||||
|
|
||||||
//
|
//
|
||||||
// merge customizer to disallow immutable MCI properties
|
// merge customizer to disallow immutable MCI properties
|
||||||
//
|
//
|
||||||
var mciCustomizer = function(objVal, srcVal, key) {
|
var mciCustomizer = function(objVal, srcVal, key) {
|
||||||
return IMMUTABLE_MCI_PROPERTIES.indexOf(key) > -1 ? objVal : srcVal;
|
return IMMUTABLE_MCI_PROPERTIES.indexOf(key) > -1 ? objVal : srcVal;
|
||||||
};
|
};
|
||||||
|
|
||||||
function getFormKeys(fromObj) {
|
function getFormKeys(fromObj) {
|
||||||
return _.remove(_.keys(fromObj), function pred(k) {
|
return _.remove(_.keys(fromObj), function pred(k) {
|
||||||
return !isNaN(k); // remove all non-numbers
|
return !isNaN(k); // remove all non-numbers
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function mergeMciProperties(dest, src) {
|
function mergeMciProperties(dest, src) {
|
||||||
Object.keys(src).forEach(function mciEntry(mci) {
|
Object.keys(src).forEach(function mciEntry(mci) {
|
||||||
_.merge(dest[mci], src[mci], mciCustomizer);
|
_.mergeWith(dest[mci], src[mci], mciCustomizer);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyThemeMciBlock(dest, src, formKey) {
|
function applyThemeMciBlock(dest, src, formKey) {
|
||||||
if(_.isObject(src.mci)) {
|
if(_.isObject(src.mci)) {
|
||||||
mergeMciProperties(dest, src.mci);
|
mergeMciProperties(dest, src.mci);
|
||||||
} else {
|
} else {
|
||||||
if(_.has(src, [ formKey, 'mci' ])) {
|
if(_.has(src, [ formKey, 'mci' ])) {
|
||||||
mergeMciProperties(dest, src[formKey].mci);
|
mergeMciProperties(dest, src[formKey].mci);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// menu.hjson can have a couple different structures:
|
// menu.hjson can have a couple different structures:
|
||||||
|
@ -180,103 +179,103 @@ function getMergedTheme(menuConfig, promptConfig, theme) {
|
||||||
// * If theme.hjson provides form ID's, use them. Otherwise, we'll apply directly assuming
|
// * If theme.hjson provides form ID's, use them. Otherwise, we'll apply directly assuming
|
||||||
// there is a generic 'mci' block.
|
// there is a generic 'mci' block.
|
||||||
//
|
//
|
||||||
function applyToForm(form, menuTheme, formKey) {
|
function applyToForm(form, menuTheme, formKey) {
|
||||||
if(_.isObject(form.mci)) {
|
if(_.isObject(form.mci)) {
|
||||||
// non-explicit: no MCI code(s) key assumed since we found 'mci' directly under form ID
|
// non-explicit: no MCI code(s) key assumed since we found 'mci' directly under form ID
|
||||||
applyThemeMciBlock(form.mci, menuTheme, formKey);
|
applyThemeMciBlock(form.mci, menuTheme, formKey);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
var menuMciCodeKeys = _.remove(_.keys(form), function pred(k) {
|
var menuMciCodeKeys = _.remove(_.keys(form), function pred(k) {
|
||||||
return k === k.toUpperCase(); // remove anything not uppercase
|
return k === k.toUpperCase(); // remove anything not uppercase
|
||||||
});
|
});
|
||||||
|
|
||||||
menuMciCodeKeys.forEach(function mciKeyEntry(mciKey) {
|
menuMciCodeKeys.forEach(function mciKeyEntry(mciKey) {
|
||||||
var applyFrom;
|
var applyFrom;
|
||||||
if(_.has(menuTheme, [ mciKey, 'mci' ])) {
|
if(_.has(menuTheme, [ mciKey, 'mci' ])) {
|
||||||
applyFrom = menuTheme[mciKey];
|
applyFrom = menuTheme[mciKey];
|
||||||
} else {
|
} else {
|
||||||
applyFrom = menuTheme;
|
applyFrom = menuTheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
applyThemeMciBlock(form[mciKey].mci, applyFrom);
|
applyThemeMciBlock(form[mciKey].mci, applyFrom);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[ 'menus', 'prompts' ].forEach(function areaEntry(sectionName) {
|
[ 'menus', 'prompts' ].forEach(function areaEntry(sectionName) {
|
||||||
_.keys(mergedTheme[sectionName]).forEach(function menuEntry(menuName) {
|
_.keys(mergedTheme[sectionName]).forEach(function menuEntry(menuName) {
|
||||||
var createdFormSection = false;
|
var createdFormSection = false;
|
||||||
var mergedThemeMenu = mergedTheme[sectionName][menuName];
|
var mergedThemeMenu = mergedTheme[sectionName][menuName];
|
||||||
|
|
||||||
if(_.has(theme, [ 'customization', sectionName, menuName ])) {
|
if(_.has(theme, [ 'customization', sectionName, menuName ])) {
|
||||||
var menuTheme = theme.customization[sectionName][menuName];
|
var menuTheme = theme.customization[sectionName][menuName];
|
||||||
|
|
||||||
// config block is direct assign/overwrite
|
// config block is direct assign/overwrite
|
||||||
// :TODO: should probably be _.merge()
|
// :TODO: should probably be _.merge()
|
||||||
if(menuTheme.config) {
|
if(menuTheme.config) {
|
||||||
mergedThemeMenu.config = _.assign(mergedThemeMenu.config || {}, menuTheme.config);
|
mergedThemeMenu.config = _.assign(mergedThemeMenu.config || {}, menuTheme.config);
|
||||||
}
|
}
|
||||||
|
|
||||||
if('menus' === sectionName) {
|
if('menus' === sectionName) {
|
||||||
if(_.isObject(mergedThemeMenu.form)) {
|
if(_.isObject(mergedThemeMenu.form)) {
|
||||||
getFormKeys(mergedThemeMenu.form).forEach(function formKeyEntry(formKey) {
|
getFormKeys(mergedThemeMenu.form).forEach(function formKeyEntry(formKey) {
|
||||||
applyToForm(mergedThemeMenu.form[formKey], menuTheme, formKey);
|
applyToForm(mergedThemeMenu.form[formKey], menuTheme, formKey);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if(_.isObject(menuTheme.mci)) {
|
if(_.isObject(menuTheme.mci)) {
|
||||||
//
|
//
|
||||||
// Not specified at menu level means we apply anything from the
|
// Not specified at menu level means we apply anything from the
|
||||||
// theme to form.0.mci{}
|
// theme to form.0.mci{}
|
||||||
//
|
//
|
||||||
mergedThemeMenu.form = { 0 : { mci : { } } };
|
mergedThemeMenu.form = { 0 : { mci : { } } };
|
||||||
mergeMciProperties(mergedThemeMenu.form[0], menuTheme);
|
mergeMciProperties(mergedThemeMenu.form[0], menuTheme);
|
||||||
createdFormSection = true;
|
createdFormSection = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if('prompts' === sectionName) {
|
} else if('prompts' === sectionName) {
|
||||||
// no 'form' or form keys for prompts -- direct to mci
|
// no 'form' or form keys for prompts -- direct to mci
|
||||||
applyToForm(mergedThemeMenu, menuTheme);
|
applyToForm(mergedThemeMenu, menuTheme);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Finished merging for this menu/prompt
|
// Finished merging for this menu/prompt
|
||||||
//
|
//
|
||||||
// If the following conditions are true, set runtime.autoNext to true:
|
// If the following conditions are true, set runtime.autoNext to true:
|
||||||
// * This is a menu
|
// * This is a menu
|
||||||
// * There is/was no explicit 'form' section
|
// * There is/was no explicit 'form' section
|
||||||
// * There is no 'prompt' specified
|
// * There is no 'prompt' specified
|
||||||
//
|
//
|
||||||
if('menus' === sectionName && !_.isString(mergedThemeMenu.prompt) &&
|
if('menus' === sectionName && !_.isString(mergedThemeMenu.prompt) &&
|
||||||
(createdFormSection || !_.isObject(mergedThemeMenu.form)))
|
(createdFormSection || !_.isObject(mergedThemeMenu.form)))
|
||||||
{
|
{
|
||||||
mergedThemeMenu.runtime = _.merge(mergedThemeMenu.runtime || {}, { autoNext : true } );
|
mergedThemeMenu.runtime = _.merge(mergedThemeMenu.runtime || {}, { autoNext : true } );
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
return mergedTheme;
|
return mergedTheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
function initAvailableThemes(cb) {
|
function initAvailableThemes(cb) {
|
||||||
var menuConfig;
|
var menuConfig;
|
||||||
var promptConfig;
|
var promptConfig;
|
||||||
|
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
function loadMenuConfig(callback) {
|
function loadMenuConfig(callback) {
|
||||||
getFullConfig(Config.general.menuFile, function gotConfig(err, mc) {
|
getFullConfig(Config.general.menuFile, function gotConfig(err, mc) {
|
||||||
menuConfig = mc;
|
menuConfig = mc;
|
||||||
callback(err);
|
callback(err);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function loadPromptConfig(callback) {
|
function loadPromptConfig(callback) {
|
||||||
getFullConfig(Config.general.promptFile, function gotConfig(err, pc) {
|
getFullConfig(Config.general.promptFile, function gotConfig(err, pc) {
|
||||||
promptConfig = pc;
|
promptConfig = pc;
|
||||||
callback(err);
|
callback(err);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function getDir(callback) {
|
function getDir(callback) {
|
||||||
fs.readdir(Config.paths.themes, function dirRead(err, files) {
|
fs.readdir(Config.paths.themes, function dirRead(err, files) {
|
||||||
callback(err, files);
|
callback(err, files);
|
||||||
|
@ -294,7 +293,7 @@ function initAvailableThemes(cb) {
|
||||||
filtered.forEach(function themeEntry(themeId) {
|
filtered.forEach(function themeEntry(themeId) {
|
||||||
loadTheme(themeId, function themeLoaded(err, theme, themePath) {
|
loadTheme(themeId, function themeLoaded(err, theme, themePath) {
|
||||||
if(!err) {
|
if(!err) {
|
||||||
availableThemes[themeId] = getMergedTheme(menuConfig, promptConfig, theme);
|
availableThemes[themeId] = getMergedTheme(menuConfig, promptConfig, theme);
|
||||||
|
|
||||||
configCache.on('recached', function recached(path) {
|
configCache.on('recached', function recached(path) {
|
||||||
if(themePath === path) {
|
if(themePath === path) {
|
||||||
|
@ -339,17 +338,17 @@ function getRandomTheme() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function setClientTheme(client, themeId) {
|
function setClientTheme(client, themeId) {
|
||||||
var desc;
|
var desc;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
client.currentTheme = getAvailableThemes()[themeId];
|
client.currentTheme = getAvailableThemes()[themeId];
|
||||||
desc = 'Set client theme';
|
desc = 'Set client theme';
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
client.currentTheme = getAvailableThemes()[Config.defaults.theme];
|
client.currentTheme = getAvailableThemes()[Config.defaults.theme];
|
||||||
desc = 'Failed setting theme by supplied ID; Using default';
|
desc = 'Failed setting theme by supplied ID; Using default';
|
||||||
}
|
}
|
||||||
|
|
||||||
client.log.debug( { themeId : themeId, info : client.currentTheme.info }, desc);
|
client.log.debug( { themeId : themeId, info : client.currentTheme.info }, desc);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getThemeArt(options, cb) {
|
function getThemeArt(options, cb) {
|
||||||
|
|
|
@ -338,7 +338,7 @@ User.prototype.removeProperty = function(propName, cb) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
User.prototype.persistProperties = function(properties, cb) {
|
User.prototype.persistProperties = function(properties, cb) {
|
||||||
|
@ -474,7 +474,7 @@ function generatePasswordDerivedKeySalt(cb) {
|
||||||
|
|
||||||
function generatePasswordDerivedKey(password, salt, cb) {
|
function generatePasswordDerivedKey(password, salt, cb) {
|
||||||
password = new Buffer(password).toString('hex');
|
password = new Buffer(password).toString('hex');
|
||||||
crypto.pbkdf2(password, salt, User.PBKDF2.iterations, User.PBKDF2.keyLen, function onDerivedKey(err, dk) {
|
crypto.pbkdf2(password, salt, User.PBKDF2.iterations, User.PBKDF2.keyLen, 'sha1', function onDerivedKey(err, dk) {
|
||||||
if(err) {
|
if(err) {
|
||||||
cb(err);
|
cb(err);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -73,8 +73,9 @@ function ViewController(options) {
|
||||||
self.switchFocus(actionForKey.viewId);
|
self.switchFocus(actionForKey.viewId);
|
||||||
self.submitForm(key);
|
self.submitForm(key);
|
||||||
} else if(_.isString(actionForKey.action)) {
|
} else if(_.isString(actionForKey.action)) {
|
||||||
|
const formData = self.getFocusedView() ? self.getFormData() : { };
|
||||||
self.handleActionWrapper(
|
self.handleActionWrapper(
|
||||||
{ ch : ch, key : key }, // formData
|
Object.assign( { ch : ch, key : key }, formData ), // formData + key info
|
||||||
actionForKey); // actionBlock
|
actionForKey); // actionBlock
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -115,6 +116,7 @@ function ViewController(options) {
|
||||||
self.emit('submit', this.getFormData(key));
|
self.emit('submit', this.getFormData(key));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// :TODO: replace this in favor of overriding toJSON() for various things such that logging will *never* output them
|
||||||
this.getLogFriendlyFormData = function(formData) {
|
this.getLogFriendlyFormData = function(formData) {
|
||||||
// :TODO: these fields should be part of menu.json sensitiveMembers[]
|
// :TODO: these fields should be part of menu.json sensitiveMembers[]
|
||||||
var safeFormData = _.cloneDeep(formData);
|
var safeFormData = _.cloneDeep(formData);
|
||||||
|
@ -585,7 +587,7 @@ ViewController.prototype.loadFromPromptConfig = function(options, cb) {
|
||||||
for(var c = 0; c < menuSubmit.length; ++c) {
|
for(var c = 0; c < menuSubmit.length; ++c) {
|
||||||
var actionBlock = menuSubmit[c];
|
var actionBlock = menuSubmit[c];
|
||||||
|
|
||||||
if(_.isEqual(formData.value, actionBlock.value, self.actionBlockValueComparator)) {
|
if(_.isEqualWith(formData.value, actionBlock.value, self.actionBlockValueComparator)) {
|
||||||
self.handleActionWrapper(formData, actionBlock);
|
self.handleActionWrapper(formData, actionBlock);
|
||||||
break; // there an only be one...
|
break; // there an only be one...
|
||||||
}
|
}
|
||||||
|
@ -713,7 +715,7 @@ ViewController.prototype.loadFromMenuConfig = function(options, cb) {
|
||||||
for(var c = 0; c < confForFormId.length; ++c) {
|
for(var c = 0; c < confForFormId.length; ++c) {
|
||||||
var actionBlock = confForFormId[c];
|
var actionBlock = confForFormId[c];
|
||||||
|
|
||||||
if(_.isEqual(formData.value, actionBlock.value, self.actionBlockValueComparator)) {
|
if(_.isEqualWith(formData.value, actionBlock.value, self.actionBlockValueComparator)) {
|
||||||
self.handleActionWrapper(formData, actionBlock);
|
self.handleActionWrapper(formData, actionBlock);
|
||||||
break; // there an only be one...
|
break; // there an only be one...
|
||||||
}
|
}
|
||||||
|
@ -827,38 +829,6 @@ ViewController.prototype.getFormData = function(key) {
|
||||||
this.client.log.error( { error : e.message }, 'Exception caught gathering form data' );
|
this.client.log.error( { error : e.message }, 'Exception caught gathering form data' );
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
/*
|
|
||||||
|
|
||||||
var viewData;
|
|
||||||
var view;
|
|
||||||
for(var id in this.views) {
|
|
||||||
try {
|
|
||||||
view = this.views[id];
|
|
||||||
viewData = view.getData();
|
|
||||||
if(!_.isUndefined(viewData)) {
|
|
||||||
if(_.isString(view.submitArgName)) {
|
|
||||||
formData.value[view.submitArgName] = viewData;
|
|
||||||
} else {
|
|
||||||
formData.value[id] = viewData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch(e) {
|
|
||||||
this.client.log.error(e); // :TODO: Log better ;)
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
return formData;
|
return formData;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
|
||||||
ViewController.prototype.formatMenuArgs = function(args) {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
return _.mapValues(args, function val(value) {
|
|
||||||
if('string' === typeof value) {
|
|
||||||
return self.formatMCIString(value);
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
*/
|
|
|
@ -509,7 +509,7 @@ exports.getModule = class FileAreaList extends MenuModule {
|
||||||
|
|
||||||
displayDetailsSection(sectionName, clearArea, cb) {
|
displayDetailsSection(sectionName, clearArea, cb) {
|
||||||
const self = this;
|
const self = this;
|
||||||
const name = `details${_.capitalize(sectionName)}`;
|
const name = `details${_.upperFirst(sectionName)}`;
|
||||||
|
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
|
|
|
@ -61,12 +61,20 @@ exports.getModule = class FileBaseDownloadQueueManager extends MenuModule {
|
||||||
viewItemInfo : (formData, extraArgs, cb) => {
|
viewItemInfo : (formData, extraArgs, cb) => {
|
||||||
},
|
},
|
||||||
removeItem : (formData, extraArgs, cb) => {
|
removeItem : (formData, extraArgs, cb) => {
|
||||||
const selectedItem = formData.value.queueItem;
|
const selectedItem = this.dlQueue.items[formData.value.queueItem];
|
||||||
this.dlQueue.removeItems(selectedItem);
|
if(!selectedItem) {
|
||||||
|
return cb(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dlQueue.removeItems(selectedItem.fileId);
|
||||||
|
|
||||||
|
// :TODO: broken: does not redraw menu properly - needs fixed!
|
||||||
return this.updateDownloadQueueView(cb);
|
return this.updateDownloadQueueView(cb);
|
||||||
},
|
},
|
||||||
clearQueue : (formData, extraArgs, cb) => {
|
clearQueue : (formData, extraArgs, cb) => {
|
||||||
this.dlQueue.clear();
|
this.dlQueue.clear();
|
||||||
|
|
||||||
|
// :TODO: broken: does not redraw menu properly - needs fixed!
|
||||||
return this.updateDownloadQueueView(cb);
|
return this.updateDownloadQueueView(cb);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -32,7 +32,8 @@ exports.getModule = class AreaViewFSEModule extends FullScreenEditorModule {
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
this.menuMethods = {
|
// assign *additional* menuMethods
|
||||||
|
Object.assign(this.menuMethods, {
|
||||||
nextMessage : (formData, extraArgs, cb) => {
|
nextMessage : (formData, extraArgs, cb) => {
|
||||||
if(self.messageIndex + 1 < self.messageList.length) {
|
if(self.messageIndex + 1 < self.messageList.length) {
|
||||||
self.messageIndex++;
|
self.messageIndex++;
|
||||||
|
@ -85,7 +86,7 @@ exports.getModule = class AreaViewFSEModule extends FullScreenEditorModule {
|
||||||
self.client.log(extraArgs, 'Missing extraArgs.menu');
|
self.client.log(extraArgs, 'Missing extraArgs.menu');
|
||||||
return cb(null);
|
return cb(null);
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,7 @@ exports.getModule = class WhosOnlineModule extends MenuModule {
|
||||||
|
|
||||||
onlineListView.setItems(_.map(onlineList, oe => {
|
onlineListView.setItems(_.map(onlineList, oe => {
|
||||||
if(oe.authenticated) {
|
if(oe.authenticated) {
|
||||||
oe.timeOn = _.capitalize(oe.timeOn.humanize());
|
oe.timeOn = _.upperFirst(oe.timeOn.humanize());
|
||||||
} else {
|
} else {
|
||||||
[ 'realName', 'location', 'affils', 'timeOn' ].forEach(m => {
|
[ 'realName', 'location', 'affils', 'timeOn' ].forEach(m => {
|
||||||
oe[m] = otherUnknown;
|
oe[m] = otherUnknown;
|
||||||
|
|
27
package.json
27
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "enigma-bbs",
|
"name": "enigma-bbs",
|
||||||
"version": "0.0.3-alpha",
|
"version": "0.0.4-alpha",
|
||||||
"description": "ENiGMA½ Bulletin Board System",
|
"description": "ENiGMA½ Bulletin Board System",
|
||||||
"author": "Bryan Ashby <bryan@l33t.codes>",
|
"author": "Bryan Ashby <bryan@l33t.codes>",
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
|
@ -18,29 +18,32 @@
|
||||||
"retro"
|
"retro"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"async": "^1.5.1",
|
"async": "^2.1.4",
|
||||||
"binary": "0.3.x",
|
"binary": "0.3.x",
|
||||||
"buffers": "0.1.x",
|
"buffers": "NuSkooler/node-buffers",
|
||||||
"bunyan": "^1.7.1",
|
"bunyan": "^1.7.1",
|
||||||
"farmhash": "^1.2.1",
|
"farmhash": "^1.2.1",
|
||||||
"fs-extra": "0.26.x",
|
"fs-extra": "^2.0.0",
|
||||||
"gaze": "^0.5.2",
|
"gaze": "^1.1.2",
|
||||||
"hjson": "1.7.x",
|
"hashids": "^1.1.1",
|
||||||
|
"hjson": "^2.4.1",
|
||||||
"iconv-lite": "^0.4.13",
|
"iconv-lite": "^0.4.13",
|
||||||
"inquirer": "^1.1.0",
|
"inquirer": "^3.0.1",
|
||||||
"later": "1.2.0",
|
"later": "1.2.0",
|
||||||
"lodash": "^3.10.1",
|
"lodash": "^4.17.4",
|
||||||
|
"mime-types": "^2.1.12",
|
||||||
"minimist": "1.2.x",
|
"minimist": "1.2.x",
|
||||||
"moment": "^2.11.0",
|
"moment": "^2.11.0",
|
||||||
"node-uuid": "^1.4.7",
|
"node-uuid": "^1.4.7",
|
||||||
"ptyw.js": "NuSkooler/ptyw.js",
|
"ptyw.js": "NuSkooler/ptyw.js",
|
||||||
"sqlite3": "^3.1.1",
|
"sqlite3": "^3.1.1",
|
||||||
"ssh2": "^0.5.1",
|
"ssh2": "^0.5.1",
|
||||||
"temp": "^0.8.3",
|
"temp": "^0.8.3"
|
||||||
"hashids" : "^1.1.1",
|
},
|
||||||
"mime-types" : "^2.1.12"
|
"devDependencies": {
|
||||||
|
"lodash-migrate": "^0.3.16"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4.2.0"
|
"node": ">=6.9.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue