diff --git a/ctrl/modopts.d/xtrnmenu.ini b/ctrl/modopts.d/xtrnmenu.ini new file mode 100644 index 0000000000000000000000000000000000000000..561fb774ea100c6e3d16bae9da860d275e0805f4 --- /dev/null +++ b/ctrl/modopts.d/xtrnmenu.ini @@ -0,0 +1,137 @@ +; Options for Xtrn Menu Mod +; http://wiki.synchro.net/module:xtrnmenu + +; Refer to the wiki for instructions on how to customize individual menus + +[xtrnmenu] + +; Uncomment if you wish to have the mod hand over section menus to xtrn_sec.js +;use_xtrn_sec = true + +; Default sort, can be "name", "key", or false (no sort) +sort = false + +; Enable multi-column display (when more than 10 external programs in a section) +multicolumn = true + +; Clear the (remote) terminal screen before displaying the menu +clear_screen = true + +; uncomment and modify any of the below to change the look of the external programs menu +; to remove titles or underline, set to the text ' ' +;multicolumn_separator: " " + +; set the singlecolumn margin to the number of lines of your footer, if you have one +;singlecolumn_margin=2 + +;header_fmt: \1n\1c\1h%s \1n\1cExternal Programs:\r\n\r\n +;titles: \1n\1cKey \1h\xb3\1n\1c Name \1n\1c +multicolumn_fmt: \1h\1c%3s \xb3 \1n\1c%-32.32s \1h +singlecolumn_fmt: \1h\1c%3s \xb3 \1n\1c%s \1h +;which: \r\n\1-\1cWhich or \1h~Q\1n\1cuit: \1h +;underline: \1c\1h\xc4\xc4\xc4\xc4\xc5\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4 +; used by the lightbar +multicolumn_fmt_inverse: \1n\1w[\1y\1h%2s\1n\1w] \1n\1w\1h\x015%-32.32s \1n\1h +singlecolumn_fmt_inverse: \1n\1w[\1y\1h%2s\1n\1w] \1n\1w\1h\x015%s \1n\1h + +; to enable cost display, use these instead +; Note: cost not supported in special menus +;titles: \1n\1cInput \1h\xb3\1n\1c Name Cost \1n\1c +;multicolumn_fmt: \1h\1c%3s \xb3 \1n\1c%-26.26s\1h %5u +;singlecolumn_fmt: \1h\1c%3s \xb3 \1n\1c%-26.26s\1h %5u +;multicolumn_fmt_inverse: \1h\1c%3s \xb3 \1n\x016\1w\1h%-26.26s\1n \1c%5u +;singlecolumn_fmt_inverse: \1h\1c%3s \xb3 \1n\x016\1w\1h%-26.26s\1n \1c%5u + +; These are used for the special menus (most launched, etc.) +; It adds the "stats" field which is the number of launches, amount of time, etc. +multicolumn_fmt_special: \1h\1c%3s \xb3 \1m%-21.21s\1h \1m%-10s +singlecolumn_fmt_special: \1h\1c%3s \xb3 \1m%-21.21s\1h \1m%-10s +multicolumn_fmt_special: \1h\1c%3s \xb3 \1m%-21.21s\1h \1m%-10s +singlecolumn_fmt_special: \1h\1c%3s \xb3 \1m%-21.21s\1h \1m%-10s + +; Override the message when there are no programs available +; (defaults to text.dat NoXtrnPrograms) +;no_programs_msg: + +; Override the restricted user msg +; (defaults to text.dat R_ExternalPrograms) +;restricted_user_msg: + +; JSON service powers special menus (most popular, recent, etc.) +; Add appropriate entry to json-service.ini and set host/port here +;json_enabled = true +;json_host = localhost +;json_port = 10088 + +; Exclude external codes or external sections from being tracked +; and showing up in special menus (most popular, recent, etc.) +blacklist_tracking_xtrncodes: bullshit,sbbslist,avatchoo +blacklist_tracking_xtrnsec: operator + +; Formatting for the "return to previous menu" option +return_msg: "\xae Return to Previous Menu" +logoff_msg: "Logoff" +quit_msg: "Quit" +; Formatting for the "return to previous menu" option +return_msg: "\xae Return to Previous Menu" +logoff_msg: "Logoff" +quit_msg: "Quit" +return_multicolumn_fmt: \1h\1c%3s \xb3 \1n\1c%-32.32s \1h +return_singlecolumn_fmt: \1h\1c%3s \xb3 \1n\1c%s \1h +return_multicolumn_fmt_inverse: \1h\1c%3s \xb3 \1n\x016\1w\1h%-32.32s \1n\1h +return_singlecolumn_fmt_inverse: \1h\1c%3s \xb3 \1n\x016\1w\1h%s \1n\1h +return_multicolumn_special_fmt: \1h\1c%3s \xb3 \1m%-32.32s\1h +return_singlecolumn_special_fmt: \1h\1c%3s \xb3 \1m%s\1h +return_multicolumn_special_fmt_inverse: \1h\1c%3s \xb3 \x015\1w\1h%-32.32s\1n +return_singlecolumn_special_fmt_inverse: \1h\1c%3s \xb3 \x015\1w\1h%s\1n + +; Change display of filearea command menu +;xfer_prompt: "\x01n\x01c\xfe \x01b\x01hFile \x01n\x01c\xfe \x01h" +;xfer_prompt2: " \x01n\x01c@DIR@: \x01n" +;command_prompt: "\r\n\x01gCommand: " +;view_prompt: "\r\n\x01c\x01hView File(s)\r\n" +;searchfname_prompt: "\r\n\x01c\x01hSearch for Filename(s)\r\n" +;remove_prompt: "\r\n\x01c\x01hRemove/Edit File(s)\r\n" +;finddesc_prompt: "\r\n\x01c\x01hFind Text in File Descriptions (no wildcards)\r\n" +;download:prompt: "\r\n\x01c\x01hDownload File(s)\r\n" + +; Change display of search menu +entersearchterm: "\x01y\x01hEnter search term: " +searchresultheader: "\x01n\x01cSearch Results for \x01h%s" +searchagainmsg: "\x01n\x01mPress S to Search Again." + +; Feedback Module +;feedback_subject: "Game Server Feedback\r\n" +;feedback_msg: Thank you for your Feedback, @SYSOP@ will get back to you ASAP!\r\n\r\n + +; Favorites Module +;favorite_add_item: "Add Item" +;favorte_remove_item: "Remove Item" +;add_favorites_msg: "\x01c\x01hAdd Favorite" +;remove_favorites_msg: "\x01c\x01hRemove Favorite" +;favorites_inst: "\x01n\x01w\x01h\x012 [Up/Down/Home/End] to Navigate, [Enter] to Select, [Q] to Quit, [S] to Search " +;favorites_inst_rem: "\x01n\x01w\x01h\x012 [Up/Down/Home/End] to Navigate, [Enter] to Select, [Q] to Quit " +;favorite_add_search_prompt: "\x01c\x01hSearch (ESC to Cancel): \x01n" + +; autocomplete (search) box on add favorite +; see cga_defs.js for color codes +; autocomplete input box +;favorite_add_search_fg=LIGHTGRAY +;favorite_add_search_bg=BG_BLACK +; autocomplete result box +;favorite_add_search_sfg=LIGHTGRAY +;favorite_add_search_sbg=BG_BLACK +; autocomplete highlighted row +;favorite_add_search_hsfg=WHITE +;favorite_add_search_hsbg=BG_MAGENTA + +; scrolling menu on add favorite +; lightbar non-current item +;favorite_add_fg=LIGHTGRAY +;favorite_add_bg=BG_BLACK +; lightbar current item +;favorite_add_lfg=WHITE +;favorite_add_lbg=BG_MAGENTA +; tree heading +;favorite_add_cfg=WHITE +;favorite_add_cbg=BG_BLACK \ No newline at end of file diff --git a/ctrl/modopts.ini b/ctrl/modopts.ini index e70b2a5f693eee9d2428faa6438a97750bedc628..efa0d44398c07b3c5d6f26d413ab4a3baec13836 100644 --- a/ctrl/modopts.ini +++ b/ctrl/modopts.ini @@ -177,3 +177,5 @@ forum_extended_ascii=false max_messages=0 nodelist_ibbs = true + darkmode_off=false + diff --git a/exec/binkit.js b/exec/binkit.js index cc45a027327e99a2bcea83e0eb9470cb982debe4..e12c365985b9737752b9ffdabcd1168a587dc394 100644 --- a/exec/binkit.js +++ b/exec/binkit.js @@ -26,6 +26,7 @@ var version_notice = "BinkIT/" + REVISION; var semaphores = []; // data/binkstats.ini var stats = { inbound: { true: {}, false: {} }, callout: { true: {}, false: {} }, totals: {} }; +var cfgfname; function update_stats(stats, addr, bp, host) { @@ -609,7 +610,7 @@ function callout(addr, scfg, locks, bicfg) log(LOG_INFO, format("%s callout to %s started", bp.revision, addr)); if (bicfg === undefined) - bicfg = new BinkITCfg(); + bicfg = new BinkITCfg(cfgfname); bp.system_operator = bicfg.sysop; bp.plain_auth_only = bicfg.plain_auth_only; bp.crypt_support = bicfg.crypt_support; @@ -845,8 +846,8 @@ function run_outbound(ran) var scfg_ob; log(LOG_DEBUG, "Running outbound"); - scfg = new SBBSEchoCfg(); - bicfg = new BinkITCfg(); + scfg = new SBBSEchoCfg(cfgfname); + bicfg = new BinkITCfg(cfgfname); if (!scfg.is_flo) { log(LOG_ERROR, "sbbsecho not configured for FLO-style mailers."); @@ -1011,8 +1012,8 @@ function run_inbound(sock) log(LOG_INFO, bp.revision + " inbound connection from " +sock.remote_ip_address+":"+sock.remote_port); bp.cb_data = { - binkitcfg:new BinkITCfg(), - binkit_scfg:new SBBSEchoCfg(), + binkitcfg:new BinkITCfg(cfgfname), + binkit_scfg:new SBBSEchoCfg(cfgfname), binkit_file_actions:{}, binkit_flow_contents:{}, binkit_locks:locks @@ -1057,7 +1058,7 @@ function poll_node(addr_str, scfg, bicfg, myaddr) var locks = []; if (scfg === undefined) - scfg = new SBBSEchoCfg(); + scfg = new SBBSEchoCfg(cfgfname); if (myaddr === undefined) myaddr = FIDO.parse_addr(system.fido_addr_list[0], 1, 'fidonet'); @@ -1092,8 +1093,8 @@ function run_polls(ran) var locks = []; log(LOG_DEBUG, "Running polls"); - scfg = new SBBSEchoCfg(); - bicfg = new BinkITCfg(); + scfg = new SBBSEchoCfg(cfgfname); + bicfg = new BinkITCfg(cfgfname); myaddr = FIDO.parse_addr(system.fido_addr_list[0], 1, 'fidonet'); Object.keys(bicfg.node).forEach(function(addr_str) { @@ -1194,6 +1195,11 @@ if (system.fido_addr_list.length < 1) { exit(1); } +for (i = 0; i < argv.length; i++) { + if(file_getext(argv[i]) == ".ini") + cfgfname = argv[i]; +} + // If we're running as a service, call run_inbound(). if (sock !== undefined && sock.descriptor !== -1) run_inbound(sock); diff --git a/exec/load/typeahead.js b/exec/load/typeahead.js index f177513024f568956b51c1bd6dd837250ccf8a88..c2af7e9f8e4e7feb7bdae50b207dad58f4d008b0 100644 --- a/exec/load/typeahead.js +++ b/exec/load/typeahead.js @@ -216,37 +216,37 @@ load('tree.js'); var Typeahead = function (options) { var properties = { - 'x' : 0, - 'y' : 0, - 'len' : console.screen_columns, - 'height' : 0, - 'prompt' : '', - 'fg' : LIGHTGRAY, - 'bg' : BG_BLUE, - 'sfg' : LIGHTGRAY, - 'sbg' : BG_BLUE, - 'hsfg' : WHITE, - 'hsbg' : BG_CYAN, - 'cursor' : ascii(219), - 'position' : 0, - 'text' : '', - 'datasources' : [], - 'delay' : 1, - 'minLength' : 1, - 'lastKey' : system.timer, - 'suggested' : false, - 'attr' : console.attributes, - 'focus' : true, - 'maxResults': 0, + x: 0, + y: 0, + len: console.screen_columns, + height: 0, + prompt: '', + fg: LIGHTGRAY, + bg: BG_BLUE, + sfg: LIGHTGRAY, + sbg: BG_BLUE, + hsfg: WHITE, + hsbg: BG_CYAN, + cursor: ascii(219), + position: 0, + text: '', + datasources: [], + delay: 1, + minLength: 1, + lastKey: system.timer, + suggested: false, + attr: console.attributes, + focus: true, + maxResults: 0, }; var display = { - 'parentFrame' : undefined, - 'frame' : undefined, - 'inputFrame' : undefined, - 'cursor' : undefined, - 'treeFrame' : undefined, - 'tree' : undefined + parentFrame: undefined, + frame: undefined, + inputFrame: undefined, + cursor: undefined, + treeFrame: undefined, + tree: undefined }; function initSettings() { @@ -355,27 +355,26 @@ var Typeahead = function (options) { display.treeFrame.invalidate(); } - if (suggestions.length < 1) { - display.tree = undefined; - return; - } - display.tree = new Tree(display.treeFrame); display.tree.colors.fg = properties.sfg; display.tree.colors.bg = properties.sbg; display.tree.colors.lfg = properties.hsfg; display.tree.colors.lbg = properties.hsbg; - display.tree.addItem(''); - for (var n = 0; n < (properties.maxResults || suggestions.length); n++) { - if (typeof suggestions[n] === 'object' && typeof suggestions[n].text === 'string') { - var item = display.tree.addItem(suggestions[n].text); - item.suggestion = suggestions[n]; - } else if (typeof suggestions[n] === 'string') { - display.tree.addItem(suggestions[n]); + if (suggestions.length < 1) { + display.tree.addItem('No results found'); + } else { + display.tree.addItem(''); + for (var n = 0; n < (properties.maxResults || suggestions.length); n++) { + if (typeof suggestions[n] === 'object' && typeof suggestions[n].text === 'string') { + var item = display.tree.addItem(suggestions[n].text); + item.suggestion = suggestions[n]; + } else if (typeof suggestions[n] === 'string') { + display.tree.addItem(suggestions[n]); + } } } - + display.tree.open(); properties.suggested = true; @@ -412,7 +411,8 @@ var Typeahead = function (options) { break; case KEY_UP: case KEY_DOWN: - if (typeof display.tree !== 'undefined') { + //if (typeof display.tree !== 'undefined') { + if (display.tree.items.length > 1) { display.tree.getcmd(key); } break; @@ -429,6 +429,7 @@ var Typeahead = function (options) { properties.position = (properties.position >= properties.text.length) ? properties.text.length : properties.position + 1; break; case '\b': + case '\x08': if (properties.position === 0) break; properties.text = properties.text.split(''); properties.text.splice((properties.position - 1), 1); diff --git a/exec/load/xtrnmenulib.js b/exec/load/xtrnmenulib.js index f9ba1d62e4dfbb59633d7f56c3dc26904934d484..71e6e790c4bf2278e337328dbdbddd6611a1341d 100644 --- a/exec/load/xtrnmenulib.js +++ b/exec/load/xtrnmenulib.js @@ -20,7 +20,7 @@ function ExternalMenus() { this.options = {}; this.xtrn_custommenu_options = {}; this.menuconfig = {}; - + this.getOptions(); this.getMenuConfig(); } @@ -33,7 +33,7 @@ ExternalMenus.prototype.getMenuConfig = function() { config_src = config_file.read(); config_file.close(); } - + if (typeof config_src !== "undefined") { this.menuconfig = JSON.parse(config_src.toString()); } @@ -43,67 +43,73 @@ ExternalMenus.prototype.getOptions = function(menutype, menuid) { if (typeof menutype === "undefined") { menutype = 'custommenu'; } - + // Get xtrn_sec options from modopts.ini [xtrn_sec] if ((this.options = load({}, "modopts.js", "xtrn_sec")) == null) { this.options = { multicolumn: true, sort: false }; } - + // Get xtrn_custommenu options from modopts.ini [xtrn_custommenu] if ((this.xtrn_custommenu_options = load({}, "modopts.js", "xtrnmenu")) == null) { this.xtrn_custommenu_options = { }; } - + // in all cases, we start with the xtrn_sec options as the base and set the defaults if (this.options.multicolumn === undefined) this.options.multicolumn = true; - + if (this.options.multicolumn_separator === undefined) this.options.multicolumn_separator = " "; - + if (this.options.multicolumn_fmt === undefined) this.options.multicolumn_fmt = system.text(XtrnProgLstFmt); - + if (this.options.singlecolumn_fmt === undefined) this.options.singlecolumn_fmt = "\x01h\x01c%3u \xb3 \x01n\x01c%s\x01h "; - + + if (this.options.multicolumn_fmt_inverse === undefined) + this.options.multicolumn_fmt_inverse = system.text(XtrnProgLstFmt); + + if (this.options.singlecolumn_fmt_inverse === undefined) + this.options.singlecolumn_fmt_inverse = "\x01h\x01c%3u \xb3 \x01n\x01c%s\x01h "; + if (this.options.singlecolumn_margin == undefined) this.options.singlecolumn_margin = 7; - + if (this.options.restricted_user_msg === undefined) this.options.restricted_user_msg = system.text(R_ExternalPrograms); - + if (this.options.no_programs_msg === undefined) this.options.no_programs_msg = system.text(NoXtrnPrograms); - + if (this.options.header_fmt === undefined) this.options.header_fmt = system.text(XtrnProgLstHdr); - + if (this.options.titles === undefined) this.options.titles = system.text(XtrnProgLstTitles); - + if (this.options.underline === undefined) this.options.underline = system.text(XtrnProgLstUnderline); - + if (this.options.which === undefined) this.options.which = system.text(WhichXtrnProg); - + if (this.options.clear_screen === undefined) this.options.clear_screen = true; - + if (this.options.section_fmt === undefined) this.options.section_fmt = "\x01y\x01g%3d:\x01n\x01g %s" - + if (this.options.section_header_fmt === undefined) this.options.section_header_fmt = "\x01-\x01gSelect \x01hExternal Program Section\x01-\x01g:" - + if (this.options.section_which === undefined) this.options.section_which = "\r\n\x01-\x01gWhich, \x01w\x01h~Q\x01n\x01guit or [1]: \x01h" - + if (typeof (this.options.use_xtrn_sec) == "undefined") { this.options.use_xtrn_sec = false; } - + // if its a custom menu, then override with custommenu_options if (menutype == 'custommenu') { if (typeof this.xtrn_custommenu_options.multicolumn_fmt !== "undefined") { @@ -112,78 +118,100 @@ ExternalMenus.prototype.getOptions = function(menutype, menuid) { // cannot default to xtrn_sec multicolumn_fmt due to use of %u instead of %s this.options.multicolumn_fmt = "\x01h\x01c%3s \xb3 \x01n\x01c%-32.32s\x01h "; } - + if (typeof this.xtrn_custommenu_options.singlecolumn_fmt !== "undefined") { this.options.singlecolumn_fmt = this.xtrn_custommenu_options.singlecolumn_fmt; } else { // cannot default to xtrn_sec multicolumn_fmt due to use of %u instead of %s this.options.singlecolumn_fmt = "\x01h\x01c%3s \xb3 \x01n\x01c%s\x01h "; } - + + if (typeof this.xtrn_custommenu_options.multicolumn_fmt_inverse !== "undefined") { + this.options.multicolumn_fmt_inverse = this.xtrn_custommenu_options.multicolumn_fmt_inverse; + } else { + // cannot default to xtrn_sec multicolumn_fmt due to use of %u instead of %s + this.options.multicolumn_fmt_inverse = "\x01h\x01c%3s \xb3 \x01n\x016\x01w\x01h%-32.32s \x01n\x01h"; + } + + if (typeof this.xtrn_custommenu_options.singlecolumn_fmt_inverse !== "undefined") { + this.options.singlecolumn_fmt_inverse = this.xtrn_custommenu_options.singlecolumn_fmt_inverse; + } else { + // cannot default to xtrn_sec multicolumn_fmt due to use of %u instead of %s + this.options.singlecolumn_fmt_inverse = "\x01h\x01c%3s \xb3 \x01n\x016\x01w\x01h%s \x01n\x01h"; + } + this.options.header_fmt = (typeof this.xtrn_custommenu_options.header_fmt !== "undefined") ? this.xtrn_custommenu_options.header_fmt : this.options.header_fmt; - + this.options.titles = (typeof this.xtrn_custommenu_options.titles !== "undefined") ? this.xtrn_custommenu_options.titles : this.options.titles; - + this.options.which = (typeof this.xtrn_custommenu_options.which !== "undefined") ? this.xtrn_custommenu_options.which : this.options.which; - + this.options.underline = (typeof this.xtrn_custommenu_options.underline !== "undefined") ? this.xtrn_custommenu_options.underline : this.options.underline; - + this.options.multicolumn_separator = (typeof this.xtrn_custommenu_options.multicolumn_separator !== "undefined") ? this.xtrn_custommenu_options.multicolumn_separator : this.options.multicolumn_separator; - + this.options.multicolumn = (typeof this.xtrn_custommenu_options.multicolumn !== "undefined") ? this.xtrn_custommenu_options.multicolumn : this.options.multicolumn; - + this.options.sort = (typeof this.xtrn_custommenu_options.sort !== "undefined") ? this.xtrn_custommenu_options.sort : this.options.sort; - + this.options.clear_screen = (typeof this.xtrn_custommenu_options.clear_screen !== "undefined") ? this.xtrn_custommenu_options.clear_screen : this.options.clear_screen; - + this.options.singlecolumn_margin = (typeof this.xtrn_custommenu_options.singlecolumn_margin !== "undefined") ? this.xtrn_custommenu_options.singlecolumn_margin : this.options.singlecolumn_margin; - + if (typeof bbs !== "undefined") { - this.options.singlecolumn_height = (typeof this.xtrn_custommenu_options.singlecolumn_height !== "undefined") - ? this.xtrn_custommenu_options.singlecolumn_height : this.options.singlecolumn_height; - - if (this.options.singlecolumn_height == undefined) - this.options.singlecolumn_height = console.screen_rows - this.options.singlecolumn_margin; - // override and turn off multicolumn if terminal width is less than 80 if (console.screen_columns < 80) options.multicolumn = false; } - + // no need to override restricted_user_msg or no_programs_msg - these // will be the same for both types of menus - + // Allow overriding on a per-menu basis var menuoptions = load({}, "modopts.js", "xtrnmenu:" + menuid); - if ((typeof menuid !== "undefined") && (menuoptions != null)) { + if ((typeof menuid !== "undefined") && (menuoptions !== null)) { for (var m in menuoptions) { this.options[m] = menuoptions[m]; } } } - + + this.options.json_enabled = (typeof this.xtrn_custommenu_options.json_enabled !== undefined) + ? this.xtrn_custommenu_options.json_enabled : false; + this.options.json_host = (typeof this.xtrn_custommenu_options.json_host !== undefined) + ? this.xtrn_custommenu_options.json_host : "localhost"; + this.options.json_port = (typeof this.xtrn_custommenu_options.json_port !== undefined) + ? this.xtrn_custommenu_options.json_port : 10088; + + this.options.blacklist_tracking_xtrncodes = (typeof this.xtrn_custommenu_options.blacklist_tracking_xtrncodes !== undefined) + ? this.xtrn_custommenu_options.blacklist_tracking_xtrncodes : ""; + this.options.blacklist_tracking_xtrnsec = (typeof this.xtrn_custommenu_options.blacklist_tracking_xtrnsec !== undefined) + ? this.xtrn_custommenu_options.blacklist_tracking_xtrnsec : ""; + // these options only apply to terminals/consoles - + // the intention is to obtain all the mod_opts options for xtrn_sec, and // override if a custom menu global setting is set - + //// The following are used for the enhanced custom menu functionality if (this.options.custom_menu_not_found_msg === undefined) { this.options.custom_menu_not_found_msg = "Menu %MENUID% not found"; } - + if (this.options.custom_menu_program_not_found_msg === undefined) { this.options.custom_menu_program_not_found_msg = "Program %PROGRAMID% not found"; } + + this.options.custom = this.xtrn_custommenu_options; return this.options; } @@ -196,9 +224,9 @@ ExternalMenus.prototype.getMenu = function(menuid) { } else { menuid = menuid.toLowerCase(); } - + var menu; - + if ((typeof this.menuconfig !== "undefined") && (typeof this.menuconfig.menus !== "undefined")) { this.menuconfig.menus.some(function (indmenu) { if (indmenu.id.toLowerCase() == menuid) { @@ -206,30 +234,30 @@ ExternalMenus.prototype.getMenu = function(menuid) { } }); } - + if (!menu && (menuid == "main")) { // no custom menus defined, make one to mimic old behavior - + var menuitems = []; - + var i; xtrn_area.sec_list.forEach(function (sec) { if (sec.can_access) { menuitems.push({ - "input": i, - "target": sec.code, - "type": "xtrnmenu", - "title": sec.name, - "access_string": sec.ars + input: i, + target: sec.code, + type: "xtrnmenu", + title: sec.name, + access_string: sec.ars }); i++; } }); - + menu = { - "id": "main", - "title": "Main Menu", - "items": menuitems + id: "main", + title: "Main Menu", + items: menuitems }; } return menu; @@ -237,61 +265,62 @@ ExternalMenus.prototype.getMenu = function(menuid) { // return a section menu object (stock synchronet external door section) ExternalMenus.prototype.getSectionMenu = function(menuid) { - + // grab the specified menu, or get the main menu if ((typeof menuid === "undefined") || !menuid) { return false; } - + var menuitems = []; - var menu, title; - + var title; + + var menu = { + id: menuid, + title: null, + items: [] + } + xtrn_area.sec_list.some(function (sec) { - if (sec.code.toLowerCase() == menuid.toLowerCase()) { - title = sec.name; - + menu.title = sec.name; + if (!sec.can_access || sec.prog_list.length < 1) { return false; } - + var i = 1; sec.prog_list.some(function (prog) { menuitems.push({ - 'input' : i, - 'target': prog.code, - 'title': prog.name, - 'type': 'xtrnprog', - 'access_string': prog.ars, - 'cost': prog.cost + input : i, + target: prog.code, + title: prog.name, + type: 'xtrnprog', + access_string: prog.ars, + cost: prog.cost }); i++; }); - + if (menuitems.length > 0) { - menu = { - 'id': menuid, - 'title': title, - 'items': menuitems, - }; + menu.items = menuitems; } return; } }); - + return menu; } // Sort the menu items according to options ExternalMenus.prototype.getSortedItems = function(menuobj) { var sort_type; - + if ((typeof menuobj.sort_type !== "undefined") && menuobj.sort_type) { sort_type = menuobj.sort_type; // "name" or "key" } else { sort_type = this.options.sort; // bool } - + // first, build a new menu with only options they have access to var menuitemsfiltered = []; for (i in menuobj.items) { @@ -305,7 +334,7 @@ ExternalMenus.prototype.getSortedItems = function(menuobj) { } } break; - + case 'xtrnprog': for (j in xtrn_area.sec_list) { for (var k in xtrn_area.sec_list[j].prog_list) { @@ -317,7 +346,7 @@ ExternalMenus.prototype.getSortedItems = function(menuobj) { } } break; - + case 'custommenu': default: if ((typeof menuobj.items[i].access_string === "undefined") || !menuobj.items[i].access_string) { @@ -332,7 +361,7 @@ ExternalMenus.prototype.getSortedItems = function(menuobj) { break; } } - + // if no custom input keys are specified for an input, then assign a numeric input // this would mimic the built-in external section menu functionality // but this is only assigned on sort_type key because if the sort type is for @@ -347,7 +376,7 @@ ExternalMenus.prototype.getSortedItems = function(menuobj) { } } sortind++; - + if (sort_type == "key") { for (i in menuitemsfiltered) { if (!menuitemsfiltered[i].input) { @@ -356,7 +385,7 @@ ExternalMenus.prototype.getSortedItems = function(menuobj) { } } } - + if (sort_type) { switch (sort_type) { case "key": @@ -369,7 +398,7 @@ ExternalMenus.prototype.getSortedItems = function(menuobj) { break; } } - + // if this is a sort by title and the key is empty, it will be assigned the next available number // this is to support auto-generated inputs like is done on the built-in section menus // however, to keep the numeric keys in order, the numbers could not be pre-assigned like it done @@ -380,7 +409,7 @@ ExternalMenus.prototype.getSortedItems = function(menuobj) { sortind++; } } - + return menuitemsfiltered; } @@ -412,3 +441,214 @@ ExternalMenus.prototype.sort_by_input = function(a, b) { } return 0; } + +// Handle json update +ExternalMenus.prototype.processUpdate = function(update) { + log(ERROR, "Unhandled JSON DB packet: " + JSON.stringify(update)); +} + +// Sort for special menus, by value numerically desc +ExternalMenus.prototype.sort_special = function(obj) { + var keys = []; + for(var key in obj) { + keys.push(key); + } + return keys.sort(function(a,b) { return obj[b]-obj[a]}); +} + +// return menu object for special menu (most recent, etc.) +ExternalMenus.prototype.getSpecial = function(menutype, title, itemcount) { + var options = this.getOptions('custommenu', menutype); + + var menu; + var menuitems = []; + + if (itemcount === undefined) { + itemcount = 0; + } + + if (menutype === undefined) { + log(LOG_DEBUG, "xtrnmenulib: getSpecial called without menutype"); + return false; + } + + if (!this.options.json_enabled) { + log(LOG_DEBUG, "xtrnmenulib: Skipping " + menutype + " because JSON is not enabled"); + return false; + } + + try { + require("json-client.js", "JSONClient"); + var jsonClient = new JSONClient(this.options.json_host, this.options.json_port); + jsonClient.callback = this.processUpdate; + } catch (e) { + writeln("NP" + e); + log(LOG_ERR, "xtrnmenulib: Could not initialize JSON database so special menu is now disabled: " + e); + return false; + } + + var sortedItems = []; + var jsonData = []; + switch (menutype) { + // game keys sorted by most recent descending + case 'recentall': + jsonData = jsonClient.read("xtrnmenu", "launchstart", 1); + break; + case 'recentuser': + jsonData = jsonClient.read("xtrnmenu", "launchstart_user_" + user.alias, 1); + break; + case 'mostlaunchedall': + jsonData = jsonClient.read("xtrnmenu", "launchnum", 1); + break; + case 'mostlauncheduser': + jsonData = jsonClient.read("xtrnmenu", "launchnum_user_" + user.alias, 1); + break; + case 'longestrunall': + jsonData = jsonClient.read("xtrnmenu", "timeran", 1); + break; + case 'longestrunuser': + jsonData = jsonClient.read("xtrnmenu", "timeran_user_" + user.alias, 1); + break; + default: + log(LOG_ERR, "xtrnmenulib: Unknown special menu type: " + menutype); + return false; + } + + if (!jsonData) { + jsonData = {}; + } + sortedItems = this.sort_special(jsonData); + + var i = 1; + for (var d=0; d<sortedItems.length; d++) { + var doorid = sortedItems[d]; + for (var e in xtrn_area.prog) { + if (xtrn_area.prog[e].code.toLowerCase() == doorid.toLowerCase()) { + var stats; + switch (menutype) { + case 'longestrunall': + case 'longestrunuser': + var seconds = jsonData[doorid]; + var hours = ~~(seconds/3600); + var mins = ~~((seconds % 3600) / 60); + var secs = ~~seconds % 60; + stats = ("00"+hours).slice(-2) + ":" + ("00"+mins).slice(-2) + ":" + ("00"+secs).slice(-2); + break; + case 'recentall': + case 'recentuser': + var elapsedtime = (time() - jsonData[doorid])*1000; + var msPerMin = 60000; + var msPerHour = msPerMin * 60; + var msPerDay = msPerHour * 24; + var msPerMon = msPerDay * 30; + var msPerYear = msPerDay * 365; + if (elapsedtime < msPerMin) { + stats = Math.round(elapsedtime/1000) + ' sec ago'; + } else if (elapsedtime < msPerHour) { + stats = Math.round(elapsedtime/msPerMin) + ' min ago'; + } else if (elapsedtime < msPerDay) { + var stat = Math.round(elapsedtime/msPerHour) + stats = Math.round(elapsedtime/msPerHour) + ' hr ago'; + } else if (elapsedtime < msPerMon) { + stats = Math.round(elapsedtime/msPerDay) + ' day ago'; + } else if (elapsedtime < msPerYear) { + stats = Math.round(elapsedtime/msPerMonth) + ' mon ago'; + } else { + stats = Math.round(elapsedtime/msPerYear) + ' yr ago'; + } + break; + default: + stats = jsonData[doorid].toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");; + break; + } + menuitems.push({ + input: i, + target: xtrn_area.prog[e].code, + title: xtrn_area.prog[e].name, + type: 'xtrnprog', + access_string: xtrn_area.prog[e].ars, + cost: xtrn_area.prog[e].cost, + stats: stats, + }); + i++; + break; + } + } + + if ((itemcount > 0) && ((d + 1) >= itemcount)) { + break; + } + } + if (menuitems.length > 0) { + menu = { + id: menutype, + title: title, + items: menuitems, + }; + } + + return menu; +} + +// return menu object for user's favorites +ExternalMenus.prototype.getFavorites = function(title, itemcount) { + var menu; + var menuitems = []; + + if (itemcount === undefined) { + itemcount = 0; + } + + if (!this.options.json_enabled) { + log(LOG_DEBUG, "xtrnmenulib: Skipping favorites because JSON is not enabled"); + return false; + } + + try { + require("json-client.js", "JSONClient"); + var jsonClient = new JSONClient(this.options.json_host, this.options.json_port); + jsonClient.callback = this.processUpdate; + } catch (e) { + log(LOG_ERR, "xtrnmenulib: Could not initialize JSON database so favorites is now disabled: " + e); + return false; + } + + var sortedItems = []; + var jsonData = jsonClient.read("xtrnmenu", "favorites_" + user.alias, 1); + + if (!jsonData) { + jsonData = {}; + } + + var i = 1; + for (var d=0; d<jsonData.length; d++) { + for (var e in xtrn_area.prog) { + if (xtrn_area.prog[e].code.toLowerCase() == jsonData[d].toLowerCase()) { + var stats; + + menuitems.push({ + input: i, + target: xtrn_area.prog[e].code, + title: xtrn_area.prog[e].name, + type: 'xtrnprog', + access_string: xtrn_area.prog[e].ars, + cost: xtrn_area.prog[e].cost, + }); + i++; + break; + } + } + + if ((itemcount > 0) && ((d + 1) >= itemcount)) { + break; + } + } + + menu = { + id: 'favorites', + title: title !== undefined ? title : 'Favorites', + items: menuitems, + }; + + return menu; +} \ No newline at end of file diff --git a/exec/xtrnmenu.js b/exec/xtrnmenu.js index 85a57e864b17153722f02140a0590ecf813e6545..3fa3fed9d1535663cc624bd2ef45ab5a15f31a0a 100644 --- a/exec/xtrnmenu.js +++ b/exec/xtrnmenu.js @@ -19,6 +19,8 @@ require("sbbsdefs.js", "K_NONE"); require("xtrnmenulib.js", "MENU_LOADED"); +const ansiterm = load({}, 'ansiterm_lib.js'); + var options, xsec = -1; //// Main @@ -27,10 +29,17 @@ const menuconfig = ExternalMenus.menuconfig; var i,j; -if ((argv[0] == 'command') && (typeof argv[1] != "undefined")) { - docommand(argv[1]); -} else if (argv[0] == 'gamesrv') { - external_section_menu_custom('gamesrv'); +var gamesrv = false; + +if ((argv[0] == 'command') && (typeof argv[1] !== "undefined")) { + docommand(argv[1], (typeof argv[2] !== "undefined" ? argv[2] : ""), (typeof argv[3] !== "undefined" ? argv[3] : "")); +} else if (argv[0] === 'gamesrv') { + gamesrv = true; + external_menu_custom(); +} else if ((argv[0] === 'pre') && (typeof argv[1] !== "undefined")) { + dopre(argv[1]); +} else if ((argv[0] === 'post') && (typeof argv[1] !== "undefined")) { + dopost(argv[1]); } else { for (i in argv) { for (j in xtrn_area.sec_list) { @@ -38,32 +47,241 @@ if ((argv[0] == 'command') && (typeof argv[1] != "undefined")) { xsec = j; } } - + if (xsec > -1) { // if its the id of a standard section menu if (options.use_xtrn_sec) { // stock menu js.exec("xtrn_sec.js", {}, xsec); } else { - external_section_menu_custom(xsec); + external_menu_custom(xsec); } } else if (typeof argv[0] !== "undefined") { // if its not a section menu, assume it is a custom menu - external_section_menu_custom(argv[0]); + external_menu_custom(argv[0]); } else { // main custom menu - external_section_menu_custom(); + external_menu_custom(); + } +} + +// Runs pre-door stat tracking +function dopre(progCode) +{ + require("nodedefs.js", "NODE_XTRN"); + if (bbs.node_action != NODE_XTRN) { + log(LOG_DEBUG, "xtrnmenu pre: Skipping " + progCode + " because its not during a normal door run"); + // don't want to track door stats for login events + return; + } + + var options = ExternalMenus.getOptions('custommenu', 'main'); + + if (!options.json_enabled) { + log(LOG_DEBUG, "xtrnmenu pre: Skipping " + progCode + " because JSON is not enabled"); + return; + } + + if (typeof options.blacklist_tracking_xtrncodes !== "undefined") { + var blacklist_xtrncodes = options.blacklist_tracking_xtrncodes.split(','); + for (var b=0; b < blacklist_xtrncodes.length; b++) { + if (blacklist_xtrncodes[b].toLowerCase() == progCode.toLowerCase()) { + log(LOG_DEBUG, "xtrnmenu pre: Skipping " + progCode + " because in blacklist_tracking_xtrncodes"); + return; + } + } + } + + // get section of this program and block tracking if its in blacklist config + var secCode; + for (var j in xtrn_area.sec_list) { + for (var k in xtrn_area.sec_list[j].prog_list) { + if (xtrn_area.sec_list[j].prog_list[k].code.toLowerCase() == progCode.toLowerCase()) { + secCode = xtrn_area.sec_list[j].code; + } + } + } + if (typeof options.blacklist_tracking_xtrnsec !== "undefined") { + var blacklist_seccodes = options.blacklist_tracking_xtrnsec.split(','); + for (var b=0; b < blacklist_seccodes.length; b++) { + if (blacklist_seccodes[b].toLowerCase() == secCode) { + log(LOG_DEBUG, "xtrnmenu pre: Skipping " + progCode + " because in blacklist_tracking_xtrnsec " + secCode); + return; + } + } + } + + try { + require("json-client.js", "JSONClient"); + var jsonClient = new JSONClient(options.json_host, options.json_port); + jsonClient.callback = ExternalMenus.processUpdate; + } catch (e) { + log(LOG_ERR, "xtrnmenu pre: Could not initialize JSON database so door tracking is now disabled: " + e); + return; + } + + // global times a door is run + var globalLaunchNum = jsonClient.read("xtrnmenu", "launchnum", 1); + + if (typeof globalLaunchNum === "undefined") { + globalLaunchNum = {}; + } + + if (typeof globalLaunchNum[progCode] !== "undefined") { + globalLaunchNum[progCode]++; + } else { + globalLaunchNum[progCode] = 1; + } + log(LOG_DEBUG, "xtrnmenu pre: globalLaunchNum " + progCode + " = " + globalLaunchNum[progCode]); + + jsonClient.write("xtrnmenu", "launchnum", globalLaunchNum, 2); + // user's times a door is run + var userLaunchNum = jsonClient.read("xtrnmenu", "launchnum_user_" + user.alias, 1); + if (!userLaunchNum) { + userLaunchNum = {}; + } + + if (typeof userLaunchNum[progCode] !== "undefined") { + userLaunchNum[progCode]++; + } else { + userLaunchNum[progCode] = 1; + } + log(LOG_DEBUG, "xtrnmenu pre: userLaunchNum " + progCode + " = " + userLaunchNum[progCode]); + jsonClient.write("xtrnmenu", "launchnum_user_" + user.alias,userLaunchNum, 2); + + // global launch start time (most recent) + var globalLaunchStart = jsonClient.read("xtrnmenu", "launchstart", 1); + if (!globalLaunchStart) { + globalLaunchStart = {}; + } + + globalLaunchStart[progCode] = time(); + log(LOG_DEBUG, "xtrnmenu pre: globalLaunchStart " + progCode + " = " + globalLaunchStart[progCode] ); + jsonClient.write("xtrnmenu", "launchstart", globalLaunchStart, 2); + + // user launch start time (most recent) + var userLaunchStart = jsonClient.read("xtrnmenu", "launchstart_user_" + user.alias, 1); + if (!userLaunchStart) { + userLaunchStart = {}; + } + + userLaunchStart[progCode] = time(); + log(LOG_DEBUG, "xtrnmenu pre: userLaunchStart " + progCode + " = " + userLaunchStart[progCode] ); + jsonClient.write("xtrnmenu", "launchstart_user_" + user.alias, userLaunchStart, 2); + + jsonClient.cycle(); +} + + +// Runs post-door stat tracking +function dopost(progCode) +{ + require("nodedefs.js", "NODE_XTRN"); + if (bbs.node_action != NODE_XTRN) { + log(LOG_DEBUG, "xtrnmenu post: Skipping " + progCode + " because its not during a normal door run"); + // don't want to track door stats for login events + return; + } + + var options = ExternalMenus.getOptions('custommenu', 'main'); + + if (!options.json_enabled) { + log(LOG_DEBUG, "xtrnmenu post: Skipping " + progCode + " because JSON is not enabled"); + return; + } + + if (typeof options.blacklist_tracking_xtrncodes !== "undefined") { + var blacklist_xtrncodes = options.blacklist_tracking_xtrncodes.split(','); + for (var b=0; b < blacklist_xtrncodes.length; b++) { + if (blacklist_xtrncodes[b].toLowerCase() == progCode.toLowerCase()) { + log(LOG_DEBUG, "xtrnmenu post: Skipping " + progCode + " because in blacklist_tracking_xtrncodes"); + return; + } + } + } + + // get section of this program and block tracking if its in blacklist config + var secCode; + for (var j in xtrn_area.sec_list) { + for (var k in xtrn_area.sec_list[j].prog_list) { + if (xtrn_area.sec_list[j].prog_list[k].code.toLowerCase() == progCode.toLowerCase()) { + secCode = xtrn_area.sec_list[j].code; + } + } + } + if (typeof options.blacklist_tracking_xtrnsec !== "undefined") { + var blacklist_seccodes = options.blacklist_tracking_xtrnsec.split(','); + for (var b=0; b < blacklist_seccodes.length; b++) { + if (blacklist_seccodes[b].toLowerCase() == secCode) { + log(LOG_DEBUG, "xtrnmenu post: Skipping " + progCode + " because in blacklist_tracking_xtrnsec " + secCode); + return; + } + } + } + + try { + require("json-client.js", "JSONClient"); + var jsonClient = new JSONClient(options.json_host, options.json_port); + jsonClient.callback = ExternalMenus.processUpdate; + } catch (e) { + log(LOG_ERR, "xtrnmenu pre: Could not initialize JSON database so door tracking is now disabled: " + e); + return; + } + + // user time ran + var newTimeRan; + var userLaunchStart = jsonClient.read("xtrnmenu", "launchstart_user_" + user.alias, 1); + if (userLaunchStart) { + + if (typeof userLaunchStart[progCode] !== "undefined") { + var lasttime = userLaunchStart[progCode]; + var now = time(); + var runtime = now - lasttime; + + var userTimeRan = jsonClient.read("xtrnmenu", "timeran_user_" + user.alias, 1); + if (!userTimeRan) { + // first time + userTimeRan = {}; + } + + if (typeof userTimeRan[progCode] === "undefined") { + userTimeRan[progCode] = runtime; + } else { + userTimeRan[progCode] = userTimeRan[progCode] + runtime; + } + jsonClient.write("xtrnmenu", "timeran_user_" + user.alias, userTimeRan, 2); + + // global time ran + var globalTimeRan = jsonClient.read("xtrnmenu", "timeran", 1); + if (typeof globalTimeRan === "undefined") { + globalTimeRan = {}; + } + + if (typeof globalTimeRan[progCode] === "undefined") { + globalTimeRan[progCode] = runtime; + } else { + globalTimeRan[progCode] = globalTimeRan[progCode] + runtime; + } + jsonClient.write("xtrnmenu", "timeran", globalTimeRan, 2); + + jsonClient.cycle(); + } } } // Runs custom commands, for gamesrv -function docommand(command) +function docommand(command, commandparam1, commandparam2) { + var options = ExternalMenus.getOptions('custommenu', 'main'); + switch (command) { case 'checkmail': - var lmsg = user.stats.mail_wait; + var lmsg = user.stats.unread_mail_waiting; if (lmsg > 0) { - console.putmsg('\r\n\x01gYou have \x01c' + parseInt(lmsg) + ' \x01gMessages waiting'); + console.crlf(); + if (console.yesno(user.stats.unread_mail_waiting + " message(s) waiting. Read now")) { + bbs.read_mail(); + } } else { console.putmsg('\r\n\x01gNo New Messages'); } @@ -71,10 +289,11 @@ function docommand(command) console.clear(); break; case 'feedback': - var subject; - console.putmsg('\r\n\x01\gPlease choose \x01wYes \x01gto forward to netmail!\r\n\r\n'); - bbs.email(1, subject = "Game Server Feedback\r\n"); - console.putmsg('Thank you for your Feedback, @SYSOP@ will get back to you ASAP!\r\n\r\n'); + var subject = options.custom.feedback_subject !== undefined + ? options.custom.feedback_subject: "Game Server Feedback\r\n"; + bbs.email(1, WM_EMAIL, "", subject); + console.putmsg(options.custom.feedback_msg !== undefined ? + options.custom.feedback_msg : 'Thank you for your Feedback, @SYSOP@ will get back to you ASAP!\r\n\r\n'); break; case 'prefs': bbs.user_config(); @@ -86,36 +305,272 @@ function docommand(command) commandstr = console.getstr(); str_cmds(commandstr); break; + case 'textsec': + bbs.text_sec(); + break; + case 'filearea': + if(file_exists(system.text_dir+"menu/tmessage.*")) + bbs.menu("tmessage"); + filearea(commandparam1, commandparam2); + break; + case 'chat': + load("chat_sec.js"); + break; default: doerror("Unknown command " + command); break; } } -// Renders the top-level external menu -function external_section_menu_custom(menuid) +// Present a basic download area +// Taken from classic shell +function filearea(filelib, filedir) { + var options = ExternalMenus.getOptions('custommenu', 'main'); + + var key; + + if (!filelib || !filedir) { + writeln("Error: No file area or dir selected"); + return; + } + + bbs.curlib = filelib; + bbs.curdir = filedir; + + file_transfers: + while (1) { + console.clear(); + + if (!bbs.menu("xtrnmenu_xfer", P_NOERROR)) { + console.crlf(); + writeln("\x01c\x01h[B]\x01n\x01c Batch"); + writeln("\x01c\x01h[D]\x01n\x01c Download"); + writeln("\x01c\x01h[F]\x01n\x01c Find Text in Descriptions"); + writeln("\x01c\x01h[V]\x01n\x01c View Files"); + writeln("\x01c\x01h[L]\x01n\x01c List Files"); + writeln("\x01c\x01h[S]\x01n\x01c Search Filename"); + writeln("\x01c\x01h[Q]\x01n\x01c Quit"); + } + + console.crlf(); + + // Update node status + bbs.node_action = NODE_XFER; + bbs.node_sync(); + + // Display main Prompt + console.print(typeof options.custom.xfer_prompt !== "undefined" ? + options.custom.xfer_prompt : "\x01n\x01c\xfe \x01b\x01hFile \x01n\x01c\xfe \x01h"); + if (bbs.compare_ars("exempt T")) + console.putmsg("@TUSED@", P_SAVEATR); + else + console.putmsg("@TLEFT@", P_SAVEATR); + console.putmsg(typeof options.custom.xfer_prompt2 !== "undefined" ? + options.custom.xfer_prompt2 : " \x01n\x01c@DIR@: \x01n"); + + // Get key (with / extended commands allowed) + var str = console.getkey().toLowerCase(); + + // Commands + switch (str) { + case ';': + require("str_cmds.js", "str_cmds"); + console.putmsg(typeof options.custom.command_prompt !== "undefined" ? + options.custom.command_prompt : "\r\n\x01gCommand: "); + var commandstr = console.getstr(); + str_cmds(commandstr); + break; + + case '!': + if (bbs.compare_ars("SYSOP")) { + bbs.menu("sysxfer"); + } + break; + + case 'b': + bbs.batch_menu(); + break; + + case 'd': + console.print(typeof options.custom.download_prompt !== "undefined" ? + options.custom.download_prompt : "\r\n\x01c\x01hDownload File(s)\r\n"); + + if (bbs.batch_dnload_total > 0) { + if (console.yesno(bbs.text(DownloadBatchQ))) { + bbs.batch_download(); + break; + } + } + str = bbs.get_filespec(); + if ((str == null) || (file_area.lib_list.length == 0)) + break; + if (user.security.restrictions & UFLAG_D) { + console.putmsg(bbs.text(R_Download), P_SAVEATR); + break; + } + if (!bbs.list_file_info(file_area.lib_list[bbs.curlib].dir_list[bbs.curdir].number, str, FI_DOWNLOAD)) { + var s = 0; + console.putmsg(bbs.text(SearchingAllDirs), P_SAVEATR); + for (i = 0; i < file_area.lib_list[bbs.curlib].dir_list.length; i++) { + if (i != bbs.curdir && + (s = bbs.list_file_info(file_area.lib_list[bbs.curlib].dir_list[i].number, str, FI_DOWNLOAD)) != 0) { + if (s == -1 || str.indexOf('?') != -1 || str.indexOf('*') != -1) { + continue file_transfers; + } + } + } + console.putmsg(bbs.text(SearchingAllLibs), P_SAVEATR); + for (i = 0; i < file_area.lib_list.length; i++) { + if (i == bbs.curlib) + continue; + for (j = 0; j < file_area.lib_list[i].dir_list.length; j++) { + if ((s = bbs.list_file_info(file_area.lib_list[i].dir_list[j].number, str, FI_DOWNLOAD)) != 0) { + if (s == -1 || str.indexOf('?') != -1 || str.indexOf('*') != -1) { + continue file_transfers; + } + } + } + } + } + break; + + case 'f': + console.print(typeof options.custom.finddesc_prompt !== "undefined" ? + options.custom.finddesc_prompt : "\r\n\x01c\x01hFind Text in File Descriptions (no wildcards)\r\n"); + bbs.scan_dirs(FL_FINDDESC, false); + break; + + case 'i': + file_info(); + break; + + case 'l': + i = bbs.list_files(); + if (i == -1) + break; + if (i == 0) + console.putmsg(bbs.text(EmptyDir), P_SAVEATR); + else + console.putmsg(format(bbs.text(NFilesListed), i), P_SAVEATR); + break; + + case 'r': + console.print(typeof options.custom.remove_prompt !== "undefined" ? + options.custom.remove_prompt : "\r\n\x01c\x01hRemove/Edit File(s)\r\n"); + str = bbs.get_filespec(); + if (str == null) + break; + if (!bbs.list_file_info(file_area.lib_list[bbs.curlib].dir_list[bbs.curdir].number, str, FI_REMOVE)) { + var s = 0; + console.putmsg(bbs.text(SearchingAllDirs), P_SAVEATR); + for (i = 0; i < file_area.lib_list[bbs.curlib].dir_list.length; i++) { + if (i != bbs.curdir && + (s = bbs.list_file_info(file_area.lib_list[bbs.curlib].dir_list[i].number, str, FI_REMOVE)) != 0) { + if (s == -1 || str.indexOf('?') != -1 || str.indexOf('*') != -1) { + continue file_transfers; + } + } + } + console.putmsg(bbs.text(SearchingAllLibs), P_SAVEATR); + for (i = 0; i < file_area.lib_list.length; i++) { + if (i == bbs.curlib) + continue; + for (j = 0; j < file_area.lib_list[i].dir_list.length; j++) { + if ((s = bbs.list_file_info(file_area.lib_list[i].dir_list[j].number, str, FI_REMOVE)) != 0) { + if (s == -1 || str.indexOf('?') != -1 || str.indexOf('*') != -1) { + continue file_transfers; + } + } + } + } + } + break; + + case 'q': + case "\x1B": + case KEY_LEFT: + return; + + case 's': + console.print(typeof options.custom.searchfname_prompt !== "undefined" ? + options.custom.searchfname_prompt : "\r\n\x01c\x01hSearch for Filename(s)\r\n"); + bbs.scan_dirs(FL_NO_HDR); + break; + + case 't': + bbs.temp_xfer(); + break; + + case 'v': + console.print(typeof options.custom.view_prompt !== "undefined" ? + options.custom.view_prompt : "\r\n\x01c\x01hView File(s)\r\n"); + str = bbs.get_filespec(); + if (str == null) + continue file_transfers; + if (!bbs.list_files(file_area.lib_list[bbs.curlib].dir_list[bbs.curdir].number, str, FL_VIEW)) { + console.putmsg(bbs.text(SearchingAllDirs), P_SAVEATR); + for (i = 0; i < file_area.lib_list[bbs.curlib].dir_list.length; i++) { + if (i == bbs.curdir) + continue; + if (bbs.list_files(file_area.lib_list[bbs.curlib].dir_list[i].number, str, FL_VIEW)) + break; + } + if (i < file_area.lib_list[bbs.curlib].dir_list.length) + continue file_transfers; + console.putmsg(bbs.text(SearchingAllLibs), P_SAVEATR); + for (i = 0; i < file_area.lib_list.length; i++) { + if (i == bbs.curlib) + continue; + for (j = 0; j < file_area.lib_list[i].dir_list.length; j++) { + if (bbs.list_files(file_area.lib_list[i].dir_list[j].number, str, FL_VIEW)) + continue file_transfers; + } + } + } + break; + + default: + break; + } + console.crlf(); + } +} + +// Renders the menu +function external_menu_custom(menuid) { var i, menucheck, menuobj, item_multicolumn_fmt, item_singlecolumn_fmt, - cost, multicolumn, menuitemsfiltered = []; + item_multicolumn_fmt_inverse, item_singlecolumn_fmt_inverse, + cost, multicolumn, selected_index = 0, menuitemsfiltered = []; var validkeys = []; // valid chars on menu selection var keymax = 0; // max integer allowed on menu selection - - var gamesrv = menuid == "gamesrv" ? true : false; - if (gamesrv) { - menuid = undefined; + + if (menuid == undefined) { + menuid = 'main'; } - + var options = ExternalMenus.getOptions('custommenu', menuid); - + menuobj = ExternalMenus.getMenu(menuid); - + // Allow overriding auto-format on a per-menu basis var multicolumn_fmt = options.multicolumn_fmt; var singlecolumn_fmt = options.singlecolumn_fmt; - + var multicolumn_fmt_inverse = options.multicolumn_fmt_inverse; + var singlecolumn_fmt_inverse = options.singlecolumn_fmt_inverse; + + var return_singlecolumn_fmt = options.custom.return_singlecolumn_fmt !== undefined + ? options.custom.return_singlecolumn_fmt : singlecolumn_fmt; + var return_multicolumn_fmt = options.custom.return_multicolumn_fmt !== undefined + ? options.custom.return_multicolumn_fmt : multicolumn_fmt; + var return_singlecolumn_fmt_inverse = options.custom.return_singlecolumn_fmt_inverse !== undefined + ? options.custom.return_singlecolumn_fmt_inverse : singlecolumn_fmt_inverse; + var return_multicolumn_fmt_inverse = options.custom.return_multicolumn_fmt_inverse !== undefined + ? options.custom.return_multicolumn_fmt_inverse : multicolumn_fmt_inverse; + while (bbs.online) { console.aborted = false; - + if (typeof menuobj === "undefined") { menuobj = ExternalMenus.getSectionMenu(menuid); if (typeof menuobj === "undefined") { @@ -123,37 +578,77 @@ function external_section_menu_custom(menuid) break; } } - + if (options.clear_screen) { console.clear(LIGHTGRAY); } - + if (user.security.restrictions&UFLAG_X) { write(options.restricted_user_msg); break; } - + if (!xtrn_area.sec_list.length) { write(options.no_programs_msg); break; } - + + // empty menu + if (!menuobj.items.length && (menuid != "main")) { + write(options.no_programs_msg); + break; + } + system.node_list[bbs.node_num-1].aux = 0; /* aux is 0, only if at menu */ bbs.node_action = NODE_XTRN; bbs.node_sync(); - + menuitemsfiltered = ExternalMenus.getSortedItems(menuobj); - + + // The quit item is intended to aid in the lightbar navigation + if (gamesrv && (menuid == 'main')) { + menuitemsfiltered.push({ + input: 'Q', + title: options.custom_logoff_msg !== undefined ? options.custom_logoff_msg : 'Logoff', + target: '', + type: 'quit', + }); + } else if (menuid == 'main') { + menuitemsfiltered.push({ + input: 'Q', + title: options.custom.quit_msg !== undefined ? options.custom.quit_msg : 'Quit', + target: '', + type: 'quit', + }); + } else { + menuitemsfiltered.unshift({ + input: 'Q', + title: options.custom.return_msg !== undefined ? options.custom.return_msg : 'Return to Previous Menu', + target: '', + type: 'quit', + }); + } + if (!bbs.menu("xtrnmenu_head_" + menuid, P_NOERROR) && !bbs.menu("xtrnmenu_head", P_NOERROR)) { bbs.menu("xtrn_head", P_NOERROR); } - + // if file exists text/menu/xtrnmenu_(menuid).[rip|ans|mon|msg|asc], // then display that, otherwise dynamiic if (!bbs.menu("xtrnmenu_" + menuid, P_NOERROR)) { - + + // determine lines left. we can't know the size of the footer so + // let the sysop use singlecolumn_margin to specify that. Below + // calcution will always leave room for titles and underline even + // if they aren't rendered + var linesleft = console.screen_rows - console.line_counter - options.singlecolumn_margin + - 2 - 2; // -2 for header_fmt/crlf and -2 for crlf and footer + if(options.titles.trimRight() != '') linesleft = linesleft - 1; + if(options.underline.trimRight() != '') linesleft = linesleft - 2; + + multicolumn = menuitemsfiltered.length > linesleft; + // if no custom menu file in text/menu, create a dynamic one - multicolumn = options.multicolumn && menuitemsfiltered.length > options.singlecolumn_height; printf(options.header_fmt, menuobj.title); if(options.titles.trimRight() != '') write(options.titles); @@ -172,7 +667,7 @@ function external_section_menu_custom(menuid) write(options.underline); } console.crlf(); - + // n is the number of items for the 1st column var n; if (multicolumn) { @@ -180,7 +675,7 @@ function external_section_menu_custom(menuid) } else { n = menuitemsfiltered.length; } - + // j is the index for each menu item on 2nd column var j = n; // start j at the first item for 2nd column for (i = 0; i < n && !console.aborted; i++) { @@ -189,9 +684,9 @@ function external_section_menu_custom(menuid) // if its an external program, get the cost cost = xtrn_area.prog[menuitemsfiltered[i].target.toLowerCase()].cost; } - + console.add_hotspot(menuitemsfiltered[i].input.toString()); - + validkeys.push(menuitemsfiltered[i].input.toString()); var intCheck = Number(menuitemsfiltered[i].input); if (!intCheck.isNaN) { @@ -199,53 +694,108 @@ function external_section_menu_custom(menuid) keymax = menuitemsfiltered[i].input; } } - + // allow overriding format on a per-item basis // great for featuring a specific game var checkkey = menuitemsfiltered[i].target + '-multicolumn_fmt'; checkkey = checkkey.toLowerCase(); item_multicolumn_fmt = (typeof options[checkkey] !== "undefined") ? options[checkkey] : options.multicolumn_fmt; - + item_multicolumn_fmt_inverse = (typeof options[checkkey + "_inverse"] !== "undefined") ? + options[checkkey + "_inverse"] : options.multicolumn_fmt_inverse; + checkkey = menuitemsfiltered[i].target + '-singlecolumn_fmt' checkkey = checkkey.toLowerCase(); item_singlecolumn_fmt = (typeof options[checkkey] !== "undefined") ? options[checkkey] : options.singlecolumn_fmt; - - printf(multicolumn ? item_multicolumn_fmt : item_singlecolumn_fmt, - menuitemsfiltered[i].input.toString().toUpperCase(), - menuitemsfiltered[i].title, - cost - ); - + item_singlecolumn_fmt_inverse = (typeof options[checkkey + "_inverse"] !== "undefined") ? + options[checkkey + "_inverse"] : options.singlecolumn_fmt_inverse; + + if (i == selected_index) { + if (menuitemsfiltered[i].type == 'quit') { + printf(multicolumn ? return_multicolumn_fmt_inverse : return_singlecolumn_fmt_inverse, + menuitemsfiltered[i].input.toString().toUpperCase(), + menuitemsfiltered[i].title, + '' + ); + } else { + printf(multicolumn ? item_multicolumn_fmt_inverse : item_singlecolumn_fmt_inverse, + menuitemsfiltered[i].input.toString().toUpperCase(), + menuitemsfiltered[i].title, + cost + ); + } + } else { + if (menuitemsfiltered[i].type == 'quit') { + printf(multicolumn ? return_multicolumn_fmt : return_singlecolumn_fmt, + menuitemsfiltered[i].input.toString().toUpperCase(), + menuitemsfiltered[i].title, + '' + ); + } else { + printf(multicolumn ? item_multicolumn_fmt : item_singlecolumn_fmt, + menuitemsfiltered[i].input.toString().toUpperCase(), + menuitemsfiltered[i].title, + cost + ); + } + } + if (multicolumn) { - if ((typeof(menuitemsfiltered[j]) !== "undefined")) { + if (typeof menuitemsfiltered[j] !== "undefined") { validkeys.push(menuitemsfiltered[j].input.toString()); - + var intCheck = Number(menuitemsfiltered[j].input); if (!intCheck.isNaN) { if (intCheck > keymax) { keymax = menuitemsfiltered[j].input; } } - + // allow overriding format on a per-item basis // great for featuring a specific game var checkkey = menuitemsfiltered[j].target + '-multicolumn_fmt'; checkkey = checkkey.toLowerCase(); item_multicolumn_fmt = (typeof options[checkkey] !== "undefined") ? options[checkkey] : options.multicolumn_fmt; - + item_multicolumn_fmt_inverse = (typeof options[checkkey + "_inverse"] !== "undefined") ? + options[checkkey + "_inverse"] : options.multicolumn_fmt_inverse; + checkkey = menuitemsfiltered[j].target + '-singlecolumn_fmt' checkkey = checkkey.toLowerCase(); - + write(options.multicolumn_separator); console.add_hotspot(menuitemsfiltered[j].input.toString()); - printf(item_multicolumn_fmt, - menuitemsfiltered[j].input.toString().toUpperCase(), - menuitemsfiltered[j].title, - cost - ); + + if (selected_index == j) { + if (menuitemsfiltered[j].type == 'quit') { + printf(multicolumn ? return_multicolumn_fmt_inverse : return_singlecolumn_fmt_inverse, + menuitemsfiltered[j].input.toString().toUpperCase(), + menuitemsfiltered[j].title, + '' + ); + } else { + printf(item_multicolumn_fmt_inverse, + menuitemsfiltered[j].input.toString().toUpperCase(), + menuitemsfiltered[j].title, + cost + ); + } + } else { + if (menuitemsfiltered[j].type == 'quit') { + printf(return_multicolumn_fmt, + menuitemsfiltered[j].input.toString().toUpperCase(), + menuitemsfiltered[j].title, + '' + ); + } else { + printf(item_multicolumn_fmt, + menuitemsfiltered[j].input.toString().toUpperCase(), + menuitemsfiltered[j].title, + cost + ); + } + } } else { write(options.multicolumn_separator); } @@ -253,29 +803,38 @@ function external_section_menu_custom(menuid) } console.crlf(); } - + if (gamesrv) { if (!bbs.menu("xtrn_gamesrv_tail_" + menuid, P_NOERROR)) { bbs.menu("xtrn_gamesrv_tail", P_NOERROR); } } - + if (!bbs.menu("xtrnmenu_tail_" + menuid, P_NOERROR) && !bbs.menu("xtrnmenu_tail", P_NOERROR)) { bbs.menu("xtrn_tail", P_NOERROR); } - + bbs.node_sync(); console.mnemonics(options.which); } - + validkeys.push('q'); - + validkeys.push("\x1B"); + validkeys.push(KEY_LEFT); + validkeys.push(KEY_RIGHT); + validkeys.push(KEY_UP); + validkeys.push(KEY_DOWN); + validkeys.push("\x0d"); //enter + var keyin, keyin2; var maxkeylen = 0; var maxfirstkey = 0; var morekeys = []; var k; for (k in validkeys) { + if (validkeys[k] == "\x1B") { + continue; + } if (validkeys[k].length > maxkeylen) { maxkeylen = validkeys[k].length; } @@ -283,58 +842,136 @@ function external_section_menu_custom(menuid) morekeys.push(validkeys[k].toString().toLowerCase().substring(0,1)); } } - + // get first key keyin = console.getkey(); keyin = keyin.toString().toLowerCase(); - + + if (gamesrv && ((keyin == "\x1B")) && (menuid == 'main')) { + // don't have ESC do anything on the main menu + } else if ((keyin == 'q') || (keyin == "\x1B")) { + if (gamesrv && (menuid == 'main')) { + // doing it this way rather than calling bbs.logoff() + // so that the prompt defaults to Yes + console.crlf(); + if (console.yesno("Do you wish to logoff")) { + bbs.logoff(false); + } + } else { + console.clear(); + return; + } + } else if (keyin == KEY_UP) { + --selected_index; + if (selected_index < 0) { + selected_index = menuitemsfiltered.length - 1; + } + continue; + } else if (keyin == KEY_DOWN) { + ++selected_index; + if (selected_index > (menuitemsfiltered.length - 1)) { + selected_index = 0; + } + continue; + } else if (keyin == KEY_LEFT) { + var row_limit = n -1; + if (selected_index == 0) { + // first item, go back menu + if (gamesrv && (menuid == 'main')) { + bbs.logoff(); + } else { + console.clear(); + return; + } + } else if (selected_index <= row_limit) { + // on first col + var new_selected_index = selected_index + n; + if (typeof menuitemsfiltered[new_selected_index] === "undefined") { + // no item on right, no change + } else { + selected_index = new_selected_index; + } + } else { + // on second col + selected_index = selected_index - n; + } + } else if (keyin == KEY_RIGHT) { + var row_limit = n - 1; + if (selected_index <= row_limit) { + // on first col + var new_selected_index = selected_index + n; + if (typeof menuitemsfiltered[new_selected_index] === "undefined") { + // no item on right, no change + } else { + selected_index = new_selected_index; + } + } else { + // on second col + selected_index = selected_index - n; + } + } + + // if ENTER on a QUIT item, exit + if (keyin == "\x0d") { + if (menuitemsfiltered[selected_index].type == "quit") { + if (gamesrv && (menuid == 'main')) { + bbs.logoff(); + } else { + console.clear(); + return; + } + } + } + // The logic below is to make it not require enter for as // many items as possible - + // if max keys is 2 and they entered something that might have // a second digit/char, then get the key - if (maxkeylen == 2) { + if ((maxkeylen == 2) && (keyin !== "\x0d")) { if (morekeys.indexOf(keyin) !== -1) { write(keyin); keyin2 = console.getkey(); // either the second digit or enter - if ((keyin2 != "\r") && (keyin2 != "\n") && (keyin2 != "\r\n")) { + if ((keyin2 !== "\r") && (keyin2 !== "\n") && (keyin2 !== "\r\n")) { keyin = keyin + keyin2.toLowerCase(); } } - } else if (maxkeylen > 2) { + } else if ((maxkeylen > 2) && (keyin !== "\x0d")) { // there there are more than 99 items, then just use getkeys // for the rest write(keyin); keyin2 = console.getkeys(validkeys, keymax); keyin = keyin + keyin2.toLowerCase(); } - + if (keyin) { - if (keyin == 'q') { - if (gamesrv && ('menuid' == 'main')) { - bbs.logoff(); - } else { - console.clear(); - return; - } - } - + menuitemsfiltered.some(function (menuitemfiltered) { - var menutarget = menuitemfiltered.target.toLowerCase(); + var menutarget = menuitemfiltered.target ? menuitemfiltered.target.toLowerCase() : null; var menuinput = menuitemfiltered.input.toString().toLowerCase(); - - if (menuinput == keyin) { + + if ((menuinput == keyin) || + ((keyin == "\x0d") && (menuitemsfiltered[selected_index].input == menuitemfiltered.input))) { + // update the selected index so the selected item is correct in the + // menu rendering if they used a key + for (var mm=0; mm<=menuitemsfiltered.length;mm++) { + if (menuitemsfiltered[mm].input == menuitemfiltered.input) { + selected_index = mm; + break; + } + } + switch (menuitemfiltered.type) { // custom menu, defined in xtrnmenu.json case 'custommenu': - external_section_menu_custom(menutarget); + external_menu_custom(menutarget); return true; // external program section case 'xtrnmenu': if (options.use_xtrn_sec) { js.exec("xtrn_sec.js", {}, menutarget); } else { - external_section_menu_custom(menutarget); + external_menu_custom(menutarget); } return true; // external program @@ -350,6 +987,20 @@ function external_section_menu_custom(menuid) case 'command': bbs.exec(menutarget); return true; + case 'recentall': + case 'recentuser': + case 'mostlaunchedall': + case 'mostlauncheduser': + case 'longestrunall': + case 'longestrunuser': + special_menu(menuitemfiltered.type, menuitemfiltered.title, menutarget); + return true; + case 'search': + search_menu(menuitemfiltered.title, menutarget); + return true; + case 'favorites': + favorites_menu(menuitemfiltered.title, menutarget); + return true; } //switch } // if menu item matched keyin }); // foreach menu item @@ -358,6 +1009,1477 @@ function external_section_menu_custom(menuid) } +// Renders the special menu (recent, most launched, etc.) +function special_menu(menutype, title, itemcount) { + if (itemcount === undefined) { + itemcount = 0; + } + + var i, menucheck, item_multicolumn_fmt, item_singlecolumn_fmt, + item_multicolumn_fmt_inverse, item_singlecolumn_fmt_inverse, + multicolumn, selected_index = 0, menuitemsfiltered = []; + var validkeys = []; // valid chars on menu selection + var keymax = 0; // max integer allowed on menu selection + + var menuid = menutype; + + var options = ExternalMenus.getOptions('custommenu', menuid); + + // Allow overriding auto-format on a per-menu basis + var multicolumn_fmt = options.custom.multicolumn_fmt_special !== undefined + ? options.custom.multicolumn_fmt_special : options.multicolumn_fmt; + var singlecolumn_fmt = options.custom.singlecolumn_fmt_special !== undefined + ? options.custom.singlecolumn_fmt_special : options.singlecolumn_fmt; + var multicolumn_fmt_inverse = options.custom.multicolumn_fmt_special_inverse !== undefined + ? options.custom.multicolumn_fmt_special_inverse : options.multicolumn_fmt_inverse; + var singlecolumn_fmt_inverse = options.custom.singlecolumn_fmt_special_inverse !== undefined + ? options.custom.singlecolumn_fmt_special_inverse : options.singlecolumn_fmt_inverse; + + var return_singlecolumn_special_fmt = options.custom.return_singlecolumn_special_fmt !== undefined + ? options.custom.return_singlecolumn_special_fmt : singlecolumn_fmt; + var return_multicolumn_special_fmt = options.custom.return_multicolumn_special_fmt !== undefined + ? options.custom.return_multicolumn_special_fmt : multicolumn_fmt; + var return_singlecolumn_special_fmt_inverse = options.custom.return_singlecolumn_special_fmt_inverse !== undefined + ? options.custom.return_singlecolumn_special_fmt_inverse : singlecolumn_fmt_inverse; + var return_multicolumn_special_fmt_inverse = options.custom.return_multicolumn_special_fmt_inverse !== undefined + ? options.custom.return_multicolumn_special_fmt_inverse : multicolumn_fmt_inverse; + + while (bbs.online) { + console.aborted = false; + + if (options.clear_screen) { + console.clear(LIGHTGRAY); + } + + if (user.security.restrictions&UFLAG_X) { + write(options.restricted_user_msg); + break; + } + + var menuobj = ExternalMenus.getSpecial(menutype, title, itemcount); + if (!bbs.menu("xtrnmenu_head_" + menuid, P_NOERROR) && !bbs.menu("xtrnmenu_head", P_NOERROR)) { + bbs.menu("xtrn_head", P_NOERROR); + } + + if ((menuobj === undefined) || (!menuobj.items.length)) { + write(options.no_programs_msg); + break; + } + + menuitemsfiltered = menuobj.items; + + // The quit item is intended to aid in the lightbar navigation + menuitemsfiltered.unshift({ + input: 'Q', + title: options.custom.return_msg !== undefined ? options.custom.return_msg : 'Return to Previous Menu', + target: '', + type: 'quit', + }); + + // determine lines left. we can't know the size of the footer so + // let the sysop use singlecolumn_margin to specify that. Below + // calcution will always leave room for titles and underline even + // if they aren't rendered + var linesleft = console.screen_rows - console.line_counter - options.singlecolumn_margin + - 2 - 2; // -2 for header_fmt/crlf and -2 for crlf and footer + if(options.titles.trimRight() != '') linesleft = linesleft - 1; + if(options.underline.trimRight() != '') linesleft = linesleft - 2; + + multicolumn = menuitemsfiltered.length > linesleft; + + // if no custom menu file in text/menu, create a dynamic one + printf(options.header_fmt, title); + if(options.titles.trimRight() != '') + write(options.titles); + if(multicolumn) { + write(options.multicolumn_separator); + if (options.titles.trimRight() != '') + write(options.titles); + } + if(options.underline.trimRight() != '') { + console.crlf(); + write(options.underline); + } + if(multicolumn) { + write(options.multicolumn_separator); + if (options.underline.trimRight() != '') + write(options.underline); + } + console.crlf(); + + // n is the number of items for the 1st column + var n; + if (multicolumn) { + n = Math.floor(menuitemsfiltered.length / 2) + (menuitemsfiltered.length & 1); + } else { + n = menuitemsfiltered.length; + } + + // j is the index for each menu item on 2nd column + var j = n; // start j at the first item for 2nd column + for (i = 0; i < n && !console.aborted; i++) { + console.add_hotspot(menuitemsfiltered[i].input.toString()); + + validkeys.push(menuitemsfiltered[i].input.toString()); + + if (menuitemsfiltered[i].input > keymax) { + keymax = menuitemsfiltered[i].input; + } + + if (i == selected_index) { + if (menuitemsfiltered[i].type == 'quit') { + printf(multicolumn ? return_multicolumn_special_fmt_inverse : return_singlecolumn_special_fmt_inverse, + menuitemsfiltered[i].input, + menuitemsfiltered[i].title, + '' + ); + } else { + printf(multicolumn ? multicolumn_fmt_inverse : singlecolumn_fmt_inverse, + menuitemsfiltered[i].input, + menuitemsfiltered[i].title, + menuitemsfiltered[i].stats + ); + } + } else { + if (menuitemsfiltered[i].type == 'quit') { + printf(multicolumn ? return_multicolumn_special_fmt : return_singlecolumn_special_fmt, + menuitemsfiltered[i].input, + menuitemsfiltered[i].title, + '' + ); + } else { + printf(multicolumn ? multicolumn_fmt : singlecolumn_fmt, + menuitemsfiltered[i].input, + menuitemsfiltered[i].title, + menuitemsfiltered[i].stats + ); + } + } + + if (multicolumn) { + if (typeof menuitemsfiltered[j] !== "undefined") { + validkeys.push(menuitemsfiltered[j].input.toString()); + + if (menuitemsfiltered[j].input > keymax) { + keymax = menuitemsfiltered[i].input; + } + + write(options.multicolumn_separator); + console.add_hotspot(menuitemsfiltered[j].input.toString()); + + if (selected_index == j) { + if (menuitemsfiltered[j].type == 'quit') { + printf(multicolumn ? return_multicolumn_special_fmt_inverse : return_singlecolumn_special_fmt_inverse, + menuitemsfiltered[j].input, + menuitemsfiltered[j].title, + '' + ); + } else { + printf(multicolumn ? multicolumn_fmt_inverse : singlecolumn_fmt_inverse, + menuitemsfiltered[j].input, + menuitemsfiltered[j].title, + menuitemsfiltered[j].stats + ); + } + } else { + if (menuitemsfiltered[j].type == 'quit') { + printf(multicolumn ? return_multicolumn_special_fmt : return_singlecolumn_special_fmt, + menuitemsfiltered[j].input, + menuitemsfiltered[j].title, + '' + ); + } else { + printf(multicolumn ? multicolumn_fmt : singlecolumn_fmt, + menuitemsfiltered[j].input, + menuitemsfiltered[j].title, + menuitemsfiltered[j].stats + ); + } + } + } else { + write(options.multicolumn_separator); + } + j++; + } + console.crlf(); + } + + if (gamesrv) { + if (!bbs.menu("xtrn_gamesrv_tail_" + menuid, P_NOERROR)) { + bbs.menu("xtrn_gamesrv_tail", P_NOERROR); + } + } + + if (!bbs.menu("xtrnmenu_tail_" + menuid, P_NOERROR) && !bbs.menu("xtrnmenu_tail", P_NOERROR)) { + bbs.menu("xtrn_tail", P_NOERROR); + } + + bbs.node_sync(); + console.mnemonics(options.which); + + validkeys.push('q'); + validkeys.push("\x1B"); + validkeys.push(KEY_LEFT); + validkeys.push(KEY_RIGHT); + validkeys.push(KEY_UP); + validkeys.push(KEY_DOWN); + validkeys.push("\x0d"); //enter + + var keyin, keyin2; + var maxkeylen = 0; + var maxfirstkey = 0; + var morekeys = []; + var k; + for (k in validkeys) { + if (validkeys[k] == "\x1B") { + continue; + } + if (validkeys[k].length > maxkeylen) { + maxkeylen = validkeys[k].length; + } + if (validkeys[k].length > 1) { + morekeys.push(validkeys[k].toString().toLowerCase().substring(0,1)); + } + } + + // get first key + keyin = console.getkey(); + keyin = keyin.toString().toLowerCase(); + + // if ENTER on a QUIT item, exit + if (keyin == "\x0d") { + if (menuitemsfiltered[selected_index].type == "quit") { + console.clear(); + return; + } + } + + if ((keyin == 'q') || (keyin == "\x1B")) { + console.clear(); + return; + } else if (keyin == KEY_UP) { + --selected_index; + if (selected_index < 0) { + selected_index = menuitemsfiltered.length - 1; + } + continue; + } else if (keyin == KEY_DOWN) { + ++selected_index; + if (selected_index > (menuitemsfiltered.length - 1)) { + selected_index = 0; + } + continue; + } else if (keyin == KEY_LEFT) { + var row_limit = n -1; + if (selected_index == 0) { + // first item, go back menu + console.clear(); + return; + } else if (selected_index <= row_limit) { + // on first col + var new_selected_index = selected_index + n; + if (typeof menuitemsfiltered[new_selected_index] === "undefined") { + // no item on right, no change + } else { + selected_index = new_selected_index; + } + } else { + // on second col + selected_index = selected_index - n; + } + } else if (keyin == KEY_RIGHT) { + var row_limit = n - 1; + if (selected_index <= row_limit) { + // on first col + var new_selected_index = selected_index + n; + if (typeof menuitemsfiltered[new_selected_index] === "undefined") { + // no item on right, no change + } else { + selected_index = new_selected_index; + } + } else { + // on second col + selected_index = selected_index - n; + } + } + + // The logic below is to make it not require enter for as + // many items as possible + + // if max keys is 2 and they entered something that might have + // a second digit/char, then get the key + if ((maxkeylen == 2) && (keyin !== "\x0d")) { + if (morekeys.indexOf(keyin) !== -1) { + write(keyin); + keyin2 = console.getkey(); // either the second digit or enter + if ((keyin2 !== "\r") && (keyin2 !== "\n") && (keyin2 !== "\r\n")) { + keyin = keyin + keyin2.toLowerCase(); + } + } + } else if ((maxkeylen > 2) && (keyin !== "\x0d")) { + // there there are more than 99 items, then just use getkeys + // for the rest + write(keyin); + keyin2 = console.getkeys(validkeys, keymax); + keyin = keyin + keyin2.toLowerCase(); + } + + if (keyin) { + menuitemsfiltered.some(function (menuitemfiltered) { + var menutarget = menuitemfiltered.target.toLowerCase(); + var menuinput = menuitemfiltered.input.toString().toLowerCase(); + + if ((menuinput == keyin) || + ((keyin == "\x0d") && (menuitemsfiltered[selected_index].input == menuitemfiltered.input))) { + // everything in this menu is an xtrnprog + + // reset selected index, because the menus are dynamic and + // the order may change between executions + selected_index = 0; + + // run the external program + if (typeof xtrn_area.prog[menutarget] !== "undefined") { + bbs.exec_xtrn(menutarget); + return true; + } else { + doerror(options.custom_menu_program_not_found_msg.replace('%PROGRAMID%', menutarget)); + } + } // if menu item matched keyin + }); // foreach menu item + } // if keyin + } // main bbs.online loop +} + + +// Renders the search menu +function search_menu(title, itemcount) { + if (itemcount === undefined) { + itemcount = 0; + } + + var i, menucheck, item_multicolumn_fmt, item_singlecolumn_fmt, + item_multicolumn_fmt_inverse, item_singlecolumn_fmt_inverse, + multicolumn, selected_index = 0, menuitemsfiltered = []; + var validkeys = []; // valid chars on menu selection + var keymax = 0; // max integer allowed on menu selection + + var menuid = 'search'; + + var options = ExternalMenus.getOptions('custommenu', menuid); + + var return_multicolumn_fmt = options.custom.return_multicolumn_fmt !== undefined + ? options.custom.return_multicolumn_fmt : multicolumn_fmt; + var return_singlecolumn_fmt = options.custom.return_singlecolumn_fmt !== undefined + ? options.custom.return_singlecolumn_fmt : singlecolumn_fmt; + var return_singlecolumn_fmt_inverse = options.custom.return_singlecolumn_fmt_inverse !== undefined + ? options.custom.return_singlecolumn_fmt_inverse : singlecolumn_fmt_inverse; + var return_multicolumn_fmt_inverse = options.custom.return_multicolumn_fmt_inverse !== undefined + ? options.custom.return_multicolumn_fmt_inverse : multicolumn_fmt_inverse; + + while (bbs.online) { + console.aborted = false; + + if (options.clear_screen) { + console.clear(LIGHTGRAY); + } + + if (user.security.restrictions&UFLAG_X) { + write(options.restricted_user_msg); + break; + } + + if (!bbs.menu("xtrnmenu_head_" + menuid, P_NOERROR) && !bbs.menu("xtrnmenu_head", P_NOERROR)) { + bbs.menu("xtrn_head", P_NOERROR); + } + + if (searchterm) { + printf(typeof options.custom.searchresultsheader !== "undefined" + ? options.custom.searchresultsheader : "\x01n\x01cSearch Results for \x01h%s", searchterm); + } else { + write(typeof options.custom.entersearchterm !== "undefined" ? + options.custom.entersearchterm : "\x01y\x01hEnter search term: "); + var searchterm = console.getstr(searchterm, 40, + K_LINE | K_EDIT | K_NOEXASC | K_TRIM); + if (!searchterm) { + return; + } else { + console.crlf(); + printf(typeof options.custom.searchresultsheader !== "undefined" + ? options.custom.searchresultsheader : "\x01n\x01cSearch Results for \x01h%s", searchterm); + } + } + + searchterm = searchterm.toLowerCase(); + menuitemsfiltered = []; + + var input = 1; + + for (i in menuconfig.menus) { + if ((itemcount > 0) && (menuitemsfiltered.length >= itemcount)) { + break + } + if (menuconfig.menus[i].title.toLowerCase().indexOf(searchterm) !== -1) { + if (user.compare_ars(menuconfig.menus[i].title.access_string)) { + menuitemsfiltered.push({ + input: input++, + id: menuconfig.menus[i].id, + title: menuconfig.menus[i].title, + target: menuconfig.menus[i].id, + type: 'custommenu' + }); + } + } + } + + for (i in xtrn_area.sec_list) { + if ((itemcount > 0) && (menuitemsfiltered.length >= itemcount)) { + break + } + if (xtrn_area.sec_list[i].name.toLowerCase().indexOf(searchterm) !== -1) { + if (xtrn_area.sec_list[i].can_access) { + menuitemsfiltered.push({ + input: input++, + id: xtrn_area.sec_list[i].code, + title: xtrn_area.sec_list[i].name, + target: xtrn_area.sec_list[i].code, + type: 'xtrnmenu' + }); + } + } + } + + for (i in xtrn_area.sec_list) { + for (var k in xtrn_area.sec_list[i].prog_list) { + if ((itemcount > 0) && (menuitemsfiltered.length >= itemcount)) { + break + } + if (xtrn_area.sec_list[i].prog_list[k].name.toLowerCase().indexOf(searchterm) !== -1) { + if (xtrn_area.sec_list[i].prog_list[k].can_access) { + menuitemsfiltered.push({ + input: input++, + id: xtrn_area.sec_list[i].prog_list[k].code, + title: xtrn_area.sec_list[i].prog_list[k].name, + target: xtrn_area.sec_list[i].prog_list[k].code, + type: 'xtrnprog' + }); + } + } + } + } + + if (!menuitemsfiltered.length) { + write(options.no_programs_msg); + break; + } else { + console.crlf(); + } + + // The quit item is intended to aid in the lightbar navigation + menuitemsfiltered.unshift({ + input: 'Q', + title: options.custom.return_msg !== undefined ? options.custom.return_msg : 'Return to Previous Menu', + target: '', + type: 'quit', + }); + + // determine lines left. we can't know the size of the footer so + // let the sysop use singlecolumn_margin to specify that. Below + // calcution will always leave room for titles and underline even + // if they aren't rendered + var linesleft = console.screen_rows - console.line_counter - options.singlecolumn_margin + - 2 - 2; // -2 for header_fmt/crlf and -2 for crlf and footer + if(options.titles.trimRight() != '') linesleft = linesleft - 1; + if(options.underline.trimRight() != '') linesleft = linesleft - 2; + + multicolumn = menuitemsfiltered.length > linesleft; + + if(options.titles.trimRight() != '') + write(options.titles); + if(multicolumn) { + write(options.multicolumn_separator); + if (options.titles.trimRight() != '') + write(options.titles); + } + if(options.underline.trimRight() != '') { + console.crlf(); + write(options.underline); + } + if(multicolumn) { + write(options.multicolumn_separator); + if (options.underline.trimRight() != '') + write(options.underline); + } + console.crlf(); + + // n is the number of items for the 1st column + var n; + if (multicolumn) { + n = Math.floor(menuitemsfiltered.length / 2) + (menuitemsfiltered.length & 1); + } else { + n = menuitemsfiltered.length; + } + + // j is the index for each menu item on 2nd column + var j = n; // start j at the first item for 2nd column + for (i = 0; i < n && !console.aborted; i++) { + console.add_hotspot(menuitemsfiltered[i].input.toString()); + + validkeys.push(menuitemsfiltered[i].input.toString()); + + if (menuitemsfiltered[i].input > keymax) { + keymax = menuitemsfiltered[i].input; + } + + if (i == selected_index) { + if (menuitemsfiltered[i].type == 'quit') { + printf(multicolumn ? return_multicolumn_fmt_inverse : return_singlecolumn_fmt_inverse, + menuitemsfiltered[i].input.toString().toUpperCase(), + menuitemsfiltered[i].title, + '' + ); + } else { + printf(multicolumn ? options.multicolumn_fmt_inverse : options.singlecolumn_fmt_inverse, + menuitemsfiltered[i].input, + menuitemsfiltered[i].title + ); + } + } else { + if (menuitemsfiltered[i].type == 'quit') { + printf(multicolumn ? return_multicolumn_fmt : return_singlecolumn_fmt, + menuitemsfiltered[i].input.toString().toUpperCase(), + menuitemsfiltered[i].title, + '' + ); + } else { + printf(multicolumn ? options.multicolumn_fmt : options.singlecolumn_fmt, + menuitemsfiltered[i].input, + menuitemsfiltered[i].title + ); + } + } + + if (multicolumn) { + if (typeof menuitemsfiltered[j] !== "undefined") { + validkeys.push(menuitemsfiltered[j].input.toString()); + if (menuitemsfiltered[j].input > keymax) { + keymax = menuitemsfiltered[j].input; + } + + write(options.multicolumn_separator); + console.add_hotspot(menuitemsfiltered[j].input.toString()); + + if (j == selected_index) { + if (menuitemsfiltered[j].type == 'quit') { + printf(multicolumn ? return_multicolumn_fmt_inverse : return_singlecolumn_fmt_inverse, + menuitemsfiltered[j].input.toString().toUpperCase(), + menuitemsfiltered[j].title, + '' + ); + } else { + printf(multicolumn ? options.multicolumn_fmt_inverse : options.singlecolumn_fmt_inverse, + menuitemsfiltered[j].input, + menuitemsfiltered[j].title + ); + } + } else { + if (menuitemsfiltered[j].type == 'quit') { + printf(multicolumn ? return_multicolumn_fmt : return_singlecolumn_fmt, + menuitemsfiltered[j].input.toString().toUpperCase(), + menuitemsfiltered[j].title, + '' + ); + } else { + printf(multicolumn ? options.multicolumn_fmt : options.singlecolumn_fmt, + menuitemsfiltered[j].input, + menuitemsfiltered[j].title + ); + } + } + } else { + write(options.multicolumn_separator); + } + j++; + } + console.crlf(); + } + + if (gamesrv) { + if (!bbs.menu("xtrn_gamesrv_tail_" + menuid, P_NOERROR)) { + bbs.menu("xtrn_gamesrv_tail", P_NOERROR); + } + } + + if (!bbs.menu("xtrnmenu_tail_" + menuid, P_NOERROR) && !bbs.menu("xtrnmenu_tail", P_NOERROR)) { + bbs.menu("xtrn_tail", P_NOERROR); + } + + console.crlf(); + writeln(options.custom.searchagainmsg !== undefined ? options.custom.searchagainmsg : + "\x01n\x01mPress S to Search Again.") + + bbs.node_sync(); + console.mnemonics(options.which); + + validkeys.push('q'); + validkeys.push('s'); + validkeys.push("\x1B"); + validkeys.push(KEY_LEFT); + validkeys.push(KEY_RIGHT); + validkeys.push(KEY_UP); + validkeys.push(KEY_DOWN); + validkeys.push("\x0d"); //enter + + var keyin, keyin2; + var maxkeylen = 0; + var maxfirstkey = 0; + var morekeys = []; + var k; + for (k in validkeys) { + if (validkeys[k] == "\x1B") { + continue; + } + if (validkeys[k].length > maxkeylen) { + maxkeylen = validkeys[k].length; + } + if (validkeys[k].length > 1) { + morekeys.push(validkeys[k].toString().toLowerCase().substring(0,1)); + } + } + + // get first key + keyin = console.getkey(); + keyin = keyin.toString().toLowerCase(); + + // The logic below is to make it not require enter for as + // many items as possible + + // if ENTER on a QUIT item, exit + if (keyin == "\x0d") { + if (menuitemsfiltered[selected_index].type == "quit") { + if (gamesrv && (menuid == 'main')) { + bbs.logoff(); + } else { + console.clear(); + return; + } + } + } + + if ((keyin == 'q') || (keyin == "\x1B")) { + console.clear(); + return; + } else if (keyin == KEY_UP) { + --selected_index; + if (selected_index < 0) { + selected_index = menuitemsfiltered.length - 1; + } + continue; + } else if (keyin == KEY_DOWN) { + ++selected_index; + if (selected_index > (menuitemsfiltered.length - 1)) { + selected_index = 0; + } + continue; + } else if (keyin == KEY_LEFT) { + var row_limit = n -1; + if (selected_index == 0) { + // first item, go back menu + if (gamesrv && (menuid == 'main')) { + bbs.logoff(); + } else { + console.clear(); + return; + } + } else if (selected_index <= row_limit) { + // on first col + var new_selected_index = selected_index + n; + if (typeof menuitemsfiltered[new_selected_index] === "undefined") { + // no item on right, no change + } else { + selected_index = new_selected_index; + } + } else { + // on second col + selected_index = selected_index - n; + } + } else if (keyin == KEY_RIGHT) { + var row_limit = n - 1; + if (selected_index <= row_limit) { + // on first col + var new_selected_index = selected_index + n; + if (typeof menuitemsfiltered[new_selected_index] === "undefined") { + // no item on right, no change + } else { + selected_index = new_selected_index; + } + } else { + // on second col + selected_index = selected_index - n; + } + } + + // if max keys is 2 and they entered something that might have + // a second digit/char, then get the key + if ((maxkeylen == 2) && (keyin !== "\x0d")) { + if (morekeys.indexOf(keyin) !== -1) { + write(keyin); + keyin2 = console.getkey(); // either the second digit or enter + if ((keyin2 !== "\r") && (keyin2 !== "\n") && (keyin2 !== "\r\n")) { + keyin = keyin + keyin2.toLowerCase(); + } + } + } else if ((maxkeylen > 2) && (keyin !== "\x0d")) { + // there there are more than 99 items, then just use getkeys + // for the rest + write(keyin); + keyin2 = console.getkeys(validkeys, keymax); + keyin = keyin + keyin2.toLowerCase(); + } + + if (keyin) { + if (keyin == 's') { + console.crlf(); + console.crlf(); + write(typeof options.custom.entersearchterm !== "undefined" ? + options.custom.entersearchterm : "Enter search term: "); + var searchterm = console.getstr(searchterm, 40, + K_LINE | K_EDIT | K_NOEXASC | K_TRIM); + } else { + menuitemsfiltered.some(function (menuitemfiltered) { + var menutarget = menuitemfiltered.target.toLowerCase(); + var menuinput = menuitemfiltered.input.toString().toLowerCase(); + + if ((menuinput == keyin) || + ((keyin == "\x0d") && (menuitemsfiltered[selected_index].input == menuitemfiltered.input))) { + switch (menuitemfiltered.type) { + // custom menu, defined in xtrnmenu.json + case 'custommenu': + external_menu_custom(menutarget); + return true; + // external program section + case 'xtrnmenu': + if (options.use_xtrn_sec) { + js.exec("xtrn_sec.js", {}, menutarget); + } else { + external_menu_custom(menutarget); + } + return true; + // external program + case 'xtrnprog': + // run the external program + if (typeof xtrn_area.prog[menutarget] !== "undefined") { + bbs.exec_xtrn(menutarget); + return true; + } else { + doerror(options.custom_menu_program_not_found_msg.replace('%PROGRAMID%', menutarget)); + } + break; + } + } // if menu item matched keyin + }); // foreach menu item + } + } // if keyin + } // main bbs.online loop +} + +// Return the favorites menu +function favorites_menu(title, itemcount) { + if (itemcount === undefined) { + itemcount = 0; + } + + var i, menucheck, item_multicolumn_fmt, item_singlecolumn_fmt, + item_multicolumn_fmt_inverse, item_singlecolumn_fmt_inverse, + multicolumn, selected_index = 0, menuitemsfiltered = []; + var validkeys = []; // valid chars on menu selection + var keymax = 0; // max integer allowed on menu selection + + var menuid = 'favorites'; + + var options = ExternalMenus.getOptions('custommenu', menuid); + + // Allow overriding auto-format on a per-menu basis + var multicolumn_fmt = options.multicolumn_fmt; + var singlecolumn_fmt = options.singlecolumn_fmt; + var multicolumn_fmt_inverse = options.multicolumn_fmt_inverse; + var singlecolumn_fmt_inverse = options.singlecolumn_fmt_inverse; + + var return_singlecolumn_fmt = options.custom.return_singlecolumn_fmt !== undefined + ? options.custom.return_singlecolumn_fmt : singlecolumn_fmt; + var return_multicolumn_fmt = options.custom.return_multicolumn_fmt !== undefined + ? options.custom.return_multicolumn_fmt : multicolumn_fmt; + var return_singlecolumn_fmt_inverse = options.custom.return_singlecolumn_fmt_inverse !== undefined + ? options.custom.return_singlecolumn_fmt_inverse : singlecolumn_fmt_inverse; + var return_multicolumn_fmt_inverse = options.custom.return_multicolumn_fmt_inverse !== undefined + ? options.custom.return_multicolumn_fmt_inverse : multicolumn_fmt_inverse; + + while (bbs.online) { + console.aborted = false; + + if (options.clear_screen) { + console.clear(LIGHTGRAY); + } + + if (user.security.restrictions&UFLAG_X) { + write(options.restricted_user_msg); + break; + } + + var menuobj = ExternalMenus.getFavorites(title, itemcount); + if (!bbs.menu("xtrnmenu_head_" + menuid, P_NOERROR) && !bbs.menu("xtrnmenu_head", P_NOERROR)) { + bbs.menu("xtrn_head", P_NOERROR); + } + + menuitemsfiltered = typeof menuobj.items !== "undefined" ? menuobj.items : {}; + + // The quit item is intended to aid in the lightbar navigation + menuitemsfiltered.unshift({ + input: 'Q', + title: options.custom.return_msg !== undefined ? options.custom.return_msg : 'Return to Previous Menu', + target: '', + type: 'quit' + }); + + menuitemsfiltered.push({ + input: '+', + title: options.custom.favorite_add_item !== undefined ? options.custom.favorite_add_item : 'Add Item', + target: '', + type: 'add' + }); + + menuitemsfiltered.push({ + input: '-', + title: options.custom.favorite_remove_item !== undefined ? options.custom.favorite_remove_item : 'Remove Item', + target: '', + type: 'remove' + }); + + // determine lines left. we can't know the size of the footer so + // let the sysop use singlecolumn_margin to specify that. Below + // calcution will always leave room for titles and underline even + // if they aren't rendered + var linesleft = console.screen_rows - console.line_counter - options.singlecolumn_margin + - 2 - 2; // -2 for header_fmt/crlf and -2 for crlf and footer + if(options.titles.trimRight() != '') linesleft = linesleft - 1; + if(options.underline.trimRight() != '') linesleft = linesleft - 2; + + multicolumn = menuitemsfiltered.length > linesleft; + + printf(options.header_fmt, title); + if(options.titles.trimRight() != '') + write(options.titles); + if(multicolumn) { + write(options.multicolumn_separator); + if (options.titles.trimRight() != '') + write(options.titles); + } + if(options.underline.trimRight() != '') { + console.crlf(); + write(options.underline); + } + if(multicolumn) { + write(options.multicolumn_separator); + if (options.underline.trimRight() != '') + write(options.underline); + } + console.crlf(); + + // n is the number of items for the 1st column + var n; + if (multicolumn) { + n = Math.floor(menuitemsfiltered.length / 2) + (menuitemsfiltered.length & 1); + } else { + n = menuitemsfiltered.length; + } + + // j is the index for each menu item on 2nd column + var j = n; // start j at the first item for 2nd column + for (i = 0; i < n && !console.aborted; i++) { + console.add_hotspot(menuitemsfiltered[i].input.toString()); + + validkeys.push(menuitemsfiltered[i].input.toString()); + + if (menuitemsfiltered[i].input > keymax) { + keymax = menuitemsfiltered[i].input; + } + + if (i == selected_index) { + if (menuitemsfiltered[i].type == 'quit') { + printf(multicolumn ? return_multicolumn_fmt_inverse : return_singlecolumn_fmt_inverse, + menuitemsfiltered[i].input, + menuitemsfiltered[i].title + ); + } else { + printf(multicolumn ? multicolumn_fmt_inverse : singlecolumn_fmt_inverse, + menuitemsfiltered[i].input, + menuitemsfiltered[i].title + ); + } + } else { + if (menuitemsfiltered[i].type == 'quit') { + printf(multicolumn ? return_multicolumn_fmt : return_singlecolumn_fmt, + menuitemsfiltered[i].input, + menuitemsfiltered[i].title + ); + } else { + printf(multicolumn ? multicolumn_fmt : singlecolumn_fmt, + menuitemsfiltered[i].input, + menuitemsfiltered[i].title + ); + } + } + + if (multicolumn) { + if (typeof menuitemsfiltered[j] !== "undefined") { + validkeys.push(menuitemsfiltered[j].input.toString()); + + if (menuitemsfiltered[j].input > keymax) { + keymax = menuitemsfiltered[i].input; + } + + write(options.multicolumn_separator); + console.add_hotspot(menuitemsfiltered[j].input.toString()); + + if (selected_index == j) { + if (menuitemsfiltered[j].type == 'quit') { + printf(multicolumn ? return_multicolumn_fmt_inverse : return_singlecolumn_fmt_inverse, + menuitemsfiltered[j].input, + menuitemsfiltered[j].title + ); + } else { + printf(multicolumn ? multicolumn_fmt_inverse : singlecolumn_fmt_inverse, + menuitemsfiltered[j].input, + menuitemsfiltered[j].title + ); + } + } else { + if (menuitemsfiltered[j].type == 'quit') { + printf(multicolumn ? return_multicolumn_fmt : return_singlecolumn_fmt, + menuitemsfiltered[j].input, + menuitemsfiltered[j].title + ); + } else { + printf(multicolumn ? multicolumn_fmt : singlecolumn_fmt, + menuitemsfiltered[j].input, + menuitemsfiltered[j].title + ); + } + } + } else { + write(options.multicolumn_separator); + } + j++; + } + console.crlf(); + } + + if (gamesrv) { + if (!bbs.menu("xtrn_gamesrv_tail_" + menuid, P_NOERROR)) { + bbs.menu("xtrn_gamesrv_tail", P_NOERROR); + } + } + + if (!bbs.menu("xtrnmenu_tail_" + menuid, P_NOERROR) && !bbs.menu("xtrnmenu_tail", P_NOERROR)) { + bbs.menu("xtrn_tail", P_NOERROR); + } + + bbs.node_sync(); + console.mnemonics(options.which); + + validkeys.push('q'); + validkeys.push("\x1B"); + validkeys.push(KEY_LEFT); + validkeys.push(KEY_RIGHT); + validkeys.push(KEY_UP); + validkeys.push(KEY_DOWN); + validkeys.push("\x0d"); //enter + + var keyin, keyin2; + var maxkeylen = 0; + var maxfirstkey = 0; + var morekeys = []; + var k; + for (k in validkeys) { + if (validkeys[k] == "\x1B") { + continue; + } + if (validkeys[k].length > maxkeylen) { + maxkeylen = validkeys[k].length; + } + if (validkeys[k].length > 1) { + morekeys.push(validkeys[k].toString().toLowerCase().substring(0,1)); + } + } + + // get first key + keyin = console.getkey(); + keyin = keyin.toString().toLowerCase(); + + // if ENTER + if (keyin == "\x0d") { + if (menuitemsfiltered[selected_index].type == "quit") { + console.clear(); + return; + } else if (menuitemsfiltered[selected_index].type == "add") { + add_favorite(); + } else if (menuitemsfiltered[selected_index].type == "remove") { + remove_favorite(); + } + } else if ((keyin == 'q') || (keyin == "\x1B")) { + console.clear(); + return; + } else if (keyin == KEY_UP) { + --selected_index; + if (selected_index < 0) { + selected_index = menuitemsfiltered.length - 1; + } + continue; + } else if (keyin == KEY_DOWN) { + ++selected_index; + if (selected_index > (menuitemsfiltered.length - 1)) { + selected_index = 0; + } + continue; + } else if (keyin == KEY_LEFT) { + var row_limit = n -1; + if (selected_index == 0) { + console.clear(); + return; + } else if (selected_index <= row_limit) { + // on first col + var new_selected_index = selected_index + n; + if (typeof menuitemsfiltered[new_selected_index] === "undefined") { + // no item on right, no change + } else { + selected_index = new_selected_index; + } + } else { + // on second col + selected_index = selected_index - n; + } + } else if (keyin == KEY_RIGHT) { + var row_limit = n - 1; + if (selected_index <= row_limit) { + // on first col + var new_selected_index = selected_index + n; + if (typeof menuitemsfiltered[new_selected_index] === "undefined") { + // no item on right, no change + } else { + selected_index = new_selected_index; + } + } else { + // on second col + selected_index = selected_index - n; + } + } else if ((maxkeylen == 2) && (keyin !== "\x0d")) { + // The logic below is to make it not require enter for as + // many items as possible + + // if max keys is 2 and they entered something that might have + // a second digit/char, then get the key + if (morekeys.indexOf(keyin) !== -1) { + write(keyin); + keyin2 = console.getkey(); // either the second digit or enter + if ((keyin2 !== "\r") && (keyin2 !== "\n") && (keyin2 !== "\r\n")) { + keyin = keyin + keyin2.toLowerCase(); + } + } + } else if ((maxkeylen > 2) && (keyin !== "\x0d")) { + // there there are more than 99 items, then just use getkeys + // for the rest + write(keyin); + keyin2 = console.getkeys(validkeys, keymax); + keyin = keyin + keyin2.toLowerCase(); + } + + if (keyin) { + if ((keyin == '+') || (keyin == '=')) { + add_favorite(); + } else if ((keyin == "\x08") || (keyin == "\x7f")) { + // delete + if (menuitemsfiltered[selected_index].type == 'xtrnprog') { + console.crlf(); + if (!console.noyes("Do you wish to remove " + + menuitemsfiltered[selected_index].title)) { + remove_favorite(menuitemsfiltered[selected_index].target.toLowerCase()); + } + } + + } else if ((keyin == '-') || (keyin == '_')) { + remove_favorite(); + } else { + for (var d in menuitemsfiltered) { + var menutarget = menuitemsfiltered[d].target.toLowerCase(); + var menuinput = menuitemsfiltered[d].input.toString().toLowerCase(); + if ((menuinput == keyin) || + ((keyin == "\x0d") && (menuitemsfiltered[selected_index].input == menuitemsfiltered[d].input))) { + // everything in this menu is an xtrnprog + + // reset selected index, because the menus are dynamic and + // the order may change between executions + selected_index = 0; + + // run the external program + if (typeof xtrn_area.prog[menutarget] !== "undefined") { + bbs.exec_xtrn(menutarget); + } else { + doerror(options.custom_menu_program_not_found_msg.replace('%PROGRAMID%', menutarget)); + } + } // if menu item matched keyin + } + } + } // if keyin + } // main bbs.online loop +} + +// Add an entry to the favorites menu +function add_favorite() +{ + require("frame.js", "Frame"); + require("tree.js", "Tree"); + require("scrollbar.js", "ScrollBar"); + require('typeahead.js', 'Typeahead'); + load(js.global, 'cga_defs.js'); + + var options = ExternalMenus.getOptions('custommenu', 'favorites'); + + if (!options.json_enabled) { + log(LOG_DEBUG, "xtrnmenulib: Skipping favorites because JSON is not enabled"); + return false; + } + + try { + require("json-client.js", "JSONClient"); + var jsonClient = new JSONClient(options.json_host, options.json_port); + jsonClient.callback = ExternalMenus.processUpdate; + } catch (e) { + log(LOG_ERR, "xtrnmenu add_favorites: Could not initialize JSON database so favorites is now disabled: " + e); + return false; + } + + var sortedItems = []; + var jsonData = jsonClient.read("xtrnmenu", "favorites_" + user.alias, 1); + + if (!jsonData) { + jsonData = []; + } + + var frame = new Frame(1, 1, console.screen_columns, console.screen_rows, WHITE); + var treeframe = new Frame(frame.x, frame.y+4, frame.width, frame.height-5, WHITE); + var tree = new Tree(treeframe); + var s = new ScrollBar(tree); + frame.open(); + treeframe.open(); + + frame.putmsg(options.custom.add_favorites_msg !== undefined + ? options.custom.add_favorites_msg : "\x01c\x01hAdd Favorite"); + frame.gotoxy(1, 3); + frame.putmsg(options.custom.favorites_inst !== undefined + ? options.custom.favorites_inst : '\x01n\x01w\x01h\x012 [Up/Down/Home/End] to Navigate, [Enter] to Select, [Q] to Quit, [S] to Search '); + + var xtrnwidth = 0; + var sortedItems = []; + for (var m in xtrn_area.prog) { + if (xtrn_area.prog[m].can_access) { + var found = false; + jsonData.some(function (jsonxtrn) { + if (jsonxtrn == xtrn_area.prog[m].code) { + found = true; + return true; + } + }); + if (!found) { + sortedItems.push({ code: xtrn_area.prog[m].code, name: xtrn_area.prog[m].name }) + if (xtrn_area.prog[m].name.length > xtrnwidth) { + xtrnwidth = xtrn_area.prog[m].name.length; + } + } + } + } + + sortedItems.sort(function(a, b) { + if(a.name.toLowerCase()>b.name.toLowerCase()) return 1; + if(a.name.toLowerCase()<b.name.toLowerCase()) return -1; + return 0; + }); + + for (m in sortedItems) { + const item = tree.addItem(sortedItems[m].name, sortedItems[m].code); + item.code = sortedItems[m].code; + } + + treeframe.width = xtrnwidth + 2; + + // lightbar non-current item + tree.colors.fg = options.custom.favorite_add_fg + ? js.global[options.custom.favorite_add_fg] : LIGHTGRAY; + tree.colors.bg = options.custom.favorite_add_bg + ? js.global[options.custom.favorite_add_bg] : BG_BLACK; + // lightbar current item + tree.colors.lfg = options.custom.favorite_add_lfg + ? js.global[options.custom.favorite_add_lfg] : WHITE; + tree.colors.lbg = options.custom.favorite_add_lbg + ? js.global[options.custom.favorite_add_lbg] : BG_MAGENTA; + // tree heading + tree.colors.cfg = options.custom.favorite_add_cfg + ? js.global[options.custom.favorite_add_cfg] : WHITE; + tree.colors.cbg = options.custom.favorite_add_cbg + ? js.global[options.custom.favorite_add_cbg] : BG_BLACK; + // other tree color settings not applicable to this implementation + tree.open(); + + console.clear(BG_BLACK|LIGHTGRAY); + + frame.draw(); + treeframe.draw(); + + var selection; + var key; + var xtrn; + while(bbs.online) { + key = console.getkey(K_NOSPIN); + if (key == "\x0d") { + // hit enter, item is selected + break; + } else if ((key.toLowerCase() == 'q') || (key == "\x1B")) { + return; + } else if (key.toLowerCase() == 's') { + xtrn = add_favorite_search(frame, treeframe, options); + break; + } else { + selection = tree.getcmd({key: key, mouse: false}); + + if (key == KEY_UP || key == KEY_DOWN || key == KEY_HOME || key == KEY_END) { + if ((key == KEY_UP) && (tree.line == 1)) { + // pressed up on first item, go to end + tree.end(); + tree.refresh(); // fixes itself with it going to next to last item + } else if ((key == KEY_DOWN) && (tree.line == sortedItems.length)) { + // pressed down on last item, go to start + tree.home(); + tree.refresh(); // fixes issue with it going to 2nd item + } + xtrn = tree.currentItem; + } + } + frame.cycle(); + treeframe.cycle(); + s.cycle(); + } + + if (typeof xtrn.code !== "undefined") { + jsonData.push(xtrn.code); + jsonClient.write("xtrnmenu", "favorites_" + user.alias, jsonData, 2); + } +} + +// Type ahead search for add favorite +function add_favorite_search(frame, treeframe, options) { + require("frame.js", "Frame"); + require("tree.js", "Tree"); + require('typeahead.js', 'Typeahead'); + load(js.global, 'cga_defs.js'); + + var sframe = new Frame( + 1, + 1, + console.screen_columns, + console.screen_rows, + LIGHTGRAY, + frame + ); + sframe.open(); + + const typeahead = new Typeahead({ + x : 1, + y : 1, + bg : options.custom.favorite_add_search_fg + ? js.global[options.custom.favorite_add_search_fg] : LIGHTGRAY, + fg: options.custom.favorite_add_search_bg + ? js.global[options.custom.favorite_add_search_bg] : BG_BLACK, + sfg: options.custom.favorite_add_search_sfg + ? js.global[options.custom.favorite_add_search_sfg] : LIGHTGRAY, + sbg: options.custom.favorite_add_search_sbg + ? js.global[options.custom.favorite_add_search_sbg] : BG_BLACK, + hsfg: options.custom.favorite_add_search_hsfg + ? js.global[options.custom.favorite_add_search_hsfg] : WHITE, + hsbg: options.custom.favorite_add_search_hsbg + ? js.global[options.custom.favorite_add_search_hsbg] : BG_MAGENTA, + prompt: options.custom.favorite_add_search_prompt + ? options.custom.favorite_add_search_prompt : "\x01c\x01hSearch (ESC to Cancel): \x01n", + len: treeframe.width, + frame: sframe, + datasources: [ + function (searchterm) { + const ret = []; + + for (i in xtrn_area.sec_list) { + for (var k in xtrn_area.sec_list[i].prog_list) { + if (xtrn_area.sec_list[i].prog_list[k].name.toLowerCase().indexOf(searchterm.toLowerCase()) !== -1) { + if (xtrn_area.sec_list[i].prog_list[k].can_access) { + ret.push({ + 'code': xtrn_area.sec_list[i].prog_list[k].code, + 'text': xtrn_area.sec_list[i].prog_list[k].name, + }); + } + } + } + } + return ret; + } + ] + }); + + var user_input = undefined; + while (typeof user_input !== 'object') { + var key = console.inkey(K_NONE, 5); + if (key == "\x1B") { + typeahead.close(); + sframe.close(); + return false; + } + + user_input = typeahead.inkey(key); + typeahead.cycle(); + if (sframe.cycle()) typeahead.updateCursor(); + } + + typeahead.close(); + sframe.close(); + return user_input; +} + +// Remove an entry from the favorites menu +function remove_favorite(xtrncode) +{ + var options = ExternalMenus.getOptions('custommenu', 'favorites'); + + if (!options.json_enabled) { + log(LOG_DEBUG, "xtrnmenulib: Skipping favorites because JSON is not enabled"); + return false; + } + + try { + require("json-client.js", "JSONClient"); + var jsonClient = new JSONClient(options.json_host, options.json_port); + jsonClient.callback = ExternalMenus.processUpdate; + } catch (e) { + log(LOG_ERR, "xtrnmenu remove_favorites: Could not initialize JSON database so favorites is now disabled: " + e); + return false; + } + + var sortedItems = []; + var jsonData = jsonClient.read("xtrnmenu", "favorites_" + user.alias, 1); + + if (!jsonData) { + jsonData = []; + } + + if ((typeof xtrncode === "undefined") || (!xtrncode)) { + require("frame.js", "Frame"); + require("tree.js", "Tree"); + require("scrollbar.js", "ScrollBar"); + + var frame = new Frame(1, 1, console.screen_columns, 4, WHITE); + var treeframe = new Frame(1, 5, console.screen_columns, console.screen_rows - 5, WHITE); + var tree = new Tree(treeframe); + var s = new ScrollBar(tree); + frame.open(); + treeframe.open(); + + frame.putmsg(options.custom.remove_favorites_msg !== undefined + ? options.custom.remove_favorites_msg : "\x01c\x01hRemove Favorite"); + frame.gotoxy(1, 3); + frame.putmsg(options.custom.favorites_inst_rem !== undefined + ? options.custom.favorites_inst_rem : '\x01n\x01w\x01h\x012 [Up/Down/Home/End] to Navigate, [Enter] to Select, [Q] to Quit '); + + var xtrnwidth = 0; + var sortedItems = []; + for (var m in xtrn_area.prog) { + if (xtrn_area.prog[m].can_access) { + var found = false; + jsonData.some(function (jsonxtrn) { + if (jsonxtrn == xtrn_area.prog[m].code) { + found = true; + return true; + } + }); + if (found) { + sortedItems.push({code: xtrn_area.prog[m].code, name: xtrn_area.prog[m].name}) + if (xtrn_area.prog[m].name.length > xtrnwidth) { + xtrnwidth = xtrn_area.prog[m].name.length; + } + } + } + } + + sortedItems.sort(function (a, b) { + if (a.name.toLowerCase() > b.name.toLowerCase()) return 1; + if (a.name.toLowerCase() < b.name.toLowerCase()) return -1; + return 0; + }); + + for (m in sortedItems) { + const item = tree.addItem(sortedItems[m].name, sortedItems[m].code); + item.code = sortedItems[m].code; + } + + treeframe.width = xtrnwidth + 2; + + tree.colors.fg = LIGHTGRAY; + tree.colors.bg = BG_BLACK; + tree.colors.lfg = WHITE; + tree.colors.lbg = BG_MAGENTA; + tree.colors.kfg = YELLOW; + + tree.open(); + + console.clear(BG_BLACK | LIGHTGRAY); + + frame.draw(); + treeframe.draw(); + + var selection; + var key; + var xtrn; + while (bbs.online) { + key = console.getkey(); + if (key == "\x0d") { + // hit enter, item is selected + break; + } + if ((key.toLowerCase() == 'q') || (key == "\x1B")) return; + + selection = tree.getcmd({key: key, mouse: false}); + + if (key == KEY_UP || key == KEY_DOWN || key == KEY_HOME || key == KEY_END) { + if ((key == KEY_UP) && (tree.line == 1)) { + // pressed up on first item, go to end + tree.end(); + tree.refresh(); // fixes itself with it going to next to last item + } else if ((key == KEY_DOWN) && (tree.line == sortedItems.length)) { + // pressed down on last item, go to start + tree.home(); + tree.refresh(); // fixes issue with it going to 2nd item + } + xtrn = tree.currentItem; + xtrncode = xtrn.code; + } + treeframe.cycle(); + s.cycle(); + } + } + + var newJsonData = []; + jsonData.forEach(function (jsonxtrn) { + if (jsonxtrn != xtrncode) { + newJsonData.push(jsonxtrn); + } + }); + + jsonClient.write("xtrnmenu", "favorites_" + user.alias, newJsonData, 2); +} + // Display error message to console and save to log function doerror(msg) { diff --git a/exec/xtrnmenucfg.js b/exec/xtrnmenucfg.js index db77a0d5076c1052be6de9c456a2a2905b43b444..531018e90d807385e02a4b6d675f98501ac2d973 100644 --- a/exec/xtrnmenucfg.js +++ b/exec/xtrnmenucfg.js @@ -72,7 +72,7 @@ var editMenu = function(menuid) { } while(1) { - uifc.help_text = word_wrap("This screen allows you to edit the configuration options for the custom menu.\r\n\r\nMost options default or are set in modopts.ini, but here you can define them on a per-menu basis.\r\n\r\nClick Edit Items to edit the individual entries (programs, menus, etc.)"); + uifc.help_text = word_wrap("This screen allows you to edit the configuration options for the custom menu.\r\n\r\nMost options default or are set in modopts.ini, but here you can define them on a per-menu basis.\r\n\r\nClick Edit Items to edit the individual entries (programs, menus, etc.)", 72); selections = []; for (var j in menu.items) { @@ -110,7 +110,7 @@ var editMenu = function(menuid) { switch (editproperty) { case 'id': - uifc.help_text = word_wrap("This is a unique ID for the menu, which can be used as the target to\r\ncall the menu from other menus.\r\n\r\nFor the top-level menu, the id should be 'main'."); + uifc.help_text = word_wrap("This is a unique ID for the menu, which can be used as the target to\r\ncall the menu from other menus.\r\n\r\nFor the top-level menu, the id should be 'main'.", 72); var selection2 = uifc.input(WIN_MID, "Menu ID", menu.id, 50, K_EDIT); if ((selection2 < 0) || (selection2 == null)) { // escape key @@ -129,7 +129,7 @@ var editMenu = function(menuid) { break; case 'sort_type': - uifc.help_text = word_wrap("How to sort the menu:\r\nby input key\r\nby title\r\nnone (the items remain in the order they are in the config)"); + uifc.help_text = word_wrap("How to sort the menu:\r\nby input key\r\nby title\r\nnone (the items remain in the order they are in the config)", 72); switch (uifc.list(WIN_ORG | WIN_MID, "Sort Type", ["key", "title", "none"])) { case 0: @@ -183,47 +183,37 @@ var editItems = function(menuid) { } } + /* commented out as it prevents pasting into new menu if ((typeof menu.items == "undefined") || (menu.items.length == 0)) { // no items, prompt them to make one editItem(menu.id, 0); } - - + */ + uifc.help_text = word_wrap("This menu allows editing the various items in this menu.\r\n\r\n" + "If you leave input key blank, it will use an auto-generated number at display time.\r\n\r\n" - + "Choose a type first and the dropdown to choose tha target will allow you to select your target.\r\n\r\n" - + "Access string only applies to custom menu items. For external sections or external programs, use the access settings in scfg.\r\n\r\n"); + + "Choose a type first and the dropdown to choose the target will allow you to select your target.\r\n\r\n" + + "Access string only applies to custom menu items, commands, and special menus. For external sections or external programs, use the access settings in scfg.\r\n\r\n", + 72); while(1) { items = []; itemids = []; for(i in menu.items) { items.push(format( - "%6s %10s %s", + "%6s %16s %s", menu.items[i].input ? menu.items[i].input : '(auto)', menu.items[i].type, menu.items[i].title )); itemids.push(i); } - // WIN_ORG = original menu - // WIN_MID = centered mid - // WIN_ACT = menu remains active after a selection - // WIN_ESC = screen is active when escape is pressed - // WIN_XTR = blank line to insert - // WIN_INS = insert key - // WIN_DEL = delete - // WIN_CUT = cut ctrl-x - // WIN_COPY = copy ctrl-c - // WIN_PUT = paste ctrl-v - // WIN_SAV = use context/save position selection = uifc.list( - WIN_ORG|WIN_MID|WIN_ACT|WIN_ESC|WIN_XTR|WIN_INS|WIN_DEL|WIN_CUT|WIN_COPY|WIN_PASTE|WIN_SAV, + WIN_ORG|WIN_MID|WIN_ACT|WIN_ESC|WIN_XTR|WIN_INS|WIN_DEL|WIN_CUT|WIN_COPY|WIN_PASTE|WIN_PASTEXTR|WIN_SAV, menu.title + ": Items", items, ctxm ); - if (selection == -1) { // esc key break; @@ -270,7 +260,8 @@ var editItems = function(menuid) { if ("type" in copyitem) { // if item already exists in list, modify if since you can't have dupes (except empty input keys) for (var i in menu.items) { - if ((menu.items[i].input == copyitem.input) && (copyitem.input !== null) && (copyitem.input !== "")) { + if ((menu.items[i].input == copyitem.input) + && (copyitem.input !== null) && (copyitem.input !== "")) { oktopaste = true; while(1) { selection2 = uifc.input(WIN_MID, "Enter New Input Key", "", 3, K_EDIT); @@ -302,13 +293,20 @@ var editItems = function(menuid) { } if ((oktopaste) || (copyitem.input === "null") || (copyitem.input === "")) { var menuitems2 = []; + var pushed = false; for (i in menu.items) { - menuitems2.push(menu.items[i]); - // paste copied item after selected item + // paste copied item before selected item if (i == itemids[selection]) { menuitems2.push(copyitem); ctxm.cur = i-1; + pushed = true; } + menuitems2.push(menu.items[i]); + } + if (!pushed) { + // add to end + menuitems2.push(copyitem); + ctxm.cur = menuitems2.length-1; } menu.items = menuitems2; } @@ -334,7 +332,7 @@ var editItem = function(menuid, itemindex) { var displayoptions = [], displayoptionids = [], newitems = []; // used for building target selection var custommenuitems = [], custommenuitemsids = [], custommenunames = []; - + if (typeof menuid === "undefined") { uifc.msg("Menu could not be found"); return; @@ -349,12 +347,13 @@ var editItem = function(menuid, itemindex) { if (typeof menu.items[itemindex] === "undefined") { // new item + // menu.items.push({ - "input": null, - "title": "New Item " + time(), - "type": null, - "target": null, - "access_string": null, + input: null, + title: null, + type: null, + target: null, + access_string: null, }); itemindex = menu.items.length - 1; present_select_targettype(menu.items[itemindex]); @@ -379,21 +378,56 @@ var editItem = function(menuid, itemindex) { ("type" in item ? item.type : ""))); displayoptionids.push("type"); - displayoptions.push(format("%23s: %s", "target", - ("target" in item ? item.target : ""))); - displayoptionids.push("target"); + switch (item.type) { + case 'recentall': + case 'recentuser': + case 'mostlaunchedall': + case 'mostlauncheduser': + case 'longestrunall': + case 'longestrunuser': + case 'search': + case 'favorites': + displayoptions.push(format("%23s: %s", "count", + ("target" in item ? item.target : ""))); + displayoptionids.push("target"); + break; + case 'custommenu': + case 'xtrnmenu': + case 'xtrnprog': + case 'command': + default: + displayoptions.push(format("%23s: %s", "target", + ("target" in item ? item.target : ""))); + displayoptionids.push("target"); + break; + } - if ((item.type == "custommenu") || (item.type == "command")) { - displayoptions.push(format("%23s: %s", "access_string", - ("access_string" in item ? item.access_string : "(default)"))); - displayoptionids.push("access_string"); + switch (item.type) { + case 'custommenu': + case 'command': + case 'recentall': + case 'recentuser': + case 'mostlaunchedall': + case 'mostlauncheduser': + case 'longestrunuser': + case 'longestrunall': + case 'search': + case 'favorites': + displayoptions.push(format("%23s: %s", "access_string", + ("access_string" in item ? item.access_string : "(default)"))); + displayoptionids.push("access_string"); + break; + case 'xtrnmenu': + case 'xtrnprog': + default: + break; } selection = uifc.list(WIN_ORG | WIN_MID | WIN_ACT | WIN_ESC, menu.title + ": Item " + itemindex, displayoptions, itemctx); if (selection < 0) { - if (!item.title || !item.type || !item.target) { + if (!item.title || !item.type) { if (uifc.list(WIN_ORG | WIN_MID, "This item is missing required items.", ["Remove Item", "Edit Item"]) == 0) { // delete item and continue newitems = []; @@ -405,6 +439,20 @@ var editItem = function(menuid, itemindex) { menu.items = newitems; break; } + } else if (!item.target && ((item.type == "custommenu") || (item.type == "command") + || (item.type == "xtrnmenu") || (item.type == "xtrnprog"))) { + if (uifc.list(WIN_ORG | WIN_MID, "This item is missing required items.", ["Remove Item", "Edit Item"]) == 0) { + // delete item and continue + newitems = []; + for (i in menu.items) { + if (i != itemindex) { + newitems.push(menu.items[i]); + } + } + menu.items = newitems; + break; + } + break; } else { // leave menu break; @@ -414,7 +462,7 @@ var editItem = function(menuid, itemindex) { switch (displayoptionids[selection]) { case 'input': - uifc.help_text = word_wrap("The input key to access this item. Can be anything except Q. Leave blank to auto-generate a number."); + uifc.help_text = word_wrap("The input key to access this item. Can be anything except Q. Leave blank to auto-generate a number.", 72); selection2 = uifc.input(WIN_MID, "Input Key", item.input, 3, K_EDIT); if ((selection2 < 0) || (selection2 == null)) { // escape key @@ -435,8 +483,10 @@ var editItem = function(menuid, itemindex) { keyused = true; } } - - if (keyused) { + + if (selection2 == "Q") { + uifc.msg("This input key Q is reserved"); + } else if (keyused) { uifc.msg("This input key is already used by another item."); } else { item.input = selection2; @@ -470,7 +520,7 @@ var editItem = function(menuid, itemindex) { break; case 'access_string': - uifc.help_text = word_wrap("The access string for the custom menu.\r\n\r\nOnly applies to custom menu items.\r\n\r\nExample: LEVEL 60"); + uifc.help_text = word_wrap("The access string for the custom menu.\r\n\r\nOnly applies to custom menu items.\r\n\r\nExample: LEVEL 60", 72); selection2 = uifc.input(WIN_MID, "Access String", item.access_string, 255, K_EDIT); if ((selection2 < 0) || (selection2 == null)) { // escape key @@ -491,9 +541,17 @@ function present_select_targettype(item) + "custommenu is a custom menu defined in this tool.\r\n\r\n" + "xtrnmenu is a standard Syncrhonet External Section Menu (refer to the scfg tool).\r\n\r\n" + "xtrnprog is a direct link to an external program (refer to the scfg tool)" - + "command is a synchronet command line. See http://wiki.synchro.net/config:cmdline"); - - var targetypectx = uifc.list.CTX(0, 0, 0, 0, 0); + + "command is a synchronet command line. See http://wiki.synchro.net/config:cmdline" + + "recentall is a special menu of most recently used games, by all users" + + "recentuser is a special menu of most recently used games, for current user" + + "mostlaunchedall is a special menu of most launched games, by all users" + + "mostlauncheduser is a special menu of most launched games, for current user" + + "longestrunall is a special menu of games that users spent the most time in" + + "longestrunuser is a special menu of games that current user spent the most time in" + + "search is a special menu item to perform a search" + + "favorites is a special menu to let the user pick favorite games to play", 72); + + // for existing items, set the popup to the correct value if (typeof item.type !== "undefined") { switch (item.type) { case 'custommenu': @@ -512,10 +570,44 @@ function present_select_targettype(item) targetypectx.cur = 3; targetypectx.bar = 3; break; + case 'recentall': + targetypectx.cur = 4; + targetypectx.bar = 4; + break; + case 'recentuser': + targetypectx.cur = 5; + targetypectx.bar = 5; + break; + case 'mostlaunchedall': + targetypectx.cur = 6; + targetypectx.bar = 6; + break; + case 'mostlauncheduser': + targetypectx.cur = 7; + targetypectx.bar = 7; + break; + case 'longestrunall': + targetypectx.cur = 8; + targetypectx.bar = 8; + break; + case 'longestrunuser': + targetypectx.cur = 9; + targetypectx.bar = 9; + break; + case 'search': + targetypectx.cur = 10; + targetypectx.bar = 10; + break; + case 'favorites': + targetypectx.cur = 11; + targetypectx.bar = 11; + break; } } switch (uifc.list(WIN_ORG | WIN_MID | WIN_SAV, - "Target Type", ["custommenu", "xtrnmenu", "xtrnprog", "command"], targetypectx)) { + "Target Type", ["custommenu", "xtrnmenu", "xtrnprog", "command", "recentall", + "recentuser", "mostlaunchedall", "mostlauncheduser", "longestrunall", + "longestrunuser", "search", "favorites"], targetypectx)) { case 0: item.type = "custommenu"; break; @@ -528,6 +620,30 @@ function present_select_targettype(item) case 3: item.type = "command"; break; + case 4: + item.type = "recentall"; + break; + case 5: + item.type = "recentuser"; + break; + case 6: + item.type = "mostlaunchedall"; + break; + case 7: + item.type = "mostlauncheduser"; + break; + case 8: + item.type = "longestrunall"; + break; + case 9: + item.type = "longestrunuser"; + break; + case 10: + item.type = "search"; + break; + case 11: + item.type = "favorites"; + break; default: // includes escape key break; @@ -539,9 +655,8 @@ function present_select_targettype(item) function present_select_target(item) { - uifc.help_text = word_wrap("This is the ID of the custom menu, external program section, or external program to link to."); - - var targetctx = uifc.list.CTX(0, 0, 0, 0, 0); + uifc.help_text = word_wrap("This is the ID of the custom menu, external program section, or external program to link to. " + + "For special menus (recentall, etc.), it is the number of items to display.", 72); var custommenuitems = []; var custommenuitemsids = []; @@ -561,23 +676,31 @@ function present_select_target(item) if ((typeof item.target !== "undefined") && item.target) { for (var p in custommenuitemsids) { if (custommenuitemsids[p] == item.target) { - targetctx.cur = p; - targetctx.bar = p; + targetctxmenu.cur = p; + targetctxmenu.bar = p; } } } - - selection2 = uifc.list(WIN_ORG | WIN_MID | WIN_SAV, "Target", custommenuitems, targetctx); + + selection2 = uifc.list(WIN_ORG | WIN_MID | WIN_SAV, "Target", custommenuitems, targetctxmenu); if ((selection2 < 0) || (selection2 == null)) { // escape key break; } + // increment counter for rapid bulk adding + if (targetctxmenu.cur < custommenuitemsids.length) { + ++targetctxmenu.cur; + ++targetctxmenu.bar; + } + item.target = custommenuitemsids[selection2]; while(1) { - if (uifc.list(WIN_ORG | WIN_MID, "Replace item title with sections's name?", ["Yes", "No"]) == 0) { + if ((item.title !== null) && uifc.list(WIN_ORG | WIN_MID, "Replace item title with menus's name?", ["Yes", "No"]) == 0) { item.title = custommenunames[selection2]; // for external program, change title to program name + } else if (item.title === null) { + item.title = custommenunames[selection2]; } break; } @@ -600,30 +723,37 @@ function present_select_target(item) if ((typeof item.target !== "undefined") && item.target) { for (var p in custommenuitemsids) { if (custommenuitemsids[p].toLowerCase() == item.target.toLowerCase()) { - targetctx.cur = p; - targetctx.bar = p; + targetctxsection.cur = p; + targetctxsection.bar = p; } } - } + } - selection2 = uifc.list(WIN_ORG | WIN_MID | WIN_SAV, "Target", custommenuitems, targetctx); + selection2 = uifc.list(WIN_ORG | WIN_MID | WIN_SAV, "Target", custommenuitems, targetctxsection); if ((selection2 < 0) || (selection2 == null)) { // escape key break; } + + // increment counter for rapid bulk adding + if (targetctxsection.cur < custommenuitemsids.length) { + ++targetctxsection.cur; + ++targetctxsection.bar; + } item.target = custommenuitemsids[selection2]; while(1) { - if (uifc.list(WIN_ORG | WIN_MID, "Replace item title with sections's name?", ["Yes", "No"]) == 0) { + if ((item.title !== null) && uifc.list(WIN_ORG | WIN_MID, "Replace item title with sections's name?", ["Yes", "No"]) == 0) { item.title = custommenunames[selection2]; // for external program, change title to program name + } else if (item.title === null) { + item.title = custommenunames[selection2]; } break; } break; case "xtrnprog": - // present list of external programs // create sorted list var proglist = []; @@ -641,44 +771,69 @@ function present_select_target(item) if ((typeof item.target !== "undefined") && item.target) { for (var p in custommenuitemsids) { if (custommenuitemsids[p].toLowerCase() == item.target.toLowerCase()) { - targetctx.cur = p; - targetctx.bar = p; + targetctxprog.cur = p; + targetctxprog.bar = p; } } - } - - selection2 = uifc.list(WIN_ORG | WIN_MID | WIN_SAV, "Target", custommenuitems, targetctx); + } + + selection2 = uifc.list(WIN_ORG | WIN_MID | WIN_SAV, "Target", custommenuitems, targetctxprog); if ((selection2 < 0) || (selection2 == null)) { // escape key break; } + + // increment counter for rapid bulk adding + if (targetctxprog.cur < custommenuitemsids.length) { + ++targetctxprog.cur; + ++targetctxprog.bar; + } + if (selection2 || selection2 === 0) { item.target = custommenuitemsids[selection2]; while(1) { - if (uifc.list(WIN_ORG | WIN_MID, "Replace item title with sections's name?", ["Yes", "No"]) == 0) { + if ((item.title !== null) && uifc.list(WIN_ORG | WIN_MID, "Replace item title with programs's name?", ["Yes", "No"]) == 0) { item.title = custommenunames[selection2]; // for external program, change title to program name + } else if (item.title === null) { + item.title = custommenunames[selection2]; } break; } } break; - - command: - selection2 = uifc.input(WIN_ORG | WIN_MID, "Command", item.target, 63, K_EDIT); + + case "command": + selection2 = uifc.input(WIN_ORG | WIN_MID, "Command", item.target, 63, K_EDIT); if ((selection2 < 0) || (selection2 == null)) { // escape key break; } - item.target = selection2; break; + + case "recentall": + case "recentuser": + case "mostlaunchedall": + case "mostlauncheduser": + case "longestrunall": + case "longestrunuser": + case "search": + case "favorites": + selection2 = uifc.input(WIN_ORG | WIN_MID, "Number of Items to Display", item.target, 63, K_EDIT); + if ((selection2 < 0) || (selection2 == null)) { + // escape key + break; + } + item.target = selection2; + break; + break; + default: selection2 = uifc.input(WIN_ORG | WIN_MID, "Target", item.target, 50, K_EDIT); if ((selection2 < 0) || (selection2 == null)) { // escape key break; } - item.target = selection2; break; } @@ -711,6 +866,13 @@ try { var menuconfig = {}; var copyitem = {}; // for menu item copy/paste var config_file = new File(file_cfgname(system.ctrl_dir, "xtrnmenu.cfg")); + // this is made to persist so that if adding multiple items, it will + // remember the position of the target type (good for bulk adds) + var targetypectx = uifc.list.CTX(0, 0, 0, 0, 0); + var targetctxmenu = uifc.list.CTX(0, 0, 0, 0, 0); + var targetctxsection = uifc.list.CTX(0, 0, 0, 0, 0); + var targetctxprog = uifc.list.CTX(0, 0, 0, 0, 0); + if (config_file.open('r+')) { var config_src = config_file.read(); try { @@ -740,7 +902,7 @@ try { var ctx = new uifc.list.CTX(0, 0, 0, 0, 0); while(1) { - uifc.help_text = word_wrap("This program allows managing the Enhanced External Program Menu feature."); + uifc.help_text = word_wrap("This program allows managing the Enhanced External Program Menu feature.", 72); // no menus or no main menu var mainmenufound = false; diff --git a/src/sbbs3/scfg/GNUmakefile b/src/sbbs3/scfg/GNUmakefile index 29128d85a82cbdf4c1d5d1b5c28be12e2a3e9d8d..3a4537061db340d28e79b598273ee094aace2120 100644 --- a/src/sbbs3/scfg/GNUmakefile +++ b/src/sbbs3/scfg/GNUmakefile @@ -8,4 +8,4 @@ vpath %.c .. $(SCFG): $(OBJS) $(CRYPT_DEPS) @echo Linking $@ - ${QUIET}$(CC) $(LDFLAGS) $(MT_LDFLAGS) -o$@ $(OBJS) $(SMBLIB_LIBS) $(ENCODE_LIBS) $(HASH_LIBS) $(UIFC-MT_LIBS) $(CIOLIB-MT_LIBS) $(XPDEV-MT_LIBS) $(CRYPT_LIBS) + ${QUIET}$(CC) $(LDFLAGS) $(MT_LDFLAGS) -o$@ $(OBJS) $(SMBLIB_LIBS) $(ENCODE_LIBS) $(HASH_LIBS) $(UIFC-MT_LIBS) $(CIOLIB-MT_LIBS) $(CRYPT_LIBS) $(XPDEV-MT_LIBS) diff --git a/webv4/components/navbar.xjs b/webv4/components/navbar.xjs index 42bf5de0cb92fc5fc7017b46d7d364960f5728d2..63fc1bedbb306c82d9fba40bb343ca503c598671 100644 --- a/webv4/components/navbar.xjs +++ b/webv4/components/navbar.xjs @@ -47,8 +47,21 @@ <ul class="nav navbar-nav"> <?xjs menu(getPageList(settings.web_pages)); ?> </ul> + <ul class="nav navbar-nav navbar-right"> - <?xjs if (user.alias === settings.guest || user.number < 1) { ?> +<?xjs if (!settings.darkmode_off) { ?> + <li class="nav-item dark-switch"> + <div class="form-group"> + <div class="checkbox checbox-switch darkswitchbox"> + <label> + <input type="checkbox" id="darkSwitch" />Dark + <span></span> + </label> + </div> + </div> + </li> +<?xjs } ?> + <?xjs if (user.alias === settings.guest || user.number < 1) { ?> <?xjs if (settings.user_registration) { ?> <li> <a href="./?page=000-register.xjs"><? write(locale.strings.main.menu_item_register); ?></a> diff --git a/webv4/lib/events/nodelist.js b/webv4/lib/events/nodelist.js index 366315854c5224847d3f38dc8154e22059a8367e..88673c04ef6a1ae6782af47a06ac95ebe65fe46b 100644 --- a/webv4/lib/events/nodelist.js +++ b/webv4/lib/events/nodelist.js @@ -73,7 +73,7 @@ function scan() { if (e.status != NODE_INUSE) { return { node: i + 1, - status: null, + status: locale.strings.sidebar_node_list.label_waiting_for_call, action: null, user: null, connection: '' diff --git a/webv4/lib/locale/en_us.ini b/webv4/lib/locale/en_us.ini index 655ad46eeb74c0d3a0420ad78ba2c0d42028886d..d231320aa24ac4d6f163a9731ef62d2249b0d262 100644 --- a/webv4/lib/locale/en_us.ini +++ b/webv4/lib/locale/en_us.ini @@ -100,6 +100,7 @@ label_node_column = Node label_send_telegram = Send a telegram label_status_column = Status label_status_web = browsing +label_waiting_for_call = Waiting for call [sidebar_recent_visitors] label_title = Recent Visitors diff --git a/webv4/pages/000-home.xjs b/webv4/pages/000-home.xjs index 32b5353df82af16356ee2a36e23572ad651df34b..53b5280a04a8689c95a03eb1d49c4fff1d80604e 100644 --- a/webv4/pages/000-home.xjs +++ b/webv4/pages/000-home.xjs @@ -20,30 +20,32 @@ <script id="fTelnetScript" src="<?xjs write(get_url()); ?>"></script> <script> - var wsp = <?xjs write(settings.wsp || GetWebSocketServicePort()); ?>; - var wssp = <?xjs write(settings.wssp || GetWebSocketServicePort(true)); ?>; - var Options = new fTelnetOptions(); - Options.BareLFtoCRLF = false; - Options.BitsPerSecond = 57600; - Options.ButtonBarVisible = true; - Options.ConnectionType = 'telnet'; - Options.Emulation = 'ansi-bbs'; - Options.Enter = '\r'; - Options.Font = 'CP437'; - Options.ForceWss = false; - Options.Hostname = '<?xjs write(http_request.vhost); ?>'; - Options.LocalEcho = false; - Options.Port = location.protocol == 'https:' ? wssp : wsp; - Options.ScreenColumns = 80; - Options.ScreenRows = 25; - Options.SplashScreen = '<?xjs write(get_splash()); ?>'; - var fTelnet = new fTelnetClient('fTelnetContainer', Options); - fTelnet.ButtonBarVisible = true; - if ($('#ftelnet-connect').length) { - $('#ftelnet-connect').click(function() { - fTelnet.Connect(); - }); - } + window.addEventListener('load', (event) => { + var wsp = <?xjs write(settings.wsp || GetWebSocketServicePort()); ?>; + var wssp = <?xjs write(settings.wssp || GetWebSocketServicePort(true)); ?>; + var Options = new fTelnetOptions(); + Options.BareLFtoCRLF = false; + Options.BitsPerSecond = 57600; + Options.ButtonBarVisible = true; + Options.ConnectionType = 'telnet'; + Options.Emulation = 'ansi-bbs'; + Options.Enter = '\r'; + Options.Font = 'CP437'; + Options.ForceWss = false; + Options.Hostname = '<?xjs write(http_request.vhost); ?>'; + Options.LocalEcho = false; + Options.Port = location.protocol == 'https:' ? wssp : wsp; + Options.ScreenColumns = 80; + Options.ScreenRows = 25; + Options.SplashScreen = '<?xjs write(get_splash()); ?>'; + var fTelnet = new fTelnetClient('fTelnetContainer', Options); + fTelnet.ButtonBarVisible = true; + if ($('#ftelnet-connect').length) { + $('#ftelnet-connect').click(function() { + fTelnet.Connect(); + }); + } + }); </script> <?xjs } ?> diff --git a/webv4/pages/003-games.xjs b/webv4/pages/003-games.xjs index 7b7604d8bb2b03cb992b44b613676ba42b0569e2..749594a43c12c17585f5b730dda21f57c02765e6 100644 --- a/webv4/pages/003-games.xjs +++ b/webv4/pages/003-games.xjs @@ -43,29 +43,31 @@ <script id="fTelnetScript" src="<?xjs write(get_url()); ?>"></script> <script type="text/javascript"> - var wsp = <?xjs write(settings.wsp || GetWebSocketServicePort()); ?>; - var wssp = <?xjs write(settings.wssp || GetWebSocketServicePort(true)); ?>; - var Options = new fTelnetOptions(); - Options.BareLFtoCRLF = false; - Options.BitsPerSecond = 57600; - Options.ConnectionType = 'rlogin'; - Options.Emulation = 'ansi-bbs'; - Options.Enter = '\r'; - Options.Font = 'CP437'; - Options.ForceWss = false; - Options.Hostname = '<?xjs write(http_request.vhost); ?>'; - Options.LocalEcho = false; - Options.Port = location.protocol == 'https:' ? wssp : wsp; - Options.RLoginClientUsername = '<?xjs write(user.security.password); ?>'; - Options.RLoginServerUsername = '<?xjs write(user.alias); ?>'; - Options.ScreenColumns = 80; - Options.ScreenRows = 25; - Options.SplashScreen = Options.SplashScreen = '<?xjs write(get_splash()); ?>'; - Options.WebSocketUrlPath = '?Port=<?xjs write(GetRLoginPort()); ?>'; - var fTelnet = new fTelnetClient('fTelnetContainer', Options); - fTelnet.OnConnectionClose = function () { - window.location.reload(); - }; + window.addEventListener('load', (event) => { + var wsp = <?xjs write(settings.wsp || GetWebSocketServicePort()); ?>; + var wssp = <?xjs write(settings.wssp || GetWebSocketServicePort(true)); ?>; + var Options = new fTelnetOptions(); + Options.BareLFtoCRLF = false; + Options.BitsPerSecond = 57600; + Options.ConnectionType = 'rlogin'; + Options.Emulation = 'ansi-bbs'; + Options.Enter = '\r'; + Options.Font = 'CP437'; + Options.ForceWss = false; + Options.Hostname = '<?xjs write(http_request.vhost); ?>'; + Options.LocalEcho = false; + Options.Port = location.protocol == 'https:' ? wssp : wsp; + Options.RLoginClientUsername = '<?xjs write(user.security.password); ?>'; + Options.RLoginServerUsername = '<?xjs write(user.alias); ?>'; + Options.ScreenColumns = 80; + Options.ScreenRows = 25; + Options.SplashScreen = Options.SplashScreen = '<?xjs write(get_splash()); ?>'; + Options.WebSocketUrlPath = '?Port=<?xjs write(GetRLoginPort()); ?>'; + window.fTelnet = new fTelnetClient('fTelnetContainer', Options); + fTelnet.OnConnectionClose = function () { + window.location.reload(); + }; + }); async function launchXtrn(code) { await v4_get('./api/system.ssjs?call=set-xtrn-intent&code=' + code); diff --git a/webv4/pages/099-xtrnmenu-games.xjs b/webv4/pages/099-xtrnmenu-games.xjs index c7106969f157d3872695bbe5d7e9b140abcde545..6f2a37dc9086f0aac5345d8e25fb70c4b9c75100 100644 --- a/webv4/pages/099-xtrnmenu-games.xjs +++ b/webv4/pages/099-xtrnmenu-games.xjs @@ -1,10 +1,10 @@ -<!--HIDDEN:Games--> +<!--Games--> <?xjs /** * Web Display for Custom External Program Menus * by Michael Long mlong innerrealmbbs.us * - * See wiki at http://wiki.synchro.net/module:xtrnmenumod + * See wiki at http://wiki.synchro.net/module:xtrnmenu */ load("ftelnethelper.js"); @@ -33,8 +33,43 @@ <?xjs var menuobj; - if ((Request.get_param('type') == 'xtrnmenu') && Request.has_param('target')) { - menuobj = ExternalMenus.getSectionMenu(Request.get_param('target')); + if (Request.get_param('type')) { + var target = Request.get_param('target'); + switch (Request.get_param('type')) { + case 'custommenu': + if (target !== undefined) { + menuobj = ExternalMenus.getMenu(target); + } + break; + case 'xtrnmenu': + if (target !== undefined) { + menuobj = ExternalMenus.getSectionMenu(target); + } + break; + case 'recentall': + menuobj = ExternalMenus.getSpecial(Request.get_param('type'), "Most Recent - All", target); + break; + case 'recentuser': + menuobj = ExternalMenus.getSpecial(Request.get_param('type'), "Most Recent - Yours", target); + break; + case 'mostlaunchedall': + menuobj = ExternalMenus.getSpecial(Request.get_param('type'), "Most Launched - All", target); + break; + case 'mostlauncheduser': + menuobj = ExternalMenus.getSpecial(Request.get_param('type'), "Most Launched - Yours", target); + break; + case 'longestrunall': + menuobj = ExternalMenus.getSpecial(Request.get_param('type'), "Longest Run - All", target); + break; + case 'longestrunuser': + menuobj = ExternalMenus.getSpecial(Request.get_param('type'), "Longest Run - Yours", target); + break; + case 'favorites': + menuobj = ExternalMenus.getFavorites("Favorites", target); + break; + default: + break; + } } else { if (Request.has_param('target')) { menuobj = ExternalMenus.getMenu(Request.get_param('target')); @@ -57,11 +92,27 @@ if (settings.xtrn_blacklist.indexOf(menuitem.target.toLowerCase()) > -1) { return; } - menuitems.push({ - 'itemtitle': menuitem.title, - 'itemtype': menuitem.type, - 'itemtarget': menuitem.target - }); + switch (menuitem.type) { + case 'custommenu': + case 'xtrnmenu': + case 'xtrnprog': + case 'favorites': + case 'recentall': + case 'recentuser': + case 'mostlaunchedall': + case 'mostlauncheduser': + case 'longestrunuser': + case 'longestrunall': + menuitems.push({ + 'itemtitle': menuitem.title, + 'itemtype': menuitem.type, + 'itemtarget': menuitem.target, + 'stats': typeof menuitem.stats !== undefined ? menuitem.stats : null + }); + break; + default: + break; + } }); } ?> @@ -69,29 +120,31 @@ <script id="fTelnetScript" src="<?xjs write(get_url()); ?>"></script> <script type="text/javascript"> - var wsp = <?xjs write(settings.wsp || GetWebSocketServicePort()); ?>; - var wssp = <?xjs write(settings.wssp || GetWebSocketServicePort(true)); ?>; - var Options = new fTelnetOptions(); - Options.BareLFtoCRLF = false; - Options.BitsPerSecond = 57600; - Options.ConnectionType = 'rlogin'; - Options.Emulation = 'ansi-bbs'; - Options.Enter = '\r'; - Options.Font = 'CP437'; - Options.ForceWss = false; - Options.Hostname = '<?xjs write(http_request.vhost); ?>'; - Options.LocalEcho = false; - Options.Port = location.protocol == 'https:' ? wssp : wsp; - Options.RLoginClientUsername = '<?xjs write(user.security.password); ?>'; - Options.RLoginServerUsername = '<?xjs write(user.alias); ?>'; - Options.ScreenColumns = 80; - Options.ScreenRows = 25; - Options.SplashScreen = Options.SplashScreen = '<?xjs write(get_splash()); ?>'; - Options.WebSocketUrlPath = '?Port=<?xjs write(GetRLoginPort()); ?>'; - var fTelnet = new fTelnetClient('fTelnetContainer', Options); - fTelnet.OnConnectionClose = function () { - window.location.reload(); - }; + window.addEventListener('load', (event) => { + var wsp = <?xjs write(settings.wsp || GetWebSocketServicePort()); ?>; + var wssp = <?xjs write(settings.wssp || GetWebSocketServicePort(true)); ?>; + var Options = new fTelnetOptions(); + Options.BareLFtoCRLF = false; + Options.BitsPerSecond = 57600; + Options.ConnectionType = 'rlogin'; + Options.Emulation = 'ansi-bbs'; + Options.Enter = '\r'; + Options.Font = 'CP437'; + Options.ForceWss = false; + Options.Hostname = '<?xjs write(http_request.vhost); ?>'; + Options.LocalEcho = false; + Options.Port = location.protocol == 'https:' ? wssp : wsp; + Options.RLoginClientUsername = '<?xjs write(user.security.password); ?>'; + Options.RLoginServerUsername = '<?xjs write(user.alias); ?>'; + Options.ScreenColumns = 80; + Options.ScreenRows = 25; + Options.SplashScreen = Options.SplashScreen = '<?xjs write(get_splash()); ?>'; + Options.WebSocketUrlPath = '?Port=<?xjs write(GetRLoginPort()); ?>'; + window.fTelnet = new fTelnetClient('fTelnetContainer', Options); + fTelnet.OnConnectionClose = function () { + window.location.reload(); + }; + }); async function launchXtrn() { var code = event.srcElement.id; @@ -109,11 +162,18 @@ sessionStorage.setItem("mainmenu", currentTitle); } var div = $('#xtrn-list'); + menuitems.forEach(function (menuitem) { var a = document.createElement('a'); $(a).addClass("list-group-item"); $(a).addClass("striped"); a.text = menuitem.itemtitle; + + if (typeof menuitem.stats !== "undefined") { + var badge = document.createElement('span'); + $(badge).addClass('badge').text(menuitem.stats); + a.appendChild(badge); + } if (menuitem.itemtype == "xtrnprog") { a.href = "#fTelnet"; diff --git a/webv4/pages/099-xtrnmenu-gamestats.xjs b/webv4/pages/099-xtrnmenu-gamestats.xjs new file mode 100644 index 0000000000000000000000000000000000000000..240f19c0aecc020414075058d367e8f56771f480 --- /dev/null +++ b/webv4/pages/099-xtrnmenu-gamestats.xjs @@ -0,0 +1,54 @@ +<!--Game Stats--> +<div class="list-group"> + +<h1>Game Stats</h1> + +<?xjs + load('sbbsdefs.js'); + load("xtrnmenulib.js"); + + var ExternalMenus = new ExternalMenus(); + + if (typeof settings.xtrn_blacklist === 'string') { + settings.xtrn_blacklist = settings.xtrn_blacklist.toLowerCase().split(','); + } else { + settings.xtrn_blacklist = []; + } + + // Edit these lines as needed to adjust the page + gameStats("recentall", "Most Recent", 10); + gameStats("mostlaunchedall", "Top 15 Most Launched", 15); + gameStats("longestrunall", "Top 15 Longest Run", 15); + + function gameStats(menutype, title, maxitems) { + var menuobj = ExternalMenus.getSpecial(menutype, title); + +?> +<div class="row list-group-item" style="background-color: #993399; color: #FFF"> + <div class="col-sm-12"><h3><?xjs write(title) ?></h3></div> +</div> + +<?xjs + if ((typeof menuobj.items !== "undefined") || (menuobj.items.length > 0)) { + var i = 0; + menuobj.items.some(function (menuitem) { + if (menuitem.type != "xtrnprog") { + return; + } + + if (settings.xtrn_blacklist.indexOf(menuitem.target.toLowerCase()) > -1) return; + + if (++i > maxitems) return; +?> + +<div class="row list-group-item striped"> + <div class="col-sm-2"><span class="badge badge-inverse"><?xjs write(menuitem.stats) ?></span></div> + <div class="col-sm-10"><?xjs write(menuitem.title) ?></div> +</div> + +<?xjs + }); + } + } +?> +</div> diff --git a/webv4/root/css/checkbox.css b/webv4/root/css/checkbox.css new file mode 100644 index 0000000000000000000000000000000000000000..a19ff7084203dc200c0f131aba9b2118cc842304 --- /dev/null +++ b/webv4/root/css/checkbox.css @@ -0,0 +1,188 @@ +/* --------------------------------------------------- + +Project : CSS Checkbox Switch +Author : Partha Kar (https://www.facebook.com/partha.creativemind) +Version : 1.0 +Release Dtae : 15 November, 2017 + +---------------------------------------------------- */ + + +.checkbox.checbox-switch { + padding-left: 0; +} + +.checkbox.checbox-switch label, +.checkbox-inline.checbox-switch { + display: inline-block; + position: relative; + padding-left: 0; +} +.checkbox.checbox-switch label input, +.checkbox-inline.checbox-switch input { + display: none; +} +.checkbox.checbox-switch label span, +.checkbox-inline.checbox-switch span { + width: 35px; + border-radius: 20px; + height: 18px; + border: 1px solid #dbdbdb; + background-color: rgb(255, 255, 255); + border-color: rgb(223, 223, 223); + box-shadow: rgb(223, 223, 223) 0px 0px 0px 0px inset; + transition: border 0.4s ease 0s, box-shadow 0.4s ease 0s; + display: inline-block; + vertical-align: middle; + margin-right: 5px; +} +.checkbox.checbox-switch label span:before, +.checkbox-inline.checbox-switch span:before { + display: inline-block; + width: 16px; + height: 16px; + border-radius: 50%; + background: rgb(255,255,255); + content: " "; + top: 0; + position: relative; + left: 0; + transition: all 0.3s ease; + box-shadow: 0 1px 4px rgba(0,0,0,0.4); +} +.checkbox.checbox-switch label > input:checked + span:before, +.checkbox-inline.checbox-switch > input:checked + span:before { + left: 17px; +} + + +/* Switch Default */ +.checkbox.checbox-switch label > input:checked + span, +.checkbox-inline.checbox-switch > input:checked + span { + background-color: rgb(180, 182, 183); + border-color: rgb(180, 182, 183); + box-shadow: rgb(180, 182, 183) 0px 0px 0px 8px inset; + transition: border 0.4s ease 0s, box-shadow 0.4s ease 0s, background-color 1.2s ease 0s; +} +.checkbox.checbox-switch label > input:checked:disabled + span, +.checkbox-inline.checbox-switch > input:checked:disabled + span { + background-color: rgb(220, 220, 220); + border-color: rgb(220, 220, 220); + box-shadow: rgb(220, 220, 220) 0px 0px 0px 8px inset; + transition: border 0.4s ease 0s, box-shadow 0.4s ease 0s, background-color 1.2s ease 0s; +} +.checkbox.checbox-switch label > input:disabled + span, +.checkbox-inline.checbox-switch > input:disabled + span { + background-color: rgb(232,235,238); + border-color: rgb(255,255,255); +} +.checkbox.checbox-switch label > input:disabled + span:before, +.checkbox-inline.checbox-switch > input:disabled + span:before { + background-color: rgb(248,249,250); + border-color: rgb(243, 243, 243); + box-shadow: 0 1px 4px rgba(0,0,0,0.1); +} + +/* Switch Light */ +.checkbox.checbox-switch.switch-light label > input:checked + span, +.checkbox-inline.checbox-switch.switch-light > input:checked + span { + background-color: rgb(248,249,250); + border-color: rgb(248,249,250); + box-shadow: rgb(248,249,250) 0px 0px 0px 8px inset; + transition: border 0.4s ease 0s, box-shadow 0.4s ease 0s, background-color 1.2s ease 0s; +} + +/* Switch Dark */ +.checkbox.checbox-switch.switch-dark label > input:checked + span, +.checkbox-inline.checbox-switch.switch-dark > input:checked + span { + background-color: rgb(52,58,64); + border-color: rgb(52,58,64); + box-shadow: rgb(52,58,64) 0px 0px 0px 8px inset; + transition: border 0.4s ease 0s, box-shadow 0.4s ease 0s, background-color 1.2s ease 0s; +} +.checkbox.checbox-switch.switch-dark label > input:checked:disabled + span, +.checkbox-inline.checbox-switch.switch-dark > input:checked:disabled + span { + background-color: rgb(100, 102, 104); + border-color: rgb(100, 102, 104); + box-shadow: rgb(100, 102, 104) 0px 0px 0px 8px inset; + transition: border 0.4s ease 0s, box-shadow 0.4s ease 0s, background-color 1.2s ease 0s; +} + +/* Switch Success */ +.checkbox.checbox-switch.switch-success label > input:checked + span, +.checkbox-inline.checbox-switch.switch-success > input:checked + span { + background-color: rgb(40, 167, 69); + border-color: rgb(40, 167, 69); + box-shadow: rgb(40, 167, 69) 0px 0px 0px 8px inset; + transition: border 0.4s ease 0s, box-shadow 0.4s ease 0s, background-color 1.2s ease 0s; +} +.checkbox.checbox-switch.switch-success label > input:checked:disabled + span, +.checkbox-inline.checbox-switch.switch-success > input:checked:disabled + span { + background-color: rgb(153, 217, 168); + border-color: rgb(153, 217, 168); + box-shadow: rgb(153, 217, 168) 0px 0px 0px 8px inset; +} + +/* Switch Danger */ +.checkbox.checbox-switch.switch-danger label > input:checked + span, +.checkbox-inline.checbox-switch.switch-danger > input:checked + span { + background-color: rgb(200, 35, 51); + border-color: rgb(200, 35, 51); + box-shadow: rgb(200, 35, 51) 0px 0px 0px 8px inset; + transition: border 0.4s ease 0s, box-shadow 0.4s ease 0s, background-color 1.2s ease 0s; +} +.checkbox.checbox-switch.switch-danger label > input:checked:disabled + span, +.checkbox-inline.checbox-switch.switch-danger > input:checked:disabled + span { + background-color: rgb(216, 119, 129); + border-color: rgb(216, 119, 129); + box-shadow: rgb(216, 119, 129) 0px 0px 0px 8px inset; + transition: border 0.4s ease 0s, box-shadow 0.4s ease 0s, background-color 1.2s ease 0s; +} + +/* Switch Primary */ +.checkbox.checbox-switch.switch-primary label > input:checked + span, +.checkbox-inline.checbox-switch.switch-primary > input:checked + span { + background-color: rgb(0, 105, 217); + border-color: rgb(0, 105, 217); + box-shadow: rgb(0, 105, 217) 0px 0px 0px 8px inset; + transition: border 0.4s ease 0s, box-shadow 0.4s ease 0s, background-color 1.2s ease 0s; +} +.checkbox.checbox-switch.switch-primary label > input:checked:disabled + span, +.checkbox-inline.checbox-switch.switch-primary > input:checked:disabled + span { + background-color: rgb(109, 163, 221); + border-color: rgb(109, 163, 221); + box-shadow: rgb(109, 163, 221) 0px 0px 0px 8px inset; + transition: border 0.4s ease 0s, box-shadow 0.4s ease 0s, background-color 1.2s ease 0s; +} + +/* Switch Info */ +.checkbox.checbox-switch.switch-info label > input:checked + span, +.checkbox-inline.checbox-switch.switch-info > input:checked + span { + background-color: rgb(23, 162, 184); + border-color: rgb(23, 162, 184); + box-shadow: rgb(23, 162, 184) 0px 0px 0px 8px inset; + transition: border 0.4s ease 0s, box-shadow 0.4s ease 0s, background-color 1.2s ease 0s; +} +.checkbox.checbox-switch.switch-info label > input:checked:disabled + span, +.checkbox-inline.checbox-switch.switch-info > input:checked:disabled + span { + background-color: rgb(102, 192, 206); + border-color: rgb(102, 192, 206); + box-shadow: rgb(102, 192, 206) 0px 0px 0px 8px inset; + transition: border 0.4s ease 0s, box-shadow 0.4s ease 0s, background-color 1.2s ease 0s; +} + +/* Switch Warning */ +.checkbox.checbox-switch.switch-warning label > input:checked + span, +.checkbox-inline.checbox-switch.switch-warning > input:checked + span { + background-color: rgb(255, 193, 7); + border-color: rgb(255, 193, 7); + box-shadow: rgb(255, 193, 7) 0px 0px 0px 8px inset; + transition: border 0.4s ease 0s, box-shadow 0.4s ease 0s, background-color 1.2s ease 0s; +} +.checkbox.checbox-switch.switch-warning label > input:checked:disabled + span, +.checkbox-inline.checbox-switch.switch-warning > input:checked:disabled + span { + background-color: rgb(226, 195, 102); + border-color: rgb(226, 195, 102); + box-shadow: rgb(226, 195, 102) 0px 0px 0px 8px inset; + transition: border 0.4s ease 0s, box-shadow 0.4s ease 0s, background-color 1.2s ease 0s; +} diff --git a/webv4/root/css/style.css b/webv4/root/css/style.css index 7e49bcf0b1719cc5ecaf0b244f814668234257bf..9aaeae40472f7c135551e8abff5a8b882d912dff 100644 --- a/webv4/root/css/style.css +++ b/webv4/root/css/style.css @@ -18,8 +18,8 @@ a.unread { } span.badge.new { - background: #2E9AFE; - color: #FFFFFF; + background: #2E9AFE; + color: #FFFFFF; } /* A read mail message in the list view. */ @@ -53,7 +53,77 @@ span.message-header.unread { background-color: #FCF8E3; } -/* You probably don't need to mess with rules below this line. */ +/*** Dark mode ***/ + +/* default links, like the breadcrumbs */ +.dark a { + color: #75b8f1; +} + +/* background of the top bar */ +.dark .navbar { + background-color: #333; +} + +/* bbs name at top left */ +.dark .navbar-brand { + color: #ccc; +} +.dark .navbar-brand:hover { + color: #fff; +} + +/* hover for the top nav buttons */ +.dark .navbar-default a:hover { + color: #8cc1ee !important; + color: #fff; + background-color: #444 !important; +} + +/* the active top nav button when opened */ +.dark .navbar-default .navbar-nav>.open>a, +.dark.navbar-default .navbar-nav>.open>a:focus, +.dark.navbar-default .navbar-nav>.open>a:hover { + background-color: #555; + color: #EEE; +} + +/* dropdown menus */ +.dark .dropdown-menu { + background-color: #333; +} +.dark .dropdown-menu li a { + color: #75b8f1; +} + +/* background color of alternate rows in lists */ +.dark .striped:nth-of-type(even), .dark .table-striped > tbody > tr:nth-child(odd) > td, .dark .table-striped > tbody > tr:nth-child(odd) > th { + background: #444; +} + +/* this is the text and color for most items, including non-alternating rows */ +.dark .list-group-item { + background-color: #555; + color: #EEE; +} + +/* A link in a list when the mouse is hovering over it (mostly applies to the Forum) */ +.dark a.list-group-item:hover, .dark a.list-group-item:active { + background-color: #888; + color: #222; +} + +.dark li.list-group-item.mail:hover { + background-color: #888; + color: #FFF; +} + +/* background color for the breadcrumb box */ +.dark .breadcrumb { + background-color: #555; +} + +/*** You probably don't need to mess with rules below this line. ***/ blockquote { margin: .5em; @@ -180,3 +250,72 @@ animation: indicator-fade 3s ease 0s 1 alternate !important; .breadcrumb li { display: inline; } + +.dark { + background-color: #222 !important; + color: #eee; +} + +.dark span.badge.ignored { + background-color: #aaa; +} + +.dark .text-danger { + color: #ef1c18; +} + +.dark .text-success { + color: #3eef41; +} + +.dark a.btn, .dark .icon { + color: #000; + background-color: #ddd !important; +} + +.dark .btn { + color: #000; + background-color: #ddd ; +} + +.dark .btn-primary { + color: #FFF; + background-color: #337ab7; +} + +.dark a.btn :hover, .dark .icon:hover { + color: #000; + background-color: #fff !important; +} + +@media (max-width: 767px) { + .dark-switch { + padding: 10px 15px; + } +} + +@media (min-width: 768px) { + .dark-switch { + padding: 0px; + } +} + +.dark .modal-content { + background-color: #555 !important; + color: #FFF !important; +} + +.darkswitchbox { + margin-block-start: 1em; + margin-block-end: 1em; + margin-inline-start: 0px; + margin-inline-end: 0px; + padding-inline-start: 40px; +} + +.dark input,.dark select,.dark textarea { + color: #333; + background-color: #DDD; +} + + diff --git a/webv4/root/index.xjs b/webv4/root/index.xjs index 477b77f4189faa010e4ed6eebed7a4e7fbea5354..b6a1f93a4726b65da3b52e28cf0248de90140703 100644 --- a/webv4/root/index.xjs +++ b/webv4/root/index.xjs @@ -41,6 +41,13 @@ <!DOCTYPE html> <html lang="en"> <head> + <style type="text/css"> + .hidden { display:none; } + </style> + <script src="./js/jquery.min.js"></script> + <script type="text/javascript"> + jQuery('html').addClass('hidden'); + </script> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> @@ -49,6 +56,9 @@ <link href="./bootstrap/css/bootstrap.min.css" rel="stylesheet"> <link href="./css/offcanvas.css" rel="stylesheet"> <link href="./css/style.css" rel="stylesheet"> +<?xjs if (!settings.darkmode_off) { ?> + <link href="./css/checkbox.css" rel="stylesheet"> +<?xjs } ?> <? if (file_exists(settings.web_root + 'css/custom.css')) { ?> <link href="./css/custom.css" rel="stylesheet"> <? } ?> @@ -56,7 +66,6 @@ <body> - <script src="./js/jquery.min.js"></script> <script src="./bootstrap/js/bootstrap.min.js"></script> <script src="./js/common.js"></script> @@ -101,7 +110,6 @@ return false; }); </script> - </body> </html> diff --git a/webv4/root/js/common.js b/webv4/root/js/common.js index aa4ff8fa7d9de414bcf14b32b44480cf2b84525a..c6208c9e2476bd8050bfe88f3a394881971820a9 100644 --- a/webv4/root/js/common.js +++ b/webv4/root/js/common.js @@ -91,6 +91,44 @@ function registerEventListener(scope, callback, params) { }; } +document.addEventListener("DOMContentLoaded", function () { + // originally based on dark-mode-switch by Christian Oliff + var darkSwitch = document.getElementById("darkSwitch"); + if (darkSwitch) { + initTheme(); + darkSwitch.addEventListener("change", function (event) { + resetTheme(); + }); + + function initTheme() { + var darkThemeSelected; + if (localStorage.getItem("darkSwitch") !== null) { + darkThemeSelected = localStorage.getItem("darkSwitch") === "dark"; + } else { + darkThemeSelected = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; + } + darkSwitch.checked = darkThemeSelected; + if (darkThemeSelected) { + jQuery("body").addClass("dark") + } else { + jQuery("body").removeClass("dark"); + } + } + + function resetTheme() { + if (darkSwitch.checked) { + jQuery("body").addClass("dark"); + localStorage.setItem("darkSwitch", "dark"); + } else { + jQuery("body").removeClass("dark"); + localStorage.setItem("darkSwitch", "light"); + } + } + } + + jQuery('html').removeClass('hidden'); +}); + window.onload = function () { $('#button-logout').click(logout); diff --git a/webv4/sidebar/001-nodelist.xjs b/webv4/sidebar/001-nodelist.xjs index ed922ea45b322d4ce4dc9ce2fbc5f779f3617035..dee0e8b50050b16a60c0dd233236f3b54b8a0586 100644 --- a/webv4/sidebar/001-nodelist.xjs +++ b/webv4/sidebar/001-nodelist.xjs @@ -1,8 +1,5 @@ <h4><?xjs write(locale.strings.sidebar_node_list.label_title); ?></h4> <div id="sbbs-nodelist"></div> -<?xjs if (settings.nodelist_ibbs) { ?> - <div id="sbbsimsg-nodelist"></div> -<?xjs } ?> <script type="text/javascript"> @@ -10,30 +7,6 @@ const nch = '<?xjs write(settings.active_node_list ? locale.strings.sidebar_node_list.label_connection_column : locale.strings.sidebar_node_list.label_node_column) ?>'; const nll = <?xjs write(system.node_list.length); ?>; var niu = 0; - var ns = 0; - - function _sb_list_node(e) { - if (e.action === null || e.user === null) return; - $('#sbbs-nodelist-table').append( - '<tr>' + - '<th scope="row">' + - (anl ? e.connection : (typeof e.node == 'number' ? e.node + 1 : e.connection)) + - '</th>' + - '<td id="nodelist-' + e.node + '">' + - (e.user == '' ? e.status : ('<strong>' + e.user + '</strong> ' + e.action)) + - '</td>' + - '</tr>' - ); - if (e.user != '' && <?xjs write(user.alias != settings.guest); ?>) { - $('#nodelist-' + e.node).attr('title', '<?xjs write(locale.strings.sidebar_node_list.label_send_telegram); ?>'); - $('#nodelist-' + e.node).css('cursor', 'pointer'); - $('#nodelist-' + e.node).click(function () { - sendTelegram(e.user); - }); - } - if (typeof e.node == 'number' && e.user != '') niu++; - ns++; - } function _sb_nodelist(evt) { const data = JSON.parse(evt.data); @@ -49,30 +22,29 @@ '</table>' ); niu = 0; - ns = 0; - if (!anl) { - const nodes = Array(nll); - data.forEach(function (e) { - if (typeof e.node == 'number') { - nodes[e.node] = e; - } else { - nodes.push(e); // Web user - } - }); - for (var n = 0; n < nodes.length; n++) { - if (!nodes[n]) { - _sb_list_node({ node: n, user: '', status :'Waiting for call' }); - } else { - _sb_list_node(nodes[n], n); - } + var ns = data.reduce((a, c) => { + if (anl && (c.action === null || c.user === null)) return a; + $('#sbbs-nodelist-table').append( + '<tr>' + + '<th scope="row">' + + (anl ? c.connection : (typeof c.node == 'number' ? c.node : c.connection)) + + '</th>' + + `<td id="nodelist-${c.node}">` + + (c.user ? `<strong>${c.user}</strong> ${c.action}` : c.status) + + '</td>' + + '</tr>' + ); + if (c.user && <?xjs write(user.alias != settings.guest); ?>) { + $(`#nodelist-${c.node}`).attr('title', '<?xjs write(locale.strings.sidebar_node_list.label_send_telegram); ?>'); + $(`#nodelist-${c.node}`).css('cursor', 'pointer'); + $(`#nodelist-${c.node}`).click(() => sendTelegram(c.user)); } + if (typeof c.node == 'number' && c.user != '') niu++; + return a + 1; + }, 0); + if (ns) { $('#sbbs-nodelist').parent().removeClass('hidden'); - } else { - data.forEach(_sb_list_node); - if (ns) { - $('#sbbs-nodelist').parent().removeClass('hidden'); - $('#sbbs-nodelist').removeClass('hidden'); - } + $('#sbbs-nodelist').removeClass('hidden'); } // Spans with these classes are used in the 'system stats' sidebar module. // Update the nodes in use / available counters there. @@ -82,68 +54,4 @@ registerEventListener('nodelist', _sb_nodelist); - <?xjs if (settings.nodelist_ibbs) { ?> - function _send_ibbs_telegram(sys, host, user) { - async function send_ibbs_tg(evt) { - if (typeof evt !== 'undefined') evt.preventDefault(); - await v4_post('./api/sbbsimsg.ssjs', { - call: 'send_telegram', - username: user, - host: host, - message: $('#telegram').val() - }); - $('#popUpModal').modal('hide'); - }; - $('#popUpModalTitle').html('Send a telegram to ' + user + '@' + sys); - $('#popUpModalBody').html( - '<form id="ibbs-telegram-form">' - + '<input type="text" class="form-control" placeholder="My message" name="telegram" id="telegram">' - + '<input type="submit" value="submit" class="hidden">' - + '</form>' - ); - $('#popUpModalActionButton').show(); - $('#ibbs-telegram-form').submit(send_ibbs_tg); - $('#popUpModalActionButton').click(send_ibbs_tg); - $('#popUpModal').modal('show'); - } - - registerEventListener('sbbsimsg', function (e) { - const data = JSON.parse(e.data); - var users = 0; - $('#sbbsimsg-nodelist').addClass('hidden'); - $('#sbbsimsg-nodelist').html('<h4>Other Systems</h4>'); - Object.keys(data).forEach(function (e, i) { - if (!data[e].users.length) return; - const id = 'sbbsimsg-nodelist-' + i; - $('#sbbsimsg-nodelist').append( - '<table id="' + id + '" class="table table-condensed table-responsive table-striped">' - + '<tr><td><strong>' + e + '</strong></td></tr>' - + '</table>' - ); - data[e].users.forEach(function (ee, ii) { - const nid = id + '-' + ii; - $('#' + id).append( - '<tr>' - + '<td id="' + nid + '">' - + '<strong>' + ee.name + '</strong> ' + ee.action - + '</td>' - +'</tr>' - ); - if (<?xjs write(user.alias != settings.guest); ?>) { - $('#' + nid).click(function () { - _send_ibbs_telegram(e, data[e].host, ee.name); - }); - $('#' + nid).attr('title', '<?xjs write(locale.strings.sidebar_node_list.label_send_telegram); ?>'); - $('#' + nid).css('cursor', 'pointer'); - } - }); - users += data[e].users.length; - }); - if (users) { - $('#sbbsimsg-nodelist').removeClass('hidden'); - $('#sbbs-nodelist').parent().removeClass('hidden'); - } - }); - <?xjs } ?> - </script> diff --git a/xtrn/3rdp-install/carlton-doormania.js b/xtrn/3rdp-install/carlton-doormania.js index b43dba8a4e4b30d21e19ef4a8b8b0fe1c7149796..4178bc97a93f5e641fdcdf6865d4f84138163459 100644 --- a/xtrn/3rdp-install/carlton-doormania.js +++ b/xtrn/3rdp-install/carlton-doormania.js @@ -17,6 +17,8 @@ var lines = file.readAll(); file.close(); lines[4] = gamedir; +lines[5] = "MANIA.ASC"; +lines[6] = "MANIA.ANS"; writeln("Beginning node config generation..."); for(i = 0; i < system.nodes; i++) { diff --git a/xtrn/3rdp-install/foodfite-wilson.js b/xtrn/3rdp-install/foodfite-wilson.js index 1b74e28d2b5feb1dd13e1d1c1fcd7dce2f8421f0..38444ea2ccc463b06a6eea99a57cbd8c56816a96 100644 --- a/xtrn/3rdp-install/foodfite-wilson.js +++ b/xtrn/3rdp-install/foodfite-wilson.js @@ -14,6 +14,8 @@ file.close(); lines[0] = "LINE"; lines[1] = system.name; lines[2] = system.operator; +lines[5] = "bull99.ans"; +lines[6] = "bull99.asc"; lines[9] = "NONE"; lines[10] = ""; lines[11] = ""; diff --git a/xtrn/3rdp-install/sunrise-aceydeucey.js b/xtrn/3rdp-install/sunrise-aceydeucey.js index 0214c91436a0b7688a0f0a8e956249e467ade8b0..7ff3d94f1eece521d652f55e3bbbe147ddc77c32 100644 --- a/xtrn/3rdp-install/sunrise-aceydeucey.js +++ b/xtrn/3rdp-install/sunrise-aceydeucey.js @@ -28,10 +28,10 @@ var op = system.operator.split(" ", 2); lines[2] = op[0]; lines[3] = op[1]; -lines[7] = gamedir + "aceyd.ans"; -lines[8] = gamedir + "aceyd.asc"; -lines[9] = gamedir + "aceydhof.ans"; -lines[10] = gamedir + "aceydhof.asc"; +lines[7] = gamedir + "aceyd.asc"; +lines[8] = gamedir + "aceyd.ans"; +lines[9] = gamedir + "aceydhof.asc"; +lines[10] = gamedir + "aceydhof.ans"; lines[11] = "1"; for(i = 0; i < system.nodes; i++) { diff --git a/xtrn/3rdp-install/sunrise-baseballdice.js b/xtrn/3rdp-install/sunrise-baseballdice.js index 3074ea2e313659df0d5bd23ff7ce9b9c907303bb..48c258347f93e8d8de58458d0691957eac098ff9 100644 --- a/xtrn/3rdp-install/sunrise-baseballdice.js +++ b/xtrn/3rdp-install/sunrise-baseballdice.js @@ -27,10 +27,10 @@ var op = system.operator.split(" ", 2); lines[2] = op[0]; lines[3] = op[1]; -lines[7] = gamedir + "bbd.ans"; -lines[8] = gamedir + "bbd.asc"; -lines[9] = gamedir + "bbdhof.ans"; -lines[10] = gamedir + "bbdhof.asc"; +lines[7] = gamedir + "bbd.asc"; +lines[8] = gamedir + "bbd.ans"; +lines[9] = gamedir + "bbdhof.asc"; +lines[10] = gamedir + "bbdhof.ans"; lines[11] = "1"; for(i = 0; i < system.nodes; i++) { diff --git a/xtrn/3rdp-install/sunrise-betsyross.js b/xtrn/3rdp-install/sunrise-betsyross.js index 8906ed4a53531748d9fe18da0d94714bd93b617a..bc3e29d9c85f1158ffc0ed26ffd56b1cbbbdce7b 100644 --- a/xtrn/3rdp-install/sunrise-betsyross.js +++ b/xtrn/3rdp-install/sunrise-betsyross.js @@ -27,10 +27,10 @@ var op = system.operator.split(" ", 2); lines[2] = op[0]; lines[3] = op[1]; -lines[6] = gamedir + "betsy.ans"; lines[7] = gamedir + "betsy.asc"; -lines[8] = gamedir + "betsyhof.ans"; +lines[8] = gamedir + "betsy.ans"; lines[9] = gamedir + "betsyhof.asc"; +lines[10] = gamedir + "betsyhof.ans"; for(i = 0; i < system.nodes; i++) { var nodenum = i + 1; diff --git a/xtrn/3rdp-install/sunrise-blackjack.js b/xtrn/3rdp-install/sunrise-blackjack.js index 8143740f1a52f287319bfcb5af5c4064b9ef1f28..279e79a61b7c1f11a2674b183dd128a17f01cc22 100644 --- a/xtrn/3rdp-install/sunrise-blackjack.js +++ b/xtrn/3rdp-install/sunrise-blackjack.js @@ -27,10 +27,10 @@ var op = system.operator.split(" ", 2); lines[2] = op[0]; lines[3] = op[1]; -lines[7] = gamedir + "srbj.ans"; -lines[8] = gamedir + "srbj.asc"; -lines[9] = gamedir + "srbjhof.ans"; -lines[10] = gamedir + "srbjhof.asc"; +lines[7] = gamedir + "srbj.asc"; +lines[8] = gamedir + "srbj.ans"; +lines[9] = gamedir + "srbjhof.asc"; +lines[10] = gamedir + "srbjhof.ans"; lines[11] = "1"; for(i = 0; i < system.nodes; i++) { diff --git a/xtrn/3rdp-install/sunrise-boxdice.js b/xtrn/3rdp-install/sunrise-boxdice.js index 4b17e3f1f33ede57f8ae127224e523d5dea4bcc5..64798716de5a0a2190ce2fd9e3a25bfe6d7dbf19 100644 --- a/xtrn/3rdp-install/sunrise-boxdice.js +++ b/xtrn/3rdp-install/sunrise-boxdice.js @@ -27,10 +27,10 @@ var op = system.operator.split(" ", 2); lines[2] = op[0]; lines[3] = op[1]; -lines[7] = gamedir + "srboxd.ans"; -lines[8] = gamedir + "srboxd.asc"; -lines[9] = gamedir + "srboxd.ans"; -lines[10] = gamedir + "srboxd.asc"; +lines[7] = gamedir + "srboxd.asc"; +lines[8] = gamedir + "srboxd.ans"; +lines[9] = gamedir + "srboxdhf.asc"; +lines[10] = gamedir + "srboxdhf.ans"; for(i = 0; i < system.nodes; i++) { var nodenum = i + 1; diff --git a/xtrn/3rdp-install/sunrise-boxpoker.js b/xtrn/3rdp-install/sunrise-boxpoker.js index 262f09bcc4f42e56583d1735e77d3c90d76c8502..b4e58671089156155aec7afd0be4e3b52f77d2d6 100644 --- a/xtrn/3rdp-install/sunrise-boxpoker.js +++ b/xtrn/3rdp-install/sunrise-boxpoker.js @@ -27,10 +27,10 @@ var op = system.operator.split(" ", 2); lines[2] = op[0]; lines[3] = op[1]; -lines[8] = gamedir + "srboxpkr.ans"; -lines[9] = gamedir + "srboxpkr.asc"; -lines[10] = gamedir + "srboxpkr.ans"; -lines[11] = gamedir + "srboxpkr.asc"; +lines[8] = gamedir + "srboxpkr.asc"; +lines[9] = gamedir + "srboxpkr.ans"; +lines[10] = gamedir + "srboxpkh.asc"; +lines[11] = gamedir + "srboxpkh.ans"; for (i in system.node_list) { var nodenum = parseInt(i, 10) + 1; diff --git a/xtrn/3rdp-install/sunrise-concentration.js b/xtrn/3rdp-install/sunrise-concentration.js index e70023bbf816a689e18b03f52e8696ec15f1785a..2b76940f2a14e4fcfda0e54e4e552572ee0fa6d0 100644 --- a/xtrn/3rdp-install/sunrise-concentration.js +++ b/xtrn/3rdp-install/sunrise-concentration.js @@ -28,10 +28,10 @@ var op = system.operator.split(" ", 2); lines[2] = op[0]; lines[3] = op[1]; -lines[7] = gamedir + "srcon.ans"; -lines[8] = gamedir + "srcon.asc"; -lines[9] = gamedir + "srconhof.ans"; -lines[10] = gamedir + "srconhof.asc"; +lines[7] = gamedir + "srcon.asc"; +lines[8] = gamedir + "srcon.ans"; +lines[9] = gamedir + "srconhof.asc"; +lines[10] = gamedir + "srconhof.ans"; for (i in system.node_list) { var nodenum = parseInt(i, 10) + 1; diff --git a/xtrn/3rdp-install/sunrise-cribbage.js b/xtrn/3rdp-install/sunrise-cribbage.js index 8d7a2dc4b83a135ac10c332c3ceacadbd31821fa..72a1c090b68c3ddba99ff0bc961e2e0a48d6be7c 100644 --- a/xtrn/3rdp-install/sunrise-cribbage.js +++ b/xtrn/3rdp-install/sunrise-cribbage.js @@ -31,10 +31,10 @@ var op = system.operator.split(" ", 2); lines[2] = op[0]; lines[3] = op[1]; -lines[7] = gamedir + "scrib.ans"; -lines[8] = gamedir + "scrib.asc"; -lines[9] = gamedir + "scribhof.ans"; -lines[10] = gamedir + "scribhof.asc"; +lines[7] = gamedir + "scrib.asc"; +lines[8] = gamedir + "scrib.ans"; +lines[9] = gamedir + "scribhof.asc"; +lines[10] = gamedir + "scribhof.ans"; lines[11] = "1"; lines[38] = "G"; diff --git a/xtrn/3rdp-install/warlordsofthebalance.ini b/xtrn/3rdp-install/warlordsofthebalance.ini new file mode 100644 index 0000000000000000000000000000000000000000..37022fc23e4511f42464cd0e28d9bb43349c885a --- /dev/null +++ b/xtrn/3rdp-install/warlordsofthebalance.ini @@ -0,0 +1,39 @@ +Name: Warlords of the Balance +Desc: Multi-player fantasy wargame +By: Evan Taylor, BRC Electronics +Cats: Games +Subs: Fantasy,War +exe: INSTALL.EXE + +[md5:bbc09f57ee623d0ffe94b7c3bea189f0] +ver = 0.97e +url = ftp://archives.thebbs.org/door_games/doors_u-z/wotb97e.zip + +[prog:WOTB] +name = Warlords of the Balance +cmd = warlord.bat %f +ars = DOS +execution_ars = NOT GUEST +settings = XTRN_ANSI | XTRN_SH +type = XTRN_GAP + +[exec:../3rdp-install/warlordsofthebalance.js] +prompt = false +required = true + +[event:WOTBMAINT] +cmd = event.exe +name = Warlords of the Balance Maintenance +; all days +days = 127 +time = 0:00 + +[pre-eval:file_exists(startup_dir + 'WARGAME.EXE')] +prompt = false +required = true +fail = Extract the zip files (*.PI~) + +[pre-eval:file_exists(startup_dir + 'LORDS.DAT')] +prompt = false +required = true +fail = Run wargen.exe diff --git a/xtrn/3rdp-install/warlordsofthebalance.js b/xtrn/3rdp-install/warlordsofthebalance.js new file mode 100644 index 0000000000000000000000000000000000000000..8d2e8d3957c246c8d0af5f85c9502df4a641d2a5 --- /dev/null +++ b/xtrn/3rdp-install/warlordsofthebalance.js @@ -0,0 +1,29 @@ +"use strict"; + +writeln("Generating batch file..."); + +var i; + +var gamedir = fullpath(js.startup_dir); + +var conffilesrc = "warlord.bat"; +var cfg_filename = gamedir + conffilesrc; + +var lines = []; +lines[0] = "@echo off"; +lines[1] = "warlord %1"; +lines[2] = "wargame %1"; + +writeln("Creating " + cfg_filename); + +var file = new File(cfg_filename); +if (!file.open("w")) { + writeln("Error " + file.error + " opening " + file.name + " for writing"); + exit(1) +} +file.writeAll(lines); +file.close(); + +writeln("Batch file generation complete"); + +exit(0); \ No newline at end of file diff --git a/xtrn/DDMsgReader/DDMsgReader.js b/xtrn/DDMsgReader/DDMsgReader.js index 69c0ed2403009a4ff43f2c4bd6af6fa4d9df2bfc..182de5e43cc2805d052bedcf9de0ff4d07ec7737 100644 --- a/xtrn/DDMsgReader/DDMsgReader.js +++ b/xtrn/DDMsgReader/DDMsgReader.js @@ -1,5 +1,3 @@ -// $Id: DDMsgReader.js,v 1.143 2020/05/23 23:30:28 nightfox Exp $ - /* This is a message reader/lister door for Synchronet. Features include: * - Listing messages in the user's current message area with the ability to * navigate forwards & backwards through the list (and for ANSI users, a @@ -76,6 +74,9 @@ * 2020-12-01 Eric Oulashin Version 1.39 * When forwarding a message, added the ability to * optionally edit the message before forwarding it. + * 2021-01-31 Michael Long Version 1.40 + * Fixed left/right colors not being customizable on message + * list lightbar */ @@ -187,8 +188,8 @@ if (system.version_num < 31500) } // Reader version information -var READER_VERSION = "1.39"; -var READER_DATE = "2020-12-01"; +var READER_VERSION = "1.40"; +var READER_DATE = "2021-01-31"; // Keyboard key codes for displaying on the screen var UP_ARROW = ascii(24); @@ -7303,13 +7304,13 @@ function DigDistMsgReader_SetMsgListPauseTextAndLightbarHelpLine() var numLeft = Math.floor(numChars / 2); var numRight = numChars - numLeft; for (var i = 0; i < numLeft; ++i) - this.msgListLightbarModeHelpLine = "�" + this.msgListLightbarModeHelpLine; + this.msgListLightbarModeHelpLine = " " + this.msgListLightbarModeHelpLine; this.msgListLightbarModeHelpLine = "\1n" + this.colors.lightbarMsgListHelpLineBkgColor + this.msgListLightbarModeHelpLine; this.msgListLightbarModeHelpLine += "\1n" + this.colors.lightbarMsgListHelpLineBkgColor; for (var i = 0; i < numRight; ++i) - this.msgListLightbarModeHelpLine += "�"; + this.msgListLightbarModeHelpLine += ' '; } } // For the DigDistMsgReader Class: Sets the hotkey help line for the enhanced @@ -13358,8 +13359,7 @@ function DigDistMsgReader_ForwardMessage(pMsgHdr, pMsgBody) newMsgBody += "==================================\n\n"; newMsgBody += pMsgBody; - // New - Editing the message - // TODO: Ask whether to edit the message before forwarding it, + // Ask whether to edit the message before forwarding it, // and use console.editfile(filename) to edit it. if (!console.noyes("Edit the message before sending")) { diff --git a/xtrn/DDMsgReader/readme.txt b/xtrn/DDMsgReader/readme.txt index 46c3e6ce0fffc3598c2f4aaea86b67f9e419615b..bb1a3b80f7f0ffd015acab3d89d4ed0ac3d1dc71 100644 --- a/xtrn/DDMsgReader/readme.txt +++ b/xtrn/DDMsgReader/readme.txt @@ -1,6 +1,6 @@ Digital Distortion Message Reader - Version 1.39 - Release date: 2020-12-01 + Version 1.40 + Release date: 2021-12-31 by diff --git a/xtrn/DDMsgReader/revision_history.txt b/xtrn/DDMsgReader/revision_history.txt index 8dd53fa16c6b3dec162927e48508c174e5d6e8f6..edf89b56a5accc85e9637872b574ba6fec06446b 100644 --- a/xtrn/DDMsgReader/revision_history.txt +++ b/xtrn/DDMsgReader/revision_history.txt @@ -5,6 +5,8 @@ Revision History (change log) ============================= Version Date Description ------- ---- ----------- +1.40 2021-01-31 (Michael Long) Fixed left/right colors not being + customizable on message list lightbar 1.39 2020-12-01 When forwarding a message, added the ability to optinally edit the message before forwarding it. 1.38 2020-11-26 Bug fix: When forwarding a message, it now correctly sets diff --git a/xtrn/xtrnmenu/099-xtrnmenu-games.xjs b/xtrn/xtrnmenu/099-xtrnmenu-games.xjs new file mode 100644 index 0000000000000000000000000000000000000000..6f2a37dc9086f0aac5345d8e25fb70c4b9c75100 --- /dev/null +++ b/xtrn/xtrnmenu/099-xtrnmenu-games.xjs @@ -0,0 +1,209 @@ +<!--Games--> +<?xjs +/** + * Web Display for Custom External Program Menus + * by Michael Long mlong innerrealmbbs.us + * + * See wiki at http://wiki.synchro.net/module:xtrnmenu + */ + + load("ftelnethelper.js"); + load('sbbsdefs.js'); + load("xtrnmenulib.js"); + load(settings.web_lib + 'ftelnet.js'); + load(settings.web_lib + 'request.js'); + + var ExternalMenus = new ExternalMenus(); + + if (typeof settings.xtrn_blacklist === 'string') { + settings.xtrn_blacklist = settings.xtrn_blacklist.toLowerCase().split(','); + } else { + settings.xtrn_blacklist = []; + } + + var menuitems = []; +?> + +<style>.fTelnetStatusBar { display : none; }</style> + +<a name="fTelnet"></a> +<div id="fTelnetContainer" class="fTelnetContainer" style="margin-bottom:1em;"></div> + +<div id="xtrn-list" class="list-group"> + +<?xjs + var menuobj; + if (Request.get_param('type')) { + var target = Request.get_param('target'); + switch (Request.get_param('type')) { + case 'custommenu': + if (target !== undefined) { + menuobj = ExternalMenus.getMenu(target); + } + break; + case 'xtrnmenu': + if (target !== undefined) { + menuobj = ExternalMenus.getSectionMenu(target); + } + break; + case 'recentall': + menuobj = ExternalMenus.getSpecial(Request.get_param('type'), "Most Recent - All", target); + break; + case 'recentuser': + menuobj = ExternalMenus.getSpecial(Request.get_param('type'), "Most Recent - Yours", target); + break; + case 'mostlaunchedall': + menuobj = ExternalMenus.getSpecial(Request.get_param('type'), "Most Launched - All", target); + break; + case 'mostlauncheduser': + menuobj = ExternalMenus.getSpecial(Request.get_param('type'), "Most Launched - Yours", target); + break; + case 'longestrunall': + menuobj = ExternalMenus.getSpecial(Request.get_param('type'), "Longest Run - All", target); + break; + case 'longestrunuser': + menuobj = ExternalMenus.getSpecial(Request.get_param('type'), "Longest Run - Yours", target); + break; + case 'favorites': + menuobj = ExternalMenus.getFavorites("Favorites", target); + break; + default: + break; + } + } else { + if (Request.has_param('target')) { + menuobj = ExternalMenus.getMenu(Request.get_param('target')); + } else { + menuobj = ExternalMenus.getMenu('main'); + } + } + + if ((typeof menuobj === "undefined") || !menuobj + || (typeof menuobj.items === "undefined") || (menuobj.items.length < 1)) { + writeln("<h4>" + ExternalMenus.options.no_programs_msg + "</h4>"); + } else if (user.security.restrictions&UFLAG_X) { + writeln("<h4>" + options.restricted_user_msg + "</h4>"); + } else { + // ok to display menu + + writeln("<h4>" + menuobj.title + "</h4>"); + var menuitemsfiltered = ExternalMenus.getSortedItems(menuobj); + menuitemsfiltered.forEach(function (menuitem) { + if (settings.xtrn_blacklist.indexOf(menuitem.target.toLowerCase()) > -1) { + return; + } + switch (menuitem.type) { + case 'custommenu': + case 'xtrnmenu': + case 'xtrnprog': + case 'favorites': + case 'recentall': + case 'recentuser': + case 'mostlaunchedall': + case 'mostlauncheduser': + case 'longestrunuser': + case 'longestrunall': + menuitems.push({ + 'itemtitle': menuitem.title, + 'itemtype': menuitem.type, + 'itemtarget': menuitem.target, + 'stats': typeof menuitem.stats !== undefined ? menuitem.stats : null + }); + break; + default: + break; + } + }); + } +?> +</div> + +<script id="fTelnetScript" src="<?xjs write(get_url()); ?>"></script> +<script type="text/javascript"> + window.addEventListener('load', (event) => { + var wsp = <?xjs write(settings.wsp || GetWebSocketServicePort()); ?>; + var wssp = <?xjs write(settings.wssp || GetWebSocketServicePort(true)); ?>; + var Options = new fTelnetOptions(); + Options.BareLFtoCRLF = false; + Options.BitsPerSecond = 57600; + Options.ConnectionType = 'rlogin'; + Options.Emulation = 'ansi-bbs'; + Options.Enter = '\r'; + Options.Font = 'CP437'; + Options.ForceWss = false; + Options.Hostname = '<?xjs write(http_request.vhost); ?>'; + Options.LocalEcho = false; + Options.Port = location.protocol == 'https:' ? wssp : wsp; + Options.RLoginClientUsername = '<?xjs write(user.security.password); ?>'; + Options.RLoginServerUsername = '<?xjs write(user.alias); ?>'; + Options.ScreenColumns = 80; + Options.ScreenRows = 25; + Options.SplashScreen = Options.SplashScreen = '<?xjs write(get_splash()); ?>'; + Options.WebSocketUrlPath = '?Port=<?xjs write(GetRLoginPort()); ?>'; + window.fTelnet = new fTelnetClient('fTelnetContainer', Options); + fTelnet.OnConnectionClose = function () { + window.location.reload(); + }; + }); + + async function launchXtrn() { + var code = event.srcElement.id; + await v4_get('./api/system.ssjs?call=set-xtrn-intent&code=' + code); + fTelnet._Options.RLoginTerminalType = 'xtrn=' + code; + fTelnet.Connect(); + } + + var menuitems = <?xjs write(JSON.stringify(menuitems)); ?>; + var currentTarget = "<?xjs Request.write_param('target'); ?>"; + var currentType = "<?xjs Request.write_param('type'); ?>"; + var currentTitle = `<?xjs write(menuobj.title) ?>`; + if (currentTitle && !currentTarget) { + // main menu - store title for breadcrumb + sessionStorage.setItem("mainmenu", currentTitle); + } + var div = $('#xtrn-list'); + + menuitems.forEach(function (menuitem) { + var a = document.createElement('a'); + $(a).addClass("list-group-item"); + $(a).addClass("striped"); + a.text = menuitem.itemtitle; + + if (typeof menuitem.stats !== "undefined") { + var badge = document.createElement('span'); + $(badge).addClass('badge').text(menuitem.stats); + a.appendChild(badge); + } + + if (menuitem.itemtype == "xtrnprog") { + a.href = "#fTelnet"; + a.id = menuitem.itemtarget; + a.onclick = function () { launchXtrn(); }; + } else { + a.href = "/?page=<?xjs Request.write_param('page') ?>&type=" +menuitem.itemtype + + "&target=" + menuitem.itemtarget; + a.onclick = function () { + sessionStorage.setItem('prev:' + menuitem.itemtarget.toLowerCase(), '<?xjs Request.write_param('target')?>'); + sessionStorage.setItem('prevtype:' + menuitem.itemtarget.toLowerCase(), '<?xjs Request.write_param('type')?>'); + sessionStorage.setItem('prevtitle:' + menuitem.itemtarget.toLowerCase(), currentTitle); + }; + } + $(div).append(a); + }); + + // breadcrumb + var prevTarget = sessionStorage.getItem('prev:' + currentTarget.toLowerCase()); + var prevType = sessionStorage.getItem('prevtype:' + currentTarget.toLowerCase()); + var prevTitle = sessionStorage.getItem('prevtitle:' + currentTarget.toLowerCase()); + if (prevType && prevTarget && prevTitle) { + $('#xtrn-list').prepend('<ol class="breadcrumb"><a href="/?page=<?xjs Request.write_param('page') ?>&type=' + prevType + + '&target=' + prevTarget + '">' + prevTitle + '</a></ol>'); + } else if (currentTarget) { + // level 2, not main menu + var mainmenuTitle = sessionStorage.getItem('mainmenu'); + if (!mainmenuTitle) { + mainmenuTitle = 'Games'; + } + $('#xtrn-list').prepend('<ol class="breadcrumb"><a href="/?page=<?xjs Request.write_param('page') ?>">' + mainmenuTitle + '</a></ol>'); + } +</script> diff --git a/xtrn/xtrnmenu/099-xtrnmenu-gamestats.xjs b/xtrn/xtrnmenu/099-xtrnmenu-gamestats.xjs new file mode 100644 index 0000000000000000000000000000000000000000..240f19c0aecc020414075058d367e8f56771f480 --- /dev/null +++ b/xtrn/xtrnmenu/099-xtrnmenu-gamestats.xjs @@ -0,0 +1,54 @@ +<!--Game Stats--> +<div class="list-group"> + +<h1>Game Stats</h1> + +<?xjs + load('sbbsdefs.js'); + load("xtrnmenulib.js"); + + var ExternalMenus = new ExternalMenus(); + + if (typeof settings.xtrn_blacklist === 'string') { + settings.xtrn_blacklist = settings.xtrn_blacklist.toLowerCase().split(','); + } else { + settings.xtrn_blacklist = []; + } + + // Edit these lines as needed to adjust the page + gameStats("recentall", "Most Recent", 10); + gameStats("mostlaunchedall", "Top 15 Most Launched", 15); + gameStats("longestrunall", "Top 15 Longest Run", 15); + + function gameStats(menutype, title, maxitems) { + var menuobj = ExternalMenus.getSpecial(menutype, title); + +?> +<div class="row list-group-item" style="background-color: #993399; color: #FFF"> + <div class="col-sm-12"><h3><?xjs write(title) ?></h3></div> +</div> + +<?xjs + if ((typeof menuobj.items !== "undefined") || (menuobj.items.length > 0)) { + var i = 0; + menuobj.items.some(function (menuitem) { + if (menuitem.type != "xtrnprog") { + return; + } + + if (settings.xtrn_blacklist.indexOf(menuitem.target.toLowerCase()) > -1) return; + + if (++i > maxitems) return; +?> + +<div class="row list-group-item striped"> + <div class="col-sm-2"><span class="badge badge-inverse"><?xjs write(menuitem.stats) ?></span></div> + <div class="col-sm-10"><?xjs write(menuitem.title) ?></div> +</div> + +<?xjs + }); + } + } +?> +</div>