<commit_before>// Copyright (c) 2015 Uber Technologies, Inc.

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var bufrw = require('bufrw');
var inherits = require('util').inherits;
var errors = require('../errors');

// TODO: different struct pattern that doesn't realize a temporary list of
// [key, val] tuples may be better. At the very least, such structure would
// allow for more precise error reporting.

function HeaderRW(countrw, keyrw, valrw, options) {
    if (!(this instanceof HeaderRW)) {
        return new HeaderRW(countrw, keyrw, valrw, options);
    }
    var self = this;
    self.countrw = countrw;
    self.keyrw = keyrw;
    self.valrw = valrw;
    self.maxHeaderCount = options.maxHeaderCount;
    self.maxKeyLength = options.maxKeyLength;
    bufrw.Base.call(self);
}
inherits(HeaderRW, bufrw.Base);

HeaderRW.prototype.byteLength = function byteLength(headers) {
    var self = this;
    var length = 0;
    var keys = Object.keys(headers);
    var res;

    length += self.countrw.width;

    for (var i = 0; i < keys.length; i++) {
        var key = keys[i];
        res = self.keyrw.byteLength(key);
        if (res.err) return res;
        length += res.length;

        res = self.valrw.byteLength(headers[key]);
        if (res.err) return res;
        length += res.length;
    }

    return bufrw.LengthResult.just(length);
};

HeaderRW.prototype.writeInto = function writeInto(headers, buffer, offset) {
    var self = this;
    var keys = Object.keys(headers);
    var res;

    res = self.countrw.writeInto(keys.length, buffer, offset);

    if (keys.length > self.maxHeaderCount) {
        return bufrw.WriteResult.error(errors.TooManyHeaders({
            offset: offset,
            endOffset: res.offset,
            count: keys.length
        }), offset);
    }

    for (var i = 0; i < keys.length; i++) {
        if (res.err) return res;
        offset = res.offset;

        var key = keys[i];
        res = self.keyrw.writeInto(key, buffer, offset);
        if (res.err) return res;

        var keyByteLength = res.offset - offset;
        if (keyByteLength > self.maxKeyLength) {
            return bufrw.WriteResult.error(errors.TransportHeaderTooLong({
                maxLength: self.maxKeyLength,
                headerName: key,
                offset: offset,
                endOffset: res.offset
            }), offset);
        }
        offset = res.offset;

        res = self.valrw.writeInto(headers[key], buffer, offset);
    }

    return res;
};

HeaderRW.prototype.readFrom = function readFrom(buffer, offset) {
    var self = this;
    var headers = {};
    var start = 0;
    var n = 0;
    var key = '';
    var val = '';
    var res;

    res = self.countrw.readFrom(buffer, offset);
    if (res.err) return res;
    offset = res.offset;
    n = res.value;

    if (n > self.maxHeaderCount) {
        return bufrw.ReadResult.error(errors.TooManyHeaders({
            offset: offset,
            endOffset: res.offset,
            count: n
        }), offset, headers);
    }

    for (var i = 0; i < n; i++) {
        start = offset;

        res = self.keyrw.readFrom(buffer, offset);
        if (res.err) return res;
        key = res.value;

        if (!key.length) {
            return bufrw.ReadResult.error(errors.NullKeyError({
                offset: offset,
                endOffset: res.offset
            }), offset, headers);
        } else if (res.offset - offset > self.maxKeyLength) {
            return bufrw.ReadResult.error(errors.TransportHeaderTooLong({
                maxLength: self.maxKeyLength,
                headerName: key,
                offset: offset,
                endOffset: res.offset
            }), offset, headers);
        }
        offset = res.offset;

        res = self.valrw.readFrom(buffer, offset);
        if (res.err) return res;
        val = res.value;

        if (headers[key] !== undefined) {
            return bufrw.ReadResult.error(errors.DuplicateHeaderKeyError({
                offset: start,
                endOffset: res.offset,
                key: key,
                value: val,
                priorValue: headers[key]
            }), offset, headers);
        }
        offset = res.offset;

        headers[key] = val;
    }

    return bufrw.ReadResult.just(offset, headers);
};

module.exports = HeaderRW;

// nh:1 (hk~1 hv~1){nh}
module.exports.header1 = HeaderRW(bufrw.UInt8, bufrw.str1, bufrw.str1, {
    maxHeaderCount: 128,
    maxKeyLength: 16
});

// nh:2 (hk~2 hv~2){nh}
module.exports.header2 = HeaderRW(bufrw.UInt16BE, bufrw.str2, bufrw.str2, {
    maxHeaderCount: Infinity,
    maxKeyLength: Infinity
});
<commit_msg>HeaderRW: check key length in #byteLength as well<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var bufrw = require('bufrw');
var inherits = require('util').inherits;
var errors = require('../errors');

// TODO: different struct pattern that doesn't realize a temporary list of
// [key, val] tuples may be better. At the very least, such structure would
// allow for more precise error reporting.

function HeaderRW(countrw, keyrw, valrw, options) {
    if (!(this instanceof HeaderRW)) {
        return new HeaderRW(countrw, keyrw, valrw, options);
    }
    var self = this;
    self.countrw = countrw;
    self.keyrw = keyrw;
    self.valrw = valrw;
    self.maxHeaderCount = options.maxHeaderCount;
    self.maxKeyLength = options.maxKeyLength;
    bufrw.Base.call(self);
}
inherits(HeaderRW, bufrw.Base);

HeaderRW.prototype.byteLength = function byteLength(headers) {
    var self = this;
    var length = 0;
    var keys = Object.keys(headers);
    var res;

    length += self.countrw.width;

    for (var i = 0; i < keys.length; i++) {
        var key = keys[i];
        res = self.keyrw.byteLength(key);
        if (res.err) return res;
        length += res.length;

        res = self.valrw.byteLength(headers[key]);
        if (res.err) return res;
        if (res.length > self.maxKeyLength) {
            return bufrw.LengthResult.error(errors.TransportHeaderTooLong({
                maxLength: self.maxKeyLength,
                headerName: key
            }));
        }
        length += res.length;
    }

    return bufrw.LengthResult.just(length);
};

HeaderRW.prototype.writeInto = function writeInto(headers, buffer, offset) {
    var self = this;
    var keys = Object.keys(headers);
    var res;

    res = self.countrw.writeInto(keys.length, buffer, offset);

    if (keys.length > self.maxHeaderCount) {
        return bufrw.WriteResult.error(errors.TooManyHeaders({
            offset: offset,
            endOffset: res.offset,
            count: keys.length
        }), offset);
    }

    for (var i = 0; i < keys.length; i++) {
        if (res.err) return res;
        offset = res.offset;

        var key = keys[i];
        res = self.keyrw.writeInto(key, buffer, offset);
        if (res.err) return res;

        var keyByteLength = res.offset - offset;
        if (keyByteLength > self.maxKeyLength) {
            return bufrw.WriteResult.error(errors.TransportHeaderTooLong({
                maxLength: self.maxKeyLength,
                headerName: key,
                offset: offset,
                endOffset: res.offset
            }), offset);
        }
        offset = res.offset;

        res = self.valrw.writeInto(headers[key], buffer, offset);
    }

    return res;
};

HeaderRW.prototype.readFrom = function readFrom(buffer, offset) {
    var self = this;
    var headers = {};
    var start = 0;
    var n = 0;
    var key = '';
    var val = '';
    var res;

    res = self.countrw.readFrom(buffer, offset);
    if (res.err) return res;
    offset = res.offset;
    n = res.value;

    if (n > self.maxHeaderCount) {
        return bufrw.ReadResult.error(errors.TooManyHeaders({
            offset: offset,
            endOffset: res.offset,
            count: n
        }), offset, headers);
    }

    for (var i = 0; i < n; i++) {
        start = offset;

        res = self.keyrw.readFrom(buffer, offset);
        if (res.err) return res;
        key = res.value;

        if (!key.length) {
            return bufrw.ReadResult.error(errors.NullKeyError({
                offset: offset,
                endOffset: res.offset
            }), offset, headers);
        } else if (res.offset - offset > self.maxKeyLength) {
            return bufrw.ReadResult.error(errors.TransportHeaderTooLong({
                maxLength: self.maxKeyLength,
                headerName: key,
                offset: offset,
                endOffset: res.offset
            }), offset, headers);
        }
        offset = res.offset;

        res = self.valrw.readFrom(buffer, offset);
        if (res.err) return res;
        val = res.value;

        if (headers[key] !== undefined) {
            return bufrw.ReadResult.error(errors.DuplicateHeaderKeyError({
                offset: start,
                endOffset: res.offset,
                key: key,
                value: val,
                priorValue: headers[key]
            }), offset, headers);
        }
        offset = res.offset;

        headers[key] = val;
    }

    return bufrw.ReadResult.just(offset, headers);
};

module.exports = HeaderRW;

// nh:1 (hk~1 hv~1){nh}
module.exports.header1 = HeaderRW(bufrw.UInt8, bufrw.str1, bufrw.str1, {
    maxHeaderCount: 128,
    maxKeyLength: 16
});

// nh:2 (hk~2 hv~2){nh}
module.exports.header2 = HeaderRW(bufrw.UInt16BE, bufrw.str2, bufrw.str2, {
    maxHeaderCount: Infinity,
    maxKeyLength: Infinity
});
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const Unit = require('../../models/Unit');
const { pathConversion } = require('./pathConversion');
const Conversion = require('../../models/Conversion');
const { getAllPaths } = require('./createConversionGraph');

/**
 * Adds the new unit and conversions to the database and the conversion graph.
 * @param {*} sourceId The source unit's id.
 * @param {*} destinationId The destination unit's id.
 * @param {*} slope The conversion's slope from source to destination.
 * @param {*} intercept The conversion's intercept from source to destination.
 * @param {*} unitName The new unit's name.
 * @param {*} conn The connection to use.
 */
async function addNewUnitAndConversion(sourceId, destinationId, slope, intercept, unitName, graph, conn) {
	const sourceUnit = await Unit.getById(sourceId, conn);
	const destinationUnit = await Unit.getById(destinationId, conn);
	// Add a new units where: name and identifier is unitName, type_of_unit is Unit.type.suffix,
	// displayable and preferredDisplay is the same as destination.
	// Note a type_of_unit of suffix is different than a unit with a suffix string.
	// Note the admin can later change identifier, displayable and preferredDisplay to something else
	// since OED does not recreate the unit if it exists so those changes will stay.
	const newUnit = new Unit(undefined, unitName, unitName, destinationUnit.unitRepresent, sourceUnit.secInRate,
		Unit.unitType.SUFFIX, undefined, '', destinationUnit.displayable, destinationUnit.preferredDisplay, 'suffix unit created by OED');
	await newUnit.insert(conn);

	// Create the conversion from the prefix unit to this new unit.
	const newConversion = new Conversion(sourceId, newUnit.id, false, slope, intercept,
		`${sourceUnit.name} → ${newUnit.name} (created by OED for unit with suffix)`);
	await newConversion.insert(conn);

	// Add the new node and conversion to the graph.
	graph.addNode(newUnit.id, newUnit.name);
	graph.addLink(sourceId, newUnit.id);
}

