<commit_before>'use strict'
const updateSuite = getData => () => {
  let data

  before(() => {
    data = getData()
  })

  // The `update` command updates documents already in the database. It
  // errors if the document doesn't exist.
  it('#.update(single_non_existent)', done => {
    data.update({ id: 1, a: 1, b: 1 })
      .subscribe(doneErrorObserver(done))
  })

  // It means you can't update a document without providing an id.
  it("#.update(single_non_id)", done => {
    data.update({ a: 1, b: 1 })
      .subscribe(doneErrorObserver(done))
  });

  // Let's store a document first, then update it.
  it("#.update(single_existing)", done => {
    data.store({ id: 1, a: { b: 1, c: 1 }, d: 1 }).toArray()
      // should return an array with an ID of the inserted document.
      .do(res => assert.deepEqual([ 1 ], res))
      // Let's make sure we get back the document that we put in.
      .flatMap(() => data.find(1).fetch())
      // Check that we get back what we put in.
      .do(res => assert.deepEqual(res, { id: 1, a: { b: 1, c: 1 }, d: 1 }))
      // Let's update the document now
      .flatMap(() => data.update({ id: 1, a: { c: 2 } })).toArray()
      // We should have gotten the ID back again
      .do((res) => assert.deepEqual([ 1 ], res))
      // Make sure `upsert` updated the original document
      .flatMap(() => data.find(1).fetch())
      // Check that the document was updated correctly
      .do(res => assert.deepEqual(res, { id: 1, a: { b: 1, c: 2 }, d: 1 }))
      .subscribe(doneObserver(done))
  })

  // Calling `update` with `null` is an error.
  it("#.update(null)", assertThrows(
    'The argument to update must be non-null',
    () => data.update(null)
  ))

  // Calling `update` with `undefined` is also an error.
  it("#.update(undefined)", assertThrows(
    'The 1st argument to update must be defined',
    () => data.update(undefined)
  ))

  it('fails if no arguments are passed', assertThrows(
    'update must receive exactly 1 argument',
    () => data.update()
  ))

  // The `update` command allows storing multiple documents in one call.
  // Let's update a few documents and make sure we get them back.
  it("#.update(multiple)", done => {
    data.store([
      { id: 1, a: { b: 1, c: 1 }, d: 1 },
      { id: 2, a: { b: 2, c: 2 }, d: 2 },
    ]).toArray()
      // should return an array with an ID of the inserted document.
      .do(res => assert.deepEqual([ 1, 2 ], res))
      // Let's make sure we get back the documents that we put in.
      .flatMap(() => data.findAll(1, 2).fetch({ asCursor: false }))
      // Check that we get back what we put in.
      .do(res => assert.sameDeepMembers(res, [
        { id: 1, a: { b: 1, c: 1 }, d: 1 },
        { id: 2, a: { b: 2, c: 2 }, d: 2 }
      ]))
      // All right. Let's update the documents now
      .flatMap(() => data.update([ { id: 1, a: { c: 2 } }, { id: 2, d: 3 } ]))
      .toArray()
      // We should have gotten the ID back again
      .do(res => assert.deepEqual(res, [ 1, 2 ]))
      // Make sure `update` updated the documents properly
      .flatMap(() => data.findAll(1, 2).fetch({ asCursor: false }))
      // Check that we get back what we put in.
      .do(res => assert.sameDeepMembers(res, [
        { id: 1, a: { b: 1, c: 2 }, d: 1 },
        { id: 2, a: { b: 2, c: 2 }, d: 3 },
      ]))
      .subscribe(doneObserver(done))
  })

  // If any operation in a batch update fails, everything is reported as a
  // failure. Note that we're updating `null` below, and a document with
  // no ID. Both are failures.
  it("#.update(multiple_one_null)", done => {
    data.update([ { id: 1, a: 1 }, null, { a: 1 } ])
      .subscribe(doneErrorObserver(done))
  })

  // Updating an empty batch of documents is ok, and returns an empty
  // array.
  it("#.update(empty_batch)", done => {
    data.update([])
      .do(res => {
        // should return an array with the IDs of the documents in
        // order, including the generated IDS.
        assert.isArray(res)
        assert.lengthOf(res, 0)
      })
      .subscribe(doneObserver(done))
  })
} // Testing `update`
<commit_msg>Verbiage changes<commit_after>'use strict'
const updateSuite = getData => () => {
  let data

  before(() => {
    data = getData()
  })

  // Let's store a document first, then update it.
  it('allows updating an existing document', done => {
    data.store({ id: 1, a: { b: 1, c: 1 }, d: 1 }).toArray()
      // should return an array with an ID of the inserted document.
      .do(res => assert.deepEqual([ 1 ], res))
      // Let's make sure we get back the document that we put in.
      .flatMap(() => data.find(1).fetch())
      // Check that we get back what we put in.
      .do(res => assert.deepEqual(res, { id: 1, a: { b: 1, c: 1 }, d: 1 }))
      // Let's update the document now
      .flatMap(() => data.update({ id: 1, a: { c: 2 } })).toArray()
      // We should have gotten the ID back again
      .do((res) => assert.deepEqual([ 1 ], res))
      // Make sure `upsert` updated the original document
      .flatMap(() => data.find(1).fetch())
      // Check that the document was updated correctly
      .do(res => assert.deepEqual(res, { id: 1, a: { b: 1, c: 2 }, d: 1 }))
      .subscribe(doneObserver(done))
  })

  // The `update` command updates documents already in the database. It
  // errors if the document doesn't exist.
  it(`fails if document doesn't exist`, done => {
    data.update({ id: 1, a: 1, b: 1 })
      .subscribe(doneErrorObserver(done))
  })

  // It means you can't update a document without providing an id.
  it('fails if document has no id provided', done => {
    data.update({ a: 1, b: 1 })
      .subscribe(doneErrorObserver(done))
  })

  // Calling `update` with `null` is an error.
  it('fails if null is passed', assertThrows(
    'The argument to update must be non-null',
    () => data.update(null)
  ))

  // Calling `update` with `undefined` is also an error.
  it('fails if undefined is passed', assertThrows(
    'The 1st argument to update must be defined',
    () => data.update(undefined)
  ))

  it('fails if no arguments are passed', assertThrows(
    'update must receive exactly 1 argument',
    () => data.update()
  ))

  // The `update` command allows storing multiple documents in one call.
  // Let's update a few documents and make sure we get them back.
  it('allows updating multiple documents in one call', done => {
    data.store([
      { id: 1, a: { b: 1, c: 1 }, d: 1 },
      { id: 2, a: { b: 2, c: 2 }, d: 2 },
    ]).toArray()
      // should return an array with an ID of the inserted document.
      .do(res => assert.deepEqual([ 1, 2 ], res))
      // Let's make sure we get back the documents that we put in.
      .flatMap(() => data.findAll(1, 2).fetch({ asCursor: false }))
      // Check that we get back what we put in.
      .do(res => assert.sameDeepMembers(res, [
        { id: 1, a: { b: 1, c: 1 }, d: 1 },
        { id: 2, a: { b: 2, c: 2 }, d: 2 }
      ]))
      // All right. Let's update the documents now
      .flatMap(() => data.update([ { id: 1, a: { c: 2 } }, { id: 2, d: 3 } ]))
      .toArray()
      // We should have gotten the ID back again
      .do(res => assert.deepEqual(res, [ 1, 2 ]))
      // Make sure `update` updated the documents properly
      .flatMap(() => data.findAll(1, 2).fetch({ asCursor: false }))
      // Check that we get back what we put in.
      .do(res => assert.sameDeepMembers(res, [
        { id: 1, a: { b: 1, c: 2 }, d: 1 },
        { id: 2, a: { b: 2, c: 2 }, d: 3 },
      ]))
      .subscribe(doneObserver(done))
  })

  // If any operation in a batch update fails, everything is reported as a
  // failure. Note that we're updating `null` below, and a document with
  // no ID. Both are failures.
  it('fails if any document in a batch fails to update', done => {
    data.update([ { id: 1, a: 1 }, null, { a: 1 } ])
      .subscribe(doneErrorObserver(done))
  })

  // Updating an empty batch of documents is ok, and returns an empty
  // array.
  it('allows updating an empty batch', done => {
    data.update([])
      .do(res => {
        // should return an array with the IDs of the documents in
        // order, including the generated IDS.
        assert.isArray(res)
        assert.lengthOf(res, 0)
      })
      .subscribe(doneObserver(done))
  })
} // Testing `update`
<|endoftext|>


<commit_before>import styled from 'styled-components';
import shortId from '@quarterto/short-id';

const bleed = `__bleed_${shortId()}`;

export const Bleed = styled.div.attrs({className: bleed})`
	grid-column: bleed;
`;

export default styled.div`
	display: grid;
	width: 100%;

	grid-template-columns: [bleed-start] 1fr [content-start] minmax(auto, 60em) [content-end] 1fr [bleed-end];
	grid-gap: 1em;

	> :not(.${bleed}) {
		grid-column: content;
	}
`;
<commit_msg>more grid lines<commit_after>import styled from 'styled-components';
import shortId from '@quarterto/short-id';

const bleed = `__bleed_${shortId()}`;

export const Bleed = styled.div.attrs({className: bleed})`
	grid-column: bleed;
`;

export default styled.div`
	display: grid;
	width: 100%;

	grid-template-columns: [bleed-start] 1fr [full-start left-start left-two-start] minmax(auto, 15em) [left-end center-start right-two-start]  minmax(auto, 30em) [center-end left-two-end right-start] minmax(auto, 15em) [full-end right-end right-two-end] 1fr [bleed-end];
	grid-gap: 1em;

	> :not(.${bleed}) {
		grid-column: full;
	}
`;
<|endoftext|>


