diff --git a/exec/load/attr_conv.js b/exec/load/attr_conv.js new file mode 100644 index 0000000000000000000000000000000000000000..e3630db5fb74f45894bb6756ea0253d245c15e85 --- /dev/null +++ b/exec/load/attr_conv.js @@ -0,0 +1,1132 @@ +// This file specifies some functions for converting attribute codes from +// other systems to Synchronet attribute codes. The functions all take a +// string and return a string: +// +// function WWIVAttrsToSyncAttrs(pText) +// function PCBoardAttrsToSyncAttrs(pText) +// function wildcatAttrsToSyncAttrs(pText) +// function celerityAttrsToSyncAttrs(pText) +// function renegadeAttrsToSyncAttrs(pText) +// function ANSIAttrsToSyncAttrs(pText) +// function convertAttrsToSyncPerSysCfg(pText) +// +// Author: Eric Oulashin (AKA Nightfox) +// BBS: Digital Distortion +// BBS address: digitaldistortionbbs.com (or digdist.synchro.net) + + +// JS strict mode says octal literals (such as "\1") are deprecated, but +// it seems better to use \1 than using an actual control character in +// a text file +//"use strict"; + +require("sbbsdefs.js", "SYS_RENEGADE"); + +///////////////////////////////////////////////////////////////////////// +// Functions for converting other BBS color codes to Synchronet attribute codes + +// Converts WWIV attribute codes to Synchronet attribute codes. +// +// Parameters: +// pText: A string containing the text to convert +// +// Return value: The text with the color codes converted +function WWIVAttrsToSyncAttrs(pText) +{ + // First, see if the text has any WWIV-style attribute codes at + // all. We'll be performing a bunch of search & replace commands, + // so we don't want to do all that work for nothing.. :) + if (/\x03[0-9]/.test(pText)) + { + var text = pText.replace(/\x030/g, "\1n"); // Normal + text = text.replace(/\x031/g, "\1n\1c\1h"); // Bright cyan + text = text.replace(/\x032/g, "\1n\1y\1h"); // Bright yellow + text = text.replace(/\x033/g, "\1n\1m"); // Magenta + text = text.replace(/\x034/g, "\1n\1h\1w\1" + "4"); // Bright white on blue + text = text.replace(/\x035/g, "\1n\1g"); // Green + text = text.replace(/\x036/g, "\1h\1r\1i"); // Bright red, blinking + text = text.replace(/\x037/g, "\1n\1h\1b"); // Bright blue + text = text.replace(/\x038/g, "\1n\1b"); // Blue + text = text.replace(/\x039/g, "\1n\1c"); // Cyan + return text; + } + else + return pText; // No WWIV-style color attribute found, so just return the text. +} + +// Converts PCBoard attribute codes to Synchronet attribute codes. +// +// Parameters: +// pText: A string containing the text to convert +// +// Return value: The text with the color codes converted +function PCBoardAttrsToSyncAttrs(pText) +{ + // First, see if the text has any PCBoard-style attribute codes at + // all. We'll be performing a bunch of search & replace commands, + // so we don't want to do all that work for nothing.. :) + if (/@[xX][0-9A-Fa-f]{2}/.test(pText)) + { + // Black background + var text = pText.replace(/@[xX]00/g, "\1n\1k\1" + "0"); // Black on black + text = text.replace(/@[xX]01/g, "\1n\1b\1" + "0"); // Blue on black + text = text.replace(/@[xX]02/g, "\1n\1g\1" + "0"); // Green on black + text = text.replace(/@[xX]03/g, "\1n\1c\1" + "0"); // Cyan on black + text = text.replace(/@[xX]04/g, "\1n\1r\1" + "0"); // Red on black + text = text.replace(/@[xX]05/g, "\1n\1m\1" + "0"); // Magenta on black + text = text.replace(/@[xX]06/g, "\1n\1y\1" + "0"); // Yellow/brown on black + text = text.replace(/@[xX]07/g, "\1n\1w\1" + "0"); // White on black + text = text.replace(/@[xX]08/g, "\1n\1w\1" + "0"); // White on black + text = text.replace(/@[xX]09/g, "\1n\1w\1" + "0"); // White on black + text = text.replace(/@[xX]08/g, "\1h\1k\1" + "0"); // Bright black on black + text = text.replace(/@[xX]09/g, "\1h\1b\1" + "0"); // Bright blue on black + text = text.replace(/@[xX]0[Aa]/g, "\1h\1g\1" + "0"); // Bright green on black + text = text.replace(/@[xX]0[Bb]/g, "\1h\1c\1" + "0"); // Bright cyan on black + text = text.replace(/@[xX]0[Cc]/g, "\1h\1r\1" + "0"); // Bright red on black + text = text.replace(/@[xX]0[Dd]/g, "\1h\1m\1" + "0"); // Bright magenta on black + text = text.replace(/@[xX]0[Ee]/g, "\1h\1y\1" + "0"); // Bright yellow on black + text = text.replace(/@[xX]0[Ff]/g, "\1h\1w\1" + "0"); // Bright white on black + // Blinking foreground + + // Blue background + text = text.replace(/@[xX]10/g, "\1n\1k\1" + "4"); // Black on blue + text = text.replace(/@[xX]11/g, "\1n\1b\1" + "4"); // Blue on blue + text = text.replace(/@[xX]12/g, "\1n\1g\1" + "4"); // Green on blue + text = text.replace(/@[xX]13/g, "\1n\1c\1" + "4"); // Cyan on blue + text = text.replace(/@[xX]14/g, "\1n\1r\1" + "4"); // Red on blue + text = text.replace(/@[xX]15/g, "\1n\1m\1" + "4"); // Magenta on blue + text = text.replace(/@[xX]16/g, "\1n\1y\1" + "4"); // Yellow/brown on blue + text = text.replace(/@[xX]17/g, "\1n\1w\1" + "4"); // White on blue + text = text.replace(/@[xX]18/g, "\1h\1k\1" + "4"); // Bright black on blue + text = text.replace(/@[xX]19/g, "\1h\1b\1" + "4"); // Bright blue on blue + text = text.replace(/@[xX]1[Aa]/g, "\1h\1g\1" + "4"); // Bright green on blue + text = text.replace(/@[xX]1[Bb]/g, "\1h\1c\1" + "4"); // Bright cyan on blue + text = text.replace(/@[xX]1[Cc]/g, "\1h\1r\1" + "4"); // Bright red on blue + text = text.replace(/@[xX]1[Dd]/g, "\1h\1m\1" + "4"); // Bright magenta on blue + text = text.replace(/@[xX]1[Ee]/g, "\1h\1y\1" + "4"); // Bright yellow on blue + text = text.replace(/@[xX]1[Ff]/g, "\1h\1w\1" + "4"); // Bright white on blue + + // Green background + text = text.replace(/@[xX]20/g, "\1n\1k\1" + "2"); // Black on green + text = text.replace(/@[xX]21/g, "\1n\1b\1" + "2"); // Blue on green + text = text.replace(/@[xX]22/g, "\1n\1g\1" + "2"); // Green on green + text = text.replace(/@[xX]23/g, "\1n\1c\1" + "2"); // Cyan on green + text = text.replace(/@[xX]24/g, "\1n\1r\1" + "2"); // Red on green + text = text.replace(/@[xX]25/g, "\1n\1m\1" + "2"); // Magenta on green + text = text.replace(/@[xX]26/g, "\1n\1y\1" + "2"); // Yellow/brown on green + text = text.replace(/@[xX]27/g, "\1n\1w\1" + "2"); // White on green + text = text.replace(/@[xX]28/g, "\1h\1k\1" + "2"); // Bright black on green + text = text.replace(/@[xX]29/g, "\1h\1b\1" + "2"); // Bright blue on green + text = text.replace(/@[xX]2[Aa]/g, "\1h\1g\1" + "2"); // Bright green on green + text = text.replace(/@[xX]2[Bb]/g, "\1h\1c\1" + "2"); // Bright cyan on green + text = text.replace(/@[xX]2[Cc]/g, "\1h\1r\1" + "2"); // Bright red on green + text = text.replace(/@[xX]2[Dd]/g, "\1h\1m\1" + "2"); // Bright magenta on green + text = text.replace(/@[xX]2[Ee]/g, "\1h\1y\1" + "2"); // Bright yellow on green + text = text.replace(/@[xX]2[Ff]/g, "\1h\1w\1" + "2"); // Bright white on green + + // Cyan background + text = text.replace(/@[xX]30/g, "\1n\1k\1" + "6"); // Black on cyan + text = text.replace(/@[xX]31/g, "\1n\1b\1" + "6"); // Blue on cyan + text = text.replace(/@[xX]32/g, "\1n\1g\1" + "6"); // Green on cyan + text = text.replace(/@[xX]33/g, "\1n\1c\1" + "6"); // Cyan on cyan + text = text.replace(/@[xX]34/g, "\1n\1r\1" + "6"); // Red on cyan + text = text.replace(/@[xX]35/g, "\1n\1m\1" + "6"); // Magenta on cyan + text = text.replace(/@[xX]36/g, "\1n\1y\1" + "6"); // Yellow/brown on cyan + text = text.replace(/@[xX]37/g, "\1n\1w\1" + "6"); // White on cyan + text = text.replace(/@[xX]38/g, "\1h\1k\1" + "6"); // Bright black on cyan + text = text.replace(/@[xX]39/g, "\1h\1b\1" + "6"); // Bright blue on cyan + text = text.replace(/@[xX]3[Aa]/g, "\1h\1g\1" + "6"); // Bright green on cyan + text = text.replace(/@[xX]3[Bb]/g, "\1h\1c\1" + "6"); // Bright cyan on cyan + text = text.replace(/@[xX]3[Cc]/g, "\1h\1r\1" + "6"); // Bright red on cyan + text = text.replace(/@[xX]3[Dd]/g, "\1h\1m\1" + "6"); // Bright magenta on cyan + text = text.replace(/@[xX]3[Ee]/g, "\1h\1y\1" + "6"); // Bright yellow on cyan + text = text.replace(/@[xX]3[Ff]/g, "\1h\1w\1" + "6"); // Bright white on cyan + + // Red background + text = text.replace(/@[xX]40/g, "\1n\1k\1" + "1"); // Black on red + text = text.replace(/@[xX]41/g, "\1n\1b\1" + "1"); // Blue on red + text = text.replace(/@[xX]42/g, "\1n\1g\1" + "1"); // Green on red + text = text.replace(/@[xX]43/g, "\1n\1c\1" + "1"); // Cyan on red + text = text.replace(/@[xX]44/g, "\1n\1r\1" + "1"); // Red on red + text = text.replace(/@[xX]45/g, "\1n\1m\1" + "1"); // Magenta on red + text = text.replace(/@[xX]46/g, "\1n\1y\1" + "1"); // Yellow/brown on red + text = text.replace(/@[xX]47/g, "\1n\1w\1" + "1"); // White on red + text = text.replace(/@[xX]48/g, "\1h\1k\1" + "1"); // Bright black on red + text = text.replace(/@[xX]49/g, "\1h\1b\1" + "1"); // Bright blue on red + text = text.replace(/@[xX]4[Aa]/g, "\1h\1g\1" + "1"); // Bright green on red + text = text.replace(/@[xX]4[Bb]/g, "\1h\1c\1" + "1"); // Bright cyan on red + text = text.replace(/@[xX]4[Cc]/g, "\1h\1r\1" + "1"); // Bright red on red + text = text.replace(/@[xX]4[Dd]/g, "\1h\1m\1" + "1"); // Bright magenta on red + text = text.replace(/@[xX]4[Ee]/g, "\1h\1y\1" + "1"); // Bright yellow on red + text = text.replace(/@[xX]4[Ff]/g, "\1h\1w\1" + "1"); // Bright white on red + + // Magenta background + text = text.replace(/@[xX]50/g, "\1n\1k\1" + "5"); // Black on magenta + text = text.replace(/@[xX]51/g, "\1n\1b\1" + "5"); // Blue on magenta + text = text.replace(/@[xX]52/g, "\1n\1g\1" + "5"); // Green on magenta + text = text.replace(/@[xX]53/g, "\1n\1c\1" + "5"); // Cyan on magenta + text = text.replace(/@[xX]54/g, "\1n\1r\1" + "5"); // Red on magenta + text = text.replace(/@[xX]55/g, "\1n\1m\1" + "5"); // Magenta on magenta + text = text.replace(/@[xX]56/g, "\1n\1y\1" + "5"); // Yellow/brown on magenta + text = text.replace(/@[xX]57/g, "\1n\1w\1" + "5"); // White on magenta + text = text.replace(/@[xX]58/g, "\1h\1k\1" + "5"); // Bright black on magenta + text = text.replace(/@[xX]59/g, "\1h\1b\1" + "5"); // Bright blue on magenta + text = text.replace(/@[xX]5[Aa]/g, "\1h\1g\1" + "5"); // Bright green on magenta + text = text.replace(/@[xX]5[Bb]/g, "\1h\1c\1" + "5"); // Bright cyan on magenta + text = text.replace(/@[xX]5[Cc]/g, "\1h\1r\1" + "5"); // Bright red on magenta + text = text.replace(/@[xX]5[Dd]/g, "\1h\1m\1" + "5"); // Bright magenta on magenta + text = text.replace(/@[xX]5[Ee]/g, "\1h\1y\1" + "5"); // Bright yellow on magenta + text = text.replace(/@[xX]5[Ff]/g, "\1h\1w\1" + "5"); // Bright white on magenta + + // Brown background + text = text.replace(/@[xX]60/g, "\1n\1k\1" + "3"); // Black on brown + text = text.replace(/@[xX]61/g, "\1n\1b\1" + "3"); // Blue on brown + text = text.replace(/@[xX]62/g, "\1n\1g\1" + "3"); // Green on brown + text = text.replace(/@[xX]63/g, "\1n\1c\1" + "3"); // Cyan on brown + text = text.replace(/@[xX]64/g, "\1n\1r\1" + "3"); // Red on brown + text = text.replace(/@[xX]65/g, "\1n\1m\1" + "3"); // Magenta on brown + text = text.replace(/@[xX]66/g, "\1n\1y\1" + "3"); // Yellow/brown on brown + text = text.replace(/@[xX]67/g, "\1n\1w\1" + "3"); // White on brown + text = text.replace(/@[xX]68/g, "\1h\1k\1" + "3"); // Bright black on brown + text = text.replace(/@[xX]69/g, "\1h\1b\1" + "3"); // Bright blue on brown + text = text.replace(/@[xX]6[Aa]/g, "\1h\1g\1" + "3"); // Bright breen on brown + text = text.replace(/@[xX]6[Bb]/g, "\1h\1c\1" + "3"); // Bright cyan on brown + text = text.replace(/@[xX]6[Cc]/g, "\1h\1r\1" + "3"); // Bright red on brown + text = text.replace(/@[xX]6[Dd]/g, "\1h\1m\1" + "3"); // Bright magenta on brown + text = text.replace(/@[xX]6[Ee]/g, "\1h\1y\1" + "3"); // Bright yellow on brown + text = text.replace(/@[xX]6[Ff]/g, "\1h\1w\1" + "3"); // Bright white on brown + + // White background + text = text.replace(/@[xX]70/g, "\1n\1k\1" + "7"); // Black on white + text = text.replace(/@[xX]71/g, "\1n\1b\1" + "7"); // Blue on white + text = text.replace(/@[xX]72/g, "\1n\1g\1" + "7"); // Green on white + text = text.replace(/@[xX]73/g, "\1n\1c\1" + "7"); // Cyan on white + text = text.replace(/@[xX]74/g, "\1n\1r\1" + "7"); // Red on white + text = text.replace(/@[xX]75/g, "\1n\1m\1" + "7"); // Magenta on white + text = text.replace(/@[xX]76/g, "\1n\1y\1" + "7"); // Yellow/brown on white + text = text.replace(/@[xX]77/g, "\1n\1w\1" + "7"); // White on white + text = text.replace(/@[xX]78/g, "\1h\1k\1" + "7"); // Bright black on white + text = text.replace(/@[xX]79/g, "\1h\1b\1" + "7"); // Bright blue on white + text = text.replace(/@[xX]7[Aa]/g, "\1h\1g\1" + "7"); // Bright green on white + text = text.replace(/@[xX]7[Bb]/g, "\1h\1c\1" + "7"); // Bright cyan on white + text = text.replace(/@[xX]7[Cc]/g, "\1h\1r\1" + "7"); // Bright red on white + text = text.replace(/@[xX]7[Dd]/g, "\1h\1m\1" + "7"); // Bright magenta on white + text = text.replace(/@[xX]7[Ee]/g, "\1h\1y\1" + "7"); // Bright yellow on white + text = text.replace(/@[xX]7[Ff]/g, "\1h\1w\1" + "7"); // Bright white on white + + // Black background, blinking foreground + text = text.replace(/@[xX]80/g, "\1n\1k\1" + "0\1i"); // Blinking black on black + text = text.replace(/@[xX]81/g, "\1n\1b\1" + "0\1i"); // Blinking blue on black + text = text.replace(/@[xX]82/g, "\1n\1g\1" + "0\1i"); // Blinking green on black + text = text.replace(/@[xX]83/g, "\1n\1c\1" + "0\1i"); // Blinking cyan on black + text = text.replace(/@[xX]84/g, "\1n\1r\1" + "0\1i"); // Blinking red on black + text = text.replace(/@[xX]85/g, "\1n\1m\1" + "0\1i"); // Blinking magenta on black + text = text.replace(/@[xX]86/g, "\1n\1y\1" + "0\1i"); // Blinking yellow/brown on black + text = text.replace(/@[xX]87/g, "\1n\1w\1" + "0\1i"); // Blinking white on black + text = text.replace(/@[xX]88/g, "\1h\1k\1" + "0\1i"); // Blinking bright black on black + text = text.replace(/@[xX]89/g, "\1h\1b\1" + "0\1i"); // Blinking bright blue on black + text = text.replace(/@[xX]8[Aa]/g, "\1h\1g\1" + "0\1i"); // Blinking bright green on black + text = text.replace(/@[xX]8[Bb]/g, "\1h\1c\1" + "0\1i"); // Blinking bright cyan on black + text = text.replace(/@[xX]8[Cc]/g, "\1h\1r\1" + "0\1i"); // Blinking bright red on black + text = text.replace(/@[xX]8[Dd]/g, "\1h\1m\1" + "0\1i"); // Blinking bright magenta on black + text = text.replace(/@[xX]8[Ee]/g, "\1h\1y\1" + "0\1i"); // Blinking bright yellow on black + text = text.replace(/@[xX]8[Ff]/g, "\1h\1w\1" + "0\1i"); // Blinking bright white on black + + // Blue background, blinking foreground + text = text.replace(/@[xX]90/g, "\1n\1k\1" + "4\1i"); // Blinking black on blue + text = text.replace(/@[xX]91/g, "\1n\1b\1" + "4\1i"); // Blinking blue on blue + text = text.replace(/@[xX]92/g, "\1n\1g\1" + "4\1i"); // Blinking green on blue + text = text.replace(/@[xX]93/g, "\1n\1c\1" + "4\1i"); // Blinking cyan on blue + text = text.replace(/@[xX]94/g, "\1n\1r\1" + "4\1i"); // Blinking red on blue + text = text.replace(/@[xX]95/g, "\1n\1m\1" + "4\1i"); // Blinking magenta on blue + text = text.replace(/@[xX]96/g, "\1n\1y\1" + "4\1i"); // Blinking yellow/brown on blue + text = text.replace(/@[xX]97/g, "\1n\1w\1" + "4\1i"); // Blinking white on blue + text = text.replace(/@[xX]98/g, "\1h\1k\1" + "4\1i"); // Blinking bright black on blue + text = text.replace(/@[xX]99/g, "\1h\1b\1" + "4\1i"); // Blinking bright blue on blue + text = text.replace(/@[xX]9[Aa]/g, "\1h\1g\1" + "4\1i"); // Blinking bright green on blue + text = text.replace(/@[xX]9[Bb]/g, "\1h\1c\1" + "4\1i"); // Blinking bright cyan on blue + text = text.replace(/@[xX]9[Cc]/g, "\1h\1r\1" + "4\1i"); // Blinking bright red on blue + text = text.replace(/@[xX]9[Dd]/g, "\1h\1m\1" + "4\1i"); // Blinking bright magenta on blue + text = text.replace(/@[xX]9[Ee]/g, "\1h\1y\1" + "4\1i"); // Blinking bright yellow on blue + text = text.replace(/@[xX]9[Ff]/g, "\1h\1w\1" + "4\1i"); // Blinking bright white on blue + + // Green background, blinking foreground + text = text.replace(/@[xX][Aa]0/g, "\1n\1k\1" + "2\1i"); // Blinking black on green + text = text.replace(/@[xX][Aa]1/g, "\1n\1b\1" + "2\1i"); // Blinking blue on green + text = text.replace(/@[xX][Aa]2/g, "\1n\1g\1" + "2\1i"); // Blinking green on green + text = text.replace(/@[xX][Aa]3/g, "\1n\1c\1" + "2\1i"); // Blinking cyan on green + text = text.replace(/@[xX][Aa]4/g, "\1n\1r\1" + "2\1i"); // Blinking red on green + text = text.replace(/@[xX][Aa]5/g, "\1n\1m\1" + "2\1i"); // Blinking magenta on green + text = text.replace(/@[xX][Aa]6/g, "\1n\1y\1" + "2\1i"); // Blinking yellow/brown on green + text = text.replace(/@[xX][Aa]7/g, "\1n\1w\1" + "2\1i"); // Blinking white on green + text = text.replace(/@[xX][Aa]8/g, "\1h\1k\1" + "2\1i"); // Blinking bright black on green + text = text.replace(/@[xX][Aa]9/g, "\1h\1b\1" + "2\1i"); // Blinking bright blue on green + text = text.replace(/@[xX][Aa][Aa]/g, "\1h\1g\1" + "2\1i"); // Blinking bright green on green + text = text.replace(/@[xX][Aa][Bb]/g, "\1h\1c\1" + "2\1i"); // Blinking bright cyan on green + text = text.replace(/@[xX][Aa][Cc]/g, "\1h\1r\1" + "2\1i"); // Blinking bright red on green + text = text.replace(/@[xX][Aa][Dd]/g, "\1h\1m\1" + "2\1i"); // Blinking bright magenta on green + text = text.replace(/@[xX][Aa][Ee]/g, "\1h\1y\1" + "2\1i"); // Blinking bright yellow on green + text = text.replace(/@[xX][Aa][Ff]/g, "\1h\1w\1" + "2\1i"); // Blinking bright white on green + + // Cyan background, blinking foreground + text = text.replace(/@[xX][Bb]0/g, "\1n\1k\1" + "6\1i"); // Blinking black on cyan + text = text.replace(/@[xX][Bb]1/g, "\1n\1b\1" + "6\1i"); // Blinking blue on cyan + text = text.replace(/@[xX][Bb]2/g, "\1n\1g\1" + "6\1i"); // Blinking green on cyan + text = text.replace(/@[xX][Bb]3/g, "\1n\1c\1" + "6\1i"); // Blinking cyan on cyan + text = text.replace(/@[xX][Bb]4/g, "\1n\1r\1" + "6\1i"); // Blinking red on cyan + text = text.replace(/@[xX][Bb]5/g, "\1n\1m\1" + "6\1i"); // Blinking magenta on cyan + text = text.replace(/@[xX][Bb]6/g, "\1n\1y\1" + "6\1i"); // Blinking yellow/brown on cyan + text = text.replace(/@[xX][Bb]7/g, "\1n\1w\1" + "6\1i"); // Blinking white on cyan + text = text.replace(/@[xX][Bb]8/g, "\1h\1k\1" + "6\1i"); // Blinking bright black on cyan + text = text.replace(/@[xX][Bb]9/g, "\1h\1b\1" + "6\1i"); // Blinking bright blue on cyan + text = text.replace(/@[xX][Bb][Aa]/g, "\1h\1g\1" + "6\1i"); // Blinking bright green on cyan + text = text.replace(/@[xX][Bb][Bb]/g, "\1h\1c\1" + "6\1i"); // Blinking bright cyan on cyan + text = text.replace(/@[xX][Bb][Cc]/g, "\1h\1r\1" + "6\1i"); // Blinking bright red on cyan + text = text.replace(/@[xX][Bb][Dd]/g, "\1h\1m\1" + "6\1i"); // Blinking bright magenta on cyan + text = text.replace(/@[xX][Bb][Ee]/g, "\1h\1y\1" + "6\1i"); // Blinking bright yellow on cyan + text = text.replace(/@[xX][Bb][Ff]/g, "\1h\1w\1" + "6\1i"); // Blinking bright white on cyan + + // Red background, blinking foreground + text = text.replace(/@[xX][Cc]0/g, "\1n\1k\1" + "1\1i"); // Blinking black on red + text = text.replace(/@[xX][Cc]1/g, "\1n\1b\1" + "1\1i"); // Blinking blue on red + text = text.replace(/@[xX][Cc]2/g, "\1n\1g\1" + "1\1i"); // Blinking green on red + text = text.replace(/@[xX][Cc]3/g, "\1n\1c\1" + "1\1i"); // Blinking cyan on red + text = text.replace(/@[xX][Cc]4/g, "\1n\1r\1" + "1\1i"); // Blinking red on red + text = text.replace(/@[xX][Cc]5/g, "\1n\1m\1" + "1\1i"); // Blinking magenta on red + text = text.replace(/@[xX][Cc]6/g, "\1n\1y\1" + "1\1i"); // Blinking yellow/brown on red + text = text.replace(/@[xX][Cc]7/g, "\1n\1w\1" + "1\1i"); // Blinking white on red + text = text.replace(/@[xX][Cc]8/g, "\1h\1k\1" + "1\1i"); // Blinking bright black on red + text = text.replace(/@[xX][Cc]9/g, "\1h\1b\1" + "1\1i"); // Blinking bright blue on red + text = text.replace(/@[xX][Cc][Aa]/g, "\1h\1g\1" + "1\1i"); // Blinking bright green on red + text = text.replace(/@[xX][Cc][Bb]/g, "\1h\1c\1" + "1\1i"); // Blinking bright cyan on red + text = text.replace(/@[xX][Cc][Cc]/g, "\1h\1r\1" + "1\1i"); // Blinking bright red on red + text = text.replace(/@[xX][Cc][Dd]/g, "\1h\1m\1" + "1\1i"); // Blinking bright magenta on red + text = text.replace(/@[xX][Cc][Ee]/g, "\1h\1y\1" + "1\1i"); // Blinking bright yellow on red + text = text.replace(/@[xX][Cc][Ff]/g, "\1h\1w\1" + "1\1i"); // Blinking bright white on red + + // Magenta background, blinking foreground + text = text.replace(/@[xX][Dd]0/g, "\1n\1k\1" + "5\1i"); // Blinking black on magenta + text = text.replace(/@[xX][Dd]1/g, "\1n\1b\1" + "5\1i"); // Blinking blue on magenta + text = text.replace(/@[xX][Dd]2/g, "\1n\1g\1" + "5\1i"); // Blinking green on magenta + text = text.replace(/@[xX][Dd]3/g, "\1n\1c\1" + "5\1i"); // Blinking cyan on magenta + text = text.replace(/@[xX][Dd]4/g, "\1n\1r\1" + "5\1i"); // Blinking red on magenta + text = text.replace(/@[xX][Dd]5/g, "\1n\1m\1" + "5\1i"); // Blinking magenta on magenta + text = text.replace(/@[xX][Dd]6/g, "\1n\1y\1" + "5\1i"); // Blinking yellow/brown on magenta + text = text.replace(/@[xX][Dd]7/g, "\1n\1w\1" + "5\1i"); // Blinking white on magenta + text = text.replace(/@[xX][Dd]8/g, "\1h\1k\1" + "5\1i"); // Blinking bright black on magenta + text = text.replace(/@[xX][Dd]9/g, "\1h\1b\1" + "5\1i"); // Blinking bright blue on magenta + text = text.replace(/@[xX][Dd][Aa]/g, "\1h\1g\1" + "5\1i"); // Blinking bright green on magenta + text = text.replace(/@[xX][Dd][Bb]/g, "\1h\1c\1" + "5\1i"); // Blinking bright cyan on magenta + text = text.replace(/@[xX][Dd][Cc]/g, "\1h\1r\1" + "5\1i"); // Blinking bright red on magenta + text = text.replace(/@[xX][Dd][Dd]/g, "\1h\1m\1" + "5\1i"); // Blinking bright magenta on magenta + text = text.replace(/@[xX][Dd][Ee]/g, "\1h\1y\1" + "5\1i"); // Blinking bright yellow on magenta + text = text.replace(/@[xX][Dd][Ff]/g, "\1h\1w\1" + "5\1i"); // Blinking bright white on magenta + + // Brown background, blinking foreground + text = text.replace(/@[xX][Ee]0/g, "\1n\1k\1" + "3\1i"); // Blinking black on brown + text = text.replace(/@[xX][Ee]1/g, "\1n\1b\1" + "3\1i"); // Blinking blue on brown + text = text.replace(/@[xX][Ee]2/g, "\1n\1g\1" + "3\1i"); // Blinking green on brown + text = text.replace(/@[xX][Ee]3/g, "\1n\1c\1" + "3\1i"); // Blinking cyan on brown + text = text.replace(/@[xX][Ee]4/g, "\1n\1r\1" + "3\1i"); // Blinking red on brown + text = text.replace(/@[xX][Ee]5/g, "\1n\1m\1" + "3\1i"); // Blinking magenta on brown + text = text.replace(/@[xX][Ee]6/g, "\1n\1y\1" + "3\1i"); // Blinking yellow/brown on brown + text = text.replace(/@[xX][Ee]7/g, "\1n\1w\1" + "3\1i"); // Blinking white on brown + text = text.replace(/@[xX][Ee]8/g, "\1h\1k\1" + "3\1i"); // Blinking bright black on brown + text = text.replace(/@[xX][Ee]9/g, "\1h\1b\1" + "3\1i"); // Blinking bright blue on brown + text = text.replace(/@[xX][Ee][Aa]/g, "\1h\1g\1" + "3\1i"); // Blinking bright green on brown + text = text.replace(/@[xX][Ee][Bb]/g, "\1h\1c\1" + "3\1i"); // Blinking bright cyan on brown + text = text.replace(/@[xX][Ee][Cc]/g, "\1h\1r\1" + "3\1i"); // Blinking bright red on brown + text = text.replace(/@[xX][Ee][Dd]/g, "\1h\1m\1" + "3\1i"); // Blinking bright magenta on brown + text = text.replace(/@[xX][Ee][Ee]/g, "\1h\1y\1" + "3\1i"); // Blinking bright yellow on brown + text = text.replace(/@[xX][Ee][Ff]/g, "\1h\1w\1" + "3\1i"); // Blinking bright white on brown + + // White background, blinking foreground + text = text.replace(/@[xX][Ff]0/g, "\1n\1k\1" + "7\1i"); // Blinking black on white + text = text.replace(/@[xX][Ff]1/g, "\1n\1b\1" + "7\1i"); // Blinking blue on white + text = text.replace(/@[xX][Ff]2/g, "\1n\1g\1" + "7\1i"); // Blinking green on white + text = text.replace(/@[xX][Ff]3/g, "\1n\1c\1" + "7\1i"); // Blinking cyan on white + text = text.replace(/@[xX][Ff]4/g, "\1n\1r\1" + "7\1i"); // Blinking red on white + text = text.replace(/@[xX][Ff]5/g, "\1n\1m\1" + "7\1i"); // Blinking magenta on white + text = text.replace(/@[xX][Ff]6/g, "\1n\1y\1" + "7\1i"); // Blinking yellow/brown on white + text = text.replace(/@[xX][Ff]7/g, "\1n\1w\1" + "7\1i"); // Blinking white on white + text = text.replace(/@[xX][Ff]8/g, "\1h\1k\1" + "7\1i"); // Blinking bright black on white + text = text.replace(/@[xX][Ff]9/g, "\1h\1b\1" + "7\1i"); // Blinking bright blue on white + text = text.replace(/@[xX][Ff][Aa]/g, "\1h\1g\1" + "7\1i"); // Blinking bright green on white + text = text.replace(/@[xX][Ff][Bb]/g, "\1h\1c\1" + "7\1i"); // Blinking bright cyan on white + text = text.replace(/@[xX][Ff][Cc]/g, "\1h\1r\1" + "7\1i"); // Blinking bright red on white + text = text.replace(/@[xX][Ff][Dd]/g, "\1h\1m\1" + "7\1i"); // Blinking bright magenta on white + text = text.replace(/@[xX][Ff][Ee]/g, "\1h\1y\1" + "7\1i"); // Blinking bright yellow on white + text = text.replace(/@[xX][Ff][Ff]/g, "\1h\1w\1" + "7\1i"); // Blinking bright white on white + + return text; + } + else + return pText; // No PCBoard-style attribute codes found, so just return the text. +} + +// Converts Wildcat attribute codes to Synchronet attribute codes. +// +// Parameters: +// pText: A string containing the text to convert +// +// Return value: The text with the color codes converted +function wildcatAttrsToSyncAttrs(pText) +{ + // First, see if the text has any Wildcat-style attribute codes at + // all. We'll be performing a bunch of search & replace commands, + // so we don't want to do all that work for nothing.. :) + if (/@[0-9A-Fa-f]{2}@/.test(pText)) + { + // Black background + var text = pText.replace(/@00@/g, "\1n\1k\1" + "0"); // Black on black + text = text.replace(/@01@/g, "\1n\1b\1" + "0"); // Blue on black + text = text.replace(/@02@/g, "\1n\1g\1" + "0"); // Green on black + text = text.replace(/@03@/g, "\1n\1c\1" + "0"); // Cyan on black + text = text.replace(/@04@/g, "\1n\1r\1" + "0"); // Red on black + text = text.replace(/@05@/g, "\1n\1m\1" + "0"); // Magenta on black + text = text.replace(/@06@/g, "\1n\1y\1" + "0"); // Yellow/brown on black + text = text.replace(/@07@/g, "\1n\1w\1" + "0"); // White on black + text = text.replace(/@08@/g, "\1n\1w\1" + "0"); // White on black + text = text.replace(/@09@/g, "\1n\1w\1" + "0"); // White on black + text = text.replace(/@08@/g, "\1h\1k\1" + "0"); // Bright black on black + text = text.replace(/@09@/g, "\1h\1b\1" + "0"); // Bright blue on black + text = text.replace(/@0[Aa]@/g, "\1h\1g\1" + "0"); // Bright green on black + text = text.replace(/@0[Bb]@/g, "\1h\1c\1" + "0"); // Bright cyan on black + text = text.replace(/@0[Cc]@/g, "\1h\1r\1" + "0"); // Bright red on black + text = text.replace(/@0[Dd]@/g, "\1h\1m\1" + "0"); // Bright magenta on black + text = text.replace(/@0[Ee]@/g, "\1h\1y\1" + "0"); // Bright yellow on black + text = text.replace(/@0[Ff]@/g, "\1h\1w\1" + "0"); // Bright white on black + // Blinking foreground + + // Blue background + text = text.replace(/@10@/g, "\1n\1k\1" + "4"); // Black on blue + text = text.replace(/@11@/g, "\1n\1b\1" + "4"); // Blue on blue + text = text.replace(/@12@/g, "\1n\1g\1" + "4"); // Green on blue + text = text.replace(/@13@/g, "\1n\1c\1" + "4"); // Cyan on blue + text = text.replace(/@14@/g, "\1n\1r\1" + "4"); // Red on blue + text = text.replace(/@15@/g, "\1n\1m\1" + "4"); // Magenta on blue + text = text.replace(/@16@/g, "\1n\1y\1" + "4"); // Yellow/brown on blue + text = text.replace(/@17@/g, "\1n\1w\1" + "4"); // White on blue + text = text.replace(/@18@/g, "\1h\1k\1" + "4"); // Bright black on blue + text = text.replace(/@19@/g, "\1h\1b\1" + "4"); // Bright blue on blue + text = text.replace(/@1[Aa]@/g, "\1h\1g\1" + "4"); // Bright green on blue + text = text.replace(/@1[Bb]@/g, "\1h\1c\1" + "4"); // Bright cyan on blue + text = text.replace(/@1[Cc]@/g, "\1h\1r\1" + "4"); // Bright red on blue + text = text.replace(/@1[Dd]@/g, "\1h\1m\1" + "4"); // Bright magenta on blue + text = text.replace(/@1[Ee]@/g, "\1h\1y\1" + "4"); // Bright yellow on blue + text = text.replace(/@1[Ff]@/g, "\1h\1w\1" + "4"); // Bright white on blue + + // Green background + text = text.replace(/@20@/g, "\1n\1k\1" + "2"); // Black on green + text = text.replace(/@21@/g, "\1n\1b\1" + "2"); // Blue on green + text = text.replace(/@22@/g, "\1n\1g\1" + "2"); // Green on green + text = text.replace(/@23@/g, "\1n\1c\1" + "2"); // Cyan on green + text = text.replace(/@24@/g, "\1n\1r\1" + "2"); // Red on green + text = text.replace(/@25@/g, "\1n\1m\1" + "2"); // Magenta on green + text = text.replace(/@26@/g, "\1n\1y\1" + "2"); // Yellow/brown on green + text = text.replace(/@27@/g, "\1n\1w\1" + "2"); // White on green + text = text.replace(/@28@/g, "\1h\1k\1" + "2"); // Bright black on green + text = text.replace(/@29@/g, "\1h\1b\1" + "2"); // Bright blue on green + text = text.replace(/@2[Aa]@/g, "\1h\1g\1" + "2"); // Bright green on green + text = text.replace(/@2[Bb]@/g, "\1h\1c\1" + "2"); // Bright cyan on green + text = text.replace(/@2[Cc]@/g, "\1h\1r\1" + "2"); // Bright red on green + text = text.replace(/@2[Dd]@/g, "\1h\1m\1" + "2"); // Bright magenta on green + text = text.replace(/@2[Ee]@/g, "\1h\1y\1" + "2"); // Bright yellow on green + text = text.replace(/@2[Ff]@/g, "\1h\1w\1" + "2"); // Bright white on green + + // Cyan background + text = text.replace(/@30@/g, "\1n\1k\1" + "6"); // Black on cyan + text = text.replace(/@31@/g, "\1n\1b\1" + "6"); // Blue on cyan + text = text.replace(/@32@/g, "\1n\1g\1" + "6"); // Green on cyan + text = text.replace(/@33@/g, "\1n\1c\1" + "6"); // Cyan on cyan + text = text.replace(/@34@/g, "\1n\1r\1" + "6"); // Red on cyan + text = text.replace(/@35@/g, "\1n\1m\1" + "6"); // Magenta on cyan + text = text.replace(/@36@/g, "\1n\1y\1" + "6"); // Yellow/brown on cyan + text = text.replace(/@37@/g, "\1n\1w\1" + "6"); // White on cyan + text = text.replace(/@38@/g, "\1h\1k\1" + "6"); // Bright black on cyan + text = text.replace(/@39@/g, "\1h\1b\1" + "6"); // Bright blue on cyan + text = text.replace(/@3[Aa]@/g, "\1h\1g\1" + "6"); // Bright green on cyan + text = text.replace(/@3[Bb]@/g, "\1h\1c\1" + "6"); // Bright cyan on cyan + text = text.replace(/@3[Cc]@/g, "\1h\1r\1" + "6"); // Bright red on cyan + text = text.replace(/@3[Dd]@/g, "\1h\1m\1" + "6"); // Bright magenta on cyan + text = text.replace(/@3[Ee]@/g, "\1h\1y\1" + "6"); // Bright yellow on cyan + text = text.replace(/@3[Ff]@/g, "\1h\1w\1" + "6"); // Bright white on cyan + + // Red background + text = text.replace(/@40@/g, "\1n\1k\1" + "1"); // Black on red + text = text.replace(/@41@/g, "\1n\1b\1" + "1"); // Blue on red + text = text.replace(/@42@/g, "\1n\1g\1" + "1"); // Green on red + text = text.replace(/@43@/g, "\1n\1c\1" + "1"); // Cyan on red + text = text.replace(/@44@/g, "\1n\1r\1" + "1"); // Red on red + text = text.replace(/@45@/g, "\1n\1m\1" + "1"); // Magenta on red + text = text.replace(/@46@/g, "\1n\1y\1" + "1"); // Yellow/brown on red + text = text.replace(/@47@/g, "\1n\1w\1" + "1"); // White on red + text = text.replace(/@48@/g, "\1h\1k\1" + "1"); // Bright black on red + text = text.replace(/@49@/g, "\1h\1b\1" + "1"); // Bright blue on red + text = text.replace(/@4[Aa]@/g, "\1h\1g\1" + "1"); // Bright green on red + text = text.replace(/@4[Bb]@/g, "\1h\1c\1" + "1"); // Bright cyan on red + text = text.replace(/@4[Cc]@/g, "\1h\1r\1" + "1"); // Bright red on red + text = text.replace(/@4[Dd]@/g, "\1h\1m\1" + "1"); // Bright magenta on red + text = text.replace(/@4[Ee]@/g, "\1h\1y\1" + "1"); // Bright yellow on red + text = text.replace(/@4[Ff]@/g, "\1h\1w\1" + "1"); // Bright white on red + + // Magenta background + text = text.replace(/@50@/g, "\1n\1k\1" + "5"); // Black on magenta + text = text.replace(/@51@/g, "\1n\1b\1" + "5"); // Blue on magenta + text = text.replace(/@52@/g, "\1n\1g\1" + "5"); // Green on magenta + text = text.replace(/@53@/g, "\1n\1c\1" + "5"); // Cyan on magenta + text = text.replace(/@54@/g, "\1n\1r\1" + "5"); // Red on magenta + text = text.replace(/@55@/g, "\1n\1m\1" + "5"); // Magenta on magenta + text = text.replace(/@56@/g, "\1n\1y\1" + "5"); // Yellow/brown on magenta + text = text.replace(/@57@/g, "\1n\1w\1" + "5"); // White on magenta + text = text.replace(/@58@/g, "\1h\1k\1" + "5"); // Bright black on magenta + text = text.replace(/@59@/g, "\1h\1b\1" + "5"); // Bright blue on magenta + text = text.replace(/@5[Aa]@/g, "\1h\1g\1" + "5"); // Bright green on magenta + text = text.replace(/@5[Bb]@/g, "\1h\1c\1" + "5"); // Bright cyan on magenta + text = text.replace(/@5[Cc]@/g, "\1h\1r\1" + "5"); // Bright red on magenta + text = text.replace(/@5[Dd]@/g, "\1h\1m\1" + "5"); // Bright magenta on magenta + text = text.replace(/@5[Ee]@/g, "\1h\1y\1" + "5"); // Bright yellow on magenta + text = text.replace(/@5[Ff]@/g, "\1h\1w\1" + "5"); // Bright white on magenta + + // Brown background + text = text.replace(/@60@/g, "\1n\1k\1" + "3"); // Black on brown + text = text.replace(/@61@/g, "\1n\1b\1" + "3"); // Blue on brown + text = text.replace(/@62@/g, "\1n\1g\1" + "3"); // Green on brown + text = text.replace(/@63@/g, "\1n\1c\1" + "3"); // Cyan on brown + text = text.replace(/@64@/g, "\1n\1r\1" + "3"); // Red on brown + text = text.replace(/@65@/g, "\1n\1m\1" + "3"); // Magenta on brown + text = text.replace(/@66@/g, "\1n\1y\1" + "3"); // Yellow/brown on brown + text = text.replace(/@67@/g, "\1n\1w\1" + "3"); // White on brown + text = text.replace(/@68@/g, "\1h\1k\1" + "3"); // Bright black on brown + text = text.replace(/@69@/g, "\1h\1b\1" + "3"); // Bright blue on brown + text = text.replace(/@6[Aa]@/g, "\1h\1g\1" + "3"); // Bright breen on brown + text = text.replace(/@6[Bb]@/g, "\1h\1c\1" + "3"); // Bright cyan on brown + text = text.replace(/@6[Cc]@/g, "\1h\1r\1" + "3"); // Bright red on brown + text = text.replace(/@6[Dd]@/g, "\1h\1m\1" + "3"); // Bright magenta on brown + text = text.replace(/@6[Ee]@/g, "\1h\1y\1" + "3"); // Bright yellow on brown + text = text.replace(/@6[Ff]@/g, "\1h\1w\1" + "3"); // Bright white on brown + + // White background + text = text.replace(/@70@/g, "\1n\1k\1" + "7"); // Black on white + text = text.replace(/@71@/g, "\1n\1b\1" + "7"); // Blue on white + text = text.replace(/@72@/g, "\1n\1g\1" + "7"); // Green on white + text = text.replace(/@73@/g, "\1n\1c\1" + "7"); // Cyan on white + text = text.replace(/@74@/g, "\1n\1r\1" + "7"); // Red on white + text = text.replace(/@75@/g, "\1n\1m\1" + "7"); // Magenta on white + text = text.replace(/@76@/g, "\1n\1y\1" + "7"); // Yellow/brown on white + text = text.replace(/@77@/g, "\1n\1w\1" + "7"); // White on white + text = text.replace(/@78@/g, "\1h\1k\1" + "7"); // Bright black on white + text = text.replace(/@79@/g, "\1h\1b\1" + "7"); // Bright blue on white + text = text.replace(/@7[Aa]@/g, "\1h\1g\1" + "7"); // Bright green on white + text = text.replace(/@7[Bb]@/g, "\1h\1c\1" + "7"); // Bright cyan on white + text = text.replace(/@7[Cc]@/g, "\1h\1r\1" + "7"); // Bright red on white + text = text.replace(/@7[Dd]@/g, "\1h\1m\1" + "7"); // Bright magenta on white + text = text.replace(/@7[Ee]@/g, "\1h\1y\1" + "7"); // Bright yellow on white + text = text.replace(/@7[Ff]@/g, "\1h\1w\1" + "7"); // Bright white on white + + // Black background, blinking foreground + text = text.replace(/@80@/g, "\1n\1k\1" + "0\1i"); // Blinking black on black + text = text.replace(/@81@/g, "\1n\1b\1" + "0\1i"); // Blinking blue on black + text = text.replace(/@82@/g, "\1n\1g\1" + "0\1i"); // Blinking green on black + text = text.replace(/@83@/g, "\1n\1c\1" + "0\1i"); // Blinking cyan on black + text = text.replace(/@84@/g, "\1n\1r\1" + "0\1i"); // Blinking red on black + text = text.replace(/@85@/g, "\1n\1m\1" + "0\1i"); // Blinking magenta on black + text = text.replace(/@86@/g, "\1n\1y\1" + "0\1i"); // Blinking yellow/brown on black + text = text.replace(/@87@/g, "\1n\1w\1" + "0\1i"); // Blinking white on black + text = text.replace(/@88@/g, "\1h\1k\1" + "0\1i"); // Blinking bright black on black + text = text.replace(/@89@/g, "\1h\1b\1" + "0\1i"); // Blinking bright blue on black + text = text.replace(/@8[Aa]@/g, "\1h\1g\1" + "0\1i"); // Blinking bright green on black + text = text.replace(/@8[Bb]@/g, "\1h\1c\1" + "0\1i"); // Blinking bright cyan on black + text = text.replace(/@8[Cc]@/g, "\1h\1r\1" + "0\1i"); // Blinking bright red on black + text = text.replace(/@8[Dd]@/g, "\1h\1m\1" + "0\1i"); // Blinking bright magenta on black + text = text.replace(/@8[Ee]@/g, "\1h\1y\1" + "0\1i"); // Blinking bright yellow on black + text = text.replace(/@8[Ff]@/g, "\1h\1w\1" + "0\1i"); // Blinking bright white on black + + // Blue background, blinking foreground + text = text.replace(/@90@/g, "\1n\1k\1" + "4\1i"); // Blinking black on blue + text = text.replace(/@91@/g, "\1n\1b\1" + "4\1i"); // Blinking blue on blue + text = text.replace(/@92@/g, "\1n\1g\1" + "4\1i"); // Blinking green on blue + text = text.replace(/@93@/g, "\1n\1c\1" + "4\1i"); // Blinking cyan on blue + text = text.replace(/@94@/g, "\1n\1r\1" + "4\1i"); // Blinking red on blue + text = text.replace(/@95@/g, "\1n\1m\1" + "4\1i"); // Blinking magenta on blue + text = text.replace(/@96@/g, "\1n\1y\1" + "4\1i"); // Blinking yellow/brown on blue + text = text.replace(/@97@/g, "\1n\1w\1" + "4\1i"); // Blinking white on blue + text = text.replace(/@98@/g, "\1h\1k\1" + "4\1i"); // Blinking bright black on blue + text = text.replace(/@99@/g, "\1h\1b\1" + "4\1i"); // Blinking bright blue on blue + text = text.replace(/@9[Aa]@/g, "\1h\1g\1" + "4\1i"); // Blinking bright green on blue + text = text.replace(/@9[Bb]@/g, "\1h\1c\1" + "4\1i"); // Blinking bright cyan on blue + text = text.replace(/@9[Cc]@/g, "\1h\1r\1" + "4\1i"); // Blinking bright red on blue + text = text.replace(/@9[Dd]@/g, "\1h\1m\1" + "4\1i"); // Blinking bright magenta on blue + text = text.replace(/@9[Ee]@/g, "\1h\1y\1" + "4\1i"); // Blinking bright yellow on blue + text = text.replace(/@9[Ff]@/g, "\1h\1w\1" + "4\1i"); // Blinking bright white on blue + + // Green background, blinking foreground + text = text.replace(/@[Aa]0@/g, "\1n\1k\1" + "2\1i"); // Blinking black on green + text = text.replace(/@[Aa]1@/g, "\1n\1b\1" + "2\1i"); // Blinking blue on green + text = text.replace(/@[Aa]2@/g, "\1n\1g\1" + "2\1i"); // Blinking green on green + text = text.replace(/@[Aa]3@/g, "\1n\1c\1" + "2\1i"); // Blinking cyan on green + text = text.replace(/@[Aa]4@/g, "\1n\1r\1" + "2\1i"); // Blinking red on green + text = text.replace(/@[Aa]5@/g, "\1n\1m\1" + "2\1i"); // Blinking magenta on green + text = text.replace(/@[Aa]6@/g, "\1n\1y\1" + "2\1i"); // Blinking yellow/brown on green + text = text.replace(/@[Aa]7@/g, "\1n\1w\1" + "2\1i"); // Blinking white on green + text = text.replace(/@[Aa]8@/g, "\1h\1k\1" + "2\1i"); // Blinking bright black on green + text = text.replace(/@[Aa]9@/g, "\1h\1b\1" + "2\1i"); // Blinking bright blue on green + text = text.replace(/@[Aa][Aa]@/g, "\1h\1g\1" + "2\1i"); // Blinking bright green on green + text = text.replace(/@[Aa][Bb]@/g, "\1h\1c\1" + "2\1i"); // Blinking bright cyan on green + text = text.replace(/@[Aa][Cc]@/g, "\1h\1r\1" + "2\1i"); // Blinking bright red on green + text = text.replace(/@[Aa][Dd]@/g, "\1h\1m\1" + "2\1i"); // Blinking bright magenta on green + text = text.replace(/@[Aa][Ee]@/g, "\1h\1y\1" + "2\1i"); // Blinking bright yellow on green + text = text.replace(/@[Aa][Ff]@/g, "\1h\1w\1" + "2\1i"); // Blinking bright white on green + + // Cyan background, blinking foreground + text = text.replace(/@[Bb]0@/g, "\1n\1k\1" + "6\1i"); // Blinking black on cyan + text = text.replace(/@[Bb]1@/g, "\1n\1b\1" + "6\1i"); // Blinking blue on cyan + text = text.replace(/@[Bb]2@/g, "\1n\1g\1" + "6\1i"); // Blinking green on cyan + text = text.replace(/@[Bb]3@/g, "\1n\1c\1" + "6\1i"); // Blinking cyan on cyan + text = text.replace(/@[Bb]4@/g, "\1n\1r\1" + "6\1i"); // Blinking red on cyan + text = text.replace(/@[Bb]5@/g, "\1n\1m\1" + "6\1i"); // Blinking magenta on cyan + text = text.replace(/@[Bb]6@/g, "\1n\1y\1" + "6\1i"); // Blinking yellow/brown on cyan + text = text.replace(/@[Bb]7@/g, "\1n\1w\1" + "6\1i"); // Blinking white on cyan + text = text.replace(/@[Bb]8@/g, "\1h\1k\1" + "6\1i"); // Blinking bright black on cyan + text = text.replace(/@[Bb]9@/g, "\1h\1b\1" + "6\1i"); // Blinking bright blue on cyan + text = text.replace(/@[Bb][Aa]@/g, "\1h\1g\1" + "6\1i"); // Blinking bright green on cyan + text = text.replace(/@[Bb][Bb]@/g, "\1h\1c\1" + "6\1i"); // Blinking bright cyan on cyan + text = text.replace(/@[Bb][Cc]@/g, "\1h\1r\1" + "6\1i"); // Blinking bright red on cyan + text = text.replace(/@[Bb][Dd]@/g, "\1h\1m\1" + "6\1i"); // Blinking bright magenta on cyan + text = text.replace(/@[Bb][Ee]@/g, "\1h\1y\1" + "6\1i"); // Blinking bright yellow on cyan + text = text.replace(/@[Bb][Ff]@/g, "\1h\1w\1" + "6\1i"); // Blinking bright white on cyan + + // Red background, blinking foreground + text = text.replace(/@[Cc]0@/g, "\1n\1k\1" + "1\1i"); // Blinking black on red + text = text.replace(/@[Cc]1@/g, "\1n\1b\1" + "1\1i"); // Blinking blue on red + text = text.replace(/@[Cc]2@/g, "\1n\1g\1" + "1\1i"); // Blinking green on red + text = text.replace(/@[Cc]3@/g, "\1n\1c\1" + "1\1i"); // Blinking cyan on red + text = text.replace(/@[Cc]4@/g, "\1n\1r\1" + "1\1i"); // Blinking red on red + text = text.replace(/@[Cc]5@/g, "\1n\1m\1" + "1\1i"); // Blinking magenta on red + text = text.replace(/@[Cc]6@/g, "\1n\1y\1" + "1\1i"); // Blinking yellow/brown on red + text = text.replace(/@[Cc]7@/g, "\1n\1w\1" + "1\1i"); // Blinking white on red + text = text.replace(/@[Cc]8@/g, "\1h\1k\1" + "1\1i"); // Blinking bright black on red + text = text.replace(/@[Cc]9@/g, "\1h\1b\1" + "1\1i"); // Blinking bright blue on red + text = text.replace(/@[Cc][Aa]@/g, "\1h\1g\1" + "1\1i"); // Blinking bright green on red + text = text.replace(/@[Cc][Bb]@/g, "\1h\1c\1" + "1\1i"); // Blinking bright cyan on red + text = text.replace(/@[Cc][Cc]@/g, "\1h\1r\1" + "1\1i"); // Blinking bright red on red + text = text.replace(/@[Cc][Dd]@/g, "\1h\1m\1" + "1\1i"); // Blinking bright magenta on red + text = text.replace(/@[Cc][Ee]@/g, "\1h\1y\1" + "1\1i"); // Blinking bright yellow on red + text = text.replace(/@[Cc][Ff]@/g, "\1h\1w\1" + "1\1i"); // Blinking bright white on red + + // Magenta background, blinking foreground + text = text.replace(/@[Dd]0@/g, "\1n\1k\1" + "5\1i"); // Blinking black on magenta + text = text.replace(/@[Dd]1@/g, "\1n\1b\1" + "5\1i"); // Blinking blue on magenta + text = text.replace(/@[Dd]2@/g, "\1n\1g\1" + "5\1i"); // Blinking green on magenta + text = text.replace(/@[Dd]3@/g, "\1n\1c\1" + "5\1i"); // Blinking cyan on magenta + text = text.replace(/@[Dd]4@/g, "\1n\1r\1" + "5\1i"); // Blinking red on magenta + text = text.replace(/@[Dd]5@/g, "\1n\1m\1" + "5\1i"); // Blinking magenta on magenta + text = text.replace(/@[Dd]6@/g, "\1n\1y\1" + "5\1i"); // Blinking yellow/brown on magenta + text = text.replace(/@[Dd]7@/g, "\1n\1w\1" + "5\1i"); // Blinking white on magenta + text = text.replace(/@[Dd]8@/g, "\1h\1k\1" + "5\1i"); // Blinking bright black on magenta + text = text.replace(/@[Dd]9@/g, "\1h\1b\1" + "5\1i"); // Blinking bright blue on magenta + text = text.replace(/@[Dd][Aa]@/g, "\1h\1g\1" + "5\1i"); // Blinking bright green on magenta + text = text.replace(/@[Dd][Bb]@/g, "\1h\1c\1" + "5\1i"); // Blinking bright cyan on magenta + text = text.replace(/@[Dd][Cc]@/g, "\1h\1r\1" + "5\1i"); // Blinking bright red on magenta + text = text.replace(/@[Dd][Dd]@/g, "\1h\1m\1" + "5\1i"); // Blinking bright magenta on magenta + text = text.replace(/@[Dd][Ee]@/g, "\1h\1y\1" + "5\1i"); // Blinking bright yellow on magenta + text = text.replace(/@[Dd][Ff]@/g, "\1h\1w\1" + "5\1i"); // Blinking bright white on magenta + + // Brown background, blinking foreground + text = text.replace(/@[Ee]0@/g, "\1n\1k\1" + "3\1i"); // Blinking black on brown + text = text.replace(/@[Ee]1@/g, "\1n\1b\1" + "3\1i"); // Blinking blue on brown + text = text.replace(/@[Ee]2@/g, "\1n\1g\1" + "3\1i"); // Blinking green on brown + text = text.replace(/@[Ee]3@/g, "\1n\1c\1" + "3\1i"); // Blinking cyan on brown + text = text.replace(/@[Ee]4@/g, "\1n\1r\1" + "3\1i"); // Blinking red on brown + text = text.replace(/@[Ee]5@/g, "\1n\1m\1" + "3\1i"); // Blinking magenta on brown + text = text.replace(/@[Ee]6@/g, "\1n\1y\1" + "3\1i"); // Blinking yellow/brown on brown + text = text.replace(/@[Ee]7@/g, "\1n\1w\1" + "3\1i"); // Blinking white on brown + text = text.replace(/@[Ee]8@/g, "\1h\1k\1" + "3\1i"); // Blinking bright black on brown + text = text.replace(/@[Ee]9@/g, "\1h\1b\1" + "3\1i"); // Blinking bright blue on brown + text = text.replace(/@[Ee][Aa]@/g, "\1h\1g\1" + "3\1i"); // Blinking bright green on brown + text = text.replace(/@[Ee][Bb]@/g, "\1h\1c\1" + "3\1i"); // Blinking bright cyan on brown + text = text.replace(/@[Ee][Cc]@/g, "\1h\1r\1" + "3\1i"); // Blinking bright red on brown + text = text.replace(/@[Ee][Dd]@/g, "\1h\1m\1" + "3\1i"); // Blinking bright magenta on brown + text = text.replace(/@[Ee][Ee]@/g, "\1h\1y\1" + "3\1i"); // Blinking bright yellow on brown + text = text.replace(/@[Ee][Ff]@/g, "\1h\1w\1" + "3\1i"); // Blinking bright white on brown + + // White background, blinking foreground + text = text.replace(/@[Ff]0@/g, "\1n\1k\1" + "7\1i"); // Blinking black on white + text = text.replace(/@[Ff]1@/g, "\1n\1b\1" + "7\1i"); // Blinking blue on white + text = text.replace(/@[Ff]2@/g, "\1n\1g\1" + "7\1i"); // Blinking green on white + text = text.replace(/@[Ff]3@/g, "\1n\1c\1" + "7\1i"); // Blinking cyan on white + text = text.replace(/@[Ff]4@/g, "\1n\1r\1" + "7\1i"); // Blinking red on white + text = text.replace(/@[Ff]5@/g, "\1n\1m\1" + "7\1i"); // Blinking magenta on white + text = text.replace(/@[Ff]6@/g, "\1n\1y\1" + "7\1i"); // Blinking yellow/brown on white + text = text.replace(/@[Ff]7@/g, "\1n\1w\1" + "7\1i"); // Blinking white on white + text = text.replace(/@[Ff]8@/g, "\1h\1k\1" + "7\1i"); // Blinking bright black on white + text = text.replace(/@[Ff]9@/g, "\1h\1b\1" + "7\1i"); // Blinking bright blue on white + text = text.replace(/@[Ff][Aa]@/g, "\1h\1g\1" + "7\1i"); // Blinking bright green on white + text = text.replace(/@[Ff][Bb]@/g, "\1h\1c\1" + "7\1i"); // Blinking bright cyan on white + text = text.replace(/@[Ff][Cc]@/g, "\1h\1r\1" + "7\1i"); // Blinking bright red on white + text = text.replace(/@[Ff][Dd]@/g, "\1h\1m\1" + "7\1i"); // Blinking bright magenta on white + text = text.replace(/@[Ff][Ee]@/g, "\1h\1y\1" + "7\1i"); // Blinking bright yellow on white + text = text.replace(/@[Ff][Ff]@/g, "\1h\1w\1" + "7\1i"); // Blinking bright white on white + + return text; + } + else + return pText; // No Wildcat-style attribute codes found, so just return the text. +} + +// Converts Celerity attribute codes to Synchronet attribute codes. +// +// Parameters: +// pText: A string containing the text to convert +// +// Return value: The text with the color codes converted +function celerityAttrsToSyncAttrs(pText) +{ + // First, see if the text has any Celerity-style attribute codes at + // all. We'll be performing a bunch of search & replace commands, + // so we don't want to do all that work for nothing.. :) + if (/\|[kbgcrmywdBGCRMYWS]/.test(pText)) + { + // Using the \|S code (swap foreground & background) + + // Blue background + var text = pText.replace(/\|b\|S\|k/g, "\1n\1k\1" + "4"); // Black on blue + text = text.replace(/\|b\|S\|b/g, "\1n\1b\1" + "4"); // Blue on blue + text = text.replace(/\|b\|S\|g/g, "\1n\1g\1" + "4"); // Green on blue + text = text.replace(/\|b\|S\|c/g, "\1n\1c\1" + "4"); // Cyan on blue + text = text.replace(/\|b\|S\|r/g, "\1n\1r\1" + "4"); // Red on blue + text = text.replace(/\|b\|S\|m/g, "\1n\1m\1" + "4"); // Magenta on blue + text = text.replace(/\|b\|S\|y/g, "\1n\1y\1" + "4"); // Yellow/brown on blue + text = text.replace(/\|b\|S\|w/g, "\1n\1w\1" + "4"); // White on blue + text = text.replace(/\|b\|S\|d/g, "\1h\1k\1" + "4"); // Bright black on blue + text = text.replace(/\|b\|S\|B/g, "\1h\1b\1" + "4"); // Bright blue on blue + text = text.replace(/\|b\|S\|G/g, "\1h\1g\1" + "4"); // Bright green on blue + text = text.replace(/\|b\|S\|C/g, "\1h\1c\1" + "4"); // Bright cyan on blue + text = text.replace(/\|b\|S\|R/g, "\1h\1r\1" + "4"); // Bright red on blue + text = text.replace(/\|b\|S\|M/g, "\1h\1m\1" + "4"); // Bright magenta on blue + text = text.replace(/\|b\|S\|Y/g, "\1h\1y\1" + "4"); // Yellow on blue + text = text.replace(/\|b\|S\|W/g, "\1h\1w\1" + "4"); // Bright white on blue + + // Green background + text = text.replace(/\|g\|S\|k/g, "\1n\1k\1" + "2"); // Black on green + text = text.replace(/\|g\|S\|b/g, "\1n\1b\1" + "2"); // Blue on green + text = text.replace(/\|g\|S\|g/g, "\1n\1g\1" + "2"); // Green on green + text = text.replace(/\|g\|S\|c/g, "\1n\1c\1" + "2"); // Cyan on green + text = text.replace(/\|g\|S\|r/g, "\1n\1r\1" + "2"); // Red on green + text = text.replace(/\|g\|S\|m/g, "\1n\1m\1" + "2"); // Magenta on green + text = text.replace(/\|g\|S\|y/g, "\1n\1y\1" + "2"); // Yellow/brown on green + text = text.replace(/\|g\|S\|w/g, "\1n\1w\1" + "2"); // White on green + text = text.replace(/\|g\|S\|d/g, "\1h\1k\1" + "2"); // Bright black on green + text = text.replace(/\|g\|S\|B/g, "\1h\1b\1" + "2"); // Bright blue on green + text = text.replace(/\|g\|S\|G/g, "\1h\1g\1" + "2"); // Bright green on green + text = text.replace(/\|g\|S\|C/g, "\1h\1c\1" + "2"); // Bright cyan on green + text = text.replace(/\|g\|S\|R/g, "\1h\1r\1" + "2"); // Bright red on green + text = text.replace(/\|g\|S\|M/g, "\1h\1m\1" + "2"); // Bright magenta on green + text = text.replace(/\|g\|S\|Y/g, "\1h\1y\1" + "2"); // Yellow on green + text = text.replace(/\|g\|S\|W/g, "\1h\1w\1" + "2"); // Bright white on green + + // Cyan background + text = text.replace(/\|c\|S\|k/g, "\1n\1k\1" + "6"); // Black on cyan + text = text.replace(/\|c\|S\|b/g, "\1n\1b\1" + "6"); // Blue on cyan + text = text.replace(/\|c\|S\|g/g, "\1n\1g\1" + "6"); // Green on cyan + text = text.replace(/\|c\|S\|c/g, "\1n\1c\1" + "6"); // Cyan on cyan + text = text.replace(/\|c\|S\|r/g, "\1n\1r\1" + "6"); // Red on cyan + text = text.replace(/\|c\|S\|m/g, "\1n\1m\1" + "6"); // Magenta on cyan + text = text.replace(/\|c\|S\|y/g, "\1n\1y\1" + "6"); // Yellow/brown on cyan + text = text.replace(/\|c\|S\|w/g, "\1n\1w\1" + "6"); // White on cyan + text = text.replace(/\|c\|S\|d/g, "\1h\1k\1" + "6"); // Bright black on cyan + text = text.replace(/\|c\|S\|B/g, "\1h\1b\1" + "6"); // Bright blue on cyan + text = text.replace(/\|c\|S\|G/g, "\1h\1g\1" + "6"); // Bright green on cyan + text = text.replace(/\|c\|S\|C/g, "\1h\1c\1" + "6"); // Bright cyan on cyan + text = text.replace(/\|c\|S\|R/g, "\1h\1r\1" + "6"); // Bright red on cyan + text = text.replace(/\|c\|S\|M/g, "\1h\1m\1" + "6"); // Bright magenta on cyan + text = text.replace(/\|c\|S\|Y/g, "\1h\1y\1" + "6"); // Yellow on cyan + text = text.replace(/\|c\|S\|W/g, "\1h\1w\1" + "6"); // Bright white on cyan + + // Red background + text = text.replace(/\|r\|S\|k/g, "\1n\1k\1" + "1"); // Black on red + text = text.replace(/\|r\|S\|b/g, "\1n\1b\1" + "1"); // Blue on red + text = text.replace(/\|r\|S\|g/g, "\1n\1g\1" + "1"); // Green on red + text = text.replace(/\|r\|S\|c/g, "\1n\1c\1" + "1"); // Cyan on red + text = text.replace(/\|r\|S\|r/g, "\1n\1r\1" + "1"); // Red on red + text = text.replace(/\|r\|S\|m/g, "\1n\1m\1" + "1"); // Magenta on red + text = text.replace(/\|r\|S\|y/g, "\1n\1y\1" + "1"); // Yellow/brown on red + text = text.replace(/\|r\|S\|w/g, "\1n\1w\1" + "1"); // White on red + text = text.replace(/\|r\|S\|d/g, "\1h\1k\1" + "1"); // Bright black on red + text = text.replace(/\|r\|S\|B/g, "\1h\1b\1" + "1"); // Bright blue on red + text = text.replace(/\|r\|S\|G/g, "\1h\1g\1" + "1"); // Bright green on red + text = text.replace(/\|r\|S\|C/g, "\1h\1c\1" + "1"); // Bright cyan on red + text = text.replace(/\|r\|S\|R/g, "\1h\1r\1" + "1"); // Bright red on red + text = text.replace(/\|r\|S\|M/g, "\1h\1m\1" + "1"); // Bright magenta on red + text = text.replace(/\|r\|S\|Y/g, "\1h\1y\1" + "1"); // Yellow on red + text = text.replace(/\|r\|S\|W/g, "\1h\1w\1" + "1"); // Bright white on red + + // Magenta background + text = text.replace(/\|m\|S\|k/g, "\1n\1k\1" + "5"); // Black on magenta + text = text.replace(/\|m\|S\|b/g, "\1n\1b\1" + "5"); // Blue on magenta + text = text.replace(/\|m\|S\|g/g, "\1n\1g\1" + "5"); // Green on magenta + text = text.replace(/\|m\|S\|c/g, "\1n\1c\1" + "5"); // Cyan on magenta + text = text.replace(/\|m\|S\|r/g, "\1n\1r\1" + "5"); // Red on magenta + text = text.replace(/\|m\|S\|m/g, "\1n\1m\1" + "5"); // Magenta on magenta + text = text.replace(/\|m\|S\|y/g, "\1n\1y\1" + "5"); // Yellow/brown on magenta + text = text.replace(/\|m\|S\|w/g, "\1n\1w\1" + "5"); // White on magenta + text = text.replace(/\|m\|S\|d/g, "\1h\1k\1" + "5"); // Bright black on magenta + text = text.replace(/\|m\|S\|B/g, "\1h\1b\1" + "5"); // Bright blue on magenta + text = text.replace(/\|m\|S\|G/g, "\1h\1g\1" + "5"); // Bright green on magenta + text = text.replace(/\|m\|S\|C/g, "\1h\1c\1" + "5"); // Bright cyan on magenta + text = text.replace(/\|m\|S\|R/g, "\1h\1r\1" + "5"); // Bright red on magenta + text = text.replace(/\|m\|S\|M/g, "\1h\1m\1" + "5"); // Bright magenta on magenta + text = text.replace(/\|m\|S\|Y/g, "\1h\1y\1" + "5"); // Yellow on magenta + text = text.replace(/\|m\|S\|W/g, "\1h\1w\1" + "5"); // Bright white on magenta + + // Brown background + text = text.replace(/\|y\|S\|k/g, "\1n\1k\1" + "3"); // Black on brown + text = text.replace(/\|y\|S\|b/g, "\1n\1b\1" + "3"); // Blue on brown + text = text.replace(/\|y\|S\|g/g, "\1n\1g\1" + "3"); // Green on brown + text = text.replace(/\|y\|S\|c/g, "\1n\1c\1" + "3"); // Cyan on brown + text = text.replace(/\|y\|S\|r/g, "\1n\1r\1" + "3"); // Red on brown + text = text.replace(/\|y\|S\|m/g, "\1n\1m\1" + "3"); // Magenta on brown + text = text.replace(/\|y\|S\|y/g, "\1n\1y\1" + "3"); // Yellow/brown on brown + text = text.replace(/\|y\|S\|w/g, "\1n\1w\1" + "3"); // White on brown + text = text.replace(/\|y\|S\|d/g, "\1h\1k\1" + "3"); // Bright black on brown + text = text.replace(/\|y\|S\|B/g, "\1h\1b\1" + "3"); // Bright blue on brown + text = text.replace(/\|y\|S\|G/g, "\1h\1g\1" + "3"); // Bright green on brown + text = text.replace(/\|y\|S\|C/g, "\1h\1c\1" + "3"); // Bright cyan on brown + text = text.replace(/\|y\|S\|R/g, "\1h\1r\1" + "3"); // Bright red on brown + text = text.replace(/\|y\|S\|M/g, "\1h\1m\1" + "3"); // Bright magenta on brown + text = text.replace(/\|y\|S\|Y/g, "\1h\1y\1" + "3"); // Yellow on brown + text = text.replace(/\|y\|S\|W/g, "\1h\1w\1" + "3"); // Bright white on brown + + // White background + text = text.replace(/\|w\|S\|k/g, "\1n\1k\1" + "7"); // Black on white + text = text.replace(/\|w\|S\|b/g, "\1n\1b\1" + "7"); // Blue on white + text = text.replace(/\|w\|S\|g/g, "\1n\1g\1" + "7"); // Green on white + text = text.replace(/\|w\|S\|c/g, "\1n\1c\1" + "7"); // Cyan on white + text = text.replace(/\|w\|S\|r/g, "\1n\1r\1" + "7"); // Red on white + text = text.replace(/\|w\|S\|m/g, "\1n\1m\1" + "7"); // Magenta on white + text = text.replace(/\|w\|S\|y/g, "\1n\1y\1" + "7"); // Yellow/brown on white + text = text.replace(/\|w\|S\|w/g, "\1n\1w\1" + "7"); // White on white + text = text.replace(/\|w\|S\|d/g, "\1h\1k\1" + "7"); // Bright black on white + text = text.replace(/\|w\|S\|B/g, "\1h\1b\1" + "7"); // Bright blue on white + text = text.replace(/\|w\|S\|G/g, "\1h\1g\1" + "7"); // Bright green on white + text = text.replace(/\|w\|S\|C/g, "\1h\1c\1" + "7"); // Bright cyan on white + text = text.replace(/\|w\|S\|R/g, "\1h\1r\1" + "7"); // Bright red on white + text = text.replace(/\|w\|S\|M/g, "\1h\1m\1" + "7"); // Bright magenta on white + text = text.replace(/\|w\|S\|Y/g, "\1h\1y\1" + "7"); // Yellow on white + text = text.replace(/\|w\|S\|W/g, "\1h\1w\1" + "7"); // Bright white on white + + // Colors on black background + text = text.replace(/\|k/g, "\1n\1k\1" + "0"); // Black on black + text = text.replace(/\|k\|S\|k/g, "\1n\1k\1" + "0"); // Black on black + text = text.replace(/\|b/g, "\1n\1b\1" + "0"); // Blue on black + text = text.replace(/\|k\|S\|b/g, "\1n\1b\1" + "0"); // Blue on black + text = text.replace(/\|g/g, "\1n\1g\1" + "0"); // Green on black + text = text.replace(/\|k\|S\|g/g, "\1n\1g\1" + "0"); // Green on black + text = text.replace(/\|c/g, "\1n\1c\1" + "0"); // Cyan on black + text = text.replace(/\|k\|S\|c/g, "\1n\1c\1" + "0"); // Cyan on black + text = text.replace(/\|r/g, "\1n\1r\1" + "0"); // Red on black + text = text.replace(/\|k\|S\|r/g, "\1n\1r\1" + "0"); // Red on black + text = text.replace(/\|m/g, "\1n\1m\1" + "0"); // Magenta on black + text = text.replace(/\|k\|S\|m/g, "\1n\1m\1" + "0"); // Magenta on black + text = text.replace(/\|y/g, "\1n\1y\1" + "0"); // Yellow/brown on black + text = text.replace(/\|k\|S\|y/g, "\1n\1y\1" + "0"); // Yellow/brown on black + text = text.replace(/\|w/g, "\1n\1w\1" + "0"); // White on black + text = text.replace(/\|k\|S\|w/g, "\1n\1w\1" + "0"); // White on black + text = text.replace(/\|d/g, "\1h\1k\1" + "0"); // Bright black on black + text = text.replace(/\|k\|S\|d/g, "\1h\1k\1" + "0"); // Bright black on black + text = text.replace(/\|B/g, "\1h\1b\1" + "0"); // Bright blue on black + text = text.replace(/\|k\|S\|B/g, "\1h\1b\1" + "0"); // Bright blue on black + text = text.replace(/\|G/g, "\1h\1g\1" + "0"); // Bright green on black + text = text.replace(/\|k\|S\|G/g, "\1h\1g\1" + "0"); // Bright green on black + text = text.replace(/\|C/g, "\1h\1c\1" + "0"); // Bright cyan on black + text = text.replace(/\|k\|S\|C/g, "\1h\1c\1" + "0"); // Bright cyan on black + text = text.replace(/\|R/g, "\1h\1r\1" + "0"); // Bright red on black + text = text.replace(/\|k\|S\|R/g, "\1h\1r\1" + "0"); // Bright red on black + text = text.replace(/\|M/g, "\1h\1m\1" + "0"); // Bright magenta on black + text = text.replace(/\|k\|S\|M/g, "\1h\1m\1" + "0"); // Bright magenta on black + text = text.replace(/\|Y/g, "\1h\1y\1" + "0"); // Yellow on black + text = text.replace(/\|k\|S\|Y/g, "\1h\1y\1" + "0"); // Yellow on black + text = text.replace(/\|W/g, "\1h\1w\1" + "0"); // Bright white on black + text = text.replace(/\|k\|S\|W/g, "\1h\1w\1" + "0"); // Bright white on black + + return text; + } + else + return pText; // No Celerity-style attribute codes found, so just return the text. +} + +// Converts Renegade attribute (color) codes to Synchronet attribute codes. +// +// Parameters: +// pText: A string containing the text to convert +// +// Return value: The text with the color codes converted +function renegadeAttrsToSyncAttrs(pText) +{ + // First, see if the text has any Renegade-style attribute codes at + // all. We'll be performing a bunch of search & replace commands, + // so we don't want to do all that work for nothing.. :) + if (/\|[0-3][0-9]/.test(pText)) + { + var text = pText.replace(/\|00/g, "\1n\1k"); // Normal black + text = text.replace(/\|01/g, "\1n\1b"); // Normal blue + text = text.replace(/\|02/g, "\1n\1g"); // Normal green + text = text.replace(/\|03/g, "\1n\1c"); // Normal cyan + text = text.replace(/\|04/g, "\1n\1r"); // Normal red + text = text.replace(/\|05/g, "\1n\1m"); // Normal magenta + text = text.replace(/\|06/g, "\1n\1y"); // Normal brown + text = text.replace(/\|07/g, "\1n\1w"); // Normal white + text = text.replace(/\|08/g, "\1n\1k\1h"); // High intensity black + text = text.replace(/\|09/g, "\1n\1b\1h"); // High intensity blue + text = text.replace(/\|10/g, "\1n\1g\1h"); // High intensity green + text = text.replace(/\|11/g, "\1n\1c\1h"); // High intensity cyan + text = text.replace(/\|12/g, "\1n\1r\1h"); // High intensity red + text = text.replace(/\|13/g, "\1n\1m\1h"); // High intensity magenta + text = text.replace(/\|14/g, "\1n\1y\1h"); // Yellow (high intensity brown) + text = text.replace(/\|15/g, "\1n\1w\1h"); // High intensity white + text = text.replace(/\|16/g, "\1" + "0"); // Background black + text = text.replace(/\|17/g, "\1" + "4"); // Background blue + text = text.replace(/\|18/g, "\1" + "2"); // Background green + text = text.replace(/\|19/g, "\1" + "6"); // Background cyan + text = text.replace(/\|20/g, "\1" + "1"); // Background red + text = text.replace(/\|21/g, "\1" + "5"); // Background magenta + text = text.replace(/\|22/g, "\1" + "3"); // Background brown + text = text.replace(/\|23/g, "\1" + "7"); // Background white + text = text.replace(/\|24/g, "\1i\1w\1" + "0"); // Blinking white on black + text = text.replace(/\|25/g, "\1i\1w\1" + "4"); // Blinking white on blue + text = text.replace(/\|26/g, "\1i\1w\1" + "2"); // Blinking white on green + text = text.replace(/\|27/g, "\1i\1w\1" + "6"); // Blinking white on cyan + text = text.replace(/\|28/g, "\1i\1w\1" + "1"); // Blinking white on red + text = text.replace(/\|29/g, "\1i\1w\1" + "5"); // Blinking white on magenta + text = text.replace(/\|30/g, "\1i\1w\1" + "3"); // Blinking white on yellow/brown + text = text.replace(/\|31/g, "\1i\1w\1" + "7"); // Blinking white on white + return text; + } + else + return pText; // No Renegade-style attribute codes found, so just return the text. +} + +// Converts ANSI attribute codes to Synchronet attribute codes. This +// is incomplete and doesn't convert all ANSI codes perfectly to Synchronet +// attribute codes. +// +// Parameters: +// pText: A string containing the text to convert +// +// Return value: The text with ANSI codes converted to Synchronet attribute codes +function ANSIAttrsToSyncAttrs(pText) +{ + // TODO: Test & update this some more.. Not sure if this is working 100% right. + + // Web pages with ANSI code information: + // http://pueblo.sourceforge.net/doc/manual/ansi_color_codes.html + // http://ascii-table.com/ansi-escape-sequences.php + // http://stackoverflow.com/questions/4842424/list-of-ansi-color-escape-sequences + + // If the string has ANSI codes in it, then go ahead and replace ANSI + // codes with Synchronet attribute codes. + if (textHasANSICodes(pText)) + { + // Attributes + var txt = pText.replace(/\033\[0[mM]/g, "\1n"); // All attributes off + txt = txt.replace(/\033\[1[mM]/g, "\1h"); // Bold on (use high intensity) + txt = txt.replace(/\033\[5[mM]/g, "\1i"); // Blink on + // Foreground colors + txt = txt.replace(/\033\[30[mM]/g, "\1k"); // Black foreground + txt = txt.replace(/\033\[31[mM]/g, "\1r"); // Red foreground + txt = txt.replace(/\033\[32[mM]/g, "\1g"); // Green foreground + txt = txt.replace(/\033\[33[mM]/g, "\1y"); // Yellow foreground + txt = txt.replace(/\033\[34[mM]/g, "\1b"); // Blue foreground + txt = txt.replace(/\033\[35[mM]/g, "\1m"); // Magenta foreground + txt = txt.replace(/\033\[36[mM]/g, "\1c"); // Cyan foreground + txt = txt.replace(/\033\[37[mM]/g, "\1w"); // White foreground + // Background colors + txt = txt.replace(/\033\[40[mM]/g, "\1" + "0"); // Black background + txt = txt.replace(/\033\[41[mM]/g, "\1" + "1"); // Red background + txt = txt.replace(/\033\[42[mM]/g, "\1" + "2"); // Green background + txt = txt.replace(/\033\[43[mM]/g, "\1" + "3"); // Yellow background + txt = txt.replace(/\033\[44[mM]/g, "\1" + "4"); // Blue background + txt = txt.replace(/\033\[45[mM]/g, "\1" + "5"); // Magenta background + txt = txt.replace(/\033\[46[mM]/g, "\1" + "6"); // Cyan background + txt = txt.replace(/\033\[47[mM]/g, "\1" + "7"); // White background + // Convert ;-delimited modes (such as \033[Value;...;Valuem) + txt = ANSIMultiConvertToSyncCodes(txt); + // Remove ANSI codes that are not wanted (such as moving the cursor, etc.) + txt = txt.replace(/\033\[[0-9]+[aA]/g, ""); // Cursor up + txt = txt.replace(/\033\[[0-9]+[bB]/g, ""); // Cursor down + txt = txt.replace(/\033\[[0-9]+[cC]/g, ""); // Cursor forward + txt = txt.replace(/\033\[[0-9]+[dD]/g, ""); // Cursor backward + txt = txt.replace(/\033\[[0-9]+;[0-9]+[hH]/g, ""); // Cursor position + txt = txt.replace(/\033\[[0-9]+;[0-9]+[fF]/g, ""); // Cursor position + txt = txt.replace(/\033\[[sS]/g, ""); // Restore cursor position + txt = txt.replace(/\033\[2[jJ]/g, ""); // Erase display + txt = txt.replace(/\033\[[kK]/g, ""); // Erase line + txt = txt.replace(/\033\[=[0-9]+[hH]/g, ""); // Set various screen modes + txt = txt.replace(/\033\[=[0-9]+[lL]/g, ""); // Reset various screen modes + return txt; + } + else + return pText; // No ANSI codes found, so just return the text. +} + +// Returns whether or not some text has any ANSI codes in it. +// +// Parameters: +// pText: The text to test +// +// Return value: Boolean - Whether or not the text has ANSI codes in it +function textHasANSICodes(pText) +{ + + return(/\033\[[0-9]+[mM]/.test(pText) || /\033\[[0-9]+(;[0-9]+)+[mM]/.test(pText) || + /\033\[[0-9]+[aAbBcCdD]/.test(pText) || /\033\[[0-9]+;[0-9]+[hHfF]/.test(pText) || + /\033\[[sSuUkK]/.test(pText) || /\033\[2[jJ]/.test(pText)); + /* + var regex1 = new RegExp(ascii(27) + "\[[0-9]+[mM]"); + var regex2 = new RegExp(ascii(27) + "\[[0-9]+(;[0-9]+)+[mM]"); + var regex3 = new RegExp(ascii(27) + "\[[0-9]+[aAbBcCdD]"); + var regex4 = new RegExp(ascii(27) + "\[[0-9]+;[0-9]+[hHfF]"); + var regex5 = new RegExp(ascii(27) + "\[[sSuUkK]"); + var regex6 = new RegExp(ascii(27) + "\[2[jJ]"); + return(regex1.test(pText) || regex2.test(pText) || regex3.test(pText) || + regex4.test(pText) || regex5.test(pText) || regex6.test(pText)); + */ +} + +// Converts ANSI ;-delimited modes (such as [Value;...;Valuem) to Synchronet +// attribute codes +// +// Parameters: +// pText: The text with ANSI ;-delimited modes to convert +// +// Return value: The text with ANSI ;-delimited codes converted to Synchronet attributes +function ANSIMultiConvertToSyncCodes(pText) +{ + var multiMatches = pText.match(/\033\[[0-9]+(;[0-9]+)+m/g); + if (multiMatches == null) + return pText; + var updatedText = pText; + for (var i = 0; i < multiMatches.length; ++i) + { + // Copy the string, with the \033[ removed from the beginning and the + // trailing 'm' removed + var text = multiMatches[i].substr(2); + text = text.substr(0, text.length-1); + var codes = text.split(";"); + var syncCodes = ""; + for (var idx = 0; idx < codes.length; ++idx) + { + if (codes[idx] == "0") // All attributes off + syncCodes += "\1n"; + else if (codes[idx] == "1") // Bold on (high intensity) + syncCodes += "\1h"; + else if (codes[idx] == "5") // Blink on + syncCodes += "\1i"; + else if (codes[idx] == "30") // Black foreground + syncCodes += "\1k"; + else if (codes[idx] == "31") // Red foreground + syncCodes += "\1r"; + else if (codes[idx] == "32") // Green foreground + syncCodes += "\1g"; + else if (codes[idx] == "33") // Yellow foreground + syncCodes += "\1y"; + else if (codes[idx] == "34") // Blue foreground + syncCodes += "\1b"; + else if (codes[idx] == "35") // Magenta foreground + syncCodes += "\1m"; + else if (codes[idx] == "36") // Cyan foreground + syncCodes += "\1c"; + else if (codes[idx] == "37") // White foreground + syncCodes += "\1w"; + else if (codes[idx] == "40") // Black background + syncCodes += "\1" + "0"; + else if (codes[idx] == "41") // Red background + syncCodes += "\1" + "1"; + else if (codes[idx] == "42") // Green background + syncCodes += "\1" + "2"; + else if (codes[idx] == "43") // Yellow background + syncCodes += "\1" + "3"; + else if (codes[idx] == "44") // Blue background + syncCodes += "\1" + "4"; + else if (codes[idx] == "45") // Magenta background + syncCodes += "\1" + "5"; + else if (codes[idx] == "46") // Cyan background + syncCodes += "\1" + "6"; + else if (codes[idx] == "47") // White background + syncCodes += "\1" + "7"; + } + updatedText = updatedText.replace(multiMatches[i], syncCodes); + } + return updatedText; +} + +// Converts non-Synchronet attribute codes in text to Synchronet attribute +// codes according to the toggle options in SCFG > Message Options > Extra +// Attribute Codes +// +// Parameters: +// pText: The text to be converted +// +// Return value: The text with various other system attribute codes converted +// to Synchronet attribute codes, or not, depending on the toggle +// options in Extra Attribute Codes in SCFG +function convertAttrsToSyncPerSysCfg(pText) +{ + // Convert any ANSI codes to Synchronet attribute codes. + // Then convert other BBS attribute codes to Synchronet attribute + // codes according to the current system configuration. + var convertedText = ANSIAttrsToSyncAttrs(pText); + if ((system.settings & SYS_RENEGADE) == SYS_RENEGADE) + convertedText = renegadeAttrsToSyncAttrs(convertedText); + if ((system.settings & SYS_WWIV) == SYS_WWIV) + convertedText = WWIVAttrsToSyncAttrs(convertedText); + if ((system.settings & SYS_CELERITY) == SYS_CELERITY) + convertedText = celerityAttrsToSyncAttrs(convertedText); + if ((system.settings & SYS_PCBOARD) == SYS_PCBOARD) + convertedText = PCBoardAttrsToSyncAttrs(convertedText); + if ((system.settings & SYS_WILDCAT) == SYS_WILDCAT) + convertedText = wildcatAttrsToSyncAttrs(convertedText); + return convertedText; +} + +// Converts Synchronet attribute codes to ANSI ;-delimited modes (such as \033[Value;...;Valuem) +// +// Parameters: +// pText: The text with Synchronet codes to convert +// +// Return value: The text with Synchronet attributes converted to ANSI ;-delimited codes +function syncAttrCodesToANSI(pText) +{ + // First, see if the text has any Synchronet attribute codes at + // all. We'll be performing a bunch of search & replace commands, + // so we don't want to do all that work for nothing.. :) + if (hasSyncAttrCodes(pText)) + { + var ANSIESCCodeStart = "\033["; + var newText = pText.replace(/\1n/gi, ANSIESCCodeStart + "0m"); // Normal + newText = newText.replace(/\1-/gi, ANSIESCCodeStart + "0m"); // Normal + newText = newText.replace(/\1_/gi, ANSIESCCodeStart + "0m"); // Normal + newText = newText.replace(/\1h/gi, ANSIESCCodeStart + "1m"); // High intensity/bold + newText = newText.replace(/\1i/gi, ANSIESCCodeStart + "5m"); // Blinking on + newText = newText.replace(/\1f/gi, ANSIESCCodeStart + "5m"); // Blinking on + newText = newText.replace(/\1k/gi, ANSIESCCodeStart + "30m"); // Black foreground + newText = newText.replace(/\1r/gi, ANSIESCCodeStart + "31m"); // Red foreground + newText = newText.replace(/\1g/gi, ANSIESCCodeStart + "32m"); // Green foreground + newText = newText.replace(/\1y/gi, ANSIESCCodeStart + "33m"); // Yellow/brown foreground + newText = newText.replace(/\1b/gi, ANSIESCCodeStart + "34m"); // Blue foreground + newText = newText.replace(/\1m/gi, ANSIESCCodeStart + "35m"); // Magenta foreground + newText = newText.replace(/\1c/gi, ANSIESCCodeStart + "36m"); // Cyan foreground + newText = newText.replace(/\1w/gi, ANSIESCCodeStart + "37m"); // White foreground + newText = newText.replace(/\1[0]/gi, ANSIESCCodeStart + "40m"); // Black background + newText = newText.replace(/\1[1]/gi, ANSIESCCodeStart + "41m"); // Red background + newText = newText.replace(/\1[2]/gi, ANSIESCCodeStart + "42m"); // Green background + newText = newText.replace(/\1[3]/gi, ANSIESCCodeStart + "43m"); // Yellow/brown background + newText = newText.replace(/\1[4]/gi, ANSIESCCodeStart + "44m"); // Blue background + newText = newText.replace(/\1[5]/gi, ANSIESCCodeStart + "45m"); // Magenta background + newText = newText.replace(/\1[6]/gi, ANSIESCCodeStart + "46m"); // Cyan background + newText = newText.replace(/\1[7]/gi, ANSIESCCodeStart + "47m"); // White background + return newText; + } + else + return pText; // No Synchronet-style attribute codes found, so just return the text. +} \ No newline at end of file diff --git a/exec/load/dd_lightbar_menu.js b/exec/load/dd_lightbar_menu.js index 1c0e7e4d8d69115a2d1f9b8b7d938ca344ec2cd3..2046978fce9e291c7ce5bfffa099fd2c45cdf4f7 100644 --- a/exec/load/dd_lightbar_menu.js +++ b/exec/load/dd_lightbar_menu.js @@ -281,6 +281,11 @@ menu with a custom OnItemSelect() function specified and you want the menu to co be displayed allowing the user to select an item. lbMenu.exitOnItemSelect = false; +OnItemNav is a function that is called when the user navigates to a new item (i.e., via +the up or down arrow, PageUp, PageDown, Home, End, etc.). Its parameters are the old +item index and the new item index. +this.OnItemNav = function(pOldItemIdx, pNewItemIdx) { } + The 'key down' behavior can be called explicitly, if needed, by calling the DoKeyDown() function. It takes 2 parameters: An object of selected item indexes (as passed to GetVal()) and, optionally, the pre-calculated number of items. @@ -509,6 +514,10 @@ function DDLightbarMenu(pX, pY, pWidth, pHeight) this.GetColorForItem = DDLightbarMenu_GetColorForItem; this.GetSelectedColorForItem = DDLightbarMenu_GetSelectedColorForItem; this.SetSelectedItemIdx = DDLightbarMenu_SetSelectedItemIdx; + this.GetBottomItemIdx = DDLightbarMenu_GetBottomItemIdx; + this.GetTopDisplayedItemPos = DDLightbarMenu_GetTopDisplayedItemPos; + this.GetBottomDisplayedItemPos = DDLightbarMenu_GetBottomDisplayedItemPos; + this.ScreenRowForItem = DDLightbarMenu_ScreenRowForItem; // ValidateSelectItem is a function for validating that the user can select an item. // It takes the selected item's return value and returns a boolean to signify whether @@ -524,6 +533,10 @@ function DDLightbarMenu(pX, pY, pWidth, pHeight) // is possible when multi-select is enabled. this.OnItemSelect = function(pItemRetval, pSelected) { } + // OnItemNav is a function that is called when the user navigates to + // new item (i.e., up/down arrow, pageUp, pageDown, home, end) + this.OnItemNav = function(pOldItemIdx, pNewItemIdx) { } + // Set some things based on the parameters passed in if ((typeof(pX) == "number") && (typeof(pY) == "number")) this.SetPos(pX, pY); @@ -1027,8 +1040,15 @@ function DDLightbarMenu_DrawPartial(pStartX, pStartY, pWidth, pHeight, pSelected if (this.scrollbarEnabled && !this.CanShowAllItemsInWindow()) { var scrollbarCol = this.borderEnabled ? this.pos.x + this.size.width - 2 : this.pos.x + this.size.width - 1; - if (this.pos.x + pStartX + width - 1 >= scrollbarCol) // The last column drawn includes the scrollbar - --itemLen; + // If the rightmost column is at or past the scrollbar column, + // then subtract from the item length so that we don't overwrite + // the scrollbar. + var rightmostCol = this.pos.x + pStartX + width - 2; + if (rightmostCol >= scrollbarCol) + { + var lenDiff = scrollbarCol - rightmostCol + 1; // The amount to subtract from the length + itemLen -= lenDiff; + } if (!this.borderEnabled && pStartX == this.size.width) writeMenuItems = false; // Just draw the whole srollbar to ensure it's updated @@ -1509,7 +1529,7 @@ function DDLightbarMenu_GetVal(pDraw, pSelectedItemIndexes) { // Draw the current item in regular colors this.WriteItemAtItsLocation(this.selectedItemIdx, false, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx)); - --this.selectedItemIdx; + var oldSelectedItemIdx = this.selectedItemIdx--; // Draw the new current item in selected colors // If the selected item is above the top of the menu, then we'll need to // scroll the items down. @@ -1524,6 +1544,8 @@ function DDLightbarMenu_GetVal(pDraw, pSelectedItemIndexes) // just draw the selected item highlighted. this.WriteItemAtItsLocation(this.selectedItemIdx, true, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx)); } + if (typeof(this.OnItemNav) === "function") + this.OnItemNav(oldSelectedItemIdx, this.selectedItemIdx); } else { @@ -1535,6 +1557,7 @@ function DDLightbarMenu_GetVal(pDraw, pSelectedItemIndexes) //this.WriteItemAtItsLocation(pIdx, pHighlight, pSelected) this.WriteItemAtItsLocation(this.selectedItemIdx, false, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx)); // Go to the last item and scroll to the bottom if necessary + var oldSelectedItemIdx = this.selectedItemIdx; this.selectedItemIdx = numItems - 1; var oldTopItemIdx = this.topItemIdx; var numItemsPerPage = (this.borderEnabled ? this.size.height - 2 : this.size.height); @@ -1548,6 +1571,8 @@ function DDLightbarMenu_GetVal(pDraw, pSelectedItemIndexes) // Draw the new current item in selected colors this.WriteItemAtItsLocation(this.selectedItemIdx, true, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx)); } + if (typeof(this.OnItemNav) === "function") + this.OnItemNav(oldSelectedItemIdx, this.selectedItemIdx); } } } @@ -1560,6 +1585,7 @@ function DDLightbarMenu_GetVal(pDraw, pSelectedItemIndexes) // Only do this if we're not already at the top of the list if (this.topItemIdx > 0) { + var oldSelectedItemIdx = this.selectedItemIdx; var numItemsPerPage = (this.borderEnabled ? this.size.height - 2 : this.size.height); var newTopItemIdx = this.topItemIdx - numItemsPerPage; if (newTopItemIdx < 0) @@ -1589,6 +1615,8 @@ function DDLightbarMenu_GetVal(pDraw, pSelectedItemIndexes) this.Draw(selectedItemIndexes); } } + if (typeof(this.OnItemNav) === "function") + this.OnItemNav(oldSelectedItemIdx, this.selectedItemIdx); } else { @@ -1597,9 +1625,12 @@ function DDLightbarMenu_GetVal(pDraw, pSelectedItemIndexes) // item, then make it so. if (this.selectedItemIdx > 0) { + var oldSelectedItemIdx = this.selectedItemIdx; this.WriteItemAtItsLocation(this.selectedItemIdx, false, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx)); this.selectedItemIdx = 0; this.WriteItemAtItsLocation(this.selectedItemIdx, true, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx)); + if (typeof(this.OnItemNav) === "function") + this.OnItemNav(oldSelectedItemIdx, this.selectedItemIdx); } } } @@ -1610,6 +1641,7 @@ function DDLightbarMenu_GetVal(pDraw, pSelectedItemIndexes) var lastItemIdx = this.NumItems() - 1; if (lastItemIdx > this.topItemIdx+numItemsPerPage-1) { + var oldSelectedItemIdx = this.selectedItemIdx; // Figure out the top index for the last page. var topIndexForLastPage = numItems - numItemsPerPage; if (topIndexForLastPage < 0) @@ -1638,6 +1670,8 @@ function DDLightbarMenu_GetVal(pDraw, pSelectedItemIndexes) } this.Draw(selectedItemIndexes); } + if (typeof(this.OnItemNav) === "function") + this.OnItemNav(oldSelectedItemIdx, this.selectedItemIdx); } else { @@ -1646,9 +1680,12 @@ function DDLightbarMenu_GetVal(pDraw, pSelectedItemIndexes) // item, then make it so. if (this.selectedItemIdx < lastItemIdx) { + var oldSelectedItemIdx = this.selectedItemIdx; this.WriteItemAtItsLocation(this.selectedItemIdx, false, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx)); this.selectedItemIdx = lastItemIdx; this.WriteItemAtItsLocation(this.selectedItemIdx, true, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx)); + if (typeof(this.OnItemNav) === "function") + this.OnItemNav(oldSelectedItemIdx, this.selectedItemIdx); } } } @@ -1657,6 +1694,7 @@ function DDLightbarMenu_GetVal(pDraw, pSelectedItemIndexes) // Go to the first item in the list if (this.selectedItemIdx > 0) { + var oldSelectedItemIdx = this.selectedItemIdx; // If the current item index is not on first current page, then scroll. // Otherwise, draw more efficiently by drawing the current item in // regular colors and the first item in highlighted colors. @@ -1685,6 +1723,8 @@ function DDLightbarMenu_GetVal(pDraw, pSelectedItemIndexes) console.gotoxy(this.pos.x, this.pos.y+this.selectedItemIdx-this.topItemIdx); this.WriteItem(this.selectedItemIdx, null, true, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx)); } + if (typeof(this.OnItemNav) === "function") + this.OnItemNav(oldSelectedItemIdx, this.selectedItemIdx); } } else if (this.lastUserInput == KEY_END) @@ -1693,6 +1733,7 @@ function DDLightbarMenu_GetVal(pDraw, pSelectedItemIndexes) var numItemsPerPage = this.GetNumItemsPerPage(); if (this.selectedItemIdx < numItems-1) { + var oldSelectedItemIdx = this.selectedItemIdx; var lastPossibleTop = numItems - numItemsPerPage; if (lastPossibleTop < 0) lastPossibleTop = 0; @@ -1726,6 +1767,8 @@ function DDLightbarMenu_GetVal(pDraw, pSelectedItemIndexes) console.gotoxy(this.pos.x, this.pos.y+this.selectedItemIdx-this.topItemIdx); this.WriteItem(this.selectedItemIdx, null, true, selectedItemIndexes.hasOwnProperty(this.selectedItemIdx)); } + if (typeof(this.OnItemNav) === "function") + this.OnItemNav(oldSelectedItemIdx, this.selectedItemIdx); } } // Enter key or additional select-item key: Select the item & quit out of the input loop @@ -1847,6 +1890,7 @@ function DDLightbarMenu_GetVal(pDraw, pSelectedItemIndexes) // and stop the input loop. if (userEnteredItemNum > 0) { + var oldSelectedItemIdx = this.selectedItemIdx; this.selectedItemIdx = userEnteredItemNum-1; if (this.multiSelect) { @@ -1868,6 +1912,8 @@ function DDLightbarMenu_GetVal(pDraw, pSelectedItemIndexes) retVal = this.GetItem(this.selectedItemIdx).retval; continueOn = false; } + if (typeof(this.OnItemNav) === "function") + this.OnItemNav(oldSelectedItemIdx, this.selectedItemIdx); } else console.gotoxy(originalCurpos); // Move the cursor back where it was @@ -1905,8 +1951,11 @@ function DDLightbarMenu_GetVal(pDraw, pSelectedItemIndexes) else { retVal = theItem.retval; + var oldSelectedItemIdx = this.selectedItemIdx; this.selectedItemIdx = i; continueOn = false; + if (typeof(this.OnItemNav) === "function") + this.OnItemNav(oldSelectedItemIdx, this.selectedItemIdx); } break; } @@ -1946,7 +1995,7 @@ function DDLightbarMenu_DoKeyDown(pSelectedItemIndexes, pNumItems) { // Draw the current item in regular colors this.WriteItemAtItsLocation(this.selectedItemIdx, false, selectedItemIndexes.hasOwnProperty(+(this.selectedItemIdx))); - ++this.selectedItemIdx; + var oldSelectedItemIdx = this.selectedItemIdx++; // Draw the new current item in selected colors // If the selected item is below the bottom of the menu, then we'll need to // scroll the items up. @@ -1962,6 +2011,8 @@ function DDLightbarMenu_DoKeyDown(pSelectedItemIndexes, pNumItems) // just draw the selected item highlighted. this.WriteItemAtItsLocation(this.selectedItemIdx, true, selectedItemIndexes.hasOwnProperty(+(this.selectedItemIdx))); } + if (typeof(this.OnItemNav) === "function") + this.OnItemNav(oldSelectedItemIdx, this.selectedItemIdx); } else { @@ -1972,6 +2023,7 @@ function DDLightbarMenu_DoKeyDown(pSelectedItemIndexes, pNumItems) // Draw the current item in regular colors this.WriteItemAtItsLocation(this.selectedItemIdx, false, selectedItemIndexes.hasOwnProperty(+(this.selectedItemIdx))); // Go to the first item and scroll to the top if necessary + var oldSelectedItemIdx = this.selectedItemIdx; this.selectedItemIdx = 0; var oldTopItemIdx = this.topItemIdx; this.topItemIdx = 0; @@ -1982,6 +2034,8 @@ function DDLightbarMenu_DoKeyDown(pSelectedItemIndexes, pNumItems) // Draw the new current item in selected colors this.WriteItemAtItsLocation(this.selectedItemIdx, true, selectedItemIndexes.hasOwnProperty(+(this.selectedItemIdx))); } + if (typeof(this.OnItemNav) === "function") + this.OnItemNav(oldSelectedItemIdx, this.selectedItemIdx); } } } @@ -2449,6 +2503,79 @@ function DDLightbarMenu_SetSelectedItemIdx(pSelectedItemIdx) this.topItemIdx = this.selectedItemIdx; } +// Gets the index of the bottommost item on the menu +function DDLightbarMenu_GetBottomItemIdx() +{ + var bottomItemIdx = this.topItemIdx + this.size.height - 1; + if (this.borderEnabled) + bottomItemIdx -= 2; + return bottomItemIdx; +} + +// Returns the absolute screen position (x, y) of the topmost displayed item on the menu +// +// Return value: An object with the following properties: +// x: The horizontal screen location of the top item (1-based) +// y: The vertical screen location of the top item (1-based) +function DDLightbarMenu_GetTopDisplayedItemPos() +{ + var itemPos = { + x: this.pos.x, + y: this.pos.y + }; + if (this.borderEnabled) + { + ++itemPos.x; + ++itemPos.y; + } + return itemPos; +} + +// Returns the absolute screen position (x, y) of the bottommost displayed item on the menu +// +// Return value: An object with the following properties: +// x: The horizontal screen location of the top item (1-based) +// y: The vertical screen location of the top item (1-based) +function DDLightbarMenu_GetBottomDisplayedItemPos() +{ + var itemPos = { + x: this.pos.x, + y: this.pos.y + this.size.height - 1 + }; + if (this.borderEnabled) + { + ++itemPos.x; + --itemPos.y; + } + return itemPos; +} + +// Returns the absolute screen row number for an item index, if it is visible +// on the menu. If the item is not visible on the menu, this will return -1. +// +// Parameters: +// pItemIdx: The index of the menu item to check +// +// Return value: The absolute row number on the screen where the item is, if it is +// visible on the menu, or -1 if the item is not visible on the menu. +function DDLightbarMenu_ScreenRowForItem(pItemIdx) +{ + if (typeof(pItemIdx) !== "number") + return -1; + if (pItemIdx < 0 || pItemIdx >= this.NumItems()) + return -1; + + var screenRow = -1; + if (pItemIdx >= this.topItemIdx && pItemIdx <= this.GetBottomItemIdx()) + { + if (this.borderEnabled) + screenRow = this.pos.y + pItemIdx - this.topItemIdx + 1; + else + screenRow = this.pos.y + pItemIdx - this.topItemIdx; + } + return screenRow; +} + // Calculates the number of solid scrollbar blocks & non-solid scrollbar blocks // to use. Saves the information in this.scrollbarInfo.numSolidScrollBlocks and // this.scrollbarInfo.numNonSolidScrollBlocks. diff --git a/xtrn/ddfilelister/ddfilelister.js b/xtrn/ddfilelister/ddfilelister.js index a1a9e101403b58730f1c2499aba833e8b72662de..c54a109bdd5ef8e92ac53c1c423066ea6a3f1c08 100644 --- a/xtrn/ddfilelister/ddfilelister.js +++ b/xtrn/ddfilelister/ddfilelister.js @@ -30,6 +30,15 @@ * 2022-03-09 Eric Oulashin Version 2.04 * Bug fix: Now successfully formats filenames without extensions * when listing files. + * 2022-03-12 Eric Oulashin Version 2.05 + * Now makes use of the user's extended file description setting: + * If the user's extended file description setting is enabled, + * the lister will now show extended file descriptions on the + * main screen in a split format, with the lightbar file list + * on the left and the extended file description for the + * highlighted file on the right. Also, made the file info + * window taller for terminals within 25 lines high. + * I had started work on this on March 9, 2022. */ if (typeof(require) === "function") @@ -40,6 +49,7 @@ if (typeof(require) === "function") require("frame.js", "Frame"); require("scrollbar.js", "ScrollBar"); require("mouse_getkey.js", "mouse_getkey"); + require("attr_conv.js", "convertAttrsToSyncPerSysCfg"); } else { @@ -49,6 +59,7 @@ else load("frame.js"); load("scrollbar.js"); load("mouse_getkey.js"); + load("attr_conv.js"); } @@ -83,8 +94,8 @@ if (system.version_num < 31900) } // Lister version information -var LISTER_VERSION = "2.04"; -var LISTER_DATE = "2022-03-09"; +var LISTER_VERSION = "2.05"; +var LISTER_DATE = "2022-03-12"; /////////////////////////////////////////////////////////////////////////////// @@ -114,10 +125,6 @@ var gListIdxes = { // The end index of each column includes the trailing space so that // highlight colors will highlight the whole field gListIdxes.filenameEnd = gListIdxes.filenameStart + 13; -// For terminals that are at least 100 characters wide, allow 10 more characters -// for the filename. This will also give more space for the description. -if (console.screen_columns >= 100) - gListIdxes.filenameEnd += 10; gListIdxes.fileSizeStart = gListIdxes.filenameEnd; gListIdxes.fileSizeEnd = gListIdxes.fileSizeStart + 7; gListIdxes.descriptionStart = gListIdxes.fileSizeEnd; @@ -208,7 +215,10 @@ parseArgs(argv); // This array will contain file metadata objects var gFileList = []; -// Populate the file list based on the script mode (list/search) +// Populate the file list based on the script mode (list/search). +// It's important that this is called before createFileListMenu(), +// since this adjusts gListIdxes.filenameEnd based on the longest +// filename length and terminal width. var listPopRetObj = populateFileList(gScriptMode); if (listPopRetObj.exitNow) exit(listPopRetObj.exitCode); @@ -240,6 +250,9 @@ var gFileListMenu = createFileListMenu(fileMenuBar.getAllActionKeysStr(true, tru // In a loop, show the file list menu, allowing the user to scroll the file list, // and respond to user input until the user decides to quit. gFileListMenu.Draw({}); +// If using extended descriptions, write the first file's description on the screen +if (extendedDescEnabled()) + displayFileExtDescOnMainScreen(0); var continueDoingFileList = true; var drawFileListMenu = false; // For screen refresh optimization while (continueDoingFileList) @@ -248,6 +261,7 @@ while (continueDoingFileList) for (var prop in gFileListMenu.selectedItemIndexes) delete gFileListMenu.selectedItemIndexes[prop]; var actionRetObj = null; + var currentActionVal = null; var userChoice = gFileListMenu.GetVal(drawFileListMenu, gFileListMenu.selectedItemIndexes); drawFileListMenu = false; // For screen refresh optimization var lastUserInputUpper = gFileListMenu.lastUserInput != null ? gFileListMenu.lastUserInput.toUpperCase() : null; @@ -259,7 +273,7 @@ while (continueDoingFileList) fileMenuBar.incrementMenuItemAndRefresh(); else if (lastUserInputUpper == KEY_ENTER) { - var currentActionVal = fileMenuBar.getCurrentSelectedAction(); + currentActionVal = fileMenuBar.getCurrentSelectedAction(); fileMenuBar.setCurrentActionCode(currentActionVal); actionRetObj = doAction(currentActionVal, gFileList, gFileListMenu); } @@ -270,11 +284,12 @@ while (continueDoingFileList) { fileMenuBar.setCurrentActionCode(FILE_DELETE, true); actionRetObj = doAction(FILE_DELETE, gFileList, gFileListMenu); + currentActionVal = FILE_DELETE; } } else { - var currentActionVal = fileMenuBar.getActionFromChar(lastUserInputUpper, false); + currentActionVal = fileMenuBar.getActionFromChar(lastUserInputUpper, false); fileMenuBar.setCurrentActionCode(currentActionVal, true); actionRetObj = doAction(currentActionVal, gFileList, gFileListMenu); } @@ -300,47 +315,87 @@ while (continueDoingFileList) if (actionRetObj.reDrawCmdBar) // Could call fileMenuBar.constructPromptText(); if needed fileMenuBar.writePromptLine(); var redrewPartOfFileListMenu = false; - if (actionRetObj.fileListPartialRedrawInfo != null) + // If we are to re-draw the main screen content, then + // enable the flag to draw the file list menu on the next + // GetVal(); also, if extended descriptions are being shown, + // write the current file's extended description too. + if (actionRetObj.reDrawMainScreenContent) { - drawFileListMenu = false; - var startX = actionRetObj.fileListPartialRedrawInfo.startX; - var startY = actionRetObj.fileListPartialRedrawInfo.startY; - var width = actionRetObj.fileListPartialRedrawInfo.width; - var height = actionRetObj.fileListPartialRedrawInfo.height; - gFileListMenu.DrawPartial(startX, startY, width, height, {}); - redrewPartOfFileListMenu = true; + drawFileListMenu = true; + if (extendedDescEnabled()) + displayFileExtDescOnMainScreen(gFileListMenu.selectedItemIdx); } else { - continueDoingFileList = actionRetObj.continueFileLister; - drawFileListMenu = actionRetObj.reDrawFileListMenu; - } - // If we're not redrawing the whole file list menu, then remove - // checkmarks from any selected files - if (!drawFileListMenu && gFileListMenu.numSelectedItemIndexes() > 0) - { - var lastItemIdxOnScreen = gFileListMenu.topItemIdx + gFileListMenu.size.height - 1; - var listItemStartRow = gFileListMenu.pos.y; - var redrawStartX = gFileListMenu.pos.x + gFileListMenu.size.width - 1; - var redrawWidth = 1; - if (gFileListMenu.borderEnabled) // Shouldn't have this enabled + // If there is partial redraw information available, then use it + // to re-draw that part of the main screen + if (actionRetObj.fileListPartialRedrawInfo != null) + { + drawFileListMenu = false; + var startX = actionRetObj.fileListPartialRedrawInfo.absStartX; + var startY = actionRetObj.fileListPartialRedrawInfo.absStartY; + var width = actionRetObj.fileListPartialRedrawInfo.width; + var height = actionRetObj.fileListPartialRedrawInfo.height; + refreshScreenMainContent(startX, startY, width, height, true); + actionRetObj.refreshedSelectedFilesAlready = true; + redrewPartOfFileListMenu = true; + } + else { - --lastItemIdxOnScreen; - ++listItemStartRow; - --redrawStartX; + // Partial screen re-draw information was not returned. + continueDoingFileList = actionRetObj.continueFileLister; + drawFileListMenu = actionRetObj.reDrawMainScreenContent; + // If displaying extended descriptions and the user deleted some files, then + // refresh the file description area to erase the delete confirmation text + if (extendedDescEnabled()/* && currentActionVal == FILE_DELETE*/) + { + if (actionRetObj.hasOwnProperty("filesDeleted") && actionRetObj.filesDeleted) + { + var numFiles = gFileListMenu.NumItems(); + if (numFiles > 0 && gFileListMenu.selectedItemIdx >= 0 && gFileListMenu.selectedItemIdx < numFiles) + displayFileExtDescOnMainScreen(gFileListMenu.selectedItemIdx); + } + else + { + var firstLine = startY + gFileListMenu.pos.y; + var lastLine = console.screen_rows - 1; + var width = console.screen_columns - gFileListMenu.size.width - 1; + displayFileExtDescOnMainScreen(gFileListMenu.selectedItemIdx, firstLine, lastLine, width); + } + } } - if (gFileListMenu.scrollbarEnabled && !gFileListMenu.CanShowAllItemsInWindow()) + } + // Remove checkmarks from any selected files in the file menu. + // For efficiency, we'd probably only do this if not re-drawing the wohle + // menu, but that's not working for now. + if (!actionRetObj.refreshedSelectedFilesAlready && /*!drawFileListMenu &&*/ gFileListMenu.numSelectedItemIndexes() > 0) + { + var bottomItemIdx = gFileListMenu.GetBottomItemIdx(); + var redrawTopY = -1; + var redrawBottomY = -1; + if (actionRetObj.fileListPartialRedrawInfo != null) { - --redrawStartX; - ++redrawWidth; + redrawTopY = actionRetObj.fileListPartialRedrawInfo.absStartY; + redrawBottomY = actionRetObj.fileListPartialRedrawInfo.height + height - 1; } for (var idx in gFileListMenu.selectedItemIndexes) { var idxNum = +idx; - if (idxNum >= gFileListMenu.topItemIdx && idxNum <= lastItemIdxOnScreen) + if (idxNum >= gFileListMenu.topItemIdx && idxNum <= bottomItemIdx) { - gFileListMenu.DrawPartialAbs(redrawStartX, listItemStartRow+idxNum, redrawWidth, 1, {}); - redrewPartOfFileListMenu = true; + var drawItem = true; + if (redrawTopY > -1 && redrawBottomY > redrawTopY) + { + var screenRowForItem = gFileListMenu.ScreenRowForItem(idxNum); + drawItem = (screenRowForItem < redrawTopY || screenRowForItem > redrawBottomY) + } + if (drawItem) + { + var isSelected = (idxNum == gFileListMenu.selectedItemIdx); + gFileListMenu.WriteItemAtItsLocation(idxNum, isSelected, false); + } + else + console.print("\1n\r\nNot drawing idx " + idxNum + "\r\n\1p"); } } } @@ -410,7 +465,8 @@ function doAction(pActionCode, pFileList, pFileListMenu) // // Return value: An object with the following properties: // continueFileLister: Boolean - Whether or not the file lister should continue, or exit -// reDrawFileListMenu: Boolean - Whether or not to re-draw the whole file list +// reDrawMainScreenContent: Boolean - Whether or not to re-draw the main screen content +// (file list, and extended description area if applicable) // reDrawListerHeader: Boolean - Whether or not to re-draw the header at the top of the screen // reDrawHeaderTextOnly: Boolean - Whether or not to re-draw the header text only. This should // take precedence over reDrawListerHeader. @@ -421,17 +477,20 @@ function doAction(pActionCode, pFileList, pFileListMenu) // startY: The starting Y coordinate for where to re-draw // width: The width to re-draw // height: The height to re-draw +// refreshedSelectedFilesAlready: Whether or not selected file checkmark items +// have already been refreshed (boolean) // exitNow: Exit the file lister now (boolean) // If no part of the file list menu needs to be re-drawn, this will be null. function getDefaultActionRetObj() { return { continueFileLister: true, - reDrawFileListMenu: false, + reDrawMainScreenContent: false, reDrawListerHeader: false, reDrawHeaderTextOnly: false, reDrawCmdBar: false, fileListPartialRedrawInfo: null, + refreshedSelectedFilesAlready: false, exitNow: false }; } @@ -450,8 +509,8 @@ function showFileInfo(pFileList, pFileListMenu) // The width of the frame to display the file info (including borders). This // is declared early so that it can be used for string length adjustment. - var frameWidth = pFileListMenu.size.width - 4; - var frameInnerWidth = frameWidth - 2; // Without borders + //var frameWidth = pFileListMenu.size.width - 4; // TODO: Remove? + var frameWidth = console.screen_columns - 4; // pFileList[pFileListMenu.selectedItemIdx] has a file metadata object without // extended information. Get a metadata object with extended information so we @@ -461,26 +520,24 @@ function showFileInfo(pFileList, pFileListMenu) var dirCode = bbs.curdir_code; if (pFileList[pFileListMenu.selectedItemIdx].hasOwnProperty("dirCode")) dirCode = pFileList[pFileListMenu.selectedItemIdx].dirCode; - var fileInfoObj = getFileInfoFromFilebase(dirCode, pFileList[pFileListMenu.selectedItemIdx].name, FileBase.DETAIL.EXTENDED); - var extdFileInfo = fileInfoObj.fileMetadataObj; - if (typeof(extdFileInfo) !== "object") - { - displayMsg("Unable to get file info!", true, true); - return; - } - var fileTime = fileInfoObj.fileTime; + var fileMetadata = null; + if (extendedDescEnabled()) + fileMetadata = pFileList[pFileListMenu.selectedItemIdx]; + else + fileMetadata = getFileInfoFromFilebase(dirCode, pFileList[pFileListMenu.selectedItemIdx].name, FileBase.DETAIL.EXTENDED); // Build a string with the file information // Make sure the displayed filename isn't too crazy long - var adjustedFilename = shortenFilename(extdFileInfo.name, frameInnerWidth, false); + var frameInnerWidth = frameWidth - 2; // Without borders + var adjustedFilename = shortenFilename(fileMetadata.name, frameInnerWidth, false); var fileInfoStr = "\1n\1wFilename"; - if (adjustedFilename.length < extdFileInfo.name.length) + if (adjustedFilename.length < fileMetadata.name.length) fileInfoStr += " (shortened)"; fileInfoStr += ":\r\n"; fileInfoStr += gColors.filename + adjustedFilename + "\1n\1w\r\n"; - // Note: File size can also be retrieved by calling a FileBase's get_size(extdFileInfo.name) + // Note: File size can also be retrieved by calling a FileBase's get_size(fileMetadata.name) // TODO: Shouldn't need the max length here - fileInfoStr += "Size: " + gColors.fileSize + getFileSizeStr(extdFileInfo.size, 99999) + "\1n\1w\r\n"; - fileInfoStr += "Timestamp: " + gColors.fileTimestamp + strftime("%Y-%m-%d %H:%M:%S", fileTime) + "\1n\1w\r\n" + fileInfoStr += "Size: " + gColors.fileSize + getFileSizeStr(fileMetadata.size, 99999) + "\1n\1w\r\n"; + fileInfoStr += "Timestamp: " + gColors.fileTimestamp + strftime("%Y-%m-%d %H:%M:%S", fileMetadata.time) + "\1n\1w\r\n" fileInfoStr += "\r\n"; // File library/directory information @@ -492,37 +549,50 @@ function showFileInfo(pFileList, pFileListMenu) fileInfoStr += "\1c\1hDir\1g: \1n\1c" + dirDesc.substr(0, frameInnerWidth-5) + "\1n\1w\r\n"; fileInfoStr += "\r\n"; - // extdFileInfo should have extdDesc, but check just in case + // fileMetadata should have extdDesc, but check just in case var fileDesc = ""; - if (extdFileInfo.hasOwnProperty("extdesc") && extdFileInfo.extdesc.length > 0) - fileDesc = extdFileInfo.extdesc; + if (fileMetadata.hasOwnProperty("extdesc") && fileMetadata.extdesc.length > 0) + fileDesc = fileMetadata.extdesc; else - fileDesc = extdFileInfo.desc; + fileDesc = fileMetadata.desc; // It's possible for fileDesc to be undefined (due to extDesc or desc being undefined), // so make sure it's a string if (typeof(fileDesc) !== "string") fileDesc = ""; + // This might be overkill, but just in case, convert any non-Synchronet + // attribute codes to Synchronet attribute codes in the description. + if (!fileMetadata.hasOwnProperty("attrsConverted")) + { + fileDesc = convertAttrsToSyncPerSysCfg(fileDesc); + fileMetadata.attrsConverted = true; + if (fileMetadata.hasOwnProperty("extdesc")) + fileMetadata.extdesc = fileDesc; + else + fileMetadata.desc = fileDesc; + } + fileInfoStr += gColors.desc; if (fileDesc.length > 0) fileInfoStr += "Description:\r\n" + fileDesc; else fileInfoStr += "No description available"; + fileInfoStr += "\r\n"; if (user.is_sysop) { var sysopFields = [ "from", "cost", "added"]; for (var sI = 0; sI < sysopFields.length; ++sI) { var prop = sysopFields[sI]; - if (extdFileInfo.hasOwnProperty(prop)) + if (fileMetadata.hasOwnProperty(prop)) { - if (typeof(extdFileInfo[prop]) === "string" && extdFileInfo[prop].length == 0) + if (typeof(fileMetadata[prop]) === "string" && fileMetadata[prop].length == 0) continue; var propName = prop.charAt(0).toUpperCase() + prop.substr(1); fileInfoStr += "\r\n\1n\1c\1h" + propName + "\1g:\1n\1c "; if (prop == "added") - fileInfoStr += strftime("%Y-%m-%d %H:%M:%S", extdFileInfo.added); + fileInfoStr += strftime("%Y-%m-%d %H:%M:%S", fileMetadata.added); else - fileInfoStr += extdFileInfo[prop].toString().substr(0, frameInnerWidth); + fileInfoStr += fileMetadata[prop].toString().substr(0, frameInnerWidth); fileInfoStr += "\1n\1w"; } } @@ -531,15 +601,15 @@ function showFileInfo(pFileList, pFileListMenu) // Construct & draw a frame with the file information & do the input loop // for the frame until the user closes the frame. - var frameUpperLeftX = pFileListMenu.pos.x + 2; - var frameUpperLeftY = pFileListMenu.pos.y + 2; + var frameUpperLeftX = 3; + var frameUpperLeftY = gNumHeaderLinesDisplayed + 3; // Note: frameWidth is declared earlier - var frameHeight = 10; + var frameHeight = console.screen_rows - 4 - frameUpperLeftY; // If the user's console is more than 25 rows high, then make the info window // taller so that its bottom row is 10 from the bottom, but only up to 45 rows tall. if (console.screen_rows > 25) { - var frameBottomRow = console.screen_rows - 10; + var frameBottomRow = console.screen_rows - 4; frameHeight = frameBottomRow - frameUpperLeftY + 1; if (frameHeight > 45) frameHeight = 45; @@ -552,9 +622,11 @@ function showFileInfo(pFileList, pFileListMenu) // Construct the file list redraw info. Note that the X and Y are relative // to the file list menu, not absolute screen coordinates. retObj.fileListPartialRedrawInfo = { - startX: 2, - startY: 2, - width: frameWidth+1, + startX: frameUpperLeftX - gFileListMenu.pos.x + 1, // Relative to the file menu + startY: frameUpperLeftY - gFileListMenu.pos.y + 1, // Relative to the file menu + absStartX: frameUpperLeftX, + absStartY: frameUpperLeftY, + width: frameWidth, height: frameHeight }; @@ -597,7 +669,7 @@ function viewFile(pFileList, pFileListMenu) console.pause(); retObj.reDrawListerHeader = true; - retObj.reDrawFileListMenu = true; + retObj.reDrawMainScreenContent = true; retObj.reDrawCmdBar = true; return retObj; } @@ -635,6 +707,7 @@ function addSelectedFilesToBatchDLQueue(pFileList, pFileListMenu) // Note that confirmFileActionWithUser() will re-draw the parts of the file // list menu that are necessary. var addFilesConfirmed = confirmFileActionWithUser(filenames, "Batch DL add", false); + retObj.refreshedSelectedFilesAlready = true; if (addFilesConfirmed) { var batchDLQueueStats = getUserDLQueueStats(); @@ -680,19 +753,9 @@ function addSelectedFilesToBatchDLQueue(pFileList, pFileListMenu) // Frame location & size for batch DL queue stats or filenames that failed var frameUpperLeftX = gFileListMenu.pos.x + 2; var frameUpperLeftY = gFileListMenu.pos.y + 2; - var frameWidth = gFileListMenu.size.width - 4; + var frameWidth = console.screen_columns - 4; // Used to be gFileListMenu.size.width - 4; var frameInnerWidth = frameWidth - 2; // Without borders var frameHeight = 8; - // To make the list refresh info to return to the main script loop - function makeBatchRefreshInfoObj(pFrameWidth, pFrameHeight) - { - return { - startX: 3, - startY: 3, - width: pFrameWidth+1, - height: pFrameHeight - }; - } // If there were no failures, then show a success message & prompt the user if they // want to download their batch queue. Otherwise, show the filenames that failed to @@ -745,22 +808,23 @@ function addSelectedFilesToBatchDLQueue(pFileList, pFileListMenu) frameHeight, gColors.batchDLInfoWindowBorder, frameTitle, gColors.batchDLInfoWindowTitle, queueStats, additionalQuitKeys); + // The main screen content (file list & extended description if applicable) + // will need to be redrawn after this. + retObj.reDrawMainScreenContent = true; + // If the user chose to download their file queue, then send it to the user. + // And the lister headers will need to be re-drawn as well. if (lastUserInput.toUpperCase() == "Y") { - retObj.reDrawFileListMenu = true; retObj.reDrawListerHeader = true; retObj.reDrawCmdBar = true; console.print("\1n"); console.gotoxy(1, console.screen_rows); console.crlf(); bbs.batch_download(); - } - else - { - retObj.reDrawFileListMenu = true; - // Construct the file list redraw info. Note that the X and Y are relative - // to the file list menu, not absolute screen coordinates. - //retObj.fileListPartialRedrawInfo = makeBatchRefreshInfoObj(frameWidth, frameHeight); + // If the user is still online (chose not to hang up after transfer), + // then pause so that the user can see the batch download status + if (bbs.online > 0) + console.pause(); } } } @@ -777,9 +841,17 @@ function addSelectedFilesToBatchDLQueue(pFileList, pFileListMenu) frameHeight, gColors.batchDLInfoWindowBorder, frameTitle, gColors.batchDLInfoWindowTitle, fileListStr, ""); - // Construct the file list redraw info. Note that the X and Y are relative + // Add the file list redraw info. Note that the X and Y are relative // to the file list menu, not absolute screen coordinates. - retObj.fileListPartialRedrawInfo = makeBatchRefreshInfoObj(frameWidth, frameHeight); + // To make the list refresh info to return to the main script loop + retObj.fileListPartialRedrawInfo = { + startX: 3, + startY: 3, + absStartX: gFileListMenu.pos.x + 3 - 1, // 1-based + absStartY: gFileListMenu.pos.y + 3 - 1, // 1-based + width: frameWidth + 1, + height: frameHeight + }; } } @@ -836,33 +908,6 @@ function getUserDLQueueStats() } } } - - /* - var sections = batchDLFile.iniGetSections(); - retObj.numFilesInQueue = sections.length; - for (var i = 0; i < sections.length; ++i) - { - //var desc = - //retObj.filenames.push({ filename: sections[i], desc: }); - // Get the dir code from the section, then get the size and cost for - // the file from the filebase and add them to the totals in retObj - var dirCode = batchDLFile.iniGetValue(sections[i], "dir", ""); - if (dirCode.length > 0) - { - var filebase = new FileBase(dirCode); - if (filebase.open()) - { - var fileInfo = filebase.get(sections[i]); - if (typeof(fileInfo) === "object") - { - retObj.totalSize += +(fileInfo.size); - retObj.totalCost += +(fileInfo.cost); - } - filebase.close(); - } - } - } - */ batchDLFile.close(); } @@ -934,7 +979,7 @@ function displayHelpScreen() //console.pause(); retObj.reDrawListerHeader = true; - retObj.reDrawFileListMenu = true; + retObj.reDrawMainScreenContent = true; retObj.reDrawCmdBar = true; return retObj; } @@ -964,6 +1009,7 @@ function chooseFilebaseAndMoveFileToOtherFilebase(pFileList, pFileListMenu) // Note that confirmFileActionWithUser() will re-draw the parts of the file // list menu that are necessary. var moveFilesConfirmed = confirmFileActionWithUser(filenames, "Move", false); + retObj.refreshedSelectedFilesAlready = true; if (!moveFilesConfirmed) return retObj; @@ -976,6 +1022,8 @@ function chooseFilebaseAndMoveFileToOtherFilebase(pFileList, pFileListMenu) var fileListPartialRedrawInfo = { startX: fileLibMenu.pos.x - pFileListMenu.pos.x + 1, startY: topYForRefresh - pFileListMenu.pos.y + 1, + absStartX: fileLibMenu.pos.x, + absStartY: topYForRefresh, width: fileLibMenu.size.width + 1, height: fileLibMenu.size.height + 1 // + 1 because of the label above the menu }; @@ -1061,39 +1109,13 @@ function chooseFilebaseAndMoveFileToOtherFilebase(pFileList, pFileListMenu) // Have the file list menu set up its description width, colors, and format // string again in case it no longer needs to use its scrollbar pFileListMenu.SetItemWidthsColorsAndFormatStr(); - retObj.reDrawFileListMenu = true; + retObj.reDrawMainScreenContent = true; } else { // Note: getFileInfoFromFilebase() will add dirCode to the metadata object - var fileDataObj = getFileInfoFromFilebase(chosenDirCode, pFileList[fileIdx].name, FileBase.DETAIL.NORM); - pFileList[fileIdx] = fileDataObj.fileMetadataObj; - /* - // If all files were in the same directory, then we'll need to update the header - // lines at the top of the file list. If there's only one file in the list, - // the header lines will need to display the correct directory. Otherwise, - // set allSameDir to false so the header lines will now say "various". - // However, if not all files were in the same directory, check to see if they - // are now, and if so, we'll need to re-draw the header lines. - if (typeof(pFileList.allSameDir) == "boolean") - { - if (pFileList.allSameDir) - { - if (pFileList.length > 1) - pFileList.allSameDir = false; - //retObj.reDrawListerHeader = true; - retObj.reDrawHeaderTextOnly = true; - } - else - { - pFileList.allSameDir = true; // Until we find it's not true - for (var fileListIdx = 1; fileListIdx < pFileList.length && pFileList.allSameDir; ++fileListIdx) - pFileList.allSameDir = (pFileList[fileListIdx].dirCode == pFileList[0].dirCode); - //retObj.reDrawListerHeader = pFileList.allSameDir; - retObj.reDrawHeaderTextOnly = pFileList.allSameDir; - } - } - */ + var fileDetail = (extendedDescEnabled() ? FileBase.DETAIL.EXTENDED : FileBase.DETAIL.NORM); + pFileList[fileIdx] = getFileInfoFromFilebase(chosenDirCode, pFileList[fileIdx].name, fileDetail); } } else @@ -1124,7 +1146,6 @@ function chooseFilebaseAndMoveFileToOtherFilebase(pFileList, pFileListMenu) { if (pFileList.length > 1) pFileList.allSameDir = false; - //retObj.reDrawListerHeader = true; retObj.reDrawHeaderTextOnly = true; } else @@ -1132,7 +1153,6 @@ function chooseFilebaseAndMoveFileToOtherFilebase(pFileList, pFileListMenu) pFileList.allSameDir = true; // Until we find it's not true for (var fileListIdx = 1; fileListIdx < pFileList.length && pFileList.allSameDir; ++fileListIdx) pFileList.allSameDir = (pFileList[fileListIdx].dirCode == pFileList[0].dirCode); - //retObj.reDrawListerHeader = pFileList.allSameDir; retObj.reDrawHeaderTextOnly = pFileList.allSameDir; } } @@ -1158,17 +1178,12 @@ function chooseFilebaseAndMoveFileToOtherFilebase(pFileList, pFileListMenu) displayMsg("There are no more files.", false); retObj.exitNow = true; } - // If not exiting now, we'll want to re-draw part of the file list to erase the - // area chooser menu. - if (!retObj.exitNow) - retObj.fileListPartialRedrawInfo = fileListPartialRedrawInfo; } - else - { - // The user has canceled out of the area selection. - // We'll want to re-draw part of the file list to erase the area chooser menu. + + // If not exiting now, we'll want to re-draw part of the file list to erase the + // area chooser menu. + if (!retObj.exitNow) retObj.fileListPartialRedrawInfo = fileListPartialRedrawInfo; - } return retObj; } @@ -1180,10 +1195,14 @@ function chooseFilebaseAndMoveFileToOtherFilebase(pFileList, pFileListMenu) // pFileListMenu: The menu object for the file diretory // // Return value: An object with values to indicate status & screen refresh actions; see -// getDefaultActionRetObj() for details. +// getDefaultActionRetObj() for details. For this function, the object +// returned will have the following additional properties: +// filesDeleted: Boolean - Whether or not files were actually deleted (after +// confirmation) function confirmAndRemoveFilesFromFilebase(pFileList, pFileListMenu) { var retObj = getDefaultActionRetObj(); + retObj.filesDeleted = false; // Confirm the action with the user. If the user confirms, then remove the file(s). // If there are multiple selected files, then prompt to remove each of them. @@ -1199,8 +1218,11 @@ function confirmAndRemoveFilesFromFilebase(pFileList, pFileListMenu) // Note that confirmFileActionWithUser() will re-draw the parts of the file list menu // that are necessary. var removeFilesConfirmed = confirmFileActionWithUser(filenames, "Remove", false); + retObj.refreshedSelectedFilesAlready = true; if (removeFilesConfirmed) { + retObj.filesDeleted = true; // Assume true even if some deletions may fail + var fileIndexes = []; if (pFileListMenu.numSelectedItemIndexes() > 0) { @@ -1238,10 +1260,36 @@ function confirmAndRemoveFilesFromFilebase(pFileList, pFileListMenu) var filebase = new FileBase(pFileList[fileIdx].dirCode); if (filebase.open()) { - // FileBase.remove(filename [,delete=false]) - removeFileSucceeded = filebase.remove(pFileList[fileIdx].name, true); - if (gScriptMode == MODE_LIST_CURDIR) - numFilesRemaining = filebase.files; + var filenameFullPath = filebase.get_path(pFileList[fileIdx].name); // For logging + try + { + removeFileSucceeded = filebase.remove(pFileList[fileIdx].name, true); + } + catch (error) + { + removeFileSucceeded = false; + // Make an entry in the BBS log that deleting the file failed + var logMsg = "ddfilelister: " + error; + log(LOG_ERR, logMsg); + bbs.log_str(logMsg); + } + // If the remove failed with deleting the file, then try without deleting the file + if (!removeFileSucceeded) + { + removeFileSucceeded = filebase.remove(pFileList[fileIdx].name, false); + if (removeFileSucceeded) + { + var logMsg = "ddfilelister: Removed " + filenameFullPath + " from the " + + "filebase but couldn't actually delete the file"; + log(LOG_INFO, logMsg); + bbs.log_str(logMsg); + } + } + if (removeFileSucceeded) + { + if (gScriptMode == MODE_LIST_CURDIR) + numFilesRemaining = filebase.files; + } filebase.close(); } else @@ -1293,7 +1341,7 @@ function confirmAndRemoveFilesFromFilebase(pFileList, pFileListMenu) // Have the file list menu set up its description width, colors, and format // string again in case it no longer needs to use its scrollbar pFileListMenu.SetItemWidthsColorsAndFormatStr(); - retObj.reDrawFileListMenu = true; + retObj.reDrawMainScreenContent = true; // If all files were not in the same directory, then check to see if all // remaining files are now. If so, we'll need to update the header lines // at the top of the file list. @@ -1635,28 +1683,29 @@ function DDFileMenuBarItem(pItemText, pPos, pRetCode) // fileTime: The timestamp of the file function getFileInfoFromFilebase(pDirCode, pFilename, pDetail) { - var retObj = { - fileMetadataObj: null, - fileTime: 0 - }; - if (typeof(pDirCode) !== "string" || pDirCode.length == 0 || typeof(pFilename) !== "string" || pFilename.length == 0) - return retObj; + return null; + var fileMetadataObj = null; var filebase = new FileBase(pDirCode); if (filebase.open()) { // Just in case the file has the full path, get just the filename from it. var filename = file_getname(pFilename); var fileDetail = (typeof(pDetail) === "number" ? pDetail : FileBase.DETAIL.NORM); - retObj.fileMetadataObj = filebase.get(filename, fileDetail); - retObj.fileMetadataObj.dirCode = pDirCode; - //retObj.fileMetadataObj.size = filebase.get_size(filename); - retObj.fileTime = filebase.get_time(filename); + if (typeof(pDetail) === "number") + fileDetail = pDetail; + else + fileDetail = (extendedDescEnabled() ? FileBase.DETAIL.EXTENDED : FileBase.DETAIL.NORM); + fileMetadataObj = filebase.get(filename, fileDetail); + fileMetadataObj.dirCode = pDirCode; + //fileMetadataObj.size = filebase.get_size(filename); + if (!fileMetadataObj.hasOwnProperty("time")) + fileMetadataObj.time = filebase.get_time(filename); filebase.close(); } - return retObj; + return fileMetadataObj; } // Moves a file from one filebase to another @@ -1965,14 +2014,9 @@ function displayListHdrLine(pMoveToLocationFirst) console.gotoxy(1, 3); var filenameLen = gListIdxes.filenameEnd - gListIdxes.filenameStart; var fileSizeLen = gListIdxes.fileSizeEnd - gListIdxes.fileSizeStart -1; - //var shortDescLen = gListIdxes.descriptionEnd - gListIdxes.descriptionStart + 1; - // shortDescLen here should always be the same (for the last blocks to always be in the same - // position), whereas descriptionEnd might change based on whether the menu is using its scrollbar - var shortDescLen = 60; - if (console.screen_columns > 80) - shortDescLen = console.screen_columns - 30; + var descLen = gListIdxes.descriptionEnd - gListIdxes.descriptionStart + 1; var formatStr = "\1n\1w\1h%-" + filenameLen + "s %" + fileSizeLen + "s %-" - + +(shortDescLen-7) + "s\1n\1w%5s\1n"; + + +(descLen-7) + "s\1n\1w%5s\1n"; var listHdrEndText = THIN_RECTANGLE_RIGHT + BLOCK4 + BLOCK3 + BLOCK2 + BLOCK1; printf(formatStr, "Filename", "Size", "Description", listHdrEndText); } @@ -1989,13 +2033,23 @@ function createFileListMenu(pQuitKeys) // Create the menu object. Place it below the header lines (which should have been written // before this), and also leave 1 row at the bottom for the prompt line var startRow = gNumHeaderLinesDisplayed > 0 ? gNumHeaderLinesDisplayed + 1 : 1; - var fileListMenu = new DDLightbarMenu(1, startRow, console.screen_columns - 1, console.screen_rows - (startRow-1) - 1); + // If we'll be displaying short (one-line) file descriptions, then use the whole width + // of the terminal (minus 1) for the menu width. But if the user has extended (multi-line) + // file descriptions enabled, then set the menu width only up through the file size, since + // the extended file description will be displayed to the right of the menu. + var menuWidth = console.screen_columns - 1; + if (extendedDescEnabled()) + menuWidth = gListIdxes.fileSizeEnd + 1; + var menuHeight = console.screen_rows - (startRow-1) - 1; + var fileListMenu = new DDLightbarMenu(1, startRow, menuWidth, menuHeight); fileListMenu.scrollbarEnabled = true; fileListMenu.borderEnabled = false; fileListMenu.multiSelect = true; fileListMenu.ampersandHotkeysInItems = false; fileListMenu.wrapNavigation = false; + fileListMenu.extdDescEnabled = extendedDescEnabled(); + // Add additional keypresses for quitting the menu's input loop so we can // respond to these keys. if (typeof(pQuitKeys) === "string") @@ -2031,8 +2085,14 @@ function createFileListMenu(pQuitKeys) this.filenameLen = gListIdxes.filenameEnd - gListIdxes.filenameStart; this.fileSizeLen = gListIdxes.fileSizeEnd - gListIdxes.fileSizeStart -1; this.shortDescLen = gListIdxes.descriptionEnd - gListIdxes.descriptionStart + 1; - this.fileFormatStr = "%-" + this.filenameLen + "s %" + this.fileSizeLen - + "s %-" + this.shortDescLen + "s"; + // If extended descriptions are enabled, then we won't be writing a description here + if (this.extdDescEnabled) + this.fileFormatStr = "%-" + this.filenameLen + "s %" + this.fileSizeLen + "s"; + else + { + this.fileFormatStr = "%-" + this.filenameLen + "s %" + this.fileSizeLen + + "s %-" + this.shortDescLen + "s"; + } return widthChanged; }; // Set up the menu's description width, colors, and format string @@ -2063,6 +2123,12 @@ function createFileListMenu(pQuitKeys) fileListMenu.numSelectedItemIndexes = function() { return Object.keys(this.selectedItemIndexes).length; }; + + // OnItemNav function for when the user navigates to a new item + fileListMenu.OnItemNav = function(pOldItemIdx, pNewItemIdx) { + displayFileExtDescOnMainScreen(pNewItemIdx); + } + return fileListMenu; } @@ -2493,7 +2559,8 @@ function eraseMsgBoxScreenArea() // Refresh the list header line and have the file list menu refresh itself over // the error message window displayListHdrLine(true); - gFileListMenu.DrawPartialAbs(gErrorMsgBoxULX, gErrorMsgBoxULY+1, gErrorMsgBoxWidth, gErrorMsgBoxHeight-2); + // This used to call gFileListMenu.DrawPartialAbs + refreshScreenMainContent(gErrorMsgBoxULX, gErrorMsgBoxULY+1, gErrorMsgBoxWidth, gErrorMsgBoxHeight-2); } // Draws a border @@ -2660,7 +2727,8 @@ function confirmFileActionWithUser(pFilenames, pActionName, pDefaultYes) actionConfirmed = console.yesno(pActionName + " " + shortFilename); else actionConfirmed = !console.noyes(pActionName + " " + shortFilename); - gFileListMenu.DrawPartialAbs(1, console.screen_rows-2, console.screen_columns, 2, {}); + // Refresh the main screen content, to erase the confirmation prompt + refreshScreenMainContent(1, console.screen_rows-2, console.screen_columns, 2, true); } else { @@ -2668,7 +2736,8 @@ function confirmFileActionWithUser(pFilenames, pActionName, pDefaultYes) // user to delete the files var frameUpperLeftX = gFileListMenu.pos.x + 2; var frameUpperLeftY = gFileListMenu.pos.y + 2; - var frameWidth = gFileListMenu.size.width - 4; + //var frameWidth = gFileListMenu.size.width - 4; + var frameWidth = console.screen_columns - 4; var frameHeight = 10; var frameTitle = pActionName + " files? (Y/N)"; var additionalQuitKeys = "yYnN"; @@ -2681,7 +2750,8 @@ function confirmFileActionWithUser(pFilenames, pActionName, pDefaultYes) frameTitle, gColors.confirmFileActionWindowWindowTitle, fileListStr, additionalQuitKeys); actionConfirmed = (lastUserInput.toUpperCase() == "Y"); - gFileListMenu.DrawPartialAbs(frameUpperLeftX, frameUpperLeftY, frameWidth, frameHeight, {}); + // Refresh the main screen content, to erase the confirmation window + refreshScreenMainContent(frameUpperLeftX, frameUpperLeftY, frameWidth, frameHeight, true); } return actionConfirmed; @@ -3061,17 +3131,20 @@ function populateFileList(pSearchMode) return retObj; } - // To check a user's file basic/extended detail information setting: - // if ((user.settings & USER_EXTDESC) == USER_EXTDESC) - - // Get a list of file data with normal detail (without extended info). When the user - // selects a file to view extended info, we'll get metadata about the file with extended detail. - gFileList = filebase.get_list("*", FileBase.DETAIL.NORM, 0, true, gFileSortOrder); // FileBase.DETAIL.EXTENDED + // Get a list of file data + var fileDetail = (extendedDescEnabled() ? FileBase.DETAIL.EXTENDED : FileBase.DETAIL.NORM); + gFileList = filebase.get_list("*", fileDetail, 0, true, gFileSortOrder); filebase.close(); // Add a dirCode property to the file metadata objects (for consistency, // as file search results may contain files from multiple directories). + // Also, if the metadata objects have an extdesc, remove any trailing CRLF + // from the end. for (var i = 0; i < gFileList.length; ++i) + { gFileList[i].dirCode = bbs.curdir_code; + if (gFileList[i].hasOwnProperty("extdesc") && /\r\n$/.test(gFileList[i].extdesc)) + gFileList[i].extdesc = gFileList[i].extdesc.substr(0, gFileList[i].extdesc.length-2); + } } else { @@ -3222,6 +3295,36 @@ function populateFileList(pSearchMode) return retObj; } + // Figure out the longest filename in the list. + var longestFilenameLen = 0; + for (var i = 0; i < gFileList.length; ++i) + { + if (gFileList[i].name.length > longestFilenameLen) + longestFilenameLen = gFileList[i].name.length; + } + var displayFilenameLen = gListIdxes.filenameEnd - gListIdxes.filenameStart + 1; + // If the user has extended descriptions enabled, then allow 47 characters for the + // description and adjust the filename length accordingly + gListIdxes.descriptionEnd = console.screen_columns - 1; // Leave 1 character remaining on the screen + if (extendedDescEnabled()) + { + gListIdxes.descriptionStart = gListIdxes.descriptionEnd - 47 + 1; + gListIdxes.fileSizeEnd = gListIdxes.descriptionStart; + gListIdxes.fileSizeStart = gListIdxes.fileSizeEnd - 7; + gListIdxes.filenameEnd = gListIdxes.fileSizeStart; + } + // If not displaying extended descriptions, then if the longest filename + // is longer than the current display filename length and the user's + // terminal is at least 100 columns wide, then increase the filename length + // for the list by 20; + else if (longestFilenameLen > displayFilenameLen && console.screen_columns >= 100) + { + gListIdxes.filenameEnd += 20; + gListIdxes.fileSizeStart = gListIdxes.filenameEnd; + gListIdxes.fileSizeEnd = gListIdxes.fileSizeStart + 7; + gListIdxes.descriptionStart = gListIdxes.fileSizeEnd; + } + return retObj; } @@ -3463,4 +3566,171 @@ function searchDirGroupOrAll(pSearchOption, pDirSearchFn) } return retObj; +} + +// Returns whether the user has their extended file description setting enabled +// and if it can be supported in the user's terminal mode. Extended descriptions +// will be displayed in the main screen if the user has that option enabled and +// the user's terminal is at least 80 columns wide. +function extendedDescEnabled() +{ + var userExtDescEnabled = ((user.settings & USER_EXTDESC) == USER_EXTDESC); + return userExtDescEnabled && console.screen_columns >= 80; +} + +// Displays a file's extended description on the main screen, next to the +// file list menu. This is to be used when the user's extended file description +// option is enabled (where the menu would take up about the left half of +// the screen). +// +// Parameters: +// pFileIdx: The index of the file metadata object in gFileList to use +// pStartScreenRow: Optional - The screen row number to start printing, for partial +// screen refreshing (can be in the middle of the extended description) +// pEndScreenRow: Optional - The screen row number to stop printing, for partial +// screen refreshing (can be in the middle of the extended description) +// pMaxWidth: Optional - The maximum width to use for printing the description lines +function displayFileExtDescOnMainScreen(pFileIdx, pStartScreenRow, pEndScreenRow, pMaxWidth) +{ + if (typeof(pFileIdx) !== "number") + return; + if (pFileIdx < 0 || pFileIdx >= gFileList.length) + return; + + // Get the file description from its metadata object + var fileMetadata = gFileList[pFileIdx]; + var fileDesc = ""; + if (fileMetadata.hasOwnProperty("extdesc") && fileMetadata.extdesc.length > 0) + fileDesc = fileMetadata.extdesc; + else + fileDesc = fileMetadata.desc; + + // This might be overkill, but just in case, convert any non-Synchronet + // attribute codes to Synchronet attribute codes in the description. + // This will help simplify getting substrings for formatting. Then for + // efficiency, put the converted description back into the metadata + // object in the array so that it doesn't have to be converted again. + if (!fileMetadata.hasOwnProperty("attrsConverted")) + { + fileDesc = convertAttrsToSyncPerSysCfg(fileDesc); + fileMetadata.attrsConverted = true; + if (fileMetadata.hasOwnProperty("extdesc")) + fileMetadata.extdesc = fileDesc; + else + fileMetadata.desc = fileDesc; + } + + // Calculate where to write the description on the screen + var startX = gFileListMenu.size.width + 1; // Assuming the file menu starts at the leftmost column + var maxDescLen = console.screen_columns - startX; + if (typeof(pMaxWidth) === "number" && pMaxWidth >= 0 && pMaxWidth < maxDescLen) + maxDescLen = pMaxWidth; + // Go to the location on the screen and write the file description + var formatStr = "%-" + maxDescLen + "s"; + // firstScreenRow is the first row on the screen where the extended description + // should start at. lastScreenRow is the last row (inclusive) to use for + // printing the extended description + var firstScreenRow = gNumHeaderLinesDisplayed + 1; + var lastScreenRow = console.screen_rows - 1; // This is inclusive + // screenRowForPrinting will be used for the actual screen row we're at while + // printing the extended description lines + var screenRowForPrinting = firstScreenRow; + // If pStartScreenRow or pEndScreenRow are specified, then use + // them to specify the start & end screen rows to actually print + if (typeof(pStartScreenRow) === "number" && pStartScreenRow >= firstScreenRow && pStartScreenRow <= lastScreenRow) + screenRowForPrinting = pStartScreenRow; + if (typeof(pEndScreenRow) === "number" && pEndScreenRow > firstScreenRow && pStartScreenRow <= lastScreenRow) + lastScreenRow = pEndScreenRow; + var fileDescArray = fileDesc.split("\r\n"); + console.print("\1n"); + // screenRowNum is to keep track of the row on the screen where the + // description line would be placed, in case the start row is after that + var screenRowNum = firstScreenRow; + for (var i = 0; i < fileDescArray.length; ++i) + { + if (screenRowForPrinting > screenRowNum++) + continue; + console.gotoxy(startX, screenRowForPrinting++); + // Note: substrWithAttrCodes() is defined in dd_lightbar_menu.js + // Normally it would be handy to use printf() to print the text line: + //printf(formatStr, substrWithAttrCodes(fileDescArray[i], 0, maxDescLen)); + // However, printf() doesn't account for attribute codes and thus may not + // fill the rest of the width. So, we do that manually. + var descLine = substrWithAttrCodes(fileDescArray[i], 0, maxDescLen); + console.print(descLine); + var remainingLen = maxDescLen - console.strlen(descLine); + if (remainingLen > 0) + printf("%" + remainingLen + "s", ""); + // Stop printing the description lines when we reach the last line on + // the screen where we want to print. + if (screenRowForPrinting > lastScreenRow) + break; + } + // Clear the rest of the lines to the bottom of the list area + console.print("\1n"); + while (screenRowForPrinting <= lastScreenRow) + { + console.gotoxy(startX, screenRowForPrinting++); + printf(formatStr, ""); + } +} + +// Refreshes (re-draws) the main content of the screen (file list menu, +// and extended description area if enabled). The coordinates are absolute +// screen coordinates. +// +// Parameters: +// pUpperLeftX: The X coordinate of the upper-left corner of the area to re-draw +// pUpperLeftY: The Y coordinate of the upper-left corner of the area to re-draw +// pWidth: The width of the area to re-draw +// pHeight: The height of the area to re-draw +// pSelectedItemIdxes: Optional: An object with selected item indexes for the file menu. +// If not passed, an empty object will be used. +// This can also be a boolean, and if true, will refresh the +// selected items on the file menu (with checkmarks) outside the +// given top & bottom screen rows. +function refreshScreenMainContent(pUpperLeftX, pUpperLeftY, pWidth, pHeight, pSelectedItemIdxes) +{ + var selectedItemIdxesIsValid = (typeof(pSelectedItemIdxes) === "object"); + var selectedItemIdxes = (selectedItemIdxesIsValid ? pSelectedItemIdxes : {}); + gFileListMenu.DrawPartialAbs(pUpperLeftX, pUpperLeftY, pWidth, pHeight, selectedItemIdxes); + // If pSelectedItemIdxes is a bool instead of an object and is true, + // refresh the selected items (with checkmarks) outside the top & bottom + // lines on the file menu + if (!selectedItemIdxesIsValid && typeof(pSelectedItemIdxes) === "boolean" && pSelectedItemIdxes && gFileListMenu.numSelectedItemIndexes() > 0) + { + var bottomScreenRow = pUpperLeftY + pHeight - 1; + for (var idx in gFileListMenu.selectedItemIndexes) + { + var idxNum = +idx; + var itemScreenRow = gFileListMenu.ScreenRowForItem(idxNum); + if (itemScreenRow == -1) + continue; + if (itemScreenRow < pUpperLeftY || itemScreenRow > bottomScreenRow) + { + var isSelected = (idxNum == gFileListMenu.selectedItemIdx); + gFileListMenu.WriteItemAtItsLocation(idxNum, isSelected, false); + } + } + } + // If the user has extended descriptions enabled, then the file menu + // is only taking up about half the screen on the left, and we'll also + // have to refresh the description area. + if (extendedDescEnabled()) + { + var fileMenuRightX = gFileListMenu.pos.x + gFileListMenu.size.width - 1; + var width = pWidth - (fileMenuRightX - pUpperLeftX + 1); + if (width > 0) + { + var firstRow = pUpperLeftY; + // The last row is inclusive. It seems like there might be an off-by-1 + // problem here? I thought 1 would need to be subtracted from lastRow + var lastRow = pUpperLeftY + pHeight; + // We don't want to overwrite the last row on the screen, since that's + // used for the command bar + if (lastRow == console.screen_rows) + --lastRow; + displayFileExtDescOnMainScreen(gFileListMenu.selectedItemIdx, firstRow, lastRow, width); + } + } } \ No newline at end of file diff --git a/xtrn/ddfilelister/readme.txt b/xtrn/ddfilelister/readme.txt index e7e3c047f405f912f4111db15a5660c06af6638f..37dfeb53389d539be11ef09160f1e698861d43b8 100644 --- a/xtrn/ddfilelister/readme.txt +++ b/xtrn/ddfilelister/readme.txt @@ -1,6 +1,6 @@ Digital Distortion File Lister - Version 2.04 - Release date: 2022-03-09 + Version 2.05 + Release date: 2022-03-12 by @@ -50,6 +50,14 @@ action. The file lister also uses message boxes to display information. If the user's terminal does not support ANSI, the file lister will run the stock Synchronet file lister interface instead. +Digital Distortion File Lister makes use of the user's extended description +setting. If the user's extended description setting is enabled, the lister +will use a split interface, with the lightbar file list on the left side and +the extended file description for the highlighted file displayed on the right +side. If the user's extended file description setting is disabled, the +lightbar file menu will use the entire width of the screen, with the short +file descriptions being displayed in a single row with each file. + When adding files to the user's batch download queue or (for the sysop) selecting files to move or delete, multi-select mode can be used, allowing the user to select multiple files using the spacebar. If the spacebar is not diff --git a/xtrn/ddfilelister/revision_history.txt b/xtrn/ddfilelister/revision_history.txt index ab234fd942d8b9988e987c75d9f36e72af7e5d04..f8c50f2d32728bb0600acdd100497fc63cd17d4e 100644 --- a/xtrn/ddfilelister/revision_history.txt +++ b/xtrn/ddfilelister/revision_history.txt @@ -5,6 +5,14 @@ Revision History (change log) ============================= Version Date Description ------- ---- ----------- +2.05 2022-03-12 Now makes use of the user's extended file description + setting: If the user's extended file description setting + is enabled, the lister will now show extended file + descriptions on the main screen in a split format, with + the lightbar file list on the left and the extended file + description for the highlighted file on the right. Also, + made the file info window taller for terminals within 25 + lines high. 2.04 2022-03-09 Bug fix: Now successfully formats filenames without extensions when listing files. 2.03 2022-02-27 For terminals over 80 rows tall, the file info window will