Newer
Older
// pHighlight: Boolean - Whether or not to write the line highlighted.
Eric Oulashin
committed
function DDMsgAreaChooser_GetMsgSubBrdLine(pGrpIndex, pSubIndex, pHighlight)
{
var subBoardLine = "\1n";
// Write the highlight background color if pHighlight is true.
if (pHighlight)
subBoardLine += this.colors.bkgHighlight;
// Determine if pGrpIndex and pSubIndex specify the user's
// currently-selected group and sub-board.
var currentSub = false;
if ((typeof(bbs.cursub_code) == "string") && (bbs.cursub_code != ""))
currentSub = ((pGrpIndex == msg_area.sub[bbs.cursub_code].grp_index) && (pSubIndex == msg_area.sub[bbs.cursub_code].index));
// Open the current sub-board with the msgBase object (so that we can get
// the date & time of the last imported message).
var msgBase = new MsgBase(msg_area.grp_list[pGrpIndex].sub_list[pSubIndex].code);
if (msgBase.open())
{

Eric Oulashin
committed
var numMsgs = numReadableMsgs(msgBase, msg_area.grp_list[pGrpIndex].sub_list[pSubIndex].code);
var newestDate = {}; // For storing the date of the newest post
// Get the date & time when the last message was imported.
var msgHeader = getLatestMsgHdr(msg_area.grp_list[pGrpIndex].sub_list[pSubIndex].code);
if (msgHeader != null)
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
{
// Construct the date & time strings of the latest post
if (this.showImportDates)
{
newestDate.date = strftime("%Y-%m-%d", msgHeader.when_imported_time);
newestDate.time = strftime("%H:%M:%S", msgHeader.when_imported_time);
}
else
{
var msgWrittenLocalBBSTime = msgWrittenTimeToLocalBBSTime(msgHeader);
if (msgWrittenLocalBBSTime != -1)
{
newestDate.date = strftime("%Y-%m-%d", msgWrittenLocalBBSTime);
newestDate.time = strftime("%H:%M:%S", msgWrittenLocalBBSTime);
}
else
{
newestDate.date = strftime("%Y-%m-%d", msgHeader.when_written_time);
newestDate.time = strftime("%H:%M:%S", msgHeader.when_written_time);
}
}
}
else
newestDate.date = newestDate.time = "";
// Print the sub-board information line.
var lengthsObj = this.GetSubNameLenAndNumMsgsLen(pGrpIndex);
subBoardLine += (currentSub ? this.colors.areaMark + "*" : " ");
if (this.showDatesInSubBoardList)
{
subBoardLine += format((pHighlight ? this.subBoardListPrintfInfo[pGrpIndex].highlightPrintfStr : this.subBoardListPrintfInfo[pGrpIndex].printfStr),
+(pSubIndex+1), msg_area.grp_list[pGrpIndex].sub_list[pSubIndex].description.substr(0, lengthsObj.nameLen),
numMsgs, newestDate.date, newestDate.time);
}
else
{
subBoardLine += format((pHighlight ? this.subBoardListPrintfInfo[pGrpIndex].highlightPrintfStr : this.subBoardListPrintfInfo[pGrpIndex].printfStr),
+(pSubIndex+1), msg_area.grp_list[pGrpIndex].sub_list[pSubIndex].description.substr(0, lengthsObj.nameLen),
numMsgs);
}
msgBase.close();
}
return subBoardLine;
}
///////////////////////////////////////////////
// Other functions for the msg. area chooser //
///////////////////////////////////////////////
// For the DDMsgAreaChooser class: Reads the configuration file.
function DDMsgAreaChooser_ReadConfigFile()
{
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
// Determine the script's startup directory.
// This code is a trick that was created by Deuce, suggested by Rob Swindell
// as a way to detect which directory the script was executed in. I've
// shortened the code a little.
var startup_path = '.';
try { throw dig.dist(dist); } catch(e) { startup_path = e.fileName; }
startup_path = backslash(startup_path.replace(/[\/\\][^\/\\]*$/,''));
// Open the configuration file
var cfgFile = new File(startup_path + "DDMsgAreaChooser.cfg");
if (cfgFile.open("r"))
{
var settingsMode = "behavior";
var fileLine = null; // A line read from the file
var equalsPos = 0; // Position of a = in the line
var commentPos = 0; // Position of the start of a comment
var setting = null; // A setting name (string)
var settingUpper = null; // Upper-case setting name
var value = null; // A value for a setting (string)
while (!cfgFile.eof)
{
// Read the next line from the config file.
fileLine = cfgFile.readln(2048);
// fileLine should be a string, but I've seen some cases
// where it isn't, so check its type.
if (typeof(fileLine) != "string")
continue;
// If the line starts with with a semicolon (the comment
// character) or is blank, then skip it.
if ((fileLine.substr(0, 1) == ";") || (fileLine.length == 0))
continue;
// If in the "behavior" section, then set the behavior-related variables.
if (fileLine.toUpperCase() == "[BEHAVIOR]")
{
settingsMode = "behavior";
continue;
}
else if (fileLine.toUpperCase() == "[COLORS]")
{
settingsMode = "colors";
continue;
}
// If the line has a semicolon anywhere in it, then remove
// everything from the semicolon onward.
commentPos = fileLine.indexOf(";");
if (commentPos > -1)
fileLine = fileLine.substr(0, commentPos);
// Look for an equals sign, and if found, separate the line
// into the setting name (before the =) and the value (after the
// equals sign).
equalsPos = fileLine.indexOf("=");
if (equalsPos > 0)
{
// Read the setting & value, and trim leading & trailing spaces.
setting = trimSpaces(fileLine.substr(0, equalsPos), true, false, true);
settingUpper = setting.toUpperCase();
value = trimSpaces(fileLine.substr(equalsPos+1), true, false, true);
if (settingsMode == "behavior")
{
// Set the appropriate value in the settings object.
if (settingUpper == "USELIGHTBARINTERFACE")
this.useLightbarInterface = (value.toUpperCase() == "TRUE");
else if (settingUpper == "SHOWIMPORTDATES")
this.showImportDates = (value.toUpperCase() == "TRUE");
else if (settingUpper == "AREACHOOSERHDRFILENAMEBASE")
this.areaChooserHdrFilenameBase = value;
else if (settingUpper == "AREACHOOSERHDRMAXLINES")
{
var maxNumLines = +value;
if (maxNumLines > 0)
this.areaChooserHdrMaxLines = maxNumLines;
}
else if (settingUpper == "SHOWDATESINSUBBOARDLIST")
this.showDatesInSubBoardList = (value.toUpperCase() == "TRUE");
Eric Oulashin
committed
else if (settingUpper == "USESUBCOLLAPSING")
this.useSubCollapsing = (value.toUpperCase() == "TRUE");
else if (settingUpper == "SUBCOLLAPSESEPARATOR")
{
if (value.length > 0)
this.subCollapseSeparator = value;
}
}
else if (settingsMode == "colors")
this.colors[setting] = value;
}
}
cfgFile.close();
}
}
// For the DDMsgAreaChooser class: Shows the help screen
//
// Parameters:
// pLightbar: Boolean - Whether or not to show lightbar help. If
// false, then this function will show regular help.
// pClearScreen: Boolean - Whether or not to clear the screen first
function DDMsgAreaChooser_showHelpScreen(pLightbar, pClearScreen)
{
if (pClearScreen)
console.clear("\1n");
else
console.print("\1n");
console.center("\1c\1hDigital Distortion Message Area Chooser");
Eric Oulashin
committed
var lineStr = "";
for (var i = 0; i < 39; ++i)
lineStr += HORIZONTAL_SINGLE;
console.attributes = "HK";
console.center(lineStr);
console.center("\1n\1cVersion \1g" + DD_MSG_AREA_CHOOSER_VERSION +
" \1w\1h(\1b" + DD_MSG_AREA_CHOOSER_VER_DATE + "\1w)");
console.crlf();
console.print("\1n\1cFirst, a listing of message groups is displayed. One can be chosen by typing");
console.crlf();
console.print("its number. Then, a listing of sub-boards within that message group will be");
console.crlf();
console.print("shown, and one can be chosen by typing its number.");
console.crlf();
if (pLightbar)
{
console.crlf();
console.print("\1n\1cThe lightbar interface also allows up & down navigation through the lists:");
console.crlf();
Eric Oulashin
committed
console.attributes = "HK";
for (var i = 0; i < 74; ++i)
console.print(HORIZONTAL_SINGLE);
console.crlf();
console.print("\1n\1c\1hUp arrow\1n\1c: Move the cursor up one line");
console.crlf();
console.print("\1hDown arrow\1n\1c: Move the cursor down one line");
console.crlf();
console.print("\1hENTER\1n\1c: Select the current group/sub-board");
console.crlf();
console.print("\1hHOME\1n\1c: Go to the first item on the screen");
console.crlf();
console.print("\1hEND\1n\1c: Go to the last item on the screen");
console.crlf();
console.print("\1hPageUp\1n\1c/\1hPageDown\1n\1c: Go to the previous/next page");
console.crlf();
console.print("\1hF\1n\1c/\1hL\1n\1c: Go to the first/last page");
console.crlf();
console.print("\1h/\1n\1c or \1hCtrl-F\1n\1c: Find by name/description");
console.crlf();
console.print("\1hN\1n\1c: Next search result (after a find)");
console.crlf();
}
console.crlf();
console.print("Additional keyboard commands:");
console.crlf();
Eric Oulashin
committed
console.attributes = "HK";
for (var i = 0; i < 29; ++i)
console.print(HORIZONTAL_SINGLE);
console.crlf();
console.print("\1n\1c\1h?\1n\1c: Show this help screen");
console.crlf();
console.print("\1hQ\1n\1c: Quit");
console.crlf();
}
// For the DDMsgAreaChooser class: Builds sub-board printf format information
// for a message group. The widths of the description & # messages columns
// are calculated based on the greatest number of messages in a sub-board for
// the message group.
//
// Parameters:
// pGrpIndex: The index of the message group
Eric Oulashin
committed
function DDMsgAreaChooser_BuildSubBoardPrintfInfoForGrp(pGrpIndex)
{
// If the array of sub-board printf strings doesn't contain the printf
// strings for this message group, then figure out the largest number
// of messages in the message group and add the printf strings.
if (typeof(this.subBoardListPrintfInfo[pGrpIndex]) == "undefined")
{
var greatestNumMsgs = getGreatestNumMsgs(pGrpIndex);
this.subBoardListPrintfInfo[pGrpIndex] = {};
this.subBoardListPrintfInfo[pGrpIndex].numMsgsLen = greatestNumMsgs.toString().length;
Eric Oulashin
committed
var numMsgsLen = this.subBoardListPrintfInfo[pGrpIndex].numMsgsLen;
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
// Sub-board name length: With a # items length of 4, this should be
// 47 for an 80-column display.
this.subBoardListPrintfInfo[pGrpIndex].nameLen = console.screen_columns - this.areaNumLen - this.subBoardListPrintfInfo[pGrpIndex].numMsgsLen - 5;
if (this.showDatesInSubBoardList)
this.subBoardListPrintfInfo[pGrpIndex].nameLen -= (this.dateLen + this.timeLen + 2);
// Create the printf strings
this.subBoardListPrintfInfo[pGrpIndex].printfStr = " " + this.colors.areaNum
+ "%" + this.areaNumLen + "d "
+ this.colors.desc + "%-"
+ this.subBoardListPrintfInfo[pGrpIndex].nameLen + "s "
+ this.colors.numItems + "%"
+ this.subBoardListPrintfInfo[pGrpIndex].numMsgsLen + "d";
if (this.showDatesInSubBoardList)
{
this.subBoardListPrintfInfo[pGrpIndex].printfStr += " "
+ this.colors.latestDate + "%" + this.dateLen + "s "
+ this.colors.latestTime + "%" + this.timeLen + "s";
}
this.subBoardListPrintfInfo[pGrpIndex].highlightPrintfStr = "\1n" + this.colors.bkgHighlight
+ " " + "\1n"
+ this.colors.bkgHighlight
+ this.colors.areaNumHighlight
+ "%" + this.areaNumLen + "d \1n"
+ this.colors.bkgHighlight
+ this.colors.descHighlight + "%-"
+ this.subBoardListPrintfInfo[pGrpIndex].nameLen + "s \1n"
+ this.colors.bkgHighlight
+ this.colors.numItemsHighlight + "%"
+ this.subBoardListPrintfInfo[pGrpIndex].numMsgsLen + "d";
if (this.showDatesInSubBoardList)
{
this.subBoardListPrintfInfo[pGrpIndex].highlightPrintfStr += " \1n"
+ this.colors.bkgHighlight
+ this.colors.dateHighlight + "%" + this.dateLen + "s \1n"
+ this.colors.bkgHighlight
+ this.colors.timeHighlight + "%" + this.timeLen + "s\1n";
}
}
}
// For the DDMsgAreaChooser class: Displays the area chooser header
//
// Parameters:
// pStartScreenRow: The row on the screen at which to start displaying the
// header information. Will be used if the user's terminal
// supports ANSI.
// pClearRowsFirst: Optional boolean - Whether or not to clear the rows first.
// Defaults to true. Only valid if the user's terminal supports
// ANSI.
function DDMsgAreaChooser_DisplayAreaChgHdr(pStartScreenRow, pClearRowsFirst)
{
if (this.areaChangeHdrLines == null)
return;
if (this.areaChangeHdrLines.length == 0)
return;
// If the user's terminal supports ANSI and pStartScreenRow is a number, then
// we can move the cursor and display the header where specified.
Eric Oulashin
committed
if (console.term_supports(USER_ANSI) && (typeof(pStartScreenRow) === "number"))
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
{
// If specified to clear the rows first, then do so.
var screenX = 1;
var screenY = pStartScreenRow;
var clearRowsFirst = (typeof(pClearRowsFirst) == "boolean" ? pClearRowsFirst : true);
if (clearRowsFirst)
{
console.print("\1n");
for (var hdrFileIdx = 0; hdrFileIdx < this.areaChangeHdrLines.length; ++hdrFileIdx)
{
console.gotoxy(screenX, screenY++);
console.cleartoeol();
}
}
// Display the header starting on the first column and the given screen row.
screenX = 1;
screenY = pStartScreenRow;
for (var hdrFileIdx = 0; hdrFileIdx < this.areaChangeHdrLines.length; ++hdrFileIdx)
{
console.gotoxy(screenX, screenY++);
console.print(this.areaChangeHdrLines[hdrFileIdx]);
//console.putmsg(this.areaChangeHdrLines[hdrFileIdx]);
//console.cleartoeol("\1n"); // Shouldn't do this, as it resets color attributes
}
}
else
{
// The user's terminal doesn't support ANSI or pStartScreenRow is not a
// number - So just output the header lines.
for (var hdrFileIdx = 0; hdrFileIdx < this.areaChangeHdrLines.length; ++hdrFileIdx)
{
console.print(this.areaChangeHdrLines[hdrFileIdx]);
//console.putmsg(this.areaChangeHdrLines[hdrFileIdx]);
//console.cleartoeol("\1n"); // Shouldn't do this, as it resets color attributes
console.crlf();
}
}
}
// For the DDMsgAreaChooser class: Writes a temporary error message at the key help line
// for lightbar mode.
//
// Parameters:
// pErrorMsg: The error message to write
// pRefreshHelpLine: Whether or not to re-draw the help line on the screen
function DDMsgAreaChooser_WriteLightbarKeyHelpErrorMsg(pErrorMsg, pRefreshHelpLine)
{
console.gotoxy(1, console.screen_rows);
console.cleartoeol("\1n");
console.gotoxy(1, console.screen_rows);
console.print("\1y\1h" + pErrorMsg + "\1n");
mswait(ERROR_WAIT_MS);
if (pRefreshHelpLine)
this.WriteKeyHelpLine();
}
Eric Oulashin
committed
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
// For the DDMsgAreaChooser class: Sets up the group_list array according to
// the sub-board collapse separator
function DDMsgAreaChooser_SetUpGrpListWithCollapsedSubBoards()
{
// Returns a default object for a message group
function defaultMsgGrpObj()
{
return {
index: 0,
number: 0,
name: "",
description: "",
ars: 0,
sub_list: []
};
}
// Returns a default object for a message sub-board. It can potentially
// contain its own list of sub-boards within the original subboard.
function defaultSubBoardObj()
{
return {
index: 0,
number: 0,
grp_index: 0,
grp_number: 0,
grp_name: 0,
code: "",
name: "",
grp_name: "",
description: "",
ars: 0,
settings: 0,
sub_subboard_list: []
};
}
function subBoardObjFromOfficialSubBoard(pGrpIdx, pSubIdx)
{
var subObj = defaultSubBoardObj();
for (var prop in subObj)
{
if (prop != "sub_subboard_list") // Doesn't exist in the official dir objects
subObj[prop] = msg_area.grp_list[pGrpIdx].sub_list[pSubIdx][prop];
}
return subObj;
}
if (this.group_list.length == 0)
{
// Copy some of the information from msg_area.grp_list
for (var grpIdx = 0; grpIdx < msg_area.grp_list.length; ++grpIdx)
{
// Go through sub_list in the curent message group, and for
// any that have the collapse separator, add only one copy
// of the name to the sub_list property for this group.
// First, we'll have to see if multiple sub-boards with
// the collapse separator have the same prefix.
// dirDescs is an object indexed by sub-board description,
// and the value will be how many times it was seen.
var subBoardDescs = {};
Eric Oulashin
committed
// First, count the number of sub-boards that have the separator.
// If all of the group's sub-boards have the separator, then we
// won't collapse the sub-boards.
var numSubBoardsWithSeparator = 0;
Eric Oulashin
committed
for (var subIdx = 0; subIdx < msg_area.grp_list[grpIdx].sub_list.length; ++subIdx)
{
var subBoardDesc = msg_area.grp_list[grpIdx].sub_list[subIdx].description;
Eric Oulashin
committed
if (subBoardDesc.indexOf(this.subCollapseSeparator) > -1)
++numSubBoardsWithSeparator;
}
// Whether or not to use sub-board collapsing for this group
var collapseThisGroup = (numSubBoardsWithSeparator > 0 && numSubBoardsWithSeparator < msg_area.grp_list[grpIdx].sub_list.length);
// Go through and build the group list
for (var subIdx = 0; subIdx < msg_area.grp_list[grpIdx].sub_list.length; ++subIdx)
{
var subBoardDesc = msg_area.grp_list[grpIdx].sub_list[subIdx].description;
if (collapseThisGroup)
{
var sepIdx = subBoardDesc.indexOf(this.subCollapseSeparator);
if (sepIdx > -1)
subBoardDesc = truncsp(subBoardDesc.substr(0, sepIdx)); // Remove trailing whitespace
}
Eric Oulashin
committed
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
if (subBoardDescs.hasOwnProperty(subBoardDesc))
subBoardDescs[subBoardDesc] += 1;
else
subBoardDescs[subBoardDesc] = 1;
}
// Create an initial file group object for this file group (except
// for sub_list, which we will build ourselves for this group)
var msgGrpObj = defaultMsgGrpObj();
for (var prop in msgGrpObj)
{
if (prop != "sub_list")
msgGrpObj[prop] = msg_area.grp_list[grpIdx][prop];
}
// Go through the dirs in this group again. For each sub-board:
// If its whole description exists in subBoardDescs, then just add it to
// the sub_list array. Otherwise:
// If a collapse seprator exists, then split on that and see if the
// prefix was seen more than once. If so, then add the separate
// subdirs as their own items in the sub_list array. Otherwise,
// add the single sub-board to the sub_list array with the whole
// description.
var addedPrefixDescriptionDirs = {};
for (var subIdx = 0; subIdx < msg_area.grp_list[grpIdx].sub_list.length; ++subIdx)
{
var subBoardDesc = msg_area.grp_list[grpIdx].sub_list[subIdx].description;
if (subBoardDescs.hasOwnProperty(subBoardDesc))
msgGrpObj.sub_list.push(subBoardObjFromOfficialSubBoard(grpIdx, subIdx));
else
{
var subSubDirDesc = "";
var sepIdx = subBoardDesc.indexOf(this.subCollapseSeparator);
if (sepIdx > -1)
{
subBoardDesc = truncsp(subBoardDesc.substr(0, sepIdx)); // Remove trailing whitespace
// If it has been seen more than once, then the description should
// be the prefix description
if (subBoardDescs[subBoardDesc] > 1)
{
var addedSubIdx = msgGrpObj.sub_list.length - 1;
if (!addedPrefixDescriptionDirs.hasOwnProperty(subBoardDesc))
{
// Add it to sub_list
msgGrpObj.sub_list.push(subBoardObjFromOfficialSubBoard(grpIdx, subIdx));
addedSubIdx = msgGrpObj.sub_list.length - 1;
msgGrpObj.sub_list[addedSubIdx].description = subBoardDesc;
addedPrefixDescriptionDirs[subBoardDesc] = true;
}
// Add the sub-subboard to the sub-board's sub-subboard list
// Using skipsp() to strip leading whitespace
subSubDirDesc = skipsp(msg_area.grp_list[grpIdx].sub_list[subIdx].description.substr(sepIdx+1));
msgGrpObj.sub_list[addedSubIdx].sub_subboard_list.push({
description: subSubDirDesc,
code: msg_area.grp_list[grpIdx].sub_list[subIdx].code,
index: msg_area.grp_list[grpIdx].sub_list[subIdx].index,
grp_index: msg_area.grp_list[grpIdx].sub_list[subIdx].grp_index
});
}
else // Add it with the full description
msgGrpObj.sub_list.push(subBoardObjFromOfficialSubBoard(grpIdx, subIdx));
}
if (subBoardDescs.hasOwnProperty(subBoardDesc))
subBoardDescs[subBoardDescs] += 1;
else
subBoardDescs[subBoardDescs] = 1;
}
}
this.group_list.push(msgGrpObj);
}
}
}
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
// Removes multiple, leading, and/or trailing spaces.
// The search & replace regular expressions used in this
// function came from the following URL:
// http://qodo.co.uk/blog/javascript-trim-leading-and-trailing-spaces
//
// Parameters:
// pString: The string to trim
// pLeading: Whether or not to trim leading spaces (optional, defaults to true)
// pMultiple: Whether or not to trim multiple spaces (optional, defaults to true)
// pTrailing: Whether or not to trim trailing spaces (optional, defaults to true)
function trimSpaces(pString, pLeading, pMultiple, pTrailing)
{
var leading = true;
var multiple = true;
var trailing = true;
if(typeof(pLeading) != "undefined")
leading = pLeading;
if(typeof(pMultiple) != "undefined")
multiple = pMultiple;
if(typeof(pTrailing) != "undefined")
trailing = pTrailing;
// To remove both leading & trailing spaces:
//pString = pString.replace(/(^\s*)|(\s*$)/gi,"");
if (leading)
pString = pString.replace(/(^\s*)/gi,"");
if (multiple)
pString = pString.replace(/[ ]{2,}/gi," ");
if (trailing)
pString = pString.replace(/(\s*$)/gi,"");
return pString;
}
// Calculates & returns a page number.
//
// Parameters:
// pTopIndex: The index (0-based) of the topmost item on the page
// pNumPerPage: The number of items per page
//
// Return value: The page number
function calcPageNum(pTopIndex, pNumPerPage)
{
return ((pTopIndex / pNumPerPage) + 1);
}
// Returns the greatest number of messages of all sub-boards within
// a message group.
//
// Parameters:
// pGrpIndex: The index of the message group
//
// Returns: The greatest number of messages of all sub-boards within
// the message group
function getGreatestNumMsgs(pGrpIndex)
{
// Sanity checking
Eric Oulashin
committed
if (typeof(pGrpIndex) !== "number")
return 0;
if (typeof(msg_area.grp_list[pGrpIndex]) == "undefined")
return 0;
var greatestNumMsgs = 0;
var msgBase = null;
for (var subIndex = 0; subIndex < msg_area.grp_list[pGrpIndex].sub_list.length; ++subIndex)
{
msgBase = new MsgBase(msg_area.grp_list[pGrpIndex].sub_list[subIndex].code);
if (msgBase == null) continue;
if (msgBase.open())
{

Eric Oulashin
committed
var numMsgs = numReadableMsgs(msgBase, msg_area.grp_list[pGrpIndex].sub_list[subIndex].code);
Eric Oulashin
committed
if (numMsgs > greatestNumMsgs)
greatestNumMsgs = numMsgs;
msgBase.close();
}
}
return greatestNumMsgs;
}
Eric Oulashin
committed
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
// Returns the number of messages in a sub-board
//
// Parameters:
// pSubCode: The internal code of the sub-board
//
// Return value: The number of messages in the sub-board, or 0 if can't be opened
function getNumMsgsInSubBoard(pSubCode)
{
if (typeof(pSubCode) !== "string")
return 0;
var numMsgs = 0;
var msgBase = new MsgBase(pSubCode);
if (msgBase != null && msgBase.open())
{
numMsgs = msgBase.total_msgs;
msgBase.close();
}
delete msgBase; // Free some memory?
return numMsgs;
}
// Inputs a keypress from the user and handles some ESC-based
// characters such as PageUp, PageDown, and ESC. If PageUp
// or PageDown are pressed, this function will return the
// string "\1PgUp" (KEY_PAGE_UP) or "\1Pgdn" (KEY_PAGE_DOWN),
// respectively. Also, F1-F5 will be returned as "\1F1"
// through "\1F5", respectively.
// Thanks goes to Psi-Jack for the original impementation
// of this function.
//
// Parameters:
// pGetKeyMode: Optional - The mode bits for console.getkey().
// If not specified, K_NONE will be used.
//
// Return value: The user's keypress
function getKeyWithESCChars(pGetKeyMode)
{
var getKeyMode = K_NONE;
Eric Oulashin
committed
if (typeof(pGetKeyMode) === "number")
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
getKeyMode = pGetKeyMode;
var userInput = console.getkey(getKeyMode);
if (userInput == KEY_ESC)
{
switch (console.inkey(K_NOECHO|K_NOSPIN, 2))
{
case '[':
switch (console.inkey(K_NOECHO|K_NOSPIN, 2))
{
case 'V':
userInput = KEY_PAGE_UP;
break;
case 'U':
userInput = KEY_PAGE_DOWN;
break;
}
break;
case 'O':
switch (console.inkey(K_NOECHO|K_NOSPIN, 2))
{
case 'P':
userInput = "\1F1";
break;
case 'Q':
userInput = "\1F2";
break;
case 'R':
userInput = "\1F3";
break;
case 'S':
userInput = "\1F4";
break;
case 't':
userInput = "\1F5";
break;
}
default:
break;
}
}
return userInput;
}
// Loads a text file (an .ans or .asc) into an array. This will first look for
// an .ans version, and if exists, convert to Synchronet colors before loading
// it. If an .ans doesn't exist, this will look for an .asc version.
//
// Parameters:
// pFilenameBase: The filename without the extension
// pMaxNumLines: Optional - The maximum number of lines to load from the text file
//
// Return value: An array containing the lines from the text file
function loadTextFileIntoArray(pFilenameBase, pMaxNumLines)
{
if (typeof(pFilenameBase) != "string")
return new Array();
Eric Oulashin
committed
var maxNumLines = (typeof(pMaxNumLines) === "number" ? pMaxNumLines : -1);
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
var txtFileLines = new Array();
// See if there is a header file that is made for the user's terminal
// width (areaChgHeader-<width>.ans/asc). If not, then just go with
// msgHeader.ans/asc.
var txtFileExists = true;
var txtFilenameFullPath = gStartupPath + pFilenameBase;
var txtFileFilename = "";
if (file_exists(txtFilenameFullPath + "-" + console.screen_columns + ".ans"))
txtFileFilename = txtFilenameFullPath + "-" + console.screen_columns + ".ans";
else if (file_exists(txtFilenameFullPath + "-" + console.screen_columns + ".asc"))
txtFileFilename = txtFilenameFullPath + "-" + console.screen_columns + ".asc";
else if (file_exists(txtFilenameFullPath + ".ans"))
txtFileFilename = txtFilenameFullPath + ".ans";
else if (file_exists(txtFilenameFullPath + ".asc"))
txtFileFilename = txtFilenameFullPath + ".asc";
else
txtFileExists = false;
if (txtFileExists)
{
var syncConvertedHdrFilename = txtFileFilename;
// If the user's console doesn't support ANSI and the header file is ANSI,
// then convert it to Synchronet attribute codes and read that file instead.
if (!console.term_supports(USER_ANSI) && (getStrAfterPeriod(txtFileFilename).toUpperCase() == "ANS"))
{
syncConvertedHdrFilename = txtFilenameFullPath + "_converted.asc";
if (!file_exists(syncConvertedHdrFilename))
{
if (getStrAfterPeriod(txtFileFilename).toUpperCase() == "ANS")
{
var filenameBase = txtFileFilename.substr(0, dotIdx);
var cmdLine = system.exec_dir + "ans2asc \"" + txtFileFilename + "\" \""
+ syncConvertedHdrFilename + "\"";
// Note: Both system.exec(cmdLine) and
// bbs.exec(cmdLine, EX_NATIVE, gStartupPath) could be used to
// execute the command, but system.exec() seems noticeably faster.
system.exec(cmdLine);
}
else
syncConvertedHdrFilename = txtFileFilename;
}
}
/*
// If the header file is ANSI, then convert it to Synchronet attribute
// codes and read that file instead. This is done so that this script can
// accurately get the file line lengths using console.strlen().
var syncConvertedHdrFilename = txtFilenameFullPath + "_converted.asc";
if (!file_exists(syncConvertedHdrFilename))
{
if (getStrAfterPeriod(txtFileFilename).toUpperCase() == "ANS")
{
var filenameBase = txtFileFilename.substr(0, dotIdx);
var cmdLine = system.exec_dir + "ans2asc \"" + txtFileFilename + "\" \""
+ syncConvertedHdrFilename + "\"";
// Note: Both system.exec(cmdLine) and
// bbs.exec(cmdLine, EX_NATIVE, gStartupPath) could be used to
// execute the command, but system.exec() seems noticeably faster.
system.exec(cmdLine);
}
else
syncConvertedHdrFilename = txtFileFilename;
}
*/
// Read the header file into txtFileLines
var hdrFile = new File(syncConvertedHdrFilename);
if (hdrFile.open("r"))
{
var fileLine = null;
while (!hdrFile.eof)
{
// Read the next line from the header file.
fileLine = hdrFile.readln(2048);
// fileLine should be a string, but I've seen some cases
// where it isn't, so check its type.
if (typeof(fileLine) != "string")
continue;
// Make sure the line isn't longer than the user's terminal
//if (fileLine.length > console.screen_columns)
// fileLine = fileLine.substr(0, console.screen_columns);
txtFileLines.push(fileLine);
// If the header array now has the maximum number of lines, then
// stop reading the header file.
if (txtFileLines.length == maxNumLines)
break;
}
hdrFile.close();
}
}
return txtFileLines;
}
// Returns the portion (if any) of a string after the period.
//
// Parameters:
// pStr: The string to extract from
//
// Return value: The portion of the string after the dot, if there is one. If
// not, then an empty string will be returned.
function getStrAfterPeriod(pStr)
{
var strAfterPeriod = "";
var dotIdx = pStr.lastIndexOf(".");
if (dotIdx > -1)
strAfterPeriod = pStr.substr(dotIdx+1);
return strAfterPeriod;
}
// Adjusts a message's when-written time to the BBS's local time.
//
// Parameters:
// pMsgHdr: A message header object
//
// Return value: The message's when_written_time adjusted to the BBS's local time.
// If the message header doesn't have a when_written_time or
// when_written_zone property, then this function will return -1.
function msgWrittenTimeToLocalBBSTime(pMsgHdr)
{
if (!pMsgHdr.hasOwnProperty("when_written_time") || !pMsgHdr.hasOwnProperty("when_written_zone_offset") || !pMsgHdr.hasOwnProperty("when_imported_zone_offset"))
return -1;
var timeZoneDiffMinutes = pMsgHdr.when_imported_zone_offset - pMsgHdr.when_written_zone_offset;
//var timeZoneDiffMinutes = pMsgHdr.when_written_zone - system.timezone;
var timeZoneDiffSeconds = timeZoneDiffMinutes * 60;
var msgWrittenTimeAdjusted = pMsgHdr.when_written_time + timeZoneDiffSeconds;
return msgWrittenTimeAdjusted;
}
// Returns an object containing bare minimum properties necessary to
// display an invalid message header. Additionally, an object returned
// by this function will have an extra property, isBogus, that will be
// a boolean set to true.
//
// Parameters:
// pSubject: Optional - A string to use as the subject in the bogus message
// header object
function getBogusMsgHdr(pSubject)
{
var msgHdr = {
subject: (typeof(pSubject) == "string" ? pSubject : ""),
when_imported_time: 0,
when_written_time: 0,
when_written_zone: 0,
date: "Fri, 1 Jan 1960 00:00:00 -0000",
attr: 0,
to: "Nobody",
from: "Nobody",
number: 0,
offset: 0,
isBogus: true
};
return msgHdr;
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
}
// Returns whether a message is readable to the user, based on its
// header and the sub-board code.
//
// Parameters:
// pMsgHdr: The header object for the message
// pSubBoardCode: The internal code for the sub-board the message is in
//
// Return value: Boolean - Whether or not the message is readable for the user
function isReadableMsgHdr(pMsgHdr, pSubBoardCode)
{
if (pMsgHdr === null)
return false;
// Let the sysop see unvalidated messages and private messages but not other users.
if (gIsSysop)
{
if (pSubBoardCode != "mail")
{
if ((msg_area.sub[pSubBoardCode].is_moderated && ((pMsgHdr.attr & MSG_VALIDATED) == 0)) ||
(((pMsgHdr.attr & MSG_PRIVATE) == MSG_PRIVATE) && !userHandleAliasNameMatch(pMsgHdr.to)))
{
return false;
}
}
}
// If the message is deleted, determine whether it should be viewable, based
// on the system settings.
if ((pMsgHdr.attr & MSG_DELETE) == MSG_DELETE)
{
// If the user is a sysop, check whether sysops can view deleted messages.
// Otherwise, check whether users can view deleted messages.
if (gIsSysop)
{
if ((system.settings & SYS_SYSVDELM) == 0)
return false;
}
else
{
if ((system.settings & SYS_USRVDELM) == 0)
return false;
}
}
// The message voting and poll variables were added in sbbsdefs.js for
// Synchronet 3.17. Make sure they're defined before referencing them.
if (typeof(MSG_UPVOTE) != "undefined")
{
if ((pMsgHdr.attr & MSG_UPVOTE) == MSG_UPVOTE)
return false;
}
if (typeof(MSG_DOWNVOTE) != "undefined")
{
if ((pMsgHdr.attr & MSG_DOWNVOTE) == MSG_DOWNVOTE)
return false;
}
// Don't include polls as being unreadable messages - They just need to have
// their answer selections read from the header instead of the message body
/*
if (typeof(MSG_POLL) != "undefined")
{
if ((pMsgHdr.attr & MSG_POLL) == MSG_POLL)
return false;
}
*/
return true;
}
// Returns the number of readable messages in a sub-board.
//
// Parameters:
// pMsgbase: The MsgBase object representing the sub-board
// pSubBoardCode: The internal code of the sub-board
//
// Return value: The number of readable messages in the sub-board
function numReadableMsgs(pMsgbase, pSubBoardCode)
{

Eric Oulashin
committed
// The posts property in msg_area.sub[sub_code] and msg_area.grp_list.sub_list is the number
// of posts excluding vote posts
if (typeof(msg_area.sub[pSubBoardCode].posts) === "number")
return msg_area.sub[pSubBoardCode].posts;
else if ((pMsgbase !== null) && pMsgbase.is_open)
{
// Just return the total number of messages.. This isn't accurate, but it's fast.
return pMsgbase.total_msgs;
}

Eric Oulashin
committed
else if (pMsgbase === null)
{
var numMsgs = 0;
var msgBase = new MsgBase(pSubBoardCode);
if (msgBase.open())
{
// Just return the total number of messages.. This isn't accurate, but it's fast.
numMsgs = msgBase.total_msgs;
msgBase.close();
}
return numMsgs;
}

Eric Oulashin
committed
else
return 0;
// Older code, used before Synchronet 3.18c when the 'posts' property was added - This is slow:
/*
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
if ((pMsgbase === null) || !pMsgbase.is_open)
return 0;
var numMsgs = 0;
if (typeof(pMsgbase.get_all_msg_headers) === "function")
{
var msgHdrs = pMsgbase.get_all_msg_headers(true);
for (var msgHdrsProp in msgHdrs)
{
if (msgHdrs[msgHdrsProp] == null)
continue;
else if (isReadableMsgHdr(msgHdrs[msgHdrsProp], pSubBoardCode))
++numMsgs;
}
}
else
{
var msgHeader;
for (var i = 0; i < pMsgbase.total_msgs; ++i)
{
msgHeader = msgBase.get_msg_header(true, i, false);
if (msgHeader == null)
continue;
else if (isReadableMsgHdr(msgHeader, pSubBoardCode))
++numMsgs;
}
}
return numMsgs;