<commit_before>/// <reference path="../lib/jquery-2.1.1.min.js" />
/// <reference path="../lib/qunit-git.js" />
/*
 * This file is part of "ZenQuery", (c) Kenshin Himura, 2013.
 *
 * "ZenQuery" is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * "ZenQuery" is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with "ZenQuery".  If not, see <http://www.gnu.org/licenses/>.
 *
 */

(function (window) {

    //'use strict';

    var ZenQuery,
        config,
        // For IE, to prevent Invalid Calling Object error on toString.call(obj)
        toString = Object.prototype.toString,
        hasOwn = Object.prototype.hasOwnProperty;

    ZenQuery = {

        // Returns the element name found in the zencoding string
        name: function (string) {
            if (arguments.length !== 1) {
                throw new Error('Incorrect Number of Arguments');
            }
            if (!ZenQuery.is('string', string)) {
                throw new Error('Invalid Arguments');
            }
            var matches = config.matches,
                names = string.match(matches.name);
            if (ZenQuery.is('null', names)) {
                return 'div';
            }
            return names.join('');
        },

        // Returns the zencoding string with attributes removed
        // Helpful because data in the attributes might interfere with selection
        // eg: [href="http://thebattosai.in/#"]
        noAttributes: function (string) {
            if (arguments.length !== 1) {
                throw new Error('Incorrect Number of Arguments');
            }
            if (!ZenQuery.is('string', string)) {
                throw new Error('Invalid Arguments');
            }
            var matches = config.matches;
            return string.replace(matches.attributes.all, '');
        },

        // Returns the classes found in the zencoding string as an array
        classes: function (string) {
            if (arguments.length !== 1) {
                throw new Error('Incorrect Number of Arguments');
            }
            if (!ZenQuery.is('string', string)) {
                throw new Error('Invalid Arguments');
            }
            var matches = config.matches,
                classes = ZenQuery.noAttributes(string).match(matches.classes);
            if (ZenQuery.is('null', classes)) {
                return [];
            }
            classes = classes.join(' ').replace(/\./g, '').split(' ');
            return classes;
        },

        // Returns the id string found in the zencoding string
        id: function (string) {
            if (arguments.length !== 1) {
                throw new Error('Incorrect Number of Arguments');
            }
            if (!ZenQuery.is('string', string)) {
                throw new Error('Invalid Arguments');
            }
            var matches = config.matches,
                id = ZenQuery.noAttributes(string).match(matches.id);
            if (ZenQuery.is('null', id)) {
                return '';
            }
            id = id.join(' ').replace('#', '');
            return id;
        },

        // Returns attributes found in the zencoding string as an object with key value pairs
        attributes: function (string) {
            if (arguments.length !== 1) {
                throw new Error('Incorrect Number of Arguments');
            }
            if (!ZenQuery.is('string', string)) {
                throw new Error('Invalid Arguments');
            }
            var matches = config.matches.attributes,
                attributes = string.match(matches.all),
                array = [],
                key = '',
                name = matches.name,
                value = '',
                data = matches.value;
            if (ZenQuery.is('null', attributes)) {
                return {};
            }
            attributes = attributes.join('').replace(/\]\[/g, ' ');
            array = attributes.match(matches.single);
            if (ZenQuery.is('null', array)) {
                return {};
            }
            attributes = {};
            for (var i in array) {
                array[i] = ZenQuery.trim(array[i]);
                key = array[i].match(name);
                if (ZenQuery.is('null', key)) {
                    continue;
                }
                value = array[i].match(data);
                if (ZenQuery.is('null', value)) {
                    value = [''];
                }
                attributes[key.join('')] = value.join('').replace(/^('|")/, '').replace(/('|")$/, '');
            }
            return attributes;
        },

        // Returns an element from a zen coding string
        element: function(string) {
            if (arguments.length !== 1) {
                throw new Error('Incorrect Number of Arguments');
            }
            if (!ZenQuery.is('string', string)) {
                throw new Error('Invalid Arguments');
            }
            var element = {},
                defaults = ZenQuery.config.element;
            element.name = ZenQuery.name(string);
            element.id = ZenQuery.id(string);
            element.classes = ZenQuery.classes(string).split(' ');
            element.attributes = ZenQuery.attributes(string);
            return ZenQuery.extend(true, {}, defaults, element);
        },

        // Returns a dom from a zen coding string
        dom: function(string) {
            if (arguments.length != 1) {
                throw new Error('Incorrect Number of Arguments');
            }
            if (!ZenQuery.is('string', string)) {
                throw new Error('Invalid Arguments');
            }
            // Needs implementation
        },

        // Returns the string representation of html of a dom
        html: function (dom) {
            // Needs implementation
        },

        // Renders a given zen coding string as html
        render: function (string) {
            if (arguments.length !== 1) {
                throw new Error('Incorrect Number of Arguments');
            }
            if (!ZenQuery.is('string', string)) {
                throw new Error('Invalid Arguments');
            }
            return ZenQuery.html(ZenQuery.dom(string));
        },
    };

    // We use the prototype to distinguish between properties that should
    // be exposed as globals (and in exports) and those that shouldn't
    (function () {
        function F() {};

        F.prototype = ZenQuery;
        ZenQuery = new F();

        // Make F QUnit's constructor so that we can add to the prototype later
        ZenQuery.constructor = F;
    }());

    // Check if Object Has Key
    function has (key, object) {
        if (!ZenQuery.is('array', object) && !ZenQuery.is('object', object)) {
            return false;
        }
        return hasOwn.call(object, key);
    };

    // Safe object type checking
    function is (type, obj) {
        return objectType(obj) === type;
    };

    // Object Type
    function objectType (obj) {
        if (typeof obj === "undefined") {
            return "undefined";
        }

        // Consider: typeof null === object
        if (obj === null) {
            return "null";
        }

        var match = toString.call(obj).match(/^\[object\s(.*)\]$/),
            type = match && match[1] || "";

        switch (type) {
            case "Number":
                if (isNaN(obj)) {
                    return "nan";
                }
                return "number";
            case "String":
            case "Boolean":
            case "Array":
            case "Date":
            case "RegExp":
            case "Function":
                return type.toLowerCase();
        }
        if (typeof obj === "object") {
            return "object";
        }
        return undefined;
    };

    // Extend
    function extend() {
        var options, name, src, copy, copyIsArray, clone,
            target = arguments[0] || {},
            i = 1,
            length = arguments.length,
            deep = false;

        // Handle a deep copy situation
        if (typeof target === "boolean") {
            deep = target;

            // skip the boolean and the target
            target = arguments[i] || {};
            i++;
        }

        // Handle case when target is a string or something (possible in deep copy)
        if (typeof target !== "object" && !is('function', target)) {
            target = {};
        }

        // extend ZenQuery itself if only one argument is passed
        if (i === length) {
            target = this;
            i--;
        }

        for (; i < length; i++) {
            // Only deal with non-null/undefined values
            if ((options = arguments[i]) != null) {
                // Extend the base object
                for (name in options) {
                    src = target[name];
                    copy = options[name];

                    // Prevent never-ending loop
                    if (target === copy) {
                        continue;
                    }

                    // Recurse if we're merging plain objects or arrays
                    if (deep && copy && (is('object', copy) || (copyIsArray = is('array', copy)))) {
                        if (copyIsArray) {
                            copyIsArray = false;
                            clone = src && is('array', src) ? src : [];

                        } else {
                            clone = src && is('object', src) ? src : {};
                        }

                        // Never move original objects, clone them
                        target[name] = jQuery.extend(deep, clone, copy);

                        // Don't bring in undefined values
                    } else if (copy !== undefined) {
                        target[name] = copy;
                    }
                }
            }
        }

        // Return the modified object
        return target;
    };

    // Config
    config = {
        // RegEx-es thanks to http://regexpal.com/
        matches: {
            name: /^[a-z-]+/,
            id: /#[a-z-]+/,
            classes: /\.[a-z-]+/g,
            attributes: {
                all: /\[(\s?[a-z-]+(=('.*'|".*"))?){1,}\]/ig,
                single: /\s??[a-z-]+(=('[^']*'|"[^"]*"){1})?\s??/ig,
                name: /^[a-z-]+/,
                value: /'[^']*'|"[^"]*"/,
            },
            trim: /^[\x20\t\r\n\f]+|((?:^|[^\\])(?:\\.)*)[\x20\t\r\n\f]+$/g,
        },
        html: {
            prefix: '',
            suffix: '',
        },
        dom: {
            elements:[],
        },
        element: {
            name: 'div',
            id: '',
            classes: [],
            attributes: {},
            children: [],
        }
    };

    //Add stuff to ZenQuery
    extend(ZenQuery, {

        // Trim
        trim: function (text) {
            return text == null ?
                "" :
                (text + "").replace(config.matches.trim, "");
        },

        // Additions
        extend: extend,
        config: config,
        has: has,
        is: is,
        objectType: objectType,
    });

    // For browser, export only select globals
    if (typeof window !== "undefined" && window != null) {
        (function() {
            var
                // Map over ZenQuery in case of overwrite
                _ZenQuery = window.ZenQuery,

                // Map over the $Z in case of overwrite
                _$Z = window.$Z;

            ZenQuery.noConflict = function(deep) {
                if (window.$Z === ZenQuery) {
                    window.$Z = _$Z;
                }
                if (deep && window.ZenQuery === ZenQuery) {
                    window.ZenQuery = _ZenQuery;
                }
                return ZenQuery;
            };

            window.ZenQuery = window.$Z = ZenQuery;
            return ZenQuery;
        })();
    }

    // Get a reference to the global object, like window in browsers
}((function () {
    return this;
})()));<commit_msg>Corrected element() to adjust for change in classes()<commit_after>/// <reference path="../lib/jquery-2.1.1.min.js" />
/// <reference path="../lib/qunit-git.js" />
/*
 * This file is part of "ZenQuery", (c) Kenshin Himura, 2013.
 *
 * "ZenQuery" is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * "ZenQuery" is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with "ZenQuery".  If not, see <http://www.gnu.org/licenses/>.
 *
 */

(function (window) {

    //'use strict';

    var ZenQuery,
        config,
        // For IE, to prevent Invalid Calling Object error on toString.call(obj)
        toString = Object.prototype.toString,
        hasOwn = Object.prototype.hasOwnProperty;

    ZenQuery = {

        // Returns the element name found in the zencoding string
        name: function (string) {
            if (arguments.length !== 1) {
                throw new Error('Incorrect Number of Arguments');
            }
            if (!ZenQuery.is('string', string)) {
                throw new Error('Invalid Arguments');
            }
            var matches = config.matches,
                names = string.match(matches.name);
            if (ZenQuery.is('null', names)) {
                return 'div';
            }
            return names.join('');
        },

        // Returns the zencoding string with attributes removed
        // Helpful because data in the attributes might interfere with selection
        // eg: [href="http://thebattosai.in/#"]
        noAttributes: function (string) {
            if (arguments.length !== 1) {
                throw new Error('Incorrect Number of Arguments');
            }
            if (!ZenQuery.is('string', string)) {
                throw new Error('Invalid Arguments');
            }
            var matches = config.matches;
            return string.replace(matches.attributes.all, '');
        },

        // Returns the classes found in the zencoding string as an array
        classes: function (string) {
            if (arguments.length !== 1) {
                throw new Error('Incorrect Number of Arguments');
            }
            if (!ZenQuery.is('string', string)) {
                throw new Error('Invalid Arguments');
            }
            var matches = config.matches,
                classes = ZenQuery.noAttributes(string).match(matches.classes);
            if (ZenQuery.is('null', classes)) {
                return [];
            }
            classes = classes.join(' ').replace(/\./g, '').split(' ');
            return classes;
        },

        // Returns the id string found in the zencoding string
        id: function (string) {
            if (arguments.length !== 1) {
                throw new Error('Incorrect Number of Arguments');
            }
            if (!ZenQuery.is('string', string)) {
                throw new Error('Invalid Arguments');
            }
            var matches = config.matches,
                id = ZenQuery.noAttributes(string).match(matches.id);
            if (ZenQuery.is('null', id)) {
                return '';
            }
            id = id.join(' ').replace('#', '');
            return id;
        },

        // Returns attributes found in the zencoding string as an object with key value pairs
        attributes: function (string) {
            if (arguments.length !== 1) {
                throw new Error('Incorrect Number of Arguments');
            }
            if (!ZenQuery.is('string', string)) {
                throw new Error('Invalid Arguments');
            }
            var matches = config.matches.attributes,
                attributes = string.match(matches.all),
                array = [],
                key = '',
                name = matches.name,
                value = '',
                data = matches.value;
            if (ZenQuery.is('null', attributes)) {
                return {};
            }
            attributes = attributes.join('').replace(/\]\[/g, ' ');
            array = attributes.match(matches.single);
            if (ZenQuery.is('null', array)) {
                return {};
            }
            attributes = {};
            for (var i in array) {
                array[i] = ZenQuery.trim(array[i]);
                key = array[i].match(name);
                if (ZenQuery.is('null', key)) {
                    continue;
                }
                value = array[i].match(data);
                if (ZenQuery.is('null', value)) {
                    value = [''];
                }
                attributes[key.join('')] = value.join('').replace(/^('|")/, '').replace(/('|")$/, '');
            }
            return attributes;
        },

        // Returns an element from a zen coding string
        element: function(string) {
            if (arguments.length !== 1) {
                throw new Error('Incorrect Number of Arguments');
            }
            if (!ZenQuery.is('string', string)) {
                throw new Error('Invalid Arguments');
            }
            var element = {},
                defaults = ZenQuery.config.element;
            element.name = ZenQuery.name(string);
            element.id = ZenQuery.id(string);
            element.classes = ZenQuery.classes(string);
            element.attributes = ZenQuery.attributes(string);
            return ZenQuery.extend(true, {}, defaults, element);
        },

        // Returns a dom from a zen coding string
        dom: function(string) {
            if (arguments.length != 1) {
                throw new Error('Incorrect Number of Arguments');
            }
            if (!ZenQuery.is('string', string)) {
                throw new Error('Invalid Arguments');
            }
            // Needs implementation
        },

        // Returns the string representation of html of a dom
        html: function (dom) {
            // Needs implementation
        },

        // Renders a given zen coding string as html
        render: function (string) {
            if (arguments.length !== 1) {
                throw new Error('Incorrect Number of Arguments');
            }
            if (!ZenQuery.is('string', string)) {
                throw new Error('Invalid Arguments');
            }
            return ZenQuery.html(ZenQuery.dom(string));
        },
    };

    // We use the prototype to distinguish between properties that should
    // be exposed as globals (and in exports) and those that shouldn't
    (function () {
        function F() {};

        F.prototype = ZenQuery;
        ZenQuery = new F();

        // Make F QUnit's constructor so that we can add to the prototype later
        ZenQuery.constructor = F;
    }());

    // Check if Object Has Key
    function has (key, object) {
        if (!ZenQuery.is('array', object) && !ZenQuery.is('object', object)) {
            return false;
        }
        return hasOwn.call(object, key);
    };

    // Safe object type checking
    function is (type, obj) {
        return objectType(obj) === type;
    };

    // Object Type
    function objectType (obj) {
        if (typeof obj === "undefined") {
            return "undefined";
        }

        // Consider: typeof null === object
        if (obj === null) {
            return "null";
        }

        var match = toString.call(obj).match(/^\[object\s(.*)\]$/),
            type = match && match[1] || "";

        switch (type) {
            case "Number":
                if (isNaN(obj)) {
                    return "nan";
                }
                return "number";
            case "String":
            case "Boolean":
            case "Array":
            case "Date":
            case "RegExp":
            case "Function":
                return type.toLowerCase();
        }
        if (typeof obj === "object") {
            return "object";
        }
        return undefined;
    };

    // Extend
    function extend() {
        var options, name, src, copy, copyIsArray, clone,
            target = arguments[0] || {},
            i = 1,
            length = arguments.length,
            deep = false;

        // Handle a deep copy situation
        if (typeof target === "boolean") {
            deep = target;

            // skip the boolean and the target
            target = arguments[i] || {};
            i++;
        }

        // Handle case when target is a string or something (possible in deep copy)
        if (typeof target !== "object" && !is('function', target)) {
            target = {};
        }

        // extend ZenQuery itself if only one argument is passed
        if (i === length) {
            target = this;
            i--;
        }

        for (; i < length; i++) {
            // Only deal with non-null/undefined values
            if ((options = arguments[i]) != null) {
                // Extend the base object
                for (name in options) {
                    src = target[name];
                    copy = options[name];

                    // Prevent never-ending loop
                    if (target === copy) {
                        continue;
                    }

                    // Recurse if we're merging plain objects or arrays
                    if (deep && copy && (is('object', copy) || (copyIsArray = is('array', copy)))) {
                        if (copyIsArray) {
                            copyIsArray = false;
                            clone = src && is('array', src) ? src : [];

                        } else {
                            clone = src && is('object', src) ? src : {};
                        }

                        // Never move original objects, clone them
                        target[name] = jQuery.extend(deep, clone, copy);

                        // Don't bring in undefined values
                    } else if (copy !== undefined) {
                        target[name] = copy;
                    }
                }
            }
        }

        // Return the modified object
        return target;
    };

    // Config
    config = {
        // RegEx-es thanks to http://regexpal.com/
        matches: {
            name: /^[a-z-]+/,
            id: /#[a-z-]+/,
            classes: /\.[a-z-]+/g,
            attributes: {
                all: /\[(\s?[a-z-]+(=('.*'|".*"))?){1,}\]/ig,
                single: /\s??[a-z-]+(=('[^']*'|"[^"]*"){1})?\s??/ig,
                name: /^[a-z-]+/,
                value: /'[^']*'|"[^"]*"/,
            },
            trim: /^[\x20\t\r\n\f]+|((?:^|[^\\])(?:\\.)*)[\x20\t\r\n\f]+$/g,
        },
        html: {
            prefix: '',
            suffix: '',
        },
        dom: {
            elements:[],
        },
        element: {
            name: 'div',
            id: '',
            classes: [],
            attributes: {},
            children: [],
        }
    };

    //Add stuff to ZenQuery
    extend(ZenQuery, {

        // Trim
        trim: function (text) {
            return text == null ?
                "" :
                (text + "").replace(config.matches.trim, "");
        },

        // Additions
        extend: extend,
        config: config,
        has: has,
        is: is,
        objectType: objectType,
    });

    // For browser, export only select globals
    if (typeof window !== "undefined" && window != null) {
        (function() {
            var
                // Map over ZenQuery in case of overwrite
                _ZenQuery = window.ZenQuery,

                // Map over the $Z in case of overwrite
                _$Z = window.$Z;

            ZenQuery.noConflict = function(deep) {
                if (window.$Z === ZenQuery) {
                    window.$Z = _$Z;
                }
                if (deep && window.ZenQuery === ZenQuery) {
                    window.ZenQuery = _ZenQuery;
                }
                return ZenQuery;
            };

            window.ZenQuery = window.$Z = ZenQuery;
            return ZenQuery;
        })();
    }

    // Get a reference to the global object, like window in browsers
}((function () {
    return this;
})()));<|endoftext|>


<commit_before>'use strict';

/**
 * POSTs newswire stories to Slack via an incoming webhook.
 *
 * @author Ændrew Rininsland      <aendrew@aendrew.com>
 * @since  30 Aug. 2015
 */

require('dotenv').load();

// module dependencies
var cheerio = require('cheerio');
var scale = require('d3-scale');
var format = require('util').format;
var request = require('request');
var environment = process.env.NODE_ENV ? process.env.NODE_ENV : 'testing';

/**
 * The handler function.
 *
 * @param {object}  event       The data regarding the event.
 * @param {object}  context     The AWS Lambda execution context.
 */
exports.handler = function(event, context) {
  /**
   * Returns a string value for priorities
   * @param  {integer} priority NewsML priority
   * @return {string}          Parsed priority
   */
  function getPriority(priority) {
    switch (parseInt(priority)) {
      case 1:
        return ':rotating_light: CRAZY-HIGH PRIORITY :rotating_light:';
      case 2:
        return ':rotating_light: Super high priority :rotating_light:';
      case 3:
        return 'High priority';
      case 4:
        return 'Medium priority';
      case 5:
        return 'Medium-low priority';
      case 6:
        return 'Low priority';
      case 7:
        return 'Lower priority';
      case 8:
        return 'Lowest priority';
      default:
        return 'Priority not set';

    }
  }

  var color = scale.linear().domain([8, 3, 1]).range(['green', 'yellow', 'red']);

  /**
   * Parses an old NewsML XML file (Press Association)
   */
  function parseArticle(article, type, priority) {
    var bodyCopy,
        excerpt,
        byline,
        link,
        newsitem,
        body,
        slugline,
        headline,
        bodyCopyString;

    article = $(article); // wrap in Cheerio

    if (type === 'PA') {
      headline = article.find('HeadLine').text();
      body = article.find('body').find('p');
      excerpt = body.eq(0).text();
      byline = article.find('ByLine').text();
      slugline = article.find('SlugLine').text();
      link = 'https://www.pressassociation.com/';
      newsitem = article.find('NewsItemId').text();
    } else if (type === 'Reuters') {
      headline = article.find('headline').text();
      body = article.find('body').children();
      excerpt = body.eq(0).text();
      byline = 'Thomson Reuters'; // TODO write some regex to extract reporter's name.
      link = 'http://about.reuters.com/';
      slugline = article.find('slugline').text();
      newsitem = article.attr('guid').split(':')[2].replace('newsml_', '');
    }

    bodyCopy = [];
    body.each(function(i, node){
      bodyCopy.push($(node).text());
    });
    bodyCopyString = bodyCopy.join('\n');

    if (byline) {
      byline = byline.replace('By ', '');
    }

    return {
      fallback: format('%s [%d] -- %s', headline, priority, excerpt),
      color: color(priority),
      title: format('%s', headline),
      pretext: undefined, //priority <= (process.env.ALERT_PRIORITY || 3) ? '@channel' : '', // Alert everyone for priorities above 3 (default)
      text: bodyCopyString,
      author_name: byline,
      author_link: link,
      fields: [
        {
          title: 'slugline',
          value: slugline,
          short: true
        },
        {
          title: 'Methode Name',
          value: undefined,
          short: true
        },
        {
          title: 'News Item ID',
          value: newsitem,
          short: true
        },
        {
          title: 'Priority',
          value: getPriority(priority),
          short: true
        }
      ]
    };
  }

  var $ = cheerio.load(event.body, {xmlMode: true});
  var type;
  var payload = {
    text: String(),
    attachments: []
  };

  var articles, priority, methode;
  var root = $.root().children('newsMessage, NewsML').get(0);
  var xml = $(root);

  if (root.tagName === 'newsMessage') { // Reuters, expectedly, uses the modern version.
    type = 'Reuters';
    articles = xml.find('newsItem');
    priority = xml.find('priority').text();
    methode = xml.find('Property[FormalName="NIMethodeName"]').attr('Value');
  } else if (root.tagName === 'NewsML') { // PA, regrettably, does not.
    type = 'PA';
    articles = xml.find('NewsItem');
    priority = xml.find('Priority');
    priority = priority.attr('FormalName');
    methode = xml.find('Property[FormalName="NIMethodeName"]').attr('Value');
  } else {
    throw 'Not valid NewsML';
  }

  payload.type = type;

  if (articles.length > 0) {
    articles.each(function(i, article){
      var parsed = parseArticle(article, type, priority);
      parsed.fields[1].value = methode;
      payload.attachments.push(parsed);
    });
  }

  // Send to Slack
  if (environment === 'production' && process.env.hasOwnProperty('SLACK_WEBHOOK')) {
    request.post({uri: process.env.SLACK_WEBHOOK, method: 'POST', json: payload}, function (error, response, body) {
      context.succeed(body);
    });
  } else {
    context.succeed(payload);
  }
};
<commit_msg>Improving inline docs<commit_after>'use strict';

/**
 * POSTs newswire stories to Slack via an incoming webhook.
 *
 * @author Ændrew Rininsland      <aendrew@aendrew.com>
 * @since  30 Aug. 2015
 */

require('dotenv').load();

// module dependencies
var cheerio = require('cheerio');
var scale = require('d3-scale');
var format = require('util').format;
var request = require('request');
var environment = process.env.NODE_ENV ? process.env.NODE_ENV : 'testing';

/**
 * The handler function.
 *
 * @param {object}  event       The data regarding the event.
 * @param {object}  context     The AWS Lambda execution context.
 */
exports.handler = function(event, context) {
  /**
   * Returns a string value for priorities
   * @param  {integer} priority NewsML priority
   * @return {string}          Parsed priority
   */
  function getPriority(priority) {
    switch (parseInt(priority)) {
      case 1:
        return ':rotating_light: CRAZY-HIGH PRIORITY :rotating_light:';
      case 2:
        return ':rotating_light: Super high priority :rotating_light:';
      case 3:
        return 'High priority';
      case 4:
        return 'Medium priority';
      case 5:
        return 'Medium-low priority';
      case 6:
        return 'Low priority';
      case 7:
        return 'Lower priority';
      case 8:
        return 'Lowest priority';
      default:
        return 'Priority not set';

    }
  }

  /**
   * Returns a colour when a number before 1 and 8 is given.
   * @param  {integer}    The priority you wish to get a colour for
   * @return {string}     Hex colour code
   */
  var color = scale.linear().domain([8, 3, 1]).range(['green', 'yellow', 'red']);

  /**
   * Parses a NewsML XML file
   */
  function parseArticle(article, type, priority) {
    var bodyCopy,
        excerpt,
        byline,
        link,
        newsitem,
        body,
        slugline,
        headline,
        bodyCopyString;

    article = $(article); // wrap in Cheerio

    if (type === 'PA') {
      headline = article.find('HeadLine').text();
      body = article.find('body').find('p');
      excerpt = body.eq(0).text();
      byline = article.find('ByLine').text();
      slugline = article.find('SlugLine').text();
      link = 'https://www.pressassociation.com/';
      newsitem = article.find('NewsItemId').text();
    } else if (type === 'Reuters') {
      headline = article.find('headline').text();
      body = article.find('body').children();
      excerpt = body.eq(0).text();
      byline = 'Thomson Reuters'; // TODO write some regex to extract reporter's name.
      link = 'http://about.reuters.com/';
      slugline = article.find('slugline').text();
      newsitem = article.attr('guid').split(':')[2].replace('newsml_', '');
    }

    bodyCopy = [];
    body.each(function(i, node){
      bodyCopy.push($(node).text());
    });
    bodyCopyString = bodyCopy.join('\n');

    if (byline) {
      byline = byline.replace('By ', '');
    }

    // Return a Slack incoming-webhook attachment
    return {
      fallback: format('%s [%d] -- %s', headline, priority, excerpt),
      color: color(priority),
      title: format('%s', headline),
      pretext: undefined, //priority <= (process.env.ALERT_PRIORITY || 3) ? '@channel' : '', // Alert everyone for priorities above 3 (default)
      text: bodyCopyString,
      author_name: byline,
      author_link: link,
      fields: [
        {
          title: 'slugline',
          value: slugline,
          short: true
        },
        {
          title: 'Methode Name',
          value: undefined,
          short: true
        },
        {
          title: 'News Item ID',
          value: newsitem,
          short: true
        },
        {
          title: 'Priority',
          value: getPriority(priority),
          short: true
        }
      ]
    };
  }

  // Main program begins
  var $ = cheerio.load(event.body, {xmlMode: true});
  var type;
  var payload = {
    text: String(),
    attachments: []
  };

  var articles, priority, methode;
  var root = $.root().children('newsMessage, NewsML').get(0);
  var xml = $(root);

  if (root.tagName === 'newsMessage') { // Reuters, expectedly, uses the modern version.
    type = 'Reuters';
    articles = xml.find('newsItem');
    priority = xml.find('priority').text();
    methode = xml.find('Property[FormalName="NIMethodeName"]').attr('Value');
  } else if (root.tagName === 'NewsML') { // PA, regrettably, does not.
    type = 'PA';
    articles = xml.find('NewsItem');
    priority = xml.find('Priority');
    priority = priority.attr('FormalName');
    methode = xml.find('Property[FormalName="NIMethodeName"]').attr('Value');
  } else {
    throw 'Not valid NewsML';
  }

  payload.type = type;

  if (articles.length > 0) {
    articles.each(function(i, article){
      var parsed = parseArticle(article, type, priority);
      parsed.fields[1].value = methode;
      payload.attachments.push(parsed);
    });
  }

  // Send to Slack
  if (environment === 'production' && process.env.hasOwnProperty('SLACK_WEBHOOK')) {
    request.post({uri: process.env.SLACK_WEBHOOK, method: 'POST', json: payload}, function (error, response, body) {
      context.succeed(body);
    });
  } else {
    context.succeed(payload);
  }
};
<|endoftext|>


<commit_before>var Botkit = require('botkit');
var controller = Botkit.slackbot();
var http = require('http');

// Env
require('dotenv').load();

var bot = controller.spawn({
  token: process.env.BOT_TOKEN
});

var heyyou = {
  clientId: process.env.CLIENT_ID, // TEMP This should be dynamic
  api: {
    host: process.env.API_HOST,
    pathPrefix: process.env.API_PATH_PREFIX
  }
};

bot.startRTM(function(err,bot,payload) {
  if (err) {
    throw new Error('Could not connect to Slack');
  }
});

// function you can use:
function getValueFromPattern(str) {
  return str.split('=')[1];
}

function getVenueByPostcode(postcode, callback) {
  // API options
  var options = {
    host: heyyou.api.host,
    headers: {
      'x-btq-client-id': heyyou.clientId
    },
    path: heyyou.api.pathPrefix + '/venues?postcode=' + postcode + '&limit=20',
    method: 'GET'
  };

  // API call
  http.request(options, function(res) {
    var body = '';

    res.on('data', function (chunk) {
      // Get the response data
      body += chunk;
    }).on('end', function(){
      var response = JSON.parse(body);

      callback('List of Venues around ' + postcode + ' are: ' + buildVenueList(response.venues));
    });
  }).on('error', function(e) {
    console.log('problem with request: ' + e.message);
  }).end();
}

function buildVenueList(venues) {
  var result = '';

  venues.forEach(function(venue, index) {
    result += '\n' + venue.id + ' ' + venue.name + ' (Status: ' + venue.status + ')';
  });

  return result;
}

function buildMenu(menu) {
  var categories = menu.categories;
  var result = '';

  categories.forEach(function(category, index) {
    result += '\n*--- ' + category.name.trim() + ' ---*';

    category.items.forEach(function(item, index) {
      result += '\n' + item.id + ' - ' + item.name.trim() + ' ' + item.price.trim();
    });
  });

  return result;
}

function getMenuByVenueId(venueId, callback) {
  // API options
  var options = {
    host: heyyou.api.host,
    headers: {
      'x-btq-client-id': heyyou.clientId
    },
    path: heyyou.api.pathPrefix + '/venues/' + venueId + '/menus',
    method: 'GET'
  };

  // API call
  http.request(options, function(res) {
    var body = '';

    res.on('data', function (chunk) {
      // Get the response data
      body += chunk;
    }).on('end', function(){
      var response = JSON.parse(body);

      callback(response);
    });
  }).on('error', function(e) {
    console.log('problem with request: ' + e.message);
  }).end();
}

function getMenuMessage(venueId, result) {
  return 'Menu for ' + venueId + ' is: ' + buildMenu(result);
}

function order(venueId, productId, callback) {
  // API options
  var postData = {
    "nonce": process.env.NONCE,
    "venueId": parseInt(venueId),
    "serviceType": "takeaway",
    "items": [{"id": parseInt(productId)}],
    "orderNote": "Submitted with HEYYOU Slack BOT"
  };

  var options = {
    host: heyyou.api.host,
    headers: {
      'x-btq-client-id': heyyou.clientId,
      Authorization: process.env.AUTH_CODE,
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    path: heyyou.api.pathPrefix + '/orders/submit',
    method: 'POST'
  };

  // API call
  var req = http.request(options, function(res) {
    var body = '';

    res.on('data', function (chunk) {
      // Get the response data
      body += chunk;
    }).on('end', function(){
      var response = JSON.parse(body);
      callback(response);
    });
  });

  req.on('error', function(e) {
    console.log('problem with request: ' + e.message);
  })

  postData = JSON.stringify(postData, null, 4);

  req.write(postData);
  req.end();
}

// controller.hears(["help","^pattern$"],["direct_message","direct_mention","mention","ambient"],function(bot,message, a, b, c) {
//   bot.reply(message,'Hi');
//   bot.reply(message,'Do you want to order? Ask me `heyyou postcode=` followed by your POSTCODE number to get a list of venues near you');
//   bot.reply(message,'Do you want to see their menus? Ask me `heyyou menu id=` followed by the Venue ID number, for example `heyyou menu id=16`');
// });

// controller.hears(["postcode=","^pattern$"],["direct_message","direct_mention","mention","ambient"],function(bot,message) {
//   var postcode = getValueFromPattern(message.text);

//   if (postcode) {
//     getVenueByPostcode(postcode, function(result){
//       bot.reply(message, result);
//     });
//   } else {
//     bot.reply(message, 'Oops - Make sure to add the correct postcode, for example for 2000 type `heyyou postcode=2000`');
//   }
// });

// controller.hears(["menu id=","^pattern$"],["direct_message","direct_mention","mention","ambient"],function(bot,message) {
//   var venueId = getValueFromPattern(message.text);

//   if (venueId) {
//     getMenuByVenueId(venueId, function(result){
//       bot.reply(message, getMenuMessage(venueId, result));
//     });
//   } else {
//     bot.reply(message, 'Oops - Make sure to add the Venue ID, for example `heyyou menu id=16`');
//   }
// });

controller.hears(['hi', 'hello', 'hey', '^pattern$'],['direct_mention', 'mention'],function(bot,message) {
  bot.reply(message, '\nHi, I\'m the HeyYouBOT\n\nTo get started type *order start*\n');
});

controller.hears(['order start'],['ambient'],function(bot,message) {
  bot.startConversation(message, askWhen);
});

askWhen = function(response, convo) {
  convo.ask("\n_*When* would you like to order?_ For example type *now* or *in 10 minutes*\n", function(response, convo) {
    askVenueIdPrePostcode(response, convo);
    convo.next();
  }, {key:'when'});
}

askVenueIdPrePostcode = function(response, convo) {
  convo.ask("\n_Do you already know the *ID* of the *Venue* you want to order from?_\n", [
    {
      pattern: bot.utterances.yes,
      callback: function(response,convo) {
        askVenueId(response, convo);
        convo.next();
      }
    },
    {
      pattern: bot.utterances.no,
      callback: function(response,convo) {
        // No
        askPostcode(response, convo);
        convo.next();
      }
    },
    {
      default: true,
      callback: function(response,convo) {
        // just repeat the question
        convo.repeat();
        convo.sayFirst('Sorry, I didn\' get that. Please say *yes* or *no* :smile:');
        convo.next();
      }
    }
  ]);
}

askPostcode = function(response, convo) {
  convo.ask("\n_What is your *Postcode*?_\n", function(response, convo) {
    getVenueByPostcode(response.text, function(result){
      convo.sayFirst(result);
    });
    setTimeout(function () {
      askVenueId(response, convo);
      convo.next();
    }, 2000);
  }, {key:'postcode'});
}

askVenueId = function(response, convo) {
  convo.ask("\n_What *Venue ID* do you want to order from?_\n", function(response, convo) {
    // Print Venue's Menu
    getMenuByVenueId(response.text, function(result){
      convo.sayFirst(getMenuMessage(response.text, result));
    });
    setTimeout(function () {
      askProductId(response, convo);
      convo.next();
    }, 2000);
  }, {key:'venueId'});
}

askProductId = function(response, convo) {
  convo.ask("\n\n_What *Product ID* do you want to order?_\n", function(response, convo) {
    var venueId = convo.extractResponse('venueId');
    var productId = convo.extractResponse('productId');
    var when = convo.extractResponse('when');

    //var venue = getVenueByID(convo.extractResponse('venueId'));
    var venueName = 'Venue ID ' + venueId;
    convo.say("\nI'm ordering " + productId + ", " + when + ", from " + venueName);

    // Order
    order(venueId, productId, function(result){
      convo.say('\nYou order has been successfully submitted and it will be ready soon. You order ID is ' + result.orderId);
      convo.say('\nBye :wave:');
      convo.next();
    });
  }, {key:'productId'});
}
<commit_msg>Small tweaks to product id<commit_after>var Botkit = require('botkit');
var controller = Botkit.slackbot();
var http = require('http');

// Env
require('dotenv').load();

var bot = controller.spawn({
  token: process.env.BOT_TOKEN
});

var heyyou = {
  clientId: process.env.CLIENT_ID, // TEMP This should be dynamic
  api: {
    host: process.env.API_HOST,
    pathPrefix: process.env.API_PATH_PREFIX
  }
};

bot.startRTM(function(err,bot,payload) {
  if (err) {
    throw new Error('Could not connect to Slack');
  }
});

// function you can use:
function getValueFromPattern(str) {
  return str.split('=')[1];
}

function getVenueByPostcode(postcode, callback) {
  // API options
  var options = {
    host: heyyou.api.host,
    headers: {
      'x-btq-client-id': heyyou.clientId
    },
    path: heyyou.api.pathPrefix + '/venues?postcode=' + postcode + '&limit=20',
    method: 'GET'
  };

  // API call
  http.request(options, function(res) {
    var body = '';

    res.on('data', function (chunk) {
      // Get the response data
      body += chunk;
    }).on('end', function(){
      var response = JSON.parse(body);

      callback('List of Venues around ' + postcode + ' are: ' + buildVenueList(response.venues));
    });
  }).on('error', function(e) {
    console.log('problem with request: ' + e.message);
  }).end();
}

function buildVenueList(venues) {
  var result = '';

  venues.forEach(function(venue, index) {
    result += '\n' + venue.id + ' ' + venue.name + ' (Status: ' + venue.status + ')';
  });

  return result;
}

function buildMenu(menu) {
  var categories = menu.categories;
  var result = '';

  categories.forEach(function(category, index) {
    result += '\n*--- ' + category.name.trim() + ' ---*';

    category.items.forEach(function(item, index) {
      result += '\n' + item.id + ' - ' + item.name.trim() + ' ' + item.price.trim();
    });
  });

  return result;
}

function getMenuByVenueId(venueId, callback) {
  // API options
  var options = {
    host: heyyou.api.host,
    headers: {
      'x-btq-client-id': heyyou.clientId
    },
    path: heyyou.api.pathPrefix + '/venues/' + venueId + '/menus',
    method: 'GET'
  };

  // API call
  http.request(options, function(res) {
    var body = '';

    res.on('data', function (chunk) {
      // Get the response data
      body += chunk;
    }).on('end', function(){
      var response = JSON.parse(body);

      callback(response);
    });
  }).on('error', function(e) {
    console.log('problem with request: ' + e.message);
  }).end();
}

function getMenuMessage(venueId, result) {
  return 'Menu for ' + venueId + ' is: ' + buildMenu(result);
}

function order(venueId, productId, callback) {
  // API options
  var postData = {
    "nonce": process.env.NONCE,
    "venueId": parseInt(venueId),
    "serviceType": "takeaway",
    "items": [{"id": parseInt(productId)}],
    "orderNote": "Submitted with HEYYOU Slack BOT"
  };

  var options = {
    host: heyyou.api.host,
    headers: {
      'x-btq-client-id': heyyou.clientId,
      Authorization: process.env.AUTH_CODE,
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    path: heyyou.api.pathPrefix + '/orders/submit',
    method: 'POST'
  };

  // API call
  var req = http.request(options, function(res) {
    var body = '';

    res.on('data', function (chunk) {
      // Get the response data
      body += chunk;
    }).on('end', function(){
      var response = JSON.parse(body);
      callback(response);
    });
  });

  req.on('error', function(e) {
    console.log('problem with request: ' + e.message);
  })

  postData = JSON.stringify(postData, null, 4);

  req.write(postData);
  req.end();
}

// controller.hears(["help","^pattern$"],["direct_message","direct_mention","mention","ambient"],function(bot,message, a, b, c) {
//   bot.reply(message,'Hi');
//   bot.reply(message,'Do you want to order? Ask me `heyyou postcode=` followed by your POSTCODE number to get a list of venues near you');
//   bot.reply(message,'Do you want to see their menus? Ask me `heyyou menu id=` followed by the Venue ID number, for example `heyyou menu id=16`');
// });

// controller.hears(["postcode=","^pattern$"],["direct_message","direct_mention","mention","ambient"],function(bot,message) {
//   var postcode = getValueFromPattern(message.text);

//   if (postcode) {
//     getVenueByPostcode(postcode, function(result){
//       bot.reply(message, result);
//     });
//   } else {
//     bot.reply(message, 'Oops - Make sure to add the correct postcode, for example for 2000 type `heyyou postcode=2000`');
//   }
// });

// controller.hears(["menu id=","^pattern$"],["direct_message","direct_mention","mention","ambient"],function(bot,message) {
//   var venueId = getValueFromPattern(message.text);

//   if (venueId) {
//     getMenuByVenueId(venueId, function(result){
//       bot.reply(message, getMenuMessage(venueId, result));
//     });
//   } else {
//     bot.reply(message, 'Oops - Make sure to add the Venue ID, for example `heyyou menu id=16`');
//   }
// });

controller.hears(['hi', 'hello', 'hey', '^pattern$'],['direct_mention', 'mention'],function(bot,message) {
  bot.reply(message, '\nHi, I\'m the HeyYouBOT\n\nTo get started type *order start*\n');
});

controller.hears(['order start'],['ambient'],function(bot,message) {
  bot.startConversation(message, askWhen);
});

askWhen = function(response, convo) {
  convo.ask("\n_*When* would you like to order?_ For example type *now* or *in 10 minutes*\n", function(response, convo) {
    askVenueIdPrePostcode(response, convo);
    convo.next();
  }, {key:'when'});
}

askVenueIdPrePostcode = function(response, convo) {
  convo.ask("\n_Do you already know the *ID* of the *Venue* you want to order from?_\n", [
    {
      pattern: bot.utterances.yes,
      callback: function(response,convo) {
        askVenueId(response, convo);
        convo.next();
      }
    },
    {
      pattern: bot.utterances.no,
      callback: function(response,convo) {
        // No
        askPostcode(response, convo);
        convo.next();
      }
    },
    {
      default: true,
      callback: function(response,convo) {
        // just repeat the question
        convo.repeat();
        convo.sayFirst('Sorry, I didn\' get that. Please say *yes* or *no* :smile:');
        convo.next();
      }
    }
  ]);
}

askPostcode = function(response, convo) {
  convo.ask("\n_What is your *Postcode*?_\n", function(response, convo) {
    getVenueByPostcode(response.text, function(result){
      convo.sayFirst(result);
    });
    setTimeout(function () {
      askVenueId(response, convo);
      convo.next();
    }, 2000);
  }, {key:'postcode'});
}

askVenueId = function(response, convo) {
  convo.ask("\n_What *Venue ID* do you want to order from?_\n", function(response, convo) {
    // Print Venue's Menu
    getMenuByVenueId(response.text, function(result){
      convo.sayFirst(getMenuMessage(response.text, result));
    });
    setTimeout(function () {
      askProductId(response, convo);
      convo.next();
    }, 2000);
  }, {key:'venueId'});
}

askProductId = function(response, convo) {
  convo.ask("\n\n_What *Product ID* do you want to order?_\n", function(response, convo) {
    var venueId = convo.extractResponse('venueId');
    var productId = convo.extractResponse('productId');
    var when = convo.extractResponse('when');

    //var venue = getVenueByID(convo.extractResponse('venueId'));
    var venueName = 'Venue ID ' + venueId;
    convo.say("\nI'm ordering Product ID " + productId + ", " + when + ", from " + venueName);

    // Order
    order(venueId, productId, function(result){
      convo.say('\nYou order has been successfully submitted and it will be ready soon.');
      convo.say('\nYou order ID is ' + result.orderId);
      convo.say('\nBye :wave:');
      convo.next();
    });
  }, {key:'productId'});
}
<|endoftext|>


<commit_before>var Dimension = Backbone.Model.extend({
    initialize: function(args) {
        this.key = args.key;
    },

    url: function() {
        return Saiku.session.username + "/discover/"
            + this.key + "/dimensions";
    },

    parse: function(response) {
        this.template = _.template("")({
            dimensions: response
        });

        return response;
    }
});

var Measure = Backbone.Model.extend({
    initialize: function(args) {
        this.key = args.key;
    },

    url: function() {
        return Saiku.session.username + "/discover/"
            + this.key + "/measures";
    },

    parse: function(response) {
        this.template = _.template("")({
            dimensions: response
        });

        return response;
    }
});

var Cube = Backbone.Model.extend({
    initialize: function(args) {
        this.key = args.key;
        this.dimensions = new Dimension({ key: this.key });
        this.measures = new Measure({ key: this.key });
    },

    fetch: function() {
        this.fetched = true;
        this.dimensions.fetch();
        this.measures.fetch();
    }
});<commit_msg>Removed function call<commit_after>var Dimension = Backbone.Model.extend({
    initialize: function(args) {
        this.url = Saiku.session.username + "/discover/"
            + args.key + "/dimensions";
    },

    parse: function(response) {
        this.template = _.template("")({
            dimensions: response
        });

        return response;
    }
});

var Measure = Backbone.Model.extend({
    initialize: function(args) {
        this.url = Saiku.session.username + "/discover/"
            + args.key + "/measures";
    },

    parse: function(response) {
        this.template = _.template("")({
            dimensions: response
        });

        return response;
    }
});

var Cube = Backbone.Model.extend({
    initialize: function(args) {
        this.key = args.key;
        this.dimensions = new Dimension({ key: this.key });
        this.measures = new Measure({ key: this.key });
    },

    fetch: function() {
        this.fetched = true;
        this.dimensions.fetch();
        this.measures.fetch();
    }
});<|endoftext|>


<commit_before>/*
*  Directives
*/
// Sidenav
angular.module('app.directives', [])
    .directive('siteSidenav', function () {
        var SidenavController = function ($scope, $mdSidenav) {
            console.info('Successfully loaded!');

            $scope.main = [
                { url: 'https://chan4077.github.io', title: 'Main', icon: 'home' },
                { url: 'https://chan4077.github.io/about', title: 'About Me', icon: 'account-box' },
                { url: 'https://chan4077.github.io/blog', title: 'Blog', icon: 'blogger' },
                { url: 'https://chan4077.github.io/media', title: 'Social Media', icon: 'google-plus' },
                { url: 'https://chan4077.github.io/angular', title: 'Angular', icon: 'angular' },
            ];
            $scope.other = [
                { url: 'https://github.com/Chan4077/chan4077.github.io', title: 'Project Page', icon: 'github-circle' },
                { url: 'https://chan4077.github.io/help', title: 'Get Help', icon: 'help-circle' },
                { url: 'https://chan4077.github.io/beta', title: 'Beta', icon: 'flask' },
                { url: 'https://chan4077.github.io/beta/blog.html', title: 'Beta Blog', icon: 'flask' },
                { url: 'https://github.com/Chan4077/chan4077.github.io/wiki/Sites', title: 'Sitemap', icon: 'sitemap' }
            ];
            $scope.projects = [
                { url: 'https://chan4077.github.io/projects', title: 'Projects', icon: 'code-tags' },
                { url: 'https://chan4077.github.io/MyFirstApp', title: 'MyFirstApp', icon: 'code-tags' },
                { url: 'https://chan4077.github.io/icons', title: 'Icons', icon: 'code-tags' }
            ];
            // Toggle left sidenav
            $scope.toggleLeftMenu = function () {
                $mdSidenav('left').toggle();
            };
            $scope.toggleAllLinks = function () {
                if (!$scope.toggledLinks && !$scope.toggledOtherLinks && !$scope.toggledProjectLinks) {
                    $scope.toggledAllLinks = true;
                    $scope.toggledLinks = true;
                    $scope.toggledProjectLinks = true;
                    $scope.toggledOtherLinks = true;
                    console.log('All are expanded.');
                } else {
                    $scope.toggledAllLinks = false;
                    $scope.toggledLinks = false;
                    $scope.toggledProjectLinks = false;
                    $scope.toggledOtherLinks = false;
                    console.log('They are collapsed.');
                }
            };
            $scope.toggleLinks = function() {
                $scope.toggledLinks = !$scope.toggledLinks;
                console.log('Toggled links: '+$scope.toggledLinks);
            }
            $scope.toggleOtherLinks = function() {
                $scope.toggledOtherLinks = !$scope.toggledOtherLinks;
                console.log('Toggled other links: '+$scope.toggledOtherLinks);
            }
            $scope.toggleProjectLinks = function() {
                $scope.toggledProjectLinks = !$scope.toggledProjectLinks;
                console.log('Toggled project links: '+$scope.toggledProjectLinks);
            }
        };
        return {
            restrict: 'E',
            templateUrl: '/templates/sidenav.html',
            transclude: true,
            controller: SidenavController
        };
    })
    .directive('siteToolbar', function () {
        return {
            restrict: 'E',
            templateUrl: '/templates/toolbar.html',
            transclude: true,
            controller: function ToolbarController($scope, $location, $mdDialog, $mdToast) {
                console.info('Loaded!');
                $scope.data = [{
                    "url": "/",
                    "name": "Home"
                }, {
                    "url": "beta",
                    "name": "Beta"
                }, {
                    "url": "cookies",
                    "name": "Cookies"
                }, {
                    "url": "projects",
                    "name": "Projects"
                }, {
                    "url": "help",
                    "name": "Help"
                }, {
                    "url": "about",
                    "name": "About"
                }, {
                    "url": "angular",
                    "name": "Angular"
                }]
                var urls = ['/', 'beta', 'cookies', 'projects', 'help', 'about', 'angular']
                angular.forEach($scope.data, function (value, key) {
                    // Based on http://plnkr.co/edit/WeQhCb?p=preview
                    if (urls.indexOf(value.url) !== -1) {
                        $scope[value.url] = value.name;
                    } else {
                        console.error('Invalid value');
                    }

                })
                console.log('url visited: ' + $location.path());
                $scope.viewGithub = function () {
                    window.location.href = "https://github.com/Chan4077/chan4077.github.io";
                };
                $scope.refreshPage = function () {
                    window.location.reload(true);
                };
                // $mdDialog (start)
                $scope.whats_new = function (ev) {
                    $mdDialog.show({
                        controller: WhatNewController,
                        templateUrl: '/templates/whats_new_tmpl.html',
                        parent: angular.element(document.body),
                        targetEvent: ev,
                        clickOutsideToClose: true,
                    });
                };
                $scope.settings = function (ev) {
                    $mdDialog.show({
                        controller: SettingsController,
                        templateUrl: '/templates/settings_tmpl.html',
                        parent: angular.element(document.body),
                        targetEvent: ev,
                        clickOutsideToClose: true,
                    });
                };
                $scope.sendFeedback = function () {
                    // Head to Github -> Create new issue
                    var toast = $mdToast.simple()
                        .textContent('Send feedback here: ')
                        .action('GO TO GITHUB')
                        .highlightAction(true)
                        .position("bottom left");

                    $mdToast.show(toast).then(function (response) {
                        if (response == 'ok') {
                            alert('Redirecting to Github...');
                            window.location.href = 'https://github.com/Chan4077/chan4077.github.io/issues/new';
                        }
                    })
                };
                // What's New
                function WhatNewController($scope, $mdDialog, $mdToast) {
                    // Hide button
                    $scope.hide = function () {
                        // Hides $mdDialog
                        $mdDialog.hide();
                    };
                    // Cancel button
                    $scope.cancel = function () {
                        // Cancels $mdDialog
                        $mdDialog.cancel();
                    };
                    // Help button
                    $scope.help = function () {
                        // Redirect to help site
                        window.location.href = "https://chan4077.github.io/help";
                        console.info('Redirecting to Help site...');
                    }
                    // More Info button
                    $scope.moreInfo = function () {
                        // Note that this is percent-encoded
                        // More information: https://en.wikipedia.org/wiki/Percent-encoding
                        window.location.href = "https://github.com/Chan4077/chan4077.github.io/wiki/What%27s-New";
                    }
                };
                // Feedback
                function FeedbackController($scope, $mdDialog, $mdToast) {
                    $scope.hide = function () {
                        $mdDialog.hide();
                    };
                    $scope.cancel = function () {
                        $mdDialog.cancel();
                        $mdToast.show(
                            $mdToast.simple()
                                .textContent('Feedback cancelled')
                                .position('bottom left')
                                .hideDelay(3000)
                        );
                    };
                    $scope.help = function () {
                        window.location.href = "https://chan4077.github.io/help/about_feedback.html";
                        console.info('Redirecting to Help site...');
                    }
                    // $scope.oriFeedback = {
                    //     name: "",
                    //     email: "",
                    //     sendFeedback: ""
                    // };
                    // // Declare a variable called oriFeedback
                    // var oriFeedback = angular.copy($scope.oriFeedback);

                    // // On initial, set feedback to original
                    // $scope.feedback = angular.copy(oriFeedback);

                    // // Reset form
                    // $scope.reset = function () {
                    //     $scope.feedback = angular.copy(oriFeedback);
                    //     // Reset form
                    //     $scope.feedbackhelp.$setPristine();
                    //     // Reset validation errors
                    //     $scope.feedbackhelp.$setUntouched();
                    // };
                    $scope.postOnGithub = function () {
                        $mdDialog.cancel();
                        console.debug('Redirecting to Github...');
                        window.location.href = "https://github.com/Chan4077/chan4077.github.io/issues/new";
                    };
                };
                // Settings
                function SettingsController($scope, $mdDialog) {
                    $scope.hide = function () {
                        $mdDialog.hide();
                    };
                    $scope.cancel = function () {
                        $mdDialog.cancel();
                    };
                    $scope.help = function () {
                        window.location.href = "https://chan4077.github.io/help";
                        console.info('Redirecting to Help site...');
                    }
                    $scope.general_settings = {
                        proBar: true,
                        enableSound: true
                    };
                    $scope.developer_settings = {
                        consoleLog: false,
                        showExtraOptions: false
                    };
                    $scope.extra_settings = {
                        showCredits: true,
                        enableOffline: true
                    };
                    // $mdDialog (end)

                    $scope.about_site = function () {
                        window.location.href = "https://chan4077.github.io/about#angular";
                    };
                };
            }
        };
    })
    // Speed Dial
    .directive('siteSpeedDial', function () {
        return {
            restrict: 'E',
            templateUrl: '/templates/speeddial.html',
            transclude: true
        };
    })
    // Controllers
    .controller('SideNavController', function ($scope, $mdSidenav) {

    })
// App config
angular.module('app.config', [])
    .config(function ($mdIconProvider, $mdToastProvider, $interpolateProvider) {
        $mdIconProvider
            .defaultIconSet('/img/mdi.svg')
            .icon('website-logo', '/img/website.svg');
        console.info('Successfully initialized!');
        $interpolateProvider.startSymbol('{(');
        $interpolateProvider.endSymbol(')}');
    });<commit_msg>fix(dependencies) - Add locationProvider.html5mode<commit_after>/*
*  Directives
*/
// Sidenav
angular.module('app.directives', [])
    .directive('siteSidenav', function () {
        var SidenavController = function ($scope, $mdSidenav) {
            console.info('Successfully loaded!');

            $scope.main = [
                { url: 'https://chan4077.github.io', title: 'Main', icon: 'home' },
                { url: 'https://chan4077.github.io/about', title: 'About Me', icon: 'account-box' },
                { url: 'https://chan4077.github.io/blog', title: 'Blog', icon: 'blogger' },
                { url: 'https://chan4077.github.io/media', title: 'Social Media', icon: 'google-plus' },
                { url: 'https://chan4077.github.io/angular', title: 'Angular', icon: 'angular' },
            ];
            $scope.other = [
                { url: 'https://github.com/Chan4077/chan4077.github.io', title: 'Project Page', icon: 'github-circle' },
                { url: 'https://chan4077.github.io/help', title: 'Get Help', icon: 'help-circle' },
                { url: 'https://chan4077.github.io/beta', title: 'Beta', icon: 'flask' },
                { url: 'https://chan4077.github.io/beta/blog.html', title: 'Beta Blog', icon: 'flask' },
                { url: 'https://github.com/Chan4077/chan4077.github.io/wiki/Sites', title: 'Sitemap', icon: 'sitemap' }
            ];
            $scope.projects = [
                { url: 'https://chan4077.github.io/projects', title: 'Projects', icon: 'code-tags' },
                { url: 'https://chan4077.github.io/MyFirstApp', title: 'MyFirstApp', icon: 'code-tags' },
                { url: 'https://chan4077.github.io/icons', title: 'Icons', icon: 'code-tags' }
            ];
            // Toggle left sidenav
            $scope.toggleLeftMenu = function () {
                $mdSidenav('left').toggle();
            };
            $scope.toggleAllLinks = function () {
                if (!$scope.toggledLinks && !$scope.toggledOtherLinks && !$scope.toggledProjectLinks) {
                    $scope.toggledAllLinks = true;
                    $scope.toggledLinks = true;
                    $scope.toggledProjectLinks = true;
                    $scope.toggledOtherLinks = true;
                    console.log('All are expanded.');
                } else {
                    $scope.toggledAllLinks = false;
                    $scope.toggledLinks = false;
                    $scope.toggledProjectLinks = false;
                    $scope.toggledOtherLinks = false;
                    console.log('They are collapsed.');
                }
            };
            $scope.toggleLinks = function() {
                $scope.toggledLinks = !$scope.toggledLinks;
                console.log('Toggled links: '+$scope.toggledLinks);
            }
            $scope.toggleOtherLinks = function() {
                $scope.toggledOtherLinks = !$scope.toggledOtherLinks;
                console.log('Toggled other links: '+$scope.toggledOtherLinks);
            }
            $scope.toggleProjectLinks = function() {
                $scope.toggledProjectLinks = !$scope.toggledProjectLinks;
                console.log('Toggled project links: '+$scope.toggledProjectLinks);
            }
        };
        return {
            restrict: 'E',
            templateUrl: '/templates/sidenav.html',
            transclude: true,
            controller: SidenavController
        };
    })
    .directive('siteToolbar', function () {
        return {
            restrict: 'E',
            templateUrl: '/templates/toolbar.html',
            transclude: true,
            controller: function ToolbarController($scope, $location, $mdDialog, $mdToast) {
                console.info('Loaded!');
                $scope.data = [{
                    "url": "/",
                    "name": "Home"
                }, {
                    "url": "beta",
                    "name": "Beta"
                }, {
                    "url": "cookies",
                    "name": "Cookies"
                }, {
                    "url": "projects",
                    "name": "Projects"
                }, {
                    "url": "help",
                    "name": "Help"
                }, {
                    "url": "about",
                    "name": "About"
                }, {
                    "url": "angular",
                    "name": "Angular"
                }]
                var urls = ['/', 'beta', 'cookies', 'projects', 'help', 'about', 'angular']
                angular.forEach($scope.data, function (value, key) {
                    // Based on http://plnkr.co/edit/WeQhCb?p=preview
                    if (urls.indexOf(value.url) !== -1) {
                        $scope[value.url] = value.name;
                    } else {
                        console.error('Invalid value');
                    }

                })
                console.log('url visited: ' + $location.path());
                $scope.viewGithub = function () {
                    window.location.href = "https://github.com/Chan4077/chan4077.github.io";
                };
                $scope.refreshPage = function () {
                    window.location.reload(true);
                };
                // $mdDialog (start)
                $scope.whats_new = function (ev) {
                    $mdDialog.show({
                        controller: WhatNewController,
                        templateUrl: '/templates/whats_new_tmpl.html',
                        parent: angular.element(document.body),
                        targetEvent: ev,
                        clickOutsideToClose: true,
                    });
                };
                $scope.settings = function (ev) {
                    $mdDialog.show({
                        controller: SettingsController,
                        templateUrl: '/templates/settings_tmpl.html',
                        parent: angular.element(document.body),
                        targetEvent: ev,
                        clickOutsideToClose: true,
                    });
                };
                $scope.sendFeedback = function () {
                    // Head to Github -> Create new issue
                    var toast = $mdToast.simple()
                        .textContent('Send feedback here: ')
                        .action('GO TO GITHUB')
                        .highlightAction(true)
                        .position("bottom left");

                    $mdToast.show(toast).then(function (response) {
                        if (response == 'ok') {
                            alert('Redirecting to Github...');
                            window.location.href = 'https://github.com/Chan4077/chan4077.github.io/issues/new';
                        }
                    })
                };
                // What's New
                function WhatNewController($scope, $mdDialog, $mdToast) {
                    // Hide button
                    $scope.hide = function () {
                        // Hides $mdDialog
                        $mdDialog.hide();
                    };
                    // Cancel button
                    $scope.cancel = function () {
                        // Cancels $mdDialog
                        $mdDialog.cancel();
                    };
                    // Help button
                    $scope.help = function () {
                        // Redirect to help site
                        window.location.href = "https://chan4077.github.io/help";
                        console.info('Redirecting to Help site...');
                    }
                    // More Info button
                    $scope.moreInfo = function () {
                        // Note that this is percent-encoded
                        // More information: https://en.wikipedia.org/wiki/Percent-encoding
                        window.location.href = "https://github.com/Chan4077/chan4077.github.io/wiki/What%27s-New";
                    }
                };
                // Feedback
                function FeedbackController($scope, $mdDialog, $mdToast) {
                    $scope.hide = function () {
                        $mdDialog.hide();
                    };
                    $scope.cancel = function () {
                        $mdDialog.cancel();
                        $mdToast.show(
                            $mdToast.simple()
                                .textContent('Feedback cancelled')
                                .position('bottom left')
                                .hideDelay(3000)
                        );
                    };
                    $scope.help = function () {
                        window.location.href = "https://chan4077.github.io/help/about_feedback.html";
                        console.info('Redirecting to Help site...');
                    }
                    // $scope.oriFeedback = {
                    //     name: "",
                    //     email: "",
                    //     sendFeedback: ""
                    // };
                    // // Declare a variable called oriFeedback
                    // var oriFeedback = angular.copy($scope.oriFeedback);

                    // // On initial, set feedback to original
                    // $scope.feedback = angular.copy(oriFeedback);

                    // // Reset form
                    // $scope.reset = function () {
                    //     $scope.feedback = angular.copy(oriFeedback);
                    //     // Reset form
                    //     $scope.feedbackhelp.$setPristine();
                    //     // Reset validation errors
                    //     $scope.feedbackhelp.$setUntouched();
                    // };
                    $scope.postOnGithub = function () {
                        $mdDialog.cancel();
                        console.debug('Redirecting to Github...');
                        window.location.href = "https://github.com/Chan4077/chan4077.github.io/issues/new";
                    };
                };
                // Settings
                function SettingsController($scope, $mdDialog) {
                    $scope.hide = function () {
                        $mdDialog.hide();
                    };
                    $scope.cancel = function () {
                        $mdDialog.cancel();
                    };
                    $scope.help = function () {
                        window.location.href = "https://chan4077.github.io/help";
                        console.info('Redirecting to Help site...');
                    }
                    $scope.general_settings = {
                        proBar: true,
                        enableSound: true
                    };
                    $scope.developer_settings = {
                        consoleLog: false,
                        showExtraOptions: false
                    };
                    $scope.extra_settings = {
                        showCredits: true,
                        enableOffline: true
                    };
                    // $mdDialog (end)

                    $scope.about_site = function () {
                        window.location.href = "https://chan4077.github.io/about#angular";
                    };
                };
            }
        };
    })
    // Speed Dial
    .directive('siteSpeedDial', function () {
        return {
            restrict: 'E',
            templateUrl: '/templates/speeddial.html',
            transclude: true
        };
    })
    // Controllers
    .controller('SideNavController', function ($scope, $mdSidenav) {

    })
// App config
angular.module('app.config', [])
    .config(function ($mdIconProvider, $mdToastProvider, $interpolateProvider, $locationProvider) {
        $mdIconProvider
            .defaultIconSet('/img/mdi.svg')
            .icon('website-logo', '/img/website.svg');
        console.info('Successfully initialized!');
        $interpolateProvider.startSymbol('{(');
        $interpolateProvider.endSymbol(')}');
        $locationProvider.html5Mode(true);
    });<|endoftext|>


<commit_before>networkMap.Link.Module = networkMap.Link.Module || {};

/**
 * The Edge module is an UI widget for controling
 * the edge point of the link.
 *
 * @param {object} options Options to override defaults.
 * @constructor
 * @borrows networkMap.Options#setOptions as #setOptions
 */
networkMap.Link.Module.Edge = function(svg, bbox, edgePoint, edgeDirection, userDefined){

	this.svg = svg;
	this.bbox = bbox;

	this.defaults = {};
	this.setDefaults(edgePoint, edgeDirection);

	function convert(obj){
		return SVG.math.Point.create(obj.x, obj.y);
	}

	if (userDefined){
		this.setUserDefined(convert(userDefined.point), convert(userDefined.pointer), convert(userDefined.direction));
	}

	this.size = 15;
	this.angleSnap = Math.PI / 14;
	this.pointSnap = 5;

	this.state = this.states.hidden;
};

networkMap.extend(networkMap.Link.Module.Edge, networkMap.Options);
networkMap.extend(networkMap.Link.Module.Edge, networkMap.Observable);

/**
 * @lends networkMap.Link.Module.Edge
 */
networkMap.extend(networkMap.Link.Module.Edge, {
	setDefaults: function(edgePoint, edgeDirection){
		this.defaults.point = edgePoint;
		this.defaults.direction = edgeDirection;

		return this;
	},

	getDefaults: function(){
		return this.defaults;
	},

	setUserDefined: function(edgePoint, edgePointer, edgeDirection){
		this.userDefined = {
			point: edgePoint,
			pointer: edgePointer,
			direction: edgeDirection
		};

		return this;
	},

	getUserDefined: function(){
		return this.userDefined;
	},

	getEdge: function(){
		return (this.userDefined) ? this.userDefined : this.defaults;
	},

	setBbox: function(bbox){
		this.bbox = bbox;

		return this.redraw();
	},

	getBbox: function(){
		return this.bbox;
	},

	show: function(bbox){
		this.state.show.call(this, bbox);
		return this;
	},

	hide: function(){
		this.state.hide.call(this);
		return this;
	},

	redraw: function(){
		this.state.redraw.call(this);
		return this;
	},

	getConfiguration: function(){
		return this.getUserDefined();
	},

	states: {
		hidden: {
			show: function(bbox){
				this.setBbox(bbox);

				var svg = this.svg;

				var edge = this.getEdge();

				var vec = edge.direction.clone().scale(30);
				vec.add(edge.point);

				var line = this.line = svg.line(edge.point.x, edge.point.y, vec.x, vec.y)
					.stroke({
						fill: 'none',
						color: '#000',
						width: 2,

					});

				var directionHandle = this.directionHandle = this.svg.circle(this.size)
					.center(vec.x, vec.y)
					.fill('#fff')
					.stroke({
						color: '#000'
					})
					.draggable(function(x, y){
						var edge = this.getEdge();
						var vec2 = networkMap.vec2.create(x, y);
						var edge2 = edge.point.clone();
						var res = vec2.sub(edge2).normalize().scale(30);
						res.roundDir(this.angleSnap).add(edge2);

						return {x: res.x, y: res.y};
					}.bind(this));

				var radius = this.size / 2;
				var edgeHandle = this.edgeHandle = this.svg.circle(this.size)
					.fill('#fff')
					.stroke({
						color: '#000'
					})
					.center(edge.point.x, edge.point.y)
					.draggable(function(x, y){
						x = x < this.bbox.x - radius  ? this.bbox.x - radius : x - x % this.pointSnap;
						x = x > (this.bbox.x + this.bbox.width - radius) ? this.bbox.x + this.bbox.width - radius : x - x % this.pointSnap;
						y = y < this.bbox.y - radius ? this.bbox.y - radius : y - y % this.pointSnap;
						y = y > (this.bbox.y + this.bbox.height - radius) ? this.bbox.y + this.bbox.height - radius : y - y % this.pointSnap;

						return {
							x: x,
							y: y
						};
					}.bind(this));

				svg.on('dblclick', this.onDoubleClick.bind(this));

				directionHandle.dragstart = this.onDragStart.bind(this);
				directionHandle.dragmove = this.onDirectionHandleMove.bind(this);
				directionHandle.dragend = this.onDragEnd.bind(this);

				edgeHandle.dragstart = this.onDragStart.bind(this);
				edgeHandle.dragmove = this.onEdgeHandleMove.bind(this);
				edgeHandle.dragend = this.onDragEnd.bind(this);

				svg.front();

				this.state = this.states.rendered;
				return this;
			},

			redraw: function(){
				var edge = this.getEdge();

				if (edge.pointer){
					edge.point = SVG.math.Point.create(this.bbox.cx, this.bbox.cy).add(edge.pointer);
				}

				return this;
			},

			hide: function(){
				return this;
			}
		},
		rendered: {
			show: function(bbox){
				this.bbox = bbox;
				return this.redraw();
			},

			redraw: function(){
				var edge = this.getEdge();

				if (edge.pointer){
					edge.point = SVG.math.Point.create(this.bbox.cx, this.bbox.cy).add(edge.pointer);
				}
				var vec = edge.direction.clone().scale(30).add(edge.point);

				this.line.plot(edge.point.x, edge.point.y, vec.x, vec.y);
				this.directionHandle.center(vec.x, vec.y);
				this.edgeHandle.center(edge.point.x, edge.point.y);
				return this;
			},

			hide: function(){
				this.directionHandle.dragmove = null;
				this.edgeHandle.dragmove = null;

				this.line.remove();
				this.directionHandle.remove();
				this.edgeHandle.remove();
				this.line = null;
				this.directionHandle = null;
				this.edgeHandle = null;

				this.state = this.states.hidden;
			}

		}
	},

	onDoubleClick: function(){
		this.reset();
		this.fireEvent('dragend');
	},

	onDragStart: function(){
		this.fireEvent('dragstart');
	},

	onDragEnd: function(){
		this.fireEvent('dragend');
	},

	onDirectionHandleMove: function(event){
		var edge = this.getEdge();

		this.setUserDefined(
			edge.point,
			edge.point.clone().sub(SVG.math.Point.create(this.bbox.cx, this.bbox.cy)),
			SVG.math.Point.create(event.target.cx(), event.target.cy()).sub(edge.point).normalize()
		);

		this.redraw();
		this.fireEvent('updated');
	},

	onEdgeHandleMove: function(event){
		var edge = this.getEdge();
		var edgePoint = SVG.math.Point.create(event.target.cx(), event.target.cy());

		this.setUserDefined(
			edgePoint,
			edgePoint.clone().sub(SVG.math.Point.create(this.bbox.cx, this.bbox.cy)),
			edge.direction
		);

		this.fireEvent('updated');
		this.redraw();
	},

	reset: function(){
		this.userDefined = undefined;
		this.redraw();

		return this;
	}
});
<commit_msg>Fixed draggable link edges.<commit_after>networkMap.Link.Module = networkMap.Link.Module || {};

/**
 * The Edge module is an UI widget for controling
 * the edge point of the link.
 *
 * @param {object} options Options to override defaults.
 * @constructor
 * @borrows networkMap.Options#setOptions as #setOptions
 */
networkMap.Link.Module.Edge = function(svg, bbox, edgePoint, edgeDirection, userDefined){

	this.svg = svg;
	this.bbox = bbox;

	this.defaults = {};
	this.setDefaults(edgePoint, edgeDirection);

	function convert(obj){
		return SVG.math.Point.create(obj.x, obj.y);
	}

	if (userDefined){
		this.setUserDefined(convert(userDefined.point), convert(userDefined.pointer), convert(userDefined.direction));
	}

	this.size = 15;
	this.angleSnap = Math.PI / 14;
	this.pointSnap = 5;

	this.state = this.states.hidden;
};

networkMap.extend(networkMap.Link.Module.Edge, networkMap.Options);
networkMap.extend(networkMap.Link.Module.Edge, networkMap.Observable);

/**
 * @lends networkMap.Link.Module.Edge
 */
networkMap.extend(networkMap.Link.Module.Edge, {
	setDefaults: function(edgePoint, edgeDirection){
		this.defaults.point = edgePoint;
		this.defaults.direction = edgeDirection;

		return this;
	},

	getDefaults: function(){
		return this.defaults;
	},

	setUserDefined: function(edgePoint, edgePointer, edgeDirection){
		this.userDefined = {
			point: edgePoint,
			pointer: edgePointer,
			direction: edgeDirection
		};

		return this;
	},

	getUserDefined: function(){
		return this.userDefined;
	},

	getEdge: function(){
		return (this.userDefined) ? this.userDefined : this.defaults;
	},

	setBbox: function(bbox){
		this.bbox = bbox;

		return this.redraw();
	},

	getBbox: function(){
		return this.bbox;
	},

	show: function(bbox){
		this.state.show.call(this, bbox);
		return this;
	},

	hide: function(){
		this.state.hide.call(this);
		return this;
	},

	redraw: function(){
		this.state.redraw.call(this);
		return this;
	},

	getConfiguration: function(){
		return this.getUserDefined();
	},

	states: {
		hidden: {
			show: function(bbox){
				this.setBbox(bbox);

				var svg = this.svg;

				var edge = this.getEdge();

				var vec = edge.direction.clone().scale(30);
				vec.add(edge.point);

				var line = this.line = svg.line(edge.point.x, edge.point.y, vec.x, vec.y)
					.stroke({
						fill: 'none',
						color: '#000',
						width: 2,

					});

				var directionHandle = this.directionHandle = this.svg.circle(this.size)
					.center(vec.x, vec.y)
					.fill('#fff')
					.stroke({
						color: '#000'
					})
					.draggable(function(x, y){
						var edge = this.getEdge();
						var vec2 = networkMap.vec2.create(x, y);
						var edge2 = edge.point.clone();
						var res = vec2.sub(edge2).normalize().scale(30);
						res.roundDir(this.angleSnap).add(edge2);

						return {x: res.x, y: res.y};
					}.bind(this));

				var radius = this.size / 2;
				var edgeHandle = this.edgeHandle = this.svg.circle(this.size)
					.fill('#fff')
					.stroke({
						color: '#000'
					})
					.center(edge.point.x, edge.point.y)
					.draggable(function(x, y){
						x = x < this.bbox.x - radius ? this.bbox.x - radius : x - x % this.pointSnap;
						x = x > (this.bbox.x + this.bbox.width - radius) ? this.bbox.x + this.bbox.width - radius : x - x % this.pointSnap;
						y = y < this.bbox.y - radius ? this.bbox.y - radius : y - y % this.pointSnap;
						y = y > (this.bbox.y + this.bbox.height - radius) ? this.bbox.y + this.bbox.height - radius : y - y % this.pointSnap;

						return {
							x: x,
							y: y
						};
					}.bind(this));

				svg.on('dblclick', this.onDoubleClick.bind(this));

				directionHandle.on('dragstart', this.onDragStart.bind(this));
				directionHandle.on('dragmove', this.onDirectionHandleMove.bind(this));
				directionHandle.on('dragend', this.onDragEnd.bind(this));

				edgeHandle.on('dragstart', this.onDragStart.bind(this));
				edgeHandle.on('dragmove', this.onEdgeHandleMove.bind(this));
				edgeHandle.on('dragend', this.onDragEnd.bind(this));

				svg.front();

				this.state = this.states.rendered;
				return this;
			},

			redraw: function(){
				var edge = this.getEdge();

				if (edge.pointer){
					edge.point = SVG.math.Point.create(this.bbox.cx, this.bbox.cy).add(edge.pointer);
				}

				return this;
			},

			hide: function(){
				return this;
			}
		},
		rendered: {
			show: function(bbox){
				this.bbox = bbox;
				return this.redraw();
			},

			redraw: function(){
				var edge = this.getEdge();

				if (edge.pointer){
					edge.point = SVG.math.Point.create(this.bbox.cx, this.bbox.cy).add(edge.pointer);
				}
				var vec = edge.direction.clone().scale(30).add(edge.point);

				this.line.plot(edge.point.x, edge.point.y, vec.x, vec.y);
				this.directionHandle.center(vec.x, vec.y);
				this.edgeHandle.center(edge.point.x, edge.point.y);
				return this;
			},

			hide: function(){
				this.directionHandle.dragmove = null;
				this.edgeHandle.dragmove = null;

				this.line.remove();
				this.directionHandle.remove();
				this.edgeHandle.remove();
				this.line = null;
				this.directionHandle = null;
				this.edgeHandle = null;

				this.state = this.states.hidden;
			}

		}
	},

	onDoubleClick: function(){
		this.reset();
		this.fireEvent('dragend');
	},

	onDragStart: function(){
		this.fireEvent('dragstart');
	},

	onDragEnd: function(){
		this.fireEvent('dragend');
	},

	onDirectionHandleMove: function(event){
		var edge = this.getEdge();

		this.setUserDefined(
			edge.point,
			edge.point.clone().sub(SVG.math.Point.create(this.bbox.cx, this.bbox.cy)),
			SVG.math.Point.create(event.target.cx.baseVal.value, event.target.cy.baseVal.value)
				.sub(edge.point)
				.normalize()
		);

		this.redraw();
		this.fireEvent('updated');
	},

	onEdgeHandleMove: function(event){
		var edge = this.getEdge();
		var edgePoint = SVG.math.Point.create(event.target.cx.baseVal.value, event.target.cy.baseVal.value);

		this.setUserDefined(
			edgePoint,
			edgePoint.clone().sub(SVG.math.Point.create(this.bbox.cx, this.bbox.cy)),
			edge.direction
		);

		this.fireEvent('updated');
		this.redraw();
	},

	reset: function(){
		this.userDefined = undefined;
		this.redraw();

		return this;
	}
});
<|endoftext|>