<commit_before>//! annyang
//! version : 0.2.0
//! author  : Tal Ater @TalAter
//! license : MIT
//! https://www.TalAter.com/annyang/
(function () {
  "use strict";

  var root = this;
  var SpeechRecognition = root.webkitSpeechRecognition ||
                          root.mozSpeechRecognition ||
                          root.msSpeechRecognition ||
                          root.oSpeechRecognition ||
                          root.SpeechRecognition;
  // Check browser support
  // This is done as early as possible, to make it as fast as possible for unsupported browsers
  if ( !SpeechRecognition ) {
    root.annyang = null;
    return null;
  }

  var commandsList;
  var recognition;
  var lang = 'en-US';
  var callbacks = { start: [], error: [], end: [], result: [], resultMatch: [], resultNoMatch: [], errorNetwork: [], errorPermissionBlocked: [], errorPermissionDenied: [] };
  var autoRestart;
  var lastStartedAt = 0;
  var debugState = false;
  var debugStyle = 'font-weight: bold; color: #00f;';

  // The command matching code is a modified version of Backbone.Router by Jeremy Ashkenas, under the MIT license.
  var optionalParam = /\s*\((.*?)\)\s*/g;
  var optionalRegex = /(\(\?:[^)]+\))\?/g;
  var namedParam    = /(\(\?)?:\w+/g;
  var splatParam    = /\*\w+/g;
  var escapeRegExp  = /[\-{}\[\]+?.,\\\^$|#]/g;
  var commandToRegExp = function(command) {
    command = command.replace(escapeRegExp, '\\$&')
                  .replace(optionalParam, '(?:$1)?')
                  .replace(namedParam, function(match, optional) {
                    return optional ? match : '([^\\s]+)';
                  })
                  .replace(splatParam, '(.*?)')
                  .replace(optionalRegex, '\\s*$1?\\s*');
    return new RegExp('^' + command + '$', 'i');
  };

  var invokeCallbacks = function(callbacks) {
    for (var j = 0, l = callbacks.length; j < l; j++) {
      callbacks[j].apply(this);
    }
  };

  root.annyang = {
    init: function(commands) {

      // Abort previous instances of recognition already running
      if (recognition && recognition.abort) {
        recognition.abort();
      }

      // initiate
      recognition = new SpeechRecognition();

      recognition.maxAlternatives = 5;
      recognition.continuous = true;
      recognition.lang = lang;

      recognition.onstart   = function()      { invokeCallbacks(callbacks.start); };

      recognition.onerror   = function(event) {
        invokeCallbacks(callbacks.error);
        switch (event.error) {
        case 'network':
          invokeCallbacks(callbacks.errorNetwork);
          break;
        case 'not-allowed':
        case 'service-not-allowed':
          autoRestart = false;
          if (new Date().getTime()-lastStartedAt < 200) {
            invokeCallbacks(callbacks.errorPermissionBlocked);
          } else {
            invokeCallbacks(callbacks.errorPermissionDenied);
          }
          break;
        }
      };

      recognition.onend     = function() {
        invokeCallbacks(callbacks.end);
        // annyang will auto restart if it is closed automatically and not by user action.
        if (autoRestart) {
          // make sure never to restart annyang automatically more than once per second
          var timeSinceLastStart = new Date().getTime()-lastStartedAt;
          if (timeSinceLastStart < 1000) {
            setTimeout(root.annyang.start, 1000-timeSinceLastStart);
          } else {
            root.annyang.start();
          }
        }
      };

      recognition.onresult  = function(event) {
        invokeCallbacks(callbacks.result);
        var results = event.results[event.resultIndex];
        var commandText;
        for (var i = 0; i<results.length; i++) {
          commandText = results[i].transcript.trim();
          if (debugState) {
            root.console.log('Speech recognized: %c'+commandText, debugStyle);
          }

          for (var j = 0, l = commandsList.length; j < l; j++) {
            var result = commandsList[j].command.exec(commandText);
            if (result) {
              var parameters = result.slice(1);
              if (debugState) {
                root.console.log('command matched: %c'+commandsList[j].originalPhrase, debugStyle);
                if (parameters.length) {
                  root.console.log('with parameters', parameters);
                }
              }
              commandsList[j].callback.apply(this, parameters);
              invokeCallbacks(callbacks.resultMatch);
              return true;
            }
          }
        }
        invokeCallbacks(callbacks.resultNoMatch);
        return false;
      };

      // build commands list
      commandsList = [];
      this.addCommands(commands);
    },

    start: function(options) {
      options = options || {};
      if (options.autoRestart !== void 0) {
        autoRestart = !!options.autoRestart;
      } else {
        autoRestart = true;
      }
      lastStartedAt = new Date().getTime();
      recognition.start();
    },

    abort: function() {
      autoRestart = false;
      recognition.abort();
    },

    debug: function(newState) {
      if (arguments.length > 0) {
        debugState = !!newState;
      } else {
        debugState = true;
      }
    },

    setLanguage: function(language) {
      lang = language;
      if (recognition && recognition.abort) {
        recognition.lang = language;
      }
    },

    addCommands: function(commands) {
      var cb,
          command;
      for (var phrase in commands) {
        if (commands.hasOwnProperty(phrase)) {
          cb = root[commands[phrase]] || commands[phrase];
          if (typeof cb !== 'function') {
            continue;
          }
          //convert command to regex
          command = commandToRegExp(phrase);

          commandsList.push({ command: command, callback: cb, originalPhrase: phrase });
        }
      }
      if (debugState) {
        root.console.log('Commands successfully loaded: %c'+commandsList.length, debugStyle);
      }
    },

    /**
     * Lets the user add a callback of one of 9 types: start, error, end, result, resultMatch, resultNoMatch, errorNetwork, errorPermissionBlocked, errorPermissionDenied
     */
    addCallback: function(type, callback) {
      if (callbacks[type]  === void 0) {
        return;
      }
      var cb = root[callback] || callback;
      if (typeof cb !== 'function') {
        return;
      }
      callbacks[type].push(cb);
    }
  };

}).call(this);
<commit_msg>Added many comments<commit_after>//! annyang
//! version : 0.2.0
//! author  : Tal Ater @TalAter
//! license : MIT
//! https://www.TalAter.com/annyang/
(function () {
  "use strict";

  // Save a reference to the global object (window in the browser)
  var root = this;

  // Get the SpeechRecognition object, while handling browser prefixes
  var SpeechRecognition = root.webkitSpeechRecognition ||
                          root.mozSpeechRecognition ||
                          root.msSpeechRecognition ||
                          root.oSpeechRecognition ||
                          root.SpeechRecognition;

  // Check browser support
  // This is done as early as possible, to make it as fast as possible for unsupported browsers
  if ( !SpeechRecognition ) {
    root.annyang = null;
    return null;
  }

  var commandsList;
  var recognition;
  var lang = 'en-US';
  var callbacks = { start: [], error: [], end: [], result: [], resultMatch: [], resultNoMatch: [], errorNetwork: [], errorPermissionBlocked: [], errorPermissionDenied: [] };
  var autoRestart;
  var lastStartedAt = 0;
  var debugState = false;
  var debugStyle = 'font-weight: bold; color: #00f;';

  // The command matching code is a modified version of Backbone.Router by Jeremy Ashkenas, under the MIT license.
  var optionalParam = /\s*\((.*?)\)\s*/g;
  var optionalRegex = /(\(\?:[^)]+\))\?/g;
  var namedParam    = /(\(\?)?:\w+/g;
  var splatParam    = /\*\w+/g;
  var escapeRegExp  = /[\-{}\[\]+?.,\\\^$|#]/g;
  var commandToRegExp = function(command) {
    command = command.replace(escapeRegExp, '\\$&')
                  .replace(optionalParam, '(?:$1)?')
                  .replace(namedParam, function(match, optional) {
                    return optional ? match : '([^\\s]+)';
                  })
                  .replace(splatParam, '(.*?)')
                  .replace(optionalRegex, '\\s*$1?\\s*');
    return new RegExp('^' + command + '$', 'i');
  };

  // This method receives an array of callbacks to iterate over, and invokes each of them
  var invokeCallbacks = function(callbacks) {
    for (var j = 0, l = callbacks.length; j < l; j++) {
      callbacks[j].apply(this);
    }
  };

  root.annyang = {
    // Initialize annyang with a list of commands to recognize.
    // e.g. annyang.init({'hello :name': helloFunction})
    // annyang understands commands with named variables, splats, and optional words.
    init: function(commands) {

      // Abort previous instances of recognition already running
      if (recognition && recognition.abort) {
        recognition.abort();
      }

      // initiate SpeechRecognition
      recognition = new SpeechRecognition();

      // Set the max number of alternative transcripts to try and match with a command
      recognition.maxAlternatives = 5;
      recognition.continuous = true;
      // Sets the language to the default 'en-US'. This can be changed with annyang.setLanguage()
      recognition.lang = lang;

      recognition.onstart   = function()      { invokeCallbacks(callbacks.start); };

      recognition.onerror   = function(event) {
        invokeCallbacks(callbacks.error);
        switch (event.error) {
        case 'network':
          invokeCallbacks(callbacks.errorNetwork);
          break;
        case 'not-allowed':
        case 'service-not-allowed':
          // if permission to use the mic is denied, turn off auto-restart
          autoRestart = false;
          // determine if permission was denied by user or automatically.
          if (new Date().getTime()-lastStartedAt < 200) {
            invokeCallbacks(callbacks.errorPermissionBlocked);
          } else {
            invokeCallbacks(callbacks.errorPermissionDenied);
          }
          break;
        }
      };

      recognition.onend     = function() {
        invokeCallbacks(callbacks.end);
        // annyang will auto restart if it is closed automatically and not by user action.
        if (autoRestart) {
          // play nicely with the browser, and never restart annyang automatically more than once per second
          var timeSinceLastStart = new Date().getTime()-lastStartedAt;
          if (timeSinceLastStart < 1000) {
            setTimeout(root.annyang.start, 1000-timeSinceLastStart);
          } else {
            root.annyang.start();
          }
        }
      };

      recognition.onresult  = function(event) {
        invokeCallbacks(callbacks.result);
        var results = event.results[event.resultIndex];
        var commandText;
        // go over each of the 5 results and alternative results received (we've set maxAlternatives to 5 above)
        for (var i = 0; i<results.length; i++) {
          // the text recognized
          commandText = results[i].transcript.trim();
          if (debugState) {
            root.console.log('Speech recognized: %c'+commandText, debugStyle);
          }

          // try and match recognized text to one of the commands on the list
          for (var j = 0, l = commandsList.length; j < l; j++) {
            var result = commandsList[j].command.exec(commandText);
            if (result) {
              var parameters = result.slice(1);
              if (debugState) {
                root.console.log('command matched: %c'+commandsList[j].originalPhrase, debugStyle);
                if (parameters.length) {
                  root.console.log('with parameters', parameters);
                }
              }
              // execute the matched command
              commandsList[j].callback.apply(this, parameters);
              invokeCallbacks(callbacks.resultMatch);
              return true;
            }
          }
        }
        invokeCallbacks(callbacks.resultNoMatch);
        return false;
      };

      // build commands list
      commandsList = [];
      this.addCommands(commands);
    },

    // Start listening (asking for permission first, if needed).
    // Call this after you've initialized annyang with commands.
    // Receives an optional options object:
    // { autoRestart: true }
    start: function(options) {
      options = options || {};
      if (options.autoRestart !== void 0) {
        autoRestart = !!options.autoRestart;
      } else {
        autoRestart = true;
      }
      lastStartedAt = new Date().getTime();
      recognition.start();
    },

    // abort the listening session (aka stop)
    abort: function() {
      autoRestart = false;
      recognition.abort();
    },

    // Turn on output of debug messages to the console. Ugly, but super-handy!
    debug: function(newState) {
      if (arguments.length > 0) {
        debugState = !!newState;
      } else {
        debugState = true;
      }
    },

    // Set the language the user will speak in. If not called, defaults to 'en-US'.
    // e.g. 'fr-FR' (French-France), 'es-CR' (Español-Costa Rica)
    setLanguage: function(language) {
      lang = language;
      if (recognition && recognition.abort) {
        recognition.lang = language;
      }
    },

    // Add additional commands that annyang will respond to. Similar in syntax to annyang.init()
    addCommands: function(commands) {
      var cb,
          command;
      for (var phrase in commands) {
        if (commands.hasOwnProperty(phrase)) {
          cb = root[commands[phrase]] || commands[phrase];
          if (typeof cb !== 'function') {
            continue;
          }
          //convert command to regex
          command = commandToRegExp(phrase);

          commandsList.push({ command: command, callback: cb, originalPhrase: phrase });
        }
      }
      if (debugState) {
        root.console.log('Commands successfully loaded: %c'+commandsList.length, debugStyle);
      }
    },

    // Lets the user add a callback of one of 9 types:
    // start, error, end, result, resultMatch, resultNoMatch, errorNetwork, errorPermissionBlocked, errorPermissionDenied
    addCallback: function(type, callback) {
      if (callbacks[type]  === void 0) {
        return;
      }
      var cb = root[callback] || callback;
      if (typeof cb !== 'function') {
        return;
      }
      callbacks[type].push(cb);
    }
  };

}).call(this);
<|endoftext|>


<commit_before>import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, waitUntil } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';

module('Integration | Modifier | animate-loading', function (hooks) {
  setupRenderingTest(hooks);

  test('it renders defaults', async function (assert) {
    await render(hbs`<div {{animate-loading}}></div>`);
    assert.dom('div').hasStyle({
      opacity: '0.1',
    });

    await waitUntil(
      () => getComputedStyle(this.element.querySelector('div'), null).opacity === '1',
      { timeout: 2000 }
    );
    assert.dom('div').hasStyle({
      opacity: '1',
      transition: 'opacity 1s linear 0s',
    });
  });

  test('it renders options', async function (assert) {
    await render(hbs`<div
      {{animate-loading initialOpacity=".3" finalOpacity="0.6" loadingTime=500}}>
    </div>`);
    assert.dom('div').hasStyle({
      opacity: '0.3',
    });

    await waitUntil(
      () => getComputedStyle(this.element.querySelector('div'), null).opacity === '0.6'
    );
    assert.dom('div').hasStyle({
      opacity: '0.6',
      transition: 'opacity 0.5s linear 0s',
    });
  });

  test('it works with tracker service', async function (assert) {
    const tracker = this.owner.lookup('service:loading-opacity-tracker');
    tracker.set('someKey', '0.23');
    await render(hbs`<div
      {{animate-loading "someKey"}}>
    </div>`);
    assert.dom('div').hasStyle({
      opacity: '0.23',
    });

    await waitUntil(
      () => getComputedStyle(this.element.querySelector('div'), null).opacity === '1'
    );
    assert.dom('div').hasStyle({
      opacity: '1',
      transition: 'opacity 1s linear 0s',
    });
    await render(hbs`<div></div>`);
    assert.strictEqual(tracker.get('someKey'), '1');
  });
});
<commit_msg>Add more timeout for flakey test<commit_after>import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, waitUntil } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';

module('Integration | Modifier | animate-loading', function (hooks) {
  setupRenderingTest(hooks);

  test('it renders defaults', async function (assert) {
    await render(hbs`<div {{animate-loading}}></div>`);
    assert.dom('div').hasStyle({
      opacity: '0.1',
    });

    await waitUntil(
      () => getComputedStyle(this.element.querySelector('div'), null).opacity === '1',
      { timeout: 5000 }
    );
    assert.dom('div').hasStyle({
      opacity: '1',
      transition: 'opacity 1s linear 0s',
    });
  });

  test('it renders options', async function (assert) {
    await render(hbs`<div
      {{animate-loading initialOpacity=".3" finalOpacity="0.6" loadingTime=500}}>
    </div>`);
    assert.dom('div').hasStyle({
      opacity: '0.3',
    });

    await waitUntil(
      () => getComputedStyle(this.element.querySelector('div'), null).opacity === '0.6'
    );
    assert.dom('div').hasStyle({
      opacity: '0.6',
      transition: 'opacity 0.5s linear 0s',
    });
  });

  test('it works with tracker service', async function (assert) {
    const tracker = this.owner.lookup('service:loading-opacity-tracker');
    tracker.set('someKey', '0.23');
    await render(hbs`<div
      {{animate-loading "someKey"}}>
    </div>`);
    assert.dom('div').hasStyle({
      opacity: '0.23',
    });

    await waitUntil(
      () => getComputedStyle(this.element.querySelector('div'), null).opacity === '1'
    );
    assert.dom('div').hasStyle({
      opacity: '1',
      transition: 'opacity 1s linear 0s',
    });
    await render(hbs`<div></div>`);
    assert.strictEqual(tracker.get('someKey'), '1');
  });
});
<|endoftext|>


<commit_before>var $ = require('jquery')
var cookie = require('js-cookie')

$(function () {
  $.ajaxSetup({
    headers: { 'X-CSRFToken': cookie.get('csrftoken') }
  })
})

var baseURL = '/api/'

var api = (function () {
  var urls = {
    report: baseURL + 'reports/',
    document: baseURL + 'modules/$moduleId/documents/',
    follow: baseURL + 'follows/',
    comment: baseURL + 'contenttypes/$contentTypeId/objects/$objectPk/comments/',
    rating: baseURL + 'contenttypes/$contentTypeId/objects/$objectPk/ratings/'
  }

  function _sendRequest (endpoint, id, options, data, contentType) {
    var $body = $('body')

    if (typeof id === 'object') {
      // there's no id, switch parameters
      data = options
      options = id
      id = null
    }

    var url = urls[endpoint]
    if (data.urlReplaces) {
      url = url.replace(/\$(\w+?)\b/g, (match, group) => {
        return data.urlReplaces[group]
      })
      data = $.extend({}, data)
      delete data.urlReplaces
    }

    if (typeof id === 'number' || typeof id === 'string') {
      url = url + id + '/'
    }
    var defaultParams = {
      url: url,
      dataType: 'json',
      data: data,
      error: function (xhr, status, err) {
        console.error(url, status, err.toString())
      },
      complete: function () {
        $body.removeClass('loading')
      }
    }
    var params = $.extend(defaultParams, options)

    if (typeof params.data !== 'undefined') {
      if (params.type === 'PUT' || params.type === 'POST' ||
          params.type === 'PATCH'
      ) {
        params.contentType = 'application/json; charset=utf-8'
        params.data = JSON.stringify(params.data)
      }
    }

    $body.addClass('loading')
    return $.ajax(params)
  }

  return {
    comments: {
      add: function (data) {
        return _sendRequest('comment', {
          type: 'POST'
        }, data)
      },

      change: function (data, id) {
        return _sendRequest('comment', id, {
          type: 'PATCH'
        }, data)
      },

      delete: function (data, id) {
        return _sendRequest('comment', id, {
          type: 'DELETE'
        }, data)
      }
    },
    rating: {
      add: function (data) {
        return _sendRequest('rating', {
          type: 'POST'
        }, data)
      },
      change: function (data, id) {
        return _sendRequest('rating', id, {
          type: 'PATCH'
        }, data)
      }
    },
    report: {
      submit: function (data) {
        return _sendRequest('report', {
          type: 'POST'
        }, data)
      }
    },
    document: {
      add: function (data) {
        return _sendRequest('document', {
          type: 'POST'
        }, data)
      },
      change: function (data, id) {
        return _sendRequest('document', id, {
          type: 'PUT'
        }, data)
      }
    },
    follow: {
      get: function (slug) {
        return _sendRequest('follow', slug, {
          type: 'GET'
        }, {})
      },
      change: function (data, slug) {
        return _sendRequest('follow', slug, {
          type: 'PUT'
        }, data)
      }
    }
  }
}())
module.exports = api
<commit_msg>Add JS api for polls in meinberlin<commit_after>var $ = require('jquery')
var cookie = require('js-cookie')

$(function () {
  $.ajaxSetup({
    headers: { 'X-CSRFToken': cookie.get('csrftoken') }
  })
})

var baseURL = '/api/'

var api = (function () {
  var urls = {
    report: baseURL + 'reports/',
    document: baseURL + 'modules/$moduleId/documents/',
    poll: baseURL + 'polls/',
    pollvote: baseURL + 'pollvotes/',
    follow: baseURL + 'follows/',
    comment: baseURL + 'contenttypes/$contentTypeId/objects/$objectPk/comments/',
    rating: baseURL + 'contenttypes/$contentTypeId/objects/$objectPk/ratings/'
  }

  function _sendRequest (endpoint, id, options, data, contentType) {
    var $body = $('body')

    if (typeof id === 'object') {
      // there's no id, switch parameters
      data = options
      options = id
      id = null
    }

    var url = urls[endpoint]
    if (data.urlReplaces) {
      url = url.replace(/\$(\w+?)\b/g, (match, group) => {
        return data.urlReplaces[group]
      })
      data = $.extend({}, data)
      delete data.urlReplaces
    }

    if (typeof id === 'number' || typeof id === 'string') {
      url = url + id + '/'
    }
    var defaultParams = {
      url: url,
      dataType: 'json',
      data: data,
      error: function (xhr, status, err) {
        console.error(url, status, err.toString())
      },
      complete: function () {
        $body.removeClass('loading')
      }
    }
    var params = $.extend(defaultParams, options)

    if (typeof params.data !== 'undefined') {
      if (params.type === 'PUT' || params.type === 'POST' ||
          params.type === 'PATCH'
      ) {
        params.contentType = 'application/json; charset=utf-8'
        params.data = JSON.stringify(params.data)
      }
    }

    $body.addClass('loading')
    return $.ajax(params)
  }

  return {
    comments: {
      add: function (data) {
        return _sendRequest('comment', {
          type: 'POST'
        }, data)
      },

      change: function (data, id) {
        return _sendRequest('comment', id, {
          type: 'PATCH'
        }, data)
      },

      delete: function (data, id) {
        return _sendRequest('comment', id, {
          type: 'DELETE'
        }, data)
      }
    },
    rating: {
      add: function (data) {
        return _sendRequest('rating', {
          type: 'POST'
        }, data)
      },
      change: function (data, id) {
        return _sendRequest('rating', id, {
          type: 'PATCH'
        }, data)
      }
    },
    report: {
      submit: function (data) {
        return _sendRequest('report', {
          type: 'POST'
        }, data)
      }
    },
    document: {
      add: function (data) {
        return _sendRequest('document', {
          type: 'POST'
        }, data)
      },
      change: function (data, id) {
        return _sendRequest('document', id, {
          type: 'PUT'
        }, data)
      }
    },
    follow: {
      get: function (slug) {
        return _sendRequest('follow', slug, {
          type: 'GET'
        }, {})
      },
      change: function (data, slug) {
        return _sendRequest('follow', slug, {
          type: 'PUT'
        }, data)
      }
    },
    poll: {
      change: function (data, id) {
        return _sendRequest('poll', id, {
          type: 'PUT'
        }, data)
      },
      vote: function (data, questionId) {
        return _sendRequest('pollvote', questionId, {
          type: 'PUT'
        }, data)
      }
    }
  }
}())
module.exports = api
<|endoftext|>


<commit_before>/*
 * Collapsible widget extension: menu behavior
 * https://github.com/filamentgroup/collapsible
 * Copyright (c) 2014 Filament Group, Inc.
 * Licensed under the MIT, GPL licenses.
 */

