<commit_before>/*
Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.
For licensing, see LICENSE.html or http://ckeditor.com/license
*/
    /**
     * Represent plain text selection range.
     */
    CKEDITOR.plugins.add('textselection',
    {
        version: 1.01,
        init: function (editor) {
            // Corresponding text range of wysiwyg bookmark.
            var wysiwygBookmark;

            // Auto sync text selection with 'WYSIWYG' mode selection range.
            if (editor.config.syncSelection
                    && CKEDITOR.plugins.sourcearea) {
                editor.on('beforeModeUnload', function (evt) {
                    if (editor.mode === 'source') {
                        if (editor.mode === 'source' && !editor.plugins.codemirror) {
                            var range = editor.getTextSelection();

                            // Fly the range when create bookmark.
                            delete range.element;
                            range.createBookmark();
                            sourceBookmark = true;
                            evt.data = range.content;
                        }
                    }
                });
                editor.on('mode', function () {
                    if (editor.mode === 'wysiwyg' && sourceBookmark) {

                        editor.focus();
                        var doc = editor.document,
                            range = new CKEDITOR.dom.range(editor.document),
                            walker,
                            startNode,
                            endNode;

                        range.setStartAt(doc.getBody(), CKEDITOR.POSITION_AFTER_START);
                        range.setEndAt(doc.getBody(), CKEDITOR.POSITION_BEFORE_END);
                        walker = new CKEDITOR.dom.walker(range);
                       // walker.type = CKEDITOR.NODE_COMMENT;
                        walker.evaluator = function (comment) {
                           //

                            var match = /cke_bookmark_\d+(\w)/.exec(comment.$.nodeValue);
                            if (match) {

                                if (match[1] === 'S') {
                                    startNode = comment;
                                } else if (match[1] === 'E') {
                                    endNode = comment;
                                    return false;
                                }
                            }
                        };
                        walker.lastForward();
                        range.setStartAfter(startNode);
                        range.setEndBefore(endNode);
                        range.select();
                        // Scroll into view for non-IE.
                        // Scroll into view for non-IE.
                        if (!CKEDITOR.env.ie || (CKEDITOR.env.ie && CKEDITOR.env.version === 9)) {
                            editor.getSelection().getStartElement().scrollIntoView(true);
                        } // Remove the comments node which are out of range.
                        startNode.remove();
                        endNode.remove();
                    }
                }, null, null, 10);

                editor.on('beforeGetModeData', function () {
                    if (editor.mode === 'wysiwyg') {
                        if (CKEDITOR.env.gecko && !editor.focusManager.hasFocus) {
                            return;
                        }
                        var sel = editor.getSelection(), range;
                        if (sel && (range = sel.getRanges()[0])) {
                            wysiwygBookmark = range.createBookmark(true);
                        }
                    }
                });
                // Build text range right after wysiwyg has unloaded.
                editor.on('afterModeUnload', function (evt) {
                    if (editor.mode === 'wysiwyg' && wysiwygBookmark) {
                        textRange = new CKEDITOR.dom.textRange(evt.data);
                        textRange.moveToBookmark(wysiwygBookmark);

                        evt.data = textRange.content;
                    }
                });
                editor.on('mode', function () {
                    if (editor.mode === 'source' && textRange && !editor.plugins.codemirror) {
                        textRange.element = new CKEDITOR.dom.element(editor._.editable.$);
                        textRange.select();
                    }
                });
            }
        }
    });

    /**
     * Gets the current text selection from the editing area when in Source mode.
     * @returns {CKEDITOR.dom.textRange} Text range represent the caret positoins.
     * @example
     * var textSelection = CKEDITOR.instances.editor1.getTextSelection();
     * alert( textSelection.startOffset );
     * alert( textSelection.endOffset );
     */
    CKEDITOR.editor.prototype.getTextSelection = function () {
        return this._.editable && getTextSelection(this._.editable.$) || null;
    };

    /**
     * Returns the caret position of the specified textfield/textarea.
     * @param {HTMLTextArea|HTMLTextInput} element
     */
    function getTextSelection(element) {
        var startOffset, endOffset;

        if (!CKEDITOR.env.ie) {
            startOffset = element.selectionStart;
            endOffset = element.selectionEnd;
        } else {
            element.focus();

            // The current selection
            if (window.getSelection) {
                startOffset = element.selectionStart;
                endOffset = element.selectionEnd;
            } else {
                var range = document.selection.createRange(),
                    textLength = range.text.length;

                // Create a 'measuring' range to help calculate the start offset by
                // stretching it from start to current position.
                var measureRange = range.duplicate();
                measureRange.moveToElementText(element);
                measureRange.setEndPoint('EndToEnd', range);

                endOffset = measureRange.text.length;
                startOffset = endOffset - textLength;
            }
        }
        return new CKEDITOR.dom.textRange(
            new CKEDITOR.dom.element(element), startOffset, endOffset);
    }

    /**
     * Represent the selection range within a html textfield/textarea element,
     * or even a flyweight string content represent the text content.
     * @constructor
     * @param {CKEDITOR.dom.element|String} element
     * @param {Number} start
     * @param {Number} end
     */
    CKEDITOR.dom.textRange = function (element, start, end) {
        if (element instanceof CKEDITOR.dom.element
            && (element.is('textarea')
                || element.is('input') && element.getAttribute('type') == 'text')) {
            this.element = element;
            this.content = element.$.value;
        } else if (typeof element == 'string')
            this.content = element;
        else
            throw 'Unkown "element" type.';
        this.startOffset = start || 0;
        this.endOffset = end || 0;
    };

     /**
     * Changes the editing mode of this editor instance. (Override of the original function)
     *
     * **Note:** The mode switch could be asynchronous depending on the mode provider.
     * Use the `callback` to hook subsequent code.
     *
     *                // Switch to "source" view.
     *                CKEDITOR.instances.editor1.setMode( 'source' );
     *                // Switch to "wysiwyg" view and be notified on completion.
     *                CKEDITOR.instances.editor1.setMode( 'wysiwyg', function() { alert( 'wysiwyg mode loaded!' ); } );
     *
     * @param {String} [newMode] If not specified, the {@link CKEDITOR.config#startupMode} will be used.
     * @param {Function} [callback] Optional callback function which is invoked once the mode switch has succeeded.
     */
    CKEDITOR.editor.prototype.setMode = function (newMode, callback) {
        var editor = this;

        var modes = this._.modes;

        // Mode loading quickly fails.
        if (newMode == editor.mode || !modes || !modes[newMode])
            return;

        editor.fire('beforeSetMode', newMode);

        if (editor.mode) {
            var isDirty = editor.checkDirty();

            editor._.previousMode = editor.mode;

            editor.fire('beforeModeUnload');

            editor.fire('beforeGetModeData');
            var data = editor.getData();

            data = editor.fire('beforeModeUnload', data);
            data = editor.fire('afterModeUnload', data);

            // Detach the current editable.
            editor.editable(0);

            editor._.data = data;

            // Clear up the mode space.
            editor.ui.space('contents').setHtml('');

            editor.mode = '';
        }

        // Fire the mode handler.
        this._.modes[newMode](function () {
            // Set the current mode.
            editor.mode = newMode;

            if (isDirty !== undefined) {
                !isDirty && editor.resetDirty();
            }

            // Delay to avoid race conditions (setMode inside setMode).
            setTimeout(function () {
                editor.fire('mode');
                callback && callback.call(editor);
            }, 0);
        });
    };

    CKEDITOR.dom.textRange.prototype =
    {
        /**
         * Sets the text selection of the specified textfield/textarea.
         * @param {HTMLTextArea|HTMLTextInput} element
         * @param {CKEDITOR.dom.textRange} range
         */
        select: function() {

            var startOffset = this.startOffset,
                endOffset = this.endOffset,
                element = this.element.$;
            if (endOffset == undefined) {
                endOffset = startOffset;
            }

            if (CKEDITOR.env.ie && CKEDITOR.env.version == 9) {
                element.focus();
                element.selectionStart = startOffset;
                element.selectionEnd = startOffset;
                setTimeout(function() {
                    element.selectionStart = startOffset;
                    element.selectionEnd = endOffset;
                }, 20);

            } else {
                if (element.setSelectionRange) {
                    if (CKEDITOR.env.ie) {
                        element.focus();
                    }
                    element.setSelectionRange(startOffset, endOffset);
                    if (!CKEDITOR.env.ie) {
                        element.focus();
                    }
                } else if (element.createTextRange) {
                    element.focus();
                    var range = element.createTextRange();
                    range.collapse(true);
                    range.moveStart('character', startOffset);
                    range.moveEnd('character', endOffset - startOffset);
                    range.select();
                }
            }
        },

        /**
         * Select the range included within the bookmark text with the bookmark
         * text removed.
         * @param {Object} bookmark Exactly the one created by CKEDITOR.dom.range.createBookmark( true ).
         */
        moveToBookmark: function(bookmark) {
            var content = this.content;

            function removeBookmarkText(bookmarkId) {

                var bookmarkRegex = new RegExp('<span[^<]*?' + bookmarkId + '.*?/span>'),
                    offset;
                content = content.replace(bookmarkRegex, function(str, index) {
                    offset = index;
                    return '';
                });
                return offset;
            }

            this.startOffset = removeBookmarkText(bookmark.startNode);
            this.endOffset = removeBookmarkText(bookmark.endNode);
            this.content = content;
            this.updateElement();
        },

        /**
         * If startOffset/endOffset anchor inside element tag, start the range before/after the element
         */
        enlarge: function() {
            var htmlTagRegexp = /<[^>]+>/g;
            var content = this.content,
                start = this.startOffset,
                end = this.endOffset,
                match,
                tagStartIndex,
                tagEndIndex;

            // Adjust offset position on parsing result.
            while (match = htmlTagRegexp.exec(content)) {
                tagStartIndex = match.index;
                tagEndIndex = tagStartIndex + match[0].length;
                if (start > tagStartIndex && start < tagEndIndex)
                    start = tagStartIndex;
                if (end > tagStartIndex && end < tagEndIndex) {
                    end = tagEndIndex;
                    break;
                }
            }

            this.startOffset = start;
            this.endOffset = end;
        },

        createBookmark: function() {
            // Enlarge the range to avoid tag partial selection.
            this.enlarge();
            var content = this.content,
                start = this.startOffset,
                end = this.endOffset,
                id = CKEDITOR.tools.getNextNumber(),
                bookmarkTemplate = '';

            content = content.substring(0, start) + bookmarkTemplate.replace('%1', id + 'S')
                + content.substring(start, end) + bookmarkTemplate.replace('%1', id + 'E')
                + content.substring(end);

            this.content = content;
            this.updateElement();
        },

        updateElement: function() {
            if (this.element)
                this.element.$.value = this.content;
        }
    };

    var Browser = {
        Version: function() {
            var version = 999;
            if (navigator.appVersion.indexOf("MSIE") != -1)
                version = parseFloat(navigator.appVersion.split("MSIE")[1]);
            return version;
        }
    };

    // Seamless selection range across different modes.
    CKEDITOR.config.syncSelection = true;

    var textRange,sourceBookmark;<commit_msg>fixed issue when using editor.destroy<commit_after>/*
Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.
For licensing, see LICENSE.html or http://ckeditor.com/license
*/
    /**
     * Represent plain text selection range.
     */
    CKEDITOR.plugins.add('textselection',
    {
        version: 1.02,
        init: function (editor) {
            // Corresponding text range of wysiwyg bookmark.
            var wysiwygBookmark;

            // Auto sync text selection with 'WYSIWYG' mode selection range.
            if (editor.config.syncSelection
                    && CKEDITOR.plugins.sourcearea) {
                editor.on('beforeModeUnload', function (evt) {
                    if (editor.mode === 'source') {
                        if (editor.mode === 'source' && !editor.plugins.codemirror) {
                            var range = editor.getTextSelection();

                            // Fly the range when create bookmark.
                            delete range.element;
                            range.createBookmark();
                            sourceBookmark = true;
                            evt.data = range.content;
                        }
                    }
                });
                editor.on('mode', function () {
                    if (editor.mode === 'wysiwyg' && sourceBookmark) {

                        editor.focus();
                        var doc = editor.document,
                            range = new CKEDITOR.dom.range(editor.document),
                            walker,
                            startNode,
                            endNode;

                        range.setStartAt(doc.getBody(), CKEDITOR.POSITION_AFTER_START);
                        range.setEndAt(doc.getBody(), CKEDITOR.POSITION_BEFORE_END);
                        walker = new CKEDITOR.dom.walker(range);
                       // walker.type = CKEDITOR.NODE_COMMENT;
                        walker.evaluator = function (comment) {
                           //

                            var match = /cke_bookmark_\d+(\w)/.exec(comment.$.nodeValue);
                            if (match) {

                                if (match[1] === 'S') {
                                    startNode = comment;
                                } else if (match[1] === 'E') {
                                    endNode = comment;
                                    return false;
                                }
                            }
                        };
                        walker.lastForward();
                        range.setStartAfter(startNode);
                        range.setEndBefore(endNode);
                        range.select();
                        // Scroll into view for non-IE.
                        // Scroll into view for non-IE.
                        if (!CKEDITOR.env.ie || (CKEDITOR.env.ie && CKEDITOR.env.version === 9)) {
                            editor.getSelection().getStartElement().scrollIntoView(true);
                        } // Remove the comments node which are out of range.
                        startNode.remove();
                        endNode.remove();
                    }
                }, null, null, 10);

                editor.on('beforeGetModeData', function () {
                    if (editor.mode === 'wysiwyg') {
                        if (CKEDITOR.env.gecko && !editor.focusManager.hasFocus) {
                            return;
                        }
                        var sel = editor.getSelection(), range;
                        if (sel && (range = sel.getRanges()[0])) {
                            wysiwygBookmark = range.createBookmark(true);
                        }
                    }
                });
                // Build text range right after wysiwyg has unloaded.
                editor.on('afterModeUnload', function (evt) {
                    if (editor.mode === 'wysiwyg' && wysiwygBookmark) {
                        textRange = new CKEDITOR.dom.textRange(evt.data);
                        textRange.moveToBookmark(wysiwygBookmark);

                        evt.data = textRange.content;
                    }
                });
                editor.on('mode', function () {
                    if (editor.mode === 'source' && textRange && !editor.plugins.codemirror) {
                        textRange.element = new CKEDITOR.dom.element(editor._.editable.$);
                        textRange.select();
                    }
                });

                editor.on('destroy', function () {
                    textRange = null;
                    sourceBookmark = null;
                });
            }
        }
    });

    /**
     * Gets the current text selection from the editing area when in Source mode.
     * @returns {CKEDITOR.dom.textRange} Text range represent the caret positoins.
     * @example
     * var textSelection = CKEDITOR.instances.editor1.getTextSelection();
     * alert( textSelection.startOffset );
     * alert( textSelection.endOffset );
     */
    CKEDITOR.editor.prototype.getTextSelection = function () {
        return this._.editable && getTextSelection(this._.editable.$) || null;
    };

    /**
     * Returns the caret position of the specified textfield/textarea.
     * @param {HTMLTextArea|HTMLTextInput} element
     */
    function getTextSelection(element) {
        var startOffset, endOffset;

        if (!CKEDITOR.env.ie) {
            startOffset = element.selectionStart;
            endOffset = element.selectionEnd;
        } else {
            element.focus();

            // The current selection
            if (window.getSelection) {
                startOffset = element.selectionStart;
                endOffset = element.selectionEnd;
            } else {
                var range = document.selection.createRange(),
                    textLength = range.text.length;

                // Create a 'measuring' range to help calculate the start offset by
                // stretching it from start to current position.
                var measureRange = range.duplicate();
                measureRange.moveToElementText(element);
                measureRange.setEndPoint('EndToEnd', range);

                endOffset = measureRange.text.length;
                startOffset = endOffset - textLength;
            }
        }
        return new CKEDITOR.dom.textRange(
            new CKEDITOR.dom.element(element), startOffset, endOffset);
    }

    /**
     * Represent the selection range within a html textfield/textarea element,
     * or even a flyweight string content represent the text content.
     * @constructor
     * @param {CKEDITOR.dom.element|String} element
     * @param {Number} start
     * @param {Number} end
     */
    CKEDITOR.dom.textRange = function (element, start, end) {
        if (element instanceof CKEDITOR.dom.element
            && (element.is('textarea')
                || element.is('input') && element.getAttribute('type') == 'text')) {
            this.element = element;
            this.content = element.$.value;
        } else if (typeof element == 'string')
            this.content = element;
        else
            throw 'Unkown "element" type.';
        this.startOffset = start || 0;
        this.endOffset = end || 0;
    };

     /**
     * Changes the editing mode of this editor instance. (Override of the original function)
     *
     * **Note:** The mode switch could be asynchronous depending on the mode provider.
     * Use the `callback` to hook subsequent code.
     *
     *                // Switch to "source" view.
     *                CKEDITOR.instances.editor1.setMode( 'source' );
     *                // Switch to "wysiwyg" view and be notified on completion.
     *                CKEDITOR.instances.editor1.setMode( 'wysiwyg', function() { alert( 'wysiwyg mode loaded!' ); } );
     *
     * @param {String} [newMode] If not specified, the {@link CKEDITOR.config#startupMode} will be used.
     * @param {Function} [callback] Optional callback function which is invoked once the mode switch has succeeded.
     */
    CKEDITOR.editor.prototype.setMode = function (newMode, callback) {
        var editor = this;

        var modes = this._.modes;

        // Mode loading quickly fails.
        if (newMode == editor.mode || !modes || !modes[newMode])
            return;

        editor.fire('beforeSetMode', newMode);

        if (editor.mode) {
            var isDirty = editor.checkDirty();

            editor._.previousMode = editor.mode;

            editor.fire('beforeModeUnload');

            editor.fire('beforeGetModeData');
            var data = editor.getData();

            data = editor.fire('beforeModeUnload', data);
            data = editor.fire('afterModeUnload', data);

            // Detach the current editable.
            editor.editable(0);

            editor._.data = data;

            // Clear up the mode space.
            editor.ui.space('contents').setHtml('');

            editor.mode = '';
        }

        // Fire the mode handler.
        this._.modes[newMode](function () {
            // Set the current mode.
            editor.mode = newMode;

            if (isDirty !== undefined) {
                !isDirty && editor.resetDirty();
            }

            // Delay to avoid race conditions (setMode inside setMode).
            setTimeout(function () {
                editor.fire('mode');
                callback && callback.call(editor);
            }, 0);
        });
    };

    CKEDITOR.dom.textRange.prototype =
    {
        /**
         * Sets the text selection of the specified textfield/textarea.
         * @param {HTMLTextArea|HTMLTextInput} element
         * @param {CKEDITOR.dom.textRange} range
         */
        select: function() {

            var startOffset = this.startOffset,
                endOffset = this.endOffset,
                element = this.element.$;
            if (endOffset == undefined) {
                endOffset = startOffset;
            }

            if (CKEDITOR.env.ie && CKEDITOR.env.version == 9) {
                element.focus();
                element.selectionStart = startOffset;
                element.selectionEnd = startOffset;
                setTimeout(function() {
                    element.selectionStart = startOffset;
                    element.selectionEnd = endOffset;
                }, 20);

            } else {
                if (element.setSelectionRange) {
                    if (CKEDITOR.env.ie) {
                        element.focus();
                    }
                    element.setSelectionRange(startOffset, endOffset);
                    if (!CKEDITOR.env.ie) {
                        element.focus();
                    }
                } else if (element.createTextRange) {
                    element.focus();
                    var range = element.createTextRange();
                    range.collapse(true);
                    range.moveStart('character', startOffset);
                    range.moveEnd('character', endOffset - startOffset);
                    range.select();
                }
            }
        },

        /**
         * Select the range included within the bookmark text with the bookmark
         * text removed.
         * @param {Object} bookmark Exactly the one created by CKEDITOR.dom.range.createBookmark( true ).
         */
        moveToBookmark: function(bookmark) {
            var content = this.content;

            function removeBookmarkText(bookmarkId) {

                var bookmarkRegex = new RegExp('<span[^<]*?' + bookmarkId + '.*?/span>'),
                    offset;
                content = content.replace(bookmarkRegex, function(str, index) {
                    offset = index;
                    return '';
                });
                return offset;
            }

            this.startOffset = removeBookmarkText(bookmark.startNode);
            this.endOffset = removeBookmarkText(bookmark.endNode);
            this.content = content;
            this.updateElement();
        },

        /**
         * If startOffset/endOffset anchor inside element tag, start the range before/after the element
         */
        enlarge: function() {
            var htmlTagRegexp = /<[^>]+>/g;
            var content = this.content,
                start = this.startOffset,
                end = this.endOffset,
                match,
                tagStartIndex,
                tagEndIndex;

            // Adjust offset position on parsing result.
            while (match = htmlTagRegexp.exec(content)) {
                tagStartIndex = match.index;
                tagEndIndex = tagStartIndex + match[0].length;
                if (start > tagStartIndex && start < tagEndIndex)
                    start = tagStartIndex;
                if (end > tagStartIndex && end < tagEndIndex) {
                    end = tagEndIndex;
                    break;
                }
            }

            this.startOffset = start;
            this.endOffset = end;
        },

        createBookmark: function() {
            // Enlarge the range to avoid tag partial selection.
            this.enlarge();
            var content = this.content,
                start = this.startOffset,
                end = this.endOffset,
                id = CKEDITOR.tools.getNextNumber(),
                bookmarkTemplate = '';

            content = content.substring(0, start) + bookmarkTemplate.replace('%1', id + 'S')
                + content.substring(start, end) + bookmarkTemplate.replace('%1', id + 'E')
                + content.substring(end);

            this.content = content;
            this.updateElement();
        },

        updateElement: function() {
            if (this.element)
                this.element.$.value = this.content;
        }
    };

    var Browser = {
        Version: function() {
            var version = 999;
            if (navigator.appVersion.indexOf("MSIE") != -1)
                version = parseFloat(navigator.appVersion.split("MSIE")[1]);
            return version;
        }
    };

    // Seamless selection range across different modes.
    CKEDITOR.config.syncSelection = true;

    var textRange,sourceBookmark;<|endoftext|>


<commit_before>
// external libs
import { Object, Reflect } from 'core-js/library';

export default class CustomElement {

	/**
	 * Register an element by give ES5/ES6 Class
	 * @param  {Function} ComponentClass
	 * @param  {Element} DOMElement
	 * @return {undefined}
	 */
	static register(ComponentClass, DOMElement = HTMLElement, prefix) {

		// extract useful properties
		let componentClassName  = CustomElement.getClassName(ComponentClass);
		let validComponentName  = CustomElement.convertToValidComponentName(componentClassName, prefix);

		// buildComponent
		DOMElement = CustomElement.buildComponent(ComponentClass, DOMElement);

		// register element
		let customCallbacks = CustomElement.getCustomCallbacks();
		let ComElement = document.registerElement(validComponentName, {
			prototype: Object.create(DOMElement.prototype, customCallbacks),
			// extends: 'div',
		});

		ComponentClass.Class = ComponentClass;
		ComponentClass.ComElement = ComElement;

		// create static factory method for creating dominstance
		ComponentClass.create = function($create_vars = null){

			// override constructor
			/**
			 * Nativ CustomElements doesnt allow to use a constructor
			 * therefore if constructor is added by the user we override that
			 * otherwise an error could be thrown.
			 *
			 */
			this.constructor = function(){};

			// extract and assign instance properties
			/**
			 * At the moment we cant extract class properties from prototype
			 * because it not visible there. Babel convert this:
			 *
			 * class Item {
			 *    prop1 = 123; // <- cant extract it directly (from prototype)
			 * }
			 *
			 * to:
			 *
			 * function Item(){
			 *    this.prop1 = 123; // <- we dont know how we get this.prop1 from there
			 * }
			 *
			 */
			let tmpInstanceProperties = new this.Class();
			let instanceProperties = {};
			for(let property of Reflect.ownKeys(tmpInstanceProperties)){
				instanceProperties[property] = tmpInstanceProperties[property];
			}
			let comElement = new this.ComElement();
			Object.assign(comElement, instanceProperties);
			// end of extracting and assign of instance properties

			comElement.createdCallback($create_vars);

			return comElement;
		};
	}

	static buildComponent(ComponentClass, DOMElement){

		let CollectionOfClasses = CustomElement.explodeByExtends(ComponentClass);
		let ConstructedClass = CustomElement.constructDOMClass(CollectionOfClasses, DOMElement);

		return ConstructedClass;
	}

	/**
	 * Convert given String (name) in valid webcomponent name
	 * @param  {String} name
	 * @param  {String} prefix
	 * @return {String}
	 */
	static convertToValidComponentName(name, prefix = 'com') {
		return `${prefix}-${name.toLowerCase()}`;
	}

	/**
	 * Explode given Class by extends
	 * @param  {Function} ComponentClass
	 * @return {Array}
	 */
	static explodeByExtends(ComponentClass) {

		let constructor = true;
		let resolvedClass = ComponentClass;
		let explodedExtends = [];
		while(constructor){
			// resolve given class "ComponentClass" recursivly
			// see tests for better understanding
			resolvedClass = CustomElement.resolveClassInComponents(
				!(1 in resolvedClass) ? resolvedClass : resolvedClass[1]
			);
			constructor = resolvedClass[0];
			if(!constructor) {
				continue;
			}
			explodedExtends.push(resolvedClass);
		}
		return explodedExtends;

	}

	/**
	 * Resolve given Class in useable components
	 * @param  {Function} ClassComponent
	 * @return {Array}
	 */
	static resolveClassInComponents(ClassComponent = {}){

		// methods collection designed for createClass
		let instanceMethods = [];
		let staticMethods = [];

		// resolve Class in useful components
		let prototype = CustomElement.getPrototype(ClassComponent);
		let className = CustomElement.getClassName(ClassComponent);
		let constructor = CustomElement.getConstructor(ClassComponent);

		// if reached className is "Object" set "see inside block" to null
		if(className === 'Object'){
			prototype = className = constructor = null;
		}

		// preparing instance and static methods for createClass
		if(constructor && prototype && className !== 'Object'){
			instanceMethods = CustomElement.getInstanceMethods(ClassComponent);
			staticMethods = CustomElement.getStaticMethods(ClassComponent);
		}

		return [ constructor, prototype, instanceMethods, staticMethods ];
	}

	/**
	 * Construct given Class-Collection with an DOMElement
	 *
	 * @param  {Array} collection
	 * @param  {Element} DOMElement
	 * @return {Function}
	 */
	static constructDOMClass(collection = [], DOMElement) {

		// default behavoir of function: if class without any parent class
		let ConstructedClass = null;
		let [ Constructor,, instanceMethods, staticMethods] = collection.pop();

		if(Constructor && DOMElement){
			CustomElement.inherits(Constructor, DOMElement);
			CustomElement.createClass(Constructor, instanceMethods, staticMethods);
		}

		if(!collection.length) {
			return Constructor;
		}

		// extended behavoir of function: if class has any parent classes
		while(collection.length) {
			let [ FirstConstructor,, instanceMethods,staticMethods] = collection.pop();
			Constructor = CustomElement.inherits(FirstConstructor, Constructor);
			CustomElement.createClass(Constructor, instanceMethods, staticMethods);
		}

		return Constructor;
	}

	/**
	 * Extract and build methods in new object structure for createClass
	 * @param  {Object} propObject
	 * @param  {Regex} exclude
	 * @return {Array} methodContainer
	 */
	static extractProperties(propObject, exclude = false){

		let methodContainer = [];
		for(let method of Reflect.ownKeys(propObject)) {
			if(exclude && method.match(exclude)) {
				continue;
			};
			methodContainer.push({
				key: method,
				value: propObject[method],
			});
		}
		return methodContainer;
	}

	/**
	 * Apply onCratedCallbacks
	 * @param  {Object} self
	 * @param  {Any} ...args
	 * @return {undefined}
	 */
	static applyOnCreatedCallback(self, ...args){
		if(self.$onCreated && self.$onCreated.length) {
			for(let $callback of self.$onCreated){
				$callback(self, ...args);
			}
		}
	}

	/**
	 * Returns custom element callbacks
	 * @return {Object}
	 */
	static getCustomCallbacks(){

		return {
			createdCallback: {value: function(...args){

				/**
				 * 1.) return if native instantiating (with new) of custom element.
				 * See CustomElement.register => .create(). What we want is to pass arguments
				 * through .create() (see tests view.spec.js) therefore
				 * we have to do that manually (see CustomElement.register .create() => createdCallback)
				 *
				 * 2.) If createdCallback has not args and has parentElement, the CustomElement
				 * its instantiated from browser itself ! Also, no used fooCustomElement.create().
				 * The element was in dom, is already exists there e.g.:
				 * <body>
				 *     <com-foo></com-foo>
				 *     <com-foo></com-foo>
				 * </body>
				 */
				if(!args.length && !this.parentElement){
					return;
				}

				CustomElement.applyOnCreatedCallback(this, ...args);
				this.created ? this.created(...args) : null;
			}},
			attachedCallback: {value: function(){
				this.attached ? this.attached() : null;
			}},
			detachedCallback: {value: function(){
				this.detached ? this.detached() : null;
			}},
			attributeChangedCallback: {value: function(...args){
				this.attributeChanged ? this.attributeChanged(...args) : null;
			}},
		}
	}

	/**
	 * Create a class by given Constructor-Function, prototypeProrps and staticProps
	 * @param  {Function} Constructor
	 * @param  {Object} protoProps
	 * @param  {Object} staticProps
	 * @return {Function} Constructor
	 */
	static createClass(Constructor, protoProps, staticProps) {

		if (protoProps) CustomElement.defineProperties(Constructor.prototype, protoProps);
		if (staticProps) CustomElement.defineProperties(Constructor, staticProps);
		return Constructor;

	}

	/**
	 * Given Subclass inherits (extends) from superClass
	 * @param  {Function} subClass
	 * @param  {Function} superClass
	 * @return {Function} subClass
	 */
	static inherits(subClass, superClass) {

		if (typeof superClass !== "function" && superClass !== null) {
			throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
		}

		subClass.prototype = Object.create(superClass && superClass.prototype, {
			constructor: {
				value: subClass,
				enumerable: false,
				writable: true,
				configurable: true
			}
		});

		if (superClass){
			Object.setPrototypeOf
				? Object.setPrototypeOf(subClass, superClass)
				: subClass.__proto__ = superClass;
		}

		return subClass;
	}

	/**
	 * Sets properties to given target
	 * @param  {Function|Object} target
	 * @param  {Object} props
	 * @return {undefined}
	 */
	static defineProperties(target, props){

		for (var i = 0; i < props.length; i++) {
			var descriptor = props[i];
			descriptor.enumerable = descriptor.enumerable || false;
			descriptor.configurable = true;
			if ("value" in descriptor) descriptor.writable = true;
			Object.defineProperty(target, descriptor.key, descriptor);
		}
	}

	/**
	 * returns instance methods of a Function-Class
	 * @param  {Object} ClassComponent
	 * @return {Array}
	 */
	static getInstanceMethods(ClassComponent) {
		let prototype = CustomElement.getPrototype(ClassComponent);
		return CustomElement.extractProperties(prototype, /constructor/);
	}

	/**
	 * returns static methods of a Function-Class
	 * @param  {Function} ClassComponent
	 * @return {Array}
	 */
	static getStaticMethods(ClassComponent) {
		let constructor = CustomElement.getConstructor(ClassComponent);
		return CustomElement.extractProperties(constructor, /length|name|prototype/);
	}

	/**
	 * Return prototype by given Function-Class
	 * @param  {Function} ClassComponent
	 * @return {Object}
	 */
	static getPrototype(ClassComponent){
		return ClassComponent.prototype || ClassComponent.__proto__ || null;
	}

	/**
	 * Return classname by given Function-Class
	 * @param  {Function} ClassComponent
	 * @return {String}
	 */
	static getClassName(ClassComponent) {
		let prototype = CustomElement.getPrototype(ClassComponent);
		return ( prototype !== null ? prototype.constructor.name : null );
	}

	/**
	 * Return constructor by given Function-Class
	 * @param  {Function} ClassComponent
	 * @return {Function}
	 */
	static getConstructor(ClassComponent) {
		let prototype = CustomElement.getPrototype(ClassComponent);
		return ( prototype !== null ? prototype.constructor : null );
	}

}
<commit_msg>added cleanup<commit_after>
// external libs
import { Object, Reflect } from 'core-js/library';

export default class CustomElement {

	/**
	 * Register an element by give ES5/ES6 Class
	 * @param  {Function} ComponentClass
	 * @param  {Element} DOMElement
	 * @return {undefined}
	 */
	static register(ComponentClass, DOMElement = HTMLElement, prefix) {

		// extract useful properties
		let componentClassName  = CustomElement.getClassName(ComponentClass);
		let validComponentName  = CustomElement.convertToValidComponentName(componentClassName, prefix);

		// buildComponent
		DOMElement = CustomElement.buildComponent(ComponentClass, DOMElement);

		// register element
		let customCallbacks = CustomElement.getCustomCallbacks();
		let ComElement = document.registerElement(validComponentName, {
			prototype: Object.create(DOMElement.prototype, customCallbacks),
			// extends: 'div',
		});

		ComponentClass.Class = ComponentClass;
		ComponentClass.ComElement = ComElement;

		// create static factory method for creating dominstance
		ComponentClass.create = function($create_vars = null){

			// override constructor
			/**
			 * Nativ CustomElements doesnt allow to use a constructor
			 * therefore if constructor is added by the user we override that
			 * otherwise an error could be thrown.
			 *
			 */
			this.constructor = function(){};

			// extract and assign instance properties
			/**
			 * At the moment we cant extract class properties from prototype
			 * because it not visible there. Babel convert this:
			 *
			 * class Item {
			 *    prop1 = 123; // <- cant extract it directly (from prototype)
			 * }
			 *
			 * to:
			 *
			 * function Item(){
			 *    this.prop1 = 123; // <- we dont know how we get this.prop1 from there
			 * }
			 *
			 */
			let tmpInstanceProperties = new this.Class();
			let instanceProperties = {};
			for(let property of Reflect.ownKeys(tmpInstanceProperties)){
				instanceProperties[property] = tmpInstanceProperties[property];
			}
			let comElement = new this.ComElement();
			Object.assign(comElement, instanceProperties);

			// cleanup
			tmpInstanceProperties = null;
			instanceProperties = null;
			// end of extracting and assign of instance properties

			comElement.createdCallback($create_vars);

			return comElement;
		};
	}

	static buildComponent(ComponentClass, DOMElement){

		let CollectionOfClasses = CustomElement.explodeByExtends(ComponentClass);
		let ConstructedClass = CustomElement.constructDOMClass(CollectionOfClasses, DOMElement);

		return ConstructedClass;
	}

	/**
	 * Convert given String (name) in valid webcomponent name
	 * @param  {String} name
	 * @param  {String} prefix
	 * @return {String}
	 */
	static convertToValidComponentName(name, prefix = 'com') {
		return `${prefix}-${name.toLowerCase()}`;
	}

	/**
	 * Explode given Class by extends
	 * @param  {Function} ComponentClass
	 * @return {Array}
	 */
	static explodeByExtends(ComponentClass) {

		let constructor = true;
		let resolvedClass = ComponentClass;
		let explodedExtends = [];
		while(constructor){
			// resolve given class "ComponentClass" recursivly
			// see tests for better understanding
			resolvedClass = CustomElement.resolveClassInComponents(
				!(1 in resolvedClass) ? resolvedClass : resolvedClass[1]
			);
			constructor = resolvedClass[0];
			if(!constructor) {
				continue;
			}
			explodedExtends.push(resolvedClass);
		}
		return explodedExtends;

	}

	/**
	 * Resolve given Class in useable components
	 * @param  {Function} ClassComponent
	 * @return {Array}
	 */
	static resolveClassInComponents(ClassComponent = {}){

		// methods collection designed for createClass
		let instanceMethods = [];
		let staticMethods = [];

		// resolve Class in useful components
		let prototype = CustomElement.getPrototype(ClassComponent);
		let className = CustomElement.getClassName(ClassComponent);
		let constructor = CustomElement.getConstructor(ClassComponent);

		// if reached className is "Object" set "see inside block" to null
		if(className === 'Object'){
			prototype = className = constructor = null;
		}

		// preparing instance and static methods for createClass
		if(constructor && prototype && className !== 'Object'){
			instanceMethods = CustomElement.getInstanceMethods(ClassComponent);
			staticMethods = CustomElement.getStaticMethods(ClassComponent);
		}

		return [ constructor, prototype, instanceMethods, staticMethods ];
	}

	/**
	 * Construct given Class-Collection with an DOMElement
	 *
	 * @param  {Array} collection
	 * @param  {Element} DOMElement
	 * @return {Function}
	 */
	static constructDOMClass(collection = [], DOMElement) {

		// default behavoir of function: if class without any parent class
		let ConstructedClass = null;
		let [ Constructor,, instanceMethods, staticMethods] = collection.pop();

		if(Constructor && DOMElement){
			CustomElement.inherits(Constructor, DOMElement);
			CustomElement.createClass(Constructor, instanceMethods, staticMethods);
		}

		if(!collection.length) {
			return Constructor;
		}

		// extended behavoir of function: if class has any parent classes
		while(collection.length) {
			let [ FirstConstructor,, instanceMethods,staticMethods] = collection.pop();
			Constructor = CustomElement.inherits(FirstConstructor, Constructor);
			CustomElement.createClass(Constructor, instanceMethods, staticMethods);
		}

		return Constructor;
	}

	/**
	 * Extract and build methods in new object structure for createClass
	 * @param  {Object} propObject
	 * @param  {Regex} exclude
	 * @return {Array} methodContainer
	 */
	static extractProperties(propObject, exclude = false){

		let methodContainer = [];
		for(let method of Reflect.ownKeys(propObject)) {
			if(exclude && method.match(exclude)) {
				continue;
			};
			methodContainer.push({
				key: method,
				value: propObject[method],
			});
		}
		return methodContainer;
	}

	/**
	 * Apply onCratedCallbacks
	 * @param  {Object} self
	 * @param  {Any} ...args
	 * @return {undefined}
	 */
	static applyOnCreatedCallback(self, ...args){
		if(self.$onCreated && self.$onCreated.length) {
			for(let $callback of self.$onCreated){
				$callback(self, ...args);
			}
		}
	}

	/**
	 * Returns custom element callbacks
	 * @return {Object}
	 */
	static getCustomCallbacks(){

		return {
			createdCallback: {value: function(...args){

				/**
				 * 1.) return if native instantiating (with new) of custom element.
				 * See CustomElement.register => .create(). What we want is to pass arguments
				 * through .create() (see tests view.spec.js) therefore
				 * we have to do that manually (see CustomElement.register .create() => createdCallback)
				 *
				 * 2.) If createdCallback has not args and has parentElement, the CustomElement
				 * its instantiated from browser itself ! Also, no used fooCustomElement.create().
				 * The element was in dom, is already exists there e.g.:
				 * <body>
				 *     <com-foo></com-foo>
				 *     <com-foo></com-foo>
				 * </body>
				 */
				if(!args.length && !this.parentElement){
					return;
				}

				CustomElement.applyOnCreatedCallback(this, ...args);
				this.created ? this.created(...args) : null;
			}},
			attachedCallback: {value: function(){
				this.attached ? this.attached() : null;
			}},
			detachedCallback: {value: function(){
				this.detached ? this.detached() : null;
			}},
			attributeChangedCallback: {value: function(...args){
				this.attributeChanged ? this.attributeChanged(...args) : null;
			}},
		}
	}

	/**
	 * Create a class by given Constructor-Function, prototypeProrps and staticProps
	 * @param  {Function} Constructor
	 * @param  {Object} protoProps
	 * @param  {Object} staticProps
	 * @return {Function} Constructor
	 */
	static createClass(Constructor, protoProps, staticProps) {

		if (protoProps) CustomElement.defineProperties(Constructor.prototype, protoProps);
		if (staticProps) CustomElement.defineProperties(Constructor, staticProps);
		return Constructor;

	}

	/**
	 * Given Subclass inherits (extends) from superClass
	 * @param  {Function} subClass
	 * @param  {Function} superClass
	 * @return {Function} subClass
	 */
	static inherits(subClass, superClass) {

		if (typeof superClass !== "function" && superClass !== null) {
			throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
		}

		subClass.prototype = Object.create(superClass && superClass.prototype, {
			constructor: {
				value: subClass,
				enumerable: false,
				writable: true,
				configurable: true
			}
		});

		if (superClass){
			Object.setPrototypeOf
				? Object.setPrototypeOf(subClass, superClass)
				: subClass.__proto__ = superClass;
		}

		return subClass;
	}

	/**
	 * Sets properties to given target
	 * @param  {Function|Object} target
	 * @param  {Object} props
	 * @return {undefined}
	 */
	static defineProperties(target, props){

		for (var i = 0; i < props.length; i++) {
			var descriptor = props[i];
			descriptor.enumerable = descriptor.enumerable || false;
			descriptor.configurable = true;
			if ("value" in descriptor) descriptor.writable = true;
			Object.defineProperty(target, descriptor.key, descriptor);
		}
	}

	/**
	 * returns instance methods of a Function-Class
	 * @param  {Object} ClassComponent
	 * @return {Array}
	 */
	static getInstanceMethods(ClassComponent) {
		let prototype = CustomElement.getPrototype(ClassComponent);
		return CustomElement.extractProperties(prototype, /constructor/);
	}

	/**
	 * returns static methods of a Function-Class
	 * @param  {Function} ClassComponent
	 * @return {Array}
	 */
	static getStaticMethods(ClassComponent) {
		let constructor = CustomElement.getConstructor(ClassComponent);
		return CustomElement.extractProperties(constructor, /length|name|prototype/);
	}

	/**
	 * Return prototype by given Function-Class
	 * @param  {Function} ClassComponent
	 * @return {Object}
	 */
	static getPrototype(ClassComponent){
		return ClassComponent.prototype || ClassComponent.__proto__ || null;
	}

	/**
	 * Return classname by given Function-Class
	 * @param  {Function} ClassComponent
	 * @return {String}
	 */
	static getClassName(ClassComponent) {
		let prototype = CustomElement.getPrototype(ClassComponent);
		return ( prototype !== null ? prototype.constructor.name : null );
	}

	/**
	 * Return constructor by given Function-Class
	 * @param  {Function} ClassComponent
	 * @return {Function}
	 */
	static getConstructor(ClassComponent) {
		let prototype = CustomElement.getPrototype(ClassComponent);
		return ( prototype !== null ? prototype.constructor : null );
	}

}
<|endoftext|>


<commit_before>/*!
 * Copyright (c) 2015-2018 Cisco Systems, Inc. See LICENSE file.
 */

const crypto = require('crypto');
const http = require('http');
const path = require('path');
const url = require('url');
const yakbak = require('yakbak');

// NOTE: CONVERSATION_SERVICE does not end in URL
const services = [
  {
    defaultUrl: 'https://atlas-a.wbx2.com/admin/api/v1',
    env: 'ATLAS_SERVICE_URL',
    name: 'atlas',
    port: 3010,
    serviceUrl: process.env.ATLAS_SERVICE_URL
  },
  // {
  //   defaultUrl: 'https://conv-a.wbx2.com/conversation/api/v1',
  //   env: 'CONVERSATION_SERVICE',
  //   name: 'conversation',
  //   port: 3020,
  //   serviceUrl: process.env.CONVERSATION_SERVICE
  // },
  {
    defaultUrl: 'https://api.ciscospark.com/v1',
    env: 'HYDRA_SERVICE_URL',
    name: 'hydra',
    port: 3030,
    serviceUrl: process.env.HYDRA_SERVICE_URL
  },
  {
    defaultUrl: 'https://wdm-a.wbx2.com/wdm/api/v1',
    env: 'WDM_SERVICE_URL',
    name: 'wdm',
    port: 3040,
    serviceUrl: process.env.WDM_SERVICE_URL
  }
];

let proxies;

/**
 * Start yakbak proxy servers for each service
 * and return an array of those servers.
 * @returns {Promise}
 */
async function startProxies() {
  await Promise.all(services.map((service) => setEnv(service)));
  return Promise.all(services.map((service) => start(service)));
}

/**
 * Stop each of the proxy servers
 * in the given array.
 * @param {Array} proxies
 * @returns {Promise}
 */
async function stopProxies() {
  if (proxies && proxies.length) {
    return Promise.all(proxies.map((proxy) => stop(proxy)));
  }
  return Promise.resolve();
}

/**
 * Sets the process's environment variable given the service,
 * e.g., HYDRA_SERVICE_URL="http://localhost:3010"
 * @param {Object} service
 * @returns {Promise}
 */
async function setEnv(service) {
  return new Promise((resolve) => {
    process.env[service.env] = `http://localhost:${service.port}`;
    resolve();
  });
}

/**
 * Starts a proxy server for the given service.
 * @param {Object} service
 * @returns {Promise|http.server} proxy server
 */
async function start(service) {
  return new Promise((resolve) => {
    const snapshotsDir = path.join(__dirname, '../../test/services/', service.name, 'snapshots');
    const app = yakbak(service.defaultUrl, {
      dirname: snapshotsDir,
      hash: customHash
    });
    const proxy = http.createServer(app).listen(service.port, () => {
      console.log(`Yakbak server listening on port ${service.port}. Proxy for ${service.defaultUrl}`);
    });
    resolve(proxy);
  });
}

/**
 * Stops the given proxy server.
 * @param {http.server} proxy
 * @returns {http.server} proxy server
 */
async function stop(proxy) {
  return new Promise((resolve) => {
    proxy.close();
    resolve(proxy);
  });
}

/**
 * Creates a custom hash used as the snapshot's filename.
 * @param {http.ClientRequest} req
 * @param {Object} body
 * @returns {String} hashed filename
 */
function customHash(req, body) {
  const hash = crypto.createHash('md5');
  updateHash(hash, req);
  hash.write(body);
  return hash.digest('hex');
}

/**
 * Updates the given hash with the appropriate
 * methods, headers, etc.
 * @param {Hash} hash
 * @param {http.ClientRequest} req
 */
function updateHash(hash, req) {
  const parts = url.parse(req.url, true);
  const headers = pruneHeaders(req.headers);

  hash.update(req.httpVersion);
  hash.update(req.method);
  hash.update(parts.pathname);
  hash.update(JSON.stringify(sort(parts.query)));
  hash.update(JSON.stringify(sort(headers)));
  hash.update(JSON.stringify(sort(req.trailers)));
}

/**
 * Remove headers that are unique for each request
 * from the given headers object. This ensures
 * that certain headers do not "bust" the hash.
 * @param {Object} requestHeaders
 * @returns {Object} a new, pruned headers object
 */
function pruneHeaders(requestHeaders) {
  const headers = Object.assign({}, requestHeaders);
  delete headers.trackingid;
  delete headers.authorization;
  return headers;
}

/**
 * Sorts the given object.
 * @param {Object} obj
 * @returns {Object} a new, sorted object
 */
function sort(obj) {
  const ret = {};
  Object.keys(obj).sort().forEach((key) => {
    ret[key] = obj[key];
  });
  return ret;
}

module.exports = {
  startProxies,
  stopProxies
};
<commit_msg>chore(proxies): add conversation service proxy<commit_after>/*!
 * Copyright (c) 2015-2018 Cisco Systems, Inc. See LICENSE file.
 */

const crypto = require('crypto');
const http = require('http');
const path = require('path');
const url = require('url');
const yakbak = require('yakbak');

// NOTE: CONVERSATION_SERVICE does not end in URL
const services = [
  {
    defaultUrl: 'https://atlas-a.wbx2.com/admin/api/v1',
    env: 'ATLAS_SERVICE_URL',
    name: 'atlas',
    port: 3010,
    serviceUrl: process.env.ATLAS_SERVICE_URL
  },
  {
    defaultUrl: 'https://conv-a.wbx2.com/conversation/api/v1',
    env: 'CONVERSATION_SERVICE',
    name: 'conversation',
    port: 3020,
    serviceUrl: process.env.CONVERSATION_SERVICE
  },
  {
    defaultUrl: 'https://api.ciscospark.com/v1',
    env: 'HYDRA_SERVICE_URL',
    name: 'hydra',
    port: 3030,
    serviceUrl: process.env.HYDRA_SERVICE_URL
  },
  {
    defaultUrl: 'https://wdm-a.wbx2.com/wdm/api/v1',
    env: 'WDM_SERVICE_URL',
    name: 'wdm',
    port: 3040,
    serviceUrl: process.env.WDM_SERVICE_URL
  }
];

let proxies;

/**
 * Start yakbak proxy servers for each service
 * and return an array of those servers.
 * @returns {Promise}
 */
async function startProxies() {
  await Promise.all(services.map((service) => setEnv(service)));
  return Promise.all(services.map((service) => start(service)));
}

/**
 * Stop each of the proxy servers
 * in the given array.
 * @param {Array} proxies
 * @returns {Promise}
 */
async function stopProxies() {
  if (proxies && proxies.length) {
    return Promise.all(proxies.map((proxy) => stop(proxy)));
  }
  return Promise.resolve();
}

/**
 * Sets the process's environment variable given the service,
 * e.g., HYDRA_SERVICE_URL="http://localhost:3010"
 * @param {Object} service
 * @returns {Promise}
 */
async function setEnv(service) {
  return new Promise((resolve) => {
    process.env[service.env] = `http://localhost:${service.port}`;
    resolve();
  });
}

/**
 * Starts a proxy server for the given service.
 * @param {Object} service
 * @returns {Promise|http.server} proxy server
 */
async function start(service) {
  return new Promise((resolve) => {
    const snapshotsDir = path.join(__dirname, '../../test/services/', service.name, 'snapshots');
    const app = yakbak(service.defaultUrl, {
      dirname: snapshotsDir,
      hash: customHash
    });
    const proxy = http.createServer(app).listen(service.port, () => {
      console.log(`Yakbak server listening on port ${service.port}. Proxy for ${service.defaultUrl}`);
    });
    resolve(proxy);
  });
}

/**
 * Stops the given proxy server.
 * @param {http.server} proxy
 * @returns {http.server} proxy server
 */
async function stop(proxy) {
  return new Promise((resolve) => {
    proxy.close();
    resolve(proxy);
  });
}

/**
 * Creates a custom hash used as the snapshot's filename.
 * @param {http.ClientRequest} req
 * @param {Object} body
 * @returns {String} hashed filename
 */
function customHash(req, body) {
  const hash = crypto.createHash('md5');
  updateHash(hash, req);
  hash.write(body);
  return hash.digest('hex');
}

/**
 * Updates the given hash with the appropriate
 * methods, headers, etc.
 * @param {Hash} hash
 * @param {http.ClientRequest} req
 */
function updateHash(hash, req) {
  const parts = url.parse(req.url, true);
  const headers = pruneHeaders(req.headers);

  hash.update(req.httpVersion);
  hash.update(req.method);
  hash.update(parts.pathname);
  hash.update(JSON.stringify(sort(parts.query)));
  hash.update(JSON.stringify(sort(headers)));
  hash.update(JSON.stringify(sort(req.trailers)));
}

/**
 * Remove headers that are unique for each request
 * from the given headers object. This ensures
 * that certain headers do not "bust" the hash.
 * @param {Object} requestHeaders
 * @returns {Object} a new, pruned headers object
 */
function pruneHeaders(requestHeaders) {
  const headers = Object.assign({}, requestHeaders);
  delete headers.trackingid;
  delete headers.authorization;
  return headers;
}

/**
 * Sorts the given object.
 * @param {Object} obj
 * @returns {Object} a new, sorted object
 */
function sort(obj) {
  const ret = {};
  Object.keys(obj).sort().forEach((key) => {
    ret[key] = obj[key];
  });
  return ret;
}

module.exports = {
  startProxies,
  stopProxies
};
<|endoftext|>


<commit_before>$(document).ready(function() {
    var COMMAND_ADD = "add"
    var COMMAND_NEXT = "next";
    var COMMAND_PLAY = "play";
    var PRIVATE_CHANNEL = "/webapp/playlist";
    var debug = true;

    /*
     *  Dev tests
     */

    function testAddMessage() {
        var testMessage = {
            topic : PRIVATE_CHANNEL,
            payload : {
                action : 'add',
                data : {
                    artist : 'Nirvana',
                    track : 'About a girl'
                }
            }
        }
        onMessage(JSON.stringify(testMessage));
    }

    function testNextMessage() {
        var testMessage = {
            topic : PRIVATE_CHANNEL,
            payload : {
                action : 'next'
            }
        }
        onMessage(JSON.stringify(testMessage));
    }

    function testPlayMessage() {
        var testMessage = {
            topic : PRIVATE_CHANNEL,
            payload : {
                action : 'play'
            }
        }
        onMessage(JSON.stringify(testMessage));
    }

    /*
    testAddMessage();
    testAddMessage();
    testPlayMessage();
    */

    /*
     *  Implementation
     */

    subscribe(PRIVATE_CHANNEL);

    function subscribe(topic) {
        WebApp.publish(topic);
    }

    function publish(topic, message) {
        WebApp.publish(topic, message);
    }

	function onMessage(message) {
		log("onMessage: " + message);
        var json_message = JSON.parse(message);
        if(json_message.topic == PRIVATE_CHANNEL) {
            handleMessagePayload(json_message.payload);
        }
	}

    function handleMessagePayload(payload) {
		log("handleMessagePayload: " + payload);
        if(payload.action == COMMAND_ADD) {
            updateList(payload.data);
        } else if (payload.action == COMMAND_NEXT) {
            $('#options-list li:first').remove();
        } else if(payload.action = COMMAND_PLAY) {
            $('#current-song-section h2').html(
                $('#options-list li:first span.artist').text() + " – " +
                $('#options-list li:first span.track').text()
            );
        }
    }

    function updateList(data) {
        $('#options-list').append(
            $('
', {
                html: '' + data.artist + '' + "
" +
                        '' + data.track + ''
            })
        );
    }

	function log(message) {
		if (debug) {
			console.log(message);
		}
	}

    $('#search-form').submit(function() {
        $.spotifyTrackSearch($('#search-input').val(), function(data) {
            var html = '';
            var result = $.filterTracksByCountryCode(data.tracks, "SE", 1);
            log(result);
            var data = {
                artist : result[0].artists[0].name ,
                track : result[0].name
            };
            updateList(data);
        });
        return false;
    });
});<commit_msg>#23 Updated private channel<commit_after>$(document).ready(function() {
    var COMMAND_ADD = "add"
    var COMMAND_NEXT = "next";
    var COMMAND_PLAY = "play";
    var PRIVATE_CHANNEL = "/playlist";
    var debug = true;

    /*
     *  Dev tests
     */

    function testAddMessage() {
        var testMessage = {
            topic : PRIVATE_CHANNEL,
            payload : {
                action : 'add',
                data : {
                    artist : 'Nirvana',
                    track : 'About a girl'
                }
            }
        }
        onMessage(JSON.stringify(testMessage));
    }

    function testNextMessage() {
        var testMessage = {
            topic : PRIVATE_CHANNEL,
            payload : {
                action : 'next'
            }
        }
        onMessage(JSON.stringify(testMessage));
    }

    function testPlayMessage() {
        var testMessage = {
            topic : PRIVATE_CHANNEL,
            payload : {
                action : 'play'
            }
        }
        onMessage(JSON.stringify(testMessage));
    }

    /*
    testAddMessage();
    testAddMessage();
    testPlayMessage();
    */

    /*
     *  Implementation
     */

    subscribe(PRIVATE_CHANNEL);

    function subscribe(topic) {
        WebApp.publish(topic);
    }

    function publish(topic, message) {
        WebApp.publish(topic, message);
    }

	function onMessage(message) {
		log("onMessage: " + message);
        var json_message = JSON.parse(message);
        if(json_message.topic == PRIVATE_CHANNEL) {
            handleMessagePayload(json_message.payload);
        }
	}

    function handleMessagePayload(payload) {
		log("handleMessagePayload: " + payload);
        if(payload.action == COMMAND_ADD) {
            updateList(payload.data);
        } else if (payload.action == COMMAND_NEXT) {
            $('#options-list li:first').remove();
        } else if(payload.action = COMMAND_PLAY) {
            $('#current-song-section h2').html(
                $('#options-list li:first span.artist').text() + " – " +
                $('#options-list li:first span.track').text()
            );
        }
    }

    function updateList(data) {
        $('#options-list').append(
            $('
', {
                html: '' + data.artist + '' + "
" +
                        '' + data.track + ''
            })
        );
    }

	function log(message) {
		if (debug) {
			console.log(message);
		}
	}

    $('#search-form').submit(function() {
        $.spotifyTrackSearch($('#search-input').val(), function(data) {
            var html = '';
            var result = $.filterTracksByCountryCode(data.tracks, "SE", 1);
            log(result);
            var data = {
                artist : result[0].artists[0].name ,
                track : result[0].name
            };
            updateList(data);
        });
        return false;
    });
});<|endoftext|>


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

// https://github.com/gruntjs/grunt-contrib-watch

// Run tasks whenever files are changed, added, or deleted.

module.exports = function (config) {
  return {
    compass: {
      files: [config.source + 'scss/{,**/}{,*.scss,*.sass}'],
      tasks: ['sass:develop', 'autoprefixer:develop', 'newer:copy:develop']
    },
    haychtml: {
      files: [config.pages + '{,**/}*.html'],
      tasks: ['haychtml:develop']
    },
    neuter: {
      files: [config.source + 'js/{,**/}{,*.js}'],
      tasks: ['neuter:app']
    },
    neuterLibs: {
      files: [config.source + 'js/libs.js'],
      tasks: ['neuter:libsDevelop']
    },
    neuterTests: {
      files: [config.source + 'tests/{,**/}{,*.js}'],
      tasks: ['neuter:tests']
    },
    shaderChunks: {
      files: config.source + 'js/shader-chunks/*',
      tasks: ['shaderChunks']
    },
    handlebarsShaders: {
      files: config.source + 'js/shaders/*',
      tasks: ['handlebars:shaders']
    },
    copy : {
      files: '<%= copy.build.src %>',
      tasks: ['newer:copy:build']
    },
    livereload: {
      options: {
        debounceDelay: 250,
        livereload: 38000
      },
      files: config.deploy + '**/*.{html,css,js,png,jpg,jpeg,gif,webp,svg}'
    }
  };
};
<commit_msg>Update shader watch<commit_after>/*jshint node:true*/
'use strict';

// https://github.com/gruntjs/grunt-contrib-watch

// Run tasks whenever files are changed, added, or deleted.

module.exports = function (config) {
  return {
    compass: {
      files: [config.source + 'scss/{,**/}{,*.scss,*.sass}'],
      tasks: ['sass:develop', 'autoprefixer:develop', 'newer:copy:develop']
    },
    haychtml: {
      files: [config.pages + '{,**/}*.html'],
      tasks: ['haychtml:develop']
    },
    neuter: {
      files: [config.source + 'js/{,**/}{,*.js}'],
      tasks: ['neuter:app']
    },
    neuterLibs: {
      files: [config.source + 'js/libs.js'],
      tasks: ['neuter:libsDevelop']
    },
    neuterTests: {
      files: [config.source + 'tests/{,**/}{,*.js}'],
      tasks: ['neuter:tests']
    },
    shaderChunks: {
      files: config.source + 'glsl/shader-chunks/*',
      tasks: ['shaderChunks']
    },
    handlebarsShaders: {
      files: config.source + 'glsl/shaders/*',
      tasks: ['handlebars:shaders']
    },
    copy : {
      files: '<%= copy.build.src %>',
      tasks: ['newer:copy:build']
    },
    livereload: {
      options: {
        debounceDelay: 250,
        livereload: 38000
      },
      files: config.deploy + '**/*.{html,css,js,png,jpg,jpeg,gif,webp,svg}'
    }
  };
};
<|endoftext|>


<commit_before>/**
 * Copyright 2015 Kitware Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Define tasks related to loading, configuring, and building plugins.
 */
module.exports = function (grunt) {
    var _ = require('underscore');
    var fs = require('fs');
    var path = require('path');

    /**
     * Adds configuration for plugin related multitasks:
     *
     *   * shell:plugin-<plugin name>
     *      Runs npm install in the plugin directory
     *   * jade:plugin-<plugin name>
     *      Compiles jade templates into the plugin's template.js
     *   * stylus:plugin-<plugin name>
     *      Compiles stylus files into the plugin's main css file
     *   * uglify:plugin-<plugin name>
     *      Compiles all javascript sources into the plugin's main js file
     *   * copy:plugin-<plugin name>
     *      Copies any other files that are served statically
     *
     *  This will also add watch tasks for each of the above.
     */
    var configurePlugin = function (plugin) {
        var pluginTarget = 'plugin-' + plugin,
            pluginPath = path.resolve(grunt.config.get('pluginDir'), plugin),
            staticPath = path.resolve(
                grunt.config.get('staticDir'), 'built', 'plugins', plugin
            ),
            packageJson = path.resolve(pluginPath, 'package.json'),
            cfg = {
                shell: {},
                jade: {},
                stylus: {},
                uglify: {},
                copy: {},
                watch: {},
                default: {},
                plugin: {},
                'plugin-install': {}
            };

        // create the plugin's build directory under the web-root
        grunt.file.mkdir(staticPath);

        cfg.shell[pluginTarget] = {
            command: function () {
                // do nothing if it has no package.json file
                if (!fs.existsSync(packageJson)) {
                    grunt.verbose.writeln('Skipping npm install');
                    return 'true';
                }
                return 'npm install';
            },
            options: {
                execOptions: {
                    cwd: pluginPath
                }
            },
            src: [packageJson]
        };
        cfg.watch[pluginTarget + '-shell'] = {
            files: [packageJson],
            tasks: ['shell:' + pluginTarget]
        };

        cfg.jade[pluginTarget] = {
            files: [{
                src: [pluginPath + '/web_client/templates/**/*.jade'],
                dest: staticPath + '/templates.js'
            }]
        };
        cfg.watch[pluginTarget + '-jade'] = {
            files: [pluginPath + '/web_client/templates/**/*.jade'],
            tasks: ['jade:' + pluginTarget]
        };

        cfg.stylus[pluginTarget] = {
            files: [{
                src: [pluginPath + '/web_client/stylesheets/**/*.styl'],
                dest: staticPath + '/plugin.min.css'
            }]
        };
        cfg.watch[pluginTarget + '-stylus'] = {
            files: [pluginPath + '/web_client/stylesheets/**/*.styl'],
            tasks: ['stylus:' + pluginTarget]
        };

        cfg.uglify[pluginTarget] = {
            files: [{
                src: [
                    pluginPath + '/web_client/js/**/*.js',
                    staticPath + '/templates.js'
                ],
                dest: staticPath + '/plugin.min.js'
            }]
        };
        cfg.watch[pluginTarget + '-uglify'] = {
            files: [
                pluginPath + '/web_client/js/**/*.js',
                staticPath + '/templates.js'
            ],
            tasks: ['uglify:' + pluginTarget]
        };

        cfg.copy[pluginTarget] = {
            files: [{
                expand: true,
                cwd: pluginPath + '/web_client',
                src: ['extra/**'],
                dest: staticPath
            }]
        };
        cfg.watch[pluginTarget + '-copy'] = {
            files: [pluginPath + '/extra/**'],
            tasks: ['copy:' + pluginTarget]
        };

        // the task 'plugin:<plugin name>' is a task alias to run all
        // of the main tasks defined above and possibly more if
        // the plugin itself appends tasks to this array
        cfg.plugin[plugin] = {
            tasks: [
                'jade:' + pluginTarget,
                'stylus:' + pluginTarget,
                'uglify:' + pluginTarget,
                'copy:' + pluginTarget
            ]
        };

        // finally, 'plugin-install:<plugin name>' is a task alias for
        // tasks that are run with `grunt init`
        cfg['plugin-install'][plugin] = {
            tasks: ['shell:' + pluginTarget]
        };

        grunt.config.merge(cfg);
    };

    grunt.config.merge({
        default: {
            plugin: {}
        },
        init: {
            'plugin-install': {}
        }
    });

    /**
     * Insert individual plugins into the plugin meta task.
     */
    grunt.file.expand(grunt.config.get('pluginDir') + '/*')
        .forEach(function (dir) {
            var plugin = path.basename(dir);
            var json = path.resolve(dir, 'plugin.json');
            var yml = path.resolve(dir, 'plugin.yml');
            var config = {}, npm;

            grunt.log.writeln((
                'Found plugin: ' + plugin
            ).bold);

            // merge in configuration for the main plugin build tasks
            configurePlugin(plugin);

            if (fs.existsSync(json)) {
                config = grunt.file.readYAML(json);
            }
            if (fs.existsSync(yml)) {
                config = grunt.file.readYAML(yml);
            }

            if (config.grunt) {
                grunt.log.writeln((
                    'Found plugin: ' + plugin + ' (custom Gruntfile)'
                ).bold);

                npm = (
                    _(config.grunt.dependencies || [])
                        .map(function (version, dep) {
                            return [
                                dep,
                                version.replace(/\./g, '\\.')
                            ].join('@');
                        })
                );

                if (npm.length) {
                    grunt.config.set(
                        'init.npm-install:' + npm.join(':'), {}
                    );
                }

                // load the plugin's gruntfile
                try {
                    require(
                        path.resolve(dir, config.grunt.file || 'Gruntfile.js')
                    )(grunt);
                } catch (e) {
                    // the error can be safely ignored when doing `grunt init`
                    // otherwise a default task will most likely fail later on
                    // write out a warning to help the developers debug errors
                    grunt.log.writeln((
                        'Failed to load ' + plugin + '/' + (config.grunt.file || 'Gruntfile.js') + ':'
                    ).yellow);
                    grunt.log.writeln('>>> ' + e.toString().split('\n').join('\n>>> ').yellow);
                }

                // add default targets
                _(config.grunt.defaultTargets || []).each(function (target) {
                    grunt.config.set('default.' + target, {});
                });
            }
        });

    /**
     * Create a multi-task for all plugin npm installs.
     */
    grunt.registerMultiTask(
        'plugin-install',
        'Run npm install in plugin directories',
        function () {
            var plugin = this.target,
                tasks = 'plugin-install.' + plugin + '.tasks';

            this.requiresConfig(tasks);

            // queue the install tasks
            grunt.config.get(tasks).forEach(function (task) {
                grunt.task.run(task);
            });
        }
    );

    /**
     * Register a "meta" task that will configure and run other tasks
     * to build a plugin.  Keys in the config for this task should be the
     * directory of the plugin within the base plugins path.
     */
    grunt.registerMultiTask('plugin', 'Build and configure plugins', function () {
        var plugin = this.target,
            tasks = 'plugin.' + plugin + '.tasks';

        this.requiresConfig('pluginDir', tasks);

        // queue the build tasks
        grunt.config.get(tasks).forEach(function (task) {
            grunt.task.run(task);
        });
    });
};
<commit_msg>add comments to grunt_tasks/plugin.js<commit_after>/**
 * Copyright 2015 Kitware Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Define tasks related to loading, configuring, and building plugins.
 */
module.exports = function (grunt) {
    var _ = require('underscore');
    var fs = require('fs');
    var path = require('path');

    /**
     * Adds configuration for plugin related multitasks:
     *
     *   * shell:plugin-<plugin name>
     *      Runs npm install in the plugin directory
     *   * jade:plugin-<plugin name>
     *      Compiles jade templates into the plugin's template.js
     *   * stylus:plugin-<plugin name>
     *      Compiles stylus files into the plugin's main css file
     *   * uglify:plugin-<plugin name>
     *      Compiles all javascript sources into the plugin's main js file
     *   * copy:plugin-<plugin name>
     *      Copies any other files that are served statically
     *
     *  This will also add watch tasks for each of the above.
     */
    var configurePlugin = function (plugin) {
        var pluginTarget = 'plugin-' + plugin,
            pluginPath = path.resolve(grunt.config.get('pluginDir'), plugin),
            staticPath = path.resolve(
                grunt.config.get('staticDir'), 'built', 'plugins', plugin
            ),
            packageJson = path.resolve(pluginPath, 'package.json'),
            cfg = {
                shell: {},
                jade: {},
                stylus: {},
                uglify: {},
                copy: {},
                watch: {},
                default: {},
                plugin: {},
                'plugin-install': {}
            };

        // create the plugin's build directory under the web-root
        grunt.file.mkdir(staticPath);

        cfg.shell[pluginTarget] = {
            command: function () {
                // do nothing if it has no package.json file
                if (!fs.existsSync(packageJson)) {
                    grunt.verbose.writeln('Skipping npm install');
                    return 'true';
                }
                return 'npm install';
            },
            options: {
                execOptions: {
                    cwd: pluginPath
                }
            },
            src: [packageJson]
        };
        cfg.watch[pluginTarget + '-shell'] = {
            files: [packageJson],
            tasks: ['shell:' + pluginTarget]
        };

        cfg.jade[pluginTarget] = {
            files: [{
                src: [pluginPath + '/web_client/templates/**/*.jade'],
                dest: staticPath + '/templates.js'
            }]
        };
        cfg.watch[pluginTarget + '-jade'] = {
            files: [pluginPath + '/web_client/templates/**/*.jade'],
            tasks: ['jade:' + pluginTarget]
        };

        cfg.stylus[pluginTarget] = {
            files: [{
                src: [pluginPath + '/web_client/stylesheets/**/*.styl'],
                dest: staticPath + '/plugin.min.css'
            }]
        };
        cfg.watch[pluginTarget + '-stylus'] = {
            files: [pluginPath + '/web_client/stylesheets/**/*.styl'],
            tasks: ['stylus:' + pluginTarget]
        };

        cfg.uglify[pluginTarget] = {
            files: [{
                src: [
                    pluginPath + '/web_client/js/**/*.js',
                    staticPath + '/templates.js'
                ],
                dest: staticPath + '/plugin.min.js'
            }]
        };
        cfg.watch[pluginTarget + '-uglify'] = {
            files: [
                pluginPath + '/web_client/js/**/*.js',
                staticPath + '/templates.js'
            ],
            tasks: ['uglify:' + pluginTarget]
        };

        cfg.copy[pluginTarget] = {
            files: [{
                expand: true,
                cwd: pluginPath + '/web_client',
                src: ['extra/**'],
                dest: staticPath
            }]
        };
        cfg.watch[pluginTarget + '-copy'] = {
            files: [pluginPath + '/extra/**'],
            tasks: ['copy:' + pluginTarget]
        };

        // the task 'plugin:<plugin name>' is a task alias to run all
        // of the main tasks defined above and possibly more if
        // the plugin itself appends tasks to this array
        cfg.plugin[plugin] = {
            tasks: [
                'jade:' + pluginTarget,
                'stylus:' + pluginTarget,
                'uglify:' + pluginTarget,
                'copy:' + pluginTarget
            ]
        };

        // finally, 'plugin-install:<plugin name>' is a task alias for
        // tasks that are run with `grunt init`
        cfg['plugin-install'][plugin] = {
            tasks: ['shell:' + pluginTarget]
        };

        grunt.config.merge(cfg);
    };

    grunt.config.merge({
        default: {
            plugin: {}
        },
        init: {
            'plugin-install': {}
        }
    });

    /**
     * Insert individual plugins into the plugin meta task.
     */
    grunt.file.expand(grunt.config.get('pluginDir') + '/*')
        .forEach(function (dir) {
            var plugin = path.basename(dir);
            var json = path.resolve(dir, 'plugin.json');
            var yml = path.resolve(dir, 'plugin.yml');
            var config = {}, npm;

            grunt.log.writeln((
                'Found plugin: ' + plugin
            ).bold);

            // merge in configuration for the main plugin build tasks
            configurePlugin(plugin);

            if (fs.existsSync(json)) {
                config = grunt.file.readYAML(json);
            }
            if (fs.existsSync(yml)) {
                config = grunt.file.readYAML(yml);
            }

            if (config.grunt) {
                grunt.log.writeln((
                    'Found plugin: ' + plugin + ' (custom Gruntfile)'
                ).bold);

                // install any additional npm packages during init
                npm = (
                    _(config.grunt.dependencies || [])
                        .map(function (version, dep) {
                            // escape any periods in the dependecy version so
                            // that grunt.config.set does not descend on each
                            // version number component
                            var escapedVersion = version.replace(/\./g, '\\.');

                            return [
                                dep,
                                escapedVersion
                            ].join('@');
                        })
                );

                if (npm.length) {
                    grunt.config.set(
                        'init.npm-install:' + npm.join(':'), {}
                    );
                }

                // load the plugin's gruntfile
                try {
                    require(
                        path.resolve(dir, config.grunt.file || 'Gruntfile.js')
                    )(grunt);
                } catch (e) {
                    // the error can be safely ignored when doing `grunt init`
                    // otherwise a default task will most likely fail later on
                    // write out a warning to help the developers debug errors
                    grunt.log.writeln((
                        'Failed to load ' + plugin + '/' + (config.grunt.file || 'Gruntfile.js') + ':'
                    ).yellow);
                    grunt.log.writeln('>>> ' + e.toString().split('\n').join('\n>>> ').yellow);
                }

                // add default targets
                _(config.grunt.defaultTargets || []).each(function (target) {
                    grunt.config.set('default.' + target, {});
                });
            }
        });

    /**
     * Create a multi-task for all plugin npm installs.
     */
    grunt.registerMultiTask(
        'plugin-install',
        'Run npm install in plugin directories',
        function () {
            var plugin = this.target,
                tasks = 'plugin-install.' + plugin + '.tasks';

            this.requiresConfig(tasks);

            // queue the install tasks
            grunt.config.get(tasks).forEach(function (task) {
                grunt.task.run(task);
            });
        }
    );

    /**
     * Register a "meta" task that will configure and run other tasks
     * to build a plugin.  Keys in the config for this task should be the
     * directory of the plugin within the base plugins path.
     */
    grunt.registerMultiTask('plugin', 'Build and configure plugins', function () {
        var plugin = this.target,
            tasks = 'plugin.' + plugin + '.tasks';

        this.requiresConfig('pluginDir', tasks);

        // queue the build tasks
        grunt.config.get(tasks).forEach(function (task) {
            grunt.task.run(task);
        });
    });
};
<|endoftext|>


<commit_before>/*
    This file is part of rhizi, a collaborative knowledge graph editor.
    Copyright (C) 2014-2015  Rhizi

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published
    by the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

var DEBUG_NODE_POSITION = false;

/**
 * core model module - currently unused
 */