/**
 * Verifies that the conversion from source to destination has not changed. If so, update the conversion.
 * @param {*} expectedSlope The expected slope.
 * @param {*} expectedIntercept The expected intercept.
 * @param {*} source The source unit.
 * @param {*} destination The destination unit.
 * @param {*} graph The conversion graph.
 * @param {*} conn The connection to use.
 */
async function verifyConversion(expectedSlope, expectedIntercept, source, destination, graph, conn) {
	const sourceId = source.id;
	const destinationId = destination.id;
	const currentConversion = await Conversion.getBySourceDestination(sourceId, destinationId, conn);
	if (!currentConversion) {
		// The destination suffix unit exists but the conversion doesn't.
		// Create a new conversion with desired values.
		const newConversion = new Conversion(sourceId, destinationId, false, expectedSlope, expectedIntercept,
			`${source.name} → ${destination.name} (created by OED for unit with suffix)`);
		// Insert the new conversion to database and graph.
		await newConversion.insert(conn);
		graph.addLink(sourceId, destinationId);
	} else if (currentConversion.slope !== expectedSlope || currentConversion.intercept !== expectedIntercept) {
		// While unlikely, the conversion changed so update
		currentConversion.slope = expectedSlope;
		currentConversion.intercept = expectedIntercept;
		await currentConversion.update(conn);
	}
}

/**
 * This function is called after adding suffix units. These new units will replace
 * the original one so we need to hide and remove all the edges from it to others.
 * @param {*} unit The suffix unit to hide.
 * @param {*} paths All shortest paths from this suffix unit to others.
 * @param {*} graph The conversion graph.
 * @param {*} conn The connection to use.
 */
async function hideSuffixUnit(unit, paths, graph, conn) {
	// Hides the suffix unit since we added the units based on it if not previously done.
	if (unit.displayable !== Unit.displayableType.NONE) {
		unit.displayable = Unit.displayableType.NONE;
		await unit.update(conn);
	}
	// Remove the edge from this unit to the next vertex that existed before the new units were added
	// since it is no longer needed as the new suffix units have the needed edges (conversions).
	// The created units have the type_of_unit be suffix so don't delete those.
	// There is probably only one edge but remove them all just in case.
	for (const p of paths) {
		const secondUnit = await Unit.getById(p[1].id, conn);
		// The paths to suffix units shouldn't be deleted.
		if (secondUnit.typeOfUnit !== Unit.unitType.SUFFIX) {
			// Removes the conversion from the conversion graph.
			graph.removeLink(p[0].id, p[1].id);
		}
	}
}

/**
 * Adds new suffix units and conversions to the database and the conversion graph.
 * @param {*} graph The conversion graph.
 * @param {*} conn The connection to use.
 */
async function handleSuffixUnits(graph, conn) {
	// Get all units that have a suffix.
	const suffixUnits = await Unit.getSuffix(conn);
	// Check each unit out.
	for (const unit of suffixUnits) {
		// Use the graph to determine all the reachable units from this suffix unit S.
		const paths = getAllPaths(graph, unit.id);
		// Analyze each path
		for (const p of paths) {
			const sourceId = p[0].id;
			const destinationId = p[p.length - 1].id;
			// The destination unit.
			const destinationUnit = await Unit.getById(destinationId, conn);
			// We don't need to create any new units/conversions if the destination unit has the type of suffix or it's not displayed.
			if (destinationUnit.typeOfUnit === Unit.unitType.SUFFIX) {
				continue;
			}
			// Find the conversion from the start to end of path.
			const [slope, intercept, suffix] = await pathConversion(p, conn);
			// The name of the needed unit is the last unit name on the path + " of " and the suffix of the path.
			const unitName = destinationUnit.name + ' of ' + suffix;
			const neededSuffixUnit = await Unit.getByName(unitName, conn);
			// See if this unit already exists. Would if this was done before where this path existed.
			if (neededSuffixUnit === null) {
				// If not then add the new unit and conversion.
				await addNewUnitAndConversion(sourceId, destinationId, slope, intercept, unitName, graph, conn);
			} else {
				// If it already exists then check if the conversion is correct.
				await verifyConversion(slope, intercept, unit, neededSuffixUnit, graph, conn);
			}
		}
		// The unit with suffix is no longer necessary since it has been replaced with new suffix units.
		// We need to hide it and remove unnecessary conversions.
		await hideSuffixUnit(unit, paths, graph, conn);
	}
}

/**
 * OED handles suffix units by adding conversions and units automatically.
 * When a unit's suffix changes, these additional conversions and units need to be removed.
 * Since it's complicated to remove units, we just set their displayable to NONE.
 * @param {*} suffixUnit Additional conversions/units of this suffixUnit will be removed.
 * @param {*} conn The connection to use.
 */
async function removeAdditionalConversionsAndUnits(suffixUnit, conn) {
	// Get all conversions from this suffix unit.
	const conversions = (await Conversion.getAll(conn)).filter((conversion) => conversion.sourceId === suffixUnit.id);
	conversions.forEach(async (conversion) => {
		const destinationUnit = await Unit.getById(conversion.destinationId, conn);
		// The units that OED adds are suffix unit.
		if (destinationUnit.typeOfUnit === Unit.unitType.SUFFIX) {
			// Delete the conversion.
			await Conversion.delete(conversion.sourceId, conversion.destinationId, conn);
			// Hide the destination unit.
			destinationUnit.displayable = Unit.displayableType.NONE;
			await destinationUnit.update(conn);
		}
	});
	suffixUnit.displayable = Unit.displayableType.ALL;
	await suffixUnit.update(conn);
}

module.exports = {
	handleSuffixUnits,
	removeAdditionalConversionsAndUnits
};<commit_msg>change suffix unit's displayable to all in handleSuffixUnit<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const Unit = require('../../models/Unit');
const { pathConversion } = require('./pathConversion');
const Conversion = require('../../models/Conversion');
const { getAllPaths } = require('./createConversionGraph');

/**
 * Adds the new unit and conversions to the database and the conversion graph.
 * @param {*} sourceId The source unit's id.
 * @param {*} destinationId The destination unit's id.
 * @param {*} slope The conversion's slope from source to destination.
 * @param {*} intercept The conversion's intercept from source to destination.
 * @param {*} unitName The new unit's name.
 * @param {*} conn The connection to use.
 */
async function addNewUnitAndConversion(sourceId, destinationId, slope, intercept, unitName, graph, conn) {
	const sourceUnit = await Unit.getById(sourceId, conn);
	const destinationUnit = await Unit.getById(destinationId, conn);
	// Add a new units where: name and identifier is unitName, type_of_unit is Unit.type.suffix,
	// displayable and preferredDisplay is the same as destination.
	// Note a type_of_unit of suffix is different than a unit with a suffix string.
	// Note the admin can later change identifier, displayable and preferredDisplay to something else
	// since OED does not recreate the unit if it exists so those changes will stay.
	const newUnit = new Unit(undefined, unitName, unitName, destinationUnit.unitRepresent, sourceUnit.secInRate,
		Unit.unitType.SUFFIX, undefined, '', destinationUnit.displayable, destinationUnit.preferredDisplay, 'suffix unit created by OED');
	await newUnit.insert(conn);

	// Create the conversion from the prefix unit to this new unit.
	const newConversion = new Conversion(sourceId, newUnit.id, false, slope, intercept,
		`${sourceUnit.name} → ${newUnit.name} (created by OED for unit with suffix)`);
	await newConversion.insert(conn);

	// Add the new node and conversion to the graph.
	graph.addNode(newUnit.id, newUnit.name);
	graph.addLink(sourceId, newUnit.id);
}

/**
 * Verifies that the conversion from source to destination has not changed. If so, update the conversion.
 * @param {*} expectedSlope The expected slope.
 * @param {*} expectedIntercept The expected intercept.
 * @param {*} source The source unit.
 * @param {*} destination The destination unit.
 * @param {*} graph The conversion graph.
 * @param {*} conn The connection to use.
 */
async function verifyConversion(expectedSlope, expectedIntercept, source, destination, graph, conn) {
	const sourceId = source.id;
	const destinationId = destination.id;
	const currentConversion = await Conversion.getBySourceDestination(sourceId, destinationId, conn);
	if (!currentConversion) {
		// The destination suffix unit exists but the conversion doesn't.
		// Create a new conversion with desired values.
		const newConversion = new Conversion(sourceId, destinationId, false, expectedSlope, expectedIntercept,
			`${source.name} → ${destination.name} (created by OED for unit with suffix)`);
		// Insert the new conversion to database and graph.
		await newConversion.insert(conn);
		graph.addLink(sourceId, destinationId);
	} else if (currentConversion.slope !== expectedSlope || currentConversion.intercept !== expectedIntercept) {
		// While unlikely, the conversion changed so update
		currentConversion.slope = expectedSlope;
		currentConversion.intercept = expectedIntercept;
		await currentConversion.update(conn);
	}
}

/**
 * This function is called after adding suffix units. These new units will replace
 * the original one so we need to hide and remove all the edges from it to others.
 * @param {*} unit The suffix unit to hide.
 * @param {*} paths All shortest paths from this suffix unit to others.
 * @param {*} graph The conversion graph.
 * @param {*} conn The connection to use.
 */
async function hideSuffixUnit(unit, paths, graph, conn) {
	// Hides the suffix unit since we added the units based on it if not previously done.
	if (unit.displayable !== Unit.displayableType.NONE) {
		unit.displayable = Unit.displayableType.NONE;
		await unit.update(conn);
	}
	// Remove the edge from this unit to the next vertex that existed before the new units were added
	// since it is no longer needed as the new suffix units have the needed edges (conversions).
	// The created units have the type_of_unit be suffix so don't delete those.
	// There is probably only one edge but remove them all just in case.
	for (const p of paths) {
		const secondUnit = await Unit.getById(p[1].id, conn);
		// The paths to suffix units shouldn't be deleted.
		if (secondUnit.typeOfUnit !== Unit.unitType.SUFFIX) {
			// Removes the conversion from the conversion graph.
			graph.removeLink(p[0].id, p[1].id);
		}
	}
}

/**
 * Check if the unit is displayed. If not then set its displayable to ALL.
 * @param {*} unit The unit.
 * @param {*} conn The connection to use.
 */
async function verifyUnit(unit, conn) {
	if (unit.displayable === Unit.displayableType.NONE) {
		unit.displayable = Unit.displayableType.ALL;
		await unit.update(conn);
	}
}

/**
 * Adds new suffix units and conversions to the database and the conversion graph.
 * @param {*} graph The conversion graph.
 * @param {*} conn The connection to use.
 */
async function handleSuffixUnits(graph, conn) {
	// Get all units that have a suffix.
	const suffixUnits = await Unit.getSuffix(conn);
	// Check each unit out.
	for (const unit of suffixUnits) {
		// Use the graph to determine all the reachable units from this suffix unit S.
		const paths = getAllPaths(graph, unit.id);
		// Analyze each path
		for (const p of paths) {
			const sourceId = p[0].id;
			const destinationId = p[p.length - 1].id;
			// The destination unit.
			const destinationUnit = await Unit.getById(destinationId, conn);
			// We don't need to create any new units/conversions if the destination unit has the type of suffix or it's not displayed.
			if (destinationUnit.typeOfUnit === Unit.unitType.SUFFIX || destinationUnit.displayable === Unit.displayableType.NONE) {
				continue;
			}
			// Find the conversion from the start to end of path.
			const [slope, intercept, suffix] = await pathConversion(p, conn);
			// The name of the needed unit is the last unit name on the path + " of " and the suffix of the path.
			const unitName = destinationUnit.name + ' of ' + suffix;
			const neededSuffixUnit = await Unit.getByName(unitName, conn);
			// See if this unit already exists. Would if this was done before where this path existed.
			if (neededSuffixUnit === null) {
				// If not then add the new unit and conversion.
				await addNewUnitAndConversion(sourceId, destinationId, slope, intercept, unitName, graph, conn);
			} else {
				// If it already exists then check if the unit and conversion are correct.
				await verifyUnit(neededSuffixUnit, conn);
				await verifyConversion(slope, intercept, unit, neededSuffixUnit, graph, conn);
			}
		}
		// The unit with suffix is no longer necessary since it has been replaced with new suffix units.
		// We need to hide it and remove unnecessary conversions.
		await hideSuffixUnit(unit, paths, graph, conn);
	}
}

/**
 * OED handles suffix units by adding conversions and units automatically.
 * When a unit's suffix changes, these additional conversions and units need to be removed.
 * Since it's complicated to remove units, we just set their displayable to NONE.
 * @param {*} suffixUnit Additional conversions/units of this suffixUnit will be removed.
 * @param {*} conn The connection to use.
 */
async function removeAdditionalConversionsAndUnits(suffixUnit, conn) {
	// Get all conversions from this suffix unit.
	const conversions = (await Conversion.getAll(conn)).filter((conversion) => conversion.sourceId === suffixUnit.id);
	conversions.forEach(async (conversion) => {
		const destinationUnit = await Unit.getById(conversion.destinationId, conn);
		// The units that OED adds are suffix unit.
		if (destinationUnit.typeOfUnit === Unit.unitType.SUFFIX) {
			// Delete the conversion.
			await Conversion.delete(conversion.sourceId, conversion.destinationId, conn);
			// Hide the destination unit.
			destinationUnit.displayable = Unit.displayableType.NONE;
			await destinationUnit.update(conn);
		}
	});
	suffixUnit.displayable = Unit.displayableType.ALL;
	await suffixUnit.update(conn);
}

module.exports = {
	handleSuffixUnits,
	removeAdditionalConversionsAndUnits
};<|endoftext|>


<commit_before>'use strict';

const Biovision = {
    storage: {
        available: function (type) {
            try {
                const x = '__storage_test__';

                window[type].setItem(x, x);
                window[type].removeItem(x);

                return true;
            }
            catch (e) {
                return false;
            }
        },
        set: function (type, key, value) {
            if (Biovision.storage.available(type)) {
                window[type].setItem(key, value);
            } else {
                console.log('set: Storage ' + type + ' is not available');
            }
        },
        get: function (type, key) {
            if (Biovision.storage.available(type)) {
                return window[type].getItem(key);
            } else {
                console.log('get: Storage ' + type + ' is not available');
                return null;
            }
        },
        remove: function (type, key) {
            if (Biovision.storage.available(type)) {
                window[type].removeItem(key);
            } else {
                console.log('remove: Storage ' + type + ' is not available');
            }
        },
        session: {
            set: function (key, value) {
                Biovision.storage.set('sessionStorage', key, value);
            },
            get: function (key) {
                return Biovision.storage.get('sessionStorage', key);
            },
            remove: function (key) {
                Biovision.storage.remove('sessionStorage', key);
            }
        },
        local: {
            set: function (key, value) {
                Biovision.storage.set('localStorage', key, value);
            },
            get: function (key) {
                return Biovision.storage.get('localStorage', key);
            },
            remove: function (key) {
                Biovision.storage.remove('localStorage', key);
            }
        }
    },
    preview_file: function (input) {
        const target_image = input.getAttribute('data-image');

        if (target_image) {
            const target = document.querySelector('#' + target_image + ' img');

            if (target && input.files && input.files[0]) {
                const reader = new FileReader();

                reader.onload = function (event) {
                    target.setAttribute('src', event.target.result);
                };

                reader.readAsDataURL(input.files[0]);
            }
        }
    },
    new_ajax_request: function (method, url, on_success, on_failure) {
        const request = new XMLHttpRequest();

        request.addEventListener('load', function () {
            if (this.status >= 200 && this.status < 400) {
                if (on_success) {
                    on_success.call(this);
                }
            } else {
                (on_failure || Biovision.handle_ajax_failure).call(this);
            }
        });
        request.addEventListener('error', function () {
            console.log(this);
        });

        request.open(method.toUpperCase(), url);
        request.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
        request.setRequestHeader('X-CSRF-Token', Biovision.csrf_token);

        return request;
    },
    handle_ajax_failure: function (response) {
        console.log(this);
        if (response.hasOwnProperty('responseJSON')) {
            console.log(response['responseJSON']);
        } else {
            console.log(response);
        }
    },
    transliterate: function (input) {
        const char_map = {
            'а': 'a', 'б': 'b', 'в': 'v', 'г': 'g', 'д': 'd',
            'е': 'e', 'ё': 'yo', 'ж': 'zh', 'з': 'z', 'и': 'i',
            'й': 'j', 'к': 'k', 'л': 'l', 'м': 'm', 'н': 'n',
            'о': 'o', 'п': 'p', 'р': 'r', 'с': 's', 'т': 't',
            'у': 'u', 'ф': 'f', 'х': 'kh', 'ц': 'c', 'ч': 'ch',
            'ш': 'sh', 'щ': 'shh', 'ъ': '', 'ы': 'y', 'ь': '',
            'э': 'e', 'ю': 'yu', 'я': 'ya'
        };
        let string = input.toLowerCase();

        for (let index in char_map) {
            if (char_map.hasOwnProperty(index)) {
                string = string.replace(new RegExp(index, 'g'), char_map[index]);
            }
        }
        string = string.replace(/[^-a-z0-9_.]/g, '-');
        string = string.replace(/^[-_.]*([-a-z0-9_.]*[a-z0-9]+)[-_.]*$/, '$1');
        string = string.replace(/--+/g, '-');

        return string;
    }
};

document.addEventListener('DOMContentLoaded', function () {
    Biovision.csrf_token = document.querySelector('meta[name="csrf-token"]').getAttribute('content');

    // Предварительный просмотр картинки при выборе файла
    document.addEventListener('change', function (event) {
        const input = event.target;

        if (input.matches('input[type=file]')) {
            Biovision.preview_file(input);
        }
    });

    document.addEventListener('click', function (event) {
        const element = event.target;

        // Выбор результата поиска пользователей в админке
        if (element.matches('.user-search .results li')) {
            const container = element.closest('.user-search');
            const target = document.getElementById(container.getAttribute('data-target'));

            target.value = element.getAttribute('data-id');
        }

        // Запирание/отпирание сущности (иконка с замком)
        if (element.matches('li.lock > a img')) {
            event.preventDefault();

            const container = element.closest('li');
            const button = element.closest('a');
            const lockable = container.parentNode.querySelectorAll('.lockable');
            const url = container.getAttribute('data-url');

            if (url.length > 1) {
                const method = button.classList.contains('lock') ? 'PUT' : 'DELETE';

                const request = Biovision.new_ajax_request(method, url, function () {
                    const response = JSON.parse(this.responseText);

                    if (response.hasOwnProperty('data') && response['data'].hasOwnProperty('locked')) {
                        const locked = response['data']['locked'];

                        if (locked) {
                            lockable.forEach(function (button) {
                                button.classList.add('hidden')
                            });
                        } else {
                            lockable.forEach(function (button) {
                                button.classList.remove('hidden')
                            });
                        }

                        container.querySelectorAll('a').forEach(function (button) {
                            const classes = button.classList;

                            if (classes.contains('lock')) {
                                locked ? classes.add('hidden') : classes.remove('hidden');
                            } else {
                                locked ? classes.remove('hidden') : classes.add('hidden');
                            }
                        });
                    }
                });

                request.send();
            }
        }

        // Переключение флагов сущности
        if (element.matches('div.toggleable > span')) {
            if (!element.classList.contains('switch')) {
                const url = element.parentNode.getAttribute('data-url');
                const parameter = element.getAttribute('data-flag');

                const on_success = function () {
                    const response = JSON.parse(this.responseText);

                    if (response.hasOwnProperty('data')) {
                        switch (response['data'][parameter]) {
                            case true:
                                element.className = 'active';
                                break;
                            case false:
                                element.className = 'inactive';
                                break;
                            default:
                                element.className = 'unknown';
                        }
                    } else {
                        element.className = 'unknown';
                    }
                };

                const on_failure = function () {
                    element.className = 'unknown';
                    Biovision.handle_ajax_failure.call(this);
                };

                const request = Biovision.new_ajax_request('POST', url, on_success, on_failure);
                const data = new FormData();
                data.append('parameter', parameter);

                element.className = 'switch';

                request.send(data);
            }
        }

        // Изменение порядка сортировки элементов
        if (element.matches('li.priority-changer > button')) {
            const delta = parseInt(element.getAttribute('data-delta'));
            const url = element.parentNode.getAttribute('data-url');
            let item = element.closest('li[data-number]');

            if (parseInt(item.getAttribute('data-number')) + delta > 0) {
                const on_success = function () {
                    const response = JSON.parse(this.responseText);

                    if (response.hasOwnProperty('data')) {
                        const data = response.data;
                        const container = item.parentNode;
                        const list = Array.prototype.slice.call(container.children);

                        if (data.hasOwnProperty('priority')) {
                            item.setAttribute('data-number', data.priority);
                        } else {
                            for (let entity_id in data) {
                                if (data.hasOwnProperty(entity_id)) {
                                    item = container.querySelector('li[data-id="' + entity_id + '"]');
                                    item.setAttribute('data-number', data[entity_id]);
                                }
                            }
                        }

                        list.sort(function (a, b) {
                            let an = parseInt(a.getAttribute('data-number'));
                            let bn = parseInt(b.getAttribute('data-number'));

                            if (an > bn) {
                                return 1;
                            } else if (an < bn) {
                                return -1;
                            } else {
                                return 0;
                            }
                        }).forEach(function (item) {
                            container.appendChild(item);
                        });
                    }
                };

                const request = Biovision.new_ajax_request('POST', url, on_success);

                const data = new FormData();
                data.append('delta', delta);

                request.send(data);
            }
        }
    });

    // Кнопка поиска пользователя в админке
    document.querySelectorAll('.user-search button').forEach(function (element) {
        element.addEventListener('click', function () {
            const container = this.closest('.user-search');
            const input = container.querySelector('input[type=search]');
            const url = container.getAttribute('data-url') + '?q=' + encodeURIComponent(input.value);

            const request = Biovision.new_ajax_request('GET', url, function () {
                const response = JSON.parse(this.responseText);
                const results = container.querySelector('.results');

                if (response.hasOwnProperty('data')) {
                    results.innerHTML = response['data']['html'];
                }
            });

            request.send();
        });
    });

    // Кнопка удаления элемента через AJAX
    document.querySelectorAll('div[data-destroy-url] button.destroy').forEach(function (element) {
        element.addEventListener('click', function () {
            const container = this.closest('div[data-destroy-url]');
            const url = container.getAttribute('data-destroy-url');
            const request = Biovision.new_ajax_request('DELETE', url, function () {
                container.remove();
            });

            this.setAttribute('disabled', 'true');
            request.send();
        });
    });

    document.querySelectorAll('[data-transliterate]').forEach(function (element) {
        element.addEventListener('blur', function () {
            const target = document.getElementById(element.getAttribute('data-transliterate'));

            if (target && target.value === '') {
                target.value = Biovision.transliterate(element.value);
                target.dispatchEvent(new Event('change'));
            }
        });
    });

    document.querySelectorAll('form[data-remote]').forEach(function (form) {
        const button = form.querySelector('button[type=submit]');

        form.addEventListener('ajax:before', function () {
            button.disabled = true;
        });

        form.addEventListener('ajax:complete', function () {
            button.disabled = false;
        });
    });

    if (typeof jQuery !== 'undefined') {
        jQuery.ajaxSetup({
            headers: {
                'X-CSRF-Token': Biovision.csrf_token
            }
        });
    }
});

