<commit_before>function generateRect(x, y, width, height, borderRadiusParam, fillParam, strokeParam, strokeWidthParam, transform, mask) {

    var el = document.createElementNS('http://www.w3.org/2000/svg', 'rect');

    el.setAttribute('x', roundDown(x));
    el.setAttribute('y', roundDown(y));
    el.setAttribute('width', width);
    el.setAttribute('height', height);
    el.setAttribute('rx', paramOrDefault(borderRadiusParam, borderRadius));
    el.setAttribute('ry', paramOrDefault(borderRadiusParam, borderRadius));
    el.setAttribute('fill', paramOrDefault(fillParam, fill));
    el.setAttribute('stroke', paramOrDefault(strokeParam, stroke));
    el.setAttribute('stroke-width', paramOrDefault(strokeWidthParam, strokeWidth));

    if (isArgument(transform)) el.setAttribute('transform', transform);
    if (isArgument(mask))      el.setAttribute('mask', getUrl(mask));

    svg.append(el);
};

function generateCircle(cx, cy, r, fillParam, strokeParam, strokeWidthParam){

    var el = document.createElementNS('http://www.w3.org/2000/svg', 'circle');

    el.setAttribute('cx', roundDown(cx));
    el.setAttribute('cy', roundDown(cy));
    el.setAttribute('r', r);
    el.setAttribute('fill', paramOrDefault(fillParam, fill));
    el.setAttribute('stroke', paramOrDefault(strokeParam, stroke));
    el.setAttribute('stroke-width', paramOrDefault(strokeWidthParam, strokeWidth));

    svg.append(el);
};

function generateText(content, x, y, fontSize, fontWeightParam, fillParam, fontFamily, textAnchorParam, transform, style, mask) {

    var el = document.createElementNS('http://www.w3.org/2000/svg', 'text');

    el.textContent = content;

    el.setAttribute('x', roundDown(x));
    el.setAttribute('y', roundDown(y));
    el.setAttribute('font-size', fontSize);
    el.setAttribute('font-weight', paramOrDefault(fontWeightParam, fontWeight));
    el.setAttribute('fill', paramOrDefault(fillParam, fontColor));
    el.setAttribute('text-anchor', paramOrDefault(textAnchorParam , textAnchor));

    if (isArgument(fontFamily)) el.setAttribute('font-family', fontFamily);
    if (isArgument(transform))  el.setAttribute('transform', transform);
    if (isArgument(style))      el.setAttribute('style', style);
    if (isArgument(mask))       el.setAttribute('mask', getUrl(mask));

    svg.append(el);
};

function generateLine(x1, y1, x2, y2, strokeParam, strokeWidthParam, mask) {

    var el = document.createElementNS('http://www.w3.org/2000/svg', 'line');

    el.setAttribute('x1', roundDown(x1));
    el.setAttribute('y1', roundDown(y1));
    el.setAttribute('x2', roundDown(x2));
    el.setAttribute('y2', roundDown(y2));
    el.setAttribute('stroke', paramOrDefault(strokeParam, stroke));
    el.setAttribute('stroke-width', paramOrDefault(strokeWidthParam, strokeWidth));

    if (isArgument(mask)) el.setAttribute('mask', getUrl(mask));

    svg.append(el);
};

function generatePolygon(points, fillParam, strokeParam, strokeWidthParam) {

    var el = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');

    el.setAttribute('points', points);
    el.setAttribute('fill', paramOrDefault(fillParam, fill));
    el.setAttribute('stroke', paramOrDefault(strokeParam, stroke));
    el.setAttribute('stroke-width', paramOrDefault(strokeWidthParam, strokeWidth));

    svg.append(el);
};

function generateQuadraticPath(startx, starty, middlex, middley, endx, endy, strokeParam, strokeWidthParam, fillParam, isRelative) {

    var d = 'M' +
            roundDown(startx) + ',' +
            roundDown(starty) + ' ' +
            (isRelative ? 'q' : 'Q') +
            roundDown(middlex) + ',' +
            roundDown(middley) + ' ' +
            roundDown(endx) + ',' +
            roundDown(endy);

    var el = document.createElementNS('http://www.w3.org/2000/svg', 'path');

    el.setAttribute('d', d);
    el.setAttribute('stroke', paramOrDefault(strokeParam, stroke));
    el.setAttribute('stroke-width', paramOrDefault(strokeWidthParam, strokeWidth));
    el.setAttribute('fill', paramOrDefault(fillParam, transparent));

    svg.append(el);
};

function generateArc(startx, starty, radiix, radiiy, rotationx, largeArc, sweep, endx, endy, isClosed, strokeParam, strokeWidthParam, fillParam) {

    var d = 'M' +
            roundDown(startx) + ',' +
            roundDown(starty) +
            ' A' +
            roundDown(radiix) + ',' +
            roundDown(radiiy) + ' ' +
            roundDown(rotationx) + ' ' +
            largeArc + ',' +
            sweep + ' ' +
            roundDown(endx) + ',' +
            roundDown(endy);

    if (isClosed) d = d + ' z';

    var el = document.createElementNS('http://www.w3.org/2000/svg', 'path');

    el.setAttribute('d', d);
    el.setAttribute('stroke', paramOrDefault(strokeParam, stroke));
    el.setAttribute('stroke-width', paramOrDefault(strokeWidthParam, strokeWidth));
    el.setAttribute('fill', paramOrDefault(fillParam, transparent));

    svg.append(el);
};

function generateMask(topY, width, height, isTop) {

    var maskEl = document.createElementNS('http://www.w3.org/2000/svg', 'mask');

    maskEl.setAttribute('maskUnits', 'userSpaceOnUse');
    maskEl.setAttribute('maskContentUnits', 'userSpaceOnUse');
    maskEl.setAttribute('id', isTop ? 'top-transparent-fade' : 'bottom-transparent-fade');

    var lindearGradientEl = document.createElementNS('http://www.w3.org/2000/svg', 'linearGradient');

    lindearGradientEl.setAttribute('id', isTop ? 'transparent-fade-fill-top' : 'transparent-fade-fill-bottom');
    lindearGradientEl.setAttribute('gradientUnits', 'objectBoundingBox');
    lindearGradientEl.setAttribute('x1', 0);
    lindearGradientEl.setAttribute('y1', 0);
    lindearGradientEl.setAttribute('x2', 0);
    lindearGradientEl.setAttribute('y2', 1);

    var stopElOpaque = document.createElementNS('http://www.w3.org/2000/svg', 'stop');

    stopElOpaque.setAttribute('stop-color', 'white');
    stopElOpaque.setAttribute('offset', isTop ? 1 : 0);

    var stopElTransparent = document.createElementNS('http://www.w3.org/2000/svg', 'stop');

    stopElTransparent.setAttribute('stop-color', 'white');
    stopElTransparent.setAttribute('stop-opacity', 0);
    stopElTransparent.setAttribute('offset', isTop ? 0 : 1);

    var rectEl = document.createElementNS('http://www.w3.org/2000/svg', 'rect');

    rectEl.setAttribute('x', 0);
    rectEl.setAttribute('y', topY);
    rectEl.setAttribute('width', width);
    rectEl.setAttribute('height', height);
    rectEl.setAttribute('stroke-width', 0);
    rectEl.setAttribute('fill', getUrl(linearGradientID));

    if (isTop) {
        $(lindearGradientEl).append(stopElTransparent).append(stopElOpaque);
    } else {
        $(lindearGradientEl).append(stopElOpaque).append(stopElTransparent);
    }

    $(maskEl).append(lindearGradientEl, rectEl);

    svg.append(maskEl);

    // <mask maskUnits="userSpaceOnUse" maskContentUnits="userSpaceOnUse" id="top-transparent-fade">
    //     <linearGradient id="transparent-fade-fill-top" gradientUnits="objectBoundingBox" x1="0" y1="0" x2="0" y2="1">
    //         <stop stop-color="white" stop-opacity="0" offset="0"></stop>
    //         <stop stop-color="white" offset="1"></stop>
    //     </linearGradient>
    //     <rect x="0" y="20" width="300" height="56" stroke-width="0" fill="url(#transparent-fade-fill-top)"></rect>
    // </mask>

    // <mask maskUnits="userSpaceOnUse" maskContentUnits="userSpaceOnUse" id="bottom-transparent-fade">
    //     <linearGradient id="transparent-fade-fill-bottom" gradientUnits="objectBoundingBox" x1="0" y1="0" x2="0" y2="1">
    //         <stop stop-color="white" offset="0"></stop>
    //         <stop stop-color="white" stop-opacity="0" offset="1"></stop>
    //     </linearGradient>
    //     <rect x="0" y="215" width="300" height="56" stroke-width="0" fill="url(#transparent-fade-fill-bottom)"></rect>
    // </mask>

}
<commit_msg>bugfix and typo<commit_after>function generateRect(x, y, width, height, borderRadiusParam, fillParam, strokeParam, strokeWidthParam, transform, mask) {

    var el = document.createElementNS('http://www.w3.org/2000/svg', 'rect');

    el.setAttribute('x', roundDown(x));
    el.setAttribute('y', roundDown(y));
    el.setAttribute('width', width);
    el.setAttribute('height', height);
    el.setAttribute('rx', paramOrDefault(borderRadiusParam, borderRadius));
    el.setAttribute('ry', paramOrDefault(borderRadiusParam, borderRadius));
    el.setAttribute('fill', paramOrDefault(fillParam, fill));
    el.setAttribute('stroke', paramOrDefault(strokeParam, stroke));
    el.setAttribute('stroke-width', paramOrDefault(strokeWidthParam, strokeWidth));

    if (isArgument(transform)) el.setAttribute('transform', transform);
    if (isArgument(mask))      el.setAttribute('mask', getUrl(mask));

    svg.append(el);
};

function generateCircle(cx, cy, r, fillParam, strokeParam, strokeWidthParam){

    var el = document.createElementNS('http://www.w3.org/2000/svg', 'circle');

    el.setAttribute('cx', roundDown(cx));
    el.setAttribute('cy', roundDown(cy));
    el.setAttribute('r', r);
    el.setAttribute('fill', paramOrDefault(fillParam, fill));
    el.setAttribute('stroke', paramOrDefault(strokeParam, stroke));
    el.setAttribute('stroke-width', paramOrDefault(strokeWidthParam, strokeWidth));

    svg.append(el);
};

function generateText(content, x, y, fontSize, fontWeightParam, fillParam, fontFamily, textAnchorParam, transform, style, mask) {

    var el = document.createElementNS('http://www.w3.org/2000/svg', 'text');

    el.textContent = content;

    el.setAttribute('x', roundDown(x));
    el.setAttribute('y', roundDown(y));
    el.setAttribute('font-size', fontSize);
    el.setAttribute('font-weight', paramOrDefault(fontWeightParam, fontWeight));
    el.setAttribute('fill', paramOrDefault(fillParam, fontColor));
    el.setAttribute('text-anchor', paramOrDefault(textAnchorParam , textAnchor));

    if (isArgument(fontFamily)) el.setAttribute('font-family', fontFamily);
    if (isArgument(transform))  el.setAttribute('transform', transform);
    if (isArgument(style))      el.setAttribute('style', style);
    if (isArgument(mask))       el.setAttribute('mask', getUrl(mask));

    svg.append(el);
};

function generateLine(x1, y1, x2, y2, strokeParam, strokeWidthParam, mask) {

    var el = document.createElementNS('http://www.w3.org/2000/svg', 'line');

    el.setAttribute('x1', roundDown(x1));
    el.setAttribute('y1', roundDown(y1));
    el.setAttribute('x2', roundDown(x2));
    el.setAttribute('y2', roundDown(y2));
    el.setAttribute('stroke', paramOrDefault(strokeParam, stroke));
    el.setAttribute('stroke-width', paramOrDefault(strokeWidthParam, strokeWidth));

    if (isArgument(mask)) el.setAttribute('mask', getUrl(mask));

    svg.append(el);
};

function generatePolygon(points, fillParam, strokeParam, strokeWidthParam) {

    var el = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');

    el.setAttribute('points', points);
    el.setAttribute('fill', paramOrDefault(fillParam, fill));
    el.setAttribute('stroke', paramOrDefault(strokeParam, stroke));
    el.setAttribute('stroke-width', paramOrDefault(strokeWidthParam, strokeWidth));

    svg.append(el);
};

function generateQuadraticPath(startx, starty, middlex, middley, endx, endy, strokeParam, strokeWidthParam, fillParam, isRelative) {

    var d = 'M' +
            roundDown(startx) + ',' +
            roundDown(starty) + ' ' +
            (isRelative ? 'q' : 'Q') +
            roundDown(middlex) + ',' +
            roundDown(middley) + ' ' +
            roundDown(endx) + ',' +
            roundDown(endy);

    var el = document.createElementNS('http://www.w3.org/2000/svg', 'path');

    el.setAttribute('d', d);
    el.setAttribute('stroke', paramOrDefault(strokeParam, stroke));
    el.setAttribute('stroke-width', paramOrDefault(strokeWidthParam, strokeWidth));
    el.setAttribute('fill', paramOrDefault(fillParam, transparent));

    svg.append(el);
};

function generateArc(startx, starty, radiix, radiiy, rotationx, largeArc, sweep, endx, endy, isClosed, strokeParam, strokeWidthParam, fillParam) {

    var d = 'M' +
            roundDown(startx) + ',' +
            roundDown(starty) +
            ' A' +
            roundDown(radiix) + ',' +
            roundDown(radiiy) + ' ' +
            roundDown(rotationx) + ' ' +
            largeArc + ',' +
            sweep + ' ' +
            roundDown(endx) + ',' +
            roundDown(endy);

    if (isClosed) d = d + ' z';

    var el = document.createElementNS('http://www.w3.org/2000/svg', 'path');

    el.setAttribute('d', d);
    el.setAttribute('stroke', paramOrDefault(strokeParam, stroke));
    el.setAttribute('stroke-width', paramOrDefault(strokeWidthParam, strokeWidth));
    el.setAttribute('fill', paramOrDefault(fillParam, transparent));

    svg.append(el);
};

function generateMask(topY, width, height, isTop) {

    var maskEl = document.createElementNS('http://www.w3.org/2000/svg', 'mask');

    maskEl.setAttribute('maskUnits', 'userSpaceOnUse');
    maskEl.setAttribute('maskContentUnits', 'userSpaceOnUse');
    maskEl.setAttribute('id', isTop ? 'top-transparent-fade' : 'bottom-transparent-fade');

    var linearGradientEl = document.createElementNS('http://www.w3.org/2000/svg', 'linearGradient');
    var linearGradientID = isTop ? 'transparent-fade-fill-top' : 'transparent-fade-fill-bottom';

    linearGradientEl.setAttribute('id', linearGradientID);
    linearGradientEl.setAttribute('gradientUnits', 'objectBoundingBox');
    linearGradientEl.setAttribute('x1', 0);
    linearGradientEl.setAttribute('y1', 0);
    linearGradientEl.setAttribute('x2', 0);
    linearGradientEl.setAttribute('y2', 1);

    var stopElOpaque = document.createElementNS('http://www.w3.org/2000/svg', 'stop');

    stopElOpaque.setAttribute('stop-color', 'white');
    stopElOpaque.setAttribute('offset', isTop ? 1 : 0);

    var stopElTransparent = document.createElementNS('http://www.w3.org/2000/svg', 'stop');

    stopElTransparent.setAttribute('stop-color', 'white');
    stopElTransparent.setAttribute('stop-opacity', 0);
    stopElTransparent.setAttribute('offset', isTop ? 0 : 1);

    var rectEl = document.createElementNS('http://www.w3.org/2000/svg', 'rect');

    rectEl.setAttribute('x', 0);
    rectEl.setAttribute('y', topY);
    rectEl.setAttribute('width', width);
    rectEl.setAttribute('height', height);
    rectEl.setAttribute('stroke-width', 0);
    rectEl.setAttribute('fill', getUrl(linearGradientID));

    if (isTop) {
        $(linearGradientEl).append(stopElTransparent).append(stopElOpaque);
    } else {
        $(linearGradientEl).append(stopElOpaque).append(stopElTransparent);
    }

    $(maskEl).append(linearGradientEl, rectEl);

    svg.append(maskEl);

    // <mask maskUnits="userSpaceOnUse" maskContentUnits="userSpaceOnUse" id="top-transparent-fade">
    //     <linearGradient id="transparent-fade-fill-top" gradientUnits="objectBoundingBox" x1="0" y1="0" x2="0" y2="1">
    //         <stop stop-color="white" stop-opacity="0" offset="0"></stop>
    //         <stop stop-color="white" offset="1"></stop>
    //     </linearGradient>
    //     <rect x="0" y="20" width="300" height="56" stroke-width="0" fill="url(#transparent-fade-fill-top)"></rect>
    // </mask>

    // <mask maskUnits="userSpaceOnUse" maskContentUnits="userSpaceOnUse" id="bottom-transparent-fade">
    //     <linearGradient id="transparent-fade-fill-bottom" gradientUnits="objectBoundingBox" x1="0" y1="0" x2="0" y2="1">
    //         <stop stop-color="white" offset="0"></stop>
    //         <stop stop-color="white" stop-opacity="0" offset="1"></stop>
    //     </linearGradient>
    //     <rect x="0" y="215" width="300" height="56" stroke-width="0" fill="url(#transparent-fade-fill-bottom)"></rect>
    // </mask>

}
<|endoftext|>


<commit_before>/**
 * @fileOverview Loader module for restartless addons
 * @author       YUKI "Piro" Hiroshi
 * @version      7
 *
 * @license
 *   The MIT License, Copyright (c) 2010-2014 YUKI "Piro" Hiroshi.
 *   https://github.com/piroor/restartless/blob/master/license.txt
 * @url http://github.com/piroor/restartless
 */

/** You can customize shared properties for loaded scripts. */
var Application = (function() {
	if ('@mozilla.org/fuel/application;1' in Components.classes)
		return Components.classes['@mozilla.org/fuel/application;1']
				.getService(Components.interfaces.fuelIApplication);
	if ('@mozilla.org/steel/application;1' in Components.classes)
		return Components.classes['@mozilla.org/steel/application;1']
				.getService(Components.interfaces.steelIApplication);
	return null;
})();
var _namespacePrototype = {
		Cc : Components.classes,
		Ci : Components.interfaces,
		Cu : Components.utils,
		Cr : Components.results,
		Application : Application,
		btoa: function(aInput) {
		  return btoa(aInput);
		},
		atob: function(aInput) {
		  return atob(aInput);
		}
	};
var _namespaces;

/**
 * This functiom loads specified script into a unique namespace for the URL.
 * Namespaces for loaded scripts have a wrapped version of this function.
 * Both this and wrapped work like as Components.utils.import().
 * Due to the reserved symbol "import", we have to use another name "load"
 * instead it.
 *
 * @param {String} aScriptURL
 *   URL of a script. Wrapped version of load() can handle related path.

 *   Related path will be resolved based on the location of the caller script.
 * @param {Object=} aExportTargetForImport
 *   EXPORTED_SYMBOLS in the loaded script will be exported to the object.
 *   If no object is specified, symbols will be exported to the global object
 *   of the caller.
 * @param {Object=} aExportTargetForRequire
 *   Properties of "exports" in the loaded script will be exported to the object.
 *
 * @returns {Object}
 *   The global object for the loaded script.
 */
function load(aURISpec, aExportTargetForImport, aExportTargetForRequire, aRoot)
{
	if (!_namespaces)
		_namespaces = {};
	var ns;
	if (aURISpec in _namespaces) {
		ns = _namespaces[aURISpec];
		_exportForImport(ns, aExportTargetForImport);
		_exportForRequire(ns, aExportTargetForRequire);
		return ns;
	}
	ns = _createNamespace(aURISpec, aRoot || aURISpec);
	try {
		Components.classes['@mozilla.org/moz/jssubscript-loader;1']
			.getService(Components.interfaces.mozIJSSubScriptLoader)
			.loadSubScript(aURISpec, ns);
	}
	catch(e) {
		let message = 'Loader::load('+aURISpec+') failed!\n'+e+'\n';
		dump(message);
		Components.utils.reportError(message + e.stack);
		throw e;
	}
	_exportForImport(ns, aExportTargetForImport);
	_exportForRequire(ns, aExportTargetForRequire);
	return _namespaces[aURISpec] = ns;
}

// JavaScript code module style
function _exportForImport(aSource, aTarget)
{
	if (
		!aTarget ||
		!('EXPORTED_SYMBOLS' in aSource) ||
		!aSource.EXPORTED_SYMBOLS ||
		!aSource.EXPORTED_SYMBOLS.forEach
		)
		return;
	for each (var symbol in aSource.EXPORTED_SYMBOLS)
	{
		aTarget[symbol] = aSource[symbol];
	}
}

// CommonJS style
function _exportForRequire(aSource, aTarget)
{
	if (
		!aTarget ||
		!('exports' in aSource) ||
		!aSource.exports ||
		typeof aSource.exports != 'object'
		)
		return;
	for (var symbol in aSource.exports)
	{
		aTarget[symbol] = aSource.exports[symbol];
	}
}

var IOService = Components.classes['@mozilla.org/network/io-service;1']
					.getService(Components.interfaces.nsIIOService);
var FileHandler = IOService.getProtocolHandler('file')
					.QueryInterface(Components.interfaces.nsIFileProtocolHandler);

/**
 * Checks existence of the file specified by the given relative path and the base URI.
 *
 * @param {String} aPath
 *   A relative path to a file or directory, from the aBaseURI.
 * @param {String} aBaseURI
 *   An absolute URI (with scheme) for relative paths.
 *
 * @returns {String}
 *   If the file (or directory) exists, returns the absolute URI. Otherwise null.
 */
function exists(aPath, aBaseURI)
{
	if (/^\w+:/.test(aPath)) {
		let leafName = aPath.match(/([^\/]+)$/);
		leafName = leafName ? leafName[1] : '' ;
		aBaseURI = aPath.replace(/(?:[^\/]+)$/, '');
		aPath = leafName;
	}
	var baseURI = aBaseURI.indexOf('file:') == 0 ?
					IOService.newFileURI(FileHandler.getFileFromURLSpec(aBaseURI)) :
					IOService.newURI(aBaseURI, null, null);
	if (aBaseURI.indexOf('jar:') == 0) {
		baseURI = baseURI.QueryInterface(Components.interfaces.nsIJARURI);
		var reader = Components.classes['@mozilla.org/libjar/zip-reader;1']
						.createInstance(Components.interfaces.nsIZipReader);
		reader.open(baseURI.JARFile.QueryInterface(Components.interfaces.nsIFileURL).file);
	    try {
			let baseEntry = baseURI.JAREntry.replace(/[^\/]+$/, '');
			let entries = reader.findEntries(baseEntry + aPath + '$');
			let found = entries.hasMore();
			return found ? baseURI.resolve(aPath) : null ;
		}
		finally {
			reader.close();
		}
	}
	else {
		let resolved = baseURI.resolve(aPath);
		return FileHandler.getFileFromURLSpec(resolved).exists() ? resolved : null ;
	}
}

function doAndWait(aAsyncTask)
{
	const Cc = Components.classes;
	const Ci = Components.interfaces;

	var done = false;
	var returnedValue = void(0);
	var continuation = function(aReturnedValue) {
			done = true;
			returnedValue = aReturnedValue;
		};

	var timer = Cc['@mozilla.org/timer;1']
					.createInstance(Ci.nsITimer);
	timer.init(function() {
		aAsyncTask(continuation);
	}, 0, Ci.nsITimer.TYPE_ONE_SHOT);

	var thread = Cc['@mozilla.org/thread-manager;1']
					.getService(Ci.nsIThreadManager)
					.currentThread;
	while (!done) {
		thread.processNextEvent(true);
	}
	return returnedValue;
}

function _readFrom(aURISpec, aEncoding)
{
	const Cc = Components.classes;
	const Ci = Components.interfaces;

	var uri = aURISpec.indexOf('file:') == 0 ?
			IOService.newFileURI(FileHandler.getFileFromURLSpec(aURISpec)) :
			IOService.newURI(aURISpec, null, null) ;
	var channel = IOService.newChannelFromURI(uri.QueryInterface(Ci.nsIURI));
	var stream = channel.open();

	var fileContents = null;
	try {
		if (aEncoding) {
			var converterStream = Cc['@mozilla.org/intl/converter-input-stream;1']
					.createInstance(Ci.nsIConverterInputStream);
			var buffer = stream.available();
			converterStream.init(stream, aEncoding, buffer,
				converterStream.DEFAULT_REPLACEMENT_CHARACTER);
			var out = { value : null };
			converterStream.readString(stream.available(), out);
			converterStream.close();
			fileContents = out.value;
		}
		else {
			var scriptableStream = Cc['@mozilla.org/scriptableinputstream;1']
					.createInstance(Ci.nsIScriptableInputStream);
			scriptableStream.init(stream);
			fileContents = scriptableStream.read(scriptableStream.available());
			scriptableStream.close();
		}
	}
	finally {
		stream.close();
	}
	return fileContents;
}