define(['util'], function(util) {
    "use strict";

    /**
     * return a random id
     */
    var random_id,
        Node;

    var random_id__hash = function() {
        return Math.random().toString(36).substring(2, 10);
    };

    var random_id__seq = function () {
        var id = 0;
        function get_next() {
            var next = id;
            id += 1;
            return next;
        }
        return get_next;
    };

    function random_node_name() {
        return random_id__hash();
    }

    function init(config){
        if (config.rand_id_generator === 'hash') {
            random_id = random_id__hash;
        }
        if (config.rand_id_generator === 'seq') {
            random_id = random_id__seq();
        }
    }

    function NodePlain() {
    }
    function NodeWithNaNXTest() {
        Object.defineProperty(this, "x", {
            set: function (new_x) {
                if (isNaN(new_x) && !isNaN(this._x)) {
                    console.log('Node ' + this.id + '.x changed to Nan from ' + this._x);
                }
                this._x = new_x;
            },
            get: function () {
                return this._x;
            }
        });
    }
    if (DEBUG_NODE_POSITION) {
        Node = NodeWithNaNXTest;
    } else {
        Node = NodePlain;
    }
    Node.prototype.equals = function(other_node){
        return this.id === other_node.id;
    };

    function Link() {
    }
    // adapt Link to force_layout create __src,__dst aliases
    Link.prototype.__defineGetter__('source', function(){
        return this.__src;
    });
    Link.prototype.__defineGetter__('target', function(){
        return this.__dst;
    });

    function __set_obj_id(obj, id) {
        Object.defineProperty(obj, "id", {
            value: id,
            enumerable: true,
            writable: false
        });
    }

    /**
     * the most flexible way to create a node: - perform spec field validation -
     * fill-in missing spec fields
     */
    function create_node_from_spec(node_spec) {

        util.assert(undefined !== node_spec.name, 'create_node_from_spec: name missing');

        var ret = new Node();

        if (undefined !== node_spec.id) {
            // reuse id if present
            __set_obj_id(ret, node_spec.id);
        }

        // type
        if (undefined === node_spec.type) {
            console.debug('create_node_from_spec: undefined type, falling back to \'perm\'');
            node_spec.type = 'perm';
        }

        // copy spec
        for (var k in node_spec) {
            if (node_spec.hasOwnProperty(k) && typeof node_spec[k] !== 'function' && k !== 'id') {
                ret[k] = node_spec[k];
            }
        }

        // default values
        ret.status = ret.status || 'unknown';

        return ret;
    }

    /**
     * @param node_spec: id must not be defined
     */
    function create_node__set_random_id(node_spec) {
        if (undefined === node_spec) {
            node_spec = {};
        }

        var ret = create_node_from_spec(node_spec);

        util.assert(undefined === ret.id); // id must not be defined in spec
        __set_obj_id(ret, random_id());

        return ret;
    }

    function create_link__set_random_id(src, dst, link_spec) {
        var ret = create_link_from_spec(src, dst, link_spec);
        __set_obj_id(ret, random_id());
        return ret;
    }

    /**
     * determine if nodes are equal by name
     *
     * @param other_node
     * @returns {Boolean}
     */
    Node.prototype.equal_by_name = function(other) {
        var ret = this.name.toLowerCase() === other.name.toLowerCase();
        if (false === ret) {
            console.debug(this.id + ' != ' + other.id);
        }
        return ret;
    };

    function create_link_from_spec(src, dst, link_spec) {
        var ret = new Link();

        if (undefined !== link_spec.id) {
            // reuse id if present
            __set_obj_id(ret, link_spec.id);
        }

        util.assert(undefined !== src, 'create_link_from_spec: src missing');
        util.assert(undefined !== dst, 'create_link_from_spec: dst missing');
        util.assert(undefined !== src.id, 'create_link_from_spec: src missing id');
        util.assert(undefined !== dst.id, 'create_link_from_spec: dst missing id');
        util.assert(undefined !== link_spec.name, 'create_link_from_spec: name missing, unable to deduce type');

        ret.__src = src;
        ret.__dst = dst;
        ret.__type = link_spec.name;

        if (undefined === link_spec.name){
            console.warn('create_link_from_spec: name: ' + link_spec.name);
            link_spec.name = "";
        }
        ret.name = link_spec.name.trim();
        delete link_spec.name;
        delete link_spec.__src;
        delete link_spec.__dst;

        for (var property in link_spec) {
            if (link_spec.hasOwnProperty(property) && typeof link_spec[property] !== 'function' && property !== 'id') {
                ret[property] = link_spec[property];
            }
        }
        return ret;
    }

    /**
     * determine if links are equal by ID
     *
     * @param other_node
     * @returns {Boolean}
     */
    Link.prototype.equal_by_id = function(other) {
        return this.id.toLowerCase() === other.id.toLowerCase();
    };

    return {
        init : init,
        Node: Node, // allow model adaptation
        Link: Link, // allow model adaptation
        random_node_name : random_node_name,
        create_node_from_spec : create_node_from_spec,
        create_node__set_random_id : create_node__set_random_id,
        create_link_from_spec : create_link_from_spec,
        create_link__set_random_id : create_link__set_random_id,
    };
});
<commit_msg>client: core debug: check isNaN on px too<commit_after>/*
    This file is part of rhizi, a collaborative knowledge graph editor.
    Copyright (C) 2014-2015  Rhizi

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published
    by the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

var DEBUG_NODE_POSITION = false;

/**
 * core model module - currently unused
 */
define(['util'], function(util) {
    "use strict";

    /**
     * return a random id
     */
    var random_id,
        Node;

    var random_id__hash = function() {
        return Math.random().toString(36).substring(2, 10);
    };

    var random_id__seq = function () {
        var id = 0;
        function get_next() {
            var next = id;
            id += 1;
            return next;
        }
        return get_next;
    };

    function random_node_name() {
        return random_id__hash();
    }

    function init(config){
        if (config.rand_id_generator === 'hash') {
            random_id = random_id__hash;
        }
        if (config.rand_id_generator === 'seq') {
            random_id = random_id__seq();
        }
    }

    function NodePlain() {
    }
    function NodeWithNaNXTest() {
        Object.defineProperty(this, "x", {
            set: function (new_x) {
                if (isNaN(new_x) && !isNaN(this._x)) {
                    console.log('Node ' + this.id + '.x changed to Nan from ' + this._x);
                }
                this._x = new_x;
            },
            get: function () {
                return this._x;
            }
        });
        Object.defineProperty(this, "px", {
            set: function (new_x) {
                if (isNaN(new_x) && !isNaN(this._x)) {
                    console.log('Node ' + this.id + '.x changed to Nan from ' + this._x);
                }
                this._px = new_x;
            },
            get: function () {
                return this._px;
            }
        });
    }
    if (DEBUG_NODE_POSITION) {
        Node = NodeWithNaNXTest;
    } else {
        Node = NodePlain;
    }
    Node.prototype.equals = function(other_node){
        return this.id === other_node.id;
    };

    function Link() {
    }
    // adapt Link to force_layout create __src,__dst aliases
    Link.prototype.__defineGetter__('source', function(){
        return this.__src;
    });
    Link.prototype.__defineGetter__('target', function(){
        return this.__dst;
    });

    function __set_obj_id(obj, id) {
        Object.defineProperty(obj, "id", {
            value: id,
            enumerable: true,
            writable: false
        });
    }

    /**
     * the most flexible way to create a node: - perform spec field validation -
     * fill-in missing spec fields
     */
    function create_node_from_spec(node_spec) {

        util.assert(undefined !== node_spec.name, 'create_node_from_spec: name missing');

        var ret = new Node();

        if (undefined !== node_spec.id) {
            // reuse id if present
            __set_obj_id(ret, node_spec.id);
        }

        // type
        if (undefined === node_spec.type) {
            console.debug('create_node_from_spec: undefined type, falling back to \'perm\'');
            node_spec.type = 'perm';
        }

        // copy spec
        for (var k in node_spec) {
            if (node_spec.hasOwnProperty(k) && typeof node_spec[k] !== 'function' && k !== 'id') {
                ret[k] = node_spec[k];
            }
        }

        // default values
        ret.status = ret.status || 'unknown';

        return ret;
    }

    /**
     * @param node_spec: id must not be defined
     */
    function create_node__set_random_id(node_spec) {
        if (undefined === node_spec) {
            node_spec = {};
        }

        var ret = create_node_from_spec(node_spec);

        util.assert(undefined === ret.id); // id must not be defined in spec
        __set_obj_id(ret, random_id());

        return ret;
    }

    function create_link__set_random_id(src, dst, link_spec) {
        var ret = create_link_from_spec(src, dst, link_spec);
        __set_obj_id(ret, random_id());
        return ret;
    }

    /**
     * determine if nodes are equal by name
     *
     * @param other_node
     * @returns {Boolean}
     */
    Node.prototype.equal_by_name = function(other) {
        var ret = this.name.toLowerCase() === other.name.toLowerCase();
        if (false === ret) {
            console.debug(this.id + ' != ' + other.id);
        }
        return ret;
    };

    function create_link_from_spec(src, dst, link_spec) {
        var ret = new Link();

        if (undefined !== link_spec.id) {
            // reuse id if present
            __set_obj_id(ret, link_spec.id);
        }

        util.assert(undefined !== src, 'create_link_from_spec: src missing');
        util.assert(undefined !== dst, 'create_link_from_spec: dst missing');
        util.assert(undefined !== src.id, 'create_link_from_spec: src missing id');
        util.assert(undefined !== dst.id, 'create_link_from_spec: dst missing id');
        util.assert(undefined !== link_spec.name, 'create_link_from_spec: name missing, unable to deduce type');

        ret.__src = src;
        ret.__dst = dst;
        ret.__type = link_spec.name;

        if (undefined === link_spec.name){
            console.warn('create_link_from_spec: name: ' + link_spec.name);
            link_spec.name = "";
        }
        ret.name = link_spec.name.trim();
        delete link_spec.name;
        delete link_spec.__src;
        delete link_spec.__dst;

        for (var property in link_spec) {
            if (link_spec.hasOwnProperty(property) && typeof link_spec[property] !== 'function' && property !== 'id') {
                ret[property] = link_spec[property];
            }
        }
        return ret;
    }

    /**
     * determine if links are equal by ID
     *
     * @param other_node
     * @returns {Boolean}
     */
    Link.prototype.equal_by_id = function(other) {
        return this.id.toLowerCase() === other.id.toLowerCase();
    };

    return {
        init : init,
        Node: Node, // allow model adaptation
        Link: Link, // allow model adaptation
        random_node_name : random_node_name,
        create_node_from_spec : create_node_from_spec,
        create_node__set_random_id : create_node__set_random_id,
        create_link_from_spec : create_link_from_spec,
        create_link__set_random_id : create_link__set_random_id,
    };
});
<|endoftext|>