Skip to content
Snippets Groups Projects
Commit 462ed88d authored by Eric Oulashin's avatar Eric Oulashin
Browse files

DDMsgReader: When viewing message headers, now shows all available message header values

parent 7291fa08
No related branches found
No related tags found
1 merge request!288DDMsgReader: When viewing message headers, now shows all available message header values
Pipeline #4123 failed
......@@ -128,6 +128,9 @@
* Bug fix: When getting header lines to view, ensure the header lines
* are not too wide for the user's terminal. Header lines that are too
* long will be split into no more than 2 lines.
* 2023-04-25 Eric Oulashin Version 1.73a
* Refactored the functions for getting message header lines. Also, now
* all message header information is retrieved.
*/
 
"use strict";
......@@ -233,8 +236,8 @@ var ansiterm = require("ansiterm_lib.js", 'expand_ctrl_a');
 
 
// Reader version information
var READER_VERSION = "1.73";
var READER_DATE = "2023-04-17";
var READER_VERSION = "1.73a";
var READER_DATE = "2023-04-25";
 
// Keyboard key codes for displaying on the screen
var UP_ARROW = ascii(24);
......@@ -493,10 +496,12 @@ if (file_exists(gFileAttachDir))
 
// See if the avatar support files are available, and load them if so
var gAvatar = null;
var gSMBDefsLoaded = false;
if (file_exists(backslash(system.exec_dir) + "load/smbdefs.js") && file_exists(backslash(system.exec_dir) + "load/avatar_lib.js"))
{
require("smbdefs.js", "SMB_POLL_ANSWER");
gAvatar = load({}, "avatar_lib.js");
gSMBDefsLoaded = true;
}
 
// User twitlist filename (and settings filename)
......@@ -854,6 +859,7 @@ function DigDistMsgReader(pSubBoardCode, pScriptArgs)
this.PromptAndDeleteOrUndeleteMessage = DigDistMsgReader_PromptAndDeleteOrUndeleteMessage;
this.PromptAndDeleteOrUndeleteSelectedMessages = DigDistMsgReader_PromptAndDeleteOrUndeleteSelectedMessages;
this.GetExtdMsgHdrInfo = DigDistMsgReader_GetExtdMsgHdrInfo;
this.GetMsgHdrFieldListText = DigDistMsgReader_GetMsgHdrFieldListText;
this.GetMsgInfoForEnhancedReader = DigDistMsgReader_GetMsgInfoForEnhancedReader;
this.GetLastReadMsgIdxAndNum = DigDistMsgReader_GetLastReadMsgIdxAndNum;
this.GetScanPtrMsgIdx = DigDistMsgReader_GetScanPtrMsgIdx;
......@@ -1028,6 +1034,7 @@ function DigDistMsgReader(pSubBoardCode, pScriptArgs)
invalidMsgNumText: "\x01n\x01y\x01hInvalid message number: %d",
readMsgNumPromptText: "\x01n\x01g\x01h\x01i* \x01n\x01cRead message #: \x01h",
msgHasBeenDeletedText: "\x01n\x01h\x01g* \x01yMessage #\x01w%d \x01yhas been deleted.",
noHdrLinesForThisMsgText: "\x01n\x01h\x01yThere are no header lines for this message.",
noKludgeLinesForThisMsgText: "\x01n\x01h\x01yThere are no kludge lines for this message.",
searchingPersonalMailText: "\x01w\x01hSearching personal mail\x01n",
searchTextPromptText: "\x01cEnter the search text\x01g\x01h:\x01n\x01c ",
......@@ -5564,7 +5571,8 @@ function DigDistMsgReader_ReadMessageEnhanced_Scrollable(msgHeader, allowChgMsgA
 
// Get an array of the extended header info/kludge lines and then
// allow the user to scroll through them.
var extdHdrInfoLines = this.GetExtdMsgHdrInfo(msgHeader, (retObj.lastKeypress == this.enhReaderKeys.showKludgeLines));
var onlyKludgeLines = (retObj.lastKeypress == this.enhReaderKeys.showKludgeLines);
var extdHdrInfoLines = this.GetExtdMsgHdrInfo(this.subBoardCode, msgHeader.number, onlyKludgeLines);
if (extdHdrInfoLines.length > 0)
{
if (this.userSettings.useEnhReaderScrollbar)
......@@ -5598,8 +5606,9 @@ function DigDistMsgReader_ReadMessageEnhanced_Scrollable(msgHeader, allowChgMsgA
}
else
{
// There are no kludge lines for this message
this.DisplayEnhReaderError(replaceAtCodesInStr(this.text.noKludgeLinesForThisMsgText), msgInfo.messageLines, topMsgLineIdx, msgLineFormatStr);
// There are no header/kludge lines for this message
var msgText = onlyKludgeLines ? this.text.noKludgeLinesForThisMsgText : this.text.noHdrLinesForThisMsgText;
this.DisplayEnhReaderError(replaceAtCodesInStr(msgText), msgInfo.messageLines, topMsgLineIdx, msgLineFormatStr);
console.gotoxy(originalCurPos);
writeMessage = false;
}
......@@ -6770,7 +6779,7 @@ function DigDistMsgReader_ReadMessageEnhanced_Traditional(msgHeader, allowChgMsg
console.crlf();
// Get an array of the extended header info/kludge lines and then
// display them.
var extdHdrInfoLines = this.GetExtdMsgHdrInfo(msgHeader, (retObj.lastKeypress == this.enhReaderKeys.showKludgeLines));
var extdHdrInfoLines = this.GetExtdMsgHdrInfo(this.subBoardCode, msgHeader.number, (retObj.lastKeypress == this.enhReaderKeys.showKludgeLines));
if (extdHdrInfoLines.length > 0)
{
console.crlf();
......@@ -8597,6 +8606,7 @@ function DigDistMsgReader_ReadConfigFile()
(setting == "invalidMsgNumText") ||
(setting == "readMsgNumPromptText") ||
(setting == "msgHasBeenDeletedText") ||
(setting == "noHdrLinesForThisMsgText") ||
(setting == "noKludgeLinesForThisMsgText") ||
(setting == "searchingPersonalMailText") ||
(setting == "searchingSubBoardAbovePromptText") ||
......@@ -9302,8 +9312,8 @@ function DigDistMsgReader_GetMsgHdrByAbsoluteNum(pMsgNum, pExpandFields, pGetVot
var msgbase = new MsgBase(this.subBoardCode);
if (msgbase.open())
{
var expandFields = (typeof(pExpandFields) == "boolean" ? pExpandFields : false);
var getVoteInfo = (typeof(pGetVoteInfo) == "boolean" ? pGetVoteInfo : false);
var expandFields = (typeof(pExpandFields) === "boolean" ? pExpandFields : false);
var getVoteInfo = (typeof(pGetVoteInfo) === "boolean" ? pGetVoteInfo : false);
msgHdr = msgbase.get_msg_header(false, pMsgNum, expandFields, getVoteInfo);
msgbase.close();
}
......@@ -9311,6 +9321,22 @@ function DigDistMsgReader_GetMsgHdrByAbsoluteNum(pMsgNum, pExpandFields, pGetVot
msgHdr = getBogusMsgHdr();
return msgHdr;
}
function DumpMsgHdr(pSubCode, pMsgNum, pExpandFields, pGetVoteInfo)
{
var hdrLines = [];
var msgHdr = null;
var msgbase = new MsgBase(pSubCode);
if (msgbase.open())
{
var expandFields = (typeof(pExpandFields) === "boolean" ? pExpandFields : false);
var getVoteInfo = (typeof(pGetVoteInfo) === "boolean" ? pGetVoteInfo : false);
msgHdr = msgbase.get_msg_header(false, pMsgNum, expandFields, getVoteInfo);
if (msgHdr != null)
hdrLines = msgbase.dump_msg_header(msgHdr);
msgbase.close();
}
return hdrLines;
}
 
// For the DigDistMsgReader class: Takes an absolute message number and returns
// its message index (offset). On error, returns -1.
......@@ -12081,7 +12107,7 @@ function DigDistMsgReader_ListSubBoardsInMsgGroup_Traditional(pGrpIndex, pMarkIn
this.WriteSubBrdListHdrLine(grpIndex);
console.crlf();
printf(this.subBoardListHdrPrintfStr, "Sub #", "Name", "# Posts", "Latest date & time");
console.print("\x01n");
console.attributes = "N";
 
// List each sub-board in the message group.
var searchText = (typeof(pSearchText) == "string" ? pSearchText.toUpperCase() : "");
......@@ -12538,27 +12564,46 @@ function DigDistMsgReader_BuildSubBoardPrintfInfoForGrp(pGrpIndex)
// retrieves will only be retrieved if they exist in the given message header.
//
// Parameters:
// pMsgHdr: A message header
// pSubCodeOrMsgbase: An internal sub-board code of the messagebase, or a MsgBase object representing
// the sub-board the message is in (assumed to be open)
// pMsgNum: The message number of the message headers to retrieve
// pKludgeOnly: Boolean - Whether or not to only get the kludge lines. If false,
// then all header fields will be retrieved.
// pUseColors: Optional boolean: Whether or not to add colors to the strings. Defaults to true.
// pWordWrap: Optional boolean: Whether or not to word-wrap the header lines to the user's
// terminal width. Defaults to true.
// pPrependHdrLabelLines: Optional boolean - Whether or not to prepend a couple lines labeling
// that these are header/kludge lines. Defaults to true.
//
// Return value: An array of strings containing the extended message header information
function DigDistMsgReader_GetExtdMsgHdrInfo(pMsgHdr, pKludgeOnly)
function DigDistMsgReader_GetExtdMsgHdrInfo(pSubCodeOrMsgbase, pMsgNum, pKludgeOnly, pUseColors, pWordWrap, pPrependHdrLabelLines)
{
// If pMsgHdr is not valid, then just return an empty array.
if (typeof(pMsgHdr) != "object")
if (typeof(pMsgNum) !== "number")
return [];
 
// Get the message header with fields expanded so we can get the most info possible.
var msgHdr = this.GetMsgHdrByAbsoluteNum(pMsgHdr.number, true, true);
var msgHdr = null;
if (typeof(pSubCodeOrMsgbase) === "string")
{
var msgbase = new MsgBase(pSubCodeOrMsgbase);
if (msgbase.open())
{
msgHdr = msgbase.get_msg_header(false, pMsgNum, true, true);
msgbase.close();
}
}
else if (typeof(pSubCodeOrMsgbase) === "object" && pSubCodeOrMsgbase.hasOwnProperty("get_msg_header") && pSubCodeOrMsgbase.is_open)
msgHdr = pSubCodeOrMsgbase.get_msg_header(false, pMsgNum, true, true);
if (msgHdr == null)
return [];
// The message header retrieved that way might not have vote information,
// so copy any additional header information from this.hdrsForCurrentSubBoard
// if there's a header there for this message.
if (this.hdrsForCurrentSubBoardByMsgNum.hasOwnProperty(pMsgHdr.number))
if (this.hdrsForCurrentSubBoardByMsgNum.hasOwnProperty(pMsgNum))
{
var tmpHdrIdx = this.hdrsForCurrentSubBoardByMsgNum[pMsgHdr.number];
var tmpHdrIdx = this.hdrsForCurrentSubBoardByMsgNum[pMsgNum];
if (this.hdrsForCurrentSubBoard.hasOwnProperty(tmpHdrIdx))
{
for (var hdrProp in this.hdrsForCurrentSubBoard[tmpHdrIdx])
......@@ -12569,29 +12614,207 @@ function DigDistMsgReader_GetExtdMsgHdrInfo(pMsgHdr, pKludgeOnly)
}
}
 
var kludgeOnly = (typeof(pKludgeOnly) == "boolean" ? pKludgeOnly : false);
var useColors = (typeof(pUseColors) === "boolean" ? pUseColors : true);
var wordWrap = (typeof(pWordWrap) === "boolean" ? pWordWrap : true);
var prependHdrLabelLines = (typeof(pPrependHdrLabelLines) === "boolean" ? pPrependHdrLabelLines : true);
var msgHdrInfoLines = [];
 
var hdrInfoLineFields = [];
var kludgeOnly = (typeof(pKludgeOnly) == "boolean" ? pKludgeOnly : false);
var formatStr;
if (useColors)
formatStr = "\x01n" + this.colors.hdrLineLabelColor + "%s: \x01n" + this.colors.hdrLineValueColor + "%-s";
else
formatStr = "%s: %-s";
// Make an array of objects with 'prop' and 'textArray' fields; this array will be sorted by prop
var msgInfoObjs = [];
for (var prop in msgHdr)
{
if (prop == "field_list")
{
//var fieldListLines = this.GetMsgHdrFieldListText(msgHdr[prop], true);
//for (var i = 0; i < fieldListLines.length; ++i)
// msgHdrInfoLines.push(fieldListLines[i]);
var fieldListObj = GetMsgHdrFieldListObj(msgHdr.field_list);
var fieldListKeys = Object.keys(msgHdr);
fieldListKeys.sort();
for (var fieldListProp in fieldListObj)
//for (var fieldListKeyIdx = 0; fieldListKeyIdx < fieldListKeys.length; ++fieldListKeyIdx)
{
//var fieldListProp = fieldListKeys[fieldListKeyIdx];
//msgHdrInfoLines.push(format(formatStr, fieldListProp, fieldListObj[fieldListProp]));
if (Array.isArray(fieldListObj[fieldListProp]))
{
var tmpTxtLines = [];
if (fieldListObj[fieldListProp].length > 0)
{
//msgHdrInfoLines.push(format(formatStr, fieldListProp, fieldListObj[fieldListProp][0]));
tmpTxtLines.push(format(formatStr, fieldListProp, fieldListObj[fieldListProp][0]));
for (var i = 1; i < fieldListObj[fieldListProp].length; ++i)
{
//msgHdrInfoLines.push(fieldListObj[fieldListProp][i]);
tmpTxtLines.push(fieldListObj[fieldListProp][i]);
}
}
msgInfoObjs.push({
'prop': fieldListProp,
'textArray': tmpTxtLines
});
}
else
{
//msgHdrInfoLines.push(format(formatStr, fieldListProp, fieldListObj[fieldListProp]));
msgInfoObjs.push({
'prop': fieldListProp,
'textArray': [ format(formatStr, fieldListProp, fieldListObj[fieldListProp]) ]
})
}
}
}
else
{
var addIt = kludgeOnly ? MsgHdrPropIsKludgeLine(prop) : true;
if (addIt)
{
// Remove underscores from the property for the label
var propLabel = prop.replace(/_/g, " ");
// Apply good-looking capitalization to the property label
if ((propLabel == "id") || (propLabel == "ftn tid"))
propLabel = propLabel.toUpperCase();
else if (propLabel == "ftn area")
propLabel = "FTN Area";
else if (propLabel == "ftn pid")
propLabel = "Program ID";
else if (propLabel == "thread id")
propLabel = "Thread ID";
else if (propLabel == "attr")
propLabel = "Attributes";
else if (propLabel == "auxattr")
propLabel = "Auxiliary attributes";
else if (propLabel == "netattr")
propLabel = "Network attributes";
else
propLabel = capitalizeFirstChar(propLabel);
// Value
var propValue = "";
if (typeof(msgHdr[prop]) === "function") // Such as get_rfc822_header
continue;
if (prop == "when_written_time") //itemValue = system.timestr(msgHdr.when_written_time);
propValue = system.timestr(msgHdr.when_written_time) + " " + system.zonestr(msgHdr.when_written_zone);
else if (prop == "when_imported_time") //propValue = system.timestr(msgHdr.when_imported_time);
propValue = system.timestr(msgHdr.when_imported_time) + " " + system.zonestr(msgHdr.when_imported_zone);
else if ((prop == "when_imported_zone") || (prop == "when_written_zone"))
propValue = system.zonestr(msgHdr[prop]);
else if (prop == "attr")
propValue = makeMainMsgAttrStr(msgHdr[prop], "None");
else if (prop == "auxattr")
propValue = makeAuxMsgAttrStr(msgHdr[prop], "None");
else if (prop == "netattr")
propValue = makeNetMsgAttrStr(msgHdr[prop], "None");
else
propValue = msgHdr[prop];
if (typeof(propValue) === "string")
{
// Replace tabs with spaces, and strip CRLF characters
// TODO: Should CRLF characters actually be split into separate lines?
propValue = propValue.trim().replace(/\t/g, " ").replace(/[^\x20-\x7E]/g, '');
propValue = propValue.replace(/\r\n/g, "");
propValue = propValue.replace(/\n/g, "").replace(/\r/g, "");
}
//msgHdrInfoLines.push(format(formatStr, propLabel, propValue));
msgInfoObjs.push({
'prop': propLabel,
'textArray': [ format(formatStr, propLabel, propValue) ]
})
}
}
}
// Sort the header lines alphabetically
msgInfoObjs.sort(function(pA, pB)
{
if (pA.prop < pB.prop)
return -1;
else if (pA.prop == pB.prop)
return 0;
else
return 1;
});
for (var i = 0; i < msgInfoObjs.length; ++i)
{
for (var j = 0; j < msgInfoObjs[i].textArray.length; ++j)
msgHdrInfoLines.push(msgInfoObjs[i].textArray[j]);
}
// Free some memory
for (var prop in msgInfoObjs)
delete msgInfoObjs[prop];
// If the caller wants to word-wrap, make sure the header lines aren't too long for the
// user's terminal. And leave a column for the scrollbar.
var hdrInfoLinesWrapped;
if (wordWrap)
{
hdrInfoLinesWrapped = [];
var maxLen = console.screen_columns - 1;
var colorFormatStr = "\x01n" + this.colors.hdrLineLabelColor + "%-s: \x01n" + this.colors.hdrLineValueColor + "%-s";
for (var i = 0; i < msgHdrInfoLines.length; ++i)
{
//var wrappedLines = word_wrap(msgHdrInfoLines[i], maxLen).split("\n");
var wrappedLines = lfexpand(word_wrap(msgHdrInfoLines[i], maxLen)).split("\r\n");
for (var wrappedI = 0; wrappedI < wrappedLines.length; ++wrappedI)
{
if (wrappedLines[wrappedI].length == 0) continue;
hdrInfoLinesWrapped.push(wrappedLines[wrappedI]);
}
}
}
else // No word wrapping
hdrInfoLinesWrapped = msgHdrInfoLines;
// If some info lines were added, then insert a header line & blank line to
// the beginning of the array, and remove the last empty line from the array.
if (hdrInfoLinesWrapped.length > 0)
{
if (prependHdrLabelLines)
{
if (kludgeOnly)
{
hdrInfoLineFields.push({ field: "ftn_msgid", label: "MSG ID:" });
hdrInfoLineFields.push({ field: "X-FTN-MSGID", label: "MSG ID:" });
hdrInfoLineFields.push({ field: "ftn_reply", label: "Reply ID:" });
hdrInfoLineFields.push({ field: "X-FTN-REPLY", label: "Reply ID:" });
hdrInfoLineFields.push({ field: "ftn_area", label: "Area tag:" });
hdrInfoLineFields.push({ field: "X-FTN-AREA", label: "Area tag:" });
hdrInfoLineFields.push({ field: "ftn_flags", label: "Flags:" });
hdrInfoLineFields.push({ field: "ftn_pid", label: "Program ID:" });
hdrInfoLineFields.push({ field: "ftn_tid", label: "Tosser ID:" });
hdrInfoLineFields.push({ field: "X-FTN-TID", label: "Tosser ID:" });
hdrInfoLineFields.push({ field: "X-FTN-Kludge", label: "Kludge:" });
hdrInfoLineFields.push({ field: "X-FTN-SEEN-BY", label: "Seen-By:" });
hdrInfoLineFields.push({ field: "X-FTN-PATH", label: "Path:" });
hdrInfoLineFields.push({ field: "when_written_time", label: "When written time:" });
hdrInfoLineFields.push({ field: "when_imported_time", label: "When imported time:" });
hdrInfoLinesWrapped.splice(0, 0, "\x01n\x01c\x01hMessage Information/Kludge Lines\x01n");
hdrInfoLinesWrapped.splice(1, 0, "\x01n\x01g\x01h--------------------------------\x01n");
}
else
{
hdrInfoLinesWrapped.splice(0, 0, "\x01n\x01c\x01hMessage Headers\x01n");
hdrInfoLinesWrapped.splice(1, 0, "\x01n\x01g\x01h---------------\x01n");
}
}
if (hdrInfoLinesWrapped[hdrInfoLinesWrapped.length-1].length == 0)
hdrInfoLinesWrapped.pop();
}
 
return hdrInfoLinesWrapped;
}
// For the DDMsgReader class: Helper for GetExtdMsgHdrInfo() - Gets
// text lines for the field_list property in a message header
//
// Parameters:
// pHdrFieldList: The value of the field_list property in a message header
// pUseColors: Boolean - Whether or not to add attribute codes to the lines
//
// Return value: An array with text lines for the field list, with colors
// for displaying message header information
function DigDistMsgReader_GetMsgHdrFieldListText(pHdrFieldList, pUseColors)
{
var textLines = [];
// This function returns the number of non-blank lines in a header info array.
//
// Return value: An object with the following properties:
......@@ -12648,21 +12871,12 @@ function DigDistMsgReader_GetExtdMsgHdrInfo(pMsgHdr, pKludgeOnly)
return itemCount;
}
 
// Get the header fields
var addTheField = true;
var customFieldLabel = "";
var propCounter = 1;
for (var prop in msgHdr)
{
addTheField = true;
customFieldLabel = "";
var fieldsAndValues = {};
 
if (prop == "field_list")
{
var hdrFieldLabel = "";
var lastHdrFieldLabel = null;
var addBlankLineAfterIdx = -1;
for (var fieldI = 0; fieldI < msgHdr.field_list.length; ++fieldI)
for (var fieldI = 0; fieldI < pHdrFieldList.length; ++fieldI)
{
// TODO: Some field types can be in the array multiple times but only
// the last is valid. For those, only get the last one:
......@@ -12673,8 +12887,13 @@ function DigDistMsgReader_GetExtdMsgHdrInfo(pMsgHdr, pKludgeOnly)
// 36 (Reply To extended)
// 37 (Reply To position)
// 38 (Reply To Organization)
hdrFieldLabel = "\x01n" + this.colors.hdrLineLabelColor + msgHdrFieldListTypeToLabel(msgHdr.field_list[fieldI].type) + "\x01n";
var infoLineWrapped = msgHdr.field_list[fieldI].data;
if (pUseColors)
hdrFieldLabel = "\x01n" + this.colors.hdrLineLabelColor + msgHdrFieldListTypeToLabel(pHdrFieldList[fieldI].type) + "\x01n";
else
hdrFieldLabel = msgHdrFieldListTypeToLabel(pHdrFieldList[fieldI].type);
hdrFieldLabel = hdrFieldLabel.replace(/\t/g, " ");
fieldsAndValues[hdrFieldLabel] = true; // TODO: Change to the actual text
var infoLineWrapped = pHdrFieldList[fieldI].data;
var infoLineWrappedArray = lfexpand(infoLineWrapped).split("\r\n");
var hdrArrayNonBlankLines = findHdrFieldDataArrayNonBlankLines(infoLineWrappedArray);
if (hdrArrayNonBlankLines.numNonBlankLines > 0)
......@@ -12682,16 +12901,21 @@ function DigDistMsgReader_GetExtdMsgHdrInfo(pMsgHdr, pKludgeOnly)
if (hdrArrayNonBlankLines.numNonBlankLines == 1)
{
var addExtraBlankLineAtEnd = false;
var hdrItem = "\x01n" + this.colors.hdrLineValueColor + infoLineWrappedArray[hdrArrayNonBlankLines.firstNonBlankLineIdx] + "\x01n";
var hdrItem = "";
if (pUseColors)
hdrItem = "\x01n" + this.colors.hdrLineValueColor + infoLineWrappedArray[hdrArrayNonBlankLines.firstNonBlankLineIdx] + "\x01n";
else
hdrItem = infoLineWrappedArray[hdrArrayNonBlankLines.firstNonBlankLineIdx];
hdrItem = hdrItem.replace(/\t/g, " ");
// If the header field label is different, then add it to the
// header info lines
if ((lastHdrFieldLabel == null) || (hdrFieldLabel != lastHdrFieldLabel))
{
var numFieldItemsWithSameType = fieldListCountSameTypes(msgHdr.field_list, fieldI);
var numFieldItemsWithSameType = fieldListCountSameTypes(pHdrFieldList, fieldI);
if (numFieldItemsWithSameType > 1)
{
msgHdrInfoLines.push("");
msgHdrInfoLines.push(hdrFieldLabel);
textLines.push("");
textLines.push(hdrFieldLabel);
addExtraBlankLineAtEnd = true;
addBlankLineAfterIdx = fieldI + numFieldItemsWithSameType - 1;
}
......@@ -12701,19 +12925,22 @@ function DigDistMsgReader_GetExtdMsgHdrInfo(pMsgHdr, pKludgeOnly)
numFieldItemsWithSameType = -1;
}
}
if (strip_ctrl(hdrItem).length < this.msgAreaWidth)
msgHdrInfoLines.push(hdrItem);
textLines.push(hdrItem);
/*
if (console.strlen((hdrItem) < this.msgAreaWidth)
textLines.push(hdrItem);
else
{
// If the header field label is different, then add a blank line
// to the header info lines
if ((lastHdrFieldLabel == null) || (hdrFieldLabel != lastHdrFieldLabel))
msgHdrInfoLines.push("");
msgHdrInfoLines.push(hdrFieldLabel);
msgHdrInfoLines.push(infoLineWrappedArray[hdrArrayNonBlankLines.firstNonBlankLineIdx]);
textLines.push("");
textLines.push(hdrFieldLabel);
//textLines.push(infoLineWrappedArray[hdrArrayNonBlankLines.firstNonBlankLineIdx]);
if ((lastHdrFieldLabel == null) || (hdrFieldLabel != lastHdrFieldLabel))
msgHdrInfoLines.push("");
textLines.push("");
}
*/
}
else
{
......@@ -12721,165 +12948,158 @@ function DigDistMsgReader_GetExtdMsgHdrInfo(pMsgHdr, pKludgeOnly)
// header info lines
if ((lastHdrFieldLabel == null) || (hdrFieldLabel != lastHdrFieldLabel))
{
msgHdrInfoLines.push("");
msgHdrInfoLines.push(hdrFieldLabel);
textLines.push("");
textLines.push(hdrFieldLabel);
}
var infoLineWrapped = msgHdr.field_list[fieldI].data;
var infoLineWrapped = pHdrFieldList[fieldI].data;
var infoLineWrappedArray = lfexpand(infoLineWrapped).split("\r\n");
var preAttrs = pUseColors ? "\x01n" + this.colors.hdrLineValueColor : "";
var postAttrs = pUseColors ? "\x01n" : "";
for (var lineIdx = 0; lineIdx < infoLineWrappedArray.length; ++lineIdx)
{
if (infoLineWrappedArray[lineIdx].length > 0)
msgHdrInfoLines.push(infoLineWrappedArray[lineIdx]);
textLines.push(preAttrs + infoLineWrappedArray[lineIdx] + postAttrs);
}
// If the header field label is different, then add a blank line to the
// header info lines
if ((lastHdrFieldLabel == null) || (hdrFieldLabel != lastHdrFieldLabel))
msgHdrInfoLines.push("");
textLines.push("");
}
if (addBlankLineAfterIdx == fieldI)
msgHdrInfoLines.push("");
textLines.push("");
}
lastHdrFieldLabel = hdrFieldLabel;
}
}
else
{
// See if we should add this field
addTheField = true;
if (hdrInfoLineFields.length > 0)
// For each line, replace tabs with spaces, remove any unprintable characters,
// and in case any line has any CRLF characters, split the lines on CRLF
for (var i = 0; i < textLines.length; ++i)
{
addTheField = false;
for (var infoLineFieldIdx = 0; infoLineFieldIdx < hdrInfoLineFields.length; ++infoLineFieldIdx)
textLines[i] = textLines[i].replace(/\t/g, " ");
//textLines[i] = textLines[i].replace(/\r|\n/g, "");
var array = textLines[i].split("\r\n");
if (array.length > 1)
{
if (prop == hdrInfoLineFields[infoLineFieldIdx].field)
textLines[i] = array[0];
for (var array2Idx = 1; array2Idx < array.length; ++array2Idx)
{
addTheField = true;
customFieldLabel = hdrInfoLineFields[infoLineFieldIdx].label;
break;
if (array[array2Idx].length > 0)
textLines.splice(i+array2Idx, 0, array[array2Idx]);
}
}
//textLines[i] = textLines[i].replace(/[^\x20-\x7E]/g, ''); // Remove unprintable characters
}
if (!addTheField)
continue;
 
var propLabel = "";
if (customFieldLabel.length > 0)
propLabel = "\x01n" + this.colors.hdrLineLabelColor + customFieldLabel + "\x01n";
else
{
// Remove underscores from the property for the label
propLabel = prop.replace(/_/g, " ");
// Apply good-looking capitalization to the property label
if ((propLabel == "id") || (propLabel == "ftn tid"))
propLabel = propLabel.toUpperCase();
else if (propLabel == "ftn area")
propLabel = "FTN Area";
else if (propLabel == "ftn pid")
propLabel = "Program ID";
else if (propLabel == "thread id")
propLabel = "Thread ID";
else if (propLabel == "attr")
propLabel = "Attributes";
else if (propLabel == "auxattr")
propLabel = "Auxiliary attributes";
else if (propLabel == "netattr")
propLabel = "Network attributes";
else
propLabel = capitalizeFirstChar(propLabel);
// Add the label color and trailing colon to the label text
propLabel = "\x01n" + this.colors.hdrLineLabelColor + propLabel + ":\x01n";
return textLines;
}
var infoLineWrapped = word_wrap(msgHdr[prop], this.msgAreaWidth);
var infoLineWrappedArray = lfexpand(infoLineWrapped).split("\r\n");
var itemValue = "";
for (var lineIdx = 0; lineIdx < infoLineWrappedArray.length; ++lineIdx)
// Helper for GetExtdMsgHdrInfo() - For the "field_list"
// property of a message header, this gathers the field labels & values into an
// object (where the object properties are the labels)
//
// Parameters:
// pHdrFieldArray: The value of the field_list property in a message header
// (this is normally an array of objects containing 'type' and
// 'data' properties)
//
// Return value: An object where the properties are the field labels, and the
// value for each is usually an array of strings
function GetMsgHdrFieldListObj(pHdrFieldArray)
{
if (infoLineWrappedArray[lineIdx].length > 0)
if (!Array.isArray(pHdrFieldArray))
return {};
// This function returns the number of non-blank lines in a header info array.
//
// Return value: An object with the following properties:
// numNonBlankLines: The number of non-blank lines in the array
// firstNonBlankLineIdx: The index of the first non-blank line
// lastNonBlankLineIdx: The index of the last non-blank line
function findHdrFieldDataArrayNonBlankLines(pHdrArray)
{
// Set itemValue to the value that should be displayed
if (typeof(msgHdr[prop]) === "function") // Such as get_rfc822_header
continue;
else if (prop == "when_written_time") //itemValue = system.timestr(msgHdr.when_written_time);
itemValue = system.timestr(msgHdr.when_written_time) + " " + system.zonestr(msgHdr.when_written_zone);
else if (prop == "when_imported_time") //itemValue = system.timestr(msgHdr.when_imported_time);
itemValue = system.timestr(msgHdr.when_imported_time) + " " + system.zonestr(msgHdr.when_imported_zone);
else if ((prop == "when_imported_zone") || (prop == "when_written_zone"))
itemValue = system.zonestr(msgHdr[prop]);
else if (prop == "attr")
itemValue = makeMainMsgAttrStr(msgHdr[prop], "None");
else if (prop == "auxattr")
itemValue = makeAuxMsgAttrStr(msgHdr[prop], "None");
else if (prop == "netattr")
itemValue = makeNetMsgAttrStr(msgHdr[prop], "None");
else
itemValue = infoLineWrappedArray[lineIdx];
// Add the value color to the value text
itemValue = "\x01n" + this.colors.hdrLineValueColor + itemValue + "\x01n";
var retObj = {
numNonBlankLines: 0,
firstNonBlankLineIdx: -1,
lastNonBlankLineIdx: -1
};
 
var hdrItem = propLabel + " " + itemValue;
if (strip_ctrl(hdrItem).length < this.msgAreaWidth)
msgHdrInfoLines.push(hdrItem);
else
for (var lineIdx = 0; lineIdx < pHdrArray.length; ++lineIdx)
{
// If this isn't the first header property, then add an empty
// line to the info lines array for spacing
if (propCounter > 1)
msgHdrInfoLines.push("");
msgHdrInfoLines.push(propLabel);
// In case the item value is too long to fit into the
// message reading area, split it and add all the split lines.
var itemValueWrapped = word_wrap(itemValue, this.msgAreaWidth);
var itemValueWrappedArray = lfexpand(itemValueWrapped).split("\r\n");
for (var lineIdx2 = 0; lineIdx2 < itemValueWrappedArray.length; ++lineIdx2)
if (pHdrArray[lineIdx].length > 0)
{
if (itemValueWrappedArray[lineIdx2].length > 0)
msgHdrInfoLines.push(itemValueWrappedArray[lineIdx2]);
}
// If this isn't the first header property, then add an empty
// line to the info lines array for spacing
if (propCounter > 1)
msgHdrInfoLines.push("");
}
}
++retObj.numNonBlankLines;
if (retObj.firstNonBlankLineIdx == -1)
retObj.firstNonBlankLineIdx = lineIdx;
retObj.lastNonBlankLineIdx = lineIdx;
}
}
 
++propCounter;
return retObj;
}
 
// If some info lines were added, then insert a header line & blank line to
// the beginning of the array, and remove the last empty line from the array.
if (msgHdrInfoLines.length > 0)
var fieldListObj = {};
for (var fieldI = 0; fieldI < pHdrFieldArray.length; ++fieldI)
{
if (kludgeOnly)
// TODO: Some field types can be in the array multiple times but only
// the last is valid. For those, only get the last one:
// 32 (Reply To)
// 33 (Reply To agent)
// 34 (Reply To net type)
// 35 (Reply To net address)
// 36 (Reply To extended)
// 37 (Reply To position)
// 38 (Reply To Organization)
var hdrFieldLabel = msgHdrFieldListTypeToLabel(pHdrFieldArray[fieldI].type, false);
hdrFieldLabel = hdrFieldLabel.replace(/\t/g, " ");
// Add the data to fieldListObj, with the label as the property
//fieldListObj[hdrFieldLabel] = pHdrFieldArray[fieldI].data.replace(/\t/g, " ");
// Make the data an array of strings, split based on CRLF characters
var infoLineWrappedArray = lfexpand(pHdrFieldArray[fieldI].data).replace(/\t/g, " ").split("\r\n");
// If any of the strings starts with a date/time ("WhenExported" or "WhenImported"),
// then format it so that the date & time are more readable
for (var i = 0; i < infoLineWrappedArray.length; ++i)
{
if ((infoLineWrappedArray[i].indexOf("WhenExported") == 0 || infoLineWrappedArray[i].indexOf("WhenImported") == 0) && infoLineWrappedArray[i].length >= 28)
{
msgHdrInfoLines.splice(0, 0, "\x01n\x01c\x01hMessage Information/Kludge Lines\x01n");
msgHdrInfoLines.splice(1, 0, "\x01n\x01g\x01h--------------------------------\x01n");
//system.timestr(msgHdr.when_imported_time) + " " + system.zonestr(msgHdr.when_imported_zone)
var firstPart = infoLineWrappedArray[i].substr(0, 14);
var yearStr = infoLineWrappedArray[i].substr(14, 4);
var monthStr = infoLineWrappedArray[i].substr(18, 2);
var dayStr = infoLineWrappedArray[i].substr(20, 2);
var hourStr = infoLineWrappedArray[i].substr(22, 2);
var minStr = infoLineWrappedArray[i].substr(24, 2);
var secStr = infoLineWrappedArray[i].substr(26, 2);
var remaining = infoLineWrappedArray[i].substr(28);
infoLineWrappedArray[i] = format("%s%s-%s-%s %s:%s:%s %s", firstPart, yearStr, monthStr, dayStr, hourStr, minStr, secStr, remaining);
}
}
if (!fieldListObj.hasOwnProperty(hdrFieldLabel))
fieldListObj[hdrFieldLabel] = infoLineWrappedArray;
else
{
msgHdrInfoLines.splice(0, 0, "\x01n\x01c\x01hMessage Headers\x01n");
msgHdrInfoLines.splice(1, 0, "\x01n\x01g\x01h---------------\x01n");
// Append to the data already there
fieldListObj[hdrFieldLabel].push("");
for (var i = 0; i < infoLineWrappedArray.length; ++i)
fieldListObj[hdrFieldLabel].push(infoLineWrappedArray[i]);
}
if (msgHdrInfoLines[msgHdrInfoLines.length-1].length == 0)
msgHdrInfoLines.pop();
}
 
// Make sure the header lines aren't too long for the user's terminal.
// Leave a column for the scrollbar.
var maxLen = console.screen_columns - 1;
var hdrInfoLinesWrapped = [];
for (var i = 0; i < msgHdrInfoLines.length; ++i)
{
var wrappedLines = word_wrap(msgHdrInfoLines[i], maxLen).split("\n");
for (var wrappedI = 0; wrappedI < wrappedLines.length; ++wrappedI)
{
if (console.strlen(wrappedLines[wrappedI]) > 0)
hdrInfoLinesWrapped.push(wrappedLines[wrappedI]);
return fieldListObj;
}
}
return hdrInfoLinesWrapped;
// Returns whether a message header property name can be considered a "kludge line"
function MsgHdrPropIsKludgeLine(pPropName)
{
if (typeof(pPropName) !== "string" || pPropName.length == 0)
return false;
var propNameUpper = pPropName.toUpperCase();
return (propNameUpper == "FTN_MSGID" || propNameUpper == "X-FTN-MSGID" || propNameUpper == "FTN_REPLY" ||
propNameUpper == "X-FTN-REPLY" || propNameUpper == "FTN_AREA" || propNameUpper == "X-FTN-AREA" ||
propNameUpper == "FTN_FLAGS" || propNameUpper == "FTN_PID" || propNameUpper == "FTN_TID" ||
propNameUpper == "X-FTN-TID" || propNameUpper == "X-FTN-KLUDGE" ||
propNameUpper == "X-FTN-SEEN-BY" || propNameUpper == "X-FTN-PATH" ||
propNameUpper == "WHEN_WRITTEN_TIME" || propNameUpper == "WHEN_IMPORTED_TIME");
}
 
// For the DigDistMsgReader class: Gets & prepares message information for
......@@ -14666,32 +14886,15 @@ function DigDistMsgReader_SaveMsgToFile(pMsgHdr, pFilename)
if (msgbase.open())
{
var msgBody = msgbase.get_msg_body(false, pMsgHdr.number, false, false, true, true);
var hdrLines = this.GetExtdMsgHdrInfo(msgbase, pMsgHdr.number, false, false, false, false);
msgbase.close();
 
var messageSaveFile = new File(pFilename);
if (messageSaveFile.open("w"))
{
// Write some header information to the file
if (pMsgHdr.hasOwnProperty("from"))
messageSaveFile.writeln("From: " + pMsgHdr.from);
if (pMsgHdr.hasOwnProperty("to"))
messageSaveFile.writeln(" To: " + pMsgHdr.to);
if (pMsgHdr.hasOwnProperty("subject"))
messageSaveFile.writeln("Subj: " + pMsgHdr.subject);
/*
if (pMsgHdr.hasOwnProperty("when_written_time"))
messageSaveFile.writeln(strftime("Date: %Y-%m-%d %H:%M:%S", msgHeader.when_written_time));
*/
if (pMsgHdr.hasOwnProperty("date"))
messageSaveFile.writeln("Date: " + pMsgHdr.date);
if (pMsgHdr.hasOwnProperty("from_net_addr"))
messageSaveFile.writeln("From net address: " + pMsgHdr.from_net_addr);
if (pMsgHdr.hasOwnProperty("to_net_addr"))
messageSaveFile.writeln("To net address: " + pMsgHdr.to_net_addr);
if (pMsgHdr.hasOwnProperty("id"))
messageSaveFile.writeln("ID: " + pMsgHdr.id);
if (pMsgHdr.hasOwnProperty("reply_id"))
messageSaveFile.writeln("Reply ID: " + pMsgHdr.reply_id);
// Write the header information to the file
for (var i = 0; i < hdrLines.length; ++i)
messageSaveFile.writeln(hdrLines[i]);
messageSaveFile.writeln("===============================");
 
// If the message body has ANSI, then use the Graphic object to strip it
......@@ -18304,98 +18507,280 @@ function msgHdrFieldListTypeToLabel(pFieldListType, pIncludeTrailingColon)
{
// The page at this URL lists the header field types:
// http://synchro.net/docs/smb.html#Header Field Types:
// Some are defined in load/smbdefs.js
 
var fieldTypeLabel = "Unknown (" + pFieldListType.toString() + ")";
var fieldTypeLabel = "";
switch (pFieldListType)
{
case 0: // Sender
case 0x00: // Sender
fieldTypeLabel = "Sender";
break;
case 1: // Sender Agent
case 0x01: // Sender Agent
fieldTypeLabel = "Sender Agent";
break;
case 2: // Sender net type
case 0x02: // Sender net type
fieldTypeLabel = "Sender Net Type";
break;
case 3: // Sender Net Address
case 0x03: // Sender Net Address
fieldTypeLabel = "Sender Net Address";
break;
case 4: // Sender Agent Extension
case 0x04: // Sender Agent Extension
fieldTypeLabel = "Sender Agent Extension";
break;
case 5: // Sending agent (Sender POS)
case 0x05: // Sending agent (Sender POS)
fieldTypeLabel = "Sender Agent";
break;
case 6: // Sender organization
case 0x06: // Sender organization
fieldTypeLabel = "Sender Organization";
break;
case 16: // Author
case 0x10: // Author
fieldTypeLabel = "Author";
break;
case 17: // Author Agent
case 0x11: // Author Agent
fieldTypeLabel = "Author Agent";
break;
case 18: // Author Net Type
case 0x12: // Author Net Type
fieldTypeLabel = "Author Net Type";
break;
case 19: // Author Net Address
case 0x13: // Author Net Address
fieldTypeLabel = "Author Net Address";
break;
case 20: // Author Extension
case 0x14: // Author Extension
fieldTypeLabel = "Author Extension";
break;
case 21: // Author Agent (Author POS)
case 0x15: // Author Agent (Author POS)
fieldTypeLabel = "Author Agent";
break;
case 22: // Author Organization
case 0x16: // Author Organization
fieldTypeLabel = "Author Organization";
break;
case 32: // Reply To
case 0x20: // Reply To
fieldTypeLabel = "Reply To";
break;
case 33: // Reply To agent
case 0x21: // Reply To agent
fieldTypeLabel = "Reply To Agent";
break;
case 34: // Reply To net type
case 0x22: // Reply To net type
fieldTypeLabel = "Reply To net type";
break;
case 35: // Reply To net address
case 0x23: // Reply To net address
fieldTypeLabel = "Reply To net address";
break;
case 36: // Reply To extension
case 0x24: // Reply To extension
fieldTypeLabel = "Reply To (extended)";
break;
case 37: // Reply To position
case 0x25: // Reply To position
fieldTypeLabel = "Reply To position";
break;
case 38: // Reply To organization (0x26 hex)
case 0x26: // Reply To organization
fieldTypeLabel = "Reply To organization";
break;
case 48: // Recipient (0x30 hex)
//case 48: // Recipient (0x30 hex)
case 0x30:
fieldTypeLabel = "Recipient";
break;
case 162: // Seen-by
case 0x31:// Recipient agent
fieldTypeLabel = "Recipient Agent";
break;
case 0x32: // Recipient net type
fieldTypeLabel = "Recipient Net Type";
break;
case 0x33:
fieldTypeLabel = "Recipient Net Address";
break;
case 0x34:
fieldTypeLabel = "Recipient Extension";
break;
case 0x35:
fieldTypeLabel = "Recipient Position";
break;
case 0x36:
fieldTypeLabel = "Recipient Organization";
break;
case 0x40:
fieldTypeLabel = "Forward To";
break;
case 0x41:
fieldTypeLabel = "Forward To Agent";
break
case 0x42:
fieldTypeLabel = "Forward To Net Type";
break
case 0x43:
fieldTypeLabel = "Forward To Net Address";
break
case 0x44:
fieldTypeLabel = "Forward To Extension";
break
case 0x45:
fieldTypeLabel = "Forward To Position";
break
case 0x46:
fieldTypeLabel = "Forward To Organization";
break
case 0x48:
fieldTypeLabel = "Forwarded date/time";
break
case 0x50:
fieldTypeLabel = "Received By";
break
case 0x51:
fieldTypeLabel = "Received By Agent";
break
case 0x52:
fieldTypeLabel = "Received By Net Type";
break
case 0x53:
fieldTypeLabel = "Received By Net Address";
break
case 0x54:
fieldTypeLabel = "Received By Extension";
break
case 0x55:
fieldTypeLabel = "Received By Position";
break
case 0x56:
fieldTypeLabel = "Received By Organization";
break
case 0x58:
fieldTypeLabel = "Received date/time";
break
case 0x60:
fieldTypeLabel = "Subject";
break
case 0x61:
fieldTypeLabel = "Summary";
break
case 0x62:
fieldTypeLabel = "Comment";
break
case 0x63:
fieldTypeLabel = "Carbon Copy";
break
case 0x64:
fieldTypeLabel = "Group";
break
case 0x65:
fieldTypeLabel = "Expiration date/time";
break
case 0x66:
fieldTypeLabel = "Priority";
break
case 0x69:
fieldTypeLabel = "Tags";
break;
case 0x70:
fieldTypeLabel = "File attachment name/specification";
break
case 0x71:
fieldTypeLabel = "Destination filename";
break
case 0x72:
fieldTypeLabel = "File attachment list";
break
case 0x73:
fieldTypeLabel = "Destination file list";
break
case 0x74:
fieldTypeLabel = "File request name";
break
case 0x75:
fieldTypeLabel = "File password";
break
case 0x76:
fieldTypeLabel = "File request list";
break
case 0x77:
fieldTypeLabel = "File password list";
break
case 0x80:
fieldTypeLabel = "Image attachment type & filename";
break
case 0x81:
fieldTypeLabel = "Animation attachment type & filename";
break
case 0x82:
fieldTypeLabel = "Font attachment type & filename";
break
case 0x83:
fieldTypeLabel = "Sound attachment type & filename";
break
case 0x84:
fieldTypeLabel = "Presentation attachment type & filename";
break
case 0x85:
fieldTypeLabel = "Video attachment type & filename";
break
case 0x86:
fieldTypeLabel = "Application data type & filename";
break
case 0x90:
fieldTypeLabel = "Image file trigger";
break
case 0x91:
fieldTypeLabel = "Animation file trigger";
break;
case 0x92:
fieldTypeLabel = "Font file trigger";
break;
case 0x93:
fieldTypeLabel = "Sound file trigger";
break;
case 0x94:
fieldTypeLabel = "Presentation file trigger";
break;
case 0x95:
fieldTypeLabel = "Video file trigger";
break;
case 0x96:
fieldTypeLabel = "Application data trigger";
break;
case 0xA0:
fieldTypeLabel = "FIDO control";
break;
case 0xA1:
fieldTypeLabel = "FIDO area";
break;
case 0xA2: // Seen-by
fieldTypeLabel = "Seen-by";
break;
case 163: // Path
fieldTypeLabel = "Path";
case 0xA3: // FIDO Path
fieldTypeLabel = "FIDO Path";
break;
case 176: // RFCC822 Header
case 0xA4:
fieldTypeLabel = "FIDO MSGID";
break;
case 0xA5:
fieldTypeLabel = "FIDO Reply ID";
break;
case 0xA6:
fieldTypeLabel = "FIDO Program ID";
break;
case 0xA7:
fieldTypeLabel = "FIDO Flags";
break;
case 0xB0: // RFCC822 Header
fieldTypeLabel = "RFCC822 Header";
break;
case 177: // RFC822 MSGID
case 0xB1: // RFC822 MSGID
fieldTypeLabel = "RFC822 MSGID";
break;
case 178: // RFC822 REPLYID
case 0xB2: // RFC822 REPLYID
fieldTypeLabel = "RFC822 REPLYID";
break;
case 240: // UNKNOWN
case 0xD3:
fieldTypeLabel = "SMTP Received";
break;
case 0xE0:
fieldTypeLabel = "Poll answer";
break;
case 0xF0: // UNKNOWN
fieldTypeLabel = "UNKNOWN";
break;
case 241: // UNKNOWNASCII
case 0xF1: // UNKNOWNASCII
fieldTypeLabel = "UNKNOWN (ASCII)";
break;
case 255:
case 0xFF:
fieldTypeLabel = "UNUSED";
break;
default:
......@@ -21700,6 +22085,7 @@ function clearScreenRectangle(pX, pY, pWidth, pHeight)
}
}
 
///////////////////////////////////////////////////////////////////////////////////
 
// For debugging: Writes some text on the screen at a given location with a given pause.
......
Digital Distortion Message Reader
Version 1.73
Release date: 2023-04-17
Version 1.73a
Release date: 2023-04-25
by
......
......@@ -5,6 +5,9 @@ Revision History (change log)
=============================
Version Date Description
------- ---- -----------
1.73a 2023-04-25 For viewing message headers, now all message header
information is displayed & sorted alphabetically by field
name (same with saving a message to a file).
1.73 2023-04-17 Bug fix: When getting header lines to view, ensure the
header lines are not too wide for the user's terminal.
Header lines that are too long will be split into no more
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment