<commit_before>"use strict";

const Immutable = require('immutable')
    , apiFetch = require('./utils/api_fetch')
    , handleResponse = require('./utils/handle_response')
    , parseLD = require('./utils/parse_ld')

const {
  REQUEST_API_RESOURCE,
  REQUEST_NAVIGATION,
} = require('./types').actions

const {
  PENDING,
  SUCCESS,
  FAILURE,
} = require('./types').readyStates

function dispatchReadyState(dispatch, type) {
  const started = new Date().getTime()
      , requestID = Math.random().toString(16).slice(2)

  return (readyState, opts) =>
    dispatch(Object.assign({}, opts, {
      requestID,
      type,
      readyState,
      started,
      updated: new Date().getTime(),
    }))
}

function navigateToPath(router, path, req=null) {
  return dispatch => {
    const match = router.match(path)
        , { resource, makeTripleStore } = match.handler
        , resourceURL = resource ? resource(path) : null
        , headers = {}

    const updateRequest = dispatchReadyState(dispatch, REQUEST_NAVIGATION);

    if (req) {
      headers.Host = req.headers.host;
      headers.cookie = req.headers.cookie;
    }

    const { requestID } = updateRequest(PENDING, { path });

    const promises = [
      dispatch(fetchAPIResource('/me/', { headers })).promise,
      resourceURL && dispatch(
        fetchAPIResource(resourceURL, { headers }, makeTripleStore)
      ).promise
    ]

    const promise = Promise.all(promises).then(
      () => {
        updateRequest(SUCCESS, { path });
      },
      err => {
        updateRequest(FAILURE, { path, err });
      }
    )

    return { requestID, promise }
  }
}

function fetchAPIResource(url, opts={}, parseTriples=false) {
  return (dispatch) => {
    let statusCode

    const updateRequest = dispatchReadyState(dispatch, REQUEST_API_RESOURCE);

    if (!process.browser) {
      url = global.API_URL + url;
    }

    const { requestID } = updateRequest(PENDING, { url });

    opts.headers = Object.assign({}, opts.headers, {
      Accept: 'application/ld+json'
    });

    const promise = apiFetch(url, opts)
      .then(handleResponse)
      .then(resp => {
        statusCode = resp.status;

        return resp.json();
      })
      .then(data => Promise.all([
        data,
        Promise.resolve(parseTriples && parseLD(data))
      ]))
      .then(([data, triples]) => {
        updateRequest(SUCCESS, {
          url,
          statusCode,
          responseData: Immutable.fromJS(data),
          responseTriples: triples
        })
      })
      .catch(err => {
        // TODO: Log stacktrace
        updateRequest(FAILURE, {
          url,
          statusCode: err.statusCode,
          responseError: err.data
        });
      });

    return { requestID, promise }
  }
}

function typeFromRecord(record) {
  const Note = require('./records/note')
      , Topic = require('./records/topic')
      , Document = require('./records/document')

  if (record instanceof Document) return 'document';
  if (record instanceof Topic) return 'topic';
  if (record instanceof Note) return 'note';

  throw new Error('Could not detect item type from record.');
}

function saveItem(id, projectURL, record) {
  return dispatch => {
    const isNew = id === null
        , type = typeFromRecord(record)
        , method = isNew ? 'post' : 'put'
        , body = JSON.stringify(record)

    let url = `${projectURL}${type}s/`

    if (!isNew) {
      url += `${id}/`;
    }

    if (type === 'topic' && !isNew) {
      url += 'w/';
    }

    return dispatch(fetchAPIResource(url, { method, body }));
  }
}

module.exports = {
  fetchAPIResource,
  navigateToPath,
  saveItem,
}
<commit_msg>Pass some response headers in actions<commit_after>"use strict";

const Immutable = require('immutable')
    , apiFetch = require('./utils/api_fetch')
    , handleResponse = require('./utils/handle_response')
    , parseLD = require('./utils/parse_ld')

const {
  REQUEST_API_RESOURCE,
  REQUEST_NAVIGATION,
} = require('./types').actions

const {
  PENDING,
  SUCCESS,
  FAILURE,
} = require('./types').readyStates

function dispatchReadyState(dispatch, type) {
  const started = new Date().getTime()
      , requestID = Math.random().toString(16).slice(2)

  return (readyState, opts) =>
    dispatch(Object.assign({}, opts, {
      requestID,
      type,
      readyState,
      started,
      updated: new Date().getTime(),
    }))
}

function navigateToPath(router, path, req=null) {
  return dispatch => {
    const match = router.match(path)
        , { resource, makeTripleStore } = match.handler
        , resourceURL = resource ? resource(path) : null
        , headers = {}

    const updateRequest = dispatchReadyState(dispatch, REQUEST_NAVIGATION);

    if (req) {
      headers.Host = req.headers.host;
      headers.cookie = req.headers.cookie;
    }

    const { requestID } = updateRequest(PENDING, { path });

    const promises = [
      dispatch(fetchAPIResource('/me/', { headers })).promise,
      resourceURL && dispatch(
        fetchAPIResource(resourceURL, { headers }, makeTripleStore)
      ).promise
    ]

    const promise = Promise.all(promises).then(
      () => {
        updateRequest(SUCCESS, { path });
      },
      err => {
        updateRequest(FAILURE, { path, err });
      }
    )

    return { requestID, promise }
  }
}

const PASSED_HEADERS = [
  'Location',
  'Date',
  'Content-Type',
]

function fetchAPIResource(url, opts={}, parseTriples=false) {
  return (dispatch) => {
    let statusCode
      , responseHeaders = {}

    const updateRequest = dispatchReadyState(dispatch, REQUEST_API_RESOURCE);

    if (!process.browser) {
      url = global.API_URL + url;
    }

    const { requestID } = updateRequest(PENDING, { url });

    opts.headers = Object.assign({}, opts.headers, {
      Accept: 'application/ld+json'
    });

    const promise = apiFetch(url, opts)
      .then(handleResponse)
      .then(resp => {

        statusCode = resp.status;

        PASSED_HEADERS.forEach(header => {
          const value = resp.headers.get(header);

          if (value) {
            responseHeaders[header] = value;
          }
        });

        return resp.json();
      })
      .then(data => Promise.all([
        data,
        Promise.resolve(parseTriples && parseLD(data))
      ]))
      .then(([data, triples]) => {
        updateRequest(SUCCESS, {
          url,
          statusCode,
          responseHeaders,
          responseData: Immutable.fromJS(data),
          responseTriples: triples
        })
      })
      .catch(err => {
        // TODO: Log stacktrace
        updateRequest(FAILURE, {
          url,
          statusCode: err.statusCode,
          responseHeaders,
          responseError: err.data
        });
      });

    return { requestID, promise }
  }
}

function typeFromRecord(record) {
  const Note = require('./records/note')
      , Topic = require('./records/topic')
      , Document = require('./records/document')

  if (record instanceof Document) return 'document';
  if (record instanceof Topic) return 'topic';
  if (record instanceof Note) return 'note';

  throw new Error('Could not detect item type from record.');
}

function saveItem(id, projectURL, record) {
  return dispatch => {
    const isNew = id === null
        , type = typeFromRecord(record)
        , method = isNew ? 'post' : 'put'
        , body = JSON.stringify(record)

    let url = `${projectURL}${type}s/`

    if (!isNew) {
      url += `${id}/`;
    }

    if (type === 'topic' && !isNew) {
      url += 'w/';
    }

    return dispatch(fetchAPIResource(url, { method, body }));
  }
}

module.exports = {
  fetchAPIResource,
  navigateToPath,
  saveItem,
}
<|endoftext|>


<commit_before>/**
 * adapter.js
 *
 * A jQuery/MooTools adapter for various DOM operations.
 * -----------------------------------------------------------------------------
 *
 * Composer.js is an MVC framework for creating and organizing javascript
 * applications. For documentation, please visit:
 *
 *     http://lyonbros.github.com/composer.js/
 *
 * -----------------------------------------------------------------------------
 *
 * Copyright (c) 2011, Lyon Bros Enterprises, LLC. (http://www.lyonbros.com)
 *
 * Licensed under The MIT License.
 * Redistributions of files must retain the above copyright notice.
 */
(function() {
	"use strict";

	var global = this;

	var has_sizzle = !!global.Sizzle;
	var has_jquery = !!global.jQuery;
	var has_slick = !!global.Slick;

	var which_adapter = function(types)
	{
		var wrap = function(fn)
		{
			return function(context, selector) {
				context || (context = document);
				if(types.native && context instanceof window.DocumentFragment)
				{
					return types.native(context, selector);
				}
				else
				{
					return fn(context, selector);
				}
			};
		};
		if(has_slick && types.slick) return wrap(types.slick);
		if(has_sizzle && types.sizzle) return wrap(types.sizzle);
		if(has_jquery && types.jquery) return wrap(types.jquery);
		if('querySelector' in document && types.native) return wrap(types.native);
		throw new Error('No selector engine present. Include Sizzle/jQuery or Slick/Mootools before loading composer (or use a modern browser with document.querySelector).');
	};

	var find = which_adapter({
		slick: function(context, selector)
		{
			return Slick.find(context, selector);
		},
		sizzle: function(context, selector)
		{
			return Sizzle.select(selector, context)[0];
		},
		jquery: function(context, selector)
		{
			return jQuery(context).find(selector)[0];
		},
		native: (function() {
			var scope = false;
			try { document.querySelector(':scope > h1'); scope = true; }
			catch(e) {}

			return function(context, selector) {
				if(scope && !(context instanceof window.DocumentFragment)) selector = ':scope '+selector;
				return context.querySelector(selector);
			};
		})()
	});

	var match = which_adapter({
		slick: function(context, selector)
		{
			return Slick.match(context, selector);
		},
		sizzle: function(context, selector)
		{
			return Sizzle.matchesSelector(context, selector);
		},
		jquery: function(context, selector)
		{
			return jQuery(context).is(selector);
		},
		native: function(context, selector)
		{
			if('matches' in context) var domatch = context.matches;
			if('msMatchesSelector' in context) var domatch = context.msMatchesSelector;
			if('mozMatchesSelector' in context) var domatch = context.mozMatchesSelector;
			if('webkitMatchesSelector' in context) var domatch = context.webkitMatchesSelector;
			return domatch.call(context, selector);
		}
	});

	var captured_events = {
		'focus': true,
		'blur': true
	};
	var add_event = (function() {
		return function(el, ev, fn, selector) {
			var capture = captured_events[ev] || false;
			if(selector)
			{
				el.addEventListener(ev, function(event) {
					// if we have a mootools event class, wrap the event in it
					if(event && window.MooTools && window.DOMEvent) event = new DOMEvent(event);
					var target = event.target || event.srcElement;
					while(target)
					{
						if(match(target, selector))
						{
							fn.apply(this, [event].concat(event.params || []));
							break;
						}
						target = target.parentNode;
						if(target == el.parentNode || target == document.body.parentNode)
						{
							target = false;
						}
					}
				}, capture);
			}
			else
			{
				el.addEventListener(ev, function(event) {
					// if we have a mootools event class, wrap the event in it
					if(event && window.MooTools && window.DOMEvent) event = new DOMEvent(event);
					fn.apply(this, [event].concat(event.params || []));
				}, capture);
			}
		};
	})();

	var remove_event = (function() {
		return function(el, ev, fn) {
			el.removeEventListener(ev, fn, false);
		};
	})();

	var fire_event = (function() {
		/**
		 * NOTE: taken from http://stackoverflow.com/a/2381862/236331
		 *
		 * Fire an event handler to the specified node. Event handlers can
		 * detect that the event was fired programatically by testing for a
		 * 'synthetic=true' property on the event object
		 *
		 * @param {HTMLNode} node The node to fire the event handler on.
		 * @param {String} eventName The name of the event without the "on" (e.g., "focus")
		 */
		return function(el, type, options) {
			options || (options = {});

			if(type == 'click' && el.click)
			{
				return el.click();
			}

			var ev = new CustomEvent(type, options.args);
			el.dispatchEvent(ev);
		};
	})();

	var find_parent = function(selector, element, stop)
	{
		if(!element) return false;
		if(element == stop) return false;
		if(match(element, selector)) return element;
		var par = element.parentNode;
		return find_parent(selector, par);
	};

	var frame = function(cb) { global.requestAnimationFrame(cb); };

	/**
	 * our xdom system! provides hooks for diffing/patching the DOM, and also
	 * allows parts of itself to be replaced by different implementations.
	 */
	var xdom = {
		/**
		 * diff two DOM elements. in the default case, we use morphdom which
		 * does the diffing/patching in one call so we don't really need to do
		 * anything here, but other DOM patching libs may have discrete steps so
		 * we want to have a hook for it.
		 */
		diff: function(from, to, options)
		{
			return [from, to];
		},

		/**
		 * patch the DOM! uses morphdom by default. takes a root DOM node to
		 * patch and a patch to apply to it.
		 */
		patch: function(root, diff, options)
		{
			options || (options = {});

			if(!root || !diff[1]) return;
			var ignore_elements = options.ignore_elements || [];
			var ignore_children = options.ignore_children || [];
			return morphdom(root, diff[1], {
				// this callback preserves form input values (text, checkboxes,
				// radios, textarea, selects)
				onBeforeElUpdated: function(from, to) {
					if(options.reset_inputs) return;

					var tag = from.tagName.toLowerCase();
					switch(tag)
					{
					case 'input':
					case 'textarea':
						to.checked = from.checked;
						to.value = from.value;
						break;
					case 'select':
						to.value = from.value;
						break;
					}
				},
				onBeforeNodeDiscarded: function(node) {
					if(ignore_elements.indexOf(node) >= 0) return false;
				},
				onBeforeElChildrenUpdated: function(from, to) {
					if(ignore_children.indexOf(from) >= 0) return false;
				},
				childrenOnly: options.children_only
			});
		},

		/**
		 * allows hooking in your own DOM diffing/patching library
		 */
		hooks: function(options)
		{
			options || (options = {});
			var diff = options.diff;
			var patch = options.patch;

			if(diff) xdom.diff = diff;
			if(patch) xdom.patch = patch;
		}
	};

	this.Composer.exp0rt({
		find: find,
		match: match,
		add_event: add_event,
		fire_event: fire_event,
		remove_event: remove_event,
		find_parent: find_parent,
		frame: frame,
		xdom: xdom
	});
}).apply((typeof exports != 'undefined') ? exports : this);

<commit_msg>xdom: allow custom `before_update` option passed to html()/xdom that allows merging in various properties of items being diffed. in other words, giving a bit more lower-level control to the way elements are diffed in xdom.<commit_after>/**
 * adapter.js
 *
 * A jQuery/MooTools adapter for various DOM operations.
 * -----------------------------------------------------------------------------
 *
 * Composer.js is an MVC framework for creating and organizing javascript
 * applications. For documentation, please visit:
 *
 *     http://lyonbros.github.com/composer.js/
 *
 * -----------------------------------------------------------------------------
 *
 * Copyright (c) 2011, Lyon Bros Enterprises, LLC. (http://www.lyonbros.com)
 *
 * Licensed under The MIT License.
 * Redistributions of files must retain the above copyright notice.
 */
(function() {
	"use strict";

	var global = this;

	var has_sizzle = !!global.Sizzle;
	var has_jquery = !!global.jQuery;
	var has_slick = !!global.Slick;

	var which_adapter = function(types)
	{
		var wrap = function(fn)
		{
			return function(context, selector) {
				context || (context = document);
				if(types.native && context instanceof window.DocumentFragment)
				{
					return types.native(context, selector);
				}
				else
				{
					return fn(context, selector);
				}
			};
		};
		if(has_slick && types.slick) return wrap(types.slick);
		if(has_sizzle && types.sizzle) return wrap(types.sizzle);
		if(has_jquery && types.jquery) return wrap(types.jquery);
		if('querySelector' in document && types.native) return wrap(types.native);
		throw new Error('No selector engine present. Include Sizzle/jQuery or Slick/Mootools before loading composer (or use a modern browser with document.querySelector).');
	};

	var find = which_adapter({
		slick: function(context, selector)
		{
			return Slick.find(context, selector);
		},
		sizzle: function(context, selector)
		{
			return Sizzle.select(selector, context)[0];
		},
		jquery: function(context, selector)
		{
			return jQuery(context).find(selector)[0];
		},
		native: (function() {
			var scope = false;
			try { document.querySelector(':scope > h1'); scope = true; }
			catch(e) {}

			return function(context, selector) {
				if(scope && !(context instanceof window.DocumentFragment)) selector = ':scope '+selector;
				return context.querySelector(selector);
			};
		})()
	});

	var match = which_adapter({
		slick: function(context, selector)
		{
			return Slick.match(context, selector);
		},
		sizzle: function(context, selector)
		{
			return Sizzle.matchesSelector(context, selector);
		},
		jquery: function(context, selector)
		{
			return jQuery(context).is(selector);
		},
		native: function(context, selector)
		{
			if('matches' in context) var domatch = context.matches;
			if('msMatchesSelector' in context) var domatch = context.msMatchesSelector;
			if('mozMatchesSelector' in context) var domatch = context.mozMatchesSelector;
			if('webkitMatchesSelector' in context) var domatch = context.webkitMatchesSelector;
			return domatch.call(context, selector);
		}
	});

	var captured_events = {
		'focus': true,
		'blur': true
	};
	var add_event = (function() {
		return function(el, ev, fn, selector) {
			var capture = captured_events[ev] || false;
			if(selector)
			{
				el.addEventListener(ev, function(event) {
					// if we have a mootools event class, wrap the event in it
					if(event && window.MooTools && window.DOMEvent) event = new DOMEvent(event);
					var target = event.target || event.srcElement;
					while(target)
					{
						if(match(target, selector))
						{
							fn.apply(this, [event].concat(event.params || []));
							break;
						}
						target = target.parentNode;
						if(target == el.parentNode || target == document.body.parentNode)
						{
							target = false;
						}
					}
				}, capture);
			}
			else
			{
				el.addEventListener(ev, function(event) {
					// if we have a mootools event class, wrap the event in it
					if(event && window.MooTools && window.DOMEvent) event = new DOMEvent(event);
					fn.apply(this, [event].concat(event.params || []));
				}, capture);
			}
		};
	})();

	var remove_event = (function() {
		return function(el, ev, fn) {
			el.removeEventListener(ev, fn, false);
		};
	})();

	var fire_event = (function() {
		/**
		 * NOTE: taken from http://stackoverflow.com/a/2381862/236331
		 *
		 * Fire an event handler to the specified node. Event handlers can
		 * detect that the event was fired programatically by testing for a
		 * 'synthetic=true' property on the event object
		 *
		 * @param {HTMLNode} node The node to fire the event handler on.
		 * @param {String} eventName The name of the event without the "on" (e.g., "focus")
		 */
		return function(el, type, options) {
			options || (options = {});

			if(type == 'click' && el.click)
			{
				return el.click();
			}

			var ev = new CustomEvent(type, options.args);
			el.dispatchEvent(ev);
		};
	})();

	var find_parent = function(selector, element, stop)
	{
		if(!element) return false;
		if(element == stop) return false;
		if(match(element, selector)) return element;
		var par = element.parentNode;
		return find_parent(selector, par);
	};

	var frame = function(cb) { global.requestAnimationFrame(cb); };

	/**
	 * our xdom system! provides hooks for diffing/patching the DOM, and also
	 * allows parts of itself to be replaced by different implementations.
	 */
	var xdom = {
		/**
		 * diff two DOM elements. in the default case, we use morphdom which
		 * does the diffing/patching in one call so we don't really need to do
		 * anything here, but other DOM patching libs may have discrete steps so
		 * we want to have a hook for it.
		 */
		diff: function(from, to, options)
		{
			return [from, to];
		},

		/**
		 * patch the DOM! uses morphdom by default. takes a root DOM node to
		 * patch and a patch to apply to it.
		 */
		patch: function(root, diff, options)
		{
			options || (options = {});

			if(!root || !diff[1]) return;
			var ignore_elements = options.ignore_elements || [];
			var ignore_children = options.ignore_children || [];
			return morphdom(root, diff[1], {
				// this callback preserves form input values (text, checkboxes,
				// radios, textarea, selects)
				onBeforeElUpdated: function(from, to) {
					if(options.reset_inputs) return;

					var tag = from.tagName.toLowerCase();
					switch(tag)
					{
					case 'input':
					case 'textarea':
						to.checked = from.checked;
						to.value = from.value;
						break;
					case 'select':
						to.value = from.value;
						break;
					}
					if(options.before_update instanceof Function)
					{
						options.before_update(from, to);
					}
				},
				onBeforeNodeDiscarded: function(node) {
					if(ignore_elements.indexOf(node) >= 0) return false;
				},
				onBeforeElChildrenUpdated: function(from, to) {
					if(ignore_children.indexOf(from) >= 0) return false;
				},
				childrenOnly: options.children_only
			});
		},

		/**
		 * allows hooking in your own DOM diffing/patching library
		 */
		hooks: function(options)
		{
			options || (options = {});
			var diff = options.diff;
			var patch = options.patch;

			if(diff) xdom.diff = diff;
			if(patch) xdom.patch = patch;
		}
	};

	this.Composer.exp0rt({
		find: find,
		match: match,
		add_event: add_event,
		fire_event: fire_event,
		remove_event: remove_event,
		find_parent: find_parent,
		frame: frame,
		xdom: xdom
	});
}).apply((typeof exports != 'undefined') ? exports : this);

<|endoftext|>


<commit_before>/**
 * kulku.js - Mobility data processing
 * Copyright (C) 2015 haphut <haphut@gmail.com>
 *
 * This file is part of kulku.js.
 *
 * kulku.js is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * kulku.js is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with kulku.js.  If not, see <http://www.gnu.org/licenses/>.
 */

import _ from 'lodash';
import turf from 'turf';

import {isNonNegative} from './util';

let transformLeafletPositionToFix = (leafletEvent) => {
  let latlng = leafletEvent.latlng;
  let coordinates = {
    'longitude': latlng.lng,
    'latitude': latlng.lat
  };

  coordinates = _(leafletEvent)
                .omit(['latlng', 'timestamp'])
                .assign(coordinates)
                .value();

  let timestamp = new Date(leafletEvent.timestamp);

  return createFix(coordinates, timestamp);
};

/**
 * Create a GeoJSON fix from a coordinates object and a timestamp.
 *
 * The function stores only finite values.
 *
 * @param coordinates {Object} The object must follow the Coordinates interface
 * in the Geolocation API Specification W3C Recommendation 2013-10-24.
 * @param timestamp {Date}
 * @see http://www.w3.org/TR/2013/REC-geolocation-API-20131024/#coordinates_interface
 */