function handle_ajax_failure(response) {
    if (response.hasOwnProperty('responseJSON')) {
        console.log(response['responseJSON']);
    } else {
        console.log(response);
    }
}

/*
 *************
 * Polyfills *
 *************
 */

/**
 * Element.matches()
 *
 * IE 9+
 *
 * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/matches
 */
if (!Element.prototype.matches) {
    Element.prototype.matches =
        Element.prototype.msMatchesSelector ||
        Element.prototype.webkitMatchesSelector;
}

/**
 * Element.closest()
 *
 * IE 9+
 *
 * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
 */
if (!Element.prototype.closest) {
    Element.prototype.closest = function (s) {
        let el = this;
        let ancestor = this;

        if (!document.documentElement.contains(el)) {
            return null;
        }
        do {
            if (ancestor.matches(s)) {
                return ancestor;
            }
            ancestor = ancestor.parentElement;
        } while (ancestor !== null);

        return null;
    };
}

/**
 * ChildNode.remove()
 *
 * IE 9+
 *
 * @see https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/remove
 */
(function (arr) {
    arr.forEach(function (item) {
        if (item.hasOwnProperty('remove')) {
            return;
        }
        Object.defineProperty(item, 'remove', {
            configurable: true,
            enumerable: true,
            writable: true,
            value: function remove() {
                if (this.parentNode !== null) {
                    this.parentNode.removeChild(this);
                }
            }
        });
    });
})([Element.prototype, CharacterData.prototype, DocumentType.prototype]);

/**
 * NodeList.forEach()
 *
 * ES5
 *
 * @see https://developer.mozilla.org/en-US/docs/Web/API/NodeList/forEach
 */
if (window.NodeList && !NodeList.prototype.forEach) {
    NodeList.prototype.forEach = function (callback, thisArg) {
        thisArg = thisArg || window;
        for (let i = 0; i < this.length; i++) {
            callback.call(thisArg, this[i], i, this);
        }
    };
}
<commit_msg>Added polyfill for ParentNode.append<commit_after>'use strict';

const Biovision = {
    storage: {
        available: function (type) {
            try {
                const x = '__storage_test__';

                window[type].setItem(x, x);
                window[type].removeItem(x);

                return true;
            }
            catch (e) {
                return false;
            }
        },
        set: function (type, key, value) {
            if (Biovision.storage.available(type)) {
                window[type].setItem(key, value);
            } else {
                console.log('set: Storage ' + type + ' is not available');
            }
        },
        get: function (type, key) {
            if (Biovision.storage.available(type)) {
                return window[type].getItem(key);
            } else {
                console.log('get: Storage ' + type + ' is not available');
                return null;
            }
        },
        remove: function (type, key) {
            if (Biovision.storage.available(type)) {
                window[type].removeItem(key);
            } else {
                console.log('remove: Storage ' + type + ' is not available');
            }
        },
        session: {
            set: function (key, value) {
                Biovision.storage.set('sessionStorage', key, value);
            },
            get: function (key) {
                return Biovision.storage.get('sessionStorage', key);
            },
            remove: function (key) {
                Biovision.storage.remove('sessionStorage', key);
            }
        },
        local: {
            set: function (key, value) {
                Biovision.storage.set('localStorage', key, value);
            },
            get: function (key) {
                return Biovision.storage.get('localStorage', key);
            },
            remove: function (key) {
                Biovision.storage.remove('localStorage', key);
            }
        }
    },
    preview_file: function (input) {
        const target_image = input.getAttribute('data-image');

        if (target_image) {
            const target = document.querySelector('#' + target_image + ' img');

            if (target && input.files && input.files[0]) {
                const reader = new FileReader();

                reader.onload = function (event) {
                    target.setAttribute('src', event.target.result);
                };

                reader.readAsDataURL(input.files[0]);
            }
        }
    },
    new_ajax_request: function (method, url, on_success, on_failure) {
        const request = new XMLHttpRequest();

        request.addEventListener('load', function () {
            if (this.status >= 200 && this.status < 400) {
                if (on_success) {
                    on_success.call(this);
                }
            } else {
                (on_failure || Biovision.handle_ajax_failure).call(this);
            }
        });
        request.addEventListener('error', function () {
            console.log(this);
        });

        request.open(method.toUpperCase(), url);
        request.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
        request.setRequestHeader('X-CSRF-Token', Biovision.csrf_token);

        return request;
    },
    handle_ajax_failure: function (response) {
        console.log(this);
        if (response.hasOwnProperty('responseJSON')) {
            console.log(response['responseJSON']);
        } else {
            console.log(response);
        }
    },
    transliterate: function (input) {
        const char_map = {
            'а': 'a', 'б': 'b', 'в': 'v', 'г': 'g', 'д': 'd',
            'е': 'e', 'ё': 'yo', 'ж': 'zh', 'з': 'z', 'и': 'i',
            'й': 'j', 'к': 'k', 'л': 'l', 'м': 'm', 'н': 'n',
            'о': 'o', 'п': 'p', 'р': 'r', 'с': 's', 'т': 't',
            'у': 'u', 'ф': 'f', 'х': 'kh', 'ц': 'c', 'ч': 'ch',
            'ш': 'sh', 'щ': 'shh', 'ъ': '', 'ы': 'y', 'ь': '',
            'э': 'e', 'ю': 'yu', 'я': 'ya'
        };
        let string = input.toLowerCase();

        for (let index in char_map) {
            if (char_map.hasOwnProperty(index)) {
                string = string.replace(new RegExp(index, 'g'), char_map[index]);
            }
        }
        string = string.replace(/[^-a-z0-9_.]/g, '-');
        string = string.replace(/^[-_.]*([-a-z0-9_.]*[a-z0-9]+)[-_.]*$/, '$1');
        string = string.replace(/--+/g, '-');

        return string;
    }
};

document.addEventListener('DOMContentLoaded', function () {
    Biovision.csrf_token = document.querySelector('meta[name="csrf-token"]').getAttribute('content');

    // Предварительный просмотр картинки при выборе файла
    document.addEventListener('change', function (event) {
        const input = event.target;

        if (input.matches('input[type=file]')) {
            Biovision.preview_file(input);
        }
    });

    document.addEventListener('click', function (event) {
        const element = event.target;

        // Выбор результата поиска пользователей в админке
        if (element.matches('.user-search .results li')) {
            const container = element.closest('.user-search');
            const target = document.getElementById(container.getAttribute('data-target'));

            target.value = element.getAttribute('data-id');
        }

        // Запирание/отпирание сущности (иконка с замком)
        if (element.matches('li.lock > a img')) {
            event.preventDefault();

            const container = element.closest('li');
            const button = element.closest('a');
            const lockable = container.parentNode.querySelectorAll('.lockable');
            const url = container.getAttribute('data-url');

            if (url.length > 1) {
                const method = button.classList.contains('lock') ? 'PUT' : 'DELETE';

                const request = Biovision.new_ajax_request(method, url, function () {
                    const response = JSON.parse(this.responseText);

                    if (response.hasOwnProperty('data') && response['data'].hasOwnProperty('locked')) {
                        const locked = response['data']['locked'];

                        if (locked) {
                            lockable.forEach(function (button) {
                                button.classList.add('hidden')
                            });
                        } else {
                            lockable.forEach(function (button) {
                                button.classList.remove('hidden')
                            });
                        }

                        container.querySelectorAll('a').forEach(function (button) {
                            const classes = button.classList;

                            if (classes.contains('lock')) {
                                locked ? classes.add('hidden') : classes.remove('hidden');
                            } else {
                                locked ? classes.remove('hidden') : classes.add('hidden');
                            }
                        });
                    }
                });

                request.send();
            }
        }

        // Переключение флагов сущности
        if (element.matches('div.toggleable > span')) {
            if (!element.classList.contains('switch')) {
                const url = element.parentNode.getAttribute('data-url');
                const parameter = element.getAttribute('data-flag');

                const on_success = function () {
                    const response = JSON.parse(this.responseText);

                    if (response.hasOwnProperty('data')) {
                        switch (response['data'][parameter]) {
                            case true:
                                element.className = 'active';
                                break;
                            case false:
                                element.className = 'inactive';
                                break;
                            default:
                                element.className = 'unknown';
                        }
                    } else {
                        element.className = 'unknown';
                    }
                };

                const on_failure = function () {
                    element.className = 'unknown';
                    Biovision.handle_ajax_failure.call(this);
                };

                const request = Biovision.new_ajax_request('POST', url, on_success, on_failure);
                const data = new FormData();
                data.append('parameter', parameter);

                element.className = 'switch';

                request.send(data);
            }
        }

        // Изменение порядка сортировки элементов
        if (element.matches('li.priority-changer > button')) {
            const delta = parseInt(element.getAttribute('data-delta'));
            const url = element.parentNode.getAttribute('data-url');
            let item = element.closest('li[data-number]');

            if (parseInt(item.getAttribute('data-number')) + delta > 0) {
                const on_success = function () {
                    const response = JSON.parse(this.responseText);

                    if (response.hasOwnProperty('data')) {
                        const data = response.data;
                        const container = item.parentNode;
                        const list = Array.prototype.slice.call(container.children);

                        if (data.hasOwnProperty('priority')) {
                            item.setAttribute('data-number', data.priority);
                        } else {
                            for (let entity_id in data) {
                                if (data.hasOwnProperty(entity_id)) {
                                    item = container.querySelector('li[data-id="' + entity_id + '"]');
                                    item.setAttribute('data-number', data[entity_id]);
                                }
                            }
                        }

                        list.sort(function (a, b) {
                            let an = parseInt(a.getAttribute('data-number'));
                            let bn = parseInt(b.getAttribute('data-number'));

                            if (an > bn) {
                                return 1;
                            } else if (an < bn) {
                                return -1;
                            } else {
                                return 0;
                            }
                        }).forEach(function (item) {
                            container.appendChild(item);
                        });
                    }
                };

                const request = Biovision.new_ajax_request('POST', url, on_success);

                const data = new FormData();
                data.append('delta', delta);

                request.send(data);
            }
        }
    });

    // Кнопка поиска пользователя в админке
    document.querySelectorAll('.user-search button').forEach(function (element) {
        element.addEventListener('click', function () {
            const container = this.closest('.user-search');
            const input = container.querySelector('input[type=search]');
            const url = container.getAttribute('data-url') + '?q=' + encodeURIComponent(input.value);

            const request = Biovision.new_ajax_request('GET', url, function () {
                const response = JSON.parse(this.responseText);
                const results = container.querySelector('.results');

                if (response.hasOwnProperty('data')) {
                    results.innerHTML = response['data']['html'];
                }
            });

            request.send();
        });
    });

    // Кнопка удаления элемента через AJAX
    document.querySelectorAll('div[data-destroy-url] button.destroy').forEach(function (element) {
        element.addEventListener('click', function () {
            const container = this.closest('div[data-destroy-url]');
            const url = container.getAttribute('data-destroy-url');
            const request = Biovision.new_ajax_request('DELETE', url, function () {
                container.remove();
            });

            this.setAttribute('disabled', 'true');
            request.send();
        });
    });

    document.querySelectorAll('[data-transliterate]').forEach(function (element) {
        element.addEventListener('blur', function () {
            const target = document.getElementById(element.getAttribute('data-transliterate'));

            if (target && target.value === '') {
                target.value = Biovision.transliterate(element.value);
                target.dispatchEvent(new Event('change'));
            }
        });
    });

    document.querySelectorAll('form[data-remote]').forEach(function (form) {
        const button = form.querySelector('button[type=submit]');

        form.addEventListener('ajax:before', function () {
            button.disabled = true;
        });

        form.addEventListener('ajax:complete', function () {
            button.disabled = false;
        });
    });

    if (typeof jQuery !== 'undefined') {
        jQuery.ajaxSetup({
            headers: {
                'X-CSRF-Token': Biovision.csrf_token
            }
        });
    }
});

function handle_ajax_failure(response) {
    if (response.hasOwnProperty('responseJSON')) {
        console.log(response['responseJSON']);
    } else {
        console.log(response);
    }
}

/*
 *************
 * Polyfills *
 *************
 */

/**
 * Element.matches()
 *
 * IE 9+
 *
 * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/matches
 */
if (!Element.prototype.matches) {
    Element.prototype.matches =
        Element.prototype.msMatchesSelector ||
        Element.prototype.webkitMatchesSelector;
}

/**
 * Element.closest()
 *
 * IE 9+
 *
 * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
 */
if (!Element.prototype.closest) {
    Element.prototype.closest = function (s) {
        let el = this;
        let ancestor = this;

        if (!document.documentElement.contains(el)) {
            return null;
        }
        do {
            if (ancestor.matches(s)) {
                return ancestor;
            }
            ancestor = ancestor.parentElement;
        } while (ancestor !== null);

        return null;
    };
}

/**
 * ParentNode.append()
 *
 * IE 9+
 *
 * @see https://developer.mozilla.org/en-US/docs/Web/API/ParentNode/append
 * @see https://github.com/jserz/js_piece/blob/master/DOM/ParentNode/append()/append().md
 */
(function (arr) {
    arr.forEach(function (item) {
        if (item.hasOwnProperty('append')) {
            return;
        }
        Object.defineProperty(item, 'append', {
            configurable: true,
            enumerable: true,
            writable: true,
            value: function append() {
                let argArr = Array.prototype.slice.call(arguments),
                    docFrag = document.createDocumentFragment();

                argArr.forEach(function (argItem) {
                    let isNode = argItem instanceof Node;
                    docFrag.appendChild(isNode ? argItem : document.createTextNode(String(argItem)));
                });

                this.appendChild(docFrag);
            }
        });
    });
})([Element.prototype, Document.prototype, DocumentFragment.prototype]);

/**
 * ChildNode.remove()
 *
 * IE 9+
 *
 * @see https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/remove
 */
(function (arr) {
    arr.forEach(function (item) {
        if (item.hasOwnProperty('remove')) {
            return;
        }
        Object.defineProperty(item, 'remove', {
            configurable: true,
            enumerable: true,
            writable: true,
            value: function remove() {
                if (this.parentNode !== null) {
                    this.parentNode.removeChild(this);
                }
            }
        });
    });
})([Element.prototype, CharacterData.prototype, DocumentType.prototype]);

/**
 * NodeList.forEach()
 *
 * ES5
 *
 * @see https://developer.mozilla.org/en-US/docs/Web/API/NodeList/forEach
 */
if (window.NodeList && !NodeList.prototype.forEach) {
    NodeList.prototype.forEach = function (callback, thisArg) {
        thisArg = thisArg || window;
        for (let i = 0; i < this.length; i++) {
            callback.call(thisArg, this[i], i, this);
        }
    };
}
<|endoftext|>


<commit_before>const chai = require("chai");
const child = require("child_process");
const fs = require("fs-extra");
const helper = require("babel-helper-fixtures");
const path = require("path");

const fixtureLoc = path.join(__dirname, "debug-fixtures");
const tmpLoc = path.join(__dirname, "tmp");

const clear = () => {
  process.chdir(__dirname);
  if (fs.existsSync(tmpLoc)) fs.removeSync(tmpLoc);
  fs.mkdirSync(tmpLoc);
  process.chdir(tmpLoc);
};

const saveInFiles = files => {
  Object.keys(files).forEach(filename => {
    const content = files[filename];
    fs.outputFileSync(filename, content);
  });
};

const testOutputType = (type, stdTarg, opts) => {
  stdTarg = stdTarg.trim();
  stdTarg = stdTarg.replace(/\\/g, "/");
  const optsTarg = opts[type];

  if (optsTarg) {
    const expectStdout = optsTarg.trim();
    chai.expect(stdTarg).to.equal(expectStdout, `${type} didn't match`);
  } else {
    const file = path.join(opts.testLoc, `${type}.txt`);
    console.log(`New test file created: ${file}`);
    fs.outputFileSync(file, stdTarg);
  }
};

const assertTest = (stdout, stderr, opts) => {
  // stderr = stderr.trim();

  // if (stderr) {
  //   throw new Error("stderr:\n" + stderr);
  // }

  testOutputType("stdout", stdout, opts);
  if (stderr) {
    testOutputType("stderr", stderr, opts);
  }
};

const buildTest = opts => {
  const binLoc = path.join(process.cwd(), "node_modules/.bin/babel");

  return callback => {
    clear();
    saveInFiles(opts.inFiles);

    let args = [binLoc];
    args = args.concat(opts.args);

    const spawn = child.spawn(process.execPath, args);

    let stdout = "";
    let stderr = "";

    spawn.stdout.on("data", chunk => stdout += chunk);
    spawn.stderr.on("data", chunk => stderr += chunk);

    spawn.on("close", () => {
      let err;

      try {
        assertTest(stdout, stderr, opts);
      } catch (e) {
        err = e;
      }

      callback(err);
    });
  };
};

describe("debug output", () => {
  fs.readdirSync(fixtureLoc).forEach(testName => {
    if (testName.slice(0, 1) === ".") return;
    const testLoc = path.join(fixtureLoc, testName);
    if (testName.slice(0, 1) === ".") return;

    const opts = {
      args: ["src", "--out-dir", "lib"],
      testLoc: testLoc,
    };

    const stdoutLoc = path.join(testLoc, "stdout.txt");
    const stderrLoc = path.join(testLoc, "stderr.txt");

    if (fs.existsSync(stdoutLoc)) {
      opts.stdout = helper.readFile(stdoutLoc);
    }

    if (fs.existsSync(stderrLoc)) {
      opts.stderr = helper.readFile(stderrLoc);
    }

    const optionsLoc = path.join(testLoc, "options.json");

    if (!fs.existsSync(optionsLoc)) {
      throw new Error(
        `Debug test '${testName}' is missing an options.json file`,
      );
    }

    const inFilesFolderLoc = path.join(testLoc, "in");

    opts.inFiles = {
      ".babelrc": helper.readFile(optionsLoc),
    };

    if (!fs.existsSync(inFilesFolderLoc)) {
      opts.inFiles["src/in.js"] = "";
    } else {
      fs.readdirSync(inFilesFolderLoc).forEach(filename => {
        opts.inFiles[`src/${filename}`] = helper.readFile(
          path.join(inFilesFolderLoc, filename),
        );
      });
    }
    it(testName, buildTest(opts));
  });
});
<commit_msg>Remove old comments.<commit_after>const chai = require("chai");
const child = require("child_process");
const fs = require("fs-extra");
const helper = require("babel-helper-fixtures");
const path = require("path");

