Initial commit (a clone of the jsmachines project as of v1.15.3)
This commit is contained in:
commit
a5e3e6a59d
714 changed files with 130602 additions and 0 deletions
28
my_modules/shared/lib/README.md
Normal file
28
my_modules/shared/lib/README.md
Normal 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') {...}
|
||||
906
my_modules/shared/lib/component.js
Normal file
906
my_modules/shared/lib/component.js
Normal 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;
|
||||
76
my_modules/shared/lib/defines.js
Normal file
76
my_modules/shared/lib/defines.js
Normal 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?
|
||||
*/
|
||||
}
|
||||
75
my_modules/shared/lib/diskapi.js
Normal file
75
my_modules/shared/lib/diskapi.js
Normal 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;
|
||||
85
my_modules/shared/lib/dumpapi.js
Normal file
85
my_modules/shared/lib/dumpapi.js
Normal 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;
|
||||
372
my_modules/shared/lib/embed.js
Normal file
372
my_modules/shared/lib/embed.js
Normal 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;
|
||||
39
my_modules/shared/lib/externs.js
Normal file
39
my_modules/shared/lib/externs.js
Normal 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;
|
||||
385
my_modules/shared/lib/netlib.js
Normal file
385
my_modules/shared/lib/netlib.js
Normal 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;
|
||||
45
my_modules/shared/lib/nodebug.js
Normal file
45
my_modules/shared/lib/nodebug.js
Normal 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;
|
||||
90
my_modules/shared/lib/proclib.js
Normal file
90
my_modules/shared/lib/proclib.js
Normal 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;
|
||||
53
my_modules/shared/lib/reportapi.js
Normal file
53
my_modules/shared/lib/reportapi.js
Normal 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;
|
||||
43
my_modules/shared/lib/sockets.js
Normal file
43
my_modules/shared/lib/sockets.js
Normal 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);
|
||||
});
|
||||
323
my_modules/shared/lib/strlib.js
Normal file
323
my_modules/shared/lib/strlib.js
Normal 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 = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
};
|
||||
|
||||
/**
|
||||
* 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;
|
||||
73
my_modules/shared/lib/userapi.js
Normal file
73
my_modules/shared/lib/userapi.js
Normal 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;
|
||||
212
my_modules/shared/lib/usrlib.js
Normal file
212
my_modules/shared/lib/usrlib.js
Normal 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;
|
||||
647
my_modules/shared/lib/weblib.js
Normal file
647
my_modules/shared/lib/weblib.js
Normal 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;
|
||||
1
my_modules/shared/templates/.gitignore
vendored
Normal file
1
my_modules/shared/templates/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
adsense.html
|
||||
35
my_modules/shared/templates/README.md
Normal file
35
my_modules/shared/templates/README.md
Normal 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.
|
||||
8
my_modules/shared/templates/analytics.html
Normal file
8
my_modules/shared/templates/analytics.html
Normal 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>
|
||||
267
my_modules/shared/templates/common.css
Normal file
267
my_modules/shared/templates/common.css
Normal 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;
|
||||
}
|
||||
}
|
||||
79
my_modules/shared/templates/common.html
Normal file
79
my_modules/shared/templates/common.html
Normal 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>
|
||||
48
my_modules/shared/templates/common.xsl
Normal file
48
my_modules/shared/templates/common.xsl
Normal 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 " "> <!ENTITY ne "≠"> <!ENTITY le "≤"> <!ENTITY ge "≥">
|
||||
<!ENTITY times "×"> <!ENTITY sdot "⋅"> <!ENTITY divide "÷">
|
||||
<!ENTITY copy "©"> <!ENTITY Sigma "Σ"> <!ENTITY sigma "σ"> <!ENTITY sum "∑"> <!ENTITY lbrace "{">
|
||||
]>
|
||||
<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>
|
||||
26
my_modules/shared/templates/dictionary.txt
Normal file
26
my_modules/shared/templates/dictionary.txt
Normal 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
|
||||
167
my_modules/shared/templates/document.css
Normal file
167
my_modules/shared/templates/document.css
Normal 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;
|
||||
}
|
||||
454
my_modules/shared/templates/document.xsl
Normal file
454
my_modules/shared/templates/document.xsl
Normal 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 " "> <!ENTITY ne "≠"> <!ENTITY le "≤"> <!ENTITY ge "≥">
|
||||
<!ENTITY times "×"> <!ENTITY sdot "⋅"> <!ENTITY divide "÷">
|
||||
<!ENTITY copy "©"> <!ENTITY Sigma "Σ"> <!ENTITY sigma "σ"> <!ENTITY sum "∑"> <!ENTITY lbrace "{">
|
||||
]>
|
||||
<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 = ''"> </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"><</xsl:template>
|
||||
<xsl:template match="gt">></xsl:template>
|
||||
<xsl:template match="ne">≠</xsl:template>
|
||||
<xsl:template match="le">≤</xsl:template>
|
||||
<xsl:template match="ge">≥</xsl:template>
|
||||
<xsl:template match="times">×</xsl:template>
|
||||
<xsl:template match="dot">⋅</xsl:template>
|
||||
<xsl:template match="divide">÷</xsl:template>
|
||||
<xsl:template match="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">∑</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">{</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><</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>></xsl:text><xsl:apply-templates mode="inner"/><xsl:text></</xsl:text><xsl:value-of select="@name"/><xsl:text>></xsl:text></xsl:when>
|
||||
<xsl:when test="normalize-space(.) != ''"><xsl:text>></xsl:text><xsl:value-of select="."/><xsl:text></</xsl:text><xsl:value-of select="@name"/><xsl:text>></xsl:text></xsl:when>
|
||||
<xsl:otherwise><xsl:text>/></xsl:text></xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</blockquote>
|
||||
</xsl:template>
|
||||
|
||||
</xsl:stylesheet>
|
||||
15
my_modules/shared/templates/entities.dtd
Normal file
15
my_modules/shared/templates/entities.dtd
Normal 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 " ">
|
||||
<!ENTITY sect "§"> <!-- OSX: option 6 -->
|
||||
<!ENTITY copy "©"> <!-- OSX: option g -->
|
||||
<!ENTITY para "¶"> <!-- OSX: option 7 -->
|
||||
<!ENTITY ndash "–"> <!-- – UTF-8: 0xE2 0x80 0x93; OSX: option - -->
|
||||
<!ENTITY mdash "—"> <!-- — UTF-8: 0xE2 0x80 0x94; OSX: shift option - -->
|
||||
<!ENTITY lsquo "‘"> <!-- OSX: option ] -->
|
||||
<!ENTITY rsquo "’"> <!-- OSX: shift option ] -->
|
||||
<!ENTITY ldquo "“"> <!-- “ UTF-8: 0xE2 0x80 0x9C; OSX: option [ -->
|
||||
<!ENTITY rdquo "”"> <!-- ” UTF-8: 0xE2 0x80 0x9D; OSX: shift option [ -->
|
||||
<!ENTITY dagger "†"> <!-- OSX: option t -->
|
||||
<!ENTITY Dagger "‡"> <!-- OSX: shift option 7 -->
|
||||
51
my_modules/shared/templates/machine.xsl
Normal file
51
my_modules/shared/templates/machine.xsl
Normal 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 " "> <!ENTITY sect "§"> <!ENTITY copy "©"> <!ENTITY para "¶"> <!ENTITY ndash "–"> <!ENTITY mdash "—">
|
||||
<!ENTITY lsquo "‘"> <!ENTITY rsquo "’"> <!ENTITY ldquo "“"> <!ENTITY rdquo "”"> <!ENTITY dagger "†"> <!ENTITY Dagger "‡">
|
||||
]>
|
||||
<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>
|
||||
251
my_modules/shared/templates/manifest.xsl
Normal file
251
my_modules/shared/templates/manifest.xsl
Normal 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 " "> <!ENTITY sect "§"> <!ENTITY copy "©"> <!ENTITY para "¶"> <!ENTITY ndash "–"> <!ENTITY mdash "—">
|
||||
<!ENTITY lsquo "‘"> <!ENTITY rsquo "’"> <!ENTITY ldquo "“"> <!ENTITY rdquo "”"> <!ENTITY dagger "†"> <!ENTITY Dagger "‡">
|
||||
]>
|
||||
<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>
|
||||
49
my_modules/shared/templates/outline.xsl
Normal file
49
my_modules/shared/templates/outline.xsl
Normal 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 " "> <!ENTITY sect "§"> <!ENTITY copy "©"> <!ENTITY para "¶"> <!ENTITY ndash "–"> <!ENTITY mdash "—">
|
||||
<!ENTITY lsquo "‘"> <!ENTITY rsquo "’"> <!ENTITY ldquo "“"> <!ENTITY rdquo "”"> <!ENTITY dagger "†"> <!ENTITY Dagger "‡">
|
||||
]>
|
||||
<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>
|
||||
Loading…
Reference in a new issue