Initial commit (a clone of the jsmachines project as of v1.15.3)

This commit is contained in:
jeffpar 2014-09-27 14:52:57 -07:00
commit a5e3e6a59d
714 changed files with 130602 additions and 0 deletions

View file

@ -0,0 +1,28 @@
Shared Sources
===
This folder contains a mix of shared code, with some files used only by Node (server) modules,
some used only by Browser (client) modules, and others used by both.
At the moment, only a few files are completely agnostic; eg: [strlib.js](strlib.js) and [usrlib.js](usrlib.js).
One give-away is that neither contain references to any globals (although references to each other
would be fine).
[netlib.js](netlib.js) is appropriate only for Node modules, because it contains code that relies on Node's
global *Buffer* object, as indicated by:
/* global Buffer: false */
And [weblib.js](weblib.js) is appropriate only for client modules, because it contains code that relies on the
brower's global *window* object, as indicated by:
/* global window: true */
We declare *window* modifiable (true) so that [defines.js](defines.js) can set *global.window* to *false*
when running within Node, allowing any other code to test the existence of *window* with a simple:
if (window) {...}
instead of:
if (typeof window !== 'undefined') {...}

View file

@ -0,0 +1,906 @@
/**
* @fileoverview The Component class used by C1Pjs and PCjs.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* Created 2012-May-14
*
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of the JavaScript Machines Project (aka JSMachines) at <http://jsmachines.net/>
* and <http://pcjs.org/>.
*
* JSMachines is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with JSMachines.
* If not, see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see Computer.sCopyright).
*
* Some JSMachines files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of the
* JSMachines Project for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
/*
* All the C1Pjs and PCjs components now use JSDoc types, primarily so that Google's Closure Compiler
* will compile everything with ZERO warnings. For more information about the JSDoc types supported by
* the Closure Compiler:
*
* https://developers.google.com/closure/compiler/docs/js-for-compiler#types
*
* I also attempted to use JSLint, but it's excessively strict for my taste, so this is the only file
* I tried massaging for JSLint's sake. I gave up when it complained about my use of "while (true)";
* replacing "true" with an assignment expression didn't make it any happier.
*
* I wasn't thrilled about replacing all "++" and "--" operators with "+= 1" and "-= 1", nor about using
* "(s || '')" instead of "(s? s : '')", because while the former may seem simpler, it is NOT more portable.
* It's not that I'm trying to write "portable JavaScript", but some of this code was ported from C code
* I'd written about 14 years earlier, and portability is good, so I see no reason to rewrite code to make
* it less portable.
*
* UPDATE: I've since switched to JSHint, which seems to have more reasonable defaults.
*/
"use strict";
/* global window: true, DEBUG: true */
if (typeof module !== 'undefined') {
require("./defines");
var usr = require("./usrlib");
var web = require("./weblib");
}
/**
* Component(type, parms, constructor)
*
* @constructor
* @param {string} type
* @param {Object} [parms]
* @param {Object} [constructor]
*
* A Component object requires:
*
* type: a user-defined type name (eg, "CPU")
*
* and accepts any or all of the following (parms) properties:
*
* id: component ID (default is "")
* name: component name (default is ""; if blank, toString() will use the type name only)
* comment: component comment string (default is undefined)
*
* Subclasses that use Component.subclass() to extend Component will likely have additional (parms) properties.
*/
function Component(type, parms, constructor)
{
this.type = type;
if (!parms) {
parms = {'id': "", 'name': ""};
}
this.id = parms['id'];
this.name = parms['name'];
this.comment = parms['comment'];
this.parms = parms;
if (this.id === undefined) this.id = "";
var i = this.id.indexOf('.');
if (i > 0) {
this.idMachine = this.id.substr(0, i);
this.idComponent = this.id.substr(i + 1);
} else {
this.idComponent = this.id;
}
/*
* Recording the constructor is really just a debugging aid, because many of our constructors
* have class constants, but they're hard to find when the constructors are buried among all the
* other globals.
*/
this[type] = constructor;
/*
* TODO: Decide how to reintegrate this code into the components that still want it....
*
if (this.initStep) this.initStep(parms);
*/
this.fnReady = null;
this.fReady = false;
this.fBusy = this.fBusyCancel = false;
this.fPowered = false;
this.clearError();
this.bindings = {};
this.dbg = null; // by default, no connection to a Debugger
this.getMachineNum(); // once again, make the function appear used
Component.add(this);
}
/**
* Component.parmsURL
*
* Initialized to the set of URL parameters, if any, for the current web page.
*
* @type {Object|null}
*/
Component.parmsURL = web.getURLParameters();
/**
* Component.inherit(p)
*
* Returns a newly created object that inherits properties from the prototype object p.
* It uses the ECMAScript 5 function Object.create() if it is defined, and otherwise falls back to an older technique.
*
* See: Flanagan, David (2011-04-18). JavaScript: The Definitive Guide: The Definitive Guide (Kindle Locations 9854-9903). OReilly Media - A. Kindle Edition (Example 6-1)
*
* @param {Object} p
*/
Component.inherit = function(p)
{
if (window) { // an alternative to "if (typeof window === 'undefined')" if require("defines") has been invoked
if (!p) throw new TypeError(); // TODO: Why does this barf under Node?
if (Object.create) {
return Object.create(p);
}
var t = typeof p;
if (t !== "object" && t !== "function") throw new TypeError();
}
/**
* @constructor
*/
function F() {}
F.prototype = p;
return new F();
};
/**
* Component.extend(o, p)
*
* Copies the enumerable properties of p to o and returns o.
* If o and p have a property by the same name, o's property is overwritten.
*
* See: Flanagan, David (2011-04-18). JavaScript: The Definitive Guide: The Definitive Guide (Kindle Locations 9854-9903). OReilly Media - A. Kindle Edition (Example 6-2)
*
* @param {Object} o
* @param {Object} p
*/
Component.extend = function(o, p)
{
for (var prop in p) {
o[prop] = p[prop];
}
return o;
};
/**
* Component.subclass(superclass, subclass, methods, statics)
*
* TODO: Determine why every subclass created by this function ends up with a name prefix of "Component.subclass"
* in Chrome's call stack, rather than the (more logical) name of the subclass constructor. Is there a different
* design pattern I should be using that creates subclasses more to Chrome's liking?
*
* See: Flanagan, David (2011-04-18). JavaScript: The Definitive Guide: The Definitive Guide (Kindle Locations 9854-9903). OReilly Media - A. Kindle Edition (Example 9-11)
*
* @param {Object} superclass is the constructor of the superclass
* @param {Object} subclass is the constructor for the new subclass
* @param {Object} [methods] contains all instance methods
* @param {Object} [statics] contains all class properties and methods
*/
Component.subclass = function(superclass, subclass, methods, statics)
{
subclass.prototype = Component.inherit(superclass.prototype);
subclass.prototype.constructor = subclass;
if (methods) {
Component.extend(subclass.prototype, methods);
}
if (statics) {
Component.extend(subclass, statics);
}
return subclass;
};
/*
* Every component created on the current page is recorded in this array (see Component.add()).
*
* This enables any component to locate another component by ID (see Component.getComponentByID())
* or by type (see Component.getComponentByType()).
*/
Component.all = [];
/**
* Component.add(component)
*
* @param {Component} component
*/
Component.add = function(component)
{
/*
* This just generates a lot of useless noise, handy in the early days, not so much these days...
*
* Component.log("Component.add(" + component.type + "," + component.id + ")");
*/
Component.all[Component.all.length] = component;
};
/**
* Component.log(s, type)
*
* For diagnostic output only.
*
* @param {string} [s] is the message text
* @param {string} [type] is the message type
*/
Component.log = function(s, type)
{
if (DEBUG) {
var msElapsed, sMsg = (type? (type + ": ") : "") + (s || "");
if (Component.msStart === undefined) {
Component.msStart = usr.getTime();
}
msElapsed = usr.getTime() - Component.msStart;
console.log(sMsg? (msElapsed + "ms: " + sMsg.replace(/\n/g, " ")) : "");
}
};
/**
* Component.assert(f, s)
*
* Used to verify conditions that must be true (for DEBUG builds only; compiled builds should automatically have all
* references to Component.assert() removed).
*
* @param {boolean} f is the expression we are asserting to be true
* @param {string} [s] is description of the assertion on failure
*/
Component.assert = function(f, s)
{
if (DEBUG) {
if (!f) {
if (!s) {
/*
* TODO: An accompanying source file/line number/function call would be nice, if there was a browser-independent way....
*/
s = "assertion failure";
}
Component.log(s);
throw new Error(s);
}
}
};
/**
* Component.println(s, type, id)
*
* For non-diagnostic output, which some components override in order to make their output visible in their own way.
*
* @param {string} [s] is the message text
* @param {string} [type] is the message type
* @param {string} [id] is the caller's ID, if any
*/
Component.println = function(s, type, id)
{
if (DEBUG) {
Component.log((id? (id + ": ") : "") + (s? ("\"" + s + "\"") : ""), type);
}
};
/**
* Component.notice(s, fPrintOnly, id)
*
* notice() is like println() but implies a need for user notification, which means calling log() isn't good enough,
* so we alert() as well; however, if Component.println() is overridden, Component.notice will be replaced with the same
* override, on the assumption that the override is taking care of all user notifications.
*
* @param {string} s is the message text
* @param {boolean} [fPrintOnly]
* @param {string} [id] is the caller's ID, if any
*/
Component.notice = function(s, fPrintOnly, id)
{
if (DEBUG) {
Component.println(s, "notice", id);
}
if (!fPrintOnly) web.alertUser(s);
};
/**
* Component.warning(s)
*
* @param {string} s describes the warning
*/
Component.warning = function(s)
{
if (DEBUG) {
Component.println(s, "warning");
}
web.alertUser(s);
};
/**
* Component.error(s)
*
* @param {string} s describes the error; an alert() is displayed as well
*/
Component.error = function(s)
{
if (DEBUG) {
Component.println(s, "error");
}
web.alertUser(s);
};
/**
* Component.getComponents(idRelated)
*
* We could store components as properties of an 'all' object, using the component's ID,
* and change this linear lookup into a property lookup, but some components may have no ID.
*
* @param {string} [idRelated] of related component
* @return {Array} of components
*/
Component.getComponents = function(idRelated)
{
var i;
var aComponents = [];
/*
* getComponentByID(id, idRelated)
*
* If idRelated is provided, we check it for a machine prefix, and use any
* existing prefix to constrain matches to IDs with the same prefix, in order to
* avoid matching components belonging to other machines.
*/
if (idRelated) {
if ((i = idRelated.indexOf('.')) > 0)
idRelated = idRelated.substr(0, i + 1);
else
idRelated = undefined;
}
for (i = 0; i < Component.all.length; i++) {
var component = Component.all[i];
if (!idRelated || !component.id.indexOf(idRelated)) {
aComponents.push(component);
}
}
return aComponents;
};
/**
* Component.getComponentByID(id, idRelated)
*
* We could store components as properties of an 'all' object, using the component's ID,
* and change this linear lookup into a property lookup, but some components may have no ID.
*
* @param {string} id of the desired component
* @param {string} [idRelated] of related component
* @return {Component|null}
*/
Component.getComponentByID = function(id, idRelated)
{
if (id !== undefined) {
var i;
/*
* If idRelated is provided, we check it for a machine prefix, and use any
* existing prefix to constrain matches to IDs with the same prefix, in order to
* avoid matching components belonging to other machines.
*/
if (idRelated && (i = idRelated.indexOf('.')) > 0) {
id = idRelated.substr(0, i + 1) + id;
}
for (i = 0; i < Component.all.length; i++) {
if (Component.all[i].id === id) {
return Component.all[i];
}
}
Component.log('Component.getComponentByID("' + id + '"): no component found', "warning");
}
return null;
};
/**
* Component.getComponentByType(sType, idRelated, componentPrev)
*
* @param {string} sType of the desired component
* @param {string} [idRelated] of related component
* @param {Component} [componentPrev] of previously returned component, if any
* @return {Component|null}
*/
Component.getComponentByType = function(sType, idRelated, componentPrev)
{
if (sType !== undefined) {
var i;
/*
* If idRelated is provided, we check it for a machine prefix, and use any
* existing prefix to constrain matches to IDs with the same prefix, in order to
* avoid matching components belonging to other machines.
*/
if (idRelated) {
if ((i = idRelated.indexOf('.')) > 0) {
idRelated = idRelated.substr(0, i + 1);
} else {
idRelated = undefined;
}
}
for (i = 0; i < Component.all.length; i++) {
if (componentPrev) {
if (componentPrev == Component.all[i]) componentPrev = null;
continue;
}
if (sType == Component.all[i].type && (!idRelated || !Component.all[i].id.indexOf(idRelated))) {
return Component.all[i];
}
}
Component.log('Component.getComponentByType("' + sType + '"): no component found', "warning");
}
return null;
};
/**
* Component.getComponentParms(element)
*
* @param {Object} element from the DOM
*/
Component.getComponentParms = function(element)
{
var parms = null,
sParms = element.getAttribute("data-value");
if (sParms) {
try {
parms = eval("({" + sParms + "})"); // jshint ignore:line
/*
* We can no longer invoke removeAttribute() because some components (eg, Panel) need
* to run their initXXX() code more than once, to avoid initialization-order dependencies.
*
* if (!DEBUG) {
* element.removeAttribute("data-value");
* }
*/
} catch (e) {
Component.error(e.message + " (" + sParms + ")");
}
}
return parms;
};
/**
* Component.bindExternalControl(component, sControl, sBinding, sType)
*
* @param {Component} component
* @param {string} sControl
* @param {string} sBinding
* @param {string} [sType] is the external component type
*/
Component.bindExternalControl = function(component, sControl, sBinding, sType)
{
if (sControl) {
if (sType === undefined) sType = "Panel";
var target = Component.getComponentByType(sType, component.id);
if (target) {
var eBinding = target.bindings[sControl];
if (eBinding) {
component.setBinding(null, null, sBinding, eBinding);
}
}
}
};
/**
* Component.bindComponentControls(component, element, sAppClass)
*
* @param {Component} component
* @param {Object} element from the DOM
* @param {string} sAppClass
*/
Component.bindComponentControls = function(component, element, sAppClass)
{
var iControl, aeControls;
aeControls = Component.getElementsByClass(element.parentNode, sAppClass + "-control");
for (iControl = 0; iControl < aeControls.length; iControl++) {
var iNode, aeChildNodes;
aeChildNodes = aeControls[iControl].childNodes;
for (iNode = 0; iNode < aeChildNodes.length; iNode++) {
var control = aeChildNodes[iNode];
if (control.nodeType !== window.document.ELEMENT_NODE) {
continue;
}
var sClass = control.getAttribute("class");
if (!sClass) continue;
var iClass, aClasses;
aClasses = sClass.split(" ");
for (iClass = 0; iClass < aClasses.length; iClass++) {
var parms;
sClass = aClasses[iClass];
switch (sClass) {
case sAppClass + "-input":
case sAppClass + "-output":
parms = Component.getComponentParms(control);
if (parms && parms['binding']) {
component.setBinding(sClass, parms['type'], parms['binding'], control);
} else {
Component.log('Component.bindComponentControls("' + component.toString() + '"): no binding info for ' + parms['type'], "warning");
}
aClasses = [];
break;
default:
// Component.log("Component.bindComponentControls(" + component.toString() + "): unrecognized control class \"" + sClass + "\"", "warning");
break;
}
}
}
}
};
/**
* Component.getElementsByClass(element, sClass, sObjClass)
*
* This is a cross-browser helper function, since not all browser's support getElementsByClassName()
*
* TODO: This should probably be moved into weblib.js at some point, along with the control binding functions above,
* to keep all the browser-related code together.
*
* @param {Object} element from the DOM
* @param {string} sClass
* @param {string} [sObjClass]
* @return {Array|NodeList}
*/
Component.getElementsByClass = function(element, sClass, sObjClass)
{
if (sObjClass) sClass += '-' + sObjClass + "-object";
/*
* Use the browser's built-in getElementsByClassName() if it appears to be available
* (for example, it's not available in IE8, but it should be available in IE9 and up)
*/
if (element.getElementsByClassName) {
return element.getElementsByClassName(sClass);
}
var i, j, ae = [];
var aeAll = element.getElementsByTagName("*");
var re = new RegExp('(^| )' + sClass + '( |$)');
for (i = 0, j = aeAll.length; i < j; i++) {
if (re.test(aeAll[i].className)) {
ae.push(aeAll[i]);
}
}
if (!ae.length) {
Component.log('no elements of class "' + sClass + '" found');
}
return ae;
};
Component.prototype = {
constructor: Component,
/**
* toString()
*
* @this {Component}
* @return {string}
*/
toString: function() {
return (this.name? this.name : (this.id || this.type));
},
/**
* getMachineNum()
*
* @this {Component}
* @return {number} unique machine number
*/
getMachineNum: function() {
var nMachine = 1;
if (this.idMachine) {
var aDigits = this.idMachine.match(/\d+/);
if (aDigits !== null)
nMachine = parseInt(aDigits[0], 10);
}
return nMachine;
},
/**
* setBinding(sHTMLClass, sHTMLType, sBinding, control)
*
* Component's setBinding() method is intended to be overridden by subclasses. The only
* exception is the Panel component, which passes two special bindings ("clear" and "print")
* back to us if no one else accepted them, so that we can redirect all println() requests
* to those controls.
*
* @this {Component}
* @param {string|null} sHTMLClass is the class of the HTML control (eg, "input", "output")
* @param {string|null} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea", "canvas")
* @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "reset")
* @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement)
* @return {boolean} true if binding was successful, false if unrecognized binding request
*/
setBinding: function(sHTMLClass, sHTMLType, sBinding, control) {
switch (sBinding) {
case "clear":
if (!this.bindings[sBinding]) {
this.bindings[sBinding] = control;
control.onclick = (function(component) {
return function() {
if (component.bindings['print']) {
component.bindings['print'].value = "";
}
};
}(this));
}
return true;
case "print":
if (!this.bindings[sBinding]) {
this.bindings[sBinding] = control;
control.value = ""; // this was added for Firefox (Safari automatically clears the <textarea> on a page reload, but Firefox does not)
Component.println = (function(control) {
return function printControl(s, type) {
s = (type !== undefined? (type + ": ") : "") + (s || "");
if (!DEBUG) { // in non-DEBUG builds, prevent the <textarea> from getting too large, otherwise printing becomes slower and slower
if (control.value.length > 8192) {
control.value = control.value.substr(control.value.length - 4096);
}
}
control.value += s + "\n";
control.scrollTop = control.scrollHeight;
if (DEBUG) console.log(s);
};
}(control));
/*
* Override Component.notice() with a replacement function that eliminates the web.alertUser() call
*/
Component.notice = function(s, fPrintOnly, id) {
Component.println(s, "notice", id);
};
/*
* HACK: Save this particular HTML element so that the Debugger can access it, too
*/
Component.controlPrint = control;
}
return true;
default:
/*
* Now that we're giving the Panel component multiple shots at binding its controls,
* to relax initialization dependencies, we need to chill when unrecognized requests come in.
*
if (sHTMLClass == "input") {
control.onclick = function() {
Component.println("unsupported " + sHTMLType + ": " + sBinding);
};
}
this.log("setBinding(\"" + sHTMLClass + "\",\"" + sHTMLType + "\",\"" + sBinding + "\"): unrecognized binding");
*/
break;
}
return false;
},
/**
* log(s, type)
*
* For diagnostic output only.
*
* WARNING: Even though this function's body is completely wrapped in DEBUG, that won't prevent the Closure Compiler
* from including it, so all calls must still be prefixed with "if (DEBUG) ....". For this reason, the class method,
* Component.log(), is preferred, because the compiler IS smart enough to remove those calls.
*
* @this {Component}
* @param {string} [s] is the message text
* @param {string} [type] is the message type
*/
log: function(s, type) {
if (DEBUG) Component.log(s, type || this.id || this.type);
},
/**
* println(s, type)
*
* For non-diagnostic output, which some components override in order to make their output visible in their own way.
*
* @this {Component}
* @param {string} [s] is the message text
* @param {string} [type] is the message type
* @param {string} [id] is the caller's ID, if any
*/
println: function(s, type, id) {
Component.println(s, type, id || this.id);
},
/**
* status(s)
*
* status() is like println() but it also includes information about the component (ie, the component ID),
* which is why there is no corresponding Component.status() function.
*
* @param {string} s is the message text
*/
status: function(s) {
this.println(this.idComponent + ": " + s);
},
/**
* notice(s, fPrintOnly)
*
* notice() is like println() but implies a need for user notification, which means calling log() isn't good enough, so we alert() as well;
* however, if Component.println() is overridden, Component.notice will be replaced with the same override, on the assumption that the override
* is taking care of user notification.
*
* @this {Component}
* @param {string} s is the message text
* @param {boolean} [fPrintOnly]
* @param {string} [id] is the caller's ID, if any
*/
notice: function(s, fPrintOnly, id) {
Component.notice(s, fPrintOnly, id || this.id);
},
/**
* warning(s)
*
* @this {Component}
* @param {string} s describes the warning
*/
warning: function(s) {
Component.warning(s);
},
/**
* error(s)
*
* @this {Component}
* @param {string} s describes the error; an alert() is displayed as well
*/
error: function(s) {
Component.error(s);
},
/**
* setError(s)
*
* Set a fatal error condition
*
* @this {Component}
* @param {string} s describes a fatal error condition
*/
setError: function(s) {
this.fError = true;
this.notice("Fatal error: " + s);
},
/**
* clearError()
*
* Clear any fatal error condition
*
* @this {Component}
*/
clearError: function() {
this.fError = false;
},
/**
* isError()
*
* Report any fatal error condition
*
* @this {Component}
* @return {boolean} true if a fatal error condition exists, false if not
*/
isError: function() {
if (this.fError) {
this.println(this.toString() + " error");
return true;
}
return false;
},
/**
* isReady(fnReady)
*
* Return the "ready" state of the component; if the component is not ready, it will queue the optional
* notification function, otherwise it will immediately call the notification function, if any, without queuing it.
*
* NOTE: Since only the Computer component actually cares about the "readiness" of other components, the so-called
* "queue" of notification functions supports exactly one function. This keeps things nice and simple.
*
* @this {Component}
* @param {function()} [fnReady]
* @return {boolean} true if the component is in a "ready" state, false if not
*/
isReady: function(fnReady) {
if (fnReady) {
if (this.fReady) {
fnReady();
} else {
if (DEBUG) this.log("NOT ready");
this.fnReady = fnReady;
}
}
return this.fReady;
},
/**
* setReady(fReady)
*
* Set the "ready" state of the component to true, and call any queued notification functions.
*
* @this {Component}
* @param {boolean} [fReady] is assumed to indicate "ready" unless EXPLICITLY set to false
*/
setReady: function(fReady) {
if (!this.fError) {
this.fReady = (fReady !== false);
if (this.fReady) {
if (DEBUG || this.name) this.log("ready");
var fnReady = this.fnReady;
this.fnReady = null;
if (fnReady) fnReady();
}
}
},
/**
* isBusy(fCancel)
*
* Return the "busy" state of the component
*
* @this {Component}
* @param {boolean} [fCancel] is set to true to cancel a "busy" state
* @return {boolean} true if "busy", false if not
*/
isBusy: function(fCancel) {
if (this.fBusy) {
if (fCancel) {
this.fBusyCancel = true;
} else if (fCancel === undefined) {
this.println(this.toString() + " busy");
}
}
return this.fBusy;
},
/**
* setBusy(fBusy)
*
* Update the current busy state; if an fCancel request is pending, it will be honored now.
*
* @this {Component}
* @param {boolean} fBusy
* @return {boolean}
*/
setBusy: function(fBusy) {
if (this.fBusyCancel) {
if (this.fBusy) {
this.fBusy = false;
}
this.fBusyCancel = false;
return false;
}
if (this.fError) {
this.println(this.toString() + " error");
return false;
}
this.fBusy = fBusy;
return this.fBusy;
},
/**
* powerUp(fSave)
*
* @this {Component}
* @param {Object|null} data
* @param {boolean} [fRepower] is true if this is "repower" notification
* @return {boolean} true if successful, false if failure
*/
powerUp: function(data, fRepower) {
this.fPowered = true;
return true;
},
/**
* powerDown(fSave, fShutdown)
*
* @this {Component}
* @param {boolean} fSave
* @param {boolean} [fShutdown]
* @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
*/
powerDown: function(fSave, fShutdown) {
if (fShutdown) this.fPowered = false;
return true;
}
};
/*
* TODO: What was this work-around for? I forget....
*/
if (window && !window.document.ELEMENT_NODE) window.document.ELEMENT_NODE = 1;
if (typeof module !== 'undefined') module.exports = Component;

View file

@ -0,0 +1,76 @@
/**
* @fileoverview Compile-time definitions used by C1Pjs and PCjs.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* Created 2014-May-08
*
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of the JavaScript Machines Project (aka JSMachines) at <http://jsmachines.net/>
* and <http://pcjs.org/>.
*
* JSMachines is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with JSMachines.
* If not, see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see Computer.sCopyright).
*
* Some JSMachines files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of the
* JSMachines Project for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
"use strict";
/**
* @define {string}
*/
var APPNAME = ""; // this @define is overridden by the Closure Compiler with either "PCjs" or "C1Pjs"
/**
* @define {string}
*/
var APPVERSION = "1.0.0"; // this @define is overridden by the Closure Compiler with the version in package.json
/**
* @define {string}
*/
var SITEHOST = "pcjs:8088"; // this @define is overridden by the Closure Compiler with "www.pcjs.org"
/**
* @define {boolean}
*/
var COMPILED = false; // this @define is overridden by the Closure Compiler (to true)
/**
* @define {boolean}
*/
var DEBUG = true; // this @define is overridden by the Closure Compiler (to false) to remove DEBUG-only code
/**
* @define {boolean}
*/
var MAXDEBUG = false; // this @define is overridden by the Closure Compiler (to false) to remove MAXDEBUG-only code
if (typeof module !== 'undefined') {
global.window = false;
global.APPNAME = APPNAME;
global.APPVERSION = APPVERSION;
global.SITEHOST = SITEHOST;
global.COMPILED = COMPILED;
global.DEBUG = DEBUG;
global.MAXDEBUG = MAXDEBUG;
/*
* TODO: When we're "required" by Node, should we return anything via module.exports?
*/
}

View file

@ -0,0 +1,75 @@
/**
* @fileoverview Disk APIs, as defined by httpapi.js and consumed by disk.js
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* Created 2014-May-08
*
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of the JavaScript Machines Project (aka JSMachines) at <http://jsmachines.net/>
* and <http://pcjs.org/>.
*
* JSMachines is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with JSMachines.
* If not, see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see Computer.sCopyright).
*
* Some JSMachines files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of the
* JSMachines Project for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
"use strict";
/*
* Our "DiskIO API" looks like:
*
* http://www.pcjs.org/api/v1/disk?action=open&volume=*10mb.img&mode=demandrw&chs=c:h:s&machine=xxx&user=yyy
*/
var DiskAPI = {
ENDPOINT: "/api/v1/disk",
QUERY: {
ACTION: "action", // value is one of DiskAPI.ACTION.*
VOLUME: "volume", // value is path of a disk image
MODE: "mode", // value is one of DiskAPI.MODE.*
CHS: "chs", // value is cylinders:heads:sectors:bytes
ADDR: "addr", // value is cylinder:head:sector:count
MACHINE: "machine", // value is machine token
USER: "user", // value is user ID
DATA: "data" // value is data to be written
},
ACTION: {
OPEN: "open",
READ: "read",
WRITE: "write",
CLOSE: "close"
},
MODE: {
LOCAL: "local", // this mode implies no API (at best, localStorage backing only)
PRELOAD: "preload", // this mode implies use of the DumpAPI
DEMANDRW: "demandrw",
DEMANDRO: "demandro"
},
FAIL: {
BADACTION: "invalid action",
BADUSER: "invalid user",
BADVOL: "invalid volume",
OPENVOL: "unable to open volume",
CREATEVOL: "unable to create volume",
WRITEVOL: "unable to write volume",
REVOKED: "access revoked"
}
};
if (typeof module !== 'undefined') module.exports = DiskAPI;

View file

@ -0,0 +1,85 @@
/**
* @fileoverview Disk APIs, as defined by diskdump.js and consumed by disk.js
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* Created 2014-May-08
*
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of the JavaScript Machines Project (aka JSMachines) at <http://jsmachines.net/>
* and <http://pcjs.org/>.
*
* JSMachines is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with JSMachines.
* If not, see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see Computer.sCopyright).
*
* Some JSMachines files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of the
* JSMachines Project for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
"use strict";
/*
* Our "DiskDump API", such as it was, used to look like:
*
* http://jsmachines.net/bin/convdisk.php?disk=/disks/pc/dos/ibm/2.00/PCDOS200-DISK1.json&format=img
*
* To make it (a bit) more "REST-like", the above request now looks like:
*
* http://www.pcjs.org/api/v1/dump?disk=/disks/pc/dos/ibm/2.00/PCDOS200-DISK1.json&format=img
*
* Similarly, our "FileDump API" used to look like:
*
* http://jsmachines.net/bin/convrom.php?rom=/devices/pc/bios/5150/1981-04-24.rom&format=json
*
* and that request now looks like:
*
* http://www.pcjs.org/api/v1/dump?file=/devices/pc/bios/5150/1981-04-24.rom&format=json
*
* I don't think it makes sense to avoid "query" parameters, because blending the path of a disk image with the
* the rest of the URL would be (a) confusing, and (b) more work to parse.
*/
var DumpAPI = {
ENDPOINT: "/api/v1/dump",
QUERY: {
DIR: "dir", // value is path of a directory (DiskDump only)
DISK: "disk", // value is path of a disk image (DiskDump only)
FILE: "file", // value is path of a ROM image file (FileDump only)
IMG: "img", // alias for DISK
PATH: "path", // value is path of a one or more files (DiskDump only)
FORMAT: "format", // value is one of FORMAT values below
COMMENTS: "comments", // value is either "true" or "false"
DECIMAL: "decimal", // value is either "true" to force all numbers to decimal, "false" or undefined otherwise
MBHD: "mbhd" // value is hard disk size in Mb (formerly "mbsize") (DiskDump only)
},
FORMAT: {
JSON: "json", // default
DATA: "data", // same as "json", but built without JSON.stringify() (DiskDump only)
HEX: "hex", // deprecated
BYTES: "bytes", // displays data as hex bytes; normally used only when comments are enabled
IMG: "img", // returns the raw disk data (ie, using a Buffer object) (DiskDump only)
ROM: "rom" // returns the raw file data (ie, using a Buffer object) (FileDump only)
}
};
/*
* Because we use an overloaded API endpoint (ie, one that's shared with the FileDump module), we must
* also provide a list of commands which, when combined with the endpoint, define a unique request.
*/
DumpAPI.asDiskCommands = [DumpAPI.QUERY.DIR, DumpAPI.QUERY.DISK, DumpAPI.QUERY.PATH];
DumpAPI.asFileCommands = [DumpAPI.QUERY.FILE];
if (typeof module !== 'undefined') module.exports = DumpAPI;

View file