function _createNamespace(aURISpec, aRoot)
{
	var baseURI = aURISpec.indexOf('file:') == 0 ?
					IOService.newFileURI(FileHandler.getFileFromURLSpec(aURISpec)) :
					IOService.newURI(aURISpec, null, null);
	var rootURI = typeof aRoot == 'string' ?
					(aRoot.indexOf('file:') == 0 ?
						IOService.newFileURI(FileHandler.getFileFromURLSpec(aRoot)) :
						IOService.newURI(aRoot, null, null)
					) :
					aRoot ;
	var ns = {
			__proto__ : _namespacePrototype,
			location : _createFakeLocation(baseURI),
			exists : function(aPath, aBase) {
				return exists(aPath, aBase || baseURI.spec);
			},
			exist : function(aPath, aBase) { // alias
				return exists(aPath, aBase || baseURI.spec);
			},
			/** JavaScript code module style */
			load : function(aURISpec, aExportTarget) {
				if (!/\.jsm?$/.test(aURISpec)) {
					if (exists(aURISpec+'.js', baseURI.spec))
						aURISpec += '.js'
					else if (exists(aURISpec+'.jsm', baseURI.spec))
						aURISpec += '.jsm'
				}
				var resolved = baseURI.resolve(aURISpec);
				if (resolved == aURISpec)
					throw new Error('Recursive load!');
				return load(resolved, aExportTarget || ns, aExportTarget, rootURI);
			},
			'import' : function() { // alias
				return this.load.apply(this, arguments);
			},
			/**
			 * CommonJS style
			 * @url http://www.commonjs.org/specs/
			 */
			require : function(aURISpec) {
				if (!/\.jsm?$/.test(aURISpec)) {
					if (exists(aURISpec+'.js', baseURI.spec))
						aURISpec += '.js'
					else if (exists(aURISpec+'.jsm', baseURI.spec))
						aURISpec += '.jsm'
				}
				var resolved = (aURISpec.charAt(0) == '.' ? rootURI : baseURI ).resolve(aURISpec);
				if (resolved == aURISpec)
					throw new Error('Recursive load!');
				var exported = {};
				load(resolved, exported, exported, rootURI);
				return exported;
			},
			/* utility to resolve relative path from the file */
			resolve : function(aURISpec, aBaseURI) {
				var base = !aBaseURI ?
								baseURI :
							aBaseURI.indexOf('file:') == 0 ?
								IOService.newFileURI(FileHandler.getFileFromURLSpec(aURISpec)) :
								IOService.newURI(aURISpec, null, null) ;
				return base.resolve(aURISpec);
			},
			/* utility to read contents of a text file */
			read : function(aURISpec, aEncoding, aBaseURI) {
				return _readFrom(this.resolve(aURISpec, aBaseURI), aEncoding);
			},
			doAndWait : function(aAsyncTask) {
				return doAndWait(aAsyncTask);
			},
			exports : {}
		};
	return ns;
}

function _createFakeLocation(aURI)
{
	aURI = aURI.QueryInterface(Components.interfaces.nsIURL)
					.QueryInterface(Components.interfaces.nsIURI);
	return {
		href     : aURI.spec,
		search   : aURI.query ? '?'+aURI.query : '' ,
		hash     : aURI.ref ? '#'+aURI.ref : '' ,
		host     : aURI.scheme == 'jar' ? '' : aURI.hostPort,
		hostname : aURI.scheme == 'jar' ? '' : aURI.host,
		port     : aURI.scheme == 'jar' ? -1 : aURI.port,
		pathname : aURI.path,
		protocol : aURI.scheme+':',
		reload   : function() {},
		replace  : function() {},
		toString : function() {
			return this.href;
		}
	};
}

function _callHandler(aHandler, aReason)
{
	for (var i in _namespaces)
	{
		try {
			if (_namespaces[i][aHandler] &&
				typeof _namespaces[i][aHandler] == 'function')
				_namespaces[i][aHandler](aReason);
		}
		catch(e) {
			let message = i+'('+aHandler+', '+aReason+')\n'+e+'\n';
			dump(message);
			Components.utils.reportError(message + e.stack);
		}
	}
}

function registerResource(aName, aRoot)
{
	IOService.getProtocolHandler('resource')
		.QueryInterface(Components.interfaces.nsIResProtocolHandler)
		.setSubstitution(aName, aRoot);
}

function unregisterResource(aName)
{
	IOService.getProtocolHandler('resource')
		.QueryInterface(Components.interfaces.nsIResProtocolHandler)
		.setSubstitution(aName, null);
}

/** Handler for "install" of the bootstrap.js */
function install(aReason)
{
	_callHandler('install', aReason);
}

/** Handler for "uninstall" of the bootstrap.js */
function uninstall(aReason)
{
	_callHandler('uninstall', aReason);
}

/** Handler for "shutdown" of the bootstrap.js */
function shutdown(aReason)
{
	_callHandler('shutdown', aReason);

	for each (let ns in _namespaces)
	{
		for (let i in ns.exports)
		{
			if (ns.exports.hasOwnProperty(i))
				delete ns.exports[i];
		}
	}
	_namespaces = void(0);
	_namespacePrototype = void(0);
	Application = void(0);

	IOService = void(0);
	FileHandler = void(0);

	load = void(0);
	_exportSymbols = void(0);
	exists = void(0);
	_createNamespace = void(0);
	_callHandler = void(0);
	registerResource = void(0);
	unregisterResource = void(0);
	install = void(0);
	uninstall = void(0);
	shutdown = void(0);
}
<commit_msg>Import atob and btoa from a namespace of a js code module<commit_after>/**
 * @fileOverview Loader module for restartless addons
 * @author       YUKI "Piro" Hiroshi
 * @version      8
 *
 * @license
 *   The MIT License, Copyright (c) 2010-2014 YUKI "Piro" Hiroshi.
 *   https://github.com/piroor/restartless/blob/master/license.txt
 * @url http://github.com/piroor/restartless
 */

/** You can customize shared properties for loaded scripts. */
var Application = (function() {
	if ('@mozilla.org/fuel/application;1' in Components.classes)
		return Components.classes['@mozilla.org/fuel/application;1']
				.getService(Components.interfaces.fuelIApplication);
	if ('@mozilla.org/steel/application;1' in Components.classes)
		return Components.classes['@mozilla.org/steel/application;1']
				.getService(Components.interfaces.steelIApplication);
	return null;
})();
// import base64 utilities from the js code module namespace
var { atob, btoa } = Components.utils.import('resource://gre/modules/Services.jsm', {});
var _namespacePrototype = {
		Cc : Components.classes,
		Ci : Components.interfaces,
		Cu : Components.utils,
		Cr : Components.results,
		Application : Application,
		btoa: function(aInput) {
			return btoa(aInput);
		},
		atob: function(aInput) {
			return atob(aInput);
		}
	};
var _namespaces;

/**
 * This functiom loads specified script into a unique namespace for the URL.
 * Namespaces for loaded scripts have a wrapped version of this function.
 * Both this and wrapped work like as Components.utils.import().
 * Due to the reserved symbol "import", we have to use another name "load"
 * instead it.
 *
 * @param {String} aScriptURL
 *   URL of a script. Wrapped version of load() can handle related path.

 *   Related path will be resolved based on the location of the caller script.
 * @param {Object=} aExportTargetForImport
 *   EXPORTED_SYMBOLS in the loaded script will be exported to the object.
 *   If no object is specified, symbols will be exported to the global object
 *   of the caller.
 * @param {Object=} aExportTargetForRequire
 *   Properties of "exports" in the loaded script will be exported to the object.
 *
 * @returns {Object}
 *   The global object for the loaded script.
 */
function load(aURISpec, aExportTargetForImport, aExportTargetForRequire, aRoot)
{
	if (!_namespaces)
		_namespaces = {};
	var ns;
	if (aURISpec in _namespaces) {
		ns = _namespaces[aURISpec];
		_exportForImport(ns, aExportTargetForImport);
		_exportForRequire(ns, aExportTargetForRequire);
		return ns;
	}
	ns = _createNamespace(aURISpec, aRoot || aURISpec);
	try {
		Components.classes['@mozilla.org/moz/jssubscript-loader;1']
			.getService(Components.interfaces.mozIJSSubScriptLoader)
			.loadSubScript(aURISpec, ns);
	}
	catch(e) {
		let message = 'Loader::load('+aURISpec+') failed!\n'+e+'\n';
		dump(message);
		Components.utils.reportError(message + e.stack);
		throw e;
	}
	_exportForImport(ns, aExportTargetForImport);
	_exportForRequire(ns, aExportTargetForRequire);
	return _namespaces[aURISpec] = ns;
}

// JavaScript code module style
function _exportForImport(aSource, aTarget)
{
	if (
		!aTarget ||
		!('EXPORTED_SYMBOLS' in aSource) ||
		!aSource.EXPORTED_SYMBOLS ||
		!aSource.EXPORTED_SYMBOLS.forEach
		)
		return;
	for each (var symbol in aSource.EXPORTED_SYMBOLS)
	{
		aTarget[symbol] = aSource[symbol];
	}
}

// CommonJS style
function _exportForRequire(aSource, aTarget)
{
	if (
		!aTarget ||
		!('exports' in aSource) ||
		!aSource.exports ||
		typeof aSource.exports != 'object'
		)
		return;
	for (var symbol in aSource.exports)
	{
		aTarget[symbol] = aSource.exports[symbol];
	}
}

var IOService = Components.classes['@mozilla.org/network/io-service;1']
					.getService(Components.interfaces.nsIIOService);
var FileHandler = IOService.getProtocolHandler('file')
					.QueryInterface(Components.interfaces.nsIFileProtocolHandler);

/**
 * Checks existence of the file specified by the given relative path and the base URI.
 *
 * @param {String} aPath
 *   A relative path to a file or directory, from the aBaseURI.
 * @param {String} aBaseURI
 *   An absolute URI (with scheme) for relative paths.
 *
 * @returns {String}
 *   If the file (or directory) exists, returns the absolute URI. Otherwise null.
 */
function exists(aPath, aBaseURI)
{
	if (/^\w+:/.test(aPath)) {
		let leafName = aPath.match(/([^\/]+)$/);
		leafName = leafName ? leafName[1] : '' ;
		aBaseURI = aPath.replace(/(?:[^\/]+)$/, '');
		aPath = leafName;
	}
	var baseURI = aBaseURI.indexOf('file:') == 0 ?
					IOService.newFileURI(FileHandler.getFileFromURLSpec(aBaseURI)) :
					IOService.newURI(aBaseURI, null, null);
	if (aBaseURI.indexOf('jar:') == 0) {
		baseURI = baseURI.QueryInterface(Components.interfaces.nsIJARURI);
		var reader = Components.classes['@mozilla.org/libjar/zip-reader;1']
						.createInstance(Components.interfaces.nsIZipReader);
		reader.open(baseURI.JARFile.QueryInterface(Components.interfaces.nsIFileURL).file);
	    try {
			let baseEntry = baseURI.JAREntry.replace(/[^\/]+$/, '');
			let entries = reader.findEntries(baseEntry + aPath + '$');
			let found = entries.hasMore();
			return found ? baseURI.resolve(aPath) : null ;
		}
		finally {
			reader.close();
		}
	}
	else {
		let resolved = baseURI.resolve(aPath);
		return FileHandler.getFileFromURLSpec(resolved).exists() ? resolved : null ;
	}
}

function doAndWait(aAsyncTask)
{
	const Cc = Components.classes;
	const Ci = Components.interfaces;

	var done = false;
	var returnedValue = void(0);
	var continuation = function(aReturnedValue) {
			done = true;
			returnedValue = aReturnedValue;
		};

	var timer = Cc['@mozilla.org/timer;1']
					.createInstance(Ci.nsITimer);
	timer.init(function() {
		aAsyncTask(continuation);
	}, 0, Ci.nsITimer.TYPE_ONE_SHOT);

	var thread = Cc['@mozilla.org/thread-manager;1']
					.getService(Ci.nsIThreadManager)
					.currentThread;
	while (!done) {
		thread.processNextEvent(true);
	}
	return returnedValue;
}

