* Code cleanup and eslint since -- remove unused variables, clean up RegExs, so on...
This commit is contained in:
parent
a106050ba3
commit
ac1433e84b
|
@ -87,11 +87,11 @@ exports.getModule = class AbracadabraModule extends MenuModule {
|
|||
[
|
||||
function validateNodeCount(callback) {
|
||||
if(self.config.nodeMax > 0 &&
|
||||
_.isNumber(activeDoorNodeInstances[self.config.name]) &&
|
||||
_.isNumber(activeDoorNodeInstances[self.config.name]) &&
|
||||
activeDoorNodeInstances[self.config.name] + 1 > self.config.nodeMax)
|
||||
{
|
||||
self.client.log.info(
|
||||
{
|
||||
self.client.log.info(
|
||||
{
|
||||
name : self.config.name,
|
||||
activeCount : activeDoorNodeInstances[self.config.name]
|
||||
},
|
||||
|
@ -118,11 +118,11 @@ exports.getModule = class AbracadabraModule extends MenuModule {
|
|||
} else {
|
||||
activeDoorNodeInstances[self.config.name] = 1;
|
||||
}
|
||||
|
||||
|
||||
callback(null);
|
||||
}
|
||||
},
|
||||
function generateDropfile(callback) {
|
||||
function generateDropfile(callback) {
|
||||
self.dropFile = new DropFile(self.client, self.config.dropFileType);
|
||||
var fullPath = self.dropFile.fullPath;
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ class ACS {
|
|||
constructor(client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
|
||||
check(acs, scope, defaultAcs) {
|
||||
acs = acs ? acs[scope] : defaultAcs;
|
||||
acs = acs || defaultAcs;
|
||||
|
@ -22,7 +22,7 @@ class ACS {
|
|||
} catch(e) {
|
||||
Log.warn( { exception : e, acs : acs }, 'Exception caught checking ACS');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
|
||||
const miscUtil = require('./misc_util.js');
|
||||
const ansi = require('./ansi_term.js');
|
||||
const Log = require('./logger.js').log;
|
||||
|
||||
// deps
|
||||
const events = require('events');
|
||||
const util = require('util');
|
||||
const _ = require('lodash');
|
||||
|
@ -24,7 +26,7 @@ function ANSIEscapeParser(options) {
|
|||
this.graphicRendition = {};
|
||||
|
||||
this.parseState = {
|
||||
re : /(?:\x1b\x5b)([\?=;0-9]*?)([ABCDHJKfhlmnpsu])/g,
|
||||
re : /(?:\x1b\x5b)([?=;0-9]*?)([ABCDHJKfhlmnpsu])/g, // eslint-disable-line no-control-regex
|
||||
};
|
||||
|
||||
options = miscUtil.valueWithDefault(options, {
|
||||
|
@ -46,7 +48,7 @@ function ANSIEscapeParser(options) {
|
|||
self.column = Math.max(self.column, 1);
|
||||
self.column = Math.min(self.column, self.termWidth); // can't move past term width
|
||||
self.row = Math.max(self.row, 1);
|
||||
|
||||
|
||||
self.positionUpdated();
|
||||
};
|
||||
|
||||
|
@ -63,7 +65,7 @@ function ANSIEscapeParser(options) {
|
|||
delete self.savedPosition;
|
||||
|
||||
self.positionUpdated();
|
||||
// self.rowUpdated();
|
||||
// self.rowUpdated();
|
||||
};
|
||||
|
||||
self.clearScreen = function() {
|
||||
|
@ -71,7 +73,7 @@ function ANSIEscapeParser(options) {
|
|||
self.emit('clear screen');
|
||||
};
|
||||
|
||||
/*
|
||||
/*
|
||||
self.rowUpdated = function() {
|
||||
self.emit('row update', self.row + self.scrollBack);
|
||||
};*/
|
||||
|
@ -95,7 +97,7 @@ function ANSIEscapeParser(options) {
|
|||
start = pos;
|
||||
|
||||
self.column = 1;
|
||||
|
||||
|
||||
self.positionUpdated();
|
||||
break;
|
||||
|
||||
|
@ -132,7 +134,7 @@ function ANSIEscapeParser(options) {
|
|||
if(self.column > self.termWidth) {
|
||||
self.column = 1;
|
||||
self.row += 1;
|
||||
|
||||
|
||||
self.positionUpdated();
|
||||
}
|
||||
|
||||
|
@ -142,17 +144,9 @@ function ANSIEscapeParser(options) {
|
|||
}
|
||||
}
|
||||
|
||||
function getProcessedMCI(mci) {
|
||||
if(self.mciReplaceChar.length > 0) {
|
||||
return ansi.getSGRFromGraphicRendition(self.graphicRendition, true) + new Array(mci.length + 1).join(self.mciReplaceChar);
|
||||
} else {
|
||||
return mci;
|
||||
}
|
||||
}
|
||||
|
||||
function parseMCI(buffer) {
|
||||
// :TODO: move this to "constants" seciton @ top
|
||||
var mciRe = /\%([A-Z]{2})([0-9]{1,2})?(?:\(([0-9A-Za-z,]+)\))*/g;
|
||||
var mciRe = /%([A-Z]{2})([0-9]{1,2})?(?:\(([0-9A-Za-z,]+)\))*/g;
|
||||
var pos = 0;
|
||||
var match;
|
||||
var mciCode;
|
||||
|
@ -186,27 +180,23 @@ function ANSIEscapeParser(options) {
|
|||
self.graphicRenditionForErase = _.clone(self.graphicRendition);
|
||||
}
|
||||
|
||||
|
||||
self.emit('mci', {
|
||||
mci : mciCode,
|
||||
|
||||
self.emit('mci', {
|
||||
mci : mciCode,
|
||||
id : id ? parseInt(id, 10) : null,
|
||||
args : args,
|
||||
args : args,
|
||||
SGR : ansi.getSGRFromGraphicRendition(self.graphicRendition, true)
|
||||
});
|
||||
|
||||
if(self.mciReplaceChar.length > 0) {
|
||||
const sgrCtrl = ansi.getSGRFromGraphicRendition(self.graphicRenditionForErase);
|
||||
|
||||
self.emit('control', sgrCtrl, 'm', sgrCtrl.slice(2).split(/[\;m]/).slice(0, 3));
|
||||
|
||||
self.emit('control', sgrCtrl, 'm', sgrCtrl.slice(2).split(/[;m]/).slice(0, 3));
|
||||
|
||||
literal(new Array(match[0].length + 1).join(self.mciReplaceChar));
|
||||
} else {
|
||||
literal(match[0]);
|
||||
}
|
||||
|
||||
//literal(getProcessedMCI(match[0]));
|
||||
|
||||
//self.emit('chunk', getProcessedMCI(match[0]));
|
||||
}
|
||||
|
||||
} while(0 !== mciRe.lastIndex);
|
||||
|
@ -220,7 +210,7 @@ function ANSIEscapeParser(options) {
|
|||
self.parseState = {
|
||||
// ignore anything past EOF marker, if any
|
||||
buffer : input.split(String.fromCharCode(0x1a), 1)[0],
|
||||
re : /(?:\x1b\x5b)([\?=;0-9]*?)([ABCDHJKfhlmnpsu])/g,
|
||||
re : /(?:\x1b\x5b)([?=;0-9]*?)([ABCDHJKfhlmnpsu])/g, // eslint-disable-line no-control-regex
|
||||
stop : false,
|
||||
};
|
||||
};
|
||||
|
@ -290,14 +280,14 @@ function ANSIEscapeParser(options) {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
parseMCI(lastBit)
|
||||
|
||||
parseMCI(lastBit);
|
||||
}
|
||||
|
||||
self.emit('complete');
|
||||
};
|
||||
|
||||
/*
|
||||
/*
|
||||
self.parse = function(buffer, savedRe) {
|
||||
// :TODO: ensure this conforms to ANSI-BBS / CTerm / bansi.txt for movement/etc.
|
||||
// :TODO: move this to "constants" section @ top
|
||||
|
@ -382,12 +372,12 @@ function ANSIEscapeParser(options) {
|
|||
break;
|
||||
|
||||
// save position
|
||||
case 's' :
|
||||
case 's' :
|
||||
self.saveCursorPosition();
|
||||
break;
|
||||
|
||||
// restore position
|
||||
case 'u' :
|
||||
case 'u' :
|
||||
self.restoreCursorPosition();
|
||||
break;
|
||||
|
||||
|
@ -422,7 +412,7 @@ function ANSIEscapeParser(options) {
|
|||
|
||||
case 1 :
|
||||
case 2 :
|
||||
case 22 :
|
||||
case 22 :
|
||||
self.graphicRendition.intensity = arg;
|
||||
break;
|
||||
|
||||
|
@ -448,7 +438,7 @@ function ANSIEscapeParser(options) {
|
|||
break;
|
||||
|
||||
default :
|
||||
console.log('Unknown attribute: ' + arg); // :TODO: Log properly
|
||||
Log.trace( { attribute : arg }, 'Unknown attribute while parsing ANSI');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
// ENiGMA½
|
||||
const ANSIEscapeParser = require('./ansi_escape_parser.js').ANSIEscapeParser;
|
||||
const ANSI = require('./ansi_term.js');
|
||||
const {
|
||||
const {
|
||||
splitTextAtTerms,
|
||||
renderStringLength
|
||||
} = require('./string_util.js');
|
||||
|
@ -41,7 +41,7 @@ module.exports = function ansiPrep(input, options, cb) {
|
|||
if(canvas[row]) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
canvas[row] = Array.from( { length : options.cols}, () => new Object() );
|
||||
}
|
||||
|
||||
|
@ -113,17 +113,17 @@ module.exports = function ansiPrep(input, options, cb) {
|
|||
const lastCol = getLastPopulatedColumn(row) + 1;
|
||||
|
||||
let i;
|
||||
line = options.indent ?
|
||||
line = options.indent ?
|
||||
output.length > 0 ? ' '.repeat(options.indent) : '' :
|
||||
'';
|
||||
|
||||
|
||||
for(i = 0; i < lastCol; ++i) {
|
||||
const col = row[i];
|
||||
|
||||
sgr = !options.asciiMode && 0 === i ?
|
||||
sgr = !options.asciiMode && 0 === i ?
|
||||
col.initialSgr ? ANSI.getSGRFromGraphicRendition(col.initialSgr) : '' :
|
||||
'';
|
||||
|
||||
|
||||
if(!options.asciiMode && col.sgr) {
|
||||
sgr += ANSI.getSGRFromGraphicRendition(col.sgr);
|
||||
}
|
||||
|
@ -148,7 +148,7 @@ module.exports = function ansiPrep(input, options, cb) {
|
|||
if(options.exportMode) {
|
||||
//
|
||||
// If we're in export mode, we do some additional hackery:
|
||||
//
|
||||
//
|
||||
// * Hard wrap ALL lines at <= 79 *characters* (not visible columns)
|
||||
// if a line must wrap early, we'll place a ESC[A ESC[<N>C where <N>
|
||||
// represents chars to get back to the position we were previously at
|
||||
|
@ -157,8 +157,8 @@ module.exports = function ansiPrep(input, options, cb) {
|
|||
//
|
||||
// :TODO: this would be better to do as part of the processing above, but this will do for now
|
||||
const MAX_CHARS = 79 - 8; // 79 max, - 8 for max ESC seq's we may prefix a line with
|
||||
let exportOutput = '';
|
||||
|
||||
let exportOutput = '';
|
||||
|
||||
let m;
|
||||
let afterSeq;
|
||||
let wantMore;
|
||||
|
@ -184,7 +184,7 @@ module.exports = function ansiPrep(input, options, cb) {
|
|||
splitAt = m.index;
|
||||
wantMore = false; // can't eat up any more
|
||||
}
|
||||
|
||||
|
||||
break; // seq's beyond this point are >= MAX_CHARS
|
||||
}
|
||||
}
|
||||
|
@ -203,7 +203,7 @@ module.exports = function ansiPrep(input, options, cb) {
|
|||
exportOutput += `${part}\r\n`;
|
||||
|
||||
if(fullLine.length > 0) { // more to go for this line?
|
||||
exportOutput += `${ANSI.up()}${ANSI.right(renderStart)}`;
|
||||
exportOutput += `${ANSI.up()}${ANSI.right(renderStart)}`;
|
||||
} else {
|
||||
exportOutput += ANSI.up();
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
//
|
||||
// ANSI Terminal Support Resources
|
||||
//
|
||||
//
|
||||
// ANSI-BBS
|
||||
// * http://ansi-bbs.org/
|
||||
//
|
||||
|
@ -31,7 +31,7 @@
|
|||
// For a board, we need to support the semi-standard ANSI-BBS "spec" which
|
||||
// is bastardized mix of DOS ANSI.SYS, cterm.txt, bansi.txt and a little other.
|
||||
// This gives us NetRunner, SyncTERM, EtherTerm, most *nix terminals, compatibilitiy
|
||||
// with legit oldschool DOS terminals, and so on.
|
||||
// with legit oldschool DOS terminals, and so on.
|
||||
//
|
||||
|
||||
// ENiGMA½
|
||||
|
@ -113,7 +113,7 @@ const CONTROL = {
|
|||
//
|
||||
// Support:
|
||||
// * SyncTERM: Works as expected
|
||||
// * NetRunner:
|
||||
// * NetRunner:
|
||||
//
|
||||
// General Notes:
|
||||
// See also notes in bansi.txt and cterm.txt about the various
|
||||
|
@ -160,7 +160,7 @@ const SGRValues = {
|
|||
negative : 7,
|
||||
hidden : 8,
|
||||
|
||||
normal : 22, //
|
||||
normal : 22, //
|
||||
steady : 25,
|
||||
positive : 27,
|
||||
|
||||
|
@ -203,7 +203,7 @@ function getBGColorValue(name) {
|
|||
// :TODO: Create mappings for aliases... maybe make this a map to values instead
|
||||
// :TODO: Break this up in to two parts:
|
||||
// 1) FONT_AND_CODE_PAGES (e.g. SyncTERM/cterm)
|
||||
// 2) SAUCE_FONT_MAP: Sauce name(s) -> items in FONT_AND_CODE_PAGES.
|
||||
// 2) SAUCE_FONT_MAP: Sauce name(s) -> items in FONT_AND_CODE_PAGES.
|
||||
// ...we can then have getFontFromSAUCEName(sauceFontName)
|
||||
// Also, create a SAUCE_ENCODING_MAP: SAUCE font name -> encodings
|
||||
|
||||
|
@ -215,45 +215,45 @@ function getBGColorValue(name) {
|
|||
//
|
||||
const SYNCTERM_FONT_AND_ENCODING_TABLE = [
|
||||
'cp437',
|
||||
'cp1251',
|
||||
'koi8_r',
|
||||
'iso8859_2',
|
||||
'iso8859_4',
|
||||
'cp866',
|
||||
'iso8859_9',
|
||||
'haik8',
|
||||
'iso8859_8',
|
||||
'koi8_u',
|
||||
'iso8859_15',
|
||||
'cp1251',
|
||||
'koi8_r',
|
||||
'iso8859_2',
|
||||
'iso8859_4',
|
||||
'koi8_r_b',
|
||||
'iso8859_4',
|
||||
'iso8859_5',
|
||||
'ARMSCII_8',
|
||||
'cp866',
|
||||
'iso8859_9',
|
||||
'haik8',
|
||||
'iso8859_8',
|
||||
'koi8_u',
|
||||
'iso8859_15',
|
||||
'cp850',
|
||||
'cp850',
|
||||
'cp885',
|
||||
'cp1251',
|
||||
'iso8859_7',
|
||||
'koi8-r_c',
|
||||
'iso8859_4',
|
||||
'iso8859_1',
|
||||
'cp866',
|
||||
'cp437',
|
||||
'cp866',
|
||||
'iso8859_4',
|
||||
'koi8_r_b',
|
||||
'iso8859_4',
|
||||
'iso8859_5',
|
||||
'ARMSCII_8',
|
||||
'iso8859_15',
|
||||
'cp850',
|
||||
'cp850',
|
||||
'cp885',
|
||||
'cp866_u',
|
||||
'iso8859_1',
|
||||
'cp1131',
|
||||
'c64_upper',
|
||||
'cp1251',
|
||||
'iso8859_7',
|
||||
'koi8-r_c',
|
||||
'iso8859_4',
|
||||
'iso8859_1',
|
||||
'cp866',
|
||||
'cp437',
|
||||
'cp866',
|
||||
'cp885',
|
||||
'cp866_u',
|
||||
'iso8859_1',
|
||||
'cp1131',
|
||||
'c64_upper',
|
||||
'c64_lower',
|
||||
'c128_upper',
|
||||
'c128_lower',
|
||||
'atari',
|
||||
'pot_noodle',
|
||||
'c128_upper',
|
||||
'c128_lower',
|
||||
'atari',
|
||||
'pot_noodle',
|
||||
'mo_soul',
|
||||
'microknight_plus',
|
||||
'microknight_plus',
|
||||
'topaz_plus',
|
||||
'microknight',
|
||||
'topaz',
|
||||
|
@ -289,7 +289,7 @@ const FONT_ALIAS_TO_SYNCTERM_MAP = {
|
|||
'topaz' : 'topaz',
|
||||
'amiga_topaz_1' : 'topaz',
|
||||
'amiga_topaz_1+' : 'topaz_plus',
|
||||
'topazplus' : 'topaz_plus',
|
||||
'topazplus' : 'topaz_plus',
|
||||
'topaz_plus' : 'topaz_plus',
|
||||
'amiga_topaz_2' : 'topaz',
|
||||
'amiga_topaz_2+' : 'topaz_plus',
|
||||
|
@ -349,7 +349,7 @@ function setCursorStyle(cursorStyle) {
|
|||
return `${ESC_CSI}${ps} q`;
|
||||
}
|
||||
return '';
|
||||
|
||||
|
||||
}
|
||||
|
||||
// Create methods such as up(), nextLine(),...
|
||||
|
|
|
@ -23,7 +23,7 @@ class Archiver {
|
|||
}
|
||||
|
||||
ok() {
|
||||
return this.canCompress() && this.canDecompress();
|
||||
return this.canCompress() && this.canDecompress();
|
||||
}
|
||||
|
||||
can(what) {
|
||||
|
@ -41,7 +41,7 @@ class Archiver {
|
|||
}
|
||||
|
||||
module.exports = class ArchiveUtil {
|
||||
|
||||
|
||||
constructor() {
|
||||
this.archivers = {};
|
||||
this.longestSignature = 0;
|
||||
|
@ -93,7 +93,7 @@ module.exports = class ArchiveUtil {
|
|||
|
||||
getArchiver(mimeTypeOrExtension) {
|
||||
mimeTypeOrExtension = resolveMimeType(mimeTypeOrExtension);
|
||||
|
||||
|
||||
if(!mimeTypeOrExtension) { // lookup returns false on failure
|
||||
return;
|
||||
}
|
||||
|
@ -103,21 +103,23 @@ module.exports = class ArchiveUtil {
|
|||
return _.get( Config, [ 'archives', 'archivers', archiveHandler ] );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
haveArchiver(archType) {
|
||||
return this.getArchiver(archType) ? true : false;
|
||||
}
|
||||
|
||||
detectTypeWithBuf(buf, cb) {
|
||||
// :TODO: implement me!
|
||||
// :TODO: implement me:
|
||||
/*
|
||||
detectTypeWithBuf(buf, cb) {
|
||||
}
|
||||
*/
|
||||
|
||||
detectType(path, cb) {
|
||||
fs.open(path, 'r', (err, fd) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
|
||||
const buf = new Buffer(this.longestSignature);
|
||||
fs.read(fd, buf, 0, buf.length, 0, (err, bytesRead) => {
|
||||
if(err) {
|
||||
|
@ -140,7 +142,7 @@ module.exports = class ArchiveUtil {
|
|||
});
|
||||
|
||||
return cb(archFormat ? null : Errors.General('Unknown type'), archFormat);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -153,15 +155,15 @@ module.exports = class ArchiveUtil {
|
|||
err = Errors.ExternalProcess(`${action} failed: ${d.trim()}`);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
proc.once('exit', exitCode => {
|
||||
return cb(exitCode ? Errors.ExternalProcess(`${action} failed with exit code: ${exitCode}`) : err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
compressTo(archType, archivePath, files, cb) {
|
||||
const archiver = this.getArchiver(archType);
|
||||
|
||||
|
||||
if(!archiver) {
|
||||
return cb(Errors.Invalid(`Unknown archive type: ${archType}`));
|
||||
}
|
||||
|
@ -189,13 +191,13 @@ module.exports = class ArchiveUtil {
|
|||
if(!cb && _.isFunction(fileList)) {
|
||||
cb = fileList;
|
||||
fileList = [];
|
||||
haveFileList = false;
|
||||
haveFileList = false;
|
||||
} else {
|
||||
haveFileList = true;
|
||||
}
|
||||
|
||||
const archiver = this.getArchiver(archType);
|
||||
|
||||
|
||||
if(!archiver) {
|
||||
return cb(Errors.Invalid(`Unknown archive type: ${archType}`));
|
||||
}
|
||||
|
@ -211,7 +213,7 @@ module.exports = class ArchiveUtil {
|
|||
const args = archiver[action].args.map( arg => {
|
||||
return '{fileList}' === arg ? arg : stringFormat(arg, fmtObj);
|
||||
});
|
||||
|
||||
|
||||
const fileListPos = args.indexOf('{fileList}');
|
||||
if(fileListPos > -1) {
|
||||
// replace {fileList} with 0:n sep file list arguments
|
||||
|
@ -230,9 +232,9 @@ module.exports = class ArchiveUtil {
|
|||
|
||||
listEntries(archivePath, archType, cb) {
|
||||
const archiver = this.getArchiver(archType);
|
||||
|
||||
|
||||
if(!archiver) {
|
||||
return cb(Errors.Invalid(`Unknown archive type: ${archType}`));
|
||||
return cb(Errors.Invalid(`Unknown archive type: ${archType}`));
|
||||
}
|
||||
|
||||
const fmtObj = {
|
||||
|
@ -240,7 +242,7 @@ module.exports = class ArchiveUtil {
|
|||
};
|
||||
|
||||
const args = archiver.list.args.map( arg => stringFormat(arg, fmtObj) );
|
||||
|
||||
|
||||
let proc;
|
||||
try {
|
||||
proc = pty.spawn(archiver.list.cmd, args, this.getPtyOpts());
|
||||
|
@ -251,7 +253,7 @@ module.exports = class ArchiveUtil {
|
|||
let output = '';
|
||||
proc.on('data', data => {
|
||||
// :TODO: hack for: execvp(3) failed.: No such file or directory
|
||||
|
||||
|
||||
output += data;
|
||||
});
|
||||
|
||||
|
@ -273,16 +275,16 @@ module.exports = class ArchiveUtil {
|
|||
}
|
||||
|
||||
return cb(null, entries);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
getPtyOpts() {
|
||||
return {
|
||||
// :TODO: cwd
|
||||
name : 'enigma-archiver',
|
||||
cols : 80,
|
||||
rows : 24,
|
||||
env : process.env,
|
||||
env : process.env,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
16
core/art.js
16
core/art.js
|
@ -33,7 +33,7 @@ const SUPPORTED_ART_TYPES = {
|
|||
'.pcb' : { name : 'PCBoard', defaultEncoding : 'cp437', eof : 0x1a },
|
||||
'.bbs' : { name : 'Wildcat', defaultEncoding : 'cp437', eof : 0x1a },
|
||||
|
||||
'.amiga' : { name : 'Amiga', defaultEncoding : 'amiga', eof : 0x1a },
|
||||
'.amiga' : { name : 'Amiga', defaultEncoding : 'amiga', eof : 0x1a },
|
||||
'.txt' : { name : 'Amiga Text', defaultEncoding : 'cp437', eof : 0x1a },
|
||||
// :TODO: extentions for wwiv, renegade, celerity, syncronet, ...
|
||||
// :TODO: extension for atari
|
||||
|
@ -93,7 +93,7 @@ function getArtFromPath(path, options, cb) {
|
|||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if(options.readSauce === true) {
|
||||
sauce.readSAUCE(data, (err, sauce) => {
|
||||
|
@ -164,7 +164,7 @@ function getArt(name, options, cb) {
|
|||
const bn = paths.basename(file, fext).toLowerCase();
|
||||
if(options.random) {
|
||||
const suppliedBn = paths.basename(name, fext).toLowerCase();
|
||||
|
||||
|
||||
//
|
||||
// Random selection enabled. We'll allow for
|
||||
// basename1.ext, basename2.ext, ...
|
||||
|
@ -208,7 +208,7 @@ function getArt(name, options, cb) {
|
|||
|
||||
return getArtFromPath(readPath, options, cb);
|
||||
}
|
||||
|
||||
|
||||
return cb(new Error(`No matching art for supplied criteria: ${name}`));
|
||||
});
|
||||
}
|
||||
|
@ -287,7 +287,7 @@ function display(client, art, options, cb) {
|
|||
return cb(null, mciMap, extraInfo);
|
||||
}
|
||||
|
||||
if(!options.disableMciCache) {
|
||||
if(!options.disableMciCache) {
|
||||
artHash = xxhash.hash(new Buffer(art), 0xCAFEBABE);
|
||||
|
||||
// see if we have a mciMap cached for this art
|
||||
|
@ -307,7 +307,7 @@ function display(client, art, options, cb) {
|
|||
if(mciCprQueue.length > 0) {
|
||||
mciMap[mciCprQueue.shift()].position = pos;
|
||||
|
||||
if(parseComplete && 0 === mciCprQueue.length) {
|
||||
if(parseComplete && 0 === mciCprQueue.length) {
|
||||
return completed();
|
||||
}
|
||||
}
|
||||
|
@ -345,7 +345,7 @@ function display(client, art, options, cb) {
|
|||
});
|
||||
}
|
||||
|
||||
ansiParser.on('literal', literal => client.term.write(literal, false) );
|
||||
ansiParser.on('literal', literal => client.term.write(literal, false) );
|
||||
ansiParser.on('control', control => client.term.rawWrite(control) );
|
||||
|
||||
ansiParser.on('complete', () => {
|
||||
|
@ -353,7 +353,7 @@ function display(client, art, options, cb) {
|
|||
|
||||
if(0 === mciCprQueue.length) {
|
||||
return completed();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let initSeq = '';
|
||||
|
|
|
@ -31,7 +31,7 @@ const ALL_ASSETS = [
|
|||
|
||||
const ASSET_RE = new RegExp('\\@(' + ALL_ASSETS.join('|') + ')\\:([\\w\\d\\.]*)(?:\\/([\\w\\d\\_]+))*');
|
||||
|
||||
function parseAsset(s) {
|
||||
function parseAsset(s) {
|
||||
const m = ASSET_RE.exec(s);
|
||||
|
||||
if(m) {
|
||||
|
@ -68,7 +68,7 @@ function getAssetWithShorthand(spec, defaultType) {
|
|||
|
||||
function getArtAsset(spec) {
|
||||
const asset = getAssetWithShorthand(spec, 'art');
|
||||
|
||||
|
||||
if(!asset) {
|
||||
return null;
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ function getArtAsset(spec) {
|
|||
|
||||
function getModuleAsset(spec) {
|
||||
const asset = getAssetWithShorthand(spec, 'systemModule');
|
||||
|
||||
|
||||
if(!asset) {
|
||||
return null;
|
||||
}
|
||||
|
@ -105,7 +105,7 @@ function resolveConfigAsset(spec) {
|
|||
return conf;
|
||||
} else {
|
||||
return spec;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resolveSystemStatAsset(spec) {
|
||||
|
|
|
@ -182,7 +182,7 @@ function initialize(cb) {
|
|||
return database.initializeDatabases(callback);
|
||||
},
|
||||
function initMimeTypes(callback) {
|
||||
return require('./mime_util.js').startup(callback);
|
||||
return require('./mime_util.js').startup(callback);
|
||||
},
|
||||
function initStatLog(callback) {
|
||||
return require('./stat_log.js').init(callback);
|
||||
|
|
|
@ -23,10 +23,10 @@ const packageJson = require('../package.json');
|
|||
authCode: XXXXX
|
||||
schemeCode: XXXX
|
||||
door: lord
|
||||
|
||||
|
||||
// default hoss: games.bbslink.net
|
||||
host: games.bbslink.net
|
||||
|
||||
|
||||
// defualt port: 23
|
||||
port: 23
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ exports.getModule = class BBSLinkModule extends MenuModule {
|
|||
this.config = options.menuConfig.config;
|
||||
this.config.host = this.config.host || 'games.bbslink.net';
|
||||
this.config.port = this.config.port || 23;
|
||||
}
|
||||
}
|
||||
|
||||
initSequence() {
|
||||
let token;
|
||||
|
@ -141,7 +141,7 @@ exports.getModule = class BBSLinkModule extends MenuModule {
|
|||
self.client.once('end', function clientEnd() {
|
||||
self.client.log.info('Connection ended. Terminating BBSLink connection');
|
||||
clientTerminated = true;
|
||||
bridgeConnection.end();
|
||||
bridgeConnection.end();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -170,7 +170,7 @@ exports.getModule = class BBSLinkModule extends MenuModule {
|
|||
],
|
||||
function complete(err) {
|
||||
if(err) {
|
||||
self.client.log.warn( { error : err.toString() }, 'BBSLink connection error');
|
||||
self.client.log.warn( { error : err.toString() }, 'BBSLink connection error');
|
||||
}
|
||||
|
||||
if(!clientTerminated) {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
// ENiGMA½
|
||||
const MenuModule = require('./menu_module.js').MenuModule;
|
||||
|
||||
const {
|
||||
const {
|
||||
getModDatabasePath,
|
||||
getTransactionDatabase
|
||||
} = require('./database.js');
|
||||
|
@ -39,7 +39,7 @@ const MciViewIds = {
|
|||
SelectedBBSLoc : 6,
|
||||
SelectedBBSSoftware : 7,
|
||||
SelectedBBSNotes : 8,
|
||||
SelectedBBSSubmitter : 9,
|
||||
SelectedBBSSubmitter : 9,
|
||||
},
|
||||
add : {
|
||||
BBSName : 1,
|
||||
|
@ -49,7 +49,7 @@ const MciViewIds = {
|
|||
Location : 5,
|
||||
Software : 6,
|
||||
Notes : 7,
|
||||
Error : 8,
|
||||
Error : 8,
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -190,12 +190,12 @@ exports.getModule = class BBSListModule extends MenuModule {
|
|||
|
||||
drawSelectedEntry(entry) {
|
||||
if(!entry) {
|
||||
Object.keys(SELECTED_MCI_NAME_TO_ENTRY).forEach(mciName => {
|
||||
Object.keys(SELECTED_MCI_NAME_TO_ENTRY).forEach(mciName => {
|
||||
this.setViewText('view', MciViewIds.view[mciName], '');
|
||||
});
|
||||
} else {
|
||||
const youSubmittedFormat = this.menuConfig.youSubmittedFormat || '{submitter} (You!)';
|
||||
|
||||
|
||||
Object.keys(SELECTED_MCI_NAME_TO_ENTRY).forEach(mciName => {
|
||||
const t = entry[SELECTED_MCI_NAME_TO_ENTRY[mciName]];
|
||||
if(MciViewIds.view[mciName]) {
|
||||
|
@ -270,7 +270,7 @@ exports.getModule = class BBSListModule extends MenuModule {
|
|||
(err, row) => {
|
||||
if (!err) {
|
||||
self.entries.push({
|
||||
id : row.id,
|
||||
id : row.id,
|
||||
bbsName : row.bbs_name,
|
||||
sysOp : row.sysop,
|
||||
telnet : row.telnet,
|
||||
|
@ -306,9 +306,9 @@ exports.getModule = class BBSListModule extends MenuModule {
|
|||
|
||||
entriesView.on('index update', idx => {
|
||||
const entry = self.entries[idx];
|
||||
|
||||
|
||||
self.drawSelectedEntry(entry);
|
||||
|
||||
|
||||
if(!entry) {
|
||||
self.selectedBBS = -1;
|
||||
} else {
|
||||
|
|
|
@ -22,7 +22,7 @@ ButtonView.prototype.onKeyPress = function(ch, key) {
|
|||
if(this.isKeyMapped('accept', key.name) || ' ' === ch) {
|
||||
this.submitData = 'accept';
|
||||
this.emit('action', 'accept');
|
||||
delete this.submitData;
|
||||
delete this.submitData;
|
||||
} else {
|
||||
ButtonView.super_.prototype.onKeyPress.call(this, ch, key);
|
||||
}
|
||||
|
|
|
@ -52,8 +52,8 @@ exports.Client = Client;
|
|||
// Resources & Standards:
|
||||
// * http://www.ansi-bbs.org/ansi-bbs-core-server.html
|
||||
//
|
||||
const RE_DSR_RESPONSE_ANYWHERE = /(?:\u001b\[)([0-9\;]+)(R)/;
|
||||
const RE_DEV_ATTR_RESPONSE_ANYWHERE = /(?:\u001b\[)[\=\?]([0-9a-zA-Z\;]+)(c)/;
|
||||
const RE_DSR_RESPONSE_ANYWHERE = /(?:\u001b\[)([0-9;]+)(R)/;
|
||||
const RE_DEV_ATTR_RESPONSE_ANYWHERE = /(?:\u001b\[)[=?]([0-9a-zA-Z;]+)(c)/;
|
||||
const RE_META_KEYCODE_ANYWHERE = /(?:\u001b)([a-zA-Z0-9])/;
|
||||
const RE_META_KEYCODE = new RegExp('^' + RE_META_KEYCODE_ANYWHERE.source + '$');
|
||||
const RE_FUNCTION_KEYCODE_ANYWHERE = new RegExp('(?:\u001b+)(O|N|\\[|\\[\\[)(?:' + [
|
||||
|
@ -64,19 +64,19 @@ const RE_FUNCTION_KEYCODE_ANYWHERE = new RegExp('(?:\u001b+)(O|N|\\[|\\[\\[)(?:'
|
|||
|
||||
const RE_FUNCTION_KEYCODE = new RegExp('^' + RE_FUNCTION_KEYCODE_ANYWHERE.source);
|
||||
const RE_ESC_CODE_ANYWHERE = new RegExp( [
|
||||
RE_FUNCTION_KEYCODE_ANYWHERE.source,
|
||||
RE_META_KEYCODE_ANYWHERE.source,
|
||||
RE_FUNCTION_KEYCODE_ANYWHERE.source,
|
||||
RE_META_KEYCODE_ANYWHERE.source,
|
||||
RE_DSR_RESPONSE_ANYWHERE.source,
|
||||
RE_DEV_ATTR_RESPONSE_ANYWHERE.source,
|
||||
/\u001b./.source
|
||||
].join('|'));
|
||||
|
||||
|
||||
function Client(input, output) {
|
||||
function Client(/*input, output*/) {
|
||||
stream.call(this);
|
||||
|
||||
const self = this;
|
||||
|
||||
|
||||
this.user = new User();
|
||||
this.currentTheme = { info : { name : 'N/A', description : 'None' } };
|
||||
this.lastKeyPressMs = Date.now();
|
||||
|
@ -125,9 +125,9 @@ function Client(input, output) {
|
|||
|
||||
if(!termClient) {
|
||||
if(_.startsWith(deviceAttr, '67;84;101;114;109')) {
|
||||
//
|
||||
//
|
||||
// See https://github.com/protomouse/synchronet/blob/master/src/conio/cterm.txt
|
||||
//
|
||||
//
|
||||
// Known clients:
|
||||
// * SyncTERM
|
||||
//
|
||||
|
@ -139,11 +139,11 @@ function Client(input, output) {
|
|||
};
|
||||
|
||||
this.isMouseInput = function(data) {
|
||||
return /\x1b\[M/.test(data) ||
|
||||
/\u001b\[M([\x00\u0020-\uffff]{3})/.test(data) ||
|
||||
return /\x1b\[M/.test(data) || // eslint-disable-line no-control-regex
|
||||
/\u001b\[M([\x00\u0020-\uffff]{3})/.test(data) || // eslint-disable-line no-control-regex
|
||||
/\u001b\[(\d+;\d+;\d+)M/.test(data) ||
|
||||
/\u001b\[<(\d+;\d+;\d+)([mM])/.test(data) ||
|
||||
/\u001b\[<(\d+;\d+;\d+;\d+)&w/.test(data) ||
|
||||
/\u001b\[<(\d+;\d+;\d+;\d+)&w/.test(data) ||
|
||||
/\u001b\[24([0135])~\[(\d+),(\d+)\]\r/.test(data) ||
|
||||
/\u001b\[(O|I)/.test(data);
|
||||
};
|
||||
|
@ -163,7 +163,7 @@ function Client(input, output) {
|
|||
'OE' : { name : 'clear' },
|
||||
'OF' : { name : 'end' },
|
||||
'OH' : { name : 'home' },
|
||||
|
||||
|
||||
// xterm/rxvt
|
||||
'[11~' : { name : 'f1' },
|
||||
'[12~' : { name : 'f2' },
|
||||
|
@ -290,7 +290,7 @@ function Client(input, output) {
|
|||
if(self.cprOffset) {
|
||||
cprArgs[0] = cprArgs[0] + self.cprOffset;
|
||||
cprArgs[1] = cprArgs[1] + self.cprOffset;
|
||||
}
|
||||
}
|
||||
self.emit('cursor position report', cprArgs);
|
||||
}
|
||||
}
|
||||
|
@ -299,7 +299,7 @@ function Client(input, output) {
|
|||
var termClient = self.getTermClient(parts[1]);
|
||||
if(termClient) {
|
||||
self.term.termClient = termClient;
|
||||
}
|
||||
}
|
||||
} else if('\r' === s) {
|
||||
key.name = 'return';
|
||||
} else if('\n' === s) {
|
||||
|
@ -347,10 +347,10 @@ function Client(input, output) {
|
|||
key.meta = true;
|
||||
key.shift = /^[A-Z]$/.test(parts[1]);
|
||||
} else if((parts = RE_FUNCTION_KEYCODE.exec(s))) {
|
||||
var code =
|
||||
var code =
|
||||
(parts[1] || '') + (parts[2] || '') +
|
||||
(parts[4] || '') + (parts[9] || '');
|
||||
|
||||
|
||||
var modifier = (parts[3] || parts[8] || 1) - 1;
|
||||
|
||||
key.ctrl = !!(modifier & 4);
|
||||
|
@ -375,7 +375,7 @@ function Client(input, output) {
|
|||
//
|
||||
// Adjust name for CTRL/Shift/Meta modifiers
|
||||
//
|
||||
key.name =
|
||||
key.name =
|
||||
(key.ctrl ? 'ctrl + ' : '') +
|
||||
(key.meta ? 'meta + ' : '') +
|
||||
(key.shift ? 'shift + ' : '') +
|
||||
|
@ -446,7 +446,7 @@ Client.prototype.end = function () {
|
|||
}
|
||||
|
||||
clearInterval(this.idleCheck);
|
||||
|
||||
|
||||
try {
|
||||
//
|
||||
// We can end up calling 'end' before TTY/etc. is established, e.g. with SSH
|
||||
|
@ -482,7 +482,7 @@ Client.prototype.isLocal = function() {
|
|||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// :TODO: getDefaultHandler(name) -- handlers in default_handlers.js or something
|
||||
Client.prototype.defaultHandlerMissingMod = function(err) {
|
||||
Client.prototype.defaultHandlerMissingMod = function() {
|
||||
var self = this;
|
||||
|
||||
function handler(err) {
|
||||
|
@ -493,12 +493,12 @@ Client.prototype.defaultHandlerMissingMod = function(err) {
|
|||
self.term.write('This has been logged for your SysOp to review.\n');
|
||||
self.term.write('\nGoodbye!\n');
|
||||
|
||||
|
||||
|
||||
//self.term.write(err);
|
||||
|
||||
//if(miscUtil.isDevelopment() && err.stack) {
|
||||
// self.term.write('\n' + err.stack + '\n');
|
||||
//}
|
||||
//}
|
||||
|
||||
self.end();
|
||||
}
|
||||
|
@ -516,8 +516,8 @@ Client.prototype.terminalSupports = function(query) {
|
|||
|
||||
case 'vtx_hyperlink' :
|
||||
return 'vtx' === termClient;
|
||||
|
||||
default :
|
||||
|
||||
default :
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -95,7 +95,7 @@ function removeClient(client) {
|
|||
clientId : client.session.id
|
||||
},
|
||||
'Client disconnected'
|
||||
);
|
||||
);
|
||||
|
||||
Events.emit('codes.l33t.enigma.system.disconnected', { client : client, connectionCount : clientConnections.length } );
|
||||
}
|
||||
|
|
|
@ -15,8 +15,6 @@ exports.ClientTerminal = ClientTerminal;
|
|||
function ClientTerminal(output) {
|
||||
this.output = output;
|
||||
|
||||
var self = this;
|
||||
|
||||
var outputEncoding = 'cp437';
|
||||
assert(iconv.encodingExists(outputEncoding));
|
||||
|
||||
|
@ -56,7 +54,7 @@ function ClientTerminal(output) {
|
|||
},
|
||||
set : function(ttype) {
|
||||
termType = ttype.toLowerCase();
|
||||
|
||||
|
||||
if(this.isANSI()) {
|
||||
this.outputEncoding = 'cp437';
|
||||
} else {
|
||||
|
@ -137,7 +135,7 @@ ClientTerminal.prototype.isANSI = function() {
|
|||
//
|
||||
// syncterm:
|
||||
// * SyncTERM
|
||||
//
|
||||
//
|
||||
// xterm:
|
||||
// * PuTTY
|
||||
//
|
||||
|
@ -168,7 +166,7 @@ ClientTerminal.prototype.rawWrite = function(s, cb) {
|
|||
if(cb) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
|
||||
if(err) {
|
||||
Log.warn( { error : err.message }, 'Failed writing to socket');
|
||||
}
|
||||
|
@ -178,18 +176,18 @@ ClientTerminal.prototype.rawWrite = function(s, cb) {
|
|||
|
||||
ClientTerminal.prototype.pipeWrite = function(s, spec, cb) {
|
||||
spec = spec || 'renegade';
|
||||
|
||||
|
||||
var conv = {
|
||||
enigma : enigmaToAnsi,
|
||||
renegade : renegadeToAnsi,
|
||||
}[spec] || renegadeToAnsi;
|
||||
|
||||
|
||||
this.write(conv(s, this), null, cb); // null = use default for |convertLineFeeds|
|
||||
};
|
||||
|
||||
ClientTerminal.prototype.encode = function(s, convertLineFeeds) {
|
||||
convertLineFeeds = _.isBoolean(convertLineFeeds) ? convertLineFeeds : this.convertLF;
|
||||
|
||||
|
||||
if(convertLineFeeds && _.isString(s)) {
|
||||
s = s.replace(/\n/g, '\r\n');
|
||||
}
|
||||
|
|
|
@ -68,14 +68,14 @@ function enigmaToAnsi(s, client) {
|
|||
attr = ansi.sgr(['normal', val - 8, 'bold']);
|
||||
}
|
||||
|
||||
result += s.substr(lastIndex, m.index - lastIndex) + attr;
|
||||
result += s.substr(lastIndex, m.index - lastIndex) + attr;
|
||||
}
|
||||
|
||||
lastIndex = re.lastIndex;
|
||||
}
|
||||
|
||||
result = (0 === result.length ? s : result + s.substr(lastIndex));
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -145,7 +145,7 @@ function renegadeToAnsi(s, client) {
|
|||
}
|
||||
|
||||
// convert to number
|
||||
val = parseInt(val, 10);
|
||||
val = parseInt(val, 10);
|
||||
if(isNaN(val)) {
|
||||
val = getPredefinedMCIValue(client, m[1]) || ('|' + m[1]); // value itself or literal
|
||||
}
|
||||
|
@ -160,7 +160,7 @@ function renegadeToAnsi(s, client) {
|
|||
lastIndex = re.lastIndex;
|
||||
}
|
||||
|
||||
return (0 === result.length ? s : result + s.substr(lastIndex));
|
||||
return (0 === result.length ? s : result + s.substr(lastIndex));
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -180,7 +180,7 @@ function renegadeToAnsi(s, client) {
|
|||
// * http://wiki.synchro.net/custom:colors
|
||||
//
|
||||
function controlCodesToAnsi(s, client) {
|
||||
const RE = /(\|([A-Z0-9]{2})|\|)|(\@X([0-9A-F]{2}))|(\@([0-9A-F]{2})\@)|(\x03[0-9]|\x03)/g; // eslint-disable-line no-control-regex
|
||||
const RE = /(\|([A-Z0-9]{2})|\|)|(@X([0-9A-F]{2}))|(@([0-9A-F]{2})@)|(\x03[0-9]|\x03)/g; // eslint-disable-line no-control-regex
|
||||
|
||||
let m;
|
||||
let result = '';
|
||||
|
|
|
@ -25,10 +25,10 @@ exports.getModule = class CombatNetModule extends MenuModule {
|
|||
this.config.host = this.config.host || 'bbs.combatnet.us';
|
||||
this.config.rloginPort = this.config.rloginPort || 4513;
|
||||
}
|
||||
|
||||
|
||||
initSequence() {
|
||||
const self = this;
|
||||
|
||||
|
||||
async.series(
|
||||
[
|
||||
function validateConfig(callback) {
|
||||
|
@ -45,59 +45,59 @@ exports.getModule = class CombatNetModule extends MenuModule {
|
|||
self.client.term.write('Connecting to CombatNet, please wait...\n');
|
||||
|
||||
const restorePipeToNormal = function() {
|
||||
self.client.term.output.removeListener('data', sendToRloginBuffer);
|
||||
self.client.term.output.removeListener('data', sendToRloginBuffer);
|
||||
};
|
||||
|
||||
const rlogin = new RLogin(
|
||||
{ 'clientUsername' : self.config.password,
|
||||
'serverUsername' : `${self.config.bbsTag}${self.client.user.username}`,
|
||||
'host' : self.config.host,
|
||||
'port' : self.config.rloginPort,
|
||||
'terminalType' : self.client.term.termClient,
|
||||
'terminalSpeed' : 57600
|
||||
}
|
||||
);
|
||||
const rlogin = new RLogin(
|
||||
{ 'clientUsername' : self.config.password,
|
||||
'serverUsername' : `${self.config.bbsTag}${self.client.user.username}`,
|
||||
'host' : self.config.host,
|
||||
'port' : self.config.rloginPort,
|
||||
'terminalType' : self.client.term.termClient,
|
||||
'terminalSpeed' : 57600
|
||||
}
|
||||
);
|
||||
|
||||
// If there was an error ...
|
||||
rlogin.on('error', err => {
|
||||
self.client.log.info(`CombatNet rlogin client error: ${err.message}`);
|
||||
restorePipeToNormal();
|
||||
callback(err);
|
||||
});
|
||||
// If there was an error ...
|
||||
rlogin.on('error', err => {
|
||||
self.client.log.info(`CombatNet rlogin client error: ${err.message}`);
|
||||
restorePipeToNormal();
|
||||
return callback(err);
|
||||
});
|
||||
|
||||
// If we've been disconnected ...
|
||||
rlogin.on('disconnect', () => {
|
||||
self.client.log.info(`Disconnected from CombatNet`);
|
||||
restorePipeToNormal();
|
||||
callback(null);
|
||||
});
|
||||
// If we've been disconnected ...
|
||||
rlogin.on('disconnect', () => {
|
||||
self.client.log.info('Disconnected from CombatNet');
|
||||
restorePipeToNormal();
|
||||
return callback(null);
|
||||
});
|
||||
|
||||
function sendToRloginBuffer(buffer) {
|
||||
rlogin.send(buffer);
|
||||
};
|
||||
function sendToRloginBuffer(buffer) {
|
||||
rlogin.send(buffer);
|
||||
}
|
||||
|
||||
rlogin.on("connect",
|
||||
/* The 'connect' event handler will be supplied with one argument,
|
||||
rlogin.on('connect',
|
||||
/* The 'connect' event handler will be supplied with one argument,
|
||||
a boolean indicating whether or not the connection was established. */
|
||||
|
||||
function(state) {
|
||||
if(state) {
|
||||
self.client.log.info('Connected to CombatNet');
|
||||
self.client.term.output.on('data', sendToRloginBuffer);
|
||||
function(state) {
|
||||
if(state) {
|
||||
self.client.log.info('Connected to CombatNet');
|
||||
self.client.term.output.on('data', sendToRloginBuffer);
|
||||
|
||||
} else {
|
||||
return callback(new Error('Failed to establish establish CombatNet connection'));
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
return callback(new Error('Failed to establish establish CombatNet connection'));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// If data (a Buffer) has been received from the server ...
|
||||
rlogin.on("data", (data) => {
|
||||
self.client.term.rawWrite(data);
|
||||
});
|
||||
// If data (a Buffer) has been received from the server ...
|
||||
rlogin.on('data', (data) => {
|
||||
self.client.term.rawWrite(data);
|
||||
});
|
||||
|
||||
// connect...
|
||||
rlogin.connect();
|
||||
// connect...
|
||||
rlogin.connect();
|
||||
|
||||
// note: no explicit callback() until we're finished!
|
||||
}
|
||||
|
@ -106,10 +106,10 @@ exports.getModule = class CombatNetModule extends MenuModule {
|
|||
if(err) {
|
||||
self.client.log.warn( { error : err.message }, 'CombatNet error');
|
||||
}
|
||||
|
||||
|
||||
// if the client is still here, go to previous
|
||||
self.prevMenu();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -10,7 +10,7 @@ exports.sortAreasOrConfs = sortAreasOrConfs;
|
|||
// Method for sorting message, file, etc. areas and confs
|
||||
// If the sort key is present and is a number, sort in numerical order;
|
||||
// Otherwise, use a locale comparison on the sort key or name as a fallback
|
||||
//
|
||||
//
|
||||
function sortAreasOrConfs(areasOrConfs, type) {
|
||||
let entryA;
|
||||
let entryB;
|
||||
|
|
|
@ -653,12 +653,12 @@ function getDefaultConfig() {
|
|||
// 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$', '^[^/\]*DESC\.SDI$', '^[^/\]*DESCRIPT\.ION$', '^[^/\]*FILE\.DES$', '^[^/\]*FILE\.SDI$', '^[^/\]*DISK\.ID$'
|
||||
'^[^/\]*FILE_ID\.ANS$', '^[^/\]*FILE_ID\.DIZ$', '^[^/\]*DESC\.SDI$', '^[^/\]*DESCRIPT\.ION$', '^[^/\]*FILE\.DES$', '^[^/\]*FILE\.SDI$', '^[^/\]*DISK\.ID$' // eslint-disable-line no-useless-escape
|
||||
],
|
||||
|
||||
// common README filename - https://en.wikipedia.org/wiki/README
|
||||
descLong : [
|
||||
'^[^/\]*\.NFO$', '^[^/\]*README\.1ST$', '^[^/\]*README\.NOW$', '^[^/\]*README\.TXT$', '^[^/\]*READ\.ME$', '^[^/\]*README$', '^[^/\]*README\.md$'
|
||||
'^[^/\]*\.NFO$', '^[^/\]*README\.1ST$', '^[^/\]*README\.NOW$', '^[^/\]*README\.TXT$', '^[^/\]*READ\.ME$', '^[^/\]*README$', '^[^/\]*README\.md$' // eslint-disable-line no-useless-escape
|
||||
],
|
||||
},
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ util.inherits(ConfigCache, events.EventEmitter);
|
|||
ConfigCache.prototype.getConfigWithOptions = function(options, cb) {
|
||||
assert(_.isString(options.filePath));
|
||||
|
||||
// var self = this;
|
||||
// var self = this;
|
||||
var isCached = (options.filePath in this.cache);
|
||||
|
||||
if(options.forceReCache || !isCached) {
|
||||
|
|
|
@ -98,7 +98,7 @@ function ansiQueryTermSizeIfNeeded(client, cb) {
|
|||
source : 'ANSI CPR'
|
||||
},
|
||||
'Window size updated'
|
||||
);
|
||||
);
|
||||
|
||||
return done(null);
|
||||
};
|
||||
|
|
|
@ -10,7 +10,7 @@ exports.CRC32 = class CRC32 {
|
|||
}
|
||||
|
||||
update(input) {
|
||||
input = Buffer.isBuffer(input) ? input : Buffer.from(input, 'binary');
|
||||
input = Buffer.isBuffer(input) ? input : Buffer.from(input, 'binary');
|
||||
return input.length > 10240 ? this.update_8(input) : this.update_4(input);
|
||||
}
|
||||
|
||||
|
@ -47,7 +47,7 @@ exports.CRC32 = class CRC32 {
|
|||
this.crc = (this.crc >>> 8) ^ CRC32_TABLE[ (this.crc ^ input[i++] ) & 0xff ];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
finalize() {
|
||||
return (this.crc ^ (-1)) >>> 0;
|
||||
}
|
||||
|
|
|
@ -36,12 +36,12 @@ function getModDatabasePath(moduleInfo, suffix) {
|
|||
// Mods that use a database are stored in Config.paths.modsDb (e.g. enigma-bbs/db/mods)
|
||||
// We expect that moduleInfo defines packageName which will be the base of the modules
|
||||
// filename. An optional suffix may be supplied as well.
|
||||
//
|
||||
const HOST_RE = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/;
|
||||
//
|
||||
const HOST_RE = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])$/;
|
||||
|
||||
assert(_.isObject(moduleInfo));
|
||||
assert(_.isString(moduleInfo.packageName), 'moduleInfo must define "packageName"!');
|
||||
|
||||
|
||||
let full = moduleInfo.packageName;
|
||||
if(suffix) {
|
||||
full += `.${suffix}`;
|
||||
|
@ -198,7 +198,7 @@ const DB_INIT_TABLE = {
|
|||
DELETE FROM message_fts WHERE docid=old.rowid;
|
||||
END;`
|
||||
);
|
||||
|
||||
|
||||
dbs.message.run(
|
||||
`CREATE TRIGGER IF NOT EXISTS message_before_delete BEFORE DELETE ON message BEGIN
|
||||
DELETE FROM message_fts WHERE docid=old.rowid;
|
||||
|
@ -256,14 +256,14 @@ const DB_INIT_TABLE = {
|
|||
UNIQUE(user_id, area_tag)
|
||||
);`
|
||||
);
|
||||
|
||||
|
||||
dbs.message.run(
|
||||
`CREATE TABLE IF NOT EXISTS message_area_last_scan (
|
||||
scan_toss VARCHAR NOT NULL,
|
||||
area_tag VARCHAR NOT NULL,
|
||||
message_id INTEGER NOT NULL,
|
||||
UNIQUE(scan_toss, area_tag)
|
||||
);`
|
||||
);`
|
||||
);
|
||||
|
||||
return cb(null);
|
||||
|
|
10
core/door.js
10
core/door.js
|
@ -20,7 +20,7 @@ function Door(client, exeInfo) {
|
|||
this.exeInfo = exeInfo;
|
||||
this.exeInfo.encoding = this.exeInfo.encoding || 'cp437';
|
||||
this.exeInfo.encoding = this.exeInfo.encoding.toLowerCase();
|
||||
let restored = false;
|
||||
let restored = false;
|
||||
|
||||
//
|
||||
// Members of exeInfo:
|
||||
|
@ -52,7 +52,7 @@ function Door(client, exeInfo) {
|
|||
};
|
||||
|
||||
this.prepareSocketIoServer = function(cb) {
|
||||
if('socket' === self.exeInfo.io) {
|
||||
if('socket' === self.exeInfo.io) {
|
||||
const sockServer = createServer(conn => {
|
||||
|
||||
sockServer.getConnections( (err, count) => {
|
||||
|
@ -60,11 +60,11 @@ function Door(client, exeInfo) {
|
|||
// We expect only one connection from our DOOR/emulator/etc.
|
||||
if(!err && count <= 1) {
|
||||
self.client.term.output.pipe(conn);
|
||||
|
||||
|
||||
conn.on('data', self.doorDataHandler);
|
||||
|
||||
conn.once('end', () => {
|
||||
return self.restoreIo(conn);
|
||||
return self.restoreIo(conn);
|
||||
});
|
||||
|
||||
conn.once('error', err => {
|
||||
|
@ -117,7 +117,7 @@ Door.prototype.run = function() {
|
|||
rows : self.client.term.termHeight,
|
||||
// :TODO: cwd
|
||||
env : self.exeInfo.env,
|
||||
});
|
||||
});
|
||||
|
||||
if('stdio' === self.exeInfo.io) {
|
||||
self.client.log.debug('Using stdio for door I/O');
|
||||
|
|
|
@ -24,13 +24,13 @@ exports.getModule = class DoorPartyModule extends MenuModule {
|
|||
this.config = options.menuConfig.config;
|
||||
this.config.host = this.config.host || 'dp.throwbackbbs.com';
|
||||
this.config.sshPort = this.config.sshPort || 2022;
|
||||
this.config.rloginPort = this.config.rloginPort || 513;
|
||||
this.config.rloginPort = this.config.rloginPort || 513;
|
||||
}
|
||||
|
||||
|
||||
initSequence() {
|
||||
let clientTerminated;
|
||||
const self = this;
|
||||
|
||||
|
||||
async.series(
|
||||
[
|
||||
function validateConfig(callback) {
|
||||
|
@ -48,26 +48,26 @@ exports.getModule = class DoorPartyModule extends MenuModule {
|
|||
function establishSecureConnection(callback) {
|
||||
self.client.term.write(resetScreen());
|
||||
self.client.term.write('Connecting to DoorParty, please wait...\n');
|
||||
|
||||
|
||||
const sshClient = new SSHClient();
|
||||
|
||||
|
||||
let pipeRestored = false;
|
||||
let pipedStream;
|
||||
const restorePipe = function() {
|
||||
if(pipedStream && !pipeRestored && !clientTerminated) {
|
||||
self.client.term.output.unpipe(pipedStream);
|
||||
self.client.term.output.unpipe(pipedStream);
|
||||
self.client.term.output.resume();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
sshClient.on('ready', () => {
|
||||
// track client termination so we can clean up early
|
||||
self.client.once('end', () => {
|
||||
self.client.log.info('Connection ended. Terminating DoorParty connection');
|
||||
clientTerminated = true;
|
||||
sshClient.end();
|
||||
sshClient.end();
|
||||
});
|
||||
|
||||
|
||||
// establish tunnel for rlogin
|
||||
sshClient.forwardOut('127.0.0.1', self.config.sshPort, self.config.host, self.config.rloginPort, (err, stream) => {
|
||||
if(err) {
|
||||
|
@ -79,17 +79,17 @@ exports.getModule = class DoorPartyModule extends MenuModule {
|
|||
// DoorParty wants the "server username" portion to be in the format of [BBS_TAG]USERNAME, e.g.
|
||||
// [XA]nuskooler
|
||||
//
|
||||
const rlogin = `\x00${self.client.user.username}\x00[${self.config.bbsTag}]${self.client.user.username}\x00${self.client.term.termType}\x00`;
|
||||
const rlogin = `\x00${self.client.user.username}\x00[${self.config.bbsTag}]${self.client.user.username}\x00${self.client.term.termType}\x00`;
|
||||
stream.write(rlogin);
|
||||
|
||||
|
||||
pipedStream = stream; // :TODO: this is hacky...
|
||||
self.client.term.output.pipe(stream);
|
||||
|
||||
|
||||
stream.on('data', d => {
|
||||
// :TODO: we should just pipe this...
|
||||
self.client.term.rawWrite(d);
|
||||
});
|
||||
|
||||
|
||||
stream.on('close', () => {
|
||||
restorePipe();
|
||||
sshClient.end();
|
||||
|
@ -100,32 +100,32 @@ exports.getModule = class DoorPartyModule extends MenuModule {
|
|||
sshClient.on('error', err => {
|
||||
self.client.log.info(`DoorParty SSH client error: ${err.message}`);
|
||||
});
|
||||
|
||||
|
||||
sshClient.on('close', () => {
|
||||
restorePipe();
|
||||
callback(null);
|
||||
});
|
||||
|
||||
|
||||
sshClient.connect( {
|
||||
host : self.config.host,
|
||||
port : self.config.sshPort,
|
||||
username : self.config.username,
|
||||
password : self.config.password,
|
||||
});
|
||||
|
||||
|
||||
// note: no explicit callback() until we're finished!
|
||||
}
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(err) {
|
||||
self.client.log.warn( { error : err.message }, 'DoorParty error');
|
||||
}
|
||||
|
||||
|
||||
// if the client is stil here, go to previous
|
||||
if(!clientTerminated) {
|
||||
self.prevMenu();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -59,14 +59,14 @@ module.exports = class DownloadQueue {
|
|||
}
|
||||
|
||||
toProperty() { return JSON.stringify(this.client.user.downloadQueue); }
|
||||
|
||||
|
||||
loadFromProperty(prop) {
|
||||
try {
|
||||
this.client.user.downloadQueue = JSON.parse(prop);
|
||||
} catch(e) {
|
||||
this.client.user.downloadQueue = [];
|
||||
|
||||
this.client.log.error( { error : e.message, property : prop }, 'Failed parsing download queue property');
|
||||
this.client.log.error( { error : e.message, property : prop }, 'Failed parsing download queue property');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -16,7 +16,7 @@ function EditTextView(options) {
|
|||
options.acceptsInput = miscUtil.valueWithDefault(options.acceptsInput, true);
|
||||
options.cursorStyle = miscUtil.valueWithDefault(options.cursorStyle, 'steady block');
|
||||
options.resizable = false;
|
||||
|
||||
|
||||
TextView.call(this, options);
|
||||
|
||||
this.cursorPos = { row : 0, col : 0 };
|
||||
|
@ -44,7 +44,7 @@ EditTextView.prototype.onKeyPress = function(ch, key) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return EditTextView.super_.prototype.onKeyPress.call(this, ch, key);
|
||||
} else if(this.isKeyMapped('clearLine', key.name)) {
|
||||
this.text = '';
|
||||
|
|
|
@ -14,7 +14,7 @@ class EnigError extends Error {
|
|||
if(typeof Error.captureStackTrace === 'function') {
|
||||
Error.captureStackTrace(this, this.constructor);
|
||||
} else {
|
||||
this.stack = (new Error(message)).stack;
|
||||
this.stack = (new Error(message)).stack;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
|
||||
// ENiGMA½
|
||||
const Config = require('./config.js').config;
|
||||
const Log = require('./logger.js').log;
|
||||
const Log = require('./logger.js').log;
|
||||
|
||||
// deps
|
||||
const assert = require('assert');
|
||||
|
||||
module.exports = function(condition, message) {
|
||||
if(Config.debug.assertsEnabled) {
|
||||
assert.apply(this, arguments);
|
||||
assert.apply(this, arguments);
|
||||
} else if(!(condition)) {
|
||||
const stack = new Error().stack;
|
||||
Log.error( { condition : condition, stack : stack }, message || 'Assertion failed' );
|
||||
|
|
|
@ -37,12 +37,12 @@ var MciViewIds = {
|
|||
function ErcClientModule(options) {
|
||||
MenuModule.prototype.ctorShim.call(this, options);
|
||||
|
||||
const self = this;
|
||||
const self = this;
|
||||
this.config = options.menuConfig.config;
|
||||
|
||||
this.chatEntryFormat = this.config.chatEntryFormat || '[{bbsTag}] {userName}: {message}';
|
||||
this.systemEntryFormat = this.config.systemEntryFormat || '[*SYSTEM*] {message}';
|
||||
|
||||
this.systemEntryFormat = this.config.systemEntryFormat || '[*SYSTEM*] {message}';
|
||||
|
||||
this.finishedLoading = function() {
|
||||
async.waterfall(
|
||||
[
|
||||
|
@ -63,12 +63,12 @@ function ErcClientModule(options) {
|
|||
};
|
||||
|
||||
const chatMessageView = self.viewControllers.menu.getView(MciViewIds.ChatDisplay);
|
||||
|
||||
|
||||
chatMessageView.setText('Connecting to server...');
|
||||
chatMessageView.redraw();
|
||||
|
||||
|
||||
self.viewControllers.menu.switchFocus(MciViewIds.InputArea);
|
||||
|
||||
|
||||
// :TODO: Track actual client->enig connection for optional prevMenu @ final CB
|
||||
self.chatConnection = net.createConnection(connectOpts.port, connectOpts.host);
|
||||
|
||||
|
@ -98,12 +98,12 @@ function ErcClientModule(options) {
|
|||
}
|
||||
|
||||
chatMessageView.addText(text);
|
||||
|
||||
|
||||
if(chatMessageView.getLineCount() > 30) { // :TODO: should probably be ChatDisplay.height?
|
||||
chatMessageView.deleteLine(0);
|
||||
chatMessageView.scrollDown();
|
||||
}
|
||||
|
||||
|
||||
chatMessageView.redraw();
|
||||
self.viewControllers.menu.switchFocus(MciViewIds.InputArea);
|
||||
}
|
||||
|
|
|
@ -24,8 +24,8 @@ exports.moduleInfo = {
|
|||
author : 'NuSkooler',
|
||||
};
|
||||
|
||||
const SCHEDULE_REGEXP = /(?:^|or )?(@watch\:)([^\0]+)?$/;
|
||||
const ACTION_REGEXP = /\@(method|execute)\:([^\0]+)?$/;
|
||||
const SCHEDULE_REGEXP = /(?:^|or )?(@watch:)([^\0]+)?$/;
|
||||
const ACTION_REGEXP = /@(method|execute):([^\0]+)?$/;
|
||||
|
||||
class ScheduledEvent {
|
||||
constructor(events, name) {
|
||||
|
@ -34,32 +34,32 @@ class ScheduledEvent {
|
|||
this.action = this.parseActionSpec(events[name].action);
|
||||
if(this.action) {
|
||||
this.action.args = events[name].args || [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
get isValid() {
|
||||
if((!this.schedule || (!this.schedule.sched && !this.schedule.watchFile)) || !this.action) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if('method' === this.action.type && !this.action.location) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
parseScheduleString(schedStr) {
|
||||
if(!schedStr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
let schedule = {};
|
||||
|
||||
|
||||
const m = SCHEDULE_REGEXP.exec(schedStr);
|
||||
if(m) {
|
||||
schedStr = schedStr.substr(0, m.index).trim();
|
||||
|
||||
|
||||
if('@watch:' === m[1]) {
|
||||
schedule.watchFile = m[2];
|
||||
}
|
||||
|
@ -69,15 +69,15 @@ class ScheduledEvent {
|
|||
const sched = later.parse.text(schedStr);
|
||||
if(-1 === sched.error) {
|
||||
schedule.sched = sched;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// return undefined if we couldn't parse out anything useful
|
||||
if(!_.isEmpty(schedule)) {
|
||||
return schedule;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
parseActionSpec(actionSpec) {
|
||||
if(actionSpec) {
|
||||
if('@' === actionSpec[0]) {
|
||||
|
@ -86,7 +86,7 @@ class ScheduledEvent {
|
|||
if(m[2].indexOf(':') > -1) {
|
||||
const parts = m[2].split(':');
|
||||
return {
|
||||
type : m[1],
|
||||
type : m[1],
|
||||
location : parts[0],
|
||||
what : parts[1],
|
||||
};
|
||||
|
@ -98,12 +98,12 @@ class ScheduledEvent {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
return {
|
||||
type : 'execute',
|
||||
what : actionSpec,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
executeAction(reason, cb) {
|
||||
|
@ -119,14 +119,14 @@ class ScheduledEvent {
|
|||
{ error : err.toString(), eventName : this.name, action : this.action },
|
||||
'Error performing scheduled event action');
|
||||
}
|
||||
|
||||
|
||||
return cb(err);
|
||||
});
|
||||
} catch(e) {
|
||||
Log.warn(
|
||||
{ error : e.toString(), eventName : this.name, action : this.action },
|
||||
'Failed to perform scheduled event action');
|
||||
|
||||
|
||||
return cb(e);
|
||||
}
|
||||
} else if('execute' === this.action.type) {
|
||||
|
@ -135,18 +135,18 @@ class ScheduledEvent {
|
|||
name : this.name,
|
||||
cols : 80,
|
||||
rows : 24,
|
||||
env : process.env,
|
||||
env : process.env,
|
||||
};
|
||||
|
||||
const proc = pty.spawn(this.action.what, this.action.args, opts);
|
||||
|
||||
proc.once('exit', exitCode => {
|
||||
proc.once('exit', exitCode => {
|
||||
if(exitCode) {
|
||||
Log.warn(
|
||||
{ eventName : this.name, action : this.action, exitCode : exitCode },
|
||||
'Bad exit code while performing scheduled event action');
|
||||
}
|
||||
return cb(exitCode ? new Error(`Bad exit code while performing scheduled event action: ${exitCode}`) : null);
|
||||
return cb(exitCode ? new Error(`Bad exit code while performing scheduled event action: ${exitCode}`) : null);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -154,58 +154,58 @@ class ScheduledEvent {
|
|||
|
||||
function EventSchedulerModule(options) {
|
||||
PluginModule.call(this, options);
|
||||
|
||||
|
||||
if(_.has(Config, 'eventScheduler')) {
|
||||
this.moduleConfig = Config.eventScheduler;
|
||||
}
|
||||
|
||||
|
||||
const self = this;
|
||||
this.runningActions = new Set();
|
||||
|
||||
|
||||
this.performAction = function(schedEvent, reason) {
|
||||
if(self.runningActions.has(schedEvent.name)) {
|
||||
return; // already running
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
self.runningActions.add(schedEvent.name);
|
||||
|
||||
schedEvent.executeAction(reason, () => {
|
||||
self.runningActions.delete(schedEvent.name);
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// convienence static method for direct load + start
|
||||
EventSchedulerModule.loadAndStart = function(cb) {
|
||||
const loadModuleEx = require('./module_util.js').loadModuleEx;
|
||||
|
||||
|
||||
const loadOpts = {
|
||||
name : path.basename(__filename, '.js'),
|
||||
path : __dirname,
|
||||
};
|
||||
|
||||
|
||||
loadModuleEx(loadOpts, (err, mod) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
|
||||
const modInst = new mod.getModule();
|
||||
modInst.startup( err => {
|
||||
return cb(err, modInst);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
EventSchedulerModule.prototype.startup = function(cb) {
|
||||
|
||||
|
||||
this.eventTimers = [];
|
||||
const self = this;
|
||||
|
||||
|
||||
if(this.moduleConfig && _.has(this.moduleConfig, 'events')) {
|
||||
const events = Object.keys(this.moduleConfig.events).map( name => {
|
||||
return new ScheduledEvent(this.moduleConfig.events, name);
|
||||
});
|
||||
|
||||
|
||||
events.forEach( schedEvent => {
|
||||
if(!schedEvent.isValid) {
|
||||
Log.warn( { eventName : schedEvent.name }, 'Invalid scheduled event entry');
|
||||
|
@ -213,7 +213,7 @@ EventSchedulerModule.prototype.startup = function(cb) {
|
|||
}
|
||||
|
||||
Log.debug(
|
||||
{
|
||||
{
|
||||
eventName : schedEvent.name,
|
||||
schedule : this.moduleConfig.events[schedEvent.name].schedule,
|
||||
action : schedEvent.action,
|
||||
|
@ -222,9 +222,9 @@ EventSchedulerModule.prototype.startup = function(cb) {
|
|||
'Scheduled event loaded'
|
||||
);
|
||||
|
||||
if(schedEvent.schedule.sched) {
|
||||
if(schedEvent.schedule.sched) {
|
||||
this.eventTimers.push(later.setInterval( () => {
|
||||
self.performAction(schedEvent, 'Schedule');
|
||||
self.performAction(schedEvent, 'Schedule');
|
||||
}, schedEvent.schedule.sched));
|
||||
}
|
||||
|
||||
|
@ -255,7 +255,7 @@ EventSchedulerModule.prototype.startup = function(cb) {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
cb(null);
|
||||
};
|
||||
|
||||
|
@ -263,6 +263,6 @@ EventSchedulerModule.prototype.shutdown = function(cb) {
|
|||
if(this.eventTimers) {
|
||||
this.eventTimers.forEach( et => et.clear() );
|
||||
}
|
||||
|
||||
|
||||
cb(null);
|
||||
};
|
||||
|
|
|
@ -23,7 +23,7 @@ const SSHClient = require('ssh2').Client;
|
|||
/*
|
||||
Configuration block:
|
||||
|
||||
|
||||
|
||||
someDoor: {
|
||||
module: exodus
|
||||
config: {
|
||||
|
@ -61,7 +61,7 @@ exports.getModule = class ExodusModule extends MenuModule {
|
|||
this.config = options.menuConfig.config || {};
|
||||
this.config.ticketHost = this.config.ticketHost || 'oddnetwork.org';
|
||||
this.config.ticketPort = this.config.ticketPort || 1984,
|
||||
this.config.ticketPath = this.config.ticketPath || '/exodus';
|
||||
this.config.ticketPath = this.config.ticketPath || '/exodus';
|
||||
this.config.rejectUnauthorized = _.get(this.config, 'rejectUnauthorized', true);
|
||||
this.config.sshHost = this.config.sshHost || this.config.ticketHost;
|
||||
this.config.sshPort = this.config.sshPort || 22;
|
||||
|
|
|
@ -65,7 +65,7 @@ exports.getModule = class FileAreaFilterEdit extends MenuModule {
|
|||
prevFilter : (formData, extraArgs, cb) => {
|
||||
this.currentFilterIndex -= 1;
|
||||
if(this.currentFilterIndex < 0) {
|
||||
this.currentFilterIndex = this.filtersArray.length - 1;
|
||||
this.currentFilterIndex = this.filtersArray.length - 1;
|
||||
}
|
||||
this.loadDataForFilter(this.currentFilterIndex);
|
||||
return cb(null);
|
||||
|
@ -116,21 +116,21 @@ exports.getModule = class FileAreaFilterEdit extends MenuModule {
|
|||
if(newActive) {
|
||||
filters.setActive(newActive.uuid);
|
||||
} else {
|
||||
// nothing to set active to
|
||||
// nothing to set active to
|
||||
this.client.user.removeProperty('file_base_filter_active_uuid');
|
||||
}
|
||||
}
|
||||
|
||||
// update UI
|
||||
this.updateActiveLabel();
|
||||
|
||||
|
||||
if(this.filtersArray.length > 0) {
|
||||
this.loadDataForFilter(this.currentFilterIndex);
|
||||
} else {
|
||||
this.clearForm();
|
||||
}
|
||||
return cb(null);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
viewValidationListener : (err, cb) => {
|
||||
|
@ -161,7 +161,7 @@ exports.getModule = class FileAreaFilterEdit extends MenuModule {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
mciReady(mciData, cb) {
|
||||
super.mciReady(mciData, err => {
|
||||
if(err) {
|
||||
|
@ -178,7 +178,7 @@ exports.getModule = class FileAreaFilterEdit extends MenuModule {
|
|||
},
|
||||
function populateAreas(callback) {
|
||||
self.availAreas = [ { name : '-ALL-' } ].concat(getSortedAvailableFileAreas(self.client) || []);
|
||||
|
||||
|
||||
const areasView = vc.getView(MciViewIds.editor.area);
|
||||
if(areasView) {
|
||||
areasView.setItems( self.availAreas.map( a => a.name ) );
|
||||
|
@ -194,7 +194,7 @@ exports.getModule = class FileAreaFilterEdit extends MenuModule {
|
|||
return cb(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getCurrentFilter() {
|
||||
|
@ -212,7 +212,7 @@ exports.getModule = class FileAreaFilterEdit extends MenuModule {
|
|||
const activeFilter = FileBaseFilters.getActiveFilter(this.client);
|
||||
if(activeFilter) {
|
||||
const activeFormat = this.menuConfig.config.activeFormat || '{name}';
|
||||
this.setText(MciViewIds.editor.activeFilterInfo, stringFormat(activeFormat, activeFilter));
|
||||
this.setText(MciViewIds.editor.activeFilterInfo, stringFormat(activeFormat, activeFilter));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -256,7 +256,7 @@ exports.getModule = class FileAreaFilterEdit extends MenuModule {
|
|||
|
||||
setAreaIndexFromCurrentFilter() {
|
||||
let index;
|
||||
const filter = this.getCurrentFilter();
|
||||
const filter = this.getCurrentFilter();
|
||||
if(filter) {
|
||||
// special treatment: areaTag saved as blank ("") if -ALL-
|
||||
index = (filter.areaTag && this.availAreas.findIndex(area => filter.areaTag === area.areaTag)) || 0;
|
||||
|
@ -295,7 +295,7 @@ exports.getModule = class FileAreaFilterEdit extends MenuModule {
|
|||
setFilterValuesFromFormData(filter, formData) {
|
||||
filter.name = formData.value.name;
|
||||
filter.areaTag = this.getSelectedAreaTag(formData.value.areaIndex);
|
||||
filter.terms = formData.value.searchTerms;
|
||||
filter.terms = formData.value.searchTerms;
|
||||
filter.tags = formData.value.tags;
|
||||
filter.order = this.getOrderBy(formData.value.orderByIndex);
|
||||
filter.sort = this.getSortBy(formData.value.sortByIndex);
|
||||
|
@ -304,7 +304,7 @@ exports.getModule = class FileAreaFilterEdit extends MenuModule {
|
|||
saveCurrentFilter(formData, cb) {
|
||||
const filters = new FileBaseFilters(this.client);
|
||||
const selectedFilter = this.filtersArray[this.currentFilterIndex];
|
||||
|
||||
|
||||
if(selectedFilter) {
|
||||
// *update* currently selected filter
|
||||
this.setFilterValuesFromFormData(selectedFilter, formData);
|
||||
|
@ -316,11 +316,11 @@ exports.getModule = class FileAreaFilterEdit extends MenuModule {
|
|||
|
||||
// set current to what we just saved
|
||||
newFilter.uuid = filters.add(newFilter);
|
||||
|
||||
|
||||
// add to our array (at current index position)
|
||||
this.filtersArray[this.currentFilterIndex] = newFilter;
|
||||
}
|
||||
|
||||
|
||||
return filters.persist(cb);
|
||||
}
|
||||
|
||||
|
@ -334,6 +334,6 @@ exports.getModule = class FileAreaFilterEdit extends MenuModule {
|
|||
this.setAreaIndexFromCurrentFilter();
|
||||
this.setSortByFromCurrentFilter();
|
||||
this.setOrderByFromCurrentFilter();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -96,7 +96,7 @@ exports.getModule = class FileAreaList extends MenuModule {
|
|||
}
|
||||
|
||||
this.menuMethods = {
|
||||
nextFile : (formData, extraArgs, cb) => {
|
||||
nextFile : (formData, extraArgs, cb) => {
|
||||
if(this.fileListPosition + 1 < this.fileList.length) {
|
||||
this.fileListPosition += 1;
|
||||
|
||||
|
@ -131,7 +131,7 @@ exports.getModule = class FileAreaList extends MenuModule {
|
|||
toggleQueue : (formData, extraArgs, cb) => {
|
||||
this.dlQueue.toggle(this.currentFileEntry);
|
||||
this.updateQueueIndicator();
|
||||
return cb(null);
|
||||
return cb(null);
|
||||
},
|
||||
showWebDownloadLink : (formData, extraArgs, cb) => {
|
||||
return this.fetchAndDisplayWebDownloadLink(cb);
|
||||
|
@ -217,7 +217,7 @@ exports.getModule = class FileAreaList extends MenuModule {
|
|||
const hashTagsSep = config.hashTagsSep || ', ';
|
||||
const isQueuedIndicator = config.isQueuedIndicator || 'Y';
|
||||
const isNotQueuedIndicator = config.isNotQueuedIndicator || 'N';
|
||||
|
||||
|
||||
const entryInfo = currEntry.entryInfo = {
|
||||
fileId : currEntry.fileId,
|
||||
areaTag : currEntry.areaTag,
|
||||
|
@ -232,7 +232,7 @@ exports.getModule = class FileAreaList extends MenuModule {
|
|||
hashTags : Array.from(currEntry.hashTags).join(hashTagsSep),
|
||||
isQueued : this.dlQueue.isQueued(currEntry) ? isQueuedIndicator : isNotQueuedIndicator,
|
||||
webDlLink : '', // :TODO: fetch web any existing web d/l link
|
||||
webDlExpire : '', // :TODO: fetch web d/l link expire time
|
||||
webDlExpire : '', // :TODO: fetch web d/l link expire time
|
||||
};
|
||||
|
||||
//
|
||||
|
@ -257,7 +257,7 @@ exports.getModule = class FileAreaList extends MenuModule {
|
|||
|
||||
// create a rating string, e.g. "**---"
|
||||
const userRatingTicked = config.userRatingTicked || '*';
|
||||
const userRatingUnticked = config.userRatingUnticked || '';
|
||||
const userRatingUnticked = config.userRatingUnticked || '';
|
||||
entryInfo.userRating = ~~Math.round(entryInfo.userRating) || 0; // be safe!
|
||||
entryInfo.userRatingString = userRatingTicked.repeat(entryInfo.userRating);
|
||||
if(entryInfo.userRating < 5) {
|
||||
|
@ -270,7 +270,7 @@ exports.getModule = class FileAreaList extends MenuModule {
|
|||
if(ErrNotEnabled === err.reasonCode) {
|
||||
entryInfo.webDlExpire = config.webDlLinkNoWebserver || 'Web server is not enabled';
|
||||
} else {
|
||||
entryInfo.webDlLink = config.webDlLinkNeedsGenerated || 'Not yet generated';
|
||||
entryInfo.webDlLink = config.webDlLinkNeedsGenerated || 'Not yet generated';
|
||||
}
|
||||
} else {
|
||||
const webDlExpireTimeFormat = config.webDlExpireTimeFormat || 'YYYY-MMM-DD @ h:mm';
|
||||
|
@ -339,10 +339,10 @@ exports.getModule = class FileAreaList extends MenuModule {
|
|||
|
||||
return vc.loadFromMenuConfig(loadOpts, callback);
|
||||
}
|
||||
|
||||
|
||||
self.viewControllers[name].setFocus(true);
|
||||
return callback(null);
|
||||
|
||||
|
||||
},
|
||||
],
|
||||
err => {
|
||||
|
@ -357,7 +357,7 @@ exports.getModule = class FileAreaList extends MenuModule {
|
|||
async.series(
|
||||
[
|
||||
function fetchEntryData(callback) {
|
||||
if(self.fileList) {
|
||||
if(self.fileList) {
|
||||
return callback(null);
|
||||
}
|
||||
return self.loadFileIds(false, callback); // false=do not force
|
||||
|
@ -371,14 +371,14 @@ exports.getModule = class FileAreaList extends MenuModule {
|
|||
function prepArtAndViewController(callback) {
|
||||
return self.displayArtAndPrepViewController('browse', { clearScreen : clearScreen }, callback);
|
||||
},
|
||||
function loadCurrentFileInfo(callback) {
|
||||
function loadCurrentFileInfo(callback) {
|
||||
self.currentFileEntry = new FileEntry();
|
||||
|
||||
self.currentFileEntry.load( self.fileList[ self.fileListPosition ], err => {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
|
||||
return self.populateCurrentEntryInfo(callback);
|
||||
});
|
||||
},
|
||||
|
@ -422,7 +422,7 @@ exports.getModule = class FileAreaList extends MenuModule {
|
|||
return callback(null);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
err => {
|
||||
if(cb) {
|
||||
return cb(err);
|
||||
}
|
||||
|
@ -448,7 +448,7 @@ exports.getModule = class FileAreaList extends MenuModule {
|
|||
function listenNavChanges(callback) {
|
||||
const navMenu = self.viewControllers.details.getView(MciViewIds.details.navMenu);
|
||||
navMenu.setFocusItemIndex(0);
|
||||
|
||||
|
||||
navMenu.on('index update', index => {
|
||||
const sectionName = {
|
||||
0 : 'general',
|
||||
|
@ -481,7 +481,7 @@ exports.getModule = class FileAreaList extends MenuModule {
|
|||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
fetchAndDisplayWebDownloadLink(cb) {
|
||||
const self = this;
|
||||
|
||||
|
@ -492,11 +492,11 @@ exports.getModule = class FileAreaList extends MenuModule {
|
|||
if(self.currentFileEntry.webDlExpireTime < moment()) {
|
||||
return callback(null);
|
||||
}
|
||||
|
||||
|
||||
const expireTime = moment().add(Config.fileBase.web.expireMinutes, 'minutes');
|
||||
|
||||
FileAreaWeb.createAndServeTempDownload(
|
||||
self.client,
|
||||
self.client,
|
||||
self.currentFileEntry,
|
||||
{ expireTime : expireTime },
|
||||
(err, url) => {
|
||||
|
@ -517,8 +517,8 @@ exports.getModule = class FileAreaList extends MenuModule {
|
|||
},
|
||||
function updateActiveViews(callback) {
|
||||
self.updateCustomViewTextsWithFilter(
|
||||
'browse',
|
||||
MciViewIds.browse.customRangeStart, self.currentFileEntry.entryInfo,
|
||||
'browse',
|
||||
MciViewIds.browse.customRangeStart, self.currentFileEntry.entryInfo,
|
||||
{ filter : [ '{webDlLink}', '{webDlExpire}' ] }
|
||||
);
|
||||
return callback(null);
|
||||
|
@ -527,7 +527,7 @@ exports.getModule = class FileAreaList extends MenuModule {
|
|||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
updateQueueIndicator() {
|
||||
|
@ -535,8 +535,8 @@ exports.getModule = class FileAreaList extends MenuModule {
|
|||
const isNotQueuedIndicator = this.menuConfig.config.isNotQueuedIndicator || 'N';
|
||||
|
||||
this.currentFileEntry.entryInfo.isQueued = stringFormat(
|
||||
this.dlQueue.isQueued(this.currentFileEntry) ?
|
||||
isQueuedIndicator :
|
||||
this.dlQueue.isQueued(this.currentFileEntry) ?
|
||||
isQueuedIndicator :
|
||||
isNotQueuedIndicator
|
||||
);
|
||||
|
||||
|
@ -558,7 +558,7 @@ exports.getModule = class FileAreaList extends MenuModule {
|
|||
if(!areaInfo) {
|
||||
return cb(Errors.Invalid('Invalid area tag'));
|
||||
}
|
||||
|
||||
|
||||
const filePath = this.currentFileEntry.filePath;
|
||||
const archiveUtil = ArchiveUtil.getInstance();
|
||||
|
||||
|
@ -574,7 +574,7 @@ exports.getModule = class FileAreaList extends MenuModule {
|
|||
|
||||
populateFileListing() {
|
||||
const fileListView = this.viewControllers.detailsFileList.getView(MciViewIds.detailsFileList.fileList);
|
||||
|
||||
|
||||
if(this.currentFileEntry.entryInfo.archiveType) {
|
||||
this.cacheArchiveEntries( (err, cacheStatus) => {
|
||||
if(err) {
|
||||
|
@ -586,7 +586,7 @@ exports.getModule = class FileAreaList extends MenuModule {
|
|||
if('re-cached' === cacheStatus) {
|
||||
const fileListEntryFormat = this.menuConfig.config.fileListEntryFormat || '{fileName} {fileSize}'; // :TODO: use byteSize here?
|
||||
const focusFileListEntryFormat = this.menuConfig.config.focusFileListEntryFormat || fileListEntryFormat;
|
||||
|
||||
|
||||
fileListView.setItems( this.currentFileEntry.archiveEntries.map( entry => stringFormat(fileListEntryFormat, entry) ) );
|
||||
fileListView.setFocusItems( this.currentFileEntry.archiveEntries.map( entry => stringFormat(focusFileListEntryFormat, entry) ) );
|
||||
|
||||
|
@ -594,7 +594,7 @@ exports.getModule = class FileAreaList extends MenuModule {
|
|||
}
|
||||
});
|
||||
} else {
|
||||
fileListView.setItems( [ stringFormat(this.menuConfig.config.notAnArchiveFormat || 'Not an archive', { fileName : this.currentFileEntry.fileName } ) ] );
|
||||
fileListView.setItems( [ stringFormat(this.menuConfig.config.notAnArchiveFormat || 'Not an archive', { fileName : this.currentFileEntry.fileName } ) ] );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -608,7 +608,7 @@ exports.getModule = class FileAreaList extends MenuModule {
|
|||
if(self.lastDetailsViewController) {
|
||||
self.lastDetailsViewController.detachClientEvents();
|
||||
}
|
||||
return callback(null);
|
||||
return callback(null);
|
||||
},
|
||||
function prepArtAndViewController(callback) {
|
||||
|
||||
|
@ -616,7 +616,7 @@ exports.getModule = class FileAreaList extends MenuModule {
|
|||
self.client.term.rawWrite(ansi.goto(self.detailsInfoArea.top[0], 1));
|
||||
}
|
||||
|
||||
gotoTopPos();
|
||||
gotoTopPos();
|
||||
|
||||
if(clearArea) {
|
||||
self.client.term.rawWrite(ansi.reset());
|
||||
|
|
|
@ -59,7 +59,7 @@ class FileAreaWebAccess {
|
|||
return callback(null); // not enabled, but no error
|
||||
}
|
||||
}
|
||||
],
|
||||
],
|
||||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
|
@ -193,7 +193,7 @@ class FileAreaWebAccess {
|
|||
getExistingTempDownloadServeItem(client, fileEntry, cb) {
|
||||
if(!this.isEnabled()) {
|
||||
return cb(notEnabledError());
|
||||
}
|
||||
}
|
||||
|
||||
const hashId = this.getSingleFileHashId(client, fileEntry);
|
||||
this.loadServedHashId(hashId, (err, servedItem) => {
|
||||
|
@ -201,10 +201,10 @@ class FileAreaWebAccess {
|
|||
return cb(err);
|
||||
}
|
||||
|
||||
servedItem.url = this.buildSingleFileTempDownloadLink(client, fileEntry);
|
||||
servedItem.url = this.buildSingleFileTempDownloadLink(client, fileEntry);
|
||||
|
||||
return cb(null, servedItem);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_addOrUpdateHashIdRecord(dbOrTrans, hashId, expireTime, cb) {
|
||||
|
@ -219,7 +219,7 @@ class FileAreaWebAccess {
|
|||
}
|
||||
|
||||
this.scheduleExpire(hashId, expireTime);
|
||||
|
||||
|
||||
return cb(null);
|
||||
}
|
||||
);
|
||||
|
@ -476,7 +476,7 @@ class FileAreaWebAccess {
|
|||
StatLog.incrementUserStat(user, 'dl_total_bytes', dlBytes);
|
||||
StatLog.incrementSystemStat('dl_total_count', 1);
|
||||
StatLog.incrementSystemStat('dl_total_bytes', dlBytes);
|
||||
|
||||
|
||||
return callback(null);
|
||||
}
|
||||
],
|
||||
|
|
|
@ -61,8 +61,8 @@ function getAvailableFileAreas(client, options) {
|
|||
|
||||
// perform ACS check per conf & omit internal if desired
|
||||
const allAreas = _.map(Config.fileBase.areas, (areaInfo, areaTag) => Object.assign(areaInfo, { areaTag : areaTag } ));
|
||||
|
||||
return _.omitBy(allAreas, areaInfo => {
|
||||
|
||||
return _.omitBy(allAreas, areaInfo => {
|
||||
if(!options.includeSystemInternal && isInternalArea(areaInfo.areaTag)) {
|
||||
return true;
|
||||
}
|
||||
|
@ -102,7 +102,7 @@ function getDefaultFileAreaTag(client, disableAcsCheck) {
|
|||
defaultArea = _.findKey(Config.fileBase.areas, (area, areaTag) => {
|
||||
return WellKnownAreaTags.MessageAreaAttach !== areaTag && (true === disableAcsCheck || client.acs.hasFileAreaRead(area));
|
||||
});
|
||||
|
||||
|
||||
return defaultArea;
|
||||
}
|
||||
|
||||
|
@ -110,7 +110,7 @@ function getFileAreaByTag(areaTag) {
|
|||
const areaInfo = Config.fileBase.areas[areaTag];
|
||||
if(areaInfo) {
|
||||
areaInfo.areaTag = areaTag; // convienence!
|
||||
areaInfo.storage = getAreaStorageLocations(areaInfo);
|
||||
areaInfo.storage = getAreaStorageLocations(areaInfo);
|
||||
return areaInfo;
|
||||
}
|
||||
}
|
||||
|
@ -165,13 +165,13 @@ function getAreaDefaultStorageDirectory(areaInfo) {
|
|||
}
|
||||
|
||||
function getAreaStorageLocations(areaInfo) {
|
||||
|
||||
const storageTags = Array.isArray(areaInfo.storageTags) ?
|
||||
areaInfo.storageTags :
|
||||
|
||||
const storageTags = Array.isArray(areaInfo.storageTags) ?
|
||||
areaInfo.storageTags :
|
||||
[ areaInfo.storageTags || '' ];
|
||||
|
||||
const avail = Config.fileBase.storageTags;
|
||||
|
||||
|
||||
return _.compact(storageTags.map(storageTag => {
|
||||
if(avail[storageTag]) {
|
||||
return {
|
||||
|
@ -230,7 +230,7 @@ function attemptSetEstimatedReleaseDate(fileEntry) {
|
|||
const patterns = Config.fileBase.yearEstPatterns.map( p => new RegExp(p, 'gmi'));
|
||||
|
||||
function getMatch(input) {
|
||||
if(input) {
|
||||
if(input) {
|
||||
let m;
|
||||
for(let i = 0; i < patterns.length; ++i) {
|
||||
m = patterns[i].exec(input);
|
||||
|
@ -249,7 +249,7 @@ function attemptSetEstimatedReleaseDate(fileEntry) {
|
|||
//
|
||||
const maxYear = moment().add(2, 'year').year();
|
||||
const match = getMatch(fileEntry.desc) || getMatch(fileEntry.descLong);
|
||||
|
||||
|
||||
if(match && match[1]) {
|
||||
let year;
|
||||
if(2 === match[1].length) {
|
||||
|
@ -316,7 +316,7 @@ function extractAndProcessDescFiles(fileEntry, filePath, archiveEntries, cb) {
|
|||
archiveUtil.extractTo(filePath, tempDir, fileEntry.meta.archive_type, extractList, err => {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
}
|
||||
|
||||
const descFiles = {
|
||||
desc : shortDescFile ? paths.join(tempDir, shortDescFile.fileName) : null,
|
||||
|
@ -327,7 +327,7 @@ function extractAndProcessDescFiles(fileEntry, filePath, archiveEntries, cb) {
|
|||
});
|
||||
});
|
||||
},
|
||||
function readDescFiles(descFiles, callback) {
|
||||
function readDescFiles(descFiles, callback) {
|
||||
async.each(Object.keys(descFiles), (descType, next) => {
|
||||
const path = descFiles[descType];
|
||||
if(!path) {
|
||||
|
@ -341,7 +341,7 @@ function extractAndProcessDescFiles(fileEntry, filePath, archiveEntries, cb) {
|
|||
|
||||
// skip entries that are too large
|
||||
const maxFileSizeKey = `max${_.upperFirst(descType)}FileByteSize`;
|
||||
|
||||
|
||||
if(Config.fileBase[maxFileSizeKey] && stats.size > Config.fileBase[maxFileSizeKey]) {
|
||||
logDebug( { byteSize : stats.size, maxByteSize : Config.fileBase[maxFileSizeKey] }, `Skipping "${descType}"; Too large` );
|
||||
return next(null);
|
||||
|
@ -353,7 +353,7 @@ function extractAndProcessDescFiles(fileEntry, filePath, archiveEntries, cb) {
|
|||
}
|
||||
|
||||
//
|
||||
// Assume FILE_ID.DIZ, NFO files, etc. are CP437.
|
||||
// Assume FILE_ID.DIZ, NFO files, etc. are CP437.
|
||||
//
|
||||
// :TODO: This isn't really always the case - how to handle this? We could do a quick detection...
|
||||
fileEntry[descType] = iconv.decode(sliceAtSauceMarker(data, 0x1a), 'cp437');
|
||||
|
@ -389,10 +389,10 @@ function extractAndProcessSingleArchiveEntry(fileEntry, filePath, archiveEntries
|
|||
}
|
||||
|
||||
const archiveUtil = ArchiveUtil.getInstance();
|
||||
|
||||
|
||||
// ensure we only extract one - there should only be one anyway -- we also just need the fileName
|
||||
const extractList = archiveEntries.slice(0, 1).map(entry => entry.fileName);
|
||||
|
||||
|
||||
archiveUtil.extractTo(filePath, tempDir, fileEntry.meta.archive_type, extractList, err => {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
|
@ -540,7 +540,7 @@ function populateFileEntryInfoFromFile(fileEntry, filePath, cb) {
|
|||
});
|
||||
}, () => {
|
||||
return cb(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function populateFileEntryNonArchive(fileEntry, filePath, stepInfo, iterator, cb) {
|
||||
|
@ -586,10 +586,6 @@ function addNewFileEntry(fileEntry, filePath, cb) {
|
|||
);
|
||||
}
|
||||
|
||||
function updateFileEntry(fileEntry, filePath, cb) {
|
||||
|
||||
}
|
||||
|
||||
const HASH_NAMES = [ 'sha1', 'sha256', 'md5', 'crc32' ];
|
||||
|
||||
function scanFile(filePath, options, iterator, cb) {
|
||||
|
@ -664,7 +660,7 @@ function scanFile(filePath, options, iterator, cb) {
|
|||
return callIter(callback);
|
||||
});
|
||||
},
|
||||
function processPhysicalFileGeneric(callback) {
|
||||
function processPhysicalFileGeneric(callback) {
|
||||
stepInfo.bytesProcessed = 0;
|
||||
|
||||
const hashes = {};
|
||||
|
@ -690,7 +686,7 @@ function scanFile(filePath, options, iterator, cb) {
|
|||
stream.on('data', data => {
|
||||
stream.pause(); // until iterator compeltes
|
||||
|
||||
stepInfo.bytesProcessed += data.length;
|
||||
stepInfo.bytesProcessed += data.length;
|
||||
stepInfo.calcHashPercent = Math.round(((stepInfo.bytesProcessed / stepInfo.byteSize) * 100));
|
||||
|
||||
//
|
||||
|
@ -710,13 +706,13 @@ function scanFile(filePath, options, iterator, cb) {
|
|||
|
||||
updateHashes(data);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
stream.on('end', () => {
|
||||
fileEntry.meta.byte_size = stepInfo.bytesProcessed;
|
||||
|
||||
async.each(hashesToCalc, (hashName, nextHash) => {
|
||||
async.each(hashesToCalc, (hashName, nextHash) => {
|
||||
if('sha256' === hashName) {
|
||||
stepInfo.sha256 = fileEntry.fileSha256 = hashes.sha256.digest('hex');
|
||||
} else if('sha1' === hashName || 'md5' === hashName) {
|
||||
|
@ -747,7 +743,9 @@ function scanFile(filePath, options, iterator, cb) {
|
|||
populateFileEntryWithArchive(fileEntry, filePath, stepInfo, callIter, err => {
|
||||
if(err) {
|
||||
populateFileEntryNonArchive(fileEntry, filePath, stepInfo, callIter, err => {
|
||||
// :TODO: log err
|
||||
if(err) {
|
||||
logDebug( { error : err.message }, 'Non-archive file entry population failed');
|
||||
}
|
||||
return callback(null); // ignore err
|
||||
});
|
||||
} else {
|
||||
|
@ -756,7 +754,9 @@ function scanFile(filePath, options, iterator, cb) {
|
|||
});
|
||||
} else {
|
||||
populateFileEntryNonArchive(fileEntry, filePath, stepInfo, callIter, err => {
|
||||
// :TODO: log err
|
||||
if(err) {
|
||||
logDebug( { error : err.message }, 'Non-archive file entry population failed');
|
||||
}
|
||||
return callback(null); // ignore err
|
||||
});
|
||||
}
|
||||
|
@ -773,7 +773,7 @@ function scanFile(filePath, options, iterator, cb) {
|
|||
return callback(null, dupeEntries);
|
||||
});
|
||||
}
|
||||
],
|
||||
],
|
||||
(err, dupeEntries) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
|
@ -858,12 +858,12 @@ function scanFileAreaForChanges(areaInfo, options, iterator, cb) {
|
|||
// :TODO: Look @ db entries for area that were *not* processed above
|
||||
return callback(null);
|
||||
}
|
||||
],
|
||||
],
|
||||
err => {
|
||||
return nextLocation(err);
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
err => {
|
||||
return cb(err);
|
||||
});
|
||||
|
@ -874,14 +874,14 @@ function getDescFromFileName(fileName) {
|
|||
const ext = paths.extname(fileName);
|
||||
const name = paths.basename(fileName, ext);
|
||||
|
||||
return _.upperFirst(name.replace(/[\-_.+]/g, ' ').replace(/\s+/g, ' '));
|
||||
return _.upperFirst(name.replace(/[-_.+]/g, ' ').replace(/\s+/g, ' '));
|
||||
}
|
||||
|
||||
//
|
||||
// Return an object of stats about an area(s)
|
||||
//
|
||||
// {
|
||||
//
|
||||
//
|
||||
// totalFiles : <totalFileCount>,
|
||||
// totalBytes : <totalByteSize>,
|
||||
// areas : {
|
||||
|
@ -892,7 +892,7 @@ function getDescFromFileName(fileName) {
|
|||
// }
|
||||
// }
|
||||
//
|
||||
function getAreaStats(cb) {
|
||||
function getAreaStats(cb) {
|
||||
FileDb.all(
|
||||
`SELECT DISTINCT f.area_tag, COUNT(f.file_id) AS total_files, SUM(m.meta_value) AS total_byte_size
|
||||
FROM file f, file_meta m
|
||||
|
@ -928,9 +928,9 @@ function getAreaStats(cb) {
|
|||
|
||||
// method exposed for event scheduler
|
||||
function updateAreaStatsScheduledEvent(args, cb) {
|
||||
getAreaStats( (err, stats) => {
|
||||
getAreaStats( (err, stats) => {
|
||||
if(!err) {
|
||||
StatLog.setNonPeristentSystemStat('file_base_area_stats', stats);
|
||||
StatLog.setNonPeristentSystemStat('file_base_area_stats', stats);
|
||||
}
|
||||
|
||||
return cb(err);
|
||||
|
|
|
@ -38,7 +38,7 @@ exports.getModule = class FileAreaSelectModule extends MenuModule {
|
|||
|
||||
const menuOpts = {
|
||||
extraArgs : {
|
||||
filterCriteria : filterCriteria,
|
||||
filterCriteria : filterCriteria,
|
||||
},
|
||||
menuFlags : [ 'popParent' ],
|
||||
};
|
||||
|
|
|
@ -59,8 +59,6 @@ exports.getModule = class FileBaseDownloadQueueManager extends MenuModule {
|
|||
|
||||
return this.gotoMenu(this.menuConfig.config.fileTransferProtocolSelection || 'fileTransferProtocolSelection', modOpts, cb);
|
||||
},
|
||||
viewItemInfo : (formData, extraArgs, cb) => {
|
||||
},
|
||||
removeItem : (formData, extraArgs, cb) => {
|
||||
const selectedItem = this.dlQueue.items[formData.value.queueItem];
|
||||
if(!selectedItem) {
|
||||
|
@ -74,7 +72,7 @@ exports.getModule = class FileBaseDownloadQueueManager extends MenuModule {
|
|||
},
|
||||
clearQueue : (formData, extraArgs, cb) => {
|
||||
this.dlQueue.clear();
|
||||
|
||||
|
||||
// :TODO: broken: does not redraw menu properly - needs fixed!
|
||||
return this.removeItemsFromDownloadQueueView('all', cb);
|
||||
}
|
||||
|
@ -230,10 +228,10 @@ exports.getModule = class FileBaseDownloadQueueManager extends MenuModule {
|
|||
|
||||
return vc.loadFromMenuConfig(loadOpts, callback);
|
||||
}
|
||||
|
||||
|
||||
self.viewControllers[name].setFocus(true);
|
||||
return callback(null);
|
||||
|
||||
|
||||
},
|
||||
],
|
||||
err => {
|
||||
|
|
|
@ -8,7 +8,7 @@ const uuidV4 = require('uuid/v4');
|
|||
module.exports = class FileBaseFilters {
|
||||
constructor(client) {
|
||||
this.client = client;
|
||||
|
||||
|
||||
this.load();
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ module.exports = class FileBaseFilters {
|
|||
'est_release_year',
|
||||
'byte_size',
|
||||
'file_name',
|
||||
];
|
||||
];
|
||||
}
|
||||
|
||||
toArray() {
|
||||
|
@ -40,11 +40,11 @@ module.exports = class FileBaseFilters {
|
|||
|
||||
add(filterInfo) {
|
||||
const filterUuid = uuidV4();
|
||||
|
||||
|
||||
filterInfo.tags = this.cleanTags(filterInfo.tags);
|
||||
|
||||
|
||||
this.filters[filterUuid] = filterInfo;
|
||||
|
||||
|
||||
return filterUuid;
|
||||
}
|
||||
|
||||
|
@ -94,18 +94,18 @@ module.exports = class FileBaseFilters {
|
|||
}
|
||||
|
||||
cleanTags(tags) {
|
||||
return tags.toLowerCase().replace(/,?\s+|\,/g, ' ').trim();
|
||||
return tags.toLowerCase().replace(/,?\s+|,/g, ' ').trim();
|
||||
}
|
||||
|
||||
setActive(filterUuid) {
|
||||
const activeFilter = this.get(filterUuid);
|
||||
|
||||
|
||||
if(activeFilter) {
|
||||
this.activeFilter = activeFilter;
|
||||
this.client.user.persistProperty('file_base_filter_active_uuid', filterUuid);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -110,7 +110,7 @@ exports.getModule = class FileBaseSearch extends MenuModule {
|
|||
|
||||
const menuOpts = {
|
||||
extraArgs : {
|
||||
filterCriteria : filterCriteria,
|
||||
filterCriteria : filterCriteria,
|
||||
},
|
||||
menuFlags : [ 'popParent' ],
|
||||
};
|
||||
|
|
|
@ -32,13 +32,13 @@ const MciViewIds = {
|
|||
queueManager : {
|
||||
queue : 1,
|
||||
navMenu : 2,
|
||||
|
||||
|
||||
customRangeStart : 10,
|
||||
}
|
||||
};
|
||||
|
||||
exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
|
||||
|
||||
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
|
@ -58,7 +58,7 @@ exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
|
|||
},
|
||||
clearQueue : (formData, extraArgs, cb) => {
|
||||
this.dlQueue.clear();
|
||||
|
||||
|
||||
// :TODO: broken: does not redraw menu properly - needs fixed!
|
||||
return this.removeItemsFromDownloadQueueView('all', cb);
|
||||
},
|
||||
|
@ -109,7 +109,7 @@ exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
|
|||
|
||||
displayFileInfoForFileEntry(fileEntry) {
|
||||
this.updateCustomViewTextsWithFilter(
|
||||
'queueManager',
|
||||
'queueManager',
|
||||
MciViewIds.queueManager.customRangeStart, fileEntry,
|
||||
{ filter : [ '{webDlLink}', '{webDlExpire}', '{fileName}' ] } // :TODO: Others....
|
||||
);
|
||||
|
@ -142,7 +142,7 @@ exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
|
|||
const expireTime = moment().add(Config.fileBase.web.expireMinutes, 'minutes');
|
||||
|
||||
FileAreaWeb.createAndServeTempBatchDownload(
|
||||
this.client,
|
||||
this.client,
|
||||
this.dlQueue.items,
|
||||
{
|
||||
expireTime : expireTime
|
||||
|
@ -162,7 +162,7 @@ exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
|
|||
|
||||
this.updateCustomViewTextsWithFilter(
|
||||
'queueManager',
|
||||
MciViewIds.queueManager.customRangeStart,
|
||||
MciViewIds.queueManager.customRangeStart,
|
||||
formatObj,
|
||||
{ filter : Object.keys(formatObj).map(k => '{' + k + '}' ) }
|
||||
);
|
||||
|
@ -187,13 +187,13 @@ exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
|
|||
FileAreaWeb.getExistingTempDownloadServeItem(self.client, fileEntry, (err, serveItem) => {
|
||||
if(err) {
|
||||
if(ErrNotEnabled === err.reasonCode) {
|
||||
return nextFileEntry(err); // we should have caught this prior
|
||||
return nextFileEntry(err); // we should have caught this prior
|
||||
}
|
||||
|
||||
const expireTime = moment().add(Config.fileBase.web.expireMinutes, 'minutes');
|
||||
|
||||
|
||||
FileAreaWeb.createAndServeTempDownload(
|
||||
self.client,
|
||||
self.client,
|
||||
fileEntry,
|
||||
{ expireTime : expireTime },
|
||||
(err, url) => {
|
||||
|
@ -202,13 +202,13 @@ exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
|
|||
}
|
||||
|
||||
fileEntry.webDlLinkRaw = url;
|
||||
fileEntry.webDlLink = ansi.vtxHyperlink(self.client, url) + url;
|
||||
fileEntry.webDlLink = ansi.vtxHyperlink(self.client, url) + url;
|
||||
fileEntry.webDlExpire = expireTime.format(webDlExpireTimeFormat);
|
||||
|
||||
return nextFileEntry(null);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
} else {
|
||||
fileEntry.webDlLinkRaw = serveItem.url;
|
||||
fileEntry.webDlLink = ansi.vtxHyperlink(self.client, serveItem.url) + serveItem.url;
|
||||
fileEntry.webDlExpire = moment(serveItem.expireTimestamp).format(webDlExpireTimeFormat);
|
||||
|
@ -272,10 +272,10 @@ exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
|
|||
|
||||
return vc.loadFromMenuConfig(loadOpts, callback);
|
||||
}
|
||||
|
||||
|
||||
self.viewControllers[name].setFocus(true);
|
||||
return callback(null);
|
||||
|
||||
|
||||
},
|
||||
],
|
||||
err => {
|
||||
|
@ -284,4 +284,3 @@ exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
|
|||
);
|
||||
}
|
||||
};
|
||||
|
|
@ -15,7 +15,7 @@ const { unlink, readFile } = require('graceful-fs');
|
|||
const crypto = require('crypto');
|
||||
const moment = require('moment');
|
||||
|
||||
const FILE_TABLE_MEMBERS = [
|
||||
const FILE_TABLE_MEMBERS = [
|
||||
'file_id', 'area_tag', 'file_sha256', 'file_name', 'storage_tag',
|
||||
'desc', 'desc_long', 'upload_timestamp'
|
||||
];
|
||||
|
@ -47,7 +47,7 @@ module.exports = class FileEntry {
|
|||
// values we always want
|
||||
dl_count : 0,
|
||||
};
|
||||
|
||||
|
||||
this.hashTags = options.hashTags || new Set();
|
||||
this.fileName = options.fileName;
|
||||
this.storageTag = options.storageTag;
|
||||
|
@ -173,7 +173,7 @@ module.exports = class FileEntry {
|
|||
async.each(Object.keys(self.meta), (n, next) => {
|
||||
const v = self.meta[n];
|
||||
return FileEntry.persistMetaValue(self.fileId, n, v, trans, next);
|
||||
},
|
||||
},
|
||||
err => {
|
||||
return callback(err, trans);
|
||||
});
|
||||
|
@ -185,7 +185,7 @@ module.exports = class FileEntry {
|
|||
},
|
||||
err => {
|
||||
return callback(err, trans);
|
||||
});
|
||||
});
|
||||
}
|
||||
],
|
||||
(err, trans) => {
|
||||
|
@ -203,10 +203,10 @@ module.exports = class FileEntry {
|
|||
|
||||
static getAreaStorageDirectoryByTag(storageTag) {
|
||||
const storageLocation = (storageTag && Config.fileBase.storageTags[storageTag]);
|
||||
|
||||
|
||||
// absolute paths as-is
|
||||
if(storageLocation && '/' === storageLocation.charAt(0)) {
|
||||
return storageLocation;
|
||||
return storageLocation;
|
||||
}
|
||||
|
||||
// relative to |areaStoragePrefix|
|
||||
|
@ -283,7 +283,7 @@ module.exports = class FileEntry {
|
|||
transOrDb.serialize( () => {
|
||||
transOrDb.run(
|
||||
`INSERT OR IGNORE INTO hash_tag (hash_tag)
|
||||
VALUES (?);`,
|
||||
VALUES (?);`,
|
||||
[ hashTag ]
|
||||
);
|
||||
|
||||
|
@ -321,7 +321,7 @@ module.exports = class FileEntry {
|
|||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
loadRating(cb) {
|
||||
|
@ -352,7 +352,7 @@ module.exports = class FileEntry {
|
|||
}
|
||||
|
||||
static get WellKnownMetaValues() {
|
||||
return Object.keys(FILE_WELL_KNOWN_META);
|
||||
return Object.keys(FILE_WELL_KNOWN_META);
|
||||
}
|
||||
|
||||
static findFileBySha(sha, cb) {
|
||||
|
@ -469,7 +469,7 @@ module.exports = class FileEntry {
|
|||
|
||||
sqlOrderBy = `ORDER BY avg_rating ${sqlOrderDir}`;
|
||||
} else {
|
||||
sql =
|
||||
sql =
|
||||
`SELECT DISTINCT f.file_id, f.${filter.sort}
|
||||
FROM file f`;
|
||||
|
||||
|
@ -531,7 +531,7 @@ module.exports = class FileEntry {
|
|||
)`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
if(filter.tags && filter.tags.length > 0) {
|
||||
// build list of quoted tags; filter.tags comes in as a space and/or comma separated values
|
||||
const tags = filter.tags.replace(/,/g, ' ').replace(/\s{2,}/g, ' ').split(' ').map( tag => `"${tag}"` ).join(',');
|
||||
|
@ -617,7 +617,7 @@ module.exports = class FileEntry {
|
|||
|
||||
const srcPath = srcFileEntry.filePath;
|
||||
const dstDir = FileEntry.getAreaStorageDirectoryByTag(destStorageTag);
|
||||
|
||||
|
||||
if(!dstDir) {
|
||||
return cb(Errors.Invalid('Invalid storage tag'));
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ exports.getModule = class TransferFileModule extends MenuModule {
|
|||
}
|
||||
|
||||
if(options.extraArgs.sendQueue) {
|
||||
this.sendQueue = options.extraArgs.sendQueue;
|
||||
this.sendQueue = options.extraArgs.sendQueue;
|
||||
}
|
||||
|
||||
if(options.extraArgs.recvFileName) {
|
||||
|
@ -107,7 +107,7 @@ exports.getModule = class TransferFileModule extends MenuModule {
|
|||
return { path : item };
|
||||
} else {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.sentFileIds = [];
|
||||
|
@ -137,7 +137,7 @@ exports.getModule = class TransferFileModule extends MenuModule {
|
|||
this.sendQueue.forEach(f => {
|
||||
f.sent = true;
|
||||
sentFiles.push(f.path);
|
||||
|
||||
|
||||
});
|
||||
|
||||
this.client.log.info( { sentFiles : sentFiles }, `Successfully sent ${sentFiles.length} file(s)` );
|
||||
|
@ -160,7 +160,7 @@ exports.getModule = class TransferFileModule extends MenuModule {
|
|||
this.sendQueue.forEach(f => {
|
||||
f.sent = true;
|
||||
sentFiles.push(f.path);
|
||||
|
||||
|
||||
});
|
||||
|
||||
this.client.log.info( { sentFiles : sentFiles }, `Successfully sent ${sentFiles.length} file(s)` );
|
||||
|
@ -180,16 +180,16 @@ exports.getModule = class TransferFileModule extends MenuModule {
|
|||
}
|
||||
return next(err);
|
||||
});
|
||||
}, err => {
|
||||
}, err => {
|
||||
return cb(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
moveFileWithCollisionHandling(src, dst, cb) {
|
||||
//
|
||||
// Move |src| -> |dst| renaming to file(1).ext, file(2).ext, etc.
|
||||
// Move |src| -> |dst| renaming to file(1).ext, file(2).ext, etc.
|
||||
// in the case of collisions.
|
||||
//
|
||||
const dstPath = paths.dirname(dst);
|
||||
|
@ -283,7 +283,7 @@ exports.getModule = class TransferFileModule extends MenuModule {
|
|||
});
|
||||
}, () => {
|
||||
return cb(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -309,7 +309,7 @@ exports.getModule = class TransferFileModule extends MenuModule {
|
|||
|
||||
temptmp.open( { prefix : TEMP_SUFFIX, suffix : '.txt' }, (err, tempFileInfo) => {
|
||||
if(err) {
|
||||
return callback(err); // failed to create it
|
||||
return callback(err); // failed to create it
|
||||
}
|
||||
|
||||
fs.write(tempFileInfo.fd, filePaths.join(SYSTEM_EOL));
|
||||
|
@ -334,7 +334,7 @@ exports.getModule = class TransferFileModule extends MenuModule {
|
|||
|
||||
return callback(null, args);
|
||||
}
|
||||
],
|
||||
],
|
||||
(err, args) => {
|
||||
return cb(err, args);
|
||||
}
|
||||
|
@ -364,7 +364,7 @@ exports.getModule = class TransferFileModule extends MenuModule {
|
|||
const externalProc = pty.spawn(cmd, args, {
|
||||
cols : this.client.term.termWidth,
|
||||
rows : this.client.term.termHeight,
|
||||
cwd : this.recvDirectory,
|
||||
cwd : this.recvDirectory,
|
||||
});
|
||||
|
||||
this.client.setTemporaryDirectDataHandler(data => {
|
||||
|
@ -376,7 +376,7 @@ exports.getModule = class TransferFileModule extends MenuModule {
|
|||
externalProc.write(data);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
externalProc.on('data', data => {
|
||||
// needed for things like sz/rz
|
||||
if(external.escapeTelnet) {
|
||||
|
@ -393,12 +393,12 @@ exports.getModule = class TransferFileModule extends MenuModule {
|
|||
|
||||
externalProc.once('exit', (exitCode) => {
|
||||
this.client.log.debug( { cmd : cmd, args : args, exitCode : exitCode }, 'Process exited' );
|
||||
|
||||
|
||||
this.restorePipeAfterExternalProc();
|
||||
externalProc.removeAllListeners();
|
||||
|
||||
return cb(exitCode ? Errors.ExternalProcess(`Process exited with exit code ${exitCode}`, 'EBADEXIT') : null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
executeExternalProtocolHandlerForSend(filePaths, cb) {
|
||||
|
@ -413,7 +413,7 @@ exports.getModule = class TransferFileModule extends MenuModule {
|
|||
|
||||
this.executeExternalProtocolHandler(args, err => {
|
||||
return cb(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -434,7 +434,7 @@ exports.getModule = class TransferFileModule extends MenuModule {
|
|||
return { sentFileIds : this.sentFileIds };
|
||||
} else {
|
||||
return { recvFilePaths : this.recvFilePaths };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateSendStats(cb) {
|
||||
|
@ -478,11 +478,11 @@ exports.getModule = class TransferFileModule extends MenuModule {
|
|||
fileIds.forEach(fileId => {
|
||||
FileEntry.incrementAndPersistMetaValue(fileId, 'dl_count', 1);
|
||||
});
|
||||
|
||||
|
||||
return cb(null);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
updateRecvStats(cb) {
|
||||
let uploadBytes = 0;
|
||||
let uploadCount = 0;
|
||||
|
@ -519,7 +519,7 @@ exports.getModule = class TransferFileModule extends MenuModule {
|
|||
function validateConfig(callback) {
|
||||
if(self.isSending()) {
|
||||
if(!Array.isArray(self.sendQueue)) {
|
||||
self.sendQueue = [ self.sendQueue ];
|
||||
self.sendQueue = [ self.sendQueue ];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -555,7 +555,7 @@ exports.getModule = class TransferFileModule extends MenuModule {
|
|||
});
|
||||
}
|
||||
},
|
||||
function cleanupTempFiles(callback) {
|
||||
function cleanupTempFiles(callback) {
|
||||
temptmp.cleanup( paths => {
|
||||
Log.debug( { paths : paths, sessionId : temptmp.sessionId }, 'Temporary files cleaned up' );
|
||||
});
|
||||
|
|
|
@ -64,7 +64,7 @@ exports.getModule = class FileTransferProtocolSelectModule extends MenuModule {
|
|||
return this.gotoMenu(this.config.downloadFilesMenu || 'sendFilesToUser', modOpts, cb);
|
||||
} else {
|
||||
return this.gotoMenu(this.config.uploadFilesMenu || 'recvFilesFromUser', modOpts, cb);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -118,7 +118,7 @@ exports.getModule = class FileTransferProtocolSelectModule extends MenuModule {
|
|||
|
||||
protListView.redraw();
|
||||
|
||||
return callback(null);
|
||||
return callback(null);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
|
|
|
@ -66,11 +66,11 @@ function moveOrCopyFileWithCollisionHandling(src, dst, operation, cb) {
|
|||
(err, finalPath) => {
|
||||
return cb(err, finalPath);
|
||||
}
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
// Move |src| -> |dst| renaming to file(1).ext, file(2).ext, etc.
|
||||
// Move |src| -> |dst| renaming to file(1).ext, file(2).ext, etc.
|
||||
// in the case of collisions.
|
||||
//
|
||||
function moveFileWithCollisionHandling(src, dst, cb) {
|
||||
|
|
|
@ -7,7 +7,7 @@ let _ = require('lodash');
|
|||
module.exports = class FNV1a {
|
||||
constructor(data) {
|
||||
this.hash = 0x811c9dc5;
|
||||
|
||||
|
||||
if(!_.isUndefined(data)) {
|
||||
this.update(data);
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ module.exports = class FNV1a {
|
|||
if(_.isNumber(data)) {
|
||||
data = data.toString();
|
||||
}
|
||||
|
||||
|
||||
if(_.isString(data)) {
|
||||
data = new Buffer(data);
|
||||
}
|
||||
|
@ -28,8 +28,8 @@ module.exports = class FNV1a {
|
|||
|
||||
for(let b of data) {
|
||||
this.hash = this.hash ^ b;
|
||||
this.hash +=
|
||||
(this.hash << 24) + (this.hash << 8) + (this.hash << 7) +
|
||||
this.hash +=
|
||||
(this.hash << 24) + (this.hash << 8) + (this.hash << 7) +
|
||||
(this.hash << 4) + (this.hash << 1);
|
||||
}
|
||||
|
||||
|
@ -46,5 +46,5 @@ module.exports = class FNV1a {
|
|||
get value() {
|
||||
return this.hash & 0xffffffff;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
134
core/fse.js
134
core/fse.js
|
@ -39,7 +39,7 @@ exports.moduleInfo = {
|
|||
TL2 - To
|
||||
TL3 - Subject
|
||||
TL4 - Area name
|
||||
|
||||
|
||||
TL5 - Date/Time (TODO: format)
|
||||
TL6 - Message number
|
||||
TL7 - Mesage total (in area)
|
||||
|
@ -50,7 +50,7 @@ exports.moduleInfo = {
|
|||
|
||||
TL12 - User1
|
||||
TL13 - User2
|
||||
|
||||
|
||||
|
||||
Footer - Viewing
|
||||
HM1 - Menu (prev/next/etc.)
|
||||
|
@ -61,14 +61,14 @@ exports.moduleInfo = {
|
|||
TL12 - User1 (fmt message object)
|
||||
TL13 - User2
|
||||
|
||||
|
||||
|
||||
*/
|
||||
const MciCodeIds = {
|
||||
ViewModeHeader : {
|
||||
From : 1,
|
||||
To : 2,
|
||||
Subject : 3,
|
||||
|
||||
|
||||
DateTime : 5,
|
||||
MsgNum : 6,
|
||||
MsgTotal : 7,
|
||||
|
@ -78,9 +78,9 @@ const MciCodeIds = {
|
|||
ReplyToMsgID : 11,
|
||||
|
||||
// :TODO: ConfName
|
||||
|
||||
|
||||
},
|
||||
|
||||
|
||||
ViewModeFooter : {
|
||||
MsgNum : 6,
|
||||
MsgTotal : 7,
|
||||
|
@ -90,7 +90,7 @@ const MciCodeIds = {
|
|||
From : 1,
|
||||
To : 2,
|
||||
Subject : 3,
|
||||
|
||||
|
||||
ErrorMsg : 13,
|
||||
},
|
||||
};
|
||||
|
@ -116,12 +116,12 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
|||
// toUserId
|
||||
//
|
||||
this.editorType = config.editorType;
|
||||
this.editorMode = config.editorMode;
|
||||
|
||||
this.editorMode = config.editorMode;
|
||||
|
||||
if(config.messageAreaTag) {
|
||||
this.messageAreaTag = config.messageAreaTag;
|
||||
}
|
||||
|
||||
|
||||
this.messageIndex = config.messageIndex || 0;
|
||||
this.messageTotal = config.messageTotal || 0;
|
||||
this.toUserId = config.toUserId || 0;
|
||||
|
@ -160,7 +160,7 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
|||
if(errMsgView) {
|
||||
if(err) {
|
||||
errMsgView.setText(err.message);
|
||||
|
||||
|
||||
if(MciCodeIds.ViewModeHeader.Subject === err.view.getId()) {
|
||||
// :TODO: for "area" mode, should probably just bail if this is emtpy (e.g. cancel)
|
||||
}
|
||||
|
@ -179,26 +179,24 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
|||
|
||||
self.switchFooter(function next(err) {
|
||||
if(err) {
|
||||
// :TODO:... what now?
|
||||
console.log(err)
|
||||
} else {
|
||||
switch(self.footerMode) {
|
||||
case 'editor' :
|
||||
if(!_.isUndefined(self.viewControllers.footerEditorMenu)) {
|
||||
//self.viewControllers.footerEditorMenu.setFocus(false);
|
||||
self.viewControllers.footerEditorMenu.detachClientEvents();
|
||||
}
|
||||
self.viewControllers.body.switchFocus(1);
|
||||
self.observeEditorEvents();
|
||||
break;
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
case 'editorMenu' :
|
||||
self.viewControllers.body.setFocus(false);
|
||||
self.viewControllers.footerEditorMenu.switchFocus(1);
|
||||
break;
|
||||
switch(self.footerMode) {
|
||||
case 'editor' :
|
||||
if(!_.isUndefined(self.viewControllers.footerEditorMenu)) {
|
||||
self.viewControllers.footerEditorMenu.detachClientEvents();
|
||||
}
|
||||
self.viewControllers.body.switchFocus(1);
|
||||
self.observeEditorEvents();
|
||||
break;
|
||||
|
||||
default : throw new Error('Unexpected mode');
|
||||
}
|
||||
case 'editorMenu' :
|
||||
self.viewControllers.body.setFocus(false);
|
||||
self.viewControllers.footerEditorMenu.switchFocus(1);
|
||||
break;
|
||||
|
||||
default : throw new Error('Unexpected mode');
|
||||
}
|
||||
|
||||
return cb(null);
|
||||
|
@ -210,9 +208,9 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
|||
return cb(null);
|
||||
},
|
||||
appendQuoteEntry: function(formData, extraArgs, cb) {
|
||||
// :TODO: Dont' use magic # ID's here
|
||||
// :TODO: Dont' use magic # ID's here
|
||||
const quoteMsgView = self.viewControllers.quoteBuilder.getView(1);
|
||||
|
||||
|
||||
if(self.newQuoteBlock) {
|
||||
self.newQuoteBlock = false;
|
||||
|
||||
|
@ -220,7 +218,7 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
|||
|
||||
quoteMsgView.addText(self.getQuoteByHeader());
|
||||
}
|
||||
|
||||
|
||||
const quoteText = self.viewControllers.quoteBuilder.getView(3).getItem(formData.value.quote);
|
||||
quoteMsgView.addText(quoteText);
|
||||
|
||||
|
@ -339,7 +337,7 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
|||
//
|
||||
// Ensure first characters indicate ANSI for detection down
|
||||
// the line (other boards/etc.). We also set explicit_encoding
|
||||
// to packetAnsiMsgEncoding (generally cp437) as various boards
|
||||
// to packetAnsiMsgEncoding (generally cp437) as various boards
|
||||
// really don't like ANSI messages in UTF-8 encoding (they should!)
|
||||
//
|
||||
msgOpts.meta = { System : { 'explicit_encoding' : Config.scannerTossers.ftn_bso.packetAnsiMsgEncoding || 'cp437' } };
|
||||
|
@ -351,7 +349,7 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
|||
|
||||
return cb(null);
|
||||
}
|
||||
|
||||
|
||||
setMessage(message) {
|
||||
this.message = message;
|
||||
|
||||
|
@ -495,7 +493,7 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
|||
// :TODO: We'd like to delete up to N rows, but this does not work
|
||||
// in NetRunner:
|
||||
self.client.term.rawWrite(ansi.reset() + ansi.deleteLine(3));
|
||||
|
||||
|
||||
self.client.term.rawWrite(ansi.reset() + ansi.eraseLine(2));
|
||||
}
|
||||
callback(null);
|
||||
|
@ -534,7 +532,7 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
|||
art[n],
|
||||
self.client,
|
||||
{ font : self.menuConfig.font },
|
||||
function displayed(err, artData) {
|
||||
function displayed(err) {
|
||||
next(err);
|
||||
}
|
||||
);
|
||||
|
@ -561,7 +559,7 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
|||
function complete(err) {
|
||||
cb(err);
|
||||
}
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
switchFooter(cb) {
|
||||
|
@ -645,14 +643,13 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
|||
],
|
||||
function complete(err) {
|
||||
if(err) {
|
||||
// :TODO: This needs properly handled!
|
||||
console.log(err)
|
||||
self.client.log.warn( { error : err.message }, 'FSE init error');
|
||||
} else {
|
||||
self.isReady = true;
|
||||
self.finishedLoading();
|
||||
}
|
||||
}
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
createInitialViews(mciData, cb) {
|
||||
|
@ -666,7 +663,7 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
|||
menuLoadOpts.mciMap = mciData.header.mciMap;
|
||||
|
||||
self.addViewController(
|
||||
'header',
|
||||
'header',
|
||||
new ViewController( { client : self.client, formId : menuLoadOpts.formId } )
|
||||
).loadFromMenuConfig(menuLoadOpts, function headerReady(err) {
|
||||
callback(err);
|
||||
|
@ -713,7 +710,7 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
|||
},
|
||||
function setInitialData(callback) {
|
||||
|
||||
switch(self.editorMode) {
|
||||
switch(self.editorMode) {
|
||||
case 'view' :
|
||||
if(self.message) {
|
||||
self.initHeaderViewMode();
|
||||
|
@ -726,7 +723,7 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
|||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case 'edit' :
|
||||
{
|
||||
const fromView = self.viewControllers.header.getView(1);
|
||||
|
@ -747,9 +744,9 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
|||
callback(null);
|
||||
},
|
||||
function setInitialFocus(callback) {
|
||||
|
||||
|
||||
switch(self.editorMode) {
|
||||
case 'edit' :
|
||||
case 'edit' :
|
||||
self.switchToHeader();
|
||||
break;
|
||||
|
||||
|
@ -763,10 +760,7 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
|||
}
|
||||
],
|
||||
function complete(err) {
|
||||
if(err) {
|
||||
console.error(err)
|
||||
}
|
||||
cb(err);
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -774,7 +768,7 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
|||
mciReadyHandler(mciData, cb) {
|
||||
|
||||
this.createInitialViews(mciData, err => {
|
||||
// :TODO: Can probably be replaced with @systemMethod:validateUserNameExists when the framework is in
|
||||
// :TODO: Can probably be replaced with @systemMethod:validateUserNameExists when the framework is in
|
||||
// place - if this is for existing usernames else validate spec
|
||||
|
||||
/*
|
||||
|
@ -787,7 +781,7 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
|||
view.clearText();
|
||||
self.viewControllers.headers.switchFocus(2);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});*/
|
||||
|
||||
|
@ -813,7 +807,7 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
|||
if(modeView) {
|
||||
this.client.term.rawWrite(ansi.savePos());
|
||||
modeView.setText('insert' === mode ? 'INS' : 'OVR');
|
||||
this.client.term.rawWrite(ansi.restorePos());
|
||||
this.client.term.rawWrite(ansi.restorePos());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -824,7 +818,7 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
|||
|
||||
initHeaderViewMode() {
|
||||
assert(_.isObject(this.message));
|
||||
|
||||
|
||||
this.setHeaderText(MciCodeIds.ViewModeHeader.From, this.message.fromUserName);
|
||||
this.setHeaderText(MciCodeIds.ViewModeHeader.To, this.message.toUserName);
|
||||
this.setHeaderText(MciCodeIds.ViewModeHeader.Subject, this.message.subject);
|
||||
|
@ -881,7 +875,7 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
|||
//
|
||||
this.newQuoteBlock = true;
|
||||
const self = this;
|
||||
|
||||
|
||||
async.waterfall(
|
||||
[
|
||||
function clearAndDisplayArt(callback) {
|
||||
|
@ -892,23 +886,23 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
|||
self.client.term.rawWrite(
|
||||
ansi.goto(self.header.height + 1, 1) +
|
||||
ansi.deleteLine(24 - self.header.height));
|
||||
|
||||
|
||||
theme.displayThemeArt( { name : self.menuConfig.config.art.quote, client : self.client }, function displayed(err, artData) {
|
||||
callback(err, artData);
|
||||
});
|
||||
},
|
||||
function createViewsIfNecessary(artData, callback) {
|
||||
var formId = self.getFormId('quoteBuilder');
|
||||
|
||||
|
||||
if(_.isUndefined(self.viewControllers.quoteBuilder)) {
|
||||
var menuLoadOpts = {
|
||||
callingMenu : self,
|
||||
formId : formId,
|
||||
mciMap : artData.mciMap,
|
||||
mciMap : artData.mciMap,
|
||||
};
|
||||
|
||||
|
||||
self.addViewController(
|
||||
'quoteBuilder',
|
||||
'quoteBuilder',
|
||||
new ViewController( { client : self.client, formId : formId } )
|
||||
).loadFromMenuConfig(menuLoadOpts, function quoteViewsReady(err) {
|
||||
callback(err);
|
||||
|
@ -954,10 +948,10 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
|||
],
|
||||
function complete(err) {
|
||||
if(err) {
|
||||
console.log(err) // :TODO: needs real impl.
|
||||
self.client.log.warn( { error : err.message }, 'Error displaying quote builder');
|
||||
}
|
||||
}
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
observeEditorEvents() {
|
||||
|
@ -1004,22 +998,22 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
|||
var body = this.viewControllers.body.getView(1);
|
||||
body.redraw();
|
||||
this.viewControllers.body.switchFocus(1);
|
||||
|
||||
|
||||
// :TODO: create method (DRY)
|
||||
|
||||
|
||||
this.updateTextEditMode(body.getTextEditMode());
|
||||
this.updateEditModePosition(body.getEditPosition());
|
||||
|
||||
this.observeEditorEvents();
|
||||
}
|
||||
|
||||
|
||||
quoteBuilderFinalize() {
|
||||
// :TODO: fix magic #'s
|
||||
const quoteMsgView = this.viewControllers.quoteBuilder.getView(1);
|
||||
const msgView = this.viewControllers.body.getView(1);
|
||||
|
||||
|
||||
let quoteLines = quoteMsgView.getData().trim();
|
||||
|
||||
|
||||
if(quoteLines.length > 0) {
|
||||
if(this.replyIsAnsi) {
|
||||
const bodyMessageView = this.viewControllers.body.getView(1);
|
||||
|
@ -1027,7 +1021,7 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
|||
}
|
||||
msgView.addText(`${quoteLines}\n\n`);
|
||||
}
|
||||
|
||||
|
||||
quoteMsgView.setText('');
|
||||
|
||||
this.footerMode = 'editor';
|
||||
|
@ -1040,14 +1034,14 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
|||
getQuoteByHeader() {
|
||||
let quoteFormat = this.menuConfig.config.quoteFormats;
|
||||
|
||||
if(Array.isArray(quoteFormat)) {
|
||||
if(Array.isArray(quoteFormat)) {
|
||||
quoteFormat = quoteFormat[ Math.floor(Math.random() * quoteFormat.length) ];
|
||||
} else if(!_.isString(quoteFormat)) {
|
||||
quoteFormat = 'On {dateTime} {userName} said...';
|
||||
}
|
||||
|
||||
const dtFormat = this.menuConfig.config.quoteDateTimeFormat || this.client.currentTheme.helpers.getDateTimeFormat();
|
||||
return stringFormat(quoteFormat, {
|
||||
const dtFormat = this.menuConfig.config.quoteDateTimeFormat || this.client.currentTheme.helpers.getDateTimeFormat();
|
||||
return stringFormat(quoteFormat, {
|
||||
dateTime : moment(this.replyToMessage.modTimestamp).format(dtFormat),
|
||||
userName : this.replyToMessage.fromUserName,
|
||||
});
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
|
||||
const _ = require('lodash');
|
||||
|
||||
const FTN_ADDRESS_REGEXP = /^([0-9]+:)?([0-9]+)(\/[0-9]+)?(\.[0-9]+)?(@[a-z0-9\-\.]+)?$/i;
|
||||
const FTN_PATTERN_REGEXP = /^([0-9\*]+:)?([0-9\*]+)(\/[0-9\*]+)?(\.[0-9\*]+)?(@[a-z0-9\-\.\*]+)?$/i;
|
||||
const FTN_ADDRESS_REGEXP = /^([0-9]+:)?([0-9]+)(\/[0-9]+)?(\.[0-9]+)?(@[a-z0-9\-.]+)?$/i;
|
||||
const FTN_PATTERN_REGEXP = /^([0-9*]+:)?([0-9*]+)(\/[0-9*]+)?(\.[0-9*]+)?(@[a-z0-9\-.*]+)?$/i;
|
||||
|
||||
module.exports = class Address {
|
||||
constructor(addr) {
|
||||
|
@ -133,7 +133,7 @@ module.exports = class Address {
|
|||
|
||||
static fromString(addrStr) {
|
||||
const m = FTN_ADDRESS_REGEXP.exec(addrStr);
|
||||
|
||||
|
||||
if(m) {
|
||||
// start with a 2D
|
||||
let addr = {
|
||||
|
@ -165,7 +165,7 @@ module.exports = class Address {
|
|||
|
||||
let addrStr = `${this.zone}:${this.net}`;
|
||||
|
||||
// allow for e.g. '4D' or 5
|
||||
// allow for e.g. '4D' or 5
|
||||
const dim = parseInt(dimensions.toString()[0]);
|
||||
|
||||
if(dim >= 3) {
|
||||
|
|
|
@ -56,7 +56,7 @@ class PacketHeader {
|
|||
this.capWordValidate = ((this.capWord & 0xff) << 8) | ((this.capWord >> 8) & 0xff); // swap
|
||||
|
||||
this.prodCodeHi = 0xfe; // see above
|
||||
this.prodRevHi = 0;
|
||||
this.prodRevHi = 0;
|
||||
}
|
||||
|
||||
get origAddress() {
|
||||
|
@ -84,9 +84,9 @@ class PacketHeader {
|
|||
|
||||
// See FSC-48
|
||||
// :TODO: disabled for now until we have separate packet writers for 2, 2+, 2+48, and 2.2
|
||||
/*if(address.point) {
|
||||
/*if(address.point) {
|
||||
this.auxNet = address.origNet;
|
||||
this.origNet = -1;
|
||||
this.origNet = -1;
|
||||
} else {
|
||||
this.origNet = address.net;
|
||||
this.auxNet = 0;
|
||||
|
@ -158,16 +158,16 @@ exports.PacketHeader = PacketHeader;
|
|||
//
|
||||
// * Type 2 FTS-0001 @ http://ftsc.org/docs/fts-0001.016 (Obsolete)
|
||||
// * Type 2.2 FSC-0045 @ http://ftsc.org/docs/fsc-0045.001
|
||||
// * Type 2+ FSC-0039 and FSC-0048 @ http://ftsc.org/docs/fsc-0039.004
|
||||
// * Type 2+ FSC-0039 and FSC-0048 @ http://ftsc.org/docs/fsc-0039.004
|
||||
// and http://ftsc.org/docs/fsc-0048.002
|
||||
//
|
||||
//
|
||||
// Additional resources:
|
||||
// * Writeup on differences between type 2, 2.2, and 2+:
|
||||
// http://walon.org/pub/fidonet/FTSC-nodelists-etc./pkt-types.txt
|
||||
//
|
||||
function Packet(options) {
|
||||
var self = this;
|
||||
|
||||
|
||||
this.options = options || {};
|
||||
|
||||
this.parsePacketHeader = function(packetBuffer, cb) {
|
||||
|
@ -240,11 +240,11 @@ function Packet(options) {
|
|||
//
|
||||
// See heuristics described in FSC-0048, "Receiving Type-2+ bundles"
|
||||
//
|
||||
const capWordValidateSwapped =
|
||||
const capWordValidateSwapped =
|
||||
((packetHeader.capWordValidate & 0xff) << 8) |
|
||||
((packetHeader.capWordValidate >> 8) & 0xff);
|
||||
|
||||
if(capWordValidateSwapped === packetHeader.capWord &&
|
||||
if(capWordValidateSwapped === packetHeader.capWord &&
|
||||
0 != packetHeader.capWord &&
|
||||
packetHeader.capWord & 0x0001)
|
||||
{
|
||||
|
@ -260,7 +260,7 @@ function Packet(options) {
|
|||
// :TODO: should fill bytes be 0?
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
packetHeader.created = moment({
|
||||
year : packetHeader.year,
|
||||
month : packetHeader.month - 1, // moment uses 0 indexed months
|
||||
|
@ -269,36 +269,36 @@ function Packet(options) {
|
|||
minute : packetHeader.minute,
|
||||
second : packetHeader.second
|
||||
});
|
||||
|
||||
|
||||
let ph = new PacketHeader();
|
||||
_.assign(ph, packetHeader);
|
||||
|
||||
cb(null, ph);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
this.getPacketHeaderBuffer = function(packetHeader) {
|
||||
let buffer = new Buffer(FTN_PACKET_HEADER_SIZE);
|
||||
|
||||
buffer.writeUInt16LE(packetHeader.origNode, 0);
|
||||
buffer.writeUInt16LE(packetHeader.destNode, 2);
|
||||
buffer.writeUInt16LE(packetHeader.year, 4);
|
||||
buffer.writeUInt16LE(packetHeader.month, 6);
|
||||
buffer.writeUInt16LE(packetHeader.month, 6);
|
||||
buffer.writeUInt16LE(packetHeader.day, 8);
|
||||
buffer.writeUInt16LE(packetHeader.hour, 10);
|
||||
buffer.writeUInt16LE(packetHeader.minute, 12);
|
||||
buffer.writeUInt16LE(packetHeader.second, 14);
|
||||
|
||||
|
||||
buffer.writeUInt16LE(packetHeader.baud, 16);
|
||||
buffer.writeUInt16LE(FTN_PACKET_HEADER_TYPE, 18);
|
||||
buffer.writeUInt16LE(-1 === packetHeader.origNet ? 0xffff : packetHeader.origNet, 20);
|
||||
buffer.writeUInt16LE(packetHeader.destNet, 22);
|
||||
buffer.writeUInt8(packetHeader.prodCodeLo, 24);
|
||||
buffer.writeUInt8(packetHeader.prodRevHi, 25);
|
||||
|
||||
|
||||
const pass = ftn.stringToNullPaddedBuffer(packetHeader.password, 8);
|
||||
pass.copy(buffer, 26);
|
||||
|
||||
|
||||
buffer.writeUInt16LE(packetHeader.origZone, 34);
|
||||
buffer.writeUInt16LE(packetHeader.destZone, 36);
|
||||
buffer.writeUInt16LE(packetHeader.auxNet, 38);
|
||||
|
@ -311,7 +311,7 @@ function Packet(options) {
|
|||
buffer.writeUInt16LE(packetHeader.origPoint, 50);
|
||||
buffer.writeUInt16LE(packetHeader.destPoint, 52);
|
||||
buffer.writeUInt32LE(packetHeader.prodData, 54);
|
||||
|
||||
|
||||
return buffer;
|
||||
};
|
||||
|
||||
|
@ -321,22 +321,22 @@ function Packet(options) {
|
|||
buffer.writeUInt16LE(packetHeader.origNode, 0);
|
||||
buffer.writeUInt16LE(packetHeader.destNode, 2);
|
||||
buffer.writeUInt16LE(packetHeader.year, 4);
|
||||
buffer.writeUInt16LE(packetHeader.month, 6);
|
||||
buffer.writeUInt16LE(packetHeader.month, 6);
|
||||
buffer.writeUInt16LE(packetHeader.day, 8);
|
||||
buffer.writeUInt16LE(packetHeader.hour, 10);
|
||||
buffer.writeUInt16LE(packetHeader.minute, 12);
|
||||
buffer.writeUInt16LE(packetHeader.second, 14);
|
||||
|
||||
|
||||
buffer.writeUInt16LE(packetHeader.baud, 16);
|
||||
buffer.writeUInt16LE(FTN_PACKET_HEADER_TYPE, 18);
|
||||
buffer.writeUInt16LE(-1 === packetHeader.origNet ? 0xffff : packetHeader.origNet, 20);
|
||||
buffer.writeUInt16LE(packetHeader.destNet, 22);
|
||||
buffer.writeUInt8(packetHeader.prodCodeLo, 24);
|
||||
buffer.writeUInt8(packetHeader.prodRevHi, 25);
|
||||
|
||||
|
||||
const pass = ftn.stringToNullPaddedBuffer(packetHeader.password, 8);
|
||||
pass.copy(buffer, 26);
|
||||
|
||||
|
||||
buffer.writeUInt16LE(packetHeader.origZone, 34);
|
||||
buffer.writeUInt16LE(packetHeader.destZone, 36);
|
||||
buffer.writeUInt16LE(packetHeader.auxNet, 38);
|
||||
|
@ -376,9 +376,9 @@ function Packet(options) {
|
|||
// likely need to re-decode as the specified encoding
|
||||
// * SAUCE is binary-ish data, so we need to inspect for it before any
|
||||
// decoding occurs
|
||||
//
|
||||
//
|
||||
let messageBodyData = {
|
||||
message : [],
|
||||
message : [],
|
||||
kludgeLines : {}, // KLUDGE:[value1, value2, ...] map
|
||||
seenBy : [],
|
||||
};
|
||||
|
@ -411,7 +411,7 @@ function Packet(options) {
|
|||
messageBodyData.kludgeLines[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let encoding = 'cp437';
|
||||
|
||||
async.series(
|
||||
|
@ -426,12 +426,12 @@ function Packet(options) {
|
|||
if(!err) {
|
||||
// we read some SAUCE - don't re-process that portion into the body
|
||||
messageBodyBuffer = messageBodyBuffer.slice(0, sauceHeaderPosition) + messageBodyBuffer.slice(sauceHeaderPosition + sauce.SAUCE_SIZE);
|
||||
// messageBodyBuffer = messageBodyBuffer.slice(0, sauceHeaderPosition);
|
||||
// messageBodyBuffer = messageBodyBuffer.slice(0, sauceHeaderPosition);
|
||||
messageBodyData.sauce = theSauce;
|
||||
} else {
|
||||
console.log(err)
|
||||
Log.warn( { error : err.message }, 'Found what looks like to be a SAUCE record, but failed to read');
|
||||
}
|
||||
callback(null); // failure to read SAUCE is OK
|
||||
return callback(null); // failure to read SAUCE is OK
|
||||
});
|
||||
} else {
|
||||
callback(null);
|
||||
|
@ -482,7 +482,7 @@ function Packet(options) {
|
|||
Log.debug( { encoding : encoding, error : e.toString() }, 'Error decoding. Falling back to ASCII');
|
||||
decoded = iconv.decode(messageBodyBuffer, 'ascii');
|
||||
}
|
||||
|
||||
|
||||
const messageLines = strUtil.splitTextAtTerms(decoded.replace(/\xec/g, ''));
|
||||
let endOfMessage = false;
|
||||
|
||||
|
@ -491,13 +491,13 @@ function Packet(options) {
|
|||
messageBodyData.message.push('');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if(line.startsWith('AREA:')) {
|
||||
messageBodyData.area = line.substring(line.indexOf(':') + 1).trim();
|
||||
} else if(line.startsWith('--- ')) {
|
||||
// Tear Lines are tracked allowing for specialized display/etc.
|
||||
messageBodyData.tearLine = line;
|
||||
} else if(/^[ ]{1,2}\* Origin\: /.test(line)) { // To spec is " * Origin: ..."
|
||||
} else if(/^[ ]{1,2}\* Origin: /.test(line)) { // To spec is " * Origin: ..."
|
||||
messageBodyData.originLine = line;
|
||||
endOfMessage = true; // Anything past origin is not part of the message body
|
||||
} else if(line.startsWith('SEEN-BY:')) {
|
||||
|
@ -523,7 +523,7 @@ function Packet(options) {
|
|||
}
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
this.parsePacketMessages = function(packetBuffer, iterator, cb) {
|
||||
binary.parse(packetBuffer)
|
||||
.word16lu('messageType')
|
||||
|
@ -540,22 +540,22 @@ function Packet(options) {
|
|||
.scan('message', NULL_TERM_BUFFER)
|
||||
.tap(function tapped(msgData) { // no arrow function; want classic this
|
||||
if(!msgData.messageType) {
|
||||
// end marker -- no more messages
|
||||
// end marker -- no more messages
|
||||
return cb(null);
|
||||
}
|
||||
|
||||
|
||||
if(FTN_PACKET_MESSAGE_TYPE != msgData.messageType) {
|
||||
return cb(new Error('Unsupported message type: ' + msgData.messageType));
|
||||
}
|
||||
|
||||
const read =
|
||||
|
||||
const read =
|
||||
14 + // fixed header size
|
||||
msgData.modDateTime.length + 1 +
|
||||
msgData.toUserName.length + 1 +
|
||||
msgData.fromUserName.length + 1 +
|
||||
msgData.subject.length + 1 +
|
||||
msgData.message.length + 1;
|
||||
|
||||
|
||||
//
|
||||
// Convert null terminated arrays to strings
|
||||
//
|
||||
|
@ -575,7 +575,7 @@ function Packet(options) {
|
|||
subject : convMsgData.subject,
|
||||
modTimestamp : ftn.getDateFromFtnDateTime(convMsgData.modDateTime),
|
||||
});
|
||||
|
||||
|
||||
msg.meta.FtnProperty = {};
|
||||
msg.meta.FtnProperty.ftn_orig_node = msgData.ftn_orig_node;
|
||||
msg.meta.FtnProperty.ftn_dest_node = msgData.ftn_dest_node;
|
||||
|
@ -587,31 +587,31 @@ function Packet(options) {
|
|||
self.processMessageBody(msgData.message, messageBodyData => {
|
||||
msg.message = messageBodyData.message;
|
||||
msg.meta.FtnKludge = messageBodyData.kludgeLines;
|
||||
|
||||
|
||||
if(messageBodyData.tearLine) {
|
||||
msg.meta.FtnProperty.ftn_tear_line = messageBodyData.tearLine;
|
||||
|
||||
|
||||
if(self.options.keepTearAndOrigin) {
|
||||
msg.message += `\r\n${messageBodyData.tearLine}\r\n`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(messageBodyData.seenBy.length > 0) {
|
||||
msg.meta.FtnProperty.ftn_seen_by = messageBodyData.seenBy;
|
||||
}
|
||||
|
||||
|
||||
if(messageBodyData.area) {
|
||||
msg.meta.FtnProperty.ftn_area = messageBodyData.area;
|
||||
}
|
||||
|
||||
|
||||
if(messageBodyData.originLine) {
|
||||
msg.meta.FtnProperty.ftn_origin = messageBodyData.originLine;
|
||||
|
||||
|
||||
if(self.options.keepTearAndOrigin) {
|
||||
msg.message += `${messageBodyData.originLine}\r\n`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// If we have a UTC offset kludge (e.g. TZUTC) then update
|
||||
// modDateTime with it
|
||||
|
@ -619,7 +619,7 @@ function Packet(options) {
|
|||
if(_.isString(msg.meta.FtnKludge.TZUTC) && msg.meta.FtnKludge.TZUTC.length > 0) {
|
||||
msg.modDateTime = msg.modTimestamp.utcOffset(msg.meta.FtnKludge.TZUTC);
|
||||
}
|
||||
|
||||
|
||||
const nextBuf = packetBuffer.slice(read);
|
||||
if(nextBuf.length > 0) {
|
||||
let next = function(e) {
|
||||
|
@ -629,12 +629,12 @@ function Packet(options) {
|
|||
self.parsePacketMessages(nextBuf, iterator, cb);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
iterator('message', msg, next);
|
||||
} else {
|
||||
cb(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -702,7 +702,7 @@ function Packet(options) {
|
|||
|
||||
//
|
||||
// message: unbound length, NULL term'd
|
||||
//
|
||||
//
|
||||
// We need to build in various special lines - kludges, area,
|
||||
// seen-by, etc.
|
||||
//
|
||||
|
@ -716,7 +716,7 @@ function Packet(options) {
|
|||
if(message.meta.FtnProperty.ftn_area) {
|
||||
msgBody += `AREA:${message.meta.FtnProperty.ftn_area}\r`; // note: no ^A (0x01)
|
||||
}
|
||||
|
||||
|
||||
// :TODO: DRY with similar function in this file!
|
||||
Object.keys(message.meta.FtnKludge).forEach(k => {
|
||||
switch(k) {
|
||||
|
@ -731,7 +731,7 @@ function Packet(options) {
|
|||
break;
|
||||
|
||||
default :
|
||||
msgBody += getAppendMeta(`\x01${k}`, message.meta.FtnKludge[k]);
|
||||
msgBody += getAppendMeta(`\x01${k}`, message.meta.FtnKludge[k]);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
@ -780,22 +780,22 @@ function Packet(options) {
|
|||
//
|
||||
msgBody += getAppendMeta('SEEN-BY', message.meta.FtnProperty.ftn_seen_by); // note: no ^A (0x01)
|
||||
msgBody += getAppendMeta('\x01PATH', message.meta.FtnKludge['PATH']);
|
||||
|
||||
|
||||
let msgBodyEncoded;
|
||||
try {
|
||||
msgBodyEncoded = iconv.encode(msgBody + '\0', options.encoding);
|
||||
} catch(e) {
|
||||
msgBodyEncoded = iconv.encode(msgBody + '\0', 'ascii');
|
||||
}
|
||||
|
||||
|
||||
return callback(
|
||||
null,
|
||||
Buffer.concat( [
|
||||
basicHeader,
|
||||
toUserNameBuf,
|
||||
fromUserNameBuf,
|
||||
null,
|
||||
Buffer.concat( [
|
||||
basicHeader,
|
||||
toUserNameBuf,
|
||||
fromUserNameBuf,
|
||||
subjectBuf,
|
||||
msgBodyEncoded
|
||||
msgBodyEncoded
|
||||
])
|
||||
);
|
||||
}
|
||||
|
@ -808,7 +808,7 @@ function Packet(options) {
|
|||
|
||||
this.writeMessage = function(message, ws, options) {
|
||||
let basicHeader = new Buffer(34);
|
||||
|
||||
|
||||
basicHeader.writeUInt16LE(FTN_PACKET_MESSAGE_TYPE, 0);
|
||||
basicHeader.writeUInt16LE(message.meta.FtnProperty.ftn_orig_node, 2);
|
||||
basicHeader.writeUInt16LE(message.meta.FtnProperty.ftn_dest_node, 4);
|
||||
|
@ -827,7 +827,7 @@ function Packet(options) {
|
|||
let encBuf = iconv.encode(message.toUserName + '\0', 'CP437').slice(0, 36);
|
||||
encBuf[encBuf.length - 1] = '\0'; // ensure it's null term'd
|
||||
ws.write(encBuf);
|
||||
|
||||
|
||||
encBuf = iconv.encode(message.fromUserName + '\0', 'CP437').slice(0, 36);
|
||||
encBuf[encBuf.length - 1] = '\0'; // ensure it's null term'd
|
||||
ws.write(encBuf);
|
||||
|
@ -839,7 +839,7 @@ function Packet(options) {
|
|||
|
||||
//
|
||||
// message: unbound length, NULL term'd
|
||||
//
|
||||
//
|
||||
// We need to build in various special lines - kludges, area,
|
||||
// seen-by, etc.
|
||||
//
|
||||
|
@ -866,7 +866,7 @@ function Packet(options) {
|
|||
if(message.meta.FtnProperty.ftn_area) {
|
||||
msgBody += `AREA:${message.meta.FtnProperty.ftn_area}\r`; // note: no ^A (0x01)
|
||||
}
|
||||
|
||||
|
||||
Object.keys(message.meta.FtnKludge).forEach(k => {
|
||||
switch(k) {
|
||||
case 'PATH' : break; // skip & save for last
|
||||
|
@ -889,8 +889,8 @@ function Packet(options) {
|
|||
if(message.meta.FtnProperty.ftn_tear_line) {
|
||||
msgBody += `${message.meta.FtnProperty.ftn_tear_line}\r`;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
//
|
||||
// Origin line should be near the bottom of a message
|
||||
//
|
||||
if(message.meta.FtnProperty.ftn_origin) {
|
||||
|
@ -918,11 +918,11 @@ function Packet(options) {
|
|||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
|
||||
let next = function(e) {
|
||||
callback(e);
|
||||
};
|
||||
|
||||
|
||||
iterator('header', header, next);
|
||||
});
|
||||
},
|
||||
|
@ -934,7 +934,7 @@ function Packet(options) {
|
|||
}
|
||||
],
|
||||
cb // complete
|
||||
);
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -954,7 +954,7 @@ Packet.Attribute = {
|
|||
InTransit : 0x0020,
|
||||
Orphan : 0x0040,
|
||||
KillSent : 0x0080,
|
||||
Local : 0x0100, // Message is from *this* system
|
||||
Local : 0x0100, // Message is from *this* system
|
||||
Hold : 0x0200,
|
||||
Reserved0 : 0x0400,
|
||||
FileRequest : 0x0800,
|
||||
|
@ -998,7 +998,7 @@ Packet.prototype.writeHeader = function(ws, packetHeader) {
|
|||
|
||||
Packet.prototype.writeMessageEntry = function(ws, msgEntry) {
|
||||
ws.write(msgEntry);
|
||||
return msgEntry.length;
|
||||
return msgEntry.length;
|
||||
};
|
||||
|
||||
Packet.prototype.writeTerminator = function(ws) {
|
||||
|
@ -1014,11 +1014,11 @@ Packet.prototype.writeStream = function(ws, messages, options) {
|
|||
if(!_.isBoolean(options.terminatePacket)) {
|
||||
options.terminatePacket = true;
|
||||
}
|
||||
|
||||
|
||||
if(_.isObject(options.packetHeader)) {
|
||||
this.writePacketHeader(options.packetHeader, ws);
|
||||
}
|
||||
|
||||
|
||||
options.encoding = options.encoding || 'utf8';
|
||||
|
||||
messages.forEach(msg => {
|
||||
|
@ -1034,12 +1034,12 @@ Packet.prototype.write = function(path, packetHeader, messages, options) {
|
|||
if(!_.isArray(messages)) {
|
||||
messages = [ messages ];
|
||||
}
|
||||
|
||||
|
||||
options = options || { encoding : 'utf8' }; // utf-8 = 'CHRS UTF-8 4'
|
||||
|
||||
this.writeStream(
|
||||
fs.createWriteStream(path), // :TODO: specify mode/etc.
|
||||
messages,
|
||||
{ packetHeader : packetHeader, terminatePacket : true }
|
||||
);
|
||||
Object.assign( { packetHeader : packetHeader, terminatePacket : true }, options)
|
||||
);
|
||||
};
|
||||
|
|
|
@ -45,7 +45,7 @@ exports.getQuotePrefix = getQuotePrefix;
|
|||
|
||||
// See list here: https://github.com/Mithgol/node-fidonet-jam
|
||||
|
||||
function stringToNullPaddedBuffer(s, bufLen) {
|
||||
function stringToNullPaddedBuffer(s, bufLen) {
|
||||
let buffer = new Buffer(bufLen).fill(0x00);
|
||||
let enc = iconv.encode(s, 'CP437').slice(0, bufLen);
|
||||
for(let i = 0; i < enc.length; ++i) {
|
||||
|
@ -56,7 +56,7 @@ function stringToNullPaddedBuffer(s, bufLen) {
|
|||
|
||||
//
|
||||
// Convert a FTN style DateTime string to a Date object
|
||||
//
|
||||
//
|
||||
// :TODO: Name the next couple methods better - for FTN *packets*
|
||||
function getDateFromFtnDateTime(dateTime) {
|
||||
//
|
||||
|
@ -103,7 +103,7 @@ function getMessageSerialNumber(messageId) {
|
|||
//
|
||||
// Return a FTS-0009.001 compliant MSGID value given a message
|
||||
// See http://ftsc.org/docs/fts-0009.001
|
||||
//
|
||||
//
|
||||
// "A MSGID line consists of the string "^AMSGID:" (where ^A is a
|
||||
// control-A (hex 01) and the double-quotes are not part of the
|
||||
// string), followed by a space, the address of the originating
|
||||
|
@ -113,9 +113,9 @@ function getMessageSerialNumber(messageId) {
|
|||
// ^AMSGID: origaddr serialno
|
||||
//
|
||||
// The originating address should be specified in a form that
|
||||
// constitutes a valid return address for the originating network.
|
||||
// constitutes a valid return address for the originating network.
|
||||
// If the originating address is enclosed in double-quotes, the
|
||||
// entire string between the beginning and ending double-quotes is
|
||||
// entire string between the beginning and ending double-quotes is
|
||||
// considered to be the orginating address. A double-quote character
|
||||
// within a quoted address is represented by by two consecutive
|
||||
// double-quote characters. The serial number may be any eight
|
||||
|
@ -123,13 +123,13 @@ function getMessageSerialNumber(messageId) {
|
|||
// messages from a given system may have the same serial number
|
||||
// within a three years. The manner in which this serial number is
|
||||
// generated is left to the implementor."
|
||||
//
|
||||
//
|
||||
//
|
||||
// Examples & Implementations
|
||||
//
|
||||
// Synchronet: <msgNum>.<conf+area>@<ftnAddr> <serial>
|
||||
// 2606.agora-agn_tst@46:1/142 19609217
|
||||
//
|
||||
//
|
||||
// Mystic: <ftnAddress> <serial>
|
||||
// 46:3/102 46686263
|
||||
//
|
||||
|
@ -145,10 +145,10 @@ function getMessageSerialNumber(messageId) {
|
|||
//
|
||||
function getMessageIdentifier(message, address, isNetMail = false) {
|
||||
const addrStr = new Address(address).toString('5D');
|
||||
return isNetMail ?
|
||||
return isNetMail ?
|
||||
`${addrStr} ${getMessageSerialNumber(message.messageId)}` :
|
||||
`${message.messageId}.${message.areaTag.toLowerCase()}@${addrStr} ${getMessageSerialNumber(message.messageId)}`
|
||||
;
|
||||
;
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -166,7 +166,7 @@ function getProductIdentifier() {
|
|||
}
|
||||
|
||||
//
|
||||
// Return a FRL-1004 style time zone offset for a
|
||||
// Return a FRL-1004 style time zone offset for a
|
||||
// 'TZUTC' kludge line
|
||||
//
|
||||
// http://ftsc.org/docs/frl-1004.002
|
||||
|
@ -178,10 +178,10 @@ function getUTCTimeZoneOffset() {
|
|||
//
|
||||
// Get a FSC-0032 style quote prefix
|
||||
// http://ftsc.org/docs/fsc-0032.001
|
||||
//
|
||||
//
|
||||
function getQuotePrefix(name) {
|
||||
let initials;
|
||||
|
||||
|
||||
const parts = name.split(' ');
|
||||
if(parts.length > 1) {
|
||||
// First & Last initials - (Bryan Ashby -> BA)
|
||||
|
@ -199,7 +199,7 @@ function getQuotePrefix(name) {
|
|||
// http://ftsc.org/docs/fts-0004.001
|
||||
//
|
||||
function getOrigin(address) {
|
||||
const origin = _.has(Config, 'messageNetworks.originLine') ?
|
||||
const origin = _.has(Config, 'messageNetworks.originLine') ?
|
||||
Config.messageNetworks.originLine :
|
||||
Config.general.boardName;
|
||||
|
||||
|
@ -220,16 +220,12 @@ function getVia(address) {
|
|||
/*
|
||||
FRL-1005.001 states teh following format:
|
||||
|
||||
^AVia: <FTN Address> @YYYYMMDD.HHMMSS[.Precise][.Time Zone]
|
||||
^AVia: <FTN Address> @YYYYMMDD.HHMMSS[.Precise][.Time Zone]
|
||||
<Program Name> <Version> [Serial Number]<CR>
|
||||
*/
|
||||
const addrStr = new Address(address).toString('5D');
|
||||
const dateTime = moment().utc().format('YYYYMMDD.HHmmSS.SSSS.UTC');
|
||||
|
||||
const version = packageJson.version
|
||||
.replace(/\-/g, '.')
|
||||
.replace(/alpha/,'a')
|
||||
.replace(/beta/,'b');
|
||||
const version = getCleanEnigmaVersion();
|
||||
|
||||
return `${addrStr} @${dateTime} ENiGMA1/2 ${version}`;
|
||||
}
|
||||
|
@ -272,7 +268,7 @@ function parseAbbreviatedNetNodeList(netNodes) {
|
|||
const re = /([0-9]+)\/([0-9]+)\s?|([0-9]+)\s?/g;
|
||||
let net;
|
||||
let m;
|
||||
let results = [];
|
||||
let results = [];
|
||||
while(null !== (m = re.exec(netNodes))) {
|
||||
if(m[1] && m[2]) {
|
||||
net = parseInt(m[1]);
|
||||
|
@ -288,7 +284,7 @@ function parseAbbreviatedNetNodeList(netNodes) {
|
|||
//
|
||||
// Return a FTS-0004.001 SEEN-BY entry(s) that include
|
||||
// all pre-existing SEEN-BY entries with the addition
|
||||
// of |additions|.
|
||||
// of |additions|.
|
||||
//
|
||||
// See http://ftsc.org/docs/fts-0004.001
|
||||
// and notes at http://ftsc.org/docs/fsc-0043.002.
|
||||
|
@ -324,9 +320,9 @@ function getUpdatedSeenByEntries(existingEntries, additions) {
|
|||
if(!_.isArray(existingEntries)) {
|
||||
existingEntries = [ existingEntries ];
|
||||
}
|
||||
|
||||
|
||||
if(!_.isString(additions)) {
|
||||
additions = parseAbbreviatedNetNodeList(getAbbreviatedNetNodeList(additions));
|
||||
additions = parseAbbreviatedNetNodeList(getAbbreviatedNetNodeList(additions));
|
||||
}
|
||||
|
||||
additions = additions.sort(Address.getComparator());
|
||||
|
@ -361,13 +357,13 @@ const ENCODING_TO_FTS_5003_001_CHARS = {
|
|||
// level 1 - generally should not be used
|
||||
ascii : [ 'ASCII', 1 ],
|
||||
'us-ascii' : [ 'ASCII', 1 ],
|
||||
|
||||
|
||||
// level 2 - 8 bit, ASCII based
|
||||
cp437 : [ 'CP437', 2 ],
|
||||
cp850 : [ 'CP850', 2 ],
|
||||
|
||||
|
||||
// level 3 - reserved
|
||||
|
||||
|
||||
// level 4
|
||||
utf8 : [ 'UTF-8', 4 ],
|
||||
'utf-8' : [ 'UTF-8', 4 ],
|
||||
|
@ -381,7 +377,7 @@ function getCharacterSetIdentifierByEncoding(encodingName) {
|
|||
|
||||
function getEncodingFromCharacterSetIdentifier(chrs) {
|
||||
const ident = chrs.split(' ')[0].toUpperCase();
|
||||
|
||||
|
||||
// :TODO: fill in the rest!!!
|
||||
return {
|
||||
// level 1
|
||||
|
@ -399,7 +395,7 @@ function getEncodingFromCharacterSetIdentifier(chrs) {
|
|||
'SWISS' : 'iso-646',
|
||||
'UK' : 'iso-646',
|
||||
'ISO-10' : 'iso-646-10',
|
||||
|
||||
|
||||
// level 2
|
||||
'CP437' : 'cp437',
|
||||
'CP850' : 'cp850',
|
||||
|
@ -414,15 +410,15 @@ function getEncodingFromCharacterSetIdentifier(chrs) {
|
|||
'LATIN-2' : 'iso-8859-2',
|
||||
'LATIN-5' : 'iso-8859-9',
|
||||
'LATIN-9' : 'iso-8859-15',
|
||||
|
||||
|
||||
// level 4
|
||||
'UTF-8' : 'utf8',
|
||||
|
||||
|
||||
// deprecated stuff
|
||||
'IBMPC' : 'cp1250', // :TODO: validate
|
||||
'IBMPC' : 'cp1250', // :TODO: validate
|
||||
'+7_FIDO' : 'cp866',
|
||||
'+7' : 'cp866',
|
||||
'+7' : 'cp866',
|
||||
'MAC' : 'macroman', // :TODO: validate
|
||||
|
||||
|
||||
}[ident];
|
||||
}
|
|
@ -63,7 +63,7 @@ function HorizontalMenuView(options) {
|
|||
}
|
||||
|
||||
var text = strUtil.stylizeString(
|
||||
item.text,
|
||||
item.text,
|
||||
this.hasFocus && item.focused ? self.focusTextStyle : self.textStyle);
|
||||
|
||||
var drawWidth = text.length + self.getSpacer().length * 2; // * 2 = sides
|
||||
|
@ -72,7 +72,7 @@ function HorizontalMenuView(options) {
|
|||
ansi.goto(self.position.row, item.col) +
|
||||
(index === self.focusedItemIndex ? self.getFocusSGR() : self.getSGR()) +
|
||||
strUtil.pad(text, drawWidth, self.fillChar, 'center')
|
||||
);
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ module.exports = class KeyEntryView extends View {
|
|||
if(key && 'tab' === key.name && !this.eatTabKey) {
|
||||
return this.emit('action', 'next', key);
|
||||
}
|
||||
|
||||
|
||||
this.emit('action', 'accept');
|
||||
// NOTE: we don't call super here. KeyEntryView is a special snowflake.
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ module.exports = class KeyEntryView extends View {
|
|||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
super.setPropertyValue(propName, propValue);
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ const _ = require('lodash');
|
|||
location
|
||||
affiliation
|
||||
ts
|
||||
|
||||
|
||||
*/
|
||||
|
||||
exports.moduleInfo = {
|
||||
|
@ -65,7 +65,7 @@ exports.getModule = class LastCallersModule extends MenuModule {
|
|||
function fetchHistory(callback) {
|
||||
callersView = vc.getView(MciCodeIds.CallerList);
|
||||
|
||||
// fetch up
|
||||
// fetch up
|
||||
StatLog.getSystemLogEntries('user_login_history', StatLog.Order.TimestampDesc, 200, (err, lh) => {
|
||||
loginHistory = lh;
|
||||
|
||||
|
@ -82,12 +82,12 @@ exports.getModule = class LastCallersModule extends MenuModule {
|
|||
loginHistory = noOpLoginHistory;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Finally, we need to trim up the list to the needed size
|
||||
//
|
||||
loginHistory = loginHistory.slice(0, callersView.dimens.height);
|
||||
|
||||
|
||||
return callback(err);
|
||||
});
|
||||
},
|
||||
|
@ -99,10 +99,10 @@ exports.getModule = class LastCallersModule extends MenuModule {
|
|||
const dateTimeFormat = self.menuConfig.config.dateTimeFormat || 'ddd MMM DD';
|
||||
|
||||
async.each(
|
||||
loginHistory,
|
||||
loginHistory,
|
||||
(item, next) => {
|
||||
item.userId = parseInt(item.log_value);
|
||||
item.ts = moment(item.timestamp).format(dateTimeFormat);
|
||||
item.ts = moment(item.timestamp).format(dateTimeFormat);
|
||||
|
||||
User.getUserName(item.userId, (err, userName) => {
|
||||
if(err) {
|
||||
|
|
|
@ -12,7 +12,7 @@ module.exports = class Log {
|
|||
static init() {
|
||||
const Config = require('./config.js').config;
|
||||
const logPath = Config.paths.logs;
|
||||
|
||||
|
||||
const err = this.checkLogPath(logPath);
|
||||
if(err) {
|
||||
console.error(err.message); // eslint-disable-line no-console
|
||||
|
@ -29,9 +29,9 @@ module.exports = class Log {
|
|||
err : bunyan.stdSerializers.err, // handle 'err' fields with stack/etc.
|
||||
};
|
||||
|
||||
// try to remove sensitive info by default, e.g. 'password' fields
|
||||
// try to remove sensitive info by default, e.g. 'password' fields
|
||||
[ 'formData', 'formValue' ].forEach(keyName => {
|
||||
serializers[keyName] = (fd) => Log.hideSensitive(fd);
|
||||
serializers[keyName] = (fd) => Log.hideSensitive(fd);
|
||||
});
|
||||
|
||||
this.log = bunyan.createLogger({
|
||||
|
@ -46,7 +46,7 @@ module.exports = class Log {
|
|||
if(!fs.statSync(logPath).isDirectory()) {
|
||||
return new Error(`${logPath} is not a directory`);
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
} catch(e) {
|
||||
if('ENOENT' === e.code) {
|
||||
|
|
|
@ -28,7 +28,7 @@ module.exports = class LoginServerModule extends ServerModule {
|
|||
} else {
|
||||
client.user.properties.theme_id = conf.config.preLoginTheme;
|
||||
}
|
||||
|
||||
|
||||
theme.setClientTheme(client, client.user.properties.theme_id);
|
||||
return cb(null); // note: currently useless to use cb here - but this may change...again...
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ module.exports = class LoginServerModule extends ServerModule {
|
|||
//
|
||||
// Start tracking the client. We'll assign it an ID which is
|
||||
// just the index in our connections array.
|
||||
//
|
||||
//
|
||||
if(_.isUndefined(client.session)) {
|
||||
client.session = {};
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ module.exports = class LoginServerModule extends ServerModule {
|
|||
client.on('close', err => {
|
||||
const logFunc = err ? logger.log.info : logger.log.debug;
|
||||
logFunc( { clientId : client.session.id }, 'Connection closed');
|
||||
|
||||
|
||||
clientConns.removeClient(client);
|
||||
});
|
||||
|
||||
|
@ -80,7 +80,7 @@ module.exports = class LoginServerModule extends ServerModule {
|
|||
// likely just doesn't exist
|
||||
client.term.write('\nIdle timeout expired. Goodbye!\n');
|
||||
client.end();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -33,4 +33,4 @@ MailPacket.prototype.write = function(options) {
|
|||
// emits 'packet' event per packet constructed
|
||||
//
|
||||
assert(_.isArray(options.messages));
|
||||
}
|
||||
};
|
|
@ -6,7 +6,7 @@ const Message = require('./message.js');
|
|||
|
||||
exports.getAddressedToInfo = getAddressedToInfo;
|
||||
|
||||
const EMAIL_REGEX = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||
const EMAIL_REGEX = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||
|
||||
/*
|
||||
Input Output
|
||||
|
|
|
@ -25,7 +25,7 @@ exports.MaskEditTextView = MaskEditTextView;
|
|||
// :TODO:
|
||||
// * Hint, e.g. YYYY/MM/DD
|
||||
// * Return values with literals in place
|
||||
//
|
||||
//
|
||||
|
||||
function MaskEditTextView(options) {
|
||||
options.acceptsFocus = miscUtil.valueWithDefault(options.acceptsFocus, true);
|
||||
|
@ -49,7 +49,7 @@ function MaskEditTextView(options) {
|
|||
|
||||
this.drawText = function(s) {
|
||||
var textToDraw = strUtil.stylizeString(s, this.hasFocus ? this.focusTextStyle : this.textStyle);
|
||||
|
||||
|
||||
assert(textToDraw.length <= self.patternArray.length);
|
||||
|
||||
// draw out the text we have so far
|
||||
|
@ -105,7 +105,7 @@ MaskEditTextView.maskPatternCharacterRegEx = {
|
|||
|
||||
MaskEditTextView.prototype.setText = function(text) {
|
||||
MaskEditTextView.super_.prototype.setText.call(this, text);
|
||||
|
||||
|
||||
if(this.patternArray) { // :TODO: This is a hack - see TextView ctor note about setText()
|
||||
this.patternArrayPos = this.patternArray.length;
|
||||
}
|
||||
|
@ -130,14 +130,14 @@ MaskEditTextView.prototype.onKeyPress = function(ch, key) {
|
|||
this.clientBackspace();
|
||||
} else {
|
||||
while(this.patternArrayPos > 0) {
|
||||
if(_.isRegExp(this.patternArray[this.patternArrayPos])) {
|
||||
if(_.isRegExp(this.patternArray[this.patternArrayPos])) {
|
||||
this.text = this.text.substr(0, this.text.length - 1);
|
||||
this.client.term.write(ansi.goto(this.position.row, this.getEndOfTextColumn() + 1));
|
||||
this.clientBackspace();
|
||||
break;
|
||||
}
|
||||
this.patternArrayPos--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -162,7 +162,7 @@ MaskEditTextView.prototype.onKeyPress = function(ch, key) {
|
|||
this.text += ch;
|
||||
this.patternArrayPos++;
|
||||
|
||||
while(this.patternArrayPos < this.patternArray.length &&
|
||||
while(this.patternArrayPos < this.patternArray.length &&
|
||||
!_.isRegExp(this.patternArray[this.patternArrayPos]))
|
||||
{
|
||||
this.patternArrayPos++;
|
||||
|
@ -186,11 +186,11 @@ MaskEditTextView.prototype.setPropertyValue = function(propName, value) {
|
|||
|
||||
MaskEditTextView.prototype.getData = function() {
|
||||
var rawData = MaskEditTextView.super_.prototype.getData.call(this);
|
||||
|
||||
|
||||
if(!rawData || 0 === rawData.length) {
|
||||
return rawData;
|
||||
}
|
||||
|
||||
|
||||
var data = '';
|
||||
|
||||
assert(rawData.length <= this.patternArray.length);
|
||||
|
|
|
@ -4,13 +4,12 @@
|
|||
// ENiGMA½
|
||||
const TextView = require('./text_view.js').TextView;
|
||||
const EditTextView = require('./edit_text_view.js').EditTextView;
|
||||
const ButtonView = require('./button_view.js').ButtonView;
|
||||
const ButtonView = require('./button_view.js').ButtonView;
|
||||
const VerticalMenuView = require('./vertical_menu_view.js').VerticalMenuView;
|
||||
const HorizontalMenuView = require('./horizontal_menu_view.js').HorizontalMenuView;
|
||||
const SpinnerMenuView = require('./spinner_menu_view.js').SpinnerMenuView;
|
||||
const ToggleMenuView = require('./toggle_menu_view.js').ToggleMenuView;
|
||||
const HorizontalMenuView = require('./horizontal_menu_view.js').HorizontalMenuView;
|
||||
const SpinnerMenuView = require('./spinner_menu_view.js').SpinnerMenuView;
|
||||
const ToggleMenuView = require('./toggle_menu_view.js').ToggleMenuView;
|
||||
const MaskEditTextView = require('./mask_edit_text_view.js').MaskEditTextView;
|
||||
//const StatusBarView = require('./status_bar_view.js').StatusBarView;
|
||||
const KeyEntryView = require('./key_entry_view.js');
|
||||
const MultiLineEditTextView = require('./multi_line_edit_text_view.js').MultiLineEditTextView;
|
||||
const getPredefinedMCIValue = require('./predefined_mci.js').getPredefinedMCIValue;
|
||||
|
@ -37,7 +36,7 @@ MCIViewFactory.UserViewCodes = [
|
|||
'XY',
|
||||
];
|
||||
|
||||
MCIViewFactory.prototype.createFromMCI = function(mci, cb) {
|
||||
MCIViewFactory.prototype.createFromMCI = function(mci) {
|
||||
assert(mci.code);
|
||||
assert(mci.id > 0);
|
||||
assert(mci.position);
|
||||
|
@ -78,7 +77,7 @@ MCIViewFactory.prototype.createFromMCI = function(mci, cb) {
|
|||
//
|
||||
switch(mci.code) {
|
||||
// Text Label (Text View)
|
||||
case 'TL' :
|
||||
case 'TL' :
|
||||
setOption(0, 'textStyle');
|
||||
setOption(1, 'justify');
|
||||
setWidth(2);
|
||||
|
@ -105,14 +104,14 @@ MCIViewFactory.prototype.createFromMCI = function(mci, cb) {
|
|||
break;
|
||||
|
||||
// Multi Line Edit Text
|
||||
case 'MT' :
|
||||
case 'MT' :
|
||||
// :TODO: apply params
|
||||
view = new MultiLineEditTextView(options);
|
||||
break;
|
||||
|
||||
// Pre-defined Label (Text View)
|
||||
// :TODO: Currently no real point of PL -- @method replaces this pretty much... probably remove
|
||||
case 'PL' :
|
||||
case 'PL' :
|
||||
if(mci.args.length > 0) {
|
||||
options.text = getPredefinedMCIValue(this.client, mci.args[0]);
|
||||
if(options.text) {
|
||||
|
@ -126,7 +125,7 @@ MCIViewFactory.prototype.createFromMCI = function(mci, cb) {
|
|||
break;
|
||||
|
||||
// Button
|
||||
case 'BT' :
|
||||
case 'BT' :
|
||||
if(mci.args.length > 0) {
|
||||
options.dimens = { width : parseInt(mci.args[0], 10) };
|
||||
}
|
||||
|
@ -144,14 +143,14 @@ MCIViewFactory.prototype.createFromMCI = function(mci, cb) {
|
|||
setOption(0, 'itemSpacing');
|
||||
setOption(1, 'justify');
|
||||
setOption(2, 'textStyle');
|
||||
|
||||
|
||||
setFocusOption(0, 'focusTextStyle');
|
||||
|
||||
view = new VerticalMenuView(options);
|
||||
break;
|
||||
|
||||
// Horizontal Menu
|
||||
case 'HM' :
|
||||
case 'HM' :
|
||||
setOption(0, 'itemSpacing');
|
||||
setOption(1, 'textStyle');
|
||||
|
||||
|
@ -165,7 +164,7 @@ MCIViewFactory.prototype.createFromMCI = function(mci, cb) {
|
|||
setOption(1, 'justify');
|
||||
|
||||
setFocusOption(0, 'focusTextStyle');
|
||||
|
||||
|
||||
view = new SpinnerMenuView(options);
|
||||
break;
|
||||
|
||||
|
|
|
@ -17,17 +17,17 @@ const assert = require('assert');
|
|||
const _ = require('lodash');
|
||||
|
||||
exports.MenuModule = class MenuModule extends PluginModule {
|
||||
|
||||
|
||||
constructor(options) {
|
||||
super(options);
|
||||
super(options);
|
||||
|
||||
this.menuName = options.menuName;
|
||||
this.menuConfig = options.menuConfig;
|
||||
this.client = options.client;
|
||||
this.menuConfig.options = options.menuConfig.options || {};
|
||||
this.menuMethods = {}; // methods called from @method's
|
||||
this.menuMethods = {}; // methods called from @method's
|
||||
this.menuConfig.config = this.menuConfig.config || {};
|
||||
|
||||
|
||||
this.cls = _.isBoolean(this.menuConfig.options.cls) ? this.menuConfig.options.cls : Config.menus.cls;
|
||||
|
||||
this.viewControllers = {};
|
||||
|
@ -70,7 +70,7 @@ exports.MenuModule = class MenuModule extends PluginModule {
|
|||
}
|
||||
);
|
||||
},
|
||||
function moveToPromptLocation(callback) {
|
||||
function moveToPromptLocation(callback) {
|
||||
if(self.menuConfig.prompt) {
|
||||
// :TODO: fetch and move cursor to prompt location, if supplied. See notes/etc. on placements
|
||||
}
|
||||
|
@ -171,10 +171,10 @@ exports.MenuModule = class MenuModule extends PluginModule {
|
|||
}
|
||||
|
||||
nextMenu(cb) {
|
||||
if(!this.haveNext()) {
|
||||
if(!this.haveNext()) {
|
||||
return this.prevMenu(cb); // no next, go to prev
|
||||
}
|
||||
|
||||
|
||||
return this.client.menuStack.next(cb);
|
||||
}
|
||||
|
||||
|
@ -210,7 +210,7 @@ exports.MenuModule = class MenuModule extends PluginModule {
|
|||
haveNext() {
|
||||
return (_.isString(this.menuConfig.next) || _.isArray(this.menuConfig.next));
|
||||
}
|
||||
|
||||
|
||||
autoNextMenu(cb) {
|
||||
const self = this;
|
||||
|
||||
|
@ -221,8 +221,8 @@ exports.MenuModule = class MenuModule extends PluginModule {
|
|||
return self.prevMenu(cb);
|
||||
}
|
||||
}
|
||||
|
||||
if(_.has(this.menuConfig, 'runtime.autoNext') && true === this.menuConfig.runtime.autoNext) {
|
||||
|
||||
if(_.has(this.menuConfig, 'runtime.autoNext') && true === this.menuConfig.runtime.autoNext) {
|
||||
if(this.hasNextTimeout()) {
|
||||
setTimeout( () => {
|
||||
return gotoNextMenu();
|
||||
|
@ -297,10 +297,10 @@ exports.MenuModule = class MenuModule extends PluginModule {
|
|||
if(options.clearScreen) {
|
||||
this.client.term.rawWrite(ansi.resetScreen());
|
||||
}
|
||||
|
||||
|
||||
return theme.displayThemedAsset(
|
||||
name,
|
||||
this.client,
|
||||
name,
|
||||
this.client,
|
||||
Object.assign( { font : this.menuConfig.config.font }, options ),
|
||||
(err, artData) => {
|
||||
if(cb) {
|
||||
|
@ -361,7 +361,7 @@ exports.MenuModule = class MenuModule extends PluginModule {
|
|||
pausePrompt(position, cb) {
|
||||
if(!cb && _.isFunction(position)) {
|
||||
cb = position;
|
||||
position = null;
|
||||
position = null;
|
||||
}
|
||||
|
||||
this.optionalMoveToPosition(position);
|
||||
|
@ -390,7 +390,7 @@ exports.MenuModule = class MenuModule extends PluginModule {
|
|||
if(!view) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if(appendMultiLine && (view instanceof MultiLineEditTextView)) {
|
||||
view.addText(text);
|
||||
} else {
|
||||
|
@ -401,7 +401,7 @@ exports.MenuModule = class MenuModule extends PluginModule {
|
|||
updateCustomViewTextsWithFilter(formName, startId, fmtObj, options) {
|
||||
options = options || {};
|
||||
|
||||
let textView;
|
||||
let textView;
|
||||
let customMciId = startId;
|
||||
const config = this.menuConfig.config;
|
||||
const endId = options.endId || 99; // we'll fail to get a view before 99
|
||||
|
|
|
@ -78,19 +78,19 @@ module.exports = class MenuStack {
|
|||
|
||||
// :TODO: leave() should really take a cb...
|
||||
this.pop().instance.leave(); // leave & remove current
|
||||
|
||||
|
||||
const previousModuleInfo = this.pop(); // get previous
|
||||
|
||||
if(previousModuleInfo) {
|
||||
const opts = {
|
||||
extraArgs : previousModuleInfo.extraArgs,
|
||||
extraArgs : previousModuleInfo.extraArgs,
|
||||
savedState : previousModuleInfo.savedState,
|
||||
lastMenuResult : menuResult,
|
||||
};
|
||||
|
||||
return this.goto(previousModuleInfo.name, opts, cb);
|
||||
}
|
||||
|
||||
|
||||
return cb(Errors.MenuStack('No previous menu available', 'NOPREV'));
|
||||
}
|
||||
|
||||
|
@ -106,14 +106,14 @@ module.exports = class MenuStack {
|
|||
|
||||
if(currentModuleInfo && name === currentModuleInfo.name) {
|
||||
if(cb) {
|
||||
cb(Errors.MenuStack('Already at supplied menu', 'ALREADYTHERE'));
|
||||
cb(Errors.MenuStack('Already at supplied menu', 'ALREADYTHERE'));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const loadOpts = {
|
||||
name : name,
|
||||
client : self.client,
|
||||
client : self.client,
|
||||
};
|
||||
|
||||
if(_.isObject(options)) {
|
||||
|
|
|
@ -42,7 +42,7 @@ function getMenuConfig(client, name, cb) {
|
|||
} else {
|
||||
callback(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
function complete(err) {
|
||||
cb(err, menuConfig);
|
||||
|
@ -53,7 +53,7 @@ function getMenuConfig(client, name, cb) {
|
|||
function loadMenu(options, cb) {
|
||||
assert(_.isObject(options));
|
||||
assert(_.isString(options.name));
|
||||
assert(_.isObject(options.client));
|
||||
assert(_.isObject(options.client));
|
||||
|
||||
async.waterfall(
|
||||
[
|
||||
|
@ -88,7 +88,7 @@ function loadMenu(options, cb) {
|
|||
|
||||
return callback(err, modData);
|
||||
});
|
||||
},
|
||||
},
|
||||
function createModuleInstance(modData, callback) {
|
||||
Log.trace(
|
||||
{ moduleName : modData.name, extraArgs : options.extraArgs, config : modData.config, info : modData.mod.modInfo },
|
||||
|
@ -98,11 +98,11 @@ function loadMenu(options, cb) {
|
|||
try {
|
||||
moduleInstance = new modData.mod.getModule({
|
||||
menuName : options.name,
|
||||
menuConfig : modData.config,
|
||||
menuConfig : modData.config,
|
||||
extraArgs : options.extraArgs,
|
||||
client : options.client,
|
||||
lastMenuResult : options.lastMenuResult,
|
||||
});
|
||||
});
|
||||
} catch(e) {
|
||||
return callback(e);
|
||||
}
|
||||
|
@ -143,7 +143,7 @@ function getFormConfigByIDAndMap(menuConfig, formId, mciMap, cb) {
|
|||
Log.trace( { mciKey : mciReqKey }, 'Using exact configuration key match');
|
||||
cb(null, formForId[mciReqKey]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Generic match
|
||||
|
@ -184,24 +184,24 @@ function handleAction(client, formData, conf, cb) {
|
|||
|
||||
switch(actionAsset.type) {
|
||||
case 'method' :
|
||||
case 'systemMethod' :
|
||||
case 'systemMethod' :
|
||||
if(_.isString(actionAsset.location)) {
|
||||
return callModuleMenuMethod(
|
||||
client,
|
||||
actionAsset,
|
||||
paths.join(Config.paths.mods, actionAsset.location),
|
||||
formData,
|
||||
conf.extraArgs,
|
||||
client,
|
||||
actionAsset,
|
||||
paths.join(Config.paths.mods, actionAsset.location),
|
||||
formData,
|
||||
conf.extraArgs,
|
||||
cb);
|
||||
} else if('systemMethod' === actionAsset.type) {
|
||||
// :TODO: Need to pass optional args here -- conf.extraArgs and args between e.g. ()
|
||||
// :TODO: Probably better as system_method.js
|
||||
return callModuleMenuMethod(
|
||||
client,
|
||||
actionAsset,
|
||||
paths.join(__dirname, 'system_menu_method.js'),
|
||||
formData,
|
||||
conf.extraArgs,
|
||||
client,
|
||||
actionAsset,
|
||||
paths.join(__dirname, 'system_menu_method.js'),
|
||||
formData,
|
||||
conf.extraArgs,
|
||||
cb);
|
||||
} else {
|
||||
// local to current module
|
||||
|
@ -209,7 +209,7 @@ function handleAction(client, formData, conf, cb) {
|
|||
if(_.isFunction(currentModule.menuMethods[actionAsset.asset])) {
|
||||
return currentModule.menuMethods[actionAsset.asset](formData, conf.extraArgs, cb);
|
||||
}
|
||||
|
||||
|
||||
const err = new Error('Method does not exist');
|
||||
client.log.warn( { method : actionAsset.asset }, err.message);
|
||||
return cb(err);
|
||||
|
@ -222,14 +222,14 @@ function handleAction(client, formData, conf, cb) {
|
|||
|
||||
function handleNext(client, nextSpec, conf, cb) {
|
||||
assert(_.isString(nextSpec) || _.isArray(nextSpec));
|
||||
|
||||
|
||||
if(_.isArray(nextSpec)) {
|
||||
nextSpec = client.acs.getConditionalValue(nextSpec, 'next');
|
||||
}
|
||||
|
||||
|
||||
const nextAsset = asset.getAssetWithShorthand(nextSpec, 'menu');
|
||||
// :TODO: getAssetWithShorthand() can return undefined - handle it!
|
||||
|
||||
|
||||
conf = conf || {};
|
||||
const extraArgs = conf.extraArgs || {};
|
||||
|
||||
|
@ -252,7 +252,7 @@ function handleNext(client, nextSpec, conf, cb) {
|
|||
|
||||
const err = new Error('Method does not exist');
|
||||
client.log.warn( { method : nextAsset.asset }, err.message);
|
||||
return cb(err);
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
case 'menu' :
|
||||
|
|
|
@ -16,7 +16,7 @@ exports.MenuView = MenuView;
|
|||
function MenuView(options) {
|
||||
options.acceptsFocus = miscUtil.valueWithDefault(options.acceptsFocus, true);
|
||||
options.acceptsInput = miscUtil.valueWithDefault(options.acceptsInput, true);
|
||||
|
||||
|
||||
View.call(this, options);
|
||||
|
||||
this.disablePipe = options.disablePipe || false;
|
||||
|
@ -65,11 +65,11 @@ util.inherits(MenuView, View);
|
|||
MenuView.prototype.setItems = function(items) {
|
||||
const self = this;
|
||||
|
||||
if(items) {
|
||||
if(items) {
|
||||
this.items = [];
|
||||
items.forEach( itemText => {
|
||||
this.items.push(
|
||||
{
|
||||
{
|
||||
text : self.disablePipe ? itemText : pipeToAnsi(itemText, self.client)
|
||||
}
|
||||
);
|
||||
|
@ -79,7 +79,7 @@ MenuView.prototype.setItems = function(items) {
|
|||
|
||||
MenuView.prototype.removeItem = function(index) {
|
||||
this.items.splice(index, 1);
|
||||
|
||||
|
||||
if(this.focusItems) {
|
||||
this.focusItems.splice(index, 1);
|
||||
}
|
||||
|
@ -95,7 +95,7 @@ MenuView.prototype.getCount = function() {
|
|||
return this.items.length;
|
||||
};
|
||||
|
||||
MenuView.prototype.getItems = function() {
|
||||
MenuView.prototype.getItems = function() {
|
||||
return this.items.map( item => {
|
||||
return item.text;
|
||||
});
|
||||
|
@ -140,7 +140,7 @@ MenuView.prototype.onKeyPress = function(ch, key) {
|
|||
|
||||
MenuView.prototype.setFocusItems = function(items) {
|
||||
const self = this;
|
||||
|
||||
|
||||
if(items) {
|
||||
this.focusItems = [];
|
||||
items.forEach( itemText => {
|
||||
|
@ -183,7 +183,7 @@ MenuView.prototype.setHotKeys = function(hotKeys) {
|
|||
this.hotKeys[key.toLowerCase()] = hotKeys[key];
|
||||
}
|
||||
} else {
|
||||
this.hotKeys = hotKeys;
|
||||
this.hotKeys = hotKeys;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
112
core/message.js
112
core/message.js
|
@ -9,9 +9,9 @@ const getISOTimestampString = require('./database.js').getISOTimestampString;
|
|||
const Errors = require('./enig_error.js').Errors;
|
||||
const ANSI = require('./ansi_term.js');
|
||||
|
||||
const {
|
||||
const {
|
||||
isAnsi, isFormattedLine,
|
||||
splitTextAtTerms,
|
||||
splitTextAtTerms,
|
||||
renderSubstr
|
||||
} = require('./string_util.js');
|
||||
|
||||
|
@ -45,7 +45,7 @@ function Message(options) {
|
|||
this.fromUserName = options.fromUserName || '';
|
||||
this.subject = options.subject || '';
|
||||
this.message = options.message || '';
|
||||
|
||||
|
||||
if(_.isDate(options.modTimestamp) || moment.isMoment(options.modTimestamp)) {
|
||||
this.modTimestamp = moment(options.modTimestamp);
|
||||
} else if(_.isString(options.modTimestamp)) {
|
||||
|
@ -115,7 +115,7 @@ Message.StateFlags0 = {
|
|||
Exported : 0x00000002, // exported to foreign system
|
||||
};
|
||||
|
||||
Message.FtnPropertyNames = {
|
||||
Message.FtnPropertyNames = {
|
||||
FtnOrigNode : 'ftn_orig_node',
|
||||
FtnDestNode : 'ftn_dest_node',
|
||||
FtnOrigNetwork : 'ftn_orig_network',
|
||||
|
@ -166,12 +166,12 @@ Message.createMessageUUID = function(areaTag, modTimestamp, subject, body) {
|
|||
if(!moment.isMoment(modTimestamp)) {
|
||||
modTimestamp = moment(modTimestamp);
|
||||
}
|
||||
|
||||
|
||||
areaTag = iconvEncode(areaTag.toUpperCase(), 'CP437');
|
||||
modTimestamp = iconvEncode(modTimestamp.format('DD MMM YY HH:mm:ss'), 'CP437');
|
||||
subject = iconvEncode(subject.toUpperCase().trim(), 'CP437');
|
||||
body = iconvEncode(body.replace(/\r\n|[\n\v\f\r\x85\u2028\u2029]/g, '').trim(), 'CP437');
|
||||
|
||||
|
||||
return uuidParse.unparse(createNamedUUID(ENIGMA_MESSAGE_UUID_NAMESPACE, Buffer.concat( [ areaTag, modTimestamp, subject, body ] )));
|
||||
};
|
||||
|
||||
|
@ -180,8 +180,8 @@ Message.getMessageIdByUuid = function(uuid, cb) {
|
|||
`SELECT message_id
|
||||
FROM message
|
||||
WHERE message_uuid = ?
|
||||
LIMIT 1;`,
|
||||
[ uuid ],
|
||||
LIMIT 1;`,
|
||||
[ uuid ],
|
||||
(err, row) => {
|
||||
if(err) {
|
||||
cb(err);
|
||||
|
@ -210,30 +210,30 @@ Message.getMessageIdsByMetaValue = function(category, name, value, cb) {
|
|||
};
|
||||
|
||||
Message.getMetaValuesByMessageId = function(messageId, category, name, cb) {
|
||||
const sql =
|
||||
const sql =
|
||||
`SELECT meta_value
|
||||
FROM message_meta
|
||||
WHERE message_id = ? AND meta_category = ? AND meta_name = ?;`;
|
||||
|
||||
|
||||
msgDb.all(sql, [ messageId, category, name ], (err, rows) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
|
||||
if(0 === rows.length) {
|
||||
return cb(new Error('No value for category/name'));
|
||||
}
|
||||
|
||||
|
||||
// single values are returned without an array
|
||||
if(1 === rows.length) {
|
||||
return cb(null, rows[0].meta_value);
|
||||
}
|
||||
|
||||
|
||||
cb(null, rows.map(r => r.meta_value)); // map to array of values only
|
||||
});
|
||||
};
|
||||
|
||||
Message.getMetaValuesByMessageUuid = function(uuid, category, name, cb) {
|
||||
Message.getMetaValuesByMessageUuid = function(uuid, category, name, cb) {
|
||||
async.waterfall(
|
||||
[
|
||||
function getMessageId(callback) {
|
||||
|
@ -256,22 +256,22 @@ Message.getMetaValuesByMessageUuid = function(uuid, category, name, cb) {
|
|||
Message.prototype.loadMeta = function(cb) {
|
||||
/*
|
||||
Example of loaded this.meta:
|
||||
|
||||
|
||||
meta: {
|
||||
System: {
|
||||
local_to_user_id: 1234,
|
||||
local_to_user_id: 1234,
|
||||
},
|
||||
FtnProperty: {
|
||||
ftn_seen_by: [ "1/102 103", "2/42 52 65" ]
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
const sql =
|
||||
}
|
||||
*/
|
||||
|
||||
const sql =
|
||||
`SELECT meta_category, meta_name, meta_value
|
||||
FROM message_meta
|
||||
WHERE message_id = ?;`;
|
||||
|
||||
|
||||
let self = this;
|
||||
msgDb.each(sql, [ this.messageId ], (err, row) => {
|
||||
if(!(row.meta_category in self.meta)) {
|
||||
|
@ -279,12 +279,12 @@ Message.prototype.loadMeta = function(cb) {
|
|||
self.meta[row.meta_category][row.meta_name] = row.meta_value;
|
||||
} else {
|
||||
if(!(row.meta_name in self.meta[row.meta_category])) {
|
||||
self.meta[row.meta_category][row.meta_name] = row.meta_value;
|
||||
self.meta[row.meta_category][row.meta_name] = row.meta_value;
|
||||
} else {
|
||||
if(_.isString(self.meta[row.meta_category][row.meta_name])) {
|
||||
self.meta[row.meta_category][row.meta_name] = [ self.meta[row.meta_category][row.meta_name] ];
|
||||
self.meta[row.meta_category][row.meta_name] = [ self.meta[row.meta_category][row.meta_name] ];
|
||||
}
|
||||
|
||||
|
||||
self.meta[row.meta_category][row.meta_name].push(row.meta_value);
|
||||
}
|
||||
}
|
||||
|
@ -315,7 +315,7 @@ Message.prototype.load = function(options, cb) {
|
|||
if(!msgRow) {
|
||||
return callback(new Error('Message (no longer) available'));
|
||||
}
|
||||
|
||||
|
||||
self.messageId = msgRow.message_id;
|
||||
self.areaTag = msgRow.area_tag;
|
||||
self.messageUuid = msgRow.message_uuid;
|
||||
|
@ -356,13 +356,13 @@ Message.prototype.persistMetaValue = function(category, name, value, transOrDb,
|
|||
const metaStmt = transOrDb.prepare(
|
||||
`INSERT INTO message_meta (message_id, meta_category, meta_name, meta_value)
|
||||
VALUES (?, ?, ?, ?);`);
|
||||
|
||||
|
||||
if(!_.isArray(value)) {
|
||||
value = [ value ];
|
||||
}
|
||||
|
||||
|
||||
let self = this;
|
||||
|
||||
|
||||
async.each(value, (v, next) => {
|
||||
metaStmt.run(self.messageId, category, name, v, err => {
|
||||
next(err);
|
||||
|
@ -379,7 +379,7 @@ Message.prototype.persist = function(cb) {
|
|||
}
|
||||
|
||||
const self = this;
|
||||
|
||||
|
||||
async.waterfall(
|
||||
[
|
||||
function beginTransaction(callback) {
|
||||
|
@ -398,7 +398,7 @@ Message.prototype.persist = function(cb) {
|
|||
|
||||
trans.run(
|
||||
`INSERT INTO message (area_tag, message_uuid, reply_to_message_id, to_user_name, from_user_name, subject, message, modified_timestamp)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?);`,
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?);`,
|
||||
[ self.areaTag, self.uuid, self.replyToMsgId, self.toUserName, self.fromUserName, self.subject, self.message, getISOTimestampString(msgTimestamp) ],
|
||||
function inserted(err) { // use non-arrow function for 'this' scope
|
||||
if(!err) {
|
||||
|
@ -415,15 +415,15 @@ Message.prototype.persist = function(cb) {
|
|||
}
|
||||
/*
|
||||
Example of self.meta:
|
||||
|
||||
|
||||
meta: {
|
||||
System: {
|
||||
local_to_user_id: 1234,
|
||||
local_to_user_id: 1234,
|
||||
},
|
||||
FtnProperty: {
|
||||
ftn_seen_by: [ "1/102 103", "2/42 52 65" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
async.each(Object.keys(self.meta), (category, nextCat) => {
|
||||
async.each(Object.keys(self.meta[category]), (name, nextName) => {
|
||||
|
@ -433,10 +433,10 @@ Message.prototype.persist = function(cb) {
|
|||
}, err => {
|
||||
nextCat(err);
|
||||
});
|
||||
|
||||
|
||||
}, err => {
|
||||
callback(err, trans);
|
||||
});
|
||||
});
|
||||
},
|
||||
function storeHashTags(trans, callback) {
|
||||
// :TODO: hash tag support
|
||||
|
@ -470,21 +470,21 @@ Message.prototype.getQuoteLines = function(options, cb) {
|
|||
if(!options.termWidth || !options.termHeight || !options.cols) {
|
||||
return cb(Errors.MissingParam());
|
||||
}
|
||||
|
||||
|
||||
options.startCol = options.startCol || 1;
|
||||
options.includePrefix = _.get(options, 'includePrefix', true);
|
||||
options.ansiResetSgr = options.ansiResetSgr || ANSI.getSGRFromGraphicRendition( { fg : 39, bg : 49 }, true);
|
||||
options.ansiFocusPrefixSgr = options.ansiFocusPrefixSgr || ANSI.getSGRFromGraphicRendition( { intensity : 'bold', fg : 39, bg : 49 } );
|
||||
options.isAnsi = options.isAnsi || isAnsi(this.message); // :TODO: If this.isAnsi, use that setting
|
||||
|
||||
|
||||
/*
|
||||
Some long text that needs to be wrapped and quoted should look right after
|
||||
doing so, don't ya think? yeah I think so
|
||||
doing so, don't ya think? yeah I think so
|
||||
|
||||
Nu> Some long text that needs to be wrapped and quoted should look right
|
||||
Nu> Some long text that needs to be wrapped and quoted should look right
|
||||
Nu> after doing so, don't ya think? yeah I think so
|
||||
|
||||
Ot> Nu> Some long text that needs to be wrapped and quoted should look
|
||||
Ot> Nu> Some long text that needs to be wrapped and quoted should look
|
||||
Ot> Nu> right after doing so, don't ya think? yeah I think so
|
||||
|
||||
*/
|
||||
|
@ -498,7 +498,7 @@ Message.prototype.getQuoteLines = function(options, cb) {
|
|||
tabHandling : 'expand',
|
||||
tabWidth : 4,
|
||||
};
|
||||
|
||||
|
||||
return wordWrapText(text, wrapOpts).wrapped.map( (w, i) => {
|
||||
return i === 0 ? `${quotePrefix}${w}` : `${quotePrefix}${extraPrefix}${w}`;
|
||||
});
|
||||
|
@ -527,44 +527,44 @@ Message.prototype.getQuoteLines = function(options, cb) {
|
|||
cols : options.cols,
|
||||
rows : 'auto',
|
||||
startCol : options.startCol,
|
||||
forceLineTerm : true,
|
||||
forceLineTerm : true,
|
||||
},
|
||||
(err, prepped) => {
|
||||
prepped = prepped || this.message;
|
||||
|
||||
|
||||
let lastSgr = '';
|
||||
const split = splitTextAtTerms(prepped);
|
||||
|
||||
|
||||
const quoteLines = [];
|
||||
const focusQuoteLines = [];
|
||||
|
||||
//
|
||||
// Do not include quote prefixes (e.g. XX> ) on ANSI replies (and therefor quote builder)
|
||||
// as while this works in ENiGMA, other boards such as Mystic, WWIV, etc. will try to
|
||||
// as while this works in ENiGMA, other boards such as Mystic, WWIV, etc. will try to
|
||||
// strip colors, colorize the lines, etc. If we exclude the prefixes, this seems to do
|
||||
// the trick and allow them to leave them alone!
|
||||
//
|
||||
split.forEach(l => {
|
||||
quoteLines.push(`${lastSgr}${l}`);
|
||||
|
||||
|
||||
focusQuoteLines.push(`${options.ansiFocusPrefixSgr}>${lastSgr}${renderSubstr(l, 1, l.length - 1)}`);
|
||||
lastSgr = (l.match(/(?:\x1b\x5b)[\?=;0-9]*m(?!.*(?:\x1b\x5b)[\?=;0-9]*m)/) || [])[0] || ''; // eslint-disable-line no-control-regex
|
||||
lastSgr = (l.match(/(?:\x1b\x5b)[?=;0-9]*m(?!.*(?:\x1b\x5b)[?=;0-9]*m)/) || [])[0] || ''; // eslint-disable-line no-control-regex
|
||||
});
|
||||
|
||||
quoteLines[quoteLines.length - 1] += options.ansiResetSgr;
|
||||
|
||||
|
||||
return cb(null, quoteLines, focusQuoteLines, true);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
const QUOTE_RE = /^ ((?:[A-Za-z0-9]{2}\> )+(?:[A-Za-z0-9]{2}\>)*) */;
|
||||
const QUOTE_RE = /^ ((?:[A-Za-z0-9]{2}> )+(?:[A-Za-z0-9]{2}>)*) */;
|
||||
const quoted = [];
|
||||
const input = _.trimEnd(this.message).replace(/\b/g, '');
|
||||
|
||||
|
||||
// find *last* tearline
|
||||
let tearLinePos = this.getTearLinePosition(input);
|
||||
tearLinePos = -1 === tearLinePos ? input.length : tearLinePos; // we just want the index or the entire string
|
||||
|
||||
|
||||
input.slice(0, tearLinePos).split(/\r\n\r\n|\n\n/).forEach(paragraph => {
|
||||
//
|
||||
// For each paragraph, a state machine:
|
||||
|
@ -612,7 +612,7 @@ Message.prototype.getQuoteLines = function(options, cb) {
|
|||
buf += ` ${line}`;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case 'quote_line' :
|
||||
if(quoteMatch) {
|
||||
const rem = line.slice(quoteMatch[0].length);
|
||||
|
@ -628,7 +628,7 @@ Message.prototype.getQuoteLines = function(options, cb) {
|
|||
state = 'line';
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
default :
|
||||
if(isFormattedLine(line)) {
|
||||
quoted.push(getFormattedLine(line));
|
||||
|
@ -637,12 +637,12 @@ Message.prototype.getQuoteLines = function(options, cb) {
|
|||
buf = 'line' === state ? line : line.replace(/\s/, ''); // trim *first* leading space, if any
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
quoted.push(...getWrapped(buf, quoteMatch ? quoteMatch[1] : null));
|
||||
});
|
||||
|
||||
|
||||
input.slice(tearLinePos).split(/\r?\n/).forEach(l => {
|
||||
quoted.push(...getWrapped(l));
|
||||
});
|
||||
|
|
|
@ -40,9 +40,9 @@ function getAvailableMessageConferences(client, options) {
|
|||
options = options || { includeSystemInternal : false };
|
||||
|
||||
assert(client || true === options.noClient);
|
||||
|
||||
// perform ACS check per conf & omit system_internal if desired
|
||||
return _.omitBy(Config.messageConferences, (conf, confTag) => {
|
||||
|
||||
// perform ACS check per conf & omit system_internal if desired
|
||||
return _.omitBy(Config.messageConferences, (conf, confTag) => {
|
||||
if(!options.includeSystemInternal && 'system_internal' === confTag) {
|
||||
return true;
|
||||
}
|
||||
|
@ -60,15 +60,15 @@ function getSortedAvailMessageConferences(client, options) {
|
|||
});
|
||||
|
||||
sortAreasOrConfs(confs, 'conf');
|
||||
|
||||
|
||||
return confs;
|
||||
}
|
||||
|
||||
// Return an *object* of available areas within |confTag|
|
||||
function getAvailableMessageAreasByConfTag(confTag, options) {
|
||||
options = options || {};
|
||||
|
||||
// :TODO: confTag === "" then find default
|
||||
|
||||
// :TODO: confTag === "" then find default
|
||||
|
||||
if(_.has(Config.messageConferences, [ confTag, 'areas' ])) {
|
||||
const areas = Config.messageConferences[confTag].areas;
|
||||
|
@ -92,9 +92,9 @@ function getSortedAvailMessageAreasByConfTag(confTag, options) {
|
|||
area : v,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
sortAreasOrConfs(areas, 'area');
|
||||
|
||||
|
||||
return areas;
|
||||
}
|
||||
|
||||
|
@ -103,7 +103,7 @@ function getDefaultMessageConferenceTag(client, disableAcsCheck) {
|
|||
// Find the first conference marked 'default'. If found,
|
||||
// inspect |client| against *read* ACS using defaults if not
|
||||
// specified.
|
||||
//
|
||||
//
|
||||
// If the above fails, just go down the list until we get one
|
||||
// that passes.
|
||||
//
|
||||
|
@ -116,14 +116,14 @@ function getDefaultMessageConferenceTag(client, disableAcsCheck) {
|
|||
const conf = Config.messageConferences[defaultConf];
|
||||
if(true === disableAcsCheck || client.acs.hasMessageConfRead(conf)) {
|
||||
return defaultConf;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// just use anything we can
|
||||
defaultConf = _.findKey(Config.messageConferences, (conf, confTag) => {
|
||||
return 'system_internal' !== confTag && (true === disableAcsCheck || client.acs.hasMessageConfRead(conf));
|
||||
});
|
||||
|
||||
|
||||
return defaultConf;
|
||||
}
|
||||
|
||||
|
@ -138,19 +138,19 @@ function getDefaultMessageAreaTagByConfTag(client, confTag, disableAcsCheck) {
|
|||
confTag = confTag || getDefaultMessageConferenceTag(client);
|
||||
|
||||
if(confTag && _.has(Config.messageConferences, [ confTag, 'areas' ])) {
|
||||
const areaPool = Config.messageConferences[confTag].areas;
|
||||
const areaPool = Config.messageConferences[confTag].areas;
|
||||
let defaultArea = _.findKey(areaPool, o => o.default);
|
||||
if(defaultArea) {
|
||||
const area = areaPool[defaultArea];
|
||||
if(true === disableAcsCheck || client.acs.hasMessageAreaRead(area)) {
|
||||
return defaultArea;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
defaultArea = _.findKey(areaPool, (area) => {
|
||||
return (true === disableAcsCheck || client.acs.hasMessageAreaRead(area));
|
||||
return (true === disableAcsCheck || client.acs.hasMessageAreaRead(area));
|
||||
});
|
||||
|
||||
|
||||
return defaultArea;
|
||||
}
|
||||
}
|
||||
|
@ -159,18 +159,6 @@ function getMessageConferenceByTag(confTag) {
|
|||
return Config.messageConferences[confTag];
|
||||
}
|
||||
|
||||
function getMessageConfByAreaTag(areaTag) {
|
||||
const confs = Config.messageConferences;
|
||||
let conf;
|
||||
_.forEach(confs, (v) => {
|
||||
if(_.has(v, [ 'areas', areaTag ])) {
|
||||
conf = v;
|
||||
return false; // stop iteration
|
||||
}
|
||||
});
|
||||
return conf;
|
||||
}
|
||||
|
||||
function getMessageConfTagByAreaTag(areaTag) {
|
||||
const confs = Config.messageConferences;
|
||||
return Object.keys(confs).find( (confTag) => {
|
||||
|
@ -194,9 +182,9 @@ function getMessageAreaByTag(areaTag, optionalConfTag) {
|
|||
if(_.has(v, [ 'areas', areaTag ])) {
|
||||
area = v.areas[areaTag];
|
||||
return false; // stop iteration
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return area;
|
||||
}
|
||||
}
|
||||
|
@ -206,7 +194,7 @@ function changeMessageConference(client, confTag, cb) {
|
|||
[
|
||||
function getConf(callback) {
|
||||
const conf = getMessageConferenceByTag(confTag);
|
||||
|
||||
|
||||
if(conf) {
|
||||
callback(null, conf);
|
||||
} else {
|
||||
|
@ -216,7 +204,7 @@ function changeMessageConference(client, confTag, cb) {
|
|||
function getDefaultAreaInConf(conf, callback) {
|
||||
const areaTag = getDefaultMessageAreaTagByConfTag(client, confTag);
|
||||
const area = getMessageAreaByTag(areaTag, confTag);
|
||||
|
||||
|
||||
if(area) {
|
||||
callback(null, conf, { areaTag : areaTag, area : area } );
|
||||
} else {
|
||||
|
@ -229,7 +217,7 @@ function changeMessageConference(client, confTag, cb) {
|
|||
} else {
|
||||
return callback(null, conf, areaInfo);
|
||||
}
|
||||
},
|
||||
},
|
||||
function changeConferenceAndArea(conf, areaInfo, callback) {
|
||||
const newProps = {
|
||||
message_conf_tag : confTag,
|
||||
|
@ -258,12 +246,12 @@ function changeMessageAreaWithOptions(client, areaTag, options, cb) {
|
|||
[
|
||||
function getArea(callback) {
|
||||
const area = getMessageAreaByTag(areaTag);
|
||||
return callback(area ? null : new Error('Invalid message areaTag'), area);
|
||||
return callback(area ? null : new Error('Invalid message areaTag'), area);
|
||||
},
|
||||
function validateAccess(area, callback) {
|
||||
//
|
||||
// Need at least *read* to access the area
|
||||
//
|
||||
//
|
||||
// Need at least *read* to access the area
|
||||
//
|
||||
if(!client.acs.hasMessageAreaRead(area)) {
|
||||
return callback(new Error('Access denied to message area'));
|
||||
} else {
|
||||
|
@ -294,7 +282,7 @@ function changeMessageAreaWithOptions(client, areaTag, options, cb) {
|
|||
}
|
||||
|
||||
//
|
||||
// Temporairly -- e.g. non-persisted -- change to an area and it's
|
||||
// Temporairly -- e.g. non-persisted -- change to an area and it's
|
||||
// associated underlying conference. ACS is checked for both.
|
||||
//
|
||||
// This is useful for example when doing a new scan
|
||||
|
@ -312,7 +300,7 @@ function tempChangeMessageConfAndArea(client, areaTag) {
|
|||
if(!client.acs.hasMessageConfRead(conf) || !client.acs.hasMessageAreaRead(area)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
client.user.properties.message_conf_tag = confTag;
|
||||
client.user.properties.message_area_tag = areaTag;
|
||||
|
||||
|
@ -324,7 +312,7 @@ function changeMessageArea(client, areaTag, cb) {
|
|||
}
|
||||
|
||||
function getMessageFromRow(row) {
|
||||
return {
|
||||
return {
|
||||
messageId : row.message_id,
|
||||
messageUuid : row.message_uuid,
|
||||
replyToMsgId : row.reply_to_message_id,
|
||||
|
@ -346,8 +334,8 @@ function getNewMessageDataInAreaForUserSql(userId, areaTag, lastMessageId, what)
|
|||
//
|
||||
// * Only messages > |lastMessageId| should be returned/counted
|
||||
//
|
||||
const selectWhat = ('count' === what) ?
|
||||
'COUNT() AS count' :
|
||||
const selectWhat = ('count' === what) ?
|
||||
'COUNT() AS count' :
|
||||
'message_id, message_uuid, reply_to_message_id, to_user_name, from_user_name, subject, modified_timestamp, view_count';
|
||||
|
||||
let sql =
|
||||
|
@ -386,7 +374,7 @@ function getNewMessageCountInAreaForUser(userId, areaTag, cb) {
|
|||
msgDb.get(sql, (err, row) => {
|
||||
return callback(err, row ? row.count : 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
],
|
||||
cb
|
||||
);
|
||||
|
@ -421,7 +409,7 @@ function getNewMessagesInAreaForUser(userId, areaTag, cb) {
|
|||
function complete(err) {
|
||||
cb(err, msgList);
|
||||
}
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
function getMessageListForArea(options, areaTag, cb) {
|
||||
|
@ -435,7 +423,7 @@ function getMessageListForArea(options, areaTag, cb) {
|
|||
|
||||
/*
|
||||
[
|
||||
{
|
||||
{
|
||||
messageId, messageUuid, replyToId, toUserName, fromUserName, subject, modTimestamp,
|
||||
status(new|old),
|
||||
viewCount
|
||||
|
@ -448,13 +436,13 @@ function getMessageListForArea(options, areaTag, cb) {
|
|||
async.series(
|
||||
[
|
||||
function fetchMessages(callback) {
|
||||
let sql =
|
||||
let sql =
|
||||
`SELECT message_id, message_uuid, reply_to_message_id, to_user_name, from_user_name, subject, modified_timestamp, view_count
|
||||
FROM message
|
||||
WHERE area_tag = ?`;
|
||||
|
||||
if(Message.isPrivateAreaTag(areaTag)) {
|
||||
sql +=
|
||||
sql +=
|
||||
` AND message_id IN (
|
||||
SELECT message_id
|
||||
FROM message_meta
|
||||
|
@ -462,7 +450,7 @@ function getMessageListForArea(options, areaTag, cb) {
|
|||
)`;
|
||||
}
|
||||
|
||||
sql += ' ORDER BY message_id;';
|
||||
sql += ' ORDER BY message_id;';
|
||||
|
||||
msgDb.each(
|
||||
sql,
|
||||
|
@ -551,12 +539,12 @@ function updateMessageAreaLastReadId(userId, areaTag, messageId, allowOlder, cb)
|
|||
],
|
||||
function complete(err, didUpdate) {
|
||||
if(err) {
|
||||
Log.debug(
|
||||
{ error : err.toString(), userId : userId, areaTag : areaTag, messageId : messageId },
|
||||
Log.debug(
|
||||
{ error : err.toString(), userId : userId, areaTag : areaTag, messageId : messageId },
|
||||
'Failed updating area last read ID');
|
||||
} else {
|
||||
if(true === didUpdate) {
|
||||
Log.trace(
|
||||
Log.trace(
|
||||
{ userId : userId, areaTag : areaTag, messageId : messageId },
|
||||
'Area last read ID updated');
|
||||
}
|
||||
|
@ -574,7 +562,7 @@ function persistMessage(message, cb) {
|
|||
},
|
||||
function recordToMessageNetworks(callback) {
|
||||
return msgNetRecord(message, callback);
|
||||
}
|
||||
}
|
||||
],
|
||||
cb
|
||||
);
|
||||
|
@ -582,7 +570,7 @@ function persistMessage(message, cb) {
|
|||
|
||||
// method exposed for event scheduler
|
||||
function trimMessageAreasScheduledEvent(args, cb) {
|
||||
|
||||
|
||||
function trimMessageAreaByMaxMessages(areaInfo, cb) {
|
||||
if(0 === areaInfo.maxMessages) {
|
||||
return cb(null);
|
||||
|
@ -605,7 +593,7 @@ function trimMessageAreasScheduledEvent(args, cb) {
|
|||
Log.debug( { areaInfo : areaInfo, type : 'maxMessages', count : this.changes }, 'Area trimmed successfully');
|
||||
}
|
||||
return cb(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -690,7 +678,7 @@ function trimMessageAreasScheduledEvent(args, cb) {
|
|||
|
||||
trimMessageAreaByMaxAgeDays(areaInfo, err => {
|
||||
return next(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
callback
|
||||
|
|
|
@ -36,6 +36,6 @@ function resolveMimeType(query) {
|
|||
if(mimeTypes.extensions[query]) {
|
||||
return query; // alreaed a mime-type
|
||||
}
|
||||
|
||||
|
||||
return mimeTypes.lookup(query) || undefined; // lookup() returns false; we want undefined
|
||||
}
|
|
@ -36,10 +36,10 @@ function resolvePath(path) {
|
|||
|
||||
function getCleanEnigmaVersion() {
|
||||
return packageJson.version
|
||||
.replace(/\-/g, '.')
|
||||
.replace(/-/g, '.')
|
||||
.replace(/alpha/,'a')
|
||||
.replace(/beta/,'b')
|
||||
;
|
||||
;
|
||||
}
|
||||
|
||||
// See also ftn_util.js getTearLine() & getProductIdentifier()
|
||||
|
|
|
@ -5,7 +5,7 @@ const messageArea = require('../core/message_area.js');
|
|||
|
||||
|
||||
exports.MessageAreaConfTempSwitcher = Sup => class extends Sup {
|
||||
|
||||
|
||||
tempMessageConfAndAreaSwitch(messageAreaTag) {
|
||||
messageAreaTag = messageAreaTag || this.messageAreaTag;
|
||||
if(!messageAreaTag) {
|
||||
|
@ -14,7 +14,7 @@ exports.MessageAreaConfTempSwitcher = Sup => class extends Sup {
|
|||
|
||||
this.prevMessageConfAndArea = {
|
||||
confTag : this.client.user.properties.message_conf_tag,
|
||||
areaTag : this.client.user.properties.message_area_tag,
|
||||
areaTag : this.client.user.properties.message_area_tag,
|
||||
};
|
||||
|
||||
if(!messageArea.tempChangeMessageConfAndArea(this.client, this.messageAreaTag)) {
|
||||
|
@ -25,7 +25,7 @@ exports.MessageAreaConfTempSwitcher = Sup => class extends Sup {
|
|||
tempMessageConfAndAreaRestore() {
|
||||
if(this.prevMessageConfAndArea) {
|
||||
this.client.user.properties.message_conf_tag = this.prevMessageConfAndArea.confTag;
|
||||
this.client.user.properties.message_area_tag = this.prevMessageConfAndArea.areaTag;
|
||||
this.client.user.properties.message_area_tag = this.prevMessageConfAndArea.areaTag;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -36,7 +36,7 @@ exports.moduleInfo = {
|
|||
const MciViewIds = {
|
||||
AreaList : 1,
|
||||
SelAreaInfo1 : 2,
|
||||
SelAreaInfo2 : 3,
|
||||
SelAreaInfo2 : 3,
|
||||
};
|
||||
|
||||
exports.getModule = class MessageAreaListModule extends MenuModule {
|
||||
|
@ -61,7 +61,7 @@ exports.getModule = class MessageAreaListModule extends MenuModule {
|
|||
self.client.term.pipeWrite(`\n|00Cannot change area: ${err.message}\n`);
|
||||
|
||||
self.prevMenuOnTimeout(1000, cb);
|
||||
} else {
|
||||
} else {
|
||||
if(_.isString(area.art)) {
|
||||
const dispOptions = {
|
||||
client : self.client,
|
||||
|
@ -72,7 +72,7 @@ exports.getModule = class MessageAreaListModule extends MenuModule {
|
|||
|
||||
displayThemeArt(dispOptions, () => {
|
||||
// pause by default, unless explicitly told not to
|
||||
if(_.has(area, 'options.pause') && false === area.options.pause) {
|
||||
if(_.has(area, 'options.pause') && false === area.options.pause) {
|
||||
return self.prevMenuOnTimeout(1000, cb);
|
||||
} else {
|
||||
self.pausePrompt( () => {
|
||||
|
@ -98,9 +98,9 @@ exports.getModule = class MessageAreaListModule extends MenuModule {
|
|||
}, timeout);
|
||||
}
|
||||
|
||||
updateGeneralAreaInfoViews(areaIndex) {
|
||||
// :TODO: these concepts have been replaced with the {someKey} style formatting - update me!
|
||||
/* experimental: not yet avail
|
||||
// :TODO: these concepts have been replaced with the {someKey} style formatting - update me!
|
||||
/*
|
||||
updateGeneralAreaInfoViews(areaIndex) {
|
||||
const areaInfo = self.messageAreas[areaIndex];
|
||||
|
||||
[ MciViewIds.SelAreaInfo1, MciViewIds.SelAreaInfo2 ].forEach(mciId => {
|
||||
|
@ -109,8 +109,8 @@ exports.getModule = class MessageAreaListModule extends MenuModule {
|
|||
v.setFormatObject(areaInfo.area);
|
||||
}
|
||||
});
|
||||
*/
|
||||
}
|
||||
*/
|
||||
|
||||
mciReady(mciData, cb) {
|
||||
super.mciReady(mciData, err => {
|
||||
|
@ -137,7 +137,7 @@ exports.getModule = class MessageAreaListModule extends MenuModule {
|
|||
function populateAreaListView(callback) {
|
||||
const listFormat = self.menuConfig.config.listFormat || '{index} ) - {name}';
|
||||
const focusListFormat = self.menuConfig.config.focusListFormat || listFormat;
|
||||
|
||||
|
||||
const areaListView = vc.getView(MciViewIds.AreaList);
|
||||
let i = 1;
|
||||
areaListView.setItems(_.map(self.messageAreas, v => {
|
||||
|
@ -145,7 +145,7 @@ exports.getModule = class MessageAreaListModule extends MenuModule {
|
|||
index : i++,
|
||||
areaTag : v.area.areaTag,
|
||||
name : v.area.name,
|
||||
desc : v.area.desc,
|
||||
desc : v.area.desc,
|
||||
});
|
||||
}));
|
||||
|
||||
|
@ -155,7 +155,7 @@ exports.getModule = class MessageAreaListModule extends MenuModule {
|
|||
index : i++,
|
||||
areaTag : v.area.areaTag,
|
||||
name : v.area.name,
|
||||
desc : v.area.desc,
|
||||
desc : v.area.desc,
|
||||
});
|
||||
}));
|
||||
|
||||
|
|
|
@ -48,9 +48,9 @@ exports.getModule = class AreaPostFSEModule extends FullScreenEditorModule {
|
|||
self.client.log.info(
|
||||
{ to : msg.toUserName, subject : msg.subject, uuid : msg.uuid },
|
||||
'Message persisted'
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return self.nextMenu(cb);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -69,7 +69,7 @@ exports.getModule = class AreaViewFSEModule extends FullScreenEditorModule {
|
|||
case 'down arrow' : bodyView.scrollDocumentUp(); break;
|
||||
case 'up arrow' : bodyView.scrollDocumentDown(); break;
|
||||
case 'page up' : bodyView.keyPressPageUp(); break;
|
||||
case 'page down' : bodyView.keyPressPageDown(); break;
|
||||
case 'page down' : bodyView.keyPressPageDown(); break;
|
||||
}
|
||||
|
||||
// :TODO: need to stop down/page down if doing so would push the last
|
||||
|
@ -83,13 +83,13 @@ exports.getModule = class AreaViewFSEModule extends FullScreenEditorModule {
|
|||
const modOpts = {
|
||||
extraArgs : {
|
||||
messageAreaTag : self.messageAreaTag,
|
||||
replyToMessage : self.message,
|
||||
}
|
||||
replyToMessage : self.message,
|
||||
}
|
||||
};
|
||||
|
||||
return self.gotoMenu(extraArgs.menu, modOpts, cb);
|
||||
}
|
||||
|
||||
|
||||
self.client.log(extraArgs, 'Missing extraArgs.menu');
|
||||
return cb(null);
|
||||
}
|
||||
|
|
|
@ -21,10 +21,10 @@ exports.moduleInfo = {
|
|||
|
||||
const MciViewIds = {
|
||||
ConfList : 1,
|
||||
|
||||
|
||||
// :TODO:
|
||||
// # areas in conf .... see Obv/2, iNiQ, ...
|
||||
//
|
||||
//
|
||||
};
|
||||
|
||||
exports.getModule = class MessageConfListModule extends MenuModule {
|
||||
|
@ -33,16 +33,16 @@ exports.getModule = class MessageConfListModule extends MenuModule {
|
|||
|
||||
this.messageConfs = messageArea.getSortedAvailMessageConferences(this.client);
|
||||
const self = this;
|
||||
|
||||
|
||||
this.menuMethods = {
|
||||
changeConference : function(formData, extraArgs, cb) {
|
||||
if(1 === formData.submitId) {
|
||||
let conf = self.messageConfs[formData.value.conf];
|
||||
const confTag = conf.confTag;
|
||||
conf = conf.conf; // what we want is embedded
|
||||
conf = conf.conf; // what we want is embedded
|
||||
|
||||
messageArea.changeMessageConference(self.client, confTag, err => {
|
||||
if(err) {
|
||||
if(err) {
|
||||
self.client.term.pipeWrite(`\n|00Cannot change conference: ${err.message}\n`);
|
||||
|
||||
setTimeout( () => {
|
||||
|
@ -59,7 +59,7 @@ exports.getModule = class MessageConfListModule extends MenuModule {
|
|||
|
||||
displayThemeArt(dispOptions, () => {
|
||||
// pause by default, unless explicitly told not to
|
||||
if(_.has(conf, 'options.pause') && false === conf.options.pause) {
|
||||
if(_.has(conf, 'options.pause') && false === conf.options.pause) {
|
||||
return self.prevMenuOnTimeout(1000, cb);
|
||||
} else {
|
||||
self.pausePrompt( () => {
|
||||
|
@ -108,7 +108,7 @@ exports.getModule = class MessageConfListModule extends MenuModule {
|
|||
function populateConfListView(callback) {
|
||||
const listFormat = self.menuConfig.config.listFormat || '{index} ) - {name}';
|
||||
const focusListFormat = self.menuConfig.config.focusListFormat || listFormat;
|
||||
|
||||
|
||||
const confListView = vc.getView(MciViewIds.ConfList);
|
||||
let i = 1;
|
||||
confListView.setItems(_.map(self.messageConfs, v => {
|
||||
|
@ -116,7 +116,7 @@ exports.getModule = class MessageConfListModule extends MenuModule {
|
|||
index : i++,
|
||||
confTag : v.conf.confTag,
|
||||
name : v.conf.name,
|
||||
desc : v.conf.desc,
|
||||
desc : v.conf.desc,
|
||||
});
|
||||
}));
|
||||
|
||||
|
@ -126,7 +126,7 @@ exports.getModule = class MessageConfListModule extends MenuModule {
|
|||
index : i++,
|
||||
confTag : v.conf.confTag,
|
||||
name : v.conf.name,
|
||||
desc : v.conf.desc,
|
||||
desc : v.conf.desc,
|
||||
});
|
||||
}));
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ const moment = require('moment');
|
|||
MCI codes:
|
||||
|
||||
VM1 : Message list
|
||||
TL2 : Message info 1: { msgNumSelected, msgNumTotal }
|
||||
TL2 : Message info 1: { msgNumSelected, msgNumTotal }
|
||||
*/
|
||||
|
||||
exports.moduleInfo = {
|
||||
|
@ -84,9 +84,9 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
|
|||
// due to the size of |messageList|. See https://github.com/trentm/node-bunyan/issues/189
|
||||
//
|
||||
modOpts.extraArgs.toJSON = function() {
|
||||
const logMsgList = (this.messageList.length <= 4) ?
|
||||
this.messageList :
|
||||
this.messageList.slice(0, 2).concat(this.messageList.slice(-2));
|
||||
const logMsgList = (this.messageList.length <= 4) ?
|
||||
this.messageList :
|
||||
this.messageList.slice(0, 2).concat(this.messageList.slice(-2));
|
||||
|
||||
return {
|
||||
messageAreaTag : this.messageAreaTag,
|
||||
|
@ -158,14 +158,14 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
|
|||
if(_.isArray(self.messageList)) {
|
||||
return callback(0 === self.messageList.length ? new Error('No messages in area') : null);
|
||||
}
|
||||
|
||||
|
||||
messageArea.getMessageListForArea( { client : self.client }, self.messageAreaTag, function msgs(err, msgList) {
|
||||
if(!msgList || 0 === msgList.length) {
|
||||
return callback(new Error('No messages in area'));
|
||||
}
|
||||
|
||||
|
||||
self.messageList = msgList;
|
||||
return callback(err);
|
||||
return callback(err);
|
||||
});
|
||||
},
|
||||
function getLastReadMesageId(callback) {
|
||||
|
@ -187,15 +187,15 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
|
|||
|
||||
if(_.isUndefined(self.initialFocusIndex) && listItem.messageId > self.lastReadId) {
|
||||
self.initialFocusIndex = index;
|
||||
}
|
||||
}
|
||||
});
|
||||
return callback(null);
|
||||
},
|
||||
function populateList(callback) {
|
||||
const msgListView = vc.getView(MCICodesIDs.MsgList);
|
||||
const listFormat = self.menuConfig.config.listFormat || '{msgNum} - {subject} - {toUserName}';
|
||||
const msgListView = vc.getView(MCICodesIDs.MsgList);
|
||||
const listFormat = self.menuConfig.config.listFormat || '{msgNum} - {subject} - {toUserName}';
|
||||
const focusListFormat = self.menuConfig.config.focusListFormat || listFormat; // :TODO: default change color here
|
||||
const messageInfo1Format = self.menuConfig.config.messageInfo1Format || '{msgNumSelected} / {msgNumTotal}';
|
||||
const messageInfo1Format = self.menuConfig.config.messageInfo1Format || '{msgNumSelected} / {msgNumTotal}';
|
||||
|
||||
// :TODO: This can take a very long time to load large lists. What we need is to implement the "owner draw" concept in
|
||||
// which items are requested (e.g. their format at least) *as-needed* vs trying to get the format for all of them at once
|
||||
|
@ -211,10 +211,10 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
|
|||
msgListView.on('index update', idx => {
|
||||
self.setViewText(
|
||||
'allViews',
|
||||
MCICodesIDs.MsgInfo1,
|
||||
MCICodesIDs.MsgInfo1,
|
||||
stringFormat(messageInfo1Format, { msgNumSelected : (idx + 1), msgNumTotal : self.messageList.length } ));
|
||||
});
|
||||
|
||||
|
||||
if(self.initialFocusIndex > 0) {
|
||||
// note: causes redraw()
|
||||
msgListView.setFocusItemIndex(self.initialFocusIndex);
|
||||
|
@ -228,29 +228,29 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
|
|||
const messageInfo1Format = self.menuConfig.config.messageInfo1Format || '{msgNumSelected} / {msgNumTotal}';
|
||||
self.setViewText(
|
||||
'allViews',
|
||||
MCICodesIDs.MsgInfo1,
|
||||
MCICodesIDs.MsgInfo1,
|
||||
stringFormat(messageInfo1Format, { msgNumSelected : self.initialFocusIndex + 1, msgNumTotal : self.messageList.length } ));
|
||||
return callback(null);
|
||||
},
|
||||
],
|
||||
],
|
||||
err => {
|
||||
if(err) {
|
||||
self.client.log.error( { error : err.message }, 'Error loading message list');
|
||||
self.client.log.error( { error : err.message }, 'Error loading message list');
|
||||
}
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getSaveState() {
|
||||
return { initialFocusIndex : this.initialFocusIndex };
|
||||
return { initialFocusIndex : this.initialFocusIndex };
|
||||
}
|
||||
|
||||
restoreSavedState(savedState) {
|
||||
if(savedState) {
|
||||
this.initialFocusIndex = savedState.initialFocusIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getMenuResult() {
|
||||
|
|
|
@ -38,17 +38,17 @@ function startup(cb) {
|
|||
|
||||
function shutdown(cb) {
|
||||
async.each(
|
||||
msgNetworkModules,
|
||||
msgNetworkModules,
|
||||
(msgNetModule, next) => {
|
||||
msgNetModule.shutdown( () => {
|
||||
return next();
|
||||
});
|
||||
},
|
||||
},
|
||||
() => {
|
||||
msgNetworkModules = [];
|
||||
return cb(null);
|
||||
}
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
function recordMessage(message, cb) {
|
||||
|
@ -59,7 +59,7 @@ function recordMessage(message, cb) {
|
|||
//
|
||||
async.each(msgNetworkModules, (modInst, next) => {
|
||||
modInst.record(message);
|
||||
next();
|
||||
next();
|
||||
}, err => {
|
||||
cb(err);
|
||||
});
|
||||
|
|
|
@ -13,12 +13,12 @@ function MessageScanTossModule() {
|
|||
require('util').inherits(MessageScanTossModule, PluginModule);
|
||||
|
||||
MessageScanTossModule.prototype.startup = function(cb) {
|
||||
cb(null);
|
||||
return cb(null);
|
||||
};
|
||||
|
||||
MessageScanTossModule.prototype.shutdown = function(cb) {
|
||||
cb(null);
|
||||
return cb(null);
|
||||
};
|
||||
|
||||
MessageScanTossModule.prototype.record = function(message) {
|
||||
MessageScanTossModule.prototype.record = function(/*message*/) {
|
||||
};
|
|
@ -4,7 +4,6 @@
|
|||
const View = require('./view.js').View;
|
||||
const strUtil = require('./string_util.js');
|
||||
const ansi = require('./ansi_term.js');
|
||||
const colorCodes = require('./color_codes.js');
|
||||
const wordWrapText = require('./word_wrap.js').wordWrapText;
|
||||
const ansiPrep = require('./ansi_prep.js');
|
||||
|
||||
|
@ -12,11 +11,11 @@ const assert = require('assert');
|
|||
const _ = require('lodash');
|
||||
|
||||
// :TODO: Determine CTRL-* keys for various things
|
||||
// See http://www.bbsdocumentary.com/library/PROGRAMS/GRAPHICS/ANSI/bansi.txt
|
||||
// http://wiki.synchro.net/howto:editor:slyedit#edit_mode
|
||||
// http://sublime-text-unofficial-documentation.readthedocs.org/en/latest/reference/keyboard_shortcuts_win.html
|
||||
// See http://www.bbsdocumentary.com/library/PROGRAMS/GRAPHICS/ANSI/bansi.txt
|
||||
// http://wiki.synchro.net/howto:editor:slyedit#edit_mode
|
||||
// http://sublime-text-unofficial-documentation.readthedocs.org/en/latest/reference/keyboard_shortcuts_win.html
|
||||
|
||||
/* Mystic
|
||||
/* Mystic
|
||||
[^B] Reformat Paragraph [^O] Show this help file
|
||||
[^I] Insert tab space [^Q] Enter quote mode
|
||||
[^K] Cut current line of text [^V] Toggle insert/overwrite
|
||||
|
@ -179,8 +178,8 @@ function MultiLineEditTextView(options) {
|
|||
|
||||
for(let i = startIndex; i < endIndex; ++i) {
|
||||
//${self.getSGRFor('text')}
|
||||
self.client.term.write(
|
||||
`${ansi.goto(absPos.row++, absPos.col)}${self.getRenderText(i)}`,
|
||||
self.client.term.write(
|
||||
`${ansi.goto(absPos.row++, absPos.col)}${self.getRenderText(i)}`,
|
||||
false // convertLineFeeds
|
||||
);
|
||||
}
|
||||
|
@ -268,7 +267,7 @@ function MultiLineEditTextView(options) {
|
|||
|
||||
if(remain > 0) {
|
||||
text += ' '.repeat(remain + 1);
|
||||
// text += new Array(remain + 1).join(' ');
|
||||
// text += new Array(remain + 1).join(' ');
|
||||
}
|
||||
|
||||
return text;
|
||||
|
@ -291,7 +290,7 @@ function MultiLineEditTextView(options) {
|
|||
|
||||
lines.forEach(line => {
|
||||
text += line.text.replace(re, '\t');
|
||||
|
||||
|
||||
if(options.forceLineTerms || (eolMarker && line.eol)) {
|
||||
text += eolMarker;
|
||||
}
|
||||
|
@ -459,7 +458,7 @@ function MultiLineEditTextView(options) {
|
|||
self.getRenderText(index).slice(self.cursorPos.col - c.length) +
|
||||
ansi.goto(absPos.row, absPos.col) +
|
||||
ansi.showCursor(), false
|
||||
);
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -502,7 +501,7 @@ function MultiLineEditTextView(options) {
|
|||
}
|
||||
|
||||
return wordWrapText(
|
||||
s,
|
||||
s,
|
||||
{
|
||||
width : width,
|
||||
tabHandling : tabHandling || 'expand',
|
||||
|
@ -1122,19 +1121,19 @@ MultiLineEditTextView.prototype.getData = function(options = { forceLineTerms :
|
|||
|
||||
MultiLineEditTextView.prototype.setPropertyValue = function(propName, value) {
|
||||
switch(propName) {
|
||||
case 'mode' :
|
||||
case 'mode' :
|
||||
this.mode = value;
|
||||
if('preview' === value && !this.specialKeyMap.next) {
|
||||
this.specialKeyMap.next = [ 'tab' ];
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'autoScroll' :
|
||||
case 'autoScroll' :
|
||||
this.autoScroll = value;
|
||||
break;
|
||||
|
||||
case 'tabSwitchesView' :
|
||||
this.tabSwitchesView = value;
|
||||
this.tabSwitchesView = value;
|
||||
this.specialKeyMap.next = this.specialKeyMap.next || [];
|
||||
this.specialKeyMap.next.push('tab');
|
||||
break;
|
||||
|
|
|
@ -25,8 +25,8 @@ exports.moduleInfo = {
|
|||
* :TODO:
|
||||
* * User configurable new scan: Area selection (avail from messages area) (sep module)
|
||||
* * Add status TL/VM (either/both should update if present)
|
||||
* *
|
||||
|
||||
* *
|
||||
|
||||
*/
|
||||
|
||||
const MciCodeIds = {
|
||||
|
@ -37,7 +37,7 @@ const MciCodeIds = {
|
|||
const Steps = {
|
||||
MessageConfs : 'messageConferences',
|
||||
FileBase : 'fileBase',
|
||||
|
||||
|
||||
Finished : 'finished',
|
||||
};
|
||||
|
||||
|
@ -53,7 +53,7 @@ exports.getModule = class NewScanModule extends MenuModule {
|
|||
|
||||
// :TODO: Make this conf/area specific:
|
||||
const config = this.menuConfig.config;
|
||||
this.scanStartFmt = config.scanStartFmt || 'Scanning {confName} - {areaName}...';
|
||||
this.scanStartFmt = config.scanStartFmt || 'Scanning {confName} - {areaName}...';
|
||||
this.scanFinishNoneFmt = config.scanFinishNoneFmt || 'Nothing new';
|
||||
this.scanFinishNewFmt = config.scanFinishNewFmt || '{count} entries found';
|
||||
this.scanCompleteMsg = config.scanCompleteMsg || 'Finished newscan';
|
||||
|
@ -62,16 +62,16 @@ exports.getModule = class NewScanModule extends MenuModule {
|
|||
updateScanStatus(statusText) {
|
||||
this.setViewText('allViews', MciCodeIds.ScanStatusLabel, statusText);
|
||||
}
|
||||
|
||||
|
||||
newScanMessageConference(cb) {
|
||||
// lazy init
|
||||
// lazy init
|
||||
if(!this.sortedMessageConfs) {
|
||||
const getAvailOpts = { includeSystemInternal : true }; // find new private messages, bulletins, etc.
|
||||
const getAvailOpts = { includeSystemInternal : true }; // find new private messages, bulletins, etc.
|
||||
|
||||
this.sortedMessageConfs = _.map(msgArea.getAvailableMessageConferences(this.client, getAvailOpts), (v, k) => {
|
||||
return {
|
||||
confTag : k,
|
||||
conf : v,
|
||||
conf : v,
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -91,27 +91,27 @@ exports.getModule = class NewScanModule extends MenuModule {
|
|||
this.currentScanAux.conf = this.currentScanAux.conf || 0;
|
||||
this.currentScanAux.area = this.currentScanAux.area || 0;
|
||||
}
|
||||
|
||||
|
||||
const currentConf = this.sortedMessageConfs[this.currentScanAux.conf];
|
||||
|
||||
this.newScanMessageArea(currentConf, () => {
|
||||
if(this.sortedMessageConfs.length > this.currentScanAux.conf + 1) {
|
||||
this.currentScanAux.conf += 1;
|
||||
this.currentScanAux.conf += 1;
|
||||
this.currentScanAux.area = 0;
|
||||
|
||||
|
||||
return this.newScanMessageConference(cb); // recursive to next conf
|
||||
}
|
||||
|
||||
this.updateScanStatus(this.scanCompleteMsg);
|
||||
return cb(Errors.DoesNotExist('No more conferences'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
newScanMessageArea(conf, cb) {
|
||||
// :TODO: it would be nice to cache this - must be done by conf!
|
||||
// :TODO: it would be nice to cache this - must be done by conf!
|
||||
const sortedAreas = msgArea.getSortedAvailMessageAreasByConfTag(conf.confTag, { client : this.client } );
|
||||
const currentArea = sortedAreas[this.currentScanAux.area];
|
||||
|
||||
|
||||
//
|
||||
// Scan and update index until we find something. If results are found,
|
||||
// we'll goto the list module & show them.
|
||||
|
@ -207,20 +207,20 @@ exports.getModule = class NewScanModule extends MenuModule {
|
|||
|
||||
performScanCurrentStep(cb) {
|
||||
switch(this.currentStep) {
|
||||
case Steps.MessageConfs :
|
||||
this.newScanMessageConference( () => {
|
||||
case Steps.MessageConfs :
|
||||
this.newScanMessageConference( () => {
|
||||
this.currentStep = Steps.FileBase;
|
||||
return this.performScanCurrentStep(cb);
|
||||
});
|
||||
break;
|
||||
|
||||
|
||||
case Steps.FileBase :
|
||||
this.newScanFileBase( () => {
|
||||
this.currentStep = Steps.Finished;
|
||||
return this.performScanCurrentStep(cb);
|
||||
return this.performScanCurrentStep(cb);
|
||||
});
|
||||
break;
|
||||
|
||||
|
||||
default : return cb(null);
|
||||
}
|
||||
}
|
||||
|
@ -241,7 +241,7 @@ exports.getModule = class NewScanModule extends MenuModule {
|
|||
|
||||
// :TODO: display scan step/etc.
|
||||
|
||||
async.series(
|
||||
async.series(
|
||||
[
|
||||
function loadFromConfig(callback) {
|
||||
const loadOpts = {
|
||||
|
|
16
core/nua.js
16
core/nua.js
|
@ -22,10 +22,10 @@ const MciViewIds = {
|
|||
};
|
||||
|
||||
exports.getModule = class NewUserAppModule extends MenuModule {
|
||||
|
||||
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
|
||||
const self = this;
|
||||
|
||||
this.menuMethods = {
|
||||
|
@ -40,7 +40,7 @@ exports.getModule = class NewUserAppModule extends MenuModule {
|
|||
viewValidationListener : function(err, cb) {
|
||||
const errMsgView = self.viewControllers.menu.getView(MciViewIds.errMsg);
|
||||
let newFocusId;
|
||||
|
||||
|
||||
if(err) {
|
||||
errMsgView.setText(err.message);
|
||||
err.view.clearText();
|
||||
|
@ -67,14 +67,14 @@ exports.getModule = class NewUserAppModule extends MenuModule {
|
|||
|
||||
//
|
||||
// We have to disable ACS checks for initial default areas as the user is not yet ready
|
||||
//
|
||||
//
|
||||
let confTag = messageArea.getDefaultMessageConferenceTag(self.client, true); // true=disableAcsCheck
|
||||
let areaTag = messageArea.getDefaultMessageAreaTagByConfTag(self.client, confTag, true); // true=disableAcsCheck
|
||||
|
||||
// can't store undefined!
|
||||
confTag = confTag || '';
|
||||
areaTag = areaTag || '';
|
||||
|
||||
|
||||
newUser.properties = {
|
||||
real_name : formData.value.realName,
|
||||
birthdate : new Date(Date.parse(formData.value.birthdate)).toISOString(), // :TODO: Use moment & explicit ISO string format
|
||||
|
@ -84,12 +84,12 @@ exports.getModule = class NewUserAppModule extends MenuModule {
|
|||
email_address : formData.value.email,
|
||||
web_address : formData.value.web,
|
||||
account_created : new Date().toISOString(), // :TODO: Use moment & explicit ISO string format
|
||||
|
||||
|
||||
message_conf_tag : confTag,
|
||||
message_area_tag : areaTag,
|
||||
|
||||
term_height : self.client.term.termHeight,
|
||||
term_width : self.client.term.termWidth,
|
||||
term_width : self.client.term.termWidth,
|
||||
|
||||
// :TODO: Other defaults
|
||||
// :TODO: should probably have a place to create defaults/etc.
|
||||
|
@ -100,7 +100,7 @@ exports.getModule = class NewUserAppModule extends MenuModule {
|
|||
} else {
|
||||
newUser.properties.theme_id = Config.defaults.theme;
|
||||
}
|
||||
|
||||
|
||||
// :TODO: User.create() should validate email uniqueness!
|
||||
newUser.create(formData.value.password, err => {
|
||||
if(err) {
|
||||
|
|
|
@ -20,7 +20,7 @@ const async = require('async');
|
|||
const _ = require('lodash');
|
||||
const moment = require('moment');
|
||||
|
||||
/*
|
||||
/*
|
||||
Module :TODO:
|
||||
* Add pipe code support
|
||||
- override max length & monitor *display* len as user types in order to allow for actual display len with color
|
||||
|
@ -73,7 +73,7 @@ exports.getModule = class OnelinerzModule extends MenuModule {
|
|||
self.client.log.warn( { error : err.message }, 'Failed saving oneliner');
|
||||
}
|
||||
|
||||
self.clearAddForm();
|
||||
self.clearAddForm();
|
||||
return self.displayViewScreen(true, cb); // true=cls
|
||||
});
|
||||
|
||||
|
@ -89,7 +89,7 @@ exports.getModule = class OnelinerzModule extends MenuModule {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
initSequence() {
|
||||
const self = this;
|
||||
async.series(
|
||||
|
@ -136,7 +136,7 @@ exports.getModule = class OnelinerzModule extends MenuModule {
|
|||
function initOrRedrawViewController(artData, callback) {
|
||||
if(_.isUndefined(self.viewControllers.add)) {
|
||||
const vc = self.addViewController(
|
||||
'view',
|
||||
'view',
|
||||
new ViewController( { client : self.client, formId : FormIds.View } )
|
||||
);
|
||||
|
||||
|
@ -149,7 +149,7 @@ exports.getModule = class OnelinerzModule extends MenuModule {
|
|||
return vc.loadFromMenuConfig(loadOpts, callback);
|
||||
} else {
|
||||
self.viewControllers.view.setFocus(true);
|
||||
self.viewControllers.view.getView(MciViewIds.ViewForm.AddPrompt).redraw();
|
||||
self.viewControllers.view.getView(MciViewIds.ViewForm.AddPrompt).redraw();
|
||||
return callback(null);
|
||||
}
|
||||
},
|
||||
|
@ -216,7 +216,7 @@ exports.getModule = class OnelinerzModule extends MenuModule {
|
|||
[
|
||||
function clearAndDisplayArt(callback) {
|
||||
self.viewControllers.view.setFocus(false);
|
||||
self.client.term.rawWrite(ansi.resetScreen());
|
||||
self.client.term.rawWrite(ansi.resetScreen());
|
||||
|
||||
theme.displayThemedAsset(
|
||||
self.menuConfig.config.art.add,
|
||||
|
@ -230,7 +230,7 @@ exports.getModule = class OnelinerzModule extends MenuModule {
|
|||
function initOrRedrawViewController(artData, callback) {
|
||||
if(_.isUndefined(self.viewControllers.add)) {
|
||||
const vc = self.addViewController(
|
||||
'add',
|
||||
'add',
|
||||
new ViewController( { client : self.client, formId : FormIds.Add } )
|
||||
);
|
||||
|
||||
|
@ -269,7 +269,7 @@ exports.getModule = class OnelinerzModule extends MenuModule {
|
|||
[
|
||||
function openDatabase(callback) {
|
||||
self.db = getTransactionDatabase(new sqlite3.Database(
|
||||
getModDatabasePath(exports.moduleInfo),
|
||||
getModDatabasePath(exports.moduleInfo),
|
||||
err => {
|
||||
return callback(err);
|
||||
}
|
||||
|
@ -284,10 +284,10 @@ exports.getModule = class OnelinerzModule extends MenuModule {
|
|||
oneliner VARCHAR NOT NULL,
|
||||
timestamp DATETIME NOT NULL
|
||||
);`
|
||||
,
|
||||
err => {
|
||||
return callback(err);
|
||||
});
|
||||
,
|
||||
err => {
|
||||
return callback(err);
|
||||
});
|
||||
}
|
||||
],
|
||||
err => {
|
||||
|
@ -327,7 +327,7 @@ exports.getModule = class OnelinerzModule extends MenuModule {
|
|||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
beforeArt(cb) {
|
||||
|
|
|
@ -3,5 +3,5 @@
|
|||
|
||||
exports.PluginModule = PluginModule;
|
||||
|
||||
function PluginModule(options) {
|
||||
function PluginModule(/*options*/) {
|
||||
}
|
||||
|
|
|
@ -92,7 +92,7 @@ const PREDEFINED_MCI_GENERATORS = {
|
|||
ST : function serverName(client) { return client.session.serverName; },
|
||||
FN : function activeFileBaseFilterName(client) {
|
||||
const activeFilter = FileBaseFilters.getActiveFilter(client);
|
||||
return activeFilter ? activeFilter.name : '';
|
||||
return activeFilter ? activeFilter.name : '';
|
||||
},
|
||||
DN : function userNumDownloads(client) { return userStatAsString(client, 'dl_total_count', 0); }, // Obv/2
|
||||
DK : function userByteDownload(client) { // Obv/2 uses DK=downloaded Kbytes
|
||||
|
@ -160,7 +160,7 @@ const PREDEFINED_MCI_GENERATORS = {
|
|||
},
|
||||
|
||||
OA : function systemArchitecture() { return os.arch(); },
|
||||
|
||||
|
||||
SC : function systemCpuModel() {
|
||||
//
|
||||
// Clean up CPU strings a bit for better display
|
||||
|
@ -190,7 +190,7 @@ const PREDEFINED_MCI_GENERATORS = {
|
|||
// System File Base, Up/Download Info
|
||||
//
|
||||
// :TODO: DD - Today's # of downloads (iNiQUiTY)
|
||||
//
|
||||
//
|
||||
SD : function systemNumDownloads() { return sysStatAsString('dl_total_count', 0); },
|
||||
SO : function systemByteDownload() {
|
||||
const byteSize = StatLog.getSystemStatNum('dl_total_bytes');
|
||||
|
@ -221,7 +221,7 @@ const PREDEFINED_MCI_GENERATORS = {
|
|||
// -> Include FTN/etc.
|
||||
// :TODO: LC - name of last caller to system (Obv/2)
|
||||
// :TODO: TZ - Average *system* post/call ratio (iNiQUiTY)
|
||||
|
||||
|
||||
|
||||
//
|
||||
// Special handling for XY
|
||||
|
|
|
@ -52,9 +52,9 @@ exports.getModule = class RumorzModule extends MenuModule {
|
|||
addEntry : (formData, extraArgs, cb) => {
|
||||
if(_.isString(formData.value.rumor) && renderStringLength(formData.value.rumor) > 0) {
|
||||
const rumor = formData.value.rumor.trim(); // remove any trailing ws
|
||||
|
||||
|
||||
StatLog.appendSystemLogEntry(STATLOG_KEY_RUMORZ, rumor, StatLog.KeepDays.Forever, StatLog.KeepType.Forever, () => {
|
||||
this.clearAddForm();
|
||||
this.clearAddForm();
|
||||
return this.displayViewScreen(true, cb); // true=cls
|
||||
});
|
||||
} else {
|
||||
|
@ -77,7 +77,7 @@ exports.getModule = class RumorzModule extends MenuModule {
|
|||
const previewView = this.viewControllers.add.getView(MciCodeIds.AddForm.EntryPreview);
|
||||
|
||||
newEntryView.setText('');
|
||||
|
||||
|
||||
// preview is optional
|
||||
if(previewView) {
|
||||
previewView.setText('');
|
||||
|
@ -130,7 +130,7 @@ exports.getModule = class RumorzModule extends MenuModule {
|
|||
function initOrRedrawViewController(artData, callback) {
|
||||
if(_.isUndefined(self.viewControllers.add)) {
|
||||
const vc = self.addViewController(
|
||||
'view',
|
||||
'view',
|
||||
new ViewController( { client : self.client, formId : FormIds.View } )
|
||||
);
|
||||
|
||||
|
@ -143,7 +143,7 @@ exports.getModule = class RumorzModule extends MenuModule {
|
|||
return vc.loadFromMenuConfig(loadOpts, callback);
|
||||
} else {
|
||||
self.viewControllers.view.setFocus(true);
|
||||
self.viewControllers.view.getView(MciCodeIds.ViewForm.AddPrompt).redraw();
|
||||
self.viewControllers.view.getView(MciCodeIds.ViewForm.AddPrompt).redraw();
|
||||
return callback(null);
|
||||
}
|
||||
},
|
||||
|
@ -186,7 +186,7 @@ exports.getModule = class RumorzModule extends MenuModule {
|
|||
[
|
||||
function clearAndDisplayArt(callback) {
|
||||
self.viewControllers.view.setFocus(false);
|
||||
self.client.term.rawWrite(resetScreen());
|
||||
self.client.term.rawWrite(resetScreen());
|
||||
|
||||
theme.displayThemedAsset(
|
||||
self.config.art.add,
|
||||
|
@ -200,7 +200,7 @@ exports.getModule = class RumorzModule extends MenuModule {
|
|||
function initOrRedrawViewController(artData, callback) {
|
||||
if(_.isUndefined(self.viewControllers.add)) {
|
||||
const vc = self.addViewController(
|
||||
'add',
|
||||
'add',
|
||||
new ViewController( { client : self.client, formId : FormIds.Add } )
|
||||
);
|
||||
|
||||
|
@ -220,7 +220,7 @@ exports.getModule = class RumorzModule extends MenuModule {
|
|||
},
|
||||
function initPreviewUpdates(callback) {
|
||||
const previewView = self.viewControllers.add.getView(MciCodeIds.AddForm.EntryPreview);
|
||||
const entryView = self.viewControllers.add.getView(MciCodeIds.AddForm.NewEntry);
|
||||
const entryView = self.viewControllers.add.getView(MciCodeIds.AddForm.NewEntry);
|
||||
if(previewView) {
|
||||
let timerId;
|
||||
entryView.on('key press', () => {
|
||||
|
@ -230,7 +230,7 @@ exports.getModule = class RumorzModule extends MenuModule {
|
|||
if(focused === entryView) {
|
||||
previewView.setText(entryView.getData());
|
||||
focused.setFocus(true);
|
||||
}
|
||||
}
|
||||
}, 500);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -8,7 +8,9 @@ exports.readSAUCE = readSAUCE;
|
|||
|
||||
const SAUCE_SIZE = 128;
|
||||
const SAUCE_ID = new Buffer([0x53, 0x41, 0x55, 0x43, 0x45]); // 'SAUCE'
|
||||
const COMNT_ID = new Buffer([0x43, 0x4f, 0x4d, 0x4e, 0x54]); // 'COMNT'
|
||||
|
||||
// :TODO read comments
|
||||
//const COMNT_ID = new Buffer([0x43, 0x4f, 0x4d, 0x4e, 0x54]); // 'COMNT'
|
||||
|
||||
exports.SAUCE_SIZE = SAUCE_SIZE;
|
||||
// :TODO: SAUCE should be a class
|
||||
|
@ -51,7 +53,7 @@ function readSAUCE(data, cb) {
|
|||
|
||||
if(!SAUCE_ID.equals(vars.id)) {
|
||||
return cb(new Error('No SAUCE record present'));
|
||||
}
|
||||
}
|
||||
|
||||
var ver = iconv.decode(vars.version, 'cp437');
|
||||
|
||||
|
@ -137,7 +139,7 @@ var SAUCE_FONT_TO_ENCODING_HINT = {
|
|||
};
|
||||
|
||||
['437', '720', '737', '775', '819', '850', '852', '855', '857', '858',
|
||||
'860', '861', '862', '863', '864', '865', '866', '869', '872'].forEach(function onPage(page) {
|
||||
'860', '861', '862', '863', '864', '865', '866', '869', '872'].forEach(function onPage(page) {
|
||||
var codec = 'cp' + page;
|
||||
SAUCE_FONT_TO_ENCODING_HINT['IBM EGA43 ' + page] = codec;
|
||||
SAUCE_FONT_TO_ENCODING_HINT['IBM EGA ' + page] = codec;
|
||||
|
|
|
@ -1213,7 +1213,30 @@ function FTNMessageScanTossModule() {
|
|||
|
||||
User.getUserIdAndNameByLookup(lookupName, (err, localToUserId, localUserName) => {
|
||||
if(err) {
|
||||
return callback(Errors.DoesNotExist(`Could not get local user ID for "${message.toUserName}": ${err.message}`));
|
||||
//
|
||||
// Couldn't find a local username. If the toUserName itself is a FTN address
|
||||
// we can only assume the message is to the +op, else we'll have to fail.
|
||||
//
|
||||
const toUserNameAsAddress = Address.fromString(message.toUserName);
|
||||
if(toUserNameAsAddress.isValid()) {
|
||||
|
||||
Log.info(
|
||||
{ toUserName : message.toUserName, fromUserName : message.fromUserName },
|
||||
'No local "to" username for FTN message. Appears to be a FTN address only; assuming addressed to SysOp'
|
||||
);
|
||||
|
||||
User.getUserName(User.RootUserID, (err, sysOpUserName) => {
|
||||
if(err) {
|
||||
return callback(Errors.UnexpectedState('Failed to get SysOp user information'));
|
||||
}
|
||||
|
||||
message.meta.System[Message.SystemMetaNames.LocalToUserID] = User.RootUserID;
|
||||
message.toUserName = sysOpUserName;
|
||||
return callback(null);
|
||||
});
|
||||
} else {
|
||||
return callback(Errors.DoesNotExist(`Could not get local user ID for "${message.toUserName}": ${err.message}`));
|
||||
}
|
||||
}
|
||||
|
||||
// we do this after such that error cases can be preseved above
|
||||
|
|
|
@ -43,7 +43,7 @@ exports.getModule = class SetNewScanDate extends MenuModule {
|
|||
const config = this.menuConfig.config;
|
||||
|
||||
this.target = config.target || 'message';
|
||||
this.scanDateFormat = config.scanDateFormat || 'YYYYMMDD';
|
||||
this.scanDateFormat = config.scanDateFormat || 'YYYYMMDD';
|
||||
|
||||
this.menuMethods = {
|
||||
scanDateSubmit : (formData, extraArgs, cb) => {
|
||||
|
@ -232,7 +232,7 @@ exports.getModule = class SetNewScanDate extends MenuModule {
|
|||
const scanDateView = vc.getView(MciViewIds.main.scanDate);
|
||||
|
||||
// :TODO: MaskTextEditView needs some love: If setText() with input that matches the mask, we should ignore the non-mask chars! Hack in place for now
|
||||
const scanDateFormat = self.scanDateFormat.replace(/[\/\-. ]/g, '');
|
||||
const scanDateFormat = self.scanDateFormat.replace(/[/\-. ]/g, '');
|
||||
scanDateView.setText(today.format(scanDateFormat));
|
||||
|
||||
if('message' === self.target) {
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
/* jslint node: true */
|
||||
'use strict';
|
||||
|
||||
var MenuView = require('./menu_view.js').MenuView;
|
||||
var ansi = require('./ansi_term.js');
|
||||
var strUtil = require('./string_util.js');
|
||||
const MenuView = require('./menu_view.js').MenuView;
|
||||
const ansi = require('./ansi_term.js');
|
||||
const strUtil = require('./string_util.js');
|
||||
|
||||
var util = require('util');
|
||||
var assert = require('assert');
|
||||
var _ = require('lodash');
|
||||
const util = require('util');
|
||||
const assert = require('assert');
|
||||
|
||||
exports.SpinnerMenuView = SpinnerMenuView;
|
||||
|
||||
|
@ -16,7 +15,7 @@ function SpinnerMenuView(options) {
|
|||
options.cursor = options.cursor || 'hide';
|
||||
|
||||
MenuView.call(this, options);
|
||||
|
||||
|
||||
var self = this;
|
||||
|
||||
/*
|
||||
|
@ -29,7 +28,7 @@ function SpinnerMenuView(options) {
|
|||
//assert(!self.positionCacheExpired);
|
||||
|
||||
assert(this.focusedItemIndex >= 0 && this.focusedItemIndex <= self.items.length);
|
||||
|
||||
|
||||
self.drawItem(this.focusedItemIndex);
|
||||
};
|
||||
|
||||
|
@ -66,19 +65,19 @@ SpinnerMenuView.prototype.setFocus = function(focused) {
|
|||
|
||||
SpinnerMenuView.prototype.setFocusItemIndex = function(index) {
|
||||
SpinnerMenuView.super_.prototype.setFocusItemIndex.call(this, index); // sets this.focusedItemIndex
|
||||
|
||||
|
||||
this.updateSelection(); // will redraw
|
||||
};
|
||||
|
||||
SpinnerMenuView.prototype.onKeyPress = function(ch, key) {
|
||||
if(key) {
|
||||
if(this.isKeyMapped('up', key.name)) {
|
||||
if(this.isKeyMapped('up', key.name)) {
|
||||
if(0 === this.focusedItemIndex) {
|
||||
this.focusedItemIndex = this.items.length - 1;
|
||||
} else {
|
||||
this.focusedItemIndex--;
|
||||
}
|
||||
|
||||
|
||||
this.updateSelection();
|
||||
return;
|
||||
} else if(this.isKeyMapped('down', key.name)) {
|
||||
|
@ -87,7 +86,7 @@ SpinnerMenuView.prototype.onKeyPress = function(ch, key) {
|
|||
} else {
|
||||
this.focusedItemIndex++;
|
||||
}
|
||||
|
||||
|
||||
this.updateSelection();
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -10,14 +10,14 @@ const moment = require('moment');
|
|||
/*
|
||||
System Event Log & Stats
|
||||
------------------------
|
||||
|
||||
|
||||
System & user specific:
|
||||
* Events for generating various statistics, logs such as last callers, etc.
|
||||
* Stats such as counters
|
||||
|
||||
User specific stats are simply an alternate interface to user properties, while
|
||||
system wide entries are handled on their own. Both are read accessible non-blocking
|
||||
making them easily available for MCI codes for example.
|
||||
making them easily available for MCI codes for example.
|
||||
*/
|
||||
class StatLog {
|
||||
constructor() {
|
||||
|
@ -66,7 +66,7 @@ class StatLog {
|
|||
TimestampDesc : 'timestamp_desc',
|
||||
Random : 'random',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
setNonPeristentSystemStat(statName, statValue) {
|
||||
this.systemStats[statName] = statValue;
|
||||
|
@ -139,7 +139,7 @@ class StatLog {
|
|||
return cb(new Error(`Value for ${statName} is not a number!`));
|
||||
}
|
||||
|
||||
newValue += incrementBy;
|
||||
newValue += incrementBy;
|
||||
} else {
|
||||
newValue = incrementBy;
|
||||
}
|
||||
|
@ -201,19 +201,19 @@ class StatLog {
|
|||
}
|
||||
}
|
||||
);
|
||||
break;
|
||||
break;
|
||||
|
||||
case 'forever' :
|
||||
default :
|
||||
// nop
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
getSystemLogEntries(logName, order, limit, cb) {
|
||||
let sql =
|
||||
let sql =
|
||||
`SELECT timestamp, log_value
|
||||
FROM system_event_log
|
||||
WHERE log_name = ?`;
|
||||
|
@ -228,7 +228,7 @@ class StatLog {
|
|||
sql += ' ORDER BY timestamp DESC';
|
||||
break;
|
||||
|
||||
case 'random' :
|
||||
case 'random' :
|
||||
sql += ' ORDER BY RANDOM()';
|
||||
}
|
||||
|
||||
|
@ -279,7 +279,7 @@ class StatLog {
|
|||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new StatLog();
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
/* jslint node: true */
|
||||
'use strict';
|
||||
|
||||
var userDb = require('./database.js').dbs.user;
|
||||
|
||||
exports.getSystemLoginHistory = getSystemLoginHistory;
|
||||
|
||||
function getSystemLoginHistory(numRequested, cb) {
|
||||
|
||||
numRequested = Math.max(1, numRequested);
|
||||
|
||||
var loginHistory = [];
|
||||
|
||||
userDb.each(
|
||||
'SELECT user_id, user_name, timestamp ' +
|
||||
'FROM user_login_history ' +
|
||||
'ORDER BY timestamp DESC ' +
|
||||
'LIMIT ' + numRequested + ';',
|
||||
function historyRow(err, histEntry) {
|
||||
loginHistory.push( {
|
||||
userId : histEntry.user_id,
|
||||
userName : histEntry.user_name,
|
||||
timestamp : histEntry.timestamp,
|
||||
} );
|
||||
},
|
||||
function complete(err, recCount) {
|
||||
cb(err, loginHistory);
|
||||
}
|
||||
);
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
/* jslint node: true */
|
||||
'use strict';
|
||||
|
||||
var View = require('./view.js').View;
|
||||
var TextView = require('./text_view.js').TextView;
|
||||
|
||||
var assert = require('assert');
|
||||
var _ = require('lodash');
|
||||
|
||||
function StatusBarView(options) {
|
||||
View.call(this, options);
|
||||
|
||||
var self = this;
|
||||
|
||||
|
||||
}
|
||||
|
||||
require('util').inherits(StatusBarView, View);
|
||||
|
||||
StatusBarView.prototype.redraw = function() {
|
||||
|
||||
StatusBarView.super_.prototype.redraw.call(this);
|
||||
|
||||
};
|
||||
|
||||
StatusBarView.prototype.setPanels = function(panels) {
|
||||
|
||||
/*
|
||||
"panels" : [
|
||||
{
|
||||
"text" : "things and stuff",
|
||||
"width" 20,
|
||||
...
|
||||
},
|
||||
{
|
||||
"width" : 40 // no text, etc... = spacer
|
||||
}
|
||||
]
|
||||
|
||||
|---------------------------------------------|
|
||||
| stuff |
|
||||
*/
|
||||
assert(_.isArray(panels));
|
||||
|
||||
this.panels = [];
|
||||
|
||||
var tvOpts = {
|
||||
cursor : 'hide',
|
||||
position : { row : this.position.row, col : 0 },
|
||||
};
|
||||
|
||||
panels.forEach(function panel(p) {
|
||||
assert(_.isObject(p));
|
||||
assert(_.has(p, 'width'));
|
||||
|
||||
if(p.text) {
|
||||
this.panels.push( new TextView( { }))
|
||||
} else {
|
||||
this.panels.push( { width : p.width } );
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
|
|
@ -5,7 +5,7 @@ const EnigError = require('./enig_error.js').EnigError;
|
|||
|
||||
const {
|
||||
pad,
|
||||
stylizeString,
|
||||
stylizeString,
|
||||
renderStringLength,
|
||||
renderSubstr,
|
||||
formatByteSize, formatByteSizeAbbr,
|
||||
|
@ -172,15 +172,15 @@ function formatNumberHelper(n, precision, type) {
|
|||
case 'b' : return n.toString(2);
|
||||
case 'o' : return n.toString(8);
|
||||
case 'x' : return n.toString(16);
|
||||
case 'e' : return n.toExponential(precision).replace(FormatNumRegExp.ExponentRep, '$&0');
|
||||
case 'f' : return n.toFixed(precision);
|
||||
case 'e' : return n.toExponential(precision).replace(FormatNumRegExp.ExponentRep, '$&0');
|
||||
case 'f' : return n.toFixed(precision);
|
||||
case 'g' :
|
||||
// we don't want useless trailing zeros. parseFloat -> back to string fixes this for us
|
||||
return parseFloat(n.toPrecision(precision || 1)).toString();
|
||||
|
||||
case '%' : return formatNumberHelper(n * 100, precision, 'f') + '%';
|
||||
case '' : return formatNumberHelper(n, precision, 'd');
|
||||
|
||||
|
||||
default :
|
||||
throw new ValueError(`Unknown format code "${type}" for object of type 'float'`);
|
||||
}
|
||||
|
@ -207,7 +207,7 @@ function formatNumber(value, tokens) {
|
|||
|
||||
if('' !== tokens.precision) {
|
||||
throw new ValueError('Precision not allowed in integer format specifier');
|
||||
}
|
||||
}
|
||||
} else if( [ 'e', 'E', 'f', 'F', 'g', 'G', '%' ].indexOf(type) > - 1) {
|
||||
if(tokens['#']) {
|
||||
throw new ValueError('Alternate form (#) not allowed in float format specifier');
|
||||
|
@ -215,7 +215,7 @@ function formatNumber(value, tokens) {
|
|||
}
|
||||
|
||||
const s = formatNumberHelper(Math.abs(value), Number(tokens.precision || 6), type);
|
||||
const sign = value < 0 || 1 / value < 0 ?
|
||||
const sign = value < 0 || 1 / value < 0 ?
|
||||
'-' :
|
||||
'-' === tokens.sign ? '' : tokens.sign;
|
||||
|
||||
|
@ -223,7 +223,7 @@ function formatNumber(value, tokens) {
|
|||
|
||||
if(tokens[',']) {
|
||||
const match = /^(\d*)(.*)$/.exec(s);
|
||||
const separated = match[1].replace(/.(?=(...)+$)/g, '$&,') + match[2];
|
||||
const separated = match[1].replace(/.(?=(...)+$)/g, '$&,') + match[2];
|
||||
|
||||
if('=' !== align) {
|
||||
return pad(sign + separated, width, fill, getPadAlign(align));
|
||||
|
@ -246,7 +246,7 @@ function formatNumber(value, tokens) {
|
|||
|
||||
if(0 === width) {
|
||||
return sign + prefix + s;
|
||||
}
|
||||
}
|
||||
|
||||
if('=' === align) {
|
||||
return sign + prefix + pad(s, width - sign.length - prefix.length, fill, getPadAlign('>'));
|
||||
|
@ -272,9 +272,9 @@ const transformers = {
|
|||
styleL33t : (s) => stylizeString(s, 'l33t'),
|
||||
|
||||
// :TODO:
|
||||
// toMegs(), toKilobytes(), ...
|
||||
// toList(), toCommaList(),
|
||||
|
||||
// toMegs(), toKilobytes(), ...
|
||||
// toList(), toCommaList(),
|
||||
|
||||
sizeWithAbbr : (n) => formatByteSize(n, true, 2),
|
||||
sizeWithoutAbbr : (n) => formatByteSize(n, false, 2),
|
||||
sizeAbbr : (n) => formatByteSizeAbbr(n),
|
||||
|
@ -293,14 +293,14 @@ function transformValue(transformerName, value) {
|
|||
}
|
||||
|
||||
// :TODO: Use explicit set of chars for paths & function/transforms such that } is allowed as fill/etc.
|
||||
const REGEXP_BASIC_FORMAT = /{([^.!:}]+(?:\.[^.!:}]+)*)(?:\!([^:}]+))?(?:\:([^}]+))?}/g;
|
||||
const REGEXP_BASIC_FORMAT = /{([^.!:}]+(?:\.[^.!:}]+)*)(?:!([^:}]+))?(?::([^}]+))?}/g;
|
||||
|
||||
function getValue(obj, path) {
|
||||
const value = _.get(obj, path);
|
||||
if(!_.isUndefined(value)) {
|
||||
return _.isFunction(value) ? value() : value;
|
||||
}
|
||||
|
||||
|
||||
throw new KeyError(quote(path));
|
||||
}
|
||||
|
||||
|
@ -350,7 +350,7 @@ module.exports = function format(fmt, obj) {
|
|||
// remainder
|
||||
if(pos < fmt.length) {
|
||||
out += fmt.slice(pos);
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
return out;
|
||||
};
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
// ENiGMA½
|
||||
const miscUtil = require('./misc_util.js');
|
||||
const ANSIEscapeParser = require('./ansi_escape_parser.js').ANSIEscapeParser;
|
||||
const ANSI = require('./ansi_term.js');
|
||||
|
||||
// deps
|
||||
|
@ -53,12 +52,12 @@ function stylizeString(s, style) {
|
|||
switch(style) {
|
||||
// None/normal
|
||||
case 'normal' :
|
||||
case 'N' :
|
||||
case 'N' :
|
||||
return s;
|
||||
|
||||
// UPPERCASE
|
||||
case 'upper' :
|
||||
case 'U' :
|
||||
case 'upper' :
|
||||
case 'U' :
|
||||
return s.toUpperCase();
|
||||
|
||||
// lowercase
|
||||
|
@ -107,8 +106,8 @@ function stylizeString(s, style) {
|
|||
return stylized;
|
||||
|
||||
// Small i's: DEMENTiA
|
||||
case 'small i' :
|
||||
case 'i' :
|
||||
case 'small i' :
|
||||
case 'i' :
|
||||
return s.toUpperCase().replace(/I/g, 'i');
|
||||
|
||||
// mIxeD CaSE (random upper/lower)
|
||||
|
@ -128,7 +127,7 @@ function stylizeString(s, style) {
|
|||
case '3' :
|
||||
for(i = 0; i < len; ++i) {
|
||||
c = SIMPLE_ELITE_MAP[s[i].toLowerCase()];
|
||||
stylized += c || s[i];
|
||||
stylized += c || s[i];
|
||||
}
|
||||
return stylized;
|
||||
}
|
||||
|
@ -147,11 +146,11 @@ function pad(s, len, padChar, dir, stringSGR, padSGR, useRenderLen) {
|
|||
useRenderLen = miscUtil.valueWithDefault(useRenderLen, true);
|
||||
|
||||
const renderLen = useRenderLen ? renderStringLength(s) : s.length;
|
||||
const padlen = len >= renderLen ? len - renderLen : 0;
|
||||
const padlen = len >= renderLen ? len - renderLen : 0;
|
||||
|
||||
switch(dir) {
|
||||
case 'L' :
|
||||
case 'left' :
|
||||
case 'left' :
|
||||
s = padSGR + new Array(padlen).join(padChar) + stringSGR + s;
|
||||
break;
|
||||
|
||||
|
@ -162,10 +161,10 @@ function pad(s, len, padChar, dir, stringSGR, padSGR, useRenderLen) {
|
|||
const right = Math.ceil(padlen / 2);
|
||||
const left = padlen - right;
|
||||
s = padSGR + new Array(left + 1).join(padChar) + stringSGR + s + padSGR + new Array(right + 1).join(padChar);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'R' :
|
||||
case 'R' :
|
||||
case 'right' :
|
||||
s = stringSGR + s + padSGR + new Array(padlen).join(padChar);
|
||||
break;
|
||||
|
@ -184,7 +183,7 @@ function replaceAt(s, n, t) {
|
|||
return s.substring(0, n) + t + s.substring(n + 1);
|
||||
}
|
||||
|
||||
const RE_NON_PRINTABLE =
|
||||
const RE_NON_PRINTABLE =
|
||||
/[\0-\x1F\x7F-\x9F\xAD\u0378\u0379\u037F-\u0383\u038B\u038D\u03A2\u0528-\u0530\u0557\u0558\u0560\u0588\u058B-\u058E\u0590\u05C8-\u05CF\u05EB-\u05EF\u05F5-\u0605\u061C\u061D\u06DD\u070E\u070F\u074B\u074C\u07B2-\u07BF\u07FB-\u07FF\u082E\u082F\u083F\u085C\u085D\u085F-\u089F\u08A1\u08AD-\u08E3\u08FF\u0978\u0980\u0984\u098D\u098E\u0991\u0992\u09A9\u09B1\u09B3-\u09B5\u09BA\u09BB\u09C5\u09C6\u09C9\u09CA\u09CF-\u09D6\u09D8-\u09DB\u09DE\u09E4\u09E5\u09FC-\u0A00\u0A04\u0A0B-\u0A0E\u0A11\u0A12\u0A29\u0A31\u0A34\u0A37\u0A3A\u0A3B\u0A3D\u0A43-\u0A46\u0A49\u0A4A\u0A4E-\u0A50\u0A52-\u0A58\u0A5D\u0A5F-\u0A65\u0A76-\u0A80\u0A84\u0A8E\u0A92\u0AA9\u0AB1\u0AB4\u0ABA\u0ABB\u0AC6\u0ACA\u0ACE\u0ACF\u0AD1-\u0ADF\u0AE4\u0AE5\u0AF2-\u0B00\u0B04\u0B0D\u0B0E\u0B11\u0B12\u0B29\u0B31\u0B34\u0B3A\u0B3B\u0B45\u0B46\u0B49\u0B4A\u0B4E-\u0B55\u0B58-\u0B5B\u0B5E\u0B64\u0B65\u0B78-\u0B81\u0B84\u0B8B-\u0B8D\u0B91\u0B96-\u0B98\u0B9B\u0B9D\u0BA0-\u0BA2\u0BA5-\u0BA7\u0BAB-\u0BAD\u0BBA-\u0BBD\u0BC3-\u0BC5\u0BC9\u0BCE\u0BCF\u0BD1-\u0BD6\u0BD8-\u0BE5\u0BFB-\u0C00\u0C04\u0C0D\u0C11\u0C29\u0C34\u0C3A-\u0C3C\u0C45\u0C49\u0C4E-\u0C54\u0C57\u0C5A-\u0C5F\u0C64\u0C65\u0C70-\u0C77\u0C80\u0C81\u0C84\u0C8D\u0C91\u0CA9\u0CB4\u0CBA\u0CBB\u0CC5\u0CC9\u0CCE-\u0CD4\u0CD7-\u0CDD\u0CDF\u0CE4\u0CE5\u0CF0\u0CF3-\u0D01\u0D04\u0D0D\u0D11\u0D3B\u0D3C\u0D45\u0D49\u0D4F-\u0D56\u0D58-\u0D5F\u0D64\u0D65\u0D76-\u0D78\u0D80\u0D81\u0D84\u0D97-\u0D99\u0DB2\u0DBC\u0DBE\u0DBF\u0DC7-\u0DC9\u0DCB-\u0DCE\u0DD5\u0DD7\u0DE0-\u0DF1\u0DF5-\u0E00\u0E3B-\u0E3E\u0E5C-\u0E80\u0E83\u0E85\u0E86\u0E89\u0E8B\u0E8C\u0E8E-\u0E93\u0E98\u0EA0\u0EA4\u0EA6\u0EA8\u0EA9\u0EAC\u0EBA\u0EBE\u0EBF\u0EC5\u0EC7\u0ECE\u0ECF\u0EDA\u0EDB\u0EE0-\u0EFF\u0F48\u0F6D-\u0F70\u0F98\u0FBD\u0FCD\u0FDB-\u0FFF\u10C6\u10C8-\u10CC\u10CE\u10CF\u1249\u124E\u124F\u1257\u1259\u125E\u125F\u1289\u128E\u128F\u12B1\u12B6\u12B7\u12BF\u12C1\u12C6\u12C7\u12D7\u1311\u1316\u1317\u135B\u135C\u137D-\u137F\u139A-\u139F\u13F5-\u13FF\u169D-\u169F\u16F1-\u16FF\u170D\u1715-\u171F\u1737-\u173F\u1754-\u175F\u176D\u1771\u1774-\u177F\u17DE\u17DF\u17EA-\u17EF\u17FA-\u17FF\u180F\u181A-\u181F\u1878-\u187F\u18AB-\u18AF\u18F6-\u18FF\u191D-\u191F\u192C-\u192F\u193C-\u193F\u1941-\u1943\u196E\u196F\u1975-\u197F\u19AC-\u19AF\u19CA-\u19CF\u19DB-\u19DD\u1A1C\u1A1D\u1A5F\u1A7D\u1A7E\u1A8A-\u1A8F\u1A9A-\u1A9F\u1AAE-\u1AFF\u1B4C-\u1B4F\u1B7D-\u1B7F\u1BF4-\u1BFB\u1C38-\u1C3A\u1C4A-\u1C4C\u1C80-\u1CBF\u1CC8-\u1CCF\u1CF7-\u1CFF\u1DE7-\u1DFB\u1F16\u1F17\u1F1E\u1F1F\u1F46\u1F47\u1F4E\u1F4F\u1F58\u1F5A\u1F5C\u1F5E\u1F7E\u1F7F\u1FB5\u1FC5\u1FD4\u1FD5\u1FDC\u1FF0\u1FF1\u1FF5\u1FFF\u200B-\u200F\u202A-\u202E\u2060-\u206F\u2072\u2073\u208F\u209D-\u209F\u20BB-\u20CF\u20F1-\u20FF\u218A-\u218F\u23F4-\u23FF\u2427-\u243F\u244B-\u245F\u2700\u2B4D-\u2B4F\u2B5A-\u2BFF\u2C2F\u2C5F\u2CF4-\u2CF8\u2D26\u2D28-\u2D2C\u2D2E\u2D2F\u2D68-\u2D6E\u2D71-\u2D7E\u2D97-\u2D9F\u2DA7\u2DAF\u2DB7\u2DBF\u2DC7\u2DCF\u2DD7\u2DDF\u2E3C-\u2E7F\u2E9A\u2EF4-\u2EFF\u2FD6-\u2FEF\u2FFC-\u2FFF\u3040\u3097\u3098\u3100-\u3104\u312E-\u3130\u318F\u31BB-\u31BF\u31E4-\u31EF\u321F\u32FF\u4DB6-\u4DBF\u9FCD-\u9FFF\uA48D-\uA48F\uA4C7-\uA4CF\uA62C-\uA63F\uA698-\uA69E\uA6F8-\uA6FF\uA78F\uA794-\uA79F\uA7AB-\uA7F7\uA82C-\uA82F\uA83A-\uA83F\uA878-\uA87F\uA8C5-\uA8CD\uA8DA-\uA8DF\uA8FC-\uA8FF\uA954-\uA95E\uA97D-\uA97F\uA9CE\uA9DA-\uA9DD\uA9E0-\uA9FF\uAA37-\uAA3F\uAA4E\uAA4F\uAA5A\uAA5B\uAA7C-\uAA7F\uAAC3-\uAADA\uAAF7-\uAB00\uAB07\uAB08\uAB0F\uAB10\uAB17-\uAB1F\uAB27\uAB2F-\uABBF\uABEE\uABEF\uABFA-\uABFF\uD7A4-\uD7AF\uD7C7-\uD7CA\uD7FC-\uF8FF\uFA6E\uFA6F\uFADA-\uFAFF\uFB07-\uFB12\uFB18-\uFB1C\uFB37\uFB3D\uFB3F\uFB42\uFB45\uFBC2-\uFBD2\uFD40-\uFD4F\uFD90\uFD91\uFDC8-\uFDEF\uFDFE\uFDFF\uFE1A-\uFE1F\uFE27-\uFE2F\uFE53\uFE67\uFE6C-\uFE6F\uFE75\uFEFD-\uFF00\uFFBF-\uFFC1\uFFC8\uFFC9\uFFD0\uFFD1\uFFD8\uFFD9\uFFDD-\uFFDF\uFFE7\uFFEF-\uFFFB\uFFFE\uFFFF]/; // eslint-disable-line no-control-regex
|
||||
|
||||
function isPrintable(s) {
|
||||
|
@ -198,11 +197,6 @@ function isPrintable(s) {
|
|||
return !RE_NON_PRINTABLE.test(s);
|
||||
}
|
||||
|
||||
function stringLength(s) {
|
||||
// :TODO: See https://mathiasbynens.be/notes/javascript-unicode
|
||||
return s.length;
|
||||
}
|
||||
|
||||
function stripAllLineFeeds(s) {
|
||||
return s.replace(/\r?\n|[\r\u2028\u2029]/g, '');
|
||||
}
|
||||
|
@ -256,7 +250,7 @@ function renderSubstr(str, start, length) {
|
|||
match = re.exec(str);
|
||||
|
||||
if(match) {
|
||||
if(match.index > pos) {
|
||||
if(match.index > pos) {
|
||||
s = str.slice(pos + start, Math.min(match.index, pos + (length - renderLen)));
|
||||
start = 0; // start offset applies only once
|
||||
out += s;
|
||||
|
@ -269,7 +263,7 @@ function renderSubstr(str, start, length) {
|
|||
|
||||
// remainder
|
||||
if(pos + start < str.length && renderLen < length) {
|
||||
out += str.slice(pos + start, (pos + start + (length - renderLen)));
|
||||
out += str.slice(pos + start, (pos + start + (length - renderLen)));
|
||||
//out += str.slice(pos + start, Math.max(1, pos + (length - renderLen - 1)));
|
||||
}
|
||||
|
||||
|
@ -277,7 +271,7 @@ function renderSubstr(str, start, length) {
|
|||
}
|
||||
|
||||
//
|
||||
// Method to return the "rendered" length taking into account Pipe and ANSI color codes.
|
||||
// Method to return the "rendered" length taking into account Pipe and ANSI color codes.
|
||||
//
|
||||
// We additionally account for ANSI *forward* movement ESC sequences
|
||||
// in the form of ESC[<N>C where <N> is the "go forward" character count.
|
||||
|
@ -291,40 +285,40 @@ function renderStringLength(s) {
|
|||
|
||||
const re = ANSI_OR_PIPE_REGEXP;
|
||||
re.lastIndex = 0; // we recycle the rege; reset
|
||||
|
||||
|
||||
//
|
||||
// Loop counting only literal (non-control) sequences
|
||||
// paying special attention to ESC[<N>C which means forward <N>
|
||||
//
|
||||
//
|
||||
do {
|
||||
pos = re.lastIndex;
|
||||
m = re.exec(s);
|
||||
|
||||
|
||||
if(m) {
|
||||
if(m.index > pos) {
|
||||
len += s.slice(pos, m.index).length;
|
||||
}
|
||||
|
||||
|
||||
if('C' === m[3]) { // ESC[<N>C is foward/right
|
||||
len += parseInt(m[2], 10) || 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while(0 !== re.lastIndex);
|
||||
|
||||
|
||||
if(pos < s.length) {
|
||||
len += s.slice(pos).length;
|
||||
}
|
||||
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
const BYTE_SIZE_ABBRS = [ 'B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' ]; // :)
|
||||
const BYTE_SIZE_ABBRS = [ 'B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' ]; // :)
|
||||
|
||||
function formatByteSizeAbbr(byteSize) {
|
||||
if(0 === byteSize) {
|
||||
return BYTE_SIZE_ABBRS[0]; // B
|
||||
}
|
||||
|
||||
|
||||
return BYTE_SIZE_ABBRS[Math.floor(Math.log(byteSize) / Math.log(1024))];
|
||||
}
|
||||
|
||||
|
@ -332,7 +326,7 @@ function formatByteSize(byteSize, withAbbr = false, decimals = 2) {
|
|||
const i = 0 === byteSize ? byteSize : Math.floor(Math.log(byteSize) / Math.log(1024));
|
||||
let result = parseFloat((byteSize / Math.pow(1024, i)).toFixed(decimals));
|
||||
if(withAbbr) {
|
||||
result += ` ${BYTE_SIZE_ABBRS[i]}`;
|
||||
result += ` ${BYTE_SIZE_ABBRS[i]}`;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -351,7 +345,7 @@ function formatCount(count, withAbbr = false, decimals = 2) {
|
|||
const i = 0 === count ? count : Math.floor(Math.log(count) / Math.log(1000));
|
||||
let result = parseFloat((count / Math.pow(1000, i)).toFixed(decimals));
|
||||
if(withAbbr) {
|
||||
result += `${COUNT_ABBRS[i]}`;
|
||||
result += `${COUNT_ABBRS[i]}`;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -359,7 +353,7 @@ function formatCount(count, withAbbr = false, decimals = 2) {
|
|||
|
||||
// :TODO: See notes in word_wrap.js about need to consolidate the various ANSI related RegExp's
|
||||
//const REGEXP_ANSI_CONTROL_CODES = /(\x1b\x5b)([\?=;0-9]*?)([0-9A-ORZcf-npsu=><])/g;
|
||||
const REGEXP_ANSI_CONTROL_CODES = /(?:\x1b\x5b)([\?=;0-9]*?)([A-ORZcf-npsu=><])/g; // eslint-disable-line no-control-regex
|
||||
const REGEXP_ANSI_CONTROL_CODES = /(?:\x1b\x5b)([?=;0-9]*?)([A-ORZcf-npsu=><])/g; // eslint-disable-line no-control-regex
|
||||
const ANSI_OPCODES_ALLOWED_CLEAN = [
|
||||
//'A', 'B', // up, down
|
||||
//'C', 'D', // right, left
|
||||
|
@ -370,17 +364,17 @@ function cleanControlCodes(input, options) {
|
|||
let m;
|
||||
let pos;
|
||||
let cleaned = '';
|
||||
|
||||
|
||||
options = options || {};
|
||||
|
||||
|
||||
//
|
||||
// Loop through |input| adding only allowed ESC
|
||||
// sequences and literals to |cleaned|
|
||||
//
|
||||
//
|
||||
do {
|
||||
pos = REGEXP_ANSI_CONTROL_CODES.lastIndex;
|
||||
m = REGEXP_ANSI_CONTROL_CODES.exec(input);
|
||||
|
||||
|
||||
if(m) {
|
||||
if(m.index > pos) {
|
||||
cleaned += input.slice(pos, m.index);
|
||||
|
@ -394,205 +388,17 @@ function cleanControlCodes(input, options) {
|
|||
cleaned += m[0];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} while(0 !== REGEXP_ANSI_CONTROL_CODES.lastIndex);
|
||||
|
||||
|
||||
// remainder
|
||||
if(pos < input.length) {
|
||||
cleaned += input.slice(pos);
|
||||
}
|
||||
|
||||
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
function prepAnsi(input, options, cb) {
|
||||
if(!input) {
|
||||
return cb(null, '');
|
||||
}
|
||||
|
||||
options.termWidth = options.termWidth || 80;
|
||||
options.termHeight = options.termHeight || 25;
|
||||
options.cols = options.cols || options.termWidth || 80;
|
||||
options.rows = options.rows || options.termHeight || 'auto';
|
||||
options.startCol = options.startCol || 1;
|
||||
options.exportMode = options.exportMode || false;
|
||||
|
||||
const canvas = Array.from( { length : 'auto' === options.rows ? 25 : options.rows }, () => Array.from( { length : options.cols}, () => new Object() ) );
|
||||
const parser = new ANSIEscapeParser( { termHeight : options.termHeight, termWidth : options.termWidth } );
|
||||
|
||||
const state = {
|
||||
row : 0,
|
||||
col : 0,
|
||||
};
|
||||
|
||||
let lastRow = 0;
|
||||
|
||||
function ensureRow(row) {
|
||||
if(Array.isArray(canvas[row])) {
|
||||
return;
|
||||
}
|
||||
|
||||
canvas[row] = Array.from( { length : options.cols}, () => new Object() );
|
||||
}
|
||||
|
||||
parser.on('position update', (row, col) => {
|
||||
state.row = row - 1;
|
||||
state.col = col - 1;
|
||||
|
||||
lastRow = Math.max(state.row, lastRow);
|
||||
});
|
||||
|
||||
parser.on('literal', literal => {
|
||||
//
|
||||
// CR/LF are handled for 'position update'; we don't need the chars themselves
|
||||
//
|
||||
literal = literal.replace(/\r?\n|[\r\u2028\u2029]/g, '');
|
||||
|
||||
for(let c of literal) {
|
||||
if(state.col < options.cols && ('auto' === options.rows || state.row < options.rows)) {
|
||||
ensureRow(state.row);
|
||||
|
||||
canvas[state.row][state.col].char = c;
|
||||
|
||||
if(state.sgr) {
|
||||
canvas[state.row][state.col].sgr = state.sgr;
|
||||
state.sgr = null;
|
||||
}
|
||||
}
|
||||
|
||||
state.col += 1;
|
||||
}
|
||||
});
|
||||
|
||||
parser.on('control', (match, opCode) => {
|
||||
//
|
||||
// Movement is handled via 'position update', so we really only care about
|
||||
// display opCodes
|
||||
//
|
||||
switch(opCode) {
|
||||
case 'm' :
|
||||
state.sgr = (state.sgr || '') + match;
|
||||
break;
|
||||
|
||||
default :
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
function getLastPopulatedColumn(row) {
|
||||
let col = row.length;
|
||||
while(--col > 0) {
|
||||
if(row[col].char || row[col].sgr) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return col;
|
||||
}
|
||||
|
||||
parser.on('complete', () => {
|
||||
let output = '';
|
||||
let lastSgr = '';
|
||||
let line;
|
||||
|
||||
canvas.slice(0, lastRow + 1).forEach(row => {
|
||||
const lastCol = getLastPopulatedColumn(row) + 1;
|
||||
|
||||
let i;
|
||||
line = '';
|
||||
for(i = 0; i < lastCol; ++i) {
|
||||
const col = row[i];
|
||||
if(col.sgr) {
|
||||
lastSgr = col.sgr;
|
||||
}
|
||||
line += `${col.sgr || ''}${col.char || ' '}`;
|
||||
}
|
||||
|
||||
output += line;
|
||||
|
||||
if(i < row.length) {
|
||||
output += `${ANSI.blackBG()}${row.slice(i).map( () => ' ').join('')}${lastSgr}`;
|
||||
}
|
||||
|
||||
//if(options.startCol + options.cols < options.termWidth || options.forceLineTerm) {
|
||||
if(options.startCol + i < options.termWidth || options.forceLineTerm) {
|
||||
output += '\r\n';
|
||||
}
|
||||
});
|
||||
|
||||
if(options.exportMode) {
|
||||
//
|
||||
// If we're in export mode, we do some additional hackery:
|
||||
//
|
||||
// * Hard wrap ALL lines at <= 79 *characters* (not visible columns)
|
||||
// if a line must wrap early, we'll place a ESC[A ESC[<N>C where <N>
|
||||
// represents chars to get back to the position we were previously at
|
||||
//
|
||||
// * Replace contig spaces with ESC[<N>C as well to save... space.
|
||||
//
|
||||
// :TODO: this would be better to do as part of the processing above, but this will do for now
|
||||
const MAX_CHARS = 79 - 8; // 79 max, - 8 for max ESC seq's we may prefix a line with
|
||||
let exportOutput = '';
|
||||
|
||||
let m;
|
||||
let afterSeq;
|
||||
let wantMore;
|
||||
let renderStart;
|
||||
|
||||
splitTextAtTerms(output).forEach(fullLine => {
|
||||
renderStart = 0;
|
||||
|
||||
while(fullLine.length > 0) {
|
||||
let splitAt;
|
||||
const ANSI_REGEXP = ANSI.getFullMatchRegExp();
|
||||
wantMore = true;
|
||||
|
||||
while((m = ANSI_REGEXP.exec(fullLine))) {
|
||||
afterSeq = m.index + m[0].length;
|
||||
|
||||
if(afterSeq < MAX_CHARS) {
|
||||
// after current seq
|
||||
splitAt = afterSeq;
|
||||
} else {
|
||||
if(m.index < MAX_CHARS) {
|
||||
// before last found seq
|
||||
splitAt = m.index;
|
||||
wantMore = false; // can't eat up any more
|
||||
}
|
||||
|
||||
break; // seq's beyond this point are >= MAX_CHARS
|
||||
}
|
||||
}
|
||||
|
||||
if(splitAt) {
|
||||
if(wantMore) {
|
||||
splitAt = Math.min(fullLine.length, MAX_CHARS - 1);
|
||||
}
|
||||
} else {
|
||||
splitAt = Math.min(fullLine.length, MAX_CHARS - 1);
|
||||
}
|
||||
|
||||
const part = fullLine.slice(0, splitAt);
|
||||
fullLine = fullLine.slice(splitAt);
|
||||
renderStart += renderStringLength(part);
|
||||
exportOutput += `${part}\r\n`;
|
||||
|
||||
if(fullLine.length > 0) { // more to go for this line?
|
||||
exportOutput += `${ANSI.up()}${ANSI.right(renderStart)}`;
|
||||
} else {
|
||||
exportOutput += ANSI.up();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return cb(null, exportOutput);
|
||||
}
|
||||
|
||||
return cb(null, output);
|
||||
});
|
||||
|
||||
parser.parse(input);
|
||||
}
|
||||
|
||||
function isAnsiLine(line) {
|
||||
return isAnsi(line);// || renderStringLength(line) < line.length;
|
||||
}
|
||||
|
@ -622,22 +428,23 @@ function isFormattedLine(line) {
|
|||
return false;
|
||||
}
|
||||
|
||||
// :TODO: rename to containsAnsi()
|
||||
function isAnsi(input) {
|
||||
if(!input || 0 === input.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// * ANSI found - limited, just colors
|
||||
// * Full ANSI art
|
||||
// *
|
||||
//
|
||||
// *
|
||||
//
|
||||
// FULL ANSI art:
|
||||
// * SAUCE present & reports as ANSI art
|
||||
// * ANSI clear screen within first 2-3 codes
|
||||
// * ANSI movement codes (goto, right, left, etc.)
|
||||
//
|
||||
// *
|
||||
//
|
||||
// *
|
||||
/*
|
||||
readSAUCE(input, (err, sauce) => {
|
||||
if(!err && ('ANSi' === sauce.fileType || 'ANSiMation' === sauce.fileType)) {
|
||||
|
@ -647,8 +454,8 @@ function isAnsi(input) {
|
|||
*/
|
||||
|
||||
// :TODO: if a similar method is kept, use exec() until threshold
|
||||
const ANSI_DET_REGEXP = /(?:\x1b\x5b)[\?=;0-9]*?[ABCDEFGHJKLMSTfhlmnprsu]/g; // eslint-disable-line no-control-regex
|
||||
const m = input.match(ANSI_DET_REGEXP) || [];
|
||||
const ANSI_DET_REGEXP = /(?:\x1b\x5b)[?=;0-9]*?[ABCDEFGHJKLMSTfhlmnprsu]/g; // eslint-disable-line no-control-regex
|
||||
const m = input.match(ANSI_DET_REGEXP) || [];
|
||||
return m.length >= 4; // :TODO: do this reasonably, e.g. a percent or soemthing
|
||||
}
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ function login(callingMenu, formData, extraArgs, cb) {
|
|||
return callingMenu.prevMenu(cb);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// success!
|
||||
return callingMenu.nextMenu(cb);
|
||||
});
|
||||
|
@ -72,7 +72,7 @@ function prevMenu(callingMenu, formData, extraArgs, cb) {
|
|||
|
||||
callingMenu.prevMenu( err => {
|
||||
if(err) {
|
||||
callingMenu.client.log.error( { error : err.message }, 'Error attempting to fallback!');
|
||||
callingMenu.client.log.error( { error : err.message }, 'Error attempting to fallback!');
|
||||
}
|
||||
return cb(err);
|
||||
});
|
||||
|
@ -119,7 +119,7 @@ function nextConf(callingMenu, formData, extraArgs, cb) {
|
|||
if(err) {
|
||||
return cb(err); // logged within changeMessageConference()
|
||||
}
|
||||
|
||||
|
||||
return reloadMenu(callingMenu, cb);
|
||||
});
|
||||
}
|
||||
|
@ -132,7 +132,7 @@ function prevArea(callingMenu, formData, extraArgs, cb) {
|
|||
if(err) {
|
||||
return cb(err); // logged within changeMessageArea()
|
||||
}
|
||||
|
||||
|
||||
return reloadMenu(callingMenu, cb);
|
||||
});
|
||||
}
|
||||
|
@ -155,10 +155,10 @@ function nextArea(callingMenu, formData, extraArgs, cb) {
|
|||
}
|
||||
|
||||
function sendForgotPasswordEmail(callingMenu, formData, extraArgs, cb) {
|
||||
const username = formData.value.username || callingMenu.client.user.username;
|
||||
const username = formData.value.username || callingMenu.client.user.username;
|
||||
|
||||
const WebPasswordReset = require('./web_password_reset.js').WebPasswordReset;
|
||||
|
||||
|
||||
WebPasswordReset.sendForgotPasswordEmail(username, err => {
|
||||
if(err) {
|
||||
callingMenu.client.log.warn( { err : err.message }, 'Failed sending forgot password email');
|
||||
|
@ -166,8 +166,8 @@ function sendForgotPasswordEmail(callingMenu, formData, extraArgs, cb) {
|
|||
|
||||
if(extraArgs.next) {
|
||||
return callingMenu.gotoMenu(extraArgs.next, cb);
|
||||
}
|
||||
|
||||
return logoff(callingMenu, formData, extraArgs, cb);
|
||||
}
|
||||
|
||||
return logoff(callingMenu, formData, extraArgs, cb);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -99,7 +99,7 @@ function validateGeneralMailAddressedTo(data, cb) {
|
|||
function validateEmailAvail(data, cb) {
|
||||
//
|
||||
// This particular method allows empty data - e.g. no email entered
|
||||
//
|
||||
//
|
||||
if(!data || 0 === data.length) {
|
||||
return cb(null);
|
||||
}
|
||||
|
@ -110,7 +110,7 @@ function validateEmailAvail(data, cb) {
|
|||
//
|
||||
// See http://stackoverflow.com/questions/7786058/find-the-regex-used-by-html5-forms-for-validation
|
||||
//
|
||||
const emailRegExp = /[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9-]+(.[a-z0-9-]+)*/;
|
||||
const emailRegExp = /[a-z0-9!#$%&'*+/=?^_`{|}~.-]+@[a-z0-9-]+(.[a-z0-9-]+)*/;
|
||||
if(!emailRegExp.test(data)) {
|
||||
return cb(new Error('Invalid email address'));
|
||||
}
|
||||
|
@ -121,8 +121,8 @@ function validateEmailAvail(data, cb) {
|
|||
} else if(uids.length > 0) {
|
||||
return cb(new Error('Email address not unique'));
|
||||
}
|
||||
|
||||
return cb(null);
|
||||
|
||||
return cb(null);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ class TelnetClientConnection extends EventEmitter {
|
|||
this.client = client;
|
||||
}
|
||||
|
||||
|
||||
|
||||
restorePipe() {
|
||||
if(!this.pipeRestored) {
|
||||
this.pipeRestored = true;
|
||||
|
@ -68,14 +68,14 @@ class TelnetClientConnection extends EventEmitter {
|
|||
this.bridgeConnection.on('data', data => {
|
||||
this.client.term.rawWrite(data);
|
||||
|
||||
//
|
||||
//
|
||||
// Wait for a terminal type request, and send it eactly once.
|
||||
// This is enough (in additional to other negotiations handled in telnet.js)
|
||||
// to get us in on most systems
|
||||
//
|
||||
if(!this.termSent && data.indexOf(IAC_DO_TERM_TYPE) > -1) {
|
||||
this.termSent = true;
|
||||
this.bridgeConnection.write(this.getTermTypeNegotiationBuffer());
|
||||
this.bridgeConnection.write(this.getTermTypeNegotiationBuffer());
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -102,9 +102,9 @@ class TelnetClientConnection extends EventEmitter {
|
|||
// actual/current terminal type.
|
||||
//
|
||||
let bufs = buffers();
|
||||
|
||||
|
||||
bufs.push(new Buffer(
|
||||
[
|
||||
[
|
||||
255, // IAC
|
||||
250, // SB
|
||||
24, // TERMINAL-TYPE
|
||||
|
@ -113,9 +113,9 @@ class TelnetClientConnection extends EventEmitter {
|
|||
));
|
||||
|
||||
bufs.push(
|
||||
new Buffer(this.client.term.termType), // e.g. "ansi"
|
||||
new Buffer(this.client.term.termType), // e.g. "ansi"
|
||||
new Buffer( [ 255, 240 ] ) // IAC, SE
|
||||
);
|
||||
);
|
||||
|
||||
return bufs.toBuffer();
|
||||
}
|
||||
|
@ -128,9 +128,9 @@ exports.getModule = class TelnetBridgeModule extends MenuModule {
|
|||
|
||||
this.config = options.menuConfig.config;
|
||||
// defaults
|
||||
this.config.port = this.config.port || 23;
|
||||
this.config.port = this.config.port || 23;
|
||||
}
|
||||
|
||||
|
||||
initSequence() {
|
||||
let clientTerminated;
|
||||
const self = this;
|
||||
|
@ -158,7 +158,7 @@ exports.getModule = class TelnetBridgeModule extends MenuModule {
|
|||
self.client.term.write(` Connecting to ${connectOpts.host}, please wait...\n`);
|
||||
|
||||
const telnetConnection = new TelnetClientConnection(self.client);
|
||||
|
||||
|
||||
telnetConnection.on('connected', () => {
|
||||
self.client.log.info(connectOpts, 'Telnet bridge connection established');
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ function TextView(options) {
|
|||
this.justify = options.justify || 'right';
|
||||
this.resizable = miscUtil.valueWithDefault(options.resizable, true);
|
||||
this.horizScroll = miscUtil.valueWithDefault(options.horizScroll, true);
|
||||
|
||||
|
||||
if(_.isString(options.textOverflow)) {
|
||||
this.textOverflow = options.textOverflow;
|
||||
}
|
||||
|
@ -44,19 +44,19 @@ function TextView(options) {
|
|||
this.textMaskChar = options.textMaskChar;
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
this.drawText = function(s) {
|
||||
|
||||
//
|
||||
//
|
||||
// |<- this.maxLength
|
||||
// ABCDEFGHIJK
|
||||
// |ABCDEFG| ^_ this.text.length
|
||||
// ^-- this.dimens.width
|
||||
//
|
||||
let textToDraw = _.isString(this.textMaskChar) ?
|
||||
new Array(s.length + 1).join(this.textMaskChar) :
|
||||
let textToDraw = _.isString(this.textMaskChar) ?
|
||||
new Array(s.length + 1).join(this.textMaskChar) :
|
||||
stylizeString(s, this.hasFocus ? this.focusTextStyle : this.textStyle);
|
||||
|
||||
|
||||
if(textToDraw.length > this.dimens.width) {
|
||||
if(this.hasFocus) {
|
||||
if(this.horizScroll) {
|
||||
|
@ -64,7 +64,7 @@ function TextView(options) {
|
|||
}
|
||||
} else {
|
||||
if(textToDraw.length > this.dimens.width) {
|
||||
if(this.textOverflow &&
|
||||
if(this.textOverflow &&
|
||||
this.dimens.width > this.textOverflow.length &&
|
||||
textToDraw.length - this.textOverflow.length >= this.textOverflow.length)
|
||||
{
|
||||
|
@ -72,7 +72,7 @@ function TextView(options) {
|
|||
} else {
|
||||
textToDraw = textToDraw.substr(0, this.dimens.width);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,7 +89,7 @@ function TextView(options) {
|
|||
|
||||
this.drawText = function(s) {
|
||||
|
||||
//
|
||||
//
|
||||
// |<- this.maxLength
|
||||
// ABCDEFGHIJK
|
||||
// |ABCDEFG| ^_ this.text.length
|
||||
|
@ -97,26 +97,26 @@ function TextView(options) {
|
|||
//
|
||||
let renderLength = renderStringLength(s); // initial; may be adjusted below:
|
||||
|
||||
let textToDraw = _.isString(this.textMaskChar) ?
|
||||
new Array(renderLength + 1).join(this.textMaskChar) :
|
||||
let textToDraw = _.isString(this.textMaskChar) ?
|
||||
new Array(renderLength + 1).join(this.textMaskChar) :
|
||||
stylizeString(s, this.hasFocus ? this.focusTextStyle : this.textStyle);
|
||||
|
||||
|
||||
renderLength = renderStringLength(textToDraw);
|
||||
|
||||
|
||||
if(renderLength >= this.dimens.width) {
|
||||
if(this.hasFocus) {
|
||||
if(this.horizScroll) {
|
||||
textToDraw = renderSubstr(textToDraw, renderLength - this.dimens.width, renderLength);
|
||||
}
|
||||
} else {
|
||||
if(this.textOverflow &&
|
||||
if(this.textOverflow &&
|
||||
this.dimens.width > this.textOverflow.length &&
|
||||
renderLength - this.textOverflow.length >= this.textOverflow.length)
|
||||
{
|
||||
textToDraw = renderSubstr(textToDraw, 0, this.dimens.width - this.textOverflow.length) + this.textOverflow;
|
||||
textToDraw = renderSubstr(textToDraw, 0, this.dimens.width - this.textOverflow.length) + this.textOverflow;
|
||||
} else {
|
||||
textToDraw = renderSubstr(textToDraw, 0, this.dimens.width);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -128,7 +128,7 @@ function TextView(options) {
|
|||
this.justify,
|
||||
this.hasFocus ? this.getFocusSGR() : this.getSGR(),
|
||||
this.getStyleSGR(1) || this.getSGR()
|
||||
),
|
||||
),
|
||||
false // no converting CRLF needed
|
||||
);
|
||||
};
|
||||
|
@ -136,7 +136,7 @@ function TextView(options) {
|
|||
|
||||
this.getEndOfTextColumn = function() {
|
||||
var offset = Math.min(this.text.length, this.dimens.width);
|
||||
return this.position.col + offset;
|
||||
return this.position.col + offset;
|
||||
};
|
||||
|
||||
this.setText(options.text || '', false); // false=do not redraw now
|
||||
|
@ -168,7 +168,7 @@ TextView.prototype.setFocus = function(focused) {
|
|||
TextView.super_.prototype.setFocus.call(this, focused);
|
||||
|
||||
this.redraw();
|
||||
|
||||
|
||||
this.client.term.write(ansi.goto(this.position.row, this.getEndOfTextColumn()));
|
||||
this.client.term.write(this.getFocusSGR());
|
||||
};
|
||||
|
@ -184,7 +184,7 @@ TextView.prototype.setText = function(text, redraw) {
|
|||
text = text.toString();
|
||||
}
|
||||
|
||||
text = pipeToAnsi(stripAllLineFeeds(text), this.client); // expand MCI/etc.
|
||||
text = pipeToAnsi(stripAllLineFeeds(text), this.client); // expand MCI/etc.
|
||||
|
||||
var widthDelta = 0;
|
||||
if(this.text && this.text !== text) {
|
||||
|
@ -199,7 +199,7 @@ TextView.prototype.setText = function(text, redraw) {
|
|||
}
|
||||
|
||||
// :TODO: it would be nice to be able to stylize strings with MCI and {special} MCI syntax, e.g. "|BN {UN!toUpper}"
|
||||
this.text = stylizeString(this.text, this.hasFocus ? this.focusTextStyle : this.textStyle);
|
||||
this.text = stylizeString(this.text, this.hasFocus ? this.focusTextStyle : this.textStyle);
|
||||
|
||||
if(this.autoScale.width) {
|
||||
this.dimens.width = renderStringLength(this.text) + widthDelta;
|
||||
|
@ -214,7 +214,7 @@ TextView.prototype.setText = function(text, redraw) {
|
|||
TextView.prototype.setText = function(text) {
|
||||
if(!_.isString(text)) {
|
||||
text = text.toString();
|
||||
}
|
||||
}
|
||||
|
||||
var widthDelta = 0;
|
||||
if(this.text && this.text !== text) {
|
||||
|
@ -227,7 +227,7 @@ TextView.prototype.setText = function(text) {
|
|||
this.text = this.text.substr(0, this.maxLength);
|
||||
}
|
||||
|
||||
this.text = stylizeString(this.text, this.hasFocus ? this.focusTextStyle : this.textStyle);
|
||||
this.text = stylizeString(this.text, this.hasFocus ? this.focusTextStyle : this.textStyle);
|
||||
|
||||
//if(this.resizable) {
|
||||
// this.dimens.width = this.text.length + widthDelta;
|
||||
|
@ -254,9 +254,9 @@ TextView.prototype.setPropertyValue = function(propName, value) {
|
|||
if(true === value) {
|
||||
this.textMaskChar = this.client.currentTheme.helpers.getPasswordChar();
|
||||
}
|
||||
break;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
|
||||
TextView.super_.prototype.setPropertyValue.call(this, propName, value);
|
||||
};
|
||||
|
|
140
core/theme.js
140
core/theme.js
|
@ -87,8 +87,8 @@ function loadTheme(themeID, cb) {
|
|||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
if(!_.isObject(theme.info) ||
|
||||
|
||||
if(!_.isObject(theme.info) ||
|
||||
!_.isString(theme.info.name) ||
|
||||
!_.isString(theme.info.author))
|
||||
{
|
||||
|
@ -114,16 +114,16 @@ const IMMUTABLE_MCI_PROPERTIES = [
|
|||
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)
|
||||
//
|
||||
|
||||
// :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)
|
||||
//
|
||||
var mergedTheme = _.cloneDeep(menuConfig);
|
||||
|
||||
|
||||
if(_.isObject(promptConfig.prompts)) {
|
||||
mergedTheme.prompts = _.cloneDeep(promptConfig.prompts);
|
||||
}
|
||||
|
@ -136,8 +136,8 @@ function getMergedTheme(menuConfig, promptConfig, theme) {
|
|||
|
||||
//
|
||||
// merge customizer to disallow immutable MCI properties
|
||||
//
|
||||
var mciCustomizer = function(objVal, srcVal, key) {
|
||||
//
|
||||
var mciCustomizer = function(objVal, srcVal, key) {
|
||||
return IMMUTABLE_MCI_PROPERTIES.indexOf(key) > -1 ? objVal : srcVal;
|
||||
};
|
||||
|
||||
|
@ -159,69 +159,69 @@ function getMergedTheme(menuConfig, promptConfig, theme) {
|
|||
} 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) {
|
||||
|
||||
//
|
||||
// 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 {
|
||||
var menuMciCodeKeys = _.remove(_.keys(form), function pred(k) {
|
||||
return k === k.toUpperCase(); // remove anything not uppercase
|
||||
return k === k.toUpperCase(); // remove anything not uppercase
|
||||
});
|
||||
|
||||
|
||||
menuMciCodeKeys.forEach(function mciKeyEntry(mciKey) {
|
||||
var applyFrom;
|
||||
var applyFrom;
|
||||
if(_.has(menuTheme, [ mciKey, 'mci' ])) {
|
||||
applyFrom = menuTheme[mciKey];
|
||||
} else {
|
||||
applyFrom = menuTheme;
|
||||
}
|
||||
|
||||
|
||||
applyThemeMciBlock(form[mciKey].mci, applyFrom);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[ 'menus', 'prompts' ].forEach(function areaEntry(sectionName) {
|
||||
_.keys(mergedTheme[sectionName]).forEach(function menuEntry(menuName) {
|
||||
var createdFormSection = false;
|
||||
var mergedThemeMenu = mergedTheme[sectionName][menuName];
|
||||
|
||||
|
||||
if(_.has(theme, [ 'customization', sectionName, menuName ])) {
|
||||
var 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) {
|
||||
|
@ -232,7 +232,7 @@ function getMergedTheme(menuConfig, promptConfig, theme) {
|
|||
//
|
||||
// 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;
|
||||
|
@ -241,9 +241,9 @@ function getMergedTheme(menuConfig, promptConfig, theme) {
|
|||
} else if('prompts' === sectionName) {
|
||||
// no 'form' or form keys for prompts -- direct to mci
|
||||
applyToForm(mergedThemeMenu, menuTheme);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Finished merging for this menu/prompt
|
||||
//
|
||||
|
@ -259,13 +259,13 @@ function getMergedTheme(menuConfig, promptConfig, theme) {
|
|||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
return mergedTheme;
|
||||
}
|
||||
|
||||
function initAvailableThemes(cb) {
|
||||
|
||||
|
||||
async.waterfall(
|
||||
[
|
||||
function loadMenuConfig(callback) {
|
||||
|
@ -285,9 +285,9 @@ function initAvailableThemes(cb) {
|
|||
}
|
||||
|
||||
return callback(
|
||||
null,
|
||||
menuConfig,
|
||||
promptConfig,
|
||||
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();
|
||||
|
@ -363,7 +363,7 @@ function setClientTheme(client, themeId) {
|
|||
logMsg = 'Failed setting theme by system default ID; Using the first one we can find';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
client.log.debug( { themeId : themeId, info : client.currentTheme.info }, logMsg);
|
||||
}
|
||||
|
||||
|
@ -371,7 +371,7 @@ function getThemeArt(options, cb) {
|
|||
//
|
||||
// options - required:
|
||||
// name
|
||||
//
|
||||
//
|
||||
// options - optional
|
||||
// client - needed for user's theme/etc.
|
||||
// themeId
|
||||
|
@ -388,7 +388,7 @@ function getThemeArt(options, cb) {
|
|||
// :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
|
||||
options.readSauce = true; // read SAUCE, if avail
|
||||
options.readSauce = true; // read SAUCE, if avail
|
||||
options.random = _.get(options, 'random', true); // FILENAME<n>.EXT support
|
||||
|
||||
//
|
||||
|
@ -406,7 +406,7 @@ function getThemeArt(options, cb) {
|
|||
//
|
||||
if('/' === options.name.charAt(0)) {
|
||||
// just take the path as-is
|
||||
options.basePath = paths.dirname(options.name);
|
||||
options.basePath = paths.dirname(options.name);
|
||||
} else if(options.name.indexOf('/') > -1) {
|
||||
// make relative to base BBS dir
|
||||
options.basePath = paths.join(__dirname, '../', paths.dirname(options.name));
|
||||
|
@ -432,7 +432,7 @@ function getThemeArt(options, cb) {
|
|||
if(artInfo || Config.defaults.theme === options.themeId) {
|
||||
return callback(null, artInfo);
|
||||
}
|
||||
|
||||
|
||||
options.basePath = paths.join(Config.paths.themes, Config.defaults.theme);
|
||||
art.getArt(options.name, options, (err, artInfo) => {
|
||||
return callback(null, artInfo);
|
||||
|
@ -442,11 +442,11 @@ function getThemeArt(options, cb) {
|
|||
if(artInfo) {
|
||||
return callback(null, artInfo);
|
||||
}
|
||||
|
||||
|
||||
options.basePath = Config.paths.art;
|
||||
art.getArt(options.name, options, (err, artInfo) => {
|
||||
return callback(err, artInfo);
|
||||
});
|
||||
});
|
||||
}
|
||||
],
|
||||
function complete(err, artInfo) {
|
||||
|
@ -483,7 +483,7 @@ function displayThemeArt(options, cb) {
|
|||
|
||||
/*
|
||||
function displayThemedPrompt(name, client, options, cb) {
|
||||
|
||||
|
||||
async.waterfall(
|
||||
[
|
||||
function loadConfig(callback) {
|
||||
|
@ -511,14 +511,14 @@ function displayThemedPrompt(name, client, options, cb) {
|
|||
|
||||
//
|
||||
// If we did not clear the screen, don't let the font change
|
||||
//
|
||||
//
|
||||
const dispOptions = Object.assign( {}, promptConfig.options );
|
||||
if(!options.clearScreen) {
|
||||
dispOptions.font = 'not_really_a_font!';
|
||||
}
|
||||
|
||||
displayThemedAsset(
|
||||
promptConfig.art,
|
||||
promptConfig.art,
|
||||
client,
|
||||
dispOptions,
|
||||
(err, artData) => {
|
||||
|
@ -576,7 +576,7 @@ function displayThemedPrompt(name, client, options, cb) {
|
|||
}
|
||||
|
||||
displayThemedAsset(
|
||||
promptConfig.art,
|
||||
promptConfig.art,
|
||||
client,
|
||||
dispOptions,
|
||||
(err, artInfo) => {
|
||||
|
@ -593,7 +593,7 @@ function displayThemedPrompt(name, client, options, cb) {
|
|||
// no need to query cursor - we're not gonna use it
|
||||
return callback(null, promptConfig, artInfo);
|
||||
}
|
||||
|
||||
|
||||
client.once('cursor position report', pos => {
|
||||
artInfo.startRow = pos[0] - artInfo.height;
|
||||
return callback(null, promptConfig, artInfo);
|
||||
|
@ -627,7 +627,7 @@ function displayThemedPrompt(name, client, options, cb) {
|
|||
if(options.clearPrompt) {
|
||||
if(artInfo.startRow && artInfo.height) {
|
||||
client.term.rawWrite(ansi.goto(artInfo.startRow, 1));
|
||||
|
||||
|
||||
// Note: Does not work properly in NetRunner < 2.0b17:
|
||||
client.term.rawWrite(ansi.deleteLine(artInfo.height));
|
||||
} else {
|
||||
|
@ -654,7 +654,7 @@ function displayThemedPrompt(name, client, options, cb) {
|
|||
|
||||
//
|
||||
// Pause prompts are a special prompt by the name 'pause'.
|
||||
//
|
||||
//
|
||||
function displayThemedPause(client, options, cb) {
|
||||
|
||||
if(!cb && _.isFunction(options)) {
|
||||
|
@ -699,7 +699,7 @@ function displayThemedAsset(assetSpec, client, options, cb) {
|
|||
});
|
||||
break;
|
||||
|
||||
case 'method' :
|
||||
case 'method' :
|
||||
// :TODO: fetch & render via method
|
||||
break;
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ module.exports = class TicFileInfo {
|
|||
|
||||
static get requiredFields() {
|
||||
return [
|
||||
'Area', 'Origin', 'From', 'File', 'Crc',
|
||||
'Area', 'Origin', 'From', 'File', 'Crc',
|
||||
// :TODO: validate this:
|
||||
//'Path', 'Seenby' // these two are questionable; some systems don't send them?
|
||||
];
|
||||
|
@ -43,16 +43,16 @@ module.exports = class TicFileInfo {
|
|||
if(value) {
|
||||
//
|
||||
// We call toString() on values to ensure numbers, addresses, etc. are converted
|
||||
//
|
||||
//
|
||||
joinWith = joinWith || '';
|
||||
if(Array.isArray(value)) {
|
||||
return value.map(v => v.toString() ).join(joinWith);
|
||||
}
|
||||
|
||||
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
get filePath() {
|
||||
return paths.join(paths.dirname(this.path), this.getAsString('File'));
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ module.exports = class TicFileInfo {
|
|||
const localInfo = {
|
||||
areaTag : config.localAreaTags.find( areaTag => areaTag.toUpperCase() === area ),
|
||||
};
|
||||
|
||||
|
||||
if(!localInfo.areaTag) {
|
||||
return callback(Errors.Invalid(`No local area for "Area" of ${area}`));
|
||||
}
|
||||
|
@ -112,7 +112,7 @@ module.exports = class TicFileInfo {
|
|||
return callback(null, localInfo);
|
||||
},
|
||||
function checksumAndSize(localInfo, callback) {
|
||||
const crcTic = self.get('Crc');
|
||||
const crcTic = self.get('Crc');
|
||||
const stream = fs.createReadStream(self.filePath);
|
||||
const crc = new CRC32();
|
||||
let sizeActual = 0;
|
||||
|
@ -193,7 +193,7 @@ module.exports = class TicFileInfo {
|
|||
// This is an optional keyword."
|
||||
//
|
||||
const to = this.get('To');
|
||||
|
||||
|
||||
if(!to) {
|
||||
return allowNonExplicit;
|
||||
}
|
||||
|
@ -219,10 +219,10 @@ module.exports = class TicFileInfo {
|
|||
let key;
|
||||
let value;
|
||||
let entry;
|
||||
|
||||
|
||||
lines.forEach(line => {
|
||||
keyEnd = line.search(/\s/);
|
||||
|
||||
|
||||
if(keyEnd < 0) {
|
||||
keyEnd = line.length;
|
||||
}
|
||||
|
@ -253,7 +253,7 @@ module.exports = class TicFileInfo {
|
|||
value = parseInt(value, 16);
|
||||
break;
|
||||
|
||||
case 'size' :
|
||||
case 'size' :
|
||||
value = parseInt(value, 10);
|
||||
break;
|
||||
|
||||
|
|
|
@ -1,94 +0,0 @@
|
|||
/* jslint node: true */
|
||||
'use strict';
|
||||
|
||||
var View = require('./view.js').View;
|
||||
var miscUtil = require('./misc_util.js');
|
||||
var strUtil = require('./string_util.js');
|
||||
var ansi = require('./ansi_term.js');
|
||||
var util = require('util');
|
||||
var assert = require('assert');
|
||||
|
||||
exports.TickerTextView = TickerTextView;
|
||||
|
||||
function TickerTextView(options) {
|
||||
View.call(this, options);
|
||||
|
||||
var self = this;
|
||||
|
||||
this.text = options.text || '';
|
||||
this.tickerStyle = options.tickerStyle || 'rightToLeft';
|
||||
assert(this.tickerStyle in TickerTextView.TickerStyles);
|
||||
|
||||
// :TODO: Ticker |text| should have ANSI stripped before calculating any lengths/etc.
|
||||
// strUtil.ansiTextLength(s)
|
||||
// strUtil.pad(..., ignoreAnsi)
|
||||
// strUtil.stylizeString(..., ignoreAnsi)
|
||||
|
||||
this.tickerState = {};
|
||||
switch(this.tickerStyle) {
|
||||
case 'rightToLeft' :
|
||||
this.tickerState.pos = this.position.row + this.dimens.width;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
self.onTickerInterval = function() {
|
||||
switch(self.tickerStyle) {
|
||||
case 'rightToLeft' : self.updateRightToLeftTicker(); break;
|
||||
}
|
||||
};
|
||||
|
||||
self.updateRightToLeftTicker = function() {
|
||||
// if pos < start
|
||||
// drawRemain()
|
||||
// if pos + remain > end
|
||||
// drawRemain(0, spaceFor)
|
||||
// else
|
||||
// drawString() + remainPading
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
util.inherits(TickerTextView, View);
|
||||
|
||||
TickerTextView.TickerStyles = {
|
||||
leftToRight : 1,
|
||||
rightToLeft : 2,
|
||||
bounce : 3,
|
||||
slamLeft : 4,
|
||||
slamRight : 5,
|
||||
slamBounce : 6,
|
||||
decrypt : 7,
|
||||
typewriter : 8,
|
||||
};
|
||||
Object.freeze(TickerTextView.TickerStyles);
|
||||
|
||||
/*
|
||||
TickerTextView.TICKER_STYLES = [
|
||||
'leftToRight',
|
||||
'rightToLeft',
|
||||
'bounce',
|
||||
'slamLeft',
|
||||
'slamRight',
|
||||
'slamBounce',
|
||||
'decrypt',
|
||||
'typewriter',
|
||||
];
|
||||
*/
|
||||
|
||||
TickerTextView.prototype.controllerAttached = function() {
|
||||
// :TODO: call super
|
||||
};
|
||||
|
||||
TickerTextView.prototype.controllerDetached = function() {
|
||||
// :TODO: call super
|
||||
|
||||
};
|
||||
|
||||
TickerTextView.prototype.setText = function(text) {
|
||||
this.text = strUtil.stylizeString(text, this.textStyle);
|
||||
|
||||
if(!this.dimens || !this.dimens.width) {
|
||||
this.dimens.width = Math.ceil(this.text.length / 2);
|
||||
}
|
||||
};
|
|
@ -1,13 +1,11 @@
|
|||
/* jslint node: true */
|
||||
'use strict';
|
||||
|
||||
var MenuView = require('./menu_view.js').MenuView;
|
||||
var ansi = require('./ansi_term.js');
|
||||
var strUtil = require('./string_util.js');
|
||||
const MenuView = require('./menu_view.js').MenuView;
|
||||
const strUtil = require('./string_util.js');
|
||||
|
||||
var util = require('util');
|
||||
var assert = require('assert');
|
||||
var _ = require('lodash');
|
||||
const util = require('util');
|
||||
const assert = require('assert');
|
||||
|
||||
exports.ToggleMenuView = ToggleMenuView;
|
||||
|
||||
|
@ -44,7 +42,7 @@ ToggleMenuView.prototype.redraw = function() {
|
|||
var item = this.items[i];
|
||||
var text = strUtil.stylizeString(
|
||||
item.text, i === this.focusedItemIndex && this.hasFocus ? this.focusTextStyle : this.textStyle);
|
||||
|
||||
|
||||
if(1 === i) {
|
||||
//console.log(this.styleColor1)
|
||||
//var sepColor = this.getANSIColor(this.styleColor1 || this.getColor());
|
||||
|
|
|
@ -85,7 +85,7 @@ exports.getModule = class UploadModule extends MenuModule {
|
|||
|
||||
fileDetailsContinue : (formData, extraArgs, cb) => {
|
||||
// see displayFileDetailsPageForUploadEntry() for this hackery:
|
||||
cb(null);
|
||||
cb(null);
|
||||
return this.fileDetailsCurrentEntrySubmitCallback(null, formData.value); // move on to the next entry, if any
|
||||
},
|
||||
|
||||
|
@ -119,7 +119,7 @@ exports.getModule = class UploadModule extends MenuModule {
|
|||
|
||||
return cb(null);
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
getSaveState() {
|
||||
|
@ -143,12 +143,12 @@ exports.getModule = class UploadModule extends MenuModule {
|
|||
|
||||
isBlindUpload() { return 'blind' === this.uploadType; }
|
||||
isFileTransferComplete() { return !_.isUndefined(this.recvFilePaths); }
|
||||
|
||||
|
||||
initSequence() {
|
||||
const self = this;
|
||||
|
||||
if(0 === this.availAreas.length) {
|
||||
//
|
||||
//
|
||||
return this.gotoMenu(this.menuConfig.config.noUploadAreasAvailMenu || 'fileBaseNoUploadAreasAvail');
|
||||
}
|
||||
|
||||
|
@ -185,7 +185,7 @@ exports.getModule = class UploadModule extends MenuModule {
|
|||
|
||||
// need a terminator for various external protocols
|
||||
this.tempRecvDirectory = pathWithTerminatingSeparator(tempRecvDirectory);
|
||||
|
||||
|
||||
const modOpts = {
|
||||
extraArgs : {
|
||||
recvDirectory : this.tempRecvDirectory, // we'll move files from here to their area container once processed/confirmed
|
||||
|
@ -203,8 +203,8 @@ exports.getModule = class UploadModule extends MenuModule {
|
|||
// Upon completion, we'll re-enter the module with some file paths handed to us
|
||||
//
|
||||
return this.gotoMenu(
|
||||
this.menuConfig.config.fileTransferProtocolSelection || 'fileTransferProtocolSelection',
|
||||
modOpts,
|
||||
this.menuConfig.config.fileTransferProtocolSelection || 'fileTransferProtocolSelection',
|
||||
modOpts,
|
||||
cb
|
||||
);
|
||||
});
|
||||
|
@ -219,7 +219,7 @@ exports.getModule = class UploadModule extends MenuModule {
|
|||
|
||||
const fmtObj = Object.assign( {}, stepInfo);
|
||||
let stepIndicatorFmt = '';
|
||||
let logStepFmt;
|
||||
let logStepFmt;
|
||||
|
||||
const fmtConfig = this.menuConfig.config;
|
||||
|
||||
|
@ -228,7 +228,7 @@ exports.getModule = class UploadModule extends MenuModule {
|
|||
|
||||
const indicator = { };
|
||||
const self = this;
|
||||
|
||||
|
||||
function updateIndicator(mci, isFinished) {
|
||||
indicator.mci = mci;
|
||||
|
||||
|
@ -253,7 +253,7 @@ exports.getModule = class UploadModule extends MenuModule {
|
|||
updateIndicator(MciViewIds.processing.calcHashIndicator);
|
||||
break;
|
||||
|
||||
case 'hash_finish' :
|
||||
case 'hash_finish' :
|
||||
stepIndicatorFmt = fmtConfig.calcHashCompleteFormat || 'Finished calculating hash/checksums';
|
||||
updateIndicator(MciViewIds.processing.calcHashIndicator, true);
|
||||
break;
|
||||
|
@ -263,7 +263,7 @@ exports.getModule = class UploadModule extends MenuModule {
|
|||
updateIndicator(MciViewIds.processing.archiveListIndicator);
|
||||
break;
|
||||
|
||||
case 'archive_list_finish' :
|
||||
case 'archive_list_finish' :
|
||||
fmtObj.archivedFileCount = stepInfo.archiveEntries.length;
|
||||
stepIndicatorFmt = fmtConfig.extractArchiveListFinishFormat || 'Archive list extracted ({archivedFileCount} files)';
|
||||
updateIndicator(MciViewIds.processing.archiveListIndicator, true);
|
||||
|
@ -273,7 +273,7 @@ exports.getModule = class UploadModule extends MenuModule {
|
|||
stepIndicatorFmt = fmtConfig.extractArchiveListFailedFormat || 'Archive list extraction failed';
|
||||
break;
|
||||
|
||||
case 'desc_files_start' :
|
||||
case 'desc_files_start' :
|
||||
stepIndicatorFmt = fmtConfig.processingDescFilesFormat || 'Processing description files';
|
||||
updateIndicator(MciViewIds.processing.descFileIndicator);
|
||||
break;
|
||||
|
@ -289,7 +289,7 @@ exports.getModule = class UploadModule extends MenuModule {
|
|||
}
|
||||
|
||||
fmtObj.stepIndicatorText = stringFormat(stepIndicatorFmt, fmtObj);
|
||||
|
||||
|
||||
if(this.hasProcessingArt) {
|
||||
this.updateCustomViewTextsWithFilter('processing', MciViewIds.processing.customRangeStart, fmtObj, { appendMultiLine : true } );
|
||||
|
||||
|
@ -339,7 +339,7 @@ exports.getModule = class UploadModule extends MenuModule {
|
|||
return nextScanStep(null);
|
||||
}
|
||||
|
||||
self.client.log.debug('Scanning file', { filePath : filePath } );
|
||||
self.client.log.debug('Scanning file', { filePath : filePath } );
|
||||
|
||||
scanFile(filePath, scanOpts, handleScanStep, (err, fileEntry, dupeEntries) => {
|
||||
if(err) {
|
||||
|
@ -389,7 +389,7 @@ exports.getModule = class UploadModule extends MenuModule {
|
|||
// name changed; ajust before persist
|
||||
newEntry.fileName = paths.basename(finalPath);
|
||||
}
|
||||
|
||||
|
||||
return nextEntry(null); // still try next file
|
||||
}
|
||||
|
||||
|
@ -474,7 +474,7 @@ exports.getModule = class UploadModule extends MenuModule {
|
|||
if(err) {
|
||||
return nextDupe(err);
|
||||
}
|
||||
|
||||
|
||||
const areaInfo = getFileAreaByTag(dupe.areaTag);
|
||||
if(areaInfo) {
|
||||
dupe.areaName = areaInfo.name;
|
||||
|
@ -553,12 +553,12 @@ exports.getModule = class UploadModule extends MenuModule {
|
|||
return callback(null, scanResults);
|
||||
}
|
||||
|
||||
return self.displayDupesPage(scanResults.dupes, () => {
|
||||
return self.displayDupesPage(scanResults.dupes, () => {
|
||||
return callback(null, scanResults);
|
||||
});
|
||||
},
|
||||
function prepDetails(scanResults, callback) {
|
||||
return self.prepDetailsForUpload(scanResults, callback);
|
||||
return self.prepDetailsForUpload(scanResults, callback);
|
||||
},
|
||||
function startMovingAndPersistingToDatabase(scanResults, callback) {
|
||||
//
|
||||
|
@ -583,14 +583,14 @@ exports.getModule = class UploadModule extends MenuModule {
|
|||
|
||||
displayOptionsPage(cb) {
|
||||
const self = this;
|
||||
|
||||
|
||||
async.series(
|
||||
[
|
||||
function prepArtAndViewController(callback) {
|
||||
return self.prepViewControllerWithArt(
|
||||
'options',
|
||||
FormIds.options,
|
||||
{ clearScreen : true, trailingLF : false },
|
||||
'options',
|
||||
FormIds.options,
|
||||
{ clearScreen : true, trailingLF : false },
|
||||
callback
|
||||
);
|
||||
},
|
||||
|
@ -621,7 +621,7 @@ exports.getModule = class UploadModule extends MenuModule {
|
|||
fileNameView.setText(sanatizeFilename(fileNameView.getData()));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
self.uploadType = 'blind';
|
||||
uploadTypeView.setFocusItemIndex(0); // default to blind
|
||||
fileNameView.setText(blindFileNameText);
|
||||
|
@ -658,14 +658,14 @@ exports.getModule = class UploadModule extends MenuModule {
|
|||
|
||||
displayFileDetailsPageForUploadEntry(fileEntry, cb) {
|
||||
const self = this;
|
||||
|
||||
|
||||
async.waterfall(
|
||||
[
|
||||
function prepArtAndViewController(callback) {
|
||||
return self.prepViewControllerWithArt(
|
||||
'fileDetails',
|
||||
'fileDetails',
|
||||
FormIds.fileDetails,
|
||||
{ clearScreen : true, trailingLF : false },
|
||||
{ clearScreen : true, trailingLF : false },
|
||||
err => {
|
||||
return callback(err);
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue