diff --git a/core/fse.js b/core/fse.js index 6082ac40..4311ae4a 100644 --- a/core/fse.js +++ b/core/fse.js @@ -212,9 +212,12 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul appendQuoteEntry: function(formData, extraArgs, cb) { // :TODO: Dont' use magic # ID's here const quoteMsgView = self.viewControllers.quoteBuilder.getView(1); - + if(self.newQuoteBlock) { self.newQuoteBlock = false; + + // :TODO: If replying to ANSI, add a blank sepration line here + quoteMsgView.addText(self.getQuoteByHeader()); } @@ -325,7 +328,8 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul toUserName : headerValues.to, fromUserName : this.client.user.username, subject : headerValues.subject, - message : this.viewControllers.body.getFormData().value.message, + // :TODO: don't hard code 1 here: + message : this.viewControllers.body.getView(1).getData( { forceLineTerms : this.replyIsAnsi } ), }; if(this.isReply()) { @@ -382,7 +386,6 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul { prepped : false, forceLineTerm : true, - preserveTextLines : this.message.replyToMsgId ? true : false, } ); } else { diff --git a/core/ftn_mail_packet.js b/core/ftn_mail_packet.js index 9fb8347b..607ae363 100644 --- a/core/ftn_mail_packet.js +++ b/core/ftn_mail_packet.js @@ -668,17 +668,12 @@ function Packet(options) { const dateTimeBuffer = new Buffer(ftn.getDateTimeString(message.modTimestamp) + '\0'); dateTimeBuffer.copy(basicHeader, 14); - // toUserName & fromUserName: up to 36 bytes in length, NULL term'd - // :TODO: DRY... - let toUserNameBuf = iconv.encode(message.toUserName + '\0', 'CP437').slice(0, 36); - toUserNameBuf[toUserNameBuf.length - 1] = '\0'; // ensure it's null term'd - - let fromUserNameBuf = iconv.encode(message.fromUserName + '\0', 'CP437').slice(0, 36); - fromUserNameBuf[fromUserNameBuf.length - 1] = '\0'; // ensure it's null term'd - - // subject: up to 72 bytes in length, NULL term'd - let subjectBuf = iconv.encode(message.subject + '\0', 'CP437').slice(0, 72); - subjectBuf[subjectBuf.length - 1] = '\0'; // ensure it's null term'd + // + // To, from, and subject must be NULL term'd and have max lengths as per spec. + // + const toUserNameBuf = strUtil.stringToNullTermBuffer(message.toUserName, { encoding : 'cp437', maxBufLen : 36 } ); + const fromUserNameBuf = strUtil.stringToNullTermBuffer(message.fromUserName, { encoding : 'cp437', maxBufLen : 36 } ); + const subjectBuf = strUtil.stringToNullTermBuffer(message.subject, { encoding : 'cp437', maxBufLen : 72 } ); // // message: unbound length, NULL term'd @@ -686,7 +681,6 @@ function Packet(options) { // We need to build in various special lines - kludges, area, // seen-by, etc. // - // :TODO: Put this in it's own method let msgBody = ''; // @@ -717,7 +711,6 @@ function Packet(options) { { cols : 80, rows : 'auto', - preserveTextLines : true, forceLineTerm : true, exportMode : true, }, diff --git a/core/ftn_util.js b/core/ftn_util.js index 8dd1f25c..4d779c4a 100644 --- a/core/ftn_util.js +++ b/core/ftn_util.js @@ -163,7 +163,10 @@ function getUTCTimeZoneOffset() { return moment().format('ZZ').replace(/\+/, ''); } -// Get a FSC-0032 style quote prefixes +// +// Get a FSC-0032 style quote prefix +// http://ftsc.org/docs/fsc-0032.001 +// function getQuotePrefix(name) { let initials; diff --git a/core/message.js b/core/message.js index a0d49df2..600ccf55 100644 --- a/core/message.js +++ b/core/message.js @@ -11,7 +11,8 @@ const ANSI = require('./ansi_term.js'); const { prepAnsi, isAnsi, - splitTextAtTerms + splitTextAtTerms, + renderSubstr } = require('./string_util.js'); // deps @@ -486,7 +487,7 @@ Message.prototype.getQuoteLines = function(options, cb) { { termWidth : options.termWidth, termHeight : options.termHeight, - cols : options.cols - quotePrefix.length, + cols : options.cols, rows : 'auto', startCol : options.startCol, forceLineTerm : true, @@ -501,14 +502,17 @@ Message.prototype.getQuoteLines = function(options, cb) { const focusQuoteLines = []; // - // Create items (standard) and inverted items for focus views + // 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 + // 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(`${options.ansiResetSgr}${quotePrefix}${lastSgr}${l}`); - focusQuoteLines.push(`${options.ansiFocusPrefixSgr}${quotePrefix}${lastSgr}${l}`); - + quoteLines.push(`${lastSgr}${l}`); + + focusQuoteLines.push(`${options.ansiFocusPrefixSgr}>${lastSgr}${renderSubstr(l, 0, l.length - 2)}`); 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; diff --git a/core/multi_line_edit_text_view.js b/core/multi_line_edit_text_view.js index d1cdebd2..d32a68b7 100644 --- a/core/multi_line_edit_text_view.js +++ b/core/multi_line_edit_text_view.js @@ -283,14 +283,15 @@ function MultiLineEditTextView(options) { return lines; }; - this.getOutputText = function(startIndex, endIndex, eolMarker) { - let lines = self.getTextLines(startIndex, endIndex); - let text = ''; - var re = new RegExp('\\t{1,' + (self.tabWidth) + '}', 'g'); + this.getOutputText = function(startIndex, endIndex, eolMarker, options) { + const lines = self.getTextLines(startIndex, endIndex); + let text = ''; + const re = new RegExp('\\t{1,' + (self.tabWidth) + '}', 'g'); lines.forEach(line => { text += line.text.replace(re, '\t'); - if(eolMarker && line.eol) { + + if(options.forceLineTerms || (eolMarker && line.eol)) { text += eolMarker; } }); @@ -598,7 +599,6 @@ function MultiLineEditTextView(options) { rows : 'auto', startCol : this.position.col, forceLineTerm : options.forceLineTerm, - preserveTextLines : options.preserveTextLines, }, (err, preppedAnsi) => { return setLines(err ? ansi : preppedAnsi); @@ -651,21 +651,6 @@ function MultiLineEditTextView(options) { self.setTextLines(wrapped, index, true); // true=termWithEol index += wrapped.length; }); - /* - for(let i = 0; i < text.length; ++i) { - wrapped = self.wordWrapSingleLine( - text[i], // input - 'expand', // tabHandling - self.dimens.width - ).wrapped; - - for(let j = 0; j < wrapped.length - 1; ++j) { - self.textLines.splice(index++, 0, { text : wrapped[j] } ); - } - - self.textLines.splice(index++, 0, { text : wrapped[wrapped.length - 1], eol : true } ); - } - */ }; this.getAbsolutePosition = function(row, col) { @@ -1129,8 +1114,8 @@ MultiLineEditTextView.prototype.addText = function(text) { } }; -MultiLineEditTextView.prototype.getData = function() { - return this.getOutputText(0, this.textLines.length, '\r\n'); +MultiLineEditTextView.prototype.getData = function(options = { forceLineTerms : false } ) { + return this.getOutputText(0, this.textLines.length, '\r\n', options); }; MultiLineEditTextView.prototype.setPropertyValue = function(propName, value) { diff --git a/core/string_util.js b/core/string_util.js index 98b691d9..cfad6ae7 100644 --- a/core/string_util.js +++ b/core/string_util.js @@ -18,6 +18,7 @@ exports.isPrintable = isPrintable; exports.stripAllLineFeeds = stripAllLineFeeds; exports.debugEscapedString = debugEscapedString; exports.stringFromNullTermBuffer = stringFromNullTermBuffer; +exports.stringToNullTermBuffer = stringToNullTermBuffer; exports.renderSubstr = renderSubstr; exports.renderStringLength = renderStringLength; exports.formatByteSizeAbbr = formatByteSizeAbbr; @@ -216,6 +217,12 @@ function stringFromNullTermBuffer(buf, encoding) { return iconv.decode(buf.slice(0, nullPos), encoding || 'utf-8'); } +function stringToNullTermBuffer(s, options = { encoding : 'utf8', maxBufLen : -1 } ) { + let buf = iconv.encode( `${s}\0`, options.encoding ).slice(0, options.maxBufLen); + buf[buf.length - 1] = '\0'; // make abs sure we null term even if truncated + return buf; +} + const PIPE_REGEXP = /(\|[A-Z\d]{2})/g; //const ANSI_REGEXP = /[\u001b\u009b][[()#;?]*([0-9]{1,4}(?:;[0-9]{0,4})*)?([0-9A-ORZcf-npqrsuy=><])/g; //const ANSI_OR_PIPE_REGEXP = new RegExp(PIPE_REGEXP.source + '|' + ANSI_REGEXP.source, 'g'); @@ -393,7 +400,6 @@ function prepAnsi(input, options, cb) { options.cols = options.cols || options.termWidth || 80; options.rows = options.rows || options.termHeight || 'auto'; options.startCol = options.startCol || 1; - options.preserveTextLines = options.preserveTextLines || false; options.exportMode = options.exportMode || false; const canvas = Array.from( { length : 'auto' === options.rows ? 25 : options.rows }, () => Array.from( { length : options.cols}, () => new Object() ) ); @@ -472,7 +478,7 @@ function prepAnsi(input, options, cb) { let output = ''; let lastSgr = ''; let line; - let textState = 'new'; + canvas.slice(0, lastRow + 1).forEach(row => { const lastCol = getLastPopulatedColumn(row) + 1; @@ -486,42 +492,15 @@ function prepAnsi(input, options, cb) { line += `${col.sgr || ''}${col.char || ' '}`; } - if(options.preserveTextLines && !isAnsiLine(line)) { - switch(textState) { - case 'new' : - line = _.trimStart(line); - if(line) { - if(output) { - output += '\r\n'; - } - - textState = 'cont'; - } - break; + output += line; - case 'cont' : - line = ' ' + line; - break; - } + if(i < row.length) { + output += `${ANSI.blackBG()}${row.slice(i).map( () => ' ').join('')}${lastSgr}`; + } - output += line; - } else { - if('cont' === textState) { - output += '\r\n'; - } - - textState = 'new'; - - 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.startCol + options.cols < options.termWidth || options.forceLineTerm) { + if(options.startCol + i < options.termWidth || options.forceLineTerm) { + output += '\r\n'; } });