const fixtureLoc = path.join(__dirname, "debug-fixtures");
const tmpLoc = path.join(__dirname, "tmp");

const clear = () => {
  process.chdir(__dirname);
  if (fs.existsSync(tmpLoc)) fs.removeSync(tmpLoc);
  fs.mkdirSync(tmpLoc);
  process.chdir(tmpLoc);
};

const saveInFiles = files => {
  Object.keys(files).forEach(filename => {
    const content = files[filename];
    fs.outputFileSync(filename, content);
  });
};

const testOutputType = (type, stdTarg, opts) => {
  stdTarg = stdTarg.trim();
  stdTarg = stdTarg.replace(/\\/g, "/");
  const optsTarg = opts[type];

  if (optsTarg) {
    const expectStdout = optsTarg.trim();
    chai.expect(stdTarg).to.equal(expectStdout, `${type} didn't match`);
  } else {
    const file = path.join(opts.testLoc, `${type}.txt`);
    console.log(`New test file created: ${file}`);
    fs.outputFileSync(file, stdTarg);
  }
};

const assertTest = (stdout, stderr, opts) => {
  testOutputType("stdout", stdout, opts);
  if (stderr) {
    testOutputType("stderr", stderr, opts);
  }
};

const buildTest = opts => {
  const binLoc = path.join(process.cwd(), "node_modules/.bin/babel");

  return callback => {
    clear();
    saveInFiles(opts.inFiles);

    let args = [binLoc];
    args = args.concat(opts.args);

    const spawn = child.spawn(process.execPath, args);

    let stdout = "";
    let stderr = "";

    spawn.stdout.on("data", chunk => stdout += chunk);
    spawn.stderr.on("data", chunk => stderr += chunk);

    spawn.on("close", () => {
      let err;

      try {
        assertTest(stdout, stderr, opts);
      } catch (e) {
        err = e;
      }

      callback(err);
    });
  };
};

describe("debug output", () => {
  fs.readdirSync(fixtureLoc).forEach(testName => {
    if (testName.slice(0, 1) === ".") return;
    const testLoc = path.join(fixtureLoc, testName);
    if (testName.slice(0, 1) === ".") return;

    const opts = {
      args: ["src", "--out-dir", "lib"],
      testLoc: testLoc,
    };

    const stdoutLoc = path.join(testLoc, "stdout.txt");
    const stderrLoc = path.join(testLoc, "stderr.txt");

    if (fs.existsSync(stdoutLoc)) {
      opts.stdout = helper.readFile(stdoutLoc);
    }

    if (fs.existsSync(stderrLoc)) {
      opts.stderr = helper.readFile(stderrLoc);
    }

    const optionsLoc = path.join(testLoc, "options.json");

    if (!fs.existsSync(optionsLoc)) {
      throw new Error(
        `Debug test '${testName}' is missing an options.json file`,
      );
    }

    const inFilesFolderLoc = path.join(testLoc, "in");

    opts.inFiles = {
      ".babelrc": helper.readFile(optionsLoc),
    };

    if (!fs.existsSync(inFilesFolderLoc)) {
      opts.inFiles["src/in.js"] = "";
    } else {
      fs.readdirSync(inFilesFolderLoc).forEach(filename => {
        opts.inFiles[`src/${filename}`] = helper.readFile(
          path.join(inFilesFolderLoc, filename),
        );
      });
    }
    it(testName, buildTest(opts));
  });
});
<|endoftext|>


<commit_before>'use strict';

let config = require('config'),
	path = require('path'),
	fs = require('fs'),
	// Http server
	express = require('express'),
	bodyParser = require('body-parser'),
	app = express(),
	port = config.get('http.port'),
	// Security
	https = require('https'),
	httpsOptions = {
		// key: fs.readFileSync('./config/ssl/backhome_privkey.pem'),
		// cert: fs.readFileSync('./config/ssl/backhome_certificate.pem')
	},
	helmet = require('helmet'),
	// Authentification Require
	crypto = require('crypto'),
	passport = require('passport'),
	LocalStrategy = require('passport-local').Strategy,
	cookieSession = require('cookie-session'),
	// Dash button Setup
	// dash_button = require('node-dash-button'),
	// dash = dash_button(config.get('dash.mac'), null, null, 'all'),
	// Plug Setup
	plugApi = require('hs100-api'),
	plugClient = new plugApi.Client(),
	plug = plugClient.getPlug(config.get('switch.host')),
	// Kodi Setup
	kodi = require('kodi-ws'),
	kodiConf = config.get('kodi'),
	// App Setup
	appConf = config.get('app'),
	musicMorning = appConf.music.morning,
	musicEvening = appConf.music.evening,
	canPlayMusic = appConf.enable,
	playMusic = function(musicId)
	{
		let youtubeRegex = /^https:\/\/www.youtube.com\/watch\?v=(.+)&?.+$/;

		if(youtubeRegex.test(musicId))
		{
			musicId = musicId.match(youtubeRegex)[1];
		}

		return kodi(kodiConf.host, kodiConf.port)
			.then(function(connection)
			{
				/* Start the video */
				return connection.Player.Open(
				{
					item :
					{
						file : 'plugin:\/\/plugin.video.youtube\/?path=\/root\/search&action=play_video&videoid=' + musicId
					}
				});
			});
	},
	stopMusic = function()
	{
		return kodi(kodiConf.host, kodiConf.port)
			.then(function(connection)
			{
				return connection.Player.GetActivePlayers()
				.then(function(players)
				{
					/* Stop everything thats playing */
					return Promise.all(players.map(function(player)
					{
						return connection.Player.Stop(player.playerid);
					}));
				});
			});
	}
	;

// Main application

// Dash Button
// dash.on("detected", () =>
// {
// 	console.log('Dash Button : pressed');

// 	plug.getPowerState()
// 	.then((state) =>
// 	{
// 		plug.setPowerState(!state);
// 		if(state)
// 		{
// 			console.log('Smart Plug : switch off');
// 			stopMusic()
// 			.then(function()
// 			{
// 				console.log('Kodi : Stop all players');
// 			})
// 			.catch(function(error)
// 			{
// 				console.error('Kodi : ' + error);
// 			});
// 		}
// 		else
// 		{
// 			console.log('Smart Plug : switch on');
// 			if (canPlayMusic)
// 			{
// 				let d = new Date(),
// 					n = new Date(),
// 					musics = musicMorning
// 					;

// 				d.setHours(12, 0, 0, 0);
// 				(n>d) && (musics = musicEvening);

// 				playMusic(musics[Math.floor(Math.random() * (musics.length))])
// 				.then(function()
// 				{
// 					console.log('Kodi : Playing Music ' + (n>d ? 'Evening' : 'Morning'));
// 				})
// 				.catch(function(error)
// 				{
// 					console.error('Kodi : ' + error);
// 				});
// 			}
// 			else
// 			{
// 				console.log('Kodi : Not Playing Music');
// 			}
// 		}
// 	})
// 	.catch(function(error)
// 	{
// 		console.error('Smart Plug : ' + error);
// 	})
// 	;
// });

console.log('Back Home Ready');

passport.use(new LocalStrategy(
	function(username, password, done)
	{

		var userApp = config.get('server.user.username'),
			passApp = config.get('server.user.password'),
			userId
			;

		if (userApp !== username ||
			passApp !== password
		)
		{
		  return done(null, false, { message: 'Incorrect username or password' });
		}

		return done(null, { id : config.get('server.user.id') });
	}
));

passport.serializeUser(function(user, done)
{
	done(null, user);
});

passport.deserializeUser(function(user, done)
{
	done(null, user);
});

app
.use(helmet())
.use('/static', express.static(path.join(__dirname, 'public')))
.use(bodyParser.json())
.use(bodyParser.urlencoded({ extended: true}))
.use(cookieSession(
{
	name: 'session',
	keys: config.get('server.auth.cookieKeys')
}))
.use(passport.initialize())
.use(passport.session())
.set('view engine', 'pug')
.get('/login', function(req, res)
{
	res.render('login', { active : 'Login' });
})
.post('/login', passport.authenticate('local',
{
	successRedirect: '/',
	failureRedirect: '/login'
}))
.get('/', function(req, res)
{
	if(!req.isAuthenticated())
	{
		res.redirect('/login');
	}

	res.render('index',
	{
		active : 'Configuration',
		enable : canPlayMusic,
		musicMorning : musicMorning,
		musicEvening : musicEvening
	});
})
.post('/saveMusic', function(req, res)
{
	if(!req.isAuthenticated())
	{
		res.redirect('/login');
	}

	musicMorning = req.body.musicMorning.filter(Boolean);
	musicEvening = req.body.musicEvening.filter(Boolean);

	if(req.body.enable !== 'undefined')
	{
		canPlayMusic = req.body.enable;
	}

	res.end();
})
.get('/play', function(req, res)
{
	if(!req.isAuthenticated())
	{
		res.redirect('/login');
	}

	playMusic(req.query.music)
	.then(function()
	{
		console.log('Kodi : Playing Music UI');
	});
})
.get('/sw', function(req, res)
{
	res.sendFile(path.join(__dirname, 'sw.js'));
})
;

//var httpsServer = https.createServer(httpsOptions, app);
app.listen(port, function()
{
	console.log(`Server listening ${port}`);
});
<commit_msg>fix getting server port<commit_after>'use strict';

let config = require('config'),
	path = require('path'),
	fs = require('fs'),
	// Http server
	express = require('express'),
	bodyParser = require('body-parser'),
	app = express(),
	port = config.get('server.port'),
	// Security
	https = require('https'),
	httpsOptions = {
		// key: fs.readFileSync('./config/ssl/backhome_privkey.pem'),
		// cert: fs.readFileSync('./config/ssl/backhome_certificate.pem')
	},
	helmet = require('helmet'),
	// Authentification Require
	crypto = require('crypto'),
	passport = require('passport'),
	LocalStrategy = require('passport-local').Strategy,
	cookieSession = require('cookie-session'),
	// Dash button Setup
	// dash_button = require('node-dash-button'),
	// dash = dash_button(config.get('dash.mac'), null, null, 'all'),
	// Plug Setup
	plugApi = require('hs100-api'),
	plugClient = new plugApi.Client(),
	plug = plugClient.getPlug(config.get('switch.host')),
	// Kodi Setup
	kodi = require('kodi-ws'),
	kodiConf = config.get('kodi'),
	// App Setup
	appConf = config.get('app'),
	musicMorning = appConf.music.morning,
	musicEvening = appConf.music.evening,
	canPlayMusic = appConf.enable,
	playMusic = function(musicId)
	{
		let youtubeRegex = /^https:\/\/www.youtube.com\/watch\?v=(.+)&?.+$/;

		if(youtubeRegex.test(musicId))
		{
			musicId = musicId.match(youtubeRegex)[1];
		}

		return kodi(kodiConf.host, kodiConf.port)
			.then(function(connection)
			{
				/* Start the video */
				return connection.Player.Open(
				{
					item :
					{
						file : 'plugin:\/\/plugin.video.youtube\/?path=\/root\/search&action=play_video&videoid=' + musicId
					}
				});
			});
	},
	stopMusic = function()
	{
		return kodi(kodiConf.host, kodiConf.port)
			.then(function(connection)
			{
				return connection.Player.GetActivePlayers()
				.then(function(players)
				{
					/* Stop everything thats playing */
					return Promise.all(players.map(function(player)
					{
						return connection.Player.Stop(player.playerid);
					}));
				});
			});
	}
	;

// Main application

// Dash Button
// dash.on("detected", () =>
// {
// 	console.log('Dash Button : pressed');

// 	plug.getPowerState()
// 	.then((state) =>
// 	{
// 		plug.setPowerState(!state);
// 		if(state)
// 		{
// 			console.log('Smart Plug : switch off');
// 			stopMusic()
// 			.then(function()
// 			{
// 				console.log('Kodi : Stop all players');
// 			})
// 			.catch(function(error)
// 			{
// 				console.error('Kodi : ' + error);
// 			});
// 		}
// 		else
// 		{
// 			console.log('Smart Plug : switch on');
// 			if (canPlayMusic)
// 			{
// 				let d = new Date(),
// 					n = new Date(),
// 					musics = musicMorning
// 					;

// 				d.setHours(12, 0, 0, 0);
// 				(n>d) && (musics = musicEvening);

// 				playMusic(musics[Math.floor(Math.random() * (musics.length))])
// 				.then(function()
// 				{
// 					console.log('Kodi : Playing Music ' + (n>d ? 'Evening' : 'Morning'));
// 				})
// 				.catch(function(error)
// 				{
// 					console.error('Kodi : ' + error);
// 				});
// 			}
// 			else
// 			{
// 				console.log('Kodi : Not Playing Music');
// 			}
// 		}
// 	})
// 	.catch(function(error)
// 	{
// 		console.error('Smart Plug : ' + error);
// 	})
// 	;
// });

console.log('Back Home Ready');

passport.use(new LocalStrategy(
	function(username, password, done)
	{

		var userApp = config.get('server.user.username'),
			passApp = config.get('server.user.password'),
			userId
			;

		if (userApp !== username ||
			passApp !== password
		)
		{
		  return done(null, false, { message: 'Incorrect username or password' });
		}

		return done(null, { id : config.get('server.user.id') });
	}
));

passport.serializeUser(function(user, done)
{
	done(null, user);
});

passport.deserializeUser(function(user, done)
{
	done(null, user);
});

app
.use(helmet())
.use('/static', express.static(path.join(__dirname, 'public')))
.use(bodyParser.json())
.use(bodyParser.urlencoded({ extended: true}))
.use(cookieSession(
{
	name: 'session',
	keys: config.get('server.auth.cookieKeys')
}))
.use(passport.initialize())
.use(passport.session())
.set('view engine', 'pug')
.get('/login', function(req, res)
{
	res.render('login', { active : 'Login' });
})
.post('/login', passport.authenticate('local',
{
	successRedirect: '/',
	failureRedirect: '/login'
}))
.get('/', function(req, res)
{
	if(!req.isAuthenticated())
	{
		res.redirect('/login');
	}

	res.render('index',
	{
		active : 'Configuration',
		enable : canPlayMusic,
		musicMorning : musicMorning,
		musicEvening : musicEvening
	});
})
.post('/saveMusic', function(req, res)
{
	if(!req.isAuthenticated())
	{
		res.redirect('/login');
	}

	musicMorning = req.body.musicMorning.filter(Boolean);
	musicEvening = req.body.musicEvening.filter(Boolean);

	if(req.body.enable !== 'undefined')
	{
		canPlayMusic = req.body.enable;
	}

	res.end();
})
.get('/play', function(req, res)
{
	if(!req.isAuthenticated())
	{
		res.redirect('/login');
	}

	playMusic(req.query.music)
	.then(function()
	{
		console.log('Kodi : Playing Music UI');
	});
})
.get('/sw', function(req, res)
{
	res.sendFile(path.join(__dirname, 'sw.js'));
})
;

//var httpsServer = https.createServer(httpsOptions, app);
app.listen(port, function()
{
	console.log(`Server listening ${port}`);
});
<|endoftext|>


