<commit_before>// Copyright 2014-2020, University of Colorado Boulder

/**
 * This node monitors the comings and goings of energy chunks on a observable list and adds/removes nodes that
 * correspond to each.  This is intended to be used in other view nodes that represent model elements that contain
 * energy chunks.
 *
 * This was done as a separate class so that it could be used in composition rather than inheritance, because
 * composition allows better control over the layering within the parent view node.
 *
 * @author John Blanco
 */

import merge from '../../../../phet-core/js/merge.js';
import Node from '../../../../scenery/js/nodes/Node.js';
import energyFormsAndChanges from '../../energyFormsAndChanges.js';
import EnergyChunkNode from './EnergyChunkNode.js';

class EnergyChunkLayer extends Node {

  /**
   * @param {ObservableArray} energyChunkList
   * @param {ModelViewTransform2} modelViewTransform
   * @param {Object} [options]
   */
  constructor( energyChunkList, modelViewTransform, options ) {
    super();

    options = merge( {

      // Property.<Vector2> - a position Property that will be used to compensate the energy chunk layer's position
      // such that it stays in untranslated screen-view coordinates. This is often used for an energy chunk layer that
      // is the child of a node that is being placed in the view according to its position value.
      parentPositionProperty: null
    }, options );

    // This function adds EnergyChunkNodes to the layer when chunks are produced in the model. It includes listeners for
    // when chunks are removed from the model.
    const chunkAddedListener = energyChunk => {

      // create and add a node to represent the energy chunk
      const energyChunkNode = new EnergyChunkNode( energyChunk, modelViewTransform );
      this.addChild( energyChunkNode );

      // when chunk is removed from the model, remove its node from the view
      const itemRemovedListener = removedChunk => {
        if ( removedChunk === energyChunk ) {
          this.removeChild( energyChunkNode );
          energyChunkNode.dispose();

          // remove this listener to avoid leaking memory
          energyChunkList.removeItemRemovedListener( itemRemovedListener );
        }
      };

      // link itemRemovedListener to the provided ObservableArray
      energyChunkList.addItemRemovedListener( itemRemovedListener );
    };

    // add the named observer function
    energyChunkList.addItemAddedListener( chunkAddedListener );

    if ( options.parentPositionProperty ) {

      // Since the energy chunk positions are in uncompensated model coordinates, this node must maintain a position
      // that is offset from the parent in order for the energy chunks to be in the correct location in the view.
      options.parentPositionProperty.link( position => {
        this.x = -modelViewTransform.modelToViewX( position.x );
        this.y = -modelViewTransform.modelToViewY( position.y );
      } );
    }
  }
}

energyFormsAndChanges.register( 'EnergyChunkLayer', EnergyChunkLayer );
export default EnergyChunkLayer;<commit_msg>Add chunkAddedListener for existing chunks, see https://github.com/phetsims/energy-forms-and-changes/issues/317<commit_after>// Copyright 2014-2020, University of Colorado Boulder

/**
 * This node monitors the comings and goings of energy chunks on a observable list and adds/removes nodes that
 * correspond to each.  This is intended to be used in other view nodes that represent model elements that contain
 * energy chunks.
 *
 * This was done as a separate class so that it could be used in composition rather than inheritance, because
 * composition allows better control over the layering within the parent view node.
 *
 * @author John Blanco
 */

import merge from '../../../../phet-core/js/merge.js';
import Node from '../../../../scenery/js/nodes/Node.js';
import energyFormsAndChanges from '../../energyFormsAndChanges.js';
import EnergyChunkNode from './EnergyChunkNode.js';

class EnergyChunkLayer extends Node {

  /**
   * @param {ObservableArray} energyChunkList
   * @param {ModelViewTransform2} modelViewTransform
   * @param {Object} [options]
   */
  constructor( energyChunkList, modelViewTransform, options ) {
    super();

    options = merge( {

      // Property.<Vector2> - a position Property that will be used to compensate the energy chunk layer's position
      // such that it stays in untranslated screen-view coordinates. This is often used for an energy chunk layer that
      // is the child of a node that is being placed in the view according to its position value.
      parentPositionProperty: null
    }, options );

    // This function adds EnergyChunkNodes to the layer when chunks are produced in the model. It includes listeners for
    // when chunks are removed from the model.
    const chunkAddedListener = energyChunk => {

      // create and add a node to represent the energy chunk
      const energyChunkNode = new EnergyChunkNode( energyChunk, modelViewTransform );
      this.addChild( energyChunkNode );

      // when chunk is removed from the model, remove its node from the view
      const itemRemovedListener = removedChunk => {
        if ( removedChunk === energyChunk ) {
          this.removeChild( energyChunkNode );
          energyChunkNode.dispose();

          // remove this listener to avoid leaking memory
          energyChunkList.removeItemRemovedListener( itemRemovedListener );
        }
      };

      // link itemRemovedListener to the provided ObservableArray
      energyChunkList.addItemRemovedListener( itemRemovedListener );
    };

    // add the named observer function for existing chunks and new chunks
    energyChunkList.forEach( chunkAddedListener );
    energyChunkList.addItemAddedListener( chunkAddedListener );

    if ( options.parentPositionProperty ) {

      // Since the energy chunk positions are in uncompensated model coordinates, this node must maintain a position
      // that is offset from the parent in order for the energy chunks to be in the correct location in the view.
      options.parentPositionProperty.link( position => {
        this.x = -modelViewTransform.modelToViewX( position.x );
        this.y = -modelViewTransform.modelToViewY( position.y );
      } );
    }
  }
}

energyFormsAndChanges.register( 'EnergyChunkLayer', EnergyChunkLayer );
export default EnergyChunkLayer;<|endoftext|>


<commit_before>'use strict';

// --- Dependencies ---
var assert   = require('assert');
var debug    = require('debug')('zotero:stream');
var util     = require('util');
var url      = require('url');
var WS       = require('ws');

var inherits = util.inherits;
var format   = util.format;

var Client = require('./client');
var pick   = require('./utils').pick;

/** @module zotero */

// Zotero API Vocabulary

var SUBSCRIBE    = 'createSubscriptions';
var UNSUBSCRIBE  = 'deleteSubscriptions';
var SUBSCRIBED   = 'subscriptionsAdded';
var UNSUBSCRIBED = 'subscriptionsDeleted';
var CONNECTED    = 'connected';

/**
 * Connects to the Zotero Streaming API
 * by establishing a WebSocket connection.
 *
 * @class Stream
 * @constructor
 * @extends Client
 *
 * @param {Object} options
 */
function Stream(options) {

  /**
   * Whether or not the Stream was initialized as
   * a single- or multi-key stream.
   *
   * @property multi
   * @type Boolean
   */
  reader(this, 'multi', !(options && options.key));

  if (!this.multi) {
    options.headers = options.headers || {};
    options.headers['Zotero-API-Key'] = options.key;

    delete options.key;
  }

  Client.call(this, options);

  this.open();
}

inherits(Stream, Client);

Stream.DEFAULT_RETRY = 1000;
Stream.MAX_RETRY     = 3;

Stream.defaults = {
  hostname: 'stream.zotero.org',
  protocol: 'https',

  headers: Client.headers
};

reader(Stream.prototype, 'url', function () {
  return url.format(this.options);
});

Stream.prototype.open = function () {
  assert(!this.socket);
  debug('opening websocket connection...');

  /**
   * The WebSocket to the Zotero API server.
   *
   * @property socket
   * @type WebSocket
   */
  this.socket = new WS(this.url, this.options)
    .on('open', this.emit.bind(this, 'open'))

    .on('close', this.closed.bind(this))

    .on('message', this.receive.bind(this))

    .on('error', this.error.bind(this));

  return this;
};

Stream.prototype.closed = function () {
  debug('websocket connection closed');

  // re-connect if closed by server

  delete this.socket;
  return this;
};

Stream.prototype.receive = function (message, flags) {
  debug('message received');

  try {
    validate(flags);

    var data = JSON.parse(message);
    var type = data.event;

    switch (type) {

    case undefined:
      throw new Error('invalid message received: no event defined');

    case CONNECTED:
      data = pick(data, 'topics', 'retry');

      if (data.retry)
        this.retry = Number(data.retry) || Stream.DEFAULT_RETRY;

      this.emit(type, data);
      break;

    case SUBSCRIBED:
      this.emit(type, data.subscriptions, data.errors);
      break;

    case UNSUBSCRIBED:
      this.emit(type);
      break;

    default:
      this.emit(type, pick(data, 'topic', 'apiKey', 'version'));

    }

  } catch (reason) {
    return this.error(reason);
  }

  return this;
};

Stream.prototype.send = function (data, callback) {
  assert(this.socket);
  assert(data && data.action);

  debug('sending %s message', data.action);

  this.socket.send(data, function (error) {
    if (typeof callback === 'function')
      callback(error);
    if (error)
      this.emit('error', error);
  });

  return this;
};

Stream.prototype.close = function () {
  assert(this.socket);
  debug('closing websocket connection...');

  this.socket.close();

  return this;
};

Stream.prototype.subscribe = function (subscriptions, callback) {
  assert(subscriptions);

  if (!Array.isArray(subscriptions))
    subscriptions = [subscriptions];

  return this.send({
    action: SUBSCRIBE,
    subscriptions: subscriptions

  }, callback);
};

Stream.prototype.unsubscribe = function (subscriptions, callback) {
  assert(subscriptions);

  if (!Array.isArray(subscriptions))
    subscriptions = [subscriptions];

  return this.send({
    action: UNSUBSCRIBE,
    subscriptions: subscriptions

  }, callback);
};

Stream.prototype.error = function (error) {
  if (typeof error === 'string')
    error = new Error(format.apply(null, arguments));

  debug(error.message);
  this.emit('error', error);

  return this;
};

// --- Private Helpers ---

function validate(flags) {
  if (flags.binary)
    throw new Error('binary stream not supported');

  if (flags.masked)
    throw new Error('masked stream not supported');

  return true;
}

function reader(ctx, name, value) {
  var prop = {};

  //prop.writable   = false;
  prop.enumerable = false;

  prop[(typeof value === 'function') ? 'get' : 'value'] = value;

  Object.defineProperty(ctx, name, prop);
}

// --- Module Exports ---
module.exports = Stream;
<commit_msg>remove event listeners before discarding socket<commit_after>'use strict';

// --- Dependencies ---
var assert   = require('assert');
var debug    = require('debug')('zotero:stream');
var util     = require('util');
var url      = require('url');
var WS       = require('ws');

var inherits = util.inherits;
var format   = util.format;

var Client = require('./client');
var pick   = require('./utils').pick;

/** @module zotero */

// Zotero API Vocabulary

var SUBSCRIBE    = 'createSubscriptions';
var UNSUBSCRIBE  = 'deleteSubscriptions';
var SUBSCRIBED   = 'subscriptionsAdded';
var UNSUBSCRIBED = 'subscriptionsDeleted';
var CONNECTED    = 'connected';

/**
 * Connects to the Zotero Streaming API
 * by establishing a WebSocket connection.
 *
 * @class Stream
 * @constructor
 * @extends Client
 *
 * @param {Object} options
 */
function Stream(options) {

  /**
   * Whether or not the Stream was initialized as
   * a single- or multi-key stream.
   *
   * @property multi
   * @type Boolean
   */
  reader(this, 'multi', !(options && options.key));

  if (!this.multi) {
    options.headers = options.headers || {};
    options.headers['Zotero-API-Key'] = options.key;

    delete options.key;
  }

  Client.call(this, options);

  this.open();
}

inherits(Stream, Client);

Stream.DEFAULT_RETRY = 1000;
Stream.MAX_RETRY     = 3;

Stream.defaults = {
  hostname: 'stream.zotero.org',
  protocol: 'https',

  headers: Client.headers
};

reader(Stream.prototype, 'url', function () {
  return url.format(this.options);
});

Stream.prototype.open = function () {
  assert(!this.socket);
  debug('opening websocket connection...');

  /**
   * The WebSocket to the Zotero API server.
   *
   * @property socket
   * @type WebSocket
   */
  this.socket = new WS(this.url, this.options)
    .on('open', this.emit.bind(this, 'open'))

    .on('close', this.closed.bind(this))

    .on('message', this.receive.bind(this))

    .on('error', this.error.bind(this));

  return this;
};

Stream.prototype.closed = function () {
  if (!this.socket) return this;

  assert(this.socket.readyState === WS.CLOSED);
  debug('websocket connection closed');

  // re-connect if closed by server

  this.socket.removeAllListeners();

  delete this.socket;
  return this;
};

Stream.prototype.receive = function (message, flags) {
  debug('message received');

  try {
    validate(flags);

    var data = JSON.parse(message);
    var type = data.event;

    switch (type) {

    case undefined:
      throw new Error('invalid message received: no event defined');

    case CONNECTED:
      data = pick(data, 'topics', 'retry');

      if (data.retry)
        this.retry = Number(data.retry) || Stream.DEFAULT_RETRY;

      this.emit(type, data);
      break;

    case SUBSCRIBED:
      this.emit(type, data.subscriptions, data.errors);
      break;

    case UNSUBSCRIBED:
      this.emit(type);
      break;

    default:
      this.emit(type, pick(data, 'topic', 'apiKey', 'version'));

    }

  } catch (reason) {
    return this.error(reason);
  }

  return this;
};

Stream.prototype.send = function (data, callback) {
  assert(this.socket);
  assert(data && data.action);

  debug('sending %s message', data.action);

  this.socket.send(data, function (error) {
    if (typeof callback === 'function')
      callback(error);
    if (error)
      this.emit('error', error);
  });

  return this;
};

Stream.prototype.close = function () {
  assert(this.socket);
  debug('closing websocket connection...');

  this.socket.close();

  return this;
};

Stream.prototype.subscribe = function (subscriptions, callback) {
  assert(subscriptions);

  if (!Array.isArray(subscriptions))
    subscriptions = [subscriptions];

  return this.send({
    action: SUBSCRIBE,
    subscriptions: subscriptions

  }, callback);
};

Stream.prototype.unsubscribe = function (subscriptions, callback) {
  assert(subscriptions);

  if (!Array.isArray(subscriptions))
    subscriptions = [subscriptions];

  return this.send({
    action: UNSUBSCRIBE,
    subscriptions: subscriptions

  }, callback);
};

Stream.prototype.error = function (error) {
  if (typeof error === 'string')
    error = new Error(format.apply(null, arguments));

  debug(error.message);
  this.emit('error', error);

  return this;
};

// --- Private Helpers ---

function validate(flags) {
  if (flags.binary)
    throw new Error('binary stream not supported');

  if (flags.masked)
    throw new Error('masked stream not supported');

  return true;
}

function reader(ctx, name, value) {
  var prop = {};

  //prop.writable   = false;
  prop.enumerable = false;

  prop[(typeof value === 'function') ? 'get' : 'value'] = value;

  Object.defineProperty(ctx, name, prop);
}

// --- Module Exports ---
module.exports = Stream;
<|endoftext|>


<commit_before>angular.module('Frosch')
    .controller('NotificacionCtrl',
    function ($scope, $state, config, $timeout, jugador, chico) {

        $scope.jugador = jugador;

        var timer = $timeout(function () {

            if (chico.termino)
                $state.go('jugar.chico.principal.termino');
            else if( chico.jugadorActual.gano && $state.current.name != 'jugar.chico.principal.ganaste')
                $state.go('jugar.chico.principal.ganaste');
            else
                $state.go('jugar.chico.principal')
        }, 3000);

        $scope.$on("$destroy",
            function (event) {
                $timeout.cancel(timer);
            }
        );

    });<commit_msg>Bugfix, si se gana de ùltimo por rana o ranita, se muestra ahora ganaste primero antes de ir a terminar<commit_after>angular.module('Frosch')
    .controller('NotificacionCtrl',
    function ($scope, $state, config, $timeout, jugador, chico) {

        $scope.jugador = jugador;

        var timer = $timeout(function () {

            if (chico.jugadorActual.gano && $state.current.name != 'jugar.chico.principal.ganaste')
                $state.go('jugar.chico.principal.ganaste');
            else if (chico.termino)
                $state.go('jugar.chico.principal.termino');
            else
                $state.go('jugar.chico.principal')
        }, 3000);

        $scope.$on("$destroy",
            function (event) {
                $timeout.cancel(timer);
            }
        );

    });<|endoftext|>


<commit_before>// MARK: - This is internal tool, we are free to use the latest javascript, thus no need to do jshint, which is for legacy frontend stuff
/* jshint ignore:start */
// MARK: - This has to pass through the gulp testing, so no var or for of loops, or any other modern features.
var splitter = '-|-';
var startTime = new Date();
if (window.opener && window.opener.userName) {
    window.userName = window.opener.userName;
} else {
    window.userName = '';
}
function convertTextToArray(t) {
    var newText = t.trim().replace(/^[\n\r\s]+/, '').replace(/[\n\r\s]+$/, '');
    newText = newText.replace(/[\r\n]+/g, splitter);
    if (newText.split(splitter).length > 1) {return newText.split(splitter);}
    var englishInfoDiv = document.createElement('DIV');
    englishInfoDiv.innerHTML = newText;
    var htmlTexts = [];
    for (var i=0; i<englishInfoDiv.children.length; i++) {
        htmlTexts.push(englishInfoDiv.children[i].outerHTML);
    }
    return htmlTexts;
}

function addTranslation(ele) {
    if (ele.className.indexOf(' selected')>=0) {return;}
    var text = ele.innerHTML;
    ele.className += ' selected';
    ele.title = '';
    var finalTranslationEle = document.getElementById('final-translation');
    var prefix = (finalTranslationEle.value === '') ? '' : '\n\n';
    finalTranslationEle.value += prefix + text;
    var ea = ele.getAttribute('data-translation-index');
    gtag('event', ea, {'event_label': window.userName, 'event_category': 'Translation Helper', 'non_interaction': false});
}

function tidyHTML(html) {
    var d = document.createElement('div');
    d.innerHTML = html;
    return d.innerHTML;
}

function confirmTranslation(ele) {
    if (ele.className.indexOf(' selected')>=0) {return;}
    var text = ele.innerHTML;
    ele.classList.add('selected');
    ele.title = '';
    var finalTranslationEle = ele.parentNode.parentNode.querySelector('textarea');
    var prefix = (finalTranslationEle.value === '') ? '' : '\n\n';
    var translatedText = prefix + text;
    finalTranslationEle.value += translatedText;
    var infoContainers = document.querySelectorAll('.info-container');
    var originalText = ele.parentNode.parentNode.querySelector('.info-original');
    // MARK: - You only need to translate once for the same phrase
    for (var i=0; i < infoContainers.length; i++) {
        var containerEle = infoContainers[i];
        var textEle = containerEle.querySelector('textarea');
        var originalEle = containerEle.querySelector('.info-original');
        if (originalText.innerHTML !== originalEle.innerHTML || textEle.value !== '') {continue;}
        textEle.value = translatedText;
        var infoTranslations = containerEle.querySelectorAll('.info-translation');
        for (var j=0; j<infoTranslations.length; j++) {
            var translationEle = infoTranslations[j];
            if (translationEle.innerHTML !== text) {continue;}
            translationEle.classList.add('selected');
        }
    }
    var ea = ele.getAttribute('data-translation-index');
    gtag('event', ea, {'event_label': window.userName, 'event_category': 'Translation Helper', 'non_interaction': false});
}

function start() {
    var englishText = document.getElementById('english-text');
    var translationInfoEle = document.getElementById('translation-info');
    var translationInfoString = translationInfoEle.value;
    var storyBodyEle = document.getElementById('story-body-container');
    if (translationInfoString !== '') {
        var translationInfo = JSON.parse(translationInfoString);
        var englishEle = document.createElement('DIV');
        englishEle.innerHTML = englishText.value;
        var k = '';
        if (window.opener && window.opener.titles) {
            for (var j=0; j<window.opener.titles.length; j++) {
                var info = window.opener.titles[j];
                var infoHTML = '
' + info.original + '
';
                var id = info.id;
                var translations = info.translations.split(splitter);
                for (var m=0; m<translations.length; m++) {
                    infoHTML += '
' + translations[m] + '
';
                }
                infoHTML = '

' + infoHTML + '

<textarea data-info-id="' + id + '" placeholder="点选右边的翻译版本，您也可以继续编辑"></textarea>

';
                k += infoHTML;
            }
        }
        for (var i=0; i<translationInfo.length; i++) {
            var info = translationInfo[i];
            var infoHTML = '';
            var id = info.id;
            var englishHTML = englishEle.querySelector('#' + id).innerHTML;
            infoHTML += '
' + englishHTML + '
';
            for (var j=0; j<info.translations.length; j++) {
                var translation = info.translations[j];
                var t = (/<.+>/g.test(translation)) ? tidyHTML(translation) : translation;
                infoHTML += '
' + t + '
';
            }
            infoHTML = '

' + infoHTML + '

<textarea data-info-id="' + id + '" placeholder="点选右边的翻译版本，您也可以继续编辑"></textarea>

';
            k += infoHTML;
        }
        k += '
<input type="button" value="完成并关闭" onclick="finishTranslation()" class="submitbutton button ui-light-btn">
';
        storyBodyEle.innerHTML = k;
        document.querySelector('.body').classList.add('full-grid');
    } else {
        var englishTextArray = convertTextToArray(englishText.value);
        var translationEles = document.querySelectorAll('.chinese-translation');
        var translationsArray = [];
        for (var i=0; i<translationEles.length; i++) {
            var translationText = translationEles[i].value;
            translationsArray.push(convertTextToArray(translationText));
        }
        var p = '';
        for (var j=0; j<englishTextArray.length; j++) {
            p += '
' + englishTextArray[j] + '
';
            for (var k=0; k<translationsArray.length; k++) {
                var currentTranslationArray = translationsArray[k];
                if (currentTranslationArray[j]) {
                    p += '
' + currentTranslationArray[j] + '
';
                }
            }
            p += '
';

        }
        storyBodyEle.innerHTML = p;
    }
    englishText.style.display = 'none';
    document.getElementById('start-button').style.display = 'none';
    document.getElementById('translations').style.display = 'none';
    translationInfoEle.style.display = 'none';
    document.querySelector('.sidebar').style.display = 'grid';
    // MARK: - If all three translations are the same, select directly
    var containers = document.querySelectorAll('.info-container')
    for (var m=0; m<containers.length; m++) {
        var container = containers[m];
        var translations = new Set();
        var translationEles = container.querySelectorAll('.info-translation')
        for (var n=0; n<translationEles.length; n++) {
            var translation = translationEles[n];
            translations.add(translation.innerHTML);
        }
        if (translations.size !== 1) {continue;}
        var value = translationEles[0].innerHTML;
        container.querySelector('textarea').value = value;
        container.querySelector('.info-translation').classList.add('selected');
    }
}

function trackFinishTimeAndClose() {
    var finishTime = new Date();
    var spentTime = Math.round(finishTime.getTime() - startTime.getTime());
    gtag('event', 'timing_complete', {
        'name' : 'finish',
        'value' : spentTime,
        'event_category' : "Quick Translation",
        'event_callback': function() {
            console.log('Spent ' + spentTime + ' miliseconds! ');
            if (window.opener) {window.close();}
        }
    });
    setTimeout(function(){
        if(window.opener) {window.close();}
    }, 3000);
}

function finish() {
    console.log ('paste the chinese text back! ');
    if (window.opener) {
        var result = document.getElementById('final-translation').value;
        window.opener.document.getElementById('cbody').value = result;
        window.opener.document.getElementById('tag').value += ',translation_confirmed';
    }
    trackFinishTimeAndClose();
}

function getCleanText(ele) {
    var cleanTexts = [];
    for (var i=0; i<ele.children.length; i++) {
        var child = ele.children[i];
        if (child.tagName.toUpperCase() === 'P') {
            cleanTexts.push(child.innerHTML);
        } else {
            cleanTexts.push(child.outerHTML);
        }
    }
    return cleanTexts.join('\n\n');
}

function formatDuration(seconds) {
    if (seconds === 0) {return 'now';}
    var formats = [
        {name: 'second', up: 60},
        {name: 'minute', up: 60},
        {name: 'hour', up: 24},
        {name: 'day', up: 365},
        {name: 'year'}
    ]
    var v = seconds;
    var times = [];
    for (var i=0; i<formats.length; i++) {
        var up = formats[i].up;
        var name = formats[i].name;
        var value = (up) ? v % up : v;
        if (value>0) {
            var suffix = (value>1) ? 's' : '';
            times.push(value + ' ' + name + suffix);
        }
        v = Math.floor(v/up);
        if (v === 0) {break;}
    }
    times = times.reverse();
    result = times.join(', ').replace(/, ([^,]+)$/, ' and $1');
    return result;
}

function finishTranslation() {
    if (typeof window.subtitleInfo === 'object') {
        finishTranslationForVideo();
    } else {
        finishTranslationForArticle();
    }
    trackFinishTimeAndClose();
}

function finishTranslationForArticle() {
    var t = document.getElementById('english-text').value;
    var newText = t.trim().replace(/^[\n\r\s]+/, '').replace(/[\n\r\s]+$/, '');
    var englishInfoDiv = document.createElement('DIV');
    englishInfoDiv.innerHTML = newText;
    var cleanEnglishText = getCleanText(englishInfoDiv);
    var titles = ['cheadline', 'clongleadbody'];
    var titlesInfo = [];
    for (var i=0; i<document.querySelectorAll('[data-info-id]').length; i++) {
        var ele = document.querySelectorAll('[data-info-id]')[i];
        var id = ele.getAttribute('data-info-id');
        var infoEle = englishInfoDiv.querySelector('#' + id);
        if (infoEle) {
            infoEle.innerHTML = ele.value.trim().replace(/^[\n\r\s]+/, '').replace(/[\n\r\s]+$/, '');
        }
        for (var m=0; m<titles.length; m++) {
            if (titles[m] === id) {
                console.log(ele);
                titlesInfo.push({id: id, value: ele.value});
            }
        }
    }
    var cleanChineseText = getCleanText(englishInfoDiv);
    if (window.opener) {
        var cbodyEles = window.opener.document.querySelectorAll('textarea.bodybox, #cbody');
        for (var j=0; j<cbodyEles.length; j++) {
            var cbodyEle = cbodyEles[j];
            cbodyEle.disabled = false;
            cbodyEle.value = cleanChineseText;
        }
        ebodyEle = window.opener.document.getElementById('ebody');
        ebodyEle.disabled = false;
        ebodyEle.value = cleanEnglishText;
        for (var k=0; k<titlesInfo.length; k++) {
            var id = titlesInfo[k].id;
            var value = titlesInfo[k].value;
            window.opener.document.getElementById(id).disabled = false;
            window.opener.document.getElementById(id).value = value;
        }
        var translationHelperButton = window.opener.document.querySelector('.translation-helper');
        if (translationHelperButton) {
            var finishTime = new Date();
            var spentTime = Math.round((finishTime.getTime() - startTime.getTime())/1000);
            translationHelperButton.innerHTML = 'Translation finished in ' + formatDuration(spentTime) + '. ';
            setTimeout(function(){translationHelperButton.style.display = 'none';}, 6000);
        }
        // MARK: - In the workflow, there's a weird requirement that a translator have to use a select menu to indicate that the translation is finished. This is totally stupid so I'll just automate it.
        var completeSelects = window.opener.document.querySelectorAll('select');
        for (var l=0; l<completeSelects.length; l++) {
            currentSelect = completeSelects[l];
            if (currentSelect.id && currentSelect.id.indexOf('complete_') === 0) {
                currentSelect.value = 1;
            }
        }
    }
}

function finishTranslationForVideo() {
    var infos = window.subtitleInfo.text;
    for (var i=0; i<infos.length; i++) {
        var p = infos[i];
        for (var j=0; j<p.length; j++) {
            var s = p[j];
            var id = s.id;
            var translation = document.querySelector('[data-info-id="' + id + '"]').value;
            s.translation = translation;
            delete s.translations;
            delete s.id;
        }
    }
    if (window.opener) {
        var cbodyEle = window.opener.document.querySelector('#cbody');
        if (cbodyEle) {
            cbodyEle.value = JSON.stringify(window.subtitleInfo);
            window.opener.document.getElementById('cshortleadbody').value = '';
            window.opener.document.getElementById('tag').value = '原声视频,PendingSubtitle';
        }
    } else {
        console.log(window.subtitleInfo);
    }
}

if (window.opener || typeof window.subtitleInfo === 'object') {
    var englishText;
    var translationText;
    if (window.opener) {
        englishText = window.opener.ebodyForTranslation || window.opener.document.getElementById('ebody').value;
        translationText = window.opener.cbodyForTranslation || window.opener.document.getElementById('cbody').value;
        if (/caption/.test(translationText) && /translations/.test(translationText) && /\"end\":/.test(translationText)) {
            window.subtitleInfo = JSON.parse(translationText);
        }
    }
    // MARK: - Update english and translation text for video subtitles
    if (typeof window.subtitleInfo === 'object') {
        var infos = window.subtitleInfo.text;
        var englishTexts = [];
        var translations = [];
        var n = 0;
        for (var i=0; i<infos.length; i++) {
            var p = infos[i];
            for (var j=0; j<p.length; j++) {
                var s = p[j];
                var id = 'id-' + n;
                englishTexts.push('
' + s.text + '
');
                var t = s.translations || [];
                translations.push({id: id, translations: t});
                s.id = id;
                n += 1;
            }
        }
        englishText = englishTexts.join('');
        translationText = JSON.stringify(translations);
    }
    document.getElementById('english-text').value = englishText;
    if (/translations/.test(translationText)) {
        document.getElementById('translation-info').value = translationText;
    } else {
        translations = translationText.split(splitter);
        var translationsHTML = '';
        for (var k=0; k<translations.length; k++) {
            translationsHTML += '<textarea class="commentTextArea chinese-translation" width="100%" rows="3">' + translations[k] + '</textarea>';
        }
        document.getElementById('translations').innerHTML = translationsHTML;
    }
    start();
}

/* jshint ignore:end */<commit_msg>backup<commit_after>// MARK: - This is internal tool, we are free to use the latest javascript, thus no need to do jshint, which is for legacy frontend stuff
/* jshint ignore:start */
// MARK: - This has to pass through the gulp testing, so no var or for of loops, or any other modern features.
var splitter = '-|-';
var startTime = new Date();
if (window.opener && window.opener.userName) {
    window.userName = window.opener.userName;
} else {
    window.userName = '';
}
function convertTextToArray(t) {
    var newText = t.trim().replace(/^[\n\r\s]+/, '').replace(/[\n\r\s]+$/, '');
    newText = newText.replace(/[\r\n]+/g, splitter);
    if (newText.split(splitter).length > 1) {return newText.split(splitter);}
    var englishInfoDiv = document.createElement('DIV');
    englishInfoDiv.innerHTML = newText;
    var htmlTexts = [];
    for (var i=0; i<englishInfoDiv.children.length; i++) {
        htmlTexts.push(englishInfoDiv.children[i].outerHTML);
    }
    return htmlTexts;
}

function addTranslation(ele) {
    if (ele.className.indexOf(' selected')>=0) {return;}
    var text = ele.innerHTML;
    ele.className += ' selected';
    ele.title = '';
    var finalTranslationEle = document.getElementById('final-translation');
    var prefix = (finalTranslationEle.value === '') ? '' : '\n\n';
    finalTranslationEle.value += prefix + text;
    var ea = ele.getAttribute('data-translation-index');
    gtag('event', ea, {'event_label': window.userName, 'event_category': 'Translation Helper', 'non_interaction': false});
}

function tidyHTML(html) {
    var d = document.createElement('div');
    d.innerHTML = html;
    return d.innerHTML;
}

function confirmTranslation(ele) {
    if (ele.className.indexOf(' selected')>=0) {return;}
    var text = ele.innerHTML;
    ele.classList.add('selected');
    ele.title = '';
    var finalTranslationEle = ele.parentNode.parentNode.querySelector('textarea');
    var prefix = (finalTranslationEle.value === '') ? '' : '\n\n';
    var translatedText = prefix + text;
    finalTranslationEle.value += translatedText;
    var infoContainers = document.querySelectorAll('.info-container');
    var originalText = ele.parentNode.parentNode.querySelector('.info-original');
    // MARK: - You only need to translate once for the same phrase
    for (var i=0; i < infoContainers.length; i++) {
        var containerEle = infoContainers[i];
        var textEle = containerEle.querySelector('textarea');
        var originalEle = containerEle.querySelector('.info-original');
        if (originalText.innerHTML !== originalEle.innerHTML || textEle.value !== '') {continue;}
        textEle.value = translatedText;
        var infoTranslations = containerEle.querySelectorAll('.info-translation');
        for (var j=0; j<infoTranslations.length; j++) {
            var translationEle = infoTranslations[j];
            if (translationEle.innerHTML !== text) {continue;}
            translationEle.classList.add('selected');
        }
    }
    var ea = ele.getAttribute('data-translation-index');
    gtag('event', ea, {'event_label': window.userName, 'event_category': 'Translation Helper', 'non_interaction': false});
}

function start() {
    var englishText = document.getElementById('english-text');
    var translationInfoEle = document.getElementById('translation-info');
    var translationInfoString = translationInfoEle.value;
    var storyBodyEle = document.getElementById('story-body-container');
    if (translationInfoString !== '') {
        var translationInfo = JSON.parse(translationInfoString);
        var englishEle = document.createElement('DIV');
        englishEle.innerHTML = englishText.value;
        var k = '';
        if (window.opener && window.opener.titles) {
            for (var j=0; j<window.opener.titles.length; j++) {
                var info = window.opener.titles[j];
                var infoHTML = '
' + info.original + '
';
                var id = info.id;
                var translations = info.translations.split(splitter);
                for (var m=0; m<translations.length; m++) {
                    infoHTML += '
' + translations[m] + '
';
                }
                infoHTML = '

' + infoHTML + '

<textarea data-info-id="' + id + '" placeholder="点选右边的翻译版本，您也可以继续编辑"></textarea>

';
                k += infoHTML;
            }
        }
        for (var i=0; i<translationInfo.length; i++) {
            var info = translationInfo[i];
            var infoHTML = '';
            var id = info.id;
            var englishHTML = englishEle.querySelector('#' + id).innerHTML;
            infoHTML += '
' + englishHTML + '
';
            for (var j=0; j<info.translations.length; j++) {
                var translation = info.translations[j];
                var t = (/<.+>/g.test(translation)) ? tidyHTML(translation) : translation;
                infoHTML += '
' + t + '
';
            }
            infoHTML = '

' + infoHTML + '

<textarea data-info-id="' + id + '" placeholder="点选右边的翻译版本，您也可以继续编辑"></textarea>

';
            k += infoHTML;
        }
        k += '
<input type="button" value="完成并关闭" onclick="finishTranslation()" class="submitbutton button ui-light-btn">
';
        storyBodyEle.innerHTML = k;
        document.querySelector('.body').classList.add('full-grid');
    } else {
        var englishTextArray = convertTextToArray(englishText.value);
        var translationEles = document.querySelectorAll('.chinese-translation');
        var translationsArray = [];
        for (var i=0; i<translationEles.length; i++) {
            var translationText = translationEles[i].value;
            translationsArray.push(convertTextToArray(translationText));
        }
        var p = '';
        for (var j=0; j<englishTextArray.length; j++) {
            p += '
' + englishTextArray[j] + '
';
            for (var k=0; k<translationsArray.length; k++) {
                var currentTranslationArray = translationsArray[k];
                if (currentTranslationArray[j]) {
                    p += '
' + currentTranslationArray[j] + '
';
                }
            }
            p += '
';

        }
        storyBodyEle.innerHTML = p;
    }
    englishText.style.display = 'none';
    document.getElementById('start-button').style.display = 'none';
    document.getElementById('translations').style.display = 'none';
    translationInfoEle.style.display = 'none';
    document.querySelector('.sidebar').style.display = 'grid';
    // MARK: - If all three translations are the same, select directly
    var containers = document.querySelectorAll('.info-container')
    for (var m=0; m<containers.length; m++) {
        var container = containers[m];
        var translations = new Set();
        var translationEles = container.querySelectorAll('.info-translation')
        for (var n=0; n<translationEles.length; n++) {
            var translation = translationEles[n];
            translations.add(translation.innerHTML);
        }
        if (translations.size !== 1) {continue;}
        var value = translationEles[0].innerHTML;
        container.querySelector('textarea').value = value;
        container.querySelector('.info-translation').classList.add('selected');
    }
}

function trackFinishTimeAndClose() {
    var finishTime = new Date();
    var spentTime = Math.round(finishTime.getTime() - startTime.getTime());
    gtag('event', 'timing_complete', {
        'name' : 'finish',
        'value' : spentTime,
        'event_category' : "Quick Translation",
        'event_callback': function() {
            console.log('Spent ' + spentTime + ' miliseconds! ');
            if (window.opener) {window.close();}
        }
    });
    setTimeout(function(){
        if(window.opener) {window.close();}
    }, 3000);
}

function finish() {
    console.log ('paste the chinese text back! ');
    if (window.opener) {
        var result = document.getElementById('final-translation').value;
        window.opener.document.getElementById('cbody').value = result;
        window.opener.document.getElementById('tag').value += ',translation_confirmed';
    }
    trackFinishTimeAndClose();
}

function getCleanText(ele) {
    var cleanTexts = [];
    for (var i=0; i<ele.children.length; i++) {
        var child = ele.children[i];
        if (child.tagName.toUpperCase() === 'P') {
            cleanTexts.push(child.innerHTML);
        } else {
            cleanTexts.push(child.outerHTML);
        }
    }
    return cleanTexts.join('\n\n');
}

function formatDuration(seconds) {
    if (seconds === 0) {return 'now';}
    var formats = [
        {name: 'second', up: 60},
        {name: 'minute', up: 60},
        {name: 'hour', up: 24},
        {name: 'day', up: 365},
        {name: 'year'}
    ]
    var v = seconds;
    var times = [];
    for (var i=0; i<formats.length; i++) {
        var up = formats[i].up;
        var name = formats[i].name;
        var value = (up) ? v % up : v;
        if (value>0) {
            var suffix = (value>1) ? 's' : '';
            times.push(value + ' ' + name + suffix);
        }
        v = Math.floor(v/up);
        if (v === 0) {break;}
    }
    times = times.reverse();
    result = times.join(', ').replace(/, ([^,]+)$/, ' and $1');
    return result;
}

function finishTranslation() {
    if (typeof window.subtitleInfo === 'object') {
        finishTranslationForVideo();
    } else {
        finishTranslationForArticle();
    }
    trackFinishTimeAndClose();
}

function finishTranslationForArticle() {
    var t = document.getElementById('english-text').value;
    var newText = t.trim().replace(/^[\n\r\s]+/, '').replace(/[\n\r\s]+$/, '');
    var englishInfoDiv = document.createElement('DIV');
    englishInfoDiv.innerHTML = newText;
    var cleanEnglishText = getCleanText(englishInfoDiv);
    var titles = ['cheadline', 'clongleadbody'];
    var titlesInfo = [];
    for (var i=0; i<document.querySelectorAll('[data-info-id]').length; i++) {
        var ele = document.querySelectorAll('[data-info-id]')[i];
        var id = ele.getAttribute('data-info-id');
        var infoEle = englishInfoDiv.querySelector('#' + id);
        if (infoEle) {
            infoEle.innerHTML = ele.value.trim().replace(/^[\n\r\s]+/, '').replace(/[\n\r\s]+$/, '');
        }
        for (var m=0; m<titles.length; m++) {
            if (titles[m] === id) {
                console.log(ele);
                titlesInfo.push({id: id, value: ele.value});
            }
        }
    }
    var cleanChineseText = getCleanText(englishInfoDiv);
    if (window.opener) {
        var cbodyEles = window.opener.document.querySelectorAll('textarea.bodybox, #cbody');
        for (var j=0; j<cbodyEles.length; j++) {
            var cbodyEle = cbodyEles[j];
            cbodyEle.disabled = false;
            cbodyEle.value = cleanChineseText;
        }
        ebodyEle = window.opener.document.getElementById('ebody');
        ebodyEle.disabled = false;
        ebodyEle.value = cleanEnglishText;
        for (var k=0; k<titlesInfo.length; k++) {
            var id = titlesInfo[k].id;
            var value = titlesInfo[k].value;
            window.opener.document.getElementById(id).disabled = false;
            window.opener.document.getElementById(id).value = value;
        }
        var translationHelperButton = window.opener.document.querySelector('.translation-helper');
        if (translationHelperButton) {
            var finishTime = new Date();
            var spentTime = Math.round((finishTime.getTime() - startTime.getTime())/1000);
            translationHelperButton.innerHTML = 'Translation finished in ' + formatDuration(spentTime) + '. ';
            setTimeout(function(){translationHelperButton.style.display = 'none';}, 6000);
        }
        // MARK: - In the workflow, there's a weird requirement that a translator have to use a select menu to indicate that the translation is finished. This is totally stupid so I'll just automate it.
        var completeSelects = window.opener.document.querySelectorAll('select');
        for (var l=0; l<completeSelects.length; l++) {
            currentSelect = completeSelects[l];
            if (currentSelect.id && currentSelect.id.indexOf('complete_') === 0) {
                currentSelect.value = 1;
            }
        }
    }
}

function finishTranslationForVideo() {
    var infos = window.subtitleInfo.text;
    for (var i=0; i<infos.length; i++) {
        var p = infos[i];
        for (var j=0; j<p.length; j++) {
            var s = p[j];
            var id = s.id;
            var translation = document.querySelector('[data-info-id="' + id + '"]').value;
            s.translation = translation;
            delete s.translations;
            delete s.id;
        }
    }
    if (window.opener) {
        var cbodyEle = window.opener.document.querySelector('#cbody');
        if (cbodyEle) {
            cbodyEle.value = JSON.stringify(window.subtitleInfo);
            window.opener.document.getElementById('cshortleadbody').value = '';
            window.opener.document.getElementById('tag').value = 'FT商学院,原声视频,中英字幕,PendingSubtitle';
        }
    } else {
        console.log(window.subtitleInfo);
    }
}

if (window.opener || typeof window.subtitleInfo === 'object') {
    var englishText;
    var translationText;
    if (window.opener) {
        englishText = window.opener.ebodyForTranslation || window.opener.document.getElementById('ebody').value;
        translationText = window.opener.cbodyForTranslation || window.opener.document.getElementById('cbody').value;
        if (/caption/.test(translationText) && /translations/.test(translationText) && /\"end\":/.test(translationText)) {
            window.subtitleInfo = JSON.parse(translationText);
        }
    }
    // MARK: - Update english and translation text for video subtitles
    if (typeof window.subtitleInfo === 'object') {
        var infos = window.subtitleInfo.text;
        var englishTexts = [];
        var translations = [];
        var n = 0;
        for (var i=0; i<infos.length; i++) {
            var p = infos[i];
            for (var j=0; j<p.length; j++) {
                var s = p[j];
                var id = 'id-' + n;
                englishTexts.push('
' + s.text + '
');
                var t = s.translations || [];
                translations.push({id: id, translations: t});
                s.id = id;
                n += 1;
            }
        }
        englishText = englishTexts.join('');
        translationText = JSON.stringify(translations);
    }
    document.getElementById('english-text').value = englishText;
    if (/translations/.test(translationText)) {
        document.getElementById('translation-info').value = translationText;
    } else {
        translations = translationText.split(splitter);
        var translationsHTML = '';
        for (var k=0; k<translations.length; k++) {
            translationsHTML += '<textarea class="commentTextArea chinese-translation" width="100%" rows="3">' + translations[k] + '</textarea>';
        }
        document.getElementById('translations').innerHTML = translationsHTML;
    }
    start();
}

/* jshint ignore:end */<|endoftext|>


<commit_before>var Oracle = {
  api: 'http://m.atb.no/xmlhttprequest.php?service=routeplannerOracle.getOracleAnswer&question=',
  msgDisconnected: 'Frakoblet fra m.atb.no',

  debug: 0,

  get: function(question, callback) {
    if (callback == undefined) {
      console.log('ERROR: Callback is required. In the callback you should insert the results into the DOM.');
      return;
    }
    if (isEmpty(question)) {
      console.log('ERROR: question is empty');
      return;
    }

    // Encode URL component.... as it says below :D
    var encodedQuestion = encodeURIComponent(question);

    var self = this;
    Ajaxer.getPlainText({
      url: self.api + encodedQuestion,
      success: function(answer) {
        answer = self.shorten(answer);

        // Store answer for later prediction
        self.consider(question, answer);

        // Call it back
        callback(answer);
      },
      error: function(jqXHR, text, err) {
        callback(self.msgDisconnected);
      },
    });
  },

  greet: function() {
    var greetings = [
      // Spørsmål
      'Når vil du ta bussen?',
      'Bussorakelet trenger noe å gjøre, har du et spørsmål?',
      'Har du et busspørsmål eller skal du bare lese litt tilfeldig tekst?',
      'Lurer du på hvilken buss du skal ta?',
      'Vil du ta bussen til Samfundet kanskje?',
      'Vil du ta bussen til kjellerne på Moholt kanskje?',
      'Har du et lite bussspørsmål?',
      'Skal det være en buss?',
      'Vil du ha en liten privat buss kanskje? En femseter?',

      // Statements
      'Orakelet tar gjerne imot spørsmål.',
      'Orakelet vet alt om hvilke busser som kjører.',
      'Orakelet svarer på alle spørsmål om busser.',
      'Spør, og du vil få svar.',
      'Bussorakelet er sultent på spørsmål, eller kantinemat, alt ettersom.',
      'Spør et busspørsmål her.',
      'De som laget Notifier er ganske snille mot AtB.',
      'Ta bussen til Tiller og kjøp no stæsj du trenger.',
      'Ta bussen til Nidar og spis masse sjokolade.',
      'Ta bussen til campus når det er for kaldt for å sykle.',
      'Tralala, jeg kan synge en sang.',
      'Buss her, buss der, buss overalt.',

      // Funfacts
      'Bussorakelet ble utviklet på NTNU, spør om noe!',
      'Tri M. Nguyen har laget APIet som brukes til sanntiden. Takk, Tri <3',
      'Tri M. Nguyen har laget APIet som Notifier bruker, det er på api.visuweb.no/bybussen',
      'AtB har minibusser også.',
      'UKA har en egen buss.',
      'Flybussen går hvert 10. minutt.',
      'Leddbusser har wifi.',
      'AtB har også langdistansebusser, de er mørkegrønne.',
      'Da Roy Sindre fant nøkkelen ble det plutselig en bussapp før AtB visste hva som skjedde.',
      'En av bussjåførene er julenisse hver desember.',
      'Både AtB og SiT liker Notifier.',
      'Notifier fikk støtte for sanntidsbuss i versjon 1.3.0.',
      'Notifier og bussorakelet ble venner i Notifier versjon 1.3.1',
      'Tenk om Kantinelizzie hadde kjørt buss.',
      'Hvis du har installert Notifier på forskjellige maskiner kan du ha forskjellige busstopp på hver.',
      'Større busser, flere busser, raskere busser, AtB gjør alle tre på 5ern.',
      'En gang gikk 5ern via Gryta og Lohove til Dragvoll.',
      'Før i tiden møttes alle bussene i Munkegata/Dronningens gate.',
      'AtB laget egen bussapp som het Bussøyet, men den var for sent etter Bartebuss.',
      'Det finnes mange bussapper.',
      'AtB sitt API er laget av italienere.',
      'AtB sitt API har italienske metodenavn.',
      'Bartebuss og ByBussen er de beste bussappene.',
      'Bussene sjekker inn med GPS på hvert stopp, cirka.',
      'Bussene som må kjøre langt mellom hvert stopp har upresis sanntid.',
      'busskartet.no er ganske morsomt.',
      'Han som laget busskartet.no må ha kost seg gløgg i hjæl med alle de små bussene sine.',
      'Sekkemannen sitter alltid på en utgave av Under Dusken for å unngå bakterier fra bussetet.',

      // Om orakelet
      'Bussorakelets wiki er på ntnu.no/wiki/display/FUIROS',
      'Bussorakelet kom til live i 1998.',
      'I 2002 begynte bussorakelet å besvare spørsmål på SMS.',
      'BussTUC betyr "Buss: The Understanding Computer".',
      'Orakelet består av tre hjerner: Den første kan beregning, den andre kan språk, den tredje forstår de to første.',
      'Orakelet forstår språk ved hjelp av et komplekst sett med regler skrevet i Prolog.',
      'Bussorakelet er den sentrale delen i FUIROS-prosjektet.',
      'Orakelet kan 4590 ord, 1204 stoppnavn, 80 busslinjer og 9970 navnevarianter.',
      'Orakelet forstår språk ved hjelp av 5000 språkregler.',
      'Orakelet kan 960 substantiv, 1100 verb og 450 adjektiv.',
      'Orakelets kodebase består av 130.500 linjer Prolog.',
      'Orakelet forstår språk ved hjelp av "Consensical Grammar" (CONtext SENSItive CompositionAL Grammar).',

      // Team Trafikk
      'AtB er tusen ganger bedre enn Team Trafikk var.',
      'I 2010 byttet Team Trafikk navn til AtB, og byttet eiere.',
      'Team Trafikk sine busser var hvite, gamle og slitne.',
      'Da Team Trafikk ble AtB ble alt mye bedre.',
      'Da Team Trafikk ble AtB fikk de miljøvennlige busser.',
      'Da Team Trafikk ble AtB fikk de grønnere busser.',
      'Da Team Trafikk ble AtB fikk de mobillett.',
      'Da Team Trafikk ble AtB fikk de nye kontorer.',
      'Da Team Trafikk ble AtB fikk de API-er som man kunne hente data fra.',
      'Da Team Trafikk ble AtB fant Roy Sindre en nøkkel til API-et, han spurte ikke om lov.',
      'Åpne bussdata var veldig lurt av AtB.',

      // Contributors
      'Christoffer J. Marcussen er en av hjernene bak bussorakelet (2011-2012).',
      'Jon S. Bratseth er en av hjernene bak bussorakelet (1996-1997).',
      'Magnus Raaum er en av hjernene bak bussorakelet (2010-2011).',
      'Martin T. Ranang er en av hjernene bak bussorakelet (2001-2011).',
      'Marius Q. Wollamo er en av hjernene bak bussorakelet (2012-2013).',
      'Runar Andersstuen er en av hjernene bak bussorakelet (2011-2012).',
      'Rune M. Andersen er en av hjernene bak bussorakelet (2011-2012).',
      'Rune Sætre er en av hjernene bak bussorakelet (2000-2012).',
      'Tore Amble er en av hjernene bak bussorakelet (1996-2012).',
      'Tore Bruland er en av hjernene bak bussorakelet (2004-2011).',
      'Trond Bø Engell er en av hjernene bak bussorakelet (2011-2012).',
    ];
    var randomGreeting = greetings[Math.floor(Math.random() * greetings.length)];
    return randomGreeting;
  },

  predict: function() {
    var oracleBrain = JSON.parse(localStorage.oracleBrain);

    // Get question from timeslot
    var timeslot = this.getTimeslot();
    var question = oracleBrain[timeslot.day][timeslot.hour];
    if (this.debug) console.log('Oracle predicting question for day '+timeslot.day+' in the '+timeslot.hour+': "'+question+'"');

    return (question != '' ? question : null);
  },

  // Private functions, do not use externally

  consider: function(question, answer) {
    if (this.debug) console.log('Oracle considering...\n- Question:', question, '\n- Answer:', answer);

    var pieces = answer.match(/buss|til|fra/gi);
    if (pieces != null) {
      if (pieces.length >= 3) {
        var oracleBrain = JSON.parse(localStorage.oracleBrain);

        // Get timeslot
        var timeslot = this.getTimeslot();

        // Insert question at correct timeslot
        oracleBrain[timeslot.day][timeslot.hour] = question;
        if (this.debug) console.log('Oracle considered question to be valuable');

        // Stringify the brain
        localStorage.oracleBrain = JSON.stringify(oracleBrain);
      }
      else {
        if (this.debug) console.log('Oracle thinks the answer did not contain enough expected keywords');
      }
    }
    else {
      if (this.debug) console.log('Oracle thinks the answer did not contain expected keywords');
    }
  },

  getTimeslot: function() {
    // Night:       0 -  5:59
    // Morning:     6 - 11:59
    // Afternoon:  12 - 17:59
    // Evening:    18 - 23:59
    var d = new Date();
    var hour = d.getHours();

    var timeslot = {};
    timeslot.day = d.getDay();

    if (0 <= hour && hour < 6)
      timeslot.hour = 'night';
    else if (6 <= hour && hour < 12)
      timeslot.hour = 'morning';
    else if (12 <= hour && hour < 18)
      timeslot.hour = 'afternoon';
    else //if (18 <= hour && hour <= 23)
      timeslot.hour = 'evening';

    return timeslot;
  },

  shorten: function(answer) {

    // Example:
    // "Holdeplassen nærmest Gløshaugen er Gløshaugen Syd. Buss 5 går fra
    // Gløshaugen Syd kl. 2054 til Prinsen kinosenter kl. 2058 og buss 19
    // går fra Prinsen kinosenter kl. 2105 til Studentersamfundet kl. 2106.
    // Tidene angir tidligste passeringer av holdeplassene."

    var pieces = answer.split('. ');

    // Slice away "Holdeplassen nærmest X er X."
    if (pieces[0].startsWith('Holdeplassen nærmest')) {
      pieces = pieces.slice(1);
    }

    // Slice away "Tidene angir tidligste passeringer av holdeplassene."
    if (pieces[pieces.length-1].startsWith('Tidene angir tidligste')) {
      pieces = pieces.slice(0, pieces.length-1);
    }

    return pieces.join('. ') + '.';
  },

}

// Defaults
if (localStorage.oracleBrain == undefined) {
  var oracleBrain = {};
  for (var i=0; i<=6; i++)
    oracleBrain[i] = {night:'', morning:'', afternoon:'', evening:''};
  localStorage.oracleBrain = JSON.stringify(oracleBrain);
}
<commit_msg>oracle: added a few messages and tweaked a little<commit_after>var Oracle = {
  api: 'http://m.atb.no/xmlhttprequest.php?service=routeplannerOracle.getOracleAnswer&question=',
  msgCantPredictLong: 'Etter å ha brukt orakelet en stund kan det forutsi spørsmålet ditt når du trykker [TAB].',
  msgCantPredictShort: 'Ved å bruke orakelet lærer det å forutsi spørsmålet ditt når du trykker [TAB]',
  msgDisconnected: 'Frakoblet fra m.atb.no',

  debug: 0,

  get: function(question, callback) {
    if (callback == undefined) {
      console.log('ERROR: Callback is required. In the callback you should insert the results into the DOM.');
      return;
    }
    if (isEmpty(question)) {
      console.log('ERROR: question is empty');
      return;
    }

    // Encode URL component.... as it says below :D
    var encodedQuestion = encodeURIComponent(question);

    var self = this;
    Ajaxer.getPlainText({
      url: self.api + encodedQuestion,
      success: function(answer) {
        answer = self.shorten(answer);

        // Store answer for later prediction
        self.consider(question, answer);

        // Call it back
        callback(answer);
      },
      error: function(jqXHR, text, err) {
        callback(self.msgDisconnected);
      },
    });
  },

  greet: function() {
    var greetings = [
      // Spørsmål
      'Når vil du ta bussen?',
      'Bussorakelet trenger noe å gjøre, har du et spørsmål?',
      'Har du et busspørsmål eller skal du bare lese litt tilfeldig tekst?',
      'Lurer du på hvilken buss du skal ta?',
      'Vil du ta bussen til Samfundet kanskje?',
      'Vil du ta bussen til kjellerne på Moholt kanskje?',
      'Har du et lite bussspørsmål?',
      'Skal det være en buss?',
      'Vil du ha en liten privat buss kanskje? En femseter?',

      // Statements
      'Orakelet tar gjerne imot spørsmål.',
      'Orakelet vet alt om hvilke busser som kjører.',
      'Orakelet svarer på alle spørsmål om busser.',
      'Spør, og du vil få svar.',
      'Bussorakelet er sultent på spørsmål, eller kantinemat, alt ettersom.',
      'Spør et busspørsmål her.',
      'De som laget Notifier er ganske snille mot AtB.',
      'Ta bussen til Tiller og kjøp no stæsj du trenger.',
      'Ta bussen til Nidar og spis masse sjokolade.',
      'Ta bussen til campus når det er for kaldt for å sykle.',
      'Tralala, jeg kan synge en sang.',
      'Buss her, buss der, buss overalt.',

      // Funfacts
      'Bussorakelet ble utviklet på NTNU, spør om noe!',
      'Tri M. Nguyen har laget APIet som brukes til sanntiden. Takk, Tri <3',
      'Tri M. Nguyen har laget APIet som Notifier bruker, det er på api.visuweb.no/bybussen',
      'AtB har minibusser også.',
      'UKA har en egen buss.',
      'Flybussen går hvert 10. minutt.',
      'Leddbusser har wifi.',
      'AtB har også langdistansebusser, de er mørkegrønne.',
      'Da Roy Sindre fant nøkkelen ble det plutselig en bussapp før AtB visste hva som skjedde.',
      'En av bussjåførene er julenisse hver desember.',
      'Både AtB og SiT liker Notifier.',
      'Notifier fikk støtte for sanntidsbuss i versjon 1.3.0.',
      'Notifier og bussorakelet ble venner i Notifier versjon 1.3.1',
      'Tenk om Kantinelizzie hadde kjørt buss.',
      'Hvis du har installert Notifier på forskjellige maskiner kan du ha forskjellige busstopp på hver.',
      'Større busser, flere busser, raskere busser, AtB gjør alle tre på 5ern.',
      'En gang gikk 5ern via Gryta og Lohove til Dragvoll.',
      'Før i tiden møttes alle bussene i Munkegata/Dronningens gate.',
      'AtB laget egen bussapp som het Bussøyet, men den var for sent etter Bartebuss.',
      'Det finnes mange bussapper.',
      'AtB sitt API er laget av italienere.',
      'AtB sitt API har italienske metodenavn.',
      'Bartebuss og ByBussen er de beste bussappene.',
      'Bussene sjekker inn med GPS på hvert stopp, cirka.',
      'Bussene som må kjøre langt mellom hvert stopp har upresis sanntid.',
      'busskartet.no er ganske morsomt.',
      'Han som laget busskartet.no må ha kost seg gløgg i hjæl med alle de små bussene sine.',
      'Sekkemannen sitter alltid på en utgave av Under Dusken for å unngå bakterier fra bussetet.',

      // Om orakelet
      'Bussorakelets wiki er på ntnu.no/wiki/display/FUIROS',
      'Bussorakelet kom til live i 1998.',
      'I 2002 begynte bussorakelet å besvare spørsmål på SMS.',
      'BussTUC betyr "Buss: The Understanding Computer".',
      'Orakelet består av tre hjerner: Den første kan beregning, den andre kan språk, den tredje forstår de to første.',
      'Orakelet forstår språk ved hjelp av et komplekst sett med regler skrevet i Prolog.',
      'Bussorakelet er den sentrale delen i FUIROS-prosjektet.',
      'Orakelet kan 4590 ord, 1204 stoppnavn, 80 busslinjer og 9970 navnevarianter.',
      'Orakelet forstår språk ved hjelp av 5000 språkregler.',
      'Orakelet kan 960 substantiv, 1100 verb og 450 adjektiv.',
      'Orakelets kodebase består av 130.500 linjer Prolog.',
      'Orakelet forstår språk ved hjelp av "Consensical Grammar" (CONtext SENSItive CompositionAL Grammar).',

      // Team Trafikk
      'AtB er tusen ganger bedre enn Team Trafikk var.',
      'I 2010 byttet Team Trafikk navn til AtB, og byttet eiere.',
      'Team Trafikk sine busser var hvite, gamle og slitne.',
      'Da Team Trafikk ble AtB ble alt mye bedre.',
      'Da Team Trafikk ble AtB fikk de miljøvennlige busser.',
      'Da Team Trafikk ble AtB fikk de grønnere busser.',
      'Da Team Trafikk ble AtB fikk de mobillett.',
      'Da Team Trafikk ble AtB fikk de nye kontorer.',
      'Da Team Trafikk ble AtB fikk de API-er som man kunne hente data fra.',
      'Da Team Trafikk ble AtB fant Roy Sindre en nøkkel til API-et, han spurte ikke om lov.',
      'Åpne bussdata var veldig lurt av AtB.',

      // Contributors
      'Christoffer J. Marcussen er en av hjernene bak bussorakelet (2011-2012).',
      'Jon S. Bratseth er en av hjernene bak bussorakelet (1996-1997).',
      'Magnus Raaum er en av hjernene bak bussorakelet (2010-2011).',
      'Martin T. Ranang er en av hjernene bak bussorakelet (2001-2011).',
      'Marius Q. Wollamo er en av hjernene bak bussorakelet (2012-2013).',
      'Runar Andersstuen er en av hjernene bak bussorakelet (2011-2012).',
      'Rune M. Andersen er en av hjernene bak bussorakelet (2011-2012).',
      'Rune Sætre er en av hjernene bak bussorakelet (2000-2012).',
      'Tore Amble er en av hjernene bak bussorakelet (1996-2012).',
      'Tore Bruland er en av hjernene bak bussorakelet (2004-2011).',
      'Trond Bø Engell er en av hjernene bak bussorakelet (2011-2012).',
    ];
    var randomGreeting = greetings[Math.floor(Math.random() * greetings.length)];
    return randomGreeting;
  },

  predict: function() {
    var oracleBrain = JSON.parse(localStorage.oracleBrain);

    // Get question from timeslot
    var timeslot = this.getTimeslot();
    var question = oracleBrain[timeslot.day][timeslot.hour];
    if (this.debug) console.log('Oracle predicting question for day '+timeslot.day+' in the '+timeslot.hour+': "'+question+'"');

    return (question != '' ? question : null);
  },

  // Private functions, do not use externally

  consider: function(question, answer) {
    if (this.debug) console.log('Oracle considering...\n- Question:', question, '\n- Answer:', answer);

    var pieces = answer.match(/buss|til|fra|passerer|kommer|senere/gi);
    if (pieces != null) {
      if (pieces.length >= 3) {
        var oracleBrain = JSON.parse(localStorage.oracleBrain);

        // Get timeslot
        var timeslot = this.getTimeslot();

        // Insert question at correct timeslot
        oracleBrain[timeslot.day][timeslot.hour] = question;
        if (this.debug) console.log('Oracle considered question to be valuable');

        // Stringify the brain
        localStorage.oracleBrain = JSON.stringify(oracleBrain);
      }
      else {
        if (this.debug) console.log('Oracle thinks the answer did not contain enough expected keywords');
      }
    }
    else {
      if (this.debug) console.log('Oracle thinks the answer did not contain expected keywords');
    }
  },

  getTimeslot: function() {
    // Night:       0 -  5:59
    // Morning:     6 - 11:59
    // Afternoon:  12 - 17:59
    // Evening:    18 - 23:59
    var d = new Date();
    var hour = d.getHours();

    var timeslot = {};
    timeslot.day = d.getDay();

    if (0 <= hour && hour < 6)
      timeslot.hour = 'night';
    else if (6 <= hour && hour < 12)
      timeslot.hour = 'morning';
    else if (12 <= hour && hour < 18)
      timeslot.hour = 'afternoon';
    else //if (18 <= hour && hour <= 23)
      timeslot.hour = 'evening';

    return timeslot;
  },

  shorten: function(answer) {

    // Example:
    // "Holdeplassen nærmest Gløshaugen er Gløshaugen Syd. Buss 5 går fra
    // Gløshaugen Syd kl. 2054 til Prinsen kinosenter kl. 2058 og buss 19
    // går fra Prinsen kinosenter kl. 2105 til Studentersamfundet kl. 2106.
    // Tidene angir tidligste passeringer av holdeplassene."

    var pieces = answer.split('. ');

    // Slice away "Holdeplassen nærmest X er X."
    if (pieces[0].startsWith('Holdeplassen nærmest')) {
      pieces = pieces.slice(1);
    }

    // Slice away "Tidene angir tidligste passeringer av holdeplassene."
    if (pieces[pieces.length-1].startsWith('Tidene angir tidligste')) {
      pieces = pieces.slice(0, pieces.length-1);
    }

    return pieces.join('. ') + '.';
  },

}

// Defaults
if (localStorage.oracleBrain == undefined) {
  var oracleBrain = {};
  for (var i=0; i<=6; i++)
    oracleBrain[i] = {night:'', morning:'', afternoon:'', evening:''};
  localStorage.oracleBrain = JSON.stringify(oracleBrain);
}
<|endoftext|>


<commit_before>module.exports = function(db) {
  return db();
}
<commit_msg>fix linting<commit_after>module.exports = function(db) {
  return db();
};
<|endoftext|>


<commit_before>/*jslint unparam: true, browser: true, indent: 2 */

;(function ($, window, document, undefined) {
  'use strict';

  Foundation.libs.reveal = {
    name : 'reveal',

    version : '4.3.2',

    locked : false,

    settings : {
      animation: 'fadeAndPop',
      animationSpeed: 250,
      closeOnBackgroundClick: true,
      closeOnEsc: true,
      dismissModalClass: 'close-reveal-modal',
      bgClass: 'reveal-modal-bg',
      open: function(){},
      opened: function(){},
      close: function(){},
      closed: function(){},
      bg : $('.reveal-modal-bg'),
      css : {
        open : {
          'opacity': 0,
          'visibility': 'visible',
          'display' : 'block'
        },
        close : {
          'opacity': 1,
          'visibility': 'hidden',
          'display': 'none'
        }
      }
    },

    init : function (scope, method, options) {
      Foundation.inherit(this, 'data_options delay');

      if (typeof method === 'object') {
        $.extend(true, this.settings, method);
      } else if (typeof options !== 'undefined') {
        $.extend(true, this.settings, options);
      }

      if (typeof method !== 'string') {
        this.events();

        return this.settings.init;
      } else {
        return this[method].call(this, options);
      }
    },

    events : function () {
      var self = this;

      $(this.scope)
        .off('.fndtn.reveal')
        .on('click.fndtn.reveal', '[data-reveal-id]', function (e) {
          e.preventDefault();

          if (!self.locked) {
            var element = $(this),
                ajax = element.data('reveal-ajax');

            self.locked = true;

            if (typeof ajax === 'undefined') {
              self.open.call(self, element);
            } else {
              var url = ajax === true ? element.attr('href') : ajax;

              self.open.call(self, element, {url: url});
            }
          }
        })
        .on('click.fndtn.reveal touchend', this.close_targets(), function (e) {
          e.preventDefault();
          if (!self.locked) {
            var settings = $.extend({}, self.settings, self.data_options($('.reveal-modal.open'))),
              bgClicked = $(e.target)[0] === $('.' + settings.bgClass)[0];
            if (bgClicked && !settings.closeOnBackgroundClick) {
              return;
            }

            self.locked = true;
            self.close.call(self, bgClicked ? $('.reveal-modal.open') : $(this).closest('.reveal-modal'));
          }
        });

      if($(this.scope).hasClass('reveal-modal')) {
        $(this.scope)
          .on('open.fndtn.reveal', this.settings.open)
          .on('opened.fndtn.reveal', this.settings.opened)
          .on('opened.fndtn.reveal', this.open_video)
          .on('close.fndtn.reveal', this.settings.close)
          .on('closed.fndtn.reveal', this.settings.closed)
          .on('closed.fndtn.reveal', this.close_video);
      } else {
        $(this.scope)
          .on('open.fndtn.reveal', '.reveal-modal', this.settings.open)
          .on('opened.fndtn.reveal', '.reveal-modal', this.settings.opened)
          .on('opened.fndtn.reveal', '.reveal-modal', this.open_video)
          .on('close.fndtn.reveal', '.reveal-modal', this.settings.close)
          .on('closed.fndtn.reveal', '.reveal-modal', this.settings.closed)
          .on('closed.fndtn.reveal', '.reveal-modal', this.close_video);
      }

      $( 'body' ).bind( 'keyup.reveal', function ( event ) {
        var open_modal = $('.reveal-modal.open'),
            settings = $.extend({}, self.settings, self.data_options(open_modal));
        if ( event.which === 27  && settings.closeOnEsc) { // 27 is the keycode for the Escape key
          open_modal.foundation('reveal', 'close');
        }
      });

      return true;
    },

    open : function (target, ajax_settings) {
      if (target) {
        if (typeof target.selector !== 'undefined') {
          var modal = $('#' + target.data('reveal-id'));
        } else {
          var modal = $(this.scope);

          ajax_settings = target;
        }
      } else {
        var modal = $(this.scope);
      }

      if (!modal.hasClass('open')) {
        var open_modal = $('.reveal-modal.open');

        if (typeof modal.data('css-top') === 'undefined') {
          modal.data('css-top', parseInt(modal.css('top'), 10))
            .data('offset', this.cache_offset(modal));
        }

        modal.trigger('open');

        if (open_modal.length < 1) {
          this.toggle_bg();
        }

        if (typeof ajax_settings === 'undefined' || !ajax_settings.url) {
          this.hide(open_modal, this.settings.css.close);
          this.show(modal, this.settings.css.open);
        } else {
          var self = this,
              old_success = typeof ajax_settings.success !== 'undefined' ? ajax_settings.success : null;

          $.extend(ajax_settings, {
            success: function (data, textStatus, jqXHR) {
              if ( $.isFunction(old_success) ) {
                old_success(data, textStatus, jqXHR);
              }

              modal.html(data);
              $(modal).foundation('section', 'reflow');

              self.hide(open_modal, self.settings.css.close);
              self.show(modal, self.settings.css.open);
            }
          });

          $.ajax(ajax_settings);
        }
      }
    },

    close : function (modal) {

      var modal = modal && modal.length ? modal : $(this.scope),
          open_modals = $('.reveal-modal.open');

      if (open_modals.length > 0) {
        this.locked = true;
        modal.trigger('close');
        this.toggle_bg();
        this.hide(open_modals, this.settings.css.close);
      }
    },

    close_targets : function () {
      var base = '.' + this.settings.dismissModalClass;

      if (this.settings.closeOnBackgroundClick) {
        return base + ', .' + this.settings.bgClass;
      }

      return base;
    },

    toggle_bg : function () {
      if ($('.' + this.settings.bgClass).length === 0) {
        this.settings.bg = $('<div />', {'class': this.settings.bgClass})
          .appendTo('body');
      }

      if (this.settings.bg.filter(':visible').length > 0) {
        this.hide(this.settings.bg);
      } else {
        this.show(this.settings.bg);
      }
    },

    show : function (el, css) {
      // is modal
      if (css) {
        if (/pop/i.test(this.settings.animation)) {
          css.top = $(window).scrollTop() - el.data('offset') + 'px';
          var end_css = {
            top: $(window).scrollTop() + el.data('css-top') + 'px',
            opacity: 1
          };

          return this.delay(function () {
            return el
              .css(css)
              .animate(end_css, this.settings.animationSpeed, 'linear', function () {
                this.locked = false;
                el.trigger('opened');
              }.bind(this))
              .addClass('open');
          }.bind(this), this.settings.animationSpeed / 2);
        }

        if (/fade/i.test(this.settings.animation)) {
          var end_css = {opacity: 1};

          return this.delay(function () {
            return el
              .css(css)
              .animate(end_css, this.settings.animationSpeed, 'linear', function () {
                this.locked = false;
                el.trigger('opened');
              }.bind(this))
              .addClass('open');
          }.bind(this), this.settings.animationSpeed / 2);
        }

        return el.css(css).show().css({opacity: 1}).addClass('open').trigger('opened');
      }

      // should we animate the background?
      if (/fade/i.test(this.settings.animation)) {
        return el.fadeIn(this.settings.animationSpeed / 2);
      }

      return el.show();
    },

    hide : function (el, css) {
      // is modal
      if (css) {
        if (/pop/i.test(this.settings.animation)) {
          var end_css = {
            top: - $(window).scrollTop() - el.data('offset') + 'px',
            opacity: 0
          };

          return this.delay(function () {
            return el
              .animate(end_css, this.settings.animationSpeed, 'linear', function () {
                this.locked = false;
                el.css(css).trigger('closed');
              }.bind(this))
              .removeClass('open');
          }.bind(this), this.settings.animationSpeed / 2);
        }

        if (/fade/i.test(this.settings.animation)) {
          var end_css = {opacity: 0};

          return this.delay(function () {
            return el
              .animate(end_css, this.settings.animationSpeed, 'linear', function () {
                this.locked = false;
                el.css(css).trigger('closed');
              }.bind(this))
              .removeClass('open');
          }.bind(this), this.settings.animationSpeed / 2);
        }

        return el.hide().css(css).removeClass('open').trigger('closed');
      }

      // should we animate the background?
      if (/fade/i.test(this.settings.animation)) {
        return el.fadeOut(this.settings.animationSpeed / 2);
      }

      return el.hide();
    },

    close_video : function (e) {
      var video = $(this).find('.flex-video'),
          iframe = video.find('iframe');

      if (iframe.length > 0) {
        iframe.attr('data-src', iframe[0].src);
        iframe.attr('src', 'about:blank');
        video.hide();
      }
    },

    open_video : function (e) {
      var video = $(this).find('.flex-video'),
          iframe = video.find('iframe');

      if (iframe.length > 0) {
        var data_src = iframe.attr('data-src');
        if (typeof data_src === 'string') {
          iframe[0].src = iframe.attr('data-src');
        } else {
          var src = iframe[0].src;
          iframe[0].src = undefined;
          iframe[0].src = src;
        }
        video.show();
      }
    },

    cache_offset : function (modal) {
      var offset = modal.show().height() + parseInt(modal.css('top'), 10);

      modal.hide();

      return offset;
    },

    off : function () {
      $(this.scope).off('.fndtn.reveal');
    },

    reflow : function () {}
  };
}(Foundation.zj, this, this.document));<commit_msg>Added support for temporarily relocating modal content that isn't a direct child of the body element<commit_after>/*jslint unparam: true, browser: true, indent: 2 */

;(function ($, window, document, undefined) {
  'use strict';

  Foundation.libs.reveal = {
    name : 'reveal',

    version : '4.3.2',

    locked : false,

    settings : {
      animation: 'fadeAndPop',
      animationSpeed: 250,
      closeOnBackgroundClick: true,
      closeOnEsc: true,
      dismissModalClass: 'close-reveal-modal',
      bgClass: 'reveal-modal-bg',
      open: function(){},
      opened: function(){},
      close: function(){},
      closed: function(){},
      bg : $('.reveal-modal-bg'),
      css : {
        open : {
          'opacity': 0,
          'visibility': 'visible',
          'display' : 'block'
        },
        close : {
          'opacity': 1,
          'visibility': 'hidden',
          'display': 'none'
        }
      }
    },

    init : function (scope, method, options) {
      Foundation.inherit(this, 'data_options delay');

      if (typeof method === 'object') {
        $.extend(true, this.settings, method);
      } else if (typeof options !== 'undefined') {
        $.extend(true, this.settings, options);
      }

      if (typeof method !== 'string') {
        this.events();

        return this.settings.init;
      } else {
        return this[method].call(this, options);
      }
    },

    events : function () {
      var self = this;

      $(this.scope)
        .off('.fndtn.reveal')
        .on('click.fndtn.reveal', '[data-reveal-id]', function (e) {
          e.preventDefault();

          if (!self.locked) {
            var element = $(this),
                ajax = element.data('reveal-ajax');

            self.locked = true;

            if (typeof ajax === 'undefined') {
              self.open.call(self, element);
            } else {
              var url = ajax === true ? element.attr('href') : ajax;

              self.open.call(self, element, {url: url});
            }
          }
        })
        .on('click.fndtn.reveal touchend', this.close_targets(), function (e) {
          e.preventDefault();
          if (!self.locked) {
            var settings = $.extend({}, self.settings, self.data_options($('.reveal-modal.open'))),
              bgClicked = $(e.target)[0] === $('.' + settings.bgClass)[0];
            if (bgClicked && !settings.closeOnBackgroundClick) {
              return;
            }

            self.locked = true;
            self.close.call(self, bgClicked ? $('.reveal-modal.open') : $(this).closest('.reveal-modal'));
          }
        });

      if($(this.scope).hasClass('reveal-modal')) {
        $(this.scope)
          .on('open.fndtn.reveal', this.settings.open)
          .on('opened.fndtn.reveal', this.settings.opened)
          .on('opened.fndtn.reveal', this.open_video)
          .on('close.fndtn.reveal', this.settings.close)
          .on('closed.fndtn.reveal', this.settings.closed)
          .on('closed.fndtn.reveal', this.close_video);
      } else {
        $(this.scope)
          .on('open.fndtn.reveal', '.reveal-modal', this.settings.open)
          .on('opened.fndtn.reveal', '.reveal-modal', this.settings.opened)
          .on('opened.fndtn.reveal', '.reveal-modal', this.open_video)
          .on('close.fndtn.reveal', '.reveal-modal', this.settings.close)
          .on('closed.fndtn.reveal', '.reveal-modal', this.settings.closed)
          .on('closed.fndtn.reveal', '.reveal-modal', this.close_video);
      }

      $( 'body' ).bind( 'keyup.reveal', function ( event ) {
        var open_modal = $('.reveal-modal.open'),
            settings = $.extend({}, self.settings, self.data_options(open_modal));
        if ( event.which === 27  && settings.closeOnEsc) { // 27 is the keycode for the Escape key
          open_modal.foundation('reveal', 'close');
        }
      });

      return true;
    },

    open : function (target, ajax_settings) {
      if (target) {
        if (typeof target.selector !== 'undefined') {
          var modal = $('#' + target.data('reveal-id'));
        } else {
          var modal = $(this.scope);

          ajax_settings = target;
        }
      } else {
        var modal = $(this.scope);
      }

      if (!modal.hasClass('open')) {
        var open_modal = $('.reveal-modal.open');

        if (typeof modal.data('css-top') === 'undefined') {
          modal.data('css-top', parseInt(modal.css('top'), 10))
            .data('offset', this.cache_offset(modal));
        }

        modal.trigger('open');

        if (open_modal.length < 1) {
          this.toggle_bg();
        }

        if (typeof ajax_settings === 'undefined' || !ajax_settings.url) {
          this.hide(open_modal, this.settings.css.close);
          this.show(modal, this.settings.css.open);
        } else {
          var self = this,
              old_success = typeof ajax_settings.success !== 'undefined' ? ajax_settings.success : null;

          $.extend(ajax_settings, {
            success: function (data, textStatus, jqXHR) {
              if ( $.isFunction(old_success) ) {
                old_success(data, textStatus, jqXHR);
              }

              modal.html(data);
              $(modal).foundation('section', 'reflow');

              self.hide(open_modal, self.settings.css.close);
              self.show(modal, self.settings.css.open);
            }
          });

          $.ajax(ajax_settings);
        }
      }
    },

    close : function (modal) {

      var modal = modal && modal.length ? modal : $(this.scope),
          open_modals = $('.reveal-modal.open');

      if (open_modals.length > 0) {
        this.locked = true;
        modal.trigger('close');
        this.toggle_bg();
        this.hide(open_modals, this.settings.css.close);
      }
    },

    close_targets : function () {
      var base = '.' + this.settings.dismissModalClass;

      if (this.settings.closeOnBackgroundClick) {
        return base + ', .' + this.settings.bgClass;
      }

      return base;
    },

    toggle_bg : function () {
      if ($('.' + this.settings.bgClass).length === 0) {
        this.settings.bg = $('<div />', {'class': this.settings.bgClass})
          .appendTo('body');
      }

      if (this.settings.bg.filter(':visible').length > 0) {
        this.hide(this.settings.bg);
      } else {
        this.show(this.settings.bg);
      }
    },

    show : function (el, css) {
      // is modal
      if (css) {
        if (el.parent('body').length === 0) {
          var placeholder = el.wrap('<div style="display: none;" />').parent();
          el.on('closed.fndtn.reveal.wrapped', function() {
            el.detach().appendTo(placeholder);
            el.unwrap().unbind('closed.fndtn.reveal.wrapped');
          });

          el.detach().appendTo('body');
        }

        if (/pop/i.test(this.settings.animation)) {
          css.top = $(window).scrollTop() - el.data('offset') + 'px';
          var end_css = {
            top: $(window).scrollTop() + el.data('css-top') + 'px',
            opacity: 1
          };

          return this.delay(function () {
            return el
              .css(css)
              .animate(end_css, this.settings.animationSpeed, 'linear', function () {
                this.locked = false;
                el.trigger('opened');
              }.bind(this))
              .addClass('open');
          }.bind(this), this.settings.animationSpeed / 2);
        }

        if (/fade/i.test(this.settings.animation)) {
          var end_css = {opacity: 1};

          return this.delay(function () {
            return el
              .css(css)
              .animate(end_css, this.settings.animationSpeed, 'linear', function () {
                this.locked = false;
                el.trigger('opened');
              }.bind(this))
              .addClass('open');
          }.bind(this), this.settings.animationSpeed / 2);
        }

        return el.css(css).show().css({opacity: 1}).addClass('open').trigger('opened');
      }

      // should we animate the background?
      if (/fade/i.test(this.settings.animation)) {
        return el.fadeIn(this.settings.animationSpeed / 2);
      }

      return el.show();
    },

    hide : function (el, css) {
      // is modal
      if (css) {
        if (/pop/i.test(this.settings.animation)) {
          var end_css = {
            top: - $(window).scrollTop() - el.data('offset') + 'px',
            opacity: 0
          };

          return this.delay(function () {
            return el
              .animate(end_css, this.settings.animationSpeed, 'linear', function () {
                this.locked = false;
                el.css(css).trigger('closed');
              }.bind(this))
              .removeClass('open');
          }.bind(this), this.settings.animationSpeed / 2);
        }

        if (/fade/i.test(this.settings.animation)) {
          var end_css = {opacity: 0};

          return this.delay(function () {
            return el
              .animate(end_css, this.settings.animationSpeed, 'linear', function () {
                this.locked = false;
                el.css(css).trigger('closed');
              }.bind(this))
              .removeClass('open');
          }.bind(this), this.settings.animationSpeed / 2);
        }

        return el.hide().css(css).removeClass('open').trigger('closed');
      }

      // should we animate the background?
      if (/fade/i.test(this.settings.animation)) {
        return el.fadeOut(this.settings.animationSpeed / 2);
      }

      return el.hide();
    },

    close_video : function (e) {
      var video = $(this).find('.flex-video'),
          iframe = video.find('iframe');

      if (iframe.length > 0) {
        iframe.attr('data-src', iframe[0].src);
        iframe.attr('src', 'about:blank');
        video.hide();
      }
    },

    open_video : function (e) {
      var video = $(this).find('.flex-video'),
          iframe = video.find('iframe');

      if (iframe.length > 0) {
        var data_src = iframe.attr('data-src');
        if (typeof data_src === 'string') {
          iframe[0].src = iframe.attr('data-src');
        } else {
          var src = iframe[0].src;
          iframe[0].src = undefined;
          iframe[0].src = src;
        }
        video.show();
      }
    },

    cache_offset : function (modal) {
      var offset = modal.show().height() + parseInt(modal.css('top'), 10);

      modal.hide();

      return offset;
    },

    off : function () {
      $(this.scope).off('.fndtn.reveal');
    },

    reflow : function () {}
  };
}(Foundation.zj, this, this.document));<|endoftext|>


<commit_before>'use strict';

/**
 * Rhizome - The API that feeds grassroots movements
 *
 * @file person.js
 * @description Person model definition.
 * @module Model
 * @exports model, schema, constants
 * @author Chris Bates-Keegan
 *
 */

var mongoose = require('mongoose');
var Model = require('../model');
// var Logging = require('../../logging');

/**
 * Constants
*/

var constants = {
};

/**
 * Schema
 */
var schema = new mongoose.Schema({
  title: String,
  forename: String,
  surname: String,
  email: {
    type: String,
    index: true
  },
  address: String,
  postcode: String,
  phone: {
    landline: String,
    mobile: String
  },
  membershipNumber: String,
  voterId: String,
  meta: [{
    key: String,
    value: String
  }],
  _dataOwner: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Group'
  },
  _apps: [Model.Schema.AppAuth]
});

var ModelDef = null;

/**
 * Schema Virtual Methods
 */
schema.virtual('details').get(function() {
  return {
    id: this._id,
    forename: this.forename,
    surname: this.surname,
    name: `${this.forename} ${this.surname}`,
    address: this.address,
    postcode: this.postcode,
    phone: {
      landline: this.landline,
      mobile: this.mobile
    },
    membershipNumber: this.membershipNumber,
    voterId: this.voterId,
    dataOwner: this._dataOwner && this._dataOwner.name ? this._dataOwner.name : false,
    meta: this.meta
  };
});

/**
 * Schema Static Methods
 */

/**
 * @param {Object} body - body passed through from a POST request
 * @param {Object} owner - Owner group Mongoose object
 * @return {Promise} - returns a promise that is fulfilled when the database request is completed
 */
schema.statics.add = (body, owner) => {
  return new Promise((resolve, reject) => {
    var app = new ModelDef({
      forename: body.forename,
      surname: body.surname,
      email: body.surname,
      telephone: {
        landline: body.landline,
        mobile: body.mobile
      },
      address: body.address,
      postcode: body.postcode,
      membershipNumber: body.membershipNumber,
      voterId: body.voterId,
      _dataOwner: owner
    });

    app.save().then(res => resolve(res.details), reject);
  });
};

ModelDef = mongoose.model('Person', schema);

/**
 * Exports
 */
module.exports.constants = constants;
module.exports.schema = schema;
module.exports.model = ModelDef;
<commit_msg>- TWEAK: Array of emails rather than one. Probably need to think of all contact details as arrays.<commit_after>'use strict';

/**
 * Rhizome - The API that feeds grassroots movements
 *
 * @file person.js
 * @description Person model definition.
 * @module Model
 * @exports model, schema, constants
 * @author Chris Bates-Keegan
 *
 */

var mongoose = require('mongoose');
var Model = require('../model');
// var Logging = require('../../logging');

/**
 * Constants
*/

var constants = {
};

/**
 * Schema
 */
var schema = new mongoose.Schema({
  title: String,
  forename: String,
  surname: String,
  emails: [String],
  address: String,
  postcode: String,
  phone: {
    landline: String,
    mobile: String
  },
  membershipNumber: String,
  voterId: String,
  meta: [{
    key: String,
    value: String
  }],
  _dataOwner: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Group'
  },
  _apps: [Model.Schema.AppAuth]
});

var ModelDef = null;

/**
 * Schema Virtual Methods
 */
schema.virtual('details').get(function() {
  return {
    id: this._id,
    forename: this.forename,
    surname: this.surname,
    name: `${this.forename} ${this.surname}`,
    address: this.address,
    postcode: this.postcode,
    phone: {
      landline: this.landline,
      mobile: this.mobile
    },
    membershipNumber: this.membershipNumber,
    voterId: this.voterId,
    dataOwner: this._dataOwner && this._dataOwner.name ? this._dataOwner.name : false,
    meta: this.meta
  };
});

/**
 * Schema Static Methods
 */

/**
 * @param {Object} body - body passed through from a POST request
 * @param {Object} owner - Owner group Mongoose object
 * @return {Promise} - returns a promise that is fulfilled when the database request is completed
 */
schema.statics.add = (body, owner) => {
  return new Promise((resolve, reject) => {
    var app = new ModelDef({
      forename: body.forename,
      surname: body.surname,
      email: body.surname,
      telephone: {
        landline: body.landline,
        mobile: body.mobile
      },
      address: body.address,
      postcode: body.postcode,
      membershipNumber: body.membershipNumber,
      voterId: body.voterId,
      _dataOwner: owner
    });

    app.save().then(res => resolve(res.details), reject);
  });
};

ModelDef = mongoose.model('Person', schema);

/**
 * Exports
 */
module.exports.constants = constants;
module.exports.schema = schema;
module.exports.model = ModelDef;
<|endoftext|>


<commit_before>define(function() {
    'use strict';

    /**
     * @class
     */
    function CurrencyParser() { }

    /**
    * Parse the supplied string as a number of British pence
    *
    * First attempt with no regexes, and only using OLD SKOOL JS
    *
    * @param {String} subject - The string to be parsed
    * @returns {Number} The value of the string in pence
    */
    CurrencyParser.prototype.parseString = function(origStr) {
        var str = origStr;

        if (!(typeof str == 'string' || str instanceof String))
            //TBD or throw TypeError? Or just call .toString on it?
            return 0;

        // Trim any trailing 'p' symbol
        if (str.substring(str.length - 1) == 'p') // TODO or endsWith - polyfill?
            str = str.substring(0, str.length - 1);

        // Trim any leading 0s
        while(str.substring(0, 1) == '0') // TODO inefficient
            str = str.substring(1);

        // Recognise and trim any currency marker
        if (str.substring(0, 1) == '£') { // TODO or startsWith
            if (str.indexOf('.') == -1)
                str += '.0';
            str = str.substring(1);
        }

        // Sanitise trailing point with no following digit
        if (str.substring(str.length - 1) == '.') // TODO or endsWith
        str += '0';

        // Check string contains only digits with at most one decimal point
        // TODO much easier with regex!
        var pointCount = 0;
        for (var i = 0; i < str.length; i++) {
            var code = str.charCodeAt(i);
            if ((code < 46 || code == 47 || code > 57) || (code == 46 && ++pointCount > 1))
                return 0;
        }

        // Handle strings which are empty, or effectively empty
        if (str.length < 1)
        return 0;

        if (str.indexOf('.') != -1)
            // If contains a decimal point, parse as pounds
            return Math.round(parseFloat(str) * 100);
        else
            // Parse as whole pence
            return parseInt(str);
    };

    // Return constructor
    return CurrencyParser;
});
<commit_msg>Rewrite parser to use regular expressions<commit_after>define(function() {
    'use strict';

    /**
     * @class
     */
    function CurrencyParser() { }

    /**
     * Parse the supplied string as a number of British pence
     * @param {String} subject - The string to be parsed
     * @returns {Number} The value of the string in pence
     */
    CurrencyParser.prototype.parseString = function(origStr) {
        var str = origStr;

        if (!(typeof str == 'string' || str instanceof String))
            //TBD or throw TypeError? Or just call .toString on it?
            return 0;

        // Trim any trailing 'p' symbol
        str = str.replace(/p$/, '');

        // Trim any leading 0s
        str = str.replace(/^0+/, '');

        // Trim any currency symbol; adjust decimal point if required
        if (/^£[^\.]+$/.test(str))
            str += '.0';
        str = str.replace(/^£/, '');

        // Sanitise trailing point with no following digit
        if (/\.$/.test(str))
            str += '0';

        // Ensure string now contains only digits with at most one decimal point
        if (/^\d+\.\d+$/.test(str))
            // Contains a decimal point, so parse as pounds
            return Math.round(parseFloat(str) * 100);
        else if (/^\d+$/.test(str))
            // Parse as whole pence
            return parseInt(str);
        else
            // Empty, or contains non-numeric characters or multiple points
            return 0;
    };

    // Return constructor
    return CurrencyParser;
});
<|endoftext|>