let createFix = (coordinates, timestamp) => {
  const longitude = coordinates.longitude;
  if (!_.isNumber(longitude) || longitude < -180 || longitude > 180) {
    throw new Error('coordinates.longitude must be a number on the interval ' +
                    '[-180, 180].');
  }
  const latitude = coordinates.latitude;
  if (!_.isNumber(latitude) || latitude < 0 || latitude > 90) {
    throw new Error('coordinates.latitude must be a number on the interval ' +
                    '[0, 90].');
  }

  let coords = [longitude, latitude];

  const altitude = coordinates.altitude;
  if (altitude) {
    if (!_.isFinite(altitude)) {
      throw new Error('coordinates.altitude, if specified, must be a finite ' +
                      'number or null.');
    }
    coords.push(altitude);
  }

  let properties = _(coordinates)
                   .pick(['speed', 'accuracy', 'altitudeAccuracy'])
                   .pick(isNonNegative)
                   .value();

  const heading = coordinates.heading;
  if (heading && (!properties.speed || properties.speed !== 0)) {
    if (!isNonNegative(heading) || heading >= 360) {
      throw new Error('coordinates.heading, if specified, must be either a ' +
                      'NaN in case coordinates.speed is 0 or a number ' +
                      'between [0, 360[ or null.');
    }
    properties.heading = heading;
  }

  if (!_.isDate(timestamp)) {
    throw new Error('timestamp must be a Date object.');
  }
  properties.timestamp = timestamp;

  return turf.point(coords, properties);
};

export {createFix, transformLeafletPositionToFix};
<commit_msg>Move transformLeaflet to later.<commit_after>/**
 * kulku.js - Mobility data processing
 * Copyright (C) 2015 haphut <haphut@gmail.com>
 *
 * This file is part of kulku.js.
 *
 * kulku.js is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * kulku.js is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with kulku.js.  If not, see <http://www.gnu.org/licenses/>.
 */

import _ from 'lodash';
import turf from 'turf';

import {isNonNegative} from './util';

/**
 * Create a GeoJSON fix from a coordinates object and a timestamp.
 *
 * The function stores only finite values.
 *
 * @param coordinates {Object} The object must follow the Coordinates interface
 * in the Geolocation API Specification W3C Recommendation 2013-10-24.
 * @param timestamp {Date}
 * @see http://www.w3.org/TR/2013/REC-geolocation-API-20131024/#coordinates_interface
 */
let createFix = (coordinates, timestamp) => {
  const longitude = coordinates.longitude;
  if (!_.isNumber(longitude) || longitude < -180 || longitude > 180) {
    throw new Error('coordinates.longitude must be a number on the interval ' +
                    '[-180, 180].');
  }
  const latitude = coordinates.latitude;
  if (!_.isNumber(latitude) || latitude < 0 || latitude > 90) {
    throw new Error('coordinates.latitude must be a number on the interval ' +
                    '[0, 90].');
  }

  let coords = [longitude, latitude];

  const altitude = coordinates.altitude;
  if (altitude) {
    if (!_.isFinite(altitude)) {
      throw new Error('coordinates.altitude, if specified, must be a finite ' +
                      'number or null.');
    }
    coords.push(altitude);
  }

  let properties = _(coordinates)
                   .pick(['speed', 'accuracy', 'altitudeAccuracy'])
                   .pick(isNonNegative)
                   .value();

  const heading = coordinates.heading;
  if (heading && (!properties.speed || properties.speed !== 0)) {
    if (!isNonNegative(heading) || heading >= 360) {
      throw new Error('coordinates.heading, if specified, must be either a ' +
                      'NaN in case coordinates.speed is 0 or a number ' +
                      'between [0, 360[ or null.');
    }
    properties.heading = heading;
  }

  if (!_.isDate(timestamp)) {
    throw new Error('timestamp must be a Date object.');
  }
  properties.timestamp = timestamp;

  return turf.point(coords, properties);
};

let transformLeafletPositionToFix = (leafletEvent) => {
  let latlng = leafletEvent.latlng;
  let coordinates = {
    'longitude': latlng.lng,
    'latitude': latlng.lat
  };

  coordinates = _(leafletEvent)
                .omit(['latlng', 'timestamp'])
                .assign(coordinates)
                .value();

  let timestamp = new Date(leafletEvent.timestamp);

  return createFix(coordinates, timestamp);
};

export {createFix, transformLeafletPositionToFix};
<|endoftext|>


<commit_before>// =============================================================================
// Boost.js | Animations
// (c) Mathigon
// =============================================================================

import {defer, delay, total, toCamelCase} from '@mathigon/core';

// prevent animations on page load
let isReady = false;
setTimeout(function() { isReady = true; });

const BOUNCE_IN = 'cubic-bezier(0.175, 0.885, 0.32, 1.275)';
const BOUNCE_OUT = 'cubic-bezier(0.68, -0.275, 0.825, 0.115)';

// -----------------------------------------------------------------------------
// Simple Animations

export function animate(callback, duration) {
  if (duration === 0) {callback(); return; }

  let startTime = Date.now();
  let time = 0;
  let running = true;

  let deferred = defer();
  let then = deferred.promise.then.bind(deferred.promise);

  function getFrame() {
    if (running && (!duration || time <= duration))
      window.requestAnimationFrame(getFrame);

    time = Date.now() - startTime;
    callback(duration ? Math.min(1,time/duration) : time);
    if (duration && time >= duration) deferred.resolve();
  }

  getFrame();

  return {
    cancel: function() { running = false; deferred.reject(); },
    then };
}

// -----------------------------------------------------------------------------
// Easing

function easeIn(type, t, s) {
  switch (type) {

    case 'quad':   return t * t;
    case 'cubic':  return t * t * t;
    case 'quart':  return t * t * t * t;
    case 'quint':  return t * t * t * t * t;
    case 'circ':   return 1 - Math.sqrt(1 - t * t);
    case 'sine':   return 1 - Math.cos(t * Math.PI / 2);
    case 'exp':    return (t <= 0) ? 0 : Math.pow(2, 10 * (t - 1));

    case 'back':
      if (!s) s = 1.70158;
      return t * t * ((s + 1) * t - s);

    case 'elastic':
      if (!s) s = 0.3;
      return - Math.pow(2, 10 * (t - 1)) * Math.sin(((t - 1) * 2 / s - 0.5) * Math.PI );

    case 'swing':
      return 0.5 - Math.cos(t * Math.PI) / 2;

    case 'spring':
      return 1 - (Math.cos(t * 4.5 * Math.PI) * Math.exp(-t * 6));

    case 'bounce':
      if (t < 1/11) return 1/64 - 7.5625 * (0.5/11 - t) * (0.5/11 - t);  // 121/16 = 7.5625
      if (t < 3/11) return 1/16 - 7.5625 * (  2/11 - t) * (  2/11 - t);
      if (t < 7/11) return 1/4  - 7.5625 * (  5/11 - t) * (  5/11 - t);
      return 1    - 7.5625 * (     1 - t) * (     1 - t);

    default:
      return t;
  }
}

export function ease(type, t = 0, s = 0) {

  if (t === 0) return 0;
  if (t === 1) return 1;
  type = type.split('-');

  if (type[1] === 'in')  return     easeIn(type[0], t, s);
  if (type[1] === 'out') return 1 - easeIn(type[0], 1 - t, s);
  if (t <= 0.5)          return     easeIn(type[0], 2 * t,     s) / 2;
  return 1 - easeIn(type[0], 2 * (1-t), s) / 2;
}

// -----------------------------------------------------------------------------
// Element Animations

export function transition($el, properties, duration=400, _delay=0, easing='ease-in-out') {
  if (!isReady) {
    Object.keys(properties).forEach(k => { let p = properties[k]; $el.css(k, Array.isArray(p) ? p[1] : p); });
    return Promise.resolve();
  }

  if (easing === 'bounce-in') easing = BOUNCE_IN;
  if (easing === 'bounce-out') easing = BOUNCE_OUT;

  // Cancel any previous animations
  if ($el._data._animation) $el._data._animation.cancel();

  const to = {}, from = {};
  const deferred = defer();

  const style = window.getComputedStyle($el._el);
  Object.keys(properties).forEach((k) => {
    const p = properties[k];
    const k1 = toCamelCase(k);
    from[k1] = Array.isArray(p) ? p[0] : style.getPropertyValue(k);
    to[k1] = Array.isArray(p) ? p[1] : p;
    // Set initial style, for the duration of the delay.
    if (_delay) $el.css(k, from[k1]);
  });

  // Special rules for animations to height: auto
  let oldHeight = to.height;
  if (to.height === 'auto') to.height = total($el.children.map($c => $c.outerHeight)) + 'px';

  let player;
  let cancelled = false;

  delay(() => {
    if (cancelled) return;

    player = $el._el.animate([from, to], {duration, easing, fill: 'forwards'});
    player.onfinish = function(e) {
      if ($el._el) Object.keys(properties).forEach(k => $el.css(k, k === 'height' ? oldHeight : to[k]));
      deferred.resolve(e);
      player.cancel();  // bit ugly, but needed for Safari...
    };
  }, _delay);

  const animation = {
    then: deferred.promise.then.bind(deferred.promise),
    cancel() {
      cancelled = true;
      if ($el._el) Object.keys(properties).forEach(k => $el.css(k, $el.css(k)));
      if (player) player.cancel();
    }
  };

  // Only allow cancelling of animation in next thread.
  setTimeout(() => $el._data._animation = animation);
  return animation;
}

// -----------------------------------------------------------------------------
// Element CSS Animations Effects

// When applying the 'pop' effect, we want to respect all existing transform
// except scale. To do that, we have to expand the matrix() notation.
const cssMatrix = /matrix\([0-9.\-\s]+,[0-9.\-\s]+,[0-9.\-\s]+,[0-9.\-\s]+,([0-9.\-\s]+),([0-9.\-\s]+)\)/;

export function enter($el, effect='fade', duration=500, _delay=0) {
  $el.show();
  if (!isReady) return Promise.resolve();

  if (effect === 'fade') {
    return transition($el, {opacity: [0, 1]}, duration, _delay);

  } else if (effect === 'pop') {
    const transform = $el.transform.replace(/scale\([0-9.]*\)/, '')
      .replace(cssMatrix, 'translate($1px,$2px)');

    // TODO Merge into one transition.
    transition($el, {opacity: [0, 1]}, duration, _delay);
    return transition($el, {transform: [transform + ' scale(0.5)',
        transform + ' scale(1)']}, duration, _delay, 'bounce-in');

  } else if (effect === 'descend') {
    const rules = {opacity: [0, 1], transform: ['translateY(-50%)', 'none']};
    return transition($el, rules, duration, _delay);

  } else if (effect.startsWith('draw')) {
    const l = $el.strokeLength + 'px';
    $el.css({opacity: 1, 'stroke-dasharray': l});
    const start = (effect === 'draw-reverse') ? '-' + l : l;
    const rules = {'stroke-dashoffset': [start, 0]};
    return transition($el, rules, duration, _delay, 'linear')
        .then(() => $el.css('stroke-dasharray', ''));

  } else if (effect.startsWith('slide')) {
    const rules = {opacity: [0, 1], transform: ['translateY(50px)', 'none']};
    if (effect.includes('down')) rules.transform[0] = 'translateY(-50px)';
    return transition($el, rules, duration, _delay);

  } else if (effect.startsWith('reveal')) {
    const rules = {opacity: [0, 1], height: [0, 'auto']};
    if (effect.includes('left')) rules.transform = ['translateX(-50%)', 'none'];
    if (effect.includes('right')) rules.transform = ['translateX(50%)', 'none'];
    return transition($el, rules, duration, _delay);
  }
}

export function exit($el, effect='fade', duration=400, delay=0, remove=false) {
  if (!$el._el) return Promise.resolve();
  if (!isReady) { $el.hide(); return Promise.resolve(); }
  if ($el.css('display') === 'none') return Promise.resolve();

  let animation;

  if (effect === 'fade') {
    animation = transition($el, {opacity: [1, 0]}, duration, delay);

  } else if (effect === 'pop') {
    const transform = $el.transform.replace(/scale\([0-9.]*\)/, '');

    transition($el, {opacity: [1, 0]}, duration, delay);
    animation = transition($el, {transform: [transform + ' scale(1)',
        transform + ' scale(0.5)']}, duration, delay, 'bounce-out');

  } else if (effect === 'ascend') {
    const rules = {opacity: [1, 0], transform: ['none', 'translateY(-50%)']};
    animation = transition($el, rules, duration, delay);

  } else if (effect.startsWith('draw')) {
    const l = $el.strokeLength + 'px';
    $el.css('stroke-dasharray', l);
    const end = (effect === 'draw-reverse') ? '-' + l : l;
    const rules = {'stroke-dashoffset': [0, end]};
    animation = transition($el, rules, duration, delay, 'linear');

  } else if (effect.startsWith('slide')) {
    const rules = {opacity: 0, transform: 'translateY(50px)'};
    if (effect.includes('up')) rules.transform = 'translateY(-50px)';
    animation = transition($el, rules, duration, delay);

  } else if (effect.startsWith('reveal')) {
    const rules = {opacity: 0, height: 0};
    if (effect.includes('left')) rules.transform = 'translateX(-50%)';
    if (effect.includes('right')) rules.transform = 'translateX(50%)';
    animation = transition($el, rules, duration, delay);
  }

  animation.then(() => remove ? $el.remove() : $el.hide());
  return animation;
}

// These animations are defined in effects.css:
// pulse-down, pulse-up, flash, bounce-up, bounce-right
export function effect(element, name) {
  element.animationEnd(function(){
    element.removeClass('effects-' + name);
  });
  element.addClass('effects-' + name);
}
<commit_msg>Pass time difference to animate() callback<commit_after>// =============================================================================
// Boost.js | Animations
// (c) Mathigon
// =============================================================================

import {defer, delay, total, toCamelCase} from '@mathigon/core';

// prevent animations on page load
let isReady = false;
setTimeout(function() { isReady = true; });

const BOUNCE_IN = 'cubic-bezier(0.175, 0.885, 0.32, 1.275)';
const BOUNCE_OUT = 'cubic-bezier(0.68, -0.275, 0.825, 0.115)';

// -----------------------------------------------------------------------------
// Simple Animations

export function animate(callback, duration) {
  if (duration === 0) {callback(); return; }

  let startTime = Date.now();
  let lastTime = 0;
  let running = true;

  let deferred = defer();
  let then = deferred.promise.then.bind(deferred.promise);

  function getFrame() {
    if (running && (!duration || lastTime <= duration))
      window.requestAnimationFrame(getFrame);

    const time = Date.now() - startTime;
    callback(duration ? Math.min(1,time/duration) : time, time - lastTime);
    if (duration && time >= duration) deferred.resolve();
    lastTime = time;
  }

  getFrame();

  return {
    cancel: function() { running = false; deferred.reject(); },
    then };
}

// -----------------------------------------------------------------------------
// Easing

function easeIn(type, t, s) {
  switch (type) {

    case 'quad':   return t * t;
    case 'cubic':  return t * t * t;
    case 'quart':  return t * t * t * t;
    case 'quint':  return t * t * t * t * t;
    case 'circ':   return 1 - Math.sqrt(1 - t * t);
    case 'sine':   return 1 - Math.cos(t * Math.PI / 2);
    case 'exp':    return (t <= 0) ? 0 : Math.pow(2, 10 * (t - 1));

    case 'back':
      if (!s) s = 1.70158;
      return t * t * ((s + 1) * t - s);

    case 'elastic':
      if (!s) s = 0.3;
      return - Math.pow(2, 10 * (t - 1)) * Math.sin(((t - 1) * 2 / s - 0.5) * Math.PI );

    case 'swing':
      return 0.5 - Math.cos(t * Math.PI) / 2;

    case 'spring':
      return 1 - (Math.cos(t * 4.5 * Math.PI) * Math.exp(-t * 6));

    case 'bounce':
      if (t < 1/11) return 1/64 - 7.5625 * (0.5/11 - t) * (0.5/11 - t);  // 121/16 = 7.5625
      if (t < 3/11) return 1/16 - 7.5625 * (  2/11 - t) * (  2/11 - t);
      if (t < 7/11) return 1/4  - 7.5625 * (  5/11 - t) * (  5/11 - t);
      return 1    - 7.5625 * (     1 - t) * (     1 - t);

    default:
      return t;
  }
}

export function ease(type, t = 0, s = 0) {

  if (t === 0) return 0;
  if (t === 1) return 1;
  type = type.split('-');

  if (type[1] === 'in')  return     easeIn(type[0], t, s);
  if (type[1] === 'out') return 1 - easeIn(type[0], 1 - t, s);
  if (t <= 0.5)          return     easeIn(type[0], 2 * t,     s) / 2;
  return 1 - easeIn(type[0], 2 * (1-t), s) / 2;
}

// -----------------------------------------------------------------------------
// Element Animations

export function transition($el, properties, duration=400, _delay=0, easing='ease-in-out') {
  if (!isReady) {
    Object.keys(properties).forEach(k => { let p = properties[k]; $el.css(k, Array.isArray(p) ? p[1] : p); });
    return Promise.resolve();
  }

  if (easing === 'bounce-in') easing = BOUNCE_IN;
  if (easing === 'bounce-out') easing = BOUNCE_OUT;

  // Cancel any previous animations
  if ($el._data._animation) $el._data._animation.cancel();

  const to = {}, from = {};
  const deferred = defer();

  const style = window.getComputedStyle($el._el);
  Object.keys(properties).forEach((k) => {
    const p = properties[k];
    const k1 = toCamelCase(k);
    from[k1] = Array.isArray(p) ? p[0] : style.getPropertyValue(k);
    to[k1] = Array.isArray(p) ? p[1] : p;
    // Set initial style, for the duration of the delay.
    if (_delay) $el.css(k, from[k1]);
  });

  // Special rules for animations to height: auto
  let oldHeight = to.height;
  if (to.height === 'auto') to.height = total($el.children.map($c => $c.outerHeight)) + 'px';

  let player;
  let cancelled = false;

  delay(() => {
    if (cancelled) return;

    player = $el._el.animate([from, to], {duration, easing, fill: 'forwards'});
    player.onfinish = function(e) {
      if ($el._el) Object.keys(properties).forEach(k => $el.css(k, k === 'height' ? oldHeight : to[k]));
      deferred.resolve(e);
      player.cancel();  // bit ugly, but needed for Safari...
    };
  }, _delay);

  const animation = {
    then: deferred.promise.then.bind(deferred.promise),
    cancel() {
      cancelled = true;
      if ($el._el) Object.keys(properties).forEach(k => $el.css(k, $el.css(k)));
      if (player) player.cancel();
    }
  };

  // Only allow cancelling of animation in next thread.
  setTimeout(() => $el._data._animation = animation);
  return animation;
}

// -----------------------------------------------------------------------------
// Element CSS Animations Effects

// When applying the 'pop' effect, we want to respect all existing transform
// except scale. To do that, we have to expand the matrix() notation.
const cssMatrix = /matrix\([0-9.\-\s]+,[0-9.\-\s]+,[0-9.\-\s]+,[0-9.\-\s]+,([0-9.\-\s]+),([0-9.\-\s]+)\)/;

export function enter($el, effect='fade', duration=500, _delay=0) {
  $el.show();
  if (!isReady) return Promise.resolve();

  if (effect === 'fade') {
    return transition($el, {opacity: [0, 1]}, duration, _delay);

  } else if (effect === 'pop') {
    const transform = $el.transform.replace(/scale\([0-9.]*\)/, '')
      .replace(cssMatrix, 'translate($1px,$2px)');

    // TODO Merge into one transition.
    transition($el, {opacity: [0, 1]}, duration, _delay);
    return transition($el, {transform: [transform + ' scale(0.5)',
        transform + ' scale(1)']}, duration, _delay, 'bounce-in');

  } else if (effect === 'descend') {
    const rules = {opacity: [0, 1], transform: ['translateY(-50%)', 'none']};
    return transition($el, rules, duration, _delay);

  } else if (effect.startsWith('draw')) {
    const l = $el.strokeLength + 'px';
    $el.css({opacity: 1, 'stroke-dasharray': l});
    const start = (effect === 'draw-reverse') ? '-' + l : l;
    const rules = {'stroke-dashoffset': [start, 0]};
    return transition($el, rules, duration, _delay, 'linear')
        .then(() => $el.css('stroke-dasharray', ''));

  } else if (effect.startsWith('slide')) {
    const rules = {opacity: [0, 1], transform: ['translateY(50px)', 'none']};
    if (effect.includes('down')) rules.transform[0] = 'translateY(-50px)';
    return transition($el, rules, duration, _delay);

  } else if (effect.startsWith('reveal')) {
    const rules = {opacity: [0, 1], height: [0, 'auto']};
    if (effect.includes('left')) rules.transform = ['translateX(-50%)', 'none'];
    if (effect.includes('right')) rules.transform = ['translateX(50%)', 'none'];
    return transition($el, rules, duration, _delay);
  }
}

export function exit($el, effect='fade', duration=400, delay=0, remove=false) {
  if (!$el._el) return Promise.resolve();
  if (!isReady) { $el.hide(); return Promise.resolve(); }
  if ($el.css('display') === 'none') return Promise.resolve();

  let animation;

  if (effect === 'fade') {
    animation = transition($el, {opacity: [1, 0]}, duration, delay);

  } else if (effect === 'pop') {
    const transform = $el.transform.replace(/scale\([0-9.]*\)/, '');

    transition($el, {opacity: [1, 0]}, duration, delay);
    animation = transition($el, {transform: [transform + ' scale(1)',
        transform + ' scale(0.5)']}, duration, delay, 'bounce-out');

  } else if (effect === 'ascend') {
    const rules = {opacity: [1, 0], transform: ['none', 'translateY(-50%)']};
    animation = transition($el, rules, duration, delay);

  } else if (effect.startsWith('draw')) {
    const l = $el.strokeLength + 'px';
    $el.css('stroke-dasharray', l);
    const end = (effect === 'draw-reverse') ? '-' + l : l;
    const rules = {'stroke-dashoffset': [0, end]};
    animation = transition($el, rules, duration, delay, 'linear');

  } else if (effect.startsWith('slide')) {
    const rules = {opacity: 0, transform: 'translateY(50px)'};
    if (effect.includes('up')) rules.transform = 'translateY(-50px)';
    animation = transition($el, rules, duration, delay);

  } else if (effect.startsWith('reveal')) {
    const rules = {opacity: 0, height: 0};
    if (effect.includes('left')) rules.transform = 'translateX(-50%)';
    if (effect.includes('right')) rules.transform = 'translateX(50%)';
    animation = transition($el, rules, duration, delay);
  }

  animation.then(() => remove ? $el.remove() : $el.hide());
  return animation;
}

// These animations are defined in effects.css:
// pulse-down, pulse-up, flash, bounce-up, bounce-right
export function effect(element, name) {
  element.animationEnd(function(){
    element.removeClass('effects-' + name);
  });
  element.addClass('effects-' + name);
}
<|endoftext|>


<commit_before>'use strict';

var co = require('co');
var path = require('path');
var util = require('util');
var debug = require('debug');
var mkdirp = require('mkdirp');
var expand = require('globby');
var chokidar = require('chokidar');
var assign = require('object-assign');

var Emitter = require('./emitter');
var utils = require('./utils');

import Cat from "concat-with-sourcemaps"
// import { dirname, join, parse, sep } from "path"
import { readFile, writeFile } from "mz/fs"
// import { log, alert, error, defer, flatten } from "./utils"
var _ = utils.debug('fly');
var clear = utils.defer(require('./rimraf'));

function Fly(options) {
	if (!(this instanceof Fly)) {
		throw new TypeError('Fly cannot be invoked without \'new\'');
	}

	// constructor defaults
	options = options || {};
	var host = options.host || {};
	var file = options.file || '.';
	var plugins = options.plugins || [];

	// ~ `super()`
	Emitter.call(this);

	// attach files & funcs into the curr instance
	assign(this, {
		file: file,
		plugins: plugins,
		root: path.dirname(file),
		alert: utils.alert,
		error: utils.error,
		defer: utils.defer,
		log: utils.log,
		host: (typeof host === 'function') ? assign(host, {default: host}) : host,
		debug: _,
		_: {
			filters: []
		},
		tasks: Object.keys(host).reduce(function (_, key) {
			return assign(_, {[key]: host[key].bind(this)});
		}, {})
	});

	plugins.forEach(function (el) {
		if (!el.plugin) {
			throw new Error('Did you forget to `npm i -D ' + el.name + '`?');
		}
		// if (plugin.default) plugin = plugin.default
		el.plugin.call(this, debug(el.name.replace('-', ':')), _('load %o', name));
	});

	_('chdir %o', this.root);

	process.chdir(this.root);
}

// `Fly extends emitter`...
util.inherits(Fly, Emitter);
module.exports = Fly;

Fly.prototype.source = function(globs) {
	assign(this, {
		_: {
			filters: [],
			globs: flatten(globs)
		}
	});

	this._.cat = undefined;
	_('source %o', this._.globs);
	return this;
};

module.exports = class Fly extends Emitter {
	/**
		Create a new Fly instance.
		@param {String} flyfile path
		@param {Object} flyfile module
		@param {[Function]} array of plugins
	*/

	/**
		Compose a new yieldable sequence.
		Reset globs, filters and writer.
		@param {...String} glob patterns
		@return Fly instance. Promises resolve to { file, source }
	 */
	source (...globs) {
		Object.assign(this, { _: { filters: [], globs: flatten(globs) }})
		this._.cat = undefined
		_("source %o", this._.globs)
		return this
	}

	/**
		Add filter / transform function.
		Create a closure bound to the current Fly instance.
		@param {String|Function} name or filter callback
		@param [{Function}] callback with the signature (cb, options) => {}
	*/
	filter (name, cb) {
		if (name instanceof Function) this.filter({ cb: name })
		else if (typeof name === "object") this._.filters.push(name)
		else {
			if (this[name] instanceof Function)
				throw new RangeError(`${name} method already defined in instance.`)
			this[name] = function (options, ...rest) {
				debug("fly")(`${name} %o, %o`, options, rest)
				return this.filter({ cb, options, rest })
			}
		}
		return this
	}

	/**
		Watch IO events in globs and run tasks.
		@param {[String]} glob patterns to observe for changes
		@param {[String]} list of tasks to run on changes
		@param {Object} start options. See Fly.proto.start
	*/
	watch (globs, tasks, options) {
		_("watch %o", globs)
		return this.emit("fly_watch").start(tasks, options)
			.then(() => chokidar.watch(flatten([globs]), { ignoreInitial: true })
				.on("all", () => this.start(tasks, options)))
	}

	/**
		Unwrap/expand source globs to files.
		@param {Function} onFulfilled
		@param {Function} onRejected
	*/
	unwrap (onFulfilled, onRejected) {
		return new Promise((resolve, reject) => {
			return Promise.all(this._.globs.map(glob => expand(glob)))
				.then((files) => resolve.call(this, files.reduce((arr, item) =>
					arr.concat(item)))).catch(reject)
			}).then(onFulfilled).catch(onRejected)
	}

	/**
		@private Execute a task.
		@param {String} name of the task
		@param {Mixed} initial value to pass into the task
		@param {Object} Fly instance the task should be bound to
	*/
	*exec (task, value, inject = this) {
		_("run %o", task)
		try {
			const start = new Date()
			this.emit("task_start", { task })
			value = (yield this.host[task].call(inject, value)) || value
			this.emit("task_complete", {
				task, duration: (new Date()).getTime() - start
			})
		} catch (error) { this.emit("task_error", { task, error }) }
		return value
	}

	/**
		Run one or more tasks. Each task's return value cascades on to the next
		task in a sequence.
		@param {Array} list of tasks
		@return {Promise}
	 */
	start (tasks = "default", { parallel = false, value } = {}) {
		_(`start %o in ${parallel ? "parallel" : "sequence"}`, tasks)
		return co.call(this, function* (tasks) {
			if (parallel) {
				yield tasks.map((task) =>
					this.exec(task, value, Object.create(this)))
			} else {
				for (let task of tasks) value = yield this.exec(task, value)
			}
			return value
		}, [].concat(tasks).filter((task) => ~Object.keys(this.host)
			.indexOf(task) || !this.emit("task_not_found", { task })))
	}

	/**
		Deferred rimraf wrapper.
		@param {...String} paths
	 */
	clear (...paths) {
		_("clear %o", paths)
		return flatten(paths).map((path) => clear(path))
	}

	/**
		Writer based in fs/mz writeFile.
		@param {String} file name
	 */
	concat (base) {
		this._.cat = new Cat(false, base, "\n")
		this._.cat.base = base
		return this
	}

	/**
		Resolve a yieldable sequence.
		Reduce source with filters and invoke writer.
		@param {Array}  dirs  target directories
		@param {Object} depth target options, for path flattening
		@return {Promise}
	 */
	target (dirs, {depth = -1} = {}) {
		dirs = Array.isArray(dirs) ? dirs : [dirs]

		return co.call(this, function* () {
			for (let glob of this._.globs) {
				for (let file of yield expand(glob)) {
					let { base, ext } = parse(file),
						data = yield readFile(file)

					for (let filter of this._.filters) {
						const res = yield Promise.resolve(
							filter.cb.apply(this, [data, Object
									.assign({ filename: base }, filter.options)]
									.concat(filter.rest))
						)
						data = res.code || res.js || res.css || res.data || res || data
						ext = res.ext || res.extension || ext
					}

					if (this._.cat) {
						this._.cat.add(`${base}`, data)
					} else {
						const base = join(
							...parse(file).dir.split(sep).filter(path => !~glob.split(sep).indexOf(path)),
							`${parse(file).name}${ext}`
						)
						yield resolve(dirs, {data, base, depth})
					}
				}
			}

			if (this._.cat) {
				yield resolve(dirs, {
					data: this._.cat.content,
					base: this._.cat.base,
					write: writeFile,
					depth
				})
			}
		})
	}
}

/** Write utility to help concat and target.
	@param {String}   dirs  parent directory
	@param {String}   base  directory/file
	@param {Mixed}    data
	@param {Integer}  depth number of parent directories to keep
	@param {Function} write promisified writer function
*/
function* resolve (dirs, { base, data, depth, write = writeFile }) {
	if (depth > -1) {
		base = dirpaths(base, depth)
	}

	for (let dir of flatten(dirs)) {
		const file = join(dir, base)
		mkdirp.sync(dirname(file))
		yield write(file, data)
	}
}

/**
 * Shorten a directory string to # of parent dirs
 * @param  {str}  full    The original full path
 * @param  {int}  depth   The number of levels to retain
 * @return {string}
 */
function dirpaths (full, depth) {
	const arr = full.split(sep)
	const len = arr.length
	return (depth==0) ? arr[len-1] : (depth >= len) ? full : arr.slice(len - 1 - depth).join(sep)
}
<commit_msg>rewrite fly.filter<commit_after>'use strict';

var co = require('co');
var path = require('path');
var util = require('util');
var debug = require('debug');
var mkdirp = require('mkdirp');
var expand = require('globby');
var chokidar = require('chokidar');
var assign = require('object-assign');
var Cat = require('concat-with-sourcemaps');

var Emitter = require('./emitter');
var utils = require('./utils');

// import { dirname, join, parse, sep } from "path"
// import { readFile, writeFile } from "mz/fs"
// import { log, alert, error, defer, flatten } from "./utils"
var _ = utils.debug('fly');
var clear = utils.defer(require('./rimraf'));

function Fly(options) {
	if (!(this instanceof Fly)) {
		throw new TypeError('Fly cannot be invoked without \'new\'');
	}

	// constructor defaults
	options = options || {};
	var host = options.host || {};
	var file = options.file || '.';
	var plugins = options.plugins || [];

	// ~ `super()`
	Emitter.call(this);

	// attach files & funcs into the curr instance
	assign(this, {
		file: file,
		plugins: plugins,
		root: path.dirname(file),
		alert: utils.alert,
		error: utils.error,
		defer: utils.defer,
		log: utils.log,
		host: (typeof host === 'function') ? assign(host, {default: host}) : host,
		debug: _,
		_: {
			filters: []
		},
		tasks: Object.keys(host).reduce(function (_, key) {
			return assign(_, {[key]: host[key].bind(this)});
		}, {})
	});

	plugins.forEach(function (el) {
		if (!el.plugin) {
			throw new Error('Did you forget to `npm i -D ' + el.name + '`?');
		}
		// if (plugin.default) plugin = plugin.default
		el.plugin.call(this, debug(el.name.replace('-', ':')), _('load %o', el.name));
	});

	_('chdir %o', this.root);

	process.chdir(this.root);
}

// `Fly extends emitter`...
util.inherits(Fly, Emitter);
module.exports = Fly;

/**
 * Compose a new, yeildable sequence.
 * Resets instance's glob, filters, and writer.
 *
 * @param  {String|Array} globs The glob selection
 * @return 											The current Fly instance.
 */
Fly.prototype.source = function (globs) {
	assign(this, {
		_: {
			filters: [],
			globs: flatten(globs)
		}
	});

	this._.cat = undefined;
	_('source %o', this._.globs);
	return this;
};

/**
/**
 * Add filter / transform function.
 * Create a closure bound to the current Fly instance.
 *
 * @param {String|Function} name 		The name of filter || the callback
 * @param {Function} 				cb 			the function: (cb, options) => {}
*/
Fly.prototype.filter = function(name, cb) {
	var type = typeof name;

	if (type === 'function') {
		this.filter({cb: name});
	} else if (type === 'object') {
		this._.filters.push(name);
	} else {
		if (typeof this[name] === 'function') {
			throw new RangeError(name + ' method is already defined!');
		}

		this[name] = function (options) {
			var rest = [];
			rest.push.apply(rest, arguments) && rest.shift();

			debug('fly')(`${name} %o, %o`, options, rest);

			return this.filter({
				cb: cb,
				options: options,
				rest: rest
			});
		};
	}

	return this;
};

module.exports = class Fly extends Emitter {

	/**
		Watch IO events in globs and run tasks.
		@param {[String]} glob patterns to observe for changes
		@param {[String]} list of tasks to run on changes
		@param {Object} start options. See Fly.proto.start
	*/
	watch (globs, tasks, options) {
		_("watch %o", globs)
		return this.emit("fly_watch").start(tasks, options)
			.then(() => chokidar.watch(flatten([globs]), { ignoreInitial: true })
				.on("all", () => this.start(tasks, options)))
	}

	/**
		Unwrap/expand source globs to files.
		@param {Function} onFulfilled
		@param {Function} onRejected
	*/
	unwrap (onFulfilled, onRejected) {
		return new Promise((resolve, reject) => {
			return Promise.all(this._.globs.map(glob => expand(glob)))
				.then((files) => resolve.call(this, files.reduce((arr, item) =>
					arr.concat(item)))).catch(reject)
			}).then(onFulfilled).catch(onRejected)
	}

	/**
		@private Execute a task.
		@param {String} name of the task
		@param {Mixed} initial value to pass into the task
		@param {Object} Fly instance the task should be bound to
	*/
	*exec (task, value, inject = this) {
		_("run %o", task)
		try {
			const start = new Date()
			this.emit("task_start", { task })
			value = (yield this.host[task].call(inject, value)) || value
			this.emit("task_complete", {
				task, duration: (new Date()).getTime() - start
			})
		} catch (error) { this.emit("task_error", { task, error }) }
		return value
	}

	/**
		Run one or more tasks. Each task's return value cascades on to the next
		task in a sequence.
		@param {Array} list of tasks
		@return {Promise}
	 */
	start (tasks = "default", { parallel = false, value } = {}) {
		_(`start %o in ${parallel ? "parallel" : "sequence"}`, tasks)
		return co.call(this, function* (tasks) {
			if (parallel) {
				yield tasks.map((task) =>
					this.exec(task, value, Object.create(this)))
			} else {
				for (let task of tasks) value = yield this.exec(task, value)
			}
			return value
		}, [].concat(tasks).filter((task) => ~Object.keys(this.host)
			.indexOf(task) || !this.emit("task_not_found", { task })))
	}

	/**
		Deferred rimraf wrapper.
		@param {...String} paths
	 */
	clear (...paths) {
		_("clear %o", paths)
		return flatten(paths).map((path) => clear(path))
	}

	/**
		Writer based in fs/mz writeFile.
		@param {String} file name
	 */
	concat (base) {
		this._.cat = new Cat(false, base, "\n")
		this._.cat.base = base
		return this
	}

	/**
		Resolve a yieldable sequence.
		Reduce source with filters and invoke writer.
		@param {Array}  dirs  target directories
		@param {Object} depth target options, for path flattening
		@return {Promise}
	 */
	target (dirs, {depth = -1} = {}) {
		dirs = Array.isArray(dirs) ? dirs : [dirs]

		return co.call(this, function* () {
			for (let glob of this._.globs) {
				for (let file of yield expand(glob)) {
					let { base, ext } = parse(file),
						data = yield readFile(file)

					for (let filter of this._.filters) {
						const res = yield Promise.resolve(
							filter.cb.apply(this, [data, Object
									.assign({ filename: base }, filter.options)]
									.concat(filter.rest))
						)
						data = res.code || res.js || res.css || res.data || res || data
						ext = res.ext || res.extension || ext
					}

					if (this._.cat) {
						this._.cat.add(`${base}`, data)
					} else {
						const base = join(
							...parse(file).dir.split(sep).filter(path => !~glob.split(sep).indexOf(path)),
							`${parse(file).name}${ext}`
						)
						yield resolve(dirs, {data, base, depth})
					}
				}
			}

			if (this._.cat) {
				yield resolve(dirs, {
					data: this._.cat.content,
					base: this._.cat.base,
					write: writeFile,
					depth
				})
			}
		})
	}
}

/** Write utility to help concat and target.
	@param {String}   dirs  parent directory
	@param {String}   base  directory/file
	@param {Mixed}    data
	@param {Integer}  depth number of parent directories to keep
	@param {Function} write promisified writer function
*/
function* resolve (dirs, { base, data, depth, write = writeFile }) {
	if (depth > -1) {
		base = dirpaths(base, depth)
	}

	for (let dir of flatten(dirs)) {
		const file = join(dir, base)
		mkdirp.sync(dirname(file))
		yield write(file, data)
	}
}

/**
 * Shorten a directory string to # of parent dirs
 * @param  {str}  full    The original full path
 * @param  {int}  depth   The number of levels to retain
 * @return {string}
 */
function dirpaths (full, depth) {
	const arr = full.split(sep)
	const len = arr.length
	return (depth==0) ? arr[len-1] : (depth >= len) ? full : arr.slice(len - 1 - depth).join(sep)
}
<|endoftext|>


<commit_before>// Copyright 2013 Google Inc. All Rights Reserved.

/**
 * @fileoverview JSCIN for Chrome Extension, content injection.
 * @author hungte@google.com (Hung-Te Lin)
 */

function IME() {
  var self = this;

  // variables.
  self.engineID = 'croscin_content_ime';
  self.frameURL = chrome.extension.getURL('input_api/ime.html');
  self.frame = undefined;
  self.contextID = undefined;
  self.ipc = undefined;
  self.debug = false;
  self.nodeStates = [];
  self.enabled = true;
  self.toggleHotKey = 9;  // TAB.

  self.log = function () {
    if (!self.debug)
      return;
    console.log.apply(console, arguments);
  }

  self.SendMessage = function () {
    self.ipc.send.apply(null, arguments);
  };

  self.KeyEventHandler = function (ev) {
    if (ev.keyCode == self.toggleHotKey) {
      self.enabled = !self.enabled;
      ev.preventDefault();

      if (self.enabled)
        self.frame.fadeIn(100);
      else
        self.frame.fadeOut(100);
      return;
    }

    if (!self.enabled)
      return;

    var ev2 = ImeEvent.ImeKeyEvent(ev);
    self.log("ImeKeyEventHandler", ev2);
    self.SendMessage("KeyEvent", self.engineID, ev2);
    // TODO(hungte) Due to browser design, we can't find a better way to re-fire
    // keyboard events if the IPC message responds "reject... so a workaround
    // here is to preserve input element state. We really need to find better
    // way to fix this and enable preventDefault() call.
    // ev.preventDefault();
    self.nodeStates.push({
      value: ev.target.value,
      selectionStart: ev.target.selectionStart,
      selectionEnd: ev.target.selectionEnd
    });
  };

  self.AttachKeyEvents = function (node) {
    node.addEventListener('keydown', self.KeyEventHandler);
    // WORKAROUND: keyup is ignored due to nodeStates.
    // node.addEventListener('keyup', self.KeyEventHandler);
  };

  self.DetachKeyEvents = function (node) {
    node.removeEventListener('keydown', self.KeyEventHandler);
    // WORKAROUND: keyup is ignored due to nodeStates.
    // node.removeEventListener('keyup', self.KeyEventHandler);
  };

  self.ImeEventHandler = function (type) {
    if (type == 'UIReady') {
      self.log("Got", type);
      self.SendMessage("Activate"); // hack: we want to update menu.
      // Delayed init.
      if (self.init_node) {
        self.log("Found init_node:", self.init_node);
        self.FocusHandler({target: self.init_node});
      }
      self.init_node = undefined;
    } else if (type == 'Focus') {
      var context = arguments[1];
      var node = self.node;
      self.contextID = context.contextID;
      self.AttachKeyEvents(node);
      var offset = $(node).offset();
      offset.left += 10;
      offset.top += $(node).height();
      self.frame.css(offset).fadeIn(250);
    } else if (type == 'KeyEvent') {
      var result = arguments[1];
      var state = self.nodeStates.shift();
      self.log("Got KeyEvent reply!", result, state);
      if (!result) {
        var node = self.node;
        node.value = state.value;
        node.selectionStart = state.selectionStart;
        node.selectionEnd = state.selectionEnd;
        self.log("Restored node state.");
      }
    } else if (type == 'commitText') {
      // WORKAROUND commitText is usually invoked before last KeyEvent.
      // We rely on this croscin behavior to calibrate nodeStates.
      var parameters = arguments[1];
      var node = self.node;
      var src = self.nodeStates.shift() || node;
      var newpos = src.selectionStart + parameters.text.length;
      // TODO selctionStart seems wrong after changing node.value...
      node.value = (src.value.substring(0, src.selectionStart) +
          parameters.text +
          src.value.substring(src.selectionEnd));
      node.selectionStart = newpos;
      node.selectionEnd = newpos;
      self.nodeStates.unshift({
        value: node.value,
        selectionStart: node.selectionStart,
        selectionEnd: node.selectionEnd});
    } else if (type == 'MenuItemActivated') {
      // forward to background.
      self.ipc.send.apply(null, arguments);
    }
  };

  self.FocusHandler = function (ev) {
    self.log("on focus");
    var node = ev.target;
    self.node = ev.target;
    self.SendMessage("Focus");
  };

  self.BlurHandler = function (ev) {
    if (self.contextID) {
      self.log("on blur", self.contextID);
      self.frame.fadeOut(100);
      self.DetachKeyEvents(ev.target);
      self.SendMessage("Blur", self.contextID);
      self.contextID = undefined;
    }
  };

  self.InstallIPC = function () {
    self.ipc = new ImeEvent.ImeExtensionIPC('content');
    self.ipc.attach();
    self.ipc.recv(self.ImeEventHandler);
  };

  self.CreateFrame = function () {
    // TODO(hungte) Don't use jquery.
    var frame = $('<iframe/>',
        { src: self.frameURL,
          scrolling: 'no',
          frameBorder: 0,
          allowTransparency: true})
        .css({
          width: 500,
          zIndex: 999999,
          border: 0,
          padding: 0,
          height: 300,
          position: 'absolute',
          backgroundColor: 'transparent'}).hide();
    $('body').append(frame);
    return frame;
  };
}

function init () {
  // Check chrome.input.ime availability.  The correct way is to check
  // chrome.input.ime binding but that would spew error message in console for
  // every page; so let's check userAgent instead because for right now only
  // ChromeOS supports that.
  if (window.navigator.userAgent.indexOf(' CrOS ') >= 0)
    return;

  // Check if we have any input DOM elements to attach IME.

  var i;
  var nodes;
  var targets = [];

  // TODO(hungte) Also attach to all nodes with [contenteditable]
  nodes = document.getElementsByTagName('input');
  for (i = 0; i < nodes.length; i++) {
    var type = nodes[i].type;
    if (type) {
      type = type.toLowerCase();
      if (type != "" && type != "text" && type != "search")
        continue;
    }
    targets.push(nodes[i]);
  }

  nodes = document.getElementsByTagName('textarea');
  for (i = 0; i < nodes.length; i++) {
    targets.push(nodes[i]);
  }

  if (!targets.length)
    return;

  var ime = new IME;
  ime.log("Installing extension IME, input elements:", targets.length);
  ime.InstallIPC();

  ime.frame = ime.CreateFrame();

  var focused = document.activeElement;
  targets.forEach(function (node) {
    node.addEventListener("focus", ime.FocusHandler);
    node.addEventListener("blur", ime.BlurHandler);
    if (focused == node)
      self.init_node = node;
  });
}

// For content.js we can't wait for readystatechange - that is controlled by
// manifest:run_at.
init();
<commit_msg>Keep enabled state on same page.<commit_after>// Copyright 2013 Google Inc. All Rights Reserved.

/**
 * @fileoverview JSCIN for Chrome Extension, content injection.
 * @author hungte@google.com (Hung-Te Lin)
 */

function IME() {
  var self = this;

  // variables.
  self.engineID = 'croscin_content_ime';
  self.frameURL = chrome.extension.getURL('input_api/ime.html');
  self.frame = undefined;
  self.contextID = undefined;
  self.ipc = undefined;
  self.debug = false;
  self.nodeStates = [];
  self.enabled = true;
  self.toggleHotKey = 9;  // TAB.

  self.log = function () {
    if (!self.debug)
      return;
    console.log.apply(console, arguments);
  }

  self.SendMessage = function () {
    self.ipc.send.apply(null, arguments);
  };

  self.KeyEventHandler = function (ev) {
    if (ev.keyCode == self.toggleHotKey) {
      self.enabled = !self.enabled;
      ev.preventDefault();

      if (self.enabled)
        self.frame.fadeIn(100);
      else
        self.frame.fadeOut(100);
      return;
    }

    if (!self.enabled)
      return;

    var ev2 = ImeEvent.ImeKeyEvent(ev);
    self.log("ImeKeyEventHandler", ev2);
    self.SendMessage("KeyEvent", self.engineID, ev2);
    // TODO(hungte) Due to browser design, we can't find a better way to re-fire
    // keyboard events if the IPC message responds "reject... so a workaround
    // here is to preserve input element state. We really need to find better
    // way to fix this and enable preventDefault() call.
    // ev.preventDefault();
    self.nodeStates.push({
      value: ev.target.value,
      selectionStart: ev.target.selectionStart,
      selectionEnd: ev.target.selectionEnd
    });
  };

  self.AttachKeyEvents = function (node) {
    node.addEventListener('keydown', self.KeyEventHandler);
    // WORKAROUND: keyup is ignored due to nodeStates.
    // node.addEventListener('keyup', self.KeyEventHandler);
  };

  self.DetachKeyEvents = function (node) {
    node.removeEventListener('keydown', self.KeyEventHandler);
    // WORKAROUND: keyup is ignored due to nodeStates.
    // node.removeEventListener('keyup', self.KeyEventHandler);
  };

  self.ImeEventHandler = function (type) {
    if (type == 'UIReady') {
      self.log("Got", type);
      self.SendMessage("Activate"); // hack: we want to update menu.
      // Delayed init.
      if (self.init_node) {
        self.log("Found init_node:", self.init_node);
        self.FocusHandler({target: self.init_node});
      }
      self.init_node = undefined;
    } else if (type == 'Focus') {
      var context = arguments[1];
      var node = self.node;
      self.contextID = context.contextID;
      self.AttachKeyEvents(node);
      var offset = $(node).offset();
      offset.left += 10;
      offset.top += $(node).height();
      if (self.enabled)
        self.frame.css(offset).fadeIn(250);
    } else if (type == 'KeyEvent') {
      var result = arguments[1];
      var state = self.nodeStates.shift();
      self.log("Got KeyEvent reply!", result, state);
      if (!result) {
        var node = self.node;
        node.value = state.value;
        node.selectionStart = state.selectionStart;
        node.selectionEnd = state.selectionEnd;
        self.log("Restored node state.");
      }
    } else if (type == 'commitText') {
      // WORKAROUND commitText is usually invoked before last KeyEvent.
      // We rely on this croscin behavior to calibrate nodeStates.
      var parameters = arguments[1];
      var node = self.node;
      var src = self.nodeStates.shift() || node;
      var newpos = src.selectionStart + parameters.text.length;
      // TODO selctionStart seems wrong after changing node.value...
      node.value = (src.value.substring(0, src.selectionStart) +
          parameters.text +
          src.value.substring(src.selectionEnd));
      node.selectionStart = newpos;
      node.selectionEnd = newpos;
      self.nodeStates.unshift({
        value: node.value,
        selectionStart: node.selectionStart,
        selectionEnd: node.selectionEnd});
    } else if (type == 'MenuItemActivated') {
      // forward to background.
      self.ipc.send.apply(null, arguments);
    }
  };

  self.FocusHandler = function (ev) {
    self.log("on focus");
    var node = ev.target;
    self.node = ev.target;
    self.SendMessage("Focus");
  };

  self.BlurHandler = function (ev) {
    if (self.contextID) {
      self.log("on blur", self.contextID);
      if (self.enabled)
        self.frame.fadeOut(100);
      self.DetachKeyEvents(ev.target);
      self.SendMessage("Blur", self.contextID);
      self.contextID = undefined;
    }
  };

  self.InstallIPC = function () {
    self.ipc = new ImeEvent.ImeExtensionIPC('content');
    self.ipc.attach();
    self.ipc.recv(self.ImeEventHandler);
  };

  self.CreateFrame = function () {
    // TODO(hungte) Don't use jquery.
    var frame = $('<iframe/>',
        { src: self.frameURL,
          scrolling: 'no',
          frameBorder: 0,
          allowTransparency: true})
        .css({
          width: 500,
          zIndex: 999999,
          border: 0,
          padding: 0,
          height: 300,
          position: 'absolute',
          backgroundColor: 'transparent'}).hide();
    $('body').append(frame);
    return frame;
  };
}

function init () {
  // Check chrome.input.ime availability.  The correct way is to check
  // chrome.input.ime binding but that would spew error message in console for
  // every page; so let's check userAgent instead because for right now only
  // ChromeOS supports that.
  if (window.navigator.userAgent.indexOf(' CrOS ') >= 0)
    return;

  // Check if we have any input DOM elements to attach IME.

  var i;
  var nodes;
  var targets = [];

  // TODO(hungte) Also attach to all nodes with [contenteditable]
  nodes = document.getElementsByTagName('input');
  for (i = 0; i < nodes.length; i++) {
    var type = nodes[i].type;
    if (type) {
      type = type.toLowerCase();
      if (type != "" && type != "text" && type != "search")
        continue;
    }
    targets.push(nodes[i]);
  }

  nodes = document.getElementsByTagName('textarea');
  for (i = 0; i < nodes.length; i++) {
    targets.push(nodes[i]);
  }

  if (!targets.length)
    return;

  var ime = new IME;
  ime.log("Installing extension IME, input elements:", targets.length);
  ime.InstallIPC();

  ime.frame = ime.CreateFrame();

  var focused = document.activeElement;
  targets.forEach(function (node) {
    node.addEventListener("focus", ime.FocusHandler);
    node.addEventListener("blur", ime.BlurHandler);
    if (focused == node)
      self.init_node = node;
  });
}

// For content.js we can't wait for readystatechange - that is controlled by
// manifest:run_at.
init();
<|endoftext|>


<commit_before>PRESTATION_TO_TEST_POSITION = 1
PRESTATION_NAME = /Prime d’activité/
PRESTATION_DESCRIPTION = /revenus modestes/
HAS_PRESTATION_WARNING = false
HAS_PRESTATION_ENGAGER_DEMARCHES_LINK = true
<commit_msg>Fix expected text in integration test<commit_after>PRESTATION_TO_TEST_POSITION = 1
PRESTATION_NAME = /Prime d’activité/
PRESTATION_DESCRIPTION = /complète les revenus/
HAS_PRESTATION_WARNING = false
HAS_PRESTATION_ENGAGER_DEMARCHES_LINK = true
<|endoftext|>


<commit_before>"use strict";

var CSLEDIT = CSLEDIT || {};

var jsonDocuments;

CSLEDIT.citationEngine = (function () {
	var oldFormattedCitation = "",
		newFormattedCitation = "",
		oldFormattedBibliography = "",
		newFormattedBibliography = "",
		diffTimeout,
		dmp = null, // for diff_match_patch object
		previousStyle = "", // to skip initializing citeproc when using the same style
		citeproc;

	var stripTags = function (html, tag) {
		var stripRegExp = new RegExp("<" + tag + ".*?>|</\s*" + tag + "\s*?\>", "g");

		// creating new string because of bug where some html from generateExampleCitations.js
		// was type object instead of string and didn't have the replace() function
		var stripped = new String(html);
		stripped = stripped.replace(stripRegExp, "");
		return stripped;
	};

	var formatCitations = function (style, documents, citationClusters, taggedOutput) {
		var bibliography,
			result,
			sys,
			citations,
			cluster,
			inLineCitations,
			inLineCitationArray,
			i,
			pos,
			makeBibliographyArgument,
			hangingindent,
			has_bibliography,
			index,
			enumerateCitations;

		// TODO: this shouldn't be a global
		jsonDocuments = documents;

		result = { "statusMessage":"", "formattedCitations":[], "formattedBibliography": [] };
		result.statusMessage = "";
		if (style !== previousStyle) {
			try
			{
				sys = new Sys(abbreviations);
				citeproc = new CSL.Engine(sys, style);
				citeproc.opt.development_extensions.csl_reverse_lookup_support = true;
				previousStyle = style;
			}
			catch(err)
			{
				result.statusMessage = "Citeproc initialisation exception: " + err;
				return result;
			}
		} else {
			citeproc.restoreProcessorState([]);
		}

		inLineCitations = "";
		inLineCitationArray = new Array();

		for (cluster=0; cluster<citationClusters.length; cluster++)
		{
			try
			{
				citations = citeproc.appendCitationCluster(citationClusters[cluster],false);
			}
			catch(err)
			{
				result.statusMessage = "Citeproc exception: " + err;
				return result;
			}

			for (i = 0; i < citations.length; i++)
			{
				pos = citations[i][0];

				if (inLineCitations != "")
				{
					inLineCitations += "
";
				}

				if (taggedOutput !== true) {
					citations[i][1] = stripTags(citations[i][1], "span");
				}

				inLineCitations += citations[i][1];
				inLineCitationArray.push(citations[i][1]);
			}
		}
		result.formattedCitations = inLineCitationArray;

		enumerateCitations = true;
		if (enumerateCitations === true) {
			makeBibliographyArgument = undefined;
		}
		else {
			makeBibliographyArgument = "citation-number";
		}

		try
		{
			bibliography = citeproc.makeBibliography(makeBibliographyArgument);
		}
		catch(err)
		{
			result.statusMessage = "Citeproc exception: " + err;
			return result;
		}

		hangingindent = false;
		has_bibliography = (bibliography !== false);

		if (has_bibliography)
		{
			hangingindent = (bibliography[0].hangingindent != 0 && "undefined" !== typeof(bibliography[0].hangingindent));
			bibliography = bibliography[1];
		}
		else
		{
			bibliography = [[(citations[0][1])]];
		}

		if (taggedOutput !== true) {
			for (index = 0; index < bibliography.length; index++) {
				bibliography[index] = stripTags(bibliography[index], "span");
			}
		}

		result.formattedBibliography = bibliography;
		return result;
	};

	var runCiteprocAndDisplayOutput = function (
			statusOut, exampleOut, citationsOut, bibliographyOut, callback) {

		console.time("runCiteprocAndDisplayOutput");

		var style = CSLEDIT.data.getCslCode(),
			inLineCitations = "",
			citations = [],
			formattedResult,
			citationTagStart = "
",
			citationTagEnd = "
",
			bibliographyTagStart = "
",
			bibliographyTagEnd = "
",
			startTime,
			citationDiffs,
			bibliographyDiffs,
			diffFormattedCitation,
			diffFormattedBibliography,
			cslData = CSLEDIT.data.get(),
			citationNode = CSLEDIT.data.getNodesFromPath("style/citation/layout", cslData),
			bibliographyNode = CSLEDIT.data.getNodesFromPath("style/bibliography/layout", cslData);

		statusOut.html("Re-formatting citations...");

		console.time("formatCitations");

		formattedResult = formatCitations(
			style, CSLEDIT.exampleCitations.getCiteprocReferences(), CSLEDIT.exampleCitations.getCitations(), true);

		console.timeEnd("formatCitations");

		statusOut.html(formattedResult.statusMessage);

		// add syntax highlighting at highest level
		if (citationNode.length > 0) {
			citationTagStart = '
';
		    citationTagEnd = '
';
		}
		if (bibliographyNode.length > 0) {
			bibliographyTagStart = '
';
			bibliographyTagEnd = '
';
		}

		oldFormattedCitation = newFormattedCitation;
		newFormattedCitation = citationTagStart;
		newFormattedCitation += formattedResult.formattedCitations.join(
			citationTagEnd + citationTagStart);
		newFormattedCitation += citationTagEnd;

		oldFormattedBibliography = newFormattedBibliography;
		newFormattedBibliography = bibliographyTagStart;
		newFormattedBibliography += formattedResult.formattedBibliography.join(
			bibliographyTagEnd + bibliographyTagStart);
		newFormattedBibliography += bibliographyTagEnd;

		if (newFormattedBibliography.indexOf("<second-field-align>") > -1) {
			exampleOut.css({
				// TODO: don't change the whole output panel CSS, just the relevant lines
				"padding-left" : "3em",
				"text-indent" : "-2em"
			});
		} else {
			exampleOut.css({
				"padding-left" : "1em",
				"text-indent" : "0"
			});
		}

		// lazy instantiation of diff_match_patch
		if (dmp === null) {
			dmp = new diff_match_patch();
		}

		citationDiffs =
			dmp.diff_main(stripTags(oldFormattedCitation, "span"), stripTags(newFormattedCitation, "span"));
		dmp.diff_cleanupSemantic(citationDiffs);
		diffFormattedCitation = unescape(CSLEDIT.diff.prettyHtml(citationDiffs));

		bibliographyDiffs =
			dmp.diff_main(stripTags(oldFormattedBibliography, "span"), stripTags(newFormattedBibliography, "span"));
		dmp.diff_cleanupSemantic(bibliographyDiffs);
		diffFormattedBibliography = unescape(CSLEDIT.diff.prettyHtml(bibliographyDiffs));

		if (dmp.diff_levenshtein(citationDiffs) === 0 && dmp.diff_levenshtein(bibliographyDiffs) === 0) {
			citationsOut.html(newFormattedCitation);
			bibliographyOut.html(newFormattedBibliography);
			if (typeof callback !== "undefined") {
				callback();
			}
		} else {
			if (CSLEDIT.storage.getItem('CSLEDIT.options.visualEditorDiffs') === "true") {
				// display the diff
				citationsOut.html(diffFormattedCitation);
				bibliographyOut.html(diffFormattedBibliography);

				// display the new version in 1000ms
				clearTimeout(diffTimeout);
				diffTimeout = setTimeout(
					function () {
						citationsOut.html(newFormattedCitation);
						bibliographyOut.html(newFormattedBibliography);
						if (typeof callback !== "undefined") {
							callback();
						}
					},
				1000);
			} else {
				// display the real result
				citationsOut.html(newFormattedCitation);
				bibliographyOut.html(newFormattedBibliography);
				if (typeof callback !== "undefined") {
					callback();
				}
			}
		}

		console.timeEnd("runCiteprocAndDisplayOutput");
	}

	// Return public members:
	return {
		formatCitations : formatCitations,
		runCiteprocAndDisplayOutput : runCiteprocAndDisplayOutput
	};

}());
<commit_msg>Don't display blank lines in the inline citation example output<commit_after>"use strict";

var CSLEDIT = CSLEDIT || {};

var jsonDocuments;

CSLEDIT.citationEngine = (function () {
	var oldFormattedCitation = "",
		newFormattedCitation = "",
		oldFormattedBibliography = "",
		newFormattedBibliography = "",
		diffTimeout,
		dmp = null, // for diff_match_patch object
		previousStyle = "", // to skip initializing citeproc when using the same style
		citeproc;

	var stripTags = function (html, tag) {
		var stripRegExp = new RegExp("<" + tag + ".*?>|</\s*" + tag + "\s*?\>", "g");

		// creating new string because of bug where some html from generateExampleCitations.js
		// was type object instead of string and didn't have the replace() function
		var stripped = new String(html);
		stripped = stripped.replace(stripRegExp, "");
		return stripped;
	};

	var formatCitations = function (style, documents, citationClusters, taggedOutput) {
		var bibliography,
			result,
			sys,
			citations,
			cluster,
			inLineCitations,
			inLineCitationArray,
			i,
			pos,
			makeBibliographyArgument,
			hangingindent,
			has_bibliography,
			index,
			enumerateCitations;

		// TODO: this shouldn't be a global
		jsonDocuments = documents;

		result = { "statusMessage":"", "formattedCitations":[], "formattedBibliography": [] };
		result.statusMessage = "";
		if (style !== previousStyle) {
			try
			{
				sys = new Sys(abbreviations);
				citeproc = new CSL.Engine(sys, style);
				citeproc.opt.development_extensions.csl_reverse_lookup_support = true;
				previousStyle = style;
			}
			catch(err)
			{
				result.statusMessage = "Citeproc initialisation exception: " + err;
				return result;
			}
		} else {
			citeproc.restoreProcessorState([]);
		}

		inLineCitations = "";
		inLineCitationArray = new Array();

		for (cluster=0; cluster<citationClusters.length; cluster++)
		{
			try
			{
				citations = citeproc.appendCitationCluster(citationClusters[cluster],false);
			}
			catch(err)
			{
				result.statusMessage = "Citeproc exception: " + err;
				return result;
			}

			for (i = 0; i < citations.length; i++)
			{
				pos = citations[i][0];

				if (inLineCitations != "")
				{
					inLineCitations += "
";
				}

				if (taggedOutput !== true) {
					citations[i][1] = stripTags(citations[i][1], "span");
				}

				inLineCitations += citations[i][1];

				if (citations[i][1] !== "") {
					inLineCitationArray.push(citations[i][1]);
				}
			}
		}
		result.formattedCitations = inLineCitationArray;

		enumerateCitations = true;
		if (enumerateCitations === true) {
			makeBibliographyArgument = undefined;
		}
		else {
			makeBibliographyArgument = "citation-number";
		}

		try
		{
			bibliography = citeproc.makeBibliography(makeBibliographyArgument);
		}
		catch(err)
		{
			result.statusMessage = "Citeproc exception: " + err;
			return result;
		}

		hangingindent = false;
		has_bibliography = (bibliography !== false);

		if (has_bibliography)
		{
			hangingindent = (bibliography[0].hangingindent != 0 && "undefined" !== typeof(bibliography[0].hangingindent));
			bibliography = bibliography[1];
		}
		else
		{
			bibliography = [[(citations[0][1])]];
		}

		if (taggedOutput !== true) {
			for (index = 0; index < bibliography.length; index++) {
				bibliography[index] = stripTags(bibliography[index], "span");
			}
		}

		result.formattedBibliography = bibliography;
		return result;
	};

	var runCiteprocAndDisplayOutput = function (
			statusOut, exampleOut, citationsOut, bibliographyOut, callback) {

		console.time("runCiteprocAndDisplayOutput");

		var style = CSLEDIT.data.getCslCode(),
			inLineCitations = "",
			citations = [],
			formattedResult,
			citationTagStart = "
",
			citationTagEnd = "
",
			bibliographyTagStart = "
",
			bibliographyTagEnd = "
",
			startTime,
			citationDiffs,
			bibliographyDiffs,
			diffFormattedCitation,
			diffFormattedBibliography,
			cslData = CSLEDIT.data.get(),
			citationNode = CSLEDIT.data.getNodesFromPath("style/citation/layout", cslData),
			bibliographyNode = CSLEDIT.data.getNodesFromPath("style/bibliography/layout", cslData);

		statusOut.html("Re-formatting citations...");

		console.time("formatCitations");

		formattedResult = formatCitations(
			style, CSLEDIT.exampleCitations.getCiteprocReferences(), CSLEDIT.exampleCitations.getCitations(), true);

		console.timeEnd("formatCitations");

		statusOut.html(formattedResult.statusMessage);

		// add syntax highlighting at highest level
		if (citationNode.length > 0) {
			citationTagStart = '
';
		    citationTagEnd = '
';
		}
		if (bibliographyNode.length > 0) {
			bibliographyTagStart = '
';
			bibliographyTagEnd = '
';
		}

		oldFormattedCitation = newFormattedCitation;
		newFormattedCitation = citationTagStart;
		newFormattedCitation += formattedResult.formattedCitations.join(
			citationTagEnd + citationTagStart);
		newFormattedCitation += citationTagEnd;

		oldFormattedBibliography = newFormattedBibliography;
		newFormattedBibliography = bibliographyTagStart;
		newFormattedBibliography += formattedResult.formattedBibliography.join(
			bibliographyTagEnd + bibliographyTagStart);
		newFormattedBibliography += bibliographyTagEnd;

		if (newFormattedBibliography.indexOf("<second-field-align>") > -1) {
			exampleOut.css({
				// TODO: don't change the whole output panel CSS, just the relevant lines
				"padding-left" : "3em",
				"text-indent" : "-2em"
			});
		} else {
			exampleOut.css({
				"padding-left" : "1em",
				"text-indent" : "0"
			});
		}

		// lazy instantiation of diff_match_patch
		if (dmp === null) {
			dmp = new diff_match_patch();
		}

		citationDiffs =
			dmp.diff_main(stripTags(oldFormattedCitation, "span"), stripTags(newFormattedCitation, "span"));
		dmp.diff_cleanupSemantic(citationDiffs);
		diffFormattedCitation = unescape(CSLEDIT.diff.prettyHtml(citationDiffs));

		bibliographyDiffs =
			dmp.diff_main(stripTags(oldFormattedBibliography, "span"), stripTags(newFormattedBibliography, "span"));
		dmp.diff_cleanupSemantic(bibliographyDiffs);
		diffFormattedBibliography = unescape(CSLEDIT.diff.prettyHtml(bibliographyDiffs));

		if (dmp.diff_levenshtein(citationDiffs) === 0 && dmp.diff_levenshtein(bibliographyDiffs) === 0) {
			citationsOut.html(newFormattedCitation);
			bibliographyOut.html(newFormattedBibliography);
			if (typeof callback !== "undefined") {
				callback();
			}
		} else {
			if (CSLEDIT.storage.getItem('CSLEDIT.options.visualEditorDiffs') === "true") {
				// display the diff
				citationsOut.html(diffFormattedCitation);
				bibliographyOut.html(diffFormattedBibliography);

				// display the new version in 1000ms
				clearTimeout(diffTimeout);
				diffTimeout = setTimeout(
					function () {
						citationsOut.html(newFormattedCitation);
						bibliographyOut.html(newFormattedBibliography);
						if (typeof callback !== "undefined") {
							callback();
						}
					},
				1000);
			} else {
				// display the real result
				citationsOut.html(newFormattedCitation);
				bibliographyOut.html(newFormattedBibliography);
				if (typeof callback !== "undefined") {
					callback();
				}
			}
		}

		console.timeEnd("runCiteprocAndDisplayOutput");
	}

	// Return public members:
	return {
		formatCitations : formatCitations,
		runCiteprocAndDisplayOutput : runCiteprocAndDisplayOutput
	};

}());
<|endoftext|>


<commit_before>Q.page("Users/session", function () {
	var url = Q.getObject("Q.Cordova.handoff.url");

	console.log("Trying to redirect to:", url);

	document.location.href = url;

	return function () {
		// code to execute before page starts unloading
	};
}, 'Users');<commit_msg>Wait for handleOpenURL function from session js handler.<commit_after>Q.page("Users/session", function () {
	var url = Q.getObject("Q.Cordova.handoff.url");

	console.log("Trying to redirect to:", url);

	var timeId = setInterval(function () {
		if (typeof handleOpenURL === 'function') {
			clearInterval(timeId);
			handleOpenURL(url);
		}
	}, 500);

	return function () {
		// code to execute before page starts unloading
	};
}, 'Users');<|endoftext|>