<commit_before>// generate default settings for tinymce editor
function cama_get_tinymce_settings(settings){
    if(!settings) settings = {};
    var def = {
        selector: ".tinymce_textarea",
        plugins: "advlist autolink lists link image charmap print preview hr anchor pagebreak searchreplace wordcount visualblocks visualchars code fullscreen insertdatetime media nonbreaking save table contextmenu directionality emoticons template paste textcolor colorpicker textpattern filemanager",
        menubar: "edit insert view format table tools",
        image_advtab: true,
        statusbar: true,
        paste: true,
        toolbar_items_size: 'small',
        content_css: tinymce_global_settings["custom_css"].join(","),
        convert_urls: false,
        //forced_root_block: '',
        extended_valid_elements: 'i[*],div[*],p[*],li[*],a[*],ol[*],ul[*],span[*]',
        toolbar: "bold italic | alignleft aligncenter alignright alignjustify | fontselect fontsizeselect | bullist numlist | outdent indent | undo redo | link unlink image media | forecolor backcolor | styleselect template "+tinymce_global_settings["custom_toolbar"].join(","),
        image_caption: true,
        language: CURRENT_LOCALE,
        relative_urls: false,
        remove_script_host: false,
        browser_spellcheck : true,
        language_url: tinymce_global_settings["language_url"],
        file_browser_callback: function(field_name, url, type, win) {
            $.fn.upload_filemanager({
                formats: type,
                selected: function(file, response){
                    $('#' + field_name).val(file.url);
                }
            });
        },
        setup: function (editor) {
            editor.on('blur', function () {
                tinymce.triggerSave();
                $('textarea#'+editor.id).trigger('change');
            });

            editor.addMenuItem('append_line', {
                text: 'New line at the end',
                context: 'insert',
                onclick: function () { editor.dom.add(editor.getBody(), 'p', {}, '-New line-');  }
            });
            editor.addMenuItem('add_line', {
                text: 'New line',
                context: 'insert',
                onclick: function () { editor.insertContent('
-New line-
');  }
            });

            // eval all extra setups
            for(var ff in tinymce_global_settings["setups"]) tinymce_global_settings["setups"][ff](editor);

            editor.on('postRender', function(e) {
                editor.settings.onPostRender(editor);
                // eval all extra setups
                for(var ff in tinymce_global_settings["post_render"]) tinymce_global_settings["post_render"][ff](editor);
            });

            editor.on('init', function(e) {
                for(var ff in tinymce_global_settings["init"]) tinymce_global_settings["init"][ff](editor);
            });
        },
        onPostRender: function(editor){}
    };
    for(var ff in tinymce_global_settings["settings"]) tinymce_global_settings["settings"][ff](settings, def);
    return $.extend({}, def, settings);
}

<commit_msg>Added Tinymce templates selector, Note: the template should have class = mceTmpl<commit_after>function cama_get_tinymce_settings(settings){
    if(!settings) settings = {};
    var def = {
        selector: ".tinymce_textarea",
        plugins: "advlist autolink lists link image charmap print preview hr anchor pagebreak searchreplace wordcount visualblocks visualchars code fullscreen insertdatetime media nonbreaking save table contextmenu directionality emoticons template paste textcolor colorpicker textpattern filemanager",
        menubar: "edit insert view format table tools",
        image_advtab: true,
        statusbar: true,
        paste: true,
        toolbar_items_size: 'small',
        content_css: tinymce_global_settings["custom_css"].join(","),
        convert_urls: false,
        //forced_root_block: '',
        extended_valid_elements: 'i[*],div[*],p[*],li[*],a[*],ol[*],ul[*],span[*]',
        toolbar: "bold italic | alignleft aligncenter alignright alignjustify | fontselect fontsizeselect | bullist numlist | outdent indent | undo redo | link unlink image media | forecolor backcolor | styleselect template "+tinymce_global_settings["custom_toolbar"].join(","),
        image_caption: true,
        language: CURRENT_LOCALE,
        relative_urls: false,
        remove_script_host: false,
        browser_spellcheck : true,
        language_url: tinymce_global_settings["language_url"],
        file_browser_callback: function(field_name, url, type, win) {
            $.fn.upload_filemanager({
                formats: type,
                selected: function(file, response){
                    $('#' + field_name).val(file.url);
                }
            });
        },
        setup: function (editor) {
            editor.on('blur', function () {
                tinymce.triggerSave();
                $('textarea#'+editor.id).trigger('change');
            });

            editor.addMenuItem('append_line', {
                text: 'New line at the end',
                context: 'insert',
                onclick: function () { editor.dom.add(editor.getBody(), 'p', {}, '-New line-');  }
            });
            editor.addMenuItem('add_line', {
                text: 'New line',
                context: 'insert',
                onclick: function () { editor.insertContent('
-New line-
');  }
            });

            // eval all extra setups
            for(var ff in tinymce_global_settings["setups"]) tinymce_global_settings["setups"][ff](editor);

            editor.on('postRender', function(e) {
                editor.settings.onPostRender(editor);
                // eval all extra setups
                for(var ff in tinymce_global_settings["post_render"]) tinymce_global_settings["post_render"][ff](editor);
            });

            editor.on('init', function(e) {
                for(var ff in tinymce_global_settings["init"]) tinymce_global_settings["init"][ff](editor);
            });
        },
        onPostRender: function(editor){}
    };
    for(var ff in tinymce_global_settings["settings"]) tinymce_global_settings["settings"][ff](settings, def);
    return $.extend({}, def, settings);
}

// Tinymce templates selector, Note: the template should have class = mceTmpl
jQuery(function(){
    tinymce_global_settings["settings"].push(cama_mce_template_manager);
    tinymce_global_settings["setups"].push(cama_mce_template_setups);
});
function cama_mce_template_manager(settings, default_sett){
    if(!('templates' in default_sett)) default_sett['templates'] = [];
    if(!('content_style' in default_sett)) default_sett['content_style'] = "";
    default_sett['templates'].push({title: 'Sample Template', description: 'Sample template', content: '

Title Template

body template

'});
    default_sett['content_style'] += ' span.mce-tpl-icon{ position: absolute; right: 0; top: 0; font-size: 7px; cursor: pointer; display: none;} .mceTmpl{border: 1px dashed transparent; position: relative;} .mceTmpl.mceTplHover{border-color: #ccc;} .mceTmpl.mceTplHover > span.mce-tpl-icon{display: block;}';
}

function cama_mce_template_setups(editor) {
    editor.on('NodeChange', function (e) {
        if ('selectionChange' in e) {
            var p = $(e.element).closest('.mceTmpl')
            $(editor.dom.doc.body).find('.mceTmpl').not(p).removeClass('mceTplHover');
            if (p.length > 0) p.addClass('mceTplHover');
        }
    });

    editor.on('BeforeSetContent', function (e) {
        var c = $('
' + e.content + '
')
        c.find('.mceTmpl').each(function () {
            $(this).removeClass('mceTplHover').children('.mce-tpl-icon').remove();
            $(this).prepend('Copy');
        });
        e.content = c.html();
    });

    editor.on('GetContent', function (e) {
        var c = $('
' + e.content + '
');
        c.find('.mceTmpl').each(function () {
            $(this).removeClass('mceTplHover').children('.mce-tpl-icon').remove();
        });
        e.content = c.html();
    });

    editor.on('init', function (e) {
        $(editor.dom.doc.body).on('click', '.mceTmpl > .mce-tpl-icon', function () {
            console.log(this);
            editor.selection.select($(this).parent()[0]);
        });
    });
}

<|endoftext|>


<commit_before>"use strict";

var point, angle, opposite, adjacent, distance;
var collidables, vp, vs, vc, op, os, oc, i, image, size = { "width": 20, "height": 45 };
var radius, type;
var viewPort = 5;
module.exports = function(ecs, game) { // eslint-disable-line no-unused-vars
    game.entities.registerSearch("linesToCollidablesSearch", ["collisions", "position", "size"]);
    ecs.add(function linesToCollidables(entities, context) {
        collidables = game.entities.find("collisions");

        vp = game.entities.get(viewPort, "position");
        vs = game.entities.get(viewPort, "size");
        vc = {
            "x": vp.x + (vs.width * 0.5),
            "y": vp.y + (vs.height * 0.5)
        };

        for (i = 0; i < collidables.length; i++) {
            if (game.entities.get(collidables[i], "food")) {
                op = game.entities.get(collidables[i], "position");
                os = game.entities.get(collidables[i], "size");
                oc = {
                    "x": op.x + (os.width * 0.5),
                    "y": op.y + (os.height * 0.5)
                };
                //boxSize = 20;
                radius = 470;
                type = game.entities.get(collidables[i], "type");
                image = new Image();
                switch (type) {
                    case 1:
                        image.src = "./images/yellow_arrow.png";
                        break;
                    case 2:
                        image.src = "./images/green_arrow.png";
                        break;
                    case 3:
                        image.src = "./images/blue_arrow.png";
                        break;
                    case 4:
                        image.src = "./images/red_arrow.png";
                        break;
                    default:
                        image.src = "./images/white_arrow.png";
                        break;
                }
                opposite = oc.y - vc.y;
                adjacent = oc.x - vc.x;
                angle = Math.atan2(opposite, adjacent);
                point = getPointOnCircle(vc, angle, radius);
                distance = Math.sqrt((vc.x - oc.x) * (vc.x - oc.x) + (vc.y - oc.y) * (vc.y - oc.y));
                if (distance > radius) {
                    angle += Math.PI;
                    context.translate(point.x, point.y);
                    context.rotate(angle);
                    context.drawImage(image, -size.width * 0.5, -size.height * 0.5, size.width, size.height);
                    context.rotate(-angle);
                    context.translate(-point.x, -point.y);
                }
            }
        }
    }, "linesToCollidablesSearch");
};

function getPointOnCircle(point, angle, radius) {
    return {
        "x": point.x + (radius * Math.cos(angle)),
        "y": point.y + (radius * Math.sin(angle))
    };
}
<commit_msg>Make radar icons change size based on distance. Law of inverse variation<commit_after>"use strict";

var point, angle, opposite, adjacent, distance;
var collidables, vp, vs, vc, op, os, oc, i, image, size = { "width": 45 }, constant = 21150;
var radius, type;
var viewPort = 5;
module.exports = function(ecs, game) { // eslint-disable-line no-unused-vars
    game.entities.registerSearch("linesToCollidablesSearch", ["collisions", "position", "size"]);
    ecs.add(function linesToCollidables(entities, context) {
        collidables = game.entities.find("collisions");

        vp = game.entities.get(viewPort, "position");
        vs = game.entities.get(viewPort, "size");
        vc = {
            "x": vp.x + (vs.width * 0.5),
            "y": vp.y + (vs.height * 0.5)
        };

        for (i = 0; i < collidables.length; i++) {
            if (game.entities.get(collidables[i], "food")) {
                op = game.entities.get(collidables[i], "position");
                os = game.entities.get(collidables[i], "size");
                oc = {
                    "x": op.x + (os.width * 0.5),
                    "y": op.y + (os.height * 0.5)
                };
                //boxSize = 20;
                radius = 470;
                type = game.entities.get(collidables[i], "type");
                image = new Image();
                switch (type) {
                    case 1:
                        image.src = "./images/yellow_arrow.png";
                        break;
                    case 2:
                        image.src = "./images/green_arrow.png";
                        break;
                    case 3:
                        image.src = "./images/blue_arrow.png";
                        break;
                    case 4:
                        image.src = "./images/red_arrow.png";
                        break;
                    default:
                        image.src = "./images/white_arrow.png";
                        break;
                }
                opposite = oc.y - vc.y;
                adjacent = oc.x - vc.x;
                angle = Math.atan2(opposite, adjacent);
                point = getPointOnCircle(vc, angle, radius);
                distance = Math.sqrt((vc.x - oc.x) * (vc.x - oc.x) + (vc.y - oc.y) * (vc.y - oc.y));
                if (distance > radius) {
                    angle += Math.PI;
                    context.translate(point.x, point.y);
                    context.rotate(angle);
                    // Law of inverse variation:
                    // Constant = 21150 so width = 45 at the radar radius
                    // @see http://www.regentsprep.org/regents/math/algtrig/ate7/inverse%20variation.htm
                    size.width = constant / distance;
                    size.height = size.width * 2.5;
                    context.drawImage(image, -size.width * 0.5, -size.height * 0.5, size.width, size.height);
                    context.rotate(-angle);
                    context.translate(-point.x, -point.y);
                }
            }
        }
    }, "linesToCollidablesSearch");
};

function getPointOnCircle(point, angle, radius) {
    return {
        "x": point.x + (radius * Math.cos(angle)),
        "y": point.y + (radius * Math.sin(angle))
    };
}
<|endoftext|>


<commit_before>var validator = module.exports = require('validator');
validator.Validator.prototype.init = function(obj) {
	this.obj = obj;
	this.err = {};
	return this;
}
validator.Validator.prototype.chk = function(key, fail_msg, compulsory) {
	this.error = function(msg) {
		if ((this.obj.hasOwnProperty(key) || compulsory) && (!this.err.hasOwnProperty(key))) {
			this.err[key] = msg;
		}
		return this;
	}
	return this.check(this.obj[key], fail_msg);
}
validator.Validator.prototype.failed = function() {
	return Object.keys(this.err).length;
}
validator.Validator.prototype.rng = function(lb, ub) {
	if (!(this.obj[lb] <= this.obj[ub])) {
		this.err[lb] = lb + ' must be less than or equal to ' + ub;
	}
	return this;
}
validator.Filter.prototype.init = function(obj) {
	this.obj = obj;
	this.res = {};
	return this;
}
validator.Filter.prototype.snt = function(key, def) {
	this.modify = function(value) {
		this.res[key] = value;
	}
	return this.sanitize(this.obj.hasOwnProperty(key) ? this.obj[key] : def);
}
validator.Filter.prototype.wrap = function(value) {
	return this;
}
validator.Filter.prototype.copy = function() {
	this.modify(this.str);
	return this.wrap(this.str);
}
validator.Filter.prototype.toLowerCase = function() {
	this.modify(this.str.toLowerCase());
	return this.wrap(this.str);
}
<commit_msg>Added xss() to validator.js because node-validator 2.0.0 had removed the xss() method<commit_after>var validator = module.exports = require('validator');
validator.Validator.prototype.init = function(obj) {
	this.obj = obj;
	this.err = {};
	return this;
}
validator.Validator.prototype.chk = function(key, fail_msg, compulsory) {
	this.error = function(msg) {
		if ((this.obj.hasOwnProperty(key) || compulsory) && (!this.err.hasOwnProperty(key))) {
			this.err[key] = msg;
		}
		return this;
	}
	return this.check(this.obj[key], fail_msg);
}
validator.Validator.prototype.failed = function() {
	return Object.keys(this.err).length;
}
validator.Validator.prototype.rng = function(lb, ub) {
	if (!(this.obj[lb] <= this.obj[ub])) {
		this.err[lb] = lb + ' must be less than or equal to ' + ub;
	}
	return this;
}
validator.Filter.prototype.init = function(obj) {
	this.obj = obj;
	this.res = {};
	return this;
}
validator.Filter.prototype.snt = function(key, def) {
	this.modify = function(value) {
		this.res[key] = value;
	}
	return this.sanitize(this.obj.hasOwnProperty(key) ? this.obj[key] : def);
}
validator.Filter.prototype.wrap = function(value) {
	return this;
}
validator.Filter.prototype.copy = function() {
	this.modify(this.str);
	return this.wrap(this.str);
}
validator.Filter.prototype.toLowerCase = function() {
	this.modify(this.str.toLowerCase());
	return this.wrap(this.str);
}
validator.Filter.prototype.xss = function() {
	this.modify(this.str.replace(/&/g, '&').replace(/"/g, '"').replace(/'/g, ''').replace(/</g, '<').replace(/>/g, '>'));
	return this.wrap(this.str);
}
<|endoftext|>