diff --git a/load/ansiframe.js b/load/ansiframe.js new file mode 100644 index 0000000..c0f488c --- /dev/null +++ b/load/ansiframe.js @@ -0,0 +1,407 @@ +var FRAME_ANSI=(1<<1); + +// Our frame object +function ANSIFrame() { + this.version=1; // The version of this frame - in case we update functionality and we need to be + // backwards compatible + this.frame=null; // Frame Number [0-9]+ + this.index=null; // Frame Index [a-z] + this.owner=0; // The Service Provider owning the frame. + this.cost=0; // The cost to view the frame @TODO + this.content=''; // The frame content, base64 encoded + + // 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] + + // Render the frame to the user + this.render=function(withHeader) { + owner = base64_decode(this.owner); + + header = '\n\r'; + + 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+']'); + + cost = (this.isAccessible ? this.cost+FRAME_COSTUNIT : ' -') + + header = '\1n'+this.pageownerlogo+' '.repeat(FRAME_HEADER-console.strlen(this.pageownerlogo))+'\1n '+ + (this.isAccessible ? '\1W' : '\1R')+'\1H'+this.page+' '.repeat(FRAME_PAGENUM-this.page.length)+' '+ + '\1G\1H'+' '.repeat(FRAME_COST-cost.toString().length+1)+cost+'\1n'+ + (console.screen_columns > 80 ? '\n\r' : ''); + } + + if ((this.type == FRAME_TYPE_LOGIN) || (this.type == FRAME_TYPE_RESPONSE)) { + return header+this.parse(base64_decode(this.content)); + } else { + return header+base64_decode(this.content); + } + }; + + 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; + } + + // Load a frame from disk (.tex file) + this.load = function(filename) { + log(LOG_DEBUG,'Loading frame from: '+filename); + + f = new File(system.mods_dir+'ansitex/text/'+filename+'.tex'); + if (! f.exists || ! f.open('r')) { + return null; + } + + try { + var load = JSON.parse(f.read()); + + for (property in load) { + this[property] = load[property]; + } + + } catch (error) { + log(LOG_ERROR,'Frame error: '+error); + return null; + } + + log(LOG_DEBUG,'Loaded 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 + */ + this.parse = function(text) { + var c = 1; // column + var r = 2; // row (row 1 is the header) + var output = ''; + this.frame_fields = []; + + // Default Attributes + f = 39; + b = 49; + i = 0; + + for(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; + + // SOS + 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 \ + 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 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>FRAME_WIDTH) { + c = 1; + r++; + } + + /* + // @todo - If we are longer than FRAME_LENGTH, move the output into the next frame. + if (r>FRAME_LENGTH) { + break; + } + */ + } + + return output; + }; + + 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'); + } + + 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); + } + } + }); + + 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,'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(pageOwner(this.frame).logo); + } + }) +} \ No newline at end of file diff --git a/load/defs.js b/load/defs.js index ea6afaa..2a22d5d 100644 --- a/load/defs.js +++ b/load/defs.js @@ -53,410 +53,4 @@ var INKEY_TIMEOUT =10000; var INACTIVE_NOLOGIN =1000; var INACTIVE_LOGIN =5*60000; -// Our frame object -function TexFrame() { - this.version=1; // The version of this frame - in case we update functionality and we need to be - // backwards compatible - this.frame=null; // Frame Number [0-9]+ - this.index=null; // Frame Index [a-z] - this.owner=0; // The Service Provider owning the frame. - this.cost=0; // The cost to view the frame @TODO - this.content=''; // The frame content, base64 encoded - - // 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] - - // Render the frame to the user - this.render=function(withHeader) { - owner = base64_decode(this.owner); - - header = '\n\r'; - - 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+']'); - - cost = (this.isAccessible ? this.cost+FRAME_COSTUNIT : ' -') - - header = '\1n'+this.pageownerlogo+' '.repeat(FRAME_HEADER-console.strlen(this.pageownerlogo))+'\1n '+ - (this.isAccessible ? '\1W' : '\1R')+'\1H'+this.page+' '.repeat(FRAME_PAGENUM-this.page.length)+' '+ - '\1G\1H'+' '.repeat(FRAME_COST-cost.toString().length+1)+cost+'\1n'+ - (console.screen_columns > 80 ? '\n\r' : ''); - } - - if ((this.type == FRAME_TYPE_LOGIN) || (this.type == FRAME_TYPE_RESPONSE)) { - return header+this.parse(base64_decode(this.content)); - } else { - return header+base64_decode(this.content); - } - }; - - 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; - } - - // Load a frame from disk (.tex file) - this.load = function(filename) { - log(LOG_DEBUG,'Loading frame from: '+filename); - - f = new File(system.mods_dir+'ansitex/text/'+filename+'.tex'); - if (! f.exists || ! f.open('r')) { - return null; - } - - try { - var load = JSON.parse(f.read()); - - for (property in load) { - this[property] = load[property]; - } - - } catch (error) { - log(LOG_ERROR,'Frame error: '+error); - return null; - } - - log(LOG_DEBUG,'Loaded 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 - */ - this.parse = function(text) { - var c = 1; // column - var r = 2; // row (row 1 is the header) - var output = ''; - this.frame_fields = []; - - // Default Attributes - f = 39; - b = 49; - i = 0; - - for(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; - - // SOS - 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 \ - 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 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>FRAME_WIDTH) { - c = 1; - r++; - } - - /* - // @todo - If we are longer than FRAME_LENGTH, move the output into the next frame. - if (r>FRAME_LENGTH) { - break; - } - */ - } - - return output; - }; - - 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'); - } - - 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); - } - } - }); - - 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,'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(pageOwner(this.frame).logo); - } - }) -} - this; \ No newline at end of file diff --git a/load/edit.js b/load/edit.js index f0f08ea..c56a68d 100644 --- a/load/edit.js +++ b/load/edit.js @@ -11,6 +11,7 @@ var CONTROL_EDIT = '1'; function edit(fo) { log(LOG_DEBUG,'+ Control EDIT loaded'); var complete = false; + var inProperty = false; Object.defineProperty(this,'getName', { get: function() { @@ -75,15 +76,30 @@ function edit(fo) { this.handle=function(read) { if (! js.terminated && ascii(read) != 27) { - editor.getcmd(read); - editor.cycle(); - frame.cycle(); + if (inProperty) { + log(LOG_DEBUG, ' + FrameEdit properties(): read'); + propFrame.putmsg(read); + propFrame.cycle(); + + } else { + editor.getcmd(read); + editor.cycle(); + frame.cycle(); + } if (! complete) return ''; // Ignore esc } else if (ascii(read) == 27) { + if (inProperty) { + log(LOG_DEBUG, ' + FrameEdit properties(): ESC'); + propFrame.close(); + frame.top(); + frame.cycle(); + inProperty = false; + } + return ''; } @@ -97,7 +113,14 @@ function edit(fo) { } function properties() { + inProperty = true; log(LOG_DEBUG, '+ FrameEdit properties()'); + frame.bottom(); + propFrame = new Frame(1,2,40,14,BG_BLUE|WHITE,frame); + propFrame.gotoxy(1,1); + propFrame.putmsg('Properties!'); + propFrame.open(); + propFrame.cycle(); } function save() { diff --git a/main.js b/main.js index 2065ee4..f88dc8e 100644 --- a/main.js +++ b/main.js @@ -12,13 +12,15 @@ load('ansitex/load/funcs.js'); // Ansitex specific includes require('ansitex/load/defs.js','ACTION_EXIT'); +require('ansitex/load/ansiframe.js','FRAME_ANSI'); +require('ansitex/load/viewdataframe.js','FRAME_VIEWDATA'); // @TODO LIST // login screen - backspace not working // login screen ** to clear the current field not working // Returning from chat should refresh the frame // Supress displays of telegrams - +log(LOG_DEBUG,'Socket:'+JSON.stringify(client.socket.local_port)); while(bbs.online) { var mode = false; // Initial mode @@ -740,7 +742,7 @@ while(bbs.online) { // If we are editing a specific frame, attempt to load it if (fe) { current = fo; - fo = new TexFrame(); + fo = (client.socket.local_port !== 516) ? new ANSIFrame() : new VIEWDATAFrame(); fo.load(pageStr(fe)); // If the frame doesnt exist, check that the parent frame exists in case we are creating a new one @@ -749,7 +751,7 @@ while(bbs.online) { // We can always create an 'a' frame if (fe.index !== 'a') { - fo = new TexFrame(); + fo = (client.socket.local_port !== 516) ? new ANSIFrame() : new VIEWDATAFrame(); fo.load(pageStr({frame: fe.frame, index: String.fromCharCode(fe.index.charCodeAt(0)-1)})); log(LOG_DEBUG,'- ACTION_EDIT: check index: '+JSON.stringify(fo)+' ('+String.fromCharCode(fe.index.charCodeAt(0)-1)+')'); @@ -763,7 +765,7 @@ while(bbs.online) { } // New frame - fo = new TexFrame(); + fo = (client.socket.local_port !== 516) ? new ANSIFrame() : new VIEWDATAFrame(); fo.frame = fe.frame; fo.index = fe.index; fo.cost = 0; @@ -784,7 +786,7 @@ while(bbs.online) { if (next_page !== null) { current = fo; - fo = new TexFrame(); + fo = (client.socket.local_port !== 516) ? new ANSIFrame() : new VIEWDATAFrame(); fo.load(pageStr(next_page)); if (fo.page == null) {