function _readFrom(aURISpec, aEncoding)
{
	const Cc = Components.classes;
	const Ci = Components.interfaces;

	var uri = aURISpec.indexOf('file:') == 0 ?
			IOService.newFileURI(FileHandler.getFileFromURLSpec(aURISpec)) :
			IOService.newURI(aURISpec, null, null) ;
	var channel = IOService.newChannelFromURI(uri.QueryInterface(Ci.nsIURI));
	var stream = channel.open();

	var fileContents = null;
	try {
		if (aEncoding) {
			var converterStream = Cc['@mozilla.org/intl/converter-input-stream;1']
					.createInstance(Ci.nsIConverterInputStream);
			var buffer = stream.available();
			converterStream.init(stream, aEncoding, buffer,
				converterStream.DEFAULT_REPLACEMENT_CHARACTER);
			var out = { value : null };
			converterStream.readString(stream.available(), out);
			converterStream.close();
			fileContents = out.value;
		}
		else {
			var scriptableStream = Cc['@mozilla.org/scriptableinputstream;1']
					.createInstance(Ci.nsIScriptableInputStream);
			scriptableStream.init(stream);
			fileContents = scriptableStream.read(scriptableStream.available());
			scriptableStream.close();
		}
	}
	finally {
		stream.close();
	}
	return fileContents;
}

function _createNamespace(aURISpec, aRoot)
{
	var baseURI = aURISpec.indexOf('file:') == 0 ?
					IOService.newFileURI(FileHandler.getFileFromURLSpec(aURISpec)) :
					IOService.newURI(aURISpec, null, null);
	var rootURI = typeof aRoot == 'string' ?
					(aRoot.indexOf('file:') == 0 ?
						IOService.newFileURI(FileHandler.getFileFromURLSpec(aRoot)) :
						IOService.newURI(aRoot, null, null)
					) :
					aRoot ;
	var ns = {
			__proto__ : _namespacePrototype,
			location : _createFakeLocation(baseURI),
			exists : function(aPath, aBase) {
				return exists(aPath, aBase || baseURI.spec);
			},
			exist : function(aPath, aBase) { // alias
				return exists(aPath, aBase || baseURI.spec);
			},
			/** JavaScript code module style */
			load : function(aURISpec, aExportTarget) {
				if (!/\.jsm?$/.test(aURISpec)) {
					if (exists(aURISpec+'.js', baseURI.spec))
						aURISpec += '.js'
					else if (exists(aURISpec+'.jsm', baseURI.spec))
						aURISpec += '.jsm'
				}
				var resolved = baseURI.resolve(aURISpec);
				if (resolved == aURISpec)
					throw new Error('Recursive load!');
				return load(resolved, aExportTarget || ns, aExportTarget, rootURI);
			},
			'import' : function() { // alias
				return this.load.apply(this, arguments);
			},
			/**
			 * CommonJS style
			 * @url http://www.commonjs.org/specs/
			 */
			require : function(aURISpec) {
				if (!/\.jsm?$/.test(aURISpec)) {
					if (exists(aURISpec+'.js', baseURI.spec))
						aURISpec += '.js'
					else if (exists(aURISpec+'.jsm', baseURI.spec))
						aURISpec += '.jsm'
				}
				var resolved = (aURISpec.charAt(0) == '.' ? rootURI : baseURI ).resolve(aURISpec);
				if (resolved == aURISpec)
					throw new Error('Recursive load!');
				var exported = {};
				load(resolved, exported, exported, rootURI);
				return exported;
			},
			/* utility to resolve relative path from the file */
			resolve : function(aURISpec, aBaseURI) {
				var base = !aBaseURI ?
								baseURI :
							aBaseURI.indexOf('file:') == 0 ?
								IOService.newFileURI(FileHandler.getFileFromURLSpec(aURISpec)) :
								IOService.newURI(aURISpec, null, null) ;
				return base.resolve(aURISpec);
			},
			/* utility to read contents of a text file */
			read : function(aURISpec, aEncoding, aBaseURI) {
				return _readFrom(this.resolve(aURISpec, aBaseURI), aEncoding);
			},
			doAndWait : function(aAsyncTask) {
				return doAndWait(aAsyncTask);
			},
			exports : {}
		};
	return ns;
}

function _createFakeLocation(aURI)
{
	aURI = aURI.QueryInterface(Components.interfaces.nsIURL)
					.QueryInterface(Components.interfaces.nsIURI);
	return {
		href     : aURI.spec,
		search   : aURI.query ? '?'+aURI.query : '' ,
		hash     : aURI.ref ? '#'+aURI.ref : '' ,
		host     : aURI.scheme == 'jar' ? '' : aURI.hostPort,
		hostname : aURI.scheme == 'jar' ? '' : aURI.host,
		port     : aURI.scheme == 'jar' ? -1 : aURI.port,
		pathname : aURI.path,
		protocol : aURI.scheme+':',
		reload   : function() {},
		replace  : function() {},
		toString : function() {
			return this.href;
		}
	};
}

function _callHandler(aHandler, aReason)
{
	for (var i in _namespaces)
	{
		try {
			if (_namespaces[i][aHandler] &&
				typeof _namespaces[i][aHandler] == 'function')
				_namespaces[i][aHandler](aReason);
		}
		catch(e) {
			let message = i+'('+aHandler+', '+aReason+')\n'+e+'\n';
			dump(message);
			Components.utils.reportError(message + e.stack);
		}
	}
}

function registerResource(aName, aRoot)
{
	IOService.getProtocolHandler('resource')
		.QueryInterface(Components.interfaces.nsIResProtocolHandler)
		.setSubstitution(aName, aRoot);
}

function unregisterResource(aName)
{
	IOService.getProtocolHandler('resource')
		.QueryInterface(Components.interfaces.nsIResProtocolHandler)
		.setSubstitution(aName, null);
}

/** Handler for "install" of the bootstrap.js */
function install(aReason)
{
	_callHandler('install', aReason);
}

/** Handler for "uninstall" of the bootstrap.js */
function uninstall(aReason)
{
	_callHandler('uninstall', aReason);
}

/** Handler for "shutdown" of the bootstrap.js */
function shutdown(aReason)
{
	_callHandler('shutdown', aReason);

	for each (let ns in _namespaces)
	{
		for (let i in ns.exports)
		{
			if (ns.exports.hasOwnProperty(i))
				delete ns.exports[i];
		}
	}
	_namespaces = void(0);
	_namespacePrototype = void(0);
	Application = void(0);

	IOService = void(0);
	FileHandler = void(0);

	load = void(0);
	_exportSymbols = void(0);
	exists = void(0);
	_createNamespace = void(0);
	_callHandler = void(0);
	registerResource = void(0);
	unregisterResource = void(0);
	install = void(0);
	uninstall = void(0);
	shutdown = void(0);
}
<|endoftext|>


<commit_before>/**
 * CSSS javascript code
 * @author Lea Verou (http://leaverou.me)
 * @version 2.0
 */

(function(head, body){

// Check for classList support and include the polyfill if it's not supported
if(!('classList' in body)) {
	var thisScript = document.querySelector('script[src$="slideshow.js"]'),
	    script = document.createElement('script');
	    script.src = thisScript.src.replace(/\bslideshow\.js/, 'classList.js');
	thisScript.parentNode.insertBefore(script, thisScript);
}

// Cache <title> element, we may need it for slides that don't have titles
var documentTitle = document.title + '';

var self = window.SlideShow = function(container, slide) {
	var me = this;

	// Set instance
	if(!window.slideshow) {
		window.slideshow = this;
	}

	container = container || body;

	// Current slide
	this.slide = slide || 0;

	// Current .delayed item in the slide
	this.item = 0;

	// Do we need to add a timer?
	this.duration = container.getAttribute('data-duration');

	if(this.duration > 0) {
		var timer = document.createElement('div'),
		    declaration = 'transition: ' + this.duration * 60 + 's linear; ';

		timer.id = 'timer';
		timer.setAttribute('style', '-moz-' + declaration + '-webkit-' + declaration + '-o-' + declaration + '-ms-' + declaration + declaration);
		container.appendChild(timer);

		setTimeout(function() {
			timer.className = 'end';
		}, 1);
	}

	// Get the slide elements into an array
	this.slides = Array.prototype.slice.apply(container.querySelectorAll('.slide'));

	for(var i=0; i<this.slides.length; i++) {
		var slide = this.slides[i]; // to speed up references

		// Asign ids to slides that don't have one
		if(!slide.id) {
			slide.id = 'slide' + (i+1);
		}

		// Set data-title attribute to the title of the slide
		if(!slide.title) {
			// no title attribute, fetch title from heading(s)
			var heading = slide.querySelector('hgroup') || slide.querySelector('h1,h2,h3,h4,h5,h6');

			if(heading && heading.textContent.trim()) {
				slide.setAttribute('data-title', heading.textContent);
			}
		}
		else {
			// The title attribute is set, use that
			slide.setAttribute('data-title', slide.title);
			slide.removeAttribute('title');
		}
	}

	// If there's already a hash, update current slide number...
	this.goto(location.hash.substr(1) || 0);

	// ...and keep doing so every time the hash changes
	this.onhashchange = function() {
		me.goto(location.hash.substr(1) || 0);
	};
	window.addEventListener('hashchange', this.onhashchange, false);

	if(window.name === 'projector') {
		document.body.classList.add('projector');
	}

	// Adjust the font-size when the window is resized
	addEventListener('resize', function() {
		me.adjustFontSize();
	}, false);

	// In some browsers DOMContentLoaded is too early, so try again onload
	addEventListener('load', function() {
		me.adjustFontSize();
	}, false);

	/**
		Keyboard navigation
		Ctrl+G : Go to slide...
		Ctrl+H : Show thumbnails and go to slide
		Ctrl+P : Presenter view
		(Shift instead of Ctrl works too)
	*/
	document.addEventListener('keyup', function(evt) {
		if(evt.ctrlKey || evt.shiftKey) {
			switch(evt.keyCode) {
				case 71: // G
					var slide = prompt('Which slide?');
					me.goto(+slide? slide - 1 : slide);
					break;
				case 72: // H
					if(body.classList.contains('show-thumbnails')) {
						body.classList.remove('show-thumbnails');
						body.classList.remove('headers-only');
					}
					else {
						body.classList.add('show-thumbnails');

						if(!evt.shiftKey || !evt.ctrlKey) {
							body.classList.add('headers-only');
						}

						body.addEventListener('click', function(evt) {
							var slide = evt.target;

							while(slide && !slide.classList.contains('slide')) {
								slide = slide.parentNode;
							}

							if(slide) {
								me.goto(slide.id);
								setTimeout(function() { me.adjustFontSize(); }, 1000); // for Opera
							}

							body.classList.remove('show-thumbnails');
							body.classList.remove('headers-only');
						}, false);
					}
					break;
				case 74: // J
					if(body.classList.contains('hide-elements')) {
						body.classList.remove('hide-elements');
					}
					else {
						body.classList.add('hide-elements');
					}
					break;
				case 80: // P
					// Open new window for attendee view
					me.projector = window.open(location, 'projector', 'menubar=yes,location=yes,resizable=yes,scrollbars=yes,status=yes');

					// Get the focus back
					window.focus();

					// Switch this one to presenter view
					body.classList.add('presenter');
			}
		}
	}, false);

	/**
		Keyboard navigation
		Home : First slide
		End : Last slide
		Space/Up/Right arrow : Next item/slide
		Ctrl + Space/Up/Right arrow : Next slide
		Down/Left arrow : Previous item/slide
		Ctrl + Down/Left arrow : Previous slide
		(Shift instead of Ctrl works too)
	*/
	document.addEventListener('keydown', function(evt) {
		if(evt.target === body || evt.target === body.parentNode || evt.altKey) {
			if(evt.keyCode >= 32 && evt.keyCode <= 40) {
				evt.preventDefault();
			}

			switch(evt.keyCode) {
				case 33: //page up
					me.previous();
					break;
				case 34: //page down
					me.next();
					break;
				case 35: // end
					me.end();
					break;
				case 36: // home
					me.start();
					break;
				case 37: // <-
				case 38: // up arrow
					me.previous(evt.ctrlKey || evt.shiftKey);
					break;
				case 32: // space
				case 39: // ->
				case 40: // down arrow
					me.next(evt.ctrlKey || evt.shiftKey);
					break;
			}
		}
	}, false);

	// Rudimentary style[scoped] polyfill
	var scoped = container.querySelectorAll('style[scoped]');

	for(var i=scoped.length; i--;) {
		var style = scoped[i],
			rulez = style.sheet.cssRules,
			parentid = style.parentNode.id || self.getSlide(style).id;

		for(var j=rulez.length; j--;) {
			var cssText = rulez[j].cssText.replace(/^|,/g, function($0) { return '#' + parentid + ' ' + $0 });

			style.sheet.deleteRule(0);
			style.sheet.insertRule(cssText, 0);
		}
	}
}

self.prototype = {
	start: function() {
		this.goto(0);
	},

	end: function() {
		this.goto(this.slides.length - 1);
	},

	/**
		@param hard {Boolean} Whether to advance to the next slide (true) or
			just the next step (which could very well be showing a list item)
	 */
	next: function(hard) {
		if(!hard && this.items.length) {
			// If there's no current, then just mark the first one as such
			if(!this.item) {
				this.items[this.item++].classList.add('current');
			}
			// Add .current to current item if it exists, otherwise advance to next slide
			else if(this.item < this.items.length) {
				classes = this.items[this.item - 1].classList; // to speed up lookups

				classes.remove('current');
				classes.add('displayed');

				this.items[this.item++].classList.add('current');
			}
			else {
				this.item = 0;
				this.next(true);
			}
		}
		else {
			this.goto(this.slide + 1);

			this.item = 0;

			// Mark all items as not displayed, if there are any
			if(this.items.length) {
				for (var i=0; i<this.items.length; i++) {
					if(this.items[i].classList) {
						this.items[i].classList.remove('displayed');
						this.items[i].classList.remove('current');
					}
				}
			}
		}
	},

	previous: function(hard) {
		if(!hard && this.item > 0) {
			var classes = this.items[this.item - 1].classList; // to speed up lookups

			classes.remove('current');

			if(this.item > 1) {
				classes = this.items[--this.item - 1].classList;

				classes.remove('displayed');
				classes.add('current');
			}
			else {
				this.item = 0;
			}
		}
		else {

			this.goto(this.slide - 1);

			this.item = this.items.length;

			// Mark all items as displayed, if there are any
			if(this.items.length) {
				for (var i=0; i<this.items.length; i++) {
					if(this.items[i].classList) {
						this.items[i].classList.add('displayed');
					}
				}

				// Mark the last one as current
				var lastItem = this.items[this.items.length - 1];

				lastItem.classList.remove('displayed');
				lastItem.classList.add('current');
			}
		}
	},

	/**
		Go to an aribtary slide
		@param which {String|Integer} Which slide (identifier or slide number)
	*/
	goto: function(which) {
		var slide;

		// We have to remove it to prevent multiple calls to goto messing up
		// our current item (and there's no point either, so we save on performance)
		window.removeEventListener('hashchange', this.onhashchange, false);

		if(which + 0 === which && which in this.slides) { // Argument is a valid slide number
			this.slide = which;

			slide = this.slides[this.slide];
			location.hash = '#' + slide.id;
		}
		else if(which + '' === which) { // Argument is a slide id
			slide = document.getElementById(which);

			if(slide) {
				this.slide = this.slides.indexOf(slide);
				location.hash = '#' + which;
			}
		}

		if(slide) { // Slide actually changed, perform any other tasks needed
			document.title = slide.getAttribute('data-title') || documentTitle;

			this.adjustFontSize();

			// Update items collection
			this.items = this.slides[this.slide].querySelectorAll('.delayed, .delayed-children > *');
			this.item = 0;

			// Tell other windows
			if(this.projector && this.projector.slideshow && this.projector.slideshow.slide != this.slide) {
				this.projector.slideshow.goto(this.slide);
			}

			if(window.opener && opener.slideshow && opener.slideshow.slide != this.slide) {
				opener.slideshow.goto(this.slide);
			}
		}

		// If you attach the listener immediately again then it will catch the event
		// We have to do it asynchronously
		var me = this;
		setTimeout(function() {
			window.addEventListener('hashchange', me.onhashchange, false);
		}, 1000);
	},

	adjustFontSize: function() {
		// Cache long lookup chains, for performance
		var bodyStyle = body.style,
			scrollRoot = document[document.documentElement.scrollHeight? 'documentElement' : 'body'],
			innerHeight = window.innerHeight,
			innerWidth = window.innerWidth,
			slide = this.slides[this.slide];

		// Clear previous styles
		bodyStyle.fontSize = '';

		if(body.classList.contains('show-thumbnails')
			|| slide.classList.contains('dont-resize')) {
			return;
		}

		for(
			var percent = 100;
			(scrollRoot.scrollHeight > innerHeight || scrollRoot.scrollWidth > innerWidth) && percent >= 35;
			percent-=5
		) {
			bodyStyle.fontSize = percent + '%';
		}

		// Individual slide

		if(slide.clientHeight && slide.clientWidth) {
			// Strange FF bug: scrollHeight doesn't work properly with overflow:hidden
			var previousStyle = slide.getAttribute('style');
			slide.style.overflow = 'auto';

			for(
				;
				(slide.scrollHeight > slide.clientHeight || slide.scrollWidth > slide.clientWidth) && percent >= 35;
				percent--
			) {
				bodyStyle.fontSize = percent + '%';
			}

			slide.setAttribute('style', previousStyle);
		}
	},

	// Is the element on the current slide?
	onCurrent: function(element) {
		var slide = self.getSlide(element);

		if(slide) {
			return '#' + slide.id === location.hash;
		}

		return false;
	}
};

/**********************************************
 * Static methods
 **********************************************/

// Helper method for plugins
self.getSlide = function(element) {
	var slide = element;

	while (slide && slide.classList && !slide.classList.contains('slide')) {
		slide = slide.parentNode;
	}

	return slide;
}

})(document.head || document.getElementsByTagName('head')[0], document.body);<commit_msg>Added convenience functions $ and $$<commit_after>/**
 * CSSS javascript code
 * @author Lea Verou (http://leaverou.me)
 * @version 2.0
 */

