<commit_before>// #############################################################################
// #
// # "SQIP" (pronounced \skwɪb\ like the non-magical folk of magical descent)
// # is a SVG-based LQIP technique - https://github.com/technopagan/sqip
// #
// # Installation as CLI:
// # npm install -g sqip
// #
// # Installation as lib:
// # npm install sqip
// #
// # Requirements:
// # * Node.js >= v6 (https://nodejs.org/en/)
// #
// #############################################################################

import path from 'path'

import Debug from 'debug'
import fs from 'fs-extra'
import glob from 'fast-glob'
import sizeOf from 'image-size'
import Vibrant from 'node-vibrant'
import sharp from 'sharp'
import termimg from 'term-img'
import Table from 'cli-table3'
import chalk from 'chalk'

const debug = Debug('sqip')

const mainKeys = ['originalWidth', 'originalHeight', 'width', 'height', 'type']

const paletteKeys = [
  'Vibrant',
  'DarkVibrant',
  'LightVibrant',
  'Muted',
  'DarkMuted',
  'LightMuted'
]

export async function resolvePlugins(plugins) {
  return Promise.all(
    plugins.map(async plugin => {
      if (typeof plugin === 'string') {
        plugin = { name: plugin }
      }
      const { name } = plugin

      if (!name) {
        throw new Error(
          `Unable to read plugin name from:\n${JSON.stringify(plugin, null, 2)}`
        )
      }
      const moduleName =
        name.indexOf('sqip-plugin-') !== -1 ? name : `sqip-plugin-${name}`
      try {
        debug(`Loading ${moduleName}`)
        const Plugin = await import(moduleName)

        return { ...plugin, Plugin }
      } catch (err) {
        if (err.code === 'MODULE_NOT_FOUND') {
          throw new Error(
            `Unable to load plugin "${moduleName}". Try installing it via:\n\n npm install ${moduleName}`
          )
        }
        throw err
      }
    })
  )
}

async function processImage({ filePath, config }) {
  const originalSizes = sizeOf(filePath)
  const vibrant = new Vibrant(filePath, { quality: 0 })
  const palette = await vibrant.getPalette()
  let metadata = {
    originalWidth: originalSizes.width,
    originalHeight: originalSizes.height,
    palette
  }

  // Load plugins
  const plugins = await resolvePlugins(config.plugins)

  // Interate through plugins and apply them to last returned image
  let imageContent = await fs.readFile(filePath)

  if (config.width > 0) {
    // Resize to desired output width
    imageContent = await sharp(imageContent)
      .resize(config.width)
      .toBuffer()

    const resizedMetadata = await sharp(imageContent).metadata()
    metadata.width = resizedMetadata.width
    metadata.height = resizedMetadata.height
  } else {
    // Fall back to original size, keep image as is
    metadata.width = originalSizes.width
    metadata.height = originalSizes.height
  }

  for (const { name, options: pluginOptions, Plugin } of plugins) {
    try {
      debug(`Construct ${name}`)
      const plugin = new Plugin({
        sqipConfig: config,
        pluginOptions,
        metadata,
        filePath
      })
      debug(`Apply ${name}`)
      imageContent = await plugin.apply(imageContent)
    } catch (err) {
      if (config.shouldThrow) {
        throw err
      }
      console.error(err)
      process.exit(1)
    }
  }

  return { svg: imageContent, metadata }
}

export default async function sqip(options) {
  // Build configuration based on passed options and default options
  const defaultOptions = {
    plugins: [
      { name: 'primitive', options: { numberOfPrimitives: 8, mode: 0 } },
      'blur',
      'svgo',
      'data-uri'
    ],
    shouldThrow: true, // @todo do we really need this?,
    width: 300,
    parseableOutput: false,
    silent: true
  }

  const config = Object.assign({}, defaultOptions, options)

  const { input, output, parseableOutput, silent } = config

  if (parseableOutput) {
    chalk.enabled = false
  }

  // Validate configuration
  if (!input) {
    throw new Error(
      'Please provide an input image, e.g. sqip({ input: "input.jpg" })'
    )
  }

  // Find all files matching the input glob
  const files = await glob(input, {
    absolute: true
  })

  debug('Found files:')
  debug(files)

  // Test if files are found
  if (!files.length) {
    throw new Error(
      `Unable to find any files via ${input}. Make sure the file exists.

If you are using globbing patterns, the following features are supported:

https://github.com/micromatch/micromatch#matching-features`
    )
  }

  // Test if all files are accessable
  for (const file of files) {
    try {
      debug('check file ' + file)
      await fs.access(file, fs.constants.R_OK)
    } catch (err) {
      throw new Error(`Unable to read file ${file}`)
    }
  }
  // Iterate over all files
  const results = []
  for (const filePath of files) {
    let outputPath

    if (!silent) {
      console.log(`Processing: ${filePath}`)
    } else {
      debug(`Processing ${filePath}`)
    }

    const result = await processImage({ filePath, config })
    debug(`Processed ${filePath}`)

    // Write result svg if desired
    if (output) {
      const name = path.parse(filePath).name

      try {
        // Test if output path already exists
        const stats = await fs.stat(output)

        // Throw if it is a file and already exists
        if (!stats.isDirectory()) {
          throw new Error(
            `File ${output} already exists. Overwriting is not yet supported.`
          )
        }
        outputPath = path.resolve(output, `${name}.svg`)
      } catch (err) {
        // Output directory or file does not exist. We will create it later on.
        outputPath = output
      }

      debug(`Writing ${outputPath}`)
      await fs.writeFile(outputPath, result.svg)
    }

    if (!silent) {
      if (outputPath) {
        console.log(`Stored at: ${outputPath}`)
      }

      // Generate preview
      if (!parseableOutput) {
        const preview = await sharp(Buffer.from(result.svg))
          .png()
          .toBuffer()
        const previewPath = path.resolve(__dirname, 'tmp.svg')
        await fs.writeFile(previewPath, preview)

        try {
          termimg(previewPath)
        } catch (err) {
          if (err.name !== 'UnsupportedTerminalError') {
            throw new err()
          }
        }
      }

      // Metadata
      const tableConfig = parseableOutput && {
        chars: {
          top: '',
          'top-mid': '',
          'top-left': '',
          'top-right': '',
          bottom: '',
          'bottom-mid': '',
          'bottom-left': '',
          'bottom-right': '',
          left: '',
          'left-mid': '',
          mid: '',
          'mid-mid': '',
          right: '',
          'right-mid': '',
          middle: ' '
        },
        style: { 'padding-left': 0, 'padding-right': 0 }
      }

      const allKeys = [...mainKeys, 'palette']
      const restMetadata = { ...result.metadata }
      allKeys.forEach(k => delete restMetadata[k])

      const mainTable = new Table(tableConfig)
      mainTable.push(mainKeys)
      mainTable.push(mainKeys.map(key => result.metadata[key]))
      console.log(mainTable.toString())

      const paletteTable = new Table(tableConfig)
      paletteTable.push(paletteKeys)
      paletteTable.push(
        paletteKeys
          .map(key => result.metadata.palette[key].getHex())
          .map(hex => chalk.hex(hex)(hex))
      )
      console.log(paletteTable.toString())

      Object.keys(restMetadata).forEach(key => {
        console.log(chalk.bold(`${key}:`))
        console.log(restMetadata[key])
      })
    }

    results.push(result)
  }

  debug(`Finished`)

  // Return as array when input was array or results is only one file
  if (Array.isArray(input) || results.length === 0) {
    return results
  }
  return results[0]
}

export class SqipPlugin {
  constructor({ sqipConfig, metadata, filePath }) {
    this.sqipConfig = sqipConfig || {}
    this.metadata = metadata || {}
    this.filePath = filePath || null
  }
}

export * from './helpers'
<commit_msg>chore(flags): remove useless shouldThrow flag<commit_after>// #############################################################################
// #
// # "SQIP" (pronounced \skwɪb\ like the non-magical folk of magical descent)
// # is a SVG-based LQIP technique - https://github.com/technopagan/sqip
// #
// # Installation as CLI:
// # npm install -g sqip
// #
// # Installation as lib:
// # npm install sqip
// #
// # Requirements:
// # * Node.js >= v6 (https://nodejs.org/en/)
// #
// #############################################################################

import path from 'path'

import Debug from 'debug'
import fs from 'fs-extra'
import glob from 'fast-glob'
import sizeOf from 'image-size'
import Vibrant from 'node-vibrant'
import sharp from 'sharp'
import termimg from 'term-img'
import Table from 'cli-table3'
import chalk from 'chalk'

const debug = Debug('sqip')

const mainKeys = ['originalWidth', 'originalHeight', 'width', 'height', 'type']

const paletteKeys = [
  'Vibrant',
  'DarkVibrant',
  'LightVibrant',
  'Muted',
  'DarkMuted',
  'LightMuted'
]

export async function resolvePlugins(plugins) {
  return Promise.all(
    plugins.map(async plugin => {
      if (typeof plugin === 'string') {
        plugin = { name: plugin }
      }
      const { name } = plugin

      if (!name) {
        throw new Error(
          `Unable to read plugin name from:\n${JSON.stringify(plugin, null, 2)}`
        )
      }
      const moduleName =
        name.indexOf('sqip-plugin-') !== -1 ? name : `sqip-plugin-${name}`
      try {
        debug(`Loading ${moduleName}`)
        const Plugin = await import(moduleName)

        return { ...plugin, Plugin }
      } catch (err) {
        if (err.code === 'MODULE_NOT_FOUND') {
          throw new Error(
            `Unable to load plugin "${moduleName}". Try installing it via:\n\n npm install ${moduleName}`
          )
        }
        throw err
      }
    })
  )
}

async function processImage({ filePath, config }) {
  const originalSizes = sizeOf(filePath)
  const vibrant = new Vibrant(filePath, { quality: 0 })
  const palette = await vibrant.getPalette()
  let metadata = {
    originalWidth: originalSizes.width,
    originalHeight: originalSizes.height,
    palette
  }

  // Load plugins
  const plugins = await resolvePlugins(config.plugins)

  // Interate through plugins and apply them to last returned image
  let imageContent = await fs.readFile(filePath)

  if (config.width > 0) {
    // Resize to desired output width
    imageContent = await sharp(imageContent)
      .resize(config.width)
      .toBuffer()

    const resizedMetadata = await sharp(imageContent).metadata()
    metadata.width = resizedMetadata.width
    metadata.height = resizedMetadata.height
  } else {
    // Fall back to original size, keep image as is
    metadata.width = originalSizes.width
    metadata.height = originalSizes.height
  }

  for (const { name, options: pluginOptions, Plugin } of plugins) {
    debug(`Construct ${name}`)
    const plugin = new Plugin({
      sqipConfig: config,
      pluginOptions,
      metadata,
      filePath
    })
    debug(`Apply ${name}`)
    imageContent = await plugin.apply(imageContent)
  }

  return { svg: imageContent, metadata }
}

export default async function sqip(options) {
  // Build configuration based on passed options and default options
  const defaultOptions = {
    plugins: [
      { name: 'primitive', options: { numberOfPrimitives: 8, mode: 0 } },
      'blur',
      'svgo',
      'data-uri'
    ],
    width: 300,
    parseableOutput: false,
    silent: true
  }

  const config = Object.assign({}, defaultOptions, options)

  const { input, output, parseableOutput, silent } = config

  if (parseableOutput) {
    chalk.enabled = false
  }

  // Validate configuration
  if (!input) {
    throw new Error(
      'Please provide an input image, e.g. sqip({ input: "input.jpg" })'
    )
  }

  // Find all files matching the input glob
  const files = await glob(input, {
    absolute: true
  })

  debug('Found files:')
  debug(files)

  // Test if files are found
  if (!files.length) {
    throw new Error(
      `Unable to find any files via ${input}. Make sure the file exists.

If you are using globbing patterns, the following features are supported:

https://github.com/micromatch/micromatch#matching-features`
    )
  }

  // Test if all files are accessable
  for (const file of files) {
    try {
      debug('check file ' + file)
      await fs.access(file, fs.constants.R_OK)
    } catch (err) {
      throw new Error(`Unable to read file ${file}`)
    }
  }
  // Iterate over all files
  const results = []
  for (const filePath of files) {
    let outputPath

    if (!silent) {
      console.log(`Processing: ${filePath}`)
    } else {
      debug(`Processing ${filePath}`)
    }

    const result = await processImage({ filePath, config })
    debug(`Processed ${filePath}`)

    // Write result svg if desired
    if (output) {
      const name = path.parse(filePath).name

      try {
        // Test if output path already exists
        const stats = await fs.stat(output)

        // Throw if it is a file and already exists
        if (!stats.isDirectory()) {
          throw new Error(
            `File ${output} already exists. Overwriting is not yet supported.`
          )
        }
        outputPath = path.resolve(output, `${name}.svg`)
      } catch (err) {
        // Output directory or file does not exist. We will create it later on.
        outputPath = output
      }

      debug(`Writing ${outputPath}`)
      await fs.writeFile(outputPath, result.svg)
    }

    if (!silent) {
      if (outputPath) {
        console.log(`Stored at: ${outputPath}`)
      }

      // Generate preview
      if (!parseableOutput) {
        const preview = await sharp(Buffer.from(result.svg))
          .png()
          .toBuffer()
        const previewPath = path.resolve(__dirname, 'tmp.svg')
        await fs.writeFile(previewPath, preview)

        try {
          termimg(previewPath)
        } catch (err) {
          if (err.name !== 'UnsupportedTerminalError') {
            throw new err()
          }
        }
      }

      // Metadata
      const tableConfig = parseableOutput && {
        chars: {
          top: '',
          'top-mid': '',
          'top-left': '',
          'top-right': '',
          bottom: '',
          'bottom-mid': '',
          'bottom-left': '',
          'bottom-right': '',
          left: '',
          'left-mid': '',
          mid: '',
          'mid-mid': '',
          right: '',
          'right-mid': '',
          middle: ' '
        },
        style: { 'padding-left': 0, 'padding-right': 0 }
      }

      const allKeys = [...mainKeys, 'palette']
      const restMetadata = { ...result.metadata }
      allKeys.forEach(k => delete restMetadata[k])

      const mainTable = new Table(tableConfig)
      mainTable.push(mainKeys)
      mainTable.push(mainKeys.map(key => result.metadata[key]))
      console.log(mainTable.toString())

      const paletteTable = new Table(tableConfig)
      paletteTable.push(paletteKeys)
      paletteTable.push(
        paletteKeys
          .map(key => result.metadata.palette[key].getHex())
          .map(hex => chalk.hex(hex)(hex))
      )
      console.log(paletteTable.toString())

      Object.keys(restMetadata).forEach(key => {
        console.log(chalk.bold(`${key}:`))
        console.log(restMetadata[key])
      })
    }

    results.push(result)
  }

  debug(`Finished`)

  // Return as array when input was array or results is only one file
  if (Array.isArray(input) || results.length === 0) {
    return results
  }
  return results[0]
}

export class SqipPlugin {
  constructor({ sqipConfig, metadata, filePath }) {
    this.sqipConfig = sqipConfig || {}
    this.metadata = metadata || {}
    this.filePath = filePath || null
  }
}

export * from './helpers'
<|endoftext|>


<commit_before>import { patch } from '../patch'

export const VUE_VIEW = '__vueVNodeRef__'

let tid = 0
export default {
  name: 'v-template',

  props: {
    name: {
      type: String
    },

    if: {
      type: String
    }
  },

  mounted() {
    if (!this.$scopedSlots.default) {
      return
    }

    this.$templates = this.$parent.$templates =
      this.$parent.$templates || new TemplateBag()
    this.$templates.registerTemplate(
      this.$props.name || (this.$props.if ? `v-template-${tid++}` : 'default'),
      this.$props.if,
      this.$scopedSlots.default
    )
  },

  render(h) {}
}

export class TemplateBag {
  constructor() {
    this._templateMap = new Map()
  }

  registerTemplate(name, condition, scopedFn) {
    this._templateMap.set(name, {
      condition,
      conditionFn: this.getConditionFn(condition),
      scopedFn,
      keyedTemplate: new VueKeyedTemplate(name, scopedFn)
    })
  }

  get selectorFn() {
    let self = this
    return function templateSelectorFn(item) {
      const iterator = self._templateMap.entries()
      let curr
      while ((curr = iterator.next().value)) {
        const [name, { conditionFn }] = curr
        if (conditionFn(item)) {
          return name
        }
      }
      return 'default'
    }
  }

  getConditionFn(condition) {
    return new Function('item', `return !!(${condition})`)
  }

  getKeyedTemplate(name) {
    const { keyedTemplate } = this._templateMap.get(name)
    return keyedTemplate
  }

  patchTemplate(name, context, oldVnode) {
    const { scopedFn } = this._templateMap.get(name)

    return patch(oldVnode, scopedFn(context)).nativeView
  }

  getAvailable() {
    return Array.from(this._templateMap.keys())
  }

  getKeyedTemplates() {
    return Array.from(this._templateMap.values()).map(
      ({ keyedTemplate }) => keyedTemplate
    )
  }
}

export class VueKeyedTemplate /* implements KeyedTemplate */ {
  constructor(key, scopedFn) {
    this._key = key
    this._scopedFn = scopedFn
  }

  get key() {
    return this._key
  }

  createView() {
    const vnode = this._scopedFn(deepProxy({}))
    const nativeView = patch(null, vnode).nativeView
    nativeView[VUE_VIEW] = vnode

    console.log('CREATE_NEW_VIEW')
    return nativeView
  }
}

function deepProxy(object, depth = 0) {
  return new Proxy(object, {
    get() {
      if (depth > 10) {
        throw new Error('deep proxy over 10 deep.')
      }
      return deepProxy({}, depth + 1)
    }
  })
}
<commit_msg>Attach $templates property to plain elements too<commit_after>import { patch } from '../patch'

export const VUE_VIEW = '__vueVNodeRef__'

let tid = 0
export default {
  name: 'v-template',

  props: {
    name: {
      type: String
    },

    if: {
      type: String
    }
  },

  mounted() {
    if (!this.$scopedSlots.default) {
      return
    }

    this.$templates = this.$el.parentNode.$templates = this.$parent.$templates =
      this.$parent.$templates || new TemplateBag()
    this.$templates.registerTemplate(
      this.$props.name || (this.$props.if ? `v-template-${tid++}` : 'default'),
      this.$props.if,
      this.$scopedSlots.default
    )
  },

  render(h) {}
}

export class TemplateBag {
  constructor() {
    this._templateMap = new Map()
  }

  registerTemplate(name, condition, scopedFn) {
    this._templateMap.set(name, {
      condition,
      conditionFn: this.getConditionFn(condition),
      scopedFn,
      keyedTemplate: new VueKeyedTemplate(name, scopedFn)
    })
  }

  get selectorFn() {
    let self = this
    return function templateSelectorFn(item) {
      const iterator = self._templateMap.entries()
      let curr
      while ((curr = iterator.next().value)) {
        const [name, { conditionFn }] = curr
        if (conditionFn(item)) {
          return name
        }
      }
      return 'default'
    }
  }

  getConditionFn(condition) {
    return new Function('item', `return !!(${condition})`)
  }

  getKeyedTemplate(name) {
    const { keyedTemplate } = this._templateMap.get(name)
    return keyedTemplate
  }

  patchTemplate(name, context, oldVnode) {
    const { scopedFn } = this._templateMap.get(name)

    const vnode = scopedFn(context)
    const nativeView = patch(oldVnode, vnode).nativeView
    nativeView[VUE_VIEW] = vnode

    return nativeView
  }

  getAvailable() {
    return Array.from(this._templateMap.keys())
  }

  getKeyedTemplates() {
    return Array.from(this._templateMap.values()).map(
      ({ keyedTemplate }) => keyedTemplate
    )
  }
}

export class VueKeyedTemplate /* implements KeyedTemplate */ {
  constructor(key, scopedFn) {
    this._key = key
    this._scopedFn = scopedFn
  }

  get key() {
    return this._key
  }

  createView() {
    const vnode = this._scopedFn(deepProxy({}))
    const nativeView = patch(null, vnode).nativeView
    nativeView[VUE_VIEW] = vnode

    return nativeView
  }
}

function deepProxy(object, depth = 0) {
  return new Proxy(object, {
    get() {
      if (depth > 10) {
        throw new Error('deepProxy over 10 deep.')
      }
      return deepProxy({}, depth + 1)
    }
  })
}
<|endoftext|>


<commit_before>(function (Q, $, window, undefined) {
	/**
	 * This tool create Websites/webpage/chat tools above urls in chat messages.
	 * @class Websites/webpage/chat
	 * @constructor
	 * @param {Object} [options] this is an object that contains parameters for this function
	 * @param {string} [options.appendTo=bubble] Where to append preview tool in chat message:
	 * 	bubble - inside bubble element of message,
	 * 	message - inside message element itself
	 */
	Q.Tool.define("Websites/webpage/chat", ["Streams/chat"], function (options) {
		var tool = this;
		var state = this.state;
		tool.chatTool = Q.Tool.from(this.element, "Streams/chat");

		// preload throbber
		$('')[0].src = Q.info.imgLoading;

		Q.addStylesheet('{{Websites}}/css/tools/webpage/chat.css');

		$(tool.chatTool.element).on('input', '.Streams_chat_composer input, .Streams_chat_composer textarea', Q.debounce(function () {
			tool.process(this);
		}, 500));

		// on before message post
		tool.chatTool.state.beforePost.set(function (fields) {
			var $preview = tool.getActivePreview();
			state.websitesPreview = {};
			if (!$preview) {
				return;
			}

			var previewTool = Q.Tool.from($preview, "Websites/webpage/preview") || Q.Tool.from($preview, "Streams/audio/preview") || Q.Tool.from($preview, "Streams/video/preview");

			if (Q.typeOf(previewTool) === 'Q.Tool') {
				fields.instructions = Q.extend({}, fields.instructions, {
					'Websites/webpages': {
						publisherId: previewTool.state.publisherId,
						streamName: previewTool.state.streamName
					}
				});
			}

			tool.removePreview(previewTool.element);
		}, tool);

		// on new message render
		tool.chatTool.state.onMessageRender.set(function (fields, html) {
			var $html = $(fields.html || html);

			$html = tool.parseChatMessage($html, fields.instructions);

			fields.html = $html[0].outerHTML;
		}, tool);

		var _openNewWindow = function () {
			var previewState = this.state;
			var $te = $(this.element);

			if ($te.closest('.Streams_chat_item').length) {
				Q.Streams.get(previewState.publisherId, previewState.streamName, function () {
					var url = this.getAttribute('url');

					$te.off(Q.Pointer.fastclick).on(Q.Pointer.fastclick, function () {
						window.open(url, '_blank');
					});
				});
			}
		};

		Q.Tool.onActivate('Websites/webpage/preview').set(_openNewWindow, tool);
		Q.Tool.onActivate('Streams/video/preview').set(_openNewWindow, tool);
		Q.Tool.onActivate('Streams/audio/preview').set(_openNewWindow, tool);

		// parse old messages
		Q.each($(".Streams_chat_item", tool.chatTool.element), function (i, element) {
			var $this = $(this);
			tool.parseChatMessage($this, $this.attr('data-instructions'));
		});
	},

	{
		websitesPreview: {},
		appendTo: 'bubble'
	},

	{
		/**
		 * Get urls from string and create Websites/webpage/preview tools for each.
		 *
		 * @method process
		 * @param {HTMLElement|jQuery} input
		 */
		process: function (input) {
			var tool = this;
			var $input = $(input);
			var inputVal = $input.val();
			var $form = $input.closest("form");
			var websitesPreview = this.state.websitesPreview;
			var urls = inputVal.matchTypes('url', {requireScheme: false});

			Q.each(urls, function (i, url) {
				if (tool.getActivePreview() || url in websitesPreview || inputVal.endsWith(url)) {
					return;
				}

				Q.Template.render('Websites/webpage/chat', {
					src: Q.info.imgLoading
				}, function (err, html) {
					if (err) {
						return;
					}

					websitesPreview[url] = $(html).prependTo($form);

					var _close = function () {
						tool.removePreview(websitesPreview[url]);
						websitesPreview[url] = null;
						tool.process(input);
					};

					$('a.Q_close', websitesPreview[url]).on(Q.Pointer.fastclick, _close);

					Q.req('Websites/scrape', ['result', 'publisherId', 'streamName'], function (err, response) {
						var msg = Q.firstErrorMessage(err, response && response.errors);
						if (msg) {
							return _close();
						}

						var streamType = Q.Websites.getStreamType(url);

						$(".Websites_webpage_chat_preview", websitesPreview[url]).tool("Streams/preview", {
							publisherId: response.slots.publisherId,
							streamName: response.slots.streamName,
							closeable: false,
							editable: false
						}).tool(streamType + "/preview", {
							publisherId: response.slots.publisherId,
							streamName: response.slots.streamName,
							editable: false
						}, Date.now()).activate(function () {
							var previewTool = this;
							var $te = $(previewTool.element);

							$te.outerWidth($form.innerWidth());

							$te.off(Q.Pointer.fastclick).on(Q.Pointer.fastclick, function () {
								window.open(url, '_blank');
							});
						});
					}, {
						method: 'post',
						fields: {url: url}
					});
				});
			});
		},
		/**
		 *	Add Websites/webpage/preview tools to chat messages
		 *
		 * @method parseChatMessage
		 * @return {jQuery|HTMLElement} element Chat message element (Streams_chat_item)
		 */
		parseChatMessage: function (element, instructions) {
			if (!(element instanceof jQuery)) {
				element = $(element);
			}

			if (element.attr('data-webpageProcessed')) {
				return;
			}

			var state = this.state;

			// parse all links in message
			var $chatMessageContent = $(".Streams_chat_message_content", element);
			var chatMessageContent = $chatMessageContent.html();
			Q.each(chatMessageContent.matchTypes('url'), function (i, url) {
				var href = url;
				if (href.indexOf('//') === -1) {
					href = '//' + href;
				}

				chatMessageContent = chatMessageContent.replace(url, "" + url + "");
			});
			$chatMessageContent.html(chatMessageContent);

			instructions = Q.getObject('Websites/webpages', JSON.parse(instructions || null));
			if (instructions) {
				var elementToAppend = state.appendTo === 'bubble' ? $(".Streams_chat_bubble", element) : element;
				var streamType = instructions.streamName.substr(0, instructions.streamName.lastIndexOf("/"));
				instructions.editable = false;

				var streamsPreview = Q.Tool.setUpElementHTML('div', 'Streams/preview', {
					publisherId: instructions.publisherId,
					streamName: instructions.streamName,
					closeable: false,
					editable: false
				});
				var specialPreview = Q.Tool.setUpElementHTML($(streamsPreview).addClass("Websites_webpage_chat_preview")[0], streamType + '/preview', {
					publisherId: instructions.publisherId,
					streamName: instructions.streamName,
					editable: false
				});

				$(specialPreview).appendTo(elementToAppend);
			}

			// mark element as processed
			element.attr('data-webpageProcessed', 1);

			return element;
		},
		/**
		 *	This is used Validation for Website URL Text in the contents.
		 *
		 * @method getActivePreview
		 * @return {Q.Tool} Returns Websites/webpage/preview tool
		 */
		getActivePreview: function () {
			var websitesPreview = this.state.websitesPreview;

			for (var url in websitesPreview) {
				if (websitesPreview[url]) {
					return $(".Streams_preview_tool", websitesPreview[url]);
				}
			}

			return null;
		},
		/**
		 *	Correctly remove preview element
		 *
		 * @method removePreview
		 * @param {HTMLElement|jQuery} element Element need to remove
		 */
		removePreview: function (element) {
			if (element instanceof HTMLElement) {
				element = $(element);
			}

			var $parent = element.closest(".Websites_webpage_chat");
			Q.Tool.remove(element[0], true, true);
			$parent.remove();
		}
	});

	Q.Template.set('Websites/webpage/chat',
		'
' +
		'

' +
		'	' +
		'
'
	);
})(Q, Q.$, window);<commit_msg>Modified Websites/webpage/chat tool: 1) Specially handle event 'paste' to process url even without space at the end. 2) Added to Streams/chat preprocess function which check whether streams created for each active preview. If not created - create one and fill preview state with publisherId, streamName to use later for fields.instructions 3) Allow to render preview tool by url (without stream)<commit_after>(function (Q, $, window, undefined) {
	/**
	 * This tool create Websites/webpage/chat tools above urls in chat messages.
	 * @class Websites/webpage/chat
	 * @constructor
	 * @param {Object} [options] this is an object that contains parameters for this function
	 * @param {string} [options.appendTo=bubble] Where to append preview tool in chat message:
	 * 	bubble - inside bubble element of message,
	 * 	message - inside message element itself
	 */
	Q.Tool.define("Websites/webpage/chat", ["Streams/chat"], function (options) {
		var tool = this;
		var state = this.state;
		tool.chatTool = Q.Tool.from(this.element, "Streams/chat");

		// preload throbber
		$('')[0].src = Q.info.imgLoading;

		Q.addStylesheet('{{Websites}}/css/tools/webpage/chat.css');

		// input event handler
		$(tool.chatTool.element).on('input', '.Streams_chat_composer input, .Streams_chat_composer textarea', Q.debounce(function () {
			tool.process(this, 'input');
		}, 500));

		// paste event handler
		$(tool.chatTool.element).on('paste', '.Streams_chat_composer input, .Streams_chat_composer textarea', Q.debounce(function () {
			tool.process(this, 'paste');
		}, 500));

		// add to preprocess function which check whether streams created for each active preview
		// if not created - create one and fill preview state with publisherId, streamName to use later for fields.instructions
		tool.chatTool.state.preprocess.push(function (callback) {
			var previewTool = tool.getToolFromElement(tool.getActivePreview());

			if (Q.typeOf(previewTool) !== 'Q.Tool' || (previewTool.state.publisherId && previewTool.state.streamName)) {
				return Q.handle(callback);
			}

			// send request to create Websites/webpages stream (if not created yet)
			Q.req("Websites/webpage", ["publisherId", "streamName"], function (err, response) {
				var msg = Q.firstErrorMessage(err, response && response.errors);
				if (msg) {
					return Q.alert(msg);
				}

				previewTool.state.publisherId = response.slots.publisherId;
				previewTool.state.streamName = response.slots.streamName;

				Q.handle(callback);
			}, {
				method: 'post',
				fields: {
					url: previewTool.state.url,
					// if relate to chat, this preview tool for some reason will appear between chat messages
					/*categoryStream: {
						publisherId: tool.chatTool.state.publisherId,
						streamName: tool.chatTool.state.streamName,
						relationType: "Websites/webpage"
					}*/
				}
			});
		});

		// on before message post
		tool.chatTool.state.beforePost.set(function (fields) {
			var previewTool = tool.getToolFromElement(tool.getActivePreview());
			state.websitesPreview = {};

			if (Q.typeOf(previewTool) !== 'Q.Tool') {
				return;
			}

			fields.instructions = Q.extend({}, fields.instructions, {
				'Websites/webpages': {
					url: previewTool.state.url,
					publisherId: previewTool.state.publisherId,
					streamName: previewTool.state.streamName
				}
			});

			tool.removePreview(previewTool.element);
		}, tool);

		// on new message render
		tool.chatTool.state.onMessageRender.set(function (fields, html) {
			var $html = $(fields.html || html);

			$html = tool.parseChatMessage($html, fields.instructions);

			fields.html = $html[0].outerHTML;
		}, tool);

		var _openNewWindow = function () {
			var previewState = this.state;
			var $te = $(this.element);

			if ($te.closest('.Streams_chat_item').length) {
				if (previewState.publisherId && previewState.streamName) {
					Q.Streams.get(previewState.publisherId, previewState.streamName, function () {
						var url = this.getAttribute('url');

						$te.off(Q.Pointer.fastclick).on(Q.Pointer.fastclick, function () {
							window.open(url, '_blank');
						});
					});
				} else {
					$te.off(Q.Pointer.fastclick).on(Q.Pointer.fastclick, function () {
						window.open(previewState.url, '_blank');
					});
				}
			}
		};

		Q.Tool.onActivate('Websites/webpage/preview').set(_openNewWindow, tool);
		Q.Tool.onActivate('Streams/video/preview').set(_openNewWindow, tool);
		Q.Tool.onActivate('Streams/audio/preview').set(_openNewWindow, tool);

		// parse old messages
		Q.each($(".Streams_chat_item", tool.chatTool.element), function (i, element) {
			var $this = $(this);
			tool.parseChatMessage($this, $this.attr('data-instructions'));
		});
	},

	{
		websitesPreview: {},
		processedURLs: {},
		appendTo: 'bubble'
	},

	{
		/**
		 * Get urls from string and create Websites/webpage/preview tools for each.
		 *
		 * @method process
		 * @param {HTMLElement|jQuery} input
		 * @param {String} eventType Type of event which called this method ('input', 'paste', etc)
		 */
		process: function (input, eventType) {
			var tool = this;
			var $input = $(input);
			var inputVal = $input.val();
			var $form = $input.closest("form");
			var websitesPreview = this.state.websitesPreview;
			var processedURLs = this.state.processedURLs;
			var urls = inputVal.matchTypes('url', {requireScheme: false});

			Q.each(urls, function (i, url) {
				if (url in websitesPreview || (eventType !== 'paste' && inputVal.endsWith(url))) {
					return;
				}

				var activePreview = tool.getActivePreview();

				// if processed url modified and active preview processed exactly this url
				if (processedURLs[i] && processedURLs[i] !== url) {
					var $element = websitesPreview[processedURLs[i]];

					// currently active preview url modified
					if ($element instanceof jQuery && $element.is(":visible")) {
						var activePreviewTool = tool.getToolFromElement(activePreview);

						if (activePreviewTool) {
							activePreviewTool.state.url = url;
							activePreviewTool.refresh();
						}
					}
				}

				// if we have already active preview - do nothing
				if (activePreview) {
					return;
				}

				Q.Template.render('Websites/webpage/chat', {
					src: Q.info.imgLoading
				}, function (err, html) {
					if (err) {
						return;
					}

					websitesPreview[url] = $(html).prependTo($form);
					processedURLs[i] = url;

					var _close = function () {
						tool.removePreview(websitesPreview[url]);
						websitesPreview[url] = null;
						tool.process(input);
					};

					$('a.Q_close', websitesPreview[url]).on(Q.Pointer.fastclick, _close);

					Q.req('Websites/scrape', ['result'], function (err, response) {
						var msg = Q.firstErrorMessage(err, response && response.errors);
						if (msg) {
							return _close();
						}

						var result = response.slots.result;
						var type = result.type;

						var streamType = type ? Q.Websites.getStreamType(type) : Q.Websites.getStreamType(url);

						$(".Websites_webpage_chat_preview", websitesPreview[url]).tool("Streams/preview", {
							publisherId: result.publisherId || Q.Users.loggedInUserId(),
							streamName: result.streamName,
							closeable: false,
							editable: false
						}).tool(streamType + "/preview", {
							url: url,
							publisherId: result.publisherId,
							streamName: result.streamName,
							editable: false
						}, Date.now()).activate(function () {
							var previewTool = this;
							var $te = $(previewTool.element);

							$te.outerWidth($form.innerWidth());

							$te.off(Q.Pointer.fastclick).on(Q.Pointer.fastclick, function () {
								window.open(url, '_blank');
							});
						});
					}, {
						method: 'post',
						fields: {url: url}
					});
				});
			});
		},
		/**
		 *	Get valid preview tool (Q/audio, Q/video, Websites/webpage) from jquery or html element
		 * @method getToolFromElement
		 * @return {jQuery|HTMLElement} element
		 */
		getToolFromElement: function (element) {
			return Q.Tool.from(element, "Websites/webpage/preview") || Q.Tool.from(element, "Streams/audio/preview") || Q.Tool.from(element, "Streams/video/preview");
		},
		/**
		 *	Add Websites/webpage/preview tools to chat messages
		 *
		 * @method parseChatMessage
		 * @return {jQuery|HTMLElement} element Chat message element (Streams_chat_item)
		 */
		parseChatMessage: function (element, instructions) {
			if (!(element instanceof jQuery)) {
				element = $(element);
			}

			if (element.attr('data-webpageProcessed')) {
				return;
			}

			var state = this.state;

			// parse all links in message
			var $chatMessageContent = $(".Streams_chat_message_content", element);
			var chatMessageContent = $chatMessageContent.html();
			Q.each(chatMessageContent.matchTypes('url'), function (i, url) {
				var href = url;
				if (href.indexOf('//') === -1) {
					href = '//' + href;
				}

				chatMessageContent = chatMessageContent.replace(url, "" + url + "");
			});
			$chatMessageContent.html(chatMessageContent);

			instructions = Q.getObject('Websites/webpages', JSON.parse(instructions || null));
			if (instructions) {
				var elementToAppend = state.appendTo === 'bubble' ? $(".Streams_chat_bubble", element) : element;
				var streamType = instructions.streamName ? instructions.streamName.substr(0, instructions.streamName.lastIndexOf("/")) : Q.Websites.getStreamType(instructions.url);
				instructions.editable = false;

				var streamsPreview = Q.Tool.setUpElementHTML('div', 'Streams/preview', {
					publisherId: instructions.publisherId,
					streamName: instructions.streamName,
					closeable: false,
					editable: false
				});
				var specialPreview = Q.Tool.setUpElementHTML($(streamsPreview).addClass("Websites_webpage_chat_preview")[0], streamType + '/preview', {
					publisherId: instructions.publisherId,
					streamName: instructions.streamName,
					url: instructions.url,
					editable: false
				});

				$(specialPreview).appendTo(elementToAppend);
			}

			// mark element as processed
			element.attr('data-webpageProcessed', 1);

			return element;
		},
		/**
		 *	Return active preview tool
		 * @method getActivePreview
		 * @return {Q.Tool} Returns Websites/webpage/preview tool
		 */
		getActivePreview: function () {
			var websitesPreview = this.state.websitesPreview;

			for (var url in websitesPreview) {
				if (websitesPreview[url]) {
					return $(".Streams_preview_tool", websitesPreview[url]);
				}
			}

			return null;
		},
		/**
		 *	Correctly remove preview element
		 *
		 * @method removePreview
		 * @param {HTMLElement|jQuery} element Element need to remove
		 */
		removePreview: function (element) {
			if (element instanceof HTMLElement) {
				element = $(element);
			}

			var $parent = element.closest(".Websites_webpage_chat");
			Q.Tool.remove(element[0], true, true);
			$parent.remove();
		}
	});

	Q.Template.set('Websites/webpage/chat',
		'
' +
		'

' +
		'	' +
		'
'
	);
})(Q, Q.$, window);<|endoftext|>


<commit_before>// Bézier Math
// Thanks to Prof. F. De Smedt at the Vrije Universiteit Brussel, 2006.

'use strict';

var _ = require('underscore');

var math = require('../util/math');

var Point = require('../objects/point');
var Rect = require('../objects/rect');

var bezier = {};

var MOVETO = bezier.MOVETO = 'M';
var LINETO = bezier.LINETO = 'L';
var CURVETO = bezier.CURVETO = 'C';
var CLOSE = bezier.CLOSE = 'Z';

// BEZIER MATH:

// Returns coordinates for the point at t (0.0-1.0) on the line.
bezier.linePoint = function (t, x0, y0, x1, y1) {
    var x = x0 + t * (x1 - x0),
        y = y0 + t * (y1 - y0);
    return { type: LINETO, x: x, y: y };
};

// Returns the length of the line.
bezier.lineLength = function (x0, y0, x1, y1) {
    var a = Math.pow(Math.abs(x0 - x1), 2),
        b = Math.pow(Math.abs(y0 - y1), 2);
    return Math.sqrt(a + b);
};

// Returns coordinates for the point at t (0.0-1.0) on the curve
// (de Casteljau interpolation algorithm).
bezier.curvePoint = function (t, x0, y0, x1, y1, x2, y2, x3, y3) {
    var dt = 1 - t,
        x01 = x0 * dt + x1 * t,
        y01 = y0 * dt + y1 * t,
        x12 = x1 * dt + x2 * t,
        y12 = y1 * dt + y2 * t,
        x23 = x2 * dt + x3 * t,
        y23 = y2 * dt + y3 * t,

        h1x = x01 * dt + x12 * t,
        h1y = y01 * dt + y12 * t,
        h2x = x12 * dt + x23 * t,
        h2y = y12 * dt + y23 * t,
        x = h1x * dt + h2x * t,
        y = h1y * dt + h2y * t;
    return { type: CURVETO, x1: h1x, y1: h1y, x2: h2x, y2: h2y, x: x, y: y };
};

// Returns the length of the curve.
// Integrates the estimated length of the cubic bezier spline defined by x0, y0, ... x3, y3,
// by adding up the length of n linear lines along the curve.
bezier.curveLength = function (x0, y0, x1, y1, x2, y2, x3, y3, n) {
    if (n === undefined) { n = 20; }
    var i, t, cmd,
        length = 0,
        xi = x0,
        yi = y0;
    for (i = 0; i < n; i += 1) {
        t = (i + 1) / n;
        cmd = bezier.curvePoint(t, x0, y0, x1, y1, x2, y2, x3, y3);
        length += Math.sqrt(
            Math.pow(Math.abs(xi - cmd.x), 2) +
                Math.pow(Math.abs(yi - cmd.y), 2)
        );
        xi = cmd.x;
        yi = cmd.y;
    }
    return length;
};

// BEZIER PATH LENGTH:

// Returns an array with the length of each command in the path.
// With relative=true, the total length of all commands is 1.0.
bezier.segmentLengths = function (commands, relative, n) {
    relative = relative !== undefined ? relative : false;
    if (n === undefined) { n = 20; }
    var i, cmd, type, closeX, closeY, x0, y0, s, lengths;
    lengths = [];
    for (i = 0; i < commands.length; i += 1) {
        cmd = commands[i];
        type = cmd.type;

        if (i === 0) {
            closeX = cmd.x;
            closeY = cmd.y;
        } else if (type === MOVETO) {
            closeX = cmd.x;
            closeY = cmd.y;
            lengths.push(0.0);
        } else if (type === CLOSE) {
            lengths.push(bezier.lineLength(x0, y0, closeX, closeY));
        } else if (type === LINETO) {
            lengths.push(bezier.lineLength(x0, y0, cmd.x, cmd.y));
        } else if (type === CURVETO) {
            lengths.push(bezier.curveLength(x0, y0, cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y, n));
        }
        if (type !== CLOSE) {
            x0 = cmd.x;
            y0 = cmd.y;
        }
    }
    if (relative === true) {
        s = math.sum(lengths);
        return (s > 0) ?
                _.map(lengths, function (v) { return v / s; }) :
                _.map(lengths, function () { return 0.0; });
    }
    return lengths;
};

// Returns the approximate length of the path.
// Calculates the length of each curve in the path using n linear samples.
// With segmented=true, returns an array with the relative length of each segment (sum=1.0).
bezier.length = function (path, segmented, n) {
    if (n === undefined) { n = 20; }
    return (!segmented) ?
            math.sum(bezier.segmentLengths(path.commands, false, n)) :
            bezier.segmentLengths(path.commands, true, n);
};

// BEZIER PATH POINT:

// For a given relative t on the path (0.0-1.0), returns an array [index, t, PathElement],
// with the index of the PathElement before t, the absolute position on this segment,
// the last MOVETO or any subsequent CLOSE commands after i.
// Note: during iteration, supplying segment lengths yourself is 30x faster.
bezier._locate = function (path, t, segmentLengths) {
    var i, cmd, closeTo;
    if (segmentLengths === undefined) {
        segmentLengths = bezier.segmentLengths(path.commands, true);
    }
    for (i = 0; i < path.commands.length; i += 1) {
        cmd = path.commands[i];
        if (i === 0 || cmd.type === MOVETO) {
            closeTo =  new Point(cmd.x, cmd.y);
        }
        if (t <= segmentLengths[i] || i === segmentLengths.length - 1) {
            break;
        }
        t -= segmentLengths[i];
    }
    if (segmentLengths[i] !== 0) { t /= segmentLengths[i]; }
    if (i === segmentLengths.length - 1 && segmentLengths[i] === 0) { i -= 1; }
    return [i, t, closeTo];
};

// Returns the DynamicPathElement at time t on the path.
// Note: in PathElement, ctrl1 is how the curve started, and ctrl2 how it arrives in this point.
// Here, ctrl1 is how the curve arrives, and ctrl2 how it continues to the next point.
bezier.point = function (path, t, segmentLengths) {
    var loc, i, closeTo, x0, y0, cmd;
    loc = bezier._locate(path, t, segmentLengths);
    i = loc[0];
    t = loc[1];
    closeTo = loc[2];
    x0 = path.commands[i].x;
    y0 = path.commands[i].y;
    cmd = path.commands[i + 1];
    if (cmd.type === LINETO || cmd.type === CLOSE) {
        cmd = (cmd.type === CLOSE) ?
                 bezier.linePoint(t, x0, y0, closeTo.x, closeTo.y) :
                 bezier.linePoint(t, x0, y0, cmd.x, cmd.y);
    } else if (cmd.type === CURVETO) {
        cmd = bezier.curvePoint(t, x0, y0, cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y);
    }
    return cmd;
};

bezier.extrema = function (x1, y1, x2, y2, x3, y3, x4, y4) {
    var minX, maxX, minY, maxY,
        ax, bx, cx, ay, by, cy,
        temp, rcp, tx, ty;

    function fuzzyCompare(p1, p2) {
        return Math.abs(p1 - p2) <= (0.000000000001 * Math.min(Math.abs(p1), Math.abs(p2)));
    }

    function coefficients(t) {
        var mT, a, b, c, d;
        mT = 1 - t;
        b = mT * mT;
        c = t * t;
        d = c * t;
        a = b * mT;
        b *= (3.0 * t);
        c *= (3.0 * mT);
        return [a, b, c, d];
    }

    function pointAt(t) {
        var a, b, c, d, coeff;
        coeff = coefficients(t);
        a = coeff[0];
        b = coeff[1];
        c = coeff[2];
        d = coeff[3];
        return {x: a * x1 + b * x2 + c * x3 + d * x4,
                y: a * y1 + b * y2 + c * y3 + d * y4};
    }

    function bezierCheck(t) {
        if (t >= 0 && t <= 1) {
            var p = pointAt(t);
            if (p.x < minX) {
                minX = p.x;
            } else if (p.x > maxX) {
                maxX = p.x;
            }
            if (p.y < minY) {
                minY = p.y;
            } else if (p.y > maxY) {
                maxY = p.y;
            }
        }
    }

    if (x1 < x4) {
        minX = x1;
        maxX = x4;
    } else {
        minX = x4;
        maxX = x1;
    }
    if (y1 < y4) {
        minY = y1;
        maxY = y4;
    } else {
        minY = y4;
        maxY = y1;
    }

    ax = 3 * (-x1 + 3 * x2 - 3 * x3 + x4);
    bx = 6 * (x1 - 2 * x2 + x3);
    cx = 3 * (-x1 + x2);

    if (fuzzyCompare(ax + 1, 1)) {
        if (!fuzzyCompare(bx + 1, 1)) {
            bezierCheck(-cx / bx);
        }
    } else {
        tx = bx * bx - 4 * ax * cx;
        if (tx >= 0) {
            temp = Math.sqrt(tx);
            rcp = 1 / (2 * ax);
            bezierCheck((-bx + temp) * rcp);
            bezierCheck((-bx - temp) * rcp);
        }
    }

    ay = 3 * (-y1 + 3 * y2 - 3 * y3 + y4);
    by = 6 * (y1 - 2 * y2 + y3);
    cy = 3 * (-y1 + y2);

    if (fuzzyCompare(ay + 1, 1)) {
        if (!fuzzyCompare(by + 1, 1)) {
            bezierCheck(-cy / by);
        }
    } else {
        ty = by * by - 4 * ay * cy;
        if (ty > 0) {
            temp = Math.sqrt(ty);
            rcp = 1 / (2 * ay);
            bezierCheck((-by + temp) * rcp);
            bezierCheck((-by - temp) * rcp);
        }
    }

    return new Rect(minX, minY, maxX - minX, maxY - minY);
};

module.exports = bezier;<commit_msg>bezier.length only returns length<commit_after>// Bézier Math
// Thanks to Prof. F. De Smedt at the Vrije Universiteit Brussel, 2006.

'use strict';

var _ = require('underscore');

var math = require('../util/math');

var Point = require('../objects/point');
var Rect = require('../objects/rect');

var bezier = {};

var MOVETO = bezier.MOVETO = 'M';
var LINETO = bezier.LINETO = 'L';
var CURVETO = bezier.CURVETO = 'C';
var CLOSE = bezier.CLOSE = 'Z';

// BEZIER MATH:

// Returns coordinates for the point at t (0.0-1.0) on the line.
bezier.linePoint = function (t, x0, y0, x1, y1) {
    var x = x0 + t * (x1 - x0),
        y = y0 + t * (y1 - y0);
    return { type: LINETO, x: x, y: y };
};

// Returns the length of the line.
bezier.lineLength = function (x0, y0, x1, y1) {
    var a = Math.pow(Math.abs(x0 - x1), 2),
        b = Math.pow(Math.abs(y0 - y1), 2);
    return Math.sqrt(a + b);
};

// Returns coordinates for the point at t (0.0-1.0) on the curve
// (de Casteljau interpolation algorithm).
bezier.curvePoint = function (t, x0, y0, x1, y1, x2, y2, x3, y3) {
    var dt = 1 - t,
        x01 = x0 * dt + x1 * t,
        y01 = y0 * dt + y1 * t,
        x12 = x1 * dt + x2 * t,
        y12 = y1 * dt + y2 * t,
        x23 = x2 * dt + x3 * t,
        y23 = y2 * dt + y3 * t,

        h1x = x01 * dt + x12 * t,
        h1y = y01 * dt + y12 * t,
        h2x = x12 * dt + x23 * t,
        h2y = y12 * dt + y23 * t,
        x = h1x * dt + h2x * t,
        y = h1y * dt + h2y * t;
    return { type: CURVETO, x1: h1x, y1: h1y, x2: h2x, y2: h2y, x: x, y: y };
};

// Returns the length of the curve.
// Integrates the estimated length of the cubic bezier spline defined by x0, y0, ... x3, y3,
// by adding up the length of n linear lines along the curve.
bezier.curveLength = function (x0, y0, x1, y1, x2, y2, x3, y3, n) {
    if (n === undefined) { n = 20; }
    var i, t, cmd,
        length = 0,
        xi = x0,
        yi = y0;
    for (i = 0; i < n; i += 1) {
        t = (i + 1) / n;
        cmd = bezier.curvePoint(t, x0, y0, x1, y1, x2, y2, x3, y3);
        length += Math.sqrt(
            Math.pow(Math.abs(xi - cmd.x), 2) +
                Math.pow(Math.abs(yi - cmd.y), 2)
        );
        xi = cmd.x;
        yi = cmd.y;
    }
    return length;
};

// BEZIER PATH LENGTH:

// Returns an array with the length of each command in the path.
// With relative=true, the total length of all commands is 1.0.
bezier.segmentLengths = function (commands, relative, n) {
    relative = relative !== undefined ? relative : false;
    if (n === undefined) { n = 20; }
    var i, cmd, type, closeX, closeY, x0, y0, s, lengths;
    lengths = [];
    for (i = 0; i < commands.length; i += 1) {
        cmd = commands[i];
        type = cmd.type;

        if (i === 0) {
            closeX = cmd.x;
            closeY = cmd.y;
        } else if (type === MOVETO) {
            closeX = cmd.x;
            closeY = cmd.y;
            lengths.push(0.0);
        } else if (type === CLOSE) {
            lengths.push(bezier.lineLength(x0, y0, closeX, closeY));
        } else if (type === LINETO) {
            lengths.push(bezier.lineLength(x0, y0, cmd.x, cmd.y));
        } else if (type === CURVETO) {
            lengths.push(bezier.curveLength(x0, y0, cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y, n));
        }
        if (type !== CLOSE) {
            x0 = cmd.x;
            y0 = cmd.y;
        }
    }
    if (relative === true) {
        s = math.sum(lengths);
        return (s > 0) ?
                _.map(lengths, function (v) { return v / s; }) :
                _.map(lengths, function () { return 0.0; });
    }
    return lengths;
};

// Returns the approximate length of the path.
// Calculates the length of each curve in the path using n linear samples.
bezier.length = function (path, n) {
    n = n || 20;
    return math.sum(bezier.segmentLengths, false, n);
};

// BEZIER PATH POINT:

// For a given relative t on the path (0.0-1.0), returns an array [index, t, PathElement],
// with the index of the PathElement before t, the absolute position on this segment,
// the last MOVETO or any subsequent CLOSE commands after i.
// Note: during iteration, supplying segment lengths yourself is 30x faster.
bezier._locate = function (path, t, segmentLengths) {
    var i, cmd, closeTo;
    if (segmentLengths === undefined) {
        segmentLengths = bezier.segmentLengths(path.commands, true);
    }
    for (i = 0; i < path.commands.length; i += 1) {
        cmd = path.commands[i];
        if (i === 0 || cmd.type === MOVETO) {
            closeTo =  new Point(cmd.x, cmd.y);
        }
        if (t <= segmentLengths[i] || i === segmentLengths.length - 1) {
            break;
        }
        t -= segmentLengths[i];
    }
    if (segmentLengths[i] !== 0) { t /= segmentLengths[i]; }
    if (i === segmentLengths.length - 1 && segmentLengths[i] === 0) { i -= 1; }
    return [i, t, closeTo];
};

// Returns the DynamicPathElement at time t on the path.
// Note: in PathElement, ctrl1 is how the curve started, and ctrl2 how it arrives in this point.
// Here, ctrl1 is how the curve arrives, and ctrl2 how it continues to the next point.
bezier.point = function (path, t, segmentLengths) {
    var loc, i, closeTo, x0, y0, cmd;
    loc = bezier._locate(path, t, segmentLengths);
    i = loc[0];
    t = loc[1];
    closeTo = loc[2];
    x0 = path.commands[i].x;
    y0 = path.commands[i].y;
    cmd = path.commands[i + 1];
    if (cmd.type === LINETO || cmd.type === CLOSE) {
        cmd = (cmd.type === CLOSE) ?
                 bezier.linePoint(t, x0, y0, closeTo.x, closeTo.y) :
                 bezier.linePoint(t, x0, y0, cmd.x, cmd.y);
    } else if (cmd.type === CURVETO) {
        cmd = bezier.curvePoint(t, x0, y0, cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y);
    }
    return cmd;
};

bezier.extrema = function (x1, y1, x2, y2, x3, y3, x4, y4) {
    var minX, maxX, minY, maxY,
        ax, bx, cx, ay, by, cy,
        temp, rcp, tx, ty;

    function fuzzyCompare(p1, p2) {
        return Math.abs(p1 - p2) <= (0.000000000001 * Math.min(Math.abs(p1), Math.abs(p2)));
    }

    function coefficients(t) {
        var mT, a, b, c, d;
        mT = 1 - t;
        b = mT * mT;
        c = t * t;
        d = c * t;
        a = b * mT;
        b *= (3.0 * t);
        c *= (3.0 * mT);
        return [a, b, c, d];
    }

    function pointAt(t) {
        var a, b, c, d, coeff;
        coeff = coefficients(t);
        a = coeff[0];
        b = coeff[1];
        c = coeff[2];
        d = coeff[3];
        return {x: a * x1 + b * x2 + c * x3 + d * x4,
                y: a * y1 + b * y2 + c * y3 + d * y4};
    }

    function bezierCheck(t) {
        if (t >= 0 && t <= 1) {
            var p = pointAt(t);
            if (p.x < minX) {
                minX = p.x;
            } else if (p.x > maxX) {
                maxX = p.x;
            }
            if (p.y < minY) {
                minY = p.y;
            } else if (p.y > maxY) {
                maxY = p.y;
            }
        }
    }

    if (x1 < x4) {
        minX = x1;
        maxX = x4;
    } else {
        minX = x4;
        maxX = x1;
    }
    if (y1 < y4) {
        minY = y1;
        maxY = y4;
    } else {
        minY = y4;
        maxY = y1;
    }

    ax = 3 * (-x1 + 3 * x2 - 3 * x3 + x4);
    bx = 6 * (x1 - 2 * x2 + x3);
    cx = 3 * (-x1 + x2);

    if (fuzzyCompare(ax + 1, 1)) {
        if (!fuzzyCompare(bx + 1, 1)) {
            bezierCheck(-cx / bx);
        }
    } else {
        tx = bx * bx - 4 * ax * cx;
        if (tx >= 0) {
            temp = Math.sqrt(tx);
            rcp = 1 / (2 * ax);
            bezierCheck((-bx + temp) * rcp);
            bezierCheck((-bx - temp) * rcp);
        }
    }

    ay = 3 * (-y1 + 3 * y2 - 3 * y3 + y4);
    by = 6 * (y1 - 2 * y2 + y3);
    cy = 3 * (-y1 + y2);

    if (fuzzyCompare(ay + 1, 1)) {
        if (!fuzzyCompare(by + 1, 1)) {
            bezierCheck(-cy / by);
        }
    } else {
        ty = by * by - 4 * ay * cy;
        if (ty > 0) {
            temp = Math.sqrt(ty);
            rcp = 1 / (2 * ay);
            bezierCheck((-by + temp) * rcp);
            bezierCheck((-by - temp) * rcp);
        }
    }

    return new Rect(minX, minY, maxX - minX, maxY - minY);
};

module.exports = bezier;<|endoftext|>


<commit_before>requirejs.config({
	urlArgs: 'v=3',
	paths: {
		'theme': 'livedesk-embed/themes/big-screen'
	}
});
require(['default.min'], function() {
	require(['../scripts/js/config'], function(){
		var name;
		for(name in livedesk.theme) {
			define('tmpl!theme/'+name, ['dust/compiler'], function(dust){
				dust.loadSource(dust.compile(livedesk.theme[name],'theme/'+name));
			});
		}
		function loadCss(url) {
			var link = document.createElement("link");
			link.type = "text/css";
			link.rel = "stylesheet";
			link.href = url;
			document.getElementsByTagName("head")[0].appendChild(link);
		}
		loadCss(require.toUrl('theme/livedesk.css'));//'css!theme/livedesk',
		require(['livedesk-embed/main']);
	});
});
<commit_msg>Update big-screen.js<commit_after>requirejs.config({
	urlArgs: 'v=4',
	paths: {
		'theme': 'livedesk-embed/themes/big-screen'
	}
});
require(['default.min'], function() {
	require(['../scripts/js/config'], function(){
		var name;
		for(name in livedesk.theme) {
			define('tmpl!theme/'+name, ['dust/compiler'], function(dust){
				dust.loadSource(dust.compile(livedesk.theme[name],'theme/'+name));
			});
		}
		function loadCss(url) {
			var link = document.createElement("link");
			link.type = "text/css";
			link.rel = "stylesheet";
			link.href = url;
			document.getElementsByTagName("head")[0].appendChild(link);
		}
		loadCss(require.toUrl('theme/livedesk.css'));//'css!theme/livedesk',
		require(['livedesk-embed/main']);
	});
});
<|endoftext|>


<commit_before>// Copyright (C) 2016 The Android Open Source Project
//
// 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.
(function() {
  'use strict';

  // Polymer makes `app` intrinsically defined on the window by virtue of the
  // custom element having the id "app", but it is made explicit here.
  var app = document.querySelector('#app');
  if (!app) {
    console.log('No gr-app found (running tests)');
  }

  var _reporting;
  function getReporting() {
    if (!_reporting) {
      _reporting = document.createElement('gr-reporting');
    }
    return _reporting;
  }

  document.onload = function() {
    getReporting().pageLoaded();
  };

  var base = window.Gerrit.BaseUrlBehavior.getBaseUrl();
  if (base) {
    page.base(base);
  }

  window.addEventListener('WebComponentsReady', function() {
    getReporting().timeEnd('WebComponentsReady');
  });

  function startRouter() {
    var restAPI = document.createElement('gr-rest-api-interface');
    var reporting = getReporting();

    // Middleware
    page(function(ctx, next) {
      document.body.scrollTop = 0;

      // Fire asynchronously so that the URL is changed by the time the event
      // is processed.
      app.async(function() {
        app.fire('location-change', {
          hash: window.location.hash,
          pathname: window.location.pathname,
        });
        reporting.locationChanged();
      }, 1);
      next();
    });

    function loadUser(ctx, next) {
      restAPI.getLoggedIn().then(function() {
        next();
      });
    }

    // Routes.
    page('/', loadUser, function(data) {
      if (data.querystring.match(/^closeAfterLogin/)) {
        // Close child window on redirect after login.
        window.close();
      }
      // For backward compatibility with GWT links.
      if (data.hash) {
        // In certain login flows the server may redirect to a hash without
        // a leading slash, which page.js doesn't handle correctly.
        if (data.hash[0] !== '/') {
          data.hash = '/' + data.hash;
        }
        var newUrl = data.hash;
        if (newUrl.indexOf('/VE/') === 0) {
          newUrl = '/settings' + data.hash;
        }
        page.redirect(newUrl);
        return;
      }
      restAPI.getLoggedIn().then(function(loggedIn) {
        if (loggedIn) {
          page.redirect('/dashboard/self');
        } else {
          page.redirect('/q/status:open');
        }
      });
    });

    page('/dashboard/(.*)', loadUser, function(data) {
      restAPI.getLoggedIn().then(function(loggedIn) {
        if (loggedIn) {
          data.params.view = 'gr-dashboard-view';
          app.params = data.params;
        } else {
          page.redirect('/login/' + encodeURIComponent(data.canonicalPath));
        }
      });
    });

    page('/admin/(.*)', loadUser, function(data) {
      restAPI.getLoggedIn().then(function(loggedIn) {
        if (loggedIn) {
          data.params.view = 'gr-admin-view';
          app.params = data.params;
        } else {
          page.redirect('/login/' + encodeURIComponent(data.canonicalPath));
        }
      });
    });

    function queryHandler(data) {
      data.params.view = 'gr-change-list-view';
      app.params = data.params;
    }

    page('/q/:query,:offset', queryHandler);
    page('/q/:query', queryHandler);

    page(/^\/(\d+)\/?/, function(ctx) {
      page.redirect('/c/' + encodeURIComponent(ctx.params[0]));
    });

    function normalizePatchRangeParams(params) {
      if (params.basePatchNum && !params.patchNum) {
        params.patchNum = params.basePatchNum;
        params.basePatchNum = null;
      }
    }

    // Matches /c/<changeNum>/[<basePatchNum>..][<patchNum>].
    page(/^\/c\/(\d+)\/?(((\d+)(\.\.(\d+))?))?$/, function(ctx) {
      // Parameter order is based on the regex group number matched.
      var params = {
        changeNum: ctx.params[0],
        basePatchNum: ctx.params[3],
        patchNum: ctx.params[5],
        view: 'gr-change-view',
      };

      // Don't allow diffing the same patch number against itself.
      if (params.basePatchNum != null &&
          params.basePatchNum === params.patchNum) {
        page.redirect('/c/' +
            encodeURIComponent(params.changeNum) +
            '/' +
            encodeURIComponent(params.patchNum) +
            '/');
        return;
      }
      normalizePatchRangeParams(params);
      app.params = params;
    });

    // Matches /c/<changeNum>/[<basePatchNum>..]<patchNum>/<path>.
    page(/^\/c\/(\d+)\/((\d+)(\.\.(\d+))?)\/(.+)/, function(ctx) {
      // Parameter order is based on the regex group number matched.
      var params = {
        changeNum: ctx.params[0],
        basePatchNum: ctx.params[2],
        patchNum: ctx.params[4],
        path: ctx.params[5],
        view: 'gr-diff-view',
      };
      // Don't allow diffing the same patch number against itself.
      if (params.basePatchNum === params.patchNum) {
        // TODO(kaspern): Utilize gr-url-encoding-behavior.html when the router
        // is replaced with a Polymer counterpart.
        // @see Issue 4255 regarding double-encoding.
        var path = encodeURIComponent(encodeURIComponent(params.path));
        // @see Issue 4577 regarding more readable URLs.
        path = path.replace(/%252F/g, '/');
        path = path.replace(/%2520/g, '+');

        page.redirect('/c/' +
            encodeURIComponent(params.changeNum) +
            '/' +
            encodeURIComponent(params.patchNum) +
            '/' +
            path);
        return;
      }

      // Check if path has an '@' which indicates it was using GWT style line
      // numbers. Even if the filename had an '@' in it, it would have already
      // been URI encoded. Redirect to hash version of path.
      if (ctx.path.indexOf('@') !== -1) {
        page.redirect(ctx.path.replace('@', '#'));
        return;
      }

      normalizePatchRangeParams(params);
      app.params = params;
    });

    page(/^\/settings\/(agreements|new-agreement)/, loadUser, function(data) {
      restAPI.getLoggedIn().then(function(loggedIn) {
        if (loggedIn) {
          data.params.view = 'gr-cla-view';
          app.params = data.params;
        } else {
          page.redirect('/login/' + encodeURIComponent(data.canonicalPath));
        }
      });
    });

    page(/^\/settings\/VE\/(\S+)/, function(data) {
      restAPI.getLoggedIn().then(function(loggedIn) {
        if (loggedIn) {
          app.params = {
            view: 'gr-settings-view',
            emailToken: data.params[0],
          };
        } else {
          page.show('/login/' + encodeURIComponent(data.canonicalPath));
        }
      });
    });

    page(/^\/settings\/?/, function(data) {
      restAPI.getLoggedIn().then(function(loggedIn) {
        if (loggedIn) {
          app.params = {view: 'gr-settings-view'};
        } else {
          page.show('/login/' + encodeURIComponent(data.canonicalPath));
        }
      });
    });

    page(/^\/register(\/.*)?/, function(ctx) {
      app.params = {justRegistered: true};
      var path = ctx.params[0] || '/';
      page.show(path);
    });

    page.start();
  }

  Polymer({
    is: 'gr-router',
    start: function() {
      if (!app) { return; }
      startRouter();
    },
  });
})();
<commit_msg>Relocate setting page base to app initialization<commit_after>// Copyright (C) 2016 The Android Open Source Project
//
// 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.
(function() {
  'use strict';

  // Polymer makes `app` intrinsically defined on the window by virtue of the
  // custom element having the id "app", but it is made explicit here.
  var app = document.querySelector('#app');
  if (!app) {
    console.log('No gr-app found (running tests)');
  }

  var _reporting;
  function getReporting() {
    if (!_reporting) {
      _reporting = document.createElement('gr-reporting');
    }
    return _reporting;
  }

  document.onload = function() {
    getReporting().pageLoaded();
  };

  window.addEventListener('WebComponentsReady', function() {
    getReporting().timeEnd('WebComponentsReady');
  });

  function startRouter() {
    var base = window.Gerrit.BaseUrlBehavior.getBaseUrl();
    if (base) {
      page.base(base);
    }

    var restAPI = document.createElement('gr-rest-api-interface');
    var reporting = getReporting();

    // Middleware
    page(function(ctx, next) {
      document.body.scrollTop = 0;

      // Fire asynchronously so that the URL is changed by the time the event
      // is processed.
      app.async(function() {
        app.fire('location-change', {
          hash: window.location.hash,
          pathname: window.location.pathname,
        });
        reporting.locationChanged();
      }, 1);
      next();
    });

    function loadUser(ctx, next) {
      restAPI.getLoggedIn().then(function() {
        next();
      });
    }

    // Routes.
    page('/', loadUser, function(data) {
      if (data.querystring.match(/^closeAfterLogin/)) {
        // Close child window on redirect after login.
        window.close();
      }
      // For backward compatibility with GWT links.
      if (data.hash) {
        // In certain login flows the server may redirect to a hash without
        // a leading slash, which page.js doesn't handle correctly.
        if (data.hash[0] !== '/') {
          data.hash = '/' + data.hash;
        }
        var newUrl = data.hash;
        if (newUrl.indexOf('/VE/') === 0) {
          newUrl = '/settings' + data.hash;
        }
        page.redirect(newUrl);
        return;
      }
      restAPI.getLoggedIn().then(function(loggedIn) {
        if (loggedIn) {
          page.redirect('/dashboard/self');
        } else {
          page.redirect('/q/status:open');
        }
      });
    });

    page('/dashboard/(.*)', loadUser, function(data) {
      restAPI.getLoggedIn().then(function(loggedIn) {
        if (loggedIn) {
          data.params.view = 'gr-dashboard-view';
          app.params = data.params;
        } else {
          page.redirect('/login/' + encodeURIComponent(data.canonicalPath));
        }
      });
    });

    page('/admin/(.*)', loadUser, function(data) {
      restAPI.getLoggedIn().then(function(loggedIn) {
        if (loggedIn) {
          data.params.view = 'gr-admin-view';
          app.params = data.params;
        } else {
          page.redirect('/login/' + encodeURIComponent(data.canonicalPath));
        }
      });
    });

    function queryHandler(data) {
      data.params.view = 'gr-change-list-view';
      app.params = data.params;
    }

    page('/q/:query,:offset', queryHandler);
    page('/q/:query', queryHandler);

    page(/^\/(\d+)\/?/, function(ctx) {
      page.redirect('/c/' + encodeURIComponent(ctx.params[0]));
    });

    function normalizePatchRangeParams(params) {
      if (params.basePatchNum && !params.patchNum) {
        params.patchNum = params.basePatchNum;
        params.basePatchNum = null;
      }
    }

    // Matches /c/<changeNum>/[<basePatchNum>..][<patchNum>].
    page(/^\/c\/(\d+)\/?(((\d+)(\.\.(\d+))?))?$/, function(ctx) {
      // Parameter order is based on the regex group number matched.
      var params = {
        changeNum: ctx.params[0],
        basePatchNum: ctx.params[3],
        patchNum: ctx.params[5],
        view: 'gr-change-view',
      };

      // Don't allow diffing the same patch number against itself.
      if (params.basePatchNum != null &&
          params.basePatchNum === params.patchNum) {
        page.redirect('/c/' +
            encodeURIComponent(params.changeNum) +
            '/' +
            encodeURIComponent(params.patchNum) +
            '/');
        return;
      }
      normalizePatchRangeParams(params);
      app.params = params;
    });

    // Matches /c/<changeNum>/[<basePatchNum>..]<patchNum>/<path>.
    page(/^\/c\/(\d+)\/((\d+)(\.\.(\d+))?)\/(.+)/, function(ctx) {
      // Parameter order is based on the regex group number matched.
      var params = {
        changeNum: ctx.params[0],
        basePatchNum: ctx.params[2],
        patchNum: ctx.params[4],
        path: ctx.params[5],
        view: 'gr-diff-view',
      };
      // Don't allow diffing the same patch number against itself.
      if (params.basePatchNum === params.patchNum) {
        // TODO(kaspern): Utilize gr-url-encoding-behavior.html when the router
        // is replaced with a Polymer counterpart.
        // @see Issue 4255 regarding double-encoding.
        var path = encodeURIComponent(encodeURIComponent(params.path));
        // @see Issue 4577 regarding more readable URLs.
        path = path.replace(/%252F/g, '/');
        path = path.replace(/%2520/g, '+');

        page.redirect('/c/' +
            encodeURIComponent(params.changeNum) +
            '/' +
            encodeURIComponent(params.patchNum) +
            '/' +
            path);
        return;
      }

      // Check if path has an '@' which indicates it was using GWT style line
      // numbers. Even if the filename had an '@' in it, it would have already
      // been URI encoded. Redirect to hash version of path.
      if (ctx.path.indexOf('@') !== -1) {
        page.redirect(ctx.path.replace('@', '#'));
        return;
      }

      normalizePatchRangeParams(params);
      app.params = params;
    });

    page(/^\/settings\/(agreements|new-agreement)/, loadUser, function(data) {
      restAPI.getLoggedIn().then(function(loggedIn) {
        if (loggedIn) {
          data.params.view = 'gr-cla-view';
          app.params = data.params;
        } else {
          page.redirect('/login/' + encodeURIComponent(data.canonicalPath));
        }
      });
    });

    page(/^\/settings\/VE\/(\S+)/, function(data) {
      restAPI.getLoggedIn().then(function(loggedIn) {
        if (loggedIn) {
          app.params = {
            view: 'gr-settings-view',
            emailToken: data.params[0],
          };
        } else {
          page.show('/login/' + encodeURIComponent(data.canonicalPath));
        }
      });
    });

    page(/^\/settings\/?/, function(data) {
      restAPI.getLoggedIn().then(function(loggedIn) {
        if (loggedIn) {
          app.params = {view: 'gr-settings-view'};
        } else {
          page.show('/login/' + encodeURIComponent(data.canonicalPath));
        }
      });
    });

    page(/^\/register(\/.*)?/, function(ctx) {
      app.params = {justRegistered: true};
      var path = ctx.params[0] || '/';
      page.show(path);
    });

    page.start();
  }

  Polymer({
    is: 'gr-router',
    start: function() {
      if (!app) { return; }
      startRouter();
    },
  });
})();
<|endoftext|>


<commit_before>/*
 * Author: Alexandre Havrileck (Oxyno-zeta)
 * Date: 22/10/16
 * Licence: See Readme
 */
/* ************************************* */
/* ********       IMPORTS       ******** */
/* ************************************* */

/* ************************************* */
/* ********      VARIABLES      ******** */
/* ************************************* */

const basicFunctionPattern = new RegExp(
    // eslint-disable-next-line prefer-template
    ''
    + /^function/.source
    + / *([$_a-zA-Z][$\w]*)?/.source // name
    + / *\([ \n]*([$_a-zA-Z][$\w]*(?:[ \n]*,[ \n]*[$_a-zA-Z][$\w]*)*)*?,?[ \n]*\)/.source // params
    + /[ \n]*{\n*(.*)[ \n]*}$/.source, // body
    's',
);

/* ************************************* */
/* ********  PRIVATE FUNCTIONS  ******** */
/* ************************************* */

/**
 * Try to regex match a string as a javascript function.
 * @param functionString {string} string to match
 * @param splitParams {boolean} whether to split parameters into an array
 * @returns {{name: string, params: string | string[], body: string} | null}
 */
function matchFunction(functionString, splitParams = false) {
    const match = basicFunctionPattern.exec(functionString);
    if (match === null) return null;
    return {
        name: match[1],
        params: splitParams ? commaSplit(match[2]) : match[2],
        body: match[3],
    };
}

/**
 * Split comma separated strings and trim surrounding whitespace.
 * @param string {string | undefined} a string of comma-separated strings
 * @returns {string[]} an array of elements that were separated by commas with
 *   surrounding whitespace trimmed. May be empty.
 */
function commaSplit(string) {
    if (!string) return [];
    return string.split(',').map(x => x.trim());
}

/**
 * Sanitize a string that might have a function definition.
 * Note that this is not a perfect solution, there are still
 * security flaws.
 * @param functionString {string} string to sanitize
 * @returns {Function | null}
 */
function sanitizeFunction(functionString) {
    /* This is not an exhaustive check by any means
     * For instance, function names may have a wide variety of
     * unicode characters and still be valid... oh well!
     *
     * TEST CASES:
     *
     * // Should match (single-line):
     * function() {}
     * function () {}
     * function myFunc(){}
     * function myFunc(arg1){}
     * function(arg1,arg2, arg3,  arg4) {}
     * function myFunc(arg1, arg2, arg3){}
     * function myFunc(arg1, arg2, arg3){console.log('something');}
     * function myFunc(arg1,){}
     * function myFunc(arg1, ){}
     * function myFunc(arg1) {if (true) {var moreCurlyBraces = 1;}}
     *
     * // Should match (multi-line):
     * function myFunc(arg1, arg2, arg3) {
     *     console.log('something');
     * }
     *
     * function myFunc() {
     *     console.log('test');
     *     if (true) {
     *         console.log('test2');
     *     }
     * }
     *
     * // Should not match (single-line):
     * anotherFunction()
     * function myFunc {}
     * function myFunc()); (anotherFunction()
     * function myFunc(){}, anotherFunction()
     */
    const match = matchFunction(functionString, true);
    if (!match) return null;

    // Here's the security flaw. We want this functionality for supporting
    // JSONP, so we've opted for the best attempt at maintaining some amount
    // of security. This should be a little better than eval because it
    // shouldn't automatically execute code, just create a function which can
    // be called later.
    // eslint-disable-next-line no-new-func
    const func = new Function(...match.params, match.body || '');
    func.displayName = match.name;
    return func;
}

/* ************************************* */
/* ********   PUBLIC FUNCTIONS  ******** */
/* ************************************* */

/**
 * Parse a string into either a function or a JSON element.
 * @param string {string} string to parse
 * @returns {Function | Object | Array | null | boolean | number | string}
 */
function parse(string) {
    // Try parsing (and sanitizing) a function
    const func = sanitizeFunction(string);
    if (func !== null) return func;

    try {
        return JSON.parse(string);
    } catch (e) {
        return string;
    }
}

/* ************************************* */
/* ********       EXPORTS       ******** */
/* ************************************* */
export default parse;
<commit_msg>Rename sanitizeFunction to createFunction and update docstring.<commit_after>/*
 * Author: Alexandre Havrileck (Oxyno-zeta)
 * Date: 22/10/16
 * Licence: See Readme
 */
/* ************************************* */
/* ********       IMPORTS       ******** */
/* ************************************* */

/* ************************************* */
/* ********      VARIABLES      ******** */
/* ************************************* */

const basicFunctionPattern = new RegExp(
    // eslint-disable-next-line prefer-template
    ''
    + /^function/.source
    + / *([$_a-zA-Z][$\w]*)?/.source // name
    + / *\([ \n]*([$_a-zA-Z][$\w]*(?:[ \n]*,[ \n]*[$_a-zA-Z][$\w]*)*)*?,?[ \n]*\)/.source // params
    + /[ \n]*{\n*(.*)[ \n]*}$/.source, // body
    's',
);

/* ************************************* */
/* ********  PRIVATE FUNCTIONS  ******** */
/* ************************************* */

/**
 * Try to regex match a string as a javascript function.
 * @param functionString {string} string to match
 * @param splitParams {boolean} whether to split parameters into an array
 * @returns {{name: string, params: string | string[], body: string} | null}
 */
function matchFunction(functionString, splitParams = false) {
    const match = basicFunctionPattern.exec(functionString);
    if (match === null) return null;
    return {
        name: match[1],
        params: splitParams ? commaSplit(match[2]) : match[2],
        body: match[3],
    };
}

/**
 * Split comma separated strings and trim surrounding whitespace.
 * @param string {string | undefined} a string of comma-separated strings
 * @returns {string[]} an array of elements that were separated by commas with
 *   surrounding whitespace trimmed. May be empty.
 */
function commaSplit(string) {
    if (!string) return [];
    return string.split(',').map(x => x.trim());
}

/**
 * Try creating an anonymous function from a string, or return null if it's
 * not a valid function definition.
 * Note that this is not a completely safe, there are still security flaws,
 * but it is safer than using `exec`.
 * @param functionString {string} string to try to parse as a function
 *   definition
 * @returns {Function | null} an anonymous function if the string is a valid
 *   function definition, else null
 */
function createFunction(functionString) {
    /* This is not an exhaustive check by any means
     * For instance, function names may have a wide variety of
     * unicode characters and still be valid... oh well!
     *
     * TEST CASES:
     *
     * // Should match (single-line):
     * function() {}
     * function () {}
     * function myFunc(){}
     * function myFunc(arg1){}
     * function(arg1,arg2, arg3,  arg4) {}
     * function myFunc(arg1, arg2, arg3){}
     * function myFunc(arg1, arg2, arg3){console.log('something');}
     * function myFunc(arg1,){}
     * function myFunc(arg1, ){}
     * function myFunc(arg1) {if (true) {var moreCurlyBraces = 1;}}
     *
     * // Should match (multi-line):
     * function myFunc(arg1, arg2, arg3) {
     *     console.log('something');
     * }
     *
     * function myFunc() {
     *     console.log('test');
     *     if (true) {
     *         console.log('test2');
     *     }
     * }
     *
     * // Should not match (single-line):
     * anotherFunction()
     * function myFunc {}
     * function myFunc()); (anotherFunction()
     * function myFunc(){}, anotherFunction()
     */
    const match = matchFunction(functionString, true);
    if (!match) return null;

    // Here's the security flaw. We want this functionality for supporting
    // JSONP, so we've opted for the best attempt at maintaining some amount
    // of security. This should be a little better than eval because it
    // shouldn't automatically execute code, just create a function which can
    // be called later.
    // eslint-disable-next-line no-new-func
    const func = new Function(...match.params, match.body || '');
    func.displayName = match.name;
    return func;
}

/* ************************************* */
/* ********   PUBLIC FUNCTIONS  ******** */
/* ************************************* */

/**
 * Parse a string into either a function or a JSON element.
 * @param string {string} string to parse
 * @returns {Function | Object | Array | null | boolean | number | string}
 */
function parse(string) {
    // Try parsing (and sanitizing) a function
    const func = createFunction(string);
    if (func !== null) return func;

    try {
        return JSON.parse(string);
    } catch (e) {
        return string;
    }
}

/* ************************************* */
/* ********       EXPORTS       ******** */
/* ************************************* */
export default parse;
<|endoftext|>


<commit_before>//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Expands a min/max into a specified number of buckets
//------------------------------------------------------------------------------
d3plus.utils.buckets = function(arr, buckets) {
  var return_arr = [], step = 1/(buckets-1)*(arr[1]-arr[0]), i = step

  for (var i = arr[0]; i <= arr[1]; i = i + step) {
    return_arr.push(i)
  }
  if (return_arr.length < buckets) {
    return_arr[buckets-1] = arr[1]
  }
  if (return_arr[return_arr.length-1] < arr[1]) {
    return_arr[return_arr.length-1] = arr[1]
  }
  return return_arr
}

//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Finds closest numeric value in array
//------------------------------------------------------------------------------
d3plus.utils.closest = function(arr,value) {
  var closest = arr[0]
  arr.forEach(function(p){
    if (Math.abs(value-p) < Math.abs(value-closest)) {
      closest = p
    }
  })
  return closest
}

//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Get connection dictionary for specified links
//------------------------------------------------------------------------------
d3plus.utils.connections = function(vars,links) {
  var connections = {};
  links.forEach(function(d) {

    if (typeof d.source != "object") {
      d.source = vars.nodes.value.filter(function(n){return n[vars.id.key] == d.source})[0]
    }

    if (typeof d.target != "object") {
      d.target = vars.nodes.value.filter(function(n){return n[vars.id.key] == d.target})[0]
    }

    if (!connections[d.source[vars.id.key]]) {
      connections[d.source[vars.id.key]] = []
    }
    connections[d.source[vars.id.key]].push(d.target)
    if (!connections[d.target[vars.id.key]]) {
      connections[d.target[vars.id.key]] = []
    }
    connections[d.target[vars.id.key]].push(d.source)
  })
  return connections;
}

//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Clones an object, removing any links to the original
//------------------------------------------------------------------------------
d3plus.utils.copy = function(obj) {
  return d3plus.utils.merge(obj)
}

//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Merge two objects to create a new one with the properties of both
//------------------------------------------------------------------------------
d3plus.utils.merge = function(obj1, obj2) {
  var obj3 = {};
  function copy_object(obj,ret) {
    for (var a in obj) {
      if (typeof obj[a] != "undefined") {
        if (obj[a] instanceof Array) {
          ret[a] = obj[a]
        }
        else if (typeof obj[a] == "object") {
          if (!ret[a]) ret[a] = {}
          copy_object(obj[a],ret[a])
        }
        else {
          ret[a] = obj[a]
        }
      }
    }
  }
  if (obj1) copy_object(obj1,obj3)
  if (obj2) copy_object(obj2,obj3)
  return obj3;
}

//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Removes all non ASCII characters
//------------------------------------------------------------------------------
d3plus.utils.strip = function(str) {

  var removed = [ "!","@","#","$","%","^","&","*","(",")",
                  "[","]","{","}",".",",","/","\\","|",
                  "'","\"",";",":","<",">","?","=","+"]

  return str.replace(/[^A-Za-z0-9\-_]/g, function(chr) {

    if (" " == chr) {
      return "_"
    }
    else if (removed.indexOf(chr) >= 0) {
      return ""
    }

    var diacritics = [
        [/[\300-\306]/g, 'A'],
        [/[\340-\346]/g, 'a'],
        [/[\310-\313]/g, 'E'],
        [/[\350-\353]/g, 'e'],
        [/[\314-\317]/g, 'I'],
        [/[\354-\357]/g, 'i'],
        [/[\322-\330]/g, 'O'],
        [/[\362-\370]/g, 'o'],
        [/[\331-\334]/g, 'U'],
        [/[\371-\374]/g, 'u'],
        [/[\321]/g, 'N'],
        [/[\361]/g, 'n'],
        [/[\307]/g, 'C'],
        [/[\347]/g, 'c'],
    ];

    var ret = ""

    for (d in diacritics) {

      if (diacritics[d][0].test(chr)) {
        ret = diacritics[d][1]
        break;
      }

    }

    return ret;

  });

}

//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Returns list of unique values
//------------------------------------------------------------------------------
d3plus.utils.uniques = function(data,value) {
  var type = null
  return d3.nest().key(function(d) {
      if (typeof value == "string") {
        if (!type) type = typeof d[value]
        return d[value]
      }
      else if (typeof value == "function") {
        if (!type) type = typeof value(d)
        return value(d)
      }
      else {
        return d
      }
    })
    .entries(data)
    .reduce(function(a,b){
      var val = b.key
      if (type == "number") val = parseFloat(val)
      return a.concat(val)
    },[]).sort(function(a,b){
      return a - b
    })
}
<commit_msg>modified merge utility to overwrite variables if the type has changed<commit_after>//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Expands a min/max into a specified number of buckets
//------------------------------------------------------------------------------
d3plus.utils.buckets = function(arr, buckets) {
  var return_arr = [], step = 1/(buckets-1)*(arr[1]-arr[0]), i = step

  for (var i = arr[0]; i <= arr[1]; i = i + step) {
    return_arr.push(i)
  }
  if (return_arr.length < buckets) {
    return_arr[buckets-1] = arr[1]
  }
  if (return_arr[return_arr.length-1] < arr[1]) {
    return_arr[return_arr.length-1] = arr[1]
  }
  return return_arr
}

//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Finds closest numeric value in array
//------------------------------------------------------------------------------
d3plus.utils.closest = function(arr,value) {
  var closest = arr[0]
  arr.forEach(function(p){
    if (Math.abs(value-p) < Math.abs(value-closest)) {
      closest = p
    }
  })
  return closest
}

//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Get connection dictionary for specified links
//------------------------------------------------------------------------------
d3plus.utils.connections = function(vars,links) {
  var connections = {};
  links.forEach(function(d) {

    if (typeof d.source != "object") {
      d.source = vars.nodes.value.filter(function(n){return n[vars.id.key] == d.source})[0]
    }

    if (typeof d.target != "object") {
      d.target = vars.nodes.value.filter(function(n){return n[vars.id.key] == d.target})[0]
    }

    if (!connections[d.source[vars.id.key]]) {
      connections[d.source[vars.id.key]] = []
    }
    connections[d.source[vars.id.key]].push(d.target)
    if (!connections[d.target[vars.id.key]]) {
      connections[d.target[vars.id.key]] = []
    }
    connections[d.target[vars.id.key]].push(d.source)
  })
  return connections;
}

//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Clones an object, removing any links to the original
//------------------------------------------------------------------------------
d3plus.utils.copy = function(obj) {
  return d3plus.utils.merge(obj)
}

//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Merge two objects to create a new one with the properties of both
//------------------------------------------------------------------------------
d3plus.utils.merge = function(obj1, obj2) {
  var obj3 = {};
  function copy_object(obj,ret) {
    for (var a in obj) {
      if (typeof obj[a] != "undefined") {
        if (typeof ret[a] != typeof obj[a]) {
          ret[a] = obj[a]
        }
        else if (obj[a] instanceof Array) {
          ret[a] = obj[a]
        }
        else if (typeof obj[a] == "object") {
          if (!ret[a]) ret[a] = {}
          copy_object(obj[a],ret[a])
        }
        else {
          ret[a] = obj[a]
        }
      }
    }
  }
  if (obj1) copy_object(obj1,obj3)
  if (obj2) copy_object(obj2,obj3)
  return obj3;
}

//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Removes all non ASCII characters
//------------------------------------------------------------------------------
d3plus.utils.strip = function(str) {

  var removed = [ "!","@","#","$","%","^","&","*","(",")",
                  "[","]","{","}",".",",","/","\\","|",
                  "'","\"",";",":","<",">","?","=","+"]

  return str.replace(/[^A-Za-z0-9\-_]/g, function(chr) {

    if (" " == chr) {
      return "_"
    }
    else if (removed.indexOf(chr) >= 0) {
      return ""
    }

    var diacritics = [
        [/[\300-\306]/g, 'A'],
        [/[\340-\346]/g, 'a'],
        [/[\310-\313]/g, 'E'],
        [/[\350-\353]/g, 'e'],
        [/[\314-\317]/g, 'I'],
        [/[\354-\357]/g, 'i'],
        [/[\322-\330]/g, 'O'],
        [/[\362-\370]/g, 'o'],
        [/[\331-\334]/g, 'U'],
        [/[\371-\374]/g, 'u'],
        [/[\321]/g, 'N'],
        [/[\361]/g, 'n'],
        [/[\307]/g, 'C'],
        [/[\347]/g, 'c'],
    ];

    var ret = ""

    for (d in diacritics) {

      if (diacritics[d][0].test(chr)) {
        ret = diacritics[d][1]
        break;
      }

    }

    return ret;

  });

}

//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Returns list of unique values
//------------------------------------------------------------------------------
d3plus.utils.uniques = function(data,value) {
  var type = null
  return d3.nest().key(function(d) {
      if (typeof value == "string") {
        if (!type) type = typeof d[value]
        return d[value]
      }
      else if (typeof value == "function") {
        if (!type) type = typeof value(d)
        return value(d)
      }
      else {
        return d
      }
    })
    .entries(data)
    .reduce(function(a,b){
      var val = b.key
      if (type == "number") val = parseFloat(val)
      return a.concat(val)
    },[]).sort(function(a,b){
      return a - b
    })
}
<|endoftext|>