;(function ($, window, document, undefined) {

	$( document ).bind( "init", function( e ){
		var pluginName = "collapsible";
		if( $( e.target ).is( "." + pluginName ) ){
			var $collapsible = $( e.target );
			var self = $collapsible.data( pluginName );
			var $trigger = $collapsible.prev().filter( "." + pluginName + "-trigger" );
			var triggerExpand = pluginName + "-trigger-expand";

			// menu behavior (clickout, and optionally hover)
			var isMenu = function(){
				return $collapsible.children( "." + pluginName + "-content" ).css( "position" ) === "absolute" || $collapsible.is( "[data-collapsible-ismenu]" );
			};

			// tapout/clickout behavior
			var targetTop;
			var touchCancel = false;
			$( "body" )
				.bind( "gesturestart." + pluginName, function(){
					touchCancel = true;
				})
				.bind( "touchstart." + pluginName, function( a ){
					targetTop = a.target.getBoundingClientRect().top;
				})
				.bind( "touchend." + pluginName + " click." + pluginName, function( a ){
					if( a.type === "touchend" ){
						if( targetTop && Math.abs( targetTop - a.target.getBoundingClientRect().top ) > 5 ){
							touchCancel = true;
						}
					}

					// if the event target is not in the collapsible, and the collapsible is expanded, and it's a menu presentation... collapse it!
					if( !$( a.target ).closest( e.target ).length &&
						!$( a.target ).closest( $trigger ).length &&
						!$collapsible.data( pluginName ).collapsed &&
						isMenu()  &&
						touchCancel === false ){

						setTimeout(function(){
							$collapsible.data( pluginName ).collapse();
						});
						if( $( a.target ).closest( "a,select,input,textarea,button,label" ).length && !$( a.target ).closest( "." + pluginName + "-header" ).length ){
							a.preventDefault();
						}
					}
					setTimeout( function(){
						targetTop = null;
						touchCancel = false;
					}, 200 );
				} );

			// hover behavior for collapsibles and triggers relies on the presence of data-collapsible-hover attr
			// the exclusive hover behavior requires a value of "exclusive"
			var startedByTouch = false;
			var hover = $collapsible.is( "[data-collapsible-hover]" );
			var exclusiveHover = $collapsible.is( "[data-collapsible-hover='exclusive']" );

			if( hover ){
				if( exclusiveHover ){
					self.header.unbind( "click tap" );
				}

				$collapsible
					.add( $trigger )
					.bind( "touchstart pointerdown", function(){
						// ignore hovers that begin with touch
						if( exclusiveHover ){
							startedByTouch = true;
						}
					} )
					// mouseover covers child collapsibles in a more friendly way than mouseleave
					.bind( "mouseover." + pluginName, function( e ){
						if( !startedByTouch && isMenu() && $( e.target ).closest( self.header ).length ){
							$collapsible.data( pluginName ).expand();
						}
					} )
					.bind( "mouseleave." + pluginName, function(){
						if( !startedByTouch && isMenu() && $( e.target ).is( $collapsible ) ){
							$collapsible.data( pluginName ).collapse();
						}
					} );

				$collapsible
					.bind( "expand", function(){
						$trigger.addClass( triggerExpand );
					} )
					.bind( "collapse", function(){
						$trigger.removeClass( triggerExpand );
					} );
			}

			// add keyboard/arrow handling
			// arrow method handles the arrow key navigation, which largely maps to the tab key within the component
			self.arrow = function( target, back ){

				// find all focusables in this collapsible content area
				var $focusables = this.content.find( this.focusable );
				// nextTab will be the next element receiving focus from this arrow keydown
				var nextTab;

				// if the keydown target is the header and it's down or right, focus on the first focusable
				if( $( target ).is( this.header ) && !back ){
					nextTab = $focusables[ 0 ];
				}
				else {
					// if it's a backward arrow, reverse the array
					if( back ){
						// this if can go away once https://github.com/filamentgroup/shoestring/issues/80 is fixed
						// check if already an array (shoestring with bug above)
						if( $focusables.reverse ){
							$focusables = $focusables.reverse();
						}
						// otherwisejquery will need a get()
						else {
							$focusables = $( $focusables.get().reverse() );
						}
					}
					// afterTarget becomes true once the target has been passed in the each loop
					var afterTarget = false;
					// loop focusables
					$focusables.each(function(){
						// if nextTab isn't defined yet, we're after the target in the loop, and the target appears to be displayed
						// NOTE: the offset checks replaced the following, which tied keyboard behavior to aria state:
							// !$( this ).closest( ".collapsible-collapsed .collapsible-content" ).length
							// unfortunately, we sometimes display visually elements that are still aria-hidden.
							// The current check caters to sighted keyboard users over non-sighted keyboard users. TODO: figure this out.
						if( !nextTab && afterTarget && this.offsetHeight > 0 && this.offsetLeft > -1 ){
							nextTab = this;
						}
						// try to set afterTarget if not already set
						if( !afterTarget ) {
							afterTarget = $( this ).is( target );
						}
					});
				}

				// if we have a next element to send focus to at this point, do that. Otherwise, focus back on header
				if( nextTab ){
					nextTab.focus();
				}
				else {
					// no nextTab? focus back to header
					this.header[ 0 ].focus();
				}
			};

			// bind keydown handlers.
			// arrow key handling applies to the entire collapsible
			self.element
				.bind( "keydown." + pluginName, function( e ){
					// arrow key behavior: collapsible must be expanded to accept arrow navigation
					if( !self.collapsed && isMenu() ){
						if( e.which === 39 || e.which === 40 ){
							self.arrow( e.target );
							e.preventDefault();
						}
						else if( e.which === 37 || e.which === 38 ){
							self.arrow( e.target, true );
							e.preventDefault();
						}
					}

				});

		}
	} );

})(jQuery, window, document);
<commit_msg>hover logic should always avoid mouseover event stacks that start with touch events.<commit_after>/*
 * Collapsible widget extension: menu behavior
 * https://github.com/filamentgroup/collapsible
 * Copyright (c) 2014 Filament Group, Inc.
 * Licensed under the MIT, GPL licenses.
 */

;(function ($, window, document, undefined) {

	$( document ).bind( "init", function( e ){
		var pluginName = "collapsible";
		if( $( e.target ).is( "." + pluginName ) ){
			var $collapsible = $( e.target );
			var self = $collapsible.data( pluginName );
			var $trigger = $collapsible.prev().filter( "." + pluginName + "-trigger" );
			var triggerExpand = pluginName + "-trigger-expand";

			// menu behavior (clickout, and optionally hover)
			var isMenu = function(){
				return $collapsible.children( "." + pluginName + "-content" ).css( "position" ) === "absolute" || $collapsible.is( "[data-collapsible-ismenu]" );
			};

			// tapout/clickout behavior
			var targetTop;
			var touchCancel = false;
			$( "body" )
				.bind( "gesturestart." + pluginName, function(){
					touchCancel = true;
				})
				.bind( "touchstart." + pluginName, function( a ){
					targetTop = a.target.getBoundingClientRect().top;
				})
				.bind( "touchend." + pluginName + " click." + pluginName, function( a ){
					if( a.type === "touchend" ){
						if( targetTop && Math.abs( targetTop - a.target.getBoundingClientRect().top ) > 5 ){
							touchCancel = true;
						}
					}

					// if the event target is not in the collapsible, and the collapsible is expanded, and it's a menu presentation... collapse it!
					if( !$( a.target ).closest( e.target ).length &&
						!$( a.target ).closest( $trigger ).length &&
						!$collapsible.data( pluginName ).collapsed &&
						isMenu()  &&
						touchCancel === false ){

						setTimeout(function(){
							$collapsible.data( pluginName ).collapse();
						});
						if( $( a.target ).closest( "a,select,input,textarea,button,label" ).length && !$( a.target ).closest( "." + pluginName + "-header" ).length ){
							a.preventDefault();
						}
					}
					setTimeout( function(){
						targetTop = null;
						touchCancel = false;
					}, 200 );
				} );

			// hover behavior for collapsibles and triggers relies on the presence of data-collapsible-hover attr
			// the exclusive hover behavior requires a value of "exclusive"
			var startedByTouch = false;
			var hover = $collapsible.is( "[data-collapsible-hover]" );
			var exclusiveHover = $collapsible.is( "[data-collapsible-hover='exclusive']" );

			if( hover ){
				if( exclusiveHover ){
					self.header.unbind( "click tap" );
				}

				$collapsible
					.add( $trigger )
					.bind( "touchstart pointerdown", function(){
						// ignore hovers that begin with touch
						startedByTouch = true;
					} )
					// mouseover covers child collapsibles in a more friendly way than mouseleave
					.bind( "mouseover." + pluginName, function( e ){
						if( !startedByTouch && isMenu() && $( e.target ).closest( self.header ).length ){
							$collapsible.data( pluginName ).expand();
						}
					} )
					.bind( "mouseleave." + pluginName, function(){
						if( !startedByTouch && isMenu() && $( e.target ).is( $collapsible ) ){
							$collapsible.data( pluginName ).collapse();
						}
					} );

				$collapsible
					.bind( "expand", function(){
						$trigger.addClass( triggerExpand );
					} )
					.bind( "collapse", function(){
						$trigger.removeClass( triggerExpand );
					} );
			}

			// add keyboard/arrow handling
			// arrow method handles the arrow key navigation, which largely maps to the tab key within the component
			self.arrow = function( target, back ){

				// find all focusables in this collapsible content area
				var $focusables = this.content.find( this.focusable );
				// nextTab will be the next element receiving focus from this arrow keydown
				var nextTab;

				// if the keydown target is the header and it's down or right, focus on the first focusable
				if( $( target ).is( this.header ) && !back ){
					nextTab = $focusables[ 0 ];
				}
				else {
					// if it's a backward arrow, reverse the array
					if( back ){
						// this if can go away once https://github.com/filamentgroup/shoestring/issues/80 is fixed
						// check if already an array (shoestring with bug above)
						if( $focusables.reverse ){
							$focusables = $focusables.reverse();
						}
						// otherwisejquery will need a get()
						else {
							$focusables = $( $focusables.get().reverse() );
						}
					}
					// afterTarget becomes true once the target has been passed in the each loop
					var afterTarget = false;
					// loop focusables
					$focusables.each(function(){
						// if nextTab isn't defined yet, we're after the target in the loop, and the target appears to be displayed
						// NOTE: the offset checks replaced the following, which tied keyboard behavior to aria state:
							// !$( this ).closest( ".collapsible-collapsed .collapsible-content" ).length
							// unfortunately, we sometimes display visually elements that are still aria-hidden.
							// The current check caters to sighted keyboard users over non-sighted keyboard users. TODO: figure this out.
						if( !nextTab && afterTarget && this.offsetHeight > 0 && this.offsetLeft > -1 ){
							nextTab = this;
						}
						// try to set afterTarget if not already set
						if( !afterTarget ) {
							afterTarget = $( this ).is( target );
						}
					});
				}

				// if we have a next element to send focus to at this point, do that. Otherwise, focus back on header
				if( nextTab ){
					nextTab.focus();
				}
				else {
					// no nextTab? focus back to header
					this.header[ 0 ].focus();
				}
			};

			// bind keydown handlers.
			// arrow key handling applies to the entire collapsible
			self.element
				.bind( "keydown." + pluginName, function( e ){
					// arrow key behavior: collapsible must be expanded to accept arrow navigation
					if( !self.collapsed && isMenu() ){
						if( e.which === 39 || e.which === 40 ){
							self.arrow( e.target );
							e.preventDefault();
						}
						else if( e.which === 37 || e.which === 38 ){
							self.arrow( e.target, true );
							e.preventDefault();
						}
					}

				});

		}
	} );

})(jQuery, window, document);
<|endoftext|>


<commit_before>'use strict';

var is = require( '../is' );

var elesfn = ({

  // fully updates (recalculates) the style for the elements
  updateStyle: function( notifyRenderer ){
    var cy = this._private.cy;

    if( !cy.styleEnabled() ){ return this; }

    if( cy._private.batchingStyle ){
      var bEles = cy._private.batchStyleEles;

      bEles.merge( this );

      return this; // chaining and exit early when batching
    }

    var style = cy.style();
    notifyRenderer = notifyRenderer || notifyRenderer === undefined ? true : false;

    style.apply( this );

    var updatedCompounds = this.updateCompoundBounds();
    var toNotify = updatedCompounds.length > 0 ? this.add( updatedCompounds ) : this;

    if( notifyRenderer ){
      toNotify.rtrigger( 'style' ); // let renderer know we changed style
    } else {
      toNotify.trigger( 'style' ); // just fire the event
    }
    return this; // chaining
  },

  // just update the mappers in the elements' styles; cheaper than eles.updateStyle()
  updateMappers: function( notifyRenderer ){
    var cy = this._private.cy;
    var style = cy.style();
    notifyRenderer = notifyRenderer || notifyRenderer === undefined ? true : false;

    if( !cy.styleEnabled() ){ return this; }

    style.updateMappers( this );

    var updatedCompounds = this.updateCompoundBounds();
    var toNotify = updatedCompounds.length > 0 ? this.add( updatedCompounds ) : this;

    if( notifyRenderer ){
      toNotify.rtrigger( 'style' ); // let renderer know we changed style
    } else {
      toNotify.trigger( 'style' ); // just fire the event
    }
    return this; // chaining
  },

  // get the internal parsed style object for the specified property
  parsedStyle: function( property ){
    var ele = this[0];
    if( !ele.cy().styleEnabled() ){ return; }

    if( ele ){
      return ele._private.style[ property ] || ele.cy().style().getDefaultProperty( property );
    }
  },

  // get the specified css property as a rendered value (i.e. on-screen value)
  // or get the whole rendered style if no property specified (NB doesn't allow setting)
  renderedStyle: function( property ){
    var cy = this.cy();
    if( !cy.styleEnabled() ){ return this; }

    var ele = this[0];

    if( ele ){
      var renstyle = ele.cy().style().getRenderedStyle( ele );

      if( property === undefined ){
        return renstyle;
      } else {
        return renstyle[ property ];
      }
    }
  },

  // read the calculated css style of the element or override the style (via a bypass)
  style: function( name, value ){
    var cy = this.cy();

    if( !cy.styleEnabled() ){ return this; }

    var updateTransitions = false;
    var style = cy.style();

    if( is.plainObject( name ) ){ // then extend the bypass
      var props = name;
      style.applyBypass( this, props, updateTransitions );

      var updatedCompounds = this.updateCompoundBounds();
      var toNotify = updatedCompounds.length > 0 ? this.add( updatedCompounds ) : this;
      toNotify.rtrigger( 'style' ); // let the renderer know we've updated style

    } else if( is.string( name ) ){

      if( value === undefined ){ // then get the property from the style
        var ele = this[0];

        if( ele ){
          return style.getStylePropertyValue( ele, name );
        } else { // empty collection => can't get any value
          return;
        }

      } else { // then set the bypass with the property value
        style.applyBypass( this, name, value, updateTransitions );

        var updatedCompounds = this.updateCompoundBounds();
        var toNotify = updatedCompounds.length > 0 ? this.add( updatedCompounds ) : this;
        toNotify.rtrigger( 'style' ); // let the renderer know we've updated style
      }

    } else if( name === undefined ){
      var ele = this[0];

      if( ele ){
        return style.getRawStyle( ele );
      } else { // empty collection => can't get any value
        return;
      }
    }

    return this; // chaining
  },

  removeStyle: function( names ){
    var cy = this.cy();

    if( !cy.styleEnabled() ){ return this; }

    var updateTransitions = false;
    var style = cy.style();
    var eles = this;

    if( names === undefined ){
      for( var i = 0; i < eles.length; i++ ){
        var ele = eles[ i ];

        style.removeAllBypasses( ele, updateTransitions );
      }
    } else {
      names = names.split( /\s+/ );

      for( var i = 0; i < eles.length; i++ ){
        var ele = eles[ i ];

        style.removeBypasses( ele, names, updateTransitions );
      }
    }

    var updatedCompounds = this.updateCompoundBounds();
    var toNotify = updatedCompounds.length > 0 ? this.add( updatedCompounds ) : this;
    toNotify.rtrigger( 'style' ); // let the renderer know we've updated style

    return this; // chaining
  },

  show: function(){
    this.css( 'display', 'element' );
    return this; // chaining
  },

  hide: function(){
    this.css( 'display', 'none' );
    return this; // chaining
  },

  visible: function(){
    var cy = this.cy();
    if( !cy.styleEnabled() ){ return true; }

    var ele = this[0];
    var hasCompoundNodes = cy.hasCompoundNodes();

    if( ele ){
      if(
        ele.pstyle( 'visibility' ).value !== 'visible'
        || ele.pstyle( 'display' ).value !== 'element'
      ){
        return false;
      }

      if( ele.pstyle('width').pfValue === 0 ){
        return false;
      }

      if( ele._private.group === 'nodes' ){
        if( !hasCompoundNodes ){ return true; }

        if( ele.pstyle('height').pfValue === 0 ){
          return false;
        }

        var parents = ele._private.data.parent ? ele.parents() : null;

        if( parents ){
          for( var i = 0; i < parents.length; i++ ){
            var parent = parents[ i ];
            var pVis = parent.pstyle( 'visibility' ).value;
            var pDis = parent.pstyle( 'display' ).value;

            if( pVis !== 'visible' || pDis !== 'element' ){
              return false;
            }
          }
        }

        return true;
      } else {
        var src = ele._private.source;
        var tgt = ele._private.target;

        return src.visible() && tgt.visible();
      }

    }
  },

  hidden: function(){
    var ele = this[0];

    if( ele ){
      return !ele.visible();
    }
  },

  effectiveOpacity: function(){
    var cy = this.cy();
    if( !cy.styleEnabled() ){ return 1; }

    var hasCompoundNodes = cy.hasCompoundNodes();
    var ele = this[0];

    if( ele ){
      var _p = ele._private;
      var parentOpacity = ele.pstyle( 'opacity' ).value;

      if( !hasCompoundNodes ){ return parentOpacity; }

      var parents = !_p.data.parent ? null : ele.parents();

      if( parents ){
        for( var i = 0; i < parents.length; i++ ){
          var parent = parents[ i ];
          var opacity = parent.pstyle( 'opacity' ).value;

          parentOpacity = opacity * parentOpacity;
        }
      }

      return parentOpacity;
    }
  },

  transparent: function(){
    var cy = this.cy();
    if( !cy.styleEnabled() ){ return false; }

    var ele = this[0];
    var hasCompoundNodes = ele.cy().hasCompoundNodes();

    if( ele ){
      if( !hasCompoundNodes ){
        return ele.pstyle( 'opacity' ).value === 0;
      } else {
        return ele.effectiveOpacity() === 0;
      }
    }
  },

  backgrounding: function(){
    var cy = this.cy();
    if( !cy.styleEnabled() ){ return false; }

    var ele = this[0];

    return ele._private.backgrounding ? true : false;
  }

});

elesfn.bypass = elesfn.css = elesfn.style;
elesfn.renderedCss = elesfn.renderedStyle;
elesfn.removeBypass = elesfn.removeCss = elesfn.removeStyle;
elesfn.pstyle = elesfn.parsedStyle;

module.exports = elesfn;
<commit_msg>Compound early exit check can be made only after height checked for nodes #1525<commit_after>'use strict';

var is = require( '../is' );

var elesfn = ({

  // fully updates (recalculates) the style for the elements
  updateStyle: function( notifyRenderer ){
    var cy = this._private.cy;

    if( !cy.styleEnabled() ){ return this; }

    if( cy._private.batchingStyle ){
      var bEles = cy._private.batchStyleEles;

      bEles.merge( this );

      return this; // chaining and exit early when batching
    }

    var style = cy.style();
    notifyRenderer = notifyRenderer || notifyRenderer === undefined ? true : false;

    style.apply( this );

    var updatedCompounds = this.updateCompoundBounds();
    var toNotify = updatedCompounds.length > 0 ? this.add( updatedCompounds ) : this;

    if( notifyRenderer ){
      toNotify.rtrigger( 'style' ); // let renderer know we changed style
    } else {
      toNotify.trigger( 'style' ); // just fire the event
    }
    return this; // chaining
  },

  // just update the mappers in the elements' styles; cheaper than eles.updateStyle()
  updateMappers: function( notifyRenderer ){
    var cy = this._private.cy;
    var style = cy.style();
    notifyRenderer = notifyRenderer || notifyRenderer === undefined ? true : false;

    if( !cy.styleEnabled() ){ return this; }

    style.updateMappers( this );

    var updatedCompounds = this.updateCompoundBounds();
    var toNotify = updatedCompounds.length > 0 ? this.add( updatedCompounds ) : this;

    if( notifyRenderer ){
      toNotify.rtrigger( 'style' ); // let renderer know we changed style
    } else {
      toNotify.trigger( 'style' ); // just fire the event
    }
    return this; // chaining
  },

  // get the internal parsed style object for the specified property
  parsedStyle: function( property ){
    var ele = this[0];
    if( !ele.cy().styleEnabled() ){ return; }

    if( ele ){
      return ele._private.style[ property ] || ele.cy().style().getDefaultProperty( property );
    }
  },

  // get the specified css property as a rendered value (i.e. on-screen value)
  // or get the whole rendered style if no property specified (NB doesn't allow setting)
  renderedStyle: function( property ){
    var cy = this.cy();
    if( !cy.styleEnabled() ){ return this; }

    var ele = this[0];

    if( ele ){
      var renstyle = ele.cy().style().getRenderedStyle( ele );

      if( property === undefined ){
        return renstyle;
      } else {
        return renstyle[ property ];
      }
    }
  },

  // read the calculated css style of the element or override the style (via a bypass)
  style: function( name, value ){
    var cy = this.cy();

    if( !cy.styleEnabled() ){ return this; }

    var updateTransitions = false;
    var style = cy.style();

    if( is.plainObject( name ) ){ // then extend the bypass
      var props = name;
      style.applyBypass( this, props, updateTransitions );

      var updatedCompounds = this.updateCompoundBounds();
      var toNotify = updatedCompounds.length > 0 ? this.add( updatedCompounds ) : this;
      toNotify.rtrigger( 'style' ); // let the renderer know we've updated style

    } else if( is.string( name ) ){

      if( value === undefined ){ // then get the property from the style
        var ele = this[0];

        if( ele ){
          return style.getStylePropertyValue( ele, name );
        } else { // empty collection => can't get any value
          return;
        }

      } else { // then set the bypass with the property value
        style.applyBypass( this, name, value, updateTransitions );

        var updatedCompounds = this.updateCompoundBounds();
        var toNotify = updatedCompounds.length > 0 ? this.add( updatedCompounds ) : this;
        toNotify.rtrigger( 'style' ); // let the renderer know we've updated style
      }

    } else if( name === undefined ){
      var ele = this[0];

      if( ele ){
        return style.getRawStyle( ele );
      } else { // empty collection => can't get any value
        return;
      }
    }

    return this; // chaining
  },

  removeStyle: function( names ){
    var cy = this.cy();

    if( !cy.styleEnabled() ){ return this; }

    var updateTransitions = false;
    var style = cy.style();
    var eles = this;

    if( names === undefined ){
      for( var i = 0; i < eles.length; i++ ){
        var ele = eles[ i ];

        style.removeAllBypasses( ele, updateTransitions );
      }
    } else {
      names = names.split( /\s+/ );

      for( var i = 0; i < eles.length; i++ ){
        var ele = eles[ i ];

        style.removeBypasses( ele, names, updateTransitions );
      }
    }

    var updatedCompounds = this.updateCompoundBounds();
    var toNotify = updatedCompounds.length > 0 ? this.add( updatedCompounds ) : this;
    toNotify.rtrigger( 'style' ); // let the renderer know we've updated style

    return this; // chaining
  },

  show: function(){
    this.css( 'display', 'element' );
    return this; // chaining
  },

  hide: function(){
    this.css( 'display', 'none' );
    return this; // chaining
  },

  visible: function(){
    var cy = this.cy();
    if( !cy.styleEnabled() ){ return true; }

    var ele = this[0];
    var hasCompoundNodes = cy.hasCompoundNodes();

    if( ele ){
      if(
        ele.pstyle( 'visibility' ).value !== 'visible'
        || ele.pstyle( 'display' ).value !== 'element'
        || ele.pstyle('width').pfValue === 0
      ){
        return false;
      }

      if( ele._private.group === 'nodes' ){
        if( ele.pstyle('height').pfValue === 0 ){ return false; }

        if( !hasCompoundNodes ){ return true; }

        var parents = ele._private.data.parent ? ele.parents() : null;

        if( parents ){
          for( var i = 0; i < parents.length; i++ ){
            var parent = parents[ i ];
            var pVis = parent.pstyle( 'visibility' ).value;
            var pDis = parent.pstyle( 'display' ).value;

            if( pVis !== 'visible' || pDis !== 'element' ){
              return false;
            }
          }
        }

        return true;
      } else {
        var src = ele._private.source;
        var tgt = ele._private.target;

        return src.visible() && tgt.visible();
      }

    }
  },

  hidden: function(){
    var ele = this[0];

    if( ele ){
      return !ele.visible();
    }
  },

  effectiveOpacity: function(){
    var cy = this.cy();
    if( !cy.styleEnabled() ){ return 1; }

    var hasCompoundNodes = cy.hasCompoundNodes();
    var ele = this[0];

    if( ele ){
      var _p = ele._private;
      var parentOpacity = ele.pstyle( 'opacity' ).value;

      if( !hasCompoundNodes ){ return parentOpacity; }

      var parents = !_p.data.parent ? null : ele.parents();

      if( parents ){
        for( var i = 0; i < parents.length; i++ ){
          var parent = parents[ i ];
          var opacity = parent.pstyle( 'opacity' ).value;

          parentOpacity = opacity * parentOpacity;
        }
      }

      return parentOpacity;
    }
  },

  transparent: function(){
    var cy = this.cy();
    if( !cy.styleEnabled() ){ return false; }

    var ele = this[0];
    var hasCompoundNodes = ele.cy().hasCompoundNodes();

    if( ele ){
      if( !hasCompoundNodes ){
        return ele.pstyle( 'opacity' ).value === 0;
      } else {
        return ele.effectiveOpacity() === 0;
      }
    }
  },

  backgrounding: function(){
    var cy = this.cy();
    if( !cy.styleEnabled() ){ return false; }

    var ele = this[0];

    return ele._private.backgrounding ? true : false;
  }

});

elesfn.bypass = elesfn.css = elesfn.style;
elesfn.renderedCss = elesfn.renderedStyle;
elesfn.removeBypass = elesfn.removeCss = elesfn.removeStyle;
elesfn.pstyle = elesfn.parsedStyle;

module.exports = elesfn;
<|endoftext|>


<commit_before>angular.module('yds').directive('ydsDashboardVisualization', ['DashboardService', '$ocLazyLoad', '$timeout',
    function (DashboardService, $ocLazyLoad, $timeout) {
        return {
            restrict: 'E',
            scope: {
                projectId: '@',         // Project ID of chart
                dashboardId: '@',       // ID used for getting selected year range from DashboardService
                addToBasket: '@',       // If true, the save to basket button will appear in visualizations
                disableColor: '@',      // If true, the component will ignore the color of the selected aggregate type
                defaultChart: '@',      // Chart to show on initialization of the component. Default is "bar".
                type: '@',              // View type of component. If not set, will get it from DashboardService
                lang: '@',              // Language of charts
                baseUrl: '@',           // Base URL to send to API
                title: '@',             // Title of component
                elementH: '@',          // Height of component

                numberOfItems: '@',     // Number of items that the charts are expected to show. If too big, will use paging
                pagingThreshold: '@',   // Number of items that when exceeded, components with paging should be used

                enableRating: '@'       // Enable rating buttons for this component
            },
            templateUrl: ((typeof Drupal != 'undefined') ? Drupal.settings.basePath + Drupal.settings.yds_project.modulePath + '/' : '') + 'templates/dashboard-visualization.html',
            link: function (scope) {
                var dashboardId = scope.dashboardId;
                var disableColor = scope.disableColor;
                var defaultChart = scope.defaultChart;
                var type = scope.type;
                scope.usePaging = false;    // Paging disabled by default

                scope.aggregateRadio = "amount";

                // Enable the Budget button only for Aid Activities
                scope.enableBudget = (dashboardId === "aidactivity");

                // Set the amount/count button translations and, if needed, the default title
                if (scope.lang === "el") {
                    scope.title = scope.title || "Λεπτομέρειες";
                    scope.translations = {
                        amount: "Ποσό",
                        budget: "Προϋπολογισμός",
                        count: "Αριθμός",
                        tooltip: "Επιλέξτε αν τα γραφήματα θα παρουσιάζουν ποσά ή αριθμούς αντικειμένων.",
                        prompt: "Προβολή:"
                    };
                } else {
                    scope.title = scope.title || "Details";
                    scope.translations = {
                        amount: "Amount",
                        budget: "Budget",
                        count: "Number",
                        tooltip: "Select whether visualisations will present amounts or counts over items.",
                        prompt: "Show:"
                    };
                }

                // If the pagingThreshold is set, watch for changes in the number of items
                scope.pagingThreshold = parseInt(scope.pagingThreshold);

                if (!_.isUndefined(scope.pagingThreshold) && !_.isNaN(scope.pagingThreshold)) {
                    scope.$watch("numberOfItems", function () {
                        // Check if paging should be used
                        scope.usePaging = parseInt(scope.numberOfItems) >= scope.pagingThreshold;
                    });
                }

                // Check if the component's height attribute is defined, else assign default value
                if (_.isUndefined(scope.elementH) || scope.elementH.trim() == "")
                    scope.elementH = 300;

                // If dashboardId is undefined, show error
                if (_.isUndefined(dashboardId) || dashboardId.trim() == "")
                    dashboardId = "default";

                // If disableColor is undefined, show error
                if (_.isUndefined(disableColor) || (disableColor != "true" && disableColor != "false"))
                    disableColor = "false";

                // If defaultChart is undefined, show bar chart
                if (_.isUndefined(defaultChart)) {
                    defaultChart = "bar";
                }

                // Set minimum height of details panel body
                scope.panelBodyStyle = {
                    "min-height": (parseInt(scope.elementH) + 30) + "px"
                };

                scope.selectedVis = DashboardService.getSelectedVisType();

                // Set the first type as default selected one
                scope.selProjectId = scope.projectId;
                scope.selViewType = "";

                // Variables for previous values, to check that something really changed before re-rendering component
                var prevViewType = "";
                var updateVis = false;

                /**
                 * Re-render the selected visualization
                 */
                var updateVisualization = function () {
                    if (updateVis) {
                        var selectedVis = defaultChart;
                        if (scope.selectedVis.length > 0) {
                            selectedVis = scope.selectedVis;
                        }

                        // Make selectedVis empty in order for the component to re-render
                        scope.selectedVis = "";

                        // Postpone to end of digest queue
                        $timeout(function () {
                            scope.selectVis(selectedVis);

                            updateVis = false;
                        });
                    }
                };

                /**
                 * Update the view type from the DashboardService
                 * Also sets the Visualization panel's color
                 */
                var updateViewType = function () {
                    prevViewType = scope.selViewType;
                    var viewType = DashboardService.getViewType(dashboardId);

                    if (!_.isUndefined(type) && type.length > 0) {
                        // Use view type from attribute
                        viewType = {
                            type: type
                        };
                    }

                    if (!_.isUndefined(viewType) && !_.isEqual(prevViewType, viewType.type)) {
                        scope.selViewType = viewType.type;

                        if (disableColor != "true") {
                            scope.panelStyle = viewType.panelStyle;
                            scope.panelHeadingStyle = _.omit(viewType.panelHeadingStyle, "min-height");
                        }

                        updateVis = true;
                    }
                };

                /**
                 * Handles view type selection changes
                 */
                var viewTypeChangeHandler = function () {
                    updateViewType();
                    updateVisualization();
                };

                if (_.isUndefined(type) || type.length == 0) {
                    // Subscribe to year selection and view type changes
                    DashboardService.subscribeViewTypeChanges(scope, viewTypeChangeHandler);
                }

                /**
                 * Change selected visualization type
                 * @param visType
                 */
                scope.selectVis = function (visType) {
                    scope.selectedVis = visType;

                    DashboardService.setVisType(visType);
                };

                // Initialize component
                viewTypeChangeHandler();
            }
        };
    }
]);
<commit_msg>Change "Amount" to "Spending" for aid activities<commit_after>angular.module('yds').directive('ydsDashboardVisualization', ['DashboardService', '$ocLazyLoad', '$timeout',
    function (DashboardService, $ocLazyLoad, $timeout) {
        return {
            restrict: 'E',
            scope: {
                projectId: '@',         // Project ID of chart
                dashboardId: '@',       // ID used for getting selected year range from DashboardService
                addToBasket: '@',       // If true, the save to basket button will appear in visualizations
                disableColor: '@',      // If true, the component will ignore the color of the selected aggregate type
                defaultChart: '@',      // Chart to show on initialization of the component. Default is "bar".
                type: '@',              // View type of component. If not set, will get it from DashboardService
                lang: '@',              // Language of charts
                baseUrl: '@',           // Base URL to send to API
                title: '@',             // Title of component
                elementH: '@',          // Height of component

                numberOfItems: '@',     // Number of items that the charts are expected to show. If too big, will use paging
                pagingThreshold: '@',   // Number of items that when exceeded, components with paging should be used

                enableRating: '@'       // Enable rating buttons for this component
            },
            templateUrl: ((typeof Drupal != 'undefined') ? Drupal.settings.basePath + Drupal.settings.yds_project.modulePath + '/' : '') + 'templates/dashboard-visualization.html',
            link: function (scope) {
                var dashboardId = scope.dashboardId;
                var disableColor = scope.disableColor;
                var defaultChart = scope.defaultChart;
                var type = scope.type;
                scope.usePaging = false;    // Paging disabled by default

                scope.aggregateRadio = "amount";
                scope.enableBudget = false;         // Budget button disabled by default

                // Set the amount/count button translations and, if needed, the default title
                if (scope.lang === "el") {
                    scope.title = scope.title || "Λεπτομέρειες";
                    scope.translations = {
                        amount: "Ποσό",
                        budget: "Προϋπολογισμός",
                        count: "Αριθμός",
                        tooltip: "Επιλέξτε αν τα γραφήματα θα παρουσιάζουν ποσά ή αριθμούς αντικειμένων.",
                        prompt: "Προβολή:"
                    };
                } else {
                    scope.title = scope.title || "Details";
                    scope.translations = {
                        amount: "Amount",
                        budget: "Budget",
                        count: "Number",
                        tooltip: "Select whether visualisations will present amounts or counts over items.",
                        prompt: "Show:"
                    };
                }

                // Enable the Budget button only for Aid Activities
                if (dashboardId === "aidactivity") {
                    scope.enableBudget = true;

                    // Set amount button to "Spending"
                    scope.translations.amount = (scope.lang === "el") ? "Δαπάνες" : "Spending";
                }

                // If the pagingThreshold is set, watch for changes in the number of items
                scope.pagingThreshold = parseInt(scope.pagingThreshold);

                if (!_.isUndefined(scope.pagingThreshold) && !_.isNaN(scope.pagingThreshold)) {
                    scope.$watch("numberOfItems", function () {
                        // Check if paging should be used
                        scope.usePaging = parseInt(scope.numberOfItems) >= scope.pagingThreshold;
                    });
                }

                // Check if the component's height attribute is defined, else assign default value
                if (_.isUndefined(scope.elementH) || scope.elementH.trim() == "")
                    scope.elementH = 300;

                // If dashboardId is undefined, show error
                if (_.isUndefined(dashboardId) || dashboardId.trim() == "")
                    dashboardId = "default";

                // If disableColor is undefined, show error
                if (_.isUndefined(disableColor) || (disableColor != "true" && disableColor != "false"))
                    disableColor = "false";

                // If defaultChart is undefined, show bar chart
                if (_.isUndefined(defaultChart)) {
                    defaultChart = "bar";
                }

                // Set minimum height of details panel body
                scope.panelBodyStyle = {
                    "min-height": (parseInt(scope.elementH) + 30) + "px"
                };

                scope.selectedVis = DashboardService.getSelectedVisType();

                // Set the first type as default selected one
                scope.selProjectId = scope.projectId;
                scope.selViewType = "";

                // Variables for previous values, to check that something really changed before re-rendering component
                var prevViewType = "";
                var updateVis = false;

                /**
                 * Re-render the selected visualization
                 */
                var updateVisualization = function () {
                    if (updateVis) {
                        var selectedVis = defaultChart;
                        if (scope.selectedVis.length > 0) {
                            selectedVis = scope.selectedVis;
                        }

                        // Make selectedVis empty in order for the component to re-render
                        scope.selectedVis = "";

                        // Postpone to end of digest queue
                        $timeout(function () {
                            scope.selectVis(selectedVis);

                            updateVis = false;
                        });
                    }
                };

                /**
                 * Update the view type from the DashboardService
                 * Also sets the Visualization panel's color
                 */
                var updateViewType = function () {
                    prevViewType = scope.selViewType;
                    var viewType = DashboardService.getViewType(dashboardId);

                    if (!_.isUndefined(type) && type.length > 0) {
                        // Use view type from attribute
                        viewType = {
                            type: type
                        };
                    }

                    if (!_.isUndefined(viewType) && !_.isEqual(prevViewType, viewType.type)) {
                        scope.selViewType = viewType.type;

                        if (disableColor != "true") {
                            scope.panelStyle = viewType.panelStyle;
                            scope.panelHeadingStyle = _.omit(viewType.panelHeadingStyle, "min-height");
                        }

                        updateVis = true;
                    }
                };

                /**
                 * Handles view type selection changes
                 */
                var viewTypeChangeHandler = function () {
                    updateViewType();
                    updateVisualization();
                };

                if (_.isUndefined(type) || type.length == 0) {
                    // Subscribe to year selection and view type changes
                    DashboardService.subscribeViewTypeChanges(scope, viewTypeChangeHandler);
                }

                /**
                 * Change selected visualization type
                 * @param visType
                 */
                scope.selectVis = function (visType) {
                    scope.selectedVis = visType;

                    DashboardService.setVisType(visType);
                };

                // Initialize component
                viewTypeChangeHandler();
            }
        };
    }
]);
<|endoftext|>


<commit_before>define(function(require) {

  var $ = require('jquery');
  var _ = require('underscore');
  var Backbone = require('backbone');
  var Doc = require('models/doc');
  var remoteStorage = require('remotestorage');

  var remoteStorageDocuments = require('remotestorage-documents');
  var rsSync = require('rs-adapter');

  var Docs = Backbone.Collection.extend({

    model: Doc,

    sync: rsSync,

    initialize: function(models, options) {

      _.bindAll(this, 'fetch', 'sort', 'saveWhenIdle', 'ensureDoc', 'rsChange');

      this
        .on('change:lastEdited', this.sort)
        .on('change:lastEdited', this.saveWhenIdle);

      this.initRemotestorage();

    },

    addNew: function() {
      this.add({
        // TODO: remotestorage should create id
        id: Math.round( Math.random() * 10000000000000 ),
        lastEdited: new Date().getTime()
      });
    },

    // Sort by 'lastEdited'
    comparator: function(first, second) {
      return first.get('lastEdited') > second.get('lastEdited') ? -1 : 1 ;
    },

    // send updates at most once per second to remotestorage
    saveTimeout: undefined,
    saveWhenIdle: function(doc) {
      clearTimeout(this.saveTimeout);
      this.saveTimeout = setTimeout(doc.save, 1000);
    },

    // fetch from remotestorage at most all 300ms
    fetch: _.debounce(function() {
      return Backbone.Collection.prototype.fetch.call(this, {
        success: this.ensureDoc
      });
    }, 300, true),

    ensureDoc: function () {
      if (this.isEmpty()) this.addNew();
    },

    before: function(id) {
      return this.at( this.indexOf( this.get(id) ) - 1 );
    },

    after: function(id) {
      return this.at( this.indexOf( this.get(id) ) + 1 );
    },

    initRemotestorage: function() {
      var docs = this;

      var origHash = document.location.hash;

      remoteStorage.on('ready', docs.fetch);

      remoteStorage.on('disconnect', function() {
        docs.reset().addNew();
      });

      remoteStorage.claimAccess('documents', 'rw');
      remoteStorage.documents.init();
      remoteStorage.displayWidget('remotestorage-connect');

      remoteStorageDocuments.onChange('notes', docs.rsChange);

      setTimeout(function() {
        var md = origHash.match(/access_token=([^&]+)/);
        if ( md && (! remoteStorage.getBearerToken()) ) {
          // backbone stole our access token
          remoteStorage.setBearerToken(md[1]);
        }
      }, 0);
    },

    rsChange: function (event) {
      if (event.origin !== 'window') this.fetch();
    }

  });

  return Docs;

});
<commit_msg>dirty hack to make fetching when initial connecting work. cc @nil<commit_after>define(function(require) {

  var $ = require('jquery');
  var _ = require('underscore');
  var Backbone = require('backbone');
  var Doc = require('models/doc');
  var remoteStorage = require('remotestorage');

  var remoteStorageDocuments = require('remotestorage-documents');
  var rsSync = require('rs-adapter');

  var Docs = Backbone.Collection.extend({

    model: Doc,

    sync: rsSync,

    initialize: function(models, options) {

      _.bindAll(this, 'fetch', 'sort', 'saveWhenIdle', 'ensureDoc', 'rsChange');

      this
        .on('change:lastEdited', this.sort)
        .on('change:lastEdited', this.saveWhenIdle);

      this.initRemotestorage();

    },

    addNew: function() {
      this.add({
        // TODO: remotestorage should create id
        id: Math.round( Math.random() * 10000000000000 ),
        lastEdited: new Date().getTime()
      });
    },

    // Sort by 'lastEdited'
    comparator: function(first, second) {
      return first.get('lastEdited') > second.get('lastEdited') ? -1 : 1 ;
    },

    // send updates at most once per second to remotestorage
    saveTimeout: undefined,
    saveWhenIdle: function(doc) {
      clearTimeout(this.saveTimeout);
      this.saveTimeout = setTimeout(doc.save, 1000);
    },

    // fetch from remotestorage at most all 300ms
    fetch: _.debounce(function() {
      return Backbone.Collection.prototype.fetch.call(this, {
        success: this.ensureDoc
      });
    }, 300, true),

    ensureDoc: function () {
      if (this.isEmpty()) this.addNew();
    },

    before: function(id) {
      return this.at( this.indexOf( this.get(id) ) - 1 );
    },

    after: function(id) {
      return this.at( this.indexOf( this.get(id) ) + 1 );
    },

    initRemotestorage: function() {
      var docs = this;

      var origHash = document.location.hash;

      remoteStorage.on('ready', setTimeout(function() { docs.fetch(); }, 1000));

      remoteStorage.on('disconnect', function() {
        docs.reset().addNew();
      });

      remoteStorage.claimAccess('documents', 'rw');
      remoteStorage.documents.init();
      remoteStorage.displayWidget('remotestorage-connect');

      remoteStorageDocuments.onChange('notes', docs.rsChange);

      setTimeout(function() {
        var md = origHash.match(/access_token=([^&]+)/);
        if ( md && (! remoteStorage.getBearerToken()) ) {
          // backbone stole our access token
          remoteStorage.setBearerToken(md[1]);
        }
      }, 0);
    },

    rsChange: function (event) {
      if (event.origin !== 'window') this.fetch();
    }

  });

  return Docs;

});
<|endoftext|>


<commit_before>"use strict";
const fs = require('fs');
const path = require('path');

const chalk = require('chalk');
const {Command} = require('switchit');
const ora = require('ora');

const constants = require('../constants');
const Repo = require('../Repo');
const VCS = require('../VCS');
const PackageManagers = require('../PackageManagers');
const Logger = require('../utils/Logger');
const FileUtil = require('../utils/FileUtil');

const isWindows = /^win/.test(process.platform);

class Install extends Command {
    beforeExecute (params) {
        super.beforeExecute(params);
        this.debug = this.root().debug;
    }

