Skip to content
Snippets Groups Projects
Commit f57d4abe authored by Rob Swindell's avatar Rob Swindell :speech_balloon:
Browse files

Merge branch 'externalmenus' into 'master'

Custom external program menus mod. Allows making custom external menus that...

See merge request !41
parents 596209be b4ac6498
No related branches found
No related tags found
2 merge requests!463MRC mods by Codefenix (2024-10-20),!41Custom external program menus mod. Allows making custom external menus that...
/**
* Custom External Program Menu Library for Custom External Program Menus
* by Michael Long mlong innerrealmbbs.us
*
* This provides common functionality for retrieving menus used by
* the loadable module xtrnmenu.js and by the web interface
* 099-xtrnmenu-games.xjs
*/
"use strict";
load("sbbsdefs.js", "K_NONE");
/* text.dat entries */
require("text.js", "XtrnProgLstFmt");
function ExternalMenus() {
this.options = {};
this.xtrn_custommenu_options = {};
this.menuconfig = {};
this.getOptions();
this.getMenuConfig();
}
ExternalMenus.prototype.getMenuConfig = function() {
var config_file = new File(system.ctrl_dir + "xtrnmenu.cfg");
var config_src;
this.menuconfig = undefined;
if (config_file.open('r+')) {
config_src = config_file.read();
config_file.close();
}
if (typeof config_src !== "undefined") {
this.menuconfig = JSON.parse(config_src.toString());
}
}
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.singlecolumn_margin == undefined)
this.options.singlecolumn_margin = 7;
if (typeof bbs !== "undefined") {
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;
}
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 its a custom menu, then override with custommenu_options
if (menutype == 'custommenu') {
if (typeof this.xtrn_custommenu_options.multicolumn_fmt !== "undefined") {
this.options.multicolumn_fmt = this.xtrn_custommenu_options.multicolumn_fmt;
} else {
// 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 ";
}
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;
}
// 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)) {
for (var m in menuoptions) {
this.options[m] = menuoptions[m];
}
}
}
// 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";
}
return this.options;
}
// return a custom menu object
ExternalMenus.prototype.getMenu = function(menuid) {
// grab the specified menu, or get the main menu
if ((typeof menuid === "undefined") || !menuid) {
menuid = "main";
} 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) {
menu = indmenu;
}
});
}
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
});
i++;
}
});
menu = {
"id": "main",
"title": "Main Menu",
"items": menuitems
};
}
return menu;
}
// 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;
xtrn_area.sec_list.some(function (sec) {
if (sec.code.toLowerCase() == menuid.toLowerCase()) {
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
});
i++;
});
if (menuitems.length > 0) {
menu = {
'id': menuid,
'title': title,
'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) {
switch (menuobj.items[i].type) {
case 'xtrnmenu':
for (j in xtrn_area.sec_list) {
if (xtrn_area.sec_list[j].code.toUpperCase() == menuobj.items[i].target.toUpperCase()) {
if (xtrn_area.sec_list[j].can_access) {
menuitemsfiltered.push(menuobj.items[i]);
}
}
}
break;
case 'xtrnprog':
for (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.toUpperCase() == menuobj.items[i].target.toUpperCase()) {
if (xtrn_area.sec_list[j].prog_list[k].can_access) {
menuitemsfiltered.push(menuobj.items[i]);
}
}
}
}
break;
case 'custommenu':
default:
if ((typeof menuobj.items[i].access_string === "undefined") || !menuobj.items[i].access_string) {
// no access string defined, everyone gets access
menuitemsfiltered.push(menuobj.items[i]);
} else {
if (user.compare_ars(menuobj.items[i].access_string)) {
// they have access
menuitemsfiltered.push(menuobj.items[i]);
}
}
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
// titles, we need to sort by title first before assigning the sequential numbers
var sortind = 0;
// make sure we only use the next available number for automatic assignment
for (i in menuitemsfiltered) {
if (!isNaN(menuitemsfiltered[i].input)) {
if (menuitemsfiltered[i].input > sortind) {
sortind = menuitemsfiltered[i].input;
}
}
}
sortind++;
if (sort_type == "key") {
for (i in menuitemsfiltered) {
if (!menuitemsfiltered[i].input) {
menuitemsfiltered[i].input = sortind;
sortind++;
}
}
}
if (sort_type) {
switch (sort_type) {
case "key":
menuitemsfiltered.sort(this.sort_by_input);
break;
default:
case "title":
case "name":
menuitemsfiltered.sort(this.sort_by_title);
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
// on the sort_type key
for (i in menuitemsfiltered) {
if ((sort_type !== "key") && !menuitemsfiltered[i].input) {
menuitemsfiltered[i].input = sortind;
sortind++;
}
}
return menuitemsfiltered;
}
// Original sort function used by external_program_menu and external_section_menu
ExternalMenus.prototype.sort_by_name = function(a, b) {
if(a.name.toLowerCase()>b.name.toLowerCase()) return 1;
if(a.name.toLowerCase()<b.name.toLowerCase()) return -1;
return 0;
}
// Sort by title - used by external_section_menu_custom
ExternalMenus.prototype.sort_by_title = function(a, b) {
if (a.title.toLowerCase() < b.title.toLowerCase()) {
return -1;
}
if (a.title.toLowerCase() > b.title.toLowerCase()) {
return 1;
}
return 0;
}
// Sort by input key - used by external_section_menu_custom
ExternalMenus.prototype.sort_by_input = function(a, b) {
if (a.input.toString().toLowerCase() < b.input.toString().toLowerCase()) {
return -1;
}
if (a.input.toString().toLowerCase() > b.input.toString().toLowerCase()) {
return 1;
}
return 0;
}
/**
* Xtrn Menu Mod
* Custom External Program Menus
* by Michael Long mlong innerrealmbbs.us
*
* This is the loadable module that displays the custom external menus
* in terminal server (telnet/rlogin/ssh)
*
* To jump to a specific menu, pass the ID as an argument
*
* To set options, add to modopts.ini [xtrnmenu]
*
* See instructions at http://wiki.synchro.net/module:xtrnmenu
*/
"use strict";
require("sbbsdefs.js", "K_NONE");
load("xtrnmenulib.js");
var options, xsec = -1;
//// Main
var ExternalMenus = new ExternalMenus();
const menuconfig = ExternalMenus.menuconfig;
{
var i,j;
for(i in argv) {
for(j in xtrn_area.sec_list) {
if(argv[i].toLowerCase()==xtrn_area.sec_list[j].code)
xsec=j;
}
}
}
if (xsec > -1) {
// if its the id of a standard section menu, send it to the
// stock menu
js.exec("xtrn_sec.js", {}, 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]);
} else {
// main custom menu
external_section_menu_custom();
}
// Renders the top-level external menu
function external_section_menu_custom(menuid)
{
var i, menucheck, menuobj, item_multicolumn_fmt, item_singlecolumn_fmt,
cost, multicolumn, menuitemsfiltered = [];
var validkeys = []; // valid chars on menu selection
var keymax = 0; // max integer allowed on menu selection
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;
while (bbs.online) {
console.aborted = false;
if (typeof menuobj === "undefined") {
doerror(options.custom_menu_not_found_msg.replace('%MENUID%', 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;
}
var keyin;
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);
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)) {
// 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);
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++) {
cost = "";
if (menuitemsfiltered[i].type == "xtrnprog") {
// 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) {
if (intCheck > keymax) {
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;
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
);
if (multicolumn) {
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;
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
);
} else {
write(options.multicolumn_separator);
}
j++;
}
console.crlf();
}
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');
keyin = console.getkeys(validkeys, keymax, K_NONE);
keyin = keyin.toString().toLowerCase();
if (keyin) {
// q for quit
if (keyin == "q") {
console.clear();
break;
}
menuitemsfiltered.some(function (menuitemfiltered) {
var menutarget = menuitemfiltered.target.toLowerCase();
var menuinput = menuitemfiltered.input.toString().toLowerCase();
if (menuinput == keyin) {
switch (menuitemfiltered.type) {
// custom menu, defined in xtrnmenu.json
case 'custommenu':
external_section_menu_custom(menutarget);
return true;
// external program section
case 'xtrnmenu':
js.exec("xtrn_sec.js", {}, menutarget);
//js.exec(js.exec_dir + 'xtrn_sec.js ' + 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;
} //switch
} // if menu item matched keyin
}); // foreach menu item
} // if keyin
} // main bbs.online loop
}
// Display error message to console and save to log
function doerror(msg)
{
console.crlf();
log(LOG_ERR, msg);
console.putmsg('\x01+\x01h\x01r' +msg + ". The sysop has been notified." + '\x01-\r\n');
}
"use strict"
/**
* Menu editor for Custom External Program Menus
* by Michael Long mlong innerrealmbbs.us
*
* This edits the file xtrnmenu.cfg
*/
load("sbbsdefs.js");
load("uifcdefs.js");
var log = function(msg) {
var f = new File("uifc.log");
f.open("a");
f.writeln(system.timestr() + ": " + msg);
f.close();
}
/**
* Write the menus out to a file
*/
var saveMenus = function() {
var filename = system.ctrl_dir + "xtrnmenu.cfg";
file_backup(filename, 5);
var config_file = new File(filename);
if (!config_file.open('w+')) {
uifc.msg("ERROR: Could not write to " + filename);
return;
}
config_file.write(JSON.stringify(menuconfig, null, ' '));
config_file.close();
uifc.msg("Config Saved");
}
/**
* Edit a custom menu
* @param menuid
*/
var editMenu = function(menuid) {
var menuindex, menu, menuindex, selection, selection2, editproperty;
var last = 0;
var selections = [], displayoptions = [], displayoptionids = [];
var menusize = menuconfig.menus.length;
// new menu but no code given, make one
if ((typeof menuid === "undefined") || (!menuid)) {
menuid = time();
}
// look for existing menu
for (var i in menuconfig.menus) {
if (menuconfig.menus[i].id == menuid) {
menu = menuconfig.menus[i];
menuindex = i;
}
}
if (typeof menu === "undefined") {
menuindex = menusize;
menuconfig.menus[menuindex] = {
'id': menuid,
'title': "New Generated Menu " + menuid,
"sort_type": "title",
'items': []
};
menu = menuconfig.menus[menuindex];
}
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.)");
selections = [];
for (var j in menu.items) {
selections.push(menu.items.index);
}
displayoptions = [];
displayoptionids = [];
// setup display menu
displayoptions.push(format("%23s: %s", "id",
("id" in menu ? menu.id : time())));
displayoptionids.push("id");
displayoptions.push(format("%23s: %s", "title",
("title" in menu ? menu.title : "")));
displayoptionids.push("title");
displayoptions.push(format("%23s: %s", "sort_type",
("sort_type" in menu ? menu.sort_type : "(default)")));
displayoptionids.push("sort_type");
displayoptions.push(format("%23s: %s", "Edit Items", "[...]"));
displayoptionids.push("items");
selection = uifc.list(WIN_ORG|WIN_MID|WIN_ACT|WIN_ESC, 0, 0, 0, last, last,
menu.title + ": Options", displayoptions);
if (selection < 0) {
// escape key
break;
}
editproperty = displayoptionids[selection];
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'.");
var selection2 = uifc.input(WIN_MID, "Menu ID", menu.id, 50, K_EDIT);
if ((selection2 < 0) || (selection2 == null)) {
// escape key
break;
}
menu.id = selection2;
break;
case 'title':
uifc.help_text = word_wrap("Title for the menu, to be shown at the top of the menu");
selection2 = uifc.input(WIN_MID, "Menu Title", menu.title, 255, K_EDIT);
if ((selection2 < 0) || (selection2 == null)) {
// escape key
break;
}
menu.title = selection2;
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)");
switch (uifc.list(WIN_ORG | WIN_MID, "Sort Type", ["key", "title", "none"])) {
case 0:
menu.sort_type = "key";
break;
case 1:
menu.sort_type = "title";
break;
case 2:
delete menu.sort_type;
break;
default:
//esc key
break;
}
case 'items':
editItems(menuid);
break;
default:
// this isn't supposed to happen
uifc.msg("Unknown option");
break;
}
last = Math.max(selection, 0);
}
}
/**
* Edit menu items in a menu
* @param menuid
*/
var editItems = function(menuid) {
var menuindex, menu, selection, selection2, keyused, items = [], itemids = [];
var i, last = 0;
// cur bar top left width
var ctxm = new uifc.list.CTX(0, 0, 0, 0, 0);
if (typeof menuid === "undefined") {
uifc.msg("Menu could not be found");
return;
} else {
for (i in menuconfig.menus) {
if (menuconfig.menus[i].id == menuid) {
menu = menuconfig.menus[i];
menuindex = i;
}
}
}
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");
while(1) {
items = [];
itemids = [];
for(i in menu.items) {
items.push(format(
"%6s %10s %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,
menu.title + ": Items",
items,
ctxm
);
if (selection == -1) {
// esc key
break;
}
if ((selection & MSK_ON) == MSK_DEL) {
// delete item
selection &= MSK_OFF;
//renumber array so there are no gaps
var menuitems2 = [];
for (var i in menu.items) {
if (i != itemids[selection]) {
menuitems2.push(menu.items[i]);
}
}
menu.items = menuitems2;
} else if (((selection & MSK_ON) == MSK_INS)) {
// new item from INSERT KEY
editItem(menuid, null);
} else if ((selection & MSK_ON) == MSK_COPY) {
// copy item
selection &= MSK_OFF;
copyitem = JSON.parse(JSON.stringify(menu.items[itemids[selection]])); // make copy
} else if ((selection & MSK_ON) == MSK_CUT) {
// cut item
selection &= MSK_OFF;
copyitem = menu.items[itemids[selection]];
//renumber array so there are no gaps
var menuitems2 = [];
for (var i in menu.items) {
if (i != itemids[selection]) {
menuitems2.push(menu.items[i]);
}
}
menu.items = menuitems2;
} else if ((selection & MSK_ON) == MSK_PASTE) {
// paste item
selection &= MSK_OFF;
var oktopaste = true;
// only paste if there is an item copied
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 !== "")) {
oktopaste = true;
while(1) {
selection2 = uifc.input(WIN_MID, "Enter New Input Key", "", 3, K_EDIT);
if ((selection2 == -1) || (selection2 === "") || (selection2 === null)) {
// escape key
copyitem.input = null;
break;
}
if (selection2 || selection2 === 0) {
selection2 = selection2.toUpperCase();
keyused = false;
for (var j in menu.items) {
if (menu.items[j].input && (menu.items[j].input.toUpperCase() == selection2)) {
keyused = true;
}
}
if (keyused) {
uifc.msg("This input key is alread used for another item");
oktopaste = false;
} else {
copyitem.input = selection2;
oktopaste = true;
break;
}
}
}
copyitem.input = selection2;
}
}
if ((oktopaste) || (copyitem.input === "null") || (copyitem.input === "")) {
var menuitems2 = [];
for (i in menu.items) {
menuitems2.push(menu.items[i]);
// paste copied item after selected item
if (i == itemids[selection]) {
menuitems2.push(copyitem);
ctxm.cur = i-1;
}
}
menu.items = menuitems2;
}
}
} else if (selection >= menu.items.length) {
// new item from blank line
editItem(menuid, null);
} else {
editItem(menuid, itemids[selection]);
}
last = Math.max(selection, 0);
}
}
/**
* Edit a specific menu item entry
* @param menuid
* @param itemindex
*/
var editItem = function(menuid, itemindex) {
var menu, menuindex, item;
var keyused, selection, selection2, i, last = 0;
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;
} else {
for (i in menuconfig.menus) {
if (menuconfig.menus[i].id == menuid) {
menu = menuconfig.menus[i];
menuindex = i;
}
}
}
if (typeof menu.items[itemindex] === "undefined") {
// new item
menu.items.push({
"input": null,
"title": "New Item " + time(),
"type": null,
"target": null,
"access_string": null,
});
itemindex = menu.items.length - 1;
present_select_targettype(menu.items[itemindex]);
}
item = menu.items[itemindex];
var itemctx = new uifc.list.CTX(0,0,0,0,0);
while(1) {
displayoptions = [];
displayoptionids = [];
// setup display menu
displayoptions.push(format("%23s: %s", "input",
(("input" in item) && (item.input !== null) && (item.input !== "") ? item.input : "(auto)")));
displayoptionids.push("input");
displayoptions.push(format("%23s: %s", "title",
("title" in item ? item.title : "")));
displayoptionids.push("title");
displayoptions.push(format("%23s: %s", "type",
("type" in item ? item.type : "")));
displayoptionids.push("type");
displayoptions.push(format("%23s: %s", "target",
("target" in item ? item.target : "")));
displayoptionids.push("target");
if (item.type == "custommenu") {
displayoptions.push(format("%23s: %s", "access_string",
("access_string" in item ? item.access_string : "(default)")));
displayoptionids.push("access_string");
}
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 (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;
}
} else {
// leave menu
break;
}
}
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.");
selection2 = uifc.input(WIN_MID, "Input Key", item.input, 3, K_EDIT);
if ((selection2 < 0) || (selection2 == null)) {
// escape key
break;
}
if (selection2 !== "") {
selection2 = selection2.toUpperCase();
keyused = false;
for (i in menu.items) {
if ((menu.items[i].input === null) || (menu.items[i].input === "")) {
// continue here as toUpperCase would break it, and they don't need to match
// anyway because you can have multiple auto-assigned input items
continue;
}
if ((menu.items[i].input.toUpperCase() == selection2) && (i != itemindex)) {
keyused = true;
}
}
if (keyused) {
uifc.msg("This input key is already used by another item.");
} else {
item.input = selection2;
}
} else {
// save blank
item.input = selection2;
}
break;
case 'title':
uifc.help_text = word_wrap("The menu item title.");
selection2 = uifc.input(WIN_MID, "Title", item.title, 255, K_EDIT);
if ((selection2 < 0) || (selection2 == null)) {
// escape key
break;
}
if (!selection2 && selection2 !== 0) {
uifc.msg("Title is required.");
} else {
item.title = selection2;
}
break;
case 'type':
present_select_targettype(item);
break;
case 'target':
present_select_target(item);
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");
selection2 = uifc.input(WIN_MID, "Access String", item.access_string, 255, K_EDIT);
if ((selection2 < 0) || (selection2 == null)) {
// escape key
break;
}
item.access_string = selection2;
break;
}
last = Math.max(selection, 0);
}
}
function present_select_targettype(item)
{
uifc.help_text = word_wrap(
"This is the type of target this item points to.\r\n\r\n"
+ "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)");
var targetypectx = uifc.list.CTX(0, 0, 0, 0, 0);
if (typeof item.type !== "undefined") {
switch (item.type) {
case 'custommenu':
targetypectx.cur = 0;
targetypectx.bar = 0;
break;
case 'xtrnmenu':
targetypectx.cur = 1;
targetypectx.bar = 1;
break;
case 'xtrnprog':
targetypectx.cur = 2;
targetypectx.bar = 2;
break;
}
}
switch (uifc.list(WIN_ORG | WIN_MID | WIN_SAV,
"Target Type", ["custommenu", "xtrnmenu", "xtrnprog"], targetypectx)) {
case 0:
item.type = "custommenu";
break;
case 1:
item.type = "xtrnmenu"
break;
case 2:
item.type = "xtrnprog";
break;
default:
// includes escape key
break;
}
// convienence... enter target selection
present_select_target(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);
var custommenuitems = [];
var custommenuitemsids = [];
var custommenunames = [];
var selection2;
switch (item.type) {
case "custommenu":
// present list of custom menus
for (i in menuconfig.menus) {
custommenuitems.push(format("%23s: %s", menuconfig.menus[i].id, menuconfig.menus[i].title));
custommenuitemsids.push(menuconfig.menus[i].id);
custommenunames.push(menuconfig.menus[i].title);
}
if ((typeof item.target !== "undefined") && item.target) {
for (var p in custommenuitemsids) {
if (custommenuitemsids[p] == item.target) {
targetctx.cur = p;
targetctx.bar = p;
}
}
}
selection2 = uifc.list(WIN_ORG | WIN_MID | WIN_SAV, "Target", custommenuitems, targetctx);
if ((selection2 < 0) || (selection2 == null)) {
// escape key
break;
}
item.target = custommenuitemsids[selection2];
while(1) {
if (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
}
break;
}
break;
case "xtrnmenu":
// present list of external program sections
var seclist = [];
for (i in xtrn_area.sec_list) {
seclist.push({ code: xtrn_area.sec_list[i].code, name: xtrn_area.sec_list[i].name});
};
seclist.sort(sort_by_code);
for (i in seclist) {
custommenuitems.push(format("%23s: %s", seclist[i].code, seclist[i].name));
custommenuitemsids.push(seclist[i].code);
custommenunames.push(seclist[i].name);
}
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;
}
}
}
selection2 = uifc.list(WIN_ORG | WIN_MID | WIN_SAV, "Target", custommenuitems, targetctx);
if ((selection2 < 0) || (selection2 == null)) {
// escape key
break;
}
item.target = custommenuitemsids[selection2];
while(1) {
if (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
}
break;
}
break;
case "xtrnprog":
// present list of external programs
// create sorted list
var proglist = [];
for (i in xtrn_area.prog) {
proglist.push({ code: xtrn_area.prog[i].code, name: xtrn_area.prog[i].name});
};
proglist.sort(sort_by_code);
for (i in proglist) {
custommenuitems.push(format("%23s: %s", proglist[i].code, proglist[i].name));
custommenuitemsids.push(proglist[i].code);
custommenunames.push(proglist[i].name);
}
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;
}
}
}
selection2 = uifc.list(WIN_ORG | WIN_MID | WIN_SAV, "Target", custommenuitems, targetctx);
if ((selection2 < 0) || (selection2 == null)) {
// escape key
break;
}
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) {
item.title = custommenunames[selection2]; // for external program, change title to program name
}
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;
}
}
function sort_by_name(a, b)
{
if (a.name.toLowerCase() > b.name.toLowerCase()) return 1;
if (a.name.toLowerCase() < b.name.toLowerCase()) return -1;
return 0;
}
function sort_by_code(a, b)
{
if (a.code.toLowerCase() > b.code.toLowerCase()) return 1;
if (a.code.toLowerCase() < b.code.toLowerCase()) return -1;
return 0;
}
function sort_by_id(a, b)
{
if (a.id.toLowerCase() > b.id.toLowerCase()) return 1;
if (a.id.toLowerCase() < b.id.toLowerCase()) return -1;
return 0;
}
// MAIN
try {
var menuconfig = {};
var copyitem = {}; // for menu item copy/paste
var config_file = new File(file_cfgname(system.ctrl_dir, "xtrnmenu.cfg"));
if (config_file.open('r+')) {
var config_src = config_file.read();
try {
menuconfig = JSON.parse(config_src.toString());
if (!menuconfig) {
writeln("ERROR! Could not parse xtrnmenu.cfg. JSON may be invalid.");
exit();
}
} catch (e) {
writeln("ERROR! Could not parse xtrnmenu.cfg. JSON may be invalid. " + e.toString());
exit();
}
}
config_file.close();
if (typeof menuconfig.menus === "undefined") {
menuconfig.menus = [];
}
uifc.init("Enhanced External Program Menus Configurator");
uifc.lightbar_color = 120;
uifc.background_color = 21;
uifc.frame_color = 15;
js.on_exit("if (uifc.initialized) uifc.bail()");
// cur bar top left width
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.");
// no menus or no main menu
var mainmenufound = false;
for (var m in menuconfig.menus) {
if (menuconfig.menus[m].id.toLowerCase() == "main") {
mainmenufound = true;
}
}
if (!mainmenufound || (menuconfig.menus.length < 1)) {
uifc.msg("No menus defined and/or missing the main menu. Setting up now.");
editMenu("main");
}
// menus is array of menuconfig menu ids
var menus = [];
var menuTitles = [];
menuconfig.menus.sort(sort_by_id);
for (var m in menuconfig.menus) {
menus.push(menuconfig.menus[m].id);
menuTitles.push(format("%20s: %s", menuconfig.menus[m].id, menuconfig.menus[m].title));
}
menuTitles.push(format("%20s %s", '', "[Save Config Without Exit]"));
// WIN_ORG = original menu, destroy valid screen area
// WIN_MID = place window in middle of screen
// WIN_XTR = add extra line at end for inserting at end
// WIN_DEL = allow user to use delete key
// WIN_ACT = menu remains active after a selection
// WIN_ESC = screen is active when escape is hit
// WIN_INS = allow user to use insert key
var selection = uifc.list(
WIN_ORG|WIN_MID|WIN_XTR|WIN_DEL|WIN_ACT|WIN_ESC|WIN_INS|WIN_SAV,
"Enhanced External Menus",
menuTitles,
ctx
);
if (selection == (menuTitles.length - 1)) {
// last item - save config
saveMenus();
uifc.pop("Config saved.");
} else if (selection < 0) {
while (1) {
var ret = uifc.list(WIN_ORG | WIN_MID, "Save Changes Before Exit?", ["Yes", "No", "Cancel"]);
if (ret == 0) {
saveMenus();
exit();
} else if (ret == 1) {
// no - exit
exit();
} else {
// cancel
break;
}
}
} else if ((selection & MSK_ON) == MSK_DEL) {
selection &= MSK_OFF;
for (var m in menuconfig.menus) {
if (menuconfig.menus[m].id == menus[selection]) {
delete menuconfig.menus[m];
}
}
//selection--;
} else if (((selection & MSK_ON) == MSK_INS) || (selection >= menuconfig.menus.length)) {
// new menu
var newid = uifc.input(
WIN_MID,
"Enter a short unique id for the menu",
"",
0
);
if (typeof newid !== "undefined") {
var menufound = false;
for (var mf in menuconfig.menus) {
if (menuconfig.menus[mf].id == newid) {
menufound = true;
}
}
if (menufound) {
uifc.msg("That ID is already in use. Please choose another.");
} else {
editMenu(newid);
}
}
} else {
editMenu(menuconfig.menus[selection].id);
}
}
} catch(err) {
if ((typeof uifc !== "undefined") && uifc.initialized) {
uifc.bail();
}
writeln(err);
log(err);
if (typeof console !== "undefined") {
console.pause();
}
}
<!--HIDDEN:Games-->
<?xjs
/**
* Web Display for Custom External Program Menus
* by Michael Long mlong innerrealmbbs.us
*
* See wiki at http://wiki.synchro.net/module:xtrnmenumod
*/
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') == 'xtrnmenu') && Request.has_param('target')) {
menuobj = ExternalMenus.getSectionMenu(Request.get_param('target'));
} 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;
}
menuitems.push({
'itemtitle': menuitem.title,
'itemtype': menuitem.type,
'itemtarget': menuitem.target
});
});
}
?>
</div>
<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();
};
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 (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>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment