commit d3e1a0c84cdc68d39dc5012c7e68b9b31a7601f1 Author: Deon George Date: Thu Oct 3 14:08:48 2019 +1000 Initial Revision diff --git a/mods/ansitex.js b/mods/ansitex.js new file mode 100644 index 0000000..7a23262 --- /dev/null +++ b/mods/ansitex.js @@ -0,0 +1,264 @@ +// Load many SBBS definitions +load('sbbsdefs.js'); +// Load text.dat defintions +load('text.js'); + +// Enable to manipulate the ANSI terminal +ansiterm = load({},'ansiterm_lib.js'); + +// Ansitex specific includes +load('texdefs.js'); +load('texfuncs.js'); + +while(bbs.online) { + var mode = null; // Initial mode + var next_page = { frame: 1,index: 'a'}; // Start Frame + var action = ACTION_GOTO; // Initial action + var inkey_timeout = 0; // Timeout waiting for input @todo required? check if idle timetout occurs + var fo = null; // Current Frame + ansiterm.send('ext_mode','clear','cursor'); + + while (action != ACTION_TERMINATE) { + bbs.nodesync(false); // @todo Stop the display of telegrams + read = console.inkey(K_NONE,inkey_timeout); + inkey_timeout = 60000; // Set our key timeout to 60s + log(LOG_DEBUG,'READ: ['+read+']'); + + log(LOG_DEBUG,'MODE START: ['+read+']'); + switch (mode) { + case false: + log(LOG_DEBUG,'- false: ['+read+']'); + cmd=''; + + switch (read) { + case '*': action = ACTION_STAR + break; + + // Frame Routing + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (fo.key[read]) { + next_page = { frame: fo.key[read] }; + action = ACTION_GOTO; + log(LOG_DEBUG,'- false: Key ['+read+'] ['+pageStr(next_page)+']'); + + } else { + sendBaseline(ERR_ROUTE,false); + } + + break; +case 'Q': +bbs.hangup(); +exit(); + } + + break; + + // Command input on bottom line + case MODE_BL: + log(LOG_DEBUG,'- MODE_BL: ['+read+']'); + if (read.match(/[0-9]/)) { + cmd += read; + console.write(read); + } + + // @todo check if CTRL_H is required? + if ((read == CTRL_H || read == KEY_DEL) && cmd.length) { + console.backspace(); + cmd = cmd.substring(0,cmd.length-1); + } + + if (cmd == '00') { + action = ACTION_RELOAD; + cmd = ''; + cursorOff(); + break; + } + + // Edit specific frame + if (cmd.match(/^04/) && read.match(/[a-z]/)) { + // @todo + mode = action = false; + cmd = ''; + cursorOff(); + sendBaseline(ERR_NOT_IMPLEMENTED,false); + break; + } + + // Bookmark frame + if (cmd == '05') { + // @todo + mode = action = false; + cmd = ''; + cursorOff(); + sendBaseline(ERR_NOT_IMPLEMENTED,false); + break; + } + + // Report Problem + if (cmd == '08') { + // @todo + mode = action = false; + cmd = ''; + cursorOff(); + sendBaseline(ERR_NOT_IMPLEMENTED,false); + break; + } + + // Reload frame + if (cmd == '09') { + // @todo + action = ACTION_GOTO; + cmd = ''; + cursorOff(); + next_page = { frame: fo.frame, index: fo.index}; + break; + } + + // Another star aborts the command + if (read == '*') { + mode = action = false; + sendBaseline('',false); + cmd = ''; + cursorOff(); + + // @todo If we are editing a field... + } + + if (read == '#' || read == '\r') { + // Nothing typed between * and # + // *# means go back + if (cmd == '') { + sendBaseline(ERR_NOT_IMPLEMENTED,false); + + } else if (cmd == '0') { + next_page = { frame: 1 }; // @todo specificy home page in config + action = ACTION_GOTO; + + // Edit frame + } else if (cmd == '04') { + sendBaseline(ERR_NOT_IMPLEMENTED,false); + + } else { + next_page = { frame: cmd }; + action = ACTION_GOTO; + } + + // Clear the command we are finished processing... + cursorOff(); + cmd = ''; + mode = false; + } + + break; + + // @todo MODE_CONTROL + + default: + log(LOG_DEBUG,'- SHOULDNT GET HERE: ['+read+']'); + } + log(LOG_DEBUG,'MODE END: ['+read+']'); + + log(LOG_DEBUG,'ACTION START: ['+read+']'); + switch (action) { + // Start command entry + case ACTION_STAR: + log(LOG_DEBUG,'- ACTION_STAR: ['+(next_page ? pageStr(next_page) : '')+']'); + + // @todo If something on the baseline preserve it + + ansiterm.send('ext_mode','set','cursor'); + sendBaseline('\1N\1G\1H*',true); + action = false; + mode = MODE_BL; + + bbs.replace_text(NodeActionRetrieving,'\1h%s \1n\1gJumping to page'); + bbs.node_action=NODE_RFSD; + + break; + + // Goto specific page + case ACTION_GOTO: + log(LOG_DEBUG,'- ACTION_GOTO: ['+(next_page ? pageStr(next_page) : '')+']'); + + if (next_page !== null) { + current = fo; + fo = getFrame(next_page); + if (! fo) { + fo = current; + // sendbaseline ERR_PAGE + sendBaseline(ERR_ROUTE,false); + mode = action = false; + break; + } + current = null; + } + + if (fo.isPublic && fo.isAccessible) { + // @todo if its a login frame and the user is already member of CUG, display error + if (false) { + // baseline USER_ALREADY_MEMBER + break; + } + + } else { + // @todo if user is not the owner + if (false) { + // @todo if frame is not accessible + if (false) { + // baseline ACCESS_DENIED + break; + } + + // @todo if user not a member + if (false) { + // baseline ACCESS_DENIED_NOT_IN_CUG + break; + } + } + } + + // @todo if page is a new location, and we are not going backwards + // if (($history->last() != $next_page) AND ($action == ACTION_GOTO || $action == ACTION_NEXT)) + if (false) { + //$history->push($next_page); + } + + next_page = null; + + case ACTION_RELOAD: + log(LOG_DEBUG,'- ACTION_RELOAD: ['+(next_page ? pageStr(next_page) : '')+']'); + + // Clear the baseline history + // $this->sendBaseline($client,''); + // $current['baseline'] = ''; + + switch(fo.type) { + // Standard Frame + default: + console.line_counter=0; // @todo fix to suppress a pause that is occurring before clear() + console.clear(); + console.putmsg(fo.render()); + mode = action = false; + + // Active frame + + // Terminate frame + } + +// bbs.node_action=NODE_RFSD; + bbs.replace_text(NodeActionMain,'\1h%s \1nViewing \1h*'+fo.frame+'#\1n ['+fo.index+']'); + bbs.node_action=NODE_MAIN; + break; + } + log(LOG_DEBUG,'ACTION END: ['+read+']'); + } +} diff --git a/mods/ansitex.src b/mods/ansitex.src new file mode 100644 index 0000000..3cb80de --- /dev/null +++ b/mods/ansitex.src @@ -0,0 +1 @@ +exec "?ansitex" diff --git a/mods/ansitex_save.js b/mods/ansitex_save.js new file mode 100644 index 0000000..a8b7309 --- /dev/null +++ b/mods/ansitex_save.js @@ -0,0 +1,54 @@ +load('texdefs.js'); +load('texfuncs.js'); + +// File to convert +file = getArg('-f','No file specified with -f',true); +// Page +p = getArg('-p','No page specified with -p',true); +// Index +i = getArg('-i','No index specified with -i',false); +// Key +key = getArg('-k','No index specified with -k',false); +// Cost +cost = getArg('-c','No index specified with -c',false); +// Owner +owner = getArg('-o','No owner specified with -o',true); + +f = new File(file); +if (! f.exists || ! f.open('r')) { + log(LOG_ERROR,'Unable to open ['+file+']'); + exit(1); +} + +frame = new Frame(p,i); +frame.owner = base64_encode(owner.replace(/\\1/g,"\1")); +frame.content = base64_encode(f.read()); + +if (key) { + frame.key = key.split(':').map(function(t){return parseInt(t)}); + + if (frame.key.length != 10) { + log(LOG_ERROR,'Must specify 10 keys with -k'); + exit(1); + } +} + +// Public +frame.isPublic = (argv.indexOf('-P') >= 0); +frame.isAccessible = (argv.indexOf('-A') >= 0); +if (cost) + frame.cost = cost; + +// Date +frame.date = new Date().toISOString(); + +w = new File(system.text_dir+'ansitex/'+frame.page+'.tex'); +if (! w.open('w')) { + log(LOG_ERROR,'Unable to create TEX file for '+frame.page); + exit(1); +} + +w.write(JSON.stringify(frame)); +w.close(); + +printf('Saved file: %s.tex',frame.page); \ No newline at end of file diff --git a/mods/load/texdefs.js b/mods/load/texdefs.js new file mode 100644 index 0000000..656445a --- /dev/null +++ b/mods/load/texdefs.js @@ -0,0 +1,54 @@ +/** + * ANSItex definitions + */ + +var ACTION_RELOAD =1; /* Reload the current frame */ +var ACTION_GOTO =2; /* Goto a specific frame */ +var ACTION_BACKUP =3; /* Goto previous frame */ +var ACTION_NEXT =4; /* Goto next frame */ +var ACTION_TERMINATE =6; /* Terminate the session */ +var ACTION_SUBMITRF =7; /* Submit form contents */ +var ACTION_STAR =7; /* Star command entry */ + +var MODE_BL =1; /* Typing * command on baseline */ + +var FRAME_LENGTH =22; /* Length of a frame */ +var FRAME_WIDTH =80; /* Width of a frame */ +var FRAME_HEADER =56; /* Size of page owner (length) */ +var FRAME_PAGENUM =12; /* Size of page number (length with a-z) */ +var FRAME_COST =9; /* Size of cost (length without unit)*/ +var FRAME_COSTUNIT ='c'; /* Unit of cost */ + +var ERR_NOT_IMPLEMENTED = '\1RNOT IMPLEMENTED'; +var ERR_ROUTE = '\1WMISTAKE? \1GTRY AGAIN OR TELL US ON *08'; + +// Our frame object +function Frame(frame,index) { + if (frame === undefined) { + print('ERROR: frame not defined.'); + exit(1); + } + + this.version=1; + this.frame=parseInt(frame); + this.index=index ? index : 'a'; + this.owner=''; // @todo + this.cost=0; // @todo + this.content=''; + this.isPublic=false; // @todo + this.isAccessible=false; // @todo + this.key=[ null,null,null,null,null,null,null,null,null,null ]; + this.render=function() { + owner = base64_decode(this.owner); + + return owner+' '.repeat(FRAME_HEADER-console.strlen(owner))+' '+ + '\1W\1H'+this.page+' '.repeat(FRAME_PAGENUM-this.page.length)+' '+ + '\1G\1H'+' '.repeat(FRAME_COST-this.cost.toString().length)+this.cost+FRAME_COSTUNIT+ + (console.screen_columns > 80 ? '\n\r' : '') + + base64_decode(this.content); + } + + Object.defineProperty(this,'page', { + get: function() {return pageStr({frame: this.frame, index: this.index }); } + }); +} diff --git a/mods/load/texfuncs.js b/mods/load/texfuncs.js new file mode 100644 index 0000000..9f2e24a --- /dev/null +++ b/mods/load/texfuncs.js @@ -0,0 +1,114 @@ +if (!String.prototype.repeat) { + String.prototype.repeat = function(count) { + 'use strict'; + if (this == null) { + throw new TypeError('can\'t convert ' + this + ' to object'); + } + var str = '' + this; + count = +count; + if (count != count) { + count = 0; + } + if (count < 0) { + throw new RangeError('repeat count must be non-negative'); + } + if (count == Infinity) { + throw new RangeError('repeat count must be less than infinity'); + } + count = Math.floor(count); + if (str.length == 0 || count == 0) { + return ''; + } + // Ensuring count is a 31-bit integer allows us to heavily optimize the + // main part. But anyway, most current (August 2014) browsers can't handle + // strings 1 << 28 chars or longer, so: + if (str.length * count >= 1 << 28) { + throw new RangeError('repeat count must not overflow maximum string size'); + } + var rpt = ''; + for (;;) { + if ((count & 1) == 1) { + rpt += str; + } + count >>>= 1; + if (count == 0) { + break; + } + str += str; + } + return rpt; + }; +} + +function cursorOff() { + ansiterm.send('ext_mode','clear','cursor'); + console.gotoxy(0,24); +} + +/** + * Return an argument from argv, or an error if it doesnt exit + * + * @param key + * @param error + */ +function getArg(key,error,abort) { + index = argv.indexOf(key); + + if ((index !== -1) && (! (argv[index+1] === undefined || argv[index+1].match(/^-/)))) { + return argv[index+1]; + } + + if (abort) { + log(LOG_ERROR,error); + exit(1); + } +} + +function getFrame(page) { + if (! page.index) + page.index = 'a'; + + // @todo Need to filter out SAUCE + f = new File(system.text_dir+'ansitex/'+pageStr(page)+'.tex'); + if (! f.exists || ! f.open('r')) { + return null; + } + + try { + frame = JSON.parse(f.read()); + x = new Frame(0); + frame.render = x.render; + + // @todo Figure out how to delete this duplicate code + Object.defineProperty(frame,'page', { + get: function() {return this.frame+this.index} + }); + } catch (error) { + log(LOG_ERROR,error); + return null; + } + + log(LOG_ERROR,'Loaded frame: ['+frame.frame+']['+frame.index+'] ('+frame.page+')'); + return frame; +} + +/** + * Return the frame as a string + */ +function pageStr(page) { + if (! page.index) + page.index = 'a'; + + return page.frame+page.index; +} + +function sendBaseline(text,reposition) { + console.pushxy(); + console.gotoxy(0,24); + console.print(text); + console.cleartoeol(); + + if (! reposition) { + console.popxy(); + } +} diff --git a/text/ansitex/1a.tex b/text/ansitex/1a.tex new file mode 100644 index 0000000..33d393b --- /dev/null +++ b/text/ansitex/1a.tex @@ -0,0 +1 @@ +{"version":1,"frame":1,"index":"a","owner":"AWgBUlQBQmUBR3MBWVQ=","cost":0,"content":"G1swbSAbWzFt2sTExMTExMS/INrExMTExMTEv9rExMTExMTEvyDaxMTExMTExL8bWzBtDQobWzMxbc0bWzE7MzdtsxtbMzBt+RtbMzdtINW41bgbWzMxbfobWzM3bbMbWzMxbc0bWzM3bbMbWzMwbfkbWzM3bSAg1c24G1szMG35G1szN22z1M24G1szMG35G1szN20gINXNvhtbMzFtzRtbMzdtsxtbMzBt+RtbMzdtICDVzbgbWzMxbfobWzM3bbMbWzMxbc0bWzBtICAgICAgICAgICAgICAgICAgICAgICAbWzE7MzBtQERBVEVUSU1FfEwxNUANChtbMDs0MW0gG1sxOzQwbbMgILPUvrMgsxtbNDFtIBtbNDBtsyAgIMDE2SCzG1szMW2yshtbMzdtsyAgILMbWzMxOzQxbbCyG1szN20gG1s0MG2zICAgsxtbMzFtshtbMzdtsyCzG1s0MW0gG1swbSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIBtbMTszMG1AVElNRXxMOEAbWzBtDQobWzE7MzE7NDFtsBtbMzc7NDBtsyAgsxtbMzFtsrEbWzM3bbMgsxtbMzE7NDFtsBtbMzc7NDBtsyAgINXNuCCzG1szMW2wsBtbMzdtsyAgILMbWzMxbbIbWzQxbduwG1szNzs0MG2zICAgsxtbMzFtsRtbMzdtsyCzG1szMTs0MW2wG1swbQ0KG1sxOzMxOzQxbbEbWzM3OzQwbbMgILMbWzMxbbEbWzQxbbAbWzM3OzQwbbMgsxtbMzE7NDFtsRtbMzc7NDBtsyAgILMbWzMxOzQxbbAbWzM3OzQwbbMgsxtbMzFtsbEbWzM3bbMgICCzG1szMTs0MW2wsbEbWzM3OzQwbbMgICCzG1szMTs0MW2wG1szNzs0MG2zILMbWzMxOzQxbbEbWzBtDQobWzMxbc0bWzE7MzdtsxtbMzBt+RtbMzdtILMbWzMxbc3NG1szN22zG1szMG35G1szN22zG1szMW3NG1szN22zG1szMG35G1szN20gILMbWzMxbc0bWzM3bbMbWzMwbfkbWzM3bbPaxNkbWzMwbfkbWzM3bSAgwMS/G1szMW3NG1szN22zG1szMG35G1szN20gILMbWzMxbc0bWzM3bbMbWzMwbfkbWzM3bbMbWzMxbc0bWzA7MzBtIBtbMTszNzs0MW0gICAgICAgICAgICowIyB0byBnZXQgYmFjayBoZXJlIGFueXRpbWUbWzBtIBtbMW3Uzc2+ICDUzb4g1M3Nzb4g1M2+1M3Nzc3Nzc2+INTNzc2+INTNvhtbMG0NCg0KICAgICAgICAgICAgICAgICAgICAgICAgIBtbMTszMDs0MW3fG1szNzs0MG0gG1szMDs0MW3fG1szNzs0MG0gIBtbMzA7NDFt3xtbNDBt3xtbNDFt3xtbMzc7NDBtICAbWzMwOzQxbd8bWzQwbd8bWzMxbdwbWzM3bSAgG1szMDs0MW3fG1s0MG3f3xtbMzdtICAbWzMwOzQxbd8bWzQwbd8bWzQxbd8bWzBtDQogICAgICAgICAgICAgICAgICAgICAgICAgG1sxOzMxOzQxbdwbWzM3OzQwbSAbWzMxOzQxbdwbWzM3OzQwbSAgG1szMTs0MW3cG1szNzs0MG0gG1szMTs0MW3cG1szNzs0MG0gIBtbMzE7NDFt3BtbMzc7NDBtIBtbMzE7NDFt3BtbMzc7NDBtICAbWzMxOzQxbdwbWzQwbd8gICAbWzQxbdwbWzQwbd/cG1swbQ0KICAgICAgICAgICAgICAgICAgICAgICAgIBtbMW3f398gIN8g3yAg398gICDf398gIN8g3xtbMG0NCiAgICAgICAgICAbWzE7MzA7NDFt3xtbNDBt398bWzM3bSAgG1szMDs0MW3fG1s0MG3fG1s0MW3fG1szNzs0MG0gIBtbMzA7NDFt3xtbNDBt3xtbNDFt3xtbMzc7NDBtICAbWzMwOzQxbd8bWzQwbd/fG1szN20gIBtbMzBt398bWzQxbd8bWzM3OzQwbSAgG1szMDs0MW3fG1s0MG3fG1s0MW3fG1szNzs0MG0gIBtbMzA7NDFt3xtbMzc7NDBtIBtbMzA7NDFt3xtbMzc7NDBtICAbWzMwOzQxbd8bWzQwbd/fG1szN20gIBtbMzBt398bWzQxbd8bWzM3OzQwbSAgG1szMG3fG1szN20gIBtbMzA7NDFt3xtbNDBt3xtbNDFt3xtbMzc7NDBtICAbWzMwOzQxbd8bWzQwbd8bWzQxbd8bWzBtDQogICAgICAgICAgG1sxOzMxOzQxbdwbWzM3OzQwbSAgICAbWzMxOzQxbdwbWzM3OzQwbSAbWzMxOzQxbdwbWzM3OzQwbSAgG1szMTs0MW3cG1szNzs0MG0gG1szMTs0MW3cG1szNzs0MG0gIBtbMzFt398bWzQxbdwbWzM3OzQwbSAgICAbWzMxOzQxbdwbWzM3OzQwbSAgG1szMTs0MW3cG1s0MG3f3BtbMzdtICAbWzMxOzQxbdwbWzM3OzQwbSAbWzMxOzQxbdwbWzM3OzQwbSAgG1szMTs0MW3cG1swbSAgICAgIBtbMTszMTs0MW3cG1szNzs0MG0gIBtbMzE7NDFt3BtbMzc7NDBtICAbWzMxOzQxbdwbWzM3OzQwbSAbWzMxOzQxbdwbWzM3OzQwbSAgG1szMTs0MW3cG1szNzs0MG0gG1szMTs0MW3cG1swbQ0KICAgICAgICAgIBtbMW3f398gIN/f3yAg3yDfICDf398gICAg3yAg3yDfICDf398gIN/f3yAgICDfICDfICDf398gIN8g3xtbMG0NCg0KDQogG1szMW0xG1sxOzM3bSBNZXNzYWdlcxtbMG0gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAbWzE7MzFtKjkwIxtbMG0gG1sxbVVzZXIgTWVudRtbMG0NCiAbWzMxbTMbWzE7MzdtIEZpZG8gTmV0d29ya3MbWzBtICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIBtbMTszMW0qOTEjG1swbSAbWzFtTWVzc2FnZXMbWzBtDQogG1szMW01G1sxOzMzbSAbWzM3bURpcmVjdG9yeRtbMG0gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIBtbMTszMW0qOTUjG1swbSAbWzFtSGVscBtbMG0NCiAbWzMxbTgbWzE7MzdtIEhlbHAbWzBtICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIBtbMTszMW0qOTcjG1swbSAbWzFtRGlyZWN0b3J5G1swbQ0KIBtbMzFtORtbMTszN20gQWJvdXQgdGhpcyBTeXN0ZW0bWzBtICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgG1sxOzMxbSo5OSMbWzBtIBtbMW1Mb2cgT2ZmG1swbQ==","isPublic":true,"isAccessible":true,"key":[null,null,null,3,null,null,null,null,null,null],"date":"2019-10-02T07:03:22.109Z"} \ No newline at end of file