Merge pull request #292 from NuSkooler/280-config-revamp
#280 Configuration Revamp
This commit is contained in:
commit
9fa0c4458e
|
@ -3,7 +3,7 @@ For :bug: bug reports, please fill out the information below plus any additional
|
|||
**Short problem description**
|
||||
|
||||
**Environment**
|
||||
- [ ] I am using Node.js v10.x LTS or higher
|
||||
- [ ] I am using Node.js v12.x LTS or higher
|
||||
- [ ] `npm install` or `yarn` reports success
|
||||
- Actual Node.js version (`node --version`):
|
||||
- Operating system (`uname -a` on *nix systems):
|
||||
|
|
33
UPGRADE.md
33
UPGRADE.md
|
@ -2,29 +2,17 @@
|
|||
This document covers basic upgrade notes for major ENiGMA½ version updates.
|
||||
|
||||
# Before Upgrading
|
||||
* Always back up your system!
|
||||
* Always back up your system! (See [Administration](/docs/admin/administration.md))
|
||||
* Seriously, always back up your system!
|
||||
* At least back up the `db` directory and your `menu.hjson` (or renamed equivalent)
|
||||
|
||||
# General Notes
|
||||
## Configuration File Updates
|
||||
In general, look at the `menu_template.in.hjson`, and `config_template.in.hjson` as well as the default `luciano_blocktronics/theme.hjson` files when you update. These files may come with new sections you wish to merge into your system!
|
||||
In general, look at template menu files in `misc/menu_templates`, and `config_template.in.hjson` as well as the default `luciano_blocktronics/theme.hjson` files when you update. These files may come with new sections you wish to merge into your system!
|
||||
|
||||
### menu.hjson
|
||||
Upgrades often come with changes to the default `menu_template.in.hjson`. It is wise to use a *different* file name for your BBS's version of this file and point to it via `config.hjson`. For example:
|
||||
|
||||
```hjson
|
||||
general: {
|
||||
menuFile: my_bbs.hjson
|
||||
}
|
||||
```
|
||||
|
||||
After updating code, use a program such as DiffMerge to merge in updates to
|
||||
`my_bbs.hjson` from the shipping `menu.hjson`.
|
||||
|
||||
### theme.hjson
|
||||
Any custom themes you have created may now be missing features as well. Take a look at the default `luciano_blocktronics/theme.hjson` file. You can use missing sections in your `theme.hjson` (which will generally correspond to sections you've also merged in to your `menu.hjson`).
|
||||
### Menus & Theme Updates
|
||||
Upgrades often come with changes to the default menu templates found in `misc/menu_tempaltes`. You can use these as references for changes and additions to the default menu sets. This also applies to the default `luciano_blocktronics` theme and it's `theme.hjson` file.
|
||||
|
||||
See [Updating](/docs/admin/updating.md) for details on menu files/etc.
|
||||
|
||||
# Upgrading the Code
|
||||
Upgrading from GitHub is easy:
|
||||
|
@ -33,7 +21,7 @@ Upgrading from GitHub is easy:
|
|||
cd /path/to/enigma-bbs
|
||||
git pull
|
||||
rm -rf npm_modules # do this any time you update Node.js itself
|
||||
npm install
|
||||
npm install # or simply 'yarn'
|
||||
```
|
||||
|
||||
# Problems
|
||||
|
@ -42,6 +30,15 @@ Report your issue on Xibalba BBS, hop in #enigma-bbs on FreeNode and chat, or
|
|||
|
||||
# 0.0.11-beta to 0.0.12-beta
|
||||
* Be aware that `master` is now mainline! This means all `git pull`'s will yield the latest version. See [WHATSNEW](WHATSNEW.md) for more information.
|
||||
* **BREAKING CHANGE** There is no longer a `prompt.hjson` file. Prompts are now simply part of the menu set in the `prompts` section. If you have an existing system you will need to add your `prompt.hjson` to your `menu.hjson`'s `includes` section at a minimum. Example:
|
||||
```hjson
|
||||
// menu.hjson
|
||||
{
|
||||
includes: [
|
||||
my-prompts.hjson // ref your old prompts here
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
# 0.0.10-alpha to 0.0.11-beta
|
||||
* Node.js 12.x LTS is now in use. Follow standard Node.js upgrade procedures (e.g.: `nvm install 12 && nvm use 12`).
|
||||
|
|
|
@ -3,7 +3,11 @@ This document attempts to track **major** changes and additions in ENiGMA½. For
|
|||
|
||||
## 0.0.12-beta
|
||||
* The `master` branch has become mainline. What this means to users is `git pull` will always give you the latest and greatest. Make sure to read [Updating](/docs/admin/updating.md) and keep an eye on `WHATSNEW.md` (this file) and [UPGRADE](UPGRADE.md)! See also [ticket #276](https://github.com/NuSkooler/enigma-bbs/issues/276).
|
||||
* The default configuration has been moved to [config_default.js](/core/config_default.js).
|
||||
* A full configuration revamp has taken place. Configuration files such as `config.hjson`, `menu.hjson`, and `theme.hjson` can now utilize includes via the `includes` directive, reference 'self' sections using `@reference:` and import environment variables with `@environment`.
|
||||
* An explicit prompt file previously specified by `general.promptFile` in `config.hjson` is no longer necessary. Instead, this now simply part of the `prompts` section in `menu.hjson`. The default setup still creates a separate prompt HJSON file, but it is `includes`ed in `menu.hjson`. With the removal of prompts the `PromptsChanged` event will no longer be fired.
|
||||
* New `PV` ACS check for arbitrary user properties. See [ACS](/docs/configuration/acs.md) for details.
|
||||
* The `message` arg used by `msg_list` has been deprecated. Please starting using `messageIndex` for this purpose. Support for `message` will be removed in the future.
|
||||
|
||||
## 0.0.11-beta
|
||||
* Upgraded from `alpha` to `beta` -- The software is far along and mature enough at this point!
|
||||
|
|
|
@ -4,6 +4,11 @@
|
|||
author: Luciano Ayres
|
||||
group: blocktronics
|
||||
enabled: true
|
||||
|
||||
//
|
||||
// Also check out Luciano's ANSIGARDEN:
|
||||
// http://www.ansigarden.com/
|
||||
//
|
||||
}
|
||||
|
||||
customization: {
|
||||
|
@ -241,7 +246,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
messageAreaMessageList: {
|
||||
messageBaseMessageList: {
|
||||
config: {
|
||||
dateTimeFormat: ddd MMM Do
|
||||
allViewsInfoFormat10: "|00|15{msgNumSelected:>4.4} |08/ |15{msgNumTotal:<4.4}"
|
||||
|
@ -250,13 +255,13 @@
|
|||
VM1: {
|
||||
height: 14
|
||||
width: 70
|
||||
itemFormat: "|00|15{msgNum:>4} |03{subject:<28.27} |11{fromUserName:<20.20} |03{ts} |15{newIndicator}"
|
||||
focusItemFormat: "|00|19|15{msgNum:>4} {subject:<28.27} {fromUserName:<20.20} {ts} {newIndicator}"
|
||||
itemFormat: "|00|15{msgNum:>4} |03{subject:<28.27} |11{fromUserName:<20.20} |03{ts:<15.16} |15{newIndicator}"
|
||||
focusItemFormat: "|00|19|15{msgNum:>4} {subject:<28.27} {fromUserName:<20.20} {ts:<15.16} {newIndicator}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
messageAreaChangeCurrentConference: {
|
||||
messageBaseChangeCurrentConference: {
|
||||
mci: {
|
||||
VM1: {
|
||||
width: 26
|
||||
|
@ -267,7 +272,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
messageAreaChangeCurrentArea: {
|
||||
messageBaseChangeCurrentArea: {
|
||||
mci: {
|
||||
VM1: {
|
||||
width: 26
|
||||
|
@ -278,7 +283,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
messageAreaSetNewScanDate: {
|
||||
messageBaseSetNewScanDate: {
|
||||
mci: {
|
||||
SM2: {
|
||||
width: 54
|
||||
|
@ -299,7 +304,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
mailMenuCreateMessage: {
|
||||
privateMailMenuCreateMessage: {
|
||||
0: {
|
||||
mci: {
|
||||
TL1: { width: 19, textOverflow: "..." }
|
||||
|
@ -314,7 +319,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
mailMenuInbox: {
|
||||
privateMailMenuInbox: {
|
||||
config: {
|
||||
dateTimeFormat: ddd MMM Do
|
||||
allViewsInfoFormat10: "|00|15{msgNumSelected:>4.4} |08/ |15{msgNumTotal:<4.4}"
|
||||
|
@ -500,7 +505,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
messageAreaSearchMessageList: {
|
||||
messageBaseSearchMessageList: {
|
||||
config: {
|
||||
allViewsInfoFormat10: "|00|15{msgNumSelected:>4.4} |08/ |15{msgNumTotal:<4.4}"
|
||||
// Fri Sep 25th
|
||||
|
@ -516,7 +521,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
messageAreaMyMessagesList: {
|
||||
messageBaseMyMessagesList: {
|
||||
config: {
|
||||
// Fri Sep 25th
|
||||
dateTimeFormat: ddd MMM Do
|
||||
|
@ -531,6 +536,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
// The 'msg_list' module looks for this entry by default
|
||||
messageAreaViewPost: {
|
||||
0: {
|
||||
mci: {
|
||||
|
@ -566,7 +572,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
messageAreaNewPost: {
|
||||
messageBaseNewPost: {
|
||||
0: {
|
||||
mci: {
|
||||
TL1: { width: 19, textOverflow: "..." }
|
||||
|
@ -582,7 +588,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
messageAreaReplyPost: {
|
||||
messageBaseReplyPost: {
|
||||
0: {
|
||||
mci: {
|
||||
TL1: { width: 19, textOverflow: "..." }
|
||||
|
@ -977,7 +983,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
fileAreaFilterEditor: {
|
||||
fileBaseFilterEditor: {
|
||||
mci: {
|
||||
ET1: {
|
||||
width: 26
|
||||
|
|
|
@ -4,10 +4,8 @@
|
|||
// ENiGMA½
|
||||
const Events = require('./events.js');
|
||||
const Config = require('./config.js').get;
|
||||
const {
|
||||
getConfigPath,
|
||||
getFullConfig,
|
||||
} = require('./config_util.js');
|
||||
const ConfigLoader = require('./config_loader');
|
||||
const { getConfigPath } = require('./config_util');
|
||||
const UserDb = require('./database.js').dbs.user;
|
||||
const {
|
||||
getISOTimestampString
|
||||
|
@ -29,13 +27,11 @@ const {
|
|||
const stringFormat = require('./string_format.js');
|
||||
const StatLog = require('./stat_log.js');
|
||||
const Log = require('./logger.js').log;
|
||||
const ConfigCache = require('./config_cache.js');
|
||||
|
||||
// deps
|
||||
const _ = require('lodash');
|
||||
const async = require('async');
|
||||
const moment = require('moment');
|
||||
const paths = require('path');
|
||||
|
||||
exports.getAchievementsEarnedByUser = getAchievementsEarnedByUser;
|
||||
|
||||
|
@ -136,63 +132,60 @@ class UserStatAchievement extends Achievement {
|
|||
class Achievements {
|
||||
constructor(events) {
|
||||
this.events = events;
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
getAchievementByTag(tag) {
|
||||
return this.achievementConfig.achievements[tag];
|
||||
return this.config.get().achievements[tag];
|
||||
}
|
||||
|
||||
isEnabled() {
|
||||
return !_.isUndefined(this.achievementConfig);
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
init(cb) {
|
||||
let achievementConfigPath = _.get(Config(), 'general.achievementFile');
|
||||
if(!achievementConfigPath) {
|
||||
const configPath = this._getConfigPath();
|
||||
if (!configPath) {
|
||||
Log.info('Achievements are not configured');
|
||||
return cb(null);
|
||||
}
|
||||
achievementConfigPath = getConfigPath(achievementConfigPath); // qualify
|
||||
|
||||
const configLoaded = (achievementConfig) => {
|
||||
if(true !== achievementConfig.enabled) {
|
||||
const configLoaded = () => {
|
||||
if(true !== this.config.get().enabled) {
|
||||
Log.info('Achievements are not enabled');
|
||||
this.enabled = false;
|
||||
this.stopMonitoringUserStatEvents();
|
||||
delete this.achievementConfig;
|
||||
} else {
|
||||
Log.info('Achievements are enabled');
|
||||
this.achievementConfig = achievementConfig;
|
||||
this.enabled = true;
|
||||
this.monitorUserStatEvents();
|
||||
}
|
||||
};
|
||||
|
||||
const changed = ( { fileName, fileRoot } ) => {
|
||||
const reCachedPath = paths.join(fileRoot, fileName);
|
||||
if(reCachedPath === achievementConfigPath) {
|
||||
getFullConfig(achievementConfigPath, (err, achievementConfig) => {
|
||||
if(err) {
|
||||
return Log.error( { error : err.message }, 'Failed to reload achievement config from cache');
|
||||
this.config = new ConfigLoader({
|
||||
onReload : err => {
|
||||
if (!err) {
|
||||
configLoaded();
|
||||
}
|
||||
}
|
||||
configLoaded(achievementConfig);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
ConfigCache.getConfigWithOptions(
|
||||
{
|
||||
filePath : achievementConfigPath,
|
||||
forceReCache : true,
|
||||
callback : changed,
|
||||
},
|
||||
(err, achievementConfig) => {
|
||||
this.config.init(configPath, err => {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
configLoaded(achievementConfig);
|
||||
configLoaded();
|
||||
return cb(null);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
_getConfigPath() {
|
||||
const path = _.get(Config(), 'general.achievementFile');
|
||||
if(!path) {
|
||||
return;
|
||||
}
|
||||
return getConfigPath(path); // qualify
|
||||
}
|
||||
|
||||
loadAchievementHitCount(user, achievementTag, field, cb) {
|
||||
|
@ -298,7 +291,7 @@ class Achievements {
|
|||
|
||||
// :TODO: Make this code generic - find + return factory created object
|
||||
const achievementTags = Object.keys(_.pickBy(
|
||||
_.get(this.achievementConfig, 'achievements', {}),
|
||||
_.get(this.config.get(), 'achievements', {}),
|
||||
achievement => {
|
||||
if(false === achievement.enabled) {
|
||||
return false;
|
||||
|
@ -498,7 +491,7 @@ class Achievements {
|
|||
const spec =
|
||||
_.get(info.details, `art.${name}`) ||
|
||||
_.get(info.achievement, `art.${name}`) ||
|
||||
_.get(this.achievementConfig, `art.${name}`);
|
||||
_.get(this.config.get(), `art.${name}`);
|
||||
if(!spec) {
|
||||
return callback(null);
|
||||
}
|
||||
|
|
|
@ -50,17 +50,17 @@ module.exports = class ArchiveUtil {
|
|||
}
|
||||
|
||||
// singleton access
|
||||
static getInstance(noWatch = false) {
|
||||
static getInstance(hotReload = true) {
|
||||
if(!archiveUtil) {
|
||||
archiveUtil = new ArchiveUtil();
|
||||
archiveUtil.init(noWatch);
|
||||
archiveUtil.init(hotReload);
|
||||
}
|
||||
return archiveUtil;
|
||||
}
|
||||
|
||||
init(noWatch = false) {
|
||||
init(hotReload = true) {
|
||||
this.reloadConfig();
|
||||
if(!noWatch) {
|
||||
if(hotReload) {
|
||||
Events.on(Events.getSystemEvents().ConfigChanged, () => {
|
||||
this.reloadConfig();
|
||||
});
|
||||
|
|
37
core/bbs.js
37
core/bbs.js
|
@ -54,6 +54,8 @@ function printVersionAndExit() {
|
|||
}
|
||||
|
||||
function main() {
|
||||
let errorDisplayed = false;
|
||||
|
||||
async.waterfall(
|
||||
[
|
||||
function processArgs(callback) {
|
||||
|
@ -69,12 +71,12 @@ function main() {
|
|||
|
||||
const configOverridePath = argv.config;
|
||||
|
||||
return callback(null, configOverridePath || conf.getDefaultPath(), _.isString(configOverridePath));
|
||||
return callback(null, configOverridePath || conf.Config.getDefaultPath(), _.isString(configOverridePath));
|
||||
},
|
||||
function initConfig(configPath, configPathSupplied, callback) {
|
||||
const configFile = configPath + 'config.hjson';
|
||||
conf.init(resolvePath(configFile), function configInit(err) {
|
||||
|
||||
conf.Config.create(resolvePath(configFile), err => {
|
||||
//
|
||||
// If the user supplied a path and we can't read/parse it
|
||||
// then it's a fatal error
|
||||
|
@ -87,7 +89,14 @@ function main() {
|
|||
configPathSupplied = null; // make non-fatal; we'll go with defaults
|
||||
}
|
||||
} else {
|
||||
console.error(err.message);
|
||||
errorDisplayed = true;
|
||||
console.error(`Configuration error: ${err.message}`); // eslint-disable-line no-console
|
||||
if (err.hint) {
|
||||
console.error(`Hint: ${err.hint}`);
|
||||
}
|
||||
if (err.configPath) {
|
||||
console.error(`Note: ${err.configPath}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
return callback(err);
|
||||
|
@ -114,8 +123,9 @@ function main() {
|
|||
});
|
||||
}
|
||||
|
||||
if(err) {
|
||||
if(err && !errorDisplayed) {
|
||||
console.error('Error initializing: ' + util.inspect(err));
|
||||
return process.exit();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -175,10 +185,11 @@ function initialize(cb) {
|
|||
async.series(
|
||||
[
|
||||
function createMissingDirectories(callback) {
|
||||
async.each(Object.keys(conf.config.paths), function entry(pathKey, next) {
|
||||
mkdirs(conf.config.paths[pathKey], function dirCreated(err) {
|
||||
const Config = conf.get();
|
||||
async.each(Object.keys(Config.paths), function entry(pathKey, next) {
|
||||
mkdirs(Config.paths[pathKey], function dirCreated(err) {
|
||||
if(err) {
|
||||
console.error('Could not create path: ' + conf.config.paths[pathKey] + ': ' + err.toString());
|
||||
console.error('Could not create path: ' + Config.paths[pathKey] + ': ' + err.toString());
|
||||
}
|
||||
return next(err);
|
||||
});
|
||||
|
@ -211,15 +222,9 @@ function initialize(cb) {
|
|||
function initStatLog(callback) {
|
||||
return require('./stat_log.js').init(callback);
|
||||
},
|
||||
function initConfigs(callback) {
|
||||
return require('./config_util.js').init(callback);
|
||||
},
|
||||
function initThemes(callback) {
|
||||
// Have to pull in here so it's after Config init
|
||||
require('./theme.js').initAvailableThemes( (err, themeCount) => {
|
||||
logger.log.info({ themeCount }, 'Themes initialized');
|
||||
return callback(err);
|
||||
});
|
||||
function initMenusAndThemes(callback) {
|
||||
const { ThemeManager } = require('./theme');
|
||||
return ThemeManager.create(callback);
|
||||
},
|
||||
function loadSysOpInformation(callback) {
|
||||
//
|
||||
|
|
|
@ -83,7 +83,7 @@ function Client(/*input, output*/) {
|
|||
const self = this;
|
||||
|
||||
this.user = new User();
|
||||
this.currentTheme = { info : { name : 'N/A', description : 'None' } };
|
||||
this.currentThemeConfig = { info : { name : 'N/A', description : 'None' } };
|
||||
this.lastActivityTime = Date.now();
|
||||
this.menuStack = new MenuStack(this);
|
||||
this.acs = new ACS( { client : this, user : this.user } );
|
||||
|
@ -94,6 +94,26 @@ function Client(/*input, output*/) {
|
|||
this.mciCache = {};
|
||||
};
|
||||
|
||||
Object.defineProperty(this, 'currentTheme', {
|
||||
get : () => {
|
||||
if (this.currentThemeConfig) {
|
||||
return this.currentThemeConfig.get();
|
||||
} else {
|
||||
return {
|
||||
info : {
|
||||
name : 'N/A',
|
||||
author : 'N/A',
|
||||
description : 'N/A',
|
||||
group : 'N/A',
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
set : (theme) => {
|
||||
this.currentThemeConfig = theme;
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'node', {
|
||||
get : function() {
|
||||
return self.session.id;
|
||||
|
@ -120,7 +140,7 @@ function Client(/*input, output*/) {
|
|||
|
||||
this.themeChangedListener = function( { themeId } ) {
|
||||
if(_.get(self.currentTheme, 'info.themeId') === themeId) {
|
||||
self.currentTheme = require('./theme.js').getAvailableThemes().get(themeId);
|
||||
self.currentThemeConfig = require('./theme.js').getAvailableThemes().get(themeId);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
1088
core/config.js
1088
core/config.js
File diff suppressed because it is too large
Load Diff
|
@ -6,6 +6,7 @@ const paths = require('path');
|
|||
const fs = require('graceful-fs');
|
||||
const hjson = require('hjson');
|
||||
const sane = require('sane');
|
||||
const _ = require('lodash');
|
||||
|
||||
module.exports = new class ConfigCache
|
||||
{
|
||||
|
@ -14,12 +15,13 @@ module.exports = new class ConfigCache
|
|||
}
|
||||
|
||||
getConfigWithOptions(options, cb) {
|
||||
options.hotReload = _.get(options, 'hotReload', true);
|
||||
const cached = this.cache.has(options.filePath);
|
||||
|
||||
if(options.forceReCache || !cached) {
|
||||
this.recacheConfigFromFile(options.filePath, (err, config) => {
|
||||
if(!err && !cached) {
|
||||
if(!options.noWatch) {
|
||||
if(options.hotReload) {
|
||||
const watcher = sane(
|
||||
paths.dirname(options.filePath),
|
||||
{
|
||||
|
@ -33,7 +35,7 @@ module.exports = new class ConfigCache
|
|||
this.recacheConfigFromFile(paths.join(fileRoot, fileName), err => {
|
||||
if(!err) {
|
||||
if(options.callback) {
|
||||
options.callback( { fileName, fileRoot } );
|
||||
options.callback( { fileName, fileRoot, configCache : this } );
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -0,0 +1,927 @@
|
|||
const paths = require('path');
|
||||
|
||||
module.exports = () => {
|
||||
return {
|
||||
general : {
|
||||
boardName : 'Another Fine ENiGMA½ BBS',
|
||||
prettyBoardName : '|08A|07nother |07F|08ine |07E|08NiGMA|07½ B|08BS',
|
||||
telnetHostname : '',
|
||||
sshHostname : '',
|
||||
website : 'https://enigma-bbs.github.io',
|
||||
description : 'An ENiGMA½ BBS',
|
||||
|
||||
// :TODO: closedSystem prob belongs under users{}?
|
||||
closedSystem : false, // is the system closed to new users?
|
||||
|
||||
menuFile : 'menu.hjson', // 'oputil.js config new' will set this appropriately in config.hjson; may be full path
|
||||
achievementFile : 'achievements.hjson',
|
||||
},
|
||||
|
||||
users : {
|
||||
usernameMin : 2,
|
||||
usernameMax : 16, // Note that FidoNet wants 36 max
|
||||
usernamePattern : '^[A-Za-z0-9~!@#$%^&*()\\-\\_+ .]+$',
|
||||
|
||||
passwordMin : 6,
|
||||
passwordMax : 128,
|
||||
|
||||
//
|
||||
// The bad password list is a text file containing a password per line.
|
||||
// Entries in this list are not allowed to be used on the system as they
|
||||
// are known to be too common.
|
||||
//
|
||||
// A great resource can be found at https://github.com/danielmiessler/SecLists
|
||||
//
|
||||
// Current list source: https://raw.githubusercontent.com/danielmiessler/SecLists/master/Passwords/probable-v2-top12000.txt
|
||||
//
|
||||
badPassFile : paths.join(__dirname, '../misc/bad_passwords.txt'),
|
||||
|
||||
realNameMax : 32,
|
||||
locationMax : 32,
|
||||
affilsMax : 32,
|
||||
emailMax : 255,
|
||||
webMax : 255,
|
||||
|
||||
requireActivation : false, // require SysOp activation? false = auto-activate
|
||||
|
||||
groups : [ 'users', 'sysops' ], // built in groups
|
||||
defaultGroups : [ 'users' ], // default groups new users belong to
|
||||
|
||||
newUserNames : [ 'new', 'apply' ], // Names reserved for applying
|
||||
|
||||
badUserNames : [
|
||||
'sysop', 'admin', 'administrator', 'root', 'all',
|
||||
'areamgr', 'filemgr', 'filefix', 'areafix', 'allfix',
|
||||
'server', 'client', 'notme'
|
||||
],
|
||||
|
||||
preAuthIdleLogoutSeconds : 60 * 3, // 3m
|
||||
idleLogoutSeconds : 60 * 6, // 6m
|
||||
|
||||
failedLogin : {
|
||||
disconnect : 3, // 0=disabled
|
||||
lockAccount : 9, // 0=disabled; Mark user status as "locked" if >= N
|
||||
autoUnlockMinutes : 60 * 6, // 0=disabled; Auto unlock after N minutes.
|
||||
},
|
||||
unlockAtEmailPwReset : true, // if true, password reset via email will unlock locked accounts
|
||||
|
||||
twoFactorAuth : {
|
||||
method : 'googleAuth',
|
||||
|
||||
otp : {
|
||||
registerEmailText : paths.join(__dirname, '../misc/otp_register_email.template.txt'),
|
||||
registerEmailHtml : paths.join(__dirname, '../misc/otp_register_email.template.html'),
|
||||
registerPageTemplate : paths.join(__dirname, '../www/otp_register.template.html'),
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
theme : {
|
||||
default : 'luciano_blocktronics',
|
||||
preLogin : 'luciano_blocktronics',
|
||||
|
||||
passwordChar : '*',
|
||||
dateFormat : {
|
||||
short : 'MM/DD/YYYY',
|
||||
long : 'ddd, MMMM Do, YYYY',
|
||||
},
|
||||
timeFormat : {
|
||||
short : 'h:mm a',
|
||||
},
|
||||
dateTimeFormat : {
|
||||
short : 'MM/DD/YYYY h:mm a',
|
||||
long : 'ddd, MMMM Do, YYYY, h:mm a',
|
||||
}
|
||||
},
|
||||
|
||||
menus : {
|
||||
cls : true, // Clear screen before each menu by default?
|
||||
},
|
||||
|
||||
paths : {
|
||||
config : paths.join(__dirname, './../config/'),
|
||||
security : paths.join(__dirname, './../config/security'), // certs, keys, etc.
|
||||
mods : paths.join(__dirname, './../mods/'),
|
||||
loginServers : paths.join(__dirname, './servers/login/'),
|
||||
contentServers : paths.join(__dirname, './servers/content/'),
|
||||
chatServers : paths.join(__dirname, './servers/chat/'),
|
||||
|
||||
scannerTossers : paths.join(__dirname, './scanner_tossers/'),
|
||||
mailers : paths.join(__dirname, './mailers/') ,
|
||||
|
||||
art : paths.join(__dirname, './../art/general/'),
|
||||
themes : paths.join(__dirname, './../art/themes/'),
|
||||
logs : paths.join(__dirname, './../logs/'),
|
||||
db : paths.join(__dirname, './../db/'),
|
||||
modsDb : paths.join(__dirname, './../db/mods/'),
|
||||
dropFiles : paths.join(__dirname, './../drop/'), // + "/node<x>/
|
||||
misc : paths.join(__dirname, './../misc/'),
|
||||
},
|
||||
|
||||
loginServers : {
|
||||
telnet : {
|
||||
port : 8888,
|
||||
enabled : true,
|
||||
firstMenu : 'telnetConnected',
|
||||
},
|
||||
ssh : {
|
||||
port : 8889,
|
||||
enabled : false, // default to false as PK/pass in config.hjson are required
|
||||
//
|
||||
// To enable SSH, perform the following steps:
|
||||
//
|
||||
// 1 - Generate a Private Key (PK):
|
||||
// Currently ENiGMA 1/2 requires a PKCS#1 PEM formatted PK.
|
||||
// To generate a secure PK, issue the following command:
|
||||
//
|
||||
// > openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 \
|
||||
// -pkeyopt rsa_keygen_pubexp:65537 | openssl rsa \
|
||||
// -out ./config/security/ssh_private_key.pem -aes128
|
||||
//
|
||||
// (The above is a more modern equivalent of the following):
|
||||
// > openssl genrsa -aes128 -out ./config/security/ssh_private_key.pem 2048
|
||||
//
|
||||
// 2 - Set 'privateKeyPass' to the password you used in step #1
|
||||
//
|
||||
// 3 - Finally, set 'enabled' to 'true'
|
||||
//
|
||||
// Additional reading:
|
||||
// - https://blog.sleeplessbeastie.eu/2017/12/28/how-to-generate-private-key/
|
||||
// - https://gist.github.com/briansmith/2ee42439923d8e65a266994d0f70180b
|
||||
//
|
||||
privateKeyPem : paths.join(__dirname, './../config/security/ssh_private_key.pem'),
|
||||
firstMenu : 'sshConnected',
|
||||
firstMenuNewUser : 'sshConnectedNewUser',
|
||||
|
||||
//
|
||||
// SSH details that can affect security. Stronger ciphers are better for example,
|
||||
// but terminals such as SyncTERM require KEX diffie-hellman-group14-sha1,
|
||||
// cipher 3des-cbc, etc.
|
||||
//
|
||||
// See https://github.com/mscdex/ssh2-streams for the full list of supported
|
||||
// algorithms.
|
||||
//
|
||||
algorithms : {
|
||||
kex : [
|
||||
'ecdh-sha2-nistp256',
|
||||
'ecdh-sha2-nistp384',
|
||||
'ecdh-sha2-nistp521',
|
||||
'diffie-hellman-group-exchange-sha256',
|
||||
'diffie-hellman-group14-sha1',
|
||||
'diffie-hellman-group-exchange-sha1',
|
||||
'diffie-hellman-group1-sha1',
|
||||
],
|
||||
cipher : [
|
||||
'aes128-ctr',
|
||||
'aes192-ctr',
|
||||
'aes256-ctr',
|
||||
'aes128-gcm',
|
||||
'aes128-gcm@openssh.com',
|
||||
'aes256-gcm',
|
||||
'aes256-gcm@openssh.com',
|
||||
'aes256-cbc',
|
||||
'aes192-cbc',
|
||||
'aes128-cbc',
|
||||
'blowfish-cbc',
|
||||
'3des-cbc',
|
||||
'arcfour256',
|
||||
'arcfour128',
|
||||
'cast128-cbc',
|
||||
'arcfour',
|
||||
],
|
||||
hmac : [
|
||||
'hmac-sha2-256',
|
||||
'hmac-sha2-512',
|
||||
'hmac-sha1',
|
||||
'hmac-md5',
|
||||
'hmac-sha2-256-96',
|
||||
'hmac-sha2-512-96',
|
||||
'hmac-ripemd160',
|
||||
'hmac-sha1-96',
|
||||
'hmac-md5-96',
|
||||
],
|
||||
// note that we disable compression by default due to issues with many clients. YMMV.
|
||||
compress : [ 'none' ]
|
||||
},
|
||||
},
|
||||
webSocket : {
|
||||
ws : {
|
||||
// non-secure ws://
|
||||
enabled : false,
|
||||
port : 8810,
|
||||
},
|
||||
wss : {
|
||||
// secure ws://
|
||||
// must provide valid certPem and keyPem
|
||||
enabled : false,
|
||||
port : 8811,
|
||||
certPem : paths.join(__dirname, './../config/https_cert.pem'),
|
||||
keyPem : paths.join(__dirname, './../config/https_cert_key.pem'),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
contentServers : {
|
||||
web : {
|
||||
domain : 'another-fine-enigma-bbs.org',
|
||||
|
||||
staticRoot : paths.join(__dirname, './../www'),
|
||||
|
||||
resetPassword : {
|
||||
//
|
||||
// The following templates have these variables available to them:
|
||||
//
|
||||
// * %BOARDNAME% : Name of BBS
|
||||
// * %USERNAME% : Username of whom to reset password
|
||||
// * %TOKEN% : Reset token
|
||||
// * %RESET_URL% : In case of email, the link to follow for reset. In case of landing page,
|
||||
// URL to POST submit reset form.
|
||||
|
||||
// templates for pw reset *email*
|
||||
resetPassEmailText : paths.join(__dirname, '../misc/reset_password_email.template.txt'), // plain text version
|
||||
resetPassEmailHtml : paths.join(__dirname, '../misc/reset_password_email.template.html'), // HTML version
|
||||
|
||||
// tempalte for pw reset *landing page*
|
||||
//
|
||||
resetPageTemplate : paths.join(__dirname, './../www/reset_password.template.html'),
|
||||
},
|
||||
|
||||
http : {
|
||||
enabled : false,
|
||||
port : 8080,
|
||||
},
|
||||
https : {
|
||||
enabled : false,
|
||||
port : 8443,
|
||||
certPem : paths.join(__dirname, './../config/https_cert.pem'),
|
||||
keyPem : paths.join(__dirname, './../config/https_cert_key.pem'),
|
||||
}
|
||||
},
|
||||
|
||||
gopher : {
|
||||
enabled : false,
|
||||
port : 8070,
|
||||
publicHostname : 'another-fine-enigma-bbs.org',
|
||||
publicPort : 8070, // adjust if behind NAT/etc.
|
||||
bannerFile : 'gopher_banner.asc',
|
||||
|
||||
//
|
||||
// Set messageConferences{} to maps of confTag -> [ areaTag1, areaTag2, ... ]
|
||||
// to export message confs/areas
|
||||
//
|
||||
},
|
||||
|
||||
nntp : {
|
||||
// internal caching of groups, message lists, etc.
|
||||
cache : {
|
||||
maxItems : 200,
|
||||
maxAge : 1000 * 30, // 30s
|
||||
},
|
||||
|
||||
//
|
||||
// Set publicMessageConferences{} to a map of confTag -> [ areaTag1, areaTag2, ... ]
|
||||
// in order to export *public* conf/areas that are available to anonymous
|
||||
// NNTP users. Other conf/areas: Standard ACS rules apply.
|
||||
//
|
||||
publicMessageConferences: {},
|
||||
|
||||
nntp : {
|
||||
enabled : false,
|
||||
port : 8119,
|
||||
},
|
||||
|
||||
nntps : {
|
||||
enabled : false,
|
||||
port : 8563,
|
||||
certPem : paths.join(__dirname, './../config/nntps_cert.pem'),
|
||||
keyPem : paths.join(__dirname, './../config/nntps_key.pem'),
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
chatServers : {
|
||||
mrc: {
|
||||
enabled : false,
|
||||
serverHostname : 'mrc.bottomlessabyss.net',
|
||||
serverPort : 5000,
|
||||
retryDelay : 10000,
|
||||
multiplexerPort : 5000,
|
||||
}
|
||||
},
|
||||
|
||||
infoExtractUtils : {
|
||||
Exiftool2Desc : {
|
||||
cmd : `${__dirname}/../util/exiftool2desc.js`, // ensure chmod +x
|
||||
},
|
||||
Exiftool : {
|
||||
cmd : 'exiftool',
|
||||
args : [
|
||||
'-charset', 'utf8', '{filePath}',
|
||||
// exclude the following:
|
||||
'--directory', '--filepermissions', '--exiftoolversion', '--filename', '--filesize',
|
||||
'--filemodifydate', '--fileaccessdate', '--fileinodechangedate', '--createdate', '--modifydate',
|
||||
'--metadatadate', '--xmptoolkit'
|
||||
]
|
||||
},
|
||||
XDMS2Desc : {
|
||||
// http://manpages.ubuntu.com/manpages/trusty/man1/xdms.1.html
|
||||
cmd : 'xdms',
|
||||
args : [ 'd', '{filePath}' ]
|
||||
},
|
||||
XDMS2LongDesc : {
|
||||
// http://manpages.ubuntu.com/manpages/trusty/man1/xdms.1.html
|
||||
cmd : 'xdms',
|
||||
args : [ 'f', '{filePath}' ]
|
||||
},
|
||||
},
|
||||
|
||||
fileTypes : {
|
||||
//
|
||||
// File types explicitly known to the system. Here we can configure
|
||||
// information extraction, archive treatment, etc.
|
||||
//
|
||||
// MIME types can be found in mime-db: https://github.com/jshttp/mime-db
|
||||
//
|
||||
// Resources for signature/magic bytes:
|
||||
// * http://www.garykessler.net/library/file_sigs.html
|
||||
//
|
||||
//
|
||||
// :TODO: text/x-ansi -> SAUCE extraction for .ans uploads
|
||||
// :TODO: textual : bool -- if text, we can view.
|
||||
// :TODO: asText : { cmd, args[] } -> viewable text
|
||||
|
||||
//
|
||||
// Audio
|
||||
//
|
||||
'audio/mpeg' : {
|
||||
desc : 'MP3 Audio',
|
||||
shortDescUtil : 'Exiftool2Desc',
|
||||
longDescUtil : 'Exiftool',
|
||||
},
|
||||
'application/pdf' : {
|
||||
desc : 'Adobe PDF',
|
||||
shortDescUtil : 'Exiftool2Desc',
|
||||
longDescUtil : 'Exiftool',
|
||||
},
|
||||
//
|
||||
// Video
|
||||
//
|
||||
'video/mp4' : {
|
||||
desc : 'MPEG Video',
|
||||
shortDescUtil : 'Exiftool2Desc',
|
||||
longDescUtil : 'Exiftool',
|
||||
},
|
||||
'video/x-matroska ' : {
|
||||
desc : 'Matroska Video',
|
||||
shortDescUtil : 'Exiftool2Desc',
|
||||
longDescUtil : 'Exiftool',
|
||||
},
|
||||
'video/x-msvideo' : {
|
||||
desc : 'Audio Video Interleave',
|
||||
shortDescUtil : 'Exiftool2Desc',
|
||||
longDescUtil : 'Exiftool',
|
||||
},
|
||||
//
|
||||
// Images
|
||||
//
|
||||
'image/jpeg' : {
|
||||
desc : 'JPEG Image',
|
||||
shortDescUtil : 'Exiftool2Desc',
|
||||
longDescUtil : 'Exiftool',
|
||||
},
|
||||
'image/png' : {
|
||||
desc : 'Portable Network Graphic Image',
|
||||
shortDescUtil : 'Exiftool2Desc',
|
||||
longDescUtil : 'Exiftool',
|
||||
},
|
||||
'image/gif' : {
|
||||
desc : 'Graphics Interchange Format Image',
|
||||
shortDescUtil : 'Exiftool2Desc',
|
||||
longDescUtil : 'Exiftool',
|
||||
},
|
||||
'image/webp' : {
|
||||
desc : 'WebP Image',
|
||||
shortDescUtil : 'Exiftool2Desc',
|
||||
longDescUtil : 'Exiftool',
|
||||
},
|
||||
//
|
||||
// Archives
|
||||
//
|
||||
'application/zip' : {
|
||||
desc : 'ZIP Archive',
|
||||
sig : '504b0304',
|
||||
offset : 0,
|
||||
archiveHandler : 'InfoZip',
|
||||
},
|
||||
/*
|
||||
'application/x-cbr' : {
|
||||
desc : 'Comic Book Archive',
|
||||
sig : '504b0304',
|
||||
},
|
||||
*/
|
||||
'application/x-arj' : {
|
||||
desc : 'ARJ Archive',
|
||||
sig : '60ea',
|
||||
offset : 0,
|
||||
archiveHandler : 'Arj',
|
||||
},
|
||||
'application/x-rar-compressed' : {
|
||||
desc : 'RAR Archive',
|
||||
sig : '526172211a07',
|
||||
offset : 0,
|
||||
archiveHandler : 'Rar',
|
||||
},
|
||||
'application/gzip' : {
|
||||
desc : 'Gzip Archive',
|
||||
sig : '1f8b',
|
||||
offset : 0,
|
||||
archiveHandler : 'TarGz',
|
||||
},
|
||||
// :TODO: application/x-bzip
|
||||
'application/x-bzip2' : {
|
||||
desc : 'BZip2 Archive',
|
||||
sig : '425a68',
|
||||
offset : 0,
|
||||
archiveHandler : '7Zip',
|
||||
},
|
||||
'application/x-lzh-compressed' : {
|
||||
desc : 'LHArc Archive',
|
||||
sig : '2d6c68',
|
||||
offset : 2,
|
||||
archiveHandler : 'Lha',
|
||||
},
|
||||
'application/x-lzx' : {
|
||||
desc : 'LZX Archive',
|
||||
sig : '4c5a5800',
|
||||
offset : 0,
|
||||
archiveHandler : 'Lzx',
|
||||
},
|
||||
'application/x-7z-compressed' : {
|
||||
desc : '7-Zip Archive',
|
||||
sig : '377abcaf271c',
|
||||
offset : 0,
|
||||
archiveHandler : '7Zip',
|
||||
},
|
||||
|
||||
//
|
||||
// Generics that need further mapping
|
||||
//
|
||||
'application/octet-stream' : [
|
||||
{
|
||||
desc : 'Amiga DISKMASHER',
|
||||
sig : '444d5321', // DMS!
|
||||
ext : '.dms',
|
||||
shortDescUtil : 'XDMS2Desc',
|
||||
longDescUtil : 'XDMS2LongDesc',
|
||||
},
|
||||
{
|
||||
desc : 'SIO2PC Atari Disk Image',
|
||||
sig : '9602', // 16bit sum of "NICKATARI"
|
||||
ext : '.atr',
|
||||
archiveHandler : 'Atr',
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
archives : {
|
||||
archivers : {
|
||||
'7Zip' : { // p7zip package
|
||||
compress : {
|
||||
cmd : '7za',
|
||||
args : [ 'a', '-tzip', '{archivePath}', '{fileList}' ],
|
||||
},
|
||||
decompress : {
|
||||
cmd : '7za',
|
||||
args : [ 'e', '-o{extractPath}', '{archivePath}' ] // :TODO: should be 'x'?
|
||||
},
|
||||
list : {
|
||||
cmd : '7za',
|
||||
args : [ 'l', '{archivePath}' ],
|
||||
entryMatch : '^[0-9]{4}-[0-9]{2}-[0-9]{2}\\s[0-9]{2}:[0-9]{2}:[0-9]{2}\\s[A-Za-z\\.]{5}\\s+([0-9]+)\\s+[0-9]+\\s+([^\\r\\n]+)$',
|
||||
},
|
||||
extract : {
|
||||
cmd : '7za',
|
||||
args : [ 'e', '-o{extractPath}', '{archivePath}', '{fileList}' ],
|
||||
},
|
||||
},
|
||||
|
||||
InfoZip: {
|
||||
compress : {
|
||||
cmd : 'zip',
|
||||
args : [ '{archivePath}', '{fileList}' ],
|
||||
},
|
||||
decompress : {
|
||||
cmd : 'unzip',
|
||||
args : [ '-n', '{archivePath}', '-d', '{extractPath}' ],
|
||||
},
|
||||
list : {
|
||||
cmd : 'unzip',
|
||||
args : [ '-l', '{archivePath}' ],
|
||||
// Annoyingly, dates can be in YYYY-MM-DD or MM-DD-YYYY format
|
||||
entryMatch : '^\\s*([0-9]+)\\s+[0-9]{2,4}-[0-9]{2}-[0-9]{2,4}\\s+[0-9]{2}:[0-9]{2}\\s+([^\\r\\n]+)$',
|
||||
},
|
||||
extract : {
|
||||
cmd : 'unzip',
|
||||
args : [ '-n', '{archivePath}', '{fileList}', '-d', '{extractPath}' ],
|
||||
}
|
||||
},
|
||||
|
||||
Lha : {
|
||||
//
|
||||
// 'lha' command can be obtained from:
|
||||
// * apt-get: lhasa
|
||||
//
|
||||
// (compress not currently supported)
|
||||
//
|
||||
decompress : {
|
||||
cmd : 'lha',
|
||||
args : [ '-efw={extractPath}', '{archivePath}' ],
|
||||
},
|
||||
list : {
|
||||
cmd : 'lha',
|
||||
args : [ '-l', '{archivePath}' ],
|
||||
entryMatch : '^[\\[a-z\\]]+(?:\\s+[0-9]+\\s+[0-9]+|\\s+)([0-9]+)\\s+[0-9]{2}\\.[0-9]\\%\\s+[A-Za-z]{3}\\s+[0-9]{1,2}\\s+[0-9]{4}\\s+([^\\r\\n]+)$',
|
||||
},
|
||||
extract : {
|
||||
cmd : 'lha',
|
||||
args : [ '-efw={extractPath}', '{archivePath}', '{fileList}' ]
|
||||
}
|
||||
},
|
||||
|
||||
Lzx : {
|
||||
//
|
||||
// 'unlzx' command can be obtained from:
|
||||
// * Debian based: https://launchpad.net/~rzr/+archive/ubuntu/ppa/+build/2486127 (amd64/x86_64)
|
||||
// * RedHat: https://fedora.pkgs.org/28/rpm-sphere/unlzx-1.1-4.1.x86_64.rpm.html
|
||||
// * Source: http://xavprods.free.fr/lzx/
|
||||
//
|
||||
decompress : {
|
||||
cmd : 'unlzx',
|
||||
// unzlx doesn't have a output dir option, but we'll cwd to the temp output dir first
|
||||
args : [ '-x', '{archivePath}' ],
|
||||
},
|
||||
list : {
|
||||
cmd : 'unlzx',
|
||||
args : [ '-v', '{archivePath}' ],
|
||||
entryMatch : '^\\s+([0-9]+)\\s+[^\\s]+\\s+[0-9]{2}:[0-9]{2}:[0-9]{2}\\s+[0-9]{1,2}-[a-z]{3}-[0-9]{4}\\s+[a-z\\-]+\\s+\\"([^"]+)\\"$',
|
||||
}
|
||||
},
|
||||
|
||||
Arj : {
|
||||
//
|
||||
// 'arj' command can be obtained from:
|
||||
// * apt-get: arj
|
||||
//
|
||||
decompress : {
|
||||
cmd : 'arj',
|
||||
args : [ 'x', '{archivePath}', '{extractPath}' ],
|
||||
},
|
||||
list : {
|
||||
cmd : 'arj',
|
||||
args : [ 'l', '{archivePath}' ],
|
||||
entryMatch : '^([^\\s]+)\\s+([0-9]+)\\s+[0-9]+\\s[0-9\\.]+\\s+[0-9]{2}\\-[0-9]{2}\\-[0-9]{2}\\s[0-9]{2}\\:[0-9]{2}\\:[0-9]{2}\\s+(?:[^\\r\\n]+)$',
|
||||
entryGroupOrder : { // defaults to { byteSize : 1, fileName : 2 }
|
||||
fileName : 1,
|
||||
byteSize : 2,
|
||||
}
|
||||
},
|
||||
extract : {
|
||||
cmd : 'arj',
|
||||
args : [ 'e', '{archivePath}', '{extractPath}', '{fileList}' ],
|
||||
}
|
||||
},
|
||||
|
||||
Rar : {
|
||||
decompress : {
|
||||
cmd : 'unrar',
|
||||
args : [ 'x', '{archivePath}', '{extractPath}' ],
|
||||
},
|
||||
list : {
|
||||
cmd : 'unrar',
|
||||
args : [ 'l', '{archivePath}' ],
|
||||
entryMatch : '^\\s+[\\.A-Z]+\\s+([\\d]+)\\s{2}[0-9]{2,4}\\-[0-9]{2}\\-[0-9]{2}\\s[0-9]{2}\\:[0-9]{2}\\s{2}([^\\r\\n]+)$',
|
||||
},
|
||||
extract : {
|
||||
cmd : 'unrar',
|
||||
args : [ 'e', '{archivePath}', '{extractPath}', '{fileList}' ],
|
||||
}
|
||||
},
|
||||
|
||||
TarGz : {
|
||||
decompress : {
|
||||
cmd : 'tar',
|
||||
args : [ '-xf', '{archivePath}', '-C', '{extractPath}', '--strip-components=1' ],
|
||||
},
|
||||
list : {
|
||||
cmd : 'tar',
|
||||
args : [ '-tvf', '{archivePath}' ],
|
||||
entryMatch : '^[drwx\\-]{10}\\s[A-Za-z0-9\\/]+\\s+([0-9]+)\\s[0-9]{4}\\-[0-9]{2}\\-[0-9]{2}\\s[0-9]{2}\\:[0-9]{2}\\s([^\\r\\n]+)$',
|
||||
},
|
||||
extract : {
|
||||
cmd : 'tar',
|
||||
args : [ '-xvf', '{archivePath}', '-C', '{extractPath}', '{fileList}' ],
|
||||
}
|
||||
},
|
||||
|
||||
Atr : {
|
||||
decompress : {
|
||||
cmd : 'atr',
|
||||
args : [ '{archivePath}', 'x', '-a', '-o', '{extractPath}' ]
|
||||
},
|
||||
list : {
|
||||
cmd : 'atr',
|
||||
args : [ '{archivePath}', 'ls', '-la1' ],
|
||||
entryMatch : '^[rwxs-]{5}\\s+([0-9]+)\\s\\([0-9\\s]+\\)\\s([^\\r\\n\\s]*)(?:[^\\r\\n]+)?$',
|
||||
},
|
||||
extract : {
|
||||
cmd : 'atr',
|
||||
// note: -l converts Atari 0x9b line feeds to 0x0a; not ideal if we're dealing with a binary of course.
|
||||
args : [ '{archivePath}', 'x', '-a', '-l', '-o', '{extractPath}', '{fileList}' ]
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
fileTransferProtocols : {
|
||||
//
|
||||
// See http://www.synchro.net/docs/sexyz.txt for information on SEXYZ
|
||||
//
|
||||
zmodem8kSexyz : {
|
||||
name : 'ZModem 8k (SEXYZ)',
|
||||
type : 'external',
|
||||
sort : 1,
|
||||
external : {
|
||||
// :TODO: Look into shipping sexyz binaries or at least hosting them somewhere for common systems
|
||||
// Linux x86_64 binary: https://l33t.codes/outgoing/sexyz
|
||||
sendCmd : 'sexyz',
|
||||
sendArgs : [ '-telnet', '-8', 'sz', '@{fileListPath}' ],
|
||||
recvCmd : 'sexyz',
|
||||
recvArgs : [ '-telnet', '-8', 'rz', '{uploadDir}' ],
|
||||
recvArgsNonBatch : [ '-telnet', '-8', 'rz', '{fileName}' ],
|
||||
}
|
||||
},
|
||||
|
||||
xmodemSexyz : {
|
||||
name : 'XModem (SEXYZ)',
|
||||
type : 'external',
|
||||
sort : 3,
|
||||
external : {
|
||||
sendCmd : 'sexyz',
|
||||
sendArgs : [ '-telnet', 'sX', '@{fileListPath}' ],
|
||||
recvCmd : 'sexyz',
|
||||
recvArgsNonBatch : [ '-telnet', 'rC', '{fileName}' ]
|
||||
}
|
||||
},
|
||||
|
||||
ymodemSexyz : {
|
||||
name : 'YModem (SEXYZ)',
|
||||
type : 'external',
|
||||
sort : 4,
|
||||
external : {
|
||||
sendCmd : 'sexyz',
|
||||
sendArgs : [ '-telnet', 'sY', '@{fileListPath}' ],
|
||||
recvCmd : 'sexyz',
|
||||
recvArgs : [ '-telnet', 'ry', '{uploadDir}' ],
|
||||
}
|
||||
},
|
||||
|
||||
zmodem8kSz : {
|
||||
name : 'ZModem 8k',
|
||||
type : 'external',
|
||||
sort : 2,
|
||||
external : {
|
||||
sendCmd : 'sz', // Avail on Debian/Ubuntu based systems as the package "lrzsz"
|
||||
sendArgs : [
|
||||
// :TODO: try -q
|
||||
'--zmodem', '--try-8k', '--binary', '--restricted', '{filePaths}'
|
||||
],
|
||||
recvCmd : 'rz', // Avail on Debian/Ubuntu based systems as the package "lrzsz"
|
||||
recvArgs : [
|
||||
'--zmodem', '--binary', '--restricted', '--keep-uppercase', // dumps to CWD which is set to {uploadDir}
|
||||
],
|
||||
processIACs : true, // escape/de-escape IACs (0xff)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
messageAreaDefaults : {
|
||||
//
|
||||
// The following can be override per-area as well
|
||||
//
|
||||
maxMessages : 1024, // 0 = unlimited
|
||||
maxAgeDays : 0, // 0 = unlimited
|
||||
},
|
||||
|
||||
messageConferences : {
|
||||
system_internal : {
|
||||
name : 'System Internal',
|
||||
desc : 'Built in conference for private messages, bulletins, etc.',
|
||||
|
||||
areas : {
|
||||
private_mail : {
|
||||
name : 'Private Mail',
|
||||
desc : 'Private user to user mail/email',
|
||||
maxExternalSentAgeDays : 30, // max external "outbox" item age
|
||||
},
|
||||
|
||||
local_bulletin : {
|
||||
name : 'System Bulletins',
|
||||
desc : 'Bulletin messages for all users',
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
scannerTossers : {
|
||||
ftn_bso : {
|
||||
paths : {
|
||||
outbound : paths.join(__dirname, './../mail/ftn_out/'),
|
||||
inbound : paths.join(__dirname, './../mail/ftn_in/'),
|
||||
secInbound : paths.join(__dirname, './../mail/ftn_secin/'),
|
||||
reject : paths.join(__dirname, './../mail/reject/'), // bad pkt, bundles, TIC attachments that fail any check, etc.
|
||||
//outboundNetMail : paths.join(__dirname, './../mail/ftn_netmail_out/'),
|
||||
// set 'retain' to a valid path to keep good pkt files
|
||||
},
|
||||
|
||||
//
|
||||
// Packet and (ArcMail) bundle target sizes are just that: targets.
|
||||
// Actual sizes may be slightly larger when we must place a full
|
||||
// PKT contents *somewhere*
|
||||
//
|
||||
packetTargetByteSize : 512000, // 512k, before placing messages in a new pkt
|
||||
bundleTargetByteSize : 2048000, // 2M, before creating another archive
|
||||
packetMsgEncoding : 'utf8', // default packet encoding. Override per node if desired.
|
||||
packetAnsiMsgEncoding : 'cp437', // packet encoding for *ANSI ART* messages
|
||||
|
||||
tic : {
|
||||
secureInOnly : true, // only bring in from secure inbound (|secInbound| path, password protected)
|
||||
uploadBy : 'ENiGMA TIC', // default upload by username (override @ network)
|
||||
allowReplace : false, // use "Replaces" TIC field
|
||||
descPriority : 'diz', // May be diz=.DIZ/etc., or tic=from TIC Ldesc
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
fileBase: {
|
||||
// areas with an explicit |storageDir| will be stored relative to |areaStoragePrefix|:
|
||||
areaStoragePrefix : paths.join(__dirname, './../file_base/'),
|
||||
|
||||
maxDescFileByteSize : 471859, // ~1/4 MB
|
||||
maxDescLongFileByteSize : 524288, // 1/2 MB
|
||||
|
||||
fileNamePatterns: {
|
||||
// These are NOT case sensitive
|
||||
// FILE_ID.DIZ - https://en.wikipedia.org/wiki/FILE_ID.DIZ
|
||||
// Some groups include a FILE_ID.ANS. We try to use that over FILE_ID.DIZ if available.
|
||||
desc : [
|
||||
'^.*FILE_ID\.ANS$', '^.*FILE_ID\.DIZ$', // eslint-disable-line no-useless-escape
|
||||
'^.*DESC\.SDI$', // eslint-disable-line no-useless-escape
|
||||
'^.*DESCRIPT\.ION$', // eslint-disable-line no-useless-escape
|
||||
'^.*FILE\.DES$', // eslint-disable-line no-useless-escape
|
||||
'^.*FILE\.SDI$', // eslint-disable-line no-useless-escape
|
||||
'^.*DISK\.ID$' // eslint-disable-line no-useless-escape
|
||||
],
|
||||
|
||||
// common README filename - https://en.wikipedia.org/wiki/README
|
||||
descLong : [
|
||||
'^[^/\]*\.NFO$', // eslint-disable-line no-useless-escape
|
||||
'^.*README\.1ST$', // eslint-disable-line no-useless-escape
|
||||
'^.*README\.NOW$', // eslint-disable-line no-useless-escape
|
||||
'^.*README\.TXT$', // eslint-disable-line no-useless-escape
|
||||
'^.*READ\.ME$', // eslint-disable-line no-useless-escape
|
||||
'^.*README$', // eslint-disable-line no-useless-escape
|
||||
'^.*README\.md$', // eslint-disable-line no-useless-escape
|
||||
'^RELEASE-INFO.ASC$' // eslint-disable-line no-useless-escape
|
||||
],
|
||||
},
|
||||
|
||||
yearEstPatterns: [
|
||||
//
|
||||
// Patterns should produce the year in the first submatch.
|
||||
// The extracted year may be YY or YYYY
|
||||
//
|
||||
'\\b((?:[1-2][0-9][0-9]{2}))[\\-\\/\\.][0-3]?[0-9][\\-\\/\\.][0-3]?[0-9]\\b', // yyyy-mm-dd, yyyy/mm/dd, ...
|
||||
'\\b[0-3]?[0-9][\\-\\/\\.][0-3]?[0-9][\\-\\/\\.]((?:[1-2][0-9][0-9]{2}))\\b', // mm/dd/yyyy, mm.dd.yyyy, ...
|
||||
'\\b((?:[1789][0-9]))[\\-\\/\\.][0-3]?[0-9][\\-\\/\\.][0-3]?[0-9]\\b', // yy-mm-dd, yy-mm-dd, ...
|
||||
'\\b[0-3]?[0-9][\\-\\/\\.][0-3]?[0-9][\\-\\/\\.]((?:[1789][0-9]))\\b', // mm-dd-yy, mm/dd/yy, ...
|
||||
//'\\b((?:[1-2][0-9][0-9]{2}))[\\-\\/\\.][0-3]?[0-9][\\-\\/\\.][0-3]?[0-9]|[0-3]?[0-9][\\-\\/\\.][0-3]?[0-9][\\-\\/\\.]((?:[0-9]{2})?[0-9]{2})\\b', // yyyy-mm-dd, m/d/yyyy, mm-dd-yyyy, etc.
|
||||
//"\\b('[1789][0-9])\\b", // eslint-disable-line quotes
|
||||
'\\b[0-3]?[0-9][\\-\\/\\.](?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec|january|february|march|april|may|june|july|august|september|october|november|december)[\\-\\/\\.]((?:[0-9]{2})?[0-9]{2})\\b',
|
||||
'\\b(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec|january|february|march|april|may|june|july|august|september|october|november|december),?\\s[0-9]+(?:st|nd|rd|th)?,?\\s((?:[0-9]{2})?[0-9]{2})\\b', // November 29th, 1997
|
||||
'\\(((?:19|20)[0-9]{2})\\)', // (19xx) or (20xx) -- with parens -- do this before 19xx 20xx such that this has priority
|
||||
'\\b((?:19|20)[0-9]{2})\\b', // simple 19xx or 20xx with word boundaries
|
||||
'\\b\'([17-9][0-9])\\b', // '95, '17, ...
|
||||
// :TODO: DD/MMM/YY, DD/MMMM/YY, DD/MMM/YYYY, etc.
|
||||
],
|
||||
|
||||
web : {
|
||||
path : '/f/',
|
||||
routePath : '/f/[a-zA-Z0-9]+$',
|
||||
expireMinutes : 1440, // 1 day
|
||||
},
|
||||
|
||||
//
|
||||
// File area storage location tag/value pairs.
|
||||
// Non-absolute paths are relative to |areaStoragePrefix|.
|
||||
//
|
||||
storageTags : {
|
||||
sys_msg_attach : 'sys_msg_attach',
|
||||
sys_temp_download : 'sys_temp_download',
|
||||
},
|
||||
|
||||
areas: {
|
||||
system_message_attachment : {
|
||||
name : 'System Message Attachments',
|
||||
desc : 'File attachments to messages',
|
||||
storageTags : [ 'sys_msg_attach' ],
|
||||
},
|
||||
|
||||
system_temporary_download : {
|
||||
name : 'System Temporary Downloads',
|
||||
desc : 'Temporary downloadables',
|
||||
storageTags : [ 'sys_temp_download' ],
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
eventScheduler : {
|
||||
|
||||
events : {
|
||||
dailyMaintenance : {
|
||||
schedule : 'at 11:59pm',
|
||||
action : '@method:core/misc_scheduled_events.js:dailyMaintenanceScheduledEvent',
|
||||
},
|
||||
trimMessageAreas : {
|
||||
// may optionally use [or ]@watch:/path/to/file
|
||||
schedule : 'every 24 hours',
|
||||
|
||||
// action:
|
||||
// - @method:path/to/module.js:theMethodName
|
||||
// (path is relative to ENiGMA base dir)
|
||||
//
|
||||
// - @execute:/path/to/something/executable.sh
|
||||
//
|
||||
action : '@method:core/message_area.js:trimMessageAreasScheduledEvent',
|
||||
},
|
||||
|
||||
nntpMaintenance : {
|
||||
schedule : 'every 12 hours', // should generally be < trimMessageAreas interval
|
||||
action : '@method:core/servers/content/nntp.js:performMaintenanceTask',
|
||||
},
|
||||
|
||||
updateFileAreaStats : {
|
||||
schedule : 'every 1 hours',
|
||||
action : '@method:core/file_base_area.js:updateAreaStatsScheduledEvent',
|
||||
},
|
||||
|
||||
forgotPasswordMaintenance : {
|
||||
schedule : 'every 24 hours',
|
||||
action : '@method:core/web_password_reset.js:performMaintenanceTask',
|
||||
args : [ '24 hours' ] // items older than this will be removed
|
||||
},
|
||||
|
||||
twoFactorRegisterTokenMaintenance : {
|
||||
schedule : 'every 24 hours',
|
||||
action : '@method:core/user_temp_token.js:temporaryTokenMaintenanceTask',
|
||||
args : [
|
||||
'auth_factor2_otp_register',
|
||||
'24 hours', // expire time
|
||||
]
|
||||
},
|
||||
|
||||
//
|
||||
// Enable the following entry in your config.hjson to periodically create/update
|
||||
// DESCRIPT.ION files for your file base
|
||||
//
|
||||
/*
|
||||
updateDescriptIonFiles : {
|
||||
schedule : 'on the last day of the week',
|
||||
action : '@method:core/file_base_list_export.js:updateFileBaseDescFilesScheduledEvent',
|
||||
}
|
||||
*/
|
||||
}
|
||||
},
|
||||
|
||||
logging : {
|
||||
rotatingFile : { // set to 'disabled' or false to disable
|
||||
type : 'rotating-file',
|
||||
fileName : 'enigma-bbs.log',
|
||||
period : '1d',
|
||||
count : 3,
|
||||
level : 'debug',
|
||||
}
|
||||
|
||||
// :TODO: syslog - https://github.com/mcavage/node-bunyan-syslog
|
||||
},
|
||||
|
||||
debug : {
|
||||
assertsEnabled : false,
|
||||
},
|
||||
|
||||
statLog : {
|
||||
systemEvents : {
|
||||
loginHistoryMax: -1, // set to -1 for forever
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
|
@ -0,0 +1,248 @@
|
|||
// deps
|
||||
const paths = require('path');
|
||||
const async = require('async');
|
||||
const moment = require('moment');
|
||||
|
||||
const _ = require('lodash');
|
||||
const mapValuesDeep = require('deepdash/getMapValuesDeep')(_);
|
||||
|
||||
module.exports = class ConfigLoader {
|
||||
constructor(
|
||||
{
|
||||
hotReload = true,
|
||||
defaultConfig = {},
|
||||
defaultsCustomizer = null,
|
||||
onReload = null,
|
||||
keepWsc = false,
|
||||
} =
|
||||
{
|
||||
hotReload : true,
|
||||
defaultConfig : {},
|
||||
defaultsCustomizer : null,
|
||||
onReload : null,
|
||||
keepWsc : false,
|
||||
}
|
||||
)
|
||||
{
|
||||
this.current = {};
|
||||
|
||||
this.hotReload = hotReload;
|
||||
this.defaultConfig = defaultConfig;
|
||||
this.defaultsCustomizer = defaultsCustomizer;
|
||||
this.onReload = onReload;
|
||||
this.keepWsc = keepWsc;
|
||||
}
|
||||
|
||||
init(baseConfigPath, cb) {
|
||||
this.baseConfigPath = baseConfigPath;
|
||||
return this._reload(baseConfigPath, cb);
|
||||
}
|
||||
|
||||
get() {
|
||||
return this.current;
|
||||
}
|
||||
|
||||
_reload(baseConfigPath, cb) {
|
||||
let defaultConfig;
|
||||
if (_.isFunction(this.defaultConfig)) {
|
||||
defaultConfig = this.defaultConfig();
|
||||
} else if (_.isObject(this.defaultConfig)) {
|
||||
defaultConfig = this.defaultConfig;
|
||||
} else {
|
||||
defaultConfig = {};
|
||||
}
|
||||
|
||||
//
|
||||
// 1 - Fetch base configuration from |baseConfigPath|
|
||||
// 2 - Merge with |defaultConfig|
|
||||
// 3 - Resolve any includes
|
||||
// 4 - Resolve @reference and @environment
|
||||
// 5 - Perform any validation
|
||||
//
|
||||
async.waterfall(
|
||||
[
|
||||
(callback) => {
|
||||
return this._loadConfigFile(baseConfigPath, callback);
|
||||
},
|
||||
(config, callback) => {
|
||||
if (_.isFunction(this.defaultsCustomizer)) {
|
||||
const stack = [];
|
||||
const mergedConfig = _.mergeWith(
|
||||
defaultConfig,
|
||||
config,
|
||||
(defaultVal, configVal, key, target, source) => {
|
||||
var path;
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
if (!stack.length) {
|
||||
stack.push({source, path : []});
|
||||
}
|
||||
|
||||
const prev = stack[stack.length - 1];
|
||||
|
||||
if (source === prev.source) {
|
||||
path = prev.path.concat(key);
|
||||
stack.push({source : configVal, path});
|
||||
break;
|
||||
}
|
||||
|
||||
stack.pop();
|
||||
}
|
||||
|
||||
path = path.join('.');
|
||||
return this.defaultsCustomizer(defaultVal, configVal, key, path);
|
||||
}
|
||||
);
|
||||
|
||||
return callback(null, mergedConfig);
|
||||
}
|
||||
|
||||
return callback(null, _.merge(defaultConfig, config));
|
||||
},
|
||||
(config, callback) => {
|
||||
const configRoot = paths.dirname(baseConfigPath);
|
||||
return this._resolveIncludes(configRoot, config, callback);
|
||||
},
|
||||
(config, callback) => {
|
||||
config = this._resolveAtSpecs(config);
|
||||
return callback(null, config);
|
||||
},
|
||||
],
|
||||
(err, config) => {
|
||||
if (!err) {
|
||||
this.current = config;
|
||||
}
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_convertTo(value, type) {
|
||||
switch (type) {
|
||||
case 'bool' :
|
||||
case 'boolean' :
|
||||
value = ('1' === value || 'true' === value.toLowerCase());
|
||||
break;
|
||||
|
||||
case 'number' :
|
||||
{
|
||||
const num = parseInt(value);
|
||||
if (!isNaN(num)) {
|
||||
value = num;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'object' :
|
||||
try {
|
||||
value = JSON.parse(value);
|
||||
} catch(e) {
|
||||
// ignored
|
||||
}
|
||||
break;
|
||||
|
||||
case 'timestamp' :
|
||||
{
|
||||
const m = moment(value);
|
||||
if (m.isValid()) {
|
||||
value = m;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
_resolveEnvironmentVariable(spec) {
|
||||
const [, varName, type, array] = spec.split(':');
|
||||
if (!varName) {
|
||||
return;
|
||||
}
|
||||
|
||||
let value = process.env[varName];
|
||||
if (!value) {
|
||||
// console is about as good as we can do here
|
||||
return console.info(`WARNING: environment variable "${varName}" from spec "${spec}" not found!`);
|
||||
}
|
||||
|
||||
if ('array' === array) {
|
||||
value = value.split(',').map(v => this._convertTo(v, type));
|
||||
} else {
|
||||
value = this._convertTo(value, type);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
_loadConfigFile(filePath, cb) {
|
||||
const ConfigCache = require('./config_cache');
|
||||
|
||||
const options = {
|
||||
filePath,
|
||||
hotReload : this.hotReload,
|
||||
keepWsc : this.keepWsc,
|
||||
callback : this._configFileChanged.bind(this),
|
||||
};
|
||||
|
||||
ConfigCache.getConfigWithOptions(options, (err, config) => {
|
||||
if (err) {
|
||||
err.configPath = options.filePath;
|
||||
}
|
||||
return cb(err, config);
|
||||
});
|
||||
}
|
||||
|
||||
_configFileChanged({fileName, fileRoot}) {
|
||||
const reCachedPath = paths.join(fileRoot, fileName);
|
||||
if (this.configPaths.includes(reCachedPath)) {
|
||||
this._reload(this.baseConfigPath, err => {
|
||||
if (_.isFunction(this.onReload)) {
|
||||
this.onReload(err, reCachedPath);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_resolveIncludes(configRoot, config, cb) {
|
||||
if (!Array.isArray(config.includes)) {
|
||||
this.configPaths = [ this.baseConfigPath ];
|
||||
return cb(null, config);
|
||||
}
|
||||
|
||||
// If a included file is changed, we need to re-cache, so this
|
||||
// must be tracked...
|
||||
const includePaths = config.includes.map(inc => paths.join(configRoot, inc));
|
||||
async.eachSeries(includePaths, (includePath, nextIncludePath) => {
|
||||
this._loadConfigFile(includePath, (err, includedConfig) => {
|
||||
if (err) {
|
||||
return nextIncludePath(err);
|
||||
}
|
||||
|
||||
_.defaultsDeep(config, includedConfig);
|
||||
return nextIncludePath(null);
|
||||
});
|
||||
},
|
||||
err => {
|
||||
this.configPaths = [ this.baseConfigPath, ...includePaths ];
|
||||
return cb(err, config);
|
||||
});
|
||||
}
|
||||
|
||||
_resolveAtSpecs(config) {
|
||||
return mapValuesDeep(
|
||||
config,
|
||||
value => {
|
||||
if (_.isString(value) && '@' === value.charAt(0)) {
|
||||
if (value.startsWith('@reference:')) {
|
||||
const refPath = value.slice(11);
|
||||
value = _.get(config, refPath, value);
|
||||
} else if (value.startsWith('@environment:')) {
|
||||
value = this._resolveEnvironmentVariable(value) || value;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
|
@ -2,66 +2,16 @@
|
|||
'use strict';
|
||||
|
||||
const Config = require('./config.js').get;
|
||||
const ConfigCache = require('./config_cache.js');
|
||||
const Events = require('./events.js');
|
||||
|
||||
// deps
|
||||
const paths = require('path');
|
||||
const async = require('async');
|
||||
|
||||
exports.init = init;
|
||||
exports.getConfigPath = getConfigPath;
|
||||
exports.getFullConfig = getFullConfig;
|
||||
|
||||
function getConfigPath(filePath) {
|
||||
// |filePath| is assumed to be in the config path if it's only a file name
|
||||
if('.' === paths.dirname(filePath)) {
|
||||
filePath = paths.join(Config().paths.config, filePath);
|
||||
}
|
||||
if (paths.isAbsolute(filePath)) {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
function init(cb) {
|
||||
// pre-cache menu.hjson and prompt.hjson + establish events
|
||||
const changed = ( { fileName, fileRoot } ) => {
|
||||
const reCachedPath = paths.join(fileRoot, fileName);
|
||||
if(reCachedPath === getConfigPath(Config().general.menuFile)) {
|
||||
Events.emit(Events.getSystemEvents().MenusChanged);
|
||||
} else if(reCachedPath === getConfigPath(Config().general.promptFile)) {
|
||||
Events.emit(Events.getSystemEvents().PromptsChanged);
|
||||
}
|
||||
};
|
||||
|
||||
const config = Config();
|
||||
async.series(
|
||||
[
|
||||
function menu(callback) {
|
||||
return ConfigCache.getConfigWithOptions(
|
||||
{
|
||||
filePath : getConfigPath(config.general.menuFile),
|
||||
callback : changed,
|
||||
},
|
||||
callback
|
||||
);
|
||||
},
|
||||
function prompt(callback) {
|
||||
return ConfigCache.getConfigWithOptions(
|
||||
{
|
||||
filePath : getConfigPath(config.general.promptFile),
|
||||
callback : changed,
|
||||
},
|
||||
callback
|
||||
);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function getFullConfig(filePath, cb) {
|
||||
ConfigCache.getConfig(getConfigPath(filePath), (err, config) => {
|
||||
return cb(err, config);
|
||||
});
|
||||
return paths.join(Config().paths.config, filePath);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
'use strict';
|
||||
|
||||
// ENiGMA½
|
||||
const conf = require('./config.js');
|
||||
const conf = require('./config');
|
||||
|
||||
// deps
|
||||
const sqlite3 = require('sqlite3');
|
||||
|
@ -30,7 +30,8 @@ function getTransactionDatabase(db) {
|
|||
}
|
||||
|
||||
function getDatabasePath(name) {
|
||||
return paths.join(conf.config.paths.db, `${name}.sqlite3`);
|
||||
const Config = conf.get();
|
||||
return paths.join(Config.paths.db, `${name}.sqlite3`);
|
||||
}
|
||||
|
||||
function getModDatabasePath(moduleInfo, suffix) {
|
||||
|
@ -53,7 +54,8 @@ function getModDatabasePath(moduleInfo, suffix) {
|
|||
(full.split('.').length > 1 && HOST_RE.test(full)),
|
||||
'packageName must follow Reverse Domain Name Notation - https://en.wikipedia.org/wiki/Reverse_domain_name_notation');
|
||||
|
||||
return paths.join(conf.config.paths.modsDb, `${full}.sqlite3`);
|
||||
const Config = conf.get();
|
||||
return paths.join(Config.paths.modsDb, `${full}.sqlite3`);
|
||||
}
|
||||
|
||||
function loadDatabaseForMod(modInfo, cb) {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
'use strict';
|
||||
|
||||
// ENiGMA½
|
||||
const conf = require('./config.js');
|
||||
const Config = require('./config').get;
|
||||
const logger = require('./logger.js');
|
||||
const ServerModule = require('./server_module.js').ServerModule;
|
||||
const clientConns = require('./client_connections.js');
|
||||
|
@ -28,7 +28,7 @@ module.exports = class LoginServerModule extends ServerModule {
|
|||
//
|
||||
// Choose initial theme before we have user context
|
||||
//
|
||||
const preLoginTheme = _.get(conf.config, 'theme.preLogin');
|
||||
const preLoginTheme = _.get(Config(), 'theme.preLogin');
|
||||
if('*' === preLoginTheme) {
|
||||
client.user.properties[UserProps.ThemeId] = theme.getRandomTheme() || '';
|
||||
} else {
|
||||
|
|
|
@ -24,10 +24,11 @@ function getMenuConfig(client, name, cb) {
|
|||
async.waterfall(
|
||||
[
|
||||
function locateMenuConfig(callback) {
|
||||
if(_.has(client.currentTheme, [ 'menus', name ])) {
|
||||
const menuConfig = client.currentTheme.menus[name];
|
||||
const menuConfig = _.get(client.currentTheme, [ 'menus', name ]);
|
||||
if (menuConfig) {
|
||||
return callback(null, menuConfig);
|
||||
}
|
||||
|
||||
return callback(Errors.DoesNotExist(`No menu entry for "${name}"`));
|
||||
},
|
||||
function locatePromptConfig(menuConfig, callback) {
|
||||
|
@ -36,7 +37,7 @@ function getMenuConfig(client, name, cb) {
|
|||
menuConfig.promptConfig = client.currentTheme.prompts[menuConfig.prompt];
|
||||
return callback(null, menuConfig);
|
||||
}
|
||||
return callback(Error.DoesNotExist(`No prompt entry for "${menuConfig.prompt}"`));
|
||||
return callback(Errors.DoesNotExist(`No prompt entry for "${menuConfig.prompt}"`));
|
||||
}
|
||||
return callback(null, menuConfig);
|
||||
}
|
||||
|
|
|
@ -60,8 +60,8 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
|
|||
this.menuMethods = {
|
||||
selectMessage : (formData, extraArgs, cb) => {
|
||||
if(MciViewIds.allViews.msgList === formData.submitId) {
|
||||
this.initialFocusIndex = formData.value.messageIndex ||
|
||||
formData.value.message; // older deprecated arg name
|
||||
// 'messageIndex' or older deprecated 'message' member
|
||||
this.initialFocusIndex = _.get(formData, 'value.messageIndex', formData.value.message);
|
||||
|
||||
const modOpts = {
|
||||
extraArgs : {
|
||||
|
@ -108,8 +108,9 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
|
|||
if(MciViewIds.allViews.msgList != formData.submitId) {
|
||||
return cb(null);
|
||||
}
|
||||
const messageIndex = formData.value.messageIndex ||
|
||||
formData.value.message; // older, deprecated arg name
|
||||
|
||||
// newer 'messageIndex' or older deprecated value
|
||||
const messageIndex = _.get(formData, 'value.messageIndex', formData.value.message);
|
||||
return this.promptDeleteMessageConfirm(messageIndex, cb);
|
||||
},
|
||||
deleteMessageYes : (formData, extraArgs, cb) => {
|
||||
|
|
|
@ -64,14 +64,14 @@ function getDefaultConfigPath() {
|
|||
}
|
||||
|
||||
function getConfigPath() {
|
||||
const baseConfigPath = argv.config ? argv.config : config.getDefaultPath();
|
||||
const baseConfigPath = argv.config ? argv.config : config.Config.getDefaultPath();
|
||||
return baseConfigPath + 'config.hjson';
|
||||
}
|
||||
|
||||
function initConfig(cb) {
|
||||
const configPath = getConfigPath();
|
||||
|
||||
config.init(configPath, { keepWsc : true, noWatch : true }, cb);
|
||||
config.Config.create(configPath, { keepWsc : true, hotReload : false }, cb);
|
||||
}
|
||||
|
||||
function initConfigAndDatabases(cb) {
|
||||
|
@ -85,7 +85,7 @@ function initConfigAndDatabases(cb) {
|
|||
},
|
||||
function initArchiveUtil(callback) {
|
||||
// ensure we init ArchiveUtil without events
|
||||
require('../../core/archive_util').getInstance(true); // true=noWatch
|
||||
require('../../core/archive_util').getInstance(false); // false=hotReload
|
||||
return callback(null);
|
||||
}
|
||||
],
|
||||
|
|
|
@ -159,7 +159,7 @@ function askNewConfigQuestions(cb) {
|
|||
},
|
||||
function basic(callback) {
|
||||
getAnswers(QUESTIONS.Basic, answers => {
|
||||
const defaultConfig = require('../../core/config.js').getDefaultConfig();
|
||||
const defaultConfig = require('../../core/config_default')();
|
||||
|
||||
// start by plopping in values we want directly from config.js
|
||||
const template = hjson.rt.parse(fs.readFileSync(paths.join(__dirname, '../../misc/config_template.in.hjson'), 'utf8'));
|
||||
|
@ -227,26 +227,44 @@ function buildNewConfig() {
|
|||
if(err) { return;
|
||||
}
|
||||
|
||||
const bn = sanatizeFilename(config.general.boardName)
|
||||
const boardName = sanatizeFilename(config.general.boardName)
|
||||
.replace(/[^a-z0-9_-]/ig, '_')
|
||||
.replace(/_+/g, '_')
|
||||
.toLowerCase();
|
||||
const menuFile = `${bn}-menu.hjson`;
|
||||
|
||||
const includeFilesIn = [
|
||||
'message_base.in.hjson',
|
||||
'private_mail.in.hjson',
|
||||
'login.in.hjson',
|
||||
'new_user.in.hjson',
|
||||
'doors.in.hjson',
|
||||
'file_base.in.hjson',
|
||||
];
|
||||
|
||||
let includeFiles = [];
|
||||
includeFilesIn.forEach(incFile => {
|
||||
const outName = `${boardName}-${incFile.replace('.in', '')}`;
|
||||
includeFiles.push(outName);
|
||||
|
||||
copyFileSyncSilent(
|
||||
paths.join(__dirname, '../../misc/menu_template.in.hjson'),
|
||||
paths.join(__dirname, '../../config/', menuFile),
|
||||
paths.join(__dirname, '../../misc/menu_templates', incFile),
|
||||
paths.join(__dirname, '../../config/menus', outName),
|
||||
fs.constants.COPYFILE_EXCL
|
||||
);
|
||||
});
|
||||
|
||||
const promptFile = `${bn}-prompt.hjson`;
|
||||
copyFileSyncSilent(
|
||||
paths.join(__dirname, '../../misc/prompt_template.in.hjson'),
|
||||
paths.join(__dirname, '../../config/', promptFile),
|
||||
fs.constants.COPYFILE_EXCL
|
||||
// We really only need includes to be replaced
|
||||
const mainTemplate = fs.readFileSync(paths.join(__dirname, '../../misc/menu_templates/main.in.hjson'), 'utf8')
|
||||
.replace(/%INCLUDE_FILES%/g, includeFiles.join('\n\t\t')); // cheesy, but works!
|
||||
|
||||
const menuFile = `${boardName}-main.hjson`;
|
||||
fs.writeFileSync(
|
||||
paths.join(__dirname, '../../config/menus', menuFile),
|
||||
mainTemplate,
|
||||
'utf8'
|
||||
);
|
||||
|
||||
config.general.menuFile = menuFile;
|
||||
config.general.promptFile = promptFile;
|
||||
config.general.menuFile = paths.join(__dirname, '../../config/menus/', menuFile);
|
||||
|
||||
if(writeConfig(config, configPath)) {
|
||||
console.info('Configuration generated');
|
||||
|
|
|
@ -9,7 +9,6 @@ module.exports = {
|
|||
ThemeChanged : 'codes.l33t.enigma.system.theme_changed', // (theme.hjson): { themeId }
|
||||
ConfigChanged : 'codes.l33t.enigma.system.config_changed', // (config.hjson)
|
||||
MenusChanged : 'codes.l33t.enigma.system.menus_changed', // (menu.hjson)
|
||||
PromptsChanged : 'codes.l33t.enigma.system.prompts_changed', // (prompt.hjson)
|
||||
|
||||
// User - includes { user, ...}
|
||||
NewUser : 'codes.l33t.enigma.system.user_new', // { ... }
|
||||
|
|
631
core/theme.js
631
core/theme.js
|
@ -5,16 +5,16 @@ const Config = require('./config.js').get;
|
|||
const art = require('./art.js');
|
||||
const ansi = require('./ansi_term.js');
|
||||
const Log = require('./logger.js').log;
|
||||
const ConfigCache = require('./config_cache.js');
|
||||
const getFullConfig = require('./config_util.js').getFullConfig;
|
||||
const asset = require('./asset.js');
|
||||
const ViewController = require('./view_controller.js').ViewController;
|
||||
const Errors = require('./enig_error.js').Errors;
|
||||
const ErrorReasons = require('./enig_error.js').ErrorReasons;
|
||||
const Events = require('./events.js');
|
||||
const AnsiPrep = require('./ansi_prep.js');
|
||||
const UserProps = require('./user_property.js');
|
||||
|
||||
const ConfigLoader = require('./config_loader');
|
||||
const { getConfigPath } = require('./config_util');
|
||||
|
||||
// deps
|
||||
const fs = require('graceful-fs');
|
||||
const paths = require('path');
|
||||
|
@ -26,17 +26,301 @@ exports.getThemeArt = getThemeArt;
|
|||
exports.getAvailableThemes = getAvailableThemes;
|
||||
exports.getRandomTheme = getRandomTheme;
|
||||
exports.setClientTheme = setClientTheme;
|
||||
exports.initAvailableThemes = initAvailableThemes;
|
||||
exports.displayPreparedArt = displayPreparedArt;
|
||||
exports.displayThemeArt = displayThemeArt;
|
||||
exports.displayThemedPause = displayThemedPause;
|
||||
exports.displayThemedPrompt = displayThemedPrompt;
|
||||
exports.displayThemedAsset = displayThemedAsset;
|
||||
|
||||
function refreshThemeHelpers(theme) {
|
||||
// global instance of ThemeManager; see ThemeManager.create()
|
||||
let themeManagerInstance;
|
||||
|
||||
exports.ThemeManager = class ThemeManager {
|
||||
constructor() {
|
||||
this.availableThemes = new Map();
|
||||
}
|
||||
|
||||
static create(cb) {
|
||||
themeManagerInstance = new ThemeManager();
|
||||
themeManagerInstance.init(err => {
|
||||
if (!err) {
|
||||
themeManagerInstance.getAvailableThemes().forEach( (themeConfig, themeId) => {
|
||||
const { name, author, group } = themeConfig.get().info;
|
||||
Log.info(
|
||||
{ themeId, themeName : name, author, group },
|
||||
'Theme loaded'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return cb(err);
|
||||
});
|
||||
}
|
||||
|
||||
getAvailableThemes() {
|
||||
return this.availableThemes;
|
||||
}
|
||||
|
||||
init(cb) {
|
||||
this.menuConfig = new ConfigLoader({
|
||||
onReload : err => {
|
||||
if (!err) {
|
||||
// menu.hjson/includes have changed; this could affect
|
||||
// all themes, so they must be reloaded
|
||||
Events.emit(Events.getSystemEvents().MenusChanged);
|
||||
|
||||
this._reloadAllThemes();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
this.menuConfig.init(getConfigPath(Config().general.menuFile), err => {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
return this._loadThemes(cb);
|
||||
});
|
||||
}
|
||||
|
||||
_loadThemes(cb) {
|
||||
const themeDir = Config().paths.themes;
|
||||
|
||||
fs.readdir(themeDir, (err, files) => {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
async.filter(files, (filename, nextFilename) => {
|
||||
const fullPath = paths.join(themeDir, filename);
|
||||
fs.stat(fullPath, (err, stats) => {
|
||||
if (err) {
|
||||
return nextFilename(err);
|
||||
}
|
||||
|
||||
return nextFilename(null, stats.isDirectory());
|
||||
});
|
||||
},
|
||||
(err, themeIds) => {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
async.each(themeIds, (themeId, nextThemeId) => {
|
||||
return this._loadTheme(themeId, nextThemeId);
|
||||
},
|
||||
err => {
|
||||
return cb(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_loadTheme(themeId, cb) {
|
||||
const themeConfig = new ConfigLoader({
|
||||
onReload : err => {
|
||||
if (!err) {
|
||||
// this particular theme has changed
|
||||
this._themeLoaded(themeId, themeConfig, err => {
|
||||
if (!err) {
|
||||
Events.emit(
|
||||
Events.getSystemEvents().ThemeChanged,
|
||||
{ themeId }
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const themeConfigPath = paths.join(Config().paths.themes, themeId, 'theme.hjson');
|
||||
|
||||
themeConfig.init(themeConfigPath, err => {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
this._themeLoaded(themeId, themeConfig);
|
||||
return cb(null);
|
||||
});
|
||||
}
|
||||
|
||||
_themeLoaded(themeId, themeConfig) {
|
||||
const theme = themeConfig.get();
|
||||
|
||||
// do some basic validation
|
||||
// :TODO: schema validation here
|
||||
if(!_.isObject(theme.info) ||
|
||||
!_.isString(theme.info.name) ||
|
||||
!_.isString(theme.info.author))
|
||||
{
|
||||
return Log.warn({ themeId }, 'Theme contains invalid or missing "info" section');
|
||||
}
|
||||
|
||||
if(false === _.get(theme, 'info.enabled')) {
|
||||
Log.info({ themeId }, 'Theme is disabled');
|
||||
return this.availableThemes.delete(themeId);
|
||||
}
|
||||
|
||||
// merge menu.hjson+theme.hjson/etc. to the final usable theme
|
||||
this._finalizeTheme(themeId, themeConfig);
|
||||
|
||||
// Theme is valid and enabled; update it in available themes
|
||||
this.availableThemes.set(themeId, themeConfig);
|
||||
|
||||
Events.emit(
|
||||
Events.getSystemEvents().ThemeChanged,
|
||||
{ themeId }
|
||||
);
|
||||
}
|
||||
|
||||
_finalizeTheme(themeId, themeConfig) {
|
||||
// These TODOs are left over from the old system - decide what/if to do with them:
|
||||
// :TODO: merge in defaults (customization.defaults{} )
|
||||
// :TODO: apply generic stuff, e.g. "VM" (vs "VM1")
|
||||
|
||||
// start out with menu.hjson
|
||||
const mergedTheme = _.cloneDeep(this.menuConfig.get());
|
||||
|
||||
const theme = themeConfig.get();
|
||||
|
||||
// some data brought directly over
|
||||
mergedTheme.info = Object.assign({}, theme.info, { themeId });
|
||||
mergedTheme.achievements = _.get(theme, 'customization.achievements');
|
||||
|
||||
// Create some helpers for this theme
|
||||
this._setThemeHelpers(mergedTheme);
|
||||
|
||||
// merge customizer to disallow immutable MCI properties
|
||||
const ImmutableMCIProperties = [
|
||||
'maxLength', 'argName', 'submit', 'validate'
|
||||
];
|
||||
|
||||
const mciCustomizer = (objVal, srcVal, key) => {
|
||||
return ImmutableMCIProperties.indexOf(key) > -1 ? objVal : srcVal;
|
||||
};
|
||||
|
||||
const getFormKeys = (obj) => {
|
||||
// remove all non-numbers
|
||||
return _.remove(Object.keys(obj), k => !isNaN(k));
|
||||
};
|
||||
|
||||
const mergeMciProperties = (dst, src) => {
|
||||
Object.keys(src).forEach(mci => {
|
||||
if (dst[mci]) {
|
||||
_.mergeWith(dst[mci], src[mci], mciCustomizer);
|
||||
} else {
|
||||
// theme contains a MCI that's not found in menu
|
||||
dst[mci] = src[mci];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const applyThemeMciBlock = (dst, src, formKey) => {
|
||||
if(_.isObject(src.mci)) {
|
||||
mergeMciProperties(dst, src.mci);
|
||||
} else if (_.has(src, [ formKey, 'mci' ])) {
|
||||
mergeMciProperties(dst, src[formKey].mci);
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// Create some handy helpers
|
||||
// menu.hjson can have a couple different structures:
|
||||
// 1) Explicit declaration of expected MCI code(s) under 'form:<id>' before a 'mci' block
|
||||
// (this allows multiple layout types defined by one menu for example)
|
||||
//
|
||||
// 2) Non-explicit declaration: 'mci' directly under 'form:<id>'
|
||||
//
|
||||
// theme.hjson has it's own mix:
|
||||
// 1) Explicit: Form ID before 'mci' (generally used where there are > 1 forms)
|
||||
//
|
||||
// 2) Non-explicit: 'mci' directly under an entry
|
||||
//
|
||||
// Additionally, #1 or #2 may be under an explicit key of MCI code(s) to match up
|
||||
// with menu.hjson in #1.
|
||||
//
|
||||
// * When theming an explicit menu.hjson entry (1), we will use a matching explicit
|
||||
// entry with a matching MCI code(s) key in theme.hjson (e.g. menu="ETVM"/theme="ETVM"
|
||||
// and fall back to generic if a match is not found.
|
||||
//
|
||||
// * If theme.hjson provides form ID's, use them. Otherwise, we'll apply directly assuming
|
||||
// there is a generic 'mci' block.
|
||||
//
|
||||
const applyToForm = (form, menuTheme, formKey) => {
|
||||
if (_.isObject(form.mci)) {
|
||||
// non-explicit: no MCI code(s) key assumed since we found 'mci' directly under form ID
|
||||
applyThemeMciBlock(form.mci, menuTheme, formKey);
|
||||
} else {
|
||||
// remove anything not uppercase
|
||||
const menuMciCodeKeys = _.remove(_.keys(form), k => k === k.toUpperCase());
|
||||
|
||||
menuMciCodeKeys.forEach(mciKey => {
|
||||
const src = _.has(menuTheme, [ mciKey, 'mci' ]) ?
|
||||
menuTheme[mciKey] :
|
||||
menuTheme;
|
||||
|
||||
applyThemeMciBlock(form[mciKey].mci, src, formKey);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
[ 'menus', 'prompts'].forEach(sectionName => {
|
||||
if (!_.isObject(mergedTheme[sectionName])) {
|
||||
return Log.error({sectionName}, 'Merged theme is missing section');
|
||||
}
|
||||
|
||||
Object.keys(mergedTheme[sectionName]).forEach(entryName => {
|
||||
let createdFormSection = false;
|
||||
const mergedThemeMenu = mergedTheme[sectionName][entryName];
|
||||
|
||||
const menuTheme = _.get(theme, [ 'customization', sectionName, entryName ]);
|
||||
if (menuTheme) {
|
||||
if (menuTheme.config) {
|
||||
// :TODO: should this be _.merge() ?
|
||||
mergedThemeMenu.config = _.assign(mergedThemeMenu.config || {}, menuTheme.config);
|
||||
}
|
||||
|
||||
if('menus' === sectionName) {
|
||||
if(_.isObject(mergedThemeMenu.form)) {
|
||||
getFormKeys(mergedThemeMenu.form).forEach(formKey => {
|
||||
applyToForm(mergedThemeMenu.form[formKey], menuTheme, formKey);
|
||||
});
|
||||
} else if(_.isObject(menuTheme.mci)) {
|
||||
//
|
||||
// Not specified at menu level means we apply anything from the
|
||||
// theme to form.0.mci{}
|
||||
//
|
||||
mergedThemeMenu.form = { 0 : { mci : { } } };
|
||||
mergeMciProperties(mergedThemeMenu.form[0], menuTheme);
|
||||
createdFormSection = true;
|
||||
}
|
||||
} else if('prompts' === sectionName) {
|
||||
// no 'form' or form keys for prompts -- direct to mci
|
||||
applyToForm(mergedThemeMenu, menuTheme);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Finished merging for this menu/prompt
|
||||
//
|
||||
// If the following conditions are true, set runtime.autoNext to true:
|
||||
// * This is a menu
|
||||
// * There is/was no explicit 'form' section
|
||||
// * There is no 'prompt' specified
|
||||
//
|
||||
if('menus' === sectionName &&
|
||||
!_.isString(mergedThemeMenu.prompt) &&
|
||||
(createdFormSection || !_.isObject(mergedThemeMenu.form)))
|
||||
{
|
||||
mergedThemeMenu.runtime = _.merge(mergedThemeMenu.runtime || {}, { autoNext : true } );
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
themeConfig.current = mergedTheme;
|
||||
}
|
||||
|
||||
_setThemeHelpers(theme) {
|
||||
theme.helpers = {
|
||||
getPasswordChar : function() {
|
||||
let pwChar = _.get(
|
||||
|
@ -68,331 +352,26 @@ function refreshThemeHelpers(theme) {
|
|||
};
|
||||
}
|
||||
|
||||
function loadTheme(themeId, cb) {
|
||||
const path = paths.join(Config().paths.themes, themeId, 'theme.hjson');
|
||||
|
||||
const changed = ( { fileName, fileRoot } ) => {
|
||||
const reCachedPath = paths.join(fileRoot, fileName);
|
||||
if(reCachedPath === path) {
|
||||
reloadTheme(themeId);
|
||||
_reloadAllThemes() {
|
||||
async.each([ ...this.availableThemes.keys() ], (themeId, nextThemeId) => {
|
||||
this._loadTheme(themeId, err => {
|
||||
if (!err) {
|
||||
Log.info({ themeId }, 'Theme reloaded');
|
||||
}
|
||||
return nextThemeId(null); // always proceed
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getOpts = {
|
||||
filePath : path,
|
||||
forceReCache : true,
|
||||
callback : changed,
|
||||
};
|
||||
|
||||
ConfigCache.getConfigWithOptions(getOpts, (err, theme) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
if(!_.isObject(theme.info) ||
|
||||
!_.isString(theme.info.name) ||
|
||||
!_.isString(theme.info.author))
|
||||
{
|
||||
return cb(Errors.Invalid('Invalid or missing "info" section'));
|
||||
}
|
||||
|
||||
if(false === _.get(theme, 'info.enabled')) {
|
||||
return cb(Errors.General('Theme is not enabled', ErrorReasons.ErrNotEnabled));
|
||||
}
|
||||
|
||||
refreshThemeHelpers(theme);
|
||||
|
||||
return cb(null, theme, path);
|
||||
});
|
||||
}
|
||||
|
||||
const availableThemes = new Map();
|
||||
|
||||
const IMMUTABLE_MCI_PROPERTIES = [
|
||||
'maxLength', 'argName', 'submit', 'validate'
|
||||
];
|
||||
|
||||
function getMergedTheme(menuConfig, promptConfig, theme) {
|
||||
assert(_.isObject(menuConfig));
|
||||
assert(_.isObject(theme));
|
||||
|
||||
// :TODO: merge in defaults (customization.defaults{} )
|
||||
// :TODO: apply generic stuff, e.g. "VM" (vs "VM1")
|
||||
|
||||
//
|
||||
// Create a *clone* of menuConfig (menu.hjson) then bring in
|
||||
// promptConfig (prompt.hjson)
|
||||
//
|
||||
const mergedTheme = _.cloneDeep(menuConfig);
|
||||
|
||||
if(_.isObject(promptConfig.prompts)) {
|
||||
mergedTheme.prompts = _.cloneDeep(promptConfig.prompts);
|
||||
}
|
||||
|
||||
//
|
||||
// Add in data we won't be altering directly from the theme
|
||||
//
|
||||
mergedTheme.info = theme.info;
|
||||
mergedTheme.helpers = theme.helpers;
|
||||
mergedTheme.achievements = _.get(theme, 'customization.achievements');
|
||||
|
||||
//
|
||||
// merge customizer to disallow immutable MCI properties
|
||||
//
|
||||
const mciCustomizer = function(objVal, srcVal, key) {
|
||||
return IMMUTABLE_MCI_PROPERTIES.indexOf(key) > -1 ? objVal : srcVal;
|
||||
};
|
||||
|
||||
function getFormKeys(fromObj) {
|
||||
// remove all non-numbers
|
||||
return _.remove(_.keys(fromObj), k => !isNaN(k));
|
||||
}
|
||||
|
||||
function mergeMciProperties(dest, src) {
|
||||
Object.keys(src).forEach(mci => {
|
||||
if(dest[mci]) {
|
||||
_.mergeWith(dest[mci], src[mci], mciCustomizer);
|
||||
} else {
|
||||
// theme contains MCI not in menu; bring in as-is
|
||||
dest[mci] = src[mci];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function applyThemeMciBlock(dest, src, formKey) {
|
||||
if(_.isObject(src.mci)) {
|
||||
mergeMciProperties(dest, src.mci);
|
||||
} else {
|
||||
if(_.has(src, [ formKey, 'mci' ])) {
|
||||
mergeMciProperties(dest, src[formKey].mci);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// menu.hjson can have a couple different structures:
|
||||
// 1) Explicit declaration of expected MCI code(s) under 'form:<id>' before a 'mci' block
|
||||
// (this allows multiple layout types defined by one menu for example)
|
||||
//
|
||||
// 2) Non-explicit declaration: 'mci' directly under 'form:<id>'
|
||||
//
|
||||
// theme.hjson has it's own mix:
|
||||
// 1) Explicit: Form ID before 'mci' (generally used where there are > 1 forms)
|
||||
//
|
||||
// 2) Non-explicit: 'mci' directly under an entry
|
||||
//
|
||||
// Additionally, #1 or #2 may be under an explicit key of MCI code(s) to match up
|
||||
// with menu.hjson in #1.
|
||||
//
|
||||
// * When theming an explicit menu.hjson entry (1), we will use a matching explicit
|
||||
// entry with a matching MCI code(s) key in theme.hjson (e.g. menu="ETVM"/theme="ETVM"
|
||||
// and fall back to generic if a match is not found.
|
||||
//
|
||||
// * If theme.hjson provides form ID's, use them. Otherwise, we'll apply directly assuming
|
||||
// there is a generic 'mci' block.
|
||||
//
|
||||
function applyToForm(form, menuTheme, formKey) {
|
||||
if(_.isObject(form.mci)) {
|
||||
// non-explicit: no MCI code(s) key assumed since we found 'mci' directly under form ID
|
||||
applyThemeMciBlock(form.mci, menuTheme, formKey);
|
||||
} else {
|
||||
// remove anything not uppercase
|
||||
const menuMciCodeKeys = _.remove(_.keys(form), k => k === k.toUpperCase());
|
||||
|
||||
menuMciCodeKeys.forEach(function mciKeyEntry(mciKey) {
|
||||
let applyFrom;
|
||||
if(_.has(menuTheme, [ mciKey, 'mci' ])) {
|
||||
applyFrom = menuTheme[mciKey];
|
||||
} else {
|
||||
applyFrom = menuTheme;
|
||||
}
|
||||
|
||||
applyThemeMciBlock(form[mciKey].mci, applyFrom, formKey);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[ 'menus', 'prompts' ].forEach(function areaEntry(sectionName) {
|
||||
_.keys(mergedTheme[sectionName]).forEach(function menuEntry(menuName) {
|
||||
let createdFormSection = false;
|
||||
const mergedThemeMenu = mergedTheme[sectionName][menuName];
|
||||
|
||||
if(_.has(theme, [ 'customization', sectionName, menuName ])) {
|
||||
const menuTheme = theme.customization[sectionName][menuName];
|
||||
|
||||
// config block is direct assign/overwrite
|
||||
// :TODO: should probably be _.merge()
|
||||
if(menuTheme.config) {
|
||||
mergedThemeMenu.config = _.assign(mergedThemeMenu.config || {}, menuTheme.config);
|
||||
}
|
||||
|
||||
if('menus' === sectionName) {
|
||||
if(_.isObject(mergedThemeMenu.form)) {
|
||||
getFormKeys(mergedThemeMenu.form).forEach(function formKeyEntry(formKey) {
|
||||
applyToForm(mergedThemeMenu.form[formKey], menuTheme, formKey);
|
||||
});
|
||||
} else {
|
||||
if(_.isObject(menuTheme.mci)) {
|
||||
//
|
||||
// Not specified at menu level means we apply anything from the
|
||||
// theme to form.0.mci{}
|
||||
//
|
||||
mergedThemeMenu.form = { 0 : { mci : { } } };
|
||||
mergeMciProperties(mergedThemeMenu.form[0], menuTheme);
|
||||
createdFormSection = true;
|
||||
}
|
||||
}
|
||||
} else if('prompts' === sectionName) {
|
||||
// no 'form' or form keys for prompts -- direct to mci
|
||||
applyToForm(mergedThemeMenu, menuTheme);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Finished merging for this menu/prompt
|
||||
//
|
||||
// If the following conditions are true, set runtime.autoNext to true:
|
||||
// * This is a menu
|
||||
// * There is/was no explicit 'form' section
|
||||
// * There is no 'prompt' specified
|
||||
//
|
||||
if('menus' === sectionName && !_.isString(mergedThemeMenu.prompt) &&
|
||||
(createdFormSection || !_.isObject(mergedThemeMenu.form)))
|
||||
{
|
||||
mergedThemeMenu.runtime = _.merge(mergedThemeMenu.runtime || {}, { autoNext : true } );
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
return mergedTheme;
|
||||
}
|
||||
|
||||
function reloadTheme(themeId) {
|
||||
const config = Config();
|
||||
async.waterfall(
|
||||
[
|
||||
function loadMenuConfig(callback) {
|
||||
getFullConfig(config.general.menuFile, (err, menuConfig) => {
|
||||
return callback(err, menuConfig);
|
||||
});
|
||||
},
|
||||
function loadPromptConfig(menuConfig, callback) {
|
||||
getFullConfig(config.general.promptFile, (err, promptConfig) => {
|
||||
return callback(err, menuConfig, promptConfig);
|
||||
});
|
||||
},
|
||||
function loadIt(menuConfig, promptConfig, callback) {
|
||||
loadTheme(themeId, (err, theme) => {
|
||||
if(err) {
|
||||
if(ErrorReasons.NotEnabled !== err.reasonCode) {
|
||||
Log.warn( { themeId : themeId, err : err.message }, 'Failed loading theme');
|
||||
return;
|
||||
}
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
Object.assign(theme.info, { themeId } );
|
||||
availableThemes.set(themeId, getMergedTheme(menuConfig, promptConfig, theme));
|
||||
|
||||
Events.emit(
|
||||
Events.getSystemEvents().ThemeChanged,
|
||||
{ themeId }
|
||||
);
|
||||
|
||||
return callback(null, theme);
|
||||
});
|
||||
}
|
||||
],
|
||||
(err, theme) => {
|
||||
if(err) {
|
||||
Log.warn( { themeId, error : err.message }, 'Failed to reload theme');
|
||||
} else {
|
||||
Log.debug( { info : theme.info }, 'Theme recached' );
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function reloadAllThemes()
|
||||
{
|
||||
async.each([ ...availableThemes.keys() ], themeId => reloadTheme(themeId));
|
||||
}
|
||||
|
||||
function initAvailableThemes(cb) {
|
||||
const config = Config();
|
||||
async.waterfall(
|
||||
[
|
||||
function loadMenuConfig(callback) {
|
||||
getFullConfig(config.general.menuFile, (err, menuConfig) => {
|
||||
return callback(err, menuConfig);
|
||||
});
|
||||
},
|
||||
function loadPromptConfig(menuConfig, callback) {
|
||||
getFullConfig(config.general.promptFile, (err, promptConfig) => {
|
||||
return callback(err, menuConfig, promptConfig);
|
||||
});
|
||||
},
|
||||
function getThemeDirectories(menuConfig, promptConfig, callback) {
|
||||
fs.readdir(config.paths.themes, (err, files) => {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
return callback(
|
||||
null,
|
||||
menuConfig,
|
||||
promptConfig,
|
||||
files.filter( f => {
|
||||
// sync normally not allowed -- initAvailableThemes() is a startup-only method, however
|
||||
return fs.statSync(paths.join(config.paths.themes, f)).isDirectory();
|
||||
})
|
||||
);
|
||||
});
|
||||
},
|
||||
function populateAvailable(menuConfig, promptConfig, themeDirectories, callback) {
|
||||
async.each(themeDirectories, (themeId, nextThemeDir) => { // theme dir = theme ID
|
||||
loadTheme(themeId, (err, theme) => {
|
||||
if(err) {
|
||||
if(ErrorReasons.NotEnabled !== err.reasonCode) {
|
||||
Log.warn( { themeId : themeId, err : err.message }, 'Failed loading theme');
|
||||
}
|
||||
|
||||
return nextThemeDir(null); // try next
|
||||
}
|
||||
|
||||
Object.assign(theme.info, { themeId } );
|
||||
availableThemes.set(themeId, getMergedTheme(menuConfig, promptConfig, theme));
|
||||
return nextThemeDir(null);
|
||||
});
|
||||
}, err => {
|
||||
return callback(err);
|
||||
});
|
||||
},
|
||||
function initEvents(callback) {
|
||||
Events.on(Events.getSystemEvents().MenusChanged, () => {
|
||||
return reloadAllThemes();
|
||||
});
|
||||
Events.on(Events.getSystemEvents().PromptsChanged, () => {
|
||||
return reloadAllThemes();
|
||||
});
|
||||
|
||||
return callback(null);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
return cb(err, availableThemes.size);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function getAvailableThemes() {
|
||||
return availableThemes;
|
||||
return themeManagerInstance.getAvailableThemes();
|
||||
}
|
||||
|
||||
function getRandomTheme() {
|
||||
if(availableThemes.size > 0) {
|
||||
const themeIds = [ ...availableThemes.keys() ];
|
||||
const avail = getAvailableThemes();
|
||||
if(avail.size > 0) {
|
||||
const themeIds = [ ...avail.keys() ];
|
||||
return themeIds[Math.floor(Math.random() * themeIds.length)];
|
||||
}
|
||||
}
|
||||
|
@ -433,10 +412,10 @@ function getThemeArt(options, cb) {
|
|||
const config = Config();
|
||||
if(!options.themeId && _.has(options, [ 'client', 'user', 'properties', UserProps.ThemeId ])) {
|
||||
options.themeId = options.client.user.properties[UserProps.ThemeId];
|
||||
} else {
|
||||
options.themeId = config.theme.default;
|
||||
}
|
||||
|
||||
options.themeId = options.themeId || config.theme.default;
|
||||
|
||||
// :TODO: replace asAnsi stuff with something like retrieveAs = 'ansi' | 'pipe' | ...
|
||||
// :TODO: Some of these options should only be set if not provided!
|
||||
options.asAnsi = true; // always convert to ANSI
|
||||
|
|
|
@ -171,7 +171,7 @@ exports.getModule = class UserConfigModule extends MenuModule {
|
|||
},
|
||||
function prepareAvailableThemes(callback) {
|
||||
self.availThemeInfo = _.sortBy([...theme.getAvailableThemes()].map(entry => {
|
||||
const theme = entry[1];
|
||||
const theme = entry[1].get();
|
||||
return {
|
||||
themeId : theme.info.themeId,
|
||||
name : theme.info.name,
|
||||
|
|
|
@ -13,10 +13,10 @@
|
|||
- Configuration
|
||||
- [Creating Config Files]({{ site.baseurl }}{% link configuration/creating-config.md %})
|
||||
- [SysOp Setup]({{ site.baseurl }}{% link configuration/sysop-setup.md %})
|
||||
- [Configuration Files]({{ site.baseurl }}{% link configuration/config-files.md %})
|
||||
- [System Configuration]({{ site.baseurl }}{% link configuration/config-hjson.md %})
|
||||
- [HJSON Config Files]({{ site.baseurl }}{% link configuration/hjson.md %})
|
||||
- [Menus]({{ site.baseurl }}{% link configuration/menu-hjson.md %})
|
||||
- [Prompts]({{ site.baseurl }}{% link configuration/prompt-hjson.md %})
|
||||
- [Directory Structure]({{ site.baseurl }}{% link configuration/directory-structure.md %})
|
||||
- [Archivers]({{ site.baseurl }}{% link configuration/archivers.md %})
|
||||
- [File Transfer Protocols]({{ site.baseurl }}{% link configuration/file-transfer-protocols.md %})
|
||||
|
|
|
@ -38,7 +38,7 @@ The `customization` block in is itself broken up into major parts:
|
|||
|-------------|---------------------------------------------------|
|
||||
| `defaults` | Default values to use when this theme is active. These values override system defaults, but can still be overridden themselves in specific areas of your theme. |
|
||||
| `menus` | The bulk of what you theme in the system will be here. Any menu (that is, anything you find in `menu.hjson`) can be tweaked. |
|
||||
| `prompts` | Similar to `menus`, this file themes prompts found in `prompts.hjson`. |
|
||||
| `prompts` | Similar to `menus`, this section themes `prompts`. |
|
||||
|
||||
#### Defaults
|
||||
| Item | Description |
|
||||
|
|
|
@ -74,7 +74,7 @@ For `list` commands, the `entryMatch` key must be provided. This key should prov
|
|||
```
|
||||
|
||||
## Archive Formats
|
||||
Archive formats can be defined such that ENiGMA½ can detect them by signature or extension, then utilize the correct *archiver* to process them. Formats are defined in the `archives:formats` key in `config.hjson`. Many differnet types come pre-configured (see `core/config.js`).
|
||||
Archive formats can be defined such that ENiGMA½ can detect them by signature or extension, then utilize the correct *archiver* to process them. Formats are defined in the `archives:formats` key in `config.hjson`. Many differnet types come pre-configured (see `core/config_default.js`).
|
||||
|
||||
### Example Archive Format Configuration
|
||||
```
|
||||
|
|
|
@ -2,9 +2,7 @@
|
|||
layout: page
|
||||
title: Colour Codes
|
||||
---
|
||||
ENiGMA½ supports Renegade-style pipe colour codes for formatting strings. You'll see them used in [`config.hjson`](config-hjson.md),
|
||||
[`prompt.hjson`](prompt-hjson.md), [`menu.hjson`](menu-hjson.md), and can also be used in places like the oneliner, rumour mod,
|
||||
full screen editor etc.
|
||||
ENiGMA½ supports Renegade-style pipe colour codes for formatting strings. You'll see them used throughout your configuration, and can also be used in places like onelinerz, rumourz, full screen editor etc.
|
||||
|
||||
## Usage
|
||||
When ENiGMA½ encounters colour codes in strings, they'll be processed in order and combined where possible.
|
||||
|
@ -21,4 +19,5 @@ For example:
|
|||
:warning: Colour codes |24 to |31 are considered "blinking" or "iCE" colour codes. On terminals that support them they'll
|
||||
be shown as the correct colours - for terminals that don't, or are that are set to "blinking" mode - they'll blink!
|
||||
|
||||
![Regegade style colour codes](../assets/images/colour-codes.png "Colour Codes")
|
||||
![Renegade style colour codes](../assets/images/colour-codes.png "Colour Codes")
|
||||
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
---
|
||||
layout: page
|
||||
title: Configuration Files
|
||||
---
|
||||
## General Information
|
||||
ENiGMA½ configuration files such as the [system config](config-hjson.md), [menus](menu-hjson.md) and [themes](../art/themes.md) are formatted in the [HJSON format](hjson.md).
|
||||
|
||||
## Hot-Reload
|
||||
Nearly all of ENiGMA½'s configuration can be hot-reloaded. That is, a live system can have it's configuration modified and it will be loaded in place.
|
||||
|
||||
## Common Directives
|
||||
### Includes
|
||||
Most configuration files offer an `includes` directive that allows users to break up large configuration files into smaller and organized parts. For example, consider a system with many menus/screens. Instead of a single `menu.hjson`, the SysOp may break this into `message-base.hjson`, `file-base.hjson`, etc.
|
||||
|
||||
The `includes` directive may be used the top-level scope of a configuration file:
|
||||
```hjson
|
||||
// menu.hjson
|
||||
{
|
||||
includes: [
|
||||
message-base.hjson
|
||||
file-base.hjson
|
||||
]
|
||||
|
||||
menus: {
|
||||
someOtherMenu: {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
```hjson
|
||||
// message-base.hjson
|
||||
{
|
||||
menus: {
|
||||
someMessageMenu: {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### References
|
||||
Often times in a configuration you will find that you're repeating yourself quite a bit. ENiGMA½ provides an `@reference` that can help with this in the form of `@reference:dot.path.to.section`.
|
||||
|
||||
Consider `actionKeys` in a menu. Often times you may show a screen and the user presses `Q` or `ESC` to fall back to the previous. Instead of repeating this in many menus, a generic block can be referenced:
|
||||
|
||||
```hjson
|
||||
{
|
||||
// note that 'recycle' here is arbitrary;
|
||||
// only 'menus' and 'prompts' is reserved at this level.
|
||||
recycle: {
|
||||
prevMenu: [
|
||||
{
|
||||
keys: [ "escape" ]
|
||||
action: @systemMethod:prevMenu
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
menus: {
|
||||
someMenu: {
|
||||
form: {
|
||||
0: {
|
||||
actionKeys: @reference:recycle.prevMenu
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:information_source: An unresolved `@reference` will be left intact.
|
||||
|
||||
### Environment Variables
|
||||
Especially in a container environment such as [Docker](/docs/installation/docker.md), environment variable access in configuration files can become very handy. ENiGMA½ provides a flexible way to access variables using the `@environment` directive. The most basic form of `@environment:VAR_NAME` produces a string value. Additionally a `:type` suffix can be supplied to coerece the value to a particular type. Variables pointing to a comma separated list can be turned to arrays using an additional `:array` suffix.
|
||||
|
||||
Below is a table of the various forms:
|
||||
| Form | Variable Value | Produces |
|
||||
|------|----------------|----------|
|
||||
| `@environment:SOME_VAR` | "Foo" | `"Foo"` (without quotes) |
|
||||
| `@environment:SOME_VAR` | "123" | `"123"` (without quotes) |
|
||||
| `@environment:SOME_VAR:string` | "Bar" | `"Bar"` (without quotes) |
|
||||
| `@environment:SOME_VAR:string:array` | "Foo,Bar" | `[ 'Foo', 'Bar' ]` |
|
||||
| `@environment:SOME_VAR:boolean` | "1" | `true` |
|
||||
| `@environment:SOME_VAR:boolean` | "True" | `true` |
|
||||
| `@environment:SOME_VAR:boolean` | "false" | `false` |
|
||||
| `@environment:SOME_VAR:boolean` | "cat" | `false` |
|
||||
| `@environment:SOME_VAR:boolean:array` | "True,false,TRUE" | `[ true, false, true ]` |
|
||||
| `@environment:SOME_VAR:number` | "123" | `123` |
|
||||
| `@environment:SOME_VAR:number:array` | "123,456" | `[ 123, 456 ]` |
|
||||
| `@environment:SOME_VAR:number` | "kitten" | (invalid) |
|
||||
| `@environment:SOME_VAR:object` | '{"a":"b"}' | `{ 'a' : 'b' }` |
|
||||
| `@environment:SOME_VAR:object:array` | '{"a":"b"},{"c":"d"}' | `[ { 'a' : 'b' }, { 'c' : 'd' } ]` |
|
||||
| `@environment:SOME_VAR:timestamp` | "2020-01-05" | A [moment](https://momentjs.com/) object representing 2020-01-05 |
|
||||
| `@environment:SOME_VAR:timestamp:array` | "2020-01-05,2016-05-16T01:15:37'" | An array of [moment](https://momentjs.com/) objects representing 2020-01-05 and 2016-05-16T01:15:37 |
|
||||
|
||||
:information_source: `bool` may be used as an alias to `boolean`.
|
||||
|
||||
:information_source: `timestamp` values can be in any form that [moment can parse](https://momentjs.com/docs/#/parsing/).
|
||||
|
||||
:information_source: An unresolved or invalid `@environment` will be left intact.
|
||||
|
||||
Consider the following fragment:
|
||||
```hjson
|
||||
{
|
||||
foo: {
|
||||
bar: @environment:BAR_VAR:number
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If the environment has `BAR_VAR=1337`, this would produce:
|
||||
```hjson
|
||||
{
|
||||
foo: {
|
||||
bar: 1337
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## See Also
|
||||
* [System Configuration](config-hjson.md)
|
||||
* [Menu Configuration](menu-hjson.md)
|
||||
* [The HJSON Format](hjson.md)
|
|
@ -3,7 +3,7 @@ layout: page
|
|||
title: System Configuration
|
||||
---
|
||||
## System Configuration
|
||||
The main system configuration file, `config.hjson` both overrides defaults and provides additional configuration such as message areas. The default path is `/enigma-bbs-install-path/config/config.hjson` though you can override the `config.hjson` location with the `--config` parameter when invoking `main.js`. Values found in `core/config.js` may be overridden by simply providing the object members you wish replace.
|
||||
The main system configuration file, `config.hjson` both overrides defaults and provides additional configuration such as message areas. The default path is `/enigma-bbs-install-path/config/config.hjson` though you can override the `config.hjson` location with the `--config` parameter when invoking `main.js`. Values found in `core/config_default.js` may be overridden by simply providing the object members you wish replace.
|
||||
|
||||
See also [HJSON General Information](hjson.md) for more information on the HJSON format.
|
||||
|
||||
|
@ -16,7 +16,7 @@ Your initial configuration skeleton should be created using the `oputil.js` comm
|
|||
You will be asked a series of questions to create an initial configuration.
|
||||
|
||||
### Overriding Defaults
|
||||
The file `core/config.js` provides various defaults to the system that you can override via `config.hjson`. For example, the default system name is defined as follows:
|
||||
The file `core/config_default.js` provides various defaults to the system that you can override via `config.hjson`. For example, the default system name is defined as follows:
|
||||
```javascript
|
||||
general : {
|
||||
boardName : 'Another Fine ENiGMA½ System'
|
||||
|
@ -32,7 +32,7 @@ general: {
|
|||
|
||||
(Note the very slightly [HJSON](hjson.md) different syntax. **You can use standard JSON if you wish!**)
|
||||
|
||||
While not everything that is available in your `config.hjson` file can be found defaulted in `core/config.js`, a lot is. [Poke around and see what you can find](https://github.com/NuSkooler/enigma-bbs/blob/master/core/config.js)!
|
||||
While not everything that is available in your `config.hjson` file can be found defaulted in `core/config_default.js`, a lot is. [Poke around and see what you can find](https://github.com/NuSkooler/enigma-bbs/blob/master/core/config_default.js)!
|
||||
|
||||
### Configuration Sections
|
||||
Below is a list of various configuration sections. There are many more, but this should get you started:
|
||||
|
|
|
@ -10,5 +10,5 @@ Your initial configuration skeleton can be created using the `oputil.js` command
|
|||
./oputil.js config new
|
||||
```
|
||||
|
||||
You will be asked a series of questions to create an initial configuration, which will be saved to `/enigma-bbs-install-path/config/config.hjson`. This will also produce `config/<bbsName>-menu.hjson` and `config/<bbsName>-prompt.hjson` files (where `<bbsName>` is replaced by the name you provided in the steps above). See [Menu HJSON](menu-hjson.md) and [Prompt HJSON](prompt-hjson.md) for more information.
|
||||
You will be asked a series of questions to create an initial configuration, which will be saved to `/enigma-bbs-install-path/config/config.hjson`. This will also produce menu files under `config/menus/`. See [Menu HJSON](menu-hjson.md) for more information.
|
||||
|
||||
|
|
|
@ -8,8 +8,10 @@ All paths mentioned here are relative to the ENiGMA½ checkout directory.
|
|||
|---------------------|-----------------------------------------------------------------------------------------------------------|
|
||||
| `/art/general` | Non-theme art - welcome ANSI, logoff ANSI, etc. See [General Art]({{ site.baseurl }}{% link art/general.md %}).
|
||||
| `/art/themes` | Theme art. Themes should be in their own subdirectory and contain a theme.hjson. See [Themes]({{ site.baseurl }}{% link art/themes.md %}).
|
||||
| `/config` | config.hjson, [menu.hjson]({{ site.baseurl }}{% link configuration/menu-hjson.md %}) and prompt.hjson storage. Also default path for SSL certs and public/private keys
|
||||
| `/db` | All ENiGMA½ databases in Sqlite3 format
|
||||
| `/config` | [config.hjson](config-hjson.md) system configuration.
|
||||
| `/config/menus` | [menu.hjson](menu-hjson.md)storage.
|
||||
| `/config/security` | D path for SSL certs and public/private keys.
|
||||
| `/db` | All ENiGMA½ databases in Sqlite3 format.
|
||||
| `/docs` | These docs ;-)
|
||||
| `/dropfiles` | Dropfiles created for [local doors]({{ site.baseurl }}{% link modding/local-doors.md %})
|
||||
| `/logs` | Logs. See [Monitoring Logs]({{ site.baseurl }}{% link troubleshooting/monitoring-logs.md %})
|
||||
|
|
|
@ -33,7 +33,7 @@ For protocols of type `external` the following members may be defined:
|
|||
* `escapeTelnet`: Optional; If set to `true`, escape all internal Telnet related codes such as IAC's. This option is required for external protocol handlers such as `sz` and `rz` that do not escape themselves.
|
||||
|
||||
### Adding Your Own
|
||||
Take a look a the example below as well as [core/config.js](/core/config.js).
|
||||
Take a look a the example below as well as [core/config_default.js](/core/config_default.js).
|
||||
|
||||
#### Example File Transfer Protocol Configuration
|
||||
```
|
||||
|
|
|
@ -3,7 +3,7 @@ layout: page
|
|||
title: HJSON Config Files
|
||||
---
|
||||
## JSON for Humans!
|
||||
HJSON is the configuration file format used by ENiGMA½ for [System Configuration](config-hjson.md), [Menus](menu-hjson.md), [Prompts](prompt-hjson.md), etc. [HJSON](https://hjson.org/) is is [JSON](https://json.org/) for humans!
|
||||
HJSON is the configuration file format used by ENiGMA½ for [System Configuration](config-hjson.md), [Menus](menu-hjson.md), etc. [HJSON](https://hjson.org/) is is [JSON](https://json.org/) for humans!
|
||||
|
||||
For those completely unfamiliar, JSON stands for JavaScript Object Notation. But don't let that scare you! JSON is simply a text file format with a bit of structure ― kind of like a fancier INI file. HJSON on the other hand as mentioned previously, is JSON for humans. That is, it has the following features and more:
|
||||
|
||||
|
@ -18,7 +18,6 @@ Through the documentation, some terms regarding HJSON and configuration files wi
|
|||
|
||||
* `config.hjson`: Refers to `/path/to/enigma-bbs/config/config.hjson`. See [System Configuration](config-hjson.md).
|
||||
* `menu.hjson`: Refers to `/path/to/enigma-bbs/config/<yourBBSName>-menu.hjson`. See [Menus](menu-hjson.md).
|
||||
* `prompt.hjson`: Refers to `/path/to/enigma-bbs/config/<yourBBSName>-prompt.hjson`. See [Prompts](prompt-hjson.md).
|
||||
* Configuration *key*: Elements in HJSON are name-value pairs where the name is the *key*. For example, provided `foo: bar`, `foo` is the key.
|
||||
* Configuration *section* or *block* (also commonly called an "Object" in code): This is referring to a section in a HJSON file that starts with a *key*. For example:
|
||||
```hjson
|
||||
|
|
|
@ -3,7 +3,7 @@ layout: page
|
|||
title: Menu HSJON
|
||||
---
|
||||
## Menu HJSON
|
||||
The core of a ENiGMA½ based BBS is `menu.hjson`. Note that when `menu.hjson` is referenced, we're actually talking about `config/yourboardname-menu.hjson` or similar. This file determines the menus (or screens) a user can see, the order they come in and how they interact with each other, ACS configuration, etc. Like all configuration within ENiGMA½, menu configuration is done in [HJSON](https://hjson.org/) format. See [HJSON General Information](hjson.md) for more information.
|
||||
The core of a ENiGMA½ based BBS is `menu.hjson`. Note that when `menu.hjson` is referenced, we're actually talking about `config/menus/yourboardname-*.hjson`. These files determines the menus (or screens) a user can see, the order they come in and how they interact with each other, ACS configuration, etc. Like all configuration within ENiGMA½, menu configuration is done in [HJSON](https://hjson.org/) format. See [HJSON General Information](hjson.md) for more information.
|
||||
|
||||
Entries in `menu.hjson` are often referred to as *blocks* or *sections*. Each entry defines a menu. A menu in this sense is something the user can see or visit. Examples include but are not limited to:
|
||||
|
||||
|
@ -13,6 +13,8 @@ Entries in `menu.hjson` are often referred to as *blocks* or *sections*. Each en
|
|||
|
||||
Menu entries live under the `menus` section of `menu.hjson`. The *key* for a menu is it's name that can be referenced by other menus and areas of the system.
|
||||
|
||||
:information_source: Remember that the top level menu may include additional files using the `includes` directive. See [Configuration Files](config-files.md) for more information on this.
|
||||
|
||||
## Common Menu Entry Members
|
||||
Below is a table of **common** menu entry members. These members apply to most entries, though entries that are backed by a specialized module (ie: `module: bbs_list`) may differ. See documentation for the module in question for particulars.
|
||||
|
||||
|
@ -21,7 +23,7 @@ Below is a table of **common** menu entry members. These members apply to most e
|
|||
| `desc` | A friendly description that can be found in places such as "Who's Online" or wherever the `%MD` MCI code is used. |
|
||||
| `art` | An art file *spec*. See [General Art Information](/docs/art/general.md). |
|
||||
| `next` | Specifies the next menu entry to go to next. Can be explicit or an array of possibilities dependent on ACS. See **Flow Control** in the **ACS Checks** section below. If `next` is not supplied, the next menu is this menus parent. |
|
||||
| `prompt` | Specifies a prompt, by name, to use along with this menu. Prompts are configured in `prompt.hjson`. |
|
||||
| `prompt` | Specifies a prompt, by name, to use along with this menu. Prompts are configured in the `prompts` section. See **Prompts** for more information. |
|
||||
| `submit` | Defines a submit handler when using `prompt`.
|
||||
| `form` | An object defining one or more *forms* available on this menu. |
|
||||
| `module` | Sets the module name to use for this menu. See **Menu Modules** below. |
|
||||
|
@ -183,6 +185,11 @@ In the above entry, you'll notice `form`. This defines a form(s) object. In this
|
|||
* The `submit` object tells the system to attempt to apply provided match entries from any view ID (`*`).
|
||||
* Upon submit, the first match will be executed. For example, if the user selects "login", the first entry with a value of `{ matrixSubmit: 0 }` will match (due to 0 being the first index in the list and `matrixSubmit` being the arg name in question) causing `action` of `@menu:login` to be executed (go to `login` menu).
|
||||
|
||||
## Prompts
|
||||
Prompts are found in the `prompts` section of menu files. Prompts allow for quick user input and shorthand form requirements for menus. Additionally, prompts are often used for for multiple menus. Consider a pause prompt or menu command input for example.
|
||||
|
||||
TODO: additional prompt docs
|
||||
|
||||
## ACS Checks
|
||||
Menu modules can check user ACS in order to restrict areas and perform flow control. See [ACS](acs.md) for available ACS syntax.
|
||||
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
layout: page
|
||||
title: prompt.hjson
|
||||
---
|
||||
:zap: This page is to describe general information the `prompt.hjson` file. It
|
||||
needs fleshing out, please submit a PR if you'd like to help!
|
||||
|
||||
See [HJSON General Information](hjson.md) for more information.
|
|
@ -9,5 +9,5 @@ The built in `file_transfer_protocol_select` module provides a way to select a l
|
|||
|
||||
### Theming
|
||||
The following `itemFormat` object is provided to MCI 1 (ie: `%VM1`) (the protocol list):
|
||||
* `name`: The name of the protocol. Each entry is +op defined in `config.hjson` with defaults found in `config.js`. Note that the standard `{text}` field also contains this value.
|
||||
* `name`: The name of the protocol. Each entry is +op defined in `config.hjson` with defaults found in `config_default.js`. Note that the standard `{text}` field also contains this value.
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ Entries available under `config.loginServers.ssh`:
|
|||
| `enabled` | :+1: | Set to `true` to enable the SSH server. |
|
||||
| `port` | :-1: | Override the default port of `8443`. |
|
||||
| `address` | :-1: | Sets an explicit bind address. |
|
||||
| `algorithms` | :-1: | Configuration block for SSH algorithms. Includes keys of `kex`, `cipher`, `hmac`, and `compress`. See the algorithms section in the [ssh2-streams](https://github.com/mscdex/ssh2-streams#ssh2stream-methods) documentation for details. For defaults set by ENiGMA½, see `core/config.js`.
|
||||
| `algorithms` | :-1: | Configuration block for SSH algorithms. Includes keys of `kex`, `cipher`, `hmac`, and `compress`. See the algorithms section in the [ssh2-streams](https://github.com/mscdex/ssh2-streams#ssh2stream-methods) documentation for details. For defaults set by ENiGMA½, see `core/config_default.js`.
|
||||
| `traceConnections` | :-1: | Set to `true` to enable full trace-level information on SSH connections.
|
||||
|
||||
### Example Configuration
|
||||
|
|
|
@ -369,7 +369,7 @@
|
|||
storageTags: {
|
||||
//
|
||||
// Example storage tag: "super_l33t_warez":
|
||||
// super_l33t_warez: "/path/to/super/l33t/warez"
|
||||
// super_l33t_warez: /path/to/super/l33t/warez
|
||||
//
|
||||
}
|
||||
|
||||
|
@ -377,10 +377,10 @@
|
|||
//
|
||||
// Example area with the areaTag of "an_example_area":
|
||||
// an_example_area: {
|
||||
// name: "Example File Area"
|
||||
// desc: "It's just an example, yo!"
|
||||
// name: Example File Area
|
||||
// desc: It's just an example, yo!
|
||||
// storageTags: [
|
||||
// "super_l33t_warez"
|
||||
// super_l33t_warez
|
||||
// ]
|
||||
// }
|
||||
//
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,132 @@
|
|||
{
|
||||
menus: {
|
||||
doorsMainMenu: {
|
||||
desc: Doors Menu
|
||||
art: DOORMNU
|
||||
prompt: menuCommand
|
||||
config: {
|
||||
interrupt: realtime
|
||||
}
|
||||
submit: [
|
||||
{
|
||||
value: { command: "G" }
|
||||
action: @menu:fullLogoffSequence
|
||||
}
|
||||
{
|
||||
value: { command: "Q" }
|
||||
action: @systemMethod:prevMenu
|
||||
}
|
||||
//
|
||||
// The system supports many ways of launching doors including
|
||||
// modules for DoorParty!, BBSLink, etc.
|
||||
//
|
||||
// Below are some examples. See the documentation for more info.
|
||||
//
|
||||
{
|
||||
value: { command: "ABRACADABRA" }
|
||||
action: @menu:doorAbracadabraExample
|
||||
}
|
||||
{
|
||||
value: { command: "TWBBSLINK" }
|
||||
action: @menu:doorTradeWars2002BBSLinkExample
|
||||
}
|
||||
{
|
||||
value: { command: "DP" }
|
||||
action: @menu:doorPartyExample
|
||||
}
|
||||
{
|
||||
value: { command: "CN" }
|
||||
action: @menu:doorCombatNetExample
|
||||
}
|
||||
{
|
||||
value: { command: "EXODUS" }
|
||||
action: @menu:doorExodusCataclysm
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
//
|
||||
// Local Door Example via abracadabra module
|
||||
//
|
||||
// This example assumes launch_door.sh (which is passed args)
|
||||
// launches the door.
|
||||
//
|
||||
doorAbracadabraExample: {
|
||||
desc: Abracadabra Example
|
||||
module: abracadabra
|
||||
config: {
|
||||
name: Example Door
|
||||
dropFileType: DORINFO
|
||||
cmd: /home/enigma/DOS/scripts/launch_door.sh
|
||||
args: [
|
||||
"{node}",
|
||||
"{dropFile}",
|
||||
"{srvPort}",
|
||||
],
|
||||
nodeMax: 1
|
||||
tooManyArt: DOORMANY
|
||||
io: socket
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// BBSLink Example (TradeWars 2000)
|
||||
//
|
||||
// Register @ https://bbslink.net/
|
||||
//
|
||||
doorTradeWars2002BBSLinkExample: {
|
||||
desc: Playing TW 2002 (BBSLink)
|
||||
module: bbs_link
|
||||
config: {
|
||||
sysCode: XXXXXXXX
|
||||
authCode: XXXXXXXX
|
||||
schemeCode: XXXXXXXX
|
||||
door: tw
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// DoorParty! Example
|
||||
//
|
||||
// Register @ http://throwbackbbs.com/
|
||||
//
|
||||
doorPartyExample: {
|
||||
desc: Using DoorParty!
|
||||
module: door_party
|
||||
config: {
|
||||
username: XXXXXXXX
|
||||
password: XXXXXXXX
|
||||
bbsTag: XX
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// CombatNet Example
|
||||
//
|
||||
// Register @ http://combatnet.us/
|
||||
//
|
||||
doorCombatNetExample: {
|
||||
desc: Using CombatNet
|
||||
module: combatnet
|
||||
config: {
|
||||
bbsTag: CBNxxx
|
||||
password: XXXXXXXXX
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Exodus Example (cataclysm)
|
||||
// Register @ https://oddnetwork.org/exodus/
|
||||
//
|
||||
doorExodusCataclysm: {
|
||||
desc: Cataclysm
|
||||
module: exodus
|
||||
config: {
|
||||
rejectUnauthorized: false
|
||||
board: XXX
|
||||
key: XXXXXXXX
|
||||
door: cataclysm
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,918 @@
|
|||
{
|
||||
menus: {
|
||||
fileBaseMainMenu: {
|
||||
desc: File Base
|
||||
art: FMENU
|
||||
prompt: fileMenuCommand
|
||||
config: {
|
||||
interrupt: realtime
|
||||
}
|
||||
submit: [
|
||||
{
|
||||
value: { menuOption: "L" }
|
||||
action: @menu:fileBaseListEntries
|
||||
}
|
||||
{
|
||||
value: { menuOption: "B" }
|
||||
action: @menu:fileBaseBrowseByAreaSelect
|
||||
}
|
||||
{
|
||||
value: { menuOption: "F" }
|
||||
action: @menu:fileBaseFilterEditor
|
||||
}
|
||||
{
|
||||
value: { menuOption: "Q" }
|
||||
action: @systemMethod:prevMenu
|
||||
}
|
||||
{
|
||||
value: { menuOption: "G" }
|
||||
action: @menu:fullLogoffSequence
|
||||
}
|
||||
{
|
||||
value: { menuOption: "D" }
|
||||
action: @menu:fileBaseDownloadManager
|
||||
}
|
||||
{
|
||||
value: { menuOption: "W" }
|
||||
action: @menu:fileBaseWebDownloadManager
|
||||
}
|
||||
{
|
||||
value: { menuOption: "U" }
|
||||
action: @menu:fileBaseUploadFiles
|
||||
}
|
||||
{
|
||||
value: { menuOption: "S" }
|
||||
action: @menu:fileBaseSearch
|
||||
}
|
||||
{
|
||||
value: { menuOption: "P" }
|
||||
action: @menu:fileBaseSetNewScanDate
|
||||
}
|
||||
{
|
||||
value: { menuOption: "E" }
|
||||
action: @menu:fileBaseExportListFilter
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
fileBaseListEntries: {
|
||||
module: file_area_list
|
||||
desc: Browsing Files
|
||||
config: {
|
||||
art: {
|
||||
browse: FBRWSE
|
||||
details: FDETAIL
|
||||
detailsGeneral: FDETGEN
|
||||
detailsNfo: FDETNFO
|
||||
detailsFileList: FDETLST
|
||||
help: FBHELP
|
||||
}
|
||||
}
|
||||
form: {
|
||||
0: {
|
||||
mci: {
|
||||
MT1: {
|
||||
mode: preview
|
||||
}
|
||||
|
||||
HM2: {
|
||||
focus: true
|
||||
submit: true
|
||||
argName: navSelect
|
||||
items: [
|
||||
"prev", "next", "details", "toggle queue", "rate", "change filter", "help", "quit"
|
||||
]
|
||||
focusItemIndex: 1
|
||||
}
|
||||
}
|
||||
|
||||
submit: {
|
||||
*: [
|
||||
{
|
||||
value: { navSelect: 0 }
|
||||
action: @method:prevFile
|
||||
}
|
||||
{
|
||||
value: { navSelect: 1 }
|
||||
action: @method:nextFile
|
||||
}
|
||||
{
|
||||
value: { navSelect: 2 }
|
||||
action: @method:viewDetails
|
||||
}
|
||||
{
|
||||
value: { navSelect: 3 }
|
||||
action: @method:toggleQueue
|
||||
}
|
||||
{
|
||||
value: { navSelect: 4 }
|
||||
action: @menu:fileBaseGetRatingForSelectedEntry
|
||||
}
|
||||
{
|
||||
value: { navSelect: 5 }
|
||||
action: @menu:fileBaseFilterEditor
|
||||
}
|
||||
{
|
||||
value: { navSelect: 6 }
|
||||
action: @method:displayHelp
|
||||
}
|
||||
{
|
||||
value: { navSelect: 7 }
|
||||
action: @systemMethod:prevMenu
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
actionKeys: [
|
||||
{
|
||||
keys: [ "w", "shift + w" ]
|
||||
action: @method:showWebDownloadLink
|
||||
}
|
||||
{
|
||||
keys: [ "escape", "q", "shift + q" ]
|
||||
action: @systemMethod:prevMenu
|
||||
}
|
||||
{
|
||||
keys: [ "t", "shift + t" ]
|
||||
action: @method:toggleQueue
|
||||
}
|
||||
{
|
||||
keys: [ "f", "shift + f" ]
|
||||
action: @menu:fileBaseFilterEditor
|
||||
}
|
||||
{
|
||||
keys: [ "v", "shift + v" ]
|
||||
action: @method:viewDetails
|
||||
}
|
||||
{
|
||||
keys: [ "r", "shift + r" ]
|
||||
action: @menu:fileBaseGetRatingForSelectedEntry
|
||||
}
|
||||
{
|
||||
keys: [ "?" ]
|
||||
action: @method:displayHelp
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
1: {
|
||||
mci: {
|
||||
HM1: {
|
||||
focus: true
|
||||
submit: true
|
||||
argName: navSelect
|
||||
items: [
|
||||
"general", "nfo/readme", "file listing"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
actionKeys: [
|
||||
{
|
||||
keys: [ "escape", "q", "shift + q" ]
|
||||
action: @method:detailsQuit
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
2: {
|
||||
// details - general
|
||||
mci: {}
|
||||
}
|
||||
|
||||
3: {
|
||||
// details - nfo/readme
|
||||
mci: {
|
||||
MT1: {
|
||||
mode: preview
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
4: {
|
||||
// details - file listing
|
||||
mci: {
|
||||
VM1: {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileBaseBrowseByAreaSelect: {
|
||||
desc: Browsing File Areas
|
||||
module: file_base_area_select
|
||||
art: FAREASEL
|
||||
form: {
|
||||
0: {
|
||||
mci: {
|
||||
VM1: {
|
||||
focus: true
|
||||
argName: areaTag
|
||||
}
|
||||
}
|
||||
|
||||
submit: {
|
||||
*: [
|
||||
{
|
||||
value: { areaTag: null }
|
||||
action: @method:selectArea
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
actionKeys: @reference:common.quitToPrev
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileBaseFilterEditor: {
|
||||
desc: File Filter Editor
|
||||
module: file_area_filter_edit
|
||||
art: FFILEDT
|
||||
form: {
|
||||
0: {
|
||||
mci: {
|
||||
ET1: {
|
||||
argName: searchTerms
|
||||
}
|
||||
ET2: {
|
||||
maxLength: 64
|
||||
argName: tags
|
||||
}
|
||||
SM3: {
|
||||
maxLength: 64
|
||||
argName: areaIndex
|
||||
}
|
||||
SM4: {
|
||||
items: [
|
||||
"upload date",
|
||||
"uploaded by",
|
||||
"downloads",
|
||||
"rating",
|
||||
"estimated year",
|
||||
"size",
|
||||
]
|
||||
argName: sortByIndex
|
||||
}
|
||||
SM5: {
|
||||
items: [
|
||||
"decending",
|
||||
"ascending"
|
||||
]
|
||||
argName: orderByIndex
|
||||
}
|
||||
ET6: {
|
||||
maxLength: 64
|
||||
argName: name
|
||||
validate: @systemMethod:validateNonEmpty
|
||||
}
|
||||
HM7: {
|
||||
focus: true
|
||||
items: [
|
||||
"prev", "next", "make active", "save", "new", "delete"
|
||||
]
|
||||
argName: navSelect
|
||||
focusItemIndex: 1
|
||||
}
|
||||
}
|
||||
|
||||
submit: {
|
||||
*: [
|
||||
{
|
||||
value: { navSelect: 0 }
|
||||
action: @method:prevFilter
|
||||
}
|
||||
{
|
||||
value: { navSelect: 1 }
|
||||
action: @method:nextFilter
|
||||
}
|
||||
{
|
||||
value: { navSelect: 2 }
|
||||
action: @method:makeFilterActive
|
||||
}
|
||||
{
|
||||
value: { navSelect: 3 }
|
||||
action: @method:saveFilter
|
||||
}
|
||||
{
|
||||
value: { navSelect: 4 }
|
||||
action: @method:newFilter
|
||||
}
|
||||
{
|
||||
value: { navSelect: 5 }
|
||||
action: @method:deleteFilter
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
actionKeys: [
|
||||
{
|
||||
keys: [ "escape" ]
|
||||
action: @systemMethod:prevMenu
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileBaseDownloadManager: {
|
||||
desc: Download Manager
|
||||
module: file_base_download_manager
|
||||
config: {
|
||||
art: {
|
||||
queueManager: FDLMGR
|
||||
/*
|
||||
NYI
|
||||
details: FDLDET
|
||||
*/
|
||||
}
|
||||
emptyQueueMenu: fileBaseDownloadManagerEmptyQueue
|
||||
}
|
||||
form: {
|
||||
0: {
|
||||
mci: {
|
||||
VM1: {
|
||||
argName: queueItem
|
||||
}
|
||||
HM2: {
|
||||
focus: true
|
||||
items: [ "download all", "quit" ]
|
||||
argName: navSelect
|
||||
}
|
||||
}
|
||||
|
||||
submit: {
|
||||
*: [
|
||||
{
|
||||
value: { navSelect: 0 }
|
||||
action: @method:downloadAll
|
||||
}
|
||||
{
|
||||
value: { navSelect: 1 }
|
||||
action: @systemMethod:prevMenu
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
actionKeys: [
|
||||
{
|
||||
keys: [ "a", "shift + a" ]
|
||||
action: @method:downloadAll
|
||||
}
|
||||
{
|
||||
keys: [ "delete", "r", "shift + r" ]
|
||||
action: @method:removeItem
|
||||
}
|
||||
{
|
||||
keys: [ "c", "shift + c" ]
|
||||
action: @method:clearQueue
|
||||
}
|
||||
{
|
||||
keys: [ "escape", "q", "shift + q" ]
|
||||
action: @systemMethod:prevMenu
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileBaseDownloadManagerEmptyQueue: {
|
||||
desc: Empty Download Queue
|
||||
art: FEMPTYQ
|
||||
config: {
|
||||
pause: true
|
||||
menuFlags: [ "noHistory", "popParent" ]
|
||||
}
|
||||
}
|
||||
|
||||
fileBaseWebDownloadManager: {
|
||||
desc: Web D/L Manager
|
||||
module: file_base_web_download_manager
|
||||
config: {
|
||||
art: {
|
||||
queueManager: FWDLMGR
|
||||
batchList: BATDLINF
|
||||
}
|
||||
emptyQueueMenu: fileBaseDownloadManagerEmptyQueue
|
||||
}
|
||||
form: {
|
||||
0: {
|
||||
mci: {
|
||||
VM1: {
|
||||
argName: queueItem
|
||||
}
|
||||
HM2: {
|
||||
focus: true
|
||||
items: [ "get batch link", "quit", "help" ]
|
||||
argName: navSelect
|
||||
}
|
||||
}
|
||||
|
||||
submit: {
|
||||
*: [
|
||||
{
|
||||
value: { navSelect: 0 }
|
||||
action: @method:getBatchLink
|
||||
}
|
||||
{
|
||||
value: { navSelect: 1 }
|
||||
action: @systemMethod:prevMenu
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
actionKeys: [
|
||||
{
|
||||
keys: [ "b", "shift + b" ]
|
||||
action: @method:getBatchLink
|
||||
}
|
||||
{
|
||||
keys: [ "delete", "r", "shift + r" ]
|
||||
action: @method:removeItem
|
||||
}
|
||||
{
|
||||
keys: [ "c", "shift + c" ]
|
||||
action: @method:clearQueue
|
||||
}
|
||||
{
|
||||
keys: [ "escape", "q", "shift + q" ]
|
||||
action: @systemMethod:prevMenu
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileBaseUploadFiles: {
|
||||
desc: Uploading
|
||||
module: upload
|
||||
config: {
|
||||
interrupt: never
|
||||
art: {
|
||||
options: ULOPTS
|
||||
fileDetails: ULDETAIL
|
||||
processing: ULCHECK
|
||||
dupes: ULDUPES
|
||||
}
|
||||
}
|
||||
|
||||
form: {
|
||||
// options
|
||||
0: {
|
||||
mci: {
|
||||
SM1: {
|
||||
argName: areaSelect
|
||||
focus: true
|
||||
}
|
||||
TM2: {
|
||||
argName: uploadType
|
||||
items: [ "blind", "supply filename" ]
|
||||
}
|
||||
ET3: {
|
||||
argName: fileName
|
||||
maxLength: 255
|
||||
validate: @method:validateNonBlindFileName
|
||||
}
|
||||
HM4: {
|
||||
argName: navSelect
|
||||
items: [ "continue", "cancel" ]
|
||||
submit: true
|
||||
}
|
||||
}
|
||||
|
||||
submit: {
|
||||
*: [
|
||||
{
|
||||
value: { navSelect: 0 }
|
||||
action: @method:optionsNavContinue
|
||||
}
|
||||
{
|
||||
value: { navSelect: 1 }
|
||||
action: @systemMethod:prevMenu
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
"actionKeys" : [
|
||||
{
|
||||
"keys" : [ "escape" ],
|
||||
action: @systemMethod:prevMenu
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
1: {
|
||||
mci: { }
|
||||
}
|
||||
|
||||
// file details entry
|
||||
2: {
|
||||
mci: {
|
||||
MT1: {
|
||||
argName: shortDesc
|
||||
tabSwitchesView: true
|
||||
focus: true
|
||||
}
|
||||
|
||||
ET2: {
|
||||
argName: tags
|
||||
}
|
||||
|
||||
ME3: {
|
||||
argName: estYear
|
||||
maskPattern: "####"
|
||||
}
|
||||
|
||||
BT4: {
|
||||
argName: continue
|
||||
text: continue
|
||||
submit: true
|
||||
}
|
||||
}
|
||||
|
||||
submit: {
|
||||
*: [
|
||||
{
|
||||
value: { continue: null }
|
||||
action: @method:fileDetailsContinue
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// dupes
|
||||
3: {
|
||||
mci: {
|
||||
VM1: {
|
||||
/*
|
||||
Use 'dupeInfoFormat' to custom format:
|
||||
|
||||
areaDesc
|
||||
areaName
|
||||
areaTag
|
||||
desc
|
||||
descLong
|
||||
fileId
|
||||
fileName
|
||||
fileSha256
|
||||
storageTag
|
||||
uploadTimestamp
|
||||
|
||||
*/
|
||||
|
||||
mode: preview
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileBaseSearch: {
|
||||
module: file_base_search
|
||||
desc: Searching Files
|
||||
art: FSEARCH
|
||||
form: {
|
||||
0: {
|
||||
mci: {
|
||||
ET1: {
|
||||
focus: true
|
||||
argName: searchTerms
|
||||
}
|
||||
BT2: {
|
||||
argName: search
|
||||
text: search
|
||||
submit: true
|
||||
}
|
||||
ET3: {
|
||||
maxLength: 64
|
||||
argName: tags
|
||||
}
|
||||
SM4: {
|
||||
maxLength: 64
|
||||
argName: areaIndex
|
||||
}
|
||||
SM5: {
|
||||
items: [
|
||||
"upload date",
|
||||
"uploaded by",
|
||||
"downloads",
|
||||
"rating",
|
||||
"estimated year",
|
||||
"size",
|
||||
"filename",
|
||||
]
|
||||
argName: sortByIndex
|
||||
}
|
||||
SM6: {
|
||||
items: [
|
||||
"decending",
|
||||
"ascending"
|
||||
]
|
||||
argName: orderByIndex
|
||||
}
|
||||
BT7: {
|
||||
argName: advancedSearch
|
||||
text: advanced search
|
||||
submit: true
|
||||
}
|
||||
}
|
||||
|
||||
submit: {
|
||||
*: [
|
||||
{
|
||||
value: { search: null }
|
||||
action: @method:search
|
||||
}
|
||||
{
|
||||
value: { advancedSearch: null }
|
||||
action: @method:search
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
actionKeys: [
|
||||
{
|
||||
keys: [ "escape" ]
|
||||
action: @systemMethod:prevMenu
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileBaseSetNewScanDate: {
|
||||
module: set_newscan_date
|
||||
desc: File Base
|
||||
art: SETFNSDATE
|
||||
config: {
|
||||
target: file
|
||||
scanDateFormat: YYYYMMDD
|
||||
}
|
||||
form: {
|
||||
0: {
|
||||
mci: {
|
||||
ME1: {
|
||||
focus: true
|
||||
submit: true
|
||||
argName: scanDate
|
||||
maskPattern: "####/##/##"
|
||||
}
|
||||
}
|
||||
submit: {
|
||||
*: [
|
||||
{
|
||||
value: { scanDate: null }
|
||||
action: @method:scanDateSubmit
|
||||
}
|
||||
]
|
||||
}
|
||||
actionKeys: @reference:common.quitToPrev
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileBaseExportListFilter: {
|
||||
module: file_base_search
|
||||
art: FBLISTEXPSEARCH
|
||||
config: {
|
||||
fileBaseListEntriesMenu: fileBaseExportList
|
||||
}
|
||||
form: {
|
||||
0: {
|
||||
mci: {
|
||||
ET1: {
|
||||
focus: true
|
||||
argName: searchTerms
|
||||
}
|
||||
BT2: {
|
||||
argName: search
|
||||
text: search
|
||||
submit: true
|
||||
}
|
||||
ET3: {
|
||||
maxLength: 64
|
||||
argName: tags
|
||||
}
|
||||
SM4: {
|
||||
maxLength: 64
|
||||
argName: areaIndex
|
||||
}
|
||||
SM5: {
|
||||
items: [
|
||||
"upload date",
|
||||
"uploaded by",
|
||||
"downloads",
|
||||
"rating",
|
||||
"estimated year",
|
||||
"size",
|
||||
"filename"
|
||||
]
|
||||
argName: sortByIndex
|
||||
}
|
||||
SM6: {
|
||||
items: [
|
||||
"decending",
|
||||
"ascending"
|
||||
]
|
||||
argName: orderByIndex
|
||||
}
|
||||
BT7: {
|
||||
argName: advancedSearch
|
||||
text: advanced search
|
||||
submit: true
|
||||
}
|
||||
}
|
||||
|
||||
submit: {
|
||||
*: [
|
||||
{
|
||||
value: { search: null }
|
||||
action: @method:search
|
||||
}
|
||||
{
|
||||
value: { advancedSearch: null }
|
||||
action: @method:search
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
actionKeys: [
|
||||
{
|
||||
keys: [ "escape" ]
|
||||
action: @systemMethod:prevMenu
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileBaseExportList: {
|
||||
module: file_base_user_list_export
|
||||
art: FBLISTEXP
|
||||
config: {
|
||||
pause: true
|
||||
templates: {
|
||||
entry: file_list_entry.asc
|
||||
}
|
||||
}
|
||||
form: {
|
||||
0: {
|
||||
mci: {
|
||||
TL1: { }
|
||||
TL2: { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileBaseExportListNoResults: {
|
||||
desc: Browsing Files
|
||||
art: FBNORES
|
||||
config: {
|
||||
pause: true
|
||||
menuFlags: [ "noHistory", "popParent" ]
|
||||
}
|
||||
}
|
||||
|
||||
// Referenced by various menus
|
||||
fileBaseGetRatingForSelectedEntry: {
|
||||
desc: Rating a File
|
||||
prompt: fileBaseRateEntryPrompt
|
||||
config: {
|
||||
cls: true
|
||||
}
|
||||
submit: [
|
||||
// :TODO: handle esc/q
|
||||
{
|
||||
// pass data back to caller
|
||||
value: { rating: null }
|
||||
action: @systemMethod:prevMenu
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// default menu entry used by the 'file_area_list' module
|
||||
// when there are no search results for the provided criteria
|
||||
fileBaseListEntriesNoResults: {
|
||||
desc: Browsing Files
|
||||
art: FBNORES
|
||||
config: {
|
||||
pause: true
|
||||
menuFlags: [ "noHistory", "popParent" ]
|
||||
}
|
||||
}
|
||||
|
||||
// default menu entry used by the 'file_base_download_manager' module
|
||||
// for protocol selection
|
||||
fileTransferProtocolSelection: {
|
||||
desc: Protocol selection
|
||||
module: file_transfer_protocol_select
|
||||
art: FPROSEL
|
||||
form: {
|
||||
0: {
|
||||
mci: {
|
||||
VM1: {
|
||||
focus: true
|
||||
argName: protocol
|
||||
}
|
||||
}
|
||||
|
||||
submit: {
|
||||
*: [
|
||||
{
|
||||
value: { protocol: null }
|
||||
action: @method:selectProtocol
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
actionKeys: [
|
||||
{
|
||||
keys: [ "escape" ]
|
||||
action: @systemMethod:prevMenu
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// default menu entry used by the 'upload' module for when
|
||||
// no areas are available for the user to upload to
|
||||
fileBaseNoUploadAreasAvail: {
|
||||
desc: File Base
|
||||
art: ULNOAREA
|
||||
config: {
|
||||
pause: true
|
||||
menuFlags: [ "noHistory", "popParent" ]
|
||||
}
|
||||
}
|
||||
|
||||
// default menu entry used by the 'file_transfer_protocol_select' module
|
||||
// when performing user downloads
|
||||
sendFilesToUser: {
|
||||
desc: Downloading
|
||||
module: file_transfer
|
||||
config: {
|
||||
// defaults - generally use extraArgs
|
||||
protocol: zmodem8kSexyz
|
||||
direction: send
|
||||
}
|
||||
}
|
||||
|
||||
// default menu entry used by the 'file_transfer_protocol_select' module
|
||||
// when performing user uploads
|
||||
recvFilesFromUser: {
|
||||
desc: Uploading
|
||||
module: file_transfer
|
||||
config: {
|
||||
// defaults - generally use extraArgs
|
||||
protocol: zmodem8kSexyz
|
||||
direction: recv
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prompts: {
|
||||
fileMenuCommand: {
|
||||
art: FILPMPT
|
||||
mci: {
|
||||
TL1: {}
|
||||
ET2: {
|
||||
argName: menuOption
|
||||
width: 20
|
||||
maxLength: 20
|
||||
textStyle: upper
|
||||
focus: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileBaseRateEntryPrompt: {
|
||||
art: RATEFILE
|
||||
mci: {
|
||||
SM1: {
|
||||
argName: rating
|
||||
items: [ "-----", "*----", "**---", "***--", "****-", "*****" ]
|
||||
}
|
||||
}
|
||||
|
||||
actionKeys: [
|
||||
{
|
||||
keys: [ "escape" ]
|
||||
action: @systemMethod:prevMenu
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
fileBaseTagEntryPrompt: {
|
||||
art: TAGFILE
|
||||
mci: {
|
||||
ET1: {
|
||||
argName: tags
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,607 @@
|
|||
{
|
||||
menus: {
|
||||
//
|
||||
// Send telnet connections to matrix where users can login, apply, etc.
|
||||
//
|
||||
telnetConnected: {
|
||||
art: CONNECT
|
||||
next: matrix
|
||||
config: { nextTimeout: 1500 }
|
||||
}
|
||||
|
||||
//
|
||||
// SSH connections are pre-authenticated via the SSH server itself.
|
||||
// Jump directly to either the 2FA/OTP auth or the login sequence
|
||||
// depending on user ACS.
|
||||
//
|
||||
sshConnected: {
|
||||
art: CONNECT
|
||||
next: [
|
||||
{
|
||||
acs: AR2
|
||||
next: loginTwoFactorAuthOTPLoop
|
||||
}
|
||||
{
|
||||
next: fullLoginSequenceLoginArt
|
||||
}
|
||||
]
|
||||
config: { nextTimeout: 1500 }
|
||||
}
|
||||
|
||||
//
|
||||
// Another SSH specialization: If the user logs in with a new user
|
||||
// name (e.g. "new", "apply", ...) they will be directed to the
|
||||
// application process.
|
||||
//
|
||||
sshConnectedNewUser: {
|
||||
art: CONNECT
|
||||
next: newUserApplicationPreSsh
|
||||
config: { nextTimeout: 1500 }
|
||||
}
|
||||
|
||||
// Ye ol' standard matrix
|
||||
matrix: {
|
||||
art: matrix
|
||||
form: {
|
||||
0: {
|
||||
VM: {
|
||||
mci: {
|
||||
VM1: {
|
||||
submit: true
|
||||
focus: true
|
||||
argName: navSelect
|
||||
items: [
|
||||
{
|
||||
text: login
|
||||
data: login
|
||||
}
|
||||
{
|
||||
text: apply
|
||||
data: apply
|
||||
}
|
||||
|
||||
//
|
||||
// To enable the forgot password option, you'll need to have
|
||||
// the web server & email configured. Once that is in place,
|
||||
// uncomment the section below.
|
||||
//
|
||||
// See docs for more information
|
||||
//
|
||||
/*
|
||||
{
|
||||
text: forgot pass
|
||||
data: forgot
|
||||
}
|
||||
*/
|
||||
{
|
||||
text: log off
|
||||
data: logoff
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
submit: {
|
||||
*: [
|
||||
{
|
||||
value: { navSelect: "login" }
|
||||
action: @menu:login
|
||||
}
|
||||
{
|
||||
value: { navSelect: "apply" }
|
||||
action: @menu:newUserApplicationPre
|
||||
}
|
||||
{
|
||||
value: { navSelect: "forgot" }
|
||||
action: @menu:forgotPassword
|
||||
}
|
||||
{
|
||||
value: { navSelect: "logoff" }
|
||||
action: @menu:logoff
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
login: {
|
||||
art: USERLOG
|
||||
next: [
|
||||
{
|
||||
//
|
||||
// Users with 2FA/OTP enabled *must* go through
|
||||
// an additional OTP authentication step
|
||||
//
|
||||
acs: AR2
|
||||
next: loginTwoFactorAuthOTPLoop
|
||||
}
|
||||
{
|
||||
// ...everyone else can carry on as per usual
|
||||
next: fullLoginSequenceLoginArt
|
||||
}
|
||||
]
|
||||
config: {
|
||||
tooNodeMenu: loginAttemptTooNode
|
||||
inactive: loginAttemptAccountInactive
|
||||
disabled: loginAttemptAccountDisabled
|
||||
locked: loginAttemptAccountLocked
|
||||
}
|
||||
form: {
|
||||
0: {
|
||||
mci: {
|
||||
ET1: {
|
||||
maxLength: @config:users.usernameMax
|
||||
argName: username
|
||||
focus: true
|
||||
}
|
||||
ET2: {
|
||||
password: true
|
||||
maxLength: @config:users.passwordMax
|
||||
argName: password
|
||||
submit: true
|
||||
}
|
||||
}
|
||||
submit: {
|
||||
*: [
|
||||
{
|
||||
value: { password: null }
|
||||
action: @systemMethod:login
|
||||
}
|
||||
]
|
||||
}
|
||||
actionKeys: @reference:common.escToPrev
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loginAttemptTooNode: {
|
||||
art: TOONODE
|
||||
config: {
|
||||
cls: true
|
||||
nextTimeout: 2000
|
||||
}
|
||||
next: logoff
|
||||
}
|
||||
|
||||
loginAttemptAccountLocked: {
|
||||
art: ACCOUNTLOCKED
|
||||
config: {
|
||||
cls: true
|
||||
nextTimeout: 2000
|
||||
}
|
||||
next: logoff
|
||||
}
|
||||
|
||||
loginAttemptAccountDisabled: {
|
||||
art: ACCOUNTDISABLED
|
||||
config: {
|
||||
cls: true
|
||||
nextTimeout: 2000
|
||||
}
|
||||
next: logoff
|
||||
}
|
||||
|
||||
loginAttemptAccountInactive: {
|
||||
art: ACCOUNTINACTIVE
|
||||
config: {
|
||||
cls: true
|
||||
nextTimeout: 2000
|
||||
}
|
||||
next: logoff
|
||||
}
|
||||
|
||||
forgotPassword: {
|
||||
desc: Forgot password
|
||||
prompt: forgotPasswordPrompt
|
||||
submit: [
|
||||
{
|
||||
value: { username: null }
|
||||
action: @systemMethod:sendForgotPasswordEmail
|
||||
extraArgs: { next: "forgotPasswordSubmitted" }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
forgotPasswordSubmitted: {
|
||||
desc: Forgot password
|
||||
art: FORGOTPWSENT
|
||||
config: {
|
||||
cls: true
|
||||
pause: true
|
||||
}
|
||||
next: @systemMethod:logoff
|
||||
}
|
||||
|
||||
fullLoginSequenceLoginArt: {
|
||||
desc: Logging In
|
||||
art: WELCOME
|
||||
config: { pause: true }
|
||||
next: fullLoginSequenceLastCallers
|
||||
}
|
||||
|
||||
fullLoginSequenceLastCallers: {
|
||||
desc: Last Callers
|
||||
module: last_callers
|
||||
art: LASTCALL
|
||||
config: {
|
||||
pause: true
|
||||
font: cp437
|
||||
}
|
||||
next: fullLoginSequenceWhosOnline
|
||||
}
|
||||
|
||||
fullLoginSequenceWhosOnline: {
|
||||
desc: Who's Online
|
||||
module: whos_online
|
||||
art: WHOSON
|
||||
config: { pause: true }
|
||||
next: fullLoginSequenceOnelinerz
|
||||
}
|
||||
|
||||
fullLoginSequenceOnelinerz: {
|
||||
desc: Viewing Onelinerz
|
||||
module: onelinerz
|
||||
next: [
|
||||
{
|
||||
// calls >= 2
|
||||
acs: NC2
|
||||
next: fullLoginSequenceNewScanConfirm
|
||||
}
|
||||
{
|
||||
// new users - skip new scan
|
||||
next: fullLoginSequenceUserStats
|
||||
}
|
||||
]
|
||||
config: {
|
||||
cls: true
|
||||
art: {
|
||||
view: ONELINER
|
||||
add: ONEADD
|
||||
}
|
||||
}
|
||||
form: {
|
||||
0: {
|
||||
mci: {
|
||||
VM1: {
|
||||
focus: false
|
||||
height: 10
|
||||
}
|
||||
TM2: {
|
||||
argName: addOrExit
|
||||
items: [ "yeah!", "nah" ]
|
||||
"hotKeys" : { "Y" : 0, "N" : 1, "Q" : 1 }
|
||||
submit: true
|
||||
focus: true
|
||||
}
|
||||
}
|
||||
submit: {
|
||||
*: [
|
||||
{
|
||||
value: { addOrExit: 0 }
|
||||
action: @method:viewAddScreen
|
||||
}
|
||||
{
|
||||
value: { addOrExit: null }
|
||||
action: @systemMethod:nextMenu
|
||||
}
|
||||
]
|
||||
}
|
||||
actionKeys: [
|
||||
{
|
||||
keys: [ "escape" ]
|
||||
action: @systemMethod:nextMenu
|
||||
}
|
||||
]
|
||||
},
|
||||
1: {
|
||||
mci: {
|
||||
ET1: {
|
||||
focus: true
|
||||
maxLength: 70
|
||||
argName: oneliner
|
||||
}
|
||||
TL2: {
|
||||
width: 60
|
||||
}
|
||||
TM3: {
|
||||
argName: addOrCancel
|
||||
items: [ "add", "cancel" ]
|
||||
"hotKeys" : { "A" : 0, "C" : 1, "Q" : 1 }
|
||||
submit: true
|
||||
}
|
||||
}
|
||||
|
||||
submit: {
|
||||
*: [
|
||||
{
|
||||
value: { addOrCancel: 0 }
|
||||
action: @method:addEntry
|
||||
}
|
||||
{
|
||||
value: { addOrCancel: 1 }
|
||||
action: @method:cancelAdd
|
||||
}
|
||||
]
|
||||
}
|
||||
actionKeys: [
|
||||
{
|
||||
keys: [ "escape" ]
|
||||
action: @method:cancelAdd
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fullLoginSequenceNewScanConfirm: {
|
||||
desc: Logging In
|
||||
prompt: loginGlobalNewScan
|
||||
submit: [
|
||||
{
|
||||
value: { promptValue: 0 }
|
||||
action: @menu:fullLoginSequenceNewScan
|
||||
}
|
||||
{
|
||||
value: { promptValue: 1 }
|
||||
action: @menu:fullLoginSequenceUserStats
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
fullLoginSequenceNewScan: {
|
||||
desc: Performing New Scan
|
||||
module: new_scan
|
||||
art: NEWSCAN
|
||||
next: fullLoginSequenceSysStats
|
||||
config: {
|
||||
messageListMenu: newScanMessageList
|
||||
}
|
||||
}
|
||||
|
||||
newScanMessageList: {
|
||||
desc: New Messages
|
||||
module: msg_list
|
||||
art: NEWMSGS
|
||||
config: {
|
||||
menuViewPost: messageAreaViewPost
|
||||
}
|
||||
form: {
|
||||
0: {
|
||||
mci: {
|
||||
VM1: {
|
||||
focus: true
|
||||
submit: true
|
||||
argName: messageIndex
|
||||
}
|
||||
TL6: { }
|
||||
}
|
||||
submit: {
|
||||
*: [
|
||||
{
|
||||
value: { messageIndex: null }
|
||||
action: @method:selectMessage
|
||||
}
|
||||
]
|
||||
}
|
||||
actionKeys: [
|
||||
{
|
||||
keys: [ "escape", "q", "shift + q" ]
|
||||
action: @systemMethod:prevMenu
|
||||
}
|
||||
{
|
||||
keys: [ "x", "shift + x" ]
|
||||
action: @method:fullExit
|
||||
}
|
||||
{
|
||||
keys: [ "m", "shift + m" ]
|
||||
action: @method:markAllRead
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newScanFileBaseList: {
|
||||
module: file_area_list
|
||||
desc: New Files
|
||||
config: {
|
||||
art: {
|
||||
browse: FNEWBRWSE
|
||||
details: FDETAIL
|
||||
detailsGeneral: FDETGEN
|
||||
detailsNfo: FDETNFO
|
||||
detailsFileList: FDETLST
|
||||
help: FBHELP
|
||||
}
|
||||
}
|
||||
form: {
|
||||
0: {
|
||||
mci: {
|
||||
MT1: {
|
||||
mode: preview
|
||||
ansiView: true
|
||||
}
|
||||
|
||||
HM2: {
|
||||
focus: true
|
||||
submit: true
|
||||
argName: navSelect
|
||||
items: [
|
||||
"prev", "next", "details", "toggle queue", "rate", "help", "quit"
|
||||
]
|
||||
focusItemIndex: 1
|
||||
}
|
||||
}
|
||||
|
||||
submit: {
|
||||
*: [
|
||||
{
|
||||
value: { navSelect: 0 }
|
||||
action: @method:prevFile
|
||||
}
|
||||
{
|
||||
value: { navSelect: 1 }
|
||||
action: @method:nextFile
|
||||
}
|
||||
{
|
||||
value: { navSelect: 2 }
|
||||
action: @method:viewDetails
|
||||
}
|
||||
{
|
||||
value: { navSelect: 3 }
|
||||
action: @method:toggleQueue
|
||||
}
|
||||
{
|
||||
value: { navSelect: 4 }
|
||||
action: @menu:fileBaseGetRatingForSelectedEntry
|
||||
}
|
||||
{
|
||||
value: { navSelect: 5 }
|
||||
action: @method:displayHelp
|
||||
}
|
||||
{
|
||||
value: { navSelect: 6 }
|
||||
action: @systemMethod:prevMenu
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
actionKeys: [
|
||||
{
|
||||
keys: [ "w", "shift + w" ]
|
||||
action: @method:showWebDownloadLink
|
||||
}
|
||||
{
|
||||
keys: [ "escape", "q", "shift + q" ]
|
||||
action: @systemMethod:prevMenu
|
||||
}
|
||||
{
|
||||
keys: [ "t", "shift + t" ]
|
||||
action: @method:toggleQueue
|
||||
}
|
||||
{
|
||||
keys: [ "v", "shift + v" ]
|
||||
action: @method:viewDetails
|
||||
}
|
||||
{
|
||||
keys: [ "r", "shift + r" ]
|
||||
action: @menu:fileBaseGetRatingForSelectedEntry
|
||||
}
|
||||
{
|
||||
keys: [ "?" ]
|
||||
action: @method:displayHelp
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
1: {
|
||||
mci: {
|
||||
HM1: {
|
||||
focus: true
|
||||
submit: true
|
||||
argName: navSelect
|
||||
items: [
|
||||
"general", "nfo/readme", "file listing"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
actionKeys: @reference:common.quitToPrev
|
||||
}
|
||||
|
||||
2: {
|
||||
// details - general
|
||||
mci: { }
|
||||
}
|
||||
|
||||
3: {
|
||||
// details - nfo/readme
|
||||
mci: {
|
||||
MT1: {
|
||||
mode: preview
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
4: {
|
||||
// details - file listing
|
||||
mci: {
|
||||
VM1: { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fullLoginSequenceSysStats: {
|
||||
desc: System Stats
|
||||
art: SYSSTAT
|
||||
config: { pause: true }
|
||||
next: fullLoginSequenceUserStats
|
||||
}
|
||||
|
||||
fullLoginSequenceUserStats: {
|
||||
desc: User Stats
|
||||
art: STATUS
|
||||
config: { pause: true }
|
||||
next: mainMenu
|
||||
}
|
||||
|
||||
//
|
||||
// Empty menu to catch us in a 2FA/OTP auth loop
|
||||
// until the user either authenticates successfully
|
||||
// or the system boots them.
|
||||
//
|
||||
loginTwoFactorAuthOTPLoop: {
|
||||
next: loginTwoFactorAuthOTP
|
||||
}
|
||||
|
||||
loginTwoFactorAuthOTP: {
|
||||
art: 2FAOTP
|
||||
next: fullLoginSequenceLoginArt
|
||||
form: {
|
||||
0: {
|
||||
mci: {
|
||||
ET1: {
|
||||
argName: token
|
||||
focus: true
|
||||
submit: true
|
||||
}
|
||||
}
|
||||
submit: {
|
||||
*: [
|
||||
{
|
||||
value: { token: null }
|
||||
action: @systemMethod:login2FA_OTP
|
||||
}
|
||||
]
|
||||
}
|
||||
actionKeys: [
|
||||
{
|
||||
// no turning back at this point...
|
||||
keys: [ "escape" ]
|
||||
action: @systemMethod:logoff
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
prompts: {
|
||||
loginGlobalNewScan: {
|
||||
art: GNSPMPT
|
||||
mci: {
|
||||
TM1: {
|
||||
argName: promptValue
|
||||
items: [ "yes", "no" ]
|
||||
focus: true
|
||||
hotKeys: { Y: 0, N: 1 }
|
||||
hotKeySubmit: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,816 @@
|
|||
{
|
||||
menus: {
|
||||
messageBaseMainMenu: {
|
||||
art: MSGMNU
|
||||
desc: Message Area
|
||||
prompt: messageBaseMenuPrompt
|
||||
config: {
|
||||
interrupt: realtime
|
||||
}
|
||||
submit: [
|
||||
{
|
||||
value: { command: "P" }
|
||||
action: @menu:messageBaseNewPost
|
||||
}
|
||||
{
|
||||
value: { command: "J" }
|
||||
action: @menu:messageBaseChangeCurrentConference
|
||||
}
|
||||
{
|
||||
value: { command: "C" }
|
||||
action: @menu:messageBaseChangeCurrentArea
|
||||
}
|
||||
{
|
||||
value: { command: "L" }
|
||||
action: @menu:messageBaseMessageList
|
||||
}
|
||||
{
|
||||
value: { command: "<" }
|
||||
action: @systemMethod:prevConf
|
||||
}
|
||||
{
|
||||
value: { command: ">" }
|
||||
action: @systemMethod:nextConf
|
||||
}
|
||||
{
|
||||
value: { command: "[" }
|
||||
action: @systemMethod:prevArea
|
||||
}
|
||||
{
|
||||
value: { command: "]" }
|
||||
action: @systemMethod:nextArea
|
||||
}
|
||||
{
|
||||
value: { command: "D" }
|
||||
action: @menu:messageBaseSetNewScanDate
|
||||
}
|
||||
{
|
||||
value: { command: "S" }
|
||||
action: @menu:messageBaseSearch
|
||||
}
|
||||
{
|
||||
value: { command: "M" }
|
||||
action: @menu:messageBaseMyMessages
|
||||
}
|
||||
{
|
||||
value: { command: "A" }
|
||||
action: @menu:editAutoSignature
|
||||
}
|
||||
{
|
||||
value: { command: "Q" }
|
||||
action: @systemMethod:prevMenu
|
||||
}
|
||||
{
|
||||
value: { command: "G" }
|
||||
action: @menu:fullLogoffSequence
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
messageBaseNewPost: {
|
||||
desc: Posting message,
|
||||
module: msg_area_post_fse
|
||||
config: {
|
||||
art: {
|
||||
header: MSGEHDR
|
||||
body: MSGBODY
|
||||
footerEditor: MSGEFTR
|
||||
footerEditorMenu: MSGEMFT
|
||||
help: MSGEHLP
|
||||
}
|
||||
editorMode: edit
|
||||
editorType: area
|
||||
}
|
||||
form: {
|
||||
0: {
|
||||
mci: {
|
||||
TL1: {
|
||||
argName: from
|
||||
}
|
||||
ET2: {
|
||||
argName: to
|
||||
focus: true
|
||||
text: All
|
||||
validate: @systemMethod:validateNonEmpty
|
||||
maxLength: 36
|
||||
}
|
||||
ET3: {
|
||||
argName: subject
|
||||
maxLength: 72
|
||||
submit: true
|
||||
validate: @systemMethod:validateNonEmpty
|
||||
}
|
||||
}
|
||||
submit: {
|
||||
3: [
|
||||
{
|
||||
value: { subject: null }
|
||||
action: @method:headerSubmit
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
actionKeys: [
|
||||
{
|
||||
keys: [ "escape" ]
|
||||
action: @systemMethod:prevMenu
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
1: {
|
||||
"mci" : {
|
||||
MT1: {
|
||||
width: 79
|
||||
argName: message
|
||||
mode: edit
|
||||
}
|
||||
}
|
||||
|
||||
submit: {
|
||||
*: [ { "value": "message", "action": "@method:editModeEscPressed" } ]
|
||||
}
|
||||
actionKeys: [
|
||||
{
|
||||
keys: [ "escape" ]
|
||||
viewId: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
2: {
|
||||
TLTL: {
|
||||
mci: {
|
||||
TL1: { width: 5 }
|
||||
TL2: { width: 4 }
|
||||
}
|
||||
}
|
||||
}
|
||||
3: {
|
||||
HM: {
|
||||
mci: {
|
||||
HM1: {
|
||||
"items" : [ "save", "discard", "help" ]
|
||||
}
|
||||
}
|
||||
submit: {
|
||||
*: [
|
||||
{
|
||||
value: { 1: 0 }
|
||||
action: @method:editModeMenuSave
|
||||
}
|
||||
{
|
||||
value: { 1: 1 }
|
||||
action: @systemMethod:prevMenu
|
||||
}
|
||||
{
|
||||
value: { 1: 2 }
|
||||
action: @method:editModeMenuHelp
|
||||
}
|
||||
]
|
||||
}
|
||||
actionKeys: [
|
||||
{
|
||||
keys: [ "escape" ]
|
||||
action: @method:editModeEscPressed
|
||||
}
|
||||
{
|
||||
keys: [ "?" ]
|
||||
action: @method:editModeMenuHelp
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
messageBaseChangeCurrentConference: {
|
||||
art: CCHANGE
|
||||
module: msg_conf_list
|
||||
form: {
|
||||
0: {
|
||||
mci: {
|
||||
VM1: {
|
||||
focus: true
|
||||
submit: true
|
||||
argName: conf
|
||||
}
|
||||
}
|
||||
submit: {
|
||||
*: [
|
||||
{
|
||||
value: { conf: null }
|
||||
action: @method:changeConference
|
||||
}
|
||||
]
|
||||
}
|
||||
actionKeys: @reference:common.quitToPrev
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
messageBaseChangeCurrentArea: {
|
||||
art: CHANGE
|
||||
module: msg_area_list
|
||||
form: {
|
||||
0: {
|
||||
mci: {
|
||||
VM1: {
|
||||
focus: true
|
||||
submit: true
|
||||
argName: area
|
||||
}
|
||||
}
|
||||
submit: {
|
||||
*: [
|
||||
{
|
||||
value: { area: null }
|
||||
action: @method:changeArea
|
||||
}
|
||||
]
|
||||
}
|
||||
actionKeys: @reference:common.quitToPrev
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
messageBaseMessageList: {
|
||||
module: msg_list
|
||||
art: MSGLIST
|
||||
config: {
|
||||
menuViewPost: messageAreaViewPost
|
||||
}
|
||||
form: {
|
||||
0: {
|
||||
mci: {
|
||||
VM1: {
|
||||
focus: true
|
||||
submit: true
|
||||
argName: messageIndex
|
||||
}
|
||||
}
|
||||
submit: {
|
||||
*: [
|
||||
{
|
||||
value: { message: null }
|
||||
action: @method:selectMessage
|
||||
}
|
||||
]
|
||||
}
|
||||
actionKeys: @reference:common.quitToPrev
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
messageBaseSetNewScanDate: {
|
||||
module: set_newscan_date
|
||||
desc: Message Base
|
||||
art: SETMNSDATE
|
||||
config: {
|
||||
target: message
|
||||
scanDateFormat: YYYYMMDD
|
||||
}
|
||||
form: {
|
||||
0: {
|
||||
mci: {
|
||||
ME1: {
|
||||
focus: true
|
||||
submit: true
|
||||
argName: scanDate
|
||||
maskPattern: "####/##/##"
|
||||
}
|
||||
SM2: {
|
||||
argName: targetSelection
|
||||
submit: false
|
||||
}
|
||||
}
|
||||
submit: {
|
||||
*: [
|
||||
{
|
||||
value: { scanDate: null }
|
||||
action: @method:scanDateSubmit
|
||||
}
|
||||
]
|
||||
}
|
||||
actionKeys: @reference:common.quitToPrev
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
messageBaseSearch: {
|
||||
desc: Message Search
|
||||
module: message_base_search
|
||||
art: MSEARCH
|
||||
config: {
|
||||
messageListMenu: messageBaseSearchResultsMessageList
|
||||
}
|
||||
form: {
|
||||
0: {
|
||||
mci: {
|
||||
ET1: {
|
||||
focus: true
|
||||
argName: searchTerms
|
||||
}
|
||||
BT2: {
|
||||
argName: search
|
||||
text: search
|
||||
submit: true
|
||||
}
|
||||
SM3: {
|
||||
argName: confTag
|
||||
}
|
||||
SM4: {
|
||||
argName: areaTag
|
||||
}
|
||||
ET5: {
|
||||
argName: toUserName
|
||||
maxLength: @config:users.usernameMax
|
||||
}
|
||||
ET6: {
|
||||
argName: fromUserName
|
||||
maxLength: @config:users.usernameMax
|
||||
}
|
||||
BT7: {
|
||||
argName: advancedSearch
|
||||
text: advanced search
|
||||
submit: true
|
||||
}
|
||||
}
|
||||
|
||||
submit: {
|
||||
*: [
|
||||
{
|
||||
value: { search: null }
|
||||
action: @method:search
|
||||
}
|
||||
{
|
||||
value: { advancedSearch: null }
|
||||
action: @method:search
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
actionKeys: [
|
||||
{
|
||||
keys: [ "escape" ]
|
||||
action: @systemMethod:prevMenu
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
messageBaseSearchResultsMessageList: {
|
||||
desc: Message Search
|
||||
module: msg_list
|
||||
art: MSRCHLST
|
||||
config: {
|
||||
menuViewPost: messageAreaViewPost
|
||||
}
|
||||
form: {
|
||||
0: {
|
||||
mci: {
|
||||
VM1: {
|
||||
focus: true
|
||||
submit: true
|
||||
argName: messageIndex
|
||||
}
|
||||
TL6: {
|
||||
// theme me!
|
||||
}
|
||||
}
|
||||
submit: {
|
||||
*: [
|
||||
{
|
||||
value: { messageIndex: null }
|
||||
action: @method:selectMessage
|
||||
}
|
||||
]
|
||||
}
|
||||
actionKeys: @reference:common.quitToPrev
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The message_base_search module looks for this entry by default
|
||||
messageSearchNoResults: {
|
||||
desc: Message Search
|
||||
art: MSRCNORES
|
||||
config: {
|
||||
pause: true
|
||||
}
|
||||
}
|
||||
|
||||
messageBaseMyMessages: {
|
||||
desc: Personal Messages
|
||||
module: my_messages
|
||||
config: {
|
||||
messageListMenu: messageBaseMyMessagesList
|
||||
}
|
||||
}
|
||||
|
||||
messageBaseMyMessagesList: {
|
||||
desc: Personal Messages
|
||||
module: msg_list
|
||||
art: MYMSGLST
|
||||
config: {
|
||||
menuViewPost: messageAreaViewPost
|
||||
}
|
||||
form: {
|
||||
0: {
|
||||
mci: {
|
||||
VM1: {
|
||||
focus: true
|
||||
submit: true
|
||||
argName: messageIndex
|
||||
}
|
||||
}
|
||||
submit: {
|
||||
*: [
|
||||
{
|
||||
value: { messageIndex: null }
|
||||
action: @method:selectMessage
|
||||
}
|
||||
]
|
||||
}
|
||||
actionKeys: @reference:common.quitToPrev
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
editAutoSignature: {
|
||||
desc: Auto Sig Editor
|
||||
module: autosig_edit
|
||||
art: autosig
|
||||
form: {
|
||||
0: {
|
||||
mci: {
|
||||
MT1: {
|
||||
argName: signature
|
||||
tabSwitchesView: true
|
||||
}
|
||||
BT2: {
|
||||
text: save
|
||||
argName: save
|
||||
submit: true
|
||||
}
|
||||
}
|
||||
submit: {
|
||||
*: [
|
||||
{
|
||||
value: { save: null }
|
||||
action: @method:saveChanges
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
actionKeys: [
|
||||
{
|
||||
keys: [ "escape" ]
|
||||
action: @systemMethod:prevMenu
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
messageAreaViewPost: {
|
||||
module: msg_area_view_fse
|
||||
config: {
|
||||
art: {
|
||||
header: MSGVHDR
|
||||
body: MSGBODY
|
||||
footerView: MSGVFTR
|
||||
help: MSGVHLP
|
||||
},
|
||||
editorMode: view
|
||||
editorType: area
|
||||
}
|
||||
form: {
|
||||
0: {
|
||||
mci: {
|
||||
// :TODO: ensure this block isn't even req. for theme to apply...
|
||||
}
|
||||
}
|
||||
1: {
|
||||
mci: {
|
||||
MT1: {
|
||||
width: 79
|
||||
mode: preview
|
||||
}
|
||||
}
|
||||
submit: {
|
||||
*: [
|
||||
{
|
||||
value: message
|
||||
action: @method:editModeEscPressed
|
||||
}
|
||||
]
|
||||
}
|
||||
actionKeys: [
|
||||
{
|
||||
keys: [ "escape" ]
|
||||
viewId: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
2: {
|
||||
TLTL: {
|
||||
mci: {
|
||||
TL1: { width: 5 }
|
||||
TL2: { width: 4 }
|
||||
}
|
||||
}
|
||||
}
|
||||
4: {
|
||||
mci: {
|
||||
HM1: {
|
||||
// :TODO: (#)Jump/(L)Index (msg list)/Last
|
||||
items: [ "prev", "next", "reply", "quit", "help" ]
|
||||
focusItemIndex: 1
|
||||
}
|
||||
}
|
||||
submit: {
|
||||
*: [
|
||||
{
|
||||
value: { 1: 0 }
|
||||
action: @method:prevMessage
|
||||
}
|
||||
{
|
||||
value: { 1: 1 }
|
||||
action: @method:nextMessage
|
||||
}
|
||||
{
|
||||
value: { 1: 2 }
|
||||
action: @method:replyMessage
|
||||
extraArgs: {
|
||||
menu: messageAreaReplyPost
|
||||
}
|
||||
}
|
||||
{
|
||||
value: { 1: 3 }
|
||||
action: @systemMethod:prevMenu
|
||||
}
|
||||
{
|
||||
value: { 1: 4 }
|
||||
action: @method:viewModeMenuHelp
|
||||
}
|
||||
]
|
||||
}
|
||||
actionKeys: [
|
||||
{
|
||||
keys: [ "p", "shift + p" ]
|
||||
action: @method:prevMessage
|
||||
}
|
||||
{
|
||||
keys: [ "n", "shift + n" ]
|
||||
action: @method:nextMessage
|
||||
}
|
||||
{
|
||||
keys: [ "r", "shift + r" ]
|
||||
action: @method:replyMessage
|
||||
extraArgs: {
|
||||
menu: messageAreaReplyPost
|
||||
}
|
||||
}
|
||||
{
|
||||
keys: [ "escape", "q", "shift + q" ]
|
||||
action: @systemMethod:prevMenu
|
||||
}
|
||||
{
|
||||
keys: [ "?" ]
|
||||
action: @method:viewModeMenuHelp
|
||||
}
|
||||
{
|
||||
keys: [ "down arrow", "up arrow", "page up", "page down" ]
|
||||
action: @method:movementKeyPressed
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
messageAreaReplyPost: {
|
||||
module: msg_area_post_fse
|
||||
config: {
|
||||
art: {
|
||||
header: MSGEHDR
|
||||
body: MSGBODY
|
||||
quote: MSGQUOT
|
||||
footerEditor: MSGEFTR
|
||||
footerEditorMenu: MSGEMFT
|
||||
help: MSGEHLP
|
||||
}
|
||||
editorMode: edit
|
||||
editorType: area
|
||||
}
|
||||
form: {
|
||||
0: {
|
||||
mci: {
|
||||
// :TODO: use appropriate system properties for max lengths
|
||||
TL1: {
|
||||
argName: from
|
||||
}
|
||||
ET2: {
|
||||
argName: to
|
||||
focus: true
|
||||
validate: @systemMethod:validateNonEmpty
|
||||
maxLength: 36
|
||||
}
|
||||
ET3: {
|
||||
argName: subject
|
||||
maxLength: 72
|
||||
submit: true
|
||||
validate: @systemMethod:validateNonEmpty
|
||||
}
|
||||
TL4: {
|
||||
// :TODO: this is for RE: line (NYI)
|
||||
//width: 27
|
||||
//textOverflow: ...
|
||||
}
|
||||
}
|
||||
submit: {
|
||||
3: [
|
||||
{
|
||||
value: { subject: null }
|
||||
action: @method:headerSubmit
|
||||
}
|
||||
]
|
||||
}
|
||||
actionKeys: [
|
||||
{
|
||||
keys: [ "escape" ]
|
||||
action: @systemMethod:prevMenu
|
||||
}
|
||||
]
|
||||
}
|
||||
1: {
|
||||
mci: {
|
||||
MT1: {
|
||||
width: 79
|
||||
height: 14
|
||||
argName: message
|
||||
mode: edit
|
||||
}
|
||||
}
|
||||
submit: {
|
||||
*: [ { "value": "message", "action": "@method:editModeEscPressed" } ]
|
||||
}
|
||||
actionKeys: [
|
||||
{
|
||||
keys: [ "escape" ],
|
||||
viewId: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
3: {
|
||||
mci: {
|
||||
HM1: {
|
||||
items: [ "save", "discard", "quote", "help" ]
|
||||
}
|
||||
}
|
||||
|
||||
submit: {
|
||||
*: [
|
||||
{
|
||||
value: { 1: 0 }
|
||||
action: @method:editModeMenuSave
|
||||
}
|
||||
{
|
||||
value: { 1: 1 }
|
||||
action: @systemMethod:prevMenu
|
||||
}
|
||||
{
|
||||
value: { 1: 2 },
|
||||
action: @method:editModeMenuQuote
|
||||
}
|
||||
{
|
||||
value: { 1: 3 }
|
||||
action: @method:editModeMenuHelp
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
actionKeys: [
|
||||
{
|
||||
keys: [ "escape" ]
|
||||
action: @method:editModeEscPressed
|
||||
}
|
||||
{
|
||||
keys: [ "s", "shift + s" ]
|
||||
action: @method:editModeMenuSave
|
||||
}
|
||||
{
|
||||
keys: [ "d", "shift + d" ]
|
||||
action: @systemMethod:prevMenu
|
||||
}
|
||||
{
|
||||
keys: [ "q", "shift + q" ]
|
||||
action: @method:editModeMenuQuote
|
||||
}
|
||||
{
|
||||
keys: [ "?" ]
|
||||
action: @method:editModeMenuHelp
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// Quote builder
|
||||
5: {
|
||||
mci: {
|
||||
MT1: {
|
||||
width: 79
|
||||
height: 7
|
||||
}
|
||||
VM3: {
|
||||
width: 79
|
||||
height: 4
|
||||
argName: quote
|
||||
}
|
||||
}
|
||||
|
||||
submit: {
|
||||
*: [
|
||||
{
|
||||
value: { quote: null }
|
||||
action: @method:appendQuoteEntry
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
actionKeys: [
|
||||
{
|
||||
keys: [ "escape" ]
|
||||
action: @method:quoteBuilderEscPressed
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// The 'msg_conf_list' module defaults to looking for
|
||||
// a menu entry of 'changeConfPreArtMenu'. If found,
|
||||
// this menu will be executed upon changing message
|
||||
// conferences using the conference tag as an art spec.
|
||||
//
|
||||
changeMessageConfPreArt: {
|
||||
module: show_art
|
||||
config: {
|
||||
method: messageConf
|
||||
key: confTag
|
||||
pause: true
|
||||
cls: true
|
||||
menuFlags: [ "popParent", "noHistory" ]
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// The 'msg_area_list' module defaults to looking for
|
||||
// a menu entry of 'changeMessageAreaPreArt'. If found,
|
||||
// this menu will be executed upon changing message
|
||||
// areas using the area tag as an art spec.
|
||||
//
|
||||
changeMessageAreaPreArt: {
|
||||
module: show_art
|
||||
config: {
|
||||
method: messageArea
|
||||
key: areaTag
|
||||
pause: true
|
||||
cls: true
|
||||
menuFlags: [ "popParent", "noHistory" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prompts: {
|
||||
messageBaseMenuPrompt: {
|
||||
art: MSGPMPT
|
||||
mci: {
|
||||
//TL1: {}
|
||||
ET2: {
|
||||
argName: command
|
||||
width: 20
|
||||
maxLength: 20
|
||||
submit: true
|
||||
textStyle: upper
|
||||
focus: true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// default prompt entry used by the 'msg_lsit' module
|
||||
deleteMessageFromListPrompt: {
|
||||
art: MSGDELPMPT
|
||||
mci: {
|
||||
TM1: {
|
||||
argName: promptValue
|
||||
items: [ "yes", "no" ]
|
||||
focus: true
|
||||
hotKeys: { Y: 0, N: 1 }
|
||||
hotKeySubmit: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,362 @@
|
|||
{
|
||||
menus: {
|
||||
// A quick preamble - defaults to warning about broken terminals
|
||||
newUserApplicationPre: {
|
||||
art: NEWUSER1
|
||||
next: newUserApplication
|
||||
desc: Applying
|
||||
config: {
|
||||
pause: true
|
||||
cls: true
|
||||
menuFlags: [ "noHistory" ]
|
||||
}
|
||||
}
|
||||
|
||||
newUserApplication: {
|
||||
module: nua
|
||||
art: NUA
|
||||
next: [
|
||||
{
|
||||
// Initial SysOp does not send feedback to themselves
|
||||
acs: ID1
|
||||
next: fullLoginSequenceLoginArt
|
||||
}
|
||||
{
|
||||
// ...everyone else does
|
||||
next: newUserFeedbackToSysOpPreamble
|
||||
}
|
||||
]
|
||||
form: {
|
||||
0: {
|
||||
mci: {
|
||||
ET1: {
|
||||
focus: true
|
||||
argName: username
|
||||
maxLength: @config:users.usernameMax
|
||||
validate: @systemMethod:validateUserNameAvail
|
||||
}
|
||||
ET2: {
|
||||
argName: realName
|
||||
maxLength: @config:users.realNameMax
|
||||
validate: @systemMethod:validateNonEmpty
|
||||
}
|
||||
MET3: {
|
||||
argName: birthdate
|
||||
maskPattern: "####/##/##"
|
||||
validate: @systemMethod:validateBirthdate
|
||||
}
|
||||
ME4: {
|
||||
argName: sex
|
||||
maskPattern: A
|
||||
textStyle: upper
|
||||
validate: @systemMethod:validateNonEmpty
|
||||
}
|
||||
ET5: {
|
||||
argName: location
|
||||
maxLength: @config:users.locationMax
|
||||
validate: @systemMethod:validateNonEmpty
|
||||
}
|
||||
ET6: {
|
||||
argName: affils
|
||||
maxLength: @config:users.affilsMax
|
||||
}
|
||||
ET7: {
|
||||
argName: email
|
||||
maxLength: @config:users.emailMax
|
||||
validate: @systemMethod:validateEmailAvail
|
||||
}
|
||||
ET8: {
|
||||
argName: web
|
||||
maxLength: @config:users.webMax
|
||||
}
|
||||
ET9: {
|
||||
argName: password
|
||||
password: true
|
||||
maxLength: @config:users.passwordMax
|
||||
validate: @systemMethod:validatePasswordSpec
|
||||
}
|
||||
ET10: {
|
||||
argName: passwordConfirm
|
||||
password: true
|
||||
maxLength: @config:users.passwordMax
|
||||
validate: @method:validatePassConfirmMatch
|
||||
}
|
||||
TM12: {
|
||||
argName: submission
|
||||
items: [ "apply", "cancel" ]
|
||||
submit: true
|
||||
}
|
||||
}
|
||||
|
||||
submit: {
|
||||
*: [
|
||||
{
|
||||
value: { "submission" : 0 }
|
||||
action: @method:submitApplication
|
||||
extraArgs: {
|
||||
inactive: userNeedsActivated
|
||||
error: newUserCreateError
|
||||
}
|
||||
}
|
||||
{
|
||||
value: { "submission" : 1 }
|
||||
action: @systemMethod:prevMenu
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
actionKeys: @reference:common.escToPrev
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A quick preamble - defaults to warning about broken terminals (SSH version)
|
||||
newUserApplicationPreSsh: {
|
||||
art: NEWUSER1
|
||||
next: newUserApplicationSsh
|
||||
desc: Applying
|
||||
config: {
|
||||
pause: true
|
||||
cls: true
|
||||
menuFlags: [ "noHistory" ]
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// SSH specialization of NUA
|
||||
// Canceling this form logs off vs falling back to matrix
|
||||
//
|
||||
newUserApplicationSsh: {
|
||||
module: nua
|
||||
art: NUA
|
||||
fallback: logoff
|
||||
next: newUserFeedbackToSysOpPreamble
|
||||
form: {
|
||||
0: {
|
||||
mci: {
|
||||
ET1: {
|
||||
focus: true
|
||||
argName: username
|
||||
maxLength: @config:users.usernameMax
|
||||
validate: @systemMethod:validateUserNameAvail
|
||||
}
|
||||
ET2: {
|
||||
argName: realName
|
||||
maxLength: @config:users.realNameMax
|
||||
validate: @systemMethod:validateNonEmpty
|
||||
}
|
||||
MET3: {
|
||||
argName: birthdate
|
||||
maskPattern: "####/##/##"
|
||||
validate: @systemMethod:validateBirthdate
|
||||
}
|
||||
ME4: {
|
||||
argName: sex
|
||||
maskPattern: A
|
||||
textStyle: upper
|
||||
validate: @systemMethod:validateNonEmpty
|
||||
}
|
||||
ET5: {
|
||||
argName: location
|
||||
maxLength: @config:users.locationMax
|
||||
validate: @systemMethod:validateNonEmpty
|
||||
}
|
||||
ET6: {
|
||||
argName: affils
|
||||
maxLength: @config:users.affilsMax
|
||||
}
|
||||
ET7: {
|
||||
argName: email
|
||||
maxLength: @config:users.emailMax
|
||||
validate: @systemMethod:validateEmailAvail
|
||||
}
|
||||
ET8: {
|
||||
argName: web
|
||||
maxLength: @config:users.webMax
|
||||
}
|
||||
ET9: {
|
||||
argName: password
|
||||
password: true
|
||||
maxLength: @config:users.passwordMax
|
||||
validate: @systemMethod:validatePasswordSpec
|
||||
}
|
||||
ET10: {
|
||||
argName: passwordConfirm
|
||||
password: true
|
||||
maxLength: @config:users.passwordMax
|
||||
validate: @method:validatePassConfirmMatch
|
||||
}
|
||||
TM12: {
|
||||
argName: submission
|
||||
items: [ "apply", "cancel" ]
|
||||
submit: true
|
||||
}
|
||||
}
|
||||
|
||||
submit: {
|
||||
*: [
|
||||
{
|
||||
value: { "submission" : 0 }
|
||||
action: @method:submitApplication
|
||||
extraArgs: {
|
||||
inactive: userNeedsActivated
|
||||
error: newUserCreateError
|
||||
}
|
||||
}
|
||||
{
|
||||
value: { "submission" : 1 }
|
||||
action: @systemMethod:logoff
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
actionKeys: [
|
||||
{
|
||||
keys: [ "escape" ]
|
||||
action: @systemMethod:logoff
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newUserFeedbackToSysOpPreamble: {
|
||||
art: LETTER
|
||||
config: { pause: true }
|
||||
next: newUserFeedbackToSysOp
|
||||
}
|
||||
|
||||
newUserFeedbackToSysOp: {
|
||||
desc: Feedback to SysOp
|
||||
module: msg_area_post_fse
|
||||
next: [
|
||||
{
|
||||
acs: AS2
|
||||
next: fullLoginSequenceLoginArt
|
||||
}
|
||||
{
|
||||
next: newUserInactiveDone
|
||||
}
|
||||
]
|
||||
config: {
|
||||
art: {
|
||||
header: MSGEHDR
|
||||
body: MSGBODY
|
||||
footerEditor: MSGEFTR
|
||||
footerEditorMenu: MSGEMFT
|
||||
help: MSGEHLP
|
||||
},
|
||||
editorMode: edit
|
||||
editorType: email
|
||||
messageAreaTag: private_mail
|
||||
toUserId: 1 /* always to +op */
|
||||
}
|
||||
form: {
|
||||
0: {
|
||||
mci: {
|
||||
TL1: {
|
||||
argName: from
|
||||
}
|
||||
ET2: {
|
||||
argName: to
|
||||
focus: true
|
||||
text: @sysStat:sysop_username
|
||||
maxLength: 36
|
||||
// :TODO: readOnly: true
|
||||
}
|
||||
ET3: {
|
||||
argName: subject
|
||||
maxLength: 72
|
||||
submit: true
|
||||
text: New user feedback
|
||||
validate: @systemMethod:validateMessageSubject
|
||||
}
|
||||
}
|
||||
submit: {
|
||||
3: [
|
||||
{
|
||||
value: { subject: null }
|
||||
action: @method:headerSubmit
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
1: {
|
||||
mci: {
|
||||
MT1: {
|
||||
width: 79
|
||||
argName: message
|
||||
mode: edit
|
||||
}
|
||||
}
|
||||
|
||||
submit: {
|
||||
*: [
|
||||
{
|
||||
value: message
|
||||
action: @method:editModeEscPressed
|
||||
}
|
||||
]
|
||||
}
|
||||
actionKeys: [
|
||||
{
|
||||
keys: [ "escape" ]
|
||||
viewId: 1
|
||||
}
|
||||
]
|
||||
},
|
||||
2: {
|
||||
TLTL: {
|
||||
mci: {
|
||||
TL1: {
|
||||
width: 5
|
||||
}
|
||||
TL2: {
|
||||
width: 4
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3: {
|
||||
HM: {
|
||||
mci: {
|
||||
HM1: {
|
||||
// :TODO: clear
|
||||
items: [ "save", "help" ]
|
||||
}
|
||||
}
|
||||
submit: {
|
||||
*: [
|
||||
{
|
||||
value: { 1: 0 }
|
||||
action: @method:editModeMenuSave
|
||||
}
|
||||
{
|
||||
value: { 1: 1 }
|
||||
action: @method:editModeMenuHelp
|
||||
}
|
||||
]
|
||||
}
|
||||
actionKeys: [
|
||||
{
|
||||
keys: [ "escape" ]
|
||||
action: @method:editModeEscPressed
|
||||
}
|
||||
{
|
||||
keys: [ "?" ]
|
||||
action: @method:editModeMenuHelp
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newUserInactiveDone: {
|
||||
desc: Finished with NUA
|
||||
art: DONE
|
||||
config: { pause: true }
|
||||
next: @menu:logoff
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,197 @@
|
|||
{
|
||||
menus: {
|
||||
privateMailMenu: {
|
||||
art: MAILMNU
|
||||
desc: Private Mail
|
||||
prompt: menuCommand
|
||||
config: {
|
||||
interrupt: realtime
|
||||
}
|
||||
submit: [
|
||||
{
|
||||
value: { command: "C" }
|
||||
action: @menu:privateMailMenuCreateMessage
|
||||
}
|
||||
{
|
||||
value: { command: "I" }
|
||||
action: @menu:privateMailMenuInbox
|
||||
}
|
||||
{
|
||||
value: { command: "Q" }
|
||||
action: @systemMethod:prevMenu
|
||||
}
|
||||
{
|
||||
value: { command: "G" }
|
||||
action: @menu:fullLogoffSequence
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
privateMailMenuCreateMessage: {
|
||||
desc: Mailing Someone
|
||||
module: msg_area_post_fse
|
||||
config: {
|
||||
art: {
|
||||
header: MSGEHDR
|
||||
body: MSGBODY
|
||||
footerEditor: MSGEFTR
|
||||
footerEditorMenu: MSGEMFT
|
||||
help: MSGEHLP
|
||||
},
|
||||
editorMode: edit
|
||||
editorType: email
|
||||
messageAreaTag: private_mail
|
||||
}
|
||||
form: {
|
||||
0: {
|
||||
mci: {
|
||||
TL1: {
|
||||
argName: from
|
||||
}
|
||||
ET2: {
|
||||
argName: to
|
||||
focus: true
|
||||
validate: @systemMethod:validateGeneralMailAddressedTo
|
||||
maxLength: 36
|
||||
}
|
||||
ET3: {
|
||||
argName: subject
|
||||
maxLength: 72
|
||||
submit: true
|
||||
validate: @systemMethod:validateMessageSubject
|
||||
}
|
||||
}
|
||||
submit: {
|
||||
3: [
|
||||
{
|
||||
value: { subject: null }
|
||||
action: @method:headerSubmit
|
||||
}
|
||||
]
|
||||
}
|
||||
actionKeys: @reference: common.escToPrev
|
||||
}
|
||||
1: {
|
||||
mci: {
|
||||
MT1: {
|
||||
width: 79
|
||||
argName: message
|
||||
mode: edit
|
||||
}
|
||||
}
|
||||
|
||||
submit: {
|
||||
*: [ { value: "message", action: "@method:editModeEscPressed" } ]
|
||||
}
|
||||
actionKeys: [
|
||||
{
|
||||
keys: [ "escape" ]
|
||||
viewId: 1
|
||||
}
|
||||
]
|
||||
},
|
||||
2: {
|
||||
TLTL: {
|
||||
mci: {
|
||||
TL1: {
|
||||
width: 5
|
||||
}
|
||||
TL2: {
|
||||
width: 4
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3: {
|
||||
HM: {
|
||||
mci: {
|
||||
HM1: {
|
||||
// :TODO: clear
|
||||
items: [ "save", "discard", "help" ]
|
||||
}
|
||||
}
|
||||
submit: {
|
||||
*: [
|
||||
{
|
||||
value: { 1: 0 }
|
||||
action: @method:editModeMenuSave
|
||||
}
|
||||
{
|
||||
value: { 1: 1 }
|
||||
action: @systemMethod:prevMenu
|
||||
}
|
||||
{
|
||||
value: { 1: 2 }
|
||||
action: @method:editModeMenuHelp
|
||||
}
|
||||
]
|
||||
}
|
||||
actionKeys: [
|
||||
{
|
||||
keys: [ "escape" ]
|
||||
action: @method:editModeEscPressed
|
||||
}
|
||||
{
|
||||
keys: [ "?" ]
|
||||
action: @method:editModeMenuHelp
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
privateMailMenuInbox: {
|
||||
module: msg_list
|
||||
art: PRVMSGLIST
|
||||
config: {
|
||||
menuViewPost: messageAreaViewPost
|
||||
messageAreaTag: private_mail
|
||||
}
|
||||
form: {
|
||||
0: { // main list
|
||||
mci: {
|
||||
VM1: {
|
||||
focus: true
|
||||
submit: true
|
||||
argName: messageIndex
|
||||
}
|
||||
}
|
||||
submit: {
|
||||
*: [
|
||||
{
|
||||
value: { messageIndex: null }
|
||||
action: @method:selectMessage
|
||||
}
|
||||
]
|
||||
}
|
||||
actionKeys: [
|
||||
{
|
||||
keys: [ "escape", "q", "shift + q" ]
|
||||
action: @systemMethod:prevMenu
|
||||
}
|
||||
{
|
||||
keys: [ "delete", "d", "shift + d" ]
|
||||
action: @method:deleteSelected
|
||||
}
|
||||
]
|
||||
}
|
||||
1: { // delete prompt form
|
||||
submit: {
|
||||
*: [
|
||||
{
|
||||
value: { promptValue: 0 }
|
||||
action: @method:deleteMessageYes
|
||||
}
|
||||
{
|
||||
value: { promptValue: 1 }
|
||||
action: @method:deleteMessageNo
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,294 +0,0 @@
|
|||
{
|
||||
/*
|
||||
./\/\.' ENiGMA½ Prompt Configuration -/--/-------- - -- -
|
||||
|
||||
_____________________ _____ ____________________ __________\_ /
|
||||
\__ ____/\_ ____ \ /____/ / _____ __ \ / ______/ // /___jp!
|
||||
// __|___// | \// |// | \// | | \// \ /___ /_____
|
||||
/____ _____| __________ ___|__| ____| \ / _____ \
|
||||
---- \______\ -- |______\ ------ /______/ ---- |______\ - |______\ /__/ // ___/
|
||||
/__ _\
|
||||
<*> ENiGMA½ // HTTPS://GITHUB.COM/NUSKOOLER/ENIGMA-BBS <*> /__/
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
This configuration is in HJSON (http://hjson.org/) format. Strict to-spec
|
||||
JSON is also perfectly valid. Use 'hjson' from npm to convert to/from JSON.
|
||||
|
||||
See http://hjson.org/ for more information and syntax.
|
||||
|
||||
|
||||
If you haven't yet, copy the conents of this file to something like
|
||||
sick_board_prompt.hjson. Point to it via config.hjson using the
|
||||
'general.promptFile' key:
|
||||
|
||||
general: { promptFile: "sick_board_prompt.hjson" }
|
||||
|
||||
*/
|
||||
// :TODO: this entire file needs cleaned up a LOT
|
||||
// :TODO: Convert all of this to HJSON
|
||||
prompts: {
|
||||
userCredentials: {
|
||||
"art" : "usercred",
|
||||
"mci" : {
|
||||
"ET1" : {
|
||||
"argName" : "username",
|
||||
"maxLength" : "@config:users.usernameMax"
|
||||
},
|
||||
"ET2" : {
|
||||
"submit" : true,
|
||||
"argName" : "password",
|
||||
"password" : true,
|
||||
"maxLength" : "@config:users.passwordMax"
|
||||
}
|
||||
}
|
||||
},
|
||||
"userLoginCredentials" : {
|
||||
"art" : "USRCRED",
|
||||
"mci" : {
|
||||
"ET1" : {
|
||||
"argName" : "username",
|
||||
"maxLength" : "@config:users.usernameMax"
|
||||
},
|
||||
"ET2" : {
|
||||
"submit" : true,
|
||||
"argName" : "password",
|
||||
"password" : true,
|
||||
"maxLength" : "@config:users.passwordMax"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
logoffConfirmation: {
|
||||
art: LOGPMPT
|
||||
mci: {
|
||||
TM1: {
|
||||
argName: promptValue
|
||||
items: [ "yes", "no" ]
|
||||
focus: true
|
||||
hotKeys: { Y: 0, N: 1 }
|
||||
hotKeySubmit: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loginSequenceFlavorSelect: {
|
||||
art: LOGINSEL
|
||||
mci: {
|
||||
TM1: {
|
||||
argName: promptValue
|
||||
items: [ "yes", "no" ]
|
||||
focus: true
|
||||
focusItemIndex: 1
|
||||
hotKeys: { Y: 0, N: 1 }
|
||||
hotKeySubmit: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loginGlobalNewScan: {
|
||||
art: GNSPMPT
|
||||
mci: {
|
||||
TM1: {
|
||||
argName: promptValue
|
||||
items: [ "yes", "no" ]
|
||||
focus: true
|
||||
hotKeys: { Y: 0, N: 1 }
|
||||
hotKeySubmit: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
menuCommand: {
|
||||
art: MNUPRMT
|
||||
mci: {
|
||||
TL1: {
|
||||
// theme me!
|
||||
}
|
||||
ET2: {
|
||||
argName: command
|
||||
width: 20
|
||||
maxLength: 20
|
||||
submit: true
|
||||
textStyle: upper
|
||||
focus: true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
messageMenuCommand: {
|
||||
art: MSGPMPT
|
||||
mci: {
|
||||
TL1: {
|
||||
// theme me!
|
||||
}
|
||||
ET2: {
|
||||
argName: command
|
||||
width: 20
|
||||
maxLength: 20
|
||||
submit: true
|
||||
textStyle: upper
|
||||
focus: true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
deleteMessageFromListPrompt: {
|
||||
art: MSGDELPMPT
|
||||
mci: {
|
||||
TM1: {
|
||||
argName: promptValue
|
||||
items: [ "yes", "no" ]
|
||||
focus: true
|
||||
hotKeys: { Y: 0, N: 1 }
|
||||
hotKeySubmit: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"newAreaPostPrompt" : {
|
||||
"art" : "message_area_new_post",
|
||||
"mci" : {
|
||||
"ET1" : {
|
||||
"argName" : "to",
|
||||
"width" : 20
|
||||
},
|
||||
"ET2" : {
|
||||
"argName" : "subject",
|
||||
"width" : 20
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
forgotPasswordPrompt: {
|
||||
art: FORGOTPW
|
||||
mci: {
|
||||
ET1: {
|
||||
argName: username
|
||||
maxLength: @config:users.usernameMax
|
||||
width: 32
|
||||
focus: true
|
||||
}
|
||||
}
|
||||
actionKeys: [
|
||||
{
|
||||
keys: [ "escape" ]
|
||||
action: @systemMethod:prevMenu
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// File Base Related
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
fileMenuCommand: {
|
||||
art: FILPMPT
|
||||
mci: {
|
||||
TL1: {}
|
||||
ET2: {
|
||||
argName: menuOption
|
||||
width: 20
|
||||
maxLength: 20
|
||||
textStyle: upper
|
||||
focus: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileBaseRateEntryPrompt: {
|
||||
art: RATEFILE
|
||||
mci: {
|
||||
SM1: {
|
||||
argName: rating
|
||||
items: [ "-----", "*----", "**---", "***--", "****-", "*****" ]
|
||||
}
|
||||
}
|
||||
|
||||
actionKeys: [
|
||||
{
|
||||
keys: [ "escape" ]
|
||||
action: @systemMethod:prevMenu
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
fileBaseTagEntryPrompt: {
|
||||
art: TAGFILE
|
||||
mci: {
|
||||
ET1: {
|
||||
argName: tags
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// Standard / Required
|
||||
//
|
||||
// Prompts in this section are considered "standard" and are required
|
||||
// to be present
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
pause: {
|
||||
//
|
||||
// Any menu 'pause' will use this prompt
|
||||
//
|
||||
art: pause
|
||||
config: {
|
||||
trailingLF: no
|
||||
}
|
||||
/*
|
||||
"mci" : {
|
||||
// :TODO: Need special pause for a key MCI
|
||||
// e.g. %PA -> themed prompt
|
||||
}
|
||||
|
||||
...or maybe pause should just be special:
|
||||
{
|
||||
...
|
||||
"pause" true
|
||||
// uses theme pause which can be art/inline/etc.
|
||||
|
||||
}
|
||||
|
||||
... better, a special prompt
|
||||
|
||||
GetKeyView
|
||||
* echoKey : false
|
||||
|
||||
*/
|
||||
}
|
||||
/*,
|
||||
"standard" : {
|
||||
// any menu 'pause' will display this, pause for a key, then erase and move on
|
||||
"pause" : {
|
||||
"art" : "pause"
|
||||
// :TODO: support mci mappings
|
||||
}
|
||||
},
|
||||
"custom" : {
|
||||
|
||||
}*/
|
||||
/*
|
||||
see notes in menu_module.js also
|
||||
...how to allow for this to come from the theme first???
|
||||
same as custom vc drawing/etc.? ...
|
||||
|
||||
{
|
||||
"theme" : {
|
||||
"inlineArt" : {
|
||||
"something" : "%MC and |01Pipe codes here"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"pause" : {
|
||||
"art" : "@inline:simplePrompt",
|
||||
// support pipe codes & MCI
|
||||
"simplePrompt" : "--------/ Pause /----------------",
|
||||
"mci" : {
|
||||
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@
|
|||
"binary-parser": "^1.6.2",
|
||||
"buffers": "github:NuSkooler/node-buffers",
|
||||
"bunyan": "^1.8.12",
|
||||
"deepdash": "^5.1.1",
|
||||
"exiftool": "^0.0.3",
|
||||
"fs-extra": "9.0.1",
|
||||
"glob": "7.1.6",
|
||||
|
@ -60,7 +61,9 @@
|
|||
"xxhash": "^0.3.0",
|
||||
"yazl": "^2.5.1"
|
||||
},
|
||||
"devDependencies": {},
|
||||
"devDependencies": {
|
||||
"eslint": "^7.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
|
|
545
yarn.lock
545
yarn.lock
|
@ -2,6 +2,27 @@
|
|||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@babel/code-frame@^7.0.0":
|
||||
version "7.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.1.tgz#d5481c5095daa1c57e16e54c6f9198443afb49ff"
|
||||
integrity sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw==
|
||||
dependencies:
|
||||
"@babel/highlight" "^7.10.1"
|
||||
|
||||
"@babel/helper-validator-identifier@^7.10.1":
|
||||
version "7.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz#5770b0c1a826c4f53f5ede5e153163e0318e94b5"
|
||||
integrity sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==
|
||||
|
||||
"@babel/highlight@^7.10.1":
|
||||
version "7.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.1.tgz#841d098ba613ba1a427a2b383d79e35552c38ae0"
|
||||
integrity sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg==
|
||||
dependencies:
|
||||
"@babel/helper-validator-identifier" "^7.10.1"
|
||||
chalk "^2.0.0"
|
||||
js-tokens "^4.0.0"
|
||||
|
||||
"@cnakazawa/watch@^1.0.3":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.3.tgz#099139eaec7ebf07a27c1786a3ff64f39464d2ef"
|
||||
|
@ -20,6 +41,26 @@ abbrev@1:
|
|||
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
|
||||
integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
|
||||
|
||||
acorn-jsx@^5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.2.0.tgz#4c66069173d6fdd68ed85239fc256226182b2ebe"
|
||||
integrity sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==
|
||||
|
||||
acorn@^7.2.0:
|
||||
version "7.3.1"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.3.1.tgz#85010754db53c3fbaf3b9ea3e083aa5c5d147ffd"
|
||||
integrity sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA==
|
||||
|
||||
ajv@^6.10.0, ajv@^6.10.2:
|
||||
version "6.12.2"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd"
|
||||
integrity sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==
|
||||
dependencies:
|
||||
fast-deep-equal "^3.1.1"
|
||||
fast-json-stable-stringify "^2.0.0"
|
||||
json-schema-traverse "^0.4.1"
|
||||
uri-js "^4.2.2"
|
||||
|
||||
ansi-escapes@^4.2.1:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.2.1.tgz#4dccdb846c3eee10f6d64dea66273eab90c37228"
|
||||
|
@ -47,6 +88,13 @@ ansi-regex@^5.0.0:
|
|||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75"
|
||||
integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==
|
||||
|
||||
ansi-styles@^3.2.0, ansi-styles@^3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
|
||||
integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
|
||||
dependencies:
|
||||
color-convert "^1.9.0"
|
||||
|
||||
ansi-styles@^4.1.0:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359"
|
||||
|
@ -76,6 +124,13 @@ are-we-there-yet@~1.1.2:
|
|||
delegates "^1.0.0"
|
||||
readable-stream "^2.0.6"
|
||||
|
||||
argparse@^1.0.7:
|
||||
version "1.0.10"
|
||||
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
|
||||
integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==
|
||||
dependencies:
|
||||
sprintf-js "~1.0.2"
|
||||
|
||||
arr-diff@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520"
|
||||
|
@ -120,6 +175,11 @@ assign-symbols@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
|
||||
integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=
|
||||
|
||||
astral-regex@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9"
|
||||
integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==
|
||||
|
||||
async@3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720"
|
||||
|
@ -230,6 +290,11 @@ cache-base@^1.0.1:
|
|||
union-value "^1.0.0"
|
||||
unset-value "^1.0.0"
|
||||
|
||||
callsites@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
|
||||
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
|
||||
|
||||
capture-exit@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4"
|
||||
|
@ -237,6 +302,15 @@ capture-exit@^2.0.0:
|
|||
dependencies:
|
||||
rsvp "^4.8.4"
|
||||
|
||||
chalk@^2.0.0:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
|
||||
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
|
||||
dependencies:
|
||||
ansi-styles "^3.2.1"
|
||||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^5.3.0"
|
||||
|
||||
chalk@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4"
|
||||
|
@ -245,6 +319,14 @@ chalk@^3.0.0:
|
|||
ansi-styles "^4.1.0"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
chalk@^4.0.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a"
|
||||
integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==
|
||||
dependencies:
|
||||
ansi-styles "^4.1.0"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
chardet@^0.7.0:
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
|
||||
|
@ -295,6 +377,13 @@ collection-visit@^1.0.0:
|
|||
map-visit "^1.0.0"
|
||||
object-visit "^1.0.0"
|
||||
|
||||
color-convert@^1.9.0:
|
||||
version "1.9.3"
|
||||
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
|
||||
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
|
||||
dependencies:
|
||||
color-name "1.1.3"
|
||||
|
||||
color-convert@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
|
||||
|
@ -302,6 +391,11 @@ color-convert@^2.0.1:
|
|||
dependencies:
|
||||
color-name "~1.1.4"
|
||||
|
||||
color-name@1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
|
||||
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
|
||||
|
||||
color-name@~1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
|
||||
|
@ -343,6 +437,15 @@ cross-spawn@^6.0.0:
|
|||
shebang-command "^1.2.0"
|
||||
which "^1.2.9"
|
||||
|
||||
cross-spawn@^7.0.2:
|
||||
version "7.0.3"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
|
||||
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
|
||||
dependencies:
|
||||
path-key "^3.1.0"
|
||||
shebang-command "^2.0.0"
|
||||
which "^2.0.1"
|
||||
|
||||
debug@^2.1.2, debug@^2.2.0, debug@^2.3.3:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||
|
@ -357,6 +460,13 @@ debug@^4.0.0:
|
|||
dependencies:
|
||||
ms "^2.1.1"
|
||||
|
||||
debug@^4.0.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
|
||||
integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
|
||||
dependencies:
|
||||
ms "^2.1.1"
|
||||
|
||||
decode-uri-component@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
|
||||
|
@ -372,6 +482,19 @@ deep-extend@^0.6.0:
|
|||
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
|
||||
integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
|
||||
|
||||
deep-is@^0.1.3:
|
||||
version "0.1.3"
|
||||
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
|
||||
integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=
|
||||
|
||||
deepdash@^5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/deepdash/-/deepdash-5.1.1.tgz#dcf68b9e15085b5df18bdb4011723790e0c40430"
|
||||
integrity sha512-esz3pjQJaeYO4z74seqCMrOYUsAAdrhO3KJuEnGEaxTGbSy8VGOWn7jTU2J3nR5WDyNpS5/hse3m/hdM1/8ZWA==
|
||||
dependencies:
|
||||
lodash "^4.17.15"
|
||||
lodash-es "^4.17.15"
|
||||
|
||||
define-property@^0.2.5:
|
||||
version "0.2.5"
|
||||
resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116"
|
||||
|
@ -426,6 +549,13 @@ detect-libc@^1.0.2:
|
|||
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
|
||||
integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=
|
||||
|
||||
doctrine@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"
|
||||
integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==
|
||||
dependencies:
|
||||
esutils "^2.0.2"
|
||||
|
||||
dtrace-provider@~0.8:
|
||||
version "0.8.7"
|
||||
resolved "https://registry.yarnpkg.com/dtrace-provider/-/dtrace-provider-0.8.7.tgz#dc939b4d3e0620cfe0c1cd803d0d2d7ed04ffd04"
|
||||
|
@ -433,6 +563,11 @@ dtrace-provider@~0.8:
|
|||
dependencies:
|
||||
nan "^2.10.0"
|
||||
|
||||
emoji-regex@^7.0.1:
|
||||
version "7.0.3"
|
||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
|
||||
integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==
|
||||
|
||||
emoji-regex@^8.0.0:
|
||||
version "8.0.0"
|
||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
|
||||
|
@ -450,6 +585,111 @@ escape-string-regexp@^1.0.5:
|
|||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
|
||||
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
|
||||
|
||||
eslint-scope@^5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.0.tgz#d0f971dfe59c69e0cada684b23d49dbf82600ce5"
|
||||
integrity sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==
|
||||
dependencies:
|
||||
esrecurse "^4.1.0"
|
||||
estraverse "^4.1.1"
|
||||
|
||||
eslint-utils@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.0.0.tgz#7be1cc70f27a72a76cd14aa698bcabed6890e1cd"
|
||||
integrity sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA==
|
||||
dependencies:
|
||||
eslint-visitor-keys "^1.1.0"
|
||||
|
||||
eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.2.0.tgz#74415ac884874495f78ec2a97349525344c981fa"
|
||||
integrity sha512-WFb4ihckKil6hu3Dp798xdzSfddwKKU3+nGniKF6HfeW6OLd2OUDEPP7TcHtB5+QXOKg2s6B2DaMPE1Nn/kxKQ==
|
||||
|
||||
eslint@^7.2.0:
|
||||
version "7.2.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.2.0.tgz#d41b2e47804b30dbabb093a967fb283d560082e6"
|
||||
integrity sha512-B3BtEyaDKC5MlfDa2Ha8/D6DsS4fju95zs0hjS3HdGazw+LNayai38A25qMppK37wWGWNYSPOR6oYzlz5MHsRQ==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.0.0"
|
||||
ajv "^6.10.0"
|
||||
chalk "^4.0.0"
|
||||
cross-spawn "^7.0.2"
|
||||
debug "^4.0.1"
|
||||
doctrine "^3.0.0"
|
||||
eslint-scope "^5.1.0"
|
||||
eslint-utils "^2.0.0"
|
||||
eslint-visitor-keys "^1.2.0"
|
||||
espree "^7.1.0"
|
||||
esquery "^1.2.0"
|
||||
esutils "^2.0.2"
|
||||
file-entry-cache "^5.0.1"
|
||||
functional-red-black-tree "^1.0.1"
|
||||
glob-parent "^5.0.0"
|
||||
globals "^12.1.0"
|
||||
ignore "^4.0.6"
|
||||
import-fresh "^3.0.0"
|
||||
imurmurhash "^0.1.4"
|
||||
inquirer "^7.0.0"
|
||||
is-glob "^4.0.0"
|
||||
js-yaml "^3.13.1"
|
||||
json-stable-stringify-without-jsonify "^1.0.1"
|
||||
levn "^0.4.1"
|
||||
lodash "^4.17.14"
|
||||
minimatch "^3.0.4"
|
||||
natural-compare "^1.4.0"
|
||||
optionator "^0.9.1"
|
||||
progress "^2.0.0"
|
||||
regexpp "^3.1.0"
|
||||
semver "^7.2.1"
|
||||
strip-ansi "^6.0.0"
|
||||
strip-json-comments "^3.1.0"
|
||||
table "^5.2.3"
|
||||
text-table "^0.2.0"
|
||||
v8-compile-cache "^2.0.3"
|
||||
|
||||
espree@^7.1.0:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/espree/-/espree-7.1.0.tgz#a9c7f18a752056735bf1ba14cb1b70adc3a5ce1c"
|
||||
integrity sha512-dcorZSyfmm4WTuTnE5Y7MEN1DyoPYy1ZR783QW1FJoenn7RailyWFsq/UL6ZAAA7uXurN9FIpYyUs3OfiIW+Qw==
|
||||
dependencies:
|
||||
acorn "^7.2.0"
|
||||
acorn-jsx "^5.2.0"
|
||||
eslint-visitor-keys "^1.2.0"
|
||||
|
||||
esprima@^4.0.0:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
|
||||
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
|
||||
|
||||
esquery@^1.2.0:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57"
|
||||
integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==
|
||||
dependencies:
|
||||
estraverse "^5.1.0"
|
||||
|
||||
esrecurse@^4.1.0:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf"
|
||||
integrity sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==
|
||||
dependencies:
|
||||
estraverse "^4.1.0"
|
||||
|
||||
estraverse@^4.1.0, estraverse@^4.1.1:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
|
||||
integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
|
||||
|
||||
estraverse@^5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.1.0.tgz#374309d39fd935ae500e7b92e8a6b4c720e59642"
|
||||
integrity sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw==
|
||||
|
||||
esutils@^2.0.2:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
|
||||
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
|
||||
|
||||
exec-sh@^0.3.2:
|
||||
version "0.3.2"
|
||||
resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.2.tgz#6738de2eb7c8e671d0366aea0b0db8c6f7d7391b"
|
||||
|
@ -524,6 +764,21 @@ extglob@^2.0.4:
|
|||
snapdragon "^0.8.1"
|
||||
to-regex "^3.0.1"
|
||||
|
||||
fast-deep-equal@^3.1.1:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
||||
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
|
||||
|
||||
fast-json-stable-stringify@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
|
||||
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
|
||||
|
||||
fast-levenshtein@^2.0.6:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
|
||||
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
|
||||
|
||||
fb-watchman@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.0.tgz#54e9abf7dfa2f26cd9b1636c588c1afc05de5d58"
|
||||
|
@ -538,6 +793,13 @@ figures@^3.0.0:
|
|||
dependencies:
|
||||
escape-string-regexp "^1.0.5"
|
||||
|
||||
file-entry-cache@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c"
|
||||
integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==
|
||||
dependencies:
|
||||
flat-cache "^2.0.1"
|
||||
|
||||
fill-range@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7"
|
||||
|
@ -548,6 +810,20 @@ fill-range@^4.0.0:
|
|||
repeat-string "^1.6.1"
|
||||
to-regex-range "^2.1.0"
|
||||
|
||||
flat-cache@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0"
|
||||
integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==
|
||||
dependencies:
|
||||
flatted "^2.0.0"
|
||||
rimraf "2.6.3"
|
||||
write "1.0.3"
|
||||
|
||||
flatted@^2.0.0:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138"
|
||||
integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==
|
||||
|
||||
for-in@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
|
||||
|
@ -590,6 +866,11 @@ fs.realpath@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
|
||||
|
||||
functional-red-black-tree@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
|
||||
integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
|
||||
|
||||
gauge@~2.7.3:
|
||||
version "2.7.4"
|
||||
resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7"
|
||||
|
@ -616,7 +897,14 @@ get-value@^2.0.3, get-value@^2.0.6:
|
|||
resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
|
||||
integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=
|
||||
|
||||
glob@7.1.6:
|
||||
glob-parent@^5.0.0:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229"
|
||||
integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==
|
||||
dependencies:
|
||||
is-glob "^4.0.1"
|
||||
|
||||
glob@7.1.6, glob@^7.1.3:
|
||||
version "7.1.6"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
|
||||
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
|
||||
|
@ -651,6 +939,13 @@ glob@^7.0.3, glob@^7.0.5, glob@^7.1.2:
|
|||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
globals@^12.1.0:
|
||||
version "12.4.0"
|
||||
resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8"
|
||||
integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==
|
||||
dependencies:
|
||||
type-fest "^0.8.1"
|
||||
|
||||
globby@^6.1.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c"
|
||||
|
@ -677,6 +972,11 @@ graceful-fs@^4.2.4:
|
|||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
|
||||
integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
|
||||
|
||||
has-flag@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
|
||||
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
|
||||
|
||||
has-flag@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
|
||||
|
@ -749,6 +1049,24 @@ ignore-walk@^3.0.1:
|
|||
dependencies:
|
||||
minimatch "^3.0.4"
|
||||
|
||||
ignore@^4.0.6:
|
||||
version "4.0.6"
|
||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
|
||||
integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
|
||||
|
||||
import-fresh@^3.0.0:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66"
|
||||
integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==
|
||||
dependencies:
|
||||
parent-module "^1.0.0"
|
||||
resolve-from "^4.0.0"
|
||||
|
||||
imurmurhash@^0.1.4:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
|
||||
integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
|
||||
|
||||
inflight@^1.0.4:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
|
||||
|
@ -776,7 +1094,7 @@ ini@~1.3.0:
|
|||
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
|
||||
integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
|
||||
|
||||
inquirer@^7.1.0:
|
||||
inquirer@^7.0.0, inquirer@^7.1.0:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.1.0.tgz#1298a01859883e17c7264b82870ae1034f92dd29"
|
||||
integrity sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==
|
||||
|
@ -858,6 +1176,11 @@ is-extendable@^1.0.1:
|
|||
dependencies:
|
||||
is-plain-object "^2.0.4"
|
||||
|
||||
is-extglob@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
|
||||
integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
|
||||
|
||||
is-fullwidth-code-point@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb"
|
||||
|
@ -875,6 +1198,13 @@ is-fullwidth-code-point@^3.0.0:
|
|||
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
|
||||
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
|
||||
|
||||
is-glob@^4.0.0, is-glob@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
|
||||
integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
|
||||
dependencies:
|
||||
is-extglob "^2.1.1"
|
||||
|
||||
is-number@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195"
|
||||
|
@ -940,6 +1270,29 @@ isobject@^3.0.0, isobject@^3.0.1:
|
|||
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
|
||||
integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8=
|
||||
|
||||
js-tokens@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
||||
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
|
||||
|
||||
js-yaml@^3.13.1:
|
||||
version "3.14.0"
|
||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482"
|
||||
integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==
|
||||
dependencies:
|
||||
argparse "^1.0.7"
|
||||
esprima "^4.0.0"
|
||||
|
||||
json-schema-traverse@^0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
|
||||
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
|
||||
|
||||
json-stable-stringify-without-jsonify@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
|
||||
integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=
|
||||
|
||||
jsonfile@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.0.1.tgz#98966cba214378c8c84b82e085907b40bf614179"
|
||||
|
@ -978,7 +1331,20 @@ later@1.2.0:
|
|||
resolved "https://registry.yarnpkg.com/later/-/later-1.2.0.tgz#f2cf6c4dd7956dd2f520adf0329836e9876bad0f"
|
||||
integrity sha1-8s9sTdeVbdL1IK3wMpg26YdrrQ8=
|
||||
|
||||
lodash@^4.17.15:
|
||||
levn@^0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade"
|
||||
integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==
|
||||
dependencies:
|
||||
prelude-ls "^1.2.1"
|
||||
type-check "~0.4.0"
|
||||
|
||||
lodash-es@^4.17.15:
|
||||
version "4.17.15"
|
||||
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78"
|
||||
integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==
|
||||
|
||||
lodash@^4.17.14, lodash@^4.17.15:
|
||||
version "4.17.15"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
|
||||
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
|
||||
|
@ -1158,6 +1524,11 @@ nanomatch@^1.2.9:
|
|||
snapdragon "^0.8.1"
|
||||
to-regex "^3.0.1"
|
||||
|
||||
natural-compare@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
||||
integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
|
||||
|
||||
ncp@~2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3"
|
||||
|
@ -1318,6 +1689,18 @@ onetime@^5.1.0:
|
|||
dependencies:
|
||||
mimic-fn "^2.1.0"
|
||||
|
||||
optionator@^0.9.1:
|
||||
version "0.9.1"
|
||||
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499"
|
||||
integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==
|
||||
dependencies:
|
||||
deep-is "^0.1.3"
|
||||
fast-levenshtein "^2.0.6"
|
||||
levn "^0.4.1"
|
||||
prelude-ls "^1.2.1"
|
||||
type-check "^0.4.0"
|
||||
word-wrap "^1.2.3"
|
||||
|
||||
os-homedir@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
|
||||
|
@ -1353,6 +1736,13 @@ p-map@^1.1.1:
|
|||
resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b"
|
||||
integrity sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==
|
||||
|
||||
parent-module@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
|
||||
integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==
|
||||
dependencies:
|
||||
callsites "^3.0.0"
|
||||
|
||||
pascalcase@^0.1.1:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14"
|
||||
|
@ -1373,6 +1763,11 @@ path-key@^2.0.0, path-key@^2.0.1:
|
|||
resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
|
||||
integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=
|
||||
|
||||
path-key@^3.1.0:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
|
||||
integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
|
||||
|
||||
pify@^2.0.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
|
||||
|
@ -1400,11 +1795,21 @@ posix-character-classes@^0.1.0:
|
|||
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
|
||||
integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=
|
||||
|
||||
prelude-ls@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
||||
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
|
||||
|
||||
process-nextick-args@~2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
|
||||
integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==
|
||||
|
||||
progress@^2.0.0:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
|
||||
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
|
||||
|
||||
pump@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
|
||||
|
@ -1413,6 +1818,11 @@ pump@^3.0.0:
|
|||
end-of-stream "^1.1.0"
|
||||
once "^1.3.1"
|
||||
|
||||
punycode@^2.1.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
||||
|
||||
qrcode-generator@^1.4.4:
|
||||
version "1.4.4"
|
||||
resolved "https://registry.yarnpkg.com/qrcode-generator/-/qrcode-generator-1.4.4.tgz#63f771224854759329a99048806a53ed278740e7"
|
||||
|
@ -1458,6 +1868,11 @@ regex-not@^1.0.0, regex-not@^1.0.2:
|
|||
extend-shallow "^3.0.2"
|
||||
safe-regex "^1.1.0"
|
||||
|
||||
regexpp@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2"
|
||||
integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==
|
||||
|
||||
remove-trailing-separator@^1.0.1:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
|
||||
|
@ -1473,6 +1888,11 @@ repeat-string@^1.6.1:
|
|||
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
|
||||
integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc=
|
||||
|
||||
resolve-from@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
|
||||
integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
|
||||
|
||||
resolve-url@^0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
|
||||
|
@ -1491,6 +1911,13 @@ ret@~0.1.10:
|
|||
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
|
||||
integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==
|
||||
|
||||
rimraf@2.6.3:
|
||||
version "2.6.3"
|
||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
|
||||
integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==
|
||||
dependencies:
|
||||
glob "^7.1.3"
|
||||
|
||||
rimraf@^2.2.8, rimraf@^2.6.1:
|
||||
version "2.6.2"
|
||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36"
|
||||
|
@ -1586,6 +2013,11 @@ semver@^5.5.0:
|
|||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
|
||||
integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==
|
||||
|
||||
semver@^7.2.1:
|
||||
version "7.3.2"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938"
|
||||
integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==
|
||||
|
||||
serialize-error@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-2.1.0.tgz#50b679d5635cdf84667bdc8e59af4e5b81d5f60a"
|
||||
|
@ -1623,16 +2055,37 @@ shebang-command@^1.2.0:
|
|||
dependencies:
|
||||
shebang-regex "^1.0.0"
|
||||
|
||||
shebang-command@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
|
||||
integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
|
||||
dependencies:
|
||||
shebang-regex "^3.0.0"
|
||||
|
||||
shebang-regex@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
|
||||
integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=
|
||||
|
||||
shebang-regex@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
|
||||
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
|
||||
|
||||
signal-exit@^3.0.0, signal-exit@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
|
||||
integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=
|
||||
|
||||
slice-ansi@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636"
|
||||
integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==
|
||||
dependencies:
|
||||
ansi-styles "^3.2.0"
|
||||
astral-regex "^1.0.0"
|
||||
is-fullwidth-code-point "^2.0.0"
|
||||
|
||||
snapdragon-node@^2.0.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b"
|
||||
|
@ -1698,6 +2151,11 @@ split2@^3.0.0:
|
|||
dependencies:
|
||||
readable-stream "^3.0.0"
|
||||
|
||||
sprintf-js@~1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
||||
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
|
||||
|
||||
sqlite3-trans@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/sqlite3-trans/-/sqlite3-trans-1.2.2.tgz#faf268cc8d04dfd1a4854d64a70a229bdb50609f"
|
||||
|
@ -1759,6 +2217,15 @@ string-width@^1.0.1:
|
|||
is-fullwidth-code-point "^2.0.0"
|
||||
strip-ansi "^4.0.0"
|
||||
|
||||
string-width@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961"
|
||||
integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==
|
||||
dependencies:
|
||||
emoji-regex "^7.0.1"
|
||||
is-fullwidth-code-point "^2.0.0"
|
||||
strip-ansi "^5.1.0"
|
||||
|
||||
string-width@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.1.0.tgz#ba846d1daa97c3c596155308063e075ed1c99aff"
|
||||
|
@ -1796,7 +2263,7 @@ strip-ansi@^4.0.0:
|
|||
dependencies:
|
||||
ansi-regex "^3.0.0"
|
||||
|
||||
strip-ansi@^5.2.0:
|
||||
strip-ansi@^5.1.0, strip-ansi@^5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
|
||||
integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==
|
||||
|
@ -1815,11 +2282,23 @@ strip-eof@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
|
||||
integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=
|
||||
|
||||
strip-json-comments@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.0.tgz#7638d31422129ecf4457440009fba03f9f9ac180"
|
||||
integrity sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==
|
||||
|
||||
strip-json-comments@~2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
||||
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
|
||||
|
||||
supports-color@^5.3.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
|
||||
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
|
||||
dependencies:
|
||||
has-flag "^3.0.0"
|
||||
|
||||
supports-color@^7.1.0:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1"
|
||||
|
@ -1827,6 +2306,16 @@ supports-color@^7.1.0:
|
|||
dependencies:
|
||||
has-flag "^4.0.0"
|
||||
|
||||
table@^5.2.3:
|
||||
version "5.4.6"
|
||||
resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e"
|
||||
integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==
|
||||
dependencies:
|
||||
ajv "^6.10.2"
|
||||
lodash "^4.17.14"
|
||||
slice-ansi "^2.1.0"
|
||||
string-width "^3.0.0"
|
||||
|
||||
tar@^4:
|
||||
version "4.4.6"
|
||||
resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.6.tgz#63110f09c00b4e60ac8bcfe1bf3c8660235fbc9b"
|
||||
|
@ -1855,6 +2344,11 @@ temptmp@^1.1.0:
|
|||
dependencies:
|
||||
del "^3.0.0"
|
||||
|
||||
text-table@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
|
||||
integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=
|
||||
|
||||
thirty-two@1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/thirty-two/-/thirty-two-1.0.2.tgz#4ca2fffc02a51290d2744b9e3f557693ca6b627a"
|
||||
|
@ -1927,11 +2421,23 @@ tweetnacl@^0.14.3:
|
|||
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
|
||||
integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=
|
||||
|
||||
type-check@^0.4.0, type-check@~0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
|
||||
integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==
|
||||
dependencies:
|
||||
prelude-ls "^1.2.1"
|
||||
|
||||
type-fest@^0.5.2:
|
||||
version "0.5.2"
|
||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.5.2.tgz#d6ef42a0356c6cd45f49485c3b6281fc148e48a2"
|
||||
integrity sha512-DWkS49EQKVX//Tbupb9TFa19c7+MK1XmzkrZUR8TAktmE/DizXoaoJV6TZ/tSIPXipqNiRI6CyAe7x69Jb6RSw==
|
||||
|
||||
type-fest@^0.8.1:
|
||||
version "0.8.1"
|
||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
|
||||
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
|
||||
|
||||
union-value@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4"
|
||||
|
@ -1955,6 +2461,13 @@ unset-value@^1.0.0:
|
|||
has-value "^0.3.1"
|
||||
isobject "^3.0.0"
|
||||
|
||||
uri-js@^4.2.2:
|
||||
version "4.2.2"
|
||||
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0"
|
||||
integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==
|
||||
dependencies:
|
||||
punycode "^2.1.0"
|
||||
|
||||
urix@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
|
||||
|
@ -1985,6 +2498,11 @@ uuid@8.1.0:
|
|||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.1.0.tgz#6f1536eb43249f473abc6bd58ff983da1ca30d8d"
|
||||
integrity sha512-CI18flHDznR0lq54xBycOVmphdCYnQLKn8abKn7PXUiKUGdEd+/l9LWNJmugXel4hXq7S+RMNl34ecyC9TntWg==
|
||||
|
||||
v8-compile-cache@^2.0.3:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745"
|
||||
integrity sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==
|
||||
|
||||
walker@~1.0.5:
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb"
|
||||
|
@ -1999,6 +2517,13 @@ which@^1.2.9:
|
|||
dependencies:
|
||||
isexe "^2.0.0"
|
||||
|
||||
which@^2.0.1:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
|
||||
integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
|
||||
dependencies:
|
||||
isexe "^2.0.0"
|
||||
|
||||
wide-align@^1.1.0:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457"
|
||||
|
@ -2006,11 +2531,23 @@ wide-align@^1.1.0:
|
|||
dependencies:
|
||||
string-width "^1.0.2 || 2"
|
||||
|
||||
word-wrap@^1.2.3:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
|
||||
integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
|
||||
|
||||
wrappy@1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
|
||||
|
||||
write@1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3"
|
||||
integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==
|
||||
dependencies:
|
||||
mkdirp "^0.5.1"
|
||||
|
||||
ws@^7.3.0:
|
||||
version "7.3.0"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.0.tgz#4b2f7f219b3d3737bc1a2fbf145d825b94d38ffd"
|
||||
|
|
Loading…
Reference in New Issue