/**
 * Make the environment a bit friendlier
 */
function $(expr, con) { return (con || document).querySelector(expr); }
function $$(expr, con) { return [].slice.call((con || document).querySelectorAll(expr)); }

(function(head, body){

// Check for classList support and include the polyfill if it's not supported
if(!('classList' in body)) {
	var thisScript = $('script[src$="slideshow.js"]'),
	    script = document.createElement('script');
	    script.src = thisScript.src.replace(/\bslideshow\.js/, 'classList.js');
	thisScript.parentNode.insertBefore(script, thisScript);
}

// Cache <title> element, we may need it for slides that don't have titles
var documentTitle = document.title + '';

var self = window.SlideShow = function(container, slide) {
	var me = this;

	// Set instance
	if(!window.slideshow) {
		window.slideshow = this;
	}

	container = container || body;

	// Current slide
	this.slide = slide || 0;

	// Current .delayed item in the slide
	this.item = 0;

	// Do we need to add a timer?
	this.duration = container.getAttribute('data-duration');

	if(this.duration > 0) {
		var timer = document.createElement('div'),
		    declaration = 'transition: ' + this.duration * 60 + 's linear; ';

		timer.id = 'timer';
		timer.setAttribute('style', '-moz-' + declaration + '-webkit-' + declaration + '-o-' + declaration + '-ms-' + declaration + declaration);
		container.appendChild(timer);

		setTimeout(function() {
			timer.className = 'end';
		}, 1);
	}

	// Get the slide elements into an array
	this.slides = Array.prototype.slice.apply($$('.slide', container));

	for(var i=0; i<this.slides.length; i++) {
		var slide = this.slides[i]; // to speed up references

		// Asign ids to slides that don't have one
		if(!slide.id) {
			slide.id = 'slide' + (i+1);
		}

		// Set data-title attribute to the title of the slide
		if(!slide.title) {
			// no title attribute, fetch title from heading(s)
			var heading = $('hgroup', screen) || $('h1,h2,h3,h4,h5,h6', slide);

			if(heading && heading.textContent.trim()) {
				slide.setAttribute('data-title', heading.textContent);
			}
		}
		else {
			// The title attribute is set, use that
			slide.setAttribute('data-title', slide.title);
			slide.removeAttribute('title');
		}
	}

	// If there's already a hash, update current slide number...
	this.goto(location.hash.substr(1) || 0);

	// ...and keep doing so every time the hash changes
	this.onhashchange = function() {
		me.goto(location.hash.substr(1) || 0);
	};
	window.addEventListener('hashchange', this.onhashchange, false);

	if(window.name === 'projector') {
		document.body.classList.add('projector');
	}

	// Adjust the font-size when the window is resized
	addEventListener('resize', function() {
		me.adjustFontSize();
	}, false);

	// In some browsers DOMContentLoaded is too early, so try again onload
	addEventListener('load', function() {
		me.adjustFontSize();
	}, false);

	/**
		Keyboard navigation
		Ctrl+G : Go to slide...
		Ctrl+H : Show thumbnails and go to slide
		Ctrl+P : Presenter view
		(Shift instead of Ctrl works too)
	*/
	document.addEventListener('keyup', function(evt) {
		if(evt.ctrlKey || evt.shiftKey) {
			switch(evt.keyCode) {
				case 71: // G
					var slide = prompt('Which slide?');
					me.goto(+slide? slide - 1 : slide);
					break;
				case 72: // H
					if(body.classList.contains('show-thumbnails')) {
						body.classList.remove('show-thumbnails');
						body.classList.remove('headers-only');
					}
					else {
						body.classList.add('show-thumbnails');

						if(!evt.shiftKey || !evt.ctrlKey) {
							body.classList.add('headers-only');
						}

						body.addEventListener('click', function(evt) {
							var slide = evt.target;

							while(slide && !slide.classList.contains('slide')) {
								slide = slide.parentNode;
							}

							if(slide) {
								me.goto(slide.id);
								setTimeout(function() { me.adjustFontSize(); }, 1000); // for Opera
							}

							body.classList.remove('show-thumbnails');
							body.classList.remove('headers-only');
						}, false);
					}
					break;
				case 74: // J
					if(body.classList.contains('hide-elements')) {
						body.classList.remove('hide-elements');
					}
					else {
						body.classList.add('hide-elements');
					}
					break;
				case 80: // P
					// Open new window for attendee view
					me.projector = window.open(location, 'projector', 'menubar=yes,location=yes,resizable=yes,scrollbars=yes,status=yes');

					// Get the focus back
					window.focus();

					// Switch this one to presenter view
					body.classList.add('presenter');
			}
		}
	}, false);

	/**
		Keyboard navigation
		Home : First slide
		End : Last slide
		Space/Up/Right arrow : Next item/slide
		Ctrl + Space/Up/Right arrow : Next slide
		Down/Left arrow : Previous item/slide
		Ctrl + Down/Left arrow : Previous slide
		(Shift instead of Ctrl works too)
	*/
	document.addEventListener('keydown', function(evt) {
		if(evt.target === body || evt.target === body.parentNode || evt.altKey) {
			if(evt.keyCode >= 32 && evt.keyCode <= 40) {
				evt.preventDefault();
			}

			switch(evt.keyCode) {
				case 33: //page up
					me.previous();
					break;
				case 34: //page down
					me.next();
					break;
				case 35: // end
					me.end();
					break;
				case 36: // home
					me.start();
					break;
				case 37: // <-
				case 38: // up arrow
					me.previous(evt.ctrlKey || evt.shiftKey);
					break;
				case 32: // space
				case 39: // ->
				case 40: // down arrow
					me.next(evt.ctrlKey || evt.shiftKey);
					break;
			}
		}
	}, false);

	// Rudimentary style[scoped] polyfill
	var scoped = $$('style[scoped]', container);

	for(var i=scoped.length; i--;) {
		var style = scoped[i],
			rulez = style.sheet.cssRules,
			parentid = style.parentNode.id || self.getSlide(style).id;

		for(var j=rulez.length; j--;) {
			var cssText = rulez[j].cssText.replace(/^|,/g, function($0) { return '#' + parentid + ' ' + $0 });

			style.sheet.deleteRule(0);
			style.sheet.insertRule(cssText, 0);
		}
	}
}

self.prototype = {
	start: function() {
		this.goto(0);
	},

	end: function() {
		this.goto(this.slides.length - 1);
	},

	/**
		@param hard {Boolean} Whether to advance to the next slide (true) or
			just the next step (which could very well be showing a list item)
	 */
	next: function(hard) {
		if(!hard && this.items.length) {
			// If there's no current, then just mark the first one as such
			if(!this.item) {
				this.items[this.item++].classList.add('current');
			}
			// Add .current to current item if it exists, otherwise advance to next slide
			else if(this.item < this.items.length) {
				classes = this.items[this.item - 1].classList; // to speed up lookups

				classes.remove('current');
				classes.add('displayed');

				this.items[this.item++].classList.add('current');
			}
			else {
				this.item = 0;
				this.next(true);
			}
		}
		else {
			this.goto(this.slide + 1);

			this.item = 0;

			// Mark all items as not displayed, if there are any
			if(this.items.length) {
				for (var i=0; i<this.items.length; i++) {
					if(this.items[i].classList) {
						this.items[i].classList.remove('displayed');
						this.items[i].classList.remove('current');
					}
				}
			}
		}
	},

	previous: function(hard) {
		if(!hard && this.item > 0) {
			var classes = this.items[this.item - 1].classList; // to speed up lookups

			classes.remove('current');

			if(this.item > 1) {
				classes = this.items[--this.item - 1].classList;

				classes.remove('displayed');
				classes.add('current');
			}
			else {
				this.item = 0;
			}
		}
		else {

			this.goto(this.slide - 1);

			this.item = this.items.length;

			// Mark all items as displayed, if there are any
			if(this.items.length) {
				for (var i=0; i<this.items.length; i++) {
					if(this.items[i].classList) {
						this.items[i].classList.add('displayed');
					}
				}

				// Mark the last one as current
				var lastItem = this.items[this.items.length - 1];

				lastItem.classList.remove('displayed');
				lastItem.classList.add('current');
			}
		}
	},

	/**
		Go to an aribtary slide
		@param which {String|Integer} Which slide (identifier or slide number)
	*/
	goto: function(which) {
		var slide;

		// We have to remove it to prevent multiple calls to goto messing up
		// our current item (and there's no point either, so we save on performance)
		window.removeEventListener('hashchange', this.onhashchange, false);

		if(which + 0 === which && which in this.slides) { // Argument is a valid slide number
			this.slide = which;

			slide = this.slides[this.slide];
			location.hash = '#' + slide.id;
		}
		else if(which + '' === which) { // Argument is a slide id
			slide = document.getElementById(which);

			if(slide) {
				this.slide = this.slides.indexOf(slide);
				location.hash = '#' + which;
			}
		}

		if(slide) { // Slide actually changed, perform any other tasks needed
			document.title = slide.getAttribute('data-title') || documentTitle;

			this.adjustFontSize();

			// Update items collection
			this.items = $$('.delayed, .delayed-children > *', this.slides[this.slide]);
			this.item = 0;

			// Tell other windows
			if(this.projector && this.projector.slideshow && this.projector.slideshow.slide != this.slide) {
				this.projector.slideshow.goto(this.slide);
			}

			if(window.opener && opener.slideshow && opener.slideshow.slide != this.slide) {
				opener.slideshow.goto(this.slide);
			}
		}

		// If you attach the listener immediately again then it will catch the event
		// We have to do it asynchronously
		var me = this;
		setTimeout(function() {
			window.addEventListener('hashchange', me.onhashchange, false);
		}, 1000);
	},

	adjustFontSize: function() {
		// Cache long lookup chains, for performance
		var bodyStyle = body.style,
			scrollRoot = document[document.documentElement.scrollHeight? 'documentElement' : 'body'],
			innerHeight = window.innerHeight,
			innerWidth = window.innerWidth,
			slide = this.slides[this.slide];

		// Clear previous styles
		bodyStyle.fontSize = '';

		if(body.classList.contains('show-thumbnails')
			|| slide.classList.contains('dont-resize')) {
			return;
		}

		for(
			var percent = 100;
			(scrollRoot.scrollHeight > innerHeight || scrollRoot.scrollWidth > innerWidth) && percent >= 35;
			percent-=5
		) {
			bodyStyle.fontSize = percent + '%';
		}

		// Individual slide

		if(slide.clientHeight && slide.clientWidth) {
			// Strange FF bug: scrollHeight doesn't work properly with overflow:hidden
			var previousStyle = slide.getAttribute('style');
			slide.style.overflow = 'auto';

			for(
				;
				(slide.scrollHeight > slide.clientHeight || slide.scrollWidth > slide.clientWidth) && percent >= 35;
				percent--
			) {
				bodyStyle.fontSize = percent + '%';
			}

			slide.setAttribute('style', previousStyle);
		}
	},

	// Is the element on the current slide?
	onCurrent: function(element) {
		var slide = self.getSlide(element);

		if(slide) {
			return '#' + slide.id === location.hash;
		}

		return false;
	}
};

/**********************************************
 * Static methods
 **********************************************/

// Helper method for plugins
self.getSlide = function(element) {
	var slide = element;

	while (slide && slide.classList && !slide.classList.contains('slide')) {
		slide = slide.parentNode;
	}

	return slide;
}

})(document.head || document.getElementsByTagName('head')[0], document.body);<|endoftext|>


<commit_before>// cross-component updating via pubsub
import _ from 'lodash';
import { create } from 'eventify';
import cuid from 'cuid';
import { pubProp, subProp, componentRoute, getComponentName } from '../utils/references';
import { ADD_SCHEMA } from './mutationTypes';
import { PRELOAD_SUCCESS } from '../preloader/mutationTypes';
import { getSchema } from '../core-data/components';

// Ask veit ek standa, heitir Yggdrasill
// hár baðmr, ausinn hvíta auri;
// þaðan koma döggvar þærs í dala falla;
// stendr æ yfir grœnn Urðar brunni.
export const yggdrasil = create();
// Subscribers they made there, and life allotted
// To the sons of components, and set their fates

// exported for testing
export let callStacks = {};
// when we publish for the first time, we generate an eventID.
// as components pub and sub, they add themselves to the call stack for that eventID
// e.g. callStacks[a8s7c].history = ['A+', 'B-', 'C-'] for components A → B → C
// when components pub, they're denoted with +, e.g. 'A+'
// when components sub, they're denoted with -, e.g. 'B-'
// this allows us to differentiate between cyclic regression (expected, but we stop the event chain at that point)
// and nondeterministic updates (unexpected, and we warn the dev so they can eliminate them).
// cyclic regression looks like [..., 'A+', 'A-'] (pub, then sub of the same component)
// nondeterministic updates look like [..., 'B-', 'B-'] (sub, then sub of the same component)
// note: they don't have to be right next to each other

var subscriberRegistry = {};
// when subscribers are registered, they add themselves to this registry.
// when the subscribers are fired, they add their data here
// (and check to see if all subcribers have been fired).
// once all subscribers for a component have fired, the data gets merged, the component saves,
// and the subscribed properties are nulled (ready for the next publish).
// when registering, an entry looks like: { <component name>: { <subscribed prop>: null } }
// when firing, it looks like { <component name>: { <subscribed prop>: { <field>: <value> } } }

/**
 * update all instances of a component
 * @param  {Object} store
 * @param  {string} name  of the component
 * @param {string} [eventID]
 * @param {boolean} [snapshot]
 */
function updateComponents(store, {name, eventID, snapshot}) {
  _.forOwn(store.state.components, (componentData, uri) => {
    if (_.includes(uri, `${componentRoute}${name}`)) {
      // grab all the data to update
      const data = _.reduce(subscriberRegistry[name], (result, field) => _.assign(result, field), {});

      // null out the subscriberRegistry
      _.forOwn(subscriberRegistry[name], (field, prop) => {
        subscriberRegistry[name][prop] = null;
      });
      store.dispatch('saveComponent', {uri, data, eventID, snapshot});
    }
  });
}

/**
 * add listeners to all schemas after preloading them
 * @param {Object} schemae
 * @param {Object} store
 */
function addPreloadedListeners(schemae, store) {
  /* istanbul ignore next */
  _.forOwn(schemae, (schema, name) => addListeners(name, schema, store));
}

/**
 * add listeners for updating the page list
 * @param {object} store
 */
function addPageListListeners(store) {
  // listen for page title
  yggdrasil.on('kilnTitle', (data) => {
    store.dispatch('updatePageList', { title: data });
  });
  // listen for page authors
  yggdrasil.on('kilnAuthors', (data) => {
    store.dispatch('updatePageList', { authors: data });
  });
}

/**
 * trace a graph to find the path to a property
 * e.g. find 'D' in { A: { prop1: { B: { prop2: { C: {}, D: {}}}}}}
 * which would return 'A.prop1.B.prop2.D'
 * @param  {object} graph to search inside
 * @param  {string} propName to search for
 * @param {string} [currentPath] if searching deep in a graph
 * @param {string} [resultPath] final result path
 * @return {array}
 */
function graphTrace(graph, propName, currentPath, resultPath) {
  currentPath = currentPath || 'root';

  // iterate through this level of the graph, recursing to the next level
  _.forOwn(graph, (val, key) => {
    if (key === propName) {
      resultPath = `${currentPath}.${propName}`;
      return false;
    } else if (!resultPath) {
      resultPath = graphTrace(val, propName, `${currentPath}.${key}`, resultPath);
    }
  });
  return resultPath ? resultPath.replace('root.', '') : null;
}

function isOdd(n) {
  return Math.abs(n % 2) === 1;
}

/**
 * format a path (to a property or component name)
 * note: this is used to inform devs about circular / nondeterministic updates
 * @param  {string} path A.prop1.B.prop2, etc
 * @param {boolean} isCompleted
 * @return {string}
 */
function formatPath(path, isCompleted) {
  const pathArray = path.split('.'),
    componentNames = pathArray.filter((key, index) => isOdd(index + 1)),
    initialNames = componentNames.length > 1 ? _.initial(componentNames) : componentNames;

  return initialNames.join(' → ') + ` ${isCompleted ? '→' : '⤏'} ${_.last(pathArray)}`;
}

/**
 * function to call when subscribe is triggered
 * @param  {Object} data
 * @param  {string} eventID
 * @param  {string} name
 * @param  {string} fieldName
 * @param {string} prop that this subscribed to
 * @param  {Object} store
 * @param {boolean} [snapshot]
 * @return {Promise|undefined}
 */
function onSubscribe(data, eventID, {name, fieldName, prop, store, snapshot}) {
  if (_.includes(callStacks[eventID].history, `${name}+`)) {
    // component has already published! don't save it again
    // we don't care about logging cyclic event chains (because they're expected)
    console.log(`Cyclic update: ${name} already published! (${formatPath(graphTrace(callStacks[eventID].graph, name))})`);
  } else {
    const path = graphTrace(callStacks[eventID].graph, prop);

    let allSubscribersFinished;

    callStacks[eventID].history.push(`${name}-`);
    _.set(callStacks[eventID].graph, `${path}.${name}`, {});
    subscriberRegistry[name][prop] = { [fieldName]: data };

    allSubscribersFinished = _.every(subscriberRegistry[name], _.isObject);
    if (allSubscribersFinished) {
      return updateComponents(store, { name, eventID, snapshot });
    }
  }
}

/**
 * add listeners in a schema
 * note: exported for testing
 * @param {string} name   of component
 * @param {Object} schema
 * @param {Object} store
 */
export function addListeners(name, schema, store) {
  _.forOwn(schema, (field, fieldName) => {
    if (_.isObject(field) && field[subProp]) {
      const prop = field[subProp];

      // fun undocumented api: eventify allows multiple space-delineated events
      yggdrasil.on(prop, (data, eventID, snapshot) => onSubscribe(data, eventID, {name, fieldName, prop, store, snapshot}));
      // add the subscribers to the registry
      subscriberRegistry[name] = subscriberRegistry[name] || {};
      subscriberRegistry[name][prop] = null;
    }
  });
}

/**
 * pubsub plugin
 * adds subscribers whenever we add new schemas
 * those subscribers update ALL instances of a component when their subscribed properties change
 * @param  {object} store
 */
export default function pubsub(store) {
  // after schemas are loaded (and when loading any new schemas),
  // parse them for new subscribers
  store.subscribe((mutation) => {
    /* istanbul ignore if */
    if (mutation.type === PRELOAD_SUCCESS) {
      addPreloadedListeners(_.get(store, 'state.schemas'), store);
      addPageListListeners(store);
    } else if (mutation.type === ADD_SCHEMA) {
      addListeners(mutation.payload.name, mutation.payload.data, store);
    }
  });
}

/**
 * quickly determine if a component has ANY fields that will trigger publish
 * @param  {object} schema
 * @return {boolean}
 */
function shouldPublish(schema) {
  return !!_.find(schema, (field) => _.isObject(field) && field[pubProp]);
}

/**
 * when publishing for the first time after a manual save,
 * generate an event ID and add the component name to the call stack.
 * this ID will propagate out to all listeners, preventing
 * infinite cyclic recursion and warning devs about nondeterministic behavior
 * @param  {string} name
 * @param  {string} [eventID]
 * @return {string}
 */
function generateEventID(name, eventID) {
  if (!eventID) {
    eventID = cuid();
    callStacks[eventID] = {
      history: [`${name}+`],
      graph: {
        [name]: {}
      }
    };
  } else {
    const path = graphTrace(callStacks[eventID].graph, name);

    // if this publish was triggered in an event chain, just add the component to the call stack
    callStacks[eventID].history.push(`${name}+`);
    _.set(callStacks[eventID].graph, path, {});
  }

  return eventID;
}

/**
 * publish properties
 * note: this is called after model.js runs
 * @param  {string} uri
 * @param  {Object} data
 * @param {string} [eventID]
 * @param {boolean} [snapshot]
 */
export function publish(uri, data, eventID, snapshot) {
  const schema = getSchema(uri),
    name = getComponentName(uri);

  if (shouldPublish(schema)) {
    eventID = generateEventID(name, eventID);
  }

  _.forOwn(schema, (field, fieldName) => {
    if (_.isObject(field) && field[pubProp]) {
      const prop = field[pubProp],
        path = graphTrace(callStacks[eventID].graph, name);

      // fun undocumented api: eventify allows multiple space-delineated events
      _.set(callStacks[eventID].graph, `${path}.${prop}`, {});
      yggdrasil.trigger(field[pubProp], data[fieldName], eventID, snapshot);
      // note: this uses the same eventID for every prop/field, so a component
      // will never save twice in the same event chain
      // (even if the props/fields they're pub-subbing are different)
    }
  });
}
<commit_msg>removed unnecesary log<commit_after>// cross-component updating via pubsub
import _ from 'lodash';
import { create } from 'eventify';
import cuid from 'cuid';
import { pubProp, subProp, componentRoute, getComponentName } from '../utils/references';
import { ADD_SCHEMA } from './mutationTypes';
import { PRELOAD_SUCCESS } from '../preloader/mutationTypes';
import { getSchema } from '../core-data/components';

// Ask veit ek standa, heitir Yggdrasill
// hár baðmr, ausinn hvíta auri;
// þaðan koma döggvar þærs í dala falla;
// stendr æ yfir grœnn Urðar brunni.
export const yggdrasil = create();
// Subscribers they made there, and life allotted
// To the sons of components, and set their fates

// exported for testing
export let callStacks = {};
// when we publish for the first time, we generate an eventID.
// as components pub and sub, they add themselves to the call stack for that eventID
// e.g. callStacks[a8s7c].history = ['A+', 'B-', 'C-'] for components A → B → C
// when components pub, they're denoted with +, e.g. 'A+'
// when components sub, they're denoted with -, e.g. 'B-'
// this allows us to differentiate between cyclic regression (expected, but we stop the event chain at that point)
// and nondeterministic updates (unexpected, and we warn the dev so they can eliminate them).
// cyclic regression looks like [..., 'A+', 'A-'] (pub, then sub of the same component)
// nondeterministic updates look like [..., 'B-', 'B-'] (sub, then sub of the same component)
// note: they don't have to be right next to each other

var subscriberRegistry = {};
// when subscribers are registered, they add themselves to this registry.
// when the subscribers are fired, they add their data here
// (and check to see if all subcribers have been fired).
// once all subscribers for a component have fired, the data gets merged, the component saves,
// and the subscribed properties are nulled (ready for the next publish).
// when registering, an entry looks like: { <component name>: { <subscribed prop>: null } }
// when firing, it looks like { <component name>: { <subscribed prop>: { <field>: <value> } } }

/**
 * update all instances of a component
 * @param  {Object} store
 * @param  {string} name  of the component
 * @param {string} [eventID]
 * @param {boolean} [snapshot]
 */
function updateComponents(store, {name, eventID, snapshot}) {
  _.forOwn(store.state.components, (componentData, uri) => {
    if (_.includes(uri, `${componentRoute}${name}`)) {
      // grab all the data to update
      const data = _.reduce(subscriberRegistry[name], (result, field) => _.assign(result, field), {});

      // null out the subscriberRegistry
      _.forOwn(subscriberRegistry[name], (field, prop) => {
        subscriberRegistry[name][prop] = null;
      });
      store.dispatch('saveComponent', {uri, data, eventID, snapshot});
    }
  });
}

/**
 * add listeners to all schemas after preloading them
 * @param {Object} schemae
 * @param {Object} store
 */
function addPreloadedListeners(schemae, store) {
  /* istanbul ignore next */
  _.forOwn(schemae, (schema, name) => addListeners(name, schema, store));
}

/**
 * add listeners for updating the page list
 * @param {object} store
 */
function addPageListListeners(store) {
  // listen for page title
  yggdrasil.on('kilnTitle', (data) => {
    store.dispatch('updatePageList', { title: data });
  });
  // listen for page authors
  yggdrasil.on('kilnAuthors', (data) => {
    store.dispatch('updatePageList', { authors: data });
  });
}

/**
 * trace a graph to find the path to a property
 * e.g. find 'D' in { A: { prop1: { B: { prop2: { C: {}, D: {}}}}}}
 * which would return 'A.prop1.B.prop2.D'
 * @param  {object} graph to search inside
 * @param  {string} propName to search for
 * @param {string} [currentPath] if searching deep in a graph
 * @param {string} [resultPath] final result path
 * @return {array}
 */
function graphTrace(graph, propName, currentPath, resultPath) {
  currentPath = currentPath || 'root';

  // iterate through this level of the graph, recursing to the next level
  _.forOwn(graph, (val, key) => {
    if (key === propName) {
      resultPath = `${currentPath}.${propName}`;
      return false;
    } else if (!resultPath) {
      resultPath = graphTrace(val, propName, `${currentPath}.${key}`, resultPath);
    }
  });
  return resultPath ? resultPath.replace('root.', '') : null;
}

/**
 * function to call when subscribe is triggered
 * @param  {Object} data
 * @param  {string} eventID
 * @param  {string} name
 * @param  {string} fieldName
 * @param {string} prop that this subscribed to
 * @param  {Object} store
 * @param {boolean} [snapshot]
 * @return {Promise|undefined}
 */
function onSubscribe(data, eventID, {name, fieldName, prop, store, snapshot}) {
  if (!_.includes(callStacks[eventID].history, `${name}+`)) {
    // component has not already been published (that would be a cyclic update)
    // cyclic updates are expected, but should be blocked as they lead to infinite regression
    const path = graphTrace(callStacks[eventID].graph, prop);

    let allSubscribersFinished;

    callStacks[eventID].history.push(`${name}-`);
    _.set(callStacks[eventID].graph, `${path}.${name}`, {});
    subscriberRegistry[name][prop] = { [fieldName]: data };

    allSubscribersFinished = _.every(subscriberRegistry[name], _.isObject);
    if (allSubscribersFinished) {
      return updateComponents(store, { name, eventID, snapshot });
    }
  }
}

/**
 * add listeners in a schema
 * note: exported for testing
 * @param {string} name   of component
 * @param {Object} schema
 * @param {Object} store
 */
export function addListeners(name, schema, store) {
  _.forOwn(schema, (field, fieldName) => {
    if (_.isObject(field) && field[subProp]) {
      const prop = field[subProp];

      // fun undocumented api: eventify allows multiple space-delineated events
      yggdrasil.on(prop, (data, eventID, snapshot) => onSubscribe(data, eventID, {name, fieldName, prop, store, snapshot}));
      // add the subscribers to the registry
      subscriberRegistry[name] = subscriberRegistry[name] || {};
      subscriberRegistry[name][prop] = null;
    }
  });
}

/**
 * pubsub plugin
 * adds subscribers whenever we add new schemas
 * those subscribers update ALL instances of a component when their subscribed properties change
 * @param  {object} store
 */
export default function pubsub(store) {
  // after schemas are loaded (and when loading any new schemas),
  // parse them for new subscribers
  store.subscribe((mutation) => {
    /* istanbul ignore if */
    if (mutation.type === PRELOAD_SUCCESS) {
      addPreloadedListeners(_.get(store, 'state.schemas'), store);
      addPageListListeners(store);
    } else if (mutation.type === ADD_SCHEMA) {
      addListeners(mutation.payload.name, mutation.payload.data, store);
    }
  });
}

/**
 * quickly determine if a component has ANY fields that will trigger publish
 * @param  {object} schema
 * @return {boolean}
 */
function shouldPublish(schema) {
  return !!_.find(schema, (field) => _.isObject(field) && field[pubProp]);
}

/**
 * when publishing for the first time after a manual save,
 * generate an event ID and add the component name to the call stack.
 * this ID will propagate out to all listeners, preventing
 * infinite cyclic recursion and warning devs about nondeterministic behavior
 * @param  {string} name
 * @param  {string} [eventID]
 * @return {string}
 */
function generateEventID(name, eventID) {
  if (!eventID) {
    eventID = cuid();
    callStacks[eventID] = {
      history: [`${name}+`],
      graph: {
        [name]: {}
      }
    };
  } else {
    const path = graphTrace(callStacks[eventID].graph, name);

    // if this publish was triggered in an event chain, just add the component to the call stack
    callStacks[eventID].history.push(`${name}+`);
    _.set(callStacks[eventID].graph, path, {});
  }

  return eventID;
}

/**
 * publish properties
 * note: this is called after model.js runs
 * @param  {string} uri
 * @param  {Object} data
 * @param {string} [eventID]
 * @param {boolean} [snapshot]
 */
export function publish(uri, data, eventID, snapshot) {
  const schema = getSchema(uri),
    name = getComponentName(uri);

  if (shouldPublish(schema)) {
    eventID = generateEventID(name, eventID);
  }

  _.forOwn(schema, (field, fieldName) => {
    if (_.isObject(field) && field[pubProp]) {
      const prop = field[pubProp],
        path = graphTrace(callStacks[eventID].graph, name);

      // fun undocumented api: eventify allows multiple space-delineated events
      _.set(callStacks[eventID].graph, `${path}.${prop}`, {});
      yggdrasil.trigger(field[pubProp], data[fieldName], eventID, snapshot);
      // note: this uses the same eventID for every prop/field, so a component
      // will never save twice in the same event chain
      // (even if the props/fields they're pub-subbing are different)
    }
  });
}
<|endoftext|>


<commit_before>/* jshint node: true */
'use strict';

var zdoHelper = {},
    zdoReqRspMap,
    zdoIndSuffix;

zdoReqRspMap = {
    nwkAddrReq:         { ind: 'nwkAddrRsp',        apiType: 'concat',  suffix: [ 'ieeeaddr', 'startindex' ]        },
    ieeeAddrReq:        { ind: 'ieeeAddrRsp',       apiType: 'concat',  suffix: [ 'shortaddr' ]                     }, // 'startindex' mismatch
    nodeDescReq:        { ind: 'nodeDescRsp',       apiType: 'generic', suffix: [ 'nwkaddrofinterest' ]             },
    powerDescReq:       { ind: 'powerDescRsp',      apiType: 'generic', suffix: [ 'nwkaddrofinterest' ]             },
    simpleDescReq:      { ind: 'simpleDescRsp',     apiType: 'generic', suffix: [ 'nwkaddrofinterest', 'endpoint' ] },
    activeEpReq:        { ind: 'activeEpRsp',       apiType: 'generic', suffix: [ 'nwkaddrofinterest' ]             },
    matchDescReq:       { ind: 'matchDescRsp',      apiType: 'generic', suffix: [ 'nwkaddrofinterest' ]             },
    complexDescReq:     { ind: 'complexDescRsp',    apiType: 'generic', suffix: [ 'nwkaddrofinterest' ]             },
    userDescReq:        { ind: 'userDescRsp',       apiType: 'generic', suffix: [ 'nwkaddrofinterest' ]             },
    userDescSet:        { ind: 'userDescConf',      apiType: 'generic', suffix: [ 'nwkaddrofinterest' ]             },
    serverDiscReq:      { ind: 'serverDiscRsp',     apiType: 'special', suffix: []                                  },
    endDeviceBindReq:   { ind: 'endDeviceBindRsp',  apiType: 'generic', suffix: [ 'dstaddr' ]                       }, // address 16bit mode unsupported
    bindReq:            { ind: 'bindRsp',           apiType: 'special', suffix: [ 'dstaddr' ]                       },
    unbindReq:          { ind: 'unbindRsp',         apiType: 'generic', suffix: [ 'dstaddr' ]                       },
    nwkDiscoveryReq:    { ind: 'nwkDiscoveryCnf',   apiType: 'generic', suffix: []                                  },
    joinReq:            { ind: 'joinCnf',           apiType: 'generic', suffix: []                                  },
    mgmtNwkDiscReq:     { ind: 'mgmtNwkDiscRsp',    apiType: 'concat',  suffix: [ 'dstaddr', 'startindex' ]         },
    mgmtLqiReq:         { ind: 'mgmtLqiRsp',        apiType: 'concat',  suffix: [ 'dstaddr', 'startindex' ]         },
    mgmtRtgReq:         { ind: 'mgmtRtgRsp',        apiType: 'concat',  suffix: [ 'dstaddr', 'startindex' ]         },
    mgmtBindReq:        { ind: 'mgmtBindRsp',       apiType: 'concat',  suffix: [ 'dstaddr', 'startindex' ]         },
    mgmtLeaveReq:       { ind: 'mgmtLeaveRsp',      apiType: 'generic', suffix: [ 'dstaddr' ]                       },
    mgmtDirectJoinReq:  { ind: 'mgmtDirectJoinRsp', apiType: 'generic', suffix: [ 'dstaddr' ]                       },
    mgmtPermitJoinReq:  { ind: 'mgmtPermitJoinRsp', apiType: 'special', suffix: [ 'dstaddr' ]                       },
    mgmtNwkUpdateReq:   null,
    endDeviceAnnce:     null,
    msgCbRegister:      null,
    msgCbRemove:        null,
    startupFromApp:     null,
    setLinkKey:         null,
    removeLinkKey:      null,
    getLinkKey:         null,
    secAddLinkKey:      null,
    secEntryLookupExt:  null,
    extRouteDisc:       null,
    extRouteCheck:      null,
    extRemoveGroup:     null,
    extRemoveAllGroup:  null,
    extFindGroup:       null,
    extAddGroup:        null,
    extCountAllGroups:  null,
    extRxIdle:          null,
    extUpdateNwkKey:    null,
    extSwitchNwkKey:    null,
    extNwkInfo:         null,
    extSecApsRemoveReq: null,
    extFindAllGroupsEndpoint: null,
    forceConcentratorChange:  null,
    extSetParams:             null,
    endDeviceTimeoutReq:      null,
    sendData:                 null,
    nwkAddrOfInterestReq:     null
};