    execute(params) {
        let me = this;
        let mondo = this.root();

        params.verbose = params.verbose || me.debug;
        if (params.verbose) {
            Logger.setThreshold('debug');
        }
        PackageManagers.configure({
            debug: params.verbose
        });

        me.binDirs = [];
        me.wrappedBins = [];

        me.packageManager = PackageManagers[mondo.settings.packageManager || 'yarn'];

        let repo = Repo.open(process.cwd());
        return me.installRepo(repo.root).then(() => {
            if (me.wrappedBins.length > 0) {
                let message = 'Linking local binaries';
                Logger.debug(message);
                if (me.spinner) {
                    me.spinner.succeed();
                    me.spinner.text = message;
                    me.spinner.start();
                }

                let createWinBinary = (binDir, wrappedBin) => {
                    let binPath = path.join(binDir, wrappedBin.name+'.cmd');
                    let message = `- Creating ${chalk.green(binPath)}`;

                    if (fs.existsSync(binPath)) {
                        message = `- Binary ${chalk.green(binPath)} already exists, overwriting`;
                        if (!me.spinner) {
                            Logger.warn(message);
                        }
                    }

                    Logger.debug(message);
                    fs.writeFileSync(binPath, `@IF EXIST "%~dp0\node.exe" (\n  "%~dp0\\node.exe"  "%~dp0\\${wrappedBin.name}" %*\n) ELSE (\n  @SETLOCAL\n  @SET PATHEXT=%PATHEXT:;.JS;=;%\n  node  "%~dp0\\${wrappedBin.name}" %*\n)\n`);
                    fs.chmodSync(binPath, '755');
                };

                let createUnixBinary = (binDir, wrappedBin) => {
                    let binPath = path.join(binDir, wrappedBin.name);
                    let message = `- Creating ${chalk.green(binPath)}`;

                    if (fs.existsSync(binPath)) {
                        message = `- Binary ${chalk.green(binPath)} already exists, overwriting`;
                        if (!me.spinner) {
                            Logger.warn(message);
                        }
                    }

                    Logger.debug(message);
                    fs.writeFileSync(binPath, `#! /usr/bin/env node\nrequire('mondorepo');\nrequire('${wrappedBin.pkg.name}/${wrappedBin.file}');\n`);
                    fs.chmodSync(binPath, '755');
                };

                me.wrappedBins.forEach(function (wrappedBin) {
                    let message = `- Linking '${chalk.green(wrappedBin.name)}'`;
                    Logger.debug(message);
                    if (me.spinner) {
                        me.spinner.succeed();
                        me.spinner.text = message;
                        me.spinner.start();
                    }
                    me.binDirs.forEach(function (binDir) {
                        if (!fs.existsSync(binDir)) {
                            FileUtil.mkdirp(binDir);
                        }
                        createUnixBinary(binDir, wrappedBin);
                        if (isWindows) {
                            createWinBinary(binDir, wrappedBin);
                        }
                    });
                });
                if (me.spinner) {
                    me.spinner.succeed();
                }
            }
        });
    }

    installRepo(repo) {
        let me = this;
        if (repo.installed) {
            return Promise.resolve(repo);
        }

        repo.installed = true;

        if (repo.exists()) {
            return me.installRepoPackages(repo);
        }

        let message = chalk.cyan(`Cloning repository '${chalk.magenta(repo.name)}' from '${chalk.yellow(repo.source.repository)}#${chalk.magenta(repo.source.branch)}' into '${chalk.magenta(path.relative(process.cwd(), repo.path))}'`);
        Logger.debug(message);
        if (me.spinner) {
            me.spinner.succeed();
            me.spinner.text = message;
            me.spinner.start();
        }

        return VCS.github.clone(repo.source.repository, repo.path, repo.source.branch).then(() => {
            fs.writeFile(path.join(repo.path, constants.child), JSON.stringify({root: path.relative(repo.path, process.cwd())}), function(err) {
                if (err) {
                    return me.raise(err);
                }
            });
            return me.installRepoPackages(repo);
        });
    }

    installChildren(repo) {
        let me = this;
        let uses;

        for (let child of repo.uses) {
            if (uses) {
                uses = uses.then(() => me.installRepo(child));
            } else {
                uses = me.installRepo(child);
            }
        }

        if (uses) {
            return uses.then(() => repo);
        }

        return Promise.resolve(repo);
    }

    installRepoPackages(repo) {
        let me = this;
        repo.open();

        let message = `Installing packages for '${chalk.yellow(repo.name)}'`;
        Logger.debug(message);
        if (!me.params.verbose) {
            if (me.spinner) {
                me.spinner.succeed();
            } else {
                me.spinner = ora();
            }
            me.spinner.text = message;
            me.spinner.start();
        }
        let install = me.packageManager().install(repo.path);
        me.binDirs.push(path.join(repo.path, 'node_modules', '.bin'));
        let packages = repo.packages;
        for (let pkg of packages) {
            install = install.then(() => {
                let message = `- Installing '${chalk.yellow(repo.name)}:${chalk.magenta(pkg.name)}'`;
                Logger.debug(message);
                if (me.spinner) {
                    me.spinner.succeed();
                    me.spinner.text= message;
                    me.spinner.start();
                }

                return me.packageManager().install(pkg.path).then(() => {
                    me.binDirs.push(path.join(pkg.path, 'node_modules', '.bin'));
                    let pkgJson = require(path.join(pkg.path, 'package.json'));
                    if (pkgJson.bin) {
                        Object.keys(pkgJson.bin).forEach(function (name) {
                            me.wrappedBins.push({
                                pkg: pkg,
                                name: name,
                                file: pkgJson.bin[name]
                            });
                        });
                    }
                });
            });
        }

        // read the repo from the disk
        return install.then(() => me.installChildren(repo));
    }

}

Install.define({
    help: {
        '': 'Brings the mondo in!',
        verbose: 'Provide additional output'
    },
    switches: '[verbose:boolean=false]'
});

module.exports = Install;
<commit_msg>Fix newline<commit_after>"use strict";
const fs = require('fs');
const path = require('path');

const chalk = require('chalk');
const {Command} = require('switchit');
const ora = require('ora');

const constants = require('../constants');
const Repo = require('../Repo');
const VCS = require('../VCS');
const PackageManagers = require('../PackageManagers');
const Logger = require('../utils/Logger');
const FileUtil = require('../utils/FileUtil');

const isWindows = /^win/.test(process.platform);

class Install extends Command {
    beforeExecute (params) {
        super.beforeExecute(params);
        this.debug = this.root().debug;
    }

    execute(params) {
        let me = this;
        let mondo = this.root();

        params.verbose = params.verbose || me.debug;
        if (params.verbose) {
            Logger.setThreshold('debug');
        }
        PackageManagers.configure({
            debug: params.verbose
        });

        me.binDirs = [];
        me.wrappedBins = [];

        me.packageManager = PackageManagers[mondo.settings.packageManager || 'yarn'];

        let repo = Repo.open(process.cwd());
        return me.installRepo(repo.root).then(() => {
            if (me.wrappedBins.length > 0) {
                let message = 'Linking local binaries';
                Logger.debug(message);
                if (me.spinner) {
                    me.spinner.succeed();
                    me.spinner.text = message;
                    me.spinner.start();
                }

                let createWinBinary = (binDir, wrappedBin) => {
                    let binPath = path.join(binDir, wrappedBin.name+'.cmd');
                    let message = `- Creating ${chalk.green(binPath)}`;

                    if (fs.existsSync(binPath)) {
                        message = `- Binary ${chalk.green(binPath)} already exists, overwriting`;
                        if (!me.spinner) {
                            Logger.warn(message);
                        }
                    }

                    Logger.debug(message);
                    fs.writeFileSync(binPath, `@IF EXIST "%~dp0\\node.exe" (\n  "%~dp0\\node.exe"  "%~dp0\\${wrappedBin.name}" %*\n) ELSE (\n  @SETLOCAL\n  @SET PATHEXT=%PATHEXT:;.JS;=;%\n  node  "%~dp0\\${wrappedBin.name}" %*\n)\n`);
                    fs.chmodSync(binPath, '755');
                };

                let createUnixBinary = (binDir, wrappedBin) => {
                    let binPath = path.join(binDir, wrappedBin.name);
                    let message = `- Creating ${chalk.green(binPath)}`;

                    if (fs.existsSync(binPath)) {
                        message = `- Binary ${chalk.green(binPath)} already exists, overwriting`;
                        if (!me.spinner) {
                            Logger.warn(message);
                        }
                    }

                    Logger.debug(message);
                    fs.writeFileSync(binPath, `#! /usr/bin/env node\nrequire('mondorepo');\nrequire('${wrappedBin.pkg.name}/${wrappedBin.file}');\n`);
                    fs.chmodSync(binPath, '755');
                };

                me.wrappedBins.forEach(function (wrappedBin) {
                    let message = `- Linking '${chalk.green(wrappedBin.name)}'`;
                    Logger.debug(message);
                    if (me.spinner) {
                        me.spinner.succeed();
                        me.spinner.text = message;
                        me.spinner.start();
                    }
                    me.binDirs.forEach(function (binDir) {
                        if (!fs.existsSync(binDir)) {
                            FileUtil.mkdirp(binDir);
                        }
                        createUnixBinary(binDir, wrappedBin);
                        if (isWindows) {
                            createWinBinary(binDir, wrappedBin);
                        }
                    });
                });
                if (me.spinner) {
                    me.spinner.succeed();
                }
            }
        });
    }

    installRepo(repo) {
        let me = this;
        if (repo.installed) {
            return Promise.resolve(repo);
        }

        repo.installed = true;

        if (repo.exists()) {
            return me.installRepoPackages(repo);
        }

        let message = chalk.cyan(`Cloning repository '${chalk.magenta(repo.name)}' from '${chalk.yellow(repo.source.repository)}#${chalk.magenta(repo.source.branch)}' into '${chalk.magenta(path.relative(process.cwd(), repo.path))}'`);
        Logger.debug(message);
        if (me.spinner) {
            me.spinner.succeed();
            me.spinner.text = message;
            me.spinner.start();
        }

        return VCS.github.clone(repo.source.repository, repo.path, repo.source.branch).then(() => {
            fs.writeFile(path.join(repo.path, constants.child), JSON.stringify({root: path.relative(repo.path, process.cwd())}), function(err) {
                if (err) {
                    return me.raise(err);
                }
            });
            return me.installRepoPackages(repo);
        });
    }

    installChildren(repo) {
        let me = this;
        let uses;

        for (let child of repo.uses) {
            if (uses) {
                uses = uses.then(() => me.installRepo(child));
            } else {
                uses = me.installRepo(child);
            }
        }

        if (uses) {
            return uses.then(() => repo);
        }

        return Promise.resolve(repo);
    }

    installRepoPackages(repo) {
        let me = this;
        repo.open();

        let message = `Installing packages for '${chalk.yellow(repo.name)}'`;
        Logger.debug(message);
        if (!me.params.verbose) {
            if (me.spinner) {
                me.spinner.succeed();
            } else {
                me.spinner = ora();
            }
            me.spinner.text = message;
            me.spinner.start();
        }
        let install = me.packageManager().install(repo.path);
        me.binDirs.push(path.join(repo.path, 'node_modules', '.bin'));
        let packages = repo.packages;
        for (let pkg of packages) {
            install = install.then(() => {
                let message = `- Installing '${chalk.yellow(repo.name)}:${chalk.magenta(pkg.name)}'`;
                Logger.debug(message);
                if (me.spinner) {
                    me.spinner.succeed();
                    me.spinner.text= message;
                    me.spinner.start();
                }

                return me.packageManager().install(pkg.path).then(() => {
                    me.binDirs.push(path.join(pkg.path, 'node_modules', '.bin'));
                    let pkgJson = require(path.join(pkg.path, 'package.json'));
                    if (pkgJson.bin) {
                        Object.keys(pkgJson.bin).forEach(function (name) {
                            me.wrappedBins.push({
                                pkg: pkg,
                                name: name,
                                file: pkgJson.bin[name]
                            });
                        });
                    }
                });
            });
        }

        // read the repo from the disk
        return install.then(() => me.installChildren(repo));
    }

}

Install.define({
    help: {
        '': 'Brings the mondo in!',
        verbose: 'Provide additional output'
    },
    switches: '[verbose:boolean=false]'
});

module.exports = Install;
<|endoftext|>