<commit_before>/*jshint strict: false */

////////////////////////////////////////////////////////////////////////////////
/// @brief monkey-patches to built-in prototypes
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2004-2013 triAGENS GmbH, Cologne, Germany
///
/// 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.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Dr. Frank Celler
/// @author Lucas Dohmen
/// @author Copyright 2011-2013, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////

// -----------------------------------------------------------------------------
// --SECTION--                                                    monkey-patches
// -----------------------------------------------------------------------------

// -----------------------------------------------------------------------------
// --SECTION--                                                  public functions
// -----------------------------------------------------------------------------

////////////////////////////////////////////////////////////////////////////////
/// @brief shallow copies properties
////////////////////////////////////////////////////////////////////////////////

Object.defineProperty(Object.prototype, "_shallowCopy", {
  get: function () {
    var that = this;

    return this.propertyKeys.reduce(function (previous, element) {
      previous[element] = that[element];
      return previous;
    }, {});
  }
});

////////////////////////////////////////////////////////////////////////////////
/// @brief returns the property keys
////////////////////////////////////////////////////////////////////////////////

Object.defineProperty(Object.prototype, "propertyKeys", {
  get: function () {
    return Object.keys(this).filter(function (element) {
      return (element[0] !== '_' && element[0] !== '$');
    });
  }
});

// -----------------------------------------------------------------------------
// --SECTION--                                                       END-OF-FILE
// -----------------------------------------------------------------------------

// Local Variables:
// mode: outline-minor
// outline-regexp: "/// @brief\\|/// @addtogroup\\|/// @page\\|// --SECTION--\\|/// @\\}\\|/\\*jslint"
// End:
<commit_msg>Added more stubs.<commit_after>/*jshint strict: false */

////////////////////////////////////////////////////////////////////////////////
/// @brief monkey-patches to built-in prototypes
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2004-2013 triAGENS GmbH, Cologne, Germany
///
/// 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.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Dr. Frank Celler
/// @author Lucas Dohmen
/// @author Copyright 2011-2013, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////

// -----------------------------------------------------------------------------
// --SECTION--                                                    monkey-patches
// -----------------------------------------------------------------------------

global.setInterval = function () {};
global.clearInterval = function () {};
global.setTimeout = function () {};
global.clearTimeout = function () {};

// -----------------------------------------------------------------------------
// --SECTION--                                                  public functions
// -----------------------------------------------------------------------------

////////////////////////////////////////////////////////////////////////////////
/// @brief shallow copies properties
////////////////////////////////////////////////////////////////////////////////

Object.defineProperty(Object.prototype, "_shallowCopy", {
  get: function () {
    var that = this;

    return this.propertyKeys.reduce(function (previous, element) {
      previous[element] = that[element];
      return previous;
    }, {});
  }
});

////////////////////////////////////////////////////////////////////////////////
/// @brief returns the property keys
////////////////////////////////////////////////////////////////////////////////

Object.defineProperty(Object.prototype, "propertyKeys", {
  get: function () {
    return Object.keys(this).filter(function (element) {
      return (element[0] !== '_' && element[0] !== '$');
    });
  }
});

// -----------------------------------------------------------------------------
// --SECTION--                                                       END-OF-FILE
// -----------------------------------------------------------------------------

// Local Variables:
// mode: outline-minor
// outline-regexp: "/// @brief\\|/// @addtogroup\\|/// @page\\|// --SECTION--\\|/// @\\}\\|/\\*jslint"
// End:
<|endoftext|>


<commit_before>// Set up a bridge between Model-R and postmessage.
//
// The convention is that everything sent via post-message is a JSON-encoded object with a
// field called "action" which is used to determine which message was sent.
//
// So, if we receive '{"action":"complete","status":200}' from the iframe,
// that will get converted into _public.triggerComplete({action:"complete", // "status":200});
//
// Conversely, if you call _public.triggerSubmit({to_url:"http://rapportive.com/foo"})
// that will be sent to the iframe as // '{"action":"submit","to_url","http://rapportive.com/foo"}'
//
// opts is an object with:
// {
//   iframe: the iframe to send messages to
//   receive: [a list of actions we expect to receive],
//   send: [a list of actions we expect to send],
//   remote_base_url: the url to send with postMessage to ensure it arrives at the correct domain
// }
lib.postMessageShim = function (_public, _protected, opts) {

    lib.hasEvent(_public, _protected, opts.send);
    lib.hasEvent(_public, _protected, opts.receive);

    opts.iframe.message(function (msg) {
        if (_(opts.receive).include(msg.action)) {
            _public.trigger(msg.action, msg);
        } else {
            fsLog("Got unexpected postMessage: " + JSON.stringify(msg));
        }
    });

    _(opts.send).each(function (name) {
        _public.on(name, function (msg) {
            $.message(opts.iframe[0], jQuery.extend({action: name}, msg), opts.remote_base_url);
        });
    });
};
<commit_msg>Allow separate postMessage sender & receivers<commit_after>// Set up a bridge between Model-R and postmessage.
//
// The convention is that everything sent via post-message is a JSON-encoded object with a
// field called "action" which is used to determine which message was sent.
//
// So, if we receive '{"action":"complete","status":200}' from the iframe,
// that will get converted into _public.triggerComplete({action:"complete", // "status":200});
//
// Conversely, if you call _public.triggerSubmit({to_url:"http://rapportive.com/foo"})
// that will be sent to the iframe as // '{"action":"submit","to_url","http://rapportive.com/foo"}'
//
// opts is an object with:
// {
//   iframe: the iframe to send messages to and receive messages from
//   receive: [a list of actions we expect to receive],
//   send: [a list of actions we expect to send],
//   remote_base_url: the url to send with postMessage to ensure it arrives at the correct domain
// }
//
// Alternatively, if you're listening and sending to different objects, you can specify them
// explicitly instead of the 'iframe' property, eg:
// {
//   listener: $(window),
//   recipient: $(window.parent),
//   receive: [a list of actions we expect to receive],
//   send: [a list of actions we expect to send],
//   remote_base_url: the url to send with postMessage to ensure it arrives at the correct domain
// }
lib.postMessageShim = function (_public, _protected, opts) {

    lib.hasEvent(_public, _protected, opts.send);
    lib.hasEvent(_public, _protected, opts.receive);

    var listener = opts.iframe || opts.listener,
        recipient = opts.iframe || opts.recipient;

    listener.message(function (msg, reply, e) {
        if (_(opts.receive).include(msg.action)) {
            _public.trigger(msg.action, msg);
        } else {
            fsLog("Got unexpected postMessage: " + JSON.stringify(msg));
        }
    });

    _(opts.send).each(function (name) {
        _public.on(name, function (msg) {
            if (recipient.jquery) {
                recipient = recipient[0];
            }
            $.message(recipient, jQuery.extend({action: name}, msg), opts.remote_base_url);
        });
    });
};
<|endoftext|>


<commit_before>'use strict';

angular.module('millerColumnBrowser', [])
  .directive('columnBrowser', function() {
	  var dummy = false;
  	var hasFocus = false;
  	var currentAjaxRequest = null;
  	var settings = angular.extend({}, {
  				'transform': function(lines) { return lines; },
  				'preloadedData': {},
  				'tabindex': 0,
  				'minWidth': 40,
  				'toolbar': {
  					'options': {}
  				},
  				'pane': {
  					'options': {}
  				}
  			});
    var currentLine = null;
	var path, columns, toolbar;

	var removeNextColumns = function(e) {
		var line = angular.element(this),
			column = line.parent(), i;

		while ( (i = column.next()) && i.length) {
			i.remove();
		}

		column.children().removeClass('selected');
		line.addClass('selected');
	};

	var getLines = function(event) {
			currentLine = angular.element(event.target);
			var id = currentLine.data('id');
			currentLine.addClass('selected').addClass('loading');

			// TODO: pass callback handler into here
			// then que up the result and pass back into buildColumn
			buildColumn(dummy);
			currentLine.removeClass('loading');
	};

	var buildColumn = function(lines) {
			if (toolbar) {
//					toolbar.children().remove();
//					$.each(settings.toolbar.options, function(key, callback) {
//							$('', { 'text': key })
//								.click(function() { callback.call(miller, loading.data('id')) })
//								.appendTo(toolbar)
//							;
//						}
//					);
			}

			if (lines.length <= 0) {
				if (settings.pane.options.length) {
					var pane = angular.element('
')
						.css({ 'top': 0, 'left': width })
						.addClass('pane')
					;

					var id = line.data('id');

					angular.forEach(settings.pane.options, function(callback, key) {
							var j = angular.element('
').text(key)
								.click(function() { callback.call(miller, loading.data('id')) })
							;
							pane.append(j);
						}
					);

					columns.append(pane);
				}
			} else {
				var column = angular.element("
");
				angular.forEach(lines, function(data, id) {
						var line = angular.element('
').text(data['name'])
							.data('id', data['id'])
							.on('click', removeNextColumns)
							.on('click', getLines)
						;
						column.append(line);

						if (data['parent']) {
							line.addClass('parent');
						}
					}
				);
				columns.append(column);
			}
		}
	;

    function link(scope, element, attrs) {
		if (!element.attr('tabindex')) {
			element.attr('tabindex', settings.tabindex);
		}

		element.addClass('miller-column-browser');
		var children = element.children();
		path = angular.element(children[0]);
		columns = angular.element(children[1]);
		toolbar = angular.element(children[2]);

		if (attrs.init) {
			// TODO: what if this is a function?
			try {
				dummy = JSON.parse(attrs.init);
				buildColumn(dummy);
			} catch (e) { }
		}
	}

    return {
		link: link,
		restrict: 'E',
		template: '

'
    };
  });
<commit_msg>build out columns with data from callback function<commit_after>'use strict';

angular.module('millerColumnBrowser', [])
  .directive('columnBrowser', function($parse, $q) {
  	var settings = {
		  				'tabindex': 0,
		  				'minWidth': 40,
		  				'toolbar': {
		  					'options': {}
		  				},
		  				'pane': {
		  					'options': {}
		  				}
		  			};
    var currentLine = null;
	var path, columns, toolbar;
	var callback;

	var removeNextColumns = function(e) {
		var line = angular.element(this),
			column = line.parent(), i;

		while ( (i = column.next()) && i.length) {
			i.remove();
		}

		column.children().removeClass('selected');
		line.addClass('selected');
	};

	var getLines = function(event) {
			currentLine = angular.element(event.target);
			var id = currentLine.data('id');
			currentLine.addClass('selected').addClass('loading');
			// TODO: pass callback handler into here
			// then que up the result and pass back into buildColumn
			var promise = $q.when(callback(currentLine));
			promise.then(function(data){
				currentLine.removeClass('loading');
				buildColumn(data);
			});
	};

	var buildColumn = function(lines) {
			if (toolbar) {
//					toolbar.children().remove();
//					$.each(settings.toolbar.options, function(key, callback) {
//							$('', { 'text': key })
//								.click(function() { callback.call(miller, loading.data('id')) })
//								.appendTo(toolbar)
//							;
//						}
//					);
			}

			if (!Array.isArray(lines) || lines.length <= 0) {
				if (settings.pane.options.length) {
					var pane = angular.element('
')
						.css({ 'top': 0, 'left': width })
						.addClass('pane')
					;

					var id = line.data('id');

					angular.forEach(settings.pane.options, function(callback, key) {
							var j = angular.element('
').text(key)
								.click(function() { callback.call(miller, loading.data('id')) })
							;
							pane.append(j);
						}
					);

					columns.append(pane);
				}
			} else {
				var column = angular.element("
");
				angular.forEach(lines, function(data, id) {
						var line = angular.element('
').text(data['name'])
							.attr('id', data['id'])
							.attr('data-json', JSON.stringify(data))
							.on('click', removeNextColumns)
							.on('click', getLines)
						;
						column.append(line);

						if (data['parent']) {
							line.addClass('parent');
						}
					}
				);
				columns.append(column);
			}
		}
	;

    function link(scope, element, attrs) {
		if (!element.attr('tabindex')) {
			element.attr('tabindex', settings.tabindex);
		}
		if (scope.onSelected) {
			callback=$parse(scope.onSelected)(scope);
		}
		if (scope.settings) {
			angular.extend(settings, scope.settings);
		}
		element.addClass('miller-column-browser');
		var children = element.children();
		path = angular.element(children[0]);
		columns = angular.element(children[1]);
		toolbar = angular.element(children[2]);

		if (attrs.init) {
			// TODO: what if this is a function?
			try {
				buildColumn(JSON.parse(attrs.init));
			} catch (e) { }
		}
	}

    return {
		link: link,
		restrict: 'E',
        scope: {
          onSelected: '&onSelected',
		  settings: '='
        },
		template: '

'
    };
  });
<|endoftext|>


<commit_before>'use strict';

import { DNAConfig } from './dna-config.next.js';
import { DNAComponent } from './dna-component.next.js';
import { DNAHelper } from './dna-helper.next.js';
import VDOM from './libs/virtual-dom.next.js';

/**
 * Simple Custom Component with template handling using the `template` property.
 * @class DNATemplateComponent
 * @extends DNAComponent
 *
 * @example
 * my-component.next.js
 * ```js
 * import { DNATemplateComponent } from 'dna/component';
 * export class MyComponent extends DNATemplateComponent {
 *   static get template() {
 *     return `<h1>${this.name}</h1>`
 *   }
 *   get name() {
 *     return 'Newton'
 *   }
 * }
 * ```
 * app.next.js
 * ```js
 * import { Register } from 'dna/component';
 * import { MyComponent } from './components/my-component/my-component.next.js';
 * var MyElement = Register(MyComponent);
 * var element = new MyElement();
 * console.log(element.innerHTML); // logs "<h1>Newton</h1>"
 * ```
 */
export class DNATemplateComponent extends DNAComponent {
    /**
     * Fires when an the element is registered.
     */
    static onRegister(...args) {
        // Create render function
        let ctr = this;
        if (this.template) {
            ctr.prototype.render = ((template) => {
                if (typeof template === 'function') {
                    return function() {
                        return template.call(this);
                    }
                } else if (typeof template == 'string') {
                    return () => template;
                } else if (template instanceof Node && template.tagName == 'TEMPLATE') {
                    return () => document.importNode(template.content, true);
                }
            })(ctr.template);
            if (DNAConfig.autoUpdateView) {
                wrapPrototype(this.prototype || this.__proto__, this.prototype || this.__proto__);
            }
        }
    }
    /**
     * Fires when an instance of the element is created.
     */
    createdCallback() {
        this.updateViewContent();
        super.createdCallback();
    }
    /**
     * Update Component child nodes.
     */
    updateViewContent() {
        // Render the template
        if (typeof this.render === 'function') {
            let nodes = this.render();
            let html = nodes;
            if (nodes instanceof NodeList) {
                html = '';
                for (let i = 0, len = nodes.length; i < len; i++) {
                    html += nodes[i].outerHTML;
                }
            } else if (nodes instanceof Node) {
                html = nodes.outerHTML;
            }
            html = html.replace(/[\n\r\t]/g, '').replace(/\s+/g, ' ');
            if (DNAConfig.useVirtualDOM) {
                let tmp = document.createElement('div');
                tmp.innerHTML = html;
                let tree = nodeToVDOM(tmp);
                if (!this._vtree) {
                    this.innerHTML = html;
                } else {
                    let diff = VDOM.diff(this._vtree, tree);
                    VDOM.patch(this, diff);
                }
                this._vtree = tree;
            } else {
                this.innerHTML = html;
            }
        }
    }
}

function wrapPrototype (main, currentProto = {}, handled = []) {
    Object.getOwnPropertyNames(currentProto).forEach((prop) => {
        if (typeof currentProto[prop] !== 'function' && handled.indexOf(prop) == -1) {
            handled.push(prop);
            let descriptor = Object.getOwnPropertyDescriptor(currentProto, prop) || {};
            Object.defineProperty(main, prop, {
                configurable: true,
                get: DNAHelper.wrapDescriptorGet(prop, descriptor),
                set: DNAHelper.wrapDescriptorSet(prop, descriptor, function() {
                    this.updateViewContent();
                })
            });
        }
    });
    let nextProto = currentProto.prototype || currentProto.__proto__;
    if (nextProto && nextProto !== HTMLElement.prototype) {
        wrapPrototype(main, nextProto, handled);
    }
}

function cssToObj (css) {
    let result = {};
    let attributes = css.split(';');

    for (let i = 0; i < attributes.length; i++) {
        let entry = attributes[i].split(':');
        result[entry.splice(0,1)[0]] = entry.join(':');
    }
    return result;
}

function attributesToProp (node) {
    var res = {};
    Array.prototype.forEach.call(node.attributes || [], function (attr) {
        res[attr.name] = attr.value;
    });
    return res;
}

function nodeToVDOM (node) {
    if (node.nodeType === Node.TEXT_NODE) {
        return new VDOM.VText(node.textContent);
    } else {
        return new VDOM.VNode(node.tagName, {
            attributes: attributesToProp(node)
        }, Array.prototype.map.call(node.childNodes || [], nodeToVDOM));
    }
}
<commit_msg>feat: add properties watching for `DNATemplateComponent`<commit_after>'use strict';

import { DNAConfig } from './dna-config.next.js';
import { DNAComponent } from './dna-component.next.js';
import { DNAHelper } from './dna-helper.next.js';
import VDOM from './libs/virtual-dom.next.js';

/**
 * Simple Custom Component with template handling using the `template` property.
 * @class DNATemplateComponent
 * @extends DNAComponent
 *
 * @example
 * my-component.next.js
 * ```js
 * import { DNATemplateComponent } from 'dna/component';
 * export class MyComponent extends DNATemplateComponent {
 *   static get template() {
 *     return `<h1>${this.name}</h1>`
 *   }
 *   get name() {
 *     return 'Newton'
 *   }
 * }
 * ```
 * app.next.js
 * ```js
 * import { Register } from 'dna/component';
 * import { MyComponent } from './components/my-component/my-component.next.js';
 * var MyElement = Register(MyComponent);
 * var element = new MyElement();
 * console.log(element.innerHTML); // logs "<h1>Newton</h1>"
 * ```
 */
export class DNATemplateComponent extends DNAComponent {
    /**
     * Fires when an the element is registered.
     */
    static onRegister(...args) {
        // Create render function
        let ctr = this;
        if (this.template) {
            ctr.prototype.render = ((template) => {
                if (typeof template === 'function') {
                    return function() {
                        return template.call(this);
                    }
                } else if (typeof template == 'string') {
                    return () => template;
                } else if (template instanceof Node && template.tagName == 'TEMPLATE') {
                    return () => document.importNode(template.content, true);
                }
            })(ctr.template);
            let propertiesToWatch = this.properties || [];
            propertiesToWatch.forEach((prop) => {
                let descriptor = DNAHelper.getDescriptor(this.prototype, prop) || {};
                Object.defineProperty(this.prototype, prop, {
                    configurable: true,
                    get: DNAHelper.wrapDescriptorGet(prop, descriptor),
                    set: DNAHelper.wrapDescriptorSet(prop, descriptor)
                });
            });
            if (DNAConfig.autoUpdateView) {
                wrapPrototype(this.prototype || this.__proto__, this.prototype || this.__proto__);
            }
        }
    }
    /**
     * Fires when an instance of the element is created.
     */
    createdCallback() {
        this.updateViewContent();
        super.createdCallback();
    }
    /**
     * Update Component child nodes.
     */
    updateViewContent() {
        // Render the template
        if (typeof this.render === 'function') {
            let nodes = this.render();
            let html = nodes;
            if (nodes instanceof NodeList) {
                html = '';
                for (let i = 0, len = nodes.length; i < len; i++) {
                    html += nodes[i].outerHTML;
                }
            } else if (nodes instanceof Node) {
                html = nodes.outerHTML;
            }
            html = html.replace(/[\n\r\t]/g, '').replace(/\s+/g, ' ');
            if (DNAConfig.useVirtualDOM) {
                let tmp = document.createElement('div');
                tmp.innerHTML = html;
                let tree = nodeToVDOM(tmp);
                if (!this._vtree) {
                    this.innerHTML = html;
                } else {
                    let diff = VDOM.diff(this._vtree, tree);
                    VDOM.patch(this, diff);
                }
                this._vtree = tree;
            } else {
                this.innerHTML = html;
            }
        }
    }
}

function wrapPrototype (main, currentProto = {}, handled = []) {
    Object.getOwnPropertyNames(currentProto).forEach((prop) => {
        if (typeof currentProto[prop] !== 'function' && handled.indexOf(prop) == -1) {
            handled.push(prop);
            let descriptor = Object.getOwnPropertyDescriptor(currentProto, prop) || {};
            Object.defineProperty(main, prop, {
                configurable: true,
                get: DNAHelper.wrapDescriptorGet(prop, descriptor),
                set: DNAHelper.wrapDescriptorSet(prop, descriptor, function() {
                    this.updateViewContent();
                })
            });
        }
    });
    let nextProto = currentProto.prototype || currentProto.__proto__;
    if (nextProto && nextProto !== HTMLElement.prototype) {
        wrapPrototype(main, nextProto, handled);
    }
}

function cssToObj (css) {
    let result = {};
    let attributes = css.split(';');

    for (let i = 0; i < attributes.length; i++) {
        let entry = attributes[i].split(':');
        result[entry.splice(0,1)[0]] = entry.join(':');
    }
    return result;
}

function attributesToProp (node) {
    var res = {};
    Array.prototype.forEach.call(node.attributes || [], function (attr) {
        res[attr.name] = attr.value;
    });
    return res;
}

function nodeToVDOM (node) {
    if (node.nodeType === Node.TEXT_NODE) {
        return new VDOM.VText(node.textContent);
    } else {
        return new VDOM.VNode(node.tagName, {
            attributes: attributesToProp(node)
        }, Array.prototype.map.call(node.childNodes || [], nodeToVDOM));
    }
}
<|endoftext|>


<commit_before>/* global URL */

const { isNumber, compact } = require('lodash');
const he = require('he');
const nodeUrl = require('url');
const LinkifyIt = require('linkify-it');

const linkify = LinkifyIt();
const { concatenateBytes, getViewOfArrayBuffer } = require('./crypto');

module.exports = {
  assembleChunks,
  findLinks,
  getChunkPattern,
  getDomain,
  getTitleMetaTag,
  getImageMetaTag,
  isLinkInWhitelist,
  isMediaLinkInWhitelist,
  isLinkSneaky,
  isStickerPack,
};

const SUPPORTED_DOMAINS = [
  'youtube.com',
  'www.youtube.com',
  'm.youtube.com',
  'youtu.be',
  'reddit.com',
  'www.reddit.com',
  'm.reddit.com',
  'imgur.com',
  'www.imgur.com',
  'm.imgur.com',
  'instagram.com',
  'www.instagram.com',
  'm.instagram.com',
  'pinterest.com',
  'www.pinterest.com',
  'pin.it',
  'signal.org',
];

function isLinkInWhitelist(link) {
  try {
    const url = new URL(link);

    if (url.protocol !== 'https:') {
      return false;
    }

    if (!url.pathname || url.pathname.length < 2) {
      return false;
    }

    const lowercase = url.host.toLowerCase();
    if (!SUPPORTED_DOMAINS.includes(lowercase)) {
      return false;
    }

    return true;
  } catch (error) {
    return false;
  }
}

function isStickerPack(link) {
  return (link || '').startsWith('https://signal.org/addstickers/');
}

const SUPPORTED_MEDIA_DOMAINS = /^([^.]+\.)*(ytimg.com|cdninstagram.com|redd.it|imgur.com|fbcdn.net|pinimg.com)$/i;
function isMediaLinkInWhitelist(link) {
  try {
    const url = new URL(link);

    if (url.protocol !== 'https:') {
      return false;
    }

    if (!url.pathname || url.pathname.length < 2) {
      return false;
    }

    if (!SUPPORTED_MEDIA_DOMAINS.test(url.host)) {
      return false;
    }

    return true;
  } catch (error) {
    return false;
  }
}

const META_TITLE = /<meta\s+property="og:title"[^>]+?content="([\s\S]+?)"[^>]*>/im;
const META_IMAGE = /<meta\s+property="og:image"[^>]+?content="([\s\S]+?)"[^>]*>/im;
function _getMetaTag(html, regularExpression) {
  const match = regularExpression.exec(html);
  if (match && match[1]) {
    return he.decode(match[1]).trim();
  }

  return null;
}

function getTitleMetaTag(html) {
  return _getMetaTag(html, META_TITLE);
}
function getImageMetaTag(html) {
  return _getMetaTag(html, META_IMAGE);
}

function findLinks(text, caretLocation) {
  const haveCaretLocation = isNumber(caretLocation);
  const textLength = text ? text.length : 0;

  const matches = linkify.match(text || '') || [];
  return compact(
    matches.map(match => {
      if (!haveCaretLocation) {
        return match.text;
      }

      if (match.lastIndex === textLength && caretLocation === textLength) {
        return match.text;
      }

      if (match.index > caretLocation || match.lastIndex < caretLocation) {
        return match.text;
      }

      return null;
    })
  );
}

function getDomain(url) {
  try {
    const urlObject = new URL(url);
    return urlObject.hostname;
  } catch (error) {
    return null;
  }
}

const MB = 1024 * 1024;
const KB = 1024;

function getChunkPattern(size, initialOffset) {
  if (size > MB) {
    return _getRequestPattern(size, MB, initialOffset);
  } else if (size > 500 * KB) {
    return _getRequestPattern(size, 500 * KB, initialOffset);
  } else if (size > 100 * KB) {
    return _getRequestPattern(size, 100 * KB, initialOffset);
  } else if (size > 50 * KB) {
    return _getRequestPattern(size, 50 * KB, initialOffset);
  } else if (size > 10 * KB) {
    return _getRequestPattern(size, 10 * KB, initialOffset);
  } else if (size > KB) {
    return _getRequestPattern(size, KB, initialOffset);
  }

  return {
    start: {
      start: initialOffset,
      end: size - 1,
    },
  };
}

function _getRequestPattern(size, increment, initialOffset) {
  const results = [];

  let offset = initialOffset || 0;
  while (size - offset > increment) {
    results.push({
      start: offset,
      end: offset + increment - 1,
      overlap: 0,
    });
    offset += increment;
  }

  if (size - offset > 0) {
    results.push({
      start: size - increment,
      end: size - 1,
      overlap: increment - (size - offset),
    });
  }

  return results;
}

function assembleChunks(chunkDescriptors) {
  const chunks = chunkDescriptors.map((chunk, index) => {
    if (index !== chunkDescriptors.length - 1) {
      return chunk.data;
    }

    if (!chunk.overlap) {
      return chunk.data;
    }

    return getViewOfArrayBuffer(
      chunk.data,
      chunk.overlap,
      chunk.data.byteLength
    );
  });

  return concatenateBytes(...chunks);
}

const LATIN_PATTERN = new RegExp(
  '[' +
    '\\u0041-\\u005A' +
    '\\u0061-\\u007A' +
    '\\u00AA' +
    '\\u00BA' +
    '\\u00C0-\\u00DC' +
    '\\u00D8-\\u00F6' +
    '\\u00F8-\\u01BA' +
    ']'
);

const CYRILLIC_PATTERN = new RegExp(
  '[' +
    '\\u0400-\\u0481' +
    '\\u0482' +
    '\\u0483-\\u0484' +
    '\\u0487' +
    '\\u0488-\\u0489' +
    '\\u048A-\\u052F' +
    '\\u1C80-\\u1C88' +
    '\\u1D2B' +
    '\\u1D78' +
    '\\u2DE0-\\u2DFF' +
    '\\uA640-\\uA66D' +
    '\\uA66E' +
    '\\uA66F' +
    '\\uA670-\\uA672' +
    '\\uA673' +
    '\\uA674-\\uA67D' +
    '\\uA67E' +
    '\\uA67F' +
    '\\uA680-\\uA69B' +
    '\\uA69C-\\uA69D' +
    '\\uA69E-\\uA69F' +
    '\\uFE2E-\\uFE2F' +
    ']'
);

const GREEK_PATTERN = new RegExp(
  '[' +
    '\\u0370-\\u0373' +
    '\\u0375' +
    '\\u0376-\\u0377' +
    '\\u037A' +
    '\\u037B-\\u037D' +
    '\\u037F' +
    '\\u0384' +
    '\\u0386' +
    '\\u0388-\\u038A' +
    '\\u038C' +
    '\\u038E-\\u03A1' +
    '\\u03A3-\\u03E1' +
    '\\u03F0-\\u03F5' +
    '\\u03F6' +
    '\\u03F7-\\u03FF' +
    '\\u1D26-\\u1D2A' +
    '\\u1D5D-\\u1D61' +
    '\\u1D66-\\u1D6A' +
    '\\u1DBF' +
    '\\u1F00-\\u1F15' +
    '\\u1F18-\\u1F1D' +
    '\\u1F20-\\u1F45' +
    '\\u1F48-\\u1F4D' +
    '\\u1F50-\\u1F57' +
    '\\u1F59' +
    '\\u1F5B' +
    '\\u1F5D' +
    '\\u1F5F-\\u1F7D' +
    '\\u1F80-\\u1FB4' +
    '\\u1FB6-\\u1FBC' +
    '\\u1FBD' +
    '\\u1FBE' +
    '\\u1FBF-\\u1FC1' +
    '\\u1FC2-\\u1FC4' +
    '\\u1FC6-\\u1FCC' +
    '\\u1FCD-\\u1FCF' +
    '\\u1FD0-\\u1FD3' +
    '\\u1FD6-\\u1FDB' +
    '\\u1FDD-\\u1FDF' +
    '\\u1FE0-\\u1FEC' +
    '\\u1FED-\\u1FEF' +
    '\\u1FF2-\\u1FF4' +
    '\\u1FF6-\\u1FFC' +
    '\\u1FFD-\\u1FFE' +
    '\\u2126' +
    '\\uAB65' +
    ']'
);

const HIGH_GREEK_PATTERN = new RegExp(
  '[' +
    `${String.fromCodePoint(0x10140)}-${String.fromCodePoint(0x10174)}` +
    `${String.fromCodePoint(0x10175)}-${String.fromCodePoint(0x10178)}` +
    `${String.fromCodePoint(0x10179)}-${String.fromCodePoint(0x10189)}` +
    `${String.fromCodePoint(0x1018a)}-${String.fromCodePoint(0x1018b)}` +
    `${String.fromCodePoint(0x1018c)}-${String.fromCodePoint(0x1018e)}` +
    `${String.fromCodePoint(0x101a0)}` +
    `${String.fromCodePoint(0x1d200)}-${String.fromCodePoint(0x1d241)}` +
    `${String.fromCodePoint(0x1d242)}-${String.fromCodePoint(0x1d244)}` +
    `${String.fromCodePoint(0x1d245)}` +
    ']',
  'u'
);

function isChunkSneaky(chunk) {
  const hasLatin = LATIN_PATTERN.test(chunk);
  if (!hasLatin) {
    return false;
  }

  const hasCyrillic = CYRILLIC_PATTERN.test(chunk);
  if (hasCyrillic) {
    return true;
  }

  const hasGreek = GREEK_PATTERN.test(chunk);
  if (hasGreek) {
    return true;
  }

  const hasHighGreek = HIGH_GREEK_PATTERN.test(chunk);
  if (hasHighGreek) {
    return true;
  }

  return false;
}

function isLinkSneaky(link) {
  const domain = getDomain(link);

  // This is necesary because getDomain returns domains in punycode form. We check whether
  //   it's available for the StyleGuide.
  const unicodeDomain = nodeUrl.domainToUnicode
    ? nodeUrl.domainToUnicode(domain)
    : domain;

  const chunks = unicodeDomain.split('.');
  for (let i = 0, max = chunks.length; i < max; i += 1) {
    const chunk = chunks[i];
    if (isChunkSneaky(chunk)) {
      return true;
    }
  }

  return false;
}
<commit_msg>Fix SUPPORTED_MEDIA_DOMAINS regex whitelist (#3459)<commit_after>/* global URL */

const { isNumber, compact } = require('lodash');
const he = require('he');
const nodeUrl = require('url');
const LinkifyIt = require('linkify-it');

const linkify = LinkifyIt();
const { concatenateBytes, getViewOfArrayBuffer } = require('./crypto');

module.exports = {
  assembleChunks,
  findLinks,
  getChunkPattern,
  getDomain,
  getTitleMetaTag,
  getImageMetaTag,
  isLinkInWhitelist,
  isMediaLinkInWhitelist,
  isLinkSneaky,
  isStickerPack,
};

const SUPPORTED_DOMAINS = [
  'youtube.com',
  'www.youtube.com',
  'm.youtube.com',
  'youtu.be',
  'reddit.com',
  'www.reddit.com',
  'm.reddit.com',
  'imgur.com',
  'www.imgur.com',
  'm.imgur.com',
  'instagram.com',
  'www.instagram.com',
  'm.instagram.com',
  'pinterest.com',
  'www.pinterest.com',
  'pin.it',
  'signal.org',
];

function isLinkInWhitelist(link) {
  try {
    const url = new URL(link);

    if (url.protocol !== 'https:') {
      return false;
    }

    if (!url.pathname || url.pathname.length < 2) {
      return false;
    }

    const lowercase = url.host.toLowerCase();
    if (!SUPPORTED_DOMAINS.includes(lowercase)) {
      return false;
    }

    return true;
  } catch (error) {
    return false;
  }
}

function isStickerPack(link) {
  return (link || '').startsWith('https://signal.org/addstickers/');
}

const SUPPORTED_MEDIA_DOMAINS = /^([^.]+\.)*(ytimg\.com|cdninstagram\.com|redd\.it|imgur\.com|fbcdn\.net|pinimg\.com)$/i;
function isMediaLinkInWhitelist(link) {
  try {
    const url = new URL(link);

    if (url.protocol !== 'https:') {
      return false;
    }

    if (!url.pathname || url.pathname.length < 2) {
      return false;
    }

    if (!SUPPORTED_MEDIA_DOMAINS.test(url.host)) {
      return false;
    }

    return true;
  } catch (error) {
    return false;
  }
}

const META_TITLE = /<meta\s+property="og:title"[^>]+?content="([\s\S]+?)"[^>]*>/im;
const META_IMAGE = /<meta\s+property="og:image"[^>]+?content="([\s\S]+?)"[^>]*>/im;
function _getMetaTag(html, regularExpression) {
  const match = regularExpression.exec(html);
  if (match && match[1]) {
    return he.decode(match[1]).trim();
  }

  return null;
}

function getTitleMetaTag(html) {
  return _getMetaTag(html, META_TITLE);
}
function getImageMetaTag(html) {
  return _getMetaTag(html, META_IMAGE);
}

function findLinks(text, caretLocation) {
  const haveCaretLocation = isNumber(caretLocation);
  const textLength = text ? text.length : 0;

  const matches = linkify.match(text || '') || [];
  return compact(
    matches.map(match => {
      if (!haveCaretLocation) {
        return match.text;
      }

      if (match.lastIndex === textLength && caretLocation === textLength) {
        return match.text;
      }

      if (match.index > caretLocation || match.lastIndex < caretLocation) {
        return match.text;
      }

      return null;
    })
  );
}

function getDomain(url) {
  try {
    const urlObject = new URL(url);
    return urlObject.hostname;
  } catch (error) {
    return null;
  }
}

const MB = 1024 * 1024;
const KB = 1024;

function getChunkPattern(size, initialOffset) {
  if (size > MB) {
    return _getRequestPattern(size, MB, initialOffset);
  } else if (size > 500 * KB) {
    return _getRequestPattern(size, 500 * KB, initialOffset);
  } else if (size > 100 * KB) {
    return _getRequestPattern(size, 100 * KB, initialOffset);
  } else if (size > 50 * KB) {
    return _getRequestPattern(size, 50 * KB, initialOffset);
  } else if (size > 10 * KB) {
    return _getRequestPattern(size, 10 * KB, initialOffset);
  } else if (size > KB) {
    return _getRequestPattern(size, KB, initialOffset);
  }

  return {
    start: {
      start: initialOffset,
      end: size - 1,
    },
  };
}

function _getRequestPattern(size, increment, initialOffset) {
  const results = [];

  let offset = initialOffset || 0;
  while (size - offset > increment) {
    results.push({
      start: offset,
      end: offset + increment - 1,
      overlap: 0,
    });
    offset += increment;
  }

  if (size - offset > 0) {
    results.push({
      start: size - increment,
      end: size - 1,
      overlap: increment - (size - offset),
    });
  }

  return results;
}

function assembleChunks(chunkDescriptors) {
  const chunks = chunkDescriptors.map((chunk, index) => {
    if (index !== chunkDescriptors.length - 1) {
      return chunk.data;
    }

    if (!chunk.overlap) {
      return chunk.data;
    }

    return getViewOfArrayBuffer(
      chunk.data,
      chunk.overlap,
      chunk.data.byteLength
    );
  });

  return concatenateBytes(...chunks);
}

const LATIN_PATTERN = new RegExp(
  '[' +
    '\\u0041-\\u005A' +
    '\\u0061-\\u007A' +
    '\\u00AA' +
    '\\u00BA' +
    '\\u00C0-\\u00DC' +
    '\\u00D8-\\u00F6' +
    '\\u00F8-\\u01BA' +
    ']'
);

const CYRILLIC_PATTERN = new RegExp(
  '[' +
    '\\u0400-\\u0481' +
    '\\u0482' +
    '\\u0483-\\u0484' +
    '\\u0487' +
    '\\u0488-\\u0489' +
    '\\u048A-\\u052F' +
    '\\u1C80-\\u1C88' +
    '\\u1D2B' +
    '\\u1D78' +
    '\\u2DE0-\\u2DFF' +
    '\\uA640-\\uA66D' +
    '\\uA66E' +
    '\\uA66F' +
    '\\uA670-\\uA672' +
    '\\uA673' +
    '\\uA674-\\uA67D' +
    '\\uA67E' +
    '\\uA67F' +
    '\\uA680-\\uA69B' +
    '\\uA69C-\\uA69D' +
    '\\uA69E-\\uA69F' +
    '\\uFE2E-\\uFE2F' +
    ']'
);

const GREEK_PATTERN = new RegExp(
  '[' +
    '\\u0370-\\u0373' +
    '\\u0375' +
    '\\u0376-\\u0377' +
    '\\u037A' +
    '\\u037B-\\u037D' +
    '\\u037F' +
    '\\u0384' +
    '\\u0386' +
    '\\u0388-\\u038A' +
    '\\u038C' +
    '\\u038E-\\u03A1' +
    '\\u03A3-\\u03E1' +
    '\\u03F0-\\u03F5' +
    '\\u03F6' +
    '\\u03F7-\\u03FF' +
    '\\u1D26-\\u1D2A' +
    '\\u1D5D-\\u1D61' +
    '\\u1D66-\\u1D6A' +
    '\\u1DBF' +
    '\\u1F00-\\u1F15' +
    '\\u1F18-\\u1F1D' +
    '\\u1F20-\\u1F45' +
    '\\u1F48-\\u1F4D' +
    '\\u1F50-\\u1F57' +
    '\\u1F59' +
    '\\u1F5B' +
    '\\u1F5D' +
    '\\u1F5F-\\u1F7D' +
    '\\u1F80-\\u1FB4' +
    '\\u1FB6-\\u1FBC' +
    '\\u1FBD' +
    '\\u1FBE' +
    '\\u1FBF-\\u1FC1' +
    '\\u1FC2-\\u1FC4' +
    '\\u1FC6-\\u1FCC' +
    '\\u1FCD-\\u1FCF' +
    '\\u1FD0-\\u1FD3' +
    '\\u1FD6-\\u1FDB' +
    '\\u1FDD-\\u1FDF' +
    '\\u1FE0-\\u1FEC' +
    '\\u1FED-\\u1FEF' +
    '\\u1FF2-\\u1FF4' +
    '\\u1FF6-\\u1FFC' +
    '\\u1FFD-\\u1FFE' +
    '\\u2126' +
    '\\uAB65' +
    ']'
);

const HIGH_GREEK_PATTERN = new RegExp(
  '[' +
    `${String.fromCodePoint(0x10140)}-${String.fromCodePoint(0x10174)}` +
    `${String.fromCodePoint(0x10175)}-${String.fromCodePoint(0x10178)}` +
    `${String.fromCodePoint(0x10179)}-${String.fromCodePoint(0x10189)}` +
    `${String.fromCodePoint(0x1018a)}-${String.fromCodePoint(0x1018b)}` +
    `${String.fromCodePoint(0x1018c)}-${String.fromCodePoint(0x1018e)}` +
    `${String.fromCodePoint(0x101a0)}` +
    `${String.fromCodePoint(0x1d200)}-${String.fromCodePoint(0x1d241)}` +
    `${String.fromCodePoint(0x1d242)}-${String.fromCodePoint(0x1d244)}` +
    `${String.fromCodePoint(0x1d245)}` +
    ']',
  'u'
);

function isChunkSneaky(chunk) {
  const hasLatin = LATIN_PATTERN.test(chunk);
  if (!hasLatin) {
    return false;
  }

  const hasCyrillic = CYRILLIC_PATTERN.test(chunk);
  if (hasCyrillic) {
    return true;
  }

  const hasGreek = GREEK_PATTERN.test(chunk);
  if (hasGreek) {
    return true;
  }

  const hasHighGreek = HIGH_GREEK_PATTERN.test(chunk);
  if (hasHighGreek) {
    return true;
  }

  return false;
}

function isLinkSneaky(link) {
  const domain = getDomain(link);

  // This is necesary because getDomain returns domains in punycode form. We check whether
  //   it's available for the StyleGuide.
  const unicodeDomain = nodeUrl.domainToUnicode
    ? nodeUrl.domainToUnicode(domain)
    : domain;

  const chunks = unicodeDomain.split('.');
  for (let i = 0, max = chunks.length; i < max; i += 1) {
    const chunk = chunks[i];
    if (isChunkSneaky(chunk)) {
      return true;
    }
  }

  return false;
}
<|endoftext|>


<commit_before>// Copyright 2015-2016, University of Colorado Boulder

/**
 * Focus highlight overlay for accessible displays.
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */

define( function( require ) {
  'use strict';

  var inherit = require( 'PHET_CORE/inherit' );

  var Color = require( 'SCENERY/util/Color' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Path = require( 'SCENERY/nodes/Path' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var scenery = require( 'SCENERY/scenery' );
  var Shape = require( 'KITE/Shape' );
  var TransformTracker = require( 'SCENERY/util/TransformTracker' );
  var Vector2 = require( 'DOT/Vector2' );

 // determined by inspection, base widths of focus highlight, transform of shape/bounds will change highlight line width
  var INNER_LINE_WIDTH_BASE = 2.5;
  var OUTER_LINE_WIDTH_BASE = 4;

  /**
   * @constructor
   *
   * @param {Display} display
   * @param {Node} focusRootNode - the root node of our display
   */
  function FocusOverlay( display, focusRootNode ) {
    this.display = display; // @private {Display}
    this.focusRootNode = focusRootNode; // @private {Node} - The root Node of our child display

    // When the focus changes, all of these are modified.
    this.trail = null; // @private {Trail|null}
    this.node = null; // @private {Node|null}
    this.mode = null; // @private {String|null} - defaults to bounds but can be overwritten by the node's focus highlight
    this.transformTracker = null; // @private {TransformTracker|null}

    // @private {boolean} - If true, the next update() will trigger an update to the highlight's transform.
    this.transformDirty = true;

    // @private - The main node for the highlight. It will be transformed.
    this.highlightNode = new Node();
    this.focusRootNode.addChild( this.highlightNode );

    // @private {Display}
    this.focusDisplay = new scenery.Display( this.focusRootNode, {
      width: this.width,
      height: this.height,
      allowWebGL: display._allowWebGL,
      allowCSSHacks: false,
      accessibility: false,
      isApplication: false,
      interactive: false
    } );

    // @private {HTMLElement}
    this.domElement = this.focusDisplay.domElement;
    this.domElement.style.pointerEvents = 'none';

    // @private Bounds highlight
    this.boundsHighlight = new Rectangle( 0, 0, 0, 0, { stroke: FocusOverlay.focusColor, visible: false } );
    this.innerBoundsHighlight = new Rectangle( 0, 0, 0, 0, { stroke: FocusOverlay.innerFocusColor } );
    this.boundsHighlight.addChild( this.innerBoundsHighlight );

    // @private Shape highlight
    this.shapeHighlight = new Path( null, { stroke: FocusOverlay.focusColor, visible: false } );
    this.innerShapeHighlight = new Path( null, { stroke: FocusOverlay.innerFocusColor } );
    this.shapeHighlight.addChild( this.innerShapeHighlight );

    // @private Node highlight
    this.nodeHighlight = null;

    this.highlightNode.addChild( this.boundsHighlight );
    this.highlightNode.addChild( this.shapeHighlight );

    // @private - Listeners bound once, so we can access them for removal.
    this.boundsListener = this.onBoundsChange.bind( this );
    this.transformListener = this.onTransformChange.bind( this );
    this.focusListener = this.onFocusChange.bind( this );

    scenery.Display.focusProperty.link( this.focusListener );
  }

  scenery.register( 'FocusOverlay', FocusOverlay );

  inherit( Object, FocusOverlay, {
    dispose: function() {
      if ( this.hasHighlight() ) {
        this.deactivateHighlight();
      }

      scenery.Display.focusProperty.unlink( this.focusListener );
    },

    hasHighlight: function() {
      return !!this.trail;
    },

    /**
     * Activates the highlight, choosing a mode for whether the highlight will be a shape, node, or bounds.
     * @private
     *
     * @param {Trail} trail - The focused trail to highlight. It assumes that this trail is in this display.
     */
    activateHighlight: function( trail ) {
      this.trail = trail;
      this.node = trail.lastNode();
      this.transformTracker = new TransformTracker( trail, {
        isStatic: true
      } );
      this.transformTracker.addListener( this.transformListener );

      // Invisible mode - no focus highlight
      if ( this.node.accessibleContent.focusHighlight === 'invisible' ) {
        this.mode = 'invisible';
      }
      // Shape mode
      else if ( this.node.accessibleContent.focusHighlight instanceof Shape ) {
        this.mode = 'shape';

        this.shapeHighlight.visible = true;
        this.shapeHighlight.shape = this.innerShapeHighlight.shape = this.node.accessibleContent.focusHighlight;
      }
      // Node mode
      else if ( this.node.accessibleContent.focusHighlight instanceof Node ) {
        this.mode = 'node';

        // If focusHighlightLayerable, then the focusHighlight is just a node in the scene graph, so set it visible
        if ( this.node.accessibleContent.focusHighlightLayerable ) {
          this.node.accessibleContent.focusHighlight.visible = true;
        }
        else {
          this.nodeHighlight = this.node.accessibleContent.focusHighlight;

          // Use the node itself as the highlight
          this.highlightNode.addChild( this.nodeHighlight );
        }
      }
      // Bounds mode
      else {
        this.mode = 'bounds';

        this.boundsHighlight.visible = true;
        this.node.onStatic( 'localBounds', this.boundsListener );

        this.onBoundsChange();
      }

      this.transformDirty = true;
    },

    /**
     * Deactivates the current highlight, disposing and removing listeners as necessary.
     * @private
     */
    deactivateHighlight: function() {
      if ( this.mode === 'shape' ) {
        this.shapeHighlight.visible = false;
      }
      else if ( this.mode === 'node' ) {

        // If focusHighlightLayerable, then the focusHighlight is just a node in the scene graph, so set it invisible
        if ( this.node.accessibleContent.focusHighlightLayerable ) {
          this.node.accessibleContent.focusHighlight.visible = false;
        }
        else {
          this.highlightNode.removeChild( this.nodeHighlight );
          this.nodeHighlight = null;
        }
      }
      else if ( this.mode === 'bounds' ) {
        this.boundsHighlight.visible = false;
        this.node.offStatic( 'localBounds', this.boundsListener );
      }

      this.trail = null;
      this.node = null;
      this.mode = null;
      this.transformTracker.removeListener( this.transformListener );
      this.transformTracker.dispose();
    },

    // Called from FocusOverlay after transforming the highlight. Only called when the transform changes.
    afterTransform: function() {
      if ( this.mode === 'shape' ) {
        var unitShapeWidthMagnitude = this.shapeHighlight.transform.transformDelta2( Vector2.X_UNIT ).magnitude();
        this.shapeHighlight.lineWidth = OUTER_LINE_WIDTH_BASE / unitShapeWidthMagnitude;
        this.innerShapeHighlight.lineWidth = INNER_LINE_WIDTH_BASE / unitShapeWidthMagnitude;
      }
      else if ( this.mode === 'bounds' ) {
        var unitBoundsWidthMagnitude = this.boundsHighlight.transform.transformDelta2( Vector2.X_UNIT ).magnitude();
        this.boundsHighlight.lineWidth = OUTER_LINE_WIDTH_BASE / unitBoundsWidthMagnitude;
        this.innerBoundsHighlight.lineWidth = INNER_LINE_WIDTH_BASE / unitBoundsWidthMagnitude;
      }
    },

    onTransformChange: function() {
      this.transformDirty = true;
    },

    // Called when bounds change on our node when we are in "Bounds" mode
    onBoundsChange: function() {
      this.boundsHighlight.setRectBounds( FocusOverlay.getStaticFocusHighlightBounds( this.node ) );
      this.innerBoundsHighlight.setRectBounds( FocusOverlay.getStaticFocusHighlightBounds( this.node ) );
    },

    // Called when the main Scenery focus pair (Display,Trail) changes.
    onFocusChange: function( focus ) {
      var newTrail = ( focus && focus.display === this.display ) ? focus.trail : null;

      if ( this.hasHighlight() ) {
        this.deactivateHighlight();
      }

      if ( newTrail ) {
        this.activateHighlight( newTrail );
      }
    },

    update: function() {
      // Transform the highlight to match the position of the node
      if ( this.hasHighlight() && this.transformDirty ) {
        this.transformDirty = false;

        this.highlightNode.setMatrix( this.transformTracker.matrix );
        this.afterTransform();
      }

      if ( !this.display.size.equals( this.focusDisplay.size ) ) {
        this.focusDisplay.setWidthHeight( this.display.width, this.display.height );
      }
      this.focusDisplay.updateDisplay();
    }
  }, {
    focusColor: new Color( 'rgba(212,19,106,0.5)' ),
    innerFocusColor: new Color( 'rgba(250,40,135,0.9)' ),

    /**
     * Get the bounds of a focus highlight for a node. The space between the inner most edge of the focus highlight
     * and the bounds of the node will be 1 / 2 times the line width of the focus highlight. Will provide
     * consistent spacing between node and inner edge of focus highlight, see
     * see https://github.com/phetsims/scenery/issues/677
     *
     * @param {Node} node - the node that will receive the focus highlight
     * @return {Bounds2}
     */
    getStaticFocusHighlightBounds: function( node ) {
      var unitBoundsWidthMagnitude = node.transform.transformDelta2( Vector2.X_UNIT ).magnitude();
      var outerHalfWidth = ( OUTER_LINE_WIDTH_BASE / unitBoundsWidthMagnitude ) * ( 3 / 4 );
      return node.localBounds.dilated( outerHalfWidth );
    }
  } );

  return FocusOverlay;
} );
<commit_msg>change name of getStaticFocusHighlightBounds to getLocalFocusHighlightBounds, see phetsims/scenery#677<commit_after>// Copyright 2015-2016, University of Colorado Boulder

/**
 * Focus highlight overlay for accessible displays.
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */

define( function( require ) {
  'use strict';

  var inherit = require( 'PHET_CORE/inherit' );

  var Color = require( 'SCENERY/util/Color' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Path = require( 'SCENERY/nodes/Path' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var scenery = require( 'SCENERY/scenery' );
  var Shape = require( 'KITE/Shape' );
  var TransformTracker = require( 'SCENERY/util/TransformTracker' );
  var Vector2 = require( 'DOT/Vector2' );

 // determined by inspection, base widths of focus highlight, transform of shape/bounds will change highlight line width
  var INNER_LINE_WIDTH_BASE = 2.5;
  var OUTER_LINE_WIDTH_BASE = 4;

  /**
   * @constructor
   *
   * @param {Display} display
   * @param {Node} focusRootNode - the root node of our display
   */
  function FocusOverlay( display, focusRootNode ) {
    this.display = display; // @private {Display}
    this.focusRootNode = focusRootNode; // @private {Node} - The root Node of our child display

    // When the focus changes, all of these are modified.
    this.trail = null; // @private {Trail|null}
    this.node = null; // @private {Node|null}
    this.mode = null; // @private {String|null} - defaults to bounds but can be overwritten by the node's focus highlight
    this.transformTracker = null; // @private {TransformTracker|null}

    // @private {boolean} - If true, the next update() will trigger an update to the highlight's transform.
    this.transformDirty = true;

    // @private - The main node for the highlight. It will be transformed.
    this.highlightNode = new Node();
    this.focusRootNode.addChild( this.highlightNode );

    // @private {Display}
    this.focusDisplay = new scenery.Display( this.focusRootNode, {
      width: this.width,
      height: this.height,
      allowWebGL: display._allowWebGL,
      allowCSSHacks: false,
      accessibility: false,
      isApplication: false,
      interactive: false
    } );

    // @private {HTMLElement}
    this.domElement = this.focusDisplay.domElement;
    this.domElement.style.pointerEvents = 'none';

    // @private Bounds highlight
    this.boundsHighlight = new Rectangle( 0, 0, 0, 0, { stroke: FocusOverlay.focusColor, visible: false } );
    this.innerBoundsHighlight = new Rectangle( 0, 0, 0, 0, { stroke: FocusOverlay.innerFocusColor } );
    this.boundsHighlight.addChild( this.innerBoundsHighlight );

    // @private Shape highlight
    this.shapeHighlight = new Path( null, { stroke: FocusOverlay.focusColor, visible: false } );
    this.innerShapeHighlight = new Path( null, { stroke: FocusOverlay.innerFocusColor } );
    this.shapeHighlight.addChild( this.innerShapeHighlight );

    // @private Node highlight
    this.nodeHighlight = null;

    this.highlightNode.addChild( this.boundsHighlight );
    this.highlightNode.addChild( this.shapeHighlight );

    // @private - Listeners bound once, so we can access them for removal.
    this.boundsListener = this.onBoundsChange.bind( this );
    this.transformListener = this.onTransformChange.bind( this );
    this.focusListener = this.onFocusChange.bind( this );

    scenery.Display.focusProperty.link( this.focusListener );
  }

  scenery.register( 'FocusOverlay', FocusOverlay );

  inherit( Object, FocusOverlay, {
    dispose: function() {
      if ( this.hasHighlight() ) {
        this.deactivateHighlight();
      }

      scenery.Display.focusProperty.unlink( this.focusListener );
    },

    hasHighlight: function() {
      return !!this.trail;
    },

    /**
     * Activates the highlight, choosing a mode for whether the highlight will be a shape, node, or bounds.
     * @private
     *
     * @param {Trail} trail - The focused trail to highlight. It assumes that this trail is in this display.
     */
    activateHighlight: function( trail ) {
      this.trail = trail;
      this.node = trail.lastNode();
      this.transformTracker = new TransformTracker( trail, {
        isStatic: true
      } );
      this.transformTracker.addListener( this.transformListener );

      // Invisible mode - no focus highlight
      if ( this.node.accessibleContent.focusHighlight === 'invisible' ) {
        this.mode = 'invisible';
      }
      // Shape mode
      else if ( this.node.accessibleContent.focusHighlight instanceof Shape ) {
        this.mode = 'shape';

        this.shapeHighlight.visible = true;
        this.shapeHighlight.shape = this.innerShapeHighlight.shape = this.node.accessibleContent.focusHighlight;
      }
      // Node mode
      else if ( this.node.accessibleContent.focusHighlight instanceof Node ) {
        this.mode = 'node';

        // If focusHighlightLayerable, then the focusHighlight is just a node in the scene graph, so set it visible
        if ( this.node.accessibleContent.focusHighlightLayerable ) {
          this.node.accessibleContent.focusHighlight.visible = true;
        }
        else {
          this.nodeHighlight = this.node.accessibleContent.focusHighlight;

          // Use the node itself as the highlight
          this.highlightNode.addChild( this.nodeHighlight );
        }
      }
      // Bounds mode
      else {
        this.mode = 'bounds';

        this.boundsHighlight.visible = true;
        this.node.onStatic( 'localBounds', this.boundsListener );

        this.onBoundsChange();
      }

      this.transformDirty = true;
    },

    /**
     * Deactivates the current highlight, disposing and removing listeners as necessary.
     * @private
     */
    deactivateHighlight: function() {
      if ( this.mode === 'shape' ) {
        this.shapeHighlight.visible = false;
      }
      else if ( this.mode === 'node' ) {

        // If focusHighlightLayerable, then the focusHighlight is just a node in the scene graph, so set it invisible
        if ( this.node.accessibleContent.focusHighlightLayerable ) {
          this.node.accessibleContent.focusHighlight.visible = false;
        }
        else {
          this.highlightNode.removeChild( this.nodeHighlight );
          this.nodeHighlight = null;
        }
      }
      else if ( this.mode === 'bounds' ) {
        this.boundsHighlight.visible = false;
        this.node.offStatic( 'localBounds', this.boundsListener );
      }

      this.trail = null;
      this.node = null;
      this.mode = null;
      this.transformTracker.removeListener( this.transformListener );
      this.transformTracker.dispose();
    },

    // Called from FocusOverlay after transforming the highlight. Only called when the transform changes.
    afterTransform: function() {
      if ( this.mode === 'shape' ) {
        var unitShapeWidthMagnitude = this.shapeHighlight.transform.transformDelta2( Vector2.X_UNIT ).magnitude();
        this.shapeHighlight.lineWidth = OUTER_LINE_WIDTH_BASE / unitShapeWidthMagnitude;
        this.innerShapeHighlight.lineWidth = INNER_LINE_WIDTH_BASE / unitShapeWidthMagnitude;
      }
      else if ( this.mode === 'bounds' ) {
        var unitBoundsWidthMagnitude = this.boundsHighlight.transform.transformDelta2( Vector2.X_UNIT ).magnitude();
        this.boundsHighlight.lineWidth = OUTER_LINE_WIDTH_BASE / unitBoundsWidthMagnitude;
        this.innerBoundsHighlight.lineWidth = INNER_LINE_WIDTH_BASE / unitBoundsWidthMagnitude;
      }
    },

    onTransformChange: function() {
      this.transformDirty = true;
    },

    // Called when bounds change on our node when we are in "Bounds" mode
    onBoundsChange: function() {
      this.boundsHighlight.setRectBounds( FocusOverlay.getStaticFocusHighlightBounds( this.node ) );
      this.innerBoundsHighlight.setRectBounds( FocusOverlay.getStaticFocusHighlightBounds( this.node ) );
    },

    // Called when the main Scenery focus pair (Display,Trail) changes.
    onFocusChange: function( focus ) {
      var newTrail = ( focus && focus.display === this.display ) ? focus.trail : null;

      if ( this.hasHighlight() ) {
        this.deactivateHighlight();
      }

      if ( newTrail ) {
        this.activateHighlight( newTrail );
      }
    },

    update: function() {
      // Transform the highlight to match the position of the node
      if ( this.hasHighlight() && this.transformDirty ) {
        this.transformDirty = false;

        this.highlightNode.setMatrix( this.transformTracker.matrix );
        this.afterTransform();
      }

      if ( !this.display.size.equals( this.focusDisplay.size ) ) {
        this.focusDisplay.setWidthHeight( this.display.width, this.display.height );
      }
      this.focusDisplay.updateDisplay();
    }
  }, {
    focusColor: new Color( 'rgba(212,19,106,0.5)' ),
    innerFocusColor: new Color( 'rgba(250,40,135,0.9)' ),

    /**
     * Get the bounds of a focus highlight for a node. The space between the inner most edge of the focus highlight
     * and the bounds of the node will be 1 / 2 times the line width of the focus highlight. Returns highlight bounds
     * based on the local bounds of the Node provided.  Will provide consistent spacing between node and inner edge of
     * focus highlight, see https://github.com/phetsims/scenery/issues/677
     *
     * @param {Node} node - the node that will receive the focus highlight
     * @return {Bounds2}
     */
    getLocalFocusHighlightBounds: function( node ) {
      var unitBoundsWidthMagnitude = node.transform.transformDelta2( Vector2.X_UNIT ).magnitude();
      var outerHalfWidth = ( OUTER_LINE_WIDTH_BASE / unitBoundsWidthMagnitude ) * ( 3 / 4 );
      return node.localBounds.dilated( outerHalfWidth );
    }
  } );

  return FocusOverlay;
} );
<|endoftext|>


<commit_before>
cstApp.controller('resourceConfig', ['$scope', 'resource', function($scope, resource) {
    $scope.resource = resource.getData();
    $scope.addMetric = { name: '', url: '', selector: '', regex: '' };
    $scope.export = { pretty: false };

    $scope.$on('resourceUpdate', function(event, data) {
        $scope.resource = resource.getData();
        if (data.apply) $scope.$apply();
    });

    $scope.urlAdd = function() {
        resource.addUrl({ url: '' });
    };
    $scope.export = function() {
        var resourceObject = JSON.stringify(resource.getData(), null, ($scope.export.pretty * 4));
        $('.cst-resource-config .cst-import-export textarea').val(resourceObject);
    }
    $scope.urlRemove = function(index) {
        resource.removeUrl(index);
    };

    $scope.metricAdd = function() {
        var result = resource.addMetric({
            name: $scope.addMetric.name,
            url: $scope.addMetric.url,
            selector: $scope.addMetric.selector,
            regex: $scope.addMetric.regex
        });
        if (!result.success) {
            $('#saveConfirmResource').html('
xFailed to add metric: '+result.message+'
');
        }
    };

    $scope.metricRemove = function(index) {
        resource.removeMetric(index);
    };

    $scope.saveResource = function() {
        var result = resource.setResource($scope.resource);
        if (result.success) {
            resource.save(function(result) {
                if (result.success) {
                    $('#saveConfirmResource').html('
xSaved!
');
                } else {
                    $('#saveConfirmResource').html('
xFailed to save: '+result.message+'
');
                }
            });
        } else {
            $('#saveConfirmResource').html('
xFailed to save: '+result.message+'
');
        }
    };

}]);
<commit_msg>Minor fix.<commit_after>
cstApp.controller('resourceConfig', ['$scope', 'resource', function($scope, resource) {
    $scope.resource = resource.getData();
    $scope.addMetric = { name: '', url: '', selector: '', regex: '' };
    $scope.export = { pretty: false };

    $scope.$on('resourceUpdate', function(event, data) {
        $scope.resource = resource.getData();
        if (data.apply) $scope.$apply();
    });

    $scope.urlAdd = function() {
        resource.addUrl({ url: '' });
    };
    $scope.export = function() {
        var resourceObject = JSON.stringify(resource.getData(), null, ($scope.export.pretty * 4));
        $('.cst-resource-config .cst-import-export textarea').val(resourceObject);
    }
    $scope.urlRemove = function(index) {
        resource.removeUrl(index);
    };

    $scope.metricAdd = function() {
        var result = resource.addMetric({
            name: $scope.addMetric.name,
            url: $scope.addMetric.url,
            selector: $scope.addMetric.selector,
            regex: $scope.addMetric.regex
        });
        if (!result.success) {
            $('#saveConfirmResource').html('
xFailed to add metric: '+result.message+'
');
        } else {
            $scope.addMetric = { name: '', url: '', selector: '', regex: '' };
        }
    };

    $scope.metricRemove = function(index) {
        resource.removeMetric(index);
    };

    $scope.saveResource = function() {
        var result = resource.setResource($scope.resource);
        if (result.success) {
            resource.save(function(result) {
                if (result.success) {
                    $('#saveConfirmResource').html('
xSaved!
');
                } else {
                    $('#saveConfirmResource').html('
xFailed to save: '+result.message+'
');
                }
            });
        } else {
            $('#saveConfirmResource').html('
xFailed to save: '+result.message+'
');
        }
    };

}]);
<|endoftext|>


<commit_before>var __ = require('underscore'),
    Backbone = require('backbone'),
    $ = require('jquery'),
    app = require('../App').getApp();

/*eslint no-use-before-define:0*/
module.exports = function (currency, callback) {
    //some APIs require currency to be upper case
    currency = currency.toUpperCase();

    var showStatus,
        btPrices = [],
        btcAverages = {rates: {}};

    //if this is the first check, show status
    if(!window.btcAverages){
        showStatus = app.statusBar.pushMessage({
            msg: polyglot.t('LoadingBitcoinPrices'),
            duration: false
        });
    }

    window.btcAverages = window.btcAverages || {};

    var callBlockchain = function () {
        $.ajax({
            method: "GET",
            url: "https://blockchain.info/ticker",
            timeOut: 4000
        })
            .done(function (response) {
                var BlockchainCurrencies = {};
                for (var bcCurrency in response) {
                    if (response.hasOwnProperty(bcCurrency)) {
                        BlockchainCurrencies[bcCurrency] = response[bcCurrency]['15m'];
                    }
                }
                btPrices.push(BlockchainCurrencies);
            })
            .fail(function (jqXHR, textStatus, errorThrown) {
                logAPIErrorInfo("Blockchain", jqXHR, textStatus, errorThrown);
            })
            .always(function () {
                callCoinkite();
            });
    };

    var callCoinkite = function () {
        $.ajax({
            method: "GET",
            url: "https://api.coinkite.com/public/rates",
            timeOut: 4000
        })
            .done(function (response) {
                var CoinkiteCurrencies = {};
                for (var ckCurrency in response.rates.BTC) {
                    if (response.rates.BTC.hasOwnProperty(ckCurrency)) {
                        CoinkiteCurrencies[ckCurrency] = response.rates.BTC[ckCurrency].rate;
                    }
                }
                btPrices.push(CoinkiteCurrencies);
            })
            .fail(function (jqXHR, textStatus, errorThrown) {
                logAPIErrorInfo("Coinkite", jqXHR, textStatus, errorThrown);
            })
            .always(function () {
                callBitcoinAvg();
            });

    };

    var callBitcoinAvg = function () {
        $.ajax({
            method: "GET",
            url: "https://api.bitcoinaverage.com/all",
            timeOut: 4000
        })
            .done(function (response) {
                var BitcoinAvgCurrencies = {};
                for (var bcaCurrency in response) {
                    if (response[bcaCurrency].averages) {
                        BitcoinAvgCurrencies[bcaCurrency] = response[bcaCurrency].averages['24h_avg'];
                    }
                }
                btPrices.push(BitcoinAvgCurrencies);
            })
            .fail(function (jqXHR, textStatus, errorThrown) {
                logAPIErrorInfo("BitcoinAverage", jqXHR, textStatus, errorThrown);
            })
            .always(function () {
                callBitcoinCharts();
            });
    };

    var callBitcoinCharts = function () {
        $.ajax({
            method: "GET",
            url: "http://api.bitcoincharts.com/v1/weighted_prices.json",
            timeOut: 4000
        })
            .done(function (response) {
                response = JSON.parse(response);
                var BitcoinChartsCurrencies = {};
                for (var bccCurrency in response) {
                    if (response.hasOwnProperty(bccCurrency)) {
                        BitcoinChartsCurrencies[bccCurrency] = response[bccCurrency]['24h'];
                    }
                }
                btPrices.push(BitcoinChartsCurrencies);
            })
            .fail(function (jqXHR, textStatus, errorThrown) {
                logAPIErrorInfo("Bitcoin Charts", jqXHR, textStatus, errorThrown);
            })
            .always(function () {
                makeAveragePrice();
            });
    };

    if(currency == "BTC" && window.currencyKeys) {
        typeof callback === 'function' && callback(1, window.currencyKeys);
    } else if (window.btcAverages.timeStamp
        && Math.floor((new Date() - window.btcAverages.timeStamp) / 60000) < 15
        && window.currencyKeys) {
        typeof callback === 'function' && callback(window.btcAverages.rates[currency], window.currencyKeys);
    } else {
        callBlockchain();
    }

    var makeAveragePrice = function () {
        var sum,
            currencyPrices,
            currency_code,
            currencyKeys,
            averagePrice,
            keys = {},
            btAve;
        btcAverages.timeStamp = new Date();
        for (var i in btPrices) {
            if (btPrices.hasOwnProperty(i)) {
                keys = $.extend(keys, btPrices[i]);
            }
        }
        if (btPrices.length === 0) {
            alert("Bitcoin exchange rates are not available.");
        }
        currencyKeys = Object.keys(keys);
        for (var index in currencyKeys) {
            if (currencyKeys.hasOwnProperty(index)) {
                currency_code = currencyKeys[index];
                currencyPrices = [];
                for (var j in btPrices) {
                    if (btPrices[j][currency_code]) {
                        currencyPrices.push(btPrices[j][currency_code]);
                    }
                }
                sum = 0;
                for (var jIndex in currencyPrices) {
                    if (currencyPrices.hasOwnProperty(jIndex)) {
                        sum += Number(currencyPrices[jIndex]);
                    }
                }
                averagePrice = sum / currencyPrices.length;  //TODO: Eliminate division due to floating point math
                btcAverages.rates[currency_code] = averagePrice;
            }
        }
        window.btcAverages = btcAverages;
        window.currencyKeys = currencyKeys;
        if(currency != "BTC"){
            btAve = btcAverages.rates[currency];
        } else {
            btAve = 1;
        }

        showStatus && showStatus.remove();

        setTimeout(() => {
            showStatus.remove();
        }, 3000);

        typeof callback === 'function' && callback(btAve, currencyKeys);
    };

    function logAPIErrorInfo(APIname, jqXHR, textStatus, errorThrown)
    {
        console.log(APIname + " request failed: ");
        console.log(jqXHR);
        console.log(textStatus);
        console.log(errorThrown);
    }
};
<commit_msg>removing some debugging code<commit_after>var __ = require('underscore'),
    Backbone = require('backbone'),
    $ = require('jquery'),
    app = require('../App').getApp();

/*eslint no-use-before-define:0*/
module.exports = function (currency, callback) {
    //some APIs require currency to be upper case
    currency = currency.toUpperCase();

    var showStatus,
        btPrices = [],
        btcAverages = {rates: {}};

    //if this is the first check, show status
    if(!window.btcAverages){
        showStatus = app.statusBar.pushMessage({
            msg: polyglot.t('LoadingBitcoinPrices'),
            duration: false
        });
    }

    window.btcAverages = window.btcAverages || {};

    var callBlockchain = function () {
        $.ajax({
            method: "GET",
            url: "https://blockchain.info/ticker",
            timeOut: 4000
        })
            .done(function (response) {
                var BlockchainCurrencies = {};
                for (var bcCurrency in response) {
                    if (response.hasOwnProperty(bcCurrency)) {
                        BlockchainCurrencies[bcCurrency] = response[bcCurrency]['15m'];
                    }
                }
                btPrices.push(BlockchainCurrencies);
            })
            .fail(function (jqXHR, textStatus, errorThrown) {
                logAPIErrorInfo("Blockchain", jqXHR, textStatus, errorThrown);
            })
            .always(function () {
                callCoinkite();
            });
    };

    var callCoinkite = function () {
        $.ajax({
            method: "GET",
            url: "https://api.coinkite.com/public/rates",
            timeOut: 4000
        })
            .done(function (response) {
                var CoinkiteCurrencies = {};
                for (var ckCurrency in response.rates.BTC) {
                    if (response.rates.BTC.hasOwnProperty(ckCurrency)) {
                        CoinkiteCurrencies[ckCurrency] = response.rates.BTC[ckCurrency].rate;
                    }
                }
                btPrices.push(CoinkiteCurrencies);
            })
            .fail(function (jqXHR, textStatus, errorThrown) {
                logAPIErrorInfo("Coinkite", jqXHR, textStatus, errorThrown);
            })
            .always(function () {
                callBitcoinAvg();
            });

    };

    var callBitcoinAvg = function () {
        $.ajax({
            method: "GET",
            url: "https://api.bitcoinaverage.com/all",
            timeOut: 4000
        })
            .done(function (response) {
                var BitcoinAvgCurrencies = {};
                for (var bcaCurrency in response) {
                    if (response[bcaCurrency].averages) {
                        BitcoinAvgCurrencies[bcaCurrency] = response[bcaCurrency].averages['24h_avg'];
                    }
                }
                btPrices.push(BitcoinAvgCurrencies);
            })
            .fail(function (jqXHR, textStatus, errorThrown) {
                logAPIErrorInfo("BitcoinAverage", jqXHR, textStatus, errorThrown);
            })
            .always(function () {
                callBitcoinCharts();
            });
    };

    var callBitcoinCharts = function () {
        $.ajax({
            method: "GET",
            url: "http://api.bitcoincharts.com/v1/weighted_prices.json",
            timeOut: 4000
        })
            .done(function (response) {
                response = JSON.parse(response);
                var BitcoinChartsCurrencies = {};
                for (var bccCurrency in response) {
                    if (response.hasOwnProperty(bccCurrency)) {
                        BitcoinChartsCurrencies[bccCurrency] = response[bccCurrency]['24h'];
                    }
                }
                btPrices.push(BitcoinChartsCurrencies);
            })
            .fail(function (jqXHR, textStatus, errorThrown) {
                logAPIErrorInfo("Bitcoin Charts", jqXHR, textStatus, errorThrown);
            })
            .always(function () {
                makeAveragePrice();
            });
    };

    if(currency == "BTC" && window.currencyKeys) {
        typeof callback === 'function' && callback(1, window.currencyKeys);
    } else if (window.btcAverages.timeStamp
        && Math.floor((new Date() - window.btcAverages.timeStamp) / 60000) < 15
        && window.currencyKeys) {
        typeof callback === 'function' && callback(window.btcAverages.rates[currency], window.currencyKeys);
    } else {
        callBlockchain();
    }

    var makeAveragePrice = function () {
        var sum,
            currencyPrices,
            currency_code,
            currencyKeys,
            averagePrice,
            keys = {},
            btAve;
        btcAverages.timeStamp = new Date();
        for (var i in btPrices) {
            if (btPrices.hasOwnProperty(i)) {
                keys = $.extend(keys, btPrices[i]);
            }
        }
        if (btPrices.length === 0) {
            alert("Bitcoin exchange rates are not available.");
        }
        currencyKeys = Object.keys(keys);
        for (var index in currencyKeys) {
            if (currencyKeys.hasOwnProperty(index)) {
                currency_code = currencyKeys[index];
                currencyPrices = [];
                for (var j in btPrices) {
                    if (btPrices[j][currency_code]) {
                        currencyPrices.push(btPrices[j][currency_code]);
                    }
                }
                sum = 0;
                for (var jIndex in currencyPrices) {
                    if (currencyPrices.hasOwnProperty(jIndex)) {
                        sum += Number(currencyPrices[jIndex]);
                    }
                }
                averagePrice = sum / currencyPrices.length;  //TODO: Eliminate division due to floating point math
                btcAverages.rates[currency_code] = averagePrice;
            }
        }
        window.btcAverages = btcAverages;
        window.currencyKeys = currencyKeys;
        if(currency != "BTC"){
            btAve = btcAverages.rates[currency];
        } else {
            btAve = 1;
        }

        showStatus && showStatus.remove();

        typeof callback === 'function' && callback(btAve, currencyKeys);
    };

    function logAPIErrorInfo(APIname, jqXHR, textStatus, errorThrown)
    {
        console.log(APIname + " request failed: ");
        console.log(jqXHR);
        console.log(textStatus);
        console.log(errorThrown);
    }
};
<|endoftext|>


<commit_before>/**
* Module of visualisation.js where most THREE.JS related tools are declared, the size of the canvas and
* all objects related to drawing, viewing and rendering. Also some styling.
* @author Robert Walhout
*/

var VisualisationJsModule = (function () {
  //TODO think about defaults for WIDTH and HEIGHT

	var getStyle = function(CLASSname) {
					var styleSheets = window.document.styleSheets;
					var styleSheetsLength = styleSheets.length;
					for(var i = 0; i < styleSheetsLength; i++){
						if (styleSheets[i].rules ) { var classes = styleSheets[i].rules; }
						else {
							try {  if(!styleSheets[i].cssRules) {continue;} }
							//Note that SecurityError exception is specific to Firefox.
							catch(e) { if(e.name == 'SecurityError') { console.log("SecurityError. Cant readd: "+ styleSheets[i].href);  continue; }}
							var classes = styleSheets[i].cssRules ;
						}
						for (var x = 0; x < classes.length; x++) {
							if (classes[x].selectorText == CLASSname) {
					 return classes[x];
								var ret = (classes[x].cssText) ? classes[x].cssText : classes[x].style.cssText ;
								if(ret.indexOf(classes[x].selectorText) == -1){ret = classes[x].selectorText + "{" + ret + "}";}
								return ret;
							}
						}
					}
					return null;
			}

	//These variables determine the initial state of the visualisation, depth = the depth that will be loaded initially.
	var DEPTH=getStyle(".maxDepth").style.order;
	var WIDTH=parseInt(getStyle('.containerAttributes').style.width)||400;
	var HEIGHT=parseInt(getStyle('.containerAttributes').style.height)||400;

	// Set camera attributes and create camera
	var VIEW_ANGLE = 20, //field of view
	    ASPECT = WIDTH / HEIGHT,
		//ASPECT  = $VisualisationJsModule.container[0].clientWidth / $VisualisationJsModule.container[0].clientHeight,
	    NEAR = 10,
	    FAR = 10000;
	var camera =  new THREE.PerspectiveCamera(VIEW_ANGLE, ASPECT, NEAR, FAR);
	var container = document.getElementById( containerDivId );
	var controls = new THREE.OrbitControls(camera, container);
	var scene = new THREE.Scene;
	var threeDObjects=[];
	var sphereArray=[]; //Array will be filled with spheres; the objects that will be intersected through on mouse events
	var newDepth;

	return  {
		//these properties can be asked by: VisualisationJsModule.propertyname
		height : HEIGHT,
		width : WIDTH,
		depth : DEPTH,
		newDepth : newDepth,
		scene : scene,
		camera : camera,
		controls : controls,
		container : container,
		sphereArray : sphereArray,
		//three arrays to contain the visible objects of the model. Distance of all objects is set.
		//used to make objects visible or not
		threeDObjects: threeDObjects,

		getContainerSize: function () {
			var size = [WIDTH,HEIGHT]
			return size;
		},

		//can ask CSS propertys in code as: VisualisationJsModule.getStyle(".className").style.color;

		init3DObjects: function(){
		  threeDObjects=[];
		},
		//functions to set distance for visible yes/no
		add3DObject(object,distance){
		  object.distance=distance;
		  this.threeDObjects.push(object);
		},
		getStyle: getStyle
	};

});<commit_msg>defined getStyleAttr to initiate variables from css<commit_after>/**
* Module of visualisation.js where most THREE.JS related tools are declared, the size of the canvas and
* all objects related to drawing, viewing and rendering. Also some styling.
* @author Robert Walhout
*/

var VisualisationJsModule = (function () {
  //TODO think about defaults for WIDTH and HEIGHT
	var getStyle = function(CLASSname) {
					var styleSheets = window.document.styleSheets;
					var styleSheetsLength = styleSheets.length;
					for(var i = 0; i < styleSheetsLength; i++){
						if (styleSheets[i].rules ) { var classes = styleSheets[i].rules; }
						else {
							try {  if(!styleSheets[i].cssRules) {continue;} }
							//Note that SecurityError exception is specific to Firefox.
							catch(e) { if(e.name == 'SecurityError') { console.log("SecurityError. Cant readd: "+ styleSheets[i].href);  continue; }}
							var classes = styleSheets[i].cssRules ;
						}
						for (var x = 0; x < classes.length; x++) {
							if (classes[x].selectorText == CLASSname) {
					 return classes[x];
								var ret = (classes[x].cssText) ? classes[x].cssText : classes[x].style.cssText ;
								if(ret.indexOf(classes[x].selectorText) == -1){ret = classes[x].selectorText + "{" + ret + "}";}
								return ret;
							}
						}
					}
					return null;
			}

	var getStyleAttr = function(CLASSname,attr,defaultValue) {
	  try {
	    var text=getStyle(CLASSname).cssText;
	    var p=text.indexOf("{");
	    text=text.substring(p);
	    p=text.indexOf(attr);
	    text=text.substring(p+attr.length);
	    var f=text.substring(0,1);
	    if (!((f==" ")|| (f==":"))) return defaultValue;
	    //TODO check for other possible occurrences of attr, by using while
	    p=text.indexOf(":");
	    text=text.substring(p+1);
	    p=text.indexOf(";");
	    text=text.substring(0,p);
	    if ( text.length>0) return text;else return defaultValue;
	  }
	  catch (e) {
	    return defaultValue;
	  }

	}

	//These variables determine the initial state of the visualisation, depth = the depth that will be loaded initially.
	var DEPTH=getStyleAttr(".maxDepth","order","4");
	var WIDTH=parseInt(getStyleAttr('.containerAttributes',"width","400"));
	var HEIGHT=parseInt(getStyleAttr('.containerAttributes',"height","400"));

	// Set camera attributes and create camera
	var VIEW_ANGLE = 20, //field of view
	    ASPECT = WIDTH / HEIGHT,
		//ASPECT  = $VisualisationJsModule.container[0].clientWidth / $VisualisationJsModule.container[0].clientHeight,
	    NEAR = 10,
	    FAR = 10000;
	var camera =  new THREE.PerspectiveCamera(VIEW_ANGLE, ASPECT, NEAR, FAR);
	var container = document.getElementById( containerDivId );
	var controls = new THREE.OrbitControls(camera, container);
	var scene = new THREE.Scene;
	var threeDObjects=[];
	var sphereArray=[]; //Array will be filled with spheres; the objects that will be intersected through on mouse events
	var newDepth;

	return  {
		//these properties can be asked by: VisualisationJsModule.propertyname
		height : HEIGHT,
		width : WIDTH,
		depth : DEPTH,
		newDepth : newDepth,
		scene : scene,
		camera : camera,
		controls : controls,
		container : container,
		sphereArray : sphereArray,
		//three arrays to contain the visible objects of the model. Distance of all objects is set.
		//used to make objects visible or not
		threeDObjects: threeDObjects,

		getContainerSize: function () {
			var size = [WIDTH,HEIGHT]
			return size;
		},

		//can ask CSS propertys in code as: VisualisationJsModule.getStyle(".className").style.color;

		init3DObjects: function(){
		  threeDObjects=[];
		},
		//functions to set distance for visible yes/no
		add3DObject(object,distance){
		  object.distance=distance;
		  this.threeDObjects.push(object);
		},
		getStyle: getStyle,
		getStyleAttr:getStyleAttr
	};

});<|endoftext|>