zdoIndSuffix = {
    nwkAddrRsp:        [ 'ieeeaddr', 'startindex' ],
    ieeeAddrRsp:       [ 'nwkaddr' ],  // 'startindex' mismatch
    nodeDescRsp:       [ 'nwkaddr' ],
    powerDescRsp:      [ 'nwkaddr' ],
    simpleDescRsp:     [ 'nwkaddr', 'endpoint' ],
    activeEpRsp:       [ 'nwkaddr' ],
    matchDescRsp:      [ 'nwkaddr' ],
    complexDescRsp:    [ 'nwkaddr' ],
    userDescRsp:       [ 'nwkaddr' ],
    userDescConf:      [ 'nwkaddr' ],
    serverDiscRsp:     null,    // special, listen at controller.on('ZDO:serverDiscRsp')
    endDeviceBindRsp:  [ 'srcaddr' ],
    bindRsp:           [ 'srcaddr' ],
    unbindRsp:         [ 'srcaddr' ],
    nwkDiscoveryCnf:   null,
    joinCnf:           null,
    mgmtNwkDiscRsp:    [ 'srcaddr', 'startindex' ],
    mgmtLqiRsp:        [ 'srcaddr', 'startindex' ],
    mgmtRtgRsp:        [ 'srcaddr', 'startindex' ],
    mgmtBindRsp:       [ 'srcaddr', 'startindex' ],
    mgmtLeaveRsp:      [ 'srcaddr' ],
    mgmtDirectJoinRsp: [ 'srcaddr' ],
    mgmtPermitJoinRsp: [ 'srcaddr' ],
    stateChangeInd:    null,    // very special, tackled in event_bridge._zdoIndicationEventBridge()
    endDeviceAnnceInd: null,
    matchDescRspSent:  null,
    statusErrorRsp:    null,
    srcRtgInd:         null,
    beacon_notify_ind: null,
    leaveInd:          null,
    msgCbIncoming:     null,
    tcDeviceInd:       null,
    permitJoinInd:     null,
    networkStatus:     ['dstaddr']
};

/*************************************************************************************************/
/*** Public APIs                                                                               ***/
/*************************************************************************************************/
zdoHelper.hasAreq = function (reqName) {
    var meta = zdoReqRspMap[reqName];
    return meta ? (!!meta.ind) : false;
};

zdoHelper.getRequestType = function (reqName) {
    var meta = zdoReqRspMap[reqName];
    return meta ? meta.apiType : 'rspless';
};

function generateEvent(indName, meta, valObj){
    if (!meta || (meta.length === 0))
        return;

    let evtName = 'ZDO:' + indName;

    for(const key of meta) {
        if(key === 'srcaddr' || key === 'nwkaddr' || key === 'nwkaddrofinterest' || key === 'dstaddr') evtName += ':' + valObj[key].toString(16)
        else evtName += ':' + valObj[key].toString()
    }

    return evtName
}

zdoHelper.generateEventOfRequest = function (reqName, valObj) {
    var meta = zdoReqRspMap[reqName]

    if (!zdoHelper.hasAreq(reqName))
        return;

    return generateEvent(meta.ind, meta.suffix, valObj)
};

zdoHelper.generateEventOfIndication = function (indName, msgData) {
    return generateEvent(indName, zdoIndSuffix[indName], msgData)
};

zdoHelper.generateDstAddrOfRequest = function(valObj){
    if(valObj.dstaddr) return valObj.dstaddr.toString(16)
    if(valObj.nwkaddrofinterest) return valObj.nwkaddrofinterest.toString(16)
};

module.exports = zdoHelper;
<commit_msg>add shortaddr to list of hexed fields<commit_after>/* jshint node: true */
'use strict';

var zdoHelper = {},
    zdoReqRspMap,
    zdoIndSuffix;

zdoReqRspMap = {
    nwkAddrReq:         { ind: 'nwkAddrRsp',        apiType: 'concat',  suffix: [ 'ieeeaddr', 'startindex' ]        },
    ieeeAddrReq:        { ind: 'ieeeAddrRsp',       apiType: 'concat',  suffix: [ 'shortaddr' ]                     }, // 'startindex' mismatch
    nodeDescReq:        { ind: 'nodeDescRsp',       apiType: 'generic', suffix: [ 'nwkaddrofinterest' ]             },
    powerDescReq:       { ind: 'powerDescRsp',      apiType: 'generic', suffix: [ 'nwkaddrofinterest' ]             },
    simpleDescReq:      { ind: 'simpleDescRsp',     apiType: 'generic', suffix: [ 'nwkaddrofinterest', 'endpoint' ] },
    activeEpReq:        { ind: 'activeEpRsp',       apiType: 'generic', suffix: [ 'nwkaddrofinterest' ]             },
    matchDescReq:       { ind: 'matchDescRsp',      apiType: 'generic', suffix: [ 'nwkaddrofinterest' ]             },
    complexDescReq:     { ind: 'complexDescRsp',    apiType: 'generic', suffix: [ 'nwkaddrofinterest' ]             },
    userDescReq:        { ind: 'userDescRsp',       apiType: 'generic', suffix: [ 'nwkaddrofinterest' ]             },
    userDescSet:        { ind: 'userDescConf',      apiType: 'generic', suffix: [ 'nwkaddrofinterest' ]             },
    serverDiscReq:      { ind: 'serverDiscRsp',     apiType: 'special', suffix: []                                  },
    endDeviceBindReq:   { ind: 'endDeviceBindRsp',  apiType: 'generic', suffix: [ 'dstaddr' ]                       }, // address 16bit mode unsupported
    bindReq:            { ind: 'bindRsp',           apiType: 'special', suffix: [ 'dstaddr' ]                       },
    unbindReq:          { ind: 'unbindRsp',         apiType: 'generic', suffix: [ 'dstaddr' ]                       },
    nwkDiscoveryReq:    { ind: 'nwkDiscoveryCnf',   apiType: 'generic', suffix: []                                  },
    joinReq:            { ind: 'joinCnf',           apiType: 'generic', suffix: []                                  },
    mgmtNwkDiscReq:     { ind: 'mgmtNwkDiscRsp',    apiType: 'concat',  suffix: [ 'dstaddr', 'startindex' ]         },
    mgmtLqiReq:         { ind: 'mgmtLqiRsp',        apiType: 'concat',  suffix: [ 'dstaddr', 'startindex' ]         },
    mgmtRtgReq:         { ind: 'mgmtRtgRsp',        apiType: 'concat',  suffix: [ 'dstaddr', 'startindex' ]         },
    mgmtBindReq:        { ind: 'mgmtBindRsp',       apiType: 'concat',  suffix: [ 'dstaddr', 'startindex' ]         },
    mgmtLeaveReq:       { ind: 'mgmtLeaveRsp',      apiType: 'generic', suffix: [ 'dstaddr' ]                       },
    mgmtDirectJoinReq:  { ind: 'mgmtDirectJoinRsp', apiType: 'generic', suffix: [ 'dstaddr' ]                       },
    mgmtPermitJoinReq:  { ind: 'mgmtPermitJoinRsp', apiType: 'special', suffix: [ 'dstaddr' ]                       },
    mgmtNwkUpdateReq:   null,
    endDeviceAnnce:     null,
    msgCbRegister:      null,
    msgCbRemove:        null,
    startupFromApp:     null,
    setLinkKey:         null,
    removeLinkKey:      null,
    getLinkKey:         null,
    secAddLinkKey:      null,
    secEntryLookupExt:  null,
    extRouteDisc:       null,
    extRouteCheck:      null,
    extRemoveGroup:     null,
    extRemoveAllGroup:  null,
    extFindGroup:       null,
    extAddGroup:        null,
    extCountAllGroups:  null,
    extRxIdle:          null,
    extUpdateNwkKey:    null,
    extSwitchNwkKey:    null,
    extNwkInfo:         null,
    extSecApsRemoveReq: null,
    extFindAllGroupsEndpoint: null,
    forceConcentratorChange:  null,
    extSetParams:             null,
    endDeviceTimeoutReq:      null,
    sendData:                 null,
    nwkAddrOfInterestReq:     null
};

zdoIndSuffix = {
    nwkAddrRsp:        [ 'ieeeaddr', 'startindex' ],
    ieeeAddrRsp:       [ 'nwkaddr' ],  // 'startindex' mismatch
    nodeDescRsp:       [ 'nwkaddr' ],
    powerDescRsp:      [ 'nwkaddr' ],
    simpleDescRsp:     [ 'nwkaddr', 'endpoint' ],
    activeEpRsp:       [ 'nwkaddr' ],
    matchDescRsp:      [ 'nwkaddr' ],
    complexDescRsp:    [ 'nwkaddr' ],
    userDescRsp:       [ 'nwkaddr' ],
    userDescConf:      [ 'nwkaddr' ],
    serverDiscRsp:     null,    // special, listen at controller.on('ZDO:serverDiscRsp')
    endDeviceBindRsp:  [ 'srcaddr' ],
    bindRsp:           [ 'srcaddr' ],
    unbindRsp:         [ 'srcaddr' ],
    nwkDiscoveryCnf:   null,
    joinCnf:           null,
    mgmtNwkDiscRsp:    [ 'srcaddr', 'startindex' ],
    mgmtLqiRsp:        [ 'srcaddr', 'startindex' ],
    mgmtRtgRsp:        [ 'srcaddr', 'startindex' ],
    mgmtBindRsp:       [ 'srcaddr', 'startindex' ],
    mgmtLeaveRsp:      [ 'srcaddr' ],
    mgmtDirectJoinRsp: [ 'srcaddr' ],
    mgmtPermitJoinRsp: [ 'srcaddr' ],
    stateChangeInd:    null,    // very special, tackled in event_bridge._zdoIndicationEventBridge()
    endDeviceAnnceInd: null,
    matchDescRspSent:  null,
    statusErrorRsp:    null,
    srcRtgInd:         null,
    beacon_notify_ind: null,
    leaveInd:          null,
    msgCbIncoming:     null,
    tcDeviceInd:       null,
    permitJoinInd:     null,
    networkStatus:     ['dstaddr']
};

/*************************************************************************************************/
/*** Public APIs                                                                               ***/
/*************************************************************************************************/
zdoHelper.hasAreq = function (reqName) {
    var meta = zdoReqRspMap[reqName];
    return meta ? (!!meta.ind) : false;
};

zdoHelper.getRequestType = function (reqName) {
    var meta = zdoReqRspMap[reqName];
    return meta ? meta.apiType : 'rspless';
};

function generateEvent(indName, meta, valObj){
    if (!meta || (meta.length === 0))
        return;

    let evtName = 'ZDO:' + indName;

    for(const key of meta) {
        if(key === 'shortaddr' || key === 'srcaddr' || key === 'nwkaddr' || key === 'nwkaddrofinterest' || key === 'dstaddr') evtName += ':' + valObj[key].toString(16)
        else evtName += ':' + valObj[key].toString()
    }

    return evtName
}

zdoHelper.generateEventOfRequest = function (reqName, valObj) {
    var meta = zdoReqRspMap[reqName]

    if (!zdoHelper.hasAreq(reqName))
        return;

    return generateEvent(meta.ind, meta.suffix, valObj)
};

zdoHelper.generateEventOfIndication = function (indName, msgData) {
    return generateEvent(indName, zdoIndSuffix[indName], msgData)
};

zdoHelper.generateDstAddrOfRequest = function(valObj){
    if(valObj.dstaddr) return valObj.dstaddr.toString(16)
    if(valObj.nwkaddrofinterest) return valObj.nwkaddrofinterest.toString(16)
};

module.exports = zdoHelper;
<|endoftext|>