@ -0,0 +1,372 @@
/**
* @fileoverview C1Pjs and PCjs embedding functionality.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* @suppress {missingProperties}
* Created 2012-Aug-28
*
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of the JavaScript Machines Project (aka JSMachines) at <http://jsmachines.net/>
* and <http://pcjs.org/>.
*
* JSMachines is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with JSMachines.
* If not, see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see Computer.sCopyright).
*
* Some JSMachines files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of the
* JSMachines Project for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
"use strict";
/* global window: true, XSLTProcessor: false, web: true, Component: true, APPNAME: false, APPVERSION: false, DEBUG: true */
if (typeof module !== 'undefined') {
var Component;
var str = require("./strlib");
var web = require("./weblib");
}
/**
* loadXML(sFile, idMachine, sStateFile, fResolve)
*
* This is the preferred way to load all XML and XSL files. It uses loadResource()
* to load them as strings, which parseXML() can massage before parsing/transforming them.
*
* For example, since I've been unable to get the XSLT document() function to work inside any
* XSL document loaded by JavaScript's XSLT processor, that has prevented me from dynamically
* loading any XML machine file that uses the "ref" attribute to refer to and incorporate
* another XML document.
*
* To solve that, I've added an fResolve parameter that tells parseXML() to fetch any
* referenced documents ITSELF and insert them into the XML string prior to parsing, instead
* of relying on the XSLT template to pull them in. That fetching is handled by resolveRefs(),
* which iterates over the XML until all "refs" have been resolved (including any nested
* references).
*
* Also, XSL files with a <!DOCTYPE [...]> cause MSIE's Microsoft.XMLDOM.loadXML() function
* to choke, so I strip that out prior to parsing as well.
*
* TODO: Figure out why the XSLT document() function works great when the web browser loads an
* XML file (and the associated XSL file) itself, but does not work when loading documents via
* JavaScript XSLT support. Is it broken, is it a security issue, or am I just calling it wrong?
*
* @param {string} sXMLFile
* @param {string} [idMachine]
* @param {string} [sStateFile]
* @param {boolean} [fResolve] is true to resolve any "ref" attributes
* @return {Array} where [0] contains the unparsed XML string data, and [1] contains a parsed XML object
*/
function loadXML(sXMLFile, idMachine, sStateFile, fResolve)
{
var response = web.loadResource(sXMLFile);
if (response[0]) {
throw new Error(response[1]);
}
return parseXML(response[1], sXMLFile, idMachine, sStateFile, fResolve);
}
/**
* parseXML(sXML, idMachine, sStateFile, fResolve)
*
* Generates an XML document from an XML string. This function also provides a work-around for XSLT's
* lack of support for the document() function (at least on some browsers), by replacing every reference
* tag (ie, a tag with a "ref" attribute) with the contents of the referenced file.
*
* @param {string|null} sXML
* @param {string|null} sXMLFile
* @param {string} [idMachine]
* @param {string} [sStateFile]
* @param {boolean} [fResolve] is true to resolve any "ref" attributes; default is false
* @return {Array} where [0] contains the unparsed XML string data, and [1] contains a parsed XML object
*/
function parseXML(sXML, sXMLFile, idMachine, sStateFile, fResolve)
{
var xmlDoc = null;
if (sXML) {
if (fResolve) {
sXML = resolveRefs(sXML);
}
if (idMachine) {
var sURL = sXMLFile;
if (sURL && sURL.indexOf('/') < 0) sURL = window.location.pathname + sURL;
sXML = sXML.replace(/(<machine[^>]*\sid=)(['"]).*?\2/, "$1$2" + idMachine + "$2" + (sStateFile? " state=$2" + sStateFile + "$2" : "") + (sURL? " url=$2" + sURL + "$2" : ""));
}
/*
* If the resource we requested is not really an XML file (or the file didn't exist and the server simply returned
* a message like "Cannot GET /configs/pc/machines/5150/cga/64kb/donkey/index.xml"), we'd like to display a more
* meaningful message, because the XML DOM parsers will blithely return a document that contains nothing useful; eg:
*
* This page contains the following errors:error on line 1 at column 1:
* Document is empty Below is a rendering of the page up to the first error.
*
* Supposedly, the IE XML DOM parser will throw an exception, but I haven't tested that, and unless all other
* browsers do that, that's not helpful.
*
* The best I can do at this stage (assuming web.loadResource() didn't drop any error information on the floor)
* is verify that the requested resource "looks like" valid XML (in other words, it begins with a "<").
*/
if (sXML.indexOf("<") === 0) {
if (window.ActiveXObject || "ActiveXObject" in window) { // second test is required for IE11 on Windows 8.1
/*
* Another hack for MSIE, which fails to properly load XSL documents containing a <!DOCTYPE [...]> tag.
*/
if (!fResolve) {
sXML = sXML.replace(/<!DOCTYPE(.|[\r\n])*\]>\s*/g, "");
}
xmlDoc = new window.ActiveXObject("Microsoft.XMLDOM");
xmlDoc.async = false;
xmlDoc.loadXML(sXML);
} else {
xmlDoc = (new window.DOMParser()).parseFromString(sXML, "text/xml");
}
} else {
throw new Error("unrecognized XML: " + (sXML.length > 255? sXML.substr(0, 255) + "..." : sXML));
}
}
return [sXML, xmlDoc];
}
/**
* resolvesRefs(sXML)
*
* Replaces every tag with a "ref" attribute with the contents of the corresponding file.
*
* TODO: Fix some of the limitations of this code, such as: 1) requiring the "ref" attribute
* to appear as the tag's first attribute, 2) requiring the "ref" attribute to be double-quoted,
* and 3) requiring the "ref" tag to be self-closing.
*
* @param {string} sXML
* @returns {string} with all tags with "ref" attributes replaced with the referenced file instead
*/
function resolveRefs(sXML)
{
var matchRef, sError;
var reRef = /<([a-z]+)\s+ref="(.*?)"(.*?)\/>/g;
while ((matchRef = reRef.exec(sXML))) {
var sRefFile = matchRef[2];
var response = web.loadResource(sRefFile);
var sXMLRef = response[1];
if (response[0] || !sXMLRef) {
sError = "unable to resolve XML reference: " + matchRef[0] + " (" + response[0] + ")";
Component.log(sError);
throw new Error(sError);
}
/*
* If there are additional attributes in the "referring" XML tag, we want to insert them
* into the "referred" XML tag; attributes that don't exist in the referred tag should be
* appended, and attributes that DO exist should be overwritten.
*/
var sRefAttrs = matchRef[3];
if (sRefAttrs) {
var aXMLRefTag = sXMLRef.match(new RegExp("<" + matchRef[1] + "[^>]*>"));
if (aXMLRefTag) {
var sXMLNewTag = aXMLRefTag[0];
/*
* Iterate over all the attributes in the "referring" XML tag (sRefAttrs)
*/
var matchAttr;
var reAttr = /( [a-z]+=)(['"])(.*?)\2/g;
while ((matchAttr = reAttr.exec(sRefAttrs))) {
if (sXMLNewTag.indexOf(matchAttr[1]) < 0) {
/*
* This is the append case
*/
sXMLNewTag = sXMLNewTag.replace(">", matchAttr[0] + ">");
} else {
/*
* This is the overwrite case
*/
sXMLNewTag = sXMLNewTag.replace(new RegExp(matchAttr[1] + "(['\"])(.*?)\\1"), matchAttr[0]);
}
}
if (aXMLRefTag[0] != sXMLNewTag) {
sXMLRef = sXMLRef.replace(aXMLRefTag[0], sXMLNewTag);
}
} else {
sError = "missing <" + matchRef[1] + "> in " + sRefFile;
Component.log(sError);
throw new Error(sError);
}
}
/*
* Apparently when a Windows Azure server delivers one of my XML files, it may modify the first line:
*
* <?xml version="1.0" encoding="UTF-8"?>\n
*
* I didn't determine exactly what it was doing at this point (probably just changing the \n to \r\n),
* but in any case, relaxing the following replace() solved it.
*/
sXMLRef = sXMLRef.replace(/<\?xml[^>]*>[\r\n]*/, "");
sXML = sXML.replace(matchRef[0], sXMLRef);
reRef.lastIndex = 0; // reset lastIndex, since we just modified the string that reRef is iterating over
}
return sXML;
}
/**
* embedMachine(sName, sVersion, idElement, sXMLFile, sXSLFile, sStateFile)
*
* This allows to you embed a machine on a web page, by transforming the machine XML into HTML.
*
* @param {string} sName is the app name (eg, "PCjs" or "C1Pjs")
* @param {string} sVersion is the app version (eg, "1.12.1")
* @param {string} idElement
* @param {string} sXMLFile
* @param {string} [sXSLFile]
* @param {string} [sStateFile]
* @return {string} containing the complete XML string data, or an error if the XML could not be parsed
*/
function embedMachine(sName, sVersion, idElement, sXMLFile, sXSLFile, sStateFile)
{
var sXML = "", sError = "", eMachine = null;
try {
eMachine = window.document.getElementById(idElement);
if (eMachine) {
var sAppClass = sName.toLowerCase(); // eg, "pcjs" or "c1pjs"
if (!sXSLFile) {
if (DEBUG && sVersion == "1.0.0") {
sXSLFile = "/my_modules/" + sAppClass + "-client/templates/components.xsl";
} else {
sXSLFile = "/versions/" + sAppClass + "/" + sVersion + "/components.xsl";
}
}
var aXML = (sXMLFile.substr(0, 1) == "<" ? parseXML(sXMLFile, null, idElement, sStateFile, false) : loadXML(sXMLFile, idElement, sStateFile, true));
sXML = aXML[0];
var xml = aXML[1];
if (xml) {
aXML = loadXML(sXSLFile);
var xsl = aXML[1];
if (xsl) {
/*
* The <machine> template in components.xsl now generates a "machine div" that makes
* the div we required the caller of embedMachine() to provide redundant, so instead
* of appending this fragment to the caller's node, we REPLACE the caller's node.
* This works only because because we ALSO inject the caller's "machine div" ID into
* the fragment's ID during parseXML().
*
* eMachine.innerHTML = sFragment;
*
* Also, if the transform function fails, make sure you're using the appropriate
* "components.xsl" and not a "machine.xsl", because the latter will not produce valid
* embeddable HTML (and is the most common cause of failure at this final stage).
*/
if (window.ActiveXObject || "ActiveXObject" in window) { // second test is required for IE11 on Windows 8.1
var sFragment = xml['transformNode'](xsl);
if (sFragment) {
eMachine.outerHTML = sFragment;
} else {
Component.log(sError = "transformNodeToObject failed");
}
}
else if (window.document.implementation && window.document.implementation.createDocument) {
var xsltProcessor = new XSLTProcessor();
xsltProcessor['importStylesheet'](xsl);
var eFragment = xsltProcessor['transformToFragment'](xml, window.document);
if (eFragment) {
eMachine.parentNode.replaceChild(eFragment, eMachine);
} else {
Component.log(sError = "transformToFragment failed");
}
} else {
/*
* Perhaps I should have performed this test at the outset; on the other hand, I'm
* not aware of any browsers don't support one or both of the above XSLT transformation
* methods, so treat this as a bug.
*/
Component.log(sError = "unable to transform XML: unsupported browser");
}
} else {
Component.log(sError = "failed to load XSL file: " + sXSLFile);
}
} else {
Component.log(sError = "failed to load XML file: " + sXMLFile);
}
} else {
Component.log(sError = "failed to find machine element: " + idElement);
}
} catch(e) {
sError = e.message;
}
if (sError && eMachine) {
/*
* Our MarkOut module (in convertMDMachineLinks()) creates machine containers that look like this:
*
* <div id="' + sMachineID + '" class="machine-placeholder"><p>Embedded PC</p><p class="machine-warning"></p></div>
*
* with the "machine-warning" paragraph pre-populated with a warning message that the user will
* see if nothing at all happens. But hopefully, in the normal case (and especially the error case),
* *something* will have happened.
*
* Note that it is the HTMLOut module (in processMachines()) that ultimately decides which scripts to
* include and then generates the embedPC() and/or embedC1P() calls.
*/
var aeError = Component.getElementsByClass(eMachine, "machine-warning");
if (aeError[0]) {
aeError[0].innerHTML = "Error: " + str.escapeHTML(sError);
}
}
return sError || sXML;
}
/**
* embedC1P(idElement, sXMLFile, sXSLFile)
*
* @param {string} idElement
* @param {string} sXMLFile
* @param {string} [sXSLFile]
* @return {string} XML string data or error message
*/
function embedC1P(idElement, sXMLFile, sXSLFile)
{
return embedMachine("C1Pjs", APPVERSION, idElement, sXMLFile, sXSLFile);
}
/**
* embedPC(idElement, sXMLFile, sXSLFile, sStateFile)
*
* @param {string} idElement
* @param {string} sXMLFile
* @param {string} [sXSLFile]
* @param {string} [sStateFile]
* @return {string} XML string data or error message
*/
function embedPC(idElement, sXMLFile, sXSLFile, sStateFile)
{
return embedMachine("PCjs", APPVERSION, idElement, sXMLFile, sXSLFile, sStateFile);
}
/**
* Prevent the Closure Compiler from renaming functions we want to export, by adding them
* as (named) properties of a global object.
*/
if (APPNAME == "PCjs") {
window['embedPC'] = embedPC;
}
if (APPNAME == "C1Pjs") {
window['embedC1P'] = embedC1P;
}
window['enableEvents'] = web.enablePageEvents;
window['sendEvent'] = web.sendPageEvent;

View file

@ -0,0 +1,39 @@
/**
* @fileoverview Externs used by PCjs and C1Pjs (for the Closure Compiler)
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* Created 2012-Dec-04
*
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of the JavaScript Machines Project (aka JSMachines) at <http://jsmachines.net/>
* and <http://pcjs.org/>.
*
* JSMachines is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with JSMachines.
* If not, see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see Computer.sCopyright).
*
* Some JSMachines files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of the
* JSMachines Project for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
"use strict";
/*
* Unless we declare "module", even code like "if (typeof module === 'undefined') ..." is disallowed by the compiler.
*/
var module;
var webkitAudioContext;

View file

@ -0,0 +1,385 @@
/**
* @fileoverview Net-related functions
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a> (@jeffpar)
* @version 1.0
* Created 2014-03-16
*
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of the JavaScript Machines Project (aka JSMachines) at <http://jsmachines.net/>
* and <http://pcjs.org/>.
*
* JSMachines is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with JSMachines.
* If not, see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see Computer.sCopyright).
*
* Some JSMachines files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of the
* JSMachines Project for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
"use strict";
/* global Buffer: false */
var net = {};
if (typeof module !== 'undefined') {
var sServerRoot;
var fs = require("fs");
var http = require("http");
var path = require("path");
var url = require("url");
var str = require("./strlib");
}
/*
* The following are (super-secret) commands that can be added to the URL to enable special features.
*
* Our super-secret command processor is affectionately call Gort, and while Gort doesn't understand commands
* like "Klaatu barada nikto", it does understand commands like "debug" and "rebuild"; eg:
*
* http://www.pcjs.org/?gort=debug
*
* hasParm() detects the presence of the specified command, and propagateParms() is a URL filter that ensures
* any commands listed in asPropagate are passed through to all other URLs on the same page; using any of these
* commands also forces the page to be rebuilt and not cached (since we would never want a cached "index.html" to
* contain/expose any of these commands).
*/
net.GORT_COMMAND = "gort";
net.GORT_DEBUG = "debug"; // use this to force uncompiled JavaScript even on a Release server
net.GORT_NODEBUG = "nodebug"; // use this to force uncompiled JavaScript but with DEBUG code disabled
net.GORT_RELEASE = "release"; // use this to force the use of compiled JavaScript even on a Debug server
net.GORT_REBUILD = "rebuild"; // use this to force the "index.html" in the current directory to be rebuilt
net.REVEAL_COMMAND = "reveal";
net.REVEAL_PDFS = "pdfs";
/*
* This is a list of the URL parameters that propagateParms() will propagate from the requester's URL to
* other URLs provided by the requester.
*/
var asPropagate = [net.GORT_COMMAND, "autostart"];
/**
* hasParm(sParm, sValue, req)
*
* @param {string} sParm
* @param {string|null} sValue (pass null to check for the presence of ANY sParm)
* @param {Object} [req] is the web server's (ie, Express) request object, if any
* @return {boolean} true if the request Object contains the specified parameter/value, false if not
*
* TODO: Consider whether sParm === null should check for the presence of ANY parameter in asPropagate.
*/
net.hasParm = function(sParm, sValue, req)
{
return (req && req.query && req.query[sParm] && (!sValue || req.query[sParm] == sValue));
};
/**
* propagateParms(sURL, req)
*
* Propagates any "special" query parameters (as listed in asPropagate) from the given
* request object (req) to the given URL (sURL).
*
* We do not modify an sURL that already contains a '?' OR that begins with a protocol
* (eg, http:, mailto:, etc), in order to keep this function simple, since it's only for
* debugging purposes anyway. I also considered blowing off any URLs with a '#' for the
* same reason, since any hash string must follow any query parameters, but stripping
* and re-appending the hash string is pretty trivial, so we do handle that.
*
* TODO: Make propagateParms() more general-purpose (eg, capable of detecting any URL
* to the same site, and capable of merging any of our "special" query parameters with any
* existing query parameters.
*
* @param {string|null} sURL
* @param {Object} [req] is the web server's (ie, Express) request object, if any
* @return {string} massaged sURL
*/
net.propagateParms = function(sURL, req)
{
if (sURL !== null && sURL.indexOf('?') < 0) {
var i;
var sHash = "";
if ((i = sURL.indexOf('#')) >= 0) {
sHash = sURL.substr(i);
sURL = sURL.substr(0, i);
}
var match = sURL.match(/^([a-z])+:(.*)/);
if (!match && req && req.query) {
for (i = 0; i < asPropagate.length; i++) {
var sQuery = asPropagate[i];
var sValue;
if ((sValue = req.query[sQuery])) {
var sParm = (sURL.indexOf('?') < 0? '?' : '&');
sParm += sQuery + '=';
if (sURL.indexOf(sParm) < 0) sURL += sParm + encodeURIComponent(sValue);
}
}
}
sURL += sHash;
}
return sURL;
};
/**
* encodeURL(sURL, req, fDebug)
*
* Used to encodes any URLs presented on the current page, using this 3-step process:
*
* 1) Replace any backslashes with slashes, in case the URL was derived from a file system path
* 2) Remap links that begin with "static/" to the corresponding URL at "http://static.pcjs.org/"
* 3) Massage the result with net.propagateParms(), so that any special parameters are passed along
* 4) Transform any "htmlspecialchars" into the corresponding entities, to help ensure proper validation
*
* @param {string} sURL
* @param {Object} req is the web server's (ie, Express) request object, if any
* @param {boolean} [fDebug]
* @return {string} encoded URL
*/
net.encodeURL = function(sURL, req, fDebug)
{
if (sURL) {
sURL = sURL.replace(/\\/g, '/');
if (!fDebug) {
if (sURL.match(/^[^:]*static/)) {
sURL = "http://static.pcjs.org" + path.join(req.path, sURL).replace("/static/", "/");
}
}
return net.propagateParms(encodeURI(sURL), req);
}
return sURL;
};
/**
* isRemote(sPath)
*
* @param {string} sPath
* @return {boolean} true if sPath is a (supported) remote path, false if not
*
* TODO: Add support for FTP? HTTPS? Anything else?
*/
net.isRemote = function(sPath)
{
return (sPath.indexOf("http:") === 0);
};
/**
* getStat(sURL, done)
*
* @param {string} sURL
* @param {function(Error,Object)} done
*/
net.getStat = function(sURL, done)
{
var options = url.parse(sURL);
options.method = "HEAD";
options.path = options.pathname; // TODO: Determine the necessity of aliasing this
var req = http.request(options, function(res) {
var err = null;
var stat = null;
// console.log(JSON.stringify(res.headers));
if (res.statusCode == 200) {
/*
* Apparently Node lower-cases response headers (at least incoming headers, despite
* lots of amusing whining by certain people in the Node community), which seems like
* a good thing, because that means I can do two simple key look-ups.
*/
var sLength = res.headers['content-length'];
var sModified = res.headers['last-modified'];
stat = {
size: sLength? parseInt(sLength, 10) : -1,
mtime: sModified? new Date(sModified) : null,
remote: true // an additional property we provide to indicate this is not your normal stats object
};
} else {
err = new Error("unexpected response code: " + res.statusCode);
}
done(err, stat);
});
req.on('error', function(err) {
done(err, null);
});
req.end();
};
/**
* getFile(sURL, sEncoding, done)
*
* @param {string} sURL is the source file
* @param {string|null} sEncoding is the encoding to assume, if any
* @param {function(Error,number,(string|Buffer))} done receives an Error, an HTTP status code, and a Buffer (if any)
*
* TODO: Add support for FTP? HTTPS? Anything else?
*/
net.getFile = function(sURL, sEncoding, done)
{
/*
* Buffer objects are a fixed size, so my choices are: 1) call getStat() first, hope it returns
* the true size, and then preallocate a buffer; or 2) create a new, larger buffer every time a new
* chunk arrives. The latter seems best.
*
* However, if an encoding is given, we'll simply concatenate all the data into a String and return
* that instead. Note that the incoming data is always a Buffer, but concatenation with a String
* performs an implied "toString()" on the Buffer.
*
* WARNING: Even when an encoding is provided, we don't make any attempt to verify that the incoming
* data matches that encoding.
*/
var sFile = "";
var bufFile = null;
http.get(sURL, function(res) {
res.on('data', function(data) {
if (sEncoding) {
sFile += data;
return;
}
if (!bufFile) {
bufFile = data;
return;
}
/*
* We need to grow bufFile. I used to do this myself, using the "copy" method:
*
* buf.copy(targetBuffer, [targetStart], [sourceStart], [sourceEnd])
*
* which defaults to 0 for [targetStart] and [sourceStart], but the docs don't clearly
* define the default value for [sourceEnd]. They say "buffer.length", but there is no
* parameter here named "buffer". Let's hope that in the case of "bufFile.copy(buf)"
* they meant "bufFile.length".
*
* However, it turns out this is moot, because there's a new kid in town: Buffer.concat().
*
* buf = new Buffer(bufFile.length + data.length);
* bufFile.copy(buf);
* data.copy(buf, bufFile.length);
* bufFile = buf;
*/
bufFile = Buffer.concat([bufFile, data], bufFile.length + data.length);
}).on('end', function() {
/*
* TODO: Decide what to do when res.statusCode is actually an error code (eg, 404), because
* in such cases, the file content will likely just be an HTML error page.
*/
if (res.statusCode < 400) {
done(null, res.statusCode, sEncoding? sFile : bufFile);
} else {
done(new Error(sEncoding? sFile : bufFile), res.statusCode, null);
}
}).on('error', function(err) {
done(err, res.statusCode, null);
});
});
};
/**
* downloadFile(sURL, sFile, done)
*
* @param {string} sURL is the source file
* @param {string} sFile is a fully-qualified target file
* @param {function(Error,number)} done is a callback that receives an Error and a HTTP status code
*/
net.downloadFile = function(sURL, sFile, done)
{
var file = fs.createWriteStream(sFile);
/*
* http.get() accepts a "url" string in lieu of an "options" object; it automatically builds
* the latter from the former using url.parse(). This is good, because it relieves me from
* building my own "options" object, and also from wondering why http functions expect "options"
* to contain a "path" property, whereas url.parse() returns a "pathname" property.
*
* Either the documentation isn't quite right for url.parse() or http.request() (the big brother
* of http.get), or one of those "options" properties is aliased to the other, or...?
*/
http.get(sURL, function(res) {
res.on('data', function(data) {
file.write(data);
}).on('end', function() {
file.end();
/*
* TODO: We should try to update the file's modification time to match the 'last-modified'
* response header value, if any.
*
* TODO: Decide what to do when res.statusCode is actually an error code (eg, 404), because
* in such cases, the file content will likely just be an HTML error page.
*/
done(null, res.statusCode);
}).on('error', function(err) {
done(err, res.statusCode);
});
});
};
/**
* loadResource(sURL, fAsync, data, componentNotify, fnNotify, pNotify)
*
* Request the specified resource (sURL), and once the request is complete,
* optionally call the specified method (fnNotify) of the specified component (componentNotify).
*
* TODO: Figure out how we can strongly type the fnNotify parameter, because the Closure Compiler has issues with:
*
* {function(this:Component, string, (string|null), number, (number|string|null|Object|Array|undefined))} [fnNotify]
*
* NOTE: This function is a mirror image of the weblib version, for server-side component testing within Node;
* since it is NOT intended for production use, it may make liberal use of synchronous functions, warning messages, etc.
*
* @param {string} sURL
* @param {boolean} [fAsync] is true for an asynchronous request
* @param {Object|null} [data] for a POST request (default is a GET request)
* @param {Component} [componentNotify]
* @param {function(...)} [fnNotify]
* @param {number|string|null|Object|Array} [pNotify] optional fnNotify parameter
* @return {Array} containing errorCode and responseText (empty array if async request)
*/
net.loadResource = function(sURL, fAsync, data, componentNotify, fnNotify, pNotify) {
var nErrorCode = -1;
var sResponse = null;
if (net.isRemote(sURL)) {
console.log('net.loadResource("' + sURL + '"): unimplemented');
} else {
if (!sServerRoot) {
sServerRoot = path.join(path.dirname(fs.realpathSync(__filename)), "../../../");
}
var sFile = path.join(sServerRoot, sURL);
if (fAsync) {
fs.readFile(sFile, {encoding: "utf8"}, function(err, s) {
/*
* TODO: If err is set, is there an error code we should return (instead of -1)?
*/
if (!err) {
sResponse = s;
nErrorCode = 0;
}
if (componentNotify && fnNotify) fnNotify.call(componentNotify, str.getBaseName(sURL), sResponse, nErrorCode, pNotify);
});
return [];
} else {
try {
sResponse = fs.readFileSync(sFile, {encoding: "utf8"});
nErrorCode = 0;
} catch(err) {
/*
* TODO: If err is set, is there an error code we should return (instead of -1)?
*/
console.log(err.message);
}
if (componentNotify && fnNotify) fnNotify.call(componentNotify, str.getBaseName(sURL), sResponse, nErrorCode, pNotify);
}
}
return [nErrorCode, sResponse];
};
if (typeof module !== 'undefined') module.exports = net;

View file

@ -0,0 +1,45 @@
/**
* @fileoverview Compile-time definitions for non-DEBUG configurations.
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* Created 2014-Aug-22
*
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
* at <http://jsmachines.net/> and <http://pcjs.org/>.
*
* PCjs is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCjs. If not,
* see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see Computer.sCopyright).
*
* Some PCjs files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of the
* PCjs program for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
"use strict";
/* global DEBUG: true */
/*
* In the compiled case, we rely on the Closure Compiler to override DEBUG, setting it to false,
* so that all DEBUG-only code will be removed by the compiler.
*
* However, when we're in "development mode" and want to run uncompiled code without any DEBUG-only
* code, we must arrange for this additional file, nodebug.js, to be loaded as early as possible,
* which will then set DEBUG to false at runtime.
*/
DEBUG = false;

View file

@ -0,0 +1,90 @@
/**
* @fileoverview Process-related helper functions
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a> (@jeffpar)
* @version 1.0
* Created 2014-05-07
*
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of the JavaScript Machines Project (aka JSMachines) at <http://jsmachines.net/>
* and <http://pcjs.org/>.
*
* JSMachines is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with JSMachines.
* If not, see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see Computer.sCopyright).
*
* Some JSMachines files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of the
* JSMachines Project for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
"use strict";
var proc = {};
/**
* getArgs()
*
* Processes command-line arguments. Arguments may be introduced by either
* a double-hyphen (--) or a long dash (), and argument values, if any, must be
* separated by an "=" without any intervening whitespace. Arguments without
* an explicit value default to true, and any argument appearing more than once
* is automatically converted to an Array.
*
* Single-hyphen (-) arguments are allowed as well; they are treated as a series
* of single-character arguments, each set to true, and any of these arguments
* appearing more than once is discarded.
*
* @return {{argc:number, argv:{}}}
*/
proc.getArgs = function() {
var argc = 0;
var argv = {};
for (var i = 2; i < process.argv.length; i++) {
var j, sSep;
var sArg = process.argv[i];
if (!sArg.indexOf(sSep = "--") || !sArg.indexOf(sSep = "—")) {
sArg = sArg.substr(sSep.length);
var sValue = true;
j = sArg.indexOf("=");
if (j > 0) {
sValue = sArg.substr(j+1);
sArg = sArg.substr(0, j);
sValue = (sValue == "true")? true : ((sValue == "false")? false : sValue);
}
if (argv[sArg] === undefined) {
argc++;
argv[sArg] = sValue;
} else {
// console.log("too many '" + sArg + "' arguments");
if (typeof argv[sArg] == "string") {
argv[sArg] = [argv[sArg]];
}
argv[sArg].push(sValue);
}
} else if (!sArg.indexOf("-")) {
for (j = 1; j < sArg.length; j++) {
var ch = sArg.charAt(j);
if (argv[ch] === undefined) {
argv[ch] = true;
argc++;
}
}
}
}
return {argc: argc, argv: argv};
};
if (typeof module !== 'undefined') module.exports = proc;

View file

@ -0,0 +1,53 @@
/**
* @fileoverview Report API, as defined by httpapi.js
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* Created 2014-May-13
*
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of the JavaScript Machines Project (aka JSMachines) at <http://jsmachines.net/>
* and <http://pcjs.org/>.
*
* JSMachines is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with JSMachines.
* If not, see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see Computer.sCopyright).
*
* Some JSMachines files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of the
* JSMachines Project for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
"use strict";
var ReportAPI = {
ENDPOINT: "/api/v1/report",
QUERY: {
APP: "app",
VER: "ver",
URL: "url",
USER: "user",
TYPE: "type",
DATA: "data"
},
TYPE: {
BUG: "bug"
},
RES: {
OK: "Thank you"
}
};
if (typeof module !== 'undefined') module.exports = ReportAPI;

View file

@ -0,0 +1,43 @@
/**
* @fileoverview Experimental code included by HTMLOut() only if "--sockets"
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* Created 2014-Apr-29
*
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of the JavaScript Machines Project (aka JSMachines) at <http://jsmachines.net/>
* and <http://pcjs.org/>.
*
* JSMachines is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with JSMachines.
* If not, see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see Computer.sCopyright).
*
* Some JSMachines files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of the
* JSMachines Project for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
"use strict";
/* global io: false */
// connect to the socket server
var socket = io.connect();
// if we get an "info" emit from the socket server then console.log the data we receive
socket.on('info', function (data) {
console.log(data);
});

View file

@ -0,0 +1,323 @@
/**
* @fileoverview String-related helper functions
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a> (@jeffpar)
* @version 1.0
* Created 2014-03-09
*
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of the JavaScript Machines Project (aka JSMachines) at <http://jsmachines.net/>
* and <http://pcjs.org/>.
*
* JSMachines is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with JSMachines.
* If not, see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see Computer.sCopyright).
*
* Some JSMachines files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of the
* JSMachines Project for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
"use strict";
var str = {};
/**
* isValidInt(s, base)
*
* Since the built-in parseInt() function has the annoying feature of returning a partial value
* when it encounters an invalid character (eg, parseInt("foo", 16) returns 0xf), use this function
* to validate the entire string first.
*
* @param {string} s is the string representation of some number
* @param {number} [base] is the radix of the number represented above (only 10 and 16 are supported)
* @return {boolean} true if valid (or we're not sure because the base isn't recognized), false if invalid
*/
str.isValidInt = function(s, base)
{
if (!base || base == 10) return s.match(/^[0-9]+$/) !== null;
if (base == 16) return s.match(/^[0-9a-f]+$/i) !== null;
return true;
};
/**
* parseInt(s, base)
*
* This is a wrapper around the built-in parseInt() function, which recognizes certain prefixes (eg,
* '$' or "0x" for hex) and suffixes (eg, 'h' for hex or '.' for decimal), and then calls isValidInt()
* to ensure we don't get partial values (see isValidInt() for details).
*
* @param {string} s is the string representation of some number
* @param {number} [base] is the radix to assume (default is 16)
* @return {number|undefined} corresponding value, or undefined if invalid
*/
str.parseInt = function(s, base)
{
var value;
if (s) {
if (!base) base = 16;
if (s.charAt(0) == '$') {
base = 16;
s = s.substr(1);
} else if (s.substr(0, 2) == "0x") {
base = 16;
s = s.substr(2);
} else {
var chSuffix = s.charAt(s.length-1).toLowerCase();
if (chSuffix == 'h') {
base = 16;
chSuffix = null;
}
else if (chSuffix == '.') {
base = 10;
chSuffix = null;
}
if (chSuffix === null) s = s.substr(0, s.length-1);
}
var v;
if (str.isValidInt(s, base) && !isNaN(v = parseInt(s, base))) {
value = v;
}
}
return value;
};
/**
* toHex(n, cch)
*
* Converts an integer to hex, with the specified number of digits (up to the default of 8).
*
* You might be tempted to use the built-in n.toString(16) instead, but it doesn't zero-pad and it
* doesn't properly convert negative values; for example, if n is -2147483647, then n.toString(16)
* will return "-7fffffff" instead of "80000001". Moreover, if n is undefined, n.toString() will throw
* an exception, whereas toHex() will simply return '?' characters.
*
* NOTE: The following work-around (adapted from code found on StackOverflow) would be another solution,
* taking care of negative values, zero-padding, and upper-casing, but not undefined/NaN values:
*
* s = (n < 0? (0xffffffff + n + 1) : n).toString(16);
* s = "00000000".substr(0, 8 - s.length) + s;
* s = s.substr(0, cch).toUpperCase();
*
* @param {number|undefined} n is a 32-bit value
* @param {number} [cch] is the desired number of hex digits (8 is both the default and the maximum)
* @return {string} the hex representation of n
*/
str.toHex = function(n, cch)
{
var s = "";
if (cch === undefined) {
cch = 8;
} else {
if (cch > 8) cch = 8;
}
if (isNaN(n)) { // detects BOTH NaN and undefined
while (cch-- > 0) s = '?' + s;
} else {
while (cch-- > 0) {
var d = n & 0xf;
d += (d >= 0 && d <= 9? 0x30 : 0x41 - 10);
s = String.fromCharCode(d) + s;
n >>= 4;
}
}
return s;
};
/**
* toHexByte(b)
*
* Alias for toHex(b, 2)
*
* @param {number|undefined} b is a byte value
* @return {string} the hex representation of b
*/
str.toHexByte = function(b)
{
return str.toHex(b, 2);
};
/**
* toHexWord(w)
*
* Alias for toHex(w, 4)
*
* @param {number|undefined} w is a word (16-bit) value
* @return {string} the hex representation of w
*/
str.toHexWord = function(w)
{
return str.toHex(w, 4);
};
/**
* toHexAddr(off, sel)
*
* @param {number} off
* @param {number} [sel]
* @return {string} the hex representation of sel:off
*/
str.toHexAddr = function(off, sel)
{
if (sel !== undefined) {
return str.toHexWord(sel) + ":" + str.toHexWord(off);
}
return str.toHex(off);
};
/**
* getBaseName(sFileName, fStripExt)
*
* This is a poor-man's version of Node's path.basename(), which Node-only components should use instead.
*
* Note that fStripExt can be used to strip ANY extension, whereas path.basename() will strip the extension only
* if it matches the second parameter (eg, path.basename("/foo/bar/baz/asdf/quux.html", ".html") returns "quux").
*
* @param {string} sFileName
* @param {boolean} [fStripExt]
* @return {string}
*/
str.getBaseName = function(sFileName, fStripExt)
{
var sBaseName = sFileName;
var i = sFileName.lastIndexOf("/");
if (i >= 0) {
sBaseName = sFileName.substr(i + 1);
}
if (fStripExt) {
i = sBaseName.lastIndexOf(".");
if (i > 0) {
sBaseName = sBaseName.substring(0, i);
}
}
return sBaseName;
};
/**
* getExtension(sFileName)
*
* This is a poor-man's version of Node's path.extname(), which Node-only components should use instead.
*
* Note that we EXCLUDE the period from the returned extension, whereas path.extname() includes it.
*
* @param {string} sFileName
* @return {string} the filename's extension (in lower-case and EXCLUDING the "."), or an empty string
*/
str.getExtension = function(sFileName)
{
var sExtension = "";
var i = sFileName.lastIndexOf(".");
if (i >= 0) {
sExtension = sFileName.substr(i + 1).toLowerCase();
}
return sExtension;
};
/**
* endsWith(s, sSuffix)
*
* @param {string} s
* @param {string} sSuffix
* @return {boolean} true if s ends with sSuffix, false if not
*/
str.endsWith = function(s, sSuffix)
{
return s.indexOf(sSuffix, s.length - sSuffix.length) !== -1;
};
str.aHTMLEscapeMap = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
/**
* escapeHTML(sHTML)
*
* @param {string} sHTML
* @return {string} with HTML entities "escaped", similar to PHP's htmlspecialchars()
*/
str.escapeHTML = function(sHTML)
{
return sHTML.replace(/[&<>"']/g, function(m) {
return str.aHTMLEscapeMap[m];
});
};
/**
* replaceAll(sFind, sReplace, s)
*
* @param {string} sFind
* @param {string} sReplace
* @param {string} s
* @return {string}
*/
str.replaceAll = function(sFind, sReplace, s)
{
var a = {};
a[sFind] = sReplace;
return str.replaceArray(a, s);
};
/**
* replaceArray(a, s)
*
* @param {Object} a
* @param {string} s
* @return {string}
*/
str.replaceArray = function(a, s)
{
var sMatch = "";
for (var k in a) {
k = k.replace(/([\\\[\]\*\{}\(\)\.\+\?])/g, "\\$1");
sMatch += (sMatch? '|' : '') + k;
}
return s.replace(new RegExp('(' + sMatch + ')', "g"), function(m) {
return a[m];
});
};
/**
* pad(s, cch)
*
* Note that the maximum amount of padding currently supported is 40 spaces.
*
* @param {string} s is a string
* @param {number} cch is desired length
* @returns {string} the original string (s) with spaces padding it to the specified length
*/
str.pad = function(s, cch)
{
return s + " ".substr(0, cch - s.length);
};
/**
* trim(s)
*
* @param {string} s
* @returns {string}
*/
str.trim = function(s)
{
if (String.prototype.trim) {
return s.trim();
}
return s.replace(/^\s+|\s+$/g, "");
};
if (typeof module !== 'undefined') module.exports = str;

View file

@ -0,0 +1,73 @@
/**
* @fileoverview User API, as defined by httpapi.js
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
* @version 1.0
* Created 2014-May-13
*
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of the JavaScript Machines Project (aka JSMachines) at <http://jsmachines.net/>
* and <http://pcjs.org/>.
*
* JSMachines is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with JSMachines.
* If not, see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see Computer.sCopyright).
*
* Some JSMachines files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of the
* JSMachines Project for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
"use strict";
/*
* Examples of User API requests:
*
* web.getHost() + UserAPI.ENDPOINT + '?' + UserAPI.QUERY.REQ + '=' + UserAPI.REQ.VERIFY + '&' + UserAPI.QUERY.USER + '=' + sUser;
*/
var UserAPI = {
ENDPOINT: "/api/v1/user",
QUERY: {
REQ: "req", // specifies a request
USER: "user", // specifies a user ID
STATE: "state", // specifies a state ID
DATA: "data" // specifies state data
},
REQ: {
CREATE: "create", // creates a user ID
VERIFY: "verify", // requests verification of a user ID
STORE: "store", // stores a machine state on the server
LOAD: "load" // loads a machine state from the server
},
RES: {
CODE: "code",
DATA: "data"
},
CODE: {
OK: "ok",
FAIL: "error"
},
FAIL: {
DUPLICATE: "user already exists",
VERIFY: "unable to verify user",
BADSTATE: "invalid state parameter",
NOSTATE: "no machine state",
BADLOAD: "unable to load machine state",
BADSTORE: "unable to save machine state"
}
};
if (typeof module !== 'undefined') module.exports = UserAPI;

View file

@ -0,0 +1,212 @@
/**
* @fileoverview Assorted helper functions
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a> (@jeffpar)
* @version 1.0
* Created 2014-03-09
*
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of the JavaScript Machines Project (aka JSMachines) at <http://jsmachines.net/>
* and <http://pcjs.org/>.
*
* JSMachines is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with JSMachines.
* If not, see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see Computer.sCopyright).
*
* Some JSMachines files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of the
* JSMachines Project for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
"use strict";
var usr = {};
/**
* binarySearch(a, v, fnCompare)
*
* @param {Array} a is an array
* @param {number|string|Array|Object} v
* @param {function((number|string), (number|string))} [fnCompare]
* @return {number} the index of matching entry if non-negative, otherwise the index of the insertion point
*/
usr.binarySearch = function(a, v, fnCompare) {
var left = 0;
var right = a.length;
var found = 0;
if (fnCompare === undefined) {
fnCompare = function(a, b) {
return a > b? 1 : a < b? -1 : 0;
};
}
while (left < right) {
var middle = (left + right) >> 1;
var compareResult;
compareResult = fnCompare(v, a[middle]);
if (compareResult > 0) {
left = middle + 1;
} else {
right = middle;
found = !compareResult;
}
}
return found? left : ~left;
};
/**
* binaryInsert(a, v, fnCompare)
*
* If element v already exists in array a, the array is unchanged (we don't allow duplicates); otherwise, the
* element is inserted into the array at the appropriate index.
*
* @param {Array} a is an array
* @param {number|string|Array|Object} v is the value to insert
* @param {function((number|string), (number|string))} [fnCompare]
*/
usr.binaryInsert = function(a, v, fnCompare) {
var index = usr.binarySearch(a, v, fnCompare);
if (index < 0) {
a.splice(-(index + 1), 0, v);
}
};
/**
* getTime()
*
* @return {number} the current time, in milliseconds
*/
usr.getTime = function() {
return Date.now() || +new Date();
};
/**
* getTimestamp()
*
* @return {string} timestamp containing the current date and time ("yyyy-mm-dd hh:mm:ss")
*/
usr.getTimestamp = function() {
var date = new Date();
var padNum = function(n) {
return (n < 10? "0" : "") + n;
};
return date.getFullYear() + "-" + padNum(date.getMonth() + 1) + "-" + padNum(date.getDate()) + " " + padNum(date.getHours()) + ":" + padNum(date.getMinutes()) + ":" + padNum(date.getSeconds());
};
/**
* getMonthDays(nMonth, nYear)
*
* Note that if we're being called on behalf of the RTC, its year is always truncated to two digits (mod 100),
* so we have no idea what century the year 0 might refer to. When using the normal leap-year formula, 0 fails
* the mod 100 test but passes the mod 400 test, so as far as the RTC is concerned, every century year is a leap
* year. Since we're most likely dealing with the year 2000, that's fine, since 2000 was also a leap year.
*
* TODO: There IS a separate CMOS byte that's supposed to be set to CMOS_ADDR.CENTURY_DATE; it's always BCD,
* so theoretically it will contain values like 0x19 or 0x20 (for the 20th and 21st centuries, respectively), and
* we could add that as another parameter to this function, to improve the accuracy, but that would go beyond what
* a real RTC actually does.
*
* @param {number} nMonth (1-12)
* @param {number} nYear (normally a 4-digit year, but it may also be mod 100)
* @return {number} the maximum (1-based) day allowed for the specified month and year
*/
usr.getMonthDays = function(nMonth, nYear)
{
var nDays = usr.aMonthDays[nMonth - 1];
if (nDays == 28) {
if ((nYear % 4) === 0 && ((nYear % 100) || (nYear % 400) === 0)) {
nDays++;
}
}
return nDays;
};
usr.asDays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
usr.asMonths = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
usr.aMonthDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
/**
* formatDate(sFormat, date)
*
* @param {string} sFormat (eg, "F j, Y", "Y-m-d H:i:s")
* @param {Date} [date] (default is the current time)
* @return {string}
*
* Supported identifiers in sFormat include:
*
* a: lowercase ante meridiem and post meridiem (am or pm)
* d: day of the month, 2 digits with leading zeros (01,...,31)
* g: hour in 12-hour format, without leading zeros (1,...,12)
* i: minutes, with leading zeros (00,...,59)
* j: day of the month, without leading zeros (1,...,31)
* l: day of the week ("Sunday",...,"Saturday")
* m: month, with leading zeros (01,...,12)
* s: seconds, with leading zeros (00,...,59)
* F: month ("January",...,"December")
* H: hour in 24-hour format, with leading zeros (00,...,23)
* Y: year (eg, 2014)
*
* For more inspiration, see: http://php.net/manual/en/function.date.php
*/
usr.formatDate = function(sFormat, date) {
var sDate = "";
if (!date) date = new Date();
var iHour = date.getHours();
var iDay = date.getDate();
var iMonth = date.getMonth() + 1;
for (var i = 0; i < sFormat.length; i++) {
var ch;
switch((ch = sFormat.charAt(i))) {
case 'a':
sDate += (iHour < 12? "am" : "pm");
break;
case 'd':
sDate += ('0' + iDay).slice(-2);
break;
case 'g':
sDate += (!iHour? 12 : (iHour > 12? iHour - 12 : iHour));
break;
case 'i':
sDate += ('0' + date.getMinutes()).slice(-2);
break;
case 'j':
sDate += iDay;
break;
case 'l':
sDate += usr.asDays[date.getDay()];
break;
case 'm':
sDate += ('0' + iMonth).slice(-2);
break;
case 's':
sDate += ('0' + date.getSeconds()).slice(-2);
break;
case 'F':
sDate += usr.asMonths[iMonth - 1];
break;
case 'H':
sDate += ('0' + iHour).slice(-2);
break;
case 'Y':
sDate += date.getFullYear();
break;
default:
sDate += ch;
break;
}
}
return sDate;
};
if (typeof module !== 'undefined') module.exports = usr;

View file

@ -0,0 +1,647 @@
/**
* @fileoverview Browser-related helper functions
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a> (@jeffpar)
* @version 1.0
* Created 2014-05-08
*
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of the JavaScript Machines Project (aka JSMachines) at <http://jsmachines.net/>
* and <http://pcjs.org/>.
*
* JSMachines is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with JSMachines.
* If not, see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see Computer.sCopyright).
*
* Some JSMachines files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of the
* JSMachines Project for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
/*
* According to http://www.w3schools.com/jsref/jsref_obj_global.asp, these are the *global* properties
* and functions of JavaScript-in-the-Browser:
*
* Property Description
* ---
* Infinity A numeric value that represents positive/negative infinity
* NaN "Not-a-Number" value
* undefined Indicates that a variable has not been assigned a value
*
* Function Description
* ---
* decodeURI() Decodes a URI
* decodeURIComponent() Decodes a URI component
* encodeURI() Encodes a URI
* encodeURIComponent() Encodes a URI component
* escape() Deprecated in version 1.5. Use encodeURI() or encodeURIComponent() instead
* eval() Evaluates a string and executes it as if it was script code
* isFinite() Determines whether a value is a finite, legal number
* isNaN() Determines whether a value is an illegal number
* Number() Converts an object's value to a number
* parseFloat() Parses a string and returns a floating point number
* parseInt() Parses a string and returns an integer
* String() Converts an object's value to a string
* unescape() Deprecated in version 1.5. Use decodeURI() or decodeURIComponent() instead
*
* And according to http://www.w3schools.com/jsref/obj_window.asp, these are the properties and functions
* of the *window* object.
*
* Property Description
* ---
* closed Returns a Boolean value indicating whether a window has been closed or not
* defaultStatus Sets or returns the default text in the statusbar of a window
* document Returns the Document object for the window (See Document object)
* frames Returns an array of all the frames (including iframes) in the current window
* history Returns the History object for the window (See History object)
* innerHeight Returns the inner height of a window's content area
* innerWidth Returns the inner width of a window's content area
* length Returns the number of frames (including iframes) in a window
* location Returns the Location object for the window (See Location object)
* name Sets or returns the name of a window
* navigator Returns the Navigator object for the window (See Navigator object)
* opener Returns a reference to the window that created the window
* outerHeight Returns the outer height of a window, including toolbars/scrollbars
* outerWidth Returns the outer width of a window, including toolbars/scrollbars
* pageXOffset Returns the pixels the current document has been scrolled (horizontally) from the upper left corner of the window
* pageYOffset Returns the pixels the current document has been scrolled (vertically) from the upper left corner of the window
* parent Returns the parent window of the current window
* screen Returns the Screen object for the window (See Screen object)
* screenLeft Returns the x coordinate of the window relative to the screen
* screenTop Returns the y coordinate of the window relative to the screen
* screenX Returns the x coordinate of the window relative to the screen
* screenY Returns the y coordinate of the window relative to the screen
* self Returns the current window
* status Sets or returns the text in the statusbar of a window
* top Returns the topmost browser window
*
* Method Description
* ---
* alert() Displays an alert box with a message and an OK button
* atob() Decodes a base-64 encoded string
* blur() Removes focus from the current window
* btoa() Encodes a string in base-64
* clearInterval() Clears a timer set with setInterval()
* clearTimeout() Clears a timer set with setTimeout()
* close() Closes the current window
* confirm() Displays a dialog box with a message and an OK and a Cancel button
* createPopup() Creates a pop-up window
* focus() Sets focus to the current window
* moveBy() Moves a window relative to its current position
* moveTo() Moves a window to the specified position
* open() Opens a new browser window
* print() Prints the content of the current window
* prompt() Displays a dialog box that prompts the visitor for input
* resizeBy() Resizes the window by the specified pixels
* resizeTo() Resizes the window to the specified width and height
* scroll() This method has been replaced by the scrollTo() method.
* scrollBy() Scrolls the content by the specified number of pixels
* scrollTo() Scrolls the content to the specified coordinates
* setInterval() Calls a function or evaluates an expression at specified intervals (in milliseconds)
* setTimeout() Calls a function or evaluates an expression after a specified number of milliseconds
* stop() Stops the window from loading
*/
"use strict";
/* global window: true, setTimeout: false, clearTimeout: false, SITEHOST: false */
/*
* We must defer loading the Component module until the function(s) requiring it are
* called; otherwise, we create an initialization cycle in which Component requires weblib
* and weblib requires Component.
*
* In an ideal world, weblib would not be dependent on Component, but we really want to use
* its logging functions.
*/
if (typeof module !== 'undefined') {
var Component;
require("./defines");
var str = require("./strlib");
var ReportAPI = require("./reportapi");
}
var web = {};
/**
* loadResource(sURL, fAsync, data, componentNotify, fnNotify, pNotify)
*
* Request the specified resource (sURL), and once the request is complete,
* optionally call the specified method (fnNotify) of the specified component (componentNotify).
*
* TODO: Figure out how we can strongly type the fnNotify parameter, because the Closure Compiler has issues with:
*
* {function(this:Component, string, (string|null), number, (number|string|null|Object|Array|undefined))} [fnNotify]
*
* @param {string} sURL
* @param {boolean} [fAsync] is true for an asynchronous request
* @param {Object|null} [data] for a POST request (default is a GET request)
* @param {Component} [componentNotify]
* @param {function(...)} [fnNotify]
* @param {number|string|null|Object|Array} [pNotify] optional fnNotify info parameter
* @return {Array} containing errorCode and responseText (empty array if fAsync is true)
*/
web.loadResource = function(sURL, fAsync, data, componentNotify, fnNotify, pNotify)
{
fAsync = !!fAsync; // ensure that fAsync is a valid boolean (Internet Explorer xmlHTTP functions insist on it)
if (typeof module !== 'undefined') {
/*
* We don't even need to load Component, because we can't use any of the code below
* within Node anyway. Instead, we must hand this request off to our network library.
*
* if (!Component) Component = require("./component");
*/
var net = require("./netlib");
return net.loadResource(sURL, fAsync, data, componentNotify, fnNotify, pNotify);
}
var nErrorCode = 0;
var sURLData = null;
var sURLName = str.getBaseName(sURL);
var xmlHTTP = (window.XMLHttpRequest? new window.XMLHttpRequest() : new window.ActiveXObject("Microsoft.XMLHTTP"));
if (fAsync) {
xmlHTTP.onreadystatechange = function() {
if (xmlHTTP.readyState === 4) {
/*
* The following line is recommended for WebKit, as a work-around to prevent the handler firing multiple
* times when debugging. Unfortunately, that's not the only XMLHttpRequest problem that occurs when
* debugging, so I think the WebKit problem is deeper than that. When we have multiple XMLHttpRequests
* pending, any debugging activity means most of them simply get dropped on floor, so what may actually be
* happening are mis-notifications rather than redundant notifications.
*/
xmlHTTP.onreadystatechange = undefined;
sURLData = xmlHTTP.responseText;
if (xmlHTTP.status == 200) {
Component.log("xmlHTTP.onreadystatechange(" + sURL + "): returned " + sURLData.length + " bytes");
}
else {
nErrorCode = xmlHTTP.status || -1;
Component.log("xmlHTTP.onreadystatechange(" + sURL + "): error code " + nErrorCode);
}
if (componentNotify && fnNotify) fnNotify.call(componentNotify, sURLName, sURLData, nErrorCode, pNotify);
}
};
}
if (data) {
var sData = "";
for (var p in data) {
if (!data.hasOwnProperty(p)) continue;
if (sData) sData += "&";
sData += p + '=' + encodeURIComponent(data[p]);
}
sData = sData.replace(/%20/g, '+');
Component.log("web.loadResource(POST " + sURL + "): " + sData.length + " bytes");
xmlHTTP.open("POST", sURL, fAsync);
xmlHTTP.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xmlHTTP.send(sData);
} else {
Component.log("web.loadResource(GET " + sURL + ")");
xmlHTTP.open("GET", sURL, fAsync);
xmlHTTP.send();
}
var response = [];
if (!fAsync) {
sURLData = xmlHTTP.responseText;
if (xmlHTTP.status == 200) {
Component.log("web.loadResource(" + sURL + "): returned " + sURLData.length + " bytes");
} else {
nErrorCode = xmlHTTP.status || -1;
Component.log("web.loadResource(" + sURL + "): error code " + nErrorCode);
}
if (componentNotify && fnNotify) fnNotify.call(componentNotify, sURLName, sURLData, nErrorCode, pNotify);
response = [nErrorCode, sURLData];
}
return response;
};
/**
* sendReport(sApp, sVer, sURL, sUser, sType, sReport, sHostName)
*
* Send a report (eg, bug report) to the server.
*
* @param {string} sApp (eg, "PCjs")
* @param {string} sVer (eg, "1.02")
* @param {string} sURL (eg, "/configs/pc/machines/5150/mda/64kb/index.xml")
* @param {string} sUser (ie, the user key, if any)
* @param {string} sType (eg, "bug"); one of ReportAPI.TYPE.*
* @param {string} sReport (eg, unparsed state data)
* @param {string} [sHostName] (default is http://SITEHOST)
*/
web.sendReport = function(sApp, sVer, sURL, sUser, sType, sReport, sHostName)
{
var data = {};
data[ReportAPI.QUERY.APP] = sApp;
data[ReportAPI.QUERY.VER] = sVer;
data[ReportAPI.QUERY.URL] = sURL;
data[ReportAPI.QUERY.USER] = sUser;
data[ReportAPI.QUERY.TYPE] = sType;
data[ReportAPI.QUERY.DATA] = sReport;
var sReportURL = (sHostName? sHostName : "http://" + SITEHOST) + ReportAPI.ENDPOINT;
web.loadResource(sReportURL, true, data);
};
/**
* getHost()
*
* @return {string}
*/
web.getHost = function()
{
return ("http://" + (window? window.location.host : SITEHOST));
};
/**
* getHostURL()
*
* @return {string|null}
*/
web.getHostURL = function()
{
return (window? window.location.href : null);
};
/**
* getUserAgent()
*
* @return {string}
*/
web.getUserAgent = function()
{
return (window? window.navigator.userAgent : "");
};
/**
* alertUser(sMessage)
*
* @param {string} sMessage
*/
web.alertUser = function(sMessage)
{
if (window) {
window.alert(sMessage);
} else {
console.log(sMessage);
}
};
/**
* confirmUser(sPrompt)
*
* @param {string} sPrompt
* @returns {boolean} true if the user clicked OK, false if Cancel/Close
*/
web.confirmUser = function(sPrompt)
{
var fResponse = false;
if (window) {
fResponse = window.confirm(sPrompt);
}
return fResponse;
};
/**
* promptUser()
*
* @param {string} sPrompt
* @param {string} [sDefault]
* @returns {string|null}
*/
web.promptUser = function(sPrompt, sDefault)
{
var sResponse = null;
if (window) {
sResponse = window.prompt(sPrompt, sDefault === undefined? "" : sDefault);
}
return sResponse;
};
/**
* getLocalStorageItem(sKey)
*
* Returns the requested key value, or null if the key does not exist, or undefined if localStorage is not available
*
* @param {string} sKey
* @return {string|null|undefined} sValue
*/
web.getLocalStorageItem = function(sKey)
{
var sValue;
if (window) {
sValue = window.localStorage.getItem(sKey);
}
return sValue;
};
/**
* setLocalStorageItem(sKey, sValue)
*
* @param {string} sKey
* @param {string} sValue
* return {boolean} true if localStorage is available, false if not
*/
web.setLocalStorageItem = function(sKey, sValue)
{
if (window) {
window.localStorage.setItem(sKey, sValue);
return true;
}
return false;
};
/**
* reloadPage()
*/
web.reloadPage = function()
{
if (window) window.location.reload();
};
/**
* isUserAgent(s)
*
* Check the browser's user-agent string for the given substring; "iOS" and "MSIE" are special values you can
* use that will match any iOS or MSIE browser, respectively (even IE11, in the case of "MSIE").
*
* 2013-11-06: In a questionable move, MSFT changed the user-agent reported by IE11 on Windows 8.1, eliminating
* the "MSIE" string (which MSDN calls a "version token"; see http://msdn.microsoft.com/library/ms537503.aspx);
* they say "public websites should rely on feature detection, rather than browser detection, in order to design
* their sites for browsers that don't support the features used by the website." So, in IE11, we get a user-agent
* that tries to fool apps into thinking the browser is more like WebKit or Gecko:
*
* Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko
*
* That's a nice idea, but in the meantime, they hosed the XSL transform code in embed.js, which contained
* some very critical browser-specific code; turning on IE's "Compatibility Mode" didn't help either, because
* that's a sledgehammer solution which restores the old user-agent string but also disables other features like
* HTML5 canvas support. As an interim solution, I'm treating any "MSIE" check as a check for either "MSIE" or
* "Trident".
*
* UPDATE: I've since found ways to make the code in embed.js more browser-agnostic, so for now, there's isn't
* any code that cares about "MSIE", but I've left the change in place, because I wouldn't be surprised if I'll
* need more IE-specific code in the future, perhaps for things like copy/paste functionality, or mouse capture.
*
* @param {string} s is a substring to search for in the user-agent; as noted above, "iOS" and "MSIE" are special values
* @return {boolean} is true if the string was found, false if not
*/
web.isUserAgent = function(s)
{
if (window) {
var userAgent = web.getUserAgent();
/*
* Here's one case where we have to be careful with Component, because when isUserAgent() is called by
* the init code below, component.js hasn't been loaded yet. The simplest solution is to remove the call.
*
* if (Component) Component.log("agent: " + userAgent);
*
* And yes, it would be pointless to use the conditional (?) operator below, if not for the Google Closure
* Compiler (v20130823) failing to detect the entire expression as a boolean.
*/
return (s == "iOS" && userAgent.match(/(iPod|iPhone|iPad)/) && userAgent.match(/AppleWebKit/) || s == "MSIE" && userAgent.match(/(MSIE|Trident)/) || (userAgent.indexOf(s) >= 0))? true : false;
}
return false;
};
/**
* getURLParameters(sParms)
*
* @param {string} [sParms] containing the parameter portion of a URL (ie, after the '?')
* @return {Object} containing properties for each parameter found
*/
web.getURLParameters = function(sParms)
{
var aParms = {};
if (window) { // an alternative to "if (typeof module === 'undefined')" if require("defines") has been invoked
if (!sParms) {
/*
* Note that window.location.href returns the entire URL, whereas window.location.search returns
* only the parameters, if any (starting with the '?', which we skip over with a substr() call).
*/
sParms = window.location.search.substr(1);
}
var match;
var pl = /\+/g; // RegExp for replacing addition symbol with a space
var search = /([^&=]+)=?([^&]*)/g;
var decode = function(s) { return decodeURIComponent(s.replace(pl, " ")); };
while ((match = search.exec(sParms))) {
aParms[decode(match[1])] = decode(match[2]);
}
}
return aParms;
};
/**
* onCountRepeat(n, fn, fnComplete, msDelay)
*
* Call fn() n times with an msDelay millisecond delay between calls, then
* call fnComplete() when the count has been exhausted OR fn() returns false.
*
* @param {number} n
* @param {function()} fn
* @param {function()} fnComplete
* @param {number} [msDelay]
*/
web.onCountRepeat = function(n, fn, fnComplete, msDelay)
{
var fnRepeat = function doCountRepeat() {
n -= 1;
if (n >= 0) {
if (!fn()) n = 0;
}
if (n > 0) {
setTimeout(fnRepeat, msDelay || 0);
return;
}
fnComplete();
};
fnRepeat();
};
/**
* onClickRepeat(e, msDelay, msRepeat, fn)
*
* Repeatedly call fn() with an initial msDelay, and an msRepeat delay thereafter,
* as long as HTML control Object e has an active "down" event and fn() returns true.
*
* @param {Object} e
* @param {number} msDelay
* @param {number} msRepeat
* @param {function(boolean)} fn is passed false on the first call, true on all repeated calls
*/
web.onClickRepeat = function(e, msDelay, msRepeat, fn)
{
var ms = 0, timer = null, fIgnoreMouseEvents = false;
var fnRepeat = function doClickRepeat() {
if (fn(ms === msRepeat)) {
timer = setTimeout(fnRepeat, ms);
ms = msRepeat;
}
};
e.onmousedown = function() {
// Component.println("onMouseDown()");
if (!fIgnoreMouseEvents) {
if (!timer) {
ms = msDelay;
fnRepeat();
}
}
};
e.ontouchstart = function() {
// Component.println("onTouchStart()");
if (!timer) {
ms = msDelay;
fnRepeat();
}
};
e.onmouseup = e.onmouseout = function() {
// Component.println("onMouseUp()/onMouseOut()");
if (timer) {
clearTimeout(timer);
timer = null;
}
};
e.ontouchend = e.ontouchcancel = function() {
// Component.println("onTouchEnd()/onTouchCancel()");
if (timer) {
clearTimeout(timer);
timer = null;
}
/*
* Devices that generate ontouch* events ALSO generate onmouse* events,
* and generally do so immediately after all the touch events are complete,
* so unless we want double the action, we need to ignore mouse events.
*/
fIgnoreMouseEvents = true;
};
};
web.aPageEventHandlers = {
'init': [], // list of window 'onload' handlers
'show': [], // list of window 'onpageshow' handlers
'exit': [] // list of window 'onunload' handlers (although we prefer to use 'onbeforeunload' if possible)
};
web.fPageEventsEnabled = true;
/**
* onPageEvent(sName, fn)
*
* @param {string} sFunc
* @param {function()} fn
*
* Use this instead of setting window['onunload'], window['onunload'], etc.
* Allows multiple JavaScript modules to define a handler for the same event.
*
* Moreover, it's risky to refer to obscure event handlers with "dot" names, because
* the Closure Compiler may erroneously replace them (eg, window.onpageshow is a good example).
*/
web.onPageEvent = function(sFunc, fn)
{
if (window) {
var fnPrev = window[sFunc];
if (typeof fnPrev !== 'function') {
window[sFunc] = fn;
} else {
/*
* TODO: Determine whether there's any value in receiving/sending the Event object that the
* browser provides when it generates the original event.
*/
window[sFunc] = function onWindowEvent() {
if (fnPrev) fnPrev();
fn();
};
}
}
};
/**
* onInit(fn)
*
* @param {function()} fn
*
* Use this instead of setting window.onload. Allows multiple JavaScript modules to define their own 'onload' event handler.
*/
web.onInit = function(fn)
{
web.aPageEventHandlers['init'].push(fn);
};
/**
* onShow(fn)
*
* @param {function()} fn
*
* Use this instead of setting window.onpageshow. Allows multiple JavaScript modules to define their own 'onpageshow' event handler.
*/
web.onShow = function(fn)
{
web.aPageEventHandlers['show'].push(fn);
};
/**
* onExit(fn)
*
* @param {function()} fn
*
* Use this instead of setting window.onunload. Allows multiple JavaScript modules to define their own 'onunload' event handler.
*/
web.onExit = function(fn)
{
web.aPageEventHandlers['exit'].push(fn);
};
/**
* doPageEvent(afn)
*
* @param {Array.<function()>} afn
*/
web.doPageEvent = function(afn)
{
if (web.fPageEventsEnabled) {
for (var i = 0; i < afn.length; i++) {
afn[i]();
}
}
};
/**
* enablePageEvents(fEnable)
*
* @param {boolean} fEnable is true to enable page events, false to disable (they're enabled by default)
*/
web.enablePageEvents = function(fEnable)
{
web.fPageEventsEnabled = fEnable;
};
/**
* sendPageEvent(sEvent)
*
* This allows us to manually trigger page events.
*
* @param {string} sEvent (one of 'init', 'show' or 'exit')
*/
web.sendPageEvent = function(sEvent)
{
if (web.aPageEventHandlers[sEvent]) {
web.doPageEvent(web.aPageEventHandlers[sEvent]);
}
};
web.onPageEvent('onload', function onPageLoad() { web.doPageEvent(web.aPageEventHandlers['init']); });
web.onPageEvent('onpageshow', function onPageShow() { web.doPageEvent(web.aPageEventHandlers['show']); });
web.onPageEvent(web.isUserAgent("Opera") || web.isUserAgent("iOS")? 'onunload' : 'onbeforeunload', function onPageUnload() { web.doPageEvent(web.aPageEventHandlers['exit']); });
if (typeof module !== 'undefined') module.exports = web;

View file

@ -0,0 +1 @@
adsense.html

View file

@ -0,0 +1,35 @@
Shared Templates
===
Template folders contain a variety of XML and HTML templates and supporting files, including:
- DTD files (Document Type Definitions)
- XSD files (XML schemas -- eventually)
- XSL files (XML stylesheets)
- CSS files (stylesheets that the XSL files rely upon)
- HTML files (HTML fragments used to generate part or all of a web page)
[*common.html*](common.html) is the HTML template file used by the [HTMLOut](/my_modules/htmlout/) module
to generate a default HTML file ("index.html") for any folder that only has a "README.md", or a "machine.xml",
or none of the above.
[*common.xsl*](common.xsl) is a collection of XSL templates used by *machine.xsl* (and deprecated *outline.xsl*)
files.
[*machine.xsl*](machine.xsl) is an XML stylesheet that takes things a step further and transforms a machine XML
into a stand-alone HTML document, which also includes all necessary compiled scripts (eg, c1p.js, c1p-dbg.js,
pc.js or pc-dbg.js). Most machine XML files explcitly link to this stylesheet, so that simply loading the XML
file in your web browser creates a working virtual machine.
[*manifest.xsl*](manifest.xsl) is an XML stylesheet that renders a software manifest XML file into a standalone
document; it may even contain a referene to a machine XML file.
[*document.xsl*](document.xsl) is a collection of XSL templates used exclusively by [*outline.xsl*](outline.xsl),
which is a more grandiose version of [*machine.xsl*](machine.xsl), designed to support XML-based documentation
that could also contain embedded machine XML files. However, I've since moved away from that approach, in favor
of simple README.md files that are more flexible and work nicely with GitHub. Also, by rendering them with our own
minimalistic Markdown converter, [MarkOut](/my_modules/markout/), it's also very easy to embed virtual machines
in a README.md document.
Since the XML document syntax that [*outline.xsl*](outline.xsl) supports was never very well documented, I'm tempted
to deprecate it and port any XML documents that still rely on it (mostly the older IAS Electronic Computer Project
documents) to Markdown files.

View file

@ -0,0 +1,8 @@
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-49658648-1', 'pcjs.org');
ga('send', 'pageview');
</script>

View file

@ -0,0 +1,267 @@
@CHARSET "UTF-8";
/**
@author Jeff Parsons (@jeffpar)
@website http://www.pcjs.org/
@created 2013-05-05
@modified 2014-02-23
@license http://www.gnu.org/licenses/gpl.html
*/
body {
margin: 0;
background: #1d1d1d;
}
h1, h2 {
margin-top: 0;
color: #cccccc;
}
h1, h2, h3, h4 {
word-wrap: break-word;
}
/*
h3 ~ *:not(h3) {
margin-left: 40px;
}
*/
h4 a {
color: #cccccc !important;
}
p {
line-height: 1.5em;
}
a img {
vertical-align: bottom;
}
pre, code {
color: #000000;
background-color: #cccccc;
font-family: Monaco, Consolas, "Lucida Console", monospace;
font-size: 12px;
}
pre {
margin: 1em 2em;
padding: 1em;
border-radius: 5px;
overflow: auto;
}
code {
padding: 1px;
}
pre a, code a {
color: #006400 !important;
}
.common {
width: 100%;
margin: 0 auto;
color: #cccccc;
}
.common a {
/*color: #80bd01;*/
color: #7fc07f;
text-decoration: none;
}
.common hr {
border-color: #808080;
}
.common a:hover {
text-decoration: underline;
}
.common, .machine {
font-family: "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif;
font-size: 15px;
}
.machine {
margin: 15px;
overflow: hidden;
}
.c1pjs {
overflow: visible;
}
.machine-placeholder {
text-align: center;
font-weight: bold;
}
.common-top {
background: #1d1d1d;
font-size: small;
}
.common-top-left {
float: left;
width: 60%;
}
.common-top-left ul {
line-height: 1.5em;
list-style-type: none;
margin: 0;
padding: 1em 1em 1em 9px;
overflow: hidden;
}
.common-top-left ul li {
display: block;
float: left;
}
.common-top-left ul li a {
border-right: 1px solid #6f6f6f;
padding: 2px 6px 2px 6px;
}
.common-top-left ul li:last-child a {
border-right: none;
}
.common-top-right {
float: right;
width: 40%;
}
.common-top-right p {
float: right;
margin: 0;
padding: 1em;
}
.common-middle {
clear: both;
padding: 1px 1em 1px 1em;
background: #303030;
}
.common-sidebar {
float: left;
font-size: small;
width: 140px; /* should be <= margin-left of common-main */
padding-bottom: 20px;
overflow: hidden;
word-wrap: break-word;
}
.common-list {
list-style-type: none;
margin-top: 0;
margin-bottom: 0;
padding-left: 0;
}
.common-list li {
/*font-variant: small-caps;*/
padding-bottom: 7px;
}
.common-list-data {
list-style-type: none;
margin-top: 0;
margin-bottom: 0;
padding-left: 0;
}
.common-list-data li {
line-height: 1.5em;
}
.common-list-data-items, .common-list-data-subitems {
font-size: x-small;
list-style-type: none;
margin-top: 0;
margin-bottom: 0;
padding-left: 2em;
}
.common-list-data-items li, .common-list-data-subitems li {
padding-bottom: 0;
}
.common-main {
margin-left: 150px;
/* padding-left: 1em;
padding-bottom: 1em;
padding-right: 1em; */
}
.common-image-gallery {
margin: 0 auto;
text-align: center;
}
.common-image-gallery:after {
content: '';
display: block;
}
.common-image-frame {
display: inline-block;
margin: 8px;
text-align: center;
}
.common-image-link {
padding: 5px;
border: 1px solid black;
border-radius: 5px;
background-color: #FAEBD7;
}
.common-image-label {
font-size: x-small;
}
.common-bottom {
clear: both;
padding-top: 1em;
}
.common-bottom:after {
content: '';
display: block;
clear: both;
}
.common-reference {
float: left;
font-size: x-small;
}
.common-reference a {
text-decoration: none;
}
.common-copyright {
float: right;
font-size: x-small;
}
.common-copyright a {
text-decoration: none;
}
.md-list {
}
.md-list li {
line-height: 1.5em;
margin-bottom: 1em;
}
.md-list li p {
padding-left: 2em;
}
.md-list-compact {
}
.md-list-compact li {
margin-bottom: 0;
}
.md-list-none {
list-style-type: none;
padding-left: 2em;
}
.md-list-none li {
margin-bottom: 0;
}
@media screen and (max-width: 900px) {
/*
h3 ~ *:not(h3) {
margin-left: 0;
}
*/
.common-sidebar {
width: 100%;
}
.common-list {
padding-left: 0;
}
.common-list-data {
padding-left: 0;
}
.common-sidebar h4, .common-list li, .common-list-data li, .common-list-data-items li {
width: 130px;
float: left;
overflow: hidden;
vertical-align: top;
padding-right: 1em;
margin-top: 0;
}
.common-list-data-subitems {
display: none;
}
.common-main {
clear: both;
margin-left: 0;
padding-left: 0;
padding-right: 0;
}
.md-list-none {
padding-left: 1em;
}
}

View file

@ -0,0 +1,79 @@
<!DOCTYPE html>
<!-- Some browsers tend to display a white page before displaying one of our dark pages, which causes annoying "flashes";
setting a background color as early as possible seems to help -->
<html style="background-color: #1d1d1d">
<head>
<title>pcjs.org | <!-- pcjs:title("The Original IBM PC In Your Web Browser") --></title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta charset="utf-8">
<!-- We could set content="width=device-width, initial-scale=1", but that apparently causes problems
when rotating to landscape mode; by setting only "initial-scale", the width is apparently inferred -->
<meta name="viewport" content="initial-scale=1">
<link rel="shortcut icon" type="image/x-icon" href="/versions/images/current/favicon.ico">
<link rel="stylesheet" type="text/css" href="/versions/pcjs/<!-- pcjs:version -->/common.css">
<!-- pcjs:sockets -->
</head>
<body>
<!-- Template revision 0037 (increment this number prior to any Git push that should trigger a rebuild of all web pages) -->
<div class="common">
<div class="common-top">
<div class="common-top-left">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/apps/pc/">Apps</a></li>
<li><a href="/disks/pc/">Disks</a></li>
<li><a href="/configs/pc/machines/">Machines</a></li>
<li><a href="/docs/pcjs/">Docs</a></li>
<li><a href="/pubs/">Pubs</a></li>
<li><a href="/blog/">Blog</a></li>
<li><a href="/docs/about/">About</a></li>
</ul>
</div>
<div class="common-top-right">
<p>Powered by <a href="http://nodejs.org" target="_blank">Node.js</a> and <a href="http://aws.amazon.com/about-aws/whats-new/2013/03/11/announcing-aws-elastic-beanstalk-for-node-js/" target="_blank">AWS</a></p>
</div>
</div>
<div class="common-middle">
<h4>Directory of C:\<a href="/">PCJS.ORG</a><!-- pcjs:pcpath --></h4>
<div class="common-sidebar">
<!-- pcjs:dirlist -->
<!-- pcjs:manifest -->
<!-- pcjs:htmlfileLater("adsense.html") -->
</div>
<div class="common-main">
<!-- pcjs:default("*", "common-image") -->
<div class="common-bottom">
<p class="common-reference"><!-- Bottom-left-hand stuff, if there was any, would go here --></p>
<p class="common-copyright">
<!-- When using AWS, the pcjs.org domain must redirect to www.pcjs.org, so let's avoid unnecessary redirects in any absolute URLs -->
<span class="common-copyright"><a href="http://www.pcjs.org/">pcjs.org</a> website © 2012-<!-- pcjs:year --> by <a href="http://twitter.com/jeffpar">@jeffpar</a></span><br/>
<span class="common-copyright">PCjs and C1Pjs released under <a href="http://gnu.org/licenses/gpl.html">GPL version 3 or later</a></span>
</p>
</div>
</div>
</div>
</div>
<script id="randomize" type="text/javascript">
(function() {
var p = document.getElementById('random');
if (p) {
var mags = ['BYTE-1975-11:98', 'MSJ-1986-10:34', 'MSJ-1987-05:90', 'PCTJ-1987-01:216', 'PCTJ-1987-02:222', 'PCTJ-1987-03:214', 'PCTJ-1987-04:218', 'PCTJ-1987-05:238', 'PCTJ-1987-06:236', 'PCTJ-1987-07:230', 'PCTJ-1987-08:250', 'PCTJ-1987-09:252', 'PCTJ-1987-10:231', 'PCTJ-1987-11:261', 'PCTJ-1987-12:242'];
var p2 = document.createElement('p');
p2.appendChild(document.createTextNode('In addition, you see a piece of paper lying on the floor.'));
var div1 = document.createElement('div'); div1.setAttribute('class', 'common-image-gallery');
var div2 = document.createElement('div'); div2.setAttribute('class', 'common-image-frame'); div1.appendChild(div2);
var div3 = document.createElement('div'); div3.setAttribute('class', 'common-image-link'); div2.appendChild(div3);
var mag = mags[Math.floor(Math.random() * mags.length)];
var parts = mag.split(':'), issue = parts[0], page = Math.floor(Math.random() * parseInt(parts[1])) + 1;
parts = mag.split('-');
var name = parts[0].toLowerCase();
var a = document.createElement('a'); a.setAttribute('href', '/pubs/pc/magazines/' + name + '/' + issue + '/#page-' + page); div3.appendChild(a);
var img = document.createElement('img'); img.setAttribute('src', 'http://static.pcjs.org/pubs/pc/magazines/' + name + '/' + issue + '/thumbs/' + issue + ' ' + page + '.jpeg'); img.setAttribute('width', '200'); a.appendChild(img);
p.parentNode.insertBefore(p2, p.nextSibling);
p2.parentNode.insertBefore(div1, p2.nextSibling)
}
})();
</script>
<!-- pcjs:htmlfile("analytics.html") -->
</body>
</html>

View file

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- author="Jeff Parsons (@jeffpar)" website="http://www.pcjs.org/" created="2012-05-05" modified="2014-02-23" license="http://www.gnu.org/licenses/gpl.html" -->
<!DOCTYPE xsl:stylesheet [
<!-- XSLT understands these entities only: lt, gt, apos, quot, and amp. Other required entities may be defined below (see entities.dtd). -->
<!ENTITY nbsp "&#160;"> <!ENTITY ne "&#8800;"> <!ENTITY le "&#8804;"> <!ENTITY ge "&#8805;">
<!ENTITY times "&#215;"> <!ENTITY sdot "&#8901;"> <!ENTITY divide "&#247;">
<!ENTITY copy "&#169;"> <!ENTITY Sigma "&#931;"> <!ENTITY sigma "&#963;"> <!ENTITY sum "&#8721;"> <!ENTITY lbrace "&#123;">
]>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template name="commonStyles">
<meta charset="utf-8"/>
<link rel="shortcut icon" href="/versions/images/current/favicon.ico" type="image/x-icon"/>
<link rel="stylesheet" type="text/css" href="/my_modules/shared/templates/common.css"/>
<!-- script type="text/javascript" src="/versions/jquery/1.7.2/jquery.min.js" -->
</xsl:template>
<xsl:template name="commonTop">
<div class="common-top">
<div class="common-top-left">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/apps/pc/">Apps</a></li>
<li><a href="/disks/pc/">Disks</a></li>
<li><a href="/configs/pc/machines/">Machines</a></li>
<li><a href="/docs/pcjs/">Docs</a></li>
<li><a href="/pubs/">Pubs</a></li>
<li><a href="/blog/">Blog</a></li>
<li><a href="/docs/about/">About</a></li>
</ul>
</div>
<div class="common-top-right">
<p>Powered by <a href="http://nodejs.org" target="_blank">Node.js</a> and <a href="http://aws.amazon.com/about-aws/whats-new/2013/03/11/announcing-aws-elastic-beanstalk-for-node-js/" target="_blank">AWS</a></p>
</div>
</div>
</xsl:template>
<xsl:template name="commonBottom">
<div class="common-bottom">
<p class="common-reference"></p>
<p class="common-copyright">
<span class="common-copyright"><a href="http://www.pcjs.org/">pcjs.org</a> website © 2012-2014 by <a href="http://twitter.com/jeffpar">@jeffpar</a></span><br/>
<span class="common-copyright">PCjs and C1Pjs released under <a href="http://gnu.org/licenses/gpl.html">GPL version 3 or later</a></span>
</p>
</div>
</xsl:template>
</xsl:stylesheet>

View file

@ -0,0 +1,26 @@
addend
chassis
augend
triodes
addends
rectifiers
kirchoff
triode
ohms
shiftable
sensitising
selectron
pentode
timeline
seriatim
operability
performable
deselected
uncompiled
repower
onbeforeunload
onunload
onload
onpageshow
diskette
masochistic

View file

@ -0,0 +1,167 @@
@CHARSET "UTF-8";
/* @author Jeff Parsons (@jeffpar)
@website http://www.pcjs.org/
@created 2013-05-05
@modified 2014-02-23
@license http://www.gnu.org/licenses/gpl.html
*/
.page {
margin: 2% 2%;
padding: 2% 2%;
min-width: 30em;
overflow: auto;
font-size: large;
font-family: Helvetica, Arial, sans-serif;
background: #303030;
color: #ccc;
/* background-color: #63b6fc; */
}
.page-header {
}
.page-header-title {
text-align: center;
/*text-transform: uppercase;*/
}
.page a {
color: #7fc07f;
text-decoration: none;
}
a.footlink, a.paralink {
text-decoration: none;
}
a.footlink:link, a.paralink:link {
color: blue;
}
a.footlink:visited, a.paralink:visited {
color: blue;
}
.galleryitem {
float: left;
width: 200px;
}
.item {
float: left;
width: 2em;
text-indent: 1em;
}
.list {
margin-left: 3em;
text-indent: 0;
text-align: justify;
}
ul {
list-style: none;
}
div.pnumber {
float: left;
width: 2em;
text-indent: 1em;
}
div.pitem {
margin-left: 10em;
}
p.indent, .justified p {
text-indent: 2em;
text-align: justify;
line-height: 1.5em;
}
p.noindent {
text-indent: 0;
text-align: justify;
}
p.center, .center {
text-align: center;
}
li.para {
margin-top: 1em;
margin-bottom: 1em;
}
.left {
text-align: left;
}
.right {
text-align: right;
}
blockquote.tag {
font-size: small;
font-family: Monaco, Fixed, monospace;
margin-top: 0;
margin-bottom: 0;
}
.blockquote {
padding-left: 1em;
text-indent: 0;
text-align: justify;
}
.italics {
font-style: italic;
}
.medium {
font-size: medium;
}
.small {
font-size: x-small;
}
.smallcaps {
font-variant: small-caps;
}
.strike {
text-decoration: line-through;
}
.summation, .bracelist {
display: inline-block;
position: relative;
vertical-align: middle;
text-align: center;
margin-bottom: 0.5ex;
text-indent: 0;
}
.bracelist-symbol {
font-size: 3em;
vertical-align: -40%;
}
.summation .summation-lower, .summation .summation-upper, .bracelist-item {
display: block;
font-size: 75%;
text-align: center;
}
.summation .summation-upper {
margin-bottom: 0;
margin-left: 0.8ex;
font-style: italic;
}
.summation .summation-lower{
margin-bottom: -0.6ex;
font-style: italic;
}
.summation .summation-symbol {
font-size: 2em;
}
p sup {
vertical-align: baseline;
position: relative;
bottom: .5em;
font-size: small;
}
p sub {
vertical-align: baseline;
position: relative;
bottom: -.5em;
font-size: small;
}
.footnote {
font-size: medium;
text-indent: 1em;
text-align: justify;
margin-top: .5em;
}
.image-right {
float: right;
margin-left: 1em;
margin-top: 1em;
margin-bottom: 1em;
}
.image-caption {
font-size: small;
text-align: center;
}

View file

@ -0,0 +1,454 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- author="Jeff Parsons (@jeffpar)" website="http://www.pcjs.org/" created="2012-05-05" modified="2014-02-23" license="http://www.gnu.org/licenses/gpl.html" -->
<!DOCTYPE xsl:stylesheet [
<!-- XSLT understands these entities only: lt, gt, apos, quot, and amp. Other required entities may be defined below (see entities.dtd). -->
<!ENTITY nbsp "&#160;"> <!ENTITY ne "&#8800;"> <!ENTITY le "&#8804;"> <!ENTITY ge "&#8805;">
<!ENTITY times "&#215;"> <!ENTITY sdot "&#8901;"> <!ENTITY divide "&#247;">
<!ENTITY copy "&#169;"> <!ENTITY Sigma "&#931;"> <!ENTITY sigma "&#963;"> <!ENTITY sum "&#8721;"> <!ENTITY lbrace "&#123;">
]>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template name="documentStyles">
<link rel="stylesheet" type="text/css" href="document.css"/>
</xsl:template>
<xsl:template match="title">
<h1><xsl:apply-templates/></h1>
</xsl:template>
<xsl:template name="p">
<xsl:if test="@id">
<a name="{@id}"></a>
</xsl:if>
<xsl:choose>
<xsl:when test="not(@class)">
<p><xsl:apply-templates/></p>
</xsl:when>
<xsl:otherwise>
<p class="{@class}"><xsl:apply-templates/></p>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="p">
<xsl:call-template name="p"/>
</xsl:template>
<xsl:template match="br">
<br/>
</xsl:template>
<xsl:template match="p[@number]">
<div class="pnumber">
<xsl:choose>
<xsl:when test="not(@number) or @number = ''">&nbsp;</xsl:when>
<xsl:otherwise><xsl:value-of select="@number"/></xsl:otherwise>
</xsl:choose>
</div>
<div class="pitem">
<xsl:call-template name="p"/>
</div>
</xsl:template>
<xsl:template match="span">
<xsl:choose>
<xsl:when test="not(@class)">
<span><xsl:apply-templates/></span>
</xsl:when>
<xsl:when test="@class = 'italics'">
<em><xsl:apply-templates/></em>
</xsl:when>
<xsl:otherwise>
<span class="{@class}"><xsl:apply-templates/></span>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="h2">
<h2><xsl:apply-templates/></h2>
</xsl:template>
<xsl:template match="h3">
<h3><xsl:apply-templates/></h3>
</xsl:template>
<xsl:template match="h4">
<h4><xsl:apply-templates/></h4>
</xsl:template>
<xsl:template match="h5">
<h5><xsl:apply-templates/></h5>
</xsl:template>
<xsl:template match="h6">
<h6><xsl:apply-templates/></h6>
</xsl:template>
<xsl:template match="em">
<em><xsl:apply-templates/></em>
</xsl:template>
<xsl:template match="strong">
<strong><xsl:apply-templates/></strong>
</xsl:template>
<xsl:template match="a">
<a href="{@href}" target="{@target}"><xsl:apply-templates/></a>
</xsl:template>
<xsl:template match="ol">
<blockquote><ol><xsl:apply-templates/></ol></blockquote>
</xsl:template>
<xsl:template match="ul">
<blockquote><ul><xsl:apply-templates/></ul></blockquote>
</xsl:template>
<xsl:template match="li">
<li><xsl:apply-templates/></li>
</xsl:template>
<xsl:template match="img">
<div><img src="{@src}" alt="image"/></div>
</xsl:template>
<xsl:template match="pre">
<pre><xsl:apply-templates/></pre>
</xsl:template>
<xsl:template match="figure">
<xsl:choose>
<xsl:when test="@pos">
<div class="{@pos}"><img src="{@ref}" alt="{.}"/><br/><xsl:value-of select="."/></div>
</xsl:when>
<xsl:otherwise>
<div><img src="{@ref}" alt="{.}"/><br/><xsl:value-of select="."/></div>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="sub">
<sub><xsl:apply-templates/></sub>
</xsl:template>
<xsl:template match="sup">
<sup><xsl:apply-templates/></sup>
</xsl:template>
<xsl:template match="lt">&lt;</xsl:template>
<xsl:template match="gt">&gt;</xsl:template>
<xsl:template match="ne">&ne;</xsl:template>
<xsl:template match="le">&le;</xsl:template>
<xsl:template match="ge">&ge;</xsl:template>
<xsl:template match="times">&times;</xsl:template>
<xsl:template match="dot">&sdot;</xsl:template>
<xsl:template match="divide">&divide;</xsl:template>
<xsl:template match="sigma">&sigma;</xsl:template>
<xsl:template match="summation">
<!-- Refer to: http://www.periodni.com/mathematical_and_chemical_equations_on_web.html -->
<span class="summation">
<span class="summation-upper"><xsl:value-of select="@upper"/></span>
<span class="summation-symbol">&sum;</span>
<span class="summation-lower"><xsl:value-of select="@lower"/></span>
</span>
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="bracelist">
<span class="bracelist-symbol">&lbrace;</span>
<span class="bracelist">
<xsl:for-each select="item">
<span class="bracelist-item"><xsl:apply-templates/></span>
</xsl:for-each>
</span>
</xsl:template>
<xsl:template match="footlink">
<xsl:variable name="docID" select="/document/@id"/>
<a class="footlink" id="fn{$docID}_ref{@n}" href="#fn{$docID}_{@n}"><sup><xsl:if test="@quoted"><xsl:text>[</xsl:text></xsl:if><xsl:value-of select="@n"/><xsl:if test="@quoted"><xsl:text>]</xsl:text></xsl:if></sup></a>
</xsl:template>
<xsl:template match="footnote">
<xsl:variable name="docID" select="/document/@id"/>
<div class="footnote"><a id="fn{$docID}_{@n}" href="#fn{$docID}_ref{@n}"><sup><xsl:value-of select="@n"/></sup></a><xsl:text> </xsl:text>
<xsl:apply-templates/>
</div>
</xsl:template>
<xsl:template name="authors">
<xsl:for-each select="author"><xsl:if test="position() != 1"><xsl:text>, </xsl:text></xsl:if><xsl:if test="position() != 1 and position() = last()"><xsl:text>and </xsl:text></xsl:if><xsl:value-of select="."/></xsl:for-each>
</xsl:template>
<xsl:template name="formatDate">
<xsl:param name="date"/>
<xsl:param name="format">MDY</xsl:param>
<!-- date format: YYYY-MM-DD (MM and/or DD can be 00 if unknown) -->
<xsl:variable name="year">
<xsl:value-of select="substring-before($date,'-')"/>
</xsl:variable>
<xsl:variable name="mon-day">
<xsl:value-of select="substring-after($date,'-')"/>
</xsl:variable>
<xsl:variable name="mon">
<xsl:value-of select="substring-before($mon-day,'-')"/>
</xsl:variable>
<xsl:variable name="full-day">
<xsl:value-of select="substring-after($mon-day,'-')"/>
</xsl:variable>
<xsl:variable name="day">
<xsl:choose>
<xsl:when test="substring($full-day,1,1) = '0'"><xsl:value-of select="substring($full-day,2)"/></xsl:when>
<xsl:otherwise><xsl:value-of select="$full-day"/></xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:choose>
<xsl:when test="$mon = '01'">January </xsl:when>
<xsl:when test="$mon = '02'">February </xsl:when>
<xsl:when test="$mon = '03'">March </xsl:when>
<xsl:when test="$mon = '04'">April </xsl:when>
<xsl:when test="$mon = '05'">May </xsl:when>
<xsl:when test="$mon = '06'">June </xsl:when>
<xsl:when test="$mon = '07'">July </xsl:when>
<xsl:when test="$mon = '08'">August </xsl:when>
<xsl:when test="$mon = '09'">September </xsl:when>
<xsl:when test="$mon = '10'">October </xsl:when>
<xsl:when test="$mon = '11'">November </xsl:when>
<xsl:when test="$mon = '12'">December </xsl:when>
<xsl:when test="$mon = '00'"/><!-- do nothing -->
</xsl:choose>
<xsl:if test="$day != '0' and $format = 'MDY'">
<xsl:value-of select="$day"/><xsl:text>, </xsl:text>
</xsl:if>
<xsl:value-of select="$year"/>
</xsl:template>
<xsl:template match="gallery">
<h2><xsl:value-of select="description"/></h2>
<div class="gallery">
<xsl:apply-templates select="item" mode="gallery"/>
</div>
<div style="clear:both;"></div>
</xsl:template>
<xsl:template match="item" mode="gallery">
<div class="galleryitem">
<a href="{@ref}"><img src="/versions/images/current/pdf-192.jpg" alt="{.}"/></a><br/>
<div style="font-size:small; text-align:center;"><xsl:value-of select="."/></div>
</div>
</xsl:template>
<xsl:template match="list[@type = 'timeline']">
<xsl:if test="not(description)">
<h2>Timeline</h2>
</xsl:if>
<xsl:if test="description">
<h2><xsl:value-of select="description"/></h2>
</xsl:if>
<blockquote>
<xsl:apply-templates select="item" mode="timeline"/>
</blockquote>
</xsl:template>
<xsl:template match="item" mode="timeline">
<xsl:if test="@ref">
<xsl:variable name="documentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
<xsl:apply-templates select="document($documentFile)/document" mode="withDate">
<xsl:with-param name="itemRef" select="@ref"/>
</xsl:apply-templates>
</xsl:if>
<xsl:if test="not(@ref)">
<h3><xsl:call-template name="formatDate"><xsl:with-param name="date" select="@date"/></xsl:call-template></h3>
<blockquote>
<xsl:value-of select="."/>
</blockquote>
</xsl:if>
</xsl:template>
<xsl:template match="list[@type = 'people']">
<xsl:if test="not(description)">
<h2>People</h2>
</xsl:if>
<xsl:if test="description">
<h2><xsl:value-of select="description"/></h2>
</xsl:if>
<blockquote>
<xsl:apply-templates select="item" mode="people"/>
</blockquote>
</xsl:template>
<xsl:template match="item" mode="people">
<h3><xsl:value-of select="name"/></h3>
<xsl:apply-templates select="list"/>
</xsl:template>
<xsl:template match="list[@type = 'documents']">
<xsl:if test="description"><h2><xsl:value-of select="description"/></h2></xsl:if>
<ul>
<xsl:apply-templates select="item" mode="document"/>
</ul>
</xsl:template>
<xsl:template match="item" mode="document">
<xsl:variable name="documentFile"><xsl:value-of select="$rootDir"/><xsl:value-of select="@ref"/></xsl:variable>
<xsl:apply-templates select="document($documentFile)/document">
<xsl:with-param name="itemRef" select="@ref"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="document">
<xsl:param name="itemRef"/>
<li>
<xsl:call-template name="documentSummary"><xsl:with-param name="itemRef" select="$itemRef"/></xsl:call-template>
</li>
</xsl:template>
<xsl:template match="document" mode="withDate">
<xsl:param name="itemRef"/>
<h3><xsl:call-template name="formatDate"><xsl:with-param name="date" select="date"/><xsl:with-param name="format" select="MY"/></xsl:call-template></h3>
<blockquote>
<p>
<xsl:call-template name="documentSummary"><xsl:with-param name="itemRef" select="$itemRef"/><xsl:with-param name="multiLine" select="'true'"/></xsl:call-template>
</p>
</blockquote>
</xsl:template>
<xsl:template name="documentSummary">
<xsl:param name="itemRef"/>
<xsl:param name="multiLine">false</xsl:param>
<xsl:choose>
<xsl:when test="content|include">
<a href="{$itemRef}"><xsl:value-of select="title"/></a>
<xsl:if test="@ref">
<span class="small">
<xsl:text> [</xsl:text><a href="{@ref}">Original</a><xsl:text>]</xsl:text>
</span>
</xsl:if>
</xsl:when>
<xsl:otherwise>
<a href="{$itemRef}"><xsl:value-of select="title"/></a>
</xsl:otherwise>
</xsl:choose>
<xsl:if test="copy">
<span class="small">
<xsl:text> [</xsl:text><a href="{copy/@ref}"><xsl:value-of select="copy"/></a><xsl:text>]</xsl:text>
</span>
</xsl:if>
<xsl:if test="author"><xsl:if test="$multiLine = 'true'"><br/></xsl:if><span class="medium"><xsl:text> by </xsl:text><xsl:call-template name="authors"/></span></xsl:if>
<xsl:if test="source">
<span class="small">
<br/>
<xsl:text>[Source: </xsl:text>
<xsl:if test="site">
<a href="{site/@url}"><xsl:value-of select="site"/></a>
</xsl:if>
<xsl:if test="not(site)">
<a href="{source/@url}"><xsl:value-of select="source"/></a>
</xsl:if>
<xsl:text>]</xsl:text>
</span>
</xsl:if>
</xsl:template>
<xsl:template match="list[@type = 'resources']">
<xsl:if test="not(description)">
<h2>Resources</h2>
</xsl:if>
<xsl:if test="description">
<h2><xsl:value-of select="description"/></h2>
</xsl:if>
<blockquote>
<xsl:apply-templates select="item" mode="resources"/>
</blockquote>
</xsl:template>
<xsl:template match="item" mode="resources">
<h3><xsl:value-of select="description"/></h3>
<xsl:apply-templates select="list"/>
</xsl:template>
<xsl:template match="list[@type = 'links']">
<xsl:if test="description">
<h4><xsl:value-of select="description"/></h4>
</xsl:if>
<ul>
<xsl:apply-templates select="item" mode="links"/>
</ul>
</xsl:template>
<xsl:template match="item" mode="links">
<li><a href="{@ref}"><xsl:value-of select="."/></a></li>
</xsl:template>
<xsl:template match="list[not(@type)]">
<xsl:if test="description">
<h2><xsl:value-of select="description"/></h2>
</xsl:if>
<blockquote>
<xsl:apply-templates select="item|tag" mode="outer"/>
</blockquote>
</xsl:template>
<xsl:template match="item" mode="outer">
<xsl:if test="description">
<h3><xsl:value-of select="description"/></h3>
</xsl:if>
<xsl:apply-templates select="list|item|tag" mode="inner"/>
</xsl:template>
<xsl:template match="list" mode="inner">
<xsl:if test="description">
<h4><xsl:value-of select="description"/></h4>
</xsl:if>
<ul>
<xsl:apply-templates select="list|item|para|tag" mode="inner"/>
</ul>
</xsl:template>
<xsl:template name="innerlist">
<xsl:if test="description">
<xsl:value-of select="description"/>
</xsl:if>
<ul>
<xsl:apply-templates select="list|item|para|tag" mode="inner"/>
</ul>
</xsl:template>
<xsl:template match="item" mode="inner">
<xsl:choose>
<xsl:when test="@ref">
<li><a href="{@ref}"><xsl:apply-templates/></a></li>
</xsl:when>
<xsl:when test="description">
<li><xsl:call-template name="innerlist"/></li>
</xsl:when>
<xsl:otherwise>
<li><xsl:apply-templates/></li>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="para" mode="inner">
<li class="para"><xsl:apply-templates/></li>
</xsl:template>
<xsl:template match="tag" mode="outer">
<xsl:call-template name="tag"/>
</xsl:template>
<xsl:template match="tag" mode="inner">
<xsl:call-template name="tag"/>
</xsl:template>
<xsl:template name="tag">
<blockquote class="tag">
<xsl:text>&lt;</xsl:text><xsl:if test="@href"><a href="{@href}"><xsl:value-of select="@name"/></a></xsl:if><xsl:if test="not(@href)"><xsl:value-of select="@name"/></xsl:if><xsl:for-each select="attr"><xsl:text> </xsl:text><xsl:value-of select="@name"/><xsl:text>="</xsl:text><xsl:value-of select="@value"/><xsl:text>"</xsl:text></xsl:for-each>
<xsl:choose>
<xsl:when test="tag"><xsl:text>&gt;</xsl:text><xsl:apply-templates mode="inner"/><xsl:text>&lt;/</xsl:text><xsl:value-of select="@name"/><xsl:text>&gt;</xsl:text></xsl:when>
<xsl:when test="normalize-space(.) != ''"><xsl:text>&gt;</xsl:text><xsl:value-of select="."/><xsl:text>&lt;/</xsl:text><xsl:value-of select="@name"/><xsl:text>&gt;</xsl:text></xsl:when>
<xsl:otherwise><xsl:text>/&gt;</xsl:text></xsl:otherwise>
</xsl:choose>
</blockquote>
</xsl:template>
</xsl:stylesheet>

View file

@ -0,0 +1,15 @@
<!-- author="Jeff Parsons (@jeffpar)" website="http://www.pcjs.org/" created="2012-05-05" modified="2014-02-23" license="http://www.gnu.org/licenses/gpl.html" -->
<!-- XSLT understands these entities only: lt, gt, apos, quot, and amp. Other useful entities are defined below. -->
<!-- Alas, Firefox doesn't support loading external entities, so the contents of this file must be pasted into every primary XSL file. -->
<!ENTITY nbsp "&#160;">
<!ENTITY sect "&#167;"> <!-- OSX: option 6 -->
<!ENTITY copy "&#169;"> <!-- OSX: option g -->
<!ENTITY para "&#182;"> <!-- OSX: option 7 -->
<!ENTITY ndash "&#8211;"> <!-- &#x2013; UTF-8: 0xE2 0x80 0x93; OSX: option - -->
<!ENTITY mdash "&#8212;"> <!-- &#x2014; UTF-8: 0xE2 0x80 0x94; OSX: shift option - -->
<!ENTITY lsquo "&#8216;"> <!-- OSX: option ] -->
<!ENTITY rsquo "&#8217;"> <!-- OSX: shift option ] -->
<!ENTITY ldquo "&#8220;"> <!-- &#x201C; UTF-8: 0xE2 0x80 0x9C; OSX: option [ -->
<!ENTITY rdquo "&#8221;"> <!-- &#x201D; UTF-8: 0xE2 0x80 0x9D; OSX: shift option [ -->
<!ENTITY dagger "&#8224;"> <!-- OSX: option t -->
<!ENTITY Dagger "&#8225;"> <!-- OSX: shift option 7 -->

View file

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- author="Jeff Parsons (@jeffpar)" website="http://www.pcjs.org/" created="2012-05-05" modified="2014-02-23" license="http://www.gnu.org/licenses/gpl.html" -->
<!DOCTYPE xsl:stylesheet [
<!-- XSLT understands these entities only: lt, gt, apos, quot, and amp. Other useful entities are defined below (see entities.dtd). -->
<!ENTITY nbsp "&#160;"> <!ENTITY sect "&#167;"> <!ENTITY copy "&#169;"> <!ENTITY para "&#182;"> <!ENTITY ndash "&#8211;"> <!ENTITY mdash "&#8212;">
<!ENTITY lsquo "&#8216;"> <!ENTITY rsquo "&#8217;"> <!ENTITY ldquo "&#8220;"> <!ENTITY rdquo "&#8221;"> <!ENTITY dagger "&#8224;"> <!ENTITY Dagger "&#8225;">
]>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output doctype-system="about:legacy-compat"/>
<xsl:include href="common.xsl"/>
<!-- There is no "shared" components.xsl, so we just pick one to eliminate IDE inspection warnings -->
<xsl:include href="../../pcjs-client/templates/components.xsl"/>
<xsl:template match="/machine">
<html lang="en">
<head>
<title><xsl:value-of select="$SITEHOST"/></title>
<xsl:call-template name="commonStyles"/>
<xsl:call-template name="componentStyles"/>
</head>
<body>
<div class="common">
<xsl:call-template name="commonTop"/>
<div class="common-middle">
<p></p>
<div id="{@id}" class="machine {@class}js">
<xsl:call-template name="component">
<xsl:with-param name="machine" select="@id"/>
<xsl:with-param name="component" select="'machine'"/>
<xsl:with-param name="class"><xsl:value-of select="@class"/>js</xsl:with-param>
<xsl:with-param name="parms"><xsl:if test="@parms">,<xsl:value-of select="@parms"/></xsl:if></xsl:with-param>
</xsl:call-template>
</div>
</div>
<xsl:call-template name="commonBottom"/>
</div>
<xsl:call-template name="componentScripts">
<xsl:with-param name="component">
<xsl:choose>
<xsl:when test="debugger"><xsl:value-of select="@class"/>-dbg</xsl:when>
<xsl:otherwise><xsl:value-of select="@class"/></xsl:otherwise>
</xsl:choose>
</xsl:with-param>
</xsl:call-template>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

View file

@ -0,0 +1,251 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- author="Jeff Parsons (@jeffpar)" website="http://www.pcjs.org/" created="2014-04-10" modified="2014-04-10" license="http://www.gnu.org/licenses/gpl.html" -->
<!DOCTYPE xsl:stylesheet [
<!-- XSLT understands these entities only: lt, gt, apos, quot, and amp. Other useful entities are defined below (see entities.dtd). -->
<!ENTITY nbsp "&#160;"> <!ENTITY sect "&#167;"> <!ENTITY copy "&#169;"> <!ENTITY para "&#182;"> <!ENTITY ndash "&#8211;"> <!ENTITY mdash "&#8212;">
<!ENTITY lsquo "&#8216;"> <!ENTITY rsquo "&#8217;"> <!ENTITY ldquo "&#8220;"> <!ENTITY rdquo "&#8221;"> <!ENTITY dagger "&#8224;"> <!ENTITY Dagger "&#8225;">
]>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output doctype-system="about:legacy-compat"/>
<xsl:include href="common.xsl"/>
<!-- There is no "shared" components.xsl, so we just pick one to eliminate IDE inspection warnings -->
<xsl:include href="../../pcjs-client/templates/components.xsl"/>
<xsl:template match="/manifest[@type = 'document']">
<html lang="en">
<head>
<title><xsl:value-of select="$SITEHOST"/></title>
<xsl:call-template name="commonStyles"/>
<xsl:call-template name="componentStyles"/>
</head>
<body>
<div class="common">
<xsl:call-template name="commonTop"/>
<div class="common-middle">
<h4>Document Manifest</h4>
<div class="common-sidebar">
<ul class="common-list-data">
<xsl:call-template name="listItem">
<xsl:with-param name="label" select="'Title'"/>
<xsl:with-param name="node" select="title"/>
<xsl:with-param name="default">None</xsl:with-param>
</xsl:call-template>
<xsl:call-template name="listItem">
<xsl:with-param name="label" select="'Version'"/>
<xsl:with-param name="node" select="version"/>
<xsl:with-param name="default" select="''"/>
</xsl:call-template>
<xsl:call-template name="listItem">
<xsl:with-param name="label" select="'Source'"/>
<xsl:with-param name="node" select="source"/>
<xsl:with-param name="default" select="''"/>
</xsl:call-template>
<xsl:call-template name="listItem">
<xsl:with-param name="label" select="'Documents'"/>
<xsl:with-param name="node" select="document"/>
<xsl:with-param name="default"><xsl:value-of select="title"/> <xsl:if test="version != ''"><xsl:text> </xsl:text><xsl:value-of select="version"/></xsl:if></xsl:with-param>
</xsl:call-template>
</ul>
</div>
<div class="common-main">
<!-- TODO: Enumerate cover elements within document elements -->
<p><xsl:value-of select="desc"/></p>
<xsl:call-template name="commonBottom"/>
</div>
</div>
</div>
</body>
</html>
</xsl:template>
<xsl:template match="/manifest[@type = 'software' or not(@type)]">
<xsl:variable name="machineClass">
<xsl:choose>
<xsl:when test="machine/@class"><xsl:value-of select="machine/@class"/></xsl:when>
<xsl:otherwise><xsl:value-of select="$MACHINECLASS"/></xsl:otherwise>
</xsl:choose>
</xsl:variable>
<html lang="en">
<head>
<title><xsl:value-of select="$SITEHOST"/></title>
<xsl:call-template name="commonStyles"/>
<xsl:call-template name="componentStyles"/>
</head>
<body>
<div class="common">
<xsl:call-template name="commonTop"/>
<div class="common-middle">
<h4>Software Manifest</h4>
<div class="common-sidebar">
<ul class="common-list-data">
<xsl:call-template name="listItem">
<xsl:with-param name="label" select="'Title'"/>
<xsl:with-param name="node" select="title"/>
<xsl:with-param name="default">None</xsl:with-param>
</xsl:call-template>
<xsl:call-template name="listItem">
<xsl:with-param name="label" select="'Version'"/>
<xsl:with-param name="node" select="version"/>
<xsl:with-param name="default">Unknown</xsl:with-param>
</xsl:call-template>
<xsl:call-template name="listItem">
<xsl:with-param name="label" select="'Type'"/>
<xsl:with-param name="node" select="type"/>
<xsl:with-param name="default">None</xsl:with-param>
</xsl:call-template>
<xsl:call-template name="listItem">
<xsl:with-param name="label" select="'Category'"/>
<xsl:with-param name="node" select="category"/>
<xsl:with-param name="default">None</xsl:with-param>
</xsl:call-template>
<xsl:call-template name="listItem">
<xsl:with-param name="label" select="'Created'"/>
<xsl:with-param name="node" select="creationDate"/>
<xsl:with-param name="default" select="''"/>
</xsl:call-template>
<xsl:call-template name="listItem">
<xsl:with-param name="label" select="'Creators'"/>
<xsl:with-param name="node" select="creator"/>
<xsl:with-param name="default" select="''"/>
</xsl:call-template>
<xsl:call-template name="listItem">
<xsl:with-param name="label"><xsl:if test="creationDate">Updated</xsl:if><xsl:if test="not(creationDate)">Released</xsl:if></xsl:with-param>
<xsl:with-param name="node" select="releaseDate"/>
<xsl:with-param name="default">Unknown</xsl:with-param>
</xsl:call-template>
<xsl:call-template name="listItem">
<xsl:with-param name="label" select="'Company'"/>
<xsl:with-param name="node" select="company"/>
<xsl:with-param name="default" select="''"/>
</xsl:call-template>
<xsl:call-template name="listItem">
<xsl:with-param name="label" select="'Authors'"/>
<xsl:with-param name="node" select="author"/>
<xsl:with-param name="default" select="''"/>
</xsl:call-template>
<xsl:call-template name="listItem">
<xsl:with-param name="label" select="'Contributors'"/>
<xsl:with-param name="node" select="contributor"/>
<xsl:with-param name="default" select="''"/>
</xsl:call-template>
<xsl:call-template name="listItem">
<xsl:with-param name="label" select="'Publisher'"/>
<xsl:with-param name="node" select="publisher"/>
<xsl:with-param name="default" select="''"/>
</xsl:call-template>
<xsl:call-template name="listItem">
<xsl:with-param name="label" select="'License'"/>
<xsl:with-param name="node" select="license"/>
<xsl:with-param name="default" select="''"/>
</xsl:call-template>
<xsl:call-template name="listItem">
<xsl:with-param name="label" select="'Source'"/>
<xsl:with-param name="node" select="source"/>
<xsl:with-param name="default" select="''"/>
</xsl:call-template>
<xsl:call-template name="listItem">
<xsl:with-param name="label" select="'Disks'"/>
<xsl:with-param name="node" select="disk"/>
<xsl:with-param name="default"><xsl:value-of select="title"/> <xsl:if test="version != ''"><xsl:text> </xsl:text><xsl:value-of select="version"/></xsl:if></xsl:with-param>
</xsl:call-template>
</ul>
</div>
<div class="common-main">
<xsl:for-each select="machine[not(@type) or @type = 'default']">
<xsl:call-template name="machine">
<xsl:with-param name="href" select="@href"/>
<xsl:with-param name="state" select="@state"/>
</xsl:call-template>
</xsl:for-each>
<xsl:if test="not(machine[not(@type) or @type = 'default'])">
<p>No default machine specified for '<xsl:value-of select="title"/>' in manifest.xml</p>
</xsl:if>
<xsl:call-template name="commonBottom"/>
</div>
</div>
</div>
<xsl:call-template name="componentScripts">
<xsl:with-param name="component">
<xsl:choose>
<xsl:when test="machine/@debugger"><xsl:value-of select="$machineClass"/>-dbg</xsl:when>
<xsl:otherwise><xsl:value-of select="$machineClass"/></xsl:otherwise>
</xsl:choose>
</xsl:with-param>
</xsl:call-template>
</body>
</html>
</xsl:template>
<xsl:template name="listItem">
<xsl:param name="label"/>
<xsl:param name="node"/>
<xsl:param name="default">Unknown</xsl:param>
<xsl:if test="$node != '' or $default != ''">
<li><xsl:value-of select="$label"/>
<ul class="common-list-data-items">
<xsl:for-each select="$node">
<xsl:variable name="desc">
<xsl:choose>
<xsl:when test="desc"><xsl:value-of select="desc"/></xsl:when>
<xsl:when test="org"><xsl:value-of select="org"/></xsl:when>
<xsl:otherwise/>
</xsl:choose>
</xsl:variable>
<li title="{$desc}">
<xsl:variable name="value">
<xsl:choose>
<xsl:when test="name">
<xsl:value-of select="name"/>
</xsl:when>
<xsl:when test="normalize-space(./text()) != ''">
<xsl:value-of select="normalize-space(./text())"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$default"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="href">
<xsl:if test="@href"><xsl:value-of select="@href"/></xsl:if>
</xsl:variable>
<xsl:choose>
<xsl:when test="$href != ''">
<a href="{$href}"><xsl:value-of select="$value"/></a>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$value"/>
</xsl:otherwise>
</xsl:choose>
<!-- Page elements are to document manifests what file elements are to software manifests, except that we don't enumerate file elements -->
<xsl:if test="page">
<ul class="common-list-data-subitems">
<xsl:for-each select="page">
<li>
<xsl:if test="@href">
<a href="{$href}{@href}"><xsl:value-of select="."/></a>
</xsl:if>
<xsl:if test="not(@href)">
<xsl:value-of select="."/>
</xsl:if>
</li>
</xsl:for-each>
</ul>
</xsl:if>
</li>
</xsl:for-each>
<xsl:if test="not($node)">
<xsl:if test="@href">
<a href="{@href}"><xsl:value-of select="$default"/></a>
</xsl:if>
<xsl:if test="not(@href)">
<xsl:value-of select="$default"/>
</xsl:if>
</xsl:if>
</ul>
</li>
</xsl:if>
</xsl:template>
</xsl:stylesheet>

View file

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- author="Jeff Parsons (@jeffpar)" website="http://www.pcjs.org/" created="2012-05-05" modified="2014-02-23" license="http://www.gnu.org/licenses/gpl.html" -->
<!DOCTYPE xsl:stylesheet [
<!-- XSLT understands these entities only: lt, gt, apos, quot, and amp. Other useful entities are defined below (see entities.dtd). -->
<!ENTITY nbsp "&#160;"> <!ENTITY sect "&#167;"> <!ENTITY copy "&#169;"> <!ENTITY para "&#182;"> <!ENTITY ndash "&#8211;"> <!ENTITY mdash "&#8212;">
<!ENTITY lsquo "&#8216;"> <!ENTITY rsquo "&#8217;"> <!ENTITY ldquo "&#8220;"> <!ENTITY rdquo "&#8221;"> <!ENTITY dagger "&#8224;"> <!ENTITY Dagger "&#8225;">
]>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output doctype-system="about:legacy-compat"/>
<xsl:include href="common.xsl"/>
<xsl:include href="document.xsl"/>
<!-- There is no "shared" components.xsl, so we just pick one to eliminate IDE inspection warnings -->
<xsl:include href="../../pcjs-client/templates/components.xsl"/>
<xsl:template match="/outline">
<xsl:variable name="machineClass">
<xsl:choose>
<xsl:when test="machine/@class"><xsl:value-of select="machine/@class"/></xsl:when>
<xsl:otherwise><xsl:value-of select="$MACHINECLASS"/></xsl:otherwise>
</xsl:choose>
</xsl:variable>
<html lang="en">
<head>
<title><xsl:value-of select="title"/><xsl:text> | </xsl:text><xsl:value-of select="$SITEHOST"/></title>
<xsl:call-template name="commonStyles"/>
<xsl:call-template name="documentStyles"/>
<xsl:call-template name="componentStyles"/>
</head>
<body>
<div class="common">
<div class="page justified">
<xsl:apply-templates/>
</div>
</div>
<xsl:call-template name="componentScripts">
<xsl:with-param name="component">
<xsl:choose>
<xsl:when test="debugger"><xsl:value-of select="$machineClass"/>-dbg</xsl:when>
<xsl:otherwise><xsl:value-of select="$machineClass"/></xsl:otherwise>
</xsl:choose>
</xsl:with-param>
</xsl:call-template>
</body>
</html>
</xsl:template>
</xsl:stylesheet>