From 1c80c3951a8bd5a0119e6fcb95e8e3bfefa4f9b7 Mon Sep 17 00:00:00 2001 From: Deon George Date: Fri, 9 Dec 2022 17:19:33 +1100 Subject: [PATCH] Work in progress with new page.js --- ctrl/videotex.ini | 8 +- export.js | 2 +- frames_check.js | 2 +- load/control-register.js | 38 +- load/control-sqrllogin.js | 4 +- load/defs.js | 5 +- load/frame-ansi.js | 213 ------- load/frame-page.js | 603 -------------------- load/frame-viewdata.js | 257 --------- load/funcs.js | 16 +- load/page.js | 1098 +++++++++++++++++++++++++++++++++++++ load/session-ansitex.js | 666 ++++++++++++++++++++++ load/session-viewdata.js | 691 +++++++++++++++++++++++ load/session.js | 252 +++++++++ load/windows.js | 991 +++++++++++++++++++++++++++++++++ main.js | 673 ++++++++++++----------- save.js | 2 +- text/981a.vtx | 2 +- text/98a.vtx | 2 +- 19 files changed, 4109 insertions(+), 1416 deletions(-) delete mode 100644 load/frame-ansi.js delete mode 100644 load/frame-page.js delete mode 100644 load/frame-viewdata.js create mode 100644 load/page.js create mode 100644 load/session-ansitex.js create mode 100644 load/session-viewdata.js create mode 100644 load/session.js create mode 100644 load/windows.js diff --git a/ctrl/videotex.ini b/ctrl/videotex.ini index 4b0ba2a..52d921e 100644 --- a/ctrl/videotex.ini +++ b/ctrl/videotex.ini @@ -20,21 +20,21 @@ auth_post=/api/sqrl [prefix] key=0@videotex -logoans=AWgBUkEBR04BQlMBWUkBbgE3AWt0ZXgBbg== +logoans=G1swbRtbMTszMW1BG1szMm1OG1szNG1TG1szM21JG1swOzMwOzQ3bXRleA== logovtx=AUECTgNTBEkHdGV4 user=1 ; System frames are owned by this key [prefix:9] key=0@videotex -logoans=AWgBUkEBR04BQlMBWUkBbgE3AWt0ZXgBbg== +logoans=G1swbRtbMTszMW1BG1szMm1OG1szNG1TG1szM21JG1swOzMwOzQ3bXRleA== logovtx=AUECTgNTBEkHdGV4 user=1 -; Ansitex Help Pages +; ANSItex Help Pages [prefix:516] key=516@videotex -logoans=AWgBUkEBR04BQlMBWUkBbgE3AWt0ZXgBbg== +logoans=G1swbRtbMTszMW1BG1szMm1OG1szNG1TG1szM21JG1swOzMwOzQ3bXRleA== logovtx=AUECTgNTBEkHdGV4 user=1 diff --git a/export.js b/export.js index 1f1dd2b..590a9d8 100644 --- a/export.js +++ b/export.js @@ -1,6 +1,6 @@ load('smbdefs.js'); -// Ansitex specific includes +// ANSItex specific includes load('ansitex/load/defs.js'); load('ansitex/load/funcs.js'); diff --git a/frames_check.js b/frames_check.js index 68528bd..ed1bfba 100644 --- a/frames_check.js +++ b/frames_check.js @@ -16,7 +16,7 @@ require('key_defs.js','KEY_ESC'); ansi = load({},'ansiterm_lib.js'); load('ansitex/load/funcs.js'); -// Ansitex specific includes +// ANSItex specific includes require('ansitex/load/defs.js','ACTION_EXIT'); require('ansitex/load/frame-ansi.js','FRAME_ANSI'); require('ansitex/load/frame-viewdata.js','FRAME_VIEWDATA'); diff --git a/load/control-register.js b/load/control-register.js index 6ea7980..4b7f8c8 100644 --- a/load/control-register.js +++ b/load/control-register.js @@ -38,7 +38,7 @@ function register() { this.handle=function(read) { // Dont allow existing users to re-register if (user.number) { - fo.sendBaseline('ALREADY_MEMBER',false); + fo.baselineSend('ALREADY_MEMBER',false); return read === '*' ? read : ''; } @@ -48,33 +48,33 @@ function register() { return read; } - log(LOG_DEBUG,'- Field '+cf.fname+'('+JSON.stringify(cf)+')'); + log(LOG_DEBUG,'- Field '+cf.name+'('+JSON.stringify(cf)+')'); - if ((cf.fname === 'TOKEN') && (read === '#' || read === "\r")) { - if (cf.fvalue === code) { + if ((cf.name === 'TOKEN') && (read === '#' || read === "\r")) { + if (cf.value === code) { complete = true; } else { - fo.sendBaseline('INVALID_CODE',false); - fo.cursorOn(cf.c+cf.fvalue.length,cf.r); + fo.baselineSend('INVALID_CODE',false); + fo.cursorOn(cf.c+cf.value.length,cf.r); fo.attr(cf.attribute); read = ''; } } - log(LOG_DEBUG,'- Field Value ['+cf.fvalue+'] ('+code+')'); + log(LOG_DEBUG,'- Field Value ['+cf.value+'] ('+code+')'); return read; } // Make sure we got an email this.prefield=function() { - log(LOG_DEBUG,'- prefield: Field '+cf.fname+'('+JSON.stringify(cf)+')'); + log(LOG_DEBUG,'- prefield: Field '+cf.name+'('+JSON.stringify(cf)+')'); - if (cf.fname === 'TOKEN') { + if (cf.name === 'TOKEN') { if (! code.length) { - log(LOG_DEBUG,' - BASELINE '+cf.fname+'('+JSON.stringify(cf)+')'); - fo.sendBaseline('TOKEN_EMAIL',false); + log(LOG_DEBUG,' - BASELINE '+cf.name+'('+JSON.stringify(cf)+')'); + fo.baselineSend('TOKEN_EMAIL',false); var email = fo.fieldValue('EMAIL'); var uid = fo.fieldValue('UID'); @@ -85,13 +85,13 @@ function register() { // Validate Email hasnt been used // Validate USER_ID hasnt been used if ((email.indexOf('@') === -1) || ! EMAIL_REGEX.test(email) || (system.matchuserdata(U_NETMAIL,email) !== 0)) { - fo.sendBaseline('INVALID_EMAIL',false); + fo.baselineSend('INVALID_EMAIL',false); return; } if (! system.check_name(uid)) { log(LOG_DEBUG,' - Cannot use user_id: ('+uid+')'); - fo.sendBaseline('INVALID_UID',false); + fo.baselineSend('INVALID_UID',false); return; } @@ -106,7 +106,7 @@ function register() { if (hdrs.to_net_type !== NET_NONE) { hdrs.to_net_addr = email; } else { - fo.sendBaseline('CANNOT_SEND_TOKEN',false); + fo.baselineSend('CANNOT_SEND_TOKEN',false); return; } hdrs.from=system.name; @@ -137,7 +137,7 @@ function register() { msgbase.close(); } - fo.sendBaseline('TOKEN_SENT',false); + fo.baselineSend('TOKEN_SENT',false); log(LOG_DEBUG,'SENT EMAIL TOKEN ('+code+') ['+JSON.stringify(hdrs)+']'); } } @@ -149,7 +149,7 @@ function register() { var newuser = system.new_user(fo.fieldValue('UID')); } catch (e) { - fo.sendBaseline('USER_EXISTS',false); + fo.baselineSend('USER_EXISTS',false); log(LOG_ERROR,"New user couldn't be created (user created while signing up)"); log(LOG_ERROR,JSON.stringify(e)); processed = true; @@ -157,7 +157,7 @@ function register() { } if (typeof newuser === 'number') { - fo.sendBaseline('USER_CREATE_ERROR',false); + fo.baselineSend('USER_CREATE_ERROR',false); log(LOG_ERROR,"New user couldn't be created (error code "+newuser+")"); processed = true; return this.isComplete; @@ -172,7 +172,7 @@ function register() { user.location = fo.fieldValue('CITY')+', '+fo.fieldValue('COUNTRY'); user.zipcode = fo.fieldValue('PCODE'); user.netmail = fo.fieldValue('EMAIL'); - user.comment = 'ANSITEX registered user'; + user.comment = 'ANSItex registered user'; bbs.user_sync(); bbs.logon(); @@ -183,7 +183,7 @@ function register() { return this.isComplete; } else { - fo.sendBaseline('LOGIN_ERROR',false); + fo.baselineSend('LOGIN_ERROR',false); log(LOG_INFO,"bbs.login() failed"); user.comment = 'Initial login failed!'; newuser.settings |= USER_DELETED; diff --git a/load/control-sqrllogin.js b/load/control-sqrllogin.js index e8c9754..a90cb8b 100644 --- a/load/control-sqrllogin.js +++ b/load/control-sqrllogin.js @@ -43,7 +43,7 @@ function sqrllogin() { var subframe = new Frame((viewdata ? fo.settings.FRAME_WIDTH : fo.settings.FRAME_WIDTH-qr.size-2),2,(viewdata ? qr.size/2 : qr.size+2),22,BG_BLACK|LIGHTGRAY); fo.qrcode(qr,subframe); - fo.sendBaseline('CANCEL_MSG',false); + fo.baselineSend('CANCEL_MSG',false); // Loop and see if the user has logged in var nut = http.body.substr(http.body.indexOf('nut='),68); @@ -127,7 +127,7 @@ function sqrllogin() { user.zipcode = '000'; user.netmail = username+'@'+system.inet_addr; - user.comment = 'ANSITEX registered user - with SQRL'; + user.comment = 'ANSItex registered user - with SQRL'; bbs.user_sync(); } else { diff --git a/load/defs.js b/load/defs.js index 14c3b3a..2c7445f 100644 --- a/load/defs.js +++ b/load/defs.js @@ -96,7 +96,7 @@ const HOME_FRAME_CONNECT ={frame: 980,index: 'a'}; const FRAME_SYSTEM_ERROR ={frame: 998,index: 'a'}; /* Attributes saved/loaded from files */ -const SAVED_FRAME_ATTRS =['version','frame','frame_fields','index','owner','cost','content','isPublic','isAccessible','type','key']; +const SAVED_FRAME_ATTRS =['content','version','frame','input_fields','index','owner','cost','isPublic','isAccessible','type','key','date']; /* The page that has our echomail area reading template */ var MAIL_TEMPLATE_FRAME ='199a'; @@ -107,4 +107,7 @@ var MAIL_TEMPLATE_AREA_SUMMARY ='198a'; // The maximum size of embedded dynamic fields in frames var DYNAMIC_FIELD_SIZE_MAX =50; +/** ESCAPE CODES **/ +const ESC = '\x1b'; + this; diff --git a/load/frame-ansi.js b/load/frame-ansi.js deleted file mode 100644 index 3ff0c7d..0000000 --- a/load/frame-ansi.js +++ /dev/null @@ -1,213 +0,0 @@ -var FRAME_ANSI = (1<<1); - -load('ansitex/load/frame-page.js'); - -// Our frame object -function FrameAnsi() { - PageFrame.apply(this,arguments); - - /* File Extension used for frames */ - this.settings.ext = 'tex'; - - /* Length of a frame */ - this.settings.FRAME_LENGTH = 22; - /* Width of a frame */ - this.settings.FRAME_WIDTH = 80; - /* Size of page owner (length) */ - this.settings.FRAME_HEADER = 56; - /* Size of page number (length with a-z) */ - this.settings.FRAME_PAGENUM = 12; - /* Size of cost (length without unit) */ - this.settings.FRAME_COST = 9; - - this.settings.MSG_SENDORNOT = '\1n\1h\1GKEY 1 TO SEND, 2 NOT TO SEND'; - this.settings.MSG_LOGON = '\1n\1h\1GKEY 1 TO LOGON, 2 TO RETURN'; - this.settings.MSG_SENT = '\1n\1h\1GMESSAGE SENT - KEY # TO CONTINUE'; - this.settings.MSG_NOTSENT = '\1n\1h\1GMESSAGE NOT SENT - KEY # TO CONTINUE'; - this.settings.ERR_NO_PARENT = '\1n\1h\1RPARENT FRAME DOESNT EXIST'; - this.settings.ERR_NOT_IMPLEMENTED = '\1n\1h\1RNOT IMPLEMENTED YET?'; - this.settings.ERR_ROUTE = '\1n\1h\1WMISTAKE? \1GTRY AGAIN OR TELL US ON *08'; - this.settings.ERR_METHOD_NOT_EXIST = '\1n\1h\1WMISTAKE? \1GTRY AGAIN OR TELL US ON *08'; - this.settings.ACCESS_DENIED = '\1n\1h\1RACCESS DENIED. MISTAKE? TRY AGAIN OR TELL US *08'; - this.settings.ALREADY_MEMBER = '\1n\1h\1RALREADY MEMBER OF CUG' - this.settings.INACTIVITY = '\1n\1h\1RINACTIVITY ALERT, DISCONNECT PENDING...'; - this.settings.INACTIVE = '\1n\1h\1RINACTIVITY DISCONNECT'; - this.settings.NOACTION = '\1n\1h\1RNO ACTION PERFORMED'; - this.settings.BASESTAR = '\1N\1G\1H*'; - this.settings.INVALID_CODE = '\1n\1h\1RINVAID CODE, PLEASE TRY AGAIN *00'; - this.settings.TOKEN_EMAIL = '\1n\1h\1RTOKEN EMAILED TO YOU...'; - this.settings.TOKEN_SENT = '\1n\1h\1RTOKEN SENT, PLEASE ENTER TOKEN'; - this.settings.INVALID_EMAIL = '\1n\1h\1RINVAID EMAIL, PLEASE TRY AGAIN *00'; - this.settings.INVALID_UID = '\1n\1h\1RINVAID USER ID, PLEASE TRY AGAIN *00'; - this.settings.CANNOT_SEND_TOKEN = '\1n\1h\1RCANNOT SEND VALIDATION CODE, PLEASE TRY AGAIN *00'; - this.settings.USER_EXISTS = '\1n\1h\1RERROR USER EXISTS, PLEASE TRY AGAIN *00'; - this.settings.USER_CREATE_ERROR = '\1n\1h\1RERROR CREATING USER, PLEASE TRY AGAIN *00'; - this.settings.LOGIN_ERROR = '\1n\1h\1RERROR LOGGING IN, PLEASE TRY AGAIN *00'; - this.settings.CANCEL_MSG = '\1n\1h\1GPRESS 2 TO CANCEL'; - this.settings.SYS_ERROR = '\1n\1h\1RSYSTEM ERROR DETECTED - TRY AGAIN OR TELL US *08'; - this.settings.LOADING = '\1n\1h\1Kloading...'; - - /** - * Set the attribute at the current position - */ - this.attr=function(field) { - console.write(ascii(27)+'['+field.i+';'+field.f+';'+field.b+'m'); - } - - /** - * Turn off the cursor - */ - this.cursorOff=function() { - ansi.send('ext_mode','clear','cursor'); - this.gotoxy(0,24); - } - - this.cursorOn=function(x,y) { - ansi.send('ext_mode','set','cursor'); - if (x && y) - this.gotoxy(x,y); - } - - // Field backspace, that leaves the field filler char - this.fieldbs=function(char) { - console.write(ascii(27)+'[D'+char+ascii(27)+'[D'); - } - - this.gotoxy=function(x,y) { - console.gotoxy(x,y); - } - - // Render the frame to the user - this.render=function(context,withoutHeader) { - log(LOG_DEBUG,'- ANSI FRAME'); - owner = base64_decode(this.owner); - - const frame = new Frame(1,1,this.settings.FRAME_WIDTH,this.settings.FRAME_LENGTH+2,LIGHTGRAY); - frame.open(); - - // Dont show the page number on system login page - if ((! withoutHeader) && (user.number || (this.type !== FRAME_TYPE_LOGIN && NO_HISTORY_FRAMES.indexOf(this.page) === -1))) { - log(LOG_DEBUG,' - Owner: ['+this.pageowner+']'); - - cost = (this.isAccessible ? this.cost+FRAME_COSTUNIT : ' -'); - - header = '\1n'+this.pageownerlogo+' '.repeat(this.settings.FRAME_HEADER-console.strlen(this.pageownerlogo))+'\1n '+ - (this.isAccessible ? '\1W' : '\1R')+'\1H'+this.page+' '.repeat(this.settings.FRAME_PAGENUM-this.page.length)+' '+ - '\1G\1H'+' '.repeat(this.settings.FRAME_COST-cost.toString().length+1)+cost+'\1n'+ - (console.screen_columns > 80 ? '\n\r' : ''); - - frame.putmsg(header); - } - - contentgraphic = new Graphic(this.settings.FRAME_WIDTH); - contentgraphic.auto_extend = true; - contentgraphic.atcodes = false; - contentgraphic.ANSI = this.parse(base64_decode(this.content),context); - - var contentframe = new Frame(1,2,this.settings.FRAME_WIDTH,this.settings.FRAME_LENGTH,LIGHTGRAY,frame); - contentframe.open(); - contentframe.lf_strict = false; - contentframe.atcodes = false; - contentframe.word_wrap = false - - contentframe.putmsg(contentgraphic.MSG) - contentframe.scrollTo(0,0); - - frame.cycle(); - - return contentframe; - }; - - this.qrcode=function(qr,subframe) { - // SMALL Image - var full = ascii(0xdb); - var top = ascii(0xdf); - var bot = ascii(0xdc); - var blank = ' '; - - var qrcode = ''; - - /* - // Render the top line - var line = ascii(27)+'[1;37m'+bot; - for (var y = 0; y < qr.size; y++) { - line += bot; - } - qrcode += line+bot+bot+ascii(27)+'[0m'+"\r\n"; - */ - - // Render the body - for (var x = -1; x < qr.size; x=x+2) { - line = ascii(27)+'[1;37m'+full; - - for (var y = 0; y < qr.size; y++) { - // Top is white - if (! ((x===-1)? 0 : qr.getModule(x, y))) { - line += (qr.getModule(x+1, y)) ? top : full; - - // Top is black - } else { - line += (qr.getModule(x+1, y)) ? blank : bot; - } - } - - qrcode += line+full+ascii(27)+'[0m'+"\r\n"; - } - - // Render the bottom - line = ascii(27)+'[1;37m'+top; - for (var y = 0; y < qr.size; y++) { - line += top; - } - qrcode += line+top+ascii(27)+'[0m'+"\r\n"; - - ans2bin(fo.parse(qrcode),subframe); - subframe.open(); - subframe.cycle(); - }; - - this.save=function() { - file = system.mods_dir+'ansitex/text/'+this.page+'.tex'; - w = new File(file); - if (! w.open('w')) { - log(LOG_ERROR,'! ERROR: Unable to create TEX file for '+this.page); - exit(1); - } - - w.write(JSON.stringify(this)); - w.close(); - - log(LOG_DEBUG,'Saved file: '+this.page+'.tex'); - } - - /** - * Send a message to the baseline. - * - * @param text - * @param reposition - */ - this.sendBaseline=function(text,reposition) { - console.pushxy(); - console.gotoxy(0,24); - console.print(this.getMessage(text)); - console.cleartoeol(); - - if (! reposition) { - console.popxy(); - } - } - - this.clearBaseline=function(reposition) { - console.pushxy(); - console.gotoxy(0,24); - console.print(''); - console.cleartoeol(); - - if (! reposition) { - console.popxy(); - } - } -} - -FrameAnsi.prototype = PageFrame.prototype; -FrameAnsi.prototype.constructor = FrameAnsi; diff --git a/load/frame-page.js b/load/frame-page.js deleted file mode 100644 index ab2f615..0000000 --- a/load/frame-page.js +++ /dev/null @@ -1,603 +0,0 @@ -// Our frame object -function PageFrame() { - 'use strict'; - - /* Frame type settings */ - this.settings = {}; - - /* Frame Version */ - this.version = 1; - - /* Frame Number [0-9] */ - this.frame = null; - - /* Frame Index [a-z] */ - this.index = null; - - /* The Service Provider owning the frame. */ - this.owner = 0; - - /* The cost to view the frame @todo to implement */ - this.cost = 0; - - /* The frame content, base64 encoded */ - this.content = ''; - - // Frame's owned by the system where: - // isPublic is FALSE - the user must be logged in to view it - // isPublic is TRUE - can be viewed by non-logged in users - - // Frame's owned by Service Providers where: - // isPublic is FALSE - can only be viewed if a user is - // a member of the Service Providers CUG - // isPublic is TRUE - can be viewed by users (if logged in) - - this.isPublic = false; // Is this frame accessible to non CUG users - // If FALSE user must be a member of the CUG to view the frame - // All users, including unauthenticated, are members of 'system' (owner = 0) - this.isAccessible = false; // Is this frame available to be accessed - // If FALSE, only the SP can view/edit the frame - - this.type = FRAME_TYPE_INFO; // The frame type - see FRAME_TYPES above - this.key = [null,null,null,null,null,null,null,null,null,null]; // Key actions [0-9] - - /** - * Determine if this frame is accessible to the current user - */ - Object.defineProperty(this,'accessible',{ - get: function() { - log(LOG_DEBUG,'- Checking if user can access frame: '+this.page); - log(LOG_DEBUG,' - User: '+JSON.stringify(user.number)); - log(LOG_DEBUG,' - Frame Owner: '+JSON.stringify(this.owner)+', System Frame: '+(this.pageowner === SYSTEM_OWNER)); - - // user.number 0 is unidentified user. - if (user.number) { - return ( - (this.isAccessible && this.pageowner === SYSTEM_OWNER && ! this.isPublic) || - (this.isAccessible && this.isPublic) || - (this.isAccessible && ! this.isPublic && this.isMember) || - (pageEditor(this.frame)) - ); - - } else { - return (this.isAccessible && this.pageowner === SYSTEM_OWNER && this.isPublic); - } - } - }); - - /** - * Return the frames fields - */ - Object.defineProperty(this,'fields', { - get: function() { - return this.frame_fields; - } - }); - - /** - * Check if the user is already a member of the CUG - */ - Object.defineProperty(this,'isMember',{ - get: function() { - log(LOG_DEBUG,'- Checking if user is a member of frame: '+this.page); - - if (user.number) { - return ( - (this.pageowner === SYSTEM_OWNER) - ); - - } else { - return false; - } - } - }) - - /** - * Return the page number - */ - Object.defineProperty(this,'page', { - get: function() { - if (this.frame === null || this.index === null) return null; - return this.frame.toString()+this.index; - } - }); - - Object.defineProperty(this,'pageowner', { - get: function() { - return pageOwner(this.frame).prefix; - } - }) - - Object.defineProperty(this,'pageownerlogo', { - get: function() { - return base64_decode(this.settings.ext === 'tex' ? pageOwner(this.frame).logoans : pageOwner(this.frame).logovtx); - } - }) -} - -/** - * Enable pulling out submitted value by its name - * - * @param key - * @returns {null|string|*} - */ -PageFrame.prototype.fieldValue = function(key) { - for each (var k in this.frame_fields) { - log(LOG_DEBUG,' - k:'+JSON.stringify(k)); - - if (k.fname === key) { - return k.fvalue; - } - } - - return null; -} - -/** - * Return the message for a index - * - * @param index - * @returns {string|*} - */ -PageFrame.prototype.getMessage = function(index) { - eval('var msg = this.settings.'+index); - return msg; -} - -/** - * Load a frame - * - * @param filename - * @returns {null} - */ -PageFrame.prototype.load = function(filename) { - log(LOG_DEBUG,'Loading FRAME from: '+filename+'.'+this.settings.ext); - this.sendBaseline('LOADING'); - - f = new File(system.mods_dir+'ansitex/text/'+filename+'.'+this.settings.ext); - if (! f.exists || ! f.open('r')) { - return null; - } - - try { - var load = JSON.parse(f.read()); - - for (property in SAVED_FRAME_ATTRS) { - this[SAVED_FRAME_ATTRS[property]] = load[SAVED_FRAME_ATTRS[property]]; - } - - // If the page doesnt match the filename, throw an error - if (this.page !== filename) - throw 'Frame doesnt match filename'; - - } catch (error) { - log(LOG_ERROR,'Frame error: '+error); - - // Load our system error frame. - this.load(pageStr(FRAME_SYSTEM_ERROR)); - return null; - } - - log(LOG_DEBUG,'Loaded frame: ['+this.frame+']['+this.index+'] ('+this.page+')'); -}; - -/** - * Load a message frame - * - * @param page - */ -PageFrame.prototype.loadMessage = function(page) { - this.frame = ''+page; - this.index = 'a'; - this.owner = 1; - this.isPublic = true; - this.isAccessible = true; - // @todo Keys should map to next/previous/send, etc as indicated in the template frame. - this.key = [this.frame.substr(0,7)+'1',null,null,null,null,null,null,null,null,null]; - // @todo validate that FRAME_TYPE_MESSAGE is a message template - this.type = FRAME_TYPE_MESSAGE; - - // Load our message - var ma = new MsgAreas() - var area = ma.getArea(this.frame); - var msg = ma.getMessage(this.frame); - var msg_header; - - if (! msg) - return undefined; - - // @todo Search 1zzzzEE..., 1zzzz... - var to = viewdata ? new FrameViewdata() : new FrameAnsi(); - to.load(MAIL_TEMPLATE_FRAME); - // @todo Check that this is a frame of type "m" and report error if not - // @todo Take the cost from the template - this.cost = 5; - - if (! to) { - log(LOG_ERROR,'Echomail template missing :['+MAIL_TEMPLATE_FRAME+'] ?'); - msg_header = 'TO: '+msg.to.substr(0,72)+"\n\r"; - msg_header += 'FROM: '+msg.from.substr(0,72)+"\n\r"; - msg_header += 'DATE: '+msg.date.substr(0,72)+"\n\r"; - msg_header += 'SUBJECT: '+msg.subject.substr(0,72)+"\n\r"; - - } else { - // @todo change this to use atcode() - msg_header = base64_decode(to.content).replace(/@(.*)@/g, - function (str, code, offset, s) { - var length = code.split(':')[1]; - switch(code.split(':')[0]) { - case 'DATE': return msg.date.substr(0,length); - case 'TO': return msg.to.substr(0,length); - case 'FROM': return msg.from.substr(0,length); - case 'SUBJECT': return msg.subject.substr(0,length); - } - } - ); - } - - //log(LOG_DEBUG,'Loaded message: '+msg_header+msg.content); - this.content = base64_encode(msg_header+msg.content); - - // Update the user's pointers - var stats = ma.getUserStats(this.frame); - - // if this message is to the user, and the msg number > scan_ptr and it is the next message on the user's new mail list - var newmsgs = area.newMsgsToMe(); - var next; - log(LOG_DEBUG,'User has: '+newmsgs.length-1+' msgs to read to ME'); - if (newmsgs.length) { - next = newmsgs[1]; - //log(LOG_DEBUG,'- NEXT is: '+next.tags+', this is: '+msg.tags); - - if (next && (next.tags === msg.tags)) { - log(LOG_DEBUG,'- Updating scan_ptr to: '+next.number); - stats.scan_ptr = next.number; - } - - // Last message - next = newmsgs[0]; - if (next !== undefined) { - log(LOG_DEBUG,'- LAST TO ME is: '+next.tags); - this.key[1] = area.page(next.tags); - } - - // Next new message - next = newmsgs[2]; - if (next !== undefined) { - log(LOG_DEBUG,'- NEXT TO ME is: '+next.tags); - this.key[2] = area.page(next.tags); - } - } - - // if this message is the next message, update last_read - newmsgs = area.newMsgs(); - log(LOG_DEBUG,'User has: '+newmsgs.length+' msgs to read'); - if (newmsgs.length) { - next = newmsgs[0]; - log(LOG_DEBUG,'- NEXT is: '+next.tags+', this is: '+msg.tags); - - if (next.tags === msg.tags) { - log(LOG_DEBUG,'- Updating last_read to: '+next.number); - stats.last_read = next.number; - } - } - - // Previous Message - x = area.MessagePrev(this.frame); - if (x) - this.key[4] = area.page(x.tags); - - // Next Message - x = area.MessageNext(this.frame); - if (x) - this.key[6] = area.page(x.tags); - - log(LOG_DEBUG,'Built frame: ['+this.frame+']['+this.index+'] ('+this.page+')'); -} - -/** - * Parse the page text, and return the frame as 2 arrays: - * + First array is all the characters and the position on the frame - * + Second array is the array of the control codes that changes the color of the character - * - * The purpose of this function is to convert any special char sequences there are interpreted directly by Ansitex - * Currently they are: - * + ESC _ [;value] ESC \ - * - * Additionally, for response frames, if the cursor is moved to a field, its to determine what attributes (eg: color) - * should apply for that field. - * - * @param text - * @param context - */ -PageFrame.prototype.parse = function(text,context) { - log(LOG_DEBUG,'Parsing Frame ...'); - - if (this.type === FRAME_TYPE_MESSAGE) { - log(LOG_DEBUG,'- Not parsing message frame'); - return text; - } - - /** Column - * @type {number} - */ - var c = 1; - /** Row - row 1 is the header - * @type {number} - */ - var r = 2; - /** - * The output to send to the screen - * @type {string} - */ - var output = ''; - /** - * Fields within the frame - * @type {{ - * ftype: fieldtype, - * flength: fieldlen, - * fchar: fieldchar, - * fname: field, - * r: r, - * c: c, - * attribute: {i:i,f:f,b:b}, - * fvalue: '', - * }} - */ - this.frame_fields = []; - - // If there is no text return - if (! text) - return output; - - // Default Attributes - // @note - this has no affect for viewdata frames - /* Foreground */ - var f = 39; - /* Background */ - var b = 49; - /* Intensity */ - var i = 0; - - for(var p=0;p= 0 && csi[num] <= 8) { - i = csi[num]; - f = 39; - b = 49; - - // Forground Color - } else if (csi[num] >= 30 && csi[num] <= 39) { - f = csi[num]; - - // Background Color - } else if (csi[num] >= 40 && csi[num] <= 49) { - b = num; - } - } - break; - - // Advance characters - case 'C': - //log(LOG_DEBUG,'CSI C ['+r+'x'+c+'] CHARS: '+matches[1]); - c += parseInt(matches[1]); // Advance our position - break; - - default: - log(LOG_DEBUG,'? CSI: '+matches[2]); - } - - break; - - case ' ': - log(LOG_DEBUG,'LOOSE ESC? ['+r+'x'+c+'] '+advance); - - break; - - // X - dynamic fields, terminated with ESC - // Dynamic fields are defined with ESC X FIELD;LENGTH;PAD ESC \ - // @note FIELD & LENGTH are required - // @note Dynamic field is can only be DYNAMIC_FIELD_SIZE_MAX chars in size - case 'X': - //log(LOG_DEBUG,'DF ['+r+'x'+c+'] '+advance); - advance++; - - // Find our end ST param in the next DYNAMIC_FIELD_SIZE_MAX chars - matches = text.substr(p+advance,DYNAMIC_FIELD_SIZE_MAX).match(/(([a-zA-Z._:^;]+[0-9]?;-?[0-9^;]+)([;]?[^;]+)?)\x1b\\/); - //log(LOG_DEBUG,'- DF ['+r+'x'+c+'] ADVANCE: '+advance+', MATCHES: '+JSON.stringify(matches)+', LENGTH: '+(matches ? matches[0].length : 0)+', STRING: '+text.substr(p+advance,50)); - - if (! matches) { - chars += nextbyte; - - break; - } - - advance += matches[0].length-1; - - var df = matches[0].substr(0,matches[0].length-2).split(';'); - //log(LOG_DEBUG,'- DF ['+r+'x'+c+'] ADVANCE: '+advance+', DF: '+df); - - log(LOG_DEBUG,'- DF found at ['+r+'x'+(c-1)+'], Field: '+df[0]+', Length: '+df[1]+', Attrs: '+JSON.stringify({i:i,f:f,b:b})); - chars = atcode(df[0],df[1],df[2],context); - byte = ''; - - break; - - // _ - has our fields that take input - case '_': - //log(LOG_DEBUG,'SOS ['+r+'x'+c+'] '+advance); - advance++; - - // Find our end ST param in the next 50 chars - matches = text.substr(p+advance,50).match(/(([A-Z]+;[0-9a-z]+)([;]?.+)?)\x1b\\/); - //log(LOG_DEBUG,'SOS ['+r+'x'+c+'] ADVANCE: '+advance+', MATCHES: '+matches+', LENGTH: '+matches[0].length+', STRING: '+text.substr(p+advance,50)); - - if (! matches) { - chars += nextbyte; - - break; - } - - advance += matches[0].length-1; - - // The last 2 chars of matches[0] are the ESC \ - var sos = matches[0].substr(0,matches[0].length-2).split(';'); - //log(LOG_DEBUG,'SOS ['+r+'x'+c+'] ADVANCE: '+advance+', SOS: '+sos); - - var num = null; - var field = null; - var fieldlen = null; - for (num in sos) { - //log(LOG_DEBUG,'SOS ['+r+'x'+c+'] NUM: '+num+', SOS: '+sos[num]); - - switch (num) { - // First value is the field name - case '0': - field = sos[num]; - break; - - // Second value is the length/type of the field - case '1': - x = sos[num].match(/([0-9]+)([a-z])/); - if (! x) { - log(LOG_ERROR,'SOS FAILED PARSING FIELD LENGTH/TYPE. ['+r+'x'+c+'] '+sos[num]); - break; - } - - //log(LOG_DEBUG,'SOS ['+r+'x'+c+'] NUM CHARS: '+x[1]+', TYPE: '+x[2]); - fieldlen = x[1]; - fieldtype = x[2]; - break; - - // Third field is the char to to use - case '2': - fieldchar = sos[num]; - break; - - default: - log(LOG_ERROR,'IGNORING ADDITIONAL SOS FIELDS. ['+r+'x'+c+'] '+sos[num]); - } - } - - if (fieldlen) { - chars = fieldchar.repeat(fieldlen); - } - - byte = ''; - - this.frame_fields.push({ - ftype: fieldtype, - flength: fieldlen, - fchar: fieldchar, - fname: field, - r: r, - c: c, - attribute: {i:i,f:f,b:b}, - fvalue: '', - }); - - log(LOG_DEBUG,'SOS Field found at ['+r+'x'+(c-1)+'], Type: '+fieldtype+', Length: '+fieldlen+', Attrs: '+JSON.stringify({i:i,f:f,b:b})); - - break; - - default: - log(LOG_DEBUG,'DEFAULT ['+r+'x'+c+'] '+advance); - } - - break; - - default: - c++; - } - - output += byte; - - if (advance) { - //log(LOG_DEBUG,'ADVANCE P ['+r+'x'+c+'] '+advance+', NEXT CHAR: '+text.charAt(p+advance)+' ('+text.charCodeAt(p+advance)+')'); - output += chars; - p += advance; - } - - if (c>this.settings.FRAME_WIDTH) { - c = 1; - r++; - } - - /* - // @todo - If we are longer than FRAME_LENGTH, move the output into the next frame. - if (r>this.settings.FRAME_LENGTH) { - break; - } - */ - } - - return output; -}; diff --git a/load/frame-viewdata.js b/load/frame-viewdata.js deleted file mode 100644 index 74a1e41..0000000 --- a/load/frame-viewdata.js +++ /dev/null @@ -1,257 +0,0 @@ -var FRAME_VIEWDATA = (1<<2); - -var VIEWDATA_LEFT = ascii(0x08); -var VIEWDATA_RIGHT = ascii(0x09); -var VIEWDATA_DOWN = ascii(0x0a); // \n -var VIEWDATA_UP = ascii(0x0b); -var VIEWDATA_CLS = ascii(0x0c); -var VIEWDATA_CR = ascii(0x0d); // \r -var VIEWDATA_CON = ascii(0x11); -var VIEWDATA_COFF = ascii(0x14); -var VIEWDATA_HOME = ascii(0x1e); -var VIEWDATA_MOSIAC_RED = ascii(27)+ascii(0x51); -var VIEWDATA_MOSIAC_GREEN = ascii(27)+ascii(0x52); -var VIEWDATA_MOSIAC_YELLOW = ascii(27)+ascii(0x53); -var VIEWDATA_MOSIAC_BLUE = ascii(27)+ascii(0x54); -var VIEWDATA_MOSIAC_MAGENTA = ascii(27)+ascii(0x55); -var VIEWDATA_MOSIAC_CYAN = ascii(27)+ascii(0x56); -var VIEWDATA_MOSIAC_WHITE = ascii(27)+ascii(0x57); - -load('ansitex/load/frame-page.js'); - -// Our frame object -function FrameViewdata() { - PageFrame.apply(this,arguments); - - /* File Extension used for frames */ - this.settings.ext = 'vtx'; - - /* Length of a frame */ - this.settings.FRAME_LENGTH = 22; - /* Width of a frame */ - this.settings.FRAME_WIDTH = 40; - /* Size of page owner (length) */ - this.settings.FRAME_HEADER = 23; - /* Size of page number (length with a-z) */ - this.settings.FRAME_PAGENUM = 11; - /* Size of cost (length without unit) */ - this.settings.FRAME_COST = 3; - - this.settings.MSG_SENDORNOT = ascii(27)+'BKEY 1 TO SEND, 2 NOT TO SEND'; - this.settings.MSG_LOGON = ascii(27)+'BKEY 1 TO LOGON, 2 TO RETURN'; - this.settings.MSG_SENT = ascii(27)+'BMESSAGE SENT - KEY _ TO CONTINUE'; - this.settings.MSG_NOTSENT = ascii(27)+'BMESSAGE NOT SENT - KEY _ TO CONTINUE'; - this.settings.ERR_NO_PARENT = ascii(27)+'APARENT FRAME DOESNT EXIST'; - this.settings.ERR_NOT_IMPLEMENTED = ascii(27)+'ANOT IMPLEMENTED YET?'; - this.settings.ERR_ROUTE = ascii(27)+'GMISTAKE?'+ascii(27)+'BTRY AGAIN OR TELL US ON *08'; - this.settings.ERR_METHOD_NOT_EXIST = ascii(27)+'GMISTAKE?'+ascii(27)+'BTRY AGAIN OR TELL US ON *08'; - this.settings.ACCESS_DENIED = ascii(27)+'AACCESS DENIED.'; - this.settings.ALREADY_MEMBER = ascii(27)+'AALREADY MEMBER OF CUG' - this.settings.INACTIVITY = ascii(27)+'AINACTIVITY ALERT, DISCONNECT PENDING...'; - this.settings.INACTIVE = ascii(27)+'AINACTIVITY DISCONNECT'; - this.settings.NOACTION = ascii(27)+'ANO ACTION PERFORMED'; - this.settings.BASESTAR = ascii(27)+'B*'; - this.settings.INVALID_CODE = ascii(27)+'AINVAID CODE, PLEASE TRY AGAIN *00'; - this.settings.TOKEN_EMAIL = ascii(27)+'ATOKEN EMAILED TO YOU...'; - this.settings.TOKEN_SENT = ascii(27)+'ATOKEN SENT, PLEASE ENTER TOKEN'; - this.settings.INVALID_EMAIL = ascii(27)+'AINVAID EMAIL, PLEASE TRY AGAIN *00'; - this.settings.INVALID_UID = ascii(27)+'AINVAID USER ID, PLEASE TRY AGAIN *00'; - this.settings.CANNOT_SEND_TOKEN = ascii(27)+'ACANNOT SEND VALIDATION CODE, PLEASE TRY AGAIN *00'; - this.settings.USER_EXISTS = ascii(27)+'AERROR USER EXISTS, PLEASE TRY AGAIN *00'; - this.settings.USER_CREATE_ERROR = ascii(27)+'AERROR CREATING USER, PLEASE TRY AGAIN *00'; - this.settings.LOGIN_ERROR = ascii(27)+'AERROR LOGGING IN, PLEASE TRY AGAIN *00'; - this.settings.CANCEL_MSG = ascii(27)+'BPRESS 2 TO CANCEL'; - this.settings.SYS_ERROR = ascii(27)+'ASYS ERR, TRY AGAIN OR TELL US ON *08'; - this.settings.LOADING = VIEWDATA_MOSIAC_WHITE+'loading...'; - - var blp=0; // Length of data on the bottom line - - /** - * Set the attribute at the current position - */ - this.attr=function(field) { - //NOOP - } - - /** - * Turn off the cursor - */ - this.cursorOff=function() { - write_raw(VIEWDATA_COFF); - this.gotoxy(1,1); - } - - this.cursorOn=function(x,y) { - write_raw(VIEWDATA_CON); - this.gotoxy(x,y); - } - - // Field backspace, that leaves the field filler char - this.fieldbs=function(char) { - console.write(VIEWDATA_LEFT+char+VIEWDATA_LEFT); - } - - this.gotoxy=function(x,y) { - // @todo This could be optimised to go the shortest route - write_raw(VIEWDATA_HOME); - if (x>0) - write_raw(VIEWDATA_RIGHT.repeat(x)); - if (y>0) - write_raw(VIEWDATA_DOWN.repeat(y)); - } - - this.strlen=function(str) { - return str.replace(/\x1b/g,'').length; - }; - - // Render the frame to the user - this.render=function(withHeader) { - log(LOG_DEBUG,'- VIEWDATA FRAME'); - owner = base64_decode(this.owner); - - header = VIEWDATA_DOWN; - - //log(LOG_DEBUG,' - FRAME User: ['+JSON.stringify(user)+']'); - - // Dont show the page number on system login page - if (user.number || (this.type !== FRAME_TYPE_LOGIN && NO_HISTORY_FRAMES.indexOf(this.page) === -1)) { - log(LOG_DEBUG,' - Owner: ['+this.pageowner+'] ('+this.strlen(videotex(this.pageownerlogo))+')'); - - cost = (this.isAccessible ? this.cost+FRAME_COSTUNIT : ' -'); - - header = videotex(this.pageownerlogo)+' '.repeat(this.settings.FRAME_HEADER-this.strlen(videotex(this.pageownerlogo)))+ - (this.isAccessible ? ascii(27)+'G' : ascii(27)+'A')+this.page+' '.repeat(this.settings.FRAME_PAGENUM-this.page.length)+ - ascii(27)+'B'+' '.repeat(this.settings.FRAME_COST-cost.toString().length+1)+cost; - } - - //console.status |= CON_RAW_IN; - write_raw(VIEWDATA_CLS); - write_raw(header); - return write_raw(videotex(base64_decode(this.content))); - }; - - this.qrcode = function(qr) { - // Render the body - var qrcode = VIEWDATA_HOME+VIEWDATA_DOWN.repeat(5); - var offset = this.settings.FRAME_WIDTH-Math.ceil(qr.size/2)-1; - - for (var x = -1; x < qr.size; x=x+3) { - var line = VIEWDATA_RIGHT.repeat(offset ? offset-1 : 0)+VIEWDATA_MOSIAC_WHITE; - - for (var y = -1; y < qr.size; y=y+2) { - var char = 0; - - //TL - char |= ((x===-1) || (y===-1) || ! qr.getModule(x,y)) ? (1<<0) : (0<<0); - //TR - char |= ((x===-1) || (y === qr.size-1) || ! qr.getModule(x,y+1)) ? (1<<1) : (0<<1); - //ML - char |= ((y===-1) || ! qr.getModule(x+1,y)) ? (1<<2) : (0<<2); - //MR - char |= ((y === qr.size-1) || ! qr.getModule(x+1,y+1)) ? (1<<3) : (0<<3); - //BL - char |= ((x===qr.size-2) || (y===-1) || ! qr.getModule(x+2,y)) ? (1<<4) : (0<<4); - //BR - char |= ((x===qr.size-2) || (y === qr.size-1) || ! qr.getModule(x+2,y+1)) ? (1<<5) : (0<<5); - - char += 0x20; - if (char > 0x3f) - char += 0x20; - - line += ascii(char); - } - - // Render the right column - if (y%2) - line += ascii(0x35); - - repeat_count = this.settings.FRAME_WIDTH-Math.ceil(qr.size/2)-offset-(offset ? 1 : 2)-(y%2 === 1 ? 0 : 1); - - qrcode += line+' '.repeat(repeat_count > 0 ? repeat_count : 0); - - // To fix some terminals where moving right from col 40 doesnt advance to col 1 on the next line - qrcode +=VIEWDATA_LEFT+VIEWDATA_CR+VIEWDATA_DOWN; - } - - log(LOG_DEBUG,'WIDTH:'+this.settings.FRAME_WIDTH); - log(LOG_DEBUG,'QR :'+(Math.ceil(qr.size/2)+1)); - log(LOG_DEBUG,'OFF :'+offset); - log(LOG_DEBUG,'Y :'+(y%2 ? 0 : 1)); - log(LOG_DEBUG,'X :'+(x%3 ? 0 : 1)); - - // Render the bottom - if (x%3) { - line = VIEWDATA_RIGHT.repeat(offset ? offset-1 : 0)+VIEWDATA_MOSIAC_WHITE; - - for (var y = 0; y < qr.size; y=y+2) { - line += ascii(0x23); - } - - // Render the right column - if (y%2 === 0) { - line += ascii(0x21); - } - - qrcode += line+' '.repeat(repeat_count > 0 ? repeat_count : 0); - } - - write_raw(qrcode); - }; - - this.save=function() { - file = system.mods_dir+'ansitex/text/'+this.page+'.tex'; - w = new File(file); - if (! w.open('w')) { - log(LOG_ERROR,'! ERROR: Unable to create TEX file for '+this.page); - exit(1); - } - - w.write(JSON.stringify(this)); - w.close(); - - log(LOG_DEBUG,'Saved file: '+this.page+'.tex'); - } - - /** - * Send a message to the baseline. - * - * @param text - * @param reposition - */ - this.sendBaseline=function(text,reposition) { - var msg = this.getMessage(text); - var x = this.strlen(msg); - - write_raw(VIEWDATA_HOME+VIEWDATA_UP+msg+ - ((blp > x) - ? (' '.repeat(blp-x)+(reposition ? VIEWDATA_HOME+VIEWDATA_UP+VIEWDATA_RIGHT.repeat(x) : '')) - : '') - ); - - blp = x; - } - - this.clearBaseline=function(reposition) { - msg = ''; - - write_raw(VIEWDATA_HOME+VIEWDATA_UP+msg+ - ((blp > msg.length) - ? (' '.repeat(blp-msg.length)+(reposition ? VIEWDATA_HOME+VIEWDATA_UP+VIEWDATA_RIGHT.repeat(msg.length) : '')) - : '') - ); - - blp = msg.length; - } -} - -function videotex(data) { - var output = ''; - //$output .= ($byte < 32) ? ESC.chr($byte+64) : chr($byte); - for (var i = 0; i < data.length; i++) { - output += (data.charCodeAt(i) < 32) ? "\x1b"+String.fromCharCode(data.charCodeAt(i)+64) : String.fromCharCode(data.charCodeAt(i)); - } - return output; -} - -FrameViewdata.prototype = PageFrame.prototype; -FrameViewdata.prototype.constructor = FrameViewdata; diff --git a/load/funcs.js b/load/funcs.js index a47d05f..f93975b 100644 --- a/load/funcs.js +++ b/load/funcs.js @@ -104,8 +104,16 @@ function ans2bin(ansi,frame) { } } -// Dynamic Field Processing -// @note bbs.atcodes() cannot process modifiers, so this function is a replacement. +/** + * Dynamic Field Processing + * + * @param field field we want to get a value for + * @param length length to fill this field + * @param pad the padding character if field is less than length + * @param context a context value to determine the value from + * @returns {string|*|null} + * @note bbs.atcodes() cannot process modifiers, so this function is a replacement. + */ function atcode(field,length,pad,context) { 'use strict'; @@ -299,7 +307,7 @@ function atcode(field,length,pad,context) { break; default: - result = bbs.atcode(field); + result = (typeof bbs === 'undefined') ? '*'.repeat(Math.abs(length)) : bbs.atcode(field); } if ((result === null) || (typeof result === 'undefined')) @@ -379,7 +387,7 @@ function getNodeID() { } } - return (matches.length === 0) ? '-' : padright(matches[0],3,'0')+padright(matches[1],3,'0')+padright(bbs.node_num,2,'0'); + return (matches.length === 0) ? '-' : padright(matches[0],3,'0')+padright(matches[1],3,'0')+padright((typeof bbs === 'undefined') ? 0 : bbs.node_num,2,'0'); } function getPageOwners() { diff --git a/load/page.js b/load/page.js new file mode 100644 index 0000000..6868837 --- /dev/null +++ b/load/page.js @@ -0,0 +1,1098 @@ +/** + PAGE.js handles ANSItex and ViewData page frames + + It also handles windows, horizontal/vertical scrolling and content paging. + This is inspired by frame.js provided by Synchronet + + Objects: + + Page - our display page, build with Windows + - Line 1 - Title (Fixed) + - Line 2..23 - Content (Scrollable) + - Line 24 - Status/Command Input (Fixed) + + = When scrolling is disabled, and the canvas is greater than the window, then "nextpage" returns the next frame + = Pageable windows cannot have children, only "CONTENT" is paged + = Pageable windows are pagable when scrolling is false and window canvas.height > window.height and canvas.width = window.width + + + Window - size W x H, where W/H can be larger than the Screen + - Window holds all the content to be shown + - x,y - attributes define the position of the window in it's parent [1..] + - z - determines which layer the window is on, higher z is shown [0..] + - width/height - determines the physical size of the window (cannot be larger than it's parent) + - canvas width/height - determines the logical size of the window, which if larger than physical enables scrolling + - ox/oy - determines the start of the canvas that is shown, relative to canvas width/height + - service - Current supported are ANSItex (80x24) and ViewData (40x24) + - content - array of Chars height/width order + - visible - determines if the window (and it's children) are renderable + + = Windows can be have children, and the z determines the layer shown relative to its parent + = Swapping z values determines which windows are hidden by others + + + Char - object holding each character, and it's color when rendered + + = Rendering + - ANSItex + + Each attribute can have it's own color (colors take up no positional space) + + We only change render control colors to change attributes when it actually changes, otherwise we render just the character + + - ViewData + + An attribute Foreground or Background or Special Function takes up a character + + Character must be set to NULL when it's a control character + + = EXAMPLE: + a = new Page('TEX') // root frame 80 x 24 for ANSItex + b = new Window(1,1,40,22,a.content) // b frame 40 x 22 - starting at 1,1 + c = new Window(41,1,40,22,a.content) // c frame 40 x 22 - starting at 41,1 (child of a) + d = new Window(1,1,21,10,c) // d frame 20 x 11 - starting at 1,1 of c + e = new Window(25,12,10,5,c) // e frame 10 x 5 - starting at 25,12 of c + f = new Window(15,8,13,7,c) // f frame 13 x 7 - starting at 15,8 of c + + --:____.____|____.____|____.____|____.____|____.____|____.____|____.____|____.____| + 01:TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT + 02:22222222222222222222222222222222222222224444444444444444444443333333333333333333 + 03:2 24 43 3 + 04:2 24 43 3 + 05:2 24 43 3 + 06:2 24 43 3 + 07:2 24 43 3 + 08:2 24 444444443333333 3 + 09:2 24 466666666666663 3 + 10:2 24 46 63 3 + 11:2 2444444444444446 63 3 + 12:2 2333333333333336 633333333 3 + 13:2 23 36 665555553 3 + 14:2 23 36 65 53 3 + 15:2 23 366666666666665 53 3 + 16:2 23 333333333335555 53 3 + 17:2 23 355555555553 3 + 18:2 23 333333333333 3 + 19:2 23 3 + 20:2 23 3 + 21:2 23 3 + 22:2 23 3 + 23:22222222222222222222222222222222222222223333333333333333333333333333333333333333 + 24:PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP + --:____.____|____.____|____.____|____.____|____.____|____.____|____.____|____.____| + */ + +load('ansitex/load/windows.js'); + +/** + * This object represents a full page that interacts with the user + * + * @param service - Type of page (tex=ANSI, vtx=VIEWDATA) + * @param debug - Whether to dump some debug information. This is an int, and will start debugging on line debug + * @constructor + * + * Pages have the following attributes: + * - dimensions - (string) representation of their width x height + * - dynamic_fields - (array) Location of fields that are dynamically filled + * - height - (Number) page height + * - input_fields - (array) Location of fields that take input + * - page - (string) Full page number (frame+index) + * - width - (Number) page width + * + * Pages have the following settings: + * - cost - (int) Cost to view the page + * - page - (object) Frame/index of the page + * - provider - (string) Name of the frame provider + * - showHeader - (boolean) Whether to show the header when rendering the frame + * - type - (TEX/VTX) Type of frame + * + * Pages have the following public functions + * - build - Compile the frame for rendering + * - display - Display the compiled frame + * - load - Load a frame from a file source + */ +function Page(service,debug) { + this.__properties__ = { + type: undefined, // Viewdata or ANSItex frame + name: new PageObject, + + layout: undefined, // Window - Full page content + header: undefined, // Window - Page Title + + provider: undefined, // Page provider (*) + pagenum: undefined, // Our page number (*) + cost: undefined, // Page cost (*) + + content: undefined, // Window - Page Content + + input_fields: [], // Array of our input fields + dynamic_fields: [], // Array of our dynamic fields + + isAccessible: undefined, // Is this page visible to all users + isPublic: undefined, // Is this page visible to public (not CUG) users + }; + + this.__defaults__ = { + attr: BG_BLACK|LIGHTGRAY, + }; + + this.__compiled__ = { + build: undefined, + }; + + /* + this.__settings__ = { + pageable: false, // If the virtual window is larger that height (and width is the same) next page is the next content + contenttitle: undefined, // Template (window) for 1st page (a) + contentsubtitle: undefined, // Template (window) for subsequent pages (b..z) + } + */ + + /** + * @todo borders for each window + * @param service + * @param debug + */ + function init(service,debug) { + log(LOG_DEBUG,'- PAGE::init(): type ['+service+']'); + + switch (service) { + case 'tex': + this.__properties__.layout = new Window(1,1,ANSI_FRAME_WIDTH,ANSI_FRAME_HEIGHT+1,'LAYOUT',this,debug); + this.__properties__.content = new Window(1,2,ANSI_FRAME_WIDTH,ANSI_FRAME_HEIGHT,'CONTENT',this.__properties__.layout,debug); + + this.__properties__.header = new Window(1,1,ANSI_FRAME_WIDTH,1,'HEADER',this.__properties__.layout,debug); + this.__properties__.provider = new Window(1,1,ANSI_FRAME_PROVIDER_LENGTH,1,'PROVIDER',this.__properties__.header,debug); + this.__properties__.pagenum = new Window(57,1,ANSI_FRAME_PAGE_LENGTH,1,'#',this.__properties__.header,debug); + this.__properties__.cost = new Window(71,1,ANSI_FRAME_COST_LENGTH,1,'$',this.__properties__.header,debug); + + break; + + case 'vtx': + // @todo VTX hasnt been worked on at all - need at last a viewdata2attrs function + this.__properties__.layout = new Window(1,1,VIEWDATA_FRAME_WIDTH,VIEWDATA_FRAME_HEIGHT+1,'LAYOUT',this,debug); + this.__properties__.content = new Window(1,2,VIEWDATA_FRAME_WIDTH,VIEWDATA_FRAME_HEIGHT,'CONTENT',this.__properties__.layout,debug) + + this.__properties__.header = new Window(1,1,VIEWDATA_FRAME_WIDTH,1,'HEADER',this.__properties__.layout,debug); + this.__properties__.provider = new Window(1,1,VIEWDATA_FRAME_PROVIDER_LENGTH,1,'PROVIDER',this.__properties__.header,debug); + this.__properties__.pagenum = new Window(24,1,VIEWDATA_FRAME_PAGE_LENGTH,1,'#',this.__properties__.header,debug); + this.__properties__.cost = new Window(35,1,VIEWDATA_FRAME_COST_LENGTH,1,'$',this.__properties__.header,debug); + + break; + + default: + throw new Error('INVALID Page Service: '+service); + } + + this.service = service; + } + + init.apply(this,arguments); + + // @todo change this to Object.defineProperty() - see session.js + /** + * Determine if this frame is accessible to the current user + */ + Page.prototype.__defineGetter__('accessible',function() { + log(LOG_DEBUG,'- Checking if user can access frame: '+this.name.name); + log(LOG_DEBUG,' - User: '+JSON.stringify(user.number)); + log(LOG_DEBUG,' - Frame Owner: '+JSON.stringify(this.owner)+', System Frame: '+(this.pageowner === SYSTEM_OWNER)); + + // user.number 0 is unidentified user. + if (user.number) { + return ( + (this.__properties__.isAccessible && this.pageowner === SYSTEM_OWNER && ! this.__properties__.isPublic) || + (this.__properties__.isAccessible && this.__properties__.isPublic) || + (this.__properties__.isAccessible && ! this.__properties__.isPublic && this.isMember) || + (pageEditor(this.frame)) + ); + + } else { + return (this.__properties__.isAccessible && this.pageowner === SYSTEM_OWNER && this.__properties__.isPublic); + } + }); + + Page.prototype.__defineSetter__('cost',function(int) { + if (typeof int !== 'number') + throw new Error('Cost must be a number'); + + switch (this.service) { + case 'tex': + if ((''+int).length > ANSI_FRAME_COST_LENGTH-1) + throw new Error('Cost too large'); + + this.__properties__.cost.__properties__.content = anstoattrs(ESC+'[1;32m'+padright(int,ANSI_FRAME_COST_LENGTH-1,' ')+'c').content; + + break; + + case 'vtx': + if ((''+int).length > VIEWDATA_FRAME_COST_LENGTH-2) + throw new Error('Cost too large'); + + this.__properties__.cost.__properties__.content = bintoattrs(VIEWDATA_BIN_GREEN+padright(int,VIEWDATA_FRAME_COST_LENGTH-2,' ')+'c').content; + + break; + + default: + throw new Error(this.service+' type not implemented'); + } + }); + + Page.prototype.__defineGetter__('dimensions',function() { + return this.width+' X '+this.height; + }); + + Page.prototype.__defineGetter__('dynamic_fields',function() { + return this.__properties__.dynamic_fields; + }); + + Page.prototype.__defineGetter__('height',function() { + return Number(this.__properties__.layout.height); + }); + + Page.prototype.__defineGetter__('input_fields',function() { + return this.__properties__.input_fields; + }); + + Page.prototype.__defineSetter__('isAccessible',function(bool) { + if ((typeof bool !== 'boolean') && (typeof bool !== 'number')) + throw new Error('isAccessible must be a boolean'); + + this.__properties__.isAccessible = (bool === 1); + }); + + Page.prototype.__defineSetter__('isPublic',function(bool) { + if ((typeof bool !== 'boolean') && (typeof bool !== 'number')) + throw new Error('isPublic must be a boolean'); + + this.__properties__.isPublic = (bool === 1); + }); + + Page.prototype.__defineGetter__('name',function() { + return this.__properties__.name; + }); + Page.prototype.__defineSetter__('name',function(object) { + if (!(object instanceof PageObject)) + throw new Error('Page must be PageObject'); + + this.__properties__.name = object; + + switch (this.service) { + case 'tex': + if ((''+this.name.frame).length > ANSI_FRAME_PAGE_LENGTH-1) + throw new Error('Pagenum too large'); + + this.__properties__.pagenum.__properties__.content = anstoattrs(ESC+'[1;37m'+this.name.name).content; + + break; + + case 'vtx': + if ((''+this.name.frame).length > VIEWDATA_FRAME_PAGE_LENGTH-2) + throw new Error('Pagenum too large'); + + this.__properties__.pagenum.__properties__.content = bintoattrs(VIEWDATA_BIN_WHITE+this.name.name).content; + + break; + + default: + throw new Error(this.service+' type not implemented'); + } + }); + + Page.prototype.__defineGetter__('pagenext',function() { + return this.__properties__.name.next; + }); + + /** + * Determine who the owner of a page is + */ + Page.prototype.__defineGetter__('pageowner',function() { + log(LOG_DEBUG,'Getting page owner for:'+this.__properties__.name.__properties__.frame); + + return pageOwner(this.__properties__.name.__properties__.frame).prefix; + }); + + Page.prototype.__defineSetter__('provider',function(ansi) { + var provider; + + switch (this.service) { + case 'tex': + provider = anstoattrs(ansi+ESC+'[0m').content; + + if (provider[1].filter(function(child) { return child.ch; }).length-1 > ANSI_FRAME_PROVIDER_LENGTH) + throw new Error('Provider too large'); + + break; + + case 'vtx': + provider = bintoattrs(ansi).content; + + if (provider[1].length-1 > VIEWDATA_FRAME_PROVIDER_LENGTH) + throw new Error('Provider too large'); + + break; + + default: + throw new Error(this.service+' not implemented'); + } + + this.__properties__.provider.__properties__.content = provider; + }); + + Page.prototype.__defineGetter__('service',function() { + return this.__properties__.service; + }); + Page.prototype.__defineSetter__('service',function(string) { + if (this.__properties__.service) + throw new Error('service already DEFINED'); + + if (['VTX','TEX'].indexOf(string) === undefined) + throw new Error('Unknown SERVICE:'+string); + + return this.__properties__.service = string; + }); + + Page.prototype.__defineSetter__('showHeader',function(bool) { + if (typeof bool !== 'boolean') + throw new Error('showHeader expected a true/false'); + + this.__properties__.header.__properties__.visible = bool; + }); + + Page.prototype.__defineGetter__('width',function() { + return Number(this.__properties__.layout.width); + }); + + /** + * Build the screen layout + * + * @returns {*} + */ + this.build = function(force) { + if (this.__compiled__.build && ! force) + throw new Error('Refusing to build without force.'); + + this.build_system_fields(); + this.__compiled__.build = this.__properties__.layout.build(1,1,false); + + // Add our dynamic values + var fields = this.dynamic_fields.filter(function(item) { return item.value !== undefined; }); + + // writeln('We have DF fields:'+fields.length); + + if (fields.length) + insert_fields(fields,this.__compiled__.build); + + // Add our dynamic values + fields = this.input_fields.filter(function(item) { return item.value !== undefined; }); + + // writeln('We have INPUT fields:'+fields.length); + if (fields.length) + insert_fields(fields,this.__compiled__.build); + + // Insert our *_field data (if it is set) + function insert_fields(fields,build) { + for (var i in fields) { + // writeln('- adding:'+fields[i].name+', with value:'+fields[i].value); + + var content = fields[i].value.split(''); + + for (x=fields[i].x;x= 0) + that.dynamic_field(field.name,atcode(field.name,field.length,field.pad,undefined)); + }); + } + + /** + * Return the compiled screen as an array of lines + * + * @param last - the last attribute sent to the screen + * @param color - whether to render the color attributes + */ + this.display = function(last,color) { + var debug = false; + + if (! this.__compiled__.build) + this.build(); + + // Our built display + var display = this.__compiled__.build; + + // Default attribute when the screen is cleared + var new_screen; + // Default attribute when a new line is started + var new_line; + + var result = []; + + var attr; + + switch (this.service) { + case 'tex': + new_screen = BG_BLACK|LIGHTGRAY; + break; + + case 'vtx': + new_screen = BG_BLACK|LIGHTGRAY; + new_line = BG_BLACK|LIGHTGRAY; + break; + + default: + throw new Error(SESSION_EXT+' dump processing not implemented'); + } + + if (last === undefined) + last = new_screen; + + // Check all our dynamic fields have been placed + df = this.dynamic_fields.filter(function(item) { return item.value === undefined; }); + + // If our dynamic fields havent been filled in + if (df.length > 0) + throw new Error('Dynamic fields without values:'+(df.map(function(item) { return item.name; }).join('|'))); + + // Render the display + for (y=1;y<=this.height;y++) { + var line = ''; + + if (new_line) + last = new_line; + + if (debug) + writeln('============== ['+y+'] ==============='); + + for (x=1;x<=this.width;x++) { + if (debug) + log(LOG_DEBUG,'* CELL : y:'+y+', x:'+x); + // The current char value + var char = (display[y] !== undefined && display[y][x] !== undefined) ? display[y][x] : undefined; + if (debug) + log(LOG_DEBUG,' - CHAR : '+(char !== undefined ? char.__properties__.ch : 'undefined')+', ATTR:'+(char !== undefined ? char.__properties__.attr : 'undefined')+', LAST:'+last); + + if (debug) { + writeln(); + writeln('-------- ['+x+'] ------'); + writeln('y:'+y+',x:'+x+', attr:'+(char !== undefined ? char.__properties__.attr : 'undefined')); + } + + if ((color === undefined) || color) { + // Only write a new attribute if it has changed (and not Videotex) + if ((this.service === 'vtx') || (last === undefined) || (last !== char.__properties__.attr)) { + // The current attribute for this character + attr = (char === undefined) ? undefined : char.attr(last,this.service,debug); + + switch (this.service) { + case 'tex': + // If the attribute is null, we'll write our default attribute + if (attr === null) + line += this.__defaults__.attr + else + line += (attr !== undefined) ? attr : ''; + + break; + + case 'vtx': + // If the attribute is null, we'll ignore it since we are drawing a character + if ((attr !== undefined) && (attr !== null)) { + if (debug) + log(LOG_DEBUG,' = SEND ATTR :'+attr+', attr length:'+attr.length+', last:'+last); + line += attr; + } + + break; + + default: + throw new Error('service type:'+this.service+' hasnt been implemented.'); + } + } + + // For no-color output and ViewData, we'll render a character + } else { + if ((this.service === 'vtx') && char.__properties__.attr) + line += '^'; + } + + if (char.ch !== undefined) { + if (debug) + log(LOG_DEBUG,' = SEND CHAR :'+char.ch+', attr:'+char.__properties__.attr+', last:'+last); + + line += (char.ch !== null) ? char.ch : ''; + + } else { + if (debug) + log(LOG_DEBUG,' = CHAR UNDEFINED'); + line += ' '; + } + + last = (char.__properties__.attr === undefined) ? undefined : char.__properties__.attr; + } + + result.push(line); + + if (debug && (y > debug)) + exit(1); + } + + return result; + } + + /** + * Dump a page in an axis grid to view that it renders correctly + * + * @param last - (int) The current cursor color + * @param color - (bool) Optionally show color + * @param debug - (int) Debugging mode starting at line + * + * @note When drawing a Char: + * + * | CH | ATTR | RESULT | + * |------------|------------|--------------------------------------| + * | undefined | undefined | no output (cursor advances 1) | NOOP + * | null | undefined | invalid | + * | defined | undefined | invalid | + * | undefined | null | invalid | + * | null | null | invalid | + * | defined | null | render ch only (cursor advances 1) | Viewdata + * | undefined | defined | render attr only (no cursor move) | ANSItex (used to close the edge of a window) + * | null | defined | render attr only (cursor advances 1) | Viewdata + * | defined | defined | render attr + ch (cursor advances 1) | ANSItex + * |------------|------------|--------------------------------------| + * + * + for ANSItex, attribute(s) dont advance the cursor, clear screen sets the default to BG_BLACK|LIGHTGRAY + * + for ViewData, an attribute does advance the cursor, and each attribute advances the cursor, also each new line starts with a default BG_BLACK|WHITE + */ + this.dump = function(last,color,debug) { + if (! this.__compiled__.build) + this.build(); + + // Our built display + var display = this.__compiled__.build; + + color = (color === undefined) || (color === '1') || (color === true); + + writeln('Dumping Page:'+this.name.name); + writeln('= Size :'+this.dimensions); + writeln('- Last :'+last); + writeln('- Color:'+color); + writeln('- Debug:'+debug); + + if (last === undefined) + last = new_screen; + + if (debug) { + writeln('==== content dump ===='); + + var yy = 1; + for (var y in display) { + write(padright(yy,2,0)+':'); + + var xx = 1; + for (var x in display[y]) { + if (debug && (y === debug)) { + writeln(JSON.stringify(display[y][x])); + writeln() + } + + write('['); + if (display[y][x].__properties__.attr === undefined) { + // NOOP + } else if (display[y][x].__properties__.attr === null) { + // NOOP + } else { + try { + write((last === display[y][x].__properties__.attr) ? '' : display[y][x].__properties__.attr); + } catch (e) { + writeln(); + writeln('error:'+e); + writeln(' y:'+y); + writeln(' x:'+x); + writeln(JSON.stringify(display[y][x].__properties__.attr)); + exit(1); + } + } + + write(':'); + + if (display[y][x].__properties__.ch === undefined) { + // NOOP - No window filled a character at this location + write((display[y][x].__properties__.attr === undefined) ? '--' : ''); + } else if (display[y][x].__properties__.ch === null) { + // NOOP + } else { + write('_'+display[y][x].__properties__.ch); + } + + write(']'); + last = display[y][x].__properties__.attr; + + xx++; + } + + writeln('|'+padright(xx-1,2,0)); + + xx = 0; + yy++; + } + + // Detail dump when debug is a line number + if (debug && (y > debug)) { + writeln('=========================='); + + for (var y in display) { + writeln ('------ ['+y+'] -------'); + + var xx = 1; + for (var x in display[y]) { + + var attr = display[y][x].__properties__.attr; + + writeln('X:'+(xx++)+'|'+attr+':'+display[y][x].__properties__.ch+'|'+display[y][x].attr(last,this.service,debug)); + + // Only write a new attribute if it has changed + if ((this.last === undefined) || (this.last !== attr)) { + this.last = attr; + } + } + } + } + + writeln('==== END content dump ===='); + } + + // Dump Header + write('--:'); + for (x=0;x 0) + this.putmsg(lines.shift()+"\r\n"); + + break; + */ + + default: + throw new Error('Unsupported filetype:'+ext); + } + + // Successful load + return true; + } + + /** + * After page load routines + */ + this.loadcomplete = function() { + var po = pageOwner(this.name.frame); + + switch (this.service) { + case 'tex': + this.__properties__.pagenum.__properties__.content = anstoattrs(ESC+'[1;37m'+this.name.name).content; + this.provider = base64_decode(po.logoans); + + break; + + case 'vtx': + this.__properties__.pagenum.__properties__.content = bintoattrs(VIEWDATA_BIN_WHITE+this.name.name).content; + this.provider = base64_decode(po.logovtx); + + break; + + default: + throw new Error(this.service+' hasnt been implemented'); + } + + // Dont show header on un-authed login frames + if (! user.number) + this.showHeader = false; + } + + /** + * Save the frame for later retrieval + * @todo Inject back all input_fields and dynamic_fields + * @todo this is not complete? + */ + this.save = function() { + var line; + + // If we have any input fields, we need to insert them back inside ESC .... ESC \ control codes + // @todo avoid the ending ESC \ with a control code. + + this.input_fields.filter(function(child) { + if (child.y === y) { + line.content = line.content.substring(0,child.x-1) + + 'FIELD:'+child.name + + line.content.substring(child.x+child.length,80) + + 'END'; + } + }) + + // We draw line by line. + for (var y=1;y<=this.height;y++) { + // Line intro + write('\x1b[0m'); + + line = this.__properties__.layout.drawline(1,this.width,y,false); + write(line.content); + + write('\x1b[0m'); + + writeln(); + } + } +} + +function PageObject(frame,index) { + this.__properties__ = { + frame: '0', // Frame number + index: 'a', // Frame index + } + + function init(frame,index) { + if (typeof frame === 'object') { + this.__properties__.frame = frame.frame; + this.__properties__.index = frame.index; + + } else { + this.__properties__.frame = frame; + this.__properties__.index = index; + } + } + + init.apply(this,arguments); + + PageObject.prototype.__defineGetter__('frame',function() { + return this.__properties__.frame; + }); + // @todo validate that string only has digits + PageObject.prototype.__defineSetter__('frame',function(string) { + if (typeof string !== 'string') + throw new Error('Page.number must be a string'); + + this.__properties__.frame = string; + }); + + PageObject.prototype.__defineGetter__('index',function() { + return this.__properties__.index; + }); + PageObject.prototype.__defineSetter__('index',function(string) { + if (typeof string !== 'string') + throw new Error('Page.index must be a string'); + + if (string.length !== 1) + throw new Error('Page.index can only be 1 char'); + + this.__properties__.index = string; + }); + + PageObject.prototype.__defineGetter__('name',function() { + return (this.__properties__.frame && this.__properties__.index) ? this.frame+this.index : null; + }); + + PageObject.prototype.__defineGetter__('next',function() { + var next = undefined; + + if (this.__properties__.index !== 'z') { + log(LOG_DEBUG,'page_next: Current page:'+this.__properties__.frame+', current index:'+this.__properties__.index); + + next = new PageObject(this.__properties__.frame,String.fromCharCode(this.__properties__.index.charCodeAt(0)+1)); + } + + return next; + }); +} diff --git a/load/session-ansitex.js b/load/session-ansitex.js new file mode 100644 index 0000000..e441ae9 --- /dev/null +++ b/load/session-ansitex.js @@ -0,0 +1,666 @@ +var SESSION_ANSITEX = (1<<1); +var SESSION_EXT = 'tex'; + +var ANSI_FRAME_WIDTH = 80; +var ANSI_FRAME_HEIGHT = 22; +var ANSI_FRAME_PROVIDER_LENGTH = 55; +var ANSI_FRAME_PAGE_LENGTH = 13; +var ANSI_FRAME_COST_LENGTH = 10; + +/** + * This function converts ANSI text into an array of attributes + * + * We include the attribute for every character, so that if a window is placed on top of this window, the edges + * render correctly. + * + * @param contents - Our ANSI content to convert + * @param width - The width before wrapping to the next line + * @param yoffset - fields offset as discovered + * @param xoffset - fields offset as discovered + * @param debug - Enable debug mode + */ +function anstoattrs(contents,width,yoffset,xoffset,debug) { + if (debug) + writeln('DEBUG active: '+debug); + + lines = (''+contents).split(/\r\n/); + + var i = 0; + var bg = BG_BLACK; + var fg = LIGHTGRAY; + var attr = fg + bg + i; + + var y = 0; + // Saving cursor positions + var saved = {}; + + var frame = { + content: [], + dynamic_fields: [], + input_fields: [], + }; + + // @todo temp hack, rework ansi variable - perhaps have a function that converts an attribute back into an ANSI sequence + var ansi = { i: 0, f: 37, b: 40 }; + + while (lines.length > 0) { + var x = 0; + var line = lines.shift(); + + if ((debug !== undefined) && (y > debug)) { + exit(1); + } + + if (debug) { + log(LOG_DEBUG,'y:'+y); + log(LOG_DEBUG,'line:'+line); + } + + while (line.length > 0) { + /* parse an attribute sequence*/ + var m = line.match(/^\x1b\[((\d+)(;?(\d+))*)+m/); + if (m !== null) { + line = line.substr(m.shift().length); + m = m.shift().split(';').sort(function(a,b) { return Number(a) < Number(b) ? -1 : 1; }); + + if ((debug !== undefined) && debug === y) { + //writeln(); + log(LOG_DEBUG,'m:'+JSON.stringify(m)); + } + + // Reset + if (Number(m[0]) === 0) { + bg = BG_BLACK; + fg = LIGHTGRAY; + i = 0; + ansi = { i:0, b:37, f:40}; + m.shift(); + + if (debug) + log(LOG_DEBUG,' - RESET'); + } + + // High Intensity + if (Number(m[0]) === 1) { + i += (((i === 0) || (i === BLINK)) ? HIGH : 0); + m.shift(); + ansi.i = 1; + + if (debug && (debug === y)) + writeln('fg:'+fg+', bg:'+bg+' i:'+i); + } + + // Blink + if (Number(m[0]) === 5) { + i += (((i === 0) || (i === HIGH)) ? BLINK : 0); + m.shift(); + } + + // Foreground + if ((Number(m[0]) >= 30) && (Number(m[0]) <= 37)) { + ansi.f = Number(m[0]); + + switch(Number(m.shift())) { + case 30: + fg = BLACK; + break; + + case 31: + fg = RED; + break; + + case 32: + fg = GREEN; + break; + + case 33: + fg = BROWN; + break; + + case 34: + fg = BLUE; + break; + + case 35: + fg = MAGENTA; + break; + + case 36: + fg = CYAN; + break; + + case 37: + fg = LIGHTGRAY; + break; + } + } + + // Background + if ((Number(m[0]) >= 40) && (Number(m[0]) <= 47)) { + ansi.b = Number(m[0]); + + switch (Number(m.shift())) { + case 40: + bg = BG_BLACK; + break; + + case 41: + bg = BG_RED; + break; + + case 42: + bg = BG_GREEN; + break; + + case 43: + bg = BG_BROWN; + break; + + case 44: + bg = BG_BLUE; + break; + + case 45: + bg = BG_MAGENTA; + break; + + case 46: + bg = BG_CYAN; + break; + + case 47: + bg = BG_LIGHTGRAY; + break; + } + } + + if (debug) { + log(LOG_DEBUG,'fg:'+fg+', bg:'+bg+' i:'+i); + log(LOG_DEBUG,'ansi:'+JSON.stringify(ansi)); + } + + attr = bg + fg + i; + continue; + } + + if (debug) + log(LOG_DEBUG,'= Current attr:'+attr); + + /* parse absolute character position */ + var m = line.match(/^\x1b\[(\d*);?(\d*)[Hf]/); + if (m !== null) { + line = line.substr(m.shift().length); + + if (m.length === 0) { + x = 0; + y = 0; + + } else { + if(m[0]) + y = Number(m.shift())-1; + + if(m[0]) + x = Number(m.shift())-1; + } + + continue; + } + + /* ignore a bullshit sequence */ + var n = line.match(/^\x1b\[\?7h/); + if (n !== null) { + line = line.substr(n.shift().length); + continue; + } + + /* parse an up positional sequence */ + var n = line.match(/^\x1b\[(\d*)A/); + if (n !== null) { + line = line.substr(n.shift().length); + + var chars = n.shift(); + + y -= (chars < 1) ? 0 : Number(chars); + + continue; + } + + /* parse a down positional sequence */ + var n = line.match(/^\x1b\[(\d*)B/); + if (n !== null) { + line = line.substr(n.shift().length); + + var chars = n.shift(); + + y += (chars < 1) ? 0 : Number(chars); + + continue; + } + + /* parse a forward positional sequence */ + var n = line.match(/^\x1b\[(\d*)C/); + if (n !== null) { + line = line.substr(n.shift().length); + + var chars = n.shift(); + + x += (chars < 1) ? 0 : Number(chars); + + continue; + } + + /* parse a backward positional sequence */ + var n = line.match(/^\x1b\[(\d*)D/); + if (n !== null) { + line = line.substr(n.shift().length); + + var chars = n.shift() + + x -= (chars < 1) ? 0 : Number(chars); + + continue; + } + + /* parse a clear screen sequence */ + var n = line.match(/^\x1b\[2J/); + if (n !== null) { + line = line.substr(n.shift().length); + + continue; + } + + /* parse save cursor sequence */ + var n = line.match(/^\x1b\[s/); + if (n !== null) { + line = line.substr(n.shift().length); + saved.x = x; + saved.y = y; + + continue; + } + + /* parse restore cursor sequence */ + var n = line.match(/^\x1b\[u/); + if (n !== null) { + line = line.substr(n.shift().length); + x = saved.x; + y = saved.y; + + continue; + } + + /* parse an input field */ + // Input field 'FIELD;valueTYPE;input char' + // @todo remove the trailing ESC \ to end the field, just use a control code ^B \x02 (Start of Text) and ^C \x03 + var m = line.match(/^\x1b_(([A-Z]+;[0-9a-z]+)([;]?.+)?)\x1b\\/); + if (m !== null) { + log(LOG_DEBUG,'Got input field: '+JSON.stringify(m)); + log(LOG_DEBUG,'ansi:'+JSON.stringify(ansi)); + // full string that matched + match = m.shift(); + + // thus, the rest of the line + line = line.substr(match.length); + //writeln('rest of line:'+JSON.stringify(line)); + + // We are interested in our field match + var sos = m.shift().split(';'); + //writeln('sos:'+JSON.stringify(sos)); + + for (var num in sos) { + switch (num) { + // First value is the field name + case '0': + field = sos[num]; + break; + + // Second value is the length/type of the field, nnX nn=size in chars, T=type (lower case) + case '1': + var c = sos[num].match(/([0-9]+)([a-z])/); + if (! c) { + log(LOG_ERROR,'SOS FAILED PARSING FIELD LENGTH/TYPE. ['+r+'x'+c+'] '+sos[num]); + break; + } + + //log(LOG_DEBUG,'SOS ['+r+'x'+c+'] NUM CHARS: '+x[1]+', TYPE: '+x[2]); + fieldlen = c[1]; + fieldtype = c[2]; + break; + + // Third field is the char to to use + case '2': + fieldchar = sos[num]; + break; + + default: + log(LOG_ERROR,'IGNORING ADDITIONAL SOS FIELDS. ['+r+'x'+c+'] '+sos[num]); + } + } + + // If we are padding our field with a char, we need to add that back to line + // @todo validate if this goes beyond our width (and if scrolling not enabled) + if (fieldlen) + line = fieldchar.repeat(fieldlen)+line; + + frame.input_fields.push({ + type: fieldtype, + length: Number(fieldlen), + char: fieldchar, + name: field, + attribute: JSON.parse(JSON.stringify(ansi)), + x: Number(x+(xoffset !== undefined ? xoffset : 0)), + y: Number(y+(yoffset !== undefined ? yoffset : 0)), + value: '', + }); + + log(LOG_DEBUG,'input_field:'+JSON.stringify(frame.input_fields.last)); + } + + /* parse dynamic value field */ + // @todo remove the trailing ESC \ to end the field, just use a control code ie: ^E \x05 (Enquiry) or ^Z \x26 (Substitute) + var m = line.match(/^\x1bX(([a-zA-Z._:^;]+[0-9]?;-?[0-9^;]+)([;]?[^;]+)?)\x1b\\/); + if (m !== null) { + // full string that matched + match = m.shift(); + + // thus, the rest of the line + line = line.substr(match.length); + //writeln('rest of line:'+JSON.stringify(line)); + + // We are interested in our field match + var df = m.shift().split(';'); + + log(LOG_DEBUG,'- DF found at ['+x+'x'+y+'], Field: '+df[0]+', Length: '+df[1]+', Pad:'+df[2]); + // If we are padding our field with a char, we need to add that back to line + // @todo validate if this goes beyond our width (and if scrolling not enabled) + line = (df[2] ? df[2] : '_').repeat(Math.abs(df[1]))+line; + + frame.dynamic_fields.push({ + name: df[0], + length: df[1], + pad: df[2], + x: x+(xoffset !== undefined ? xoffset : 0), + y: y+(yoffset !== undefined ? yoffset : 0), + value: undefined, + }); + } + + /* set character and attribute */ + var ch = line[0]; + line = line.substr(1); + + if (debug && debug === y) { + log(LOG_DEBUG,'Got char: '+ch); + //write(ch); + } + + /* validate position */ + if (y < 0) + y = 0; + if (x < 0) + x = 0; + + if (x >= width) { + x = 0; + y++; + } + + /* set character and attribute */ + if (! frame.content[y+1]) + frame.content[y+1]=[]; + + if (attr === null) + throw new Error('Attribute is null?'); + + frame.content[y+1][x+1] = new Char(ch,attr); + + x++; + } + + // If we got a BG_BLACK|LIGHTGRAY ESC [0m, but not character, we include it as it resets any background that was going on + if ((attr === BG_BLACK|LIGHTGRAY) && frame.content[y+1] && (frame.content[y+1][x].__properties__.attr !== attr)) + frame.content[y+1][x+1] = new Char(undefined,attr); + + y++; + } + + return frame; +} + +load('ansitex/load/session.js'); + +// Our frame object +function SessionAnsitex() { + Session.apply(this,arguments); + + /* File Extension used for frames */ + this.settings.ext = 'tex'; + + /* Length of a frame */ + this.settings.FRAME_LENGTH = 22; + /* Width of a frame */ + this.settings.FRAME_WIDTH = 80; + /* Size of page owner (length) */ + this.settings.FRAME_HEADER = 56; + /* Size of page number (length with a-z) */ + this.settings.FRAME_PAGENUM = 12; + /* Size of cost (length without unit) */ + this.settings.FRAME_COST = 9; + + this.settings.MSG_SENDORNOT = '\1n\1h\1GKEY 1 TO SEND, 2 NOT TO SEND'; + this.settings.MSG_LOGON = '\1n\1h\1GKEY 1 TO LOGON, 2 TO RETURN'; + this.settings.MSG_SENT = '\1n\1h\1GMESSAGE SENT - KEY # TO CONTINUE'; + this.settings.MSG_NOTSENT = '\1n\1h\1GMESSAGE NOT SENT - KEY # TO CONTINUE'; + this.settings.ERR_NO_PARENT = '\1n\1h\1RPARENT FRAME DOESNT EXIST'; + this.settings.ERR_NOT_IMPLEMENTED = '\1n\1h\1RNOT IMPLEMENTED YET?'; + this.settings.ERR_ROUTE = '\1n\1h\1WMISTAKE? \1GTRY AGAIN OR TELL US ON *08'; + this.settings.ERR_METHOD_NOT_EXIST = '\1n\1h\1WMISTAKE? \1GTRY AGAIN OR TELL US ON *08'; + this.settings.ACCESS_DENIED = '\1n\1h\1RACCESS DENIED. MISTAKE? TRY AGAIN OR TELL US *08'; + this.settings.ALREADY_MEMBER = '\1n\1h\1RALREADY MEMBER OF CUG' + this.settings.INACTIVITY = '\1n\1h\1RINACTIVITY ALERT, DISCONNECT PENDING...'; + this.settings.INACTIVE = '\1n\1h\1RINACTIVITY DISCONNECT'; + this.settings.NOACTION = '\1n\1h\1RNO ACTION PERFORMED'; + this.settings.BASESTAR = '\1N\1G\1H*'; + this.settings.INVALID_CODE = '\1n\1h\1RINVAID CODE, PLEASE TRY AGAIN *00'; + this.settings.TOKEN_EMAIL = '\1n\1h\1RTOKEN EMAILED TO YOU...'; + this.settings.TOKEN_SENT = '\1n\1h\1RTOKEN SENT, PLEASE ENTER TOKEN'; + this.settings.INVALID_EMAIL = '\1n\1h\1RINVAID EMAIL, PLEASE TRY AGAIN *00'; + this.settings.INVALID_UID = '\1n\1h\1RINVAID USER ID, PLEASE TRY AGAIN *00'; + this.settings.CANNOT_SEND_TOKEN = '\1n\1h\1RCANNOT SEND VALIDATION CODE, PLEASE TRY AGAIN *00'; + this.settings.USER_EXISTS = '\1n\1h\1RERROR USER EXISTS, PLEASE TRY AGAIN *00'; + this.settings.USER_CREATE_ERROR = '\1n\1h\1RERROR CREATING USER, PLEASE TRY AGAIN *00'; + this.settings.LOGIN_ERROR = '\1n\1h\1RERROR LOGGING IN, PLEASE TRY AGAIN *00'; + this.settings.CANCEL_MSG = '\1n\1h\1GPRESS 2 TO CANCEL'; + this.settings.SYS_ERROR = '\1n\1h\1RSYSTEM ERROR DETECTED - TRY AGAIN OR TELL US *08'; + this.settings.LOADING = '\1n\1h\1Kloading...'; + this.settings.PROCESSING = '\1n\1h\1Kprocessing...'; + + /** + * Set the attribute at the current position + */ + this.attr = function(field) { + write('\x1b['+field.i+';'+field.f+';'+field.b+'m'); + } + + this.baselineClear = function(reposition) { + this.cursorSave(); + this.gotoxy(0,24); + this.cleareol(); + + if (! reposition) + this.cursorRestore(); + } + + /** + * Send a message to the baseline. + * + * @param text + * @param reposition + */ + this.baselineSend = function(text,reposition) { + this.cursorSave(); + this.gotoxy(0,24); + write(this.getMessage(text)); + this.cleareol(); + + if (! reposition) + this.cursorRestore(); + } + + /** + * Turn off the cursor + */ + this.cursorOff = function() { + write('\x1b[?25l'); + write('\x1b[24;0H'); + } + + /** + * Turn on cursor + * @param x + * @param y + */ + this.cursorOn = function(x,y) { + write('\x1b[?25h'); + + if (x && y) + this.gotoxy(x,y); + } + + this.cursorRestore = function() { + write('\x1b[u'); + } + + this.cursorSave = function() { + write('\x1b[s'); + } + + this.cleareol = function() { + write('\x1b[0K'); + } + + // Field backspace, that leaves the field filler char + this.fieldbs = function(char) { + write('\x1b[D'+char+'\x1b[D'); + } + + /** + * Position the cursor in a specific location + * + * @param x + * @param y + */ + this.gotoxy = function(x,y) { + write('\x1b['+y+';'+x+'H'); + } + + /* + // Render the frame to the user + this.render=function(context,withoutHeader) { + log(LOG_DEBUG,'- ANSI FRAME'); + owner = base64_decode(this.owner); + + const frame = new Frame(1,1,this.settings.FRAME_WIDTH,this.settings.FRAME_LENGTH+2,LIGHTGRAY); + frame.open(); + + // Dont show the page number on system login page + if ((! withoutHeader) && (user.number || (this.type !== FRAME_TYPE_LOGIN && NO_HISTORY_FRAMES.indexOf(this.page) === -1))) { + log(LOG_DEBUG,' - Owner: ['+this.pageowner+']'); + + cost = (this.isAccessible ? this.cost+FRAME_COSTUNIT : ' -'); + + header = '\1n'+this.pageownerlogo+' '.repeat(this.settings.FRAME_HEADER-console.strlen(this.pageownerlogo))+'\1n '+ + (this.isAccessible ? '\1W' : '\1R')+'\1H'+this.page+' '.repeat(this.settings.FRAME_PAGENUM-this.page.length)+' '+ + '\1G\1H'+' '.repeat(this.settings.FRAME_COST-cost.toString().length+1)+cost+'\1n'+ + (console.screen_columns > 80 ? '\n\r' : ''); + + frame.putmsg(header); + } + + contentgraphic = new Graphic(this.settings.FRAME_WIDTH); + contentgraphic.auto_extend = true; + contentgraphic.atcodes = false; + contentgraphic.ANSI = this.parse(base64_decode(this.content),context); + + var contentframe = new Frame(1,2,this.settings.FRAME_WIDTH,this.settings.FRAME_LENGTH,LIGHTGRAY,frame); + contentframe.open(); + contentframe.lf_strict = false; + contentframe.atcodes = false; + contentframe.word_wrap = false + + contentframe.putmsg(contentgraphic.MSG) + contentframe.scrollTo(0,0); + + frame.cycle(); + + return contentframe; + }; + */ + + this.qrcode=function(qr,subframe) { + // SMALL Image + var full = ascii(0xdb); + var top = ascii(0xdf); + var bot = ascii(0xdc); + var blank = ' '; + + var qrcode = ''; + + /* + // Render the top line + var line = ascii(27)+'[1;37m'+bot; + for (var y = 0; y < qr.size; y++) { + line += bot; + } + qrcode += line+bot+bot+ascii(27)+'[0m'+"\r\n"; + */ + + // Render the body + for (var x = -1; x < qr.size; x=x+2) { + line = ascii(27)+'[1;37m'+full; + + for (var y = 0; y < qr.size; y++) { + // Top is white + if (! ((x===-1)? 0 : qr.getModule(x, y))) { + line += (qr.getModule(x+1, y)) ? top : full; + + // Top is black + } else { + line += (qr.getModule(x+1, y)) ? blank : bot; + } + } + + qrcode += line+full+ascii(27)+'[0m'+"\r\n"; + } + + // Render the bottom + line = ascii(27)+'[1;37m'+top; + for (var y = 0; y < qr.size; y++) { + line += top; + } + qrcode += line+top+ascii(27)+'[0m'+"\r\n"; + + ans2bin(fo.parse(qrcode),subframe); + subframe.open(); + subframe.cycle(); + }; + + /* + this.save=function() { + file = system.mods_dir+'ansitex/text/'+this.page+'.tex'; + w = new File(file); + if (! w.open('w')) { + log(LOG_ERROR,'! ERROR: Unable to create TEX file for '+this.page); + exit(1); + } + + w.write(JSON.stringify(this)); + w.close(); + + log(LOG_DEBUG,'Saved file: '+this.page+'.tex'); + } + */ +} + +SessionAnsitex.prototype = Session.prototype; +SessionAnsitex.prototype.constructor = SessionAnsitex; diff --git a/load/session-viewdata.js b/load/session-viewdata.js new file mode 100644 index 0000000..f824586 --- /dev/null +++ b/load/session-viewdata.js @@ -0,0 +1,691 @@ +var SESSION_VIEWDATA = (1<<2); +var SESSION_EXT = 'vtx'; + +var VIEWDATA_FRAME_WIDTH = 40; +var VIEWDATA_FRAME_HEIGHT = 22; +var VIEWDATA_FRAME_PROVIDER_LENGTH = 23; +var VIEWDATA_FRAME_PAGE_LENGTH = 11; +var VIEWDATA_FRAME_COST_LENGTH = 6; + +var VIEWDATA_LEFT = '\x08'; +var VIEWDATA_RIGHT = '\x09'; +var VIEWDATA_DOWN = '\x0a'; // \n +var VIEWDATA_UP = '\x0b'; +var VIEWDATA_CLS = '\x0c'; +var VIEWDATA_CR = '\x0d'; // \r +var VIEWDATA_CON = '\x11'; +var VIEWDATA_COFF = '\x14'; +var VIEWDATA_HOME = '\x1e'; + +var VIEWDATA_BLINK = '\x48'; +var VIEWDATA_STEADY = '\x49'; +var VIEWDATA_NORMAL = '\x4c'; +var VIEWDATA_DOUBLE = '\x4d'; +var VIEWDATA_CONCEAL = '\x58'; +var VIEWDATA_BLOCKS = '\x59'; +var VIEWDATA_SEPARATED = '\x5a'; +var VIEWDATA_BLACKBACK = '\x5c'; +var VIEWDATA_NEWBACK = '\x5d'; +var VIEWDATA_HOLD = '\x5e'; +var VIEWDATA_REVEAL = '\x5f'; + +var VIEWDATA_RED = '\x41'; +var VIEWDATA_GREEN = '\x42'; +var VIEWDATA_YELLOW = '\x43'; // C +var VIEWDATA_BLUE = '\x44'; +var VIEWDATA_MAGENTA = '\x45'; +var VIEWDATA_CYAN = '\x46'; +var VIEWDATA_WHITE = '\x47'; + +var VIEWDATA_MOSIAC_RED = '\x51'; +var VIEWDATA_MOSIAC_GREEN = '\x52'; +var VIEWDATA_MOSIAC_YELLOW = '\x53'; +var VIEWDATA_MOSIAC_BLUE = '\x54'; +var VIEWDATA_MOSIAC_MAGENTA = '\x55'; +var VIEWDATA_MOSIAC_CYAN = '\x56'; +var VIEWDATA_MOSIAC_WHITE = '\x57'; // W + +/* BINARY DUMP LEVEL 1 ATTRIBUTES */ +var VIEWDATA_BIN_RED = '\x01'; +var VIEWDATA_BIN_GREEN = '\x02'; +var VIEWDATA_BIN_YELLOW = '\x03'; +var VIEWDATA_BIN_BLUE = '\x04'; +var VIEWDATA_BIN_MAGENTA = '\x05'; +var VIEWDATA_BIN_CYAN = '\x06'; +var VIEWDATA_BIN_WHITE = '\x07'; + +/** + * ViewData characters are 7bit (0x00-0x7f) + * + * Chars 0x00-0x1f are control characters (display attributes) and are sent to the terminal with 0x1b + * + 0x00-0x07 are foreground colors + * + 0x08-0x09 flash/steady + * + 0x0a-0x0b end/start box (?) * + * + 0x0c-0x0d normal/double height + * + 0x0e-0x0f double width (?) * + * + 0x10-0x17 are foreground graphics (mosiac) colors + * + 0x18/0x1f conceal/reveal + * + 0x19-0x1a solid/seperated graphics + * + 0x1b unused + * + 0x1c-1x1d Black/New Background (new background converts color foreground to background) + * + 0x1e-0x1f graphics hold/release (enables changing color and repeats previous graphics char) + * Chars 0x20-0x7f are normal printed ASCII chars + * Chars 0x20-0x3f & 0x60-0x7f when activated with a MOSIAC color sends a 2x3 pixel character + * + * We can map these into cga_defs with the following amendments: + * 0x00-0x0f = foreground/background colors (4 bits) (8 foreground/8 background colors) + * 0x10 - mosiac (bit 4) + * 0x20 - conceal (bit 5) + * 0x40 - seperated graphics (bit 6) + * 0x80 - flash (bit 7) + * 0x100 - double height (bit 8) + * 0x200 - hold (bit 9) + * 0x400 - new background (bits 10/11) + * 0x800 - black background (bits 10/11) + * 0xc00 - unused (bits 10/11) + * bits (12-15) unused + * + * @type {number} + */ +var MOSIAC = 0x10; + +// Toggles +var CONCEAL = 0x20; +var REVEAL = 0x2000; // @temp Turns off Conceal + +var SEPARATED = 0x40; +var BLOCKS = 0x4000; // @temp Turns off Separated + +var STEADY = 0x8000; // @temp (turn off flash) + +var DOUBLE = 0x100; +var NORMAL = 0x1000; // @temp Turns off Double Height + +var HOLD = 0x200; +var RELEASE = 0x20000; // @temp turns off Hold + +var NEWBACK = 0x400; +var BLACKBACK = 0x800; + +/** + * This function converts ANSI text into an array of attributes + * + * @param contents - Our ANSI content to convert + * @param width - The width before wrapping to the next line + * @param yoffset - fields offset as discovered + * @param xoffset - fields offset as discovered + * @param debug - Enable debug mode + */ +function bintoattrs(contents,width,yoffset,xoffset,debug) { + if (debug) + writeln('DEBUG active: '+debug); + + lines = (''+contents).split(/\r\n/); + + var i = 0; + var bg = BG_BLACK; + var fg = LIGHTGRAY; + var attr = fg + bg + i; + + // Attribute state on a new line + var new_line = attr; + + var y = 0; + + var frame = { + content: [], + dynamic_fields: [], + input_fields: [], + }; + + // @todo temp hack, rework ansi variable - perhaps have a function that converts an attribute back into an ANSI sequence + var ansi = { i: 0, f: 37, b: 40 }; + + while (lines.length > 0) { + var x = 0; + var line = lines.shift(); + + if ((debug !== undefined) && (y > debug)) { + exit(1); + } + + if (debug) { + log(LOG_DEBUG,'y:'+y); + log(LOG_DEBUG,'line:'+line); + write('y:'+y+', line:'+line); + } + + while (line.length > 0) { + if (x >= width) { + x = 0; + // Each new line, we reset the attrs + attr = new_line; + y++; + } + //writeln('next ch:'+line[0].charCodeAt(0)); + + /* parse control codes */ + var m = line.match(/^([\x00-\x1f])/); + if (m !== null) { + line = line.substr(m[0].length); + attr = 0; + + match = m.shift().charCodeAt(0); + + /* + writeln('- match:'+match); + + writeln('- match 0x0f:'+(match & 0x0f)); + if (match & 0x10) { + writeln(' - got mosiac'); + attr += MOSIAC; + } + + */ + //if (match < 0x0f) { + //switch(match & 0x07) { + switch(match) { + case 0x00: + attr += BLACK; + break; + case 0x01: + attr += RED; + break; + case 0x02: + attr += GREEN; + break; + case 0x03: + attr += YELLOW; + break; + case 0x04: + attr += BLUE; + break; + case 0x05: + attr += MAGENTA; + break; + case 0x06: + attr += CYAN; + break; + case 0x07: + attr += LIGHTGRAY; + break; + case 0x08: + attr = BLINK; + break; + case 0x09: + attr = STEADY; + break; + /* + case 0x0a: + //attr = ENDBOX; // End Box (Unused?) + break; + case 0x0b: + //attr = STARTBOX; // Start Box (Unused?) + break; + */ + case 0x0c: + //attr &= ~DOUBLE; + attr = NORMAL; + break; + case 0x0d: + attr = DOUBLE; + break; + case 0x0e: + attr = NORMAL; // @todo Double Width (Unused)? + break; + case 0x0f: + attr = NORMAL; // @todo Double Width (Unused?) + break; + case 0x10: + attr = MOSIAC|BLACK; + break; + case 0x11: + attr += MOSIAC|RED; + break; + case 0x12: + attr += MOSIAC|GREEN; + break; + case 0x13: + attr += MOSIAC|YELLOW; + break; + case 0x14: + attr += MOSIAC|BLUE; + break; + case 0x15: + attr += MOSIAC|MAGENTA; + break; + case 0x16: + attr += MOSIAC|CYAN; + break; + case 0x17: + attr += MOSIAC|LIGHTGRAY; + break; + case 0x18: + attr = CONCEAL; + break; + case 0x19: + attr = BLOCKS; + break; + case 0x1a: + attr = SEPARATED; + break; + /* + case 0x1b: + //attr = NORMAL; // CSI + break; + */ + case 0x1c: + attr = BLACKBACK; // Black Background + break; + case 0x1d: + attr = NEWBACK; // New Background + break; + case 0x1e: + attr = HOLD; // Mosiac Hold + break; + case 0x1f: + attr = RELEASE; // Mosiac Release + break; + + // Catch all for other codes + default: + attr = 0xff00; + } + + if (debug) + writeln(' - got control code:'+attr+'['+y+','+x+'] - length:'+attr.length); + + store(x++,y,null,attr); + attr = undefined; + + continue; + } + + /* parse an input field */ + // Input field 'FIELD;valueTYPE;input char' + // @todo remove the trailing ESC \ to end the field, just use a control code ^B \x02 (Start of Text) and ^C \x03 + var m = line.match(/^\x1b_(([A-Z]+;[0-9a-z]+)([;]?.+)?)\x1b\\/); + if (m !== null) { + log(LOG_DEBUG,'Got input field: '+JSON.stringify(m)); + log(LOG_DEBUG,'ansi:'+JSON.stringify(ansi)); + // full string that matched + match = m.shift(); + + // thus, the rest of the line + line = line.substr(match.length); + //writeln('rest of line:'+JSON.stringify(line)); + + // We are interested in our field match + var sos = m.shift().split(';'); + //writeln('sos:'+JSON.stringify(sos)); + + for (var num in sos) { + switch (num) { + // First value is the field name + case '0': + field = sos[num]; + break; + + // Second value is the length/type of the field, nnX nn=size in chars, T=type (lower case) + case '1': + var c = sos[num].match(/([0-9]+)([a-z])/); + if (! c) { + log(LOG_ERROR,'SOS FAILED PARSING FIELD LENGTH/TYPE. ['+r+'x'+c+'] '+sos[num]); + break; + } + + //log(LOG_DEBUG,'SOS ['+r+'x'+c+'] NUM CHARS: '+x[1]+', TYPE: '+x[2]); + fieldlen = c[1]; + fieldtype = c[2]; + break; + + // Third field is the char to to use + case '2': + fieldchar = sos[num]; + break; + + default: + log(LOG_ERROR,'IGNORING ADDITIONAL SOS FIELDS. ['+r+'x'+c+'] '+sos[num]); + } + } + + // If we are padding our field with a char, we need to add that back to line + // @todo validate if this goes beyond our width (and if scrolling not enabled) + if (fieldlen) + line = fieldchar.repeat(fieldlen)+line; + + frame.input_fields.push({ + type: fieldtype, + length: Number(fieldlen), + char: fieldchar, + name: field, + attribute: JSON.parse(JSON.stringify(ansi)), + x: Number(x+(xoffset !== undefined ? xoffset : 0)), + y: Number(y+(yoffset !== undefined ? yoffset : 0)), + value: '', + }); + + log(LOG_DEBUG,'input_field:'+JSON.stringify(frame.input_fields.last)); + } + + /* parse dynamic value field */ + // @todo remove the trailing ESC \ to end the field, just use a control code ie: ^E \x05 (Enquiry) or ^Z \x26 (Substitute) + var m = line.match(/^\x1bX(([a-zA-Z._:^;]+[0-9]?;-?[0-9^;]+)([;]?[^;]+)?)\x1b\\/); + if (m !== null) { + // full string that matched + match = m.shift(); + + // thus, the rest of the line + line = line.substr(match.length); + //writeln('rest of line:'+JSON.stringify(line)); + + // We are interested in our field match + var df = m.shift().split(';'); + + log(LOG_DEBUG,'- DF found at ['+x+'x'+y+'], Field: '+df[0]+', Length: '+df[1]+', Pad:'+df[2]); + // If we are padding our field with a char, we need to add that back to line + // @todo validate if this goes beyond our width (and if scrolling not enabled) + line = (df[2] ? df[2] : '_').repeat(Math.abs(df[1]))+line; + + frame.dynamic_fields.push({ + name: df[0], + length: df[1], + pad: df[2], + x: x+(xoffset !== undefined ? xoffset : 0), + y: y+(yoffset !== undefined ? yoffset : 0), + value: undefined, + }); + } + + /* set character and attribute */ + var ch = line[0]; + line = line.substr(1); + + if (debug && (debug === y)) { + writeln('y:'+y+', x:'+x+', ch:'+ch); + } + + /* validate position */ + if (y < 0) + y = 0; + if (x < 0) + x = 0; + + store(x,y,ch,undefined); + x++; + } + + // Each new line, we reset the attrs + attr = undefined; + y++; + } + + return frame; + + function store(x,y,ch,attr) { + /* set character and attribute */ + if (! frame.content[y+1]) + frame.content[y+1]=[]; + + frame.content[y+1][x+1] = new Char(ch,attr,SESSION_EXT); + } +} + +load('ansitex/load/session.js'); + +// Our frame object +function SessionViewdata() { + Session.apply(this,arguments); + + /* File Extension used for frames */ + this.settings.ext = 'vtx'; + + /* Length of a frame */ + this.settings.FRAME_LENGTH = 22; + /* Width of a frame */ + this.settings.FRAME_WIDTH = 40; + /* Size of page owner (length) */ + this.settings.FRAME_HEADER = 23; + /* Size of page number (length with a-z) */ + this.settings.FRAME_PAGENUM = 11; + /* Size of cost (length without unit) */ + this.settings.FRAME_COST = 3; + + this.settings.MSG_SENDORNOT = ascii(27)+'BKEY 1 TO SEND, 2 NOT TO SEND'; + this.settings.MSG_LOGON = ascii(27)+'BKEY 1 TO LOGON, 2 TO RETURN'; + this.settings.MSG_SENT = ascii(27)+'BMESSAGE SENT - KEY _ TO CONTINUE'; + this.settings.MSG_NOTSENT = ascii(27)+'BMESSAGE NOT SENT - KEY _ TO CONTINUE'; + this.settings.ERR_NO_PARENT = ascii(27)+'APARENT FRAME DOESNT EXIST'; + this.settings.ERR_NOT_IMPLEMENTED = ascii(27)+'ANOT IMPLEMENTED YET?'; + this.settings.ERR_ROUTE = ascii(27)+'GMISTAKE?'+ascii(27)+'BTRY AGAIN OR TELL US ON *08'; + this.settings.ERR_METHOD_NOT_EXIST = ascii(27)+'GMISTAKE?'+ascii(27)+'BTRY AGAIN OR TELL US ON *08'; + this.settings.ACCESS_DENIED = ascii(27)+'AACCESS DENIED.'; + this.settings.ALREADY_MEMBER = ascii(27)+'AALREADY MEMBER OF CUG' + this.settings.INACTIVITY = ascii(27)+'AINACTIVITY ALERT, DISCONNECT PENDING...'; + this.settings.INACTIVE = ascii(27)+'AINACTIVITY DISCONNECT'; + this.settings.NOACTION = ascii(27)+'ANO ACTION PERFORMED'; + this.settings.BASESTAR = ascii(27)+'B*'; + this.settings.INVALID_CODE = ascii(27)+'AINVAID CODE, PLEASE TRY AGAIN *00'; + this.settings.TOKEN_EMAIL = ascii(27)+'ATOKEN EMAILED TO YOU...'; + this.settings.TOKEN_SENT = ascii(27)+'ATOKEN SENT, PLEASE ENTER TOKEN'; + this.settings.INVALID_EMAIL = ascii(27)+'AINVAID EMAIL, PLEASE TRY AGAIN *00'; + this.settings.INVALID_UID = ascii(27)+'AINVAID USER ID, PLEASE TRY AGAIN *00'; + this.settings.CANNOT_SEND_TOKEN = ascii(27)+'ACANNOT SEND VALIDATION CODE, PLEASE TRY AGAIN *00'; + this.settings.USER_EXISTS = ascii(27)+'AERROR USER EXISTS, PLEASE TRY AGAIN *00'; + this.settings.USER_CREATE_ERROR = ascii(27)+'AERROR CREATING USER, PLEASE TRY AGAIN *00'; + this.settings.LOGIN_ERROR = ascii(27)+'AERROR LOGGING IN, PLEASE TRY AGAIN *00'; + this.settings.CANCEL_MSG = ascii(27)+'BPRESS 2 TO CANCEL'; + this.settings.SYS_ERROR = ascii(27)+'ASYS ERR, TRY AGAIN OR TELL US ON *08'; + this.settings.LOADING = ascii(27)+'Cloading...'; + this.settings.PROCESSING = ESC+VIEWDATA_YELLOW+'processing...'; + + var blp = 0; // Length of data on the bottom line + + /** + * Set the attribute at the current position + */ + this.attr = function(field) { + //NOOP - the terminal takes care of this + } + + this.baselineClear = function(reposition) { + msg = ''; + + log(LOG_DEBUG,'- Clear Bottom Line ['+blp+'] - reposition ['+reposition+']'); + + write_raw(VIEWDATA_HOME+VIEWDATA_UP+msg+ + ((blp > msg.length) + ? (' '.repeat(blp-msg.length)+(reposition ? VIEWDATA_HOME+VIEWDATA_UP+VIEWDATA_RIGHT.repeat(msg.length) : '')) + : '') + ); + + blp = msg.length; + } + + /** + * Send a message to the baseline. + * + * @param text + * @param reposition + */ + this.baselineSend = function(text,reposition) { + var msg = this.getMessage(text); + var x = this.strlen(msg); + + log(LOG_DEBUG,'- Bottom Line ['+msg+'] ('+x+') - reposition ['+reposition+'] BLP:'+blp); + + write_raw(VIEWDATA_HOME+VIEWDATA_UP+msg+ + ((blp > x) + ? (' '.repeat(blp-x)+(reposition ? VIEWDATA_HOME+VIEWDATA_UP+VIEWDATA_RIGHT.repeat(x) : '')) + : '') + ); + + blp = x; + } + + /** + * Turn off the cursor + */ + this.cursorOff = function() { + write_raw(VIEWDATA_COFF); + } + + /** + * Turn on cursor + * @param x + * @param y + */ + this.cursorOn = function(x,y) { + write_raw(VIEWDATA_CON); + + if (x && y) + this.gotoxy(x,y); + } + + // Field backspace, that leaves the field filler char + this.fieldbs = function(char) { + log(LOG_DEBUG,'- Field backspace with char:'+char); + write_raw(VIEWDATA_LEFT+char+VIEWDATA_LEFT); + } + + this.gotoxy = function(x,y) { + log(LOG_DEBUG,'- Moving cursor to y:'+y+', x:'+x); + + // @todo This could be optimised to go the shortest route + write_raw(VIEWDATA_HOME); + + if (x > 0) + write_raw(VIEWDATA_RIGHT.repeat(x)); + + if (y > 0) + write_raw(VIEWDATA_DOWN.repeat(y)); + } + + this.strlen = function(str) { + return str.replace(/\x1b/g,'').length; + }; + + /* + // Render the frame to the user + this.render=function(withHeader) { + log(LOG_DEBUG,'- VIEWDATA FRAME'); + owner = base64_decode(this.owner); + + header = VIEWDATA_DOWN; + + //log(LOG_DEBUG,' - FRAME User: ['+JSON.stringify(user)+']'); + + // Dont show the page number on system login page + if (user.number || (this.type !== FRAME_TYPE_LOGIN && NO_HISTORY_FRAMES.indexOf(this.page) === -1)) { + log(LOG_DEBUG,' - Owner: ['+this.pageowner+'] ('+this.strlen(videotex(this.pageownerlogo))+')'); + + cost = (this.isAccessible ? this.cost+FRAME_COSTUNIT : ' -'); + + header = videotex(this.pageownerlogo)+' '.repeat(this.settings.FRAME_HEADER-this.strlen(videotex(this.pageownerlogo)))+ + (this.isAccessible ? ascii(27)+'G' : ascii(27)+'A')+this.page+' '.repeat(this.settings.FRAME_PAGENUM-this.page.length)+ + ascii(27)+'B'+' '.repeat(this.settings.FRAME_COST-cost.toString().length+1)+cost; + } + + //console.status |= CON_RAW_IN; + write_raw(VIEWDATA_CLS); + write_raw(header); + return write_raw(videotex(base64_decode(this.content))); + }; + */ + + this.qrcode = function(qr) { + // Render the body + var qrcode = VIEWDATA_HOME+VIEWDATA_DOWN.repeat(5); + var offset = this.settings.FRAME_WIDTH-Math.ceil(qr.size/2)-1; + + for (var x = -1; x < qr.size; x=x+3) { + var line = VIEWDATA_RIGHT.repeat(offset ? offset-1 : 0)+ESC+VIEWDATA_MOSIAC_WHITE; + + for (var y = -1; y < qr.size; y=y+2) { + var char = 0; + + //TL + char |= ((x===-1) || (y===-1) || ! qr.getModule(x,y)) ? (1<<0) : (0<<0); + //TR + char |= ((x===-1) || (y === qr.size-1) || ! qr.getModule(x,y+1)) ? (1<<1) : (0<<1); + //ML + char |= ((y===-1) || ! qr.getModule(x+1,y)) ? (1<<2) : (0<<2); + //MR + char |= ((y === qr.size-1) || ! qr.getModule(x+1,y+1)) ? (1<<3) : (0<<3); + //BL + char |= ((x===qr.size-2) || (y===-1) || ! qr.getModule(x+2,y)) ? (1<<4) : (0<<4); + //BR + char |= ((x===qr.size-2) || (y === qr.size-1) || ! qr.getModule(x+2,y+1)) ? (1<<5) : (0<<5); + + char += 0x20; + if (char > 0x3f) + char += 0x20; + + line += ascii(char); + } + + // Render the right column + if (y%2) + line += '\x35'; + + repeat_count = this.settings.FRAME_WIDTH-Math.ceil(qr.size/2)-offset-(offset ? 1 : 2)-(y%2 === 1 ? 0 : 1); + + qrcode += line+' '.repeat(repeat_count > 0 ? repeat_count : 0); + + // To fix some terminals where moving right from col 40 doesnt advance to col 1 on the next line + qrcode +=VIEWDATA_LEFT+VIEWDATA_CR+VIEWDATA_DOWN; + } + + log(LOG_DEBUG,'WIDTH:'+this.settings.FRAME_WIDTH); + log(LOG_DEBUG,'QR :'+(Math.ceil(qr.size/2)+1)); + log(LOG_DEBUG,'OFF :'+offset); + log(LOG_DEBUG,'Y :'+(y%2 ? 0 : 1)); + log(LOG_DEBUG,'X :'+(x%3 ? 0 : 1)); + + // Render the bottom + if (x%3) { + line = VIEWDATA_RIGHT.repeat(offset ? offset-1 : 0)+ESC+VIEWDATA_MOSIAC_WHITE; + + for (var y = 0; y < qr.size; y=y+2) { + line += '\x23'; + } + + // Render the right column + if (y%2 === 0) { + line += '\x21'; + } + + qrcode += line+' '.repeat(repeat_count > 0 ? repeat_count : 0); + } + + write_raw(qrcode); + }; + + /* + this.save=function() { + file = system.mods_dir+'ansitex/text/'+this.page+'.tex'; + w = new File(file); + if (! w.open('w')) { + log(LOG_ERROR,'! ERROR: Unable to create TEX file for '+this.page); + exit(1); + } + + w.write(JSON.stringify(this)); + w.close(); + + log(LOG_DEBUG,'Saved file: '+this.page+'.tex'); + } + */ +} + +function videotex(data) { + var output = ''; + //$output .= ($byte < 32) ? ESC.chr($byte+64) : chr($byte); + for (var i = 0; i < data.length; i++) { + output += (data.charCodeAt(i) < 32) ? "\x1b"+String.fromCharCode(data.charCodeAt(i)+64) : String.fromCharCode(data.charCodeAt(i)); + } + return output; +} + +SessionViewdata.prototype = Session.prototype; +SessionViewdata.prototype.constructor = SessionViewdata; diff --git a/load/session.js b/load/session.js new file mode 100644 index 0000000..8f42e93 --- /dev/null +++ b/load/session.js @@ -0,0 +1,252 @@ +/** + * This handles session specific aspects of each frame type, eg: sending to the baseline, clearing, accepting + * input, moving around the frame (and windows within one if any) + * + * @constructor + */ +function Session() { + 'use strict'; + + // Our page object + this.page = undefined; + + /* Frame type settings */ + this.settings = {}; + + /* Frame Version */ + this.version = 1; + + /* Frame Number [0-9] */ + this.frame = null; + + /* Frame Index [a-z] */ + this.index = null; + + /* The Service Provider owning the frame. */ + this.owner = 0; + + /* The cost to view the frame @todo to implement */ + this.cost = 0; + + /* The frame content, base64 encoded */ + this.content = ''; + + // Frame's owned by the system where: + // isPublic is FALSE - the user must be logged in to view it + // isPublic is TRUE - can be viewed by non-logged in users + + // Frame's owned by Service Providers where: + // isPublic is FALSE - can only be viewed if a user is + // a member of the Service Providers CUG + // isPublic is TRUE - can be viewed by users (if logged in) + + this.isPublic = false; // Is this frame accessible to non CUG users + // If FALSE user must be a member of the CUG to view the frame + // All users, including unauthenticated, are members of 'system' (owner = 0) + this.isAccessible = false; // Is this frame available to be accessed + // If FALSE, only the SP can view/edit the frame + + this.type = FRAME_TYPE_INFO; // The frame type - see FRAME_TYPES above + this.key = [null,null,null,null,null,null,null,null,null,null]; // Key actions [0-9] + + /** + * Return the frames fields + */ + Object.defineProperty(this,'fields', { + get: function() { + return this.frame_fields; + } + }); + + /** + * Check if the user is already a member of the CUG + */ + Object.defineProperty(this,'isMember',{ + get: function() { + log(LOG_DEBUG,'- Checking if user is a member of frame: '+this.page); + + if (user.number) { + return ( + (this.pageowner === SYSTEM_OWNER) + ); + + } else { + return false; + } + } + }) + + Object.defineProperty(this,'pageownerlogo', { + get: function() { + return base64_decode(this.settings.ext === 'tex' ? pageOwner(this.frame).logoans : pageOwner(this.frame).logovtx); + } + }) + + /** + * Enable pulling out submitted value by its name + * + * @param key + * @returns {null|string|*} + */ + this.fieldValue = function(key) { + for each (var k in this.frame_fields) { + log(LOG_DEBUG,' - k:'+JSON.stringify(k)); + + if (k.fname === key) { + return k.fvalue; + } + } + + return null; + } + + this.get = function(page) { + if (!(page instanceof PageObject)) + throw new Error('page must be a PageObject'); + + switch (this.settings.ext) { + case 'tex': + case 'vtx': + this.page = new Page(this.settings.ext); + break; + + default: + throw new Error(this.settings.ext+' has not been implemented'); + } + + this.baselineSend('LOADING'); + this.page.get(page,this.settings.ext); + this.baselineClear(); + } + + /** + * Load a message frame + * + * @param page + */ + this.loadMessage = function(page) { + this.frame = ''+page; + this.index = 'a'; + this.owner = 1; + this.isPublic = true; + this.isAccessible = true; + // @todo Keys should map to next/previous/send, etc as indicated in the template frame. + this.key = [this.frame.substr(0,7)+'1',null,null,null,null,null,null,null,null,null]; + // @todo validate that FRAME_TYPE_MESSAGE is a message template + this.type = FRAME_TYPE_MESSAGE; + + // Load our message + var ma = new MsgAreas() + var area = ma.getArea(this.frame); + var msg = ma.getMessage(this.frame); + var msg_header; + + if (! msg) + return undefined; + + // @todo Search 1zzzzEE..., 1zzzz... + var to = viewdata ? new FrameViewdata() : new FrameAnsi(); + to.load(MAIL_TEMPLATE_FRAME); + // @todo Check that this is a frame of type "m" and report error if not + // @todo Take the cost from the template + this.cost = 5; + + if (! to) { + log(LOG_ERROR,'Echomail template missing :['+MAIL_TEMPLATE_FRAME+'] ?'); + msg_header = 'TO: '+msg.to.substr(0,72)+"\n\r"; + msg_header += 'FROM: '+msg.from.substr(0,72)+"\n\r"; + msg_header += 'DATE: '+msg.date.substr(0,72)+"\n\r"; + msg_header += 'SUBJECT: '+msg.subject.substr(0,72)+"\n\r"; + + } else { + // @todo change this to use atcode() + msg_header = base64_decode(to.content).replace(/@(.*)@/g, + function (str, code, offset, s) { + var length = code.split(':')[1]; + switch(code.split(':')[0]) { + case 'DATE': return msg.date.substr(0,length); + case 'TO': return msg.to.substr(0,length); + case 'FROM': return msg.from.substr(0,length); + case 'SUBJECT': return msg.subject.substr(0,length); + } + } + ); + } + + //log(LOG_DEBUG,'Loaded message: '+msg_header+msg.content); + this.content = base64_encode(msg_header+msg.content); + + // Update the user's pointers + var stats = ma.getUserStats(this.frame); + + // if this message is to the user, and the msg number > scan_ptr and it is the next message on the user's new mail list + var newmsgs = area.newMsgsToMe(); + var next; + log(LOG_DEBUG,'User has: '+newmsgs.length-1+' msgs to read to ME'); + if (newmsgs.length) { + next = newmsgs[1]; + //log(LOG_DEBUG,'- NEXT is: '+next.tags+', this is: '+msg.tags); + + if (next && (next.tags === msg.tags)) { + log(LOG_DEBUG,'- Updating scan_ptr to: '+next.number); + stats.scan_ptr = next.number; + } + + // Last message + next = newmsgs[0]; + if (next !== undefined) { + log(LOG_DEBUG,'- LAST TO ME is: '+next.tags); + this.key[1] = area.page(next.tags); + } + + // Next new message + next = newmsgs[2]; + if (next !== undefined) { + log(LOG_DEBUG,'- NEXT TO ME is: '+next.tags); + this.key[2] = area.page(next.tags); + } + } + + // if this message is the next message, update last_read + newmsgs = area.newMsgs(); + log(LOG_DEBUG,'User has: '+newmsgs.length+' msgs to read'); + if (newmsgs.length) { + next = newmsgs[0]; + log(LOG_DEBUG,'- NEXT is: '+next.tags+', this is: '+msg.tags); + + if (next.tags === msg.tags) { + log(LOG_DEBUG,'- Updating last_read to: '+next.number); + stats.last_read = next.number; + } + } + + // Previous Message + x = area.MessagePrev(this.frame); + if (x) + this.key[4] = area.page(x.tags); + + // Next Message + x = area.MessageNext(this.frame); + if (x) + this.key[6] = area.page(x.tags); + + log(LOG_DEBUG,'Built frame: ['+this.frame+']['+this.index+'] ('+this.page+')'); + } + + // Render the page + this.render = function() { + this.gotoxy(0,0); + write(so.page.display().join('')); + } +} + +/** + * Return the message for a index + * + * @param index + * @returns {string|*} + */ +Session.prototype.getMessage = function(index) { + eval('var msg = this.settings.'+index); + return msg; +} diff --git a/load/windows.js b/load/windows.js new file mode 100644 index 0000000..42d5f0c --- /dev/null +++ b/load/windows.js @@ -0,0 +1,991 @@ +/** + * Windows are elements of a Page object + * + * @param x - (int) starting x of it's parent [1..] + * @param y - (int) starting y of it's parent [1..] + * @param width - (int) full width of the window (text content will be smaller if there are scroll bars/boarder) + * @param height - (int) full height of the window (text content will be smaller if there are scroll bars/boarder) + * @param name - (string) internal name for the window (useful for debugging) + * @param parent - (object) parent of this window + * @param debug - (int) debug mode, which fills the window with debug content + * @constructor + * + * Pages have the following attributes: + * - bx/by - (int) right/bottom most boundary of the window representing the start + width/height of the window + * - child - (array) children in this window + * - height - (int) Window's height + * - name - (string) Windows name (useful for internal debugging) + * - parent - (object) Parent that this window belongs to + * - x/y - (int) start position of the window + * - visible - (bool) whether this window is visible + * - width - (int) Window's width + * - z - (int) Window's depth indicator + * + * Windows have the following public functions + * - build - Compile the frame for rendering + * - debug - Useful for debugging with properties of this Window + * - draw - Draw a part of this Window + * - drawline - Draw a y line for this Window + * - visibleChildren - Children that will be included when this window is rendered + */ +function Window(x,y,width,height,name,parent,debug) { + this.__properties__ = { + x: undefined, // X offset of parent that the canvas starts [1..width] + y: undefined, // Y offset of parent that the canvas starts [1..height] + z: 0, // Window top-bottom position, higher z is shown [0..] + ox: 0, // When canvas width > width, this is the offset we display [0..] + oy: 0, // When canvas height > height, this is the offset we display [0..] + width: undefined, // Display Width + (1 char if scrollbars = true) + height: undefined, // Display Height + canvaswidth: undefined, // Width of Canvas (default display width) + canvasheight: undefined, // Height of Canvas (default display height) + content: [], // Window content - starting at 0,0 = 1,1 + visible: true, // Is this window visible + }; + + /* + this.__settings__ = { + checkbounds: true, // Can this frame move outside of the parent + v_scroll: true, // Can the content scroll vertically (takes up 1 line) [AUTO DETERMINE IF canvas > width] + h_scroll: false, // Can the content scroll horizontally (takes up 1 char) [AUTO DETERMINE IF canvas > height] + delay: 0, // Delay while rendering + word_wrap: false, // Word wrap content + pageable: false, // Overflowed content is rendered with the next page + }; + */ + + this.__relations__ = { + parent: undefined, + child: [], + }; + + /* + this.__position__ = { + cursor: undefined, + }; + */ + + /* + Validation to implement: + + X BOUNDARY + - x cannot be < parent.x if checkbounds is true [when moving window] + - x+width(-1 if h_scroll is true) cannot be greater than parent.width if checkbounds is true + - v_scroll must be true for canvaswidth > width + - when scrolling ox cannot be > width-x + - when layout.pageable is true, next page will only have windows included that have a y in the range + ie: if height is 44 (window is 22), next page is 23-44 and will only include children where y=23-44 + + Y BOUNDARY + - y cannot be < parent.y if checkbounds is true [when moving window] + - y+height(-1 if v_scroll is true) cannot be greater than parent.height if checkbounds is true + - h_scroll must be true for canvasheight > height + - when scrolling oy cannot be > height-y + - when layout.pageable is true, children height cannot be greater than parent.height - y. + */ + + function init(x,y,width,height,name,parent,debug) { + if (parent instanceof Window) { + this.z = parent.__relations__.child.length+1; + this.parent = parent; + + parent.child = this; + + // Check that our height/widths is not outside of our parent + if ((this.x < 1) || (width > this.parent.width)) + throw new Error('Window: '+name+' width ['+width+'] is beyond our parent\'s width ['+this.parent.width+'].'); + if ((x > this.parent.bx) || (x+width-1 > this.parent.bx)) + throw new Error('Window: '+name+' start x ['+x+'] and width ['+width+'] is beyond our parent\'s end x ['+this.parent.bx+'].'); + + if ((this.y < 1) || (height > this.parent.height)) + throw new Error('Window: '+name+' height ['+height+'] is beyond our parent\'s height ['+this.parent.height+'].'); + if ((y > this.parent.by) || (y+height-1 > this.parent.by)) + throw new Error('Window: '+name+' start y ['+y+'] and height ['+height+'] is beyond our parent\'s end y ['+this.parent.by+'].'); + + } else if (parent instanceof Page) { + this.parent = parent; + + } else { + throw new Error('INVALID Parent Type: '+parent); + } + + this.name = name; + this.x = x; + this.y = y; + this.__properties__.width = this.__properties__.canvaswidth = width; + this.__properties__.height = this.__properties__.canvasheight = height; + + if (debug) { + this.__properties__.canvaswidth = width*2; + this.__properties__.canvasheight = height*2; + } + + // Fill with data + for(var y=1;y<=this.__properties__.canvasheight;y++) { + for(var x=1;x<=this.__properties__.canvaswidth;x++) { + if (this.__properties__.content[y] == undefined) + this.__properties__.content[y] = []; + + this.__properties__.content[y][x] = debug + ? new Char((x > this.width) || (y > this.height) ? this.name[0].toUpperCase() : this.name[0].toLowerCase(),undefined) + : new Char(undefined,undefined); + } + } + } + + init.apply(this,arguments); + + // Windows boundary (right most width) of parent [1..] + Window.prototype.__defineGetter__('bx',function() { + return this.x+this.width-1; + }); + // Windows boundary (bottom most height) of parent [1..] + Window.prototype.__defineGetter__('by',function() { + return this.y+this.height-1; + }); + + /* + // Can this window be moved outside of the parents visible area + Window.prototype.__defineGetter__('checkbounds',function() { + return this.__settings__.checkbounds; + }); + Window.prototype.__defineSetter__('checkbounds',function(bool) { + this.__settings__.checkbounds = bool; + }); + */ + + // Management of children objects, in highest z order + Window.prototype.__defineGetter__('child',function() { + // Return the children sorted in z order lowest to highest + return this.__relations__.child.sort(function(a,b) { return (a.z < b.z) ? -1 : ((b.z < a.z) ? 1 : 0); }); + }); + Window.prototype.__defineSetter__('child',function(obj) { + if(obj instanceof Window) { + this.__relations__.child.push(obj); + + } else + throw new Error('child not an instance of Window()'); + }); + + // Window name + Window.prototype.__defineGetter__('name',function() { + return this.__properties__.name; + }); + Window.prototype.__defineSetter__('name',function(string) { + if (this.__properties__.name) + throw new Error('name already DEFINED'); + + return this.__properties__.name = string; + }); + + // Parent window object + Window.prototype.__defineGetter__('parent',function() { + return this.__relations__.parent; + }); + Window.prototype.__defineSetter__('parent',function(obj) { + if (this.__relations__.parent) + throw new Error('parent already DEFINED'); + + return this.__relations__.parent = obj; + }); + + // Window's display height + Window.prototype.__defineGetter__('height',function() { + return this.__properties__.height; + }); + Window.prototype.__defineSetter__('height',function(int) { + if (this.__properties__.height) + throw new Error('height already DEFINED'); + + this.__properties__.height = int; + }); + + // Window's display width + Window.prototype.__defineGetter__('width',function() { + return this.__properties__.width; + }); + Window.prototype.__defineSetter__('width',function(int) { + if (this.__properties__.width) + throw new Error('width already DEFINED'); + + this.__properties__.width = int; + }); + + // Window's start position on it's parent (width) + Window.prototype.__defineGetter__('x',function() { + return this.__properties__.x; + }); + Window.prototype.__defineSetter__('x',function(int) { + if (this.__properties__.x) + throw new Error('x already DEFINED'); + + this.__properties__.x = int; + }); + + // Window's start position on it's parent (height) + Window.prototype.__defineGetter__('y',function() { + return this.__properties__.y; + }); + Window.prototype.__defineSetter__('y',function(int) { + if (this.__properties__.y) + throw new Error('y already DEFINED'); + + this.__properties__.y = int; + }); + + // Is the current window visible + Window.prototype.__defineGetter__('visible',function() { + return this.__properties__.visible; + }); + + // What position is this window (highest is visible) + Window.prototype.__defineSetter__('z',function(int) { + if (this.__properties__.z) + throw new Error('z already DEFINED'); + + this.__properties__.z = int; + }); + Window.prototype.__defineGetter__('z',function() { + return this.__properties__.z; + }); + + /** + * Build this window, returning an array of Char that will be rendered by Page + * + * @param xoffset - (int) This windows x position for its parent + * @param yoffset - (int) This windows y position for its parent + * @param debug - (int) debug mode, which fills the window with debug content + * @returns {*[]} + */ + Window.prototype.build = function(xoffset,yoffset,debug) { + var display = []; + + if (debug) { + writeln('********* ['+this.name+'] *********'); + writeln('name :'+this.name); + writeln('xoff :'+xoffset); + writeln('yoff :'+yoffset); + writeln('x :'+this.x); + writeln('bx :'+this.bx) + writeln('ox :'+this.__properties__.ox); + writeln('y :'+this.y); + writeln('by :'+this.by) + writeln('oy :'+this.__properties__.oy); + writeln('lines :'+this.__properties__.content.length); + writeln('content:'+JSON.stringify(Object.keys(this.__properties__.content).join(','))); + } + + if (debug) + writeln('-------------'); + + for (y=1;y<=this.height;y++) { + if (debug) + write(padright(y,2,0)+':'); + + var sy = this.y-1+y+yoffset-1; + + for (x=1;x<=this.width;x++) { + if (debug) + writeln('- Checking :'+this.name+', y:'+(y+this.__properties__.oy)+', x:'+(x+this.__properties__.ox)); + + var sx = this.x-1+x+xoffset-1; + if (display[sy] === undefined) + display[sy] = []; + + if ((this.__properties__.content[y+this.__properties__.oy] !== undefined) && (this.__properties__.content[y+this.__properties__.oy][x+this.__properties__.ox] !== undefined)) { + display[sy][sx] = this.__properties__.content[y+this.__properties__.oy][x+this.__properties__.ox]; + + if (debug) + writeln('- storing in y:'+(sy)+', x:'+(sx)+', ch:'+display[sy][sx].ch); + + } else { + //display[sy][sx] = new Char(null,BG_BLACK|LIGHTGRAY); + display[sy][sx] = new Char(); + + if (debug) + writeln('- nothing for y:'+(sy)+', x:'+(sx)+', ch:'+display[sy][sx].ch); + } + } + + if (debug) + writeln(); + } + + if (debug) { + writeln('Window:'+this.name+', has ['+this.child.filter(function(child) { return child.visible; }).length+'] children'); + this.child.forEach(function(child) { + writeln(' - child:'+child.name+', visible:'+child.visible); + }) + } + + // Fill the array with our values + var that = this; + this.child.filter(function(child) { return child.visible; }).forEach(function(child) { + if (debug) { + writeln('=========== ['+child.name+'] ============='); + writeln('xoff :'+xoffset); + writeln('yoff :'+yoffset); + writeln('this.x :'+that.x); + writeln('this.y :'+that.y); + } + + draw = child.build(that.x+xoffset-1,that.y+yoffset-1,debug); + + if (debug) { + writeln('draw y:'+JSON.stringify(Object.keys(draw).join(','))); + writeln('draw 1:'+JSON.stringify(Object.keys(draw[1]).join(','))); + } + + for (var y in draw) + for (var x in draw[y]) { + if (display[y] === undefined) + display[y] = []; + + display[y][x] = draw[y][x]; + } + + if (debug) { + writeln('draw 1:'+JSON.stringify(Object.keys(draw[1]).join(','))); + writeln('=========== END ['+child.name+'] =============') + } + }) + + if (debug) { + writeln('this.name:'+this.name); + writeln('this.y:'+this.y); + writeln('display now:'+Object.keys(display[this.y]).join(',')); + writeln('********* END ['+this.name+'] *********'); + } + + return display; + } + + Window.prototype.debug = function(text) { + return '- '+text+': '+this.name+'('+this.x+'->'+(this.bx)+') width:'+this.width+' ['+this.y+'=>'+this.by+'] with z:'+this.z; + }; + + /** + * Render this window + * + * @param start - (int) Starting x position + * @param end - (int) Ending x position + * @param y - (int) Line to render + * @param color - (bool) Whether to include color + * @returns {{x: number, content: string}} + */ + Window.prototype.draw = function(start,end,y,color) { + var content = ''; + + for (x=start;x<=end;x++) { + var rx = this.__properties__.ox+x; + var ry = this.__properties__.oy+y; + + // Check if we have an attribute to draw + if (! (ry in this.__properties__.content) || ! (rx in this.__properties__.content[ry])) { + content += ' '; + continue; + } + + if (color === undefined || color === true) { + // Only write a new attribute if it has changed + if ((this.last === undefined) || (this.last !== this.__properties__.content[ry][rx].attr)) { + this.last = this.__properties__.content[ry][rx].attr; + + content += (this.last === null ? BG_BLACK|LIGHTGRAY : this.last); + } + } + + try { + content += (this.__properties__.content[ry][rx].ch !== null ? this.__properties__.content[ry][rx].ch : ' '); + + } catch (e) { + writeln(e); + writeln('---'); + writeln('x:'+(x-this.x)); + writeln('y:'+(y-this.y)); + writeln('ox:'+this.__properties__.ox); + writeln('oy:'+this.__properties__.oy); + writeln('rx:'+rx); + writeln('ry:'+ry); + exit(); + } + } + + return { content: content, x: end - start + 1 }; + } + + /** + * DRAW a line for this Window + * + * @param startx - the start position to render this window [1..] + * @param endx - the stop position to stop rendinering this window [1..] + * @param y - the current windows line number [1..] + * @param color - output color + * @param debug - turn on debugging + * + * Other Attributes: + * @param x - Our current X position + * + * When rendering, for each children on a y axis, the one with a character in x and the highest z wins. + * + * = Thus, if there is only 1 child on a y line, we render that child.width, with optional padding on the left/right. + * + * = If there are more children: + * + build an array of each childs x starting position sorted by z (asc). + * + if we need, we pad until the first child to rendered + * + after the child is rendered, we pad on the right if it didnt render to window.width + * + * + we render the a.x -> a.bx, unless there are other children with a higher z and it's x < a.bx + * + "x" keeps track of where we are + * + repeat until all children are processed, repeating from the begining again if necessary where children with a lower z have a wider width + */ + Window.prototype.drawline = function(startx,endx,y,color,debug) { + // Some sanity checking + if (startx < 1) + throw new Error('Nope, startx < 1:'+startx); + if (endx < startx) + throw new Error('Nope, endx:'+endx+' < startx:'+startx); + if (y < 1) + throw new Error('Nope, y < 1:'+y); + + // Advance x if required + var x = startx; + + // Get any of our children + var children = this.visibleChildren(); + + // Find children with something on this line. + var that = this; + var kids = children.filter(function(child) { + return (y-that.y+1 >= child.y) && (y-that.y+1 <= child.by) && (child.x < endx) && (child.bx > startx); + }); + + var draw; + var content = ''; + + if (debug !== false && (y > debug)) { + writeln() + writeln('********* drawline for ['+this.name+'] ***********'); + writeln('* chars :'+startx+'->'+endx); + writeln('* line :'+y); + writeln('* parents start line :'+(this.parent.y !== undefined ? this.parent.y : '-no parent-')); + writeln(this.debug('drawline')); + + writeln('* we have children :'+children.length); + + children.forEach(function(child) { + if (y > debug) writeln(child.debug('CHILD')); + }) + + writeln('* relevant :'+kids.length); + kids.forEach(function(child) { + writeln(child.debug('KID')); + }) + + writeln('*************************************'+'*'.repeat(this.name.length)); + } + + var child = null; + + // Only 1 child, so we render the line with it + if (kids.length === 1) { + child = kids.pop(); + + if (debug !== false && (y > debug)) { + writeln(': Only 1 child :'+child.name); + } + + if (child.x > x) { + if (debug !== false && (y > debug)) { + writeln(': Padding until :'+x+'->'+(endx < child.x-1 ? endx : child.x-1)+' on:'+(y-this.y+1)+' with:'+this.name); + writeln(': Padded :'+((endx < child.x-1 ? endx : child.x-1)-x)); + } + + draw = this.draw(x,endx < child.x-1 ? endx : child.x-1,y-this.y+1,color); + content += draw.content; + x += draw.x; + + if (debug !== false && (y > debug)) writeln(); + } + + if (debug !== false && (y > debug)) { + writeln(child.debug('THIS')); + writeln(': x :'+x); + writeln(': endx :'+endx); + writeln(': size of child :'+(child.bx-child.x+1)); + writeln(': draw :'+(x-child.x+1)+'->'+((endx-startx+1 < child.width ? endx : child.bx)-child.x+1)+' on:'+(y-this.y+1)) + } + + if (x < endx) { + draw = child.drawline(x-child.x+1,(endx < child.bx ? endx : child.bx)-child.x+1,y-this.y+1,color,(debug !== false ? debug-this.y+1 : debug)); + content += draw.content; + x += draw.x; + } + + if (debug !== false && (y > debug)) writeln(); + + } else if (kids.length !== 0) { + if (debug !== false && (y > debug)) writeln('| multiple children :'+kids.length); + + // Sort the kids in x start order + kids = kids.sort(function(a,b) { + return (a.x < b.x) ? -1 : ((b.x < a.x) ? 1 : 0); + }); + + if (debug !== false) + kids.forEach(function(child) { + if (y > debug) writeln(child.debug('SORT')); + }) + + var c = 0; // Our kids index + var C = null; // If we need to come back and reprocess the list + + while (c < kids.length) { + child = kids[c++]; + var drawendx = endx; + + if (debug !== false && (y > debug)) { + writeln('| -------------- ['+child.name+'] -----------'); + writeln('| x :'+x); + writeln('| child.x :'+child.x); + writeln('| child.bx:'+child.bx); + writeln('| startx :'+startx); + writeln(child.debug('CHILD')); + } + + // If this child cannot be rendered, skip it (because it is underneath another window) + if (x > child.bx) { + if (debug !== false && (y > debug)) writeln('| skipping:'+child.name); + continue; + } + + // Pad the beginning of this child + if (child.x > x) { + if (debug !== false && (y > debug)) { + writeln('| Padding :'+x+'->'+(child.x-1)+' for: '+this.name); + } + + draw = this.draw(x,child.x-1,y-this.y+1,color); + content += draw.content; + x += draw.x; + + if (debug !== false && (y > debug)) writeln(); + } + + /* + // If this child cannot be rendered, skip it + if (x < child.x) { + if (debug !== false && (y > debug)) writeln('| skipping:'+child.name); + continue; + } + */ + + if (kids[c] !== undefined) { + if (debug !== false && (y > debug)) writeln(kids[c].debug('AFTER')); + + // If the next child has a higher z and the same x skip this one + if ((kids[c].x === x) && (kids[c].z > child.z)) { + if (debug !== false && (y > debug)) writeln('|- Skipping:'+child.name); + C = kids[c].x; + continue; + } + + if (debug !== false && (y > debug)) writeln('| FILTERING: x le:'+x+' bx lt:'+(child.bx)+' and z gt:'+child.z); + var nextkid = kids.filter(function(kid) { + if (debug !== false && (y > debug)) writeln('| - EVAL: '+kid.name+': x:'+(kid.x)+' bx:'+(kid.bx)+' and z:'+kid.z); + return ((kid.bx > x+startx-1)) && (kid.x < child.bx) && (kid.z > child.z); + }); + if (debug !== false && (y > debug)) writeln('| Got next children: '+nextkid.length); + + if (debug !== false) + nextkid.forEach(function(child) { + if (y > debug) writeln(child.debug('NEXT')); + }) + + // If a next child who starts before we finish and has a higher z, we'll stop at that next child + if (nextkid.length) { + // If nextkid should already be showing, we'll skip to it + if (x > nextkid[0].x) { + C = nextkid[0].bx; + if (debug !== false && (y > debug)) writeln('| NEXTKID should have started, skipping to it:'+nextkid[0].x+' (C:'+C+')'); + continue; + } + + drawendx = (endx-child.x+1 < nextkid[0].x-1 ? endx : nextkid[0].x-1); + if (debug !== false && (y > debug)) { + writeln('| x :'+x); + writeln('| startx :'+startx); + writeln('| childx :'+child.x); + } + + if (debug !== false && (y > debug)) { + writeln(nextkid[0].debug('NEXTKID')); + writeln('| draw partial width of me:'+child.name+', drawendx:'+drawendx+' because nextkid starts:'+nextkid[0].name+' at:'+nextkid[0].x); + } + + // If I need to continue after next kid, we'll come back + if ((! C) && (nextkid[0].bx < child.bx)) { + C = nextkid[0].bx; + if (debug !== false && (y > debug)) writeln('| coming back to:'+child.name+' at:'+C); + } + + // If C is set, we need to push it out. + if (C) + C = drawendx; + + // No next children + } else { + if (debug !== false && (y > debug)) { + writeln('| x :'+x); + writeln('| startx :'+startx); + writeln('| endx :'+endx); + writeln('| child :'+child.name); + writeln('| childx :'+child.x); + writeln('| childbx:'+child.bx); + writeln('| calcsta:'+(endx-child.x+1)); + } + + drawendx = (endx-child.x+1 < child.bx ? x+child.width : child.bx); + if (debug !== false && (y > debug)) writeln('| draw full width of me:'+child.name+', from:'+(x-child.x+1)+' until:'+drawendx); + } + + // No other children + } else { + if (debug !== false && (y > debug)) { + writeln('| No other child'); + writeln(child.debug('DRAW')); + } + + /* + // If there is a gap, we'll need to pad it. + if (x < child.x) { + write(':'.repeat(xx=child.x-x)); + x += xx; + } + */ + + drawendx = (endx-child.x+1 < child.width ? endx : child.bx); + + if (debug !== false && (y > debug)) { + writeln('| will render:'+child.name+', from:'+(x-child.x+1)+'->'+(drawendx-child.x+1)+' on:'+(y-this.y+1)); + } + } + + if (x < endx) { + draw = child.drawline(x-child.x+1,(x < endx ? drawendx : child.bx)-child.x+1,y-this.y+1,color,(debug !== false ? debug-this.y+1 : debug)); + content += draw.content; + x += draw.x; + } + + if (debug !== false && (y > debug)) { + writeln(); + writeln('| C :'+C); + writeln('| x :'+x); + } + + if (C && (x > C)) { + c = 0; + C = null; + + if (debug !== false && (y > debug)) writeln('! Resetting c back'); + } + } + } + + if (debug !== false && (y > debug)) { + writeln('= x :'+x); + writeln('= startx :'+startx); + } + + // Pad the beginning of this child + if (x < startx) { + if (debug !== false && (y > debug)) writeln('! Padding until:'+this.x); + write(' '.repeat(xx=this.x-startx)); + x += xx; + } + + // Render our item + if (debug !== false && (y > debug)) writeln('= drawing:'+this.name+' ('+x+'->'+endx+')'); + draw = this.draw(x,endx,y-this.y+1,color); + content += draw.content; + x += draw.x; + + if (debug !== false && (y > debug)) { + writeln(); + writeln('- DONE, x now :'+x+' (endx:'+endx+') for:'+this.name); + writeln('- DREW :'+(x-startx)); + writeln('************** end for ['+this.name+'] ***********'); + } + + if (debug !== false && y > debug+1) exit(); + + return { content: content, x: endx-startx+1 }; + } + + // Return the visible children (child should have sort by z) + Window.prototype.visibleChildren = function() { + return this.child.filter(function(child) { + return child.visible; + }); + } +} + +/** + * Each character in a window + * + * @todo Need to add a Viewdata implementation + * @param ch + * @param attr + * @param ext - tex = ANSItex, vtx = ViewDasta + * @constructor + */ +function Char(ch,attr) { + this.__properties__ = { + attr: attr, // - Attributes for the character (ie: color) + ch: ch, // - Character to be shown + //changed: false, // - Has this ch or attr been changed? + }; + + /** + * Return the color codes required to draw the current character + * + * @todo Implement Viewdata + * @param last - last rendered char + * @param ext - service we are rendering for + * @param debug - debug mode + * @returns {string|undefined} + */ + Char.prototype.attr = function(last,ext,debug) { + // If our attr is undefined, we'll return + if (this.__properties__.attr === undefined) + return; + + // @todo This condition appears to fail? + if (this.__properties__.attr === null) + throw new Error('Attributes shouldnt be null'); + + var ansi = []; + var c; + var l; + var r = ''; + + if (debug) { + writeln(); + writeln('- last:'+last+', this:'+this.__properties__.attr); + } + + switch (ext) { + case 'tex': + if (debug) { + writeln(' - this BG_BLACK:'+(this.__properties__.attr & BG_BLACK)); + writeln(' - last BG_BLACK:'+(last & BG_BLACK)); + + writeln(' - this HIGH:'+(this.__properties__.attr & HIGH)); + writeln(' - last HIGH:'+(last & HIGH)); + + writeln(' - this BLINK:'+(this.__properties__.attr & BLINK)); + writeln(' - last BLINK:'+(last & BLINK)); + } + + if ( + (((this.__properties__.attr & BG_BLACK) !== (last & BG_BLACK)) + || ((this.__properties__.attr & HIGH) !== (last & HIGH)) + || ((this.__properties__.attr & BLINK) !== (last & BLINK)))) + { + ansi.push('0'); + last = BG_BLACK|LIGHTGRAY; + } + + if ((this.__properties__.attr & HIGH) && ((this.__properties__.attr & HIGH) !== (last & HIGH))) { + ansi.push('1'); + } + + if ((this.__properties__.attr & BLINK) && ((this.__properties__.attr & BLINK) !== (last & BLINK))) { + ansi.push('5'); + } + + c = (this.__properties__.attr & 0x07); + l = (last & 0x07); + + // Foreground + switch (c) { + case BLACK: + r = 30; + break; + case RED: + r = 31; + break; + case GREEN: + r = 32; + break; + case BROWN: + r = 33; + break; + case BLUE: + r = 34; + break; + case MAGENTA: + r = 35; + break; + case CYAN: + r = 36; + break; + case LIGHTGRAY: + r = 37; + break; + } + + //writeln('r:'+r+', l:'+l+', c:'+c); + if (r && (c !== l)) + ansi.push(r); + + // Background + if (this.__properties__.attr & 0x70) { + c = (this.__properties__.attr & 0x70); + l = (last & 0x70); + + switch (this.__properties__.attr & 0x70) { + case BG_BLACK: + r = 40; + break; + case BG_RED: + r = 41; + break; + case BG_GREEN: + r = 42; + break; + case BG_BROWN: + r = 43; + break; + case BG_BLUE: + r = 44; + break; + case BG_MAGENTA: + r = 45; + break; + case BG_CYAN: + r = 46; + break; + case BG_LIGHTGRAY: + r = 47 + break; + } + + if (r && (c !== l)) + ansi.push(r); + } + + if (false && debug) + writeln(' - ansi:'+ansi); + + return ansi.length ? (debug ? '': '\x1b')+'['+ansi.join(';')+'m' : undefined; + + case 'vtx': + log(LOG_DEBUG,'+ last:'+last+', attr ('+this.__properties__.attr+')'); + switch (this.__properties__.attr) { + // \x08 + case BLINK: + r = VIEWDATA_BLINK; + break; + // \x09 + case STEADY: + r = VIEWDATA_STEADY; + break; + // \x0c + case NORMAL: + r = VIEWDATA_NORMAL; + break; + // \x0d + case DOUBLE: + r = VIEWDATA_DOUBLE; + break; + // \x18 + case CONCEAL: + r = VIEWDATA_CONCEAL; + break; + // \x19 + case BLOCKS: + r = VIEWDATA_BLOCKS; + break; + // \x1a + case SEPARATED: + r = VIEWDATA_SEPARATED; + break; + // \x1c + case BLACKBACK: + r = VIEWDATA_BLACKBACK; + break; + // \x1d + case NEWBACK: + r = VIEWDATA_NEWBACK; + break; + // \x1e + case HOLD: + r = VIEWDATA_HOLD; + break; + // \x1f + case RELEASE: + r = VIEWDATA_REVEAL; + break; + + // Not handled + // \x0a-b,\x0e-f,\x1b + case 0xff00: + return '?'; + + default: + var mosiac = (this.__properties__.attr & MOSIAC); + c = (this.__properties__.attr & 0x07); + + // Color control \x00-\x07, \x10-\x17 + switch (c) { + case BLACK: + r = VIEWDATA_BLACKBACK; + break; + case RED: + r = mosiac ? VIEWDATA_MOSIAC_RED : VIEWDATA_RED; + break; + case GREEN: + r = mosiac ? VIEWDATA_MOSIAC_GREEN : VIEWDATA_GREEN; + break; + case BROWN: + r = mosiac ? VIEWDATA_MOSIAC_YELLOW : VIEWDATA_YELLOW; + break; + case BLUE: + r = mosiac ? VIEWDATA_MOSIAC_BLUE : VIEWDATA_BLUE; + break; + case MAGENTA: + r = mosiac ? VIEWDATA_MOSIAC_MAGENTA : VIEWDATA_MAGENTA; + break; + case CYAN: + r = mosiac ? VIEWDATA_MOSIAC_CYAN : VIEWDATA_CYAN; + break; + case LIGHTGRAY: + r = mosiac ? VIEWDATA_MOSIAC_WHITE : VIEWDATA_WHITE; + break; + + default: + log(LOG_DEBUG,'Not a color?:'+c); + return '?'; + } + } + + log(LOG_DEBUG,'= result:'+r.charCodeAt(0)+', ('+r+')'); + return ESC+r; + + default: + throw new Error(this.__properties__.type+': has not been implemented'); + } + }; + + Char.prototype.__defineGetter__('ch',function() { + return this.__properties__.ch; + }); + Char.prototype.__defineSetter__('ch',function(char) { + if (typeof char !== 'string') + throw new Error('ch is not a string') + + if (char.length !== 1) + throw new Error('ch can only be 1 character'); + + this.__properties__.ch = char; + }) +} diff --git a/main.js b/main.js index 6ca982c..822eee6 100644 --- a/main.js +++ b/main.js @@ -1,56 +1,83 @@ -log(LOG_DEBUG,'- INIT: ANSITEX'); +/** + * This is the core of ANSItex + * + * When called as login.js, it will handle the un-authenticated user. + * When called as main.js, it's assumed that the user has been authenticated. + * (In fact the functionality between the two is no different.) + */ -// Load many SBBS definitions +log(LOG_DEBUG,'* INIT: ANSItex'); + +// SBBS General definitions require('sbbsdefs.js','SS_USERON'); -// Load text.dat definitions -require('text.js','TOTAL_TEXT'); -// Key definitions +// SBBS Key definitions require('key_defs.js','KEY_ESC'); -ansi = load({},'ansiterm_lib.js'); - -load('frame.js'); -load('graphic.js'); - -// Ansitex specific includes +// ANSItex specific includes load('ansitex/load/funcs.js'); load('ansitex/load/msgbases.js'); +// Our page handler +load('ansitex/load/page.js'); + require('ansitex/load/defs.js','ACTION_EXIT'); -require('ansitex/load/frame-ansi.js','FRAME_ANSI'); -require('ansitex/load/frame-viewdata.js','FRAME_VIEWDATA'); // @todo Returning from chat should refresh the frame -// @todo Suppress displays of telegrams -bbs.node_settings &= ~(NM_LOGON_P); // Dont always ask for a password. +// @todo Suppress displays of telegrams (but trigger when they arrive) +/** + * SBBS terminal settings init + */ +// Dont always ask for a password. +bbs.node_settings &= ~(NM_LOGON_P); +// We'll take care of inactivity +bbs.node_settings &= NM_NO_INACT; +// No spinning cursors +bbs.node_settings &= NM_NOPAUSESPIN; +// Dont allow users to login with a number +bbs.node_settings &= NM_NO_NUM; + +// Suppress some SBBS message prompts, as we handle them +bbs.replace_text(390,''); // Unknown User +bbs.replace_text(391,''); // Invalid Login +bbs.replace_text(826,''); // LoggingOn + +switch (client.socket.local_port) { + case 516: + require('ansitex/load/session-viewdata.js','SESSION_VIEWDATA'); + break; + + // Assume ANSItex + default: + require('ansitex/load/session-ansitex.js','SESSION_ANSITEX'); +} /** - * This is our main event loop - we where interact with the user. + * This is our main event loop - where we interact with the user. * This loop takes care of first connect (unauthenticated users), or after authentication (main shell). */ -while(bbs.online) { - /** - * State of the current action - * - FALSE means we are not doing anything - * - MODE_* is the mode (as defined in defs.js) - * @type number|boolean - */ - var mode = false; // Initial mode - - /** - * The next page to render - * @type {{frame: number,index: string}} - */ - var next_page = user.name ? HOME_FRAME_AUTH : HOME_FRAME_CONNECT; // Start Frame - +while (bbs.online) { /** * Next action to take - * - FALSE means no action + * - NULL means no action * - ACTION_* is the action (as defined in defs.js) - * @type {number} + * @type {number|null} */ var action = ACTION_GOTO; // Initial action + /** + * State of the current action + * - NULL means we are not doing anything + * - MODE_* is the mode (as defined in defs.js) + * @type {number|null} + */ + var mode = null; // Initial mode + + /** + * The next page to display + * @type {PageObject} + */ + var next_page = new PageObject(user.name ? HOME_FRAME_AUTH : HOME_FRAME_CONNECT); // Start Frame + /** * Variable holding our current key timeout value * @type {number} @@ -58,12 +85,18 @@ while(bbs.online) { var inkey_timeout = INKEY_TIMEOUT; /** - * Current Frame Object that is being displayed to the user - * - FrameViewdata - for viewdata frames - * - FrameAnsi - for ANSItex frames - * @type {FrameAnsi|FrameViewdata} + * Current Session Object that describe the terminal that the user has connected on + * - SessionViewdata - for ViewData sessions + * - SessionAnsitex - for ANSItex sessions + * @type {SessionAnsitex|SessionViewdata|null} */ - var fo = null; + var so = null; + + /** + * History of PageObjects that the user has seen this session + * @type {array} + */ + var history = []; /** * Current input field being edited when a frame has input fields @@ -72,18 +105,11 @@ while(bbs.online) { */ var fn = null; - /** - * History of frames that the user has seen this session - * @type {array} - */ - var history = []; - /** * Current Input Field. * @type {object} */ var cf = null; - var cc = null; // Current Control Method /** * User has hit the inactivity timeout without any input @@ -97,6 +123,12 @@ while(bbs.online) { */ var timer = time(); + /** + * Current Control Method + * @type {null} + */ + var cc = null; + /** * Which command control methods are in play and process input * @type {array} @@ -116,29 +148,33 @@ while(bbs.online) { var extendedkey = ''; /** - * Our session is a viewdata session or an ansitex session - * @type {boolean} + * The current input character + * @type {string} */ - const viewdata = (client.socket.local_port === 516); + var read = ''; + + /** + * ESC key sequence received + */ + var esc = false; while (action !== ACTION_TERMINATE && action !== ACTION_EXIT && bbs.online) { try { - bbs.nodesync(); // @todo Stop the display of telegrams - - /* The current input character */ - var read = ''; - /* ESC key sequence received */ - var esc = false; - log(LOG_DEBUG,'- Start ACTION is ['+action+']'); + bbs.nodesync(); + + read = ''; + esc = false; + // If we have no action, read from the terminal - if (action === false) { + if (action === null) { // If a special key sequence is coming... while ((esc || ! read) && (action !== ACTION_TERMINATE)) { log(LOG_DEBUG,'================================================'); log(LOG_DEBUG,'- READ START : control.length='+control.length); log(LOG_DEBUG,'- READ START : inkey_timeout='+inkey_timeout); + // Wait for a key from the user read = console.inkey(K_NONE,inkey_timeout); @@ -180,6 +216,7 @@ while(bbs.online) { // Record the character as an extended key } else if (esc) { log(LOG_DEBUG,'- READ SPECIAL KEY ['+read+'] ('+read.charCodeAt(0)+')'); + extendedkey += read; read = false; } @@ -191,30 +228,31 @@ while(bbs.online) { // Terminate the user if they have been inactive too long. if (time() > timer+((user.number ? INACTIVE_LOGIN : INACTIVE_NOLOGIN)+INKEY_TIMEOUT)/1000) { - fo.sendBaseline('INACTIVE',false); + so.baselineSend('INACTIVE',false); action = ACTION_TERMINATE; - mode = false; + mode = null; + log(LOG_INFO,'User INACTIVE - terminating...'); // Idle warning - due to inactivity. } else if (time() > timer+(user.number ? INACTIVE_LOGIN : INACTIVE_NOLOGIN)/1000) { timeout = true; - fo.sendBaseline('INACTIVITY',false); + so.baselineSend('INACTIVITY',false); if (cf) { - fo.gotoxy(cf.c+cf.fvalue.length,cf.r); - fo.attr(cf.attribute); + so.gotoxy(cf.x+cf.value.length,cf.y); + so.attr(cf.attribute); } } } else { // If the user become active during inactivity, clear the baseline message if (timeout) { - fo.clearBaseline(false); + so.baselineClear(false); if (cf) { - fo.gotoxy(cf.c+cf.fvalue.length,cf.r); - fo.attr(cf.attribute); + so.gotoxy(cf.x+cf.value.length,cf.y); + so.attr(cf.attribute); } } @@ -222,21 +260,20 @@ while(bbs.online) { timeout = false; } - if (esc) { - log(LOG_DEBUG,'- READ SPECIAL KEY LOOP'); - } - // If we are in a control, we need to break here so that the control takes the input if (control.length) break; } } + log(LOG_DEBUG,'READ: ['+read+'] ('+read.charCodeAt(0)+')'); - system.node_list[bbs.node_num-1].action=0xff; // to ensure our node status is updated correctly + // to ensure our node status is updated correctly + system.node_list[bbs.node_num-1].action = 0xff; // After reading from the terminal, see if we need to pass that input to a control module, // except if input is on the bottom line + log(LOG_DEBUG,'------------------------------------------------'); log(LOG_DEBUG,'CONTROL: mode ['+mode+'] ('+control.length+')'); if ((mode !== MODE_BL) && control.length) { log(LOG_DEBUG,'CONTROL DEBUG: ['+control.length+'] ('+JSON.stringify(control)+')'); @@ -253,18 +290,20 @@ while(bbs.online) { log(LOG_DEBUG,'CONTROL COMPLETE: ['+read+'] ('+control.length+')'); // If there are no more control items - if (control.length == 0) + if (control.length === 0) inkey_timeout = INKEY_TIMEOUT; } - - log(LOG_DEBUG,'CONTROL END: ['+read+']'); } + log(LOG_DEBUG,'CONTROL END: ['+read+']'); + log(LOG_DEBUG,'------------------------------------------------'); + + log(LOG_DEBUG,'------------------------------------------------'); log(LOG_DEBUG,'MODE START: ['+read.charCodeAt(0)+'] ('+mode+')'); switch (mode) { // Normal navigation - case false: - log(LOG_DEBUG,'- MODE false: ['+read+']'); + case null: + log(LOG_DEBUG,'- MODE NULL: ['+read+']'); switch (read) { case '*': action = ACTION_STAR; @@ -281,41 +320,43 @@ while(bbs.online) { case '7': case '8': case '9': - log(LOG_DEBUG,'- MODE false: Key ['+read+'] Route ['+fo.key[read]+']'); - if (fo.key[read] !== null) { - // If are requesting a home page - if (fo.key[read] === 0) { - next_page = user.number ? HOME_FRAME : LOGIN_FRAME; - action = ACTION_GOTO; - log(LOG_DEBUG,'- false: Key ['+read+'] ['+pageStr(next_page)+']'); + log(LOG_DEBUG,'- MODE null: Key ['+read+'] Route ['+so.page.key[read]+']'); - } else if (fo.key[read].toString().match(/^[0-9]+/)) { - next_page = {frame: fo.key[read],index: 'a'}; + if (so.page.key[read] !== null) { + // If a page routes to 0, requesting the home page + if (so.page.key[read] === 0) { + next_page = new PageObject(user.number ? HOME_FRAME : LOGIN_FRAME); action = ACTION_GOTO; - log(LOG_DEBUG,'- false: Key ['+read+'] ['+pageStr(next_page)+']'); + log(LOG_DEBUG,'- NULL: Key ['+read+'] ['+next_page.name+']'); + + } else if (so.page.key[read].toString().match(/^[0-9]+/)) { + next_page = new PageObject(so.page.key[read],'a'); + action = ACTION_GOTO; + log(LOG_DEBUG,'- NULL: Key ['+read+'] ['+next_page.name+']'); } else { - fo.sendBaseline('ERR_ROUTE',false); + so.baselineSend('ERR_ROUTE',false); } } else { - fo.sendBaseline('ERR_ROUTE',false); + so.baselineSend('ERR_ROUTE',false); } break; case '_': // Viewdata terminal's # is an _ character - if (! viewdata) break; + if (SESSION_EXT === 'tex') break; /* fallthrough */ case '#': - log(LOG_DEBUG,'- false: Key ['+read+'] ['+pageStr(fo)+']'); - if (fo.index !== 'z') { - next_page = {frame: fo.frame,index: String.fromCharCode(fo.index.charCodeAt(0)+1)}; + log(LOG_DEBUG,'- NULL: Key ['+read+'] ['+so.page.name.name+']'); + next_page = so.page.pagenext; + + if (next_page) { action = ACTION_GOTO; } else { - fo.sendBaseline('ERR_ROUTE',false); + so.baselineSend('ERR_ROUTE',false); } break; @@ -343,15 +384,18 @@ while(bbs.online) { if (cmd === '00') { action = ACTION_RELOAD; cmd = ''; - fo.cursorOff(); + so.cursorOff(); + so.baselineClear(); break; } // Special code to launch SQRL if (cmd === '01' && ! user.number) { action = ACTION_GOTO; - next_page = SQRL_FRAME; cmd = ''; + so.cursorOff(); + so.baselineClear(); + next_page = new PageObject(SQRL_FRAME); break; } @@ -359,10 +403,10 @@ while(bbs.online) { if (cmd.match(/^0[12367]/)) { log(LOG_DEBUG,'- MODE_BL: Invalid System Page ['+cmd+']'); - fo.cursorOff(); - fo.sendBaseline('ERR_ROUTE',false); - mode = action = false; + action = mode = null; cmd = ''; + so.cursorOff(); + so.baselineSend('ERR_ROUTE',false); } // Edit specific frame @@ -371,20 +415,20 @@ while(bbs.online) { // If we are not a user if (! user.number) { - fo.cursorOff(); - fo.sendBaseline('ERR_ROUTE',false); - action = false; + action = null; + so.baselineSend('ERR_ROUTE',false); } else { - next_page = {frame: parseInt(cmd.substr(2,cmd.length-1)),index: read}; - fo.cursorOff(); action = ACTION_EDIT; + so.baselineClear(); + next_page = new PageObject(parseInt(cmd.substr(2,cmd.length-1)),read); - log(LOG_DEBUG,'- MODE_BL: EDIT ['+JSON.stringify(next_page)+']'); + log(LOG_DEBUG,'- MODE_BL: EDIT ['+JSON.stringify(next_page.name)+']'); } - mode = false; + mode = null; cmd = ''; + so.cursorOff(); break; } @@ -394,51 +438,47 @@ while(bbs.online) { log(LOG_DEBUG,'- MODE_BL: Bookmark ['+cmd+']'); if (! user.number) { - fo.cursorOff(); - fo.sendBaseline('ERR_ROUTE',false); - mode = action = false; - cmd = ''; + so.baselineSend('ERR_ROUTE',false); } else { // @todo - fo.cursorOff(); - fo.sendBaseline('ERR_NOT_IMPLEMENTED',false); - mode = action = false; - cmd = ''; + so.baselineSend('ERR_NOT_IMPLEMENTED',false); } + action = mode = null; + cmd = ''; + so.cursorOff(); + break; } // Report Problem if (cmd === '08') { - log(LOG_DEBUG,'- MODE_BL: Report Problem ['+cmd+'] ('+fo.page+')'); + log(LOG_DEBUG,'- MODE_BL: Report Problem ['+cmd+'] ('+so.page.name.name+')'); if (! user.number) { - fo.cursorOff(); - fo.sendBaseline('ERR_ROUTE',false); - mode = action = false; - cmd = ''; + so.baselineSend('ERR_ROUTE',false); } else { // @todo - fo.cursorOff(); - fo.sendBaseline('ERR_NOT_IMPLEMENTED',false); - mode = action = false; - cmd = ''; + so.baselineSend('ERR_NOT_IMPLEMENTED',false); } + action = mode = null; + cmd = ''; + so.cursorOff(); + break; } // Reload frame if (cmd === '09') { - log(LOG_DEBUG,'- MODE_BL: Reload frame ['+cmd+'] ('+fo.page+')'); + log(LOG_DEBUG,'- MODE_BL: Reload frame ['+cmd+'] ('+so.page.name.name+')'); - fo.cursorOff(); action = ACTION_GOTO; cmd = ''; - next_page = {frame: fo.frame,index: fo.index}; + so.cursorOff(); + next_page = so.page.name; break; } @@ -447,10 +487,10 @@ while(bbs.online) { if (read === '*') { log(LOG_DEBUG,'- MODE_BL: Abort ['+cmd+'])'); - fo.clearBaseline(false); - fo.cursorOff(); - mode = action = false; + action = mode = null; cmd = ''; + so.cursorOff(); + so.baselineClear(false); if (cf) { // If there is a control for this field, @@ -458,47 +498,47 @@ while(bbs.online) { cc.prefield(); mode = MODE_FIELD; - fo.gotoxy(cf.c,cf.r); - fo.attr(cf.attribute); - console.write(cf.fchar.repeat(cf.fvalue.length)); - fo.cursorOn(cf.c,cf.r); - cf.fvalue = ''; + so.gotoxy(cf.x,cf.y); + so.attr(cf.attribute); + console.write(cf.char.repeat(cf.value.length)); + so.cursorOn(cf.x,cf.y); + cf.value = ''; } } - if ((viewdata && read === '_') || (! viewdata && read === '#') || ((read === "\r") && (cmd.length > 0))) { + if (((SESSION_EXT === 'vtx') && read === '_') || ((SESSION_EXT === 'tex') && read === '#') || ((read === "\r") && (cmd.length > 0))) { log(LOG_DEBUG,'- MODE_BL: Return Received ['+cmd+'])'); // Nothing typed between * and # // *# means go back if (cmd === '') { - fo.clearBaseline(false); + so.baselineClear(false); action = ACTION_BACKUP; } else if (cmd === '0') { - next_page = user.number ? HOME_FRAME : LOGIN_FRAME; + next_page = new PageObject(user.number ? HOME_FRAME : LOGIN_FRAME); action = ACTION_GOTO; // Edit frame } else if (cmd === '04') { // If we are not a user if (! user.number) { - fo.sendBaseline('ERR_ROUTE',false); - action = false; + so.baselineSend('ERR_ROUTE',false); + action = null; } else { action = ACTION_EDIT; } } else { - next_page = {frame: parseInt(cmd),index: 'a'}; + next_page = new PageObject(cmd,'a'); action = ACTION_GOTO; } // Clear the command we are finished processing... - fo.cursorOff(); cmd = ''; - mode = false; + mode = null; + so.cursorOff(); } log(LOG_DEBUG,'- MODE_BL: END'); @@ -506,22 +546,22 @@ while(bbs.online) { // Key presses during field input. case MODE_FIELD: - action = false; + action = null; - switch (fo.type) { + switch (so.page.type) { // Login frame. case FRAME_TYPE_LOGIN: switch (read) { case '_': - if (! viewdata) break; + if (SESSION_EXT === 'tex') break; /* fallthrough */ case '#': case "\r": log(LOG_DEBUG,'- MODE_FIELD:FRAME_TYPE_LOGIN: ['+read+'] A'); // If we are the main login screen, see if it is a new user - if (cf.ftype === 't' && cf.fvalue.toUpperCase() === 'NEW') { + if (cf.type === 't' && cf.value.toUpperCase() === 'NEW') { action = ACTION_GOTO; - next_page = REGISTER_FRAME; + next_page = new PageObject(REGISTER_FRAME); break; } @@ -529,7 +569,6 @@ while(bbs.online) { break; } /* fallthrough */ - // Response frame. case FRAME_TYPE_RESPONSE: // If we came from FRAME_TYPE_LOGIN and the user typed NEW to register @@ -539,7 +578,7 @@ while(bbs.online) { switch (read) { // End of field entry. case '_': - if (! viewdata) break; + if (SESSION_EXT === 'tex') break; /* fallthrough */ case '#': case "\r": @@ -547,7 +586,7 @@ while(bbs.online) { // Next Field fn++; - cf = fo.frame_fields[fn]; + cf = so.page.input_fields[fn]; log(LOG_DEBUG,'fn:'+fn+', cf'+JSON.stringify(cf)); if (cf) { @@ -556,8 +595,8 @@ while(bbs.online) { cc.prefield(); mode = MODE_FIELD; - fo.gotoxy(cf.c,cf.r); - fo.attr(cf.attribute); + so.gotoxy(cf.x,cf.y); + so.attr(cf.attribute); // Finished all editable fields. } else { @@ -576,11 +615,11 @@ while(bbs.online) { // Delete Key pressed case CTRL_H: case KEY_DEL: - log(LOG_DEBUG,'- MODE_FIELD:FRAME_TYPE_RESPONSE: DEL ['+read+']'+' cf:'+(cf ? cf.fvalue.length : '{}')+' ct:'+cf.ftype); + log(LOG_DEBUG,'- MODE_FIELD:FRAME_TYPE_RESPONSE: DEL ['+read+']'+' cf:'+(cf ? cf.value.length : '{}')+' ct:'+cf.type); - if (cf.fvalue.length > 0) { - cf.fvalue = cf.fvalue.substring(0,cf.fvalue.length-1); - fo.fieldbs(cf.fchar); + if (cf.value.length > 0) { + cf.value = cf.value.substring(0,cf.value.length-1); + so.fieldbs(cf.char); } break; @@ -594,12 +633,12 @@ while(bbs.online) { // Next Field fn++; - cf = fo.frame_fields[fn]; + cf = so.page.input_fields[fn]; log(LOG_DEBUG,'fn:'+fn+', cf'+JSON.stringify(cf)); if (! cf) { fn = 0; - cf = fo.frame_fields[fn]; + cf = so.page.input_fields[fn]; } // If there is a control for this field, @@ -607,8 +646,8 @@ while(bbs.online) { cc.prefield(); mode = MODE_FIELD; - fo.gotoxy(cf.c+cf.fvalue.length,cf.r); - fo.attr(cf.attribute); + so.gotoxy(cf.x+cf.value.length,cf.y); + so.attr(cf.attribute); break; @@ -618,10 +657,10 @@ while(bbs.online) { fn--; if (fn < 0) { - fn = fo.frame_fields.length-1; + fn = so.page.input_fields.length-1; } - cf = fo.frame_fields[fn]; + cf = so.page.input_fields[fn]; log(LOG_DEBUG,'fn:'+fn+', cf'+JSON.stringify(cf)); // If there is a control for this field, @@ -629,18 +668,18 @@ while(bbs.online) { cc.prefield(); mode = MODE_FIELD; - fo.gotoxy(cf.c+cf.fvalue.length,cf.r); - fo.attr(cf.attribute); + so.gotoxy(cf.x+cf.value.length,cf.y); + so.attr(cf.attribute); break; // Record Data Entry default: - log(LOG_DEBUG,'- MODE_FIELD:FRAME_TYPE_RESPONSE: ['+read+'] E:'+read.charCodeAt(0)+' cf:'+(cf ? cf.flength : '{}')); + log(LOG_DEBUG,'- MODE_FIELD:FRAME_TYPE_RESPONSE: ['+read+'] E:'+read.charCodeAt(0)+' cf:'+(cf ? cf.length : '{}')); - if (read.charCodeAt(0) > 31 && cf.fvalue.length < cf.flength) { - cf.fvalue += read; - console.write((cf.ftype === 't') ? read : 'x'); + if (read.charCodeAt(0) > 31 && cf.value.length < cf.length) { + cf.value += read; + console.write((cf.type === 't') ? read : 'x'); } } @@ -656,11 +695,15 @@ while(bbs.online) { // Form submission: 1 to send, 2 not to send. case MODE_SUBMITRF: + so.cursorOff(); + switch (read) { case '1': - log(LOG_DEBUG,'- MODE_SUBMITRF: Key ['+read+'] ['+pageStr(fo)+']'); - log(LOG_DEBUG,' - Frame fields: '+JSON.stringify(fo.frame_fields)); - log(LOG_DEBUG,' - Key 1 is:'+JSON.stringify(fo.key[1])); + so.baselineSend('PROCESSING'); + + log(LOG_DEBUG,'- MODE_SUBMITRF: Key ['+read+'] ['+pageStr(so)+']'); + log(LOG_DEBUG,' - Frame fields: '+JSON.stringify(so.page.input_fields)); + log(LOG_DEBUG,' - Key 1 is:'+JSON.stringify(so.page.key[1])); // If we are in a control method, complete it if (control.length) { @@ -668,20 +711,20 @@ while(bbs.online) { control[control.length-1].process(); - } else if (fo.key[1] === '*' || fo.key[1].match(/[0-9]/)) { - fo.sendBaseline('NOACTION',false); + } else if (so.page.key[1] === '*' || so.page.key[1].match(/[0-9]/)) { + so.baselineSend('NOACTION',false); mode = MODE_RFSENT; } else { - log(LOG_DEBUG,' - Key 1 is a METHOD check it exists: '+JSON.stringify(fo.key[1])); + log(LOG_DEBUG,' - Key 1 is a METHOD check it exists: '+JSON.stringify(so.page.key[1])); - switch(fo.key[1]) { + switch(so.page.key[1]) { // User is logging in to system or CUG case 'login': - log(LOG_DEBUG,' - User: '+fo.frame_fields[0].fvalue+'/'+fo.frame_fields[1].fvalue); + log(LOG_DEBUG,' - User: '+so.page.input_fields[0].value+'/'+so.page.input_fields[1].value); // If login is successful, we'll exit here - if (bbs.login(fo.frame_fields[0].fvalue,'',fo.frame_fields[1].fvalue)) { + if (bbs.login(so.page.input_fields[0].value,'',so.page.input_fields[1].value)) { log(LOG_DEBUG,' - User: '+JSON.stringify(user.number)); bbs.logon(); log(LOG_DEBUG,' - SEND TO EXIT:'); @@ -690,14 +733,15 @@ while(bbs.online) { break; } - log(LOG_DEBUG,' ! Login failed for User:'+JSON.stringify(fo.frame_fields[0].fvalue)); + log(LOG_DEBUG,'***:'+JSON.stringify(bbs)); + log(LOG_DEBUG,' ! Login failed for User:'+JSON.stringify(so.page.input_fields[0].value)); action = ACTION_GOTO; - next_page = LOGIN_FAILED_FRAME; + next_page = new PageObject(LOGIN_FAILED_FRAME); break; default: // Its assumed that you get here after completing a form and you have pressed 1 to submit that form. - log(LOG_DEBUG,' ! EVAL method:'+JSON.stringify(fo.key)); + log(LOG_DEBUG,' ! EVAL method:'+JSON.stringify(so.page.key)); if (cc.process()) { control.pop(); log(LOG_DEBUG,' = Process Completed.'); @@ -714,25 +758,27 @@ while(bbs.online) { $next_page = $ao->page; } else { - fo.sendBaseline('ERR_METHOD_NOT_EXIST',false); + so.baselineSend('ERR_METHOD_NOT_EXIST',false); mode = MODE_RFSENT; } - */ + */ } break; case '2': - log(LOG_DEBUG,'- MODE_SUBMITRF: Key ['+read+'] ['+pageStr(fo)+']'); + so.baselineClear(); + + log(LOG_DEBUG,'- MODE_SUBMITRF: Key ['+read+'] ['+pageStr(so)+']'); // @todo Check if HASH is a valid next destination - if (fo.type === 'l') { + if (so.page.type === 'l') { action = ACTION_RELOAD; - mode = false; + mode = null; } else { - fo.sendBaseline('MSG_NOTSENT',false); + so.baselineSend('MSG_NOTSENT',false); mode = MODE_RFNOTSENT; } @@ -767,7 +813,7 @@ while(bbs.online) { // Response form after Sent processing case MODE_RFSENT: - fo.cursorOff(); + so.cursorOff(); switch (read) { case '*': @@ -776,6 +822,7 @@ while(bbs.online) { } /* + // @todo to implement if ($read === HASH) { if ($x = $this->fo->route(2) AND $x !== '*' AND is_numeric($x)) { $next_page = ['frame'=>$x]; @@ -792,10 +839,9 @@ while(bbs.online) { } $action = ACTION_GOTO; - } - */ + break; // Response form after NOT sending @@ -803,10 +849,11 @@ while(bbs.online) { // Response form ERROR case MODE_RFERROR: - fo.cursorOff(); + so.cursorOff(); - if ((viewdata && read === '_') || (! viewdata && read === '#')) { + if (((SESSION_EXT === 'vtx') && read === '_') || ((SESSION_EXT === 'tex') && read === '#')) { /* + // @todo to implement if ($x = $this->fo->route(2) AND $x !== '*' AND is_numeric($x)) { $next_page = ['frame'=>$x]; @@ -820,7 +867,7 @@ while(bbs.online) { } else { $next_page = ['frame'=>0]; } - */ + */ action = ACTION_GOTO; @@ -837,7 +884,9 @@ while(bbs.online) { action = ACTION_TERMINATE; } log(LOG_DEBUG,'MODE END: ['+read+']'); + log(LOG_DEBUG,'------------------------------------------------'); + log(LOG_DEBUG,'------------------------------------------------'); log(LOG_DEBUG,'ACTION START: ['+read+'] ('+action+')'); switch (action) { // Start command entry @@ -845,24 +894,26 @@ while(bbs.online) { log(LOG_DEBUG,'- ACTION_STAR: ['+(next_page ? pageStr(next_page) : '')+']'); // @todo If something on the baseline preserve it - - fo.cursorOn(0,24); - fo.sendBaseline('BASESTAR',true); - action = false; + // @todo This should be SCREEN_LENGTH, not hard coded 24 + so.cursorOn(0,24); + so.baselineSend('BASESTAR',true); + action = null; mode = MODE_BL; - bbs.replace_text(NodeActionRetrieving,'\1h%s \1n\1gJumping to page'); + // 719 = NodeActionRetrieving + bbs.replace_text(719,'\1h%s \1n\1gJumping to page'); bbs.node_action=NODE_RFSD; break; // Submitting forms case ACTION_SUBMITRF: - action = false; - fo.cursorOff(); - log(LOG_DEBUG,'- ACTION_SUBMITRF: ['+fo.type+']'); - fo.sendBaseline((fo.type === 'l' ? 'MSG_LOGON' : 'MSG_SENDORNOT'),true); + action = null; mode = MODE_SUBMITRF; + so.cursorOff(); + + log(LOG_DEBUG,'- ACTION_SUBMITRF: ['+so.page.type+']'); + so.baselineSend((so.page.type === 'l' ? 'MSG_LOGON' : 'MSG_SENDORNOT'),true); break; @@ -870,10 +921,11 @@ while(bbs.online) { case ACTION_EDIT: log(LOG_DEBUG,'- ACTION_EDIT: ['+JSON.stringify(next_page)+']'); - if ((fo.type === FRAME_TYPE_MESSAGE) || (! pageEditor(next_page ? next_page.frame : fo.frame))) { - fo.cursorOff(); - fo.sendBaseline('ACCESS_DENIED',false); - action = false; + if ((so.page.type === FRAME_TYPE_MESSAGE) || (! pageEditor(next_page ? next_page.frame : so.frame))) { + action = null; + so.cursorOff(); + so.baselineSend('ACCESS_DENIED',false); + break; } @@ -881,43 +933,42 @@ while(bbs.online) { // If we are editing a specific frame, attempt to load it if (next_page) { - var current = fo; - fo = viewdata ? new FrameViewdata() : new FrameAnsi(); - fo.load(pageStr(next_page)); + var current = so; + so = (SESSION_EXT === 'vtx') ? new SessionViewdata() : new SessionAnsitex(); + so.get(next_page); // If the frame doesnt exist, check that the parent frame exists in case we are creating a new one - if (fo.page === null) { + if (so.page.name.name === null) { log(LOG_DEBUG,'- ACTION_EDIT: check index: '+next_page.index+' ('+String.fromCharCode(next_page.index.charCodeAt(0)-1)+')'); // We can always create an 'a' frame if (next_page.index !== 'a') { - fo = viewdata ? new FrameViewdata() : new FrameAnsi(); - fo.load(pageStr({frame: next_page.frame, index: String.fromCharCode(next_page.index.charCodeAt(0)-1)})); + so = (SESSION_EXT === 'vtx') ? new SessionViewdata() : new SessionAnsitex(); + so.get(pageStr({frame: next_page.frame, index: String.fromCharCode(next_page.index.charCodeAt(0)-1)})); - log(LOG_DEBUG,'- ACTION_EDIT: check index: '+JSON.stringify(fo)+' ('+String.fromCharCode(next_page.index.charCodeAt(0)-1)+')'); - if (fo.page === null) { - fo = current; + log(LOG_DEBUG,'- ACTION_EDIT: check index: '+JSON.stringify(so)+' ('+String.fromCharCode(next_page.index.charCodeAt(0)-1)+')'); + if (so.page.name.name === null) { + so = current; // sendbaseline ERR_PAGE - fo.sendBaseline('ERR_NO_PARENT',false); - mode = action = false; + so.baselineSend('ERR_NO_PARENT',false); + action = mode = null; break; } } // New frame - fo = viewdata ? new FrameViewdata() : new FrameAnsi(); - fo.frame = next_page.frame; - fo.index = next_page.index; - fo.cost = 0; - fo.owner = base64_decode(pageOwner(pageStr(next_page)).logo); - fo.content = base64_encode('Start your new page...'); + so = (SESSION_EXT === 'vtx') ? new SessionViewdata() : new SessionAnsitex(); + so.frame = next_page.frame; + so.index = next_page.index; + so.cost = 0; + so.owner = base64_decode(pageOwner(pageStr(next_page)).logo); + so.content = base64_encode('Start your new page...'); } } - control.push(new edit(fo)); + control.push(new edit(so)); - mode = false; - action = false; + action = mode = null; break; // GO Backwards @@ -931,11 +982,11 @@ while(bbs.online) { next_page = (history.length > 0) ? history[history.length-1] : null; - log(LOG_DEBUG,'- ACTION_BACKUP: Backing up to ['+JSON.stringify(next_page)+'] current ['+fo.page+']'); + log(LOG_DEBUG,'- ACTION_BACKUP: Backing up to ['+JSON.stringify(next_page)+'] current ['+so.page.name.name+']'); // If there is no next page, we'll ignore the request. - if (! next_page || (pageStr(next_page) === fo.page)) { - action = false; + if (! next_page || (next_page.name === so.page.name.name)) { + action = null; break; } @@ -944,7 +995,7 @@ while(bbs.online) { // Clear any controls control = []; - log(LOG_DEBUG,'- ACTION_GOTO: ['+(next_page ? pageStr(next_page) : '')+']'); + log(LOG_DEBUG,'- ACTION_GOTO: ['+(next_page.name+']')); var current = null; // For logged in users, we'll see if this is a mail page. @@ -955,7 +1006,7 @@ while(bbs.online) { if (/^1[0-9]{6}1$/.test(next_page.frame)) { log(LOG_DEBUG,'- ACTION_GOTO - load echoarea summary: ['+next_page.frame+']'); - to = viewdata ? new FrameViewdata() : new FrameAnsi(); + to = (SESSION_EXT === 'vtx') ? new SessionViewdata() : new SessionAnsitex(); // @todo look for a template in the area or group first to.load(MAIL_TEMPLATE_AREA_SUMMARY); @@ -964,33 +1015,33 @@ while(bbs.online) { // If the template page doesnt exist if ((! to.content) || (! area)) { - fo.sendBaseline('ERR_ROUTE',false); - mode = action = false; + so.baselineSend('ERR_ROUTE',false); + action = mode = null; break; } - current = fo; + current = so; - fo = viewdata ? new FrameViewdata() : new FrameAnsi(); - fo.frame = next_page.frame; - fo.index = next_page.index; - fo.content = to.content; - fo.isPublic = true; - fo.isAccessible = to.isAccessible; - fo.owner = to.owner; - fo.type = to.type; + so = (SESSION_EXT === 'vtx') ? new SessionViewdata() : new SessionAnsitex(); + so.frame = next_page.frame; + so.index = next_page.index; + so.content = to.content; + so.isPublic = true; + so.isAccessible = to.isAccessible; + so.owner = to.owner; + so.page.type = to.type; // Parent - fo.key[0] = (''+next_page.frame).substr(0,7); + so.page.key[0] = (''+next_page.frame).substr(0,7); // First to me - fo.key[1] = atcode('msg_area_msgtome_page',null,null,area); + so.page.key[1] = atcode('msg_area_msgtome_page',null,null,area); // First Unread - fo.key[2] = atcode('msg_area_msgunread_page',null,null,area); + so.page.key[2] = atcode('msg_area_msgunread_page',null,null,area); // Oldest - fo.key[3] = atcode('msg_area_msgoldest_page',null,null,area); + so.page.key[3] = atcode('msg_area_msgoldest_page',null,null,area); // Newest - fo.key[4] = atcode('msg_area_msgnewest_page',null,null,area); + so.page.key[4] = atcode('msg_area_msgnewest_page',null,null,area); next_page = null; @@ -1001,52 +1052,53 @@ while(bbs.online) { if (next_page.index === 'a') { require('ansitex/load/control-echomail.js','CONTROL_ECHOMAIL'); control.push(new echomail(next_page.frame)); - action = false; + action = null; next_page = null; log(LOG_DEBUG,'- ACTION_GOTO - control message: ['+JSON.stringify(control[control.length-1])+'] ('+control.length+')'); if (! control[control.length-1].ready()) { log(LOG_DEBUG,'- ACTION_GOTO - control not ready aborting...'); control.pop(); - mode = false; + mode = null; - fo.sendBaseline('ERR_ROUTE',false); + so.baselineSend('ERR_ROUTE',false); break; } // @todo - Show message stats } else if (next_page.index === 'b') { - action = mode = false; + action = mode = null; next_page = null; - fo.sendBaseline('ERR_NOT_IMPLEMENTED',false); + so.baselineSend('ERR_NOT_IMPLEMENTED',false); break; } else { - mode = action = false; + action = mode = null; next_page = null; - fo.sendBaseline('ERR_ROUTE',false); + so.baselineSend('ERR_ROUTE',false); break; } } } if (next_page !== null) { - current = fo; - fo = viewdata ? new FrameViewdata() : new FrameAnsi(); - fo.load(pageStr(next_page)); + current = so; + so = (SESSION_EXT === 'vtx') ? new SessionViewdata() : new SessionAnsitex(); + so.get(next_page); - if (fo.page === null) { - fo = current; + if (so.page.name.name === null) { + log(LOG_DEBUG,'- Next Page: ['+(next_page.name+'] doesnt exist?')); + so = current; // In case the frame doesnt exist - if (fo === null) - fo = viewdata ? new FrameViewdata() : new FrameAnsi(); + if (so === null) + so = (SESSION_EXT === 'vtx') ? new SessionViewdata() : new SessionAnsitex(); // sendbaseline ERR_PAGE - fo.sendBaseline('ERR_ROUTE',false); - mode = action = false; + so.baselineSend('ERR_ROUTE',false); + action = mode = null; break; } @@ -1054,30 +1106,30 @@ while(bbs.online) { } // If the user has access to the frame - if (fo.accessible) { - if (fo.isMember && fo.type === FRAME_TYPE_LOGIN) { - fo.sendBaseline('ALREADY_MEMBER',false); - mode = action = false; + if (so.page.accessible) { + if (so.page.isMember && so.page.type === FRAME_TYPE_LOGIN) { + so.baselineSend('ALREADY_MEMBER',false); + action = mode = null; break; } // Check if the frame exists, and the user is the Service Provider } else { - fo.sendBaseline('ACCESS_DENIED',false); - mode = action = false; + so.baselineSend('ACCESS_DENIED',false); + action = mode = null; // Reset the current frame to what it was. - fo = current; + so = current; break; } log(LOG_DEBUG,'- ACTION_GOTO: next_page ['+JSON.stringify(next_page)+'] last history ['+JSON.stringify(history[history.length-1])+']'); // Record our history - if ((! history.length || (pageStr(history[history.length-1]) !== fo.page)) && (fo.type !== FRAME_TYPE_LOGIN)) { + if ((! history.length || (pageStr(history[history.length-1]) !== so.page.name)) && (so.page.type !== FRAME_TYPE_LOGIN)) { // Ignore the login frames - if (NO_HISTORY_FRAMES.indexOf(fo.page) === -1) { - history.push({frame:fo.frame,index:fo.index}); - log(LOG_DEBUG,'- ACTION_GOTO: Added to history ['+fo.page+'] now ['+history.length+']'); + if (NO_HISTORY_FRAMES.indexOf(so.page.name) === -1) { + history.push(so.page.name); + log(LOG_DEBUG,'- ACTION_GOTO: Added to history ['+so.page.name.name+'] now ['+history.length+']'); } } @@ -1089,34 +1141,34 @@ while(bbs.online) { log(LOG_DEBUG,'- ACTION_RELOAD: ['+(next_page ? pageStr(next_page) : '')+']'); console.line_counter = 0; // @todo fix to suppress a pause that is occurring before clear() - fo.cursorOff(); + so.cursorOff(); - bbs.replace_text(NodeActionMain,'\1h%s \1nViewing \1h*'+fo.frame+'#\1n ['+fo.index+']'); - bbs.log_str(fo.page+'|'); + // 695 = NodeActionMain + bbs.replace_text(695,'\1h%s \1nViewing \1h*'+so.page.name.name+'#\1n'); + bbs.log_str(so.page.name.name+'|'); bbs.node_action=NODE_MAIN; - switch(fo.type) { + switch(so.page.type) { // Terminate frame case FRAME_TYPE_MAIL_TEMPLATE: - log(LOG_DEBUG,'- MAIL_TEMPLATE: ['+fo.frame+']'); - fo.render(ma.getArea(fo.frame)); - mode = false; - action = false; + log(LOG_DEBUG,'- MAIL_TEMPLATE: ['+so.frame+']'); + so.render(ma.getArea(so.frame)); + action = mode = null; break; // Terminate frame case FRAME_TYPE_TERMINATE: - fo.render(); - mode = false; + so.render(); action = ACTION_TERMINATE; + mode = null; break; // External Frame // @todo returning from the frame, go to the 0 key if it is set case FRAME_TYPE_EXTERNAL: - var content = base64_decode(fo.content); + var content = base64_decode(so.content); log(LOG_DEBUG,'- ACTION_GOTO: EXTERNAL ['+JSON.stringify(content)+']'); switch(content.replace(/\n/,'')) { @@ -1137,69 +1189,71 @@ while(bbs.online) { default: console.putmsg(JSON.stringify(content)); - fo.sendBaseline('ERR_ROUTE',false); - action = false; + so.baselineSend('ERR_ROUTE',false); + action = null; break; } - mode = false; + mode = null; break; case FRAME_TYPE_LOGIN: - action = false; + action = null; /* fallthrough */ case FRAME_TYPE_RESPONSE: fn = 0; cf = null; - fo.render(); + so.render(); - if (fo.frame_fields.length) { - cf = fo.frame_fields[fn]; + if (so.page.input_fields.length) { + cf = so.page.input_fields[fn]; log(LOG_DEBUG,'cf'+JSON.stringify(cf)); if (cf) { mode = MODE_FIELD; - fo.cursorOn(cf.c,cf.r); - fo.attr(cf.attribute); + so.cursorOn(cf.x,cf.y); + log(LOG_DEBUG,'- Current Field:'+JSON.stringify(cf)); + log(LOG_DEBUG,'Writing attribute:'+JSON.stringify(cf.attribute)); + so.attr(cf.attribute); // There were no editable fields. } else { mode = MODE_COMPLETE; - fo.cursorOff(); + so.cursorOff(); } } else { - mode = false; + mode = null; } // If this is the register page - if (fo.page === pageStr(REGISTER_FRAME)) { + if (so.page.name === new PageObject(REGISTER_FRAME)) { log(LOG_DEBUG,'Adding REGISTER to control stack'); - require('ansitex/load/control-'+fo.key[1]+'.js','CONTROL_REGISTER'); - control.push(eval('new '+fo.key[1]+'();')); + require('ansitex/load/control-'+so.page.key[1]+'.js','CONTROL_REGISTER'); + control.push(eval('new '+so.page.key[1]+'();')); - } else if (fo.page === pageStr(SQRL_FRAME)) { + } else if (so.page.name === new PageObject(SQRL_FRAME)) { log(LOG_DEBUG,'Adding SQRL to control stack'); - require('ansitex/load/control-'+fo.key[1]+'.js','CONTROL_SQRL'); - control.push(eval('new '+fo.key[1]+'();')); + require('ansitex/load/control-'+so.page.key[1]+'.js','CONTROL_SQRL'); + control.push(eval('new '+so.page.key[1]+'();')); inkey_timeout = 1000; - } else if (fo.key[1] && (fo.type === FRAME_TYPE_RESPONSE) && (typeof(fo.key[1]) !== 'number')) { - log(LOG_DEBUG,'Adding METHOD to control stack: '+fo.key[1]); - require('ansitex/load/control-'+fo.key[1]+'.js','CONTROL_'+fo.key[1].toUpperCase()); - control.push(eval('new '+fo.key[1]+'();')); + } else if (so.page.key[1] && (so.page.type === FRAME_TYPE_RESPONSE) && (typeof(so.page.key[1]) !== 'number')) { + log(LOG_DEBUG,'Adding METHOD to control stack: '+so.page.key[1]); + require('ansitex/load/control-'+so.page.key[1]+'.js','CONTROL_'+so.page.key[1].toUpperCase()); + control.push(eval('new '+so.page.key[1]+'();')); } - action = false; + action = null; break; // Standard Frame case FRAME_TYPE_INFO: default: - fo.render(); - mode = action = false; + so.render(); + action = mode = null; break; // Active frame @@ -1208,20 +1262,23 @@ while(bbs.online) { break; } log(LOG_DEBUG,'ACTION END: ['+read+']'); + log(LOG_DEBUG,'------------------------------------------------'); } catch (e) { log(LOG_ERROR,JSON.stringify(e)); - fo.sendBaseline('SYS_ERROR',false); - mode = action = false; + so.baselineSend('SYS_ERROR',false); + action = mode = null; } } log(LOG_DEBUG,'- FINISHED'); if (action === ACTION_TERMINATE) { log(LOG_DEBUG,'! Hangup'); - fo.cursorOn(); + so.cursorOn(); bbs.hangup(); } + // We need to clear the baseline before we exit, esp during transition from login -> logged on + so.baselineClear(); exit(); } diff --git a/save.js b/save.js index dc85864..ec17c45 100644 --- a/save.js +++ b/save.js @@ -1,4 +1,4 @@ -// Ansitex specific includes +// ANSItex specific includes load('ansitex/load/defs.js'); load('ansitex/load/funcs.js'); diff --git a/text/981a.vtx b/text/981a.vtx index 9487645..754627e 100644 --- a/text/981a.vtx +++ b/text/981a.vtx @@ -1 +1 @@ -{"version":1,"frame":981,"index":"a","owner":9,"cost":0,"content":"ICARLGwSfGwUfCwTLBc3a38jMzdrIzUgICAgICACMDAwMTAwMDEwMSAgEX9rEn9qFC98E38XNWhvIDw9JCw1ByAgICAgICAgICAgICAgICAgIBEvLhIvKhQsLxMvF3VwenBxdXpwNSAgICAgICAgICAgICAgICAgICAgVmlkZW90ZXggICAgICAgICAgICAgICAgICAgIA1SZWdpc3RlciAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICACKiogB3RvIGNsZWFyIGlucHV0ICAgICAgICAgICAgICAgICAgICAgAiowMAd0byBzdGFydCBhZ2FpbiAgICAgICBVc2UCXwd0byBFbnRlciAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICABRW1haWwgICAgOgcuLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uICAgAVVzZXIgSUQgIDoHLi4uLi4uLi4uLi4uLi4uICAgICAgICAgICAgIAFQYXNzd29yZCA6By4uLi4uLi4uLi4uLi4uLiAgICAgICAgICAgICABRnVsbCBOYW1lOgcuLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uICAgAUNpdHkgICAgIDoHLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLiAgIAFDb3VudHJ5ICA6By4uLiAgICAgICAgICAgICAgICAgICAgICAgICABUG9zdCBDb2RlOgcuLi4uLi4uLi4uICAgICAgICAgICAgICAgICAgAVRva2VuICAgIDoHLi4uLi4uICAgICAgICAgICAgICAgICAgICAgIBoXODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFJlZ2lzdGVyaW5nIGFuZCB1c2luZyB0aGlzIHN5c3RlbSwgeW91ICBhZ3JlZSB0byBhYmlkZSBieSB0aGUgc3lzdGVtIHJ1bGVzLiBTZWUCKjk4OF8gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA==","isPublic":1,"isAccessible":1,"type":"r","key":[980,"register",null,null,null,null,null,null,null,null],"frame_fields":[{"ftype":"t","flength":25,"fchar":".","fname":"EMAIL","r":9,"c":12,"attribute":{},"fvalue":""},{"ftype":"t","flength":15,"fchar":".","fname":"UID","r":10,"c":12,"attribute":{},"fvalue":""},{"ftype":"p","flength":15,"fchar":".","fname":"PASS","r":11,"c":12,"attribute":{},"fvalue":""},{"ftype":"t","flength":25,"fchar":".","fname":"FULLNAME","r":12,"c":12,"attribute":{},"fvalue":""},{"ftype":"t","flength":25,"fchar":".","fname":"CITY","r":13,"c":12,"attribute":{},"fvalue":""},{"ftype":"t","flength":3,"fchar":".","fname":"COUNTRY","r":14,"c":12,"attribute":{},"fvalue":""},{"ftype":"t","flength":10,"fchar":".","fname":"PCODE","r":15,"c":12,"attribute":{},"fvalue":""},{"ftype":"t","flength":10,"fchar":".","fname":"TOKEN","r":16,"c":12,"attribute":{},"fvalue":""}],"date":"2020-07-08T05:17:35.174Z"} +{"version":1,"frame":981,"index":"a","owner":9,"cost":0,"content":"ICARLGwSfGwUfCwTLBc3a38jMzdrIzUgICAgICACMDAwMTAwMDEwMSAgEX9rEn9qFC98E38XNWhvIDw9JCw1ByAgICAgICAgICAgICAgICAgIBEvLhIvKhQsLxMvF3VwenBxdXpwNSAgICAgICAgICAgICAgICAgICAgVmlkZW90ZXggICAgICAgICAgICAgICAgICAgIA1SZWdpc3RlciAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICACKiogB3RvIGNsZWFyIGlucHV0ICAgICAgICAgICAgICAgICAgICAgAiowMAd0byBzdGFydCBhZ2FpbiAgICAgICBVc2UCXwd0byBFbnRlciAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICABRW1haWwgICAgOgcuLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uICAgAVVzZXIgSUQgIDoHLi4uLi4uLi4uLi4uLi4uICAgICAgICAgICAgIAFQYXNzd29yZCA6By4uLi4uLi4uLi4uLi4uLiAgICAgICAgICAgICABRnVsbCBOYW1lOgcuLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uICAgAUNpdHkgICAgIDoHLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLiAgIAFDb3VudHJ5ICA6By4uLiAgICAgICAgICAgICAgICAgICAgICAgICABUG9zdCBDb2RlOgcuLi4uLi4uLi4uICAgICAgICAgICAgICAgICAgAVRva2VuICAgIDoHLi4uLi4uICAgICAgICAgICAgICAgICAgICAgIBoXODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFJlZ2lzdGVyaW5nIGFuZCB1c2luZyB0aGlzIHN5c3RlbSwgeW91ICBhZ3JlZSB0byBhYmlkZSBieSB0aGUgc3lzdGVtIHJ1bGVzLiBTZWUCKjk4OF8gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA==","isPublic":1,"isAccessible":1,"type":"r","key":[980,"register",null,null,null,null,null,null,null,null],"input_fields":[{"type":"t","length":25,"char":".","name":"EMAIL","y":9,"x":12,"attribute":{},"value":""},{"type":"t","length":15,"char":".","name":"UID","y":10,"x":12,"attribute":{},"value":""},{"type":"p","length":15,"char":".","name":"PASS","y":11,"x":12,"attribute":{},"value":""},{"type":"t","length":25,"char":".","name":"FULLNAME","y":12,"x":12,"attribute":{},"value":""},{"type":"t","length":25,"char":".","name":"CITY","y":13,"x":12,"attribute":{},"value":""},{"type":"t","length":3,"char":".","name":"COUNTRY","y":14,"x":12,"attribute":{},"value":""},{"type":"t","length":10,"char":".","name":"PCODE","y":15,"x":12,"attribute":{},"value":""},{"type":"t","length":10,"char":".","name":"TOKEN","y":16,"x":12,"attribute":{},"value":""}],"date":"2020-07-08T05:17:35.174Z"} diff --git a/text/98a.vtx b/text/98a.vtx index e785998..1ed5689 100644 --- a/text/98a.vtx +++ b/text/98a.vtx @@ -1 +1 @@ -{"version":1,"frame":98,"index":"a","owner":9,"cost":0,"content":"ICARLGwSfGwUfCwTLBc3a38jMzdrIzUgICAgICACMDAwMTAwMDEwMSAgEX9rEn9qFC98E38XNWhvIDw9JCw1ByAgICAgICAgICAgICAgICAgIBEvLhIvKhQsLxMvF3VwenBxdXpwNSAgICAgICAgICAgICAgICAgICAgVmlkZW90ZXggICAgICAgICAgICAgICAgICAgICANU2lnbiBJbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgAVVzZXIHLi4uLi4uLi4uLi4uLi4uICAgICAgICAgICAgICAgICAgIAFQYXNzBy4uLi4uLi4uLi4uLi4uLiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBFbnRlcgJORVcHdG8gcmVnaXN0ZXIgbmV3IGFjY291bnQgICAgICAgUHJlc3MCKioHdG8gY2xlYXIgeW91ciBpbnB1dCAgICAgICAgICAgIFByZXNzAiowMAd0byBzdGFydCBhZ2FpbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA==","isPublic":1,"isAccessible":1,"type":"l","key":[null,"login",null,null,null,null,null,null,null,null],"frame_fields": [{"ftype":"t","flength":15,"fchar":".","fname":"USER","r":10,"c":15,"attribute":{},"fvalue":""},{"ftype":"p","flength":15,"fchar":".","fname":"PASS","r":11,"c":15,"attribute":{},"fvalue":""}],"date":"2020-07-08T05:17:35.174Z"} +{"version":1,"frame":98,"index":"a","owner":9,"cost":0,"content":"ICARLGwSfGwUfCwTLBc3a38jMzdrIzUgICAgICACMDAwMTAwMDEwMSAgEX9rEn9qFC98E38XNWhvIDw9JCw1ByAgICAgICAgICAgICAgICAgIBEvLhIvKhQsLxMvF3VwenBxdXpwNSAgICAgICAgICAgICAgICAgICAgVmlkZW90ZXggICAgICAgICAgICAgICAgICAgICANU2lnbiBJbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgAVVzZXIHLi4uLi4uLi4uLi4uLi4uICAgICAgICAgICAgICAgICAgIAFQYXNzBy4uLi4uLi4uLi4uLi4uLiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBFbnRlcgJORVcHdG8gcmVnaXN0ZXIgbmV3IGFjY291bnQgICAgICAgUHJlc3MCKioHdG8gY2xlYXIgeW91ciBpbnB1dCAgICAgICAgICAgIFByZXNzAiowMAd0byBzdGFydCBhZ2FpbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA==","isPublic":1,"isAccessible":1,"type":"l","key":[null,"login",null,null,null,null,null,null,null,null],"input_fields": [{"type":"t","length":15,"char":".","name":"USER","y":10,"x":15,"attribute":{},"value":""},{"type":"p","length":15,"char":".","name":"PASS","y":11,"x":15,"attribute":{},"value":""}],"date":"2020-07-08T05:17:35.174Z"}